refactor(db)!: finalize pooled WNDB v2 migration

This commit is contained in:
2026-08-27 17:26:22 +08:00
parent fa188af0b1
commit b74799a39d
105 changed files with 4988 additions and 5565 deletions
-13
View File
@@ -94,16 +94,3 @@ def delete_demand_by_junction(name: str, junction: str) -> ChangeSet:
if row is None:
return ChangeSet()
return ChangeSet(g_update_prefix | {'type': 'demand', 'junction': junction, 'demands': []})
def unset_demand_by_pattern(name: str, pattern: str) -> ChangeSet:
cs = ChangeSet()
rows = read_all(name, "select distinct junction_id as junction from network.demands where pattern_id = %s", (pattern,))
for row in rows:
ds = get_demand(name, row['junction'])
for d in ds['demands']:
d['pattern'] = None
cs.append(g_update_prefix | {'type': 'demand', 'junction': row['junction'], 'demands': ds['demands']})
return cs
+2 -36
View File
@@ -159,26 +159,6 @@ def get_nodes(name: str) -> list[str]:
return _get_all(name, _NODE)
def get_nodes_id_and_type(name: str) -> dict[str, str]:
rows = read_all_typed(name, "SELECT id, node_type FROM network.nodes", ())
return {row["id"]: row["node_type"] for row in rows}
def get_major_nodes(name: str, diameter: int) -> list[str]:
rows = read_all_typed(
name,
"""
SELECT DISTINCT endpoint
FROM network.links AS l
JOIN network.pipes AS p ON p.link_id = l.id
CROSS JOIN LATERAL (VALUES (l.start_node_id), (l.end_node_id)) AS e(endpoint)
WHERE p.diameter > %s
""",
(diameter,),
)
return [row["endpoint"] for row in rows]
def get_junctions(name: str) -> list[str]:
return _get_nodes_by_type(name, JUNCTION)
@@ -195,20 +175,6 @@ def get_links(name: str) -> list[str]:
return _get_all(name, _LINK)
def get_links_id_and_type(name: str) -> dict[str, str]:
rows = read_all_typed(name, "SELECT id, link_type FROM network.links", ())
return {row["id"]: row["link_type"] for row in rows}
def get_major_pipes(name: str, diameter: int) -> list[str]:
rows = read_all_typed(
name,
"SELECT link_id FROM network.pipes WHERE diameter > %s ORDER BY link_id",
(diameter,),
)
return [row["link_id"] for row in rows]
def get_pipes(name: str) -> list[str]:
return _get_links_by_type(name, PIPE)
@@ -247,10 +213,10 @@ def get_node_links(name: str, node_id: str) -> list[str]:
def get_all_node_links(name: str) -> dict[str, list[str]]:
"""Build the node adjacency map with one scan of the link table."""
"""Build the node adjacency map with one scan of the unified GIS view."""
rows = read_all_typed(
name,
"SELECT id, start_node_id, end_node_id FROM network.links ORDER BY id",
"SELECT id, start_node_id, end_node_id FROM gis.network_links ORDER BY id",
(),
)
result: dict[str, list[str]] = {}
+15 -6
View File
@@ -6,6 +6,7 @@ from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
execute_locked_command,
g_add_prefix,
g_delete_prefix,
g_update_prefix,
@@ -94,8 +95,12 @@ class Junction(object):
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id, 'x': self.x, 'y': self.y, 'elevation': self.elevation }
def _set_junction(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_junction(name, cs.operations[0]['id'])
def _set_junction(
name: str,
cs: ChangeSet,
current: dict[str, Any] | None = None,
) -> DatabaseCommand:
raw_new = current if current is not None else get_junction(name, cs.operations[0]['id'])
new_dict = cs.operations[0]
schema = get_junction_schema(name)
@@ -113,11 +118,15 @@ def _set_junction(name: str, cs: ChangeSet) -> DatabaseCommand:
def set_junction(name: str, cs: ChangeSet) -> ChangeSet:
if 'id' not in cs.operations[0]:
operation = cs.operations[0]
if 'id' not in operation:
return ChangeSet()
if get_junction(name, cs.operations[0]['id']) == {}:
return ChangeSet()
return execute_command(name, _set_junction(name, cs))
def build_command() -> DatabaseCommand | None:
current = get_junction(name, operation['id'])
return None if current == {} else _set_junction(name, cs, current)
return execute_locked_command(name, build_command)
def _add_junction(name: str, cs: ChangeSet) -> DatabaseCommand:
@@ -1,10 +1,8 @@
from psycopg import sql
from ..core.database import ChangeSet, g_update_prefix, read_all, sql_literal
from .options import get_option_schema, generate_v3
def _inp_in_option(section: list[str]) -> ChangeSet:
def _inp_in_option_v2(section: list[str]) -> ChangeSet:
if len(section) <= 0:
return ChangeSet()
@@ -34,9 +32,9 @@ def _inp_in_option(section: list[str]) -> ChangeSet:
return result
def inp_in_option(section: list[str]) -> str:
def inp_in_option_v2(section: list[str]) -> str:
sql = ''
result = _inp_in_option(section)
result = _inp_in_option_v2(section)
for op in result.operations:
for key in op.keys():
if key == 'operation' or key == 'type':
@@ -48,7 +46,7 @@ def inp_in_option(section: list[str]) -> str:
return sql
def inp_out_option(name: str) -> list[str]:
def inp_out_option_v2(name: str) -> list[str]:
lines = []
objs = read_all(name, "select key, value from network.simulation_settings where engine_version = 'legacy' order by key")
@@ -71,7 +69,7 @@ def inp_out_option(name: str) -> list[str]:
# why write this ?
if key == 'PRESSURE':
continue
# release version does not support new keys and has error message
# EPANET V2 does not support these newer keys.
if key == 'HTOL' or key == 'QTOL' or key == 'RQTOL':
continue
# ignore some weird settings for DDA
-2
View File
@@ -1,5 +1,3 @@
from psycopg import sql
from ..core.database import ChangeSet, g_update_prefix, read_all, sql_literal
from .options import get_option_schema, get_option_v3_schema, generate_v2, generate_v3
+4 -1
View File
@@ -93,7 +93,10 @@ def _delete_pattern(name: str, cs: ChangeSet) -> DatabaseCommand:
id = cs.operations[0]['id']
f_id = sql_literal(id)
statement = f"delete from network.patterns where id = {f_id};"
statement = (
f"update network.demands set pattern_id = null where pattern_id = {f_id};"
f"\ndelete from network.patterns where id = {f_id};"
)
change = g_delete_prefix | { 'type': 'pattern' } | { 'id': id }
+52 -8
View File
@@ -4,6 +4,7 @@ from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
execute_locked_command,
g_add_prefix,
g_delete_prefix,
g_update_prefix,
@@ -144,9 +145,12 @@ class Pipe(object):
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id, 'node1': self.node1, 'node2': self.node2, 'length': self.length, 'diameter': self.diameter, 'roughness': self.roughness, 'minor_loss': self.minor_loss, 'status': self.status }
def _set_pipe(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_pipe(name, cs.operations[0]['id'])
def _set_pipe(
name: str,
cs: ChangeSet,
current: dict[str, Any] | None = None,
) -> DatabaseCommand:
raw_new = current if current is not None else get_pipe(name, cs.operations[0]['id'])
new_dict = cs.operations[0]
schema = get_pipe_schema(name)
for key, value in schema.items():
@@ -154,19 +158,59 @@ def _set_pipe(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new[key] = new_dict[key]
new = Pipe(raw_new)
statement = f"update network.links set start_node_id = {new.f_node1}, end_node_id = {new.f_node2} where id = {new.f_id};"
statement += f"\nupdate network.pipes set length = {new.f_length}, diameter = {new.f_diameter}, roughness = {new.f_roughness}, minor_loss = {new.f_minor_loss}, status = {new.f_status} where link_id = {new.f_id};"
link_columns = {
'node1': ('start_node_id', new.f_node1),
'node2': ('end_node_id', new.f_node2),
}
pipe_columns = {
'length': ('length', new.f_length),
'diameter': ('diameter', new.f_diameter),
'roughness': ('roughness', new.f_roughness),
'minor_loss': ('minor_loss', new.f_minor_loss),
'status': ('status', new.f_status),
}
statements = []
link_assignments = [
f"{column} = {value}"
for field, (column, value) in link_columns.items()
if field in new_dict
]
if link_assignments:
statements.append(
f"update network.links set {', '.join(link_assignments)} where id = {new.f_id};"
)
pipe_assignments = [
f"{column} = {value}"
for field, (column, value) in pipe_columns.items()
if field in new_dict
]
if pipe_assignments:
statements.append(
f"update network.pipes set {', '.join(pipe_assignments)} where link_id = {new.f_id};"
)
statement = "\n".join(statements)
change = g_update_prefix | new.as_dict()
return DatabaseCommand(statement, [change])
def set_pipe(name: str, cs: ChangeSet) -> ChangeSet:
if 'id' not in cs.operations[0]:
operation = cs.operations[0]
if 'id' not in operation:
return ChangeSet()
if get_pipe(name, cs.operations[0]['id']) == {}:
mutable_fields = {
'node1', 'node2', 'length', 'diameter', 'roughness', 'minor_loss', 'status'
}
if not mutable_fields.intersection(operation):
return ChangeSet()
return execute_command(name, _set_pipe(name, cs))
def build_command() -> DatabaseCommand | None:
current = get_pipe(name, operation['id'])
if current == {}:
return None
return _set_pipe(name, cs, current)
return execute_locked_command(name, build_command)
def _add_pipe(name: str, cs: ChangeSet) -> DatabaseCommand:
+15 -6
View File
@@ -4,6 +4,7 @@ from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
execute_locked_command,
g_add_prefix,
g_delete_prefix,
g_update_prefix,
@@ -87,8 +88,12 @@ class Pump(object):
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id, 'node1': self.node1, 'node2': self.node2, 'power': self.power, 'head': self.head, 'speed': self.speed, 'pattern': self.pattern }
def _set_pump(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_pump(name, cs.operations[0]['id'])
def _set_pump(
name: str,
cs: ChangeSet,
current: dict[str, Any] | None = None,
) -> DatabaseCommand:
raw_new = current if current is not None else get_pump(name, cs.operations[0]['id'])
new_dict = cs.operations[0]
schema = get_pump_schema(name)
@@ -105,11 +110,15 @@ def _set_pump(name: str, cs: ChangeSet) -> DatabaseCommand:
def set_pump(name: str, cs: ChangeSet) -> ChangeSet:
if 'id' not in cs.operations[0]:
operation = cs.operations[0]
if 'id' not in operation:
return ChangeSet()
if get_pump(name, cs.operations[0]['id']) == {}:
return ChangeSet()
return execute_command(name, _set_pump(name, cs))
def build_command() -> DatabaseCommand | None:
current = get_pump(name, operation['id'])
return None if current == {} else _set_pump(name, cs, current)
return execute_locked_command(name, build_command)
def _add_pump(name: str, cs: ChangeSet) -> DatabaseCommand:
+15 -6
View File
@@ -4,6 +4,7 @@ from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
execute_locked_command,
g_add_prefix,
g_delete_prefix,
g_update_prefix,
@@ -85,8 +86,12 @@ class Reservoir(object):
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id, 'x': self.x, 'y': self.y, 'head': self.head, 'pattern': self.pattern }
def _set_reservoir(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_reservoir(name, cs.operations[0]['id'])
def _set_reservoir(
name: str,
cs: ChangeSet,
current: dict[str, Any] | None = None,
) -> DatabaseCommand:
raw_new = current if current is not None else get_reservoir(name, cs.operations[0]['id'])
new_dict = cs.operations[0]
schema = get_reservoir_schema(name)
@@ -104,11 +109,15 @@ def _set_reservoir(name: str, cs: ChangeSet) -> DatabaseCommand:
def set_reservoir(name: str, cs: ChangeSet) -> ChangeSet:
if 'id' not in cs.operations[0]:
operation = cs.operations[0]
if 'id' not in operation:
return ChangeSet()
if get_reservoir(name, cs.operations[0]['id']) == {}:
return ChangeSet()
return execute_command(name, _set_reservoir(name, cs))
def build_command() -> DatabaseCommand | None:
current = get_reservoir(name, operation['id'])
return None if current == {} else _set_reservoir(name, cs, current)
return execute_locked_command(name, build_command)
def _add_reservoir(name: str, cs: ChangeSet) -> DatabaseCommand:
+15 -6
View File
@@ -4,6 +4,7 @@ from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
execute_locked_command,
g_add_prefix,
g_delete_prefix,
g_update_prefix,
@@ -121,8 +122,12 @@ class Tank(object):
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id, 'x': self.x, 'y': self.y, 'elevation': self.elevation, 'init_level': self.init_level, 'min_level': self.min_level, 'max_level': self.max_level, 'diameter': self.diameter, 'min_vol': self.min_vol, 'vol_curve': self.vol_curve, 'overflow': self.overflow }
def _set_tank(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_tank(name, cs.operations[0]['id'])
def _set_tank(
name: str,
cs: ChangeSet,
current: dict[str, Any] | None = None,
) -> DatabaseCommand:
raw_new = current if current is not None else get_tank(name, cs.operations[0]['id'])
new_dict = cs.operations[0]
schema = get_tank_schema(name)
@@ -140,11 +145,15 @@ def _set_tank(name: str, cs: ChangeSet) -> DatabaseCommand:
def set_tank(name: str, cs: ChangeSet) -> ChangeSet:
if 'id' not in cs.operations[0]:
operation = cs.operations[0]
if 'id' not in operation:
return ChangeSet()
if get_tank(name, cs.operations[0]['id']) == {}:
return ChangeSet()
return execute_command(name, _set_tank(name, cs))
def build_command() -> DatabaseCommand | None:
current = get_tank(name, operation['id'])
return None if current == {} else _set_tank(name, cs, current)
return execute_locked_command(name, build_command)
def _add_tank(name: str, cs: ChangeSet) -> DatabaseCommand:
+15 -6
View File
@@ -4,6 +4,7 @@ from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
execute_locked_command,
g_add_prefix,
g_delete_prefix,
g_update_prefix,
@@ -94,8 +95,12 @@ class Valve(object):
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id, 'node1': self.node1, 'node2': self.node2, 'diameter': self.diameter, 'v_type': self.v_type, 'setting': self.setting, 'minor_loss': self.minor_loss }
def _set_valve(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_valve(name, cs.operations[0]['id'])
def _set_valve(
name: str,
cs: ChangeSet,
current: dict[str, Any] | None = None,
) -> DatabaseCommand:
raw_new = current if current is not None else get_valve(name, cs.operations[0]['id'])
new_dict = cs.operations[0]
schema = get_valve_schema(name)
@@ -112,11 +117,15 @@ def _set_valve(name: str, cs: ChangeSet) -> DatabaseCommand:
def set_valve(name: str, cs: ChangeSet) -> ChangeSet:
if 'id' not in cs.operations[0]:
operation = cs.operations[0]
if 'id' not in operation:
return ChangeSet()
if get_valve(name, cs.operations[0]['id']) == {}:
return ChangeSet()
return execute_command(name, _set_valve(name, cs))
def build_command() -> DatabaseCommand | None:
current = get_valve(name, operation['id'])
return None if current == {} else _set_valve(name, cs, current)
return execute_locked_command(name, build_command)
def _add_valve(name: str, cs: ChangeSet) -> DatabaseCommand: