Support batch operation table

This commit is contained in:
WQY\qiong
2023-03-31 14:51:49 +08:00
parent 8de01a3e02
commit a579272ea9
6 changed files with 186 additions and 17 deletions

View File

@@ -19,8 +19,7 @@ from .database import pick_snapshot
from .database import pick_operation, sync_with_server from .database import pick_operation, sync_with_server
from .database import get_restore_operation, set_restore_operation, set_restore_operation_to_current, restore from .database import get_restore_operation, set_restore_operation, set_restore_operation_to_current, restore
from .batch_cmd import execute_batch_command from .batch_cmds import execute_batch_commands, execute_batch_command
from .batch_cmds import execute_batch_commands
from .s0_base import JUNCTION, RESERVOIR, TANK, PIPE, PUMP, VALVE, PATTERN, CURVE from .s0_base import JUNCTION, RESERVOIR, TANK, PIPE, PUMP, VALVE, PATTERN, CURVE
from .s0_base import is_node, is_junction, is_reservoir, is_tank from .s0_base import is_node, is_junction, is_reservoir, is_tank

View File

@@ -1,5 +1,6 @@
from typing import Any
from .sections import * from .sections import *
from .database import API_ADD, API_UPDATE, API_DELETE, ChangeSet from .database import API_ADD, API_UPDATE, API_DELETE, ChangeSet, write, read, read_all, get_current_operation
from .s1_title import set_title from .s1_title import set_title
from .s2_junctions import set_junction, add_junction, delete_junction from .s2_junctions import set_junction, add_junction, delete_junction
from .s3_reservoirs import set_reservoir, add_reservoir, delete_reservoir from .s3_reservoirs import set_reservoir, add_reservoir, delete_reservoir
@@ -290,7 +291,71 @@ def execute_batch_commands(name: str, cs: ChangeSet) -> ChangeSet:
elif operation == API_DELETE: elif operation == API_DELETE:
result.merge(execute_delete_command(name, ChangeSet(op))) result.merge(execute_delete_command(name, ChangeSet(op)))
except: except:
print(f'ERROR: Fail to execute {todo}!') print(f'ERROR: Fail to execute {todo}')
pass
return result
def execute_batch_command(name: str, cs: ChangeSet) -> ChangeSet:
write(name, 'delete from batch_operation where id > 0')
write(name, "update operation_table set option = 'batch_operation' where option = 'operation'")
# for delete, generate cascade command
new_cs = ChangeSet()
for op in cs.operations:
if op['operation'] == API_DELETE:
new_cs.merge(del_cascade_cmd(name, ChangeSet(op)))
else:
new_cs.merge(ChangeSet(op))
result = ChangeSet()
todo = {}
try:
for op in new_cs.operations:
todo = op
operation = op['operation']
if operation == API_ADD:
result.merge(execute_add_command(name, ChangeSet(op)))
elif operation == API_UPDATE:
result.merge(execute_update_command(name, ChangeSet(op)))
elif operation == API_DELETE:
result.merge(execute_delete_command(name, ChangeSet(op)))
except:
print(f'ERROR: Fail to execute {todo}!')
count = read(name, 'select count(*) as count from batch_operation')['count']
if count == 1:
write(name, 'delete from batch_operation where id > 0')
write(name, "update operation_table set option = 'operation' where option = 'batch_operation'")
return ChangeSet()
redo_list: list[str] = []
redo_cs_list: list[dict[str, Any]] = []
redo_rows = read_all(name, 'select redo, redo_cs from batch_operation where id > 0 order by id asc')
for row in redo_rows:
redo_list.append(row['redo'])
redo_cs_list += eval(row['redo_cs'])
undo_list: list[str] = []
undo_cs_list: list[dict[str, Any]] = []
undo_rows = read_all(name, 'select undo, undo_cs from batch_operation where id > 0 order by id desc')
for row in undo_rows:
undo_list.append(row['undo'])
undo_cs_list += eval(row['undo_cs'])
redo = '\n'.join(redo_list).replace("'", "''")
redo_cs = str(redo_cs_list).replace("'", "''")
undo = '\n'.join(undo_list).replace("'", "''")
undo_cs = str(undo_cs_list).replace("'", "''")
parent = get_current_operation(name)
write(name, f"insert into operation (id, redo, undo, parent, redo_cs, undo_cs) values (default, '{redo}', '{undo}', {parent}, '{redo_cs}', '{undo_cs}')")
current = read(name, 'select max(id) as id from operation')['id']
write(name, f"update current_operation set id = {current}")
write(name, 'delete from batch_operation where id > 0')
write(name, "update operation_table set option = 'operation' where option = 'batch_operation'")
return result return result

View File

