Huge refactor to api and add batch api
This commit is contained in:
291
api/operation.py
291
api/operation.py
@@ -1,22 +1,123 @@
|
||||
from typing import Any
|
||||
from psycopg.rows import dict_row, Row
|
||||
from .connection import g_conn_dict as conn
|
||||
from .utility import *
|
||||
from .change_set import *
|
||||
|
||||
|
||||
API_ADD = 'add'
|
||||
API_DELETE = 'delete'
|
||||
API_UPDATE = 'update'
|
||||
API_DELETE = 'delete'
|
||||
|
||||
g_add_prefix = { 'operation': API_ADD }
|
||||
g_update_prefix = { 'operation': API_UPDATE }
|
||||
g_delete_prefix = { 'operation': API_DELETE }
|
||||
|
||||
|
||||
def _remove_operation(name: str, id: int) -> None:
|
||||
row = read(name, f'select * from operation where parent = {id}')
|
||||
if row != None:
|
||||
raise Exception('Disallow to remove parent operation !')
|
||||
class ChangeSet:
|
||||
def __init__(self, ps: dict[str, Any] | None = None):
|
||||
self.operations : list[dict[str, Any]] = []
|
||||
if ps != None:
|
||||
self.append(ps)
|
||||
|
||||
sql = f'delete from snapshot_operation where id = {id};'
|
||||
sql += f'delete from operation where id = {id}'
|
||||
write(name, sql)
|
||||
def add(self, ps: dict[str, Any]):
|
||||
self.operations.append(g_add_prefix | ps)
|
||||
return self
|
||||
|
||||
def update(self, ps: dict[str, Any]):
|
||||
self.operations.append(g_update_prefix | ps)
|
||||
return self
|
||||
|
||||
def delete(self, ps: dict[str, Any]):
|
||||
self.operations.append(g_delete_prefix | ps)
|
||||
return self
|
||||
|
||||
def append(self, ps: dict[str, Any]):
|
||||
self.operations.append(ps)
|
||||
return self
|
||||
|
||||
def merge(self, cs):
|
||||
if len(cs.operations) > 0:
|
||||
self.operations += cs.operations
|
||||
return self
|
||||
|
||||
def compress(self):
|
||||
return self
|
||||
|
||||
|
||||
def read(name: str, sql: str) -> Row:
|
||||
with conn[name].cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(sql)
|
||||
row = cur.fetchone()
|
||||
if row == None:
|
||||
raise Exception(sql)
|
||||
return row
|
||||
|
||||
|
||||
def write(name: str, sql: str) -> None:
|
||||
with conn[name].cursor() as cur:
|
||||
cur.execute(sql)
|
||||
|
||||
def get_current_operation(name: str) -> int:
|
||||
return int(read(name, 'select id from current_operation')['id'])
|
||||
|
||||
|
||||
def execute_command(name: str, redo_sql: str, undo_sql: str, redo_cs: dict[str, str], undo_cs: dict[str, str]) -> ChangeSet:
|
||||
write(name, redo_sql)
|
||||
|
||||
parent = get_current_operation(name)
|
||||
redo_sql = redo_sql.replace("'", "''")
|
||||
undo_sql = undo_sql.replace("'", "''")
|
||||
redo_cs_str = str(redo_cs).replace("'", "''")
|
||||
undo_cs_str = str(undo_cs).replace("'", "''")
|
||||
write(name, f"insert into operation (id, redo, undo, parent, redo_cs, undo_cs) values (default, '{redo_sql}', '{undo_sql}', {parent}, '{redo_cs_str}', '{undo_cs_str}')")
|
||||
|
||||
current = read(name, 'select max(id) as id from operation')['id']
|
||||
write(name, f"update current_operation set id = {current}")
|
||||
|
||||
return ChangeSet(redo_cs)
|
||||
|
||||
|
||||
def execute_undo(name: str, discard: bool = False) -> ChangeSet:
|
||||
row = read(name, f'select * from operation where id = {get_current_operation(name)}')
|
||||
|
||||
write(name, row['undo'])
|
||||
|
||||
# update foreign key
|
||||
write(name, f"update current_operation set id = {row['parent']} where id = {row['id']}")
|
||||
|
||||
if discard:
|
||||
# update foreign key
|
||||
write(name, f"update operation set redo_child = null where id = {row['parent']}")
|
||||
# on delete cascade => child & snapshot
|
||||
write(name, f"delete from operation where id = {row['id']}")
|
||||
else:
|
||||
write(name, f"update operation set redo_child = {row['id']} where id = {row['parent']}")
|
||||
|
||||
return ChangeSet(eval(row['undo_cs']))
|
||||
|
||||
|
||||
def execute_redo(name: str) -> ChangeSet:
|
||||
row = read(name, f'select * from operation where id = {get_current_operation(name)}')
|
||||
if row['redo_child'] == None:
|
||||
return ChangeSet()
|
||||
|
||||
row = read(name, f"select * from operation where id = {row['redo_child']}")
|
||||
write(name, row['redo'])
|
||||
|
||||
write(name, f"update current_operation set id = {row['id']} where id = {row['parent']}")
|
||||
|
||||
return ChangeSet(eval(row['redo_cs']))
|
||||
|
||||
|
||||
def have_snapshot(name: str, tag: str) -> bool:
|
||||
return read(name, f"select id from snapshot_operation where tag = '{tag}'") != None
|
||||
|
||||
|
||||
def take_snapshot(name: str, tag: str) -> int | None:
|
||||
if tag == None or tag == '':
|
||||
return None
|
||||
|
||||
current = get_current_operation(name)
|
||||
write(name, f"insert into snapshot_operation (id, tag) values ({current}, '{tag}')")
|
||||
return current
|
||||
|
||||
|
||||
def _get_parents(name: str, id: int) -> list[int]:
|
||||
@@ -27,149 +128,11 @@ def _get_parents(name: str, id: int) -> list[int]:
|
||||
return ids
|
||||
|
||||
|
||||
def get_current_operation(name: str) -> int:
|
||||
row = read(name, f'select id from current_operation')
|
||||
return int(row['id'])
|
||||
|
||||
|
||||
def _update_current_operation(name: str, old_id: int, id: int) -> None:
|
||||
return write(name, f'update current_operation set id = {id} where id = {old_id}')
|
||||
|
||||
|
||||
def _add_redo_undo(name: str, redo: str, undo: str, api_id: str, api_op: str, api_object_type: str, api_object_id: str, api_object_properties: list[str]) -> int:
|
||||
parent = get_current_operation(name)
|
||||
ps = []
|
||||
for p in api_object_properties:
|
||||
ps.append(f'"{p}"')
|
||||
if len(ps) > 0:
|
||||
ps = ','.join(ps)
|
||||
ps = '{' + ps + '}'
|
||||
sql = f"insert into operation (id, redo, undo, parent, api_id, api_op, api_object_type, api_object_id, api_object_properties) values (default, '{redo}', '{undo}', {parent}, '{api_id}', '{api_op}', '{api_object_type}', '{api_object_id}', '{ps}')"
|
||||
else:
|
||||
sql = f"insert into operation (id, redo, undo, parent, api_id, api_op, api_object_type, api_object_id) values (default, '{redo}', '{undo}', {parent}, '{api_id}', '{api_op}', '{api_object_type}', '{api_object_id}')"
|
||||
write(name, sql)
|
||||
|
||||
return int(read(name, 'select max(id) from operation')['max'])
|
||||
|
||||
|
||||
def _query_operation(name: str, id: str) -> dict[str, str]:
|
||||
return read(name, f'select * from operation where id = {id}')
|
||||
|
||||
|
||||
def _query_redo_child(name: str, id: str) -> str:
|
||||
row = read(name, f'select redo_child from operation where id = {id}')
|
||||
return row['redo_child']
|
||||
|
||||
|
||||
def _set_redo_child(name: str, id: int, child: int | str) -> None:
|
||||
return write(name, f'update operation set redo_child = {child} where id = {id}')
|
||||
|
||||
|
||||
def add_operation(name: str, redo: str, undo: str, api_id: str, api_op: str, api_object_type: str, api_object_id: str, api_object_properties: list[str] = []) -> None:
|
||||
curr = _add_redo_undo(name, redo, undo, api_id, api_op, api_object_type, api_object_id, api_object_properties)
|
||||
old = get_current_operation(name)
|
||||
_update_current_operation(name, old, curr)
|
||||
|
||||
|
||||
def _reverser_op(op: str) -> str:
|
||||
if op == API_ADD:
|
||||
return API_DELETE
|
||||
elif op == API_DELETE:
|
||||
return API_ADD
|
||||
else:
|
||||
return op
|
||||
|
||||
|
||||
def _get_change_set(row: dict[str, str], undo: bool) -> ChangeSet:
|
||||
op = row['api_op']
|
||||
if undo:
|
||||
op = _reverser_op(op)
|
||||
|
||||
type = row['api_object_type']
|
||||
id = row['api_object_id']
|
||||
|
||||
change = ChangeSet()
|
||||
|
||||
if op == API_ADD:
|
||||
change.add(type, id)
|
||||
elif op == API_DELETE:
|
||||
change.delete(type, id)
|
||||
elif op == API_UPDATE:
|
||||
change.update(type, id, row['api_object_properties'])
|
||||
|
||||
return change
|
||||
|
||||
|
||||
def execute_undo(name: str, discard: bool) -> ChangeSet:
|
||||
curr = get_current_operation(name)
|
||||
|
||||
row = _query_operation(name, curr)
|
||||
undo = row['undo']
|
||||
if undo == '':
|
||||
print("nothing to undo!")
|
||||
return
|
||||
|
||||
change = _get_change_set(row, True)
|
||||
|
||||
parent = int(row['parent'])
|
||||
_set_redo_child(name, parent, 'null' if discard else curr)
|
||||
|
||||
write(name, undo)
|
||||
_update_current_operation(name, curr, parent)
|
||||
|
||||
if discard:
|
||||
_remove_operation(name, curr)
|
||||
|
||||
return change
|
||||
|
||||
|
||||
def execute_redo(name: str) -> ChangeSet:
|
||||
curr = get_current_operation(name)
|
||||
redoChild = _query_redo_child(name, curr)
|
||||
if redoChild == None:
|
||||
print("nothing to redo!")
|
||||
return
|
||||
|
||||
child = int(redoChild)
|
||||
row = _query_operation(name, child)
|
||||
redo = row['redo']
|
||||
|
||||
change = _get_change_set(row, False)
|
||||
|
||||
write(name, redo)
|
||||
_update_current_operation(name, curr, child)
|
||||
|
||||
return change
|
||||
|
||||
|
||||
def _get_operation_by_tag(name: str, tag: str) -> int | None:
|
||||
row = read(name, f"select id from snapshot_operation where tag = '{tag}'")
|
||||
return int(row['id']) if row != None else None
|
||||
|
||||
|
||||
def have_snapshot(name: str, tag: str) -> bool:
|
||||
return _get_operation_by_tag(name, tag) != None
|
||||
|
||||
|
||||
def take_snapshot(name: str, tag: str) -> int | None:
|
||||
if tag == None or tag == '':
|
||||
print('Non empty tag is expected!')
|
||||
return None
|
||||
|
||||
curr = get_current_operation(name)
|
||||
write(name, f"insert into snapshot_operation (id, tag) values ({curr}, '{tag}')")
|
||||
return curr
|
||||
|
||||
|
||||
def pick_snapshot(name: str, tag: str, discard: bool) -> ChangeSet:
|
||||
if tag == None or tag == '':
|
||||
print('Non empty tag is expected!')
|
||||
if not have_snapshot(name, tag):
|
||||
return ChangeSet()
|
||||
|
||||
target = _get_operation_by_tag(name, tag)
|
||||
if target == None:
|
||||
print('No such snapshot!')
|
||||
return ChangeSet()
|
||||
target = int(read(name, f"select id from snapshot_operation where tag = '{tag}'")['id'])
|
||||
|
||||
curr = get_current_operation(name)
|
||||
|
||||
@@ -180,14 +143,14 @@ def pick_snapshot(name: str, tag: str, discard: bool) -> ChangeSet:
|
||||
|
||||
if target in curr_parents:
|
||||
for _ in range(curr_parents.index(target)):
|
||||
change.append(execute_undo(name, discard))
|
||||
change.merge(execute_undo(name, discard))
|
||||
|
||||
elif curr in target_parents:
|
||||
target_parents.reverse()
|
||||
curr_index = target_parents.index(curr)
|
||||
for i in range(curr_index, len(target_parents) - 1):
|
||||
write(name, f"update operation set redo_child = '{target_parents[i + 1]}' where id = '{target_parents[i]}'")
|
||||
change.append(execute_redo(name))
|
||||
change.merge(execute_redo(name))
|
||||
|
||||
else:
|
||||
ancestor_index = -1
|
||||
@@ -196,24 +159,20 @@ def pick_snapshot(name: str, tag: str, discard: bool) -> ChangeSet:
|
||||
ancestor = curr_parents[ancestor_index + 1]
|
||||
|
||||
for _ in range(curr_parents.index(ancestor)):
|
||||
change.append(execute_undo(name, discard))
|
||||
change.merge(execute_undo(name, discard))
|
||||
|
||||
target_parents.reverse()
|
||||
curr_index = target_parents.index(ancestor)
|
||||
for i in range(curr_index, len(target_parents) - 1):
|
||||
write(name, f"update operation set redo_child = '{target_parents[i + 1]}' where id = '{target_parents[i]}'")
|
||||
change.append(execute_redo(name))
|
||||
change.merge(execute_redo(name))
|
||||
|
||||
return change.compress()
|
||||
|
||||
|
||||
def get_change_set(name: str, operation: int, undo: bool) -> ChangeSet:
|
||||
row = read(name, f'select api_id, api_op, api_object_type, api_object_id, api_object_properties from operation where id = {operation}')
|
||||
return _get_change_set(row, undo)
|
||||
|
||||
|
||||
def get_current_change_set(name: str) -> ChangeSet:
|
||||
return get_change_set(name, get_current_operation(name), False)
|
||||
def _get_change_set(name: str, operation: int, undo: bool) -> dict[str, Any]:
|
||||
row = read(name, f'select * from operation where id = {operation}')
|
||||
return eval(row['undo']) if undo else eval(row['redo'])
|
||||
|
||||
|
||||
def sync_with_server(name: str, operation: int) -> ChangeSet:
|
||||
@@ -228,13 +187,13 @@ def sync_with_server(name: str, operation: int) -> ChangeSet:
|
||||
if fr in to_parents:
|
||||
index = to_parents.index(fr) - 1
|
||||
while index >= 0:
|
||||
change.append(get_change_set(name, to_parents[index], False))
|
||||
change.append(_get_change_set(name, to_parents[index], False)) #redo
|
||||
index -= 1
|
||||
|
||||
elif to in fr_parents:
|
||||
index = 0
|
||||
while index <= fr_parents.index(to) - 1:
|
||||
change.append(get_change_set(name, fr_parents[index], True))
|
||||
change.append(_get_change_set(name, fr_parents[index], True))
|
||||
index += 1
|
||||
|
||||
else:
|
||||
@@ -246,12 +205,12 @@ def sync_with_server(name: str, operation: int) -> ChangeSet:
|
||||
|
||||
index = 0
|
||||
while index <= fr_parents.index(ancestor) - 1:
|
||||
change.append(get_change_set(name, fr_parents[index], True))
|
||||
change.append(_get_change_set(name, fr_parents[index], True))
|
||||
index += 1
|
||||
|
||||
index = to_parents.index(ancestor) - 1
|
||||
while index >= 0:
|
||||
change.append(get_change_set(name, to_parents[index], False))
|
||||
change.append(_get_change_set(name, to_parents[index], False))
|
||||
index -= 1
|
||||
|
||||
return change.compress()
|
||||
|
||||
Reference in New Issue
Block a user