refactor(db)!: adopt project-routed pooled databases
Reorganize WNDB by responsibility and remove legacy scheme endpoints.\n\nRoute analysis and time-series access through project pools, preserve transactional realtime replacement, and refresh GIS materialized views after writes.\n\nAdd database architecture documentation, live pooling coverage, API contract updates, and executable container verification.\n\nBREAKING CHANGE: legacy scheme APIs and flat app.native.wndb module imports are removed.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
"""Model command rewriting, cascade handling, and execution."""
|
||||
@@ -0,0 +1,65 @@
|
||||
"""Convenience entry points for cascade deletes and option synchronization."""
|
||||
|
||||
from ..core.database import API_DELETE, API_UPDATE, ChangeSet
|
||||
from .executor import execute_batch_command
|
||||
|
||||
|
||||
def _execute(
|
||||
name: str,
|
||||
change_set: ChangeSet,
|
||||
*,
|
||||
operation: str,
|
||||
element_type: str,
|
||||
) -> ChangeSet:
|
||||
change_set.operations[0].update(
|
||||
{"operation": operation, "type": element_type}
|
||||
)
|
||||
return execute_batch_command(name, change_set)
|
||||
|
||||
|
||||
def delete_junction_cascade(name: str, change_set: ChangeSet) -> ChangeSet:
|
||||
return _execute(
|
||||
name, change_set, operation=API_DELETE, element_type="junction"
|
||||
)
|
||||
|
||||
|
||||
def delete_reservoir_cascade(name: str, change_set: ChangeSet) -> ChangeSet:
|
||||
return _execute(
|
||||
name, change_set, operation=API_DELETE, element_type="reservoir"
|
||||
)
|
||||
|
||||
|
||||
def delete_tank_cascade(name: str, change_set: ChangeSet) -> ChangeSet:
|
||||
return _execute(name, change_set, operation=API_DELETE, element_type="tank")
|
||||
|
||||
|
||||
def delete_pipe_cascade(name: str, change_set: ChangeSet) -> ChangeSet:
|
||||
return _execute(name, change_set, operation=API_DELETE, element_type="pipe")
|
||||
|
||||
|
||||
def delete_pump_cascade(name: str, change_set: ChangeSet) -> ChangeSet:
|
||||
return _execute(name, change_set, operation=API_DELETE, element_type="pump")
|
||||
|
||||
|
||||
def delete_valve_cascade(name: str, change_set: ChangeSet) -> ChangeSet:
|
||||
return _execute(name, change_set, operation=API_DELETE, element_type="valve")
|
||||
|
||||
|
||||
def delete_pattern_cascade(name: str, change_set: ChangeSet) -> ChangeSet:
|
||||
return _execute(
|
||||
name, change_set, operation=API_DELETE, element_type="pattern"
|
||||
)
|
||||
|
||||
|
||||
def delete_curve_cascade(name: str, change_set: ChangeSet) -> ChangeSet:
|
||||
return _execute(name, change_set, operation=API_DELETE, element_type="curve")
|
||||
|
||||
|
||||
def set_option_ex(name: str, change_set: ChangeSet) -> ChangeSet:
|
||||
return _execute(name, change_set, operation=API_UPDATE, element_type="option")
|
||||
|
||||
|
||||
def set_option_v3_ex(name: str, change_set: ChangeSet) -> ChangeSet:
|
||||
return _execute(
|
||||
name, change_set, operation=API_UPDATE, element_type="option_v3"
|
||||
)
|
||||
@@ -0,0 +1,244 @@
|
||||
from collections.abc import Callable
|
||||
|
||||
from ..core.database import API_DELETE, API_UPDATE, ChangeSet, g_delete_prefix, try_read
|
||||
from ..model.elements import get_node_links, is_pipe, is_pump, is_valve
|
||||
|
||||
from ..model.reservoirs import unset_reservoir_by_pattern
|
||||
from ..model.tanks import unset_tank_by_curve
|
||||
from ..model.pumps import unset_pump_by_curve, unset_pump_by_pattern
|
||||
from ..model.tags import delete_tag_by_node, delete_tag_by_link
|
||||
from ..model.demands import delete_demand_by_junction, unset_demand_by_pattern
|
||||
from ..model.status import delete_status_by_link
|
||||
from ..model.energy import delete_pump_energy_by_pump, unset_pump_energy_by_pattern, unset_pump_energy_by_curve
|
||||
from ..model.emitters import delete_emitter_by_junction
|
||||
from ..model.quality import delete_quality_by_node
|
||||
from ..model.sources import delete_source_by_node, unset_source_by_pattern
|
||||
from ..model.reactions import delete_pipe_reaction_by_pipe, delete_tank_reaction_by_tank
|
||||
from ..model.mixing import delete_mixing_by_tank
|
||||
from ..gis.vertices import delete_vertex_by_link
|
||||
from ..gis.labels import unset_label_by_node
|
||||
|
||||
from ..model.options import generate_v2, generate_v3
|
||||
|
||||
|
||||
def expand_junction_delete(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
result = ChangeSet()
|
||||
|
||||
id = cs.operations[0]['id']
|
||||
row = try_read(name, "select 1 from network.junctions where node_id = %s", (id,))
|
||||
if row == None:
|
||||
return result
|
||||
|
||||
links = get_node_links(name, id)
|
||||
|
||||
for link in links:
|
||||
if is_pipe(name, link):
|
||||
result.merge(expand_pipe_delete(name, ChangeSet(g_delete_prefix | {'type': 'pipe', 'id': link})))
|
||||
if is_pump(name, link):
|
||||
result.merge(expand_pump_delete(name, ChangeSet(g_delete_prefix | {'type': 'pump', 'id': link})))
|
||||
if is_valve(name, link):
|
||||
result.merge(expand_valve_delete(name, ChangeSet(g_delete_prefix | {'type': 'valve', 'id': link})))
|
||||
|
||||
result.merge(delete_tag_by_node(name, id))
|
||||
result.merge(delete_demand_by_junction(name, id))
|
||||
result.merge(delete_emitter_by_junction(name, id))
|
||||
result.merge(delete_quality_by_node(name, id))
|
||||
result.merge(delete_source_by_node(name, id))
|
||||
result.merge(unset_label_by_node(name, id))
|
||||
result.merge(cs)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def expand_reservoir_delete(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
result = ChangeSet()
|
||||
|
||||
id = cs.operations[0]['id']
|
||||
row = try_read(name, "select 1 from network.reservoirs where node_id = %s", (id,))
|
||||
if row == None:
|
||||
return result
|
||||
|
||||
links = get_node_links(name, id)
|
||||
|
||||
for link in links:
|
||||
if is_pipe(name, link):
|
||||
result.merge(expand_pipe_delete(name, ChangeSet(g_delete_prefix | {'type': 'pipe', 'id': link})))
|
||||
if is_pump(name, link):
|
||||
result.merge(expand_pump_delete(name, ChangeSet(g_delete_prefix | {'type': 'pump', 'id': link})))
|
||||
if is_valve(name, link):
|
||||
result.merge(expand_valve_delete(name, ChangeSet(g_delete_prefix | {'type': 'valve', 'id': link})))
|
||||
|
||||
result.merge(delete_tag_by_node(name, id))
|
||||
result.merge(delete_quality_by_node(name, id))
|
||||
result.merge(delete_source_by_node(name, id))
|
||||
result.merge(unset_label_by_node(name, id))
|
||||
result.merge(cs)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def expand_tank_delete(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
result = ChangeSet()
|
||||
|
||||
id = cs.operations[0]['id']
|
||||
row = try_read(name, "select 1 from network.tanks where node_id = %s", (id,))
|
||||
if row == None:
|
||||
return result
|
||||
|
||||
links = get_node_links(name, id)
|
||||
|
||||
for link in links:
|
||||
if is_pipe(name, link):
|
||||
result.merge(expand_pipe_delete(name, ChangeSet(g_delete_prefix | {'type': 'pipe', 'id': link})))
|
||||
if is_pump(name, link):
|
||||
result.merge(expand_pump_delete(name, ChangeSet(g_delete_prefix | {'type': 'pump', 'id': link})))
|
||||
if is_valve(name, link):
|
||||
result.merge(expand_valve_delete(name, ChangeSet(g_delete_prefix | {'type': 'valve', 'id': link})))
|
||||
|
||||
result.merge(delete_tag_by_node(name, id))
|
||||
result.merge(delete_quality_by_node(name, id))
|
||||
result.merge(delete_source_by_node(name, id))
|
||||
result.merge(delete_tank_reaction_by_tank(name, id))
|
||||
result.merge(delete_mixing_by_tank(name, id))
|
||||
result.merge(unset_label_by_node(name, id))
|
||||
result.merge(cs)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def expand_pipe_delete(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
result = ChangeSet()
|
||||
|
||||
id = cs.operations[0]['id']
|
||||
row = try_read(name, "select 1 from network.pipes where link_id = %s", (id,))
|
||||
if row == None:
|
||||
return result
|
||||
|
||||
result.merge(delete_tag_by_link(name, id))
|
||||
result.merge(delete_status_by_link(name, id))
|
||||
result.merge(delete_pipe_reaction_by_pipe(name, id))
|
||||
result.merge(delete_vertex_by_link(name, id))
|
||||
result.merge(cs)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def expand_pump_delete(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
result = ChangeSet()
|
||||
|
||||
id = cs.operations[0]['id']
|
||||
row = try_read(name, "select 1 from network.pumps where link_id = %s", (id,))
|
||||
if row == None:
|
||||
return result
|
||||
|
||||
result.merge(delete_tag_by_link(name, id))
|
||||
result.merge(delete_status_by_link(name, id))
|
||||
result.merge(delete_pump_energy_by_pump(name, id))
|
||||
result.merge(delete_vertex_by_link(name, id))
|
||||
result.merge(cs)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def expand_valve_delete(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
result = ChangeSet()
|
||||
|
||||
id = cs.operations[0]['id']
|
||||
row = try_read(name, "select 1 from network.valves where link_id = %s", (id,))
|
||||
if row == None:
|
||||
return result
|
||||
|
||||
result.merge(delete_tag_by_link(name, id))
|
||||
result.merge(delete_status_by_link(name, id))
|
||||
result.merge(delete_vertex_by_link(name, id))
|
||||
|
||||
result.merge(cs)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def expand_pattern_delete(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
result = ChangeSet()
|
||||
|
||||
id = cs.operations[0]['id']
|
||||
row = try_read(name, "select 1 from network.patterns where id = %s", (id,))
|
||||
if row == None:
|
||||
return result
|
||||
|
||||
result.merge(unset_reservoir_by_pattern(name, id))
|
||||
result.merge(unset_pump_by_pattern(name, id))
|
||||
result.merge(unset_demand_by_pattern(name, id))
|
||||
result.merge(unset_pump_energy_by_pattern(name, id))
|
||||
result.merge(unset_source_by_pattern(name, id))
|
||||
result.merge(cs)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def expand_curve_delete(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
result = ChangeSet()
|
||||
|
||||
id = cs.operations[0]['id']
|
||||
row = try_read(name, "select 1 from network.curves where id = %s", (id,))
|
||||
if row == None:
|
||||
return result
|
||||
|
||||
result.merge(unset_tank_by_curve(name, id))
|
||||
result.merge(unset_pump_by_curve(name, id))
|
||||
result.merge(unset_pump_energy_by_curve(name, id))
|
||||
result.merge(cs)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def expand_legacy_options_update(cs: ChangeSet) -> ChangeSet:
|
||||
cs.operations[0]['operation'] = API_UPDATE
|
||||
cs.operations[0]['type'] = 'option'
|
||||
new_cs = cs
|
||||
new_cs.merge(generate_v3(cs))
|
||||
return new_cs
|
||||
|
||||
|
||||
def expand_v3_options_update(cs: ChangeSet) -> ChangeSet:
|
||||
cs.operations[0]['operation'] = API_UPDATE
|
||||
cs.operations[0]['type'] = 'option_v3'
|
||||
new_cs = cs
|
||||
new_cs.merge(generate_v2(cs))
|
||||
return new_cs
|
||||
|
||||
|
||||
def expand_command(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
op = cs.operations[0]
|
||||
operation = op['operation']
|
||||
element_type = op['type']
|
||||
|
||||
if operation == API_DELETE:
|
||||
handler = _DELETE_REWRITERS.get(element_type)
|
||||
if handler:
|
||||
return handler(name, cs)
|
||||
elif operation == API_UPDATE:
|
||||
handler = _UPDATE_REWRITERS.get(element_type)
|
||||
if handler:
|
||||
return handler(cs)
|
||||
|
||||
return cs
|
||||
|
||||
|
||||
DeleteRewriter = Callable[[str, ChangeSet], ChangeSet]
|
||||
UpdateRewriter = Callable[[ChangeSet], ChangeSet]
|
||||
|
||||
_DELETE_REWRITERS: dict[str, DeleteRewriter] = {
|
||||
"junction": expand_junction_delete,
|
||||
"reservoir": expand_reservoir_delete,
|
||||
"tank": expand_tank_delete,
|
||||
"pipe": expand_pipe_delete,
|
||||
"pump": expand_pump_delete,
|
||||
"valve": expand_valve_delete,
|
||||
"pattern": expand_pattern_delete,
|
||||
"curve": expand_curve_delete,
|
||||
}
|
||||
|
||||
_UPDATE_REWRITERS: dict[str, UpdateRewriter] = {
|
||||
"option": expand_legacy_options_update,
|
||||
"option_v3": expand_v3_options_update,
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
"""Transactional command dispatch for WNDB model mutations."""
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from ..core.connection import project_transaction
|
||||
from ..core.database import (
|
||||
API_ADD,
|
||||
API_DELETE,
|
||||
API_UPDATE,
|
||||
ChangeSet,
|
||||
refresh_materialized_views,
|
||||
)
|
||||
from ..gis.backdrop import set_backdrop
|
||||
from ..gis.labels import add_label, delete_label, set_label
|
||||
from ..gis.regions import add_region, delete_region, set_region
|
||||
from ..gis.vertices import add_vertex, delete_vertex, set_vertex
|
||||
from ..model.controls import set_control
|
||||
from ..model.curves import add_curve, delete_curve, set_curve
|
||||
from ..model.demands import set_demand
|
||||
from ..model.emitters import set_emitter
|
||||
from ..model.energy import set_energy, set_pump_energy
|
||||
from ..model.junctions import add_junction, delete_junction, set_junction
|
||||
from ..model.mixing import add_mixing, delete_mixing, set_mixing
|
||||
from ..model.options import set_option, set_option_v3
|
||||
from ..model.patterns import add_pattern, delete_pattern, set_pattern
|
||||
from ..model.pipes import add_pipe, delete_pipe, set_pipe
|
||||
from ..model.pumps import add_pump, delete_pump, set_pump
|
||||
from ..model.quality import set_quality
|
||||
from ..model.reactions import (
|
||||
set_pipe_reaction,
|
||||
set_reaction,
|
||||
set_tank_reaction,
|
||||
)
|
||||
from ..model.reservoirs import add_reservoir, delete_reservoir, set_reservoir
|
||||
from ..model.rules import set_rule
|
||||
from ..model.sources import add_source, delete_source, set_source
|
||||
from ..model.status import set_status
|
||||
from ..model.tags import set_tag
|
||||
from ..model.tanks import add_tank, delete_tank, set_tank
|
||||
from ..model.times import set_time
|
||||
from ..model.title import set_title
|
||||
from ..model.valves import add_valve, delete_valve, set_valve
|
||||
from .cascade import expand_command
|
||||
|
||||
CommandHandler = Callable[[str, ChangeSet], ChangeSet]
|
||||
|
||||
_ADD_HANDLERS: dict[str, CommandHandler] = {
|
||||
"junction": add_junction,
|
||||
"reservoir": add_reservoir,
|
||||
"tank": add_tank,
|
||||
"pipe": add_pipe,
|
||||
"pump": add_pump,
|
||||
"valve": add_valve,
|
||||
"pattern": add_pattern,
|
||||
"curve": add_curve,
|
||||
"source": add_source,
|
||||
"mixing": add_mixing,
|
||||
"vertex": add_vertex,
|
||||
"label": add_label,
|
||||
"region": add_region,
|
||||
}
|
||||
|
||||
_UPDATE_HANDLERS: dict[str, CommandHandler] = {
|
||||
"title": set_title,
|
||||
"junction": set_junction,
|
||||
"reservoir": set_reservoir,
|
||||
"tank": set_tank,
|
||||
"pipe": set_pipe,
|
||||
"pump": set_pump,
|
||||
"valve": set_valve,
|
||||
"tag": set_tag,
|
||||
"demand": set_demand,
|
||||
"status": set_status,
|
||||
"pattern": set_pattern,
|
||||
"curve": set_curve,
|
||||
"control": set_control,
|
||||
"rule": set_rule,
|
||||
"energy": set_energy,
|
||||
"pump_energy": set_pump_energy,
|
||||
"emitter": set_emitter,
|
||||
"quality": set_quality,
|
||||
"source": set_source,
|
||||
"reaction": set_reaction,
|
||||
"pipe_reaction": set_pipe_reaction,
|
||||
"tank_reaction": set_tank_reaction,
|
||||
"mixing": set_mixing,
|
||||
"time": set_time,
|
||||
"option": set_option,
|
||||
"option_v3": set_option_v3,
|
||||
"vertex": set_vertex,
|
||||
"label": set_label,
|
||||
"backdrop": set_backdrop,
|
||||
"region": set_region,
|
||||
}
|
||||
|
||||
_DELETE_HANDLERS: dict[str, CommandHandler] = {
|
||||
"junction": delete_junction,
|
||||
"reservoir": delete_reservoir,
|
||||
"tank": delete_tank,
|
||||
"pipe": delete_pipe,
|
||||
"pump": delete_pump,
|
||||
"valve": delete_valve,
|
||||
"pattern": delete_pattern,
|
||||
"curve": delete_curve,
|
||||
"source": delete_source,
|
||||
"mixing": delete_mixing,
|
||||
"vertex": delete_vertex,
|
||||
"label": delete_label,
|
||||
"region": delete_region,
|
||||
}
|
||||
|
||||
|
||||
def _dispatch(
|
||||
handlers: dict[str, CommandHandler], name: str, change_set: ChangeSet
|
||||
) -> ChangeSet:
|
||||
element_type = change_set.operations[0]["type"]
|
||||
handler = handlers.get(element_type)
|
||||
return handler(name, change_set) if handler else ChangeSet()
|
||||
|
||||
|
||||
def _execute_add_command(name: str, change_set: ChangeSet) -> ChangeSet:
|
||||
return _dispatch(_ADD_HANDLERS, name, change_set)
|
||||
|
||||
|
||||
def _execute_update_command(name: str, change_set: ChangeSet) -> ChangeSet:
|
||||
return _dispatch(_UPDATE_HANDLERS, name, change_set)
|
||||
|
||||
|
||||
def _execute_delete_command(name: str, change_set: ChangeSet) -> ChangeSet:
|
||||
return _dispatch(_DELETE_HANDLERS, name, change_set)
|
||||
|
||||
|
||||
def execute_batch_commands(name: str, change_set: ChangeSet) -> ChangeSet:
|
||||
with project_transaction(name):
|
||||
rewritten = ChangeSet()
|
||||
for operation in change_set.operations:
|
||||
rewritten.merge(expand_command(name, ChangeSet(operation)))
|
||||
|
||||
result = ChangeSet()
|
||||
for operation in rewritten.operations:
|
||||
operation_type = operation["operation"]
|
||||
if operation_type == API_ADD:
|
||||
result.merge(_execute_add_command(name, ChangeSet(operation)))
|
||||
elif operation_type == API_UPDATE:
|
||||
result.merge(_execute_update_command(name, ChangeSet(operation)))
|
||||
elif operation_type == API_DELETE:
|
||||
result.merge(_execute_delete_command(name, ChangeSet(operation)))
|
||||
|
||||
if rewritten.operations:
|
||||
refresh_materialized_views(name)
|
||||
return result
|
||||
|
||||
|
||||
def execute_batch_command(name: str, change_set: ChangeSet) -> ChangeSet:
|
||||
return execute_batch_commands(name, change_set)
|
||||
Reference in New Issue
Block a user