@@ -113,6 +113,8 @@ def get_current_operation(name: str) -> int:
def execute_command(name: str, command: DbChangeSet) -> ChangeSet: def execute_command(name: str, command: DbChangeSet) -> ChangeSet:
op_table = read(name, "select * from operation_table")['option']
write(name, command.redo_sql) write(name, command.redo_sql)
parent = get_current_operation(name) parent = get_current_operation(name)
@@ -120,10 +122,11 @@ def execute_command(name: str, command: DbChangeSet) -> ChangeSet:
undo_sql = command.undo_sql.replace("'", "''") undo_sql = command.undo_sql.replace("'", "''")
redo_cs_str = str(command.redo_cs).replace("'", "''") redo_cs_str = str(command.redo_cs).replace("'", "''")
undo_cs_str = str(command.undo_cs).replace("'", "''") undo_cs_str = str(command.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}')") write(name, f"insert into {op_table} (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'] if op_table == 'operation':
write(name, f"update current_operation set id = {current}") current = read(name, 'select max(id) as id from operation')['id']
write(name, f"update current_operation set id = {current}")
return ChangeSet.from_list(command.redo_cs) return ChangeSet.from_list(command.redo_cs)

View File

@@ -30,3 +30,26 @@ create table restore_operation
); );
insert into restore_operation (id) values (0); insert into restore_operation (id) values (0);
create table batch_operation
(
id bigserial primary key
, redo text not null
, undo text not null
, parent integer references operation(id) on delete cascade
, redo_child integer references operation(id) -- must update before delete
, redo_cs text not null
, undo_cs text not null
);
insert into batch_operation (id, redo, undo, redo_cs, undo_cs) values (0, '', '', '', '');
create type operation_table_option as enum ('operation', 'batch_operation');
create table operation_table
(
option operation_table_option primary key
);
insert into operation_table (option) values ('operation');

View File

@@ -1,3 +1,9 @@
drop table if exists operation_table;
drop type if exists operation_table_option;
drop table if exists batch_operation;
drop table if exists restore_operation; drop table if exists restore_operation;
drop table if exists snapshot_operation; drop table if exists snapshot_operation;

View File

@@ -157,15 +157,7 @@ class TestApi:
cs.add({'type': JUNCTION, 'id': 'j2', 'x': 0.0, 'y': 10.0, 'elevation': 20.0}) # fail cs.add({'type': JUNCTION, 'id': 'j2', 'x': 0.0, 'y': 10.0, 'elevation': 20.0}) # fail
cs = execute_batch_command(p, cs) cs = execute_batch_command(p, cs)
assert len(cs.operations) == 0 assert len(cs.operations) == 2
assert get_current_operation(p) == 0
cs = ChangeSet()
cs.add({'type': JUNCTION, 'id': 'j1', 'x': 0.0, 'y': 10.0, 'elevation': 20.0})
cs.add({'type': JUNCTION, 'id': 'j2', 'x': 0.0, 'y': 10.0, 'elevation': 20.0})
cs = execute_batch_command(p, cs)
assert get_current_operation(p) == 1 assert get_current_operation(p) == 1
@@ -399,6 +391,87 @@ class TestApi:
self.leave(p) self.leave(p)
def test_delete_nodes_then_restore_command(self):
p = 'test_delete_nodes_then_restore_commands'
read_inp(p, f'./inp/net3.inp', '2')
open_project(p)
nodes = get_nodes(p)
links = get_links(p)
for _ in range(10):
random.shuffle(nodes)
batch = ChangeSet()
for node in nodes:
if is_junction(p, node):
batch.delete({'type' : 'junction', 'id': node })
if is_reservoir(p, node):
batch.delete({'type' : 'reservoir', 'id': node })
if is_tank(p, node):
batch.delete({'type' : 'tank', 'id': node })
execute_batch_command(p, batch)
for node in nodes:
assert is_node(p, node) == False
for link in links:
assert is_link(p, link) == False
assert get_nodes(p) == []
assert get_links(p) == []
op = get_restore_operation(p)
pick_operation(p, op)
for node in nodes:
assert is_node(p, node)
for link in links:
assert is_link(p, link)
self.leave(p)
def test_delete_links_then_restore_command(self):
p = 'test_delete_links_then_restore_commands'
read_inp(p, f'./inp/net3.inp', '2')
open_project(p)
nodes = get_nodes(p)
links = get_links(p)
for _ in range(10):
random.shuffle(links)
batch = ChangeSet()
for link in links:
if is_pipe(p, link):
batch.delete({'type' : 'pipe', 'id': link })
if is_pump(p, link):
batch.delete({'type' : 'pump', 'id': link })
if is_valve(p, link):
batch.delete({'type' : 'valve', 'id': link })
execute_batch_command(p, batch)
for node in nodes:
assert is_node(p, node)
for link in links:
assert is_link(p, link) == False
assert get_links(p) == []
op = get_restore_operation(p)
pick_operation(p, op)
for node in nodes:
assert is_node(p, node)
for link in links:
assert is_link(p, link)
self.leave(p)
def test_delete_nodes_links_then_restore_v2(self): def test_delete_nodes_links_then_restore_v2(self):
p = 'test_delete_nodes_links_then_restore_v2' p = 'test_delete_nodes_links_then_restore_v2'
read_inp(p, f'./inp/net3.inp', '2') read_inp(p, f'./inp/net3.inp', '2')