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:
2026-08-25 18:35:05 +08:00
parent fdbcc5c033
commit fa188af0b1
181 changed files with 8446 additions and 33546 deletions
+143
View File
@@ -0,0 +1,143 @@
from collections.abc import Mapping, Sequence
from typing import Any
from psycopg import sql
from psycopg.rows import Row, dict_row
from .connection import is_project_transaction_active, project_connection
API_ADD = "add"
API_UPDATE = "update"
API_DELETE = "delete"
g_add_prefix = {"operation": API_ADD}
g_update_prefix = {"operation": API_UPDATE}
g_delete_prefix = {"operation": API_DELETE}
class ChangeSet:
def __init__(self, ps: dict[str, Any] | None = None):
self.operations: list[dict[str, Any]] = []
if ps is not None:
self.append(ps)
@staticmethod
def from_list(ps: list[dict[str, Any]]):
change_set = ChangeSet()
for item in ps:
change_set.append(item)
return change_set
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, change_set):
self.operations.extend(change_set.operations)
return self
def dump(self):
for operation in self.operations:
print(operation)
def compress(self):
return self
class DatabaseCommand:
def __init__(self, statement: str, changes: list[dict[str, Any]]) -> None:
self.sql = statement
self.changes = changes
QueryParams = Sequence[Any] | Mapping[str, Any]
def sql_literal(value: Any) -> str:
"""Render one PostgreSQL literal for legacy WNDB SQL batch builders.
WNDB still assembles multi-statement model changes before executing them as
one transaction. Every interpolated value must pass through this helper;
identifiers remain static strings owned by the backend.
"""
return sql.Literal(value).as_string()
def _execute(cur, query: str, params: QueryParams | None = None):
return cur.execute(query, params) if params is not None else cur.execute(query)
def read(name: str, query: str, params: QueryParams | None = None) -> Row:
with project_connection(name) as conn, conn.cursor(row_factory=dict_row) as cur:
_execute(cur, query, params)
row = cur.fetchone()
if row is None:
raise LookupError(query)
return row
def read_all(
name: str, query: str, params: QueryParams | None = None
) -> list[Row]:
with project_connection(name) as conn, conn.cursor(row_factory=dict_row) as cur:
_execute(cur, query, params)
return cur.fetchall()
def try_read(
name: str, query: str, params: QueryParams | None = None
) -> Row | None:
with project_connection(name) as conn, conn.cursor(row_factory=dict_row) as cur:
_execute(cur, query, params)
return cur.fetchone()
def write(name: str, query: str, params: QueryParams | None = None) -> None:
with project_connection(name) as conn, conn.cursor() as cur:
_execute(cur, query, params)
def refresh_materialized_views(name: str, *, concurrently: bool = True) -> None:
"""Refresh the GIS query layer after committed model or asset changes."""
with project_connection(name) as conn, conn.cursor() as cur:
cur.execute("CALL gis.refresh_all_materialized_views(%s)", (concurrently,))
_MATERIALIZED_VIEW_SOURCES = (
"network.nodes",
"network.junctions",
"network.reservoirs",
"network.tanks",
"network.links",
"network.pipes",
"network.pumps",
"network.valves",
"network.demands",
"gis.node_geometries",
"gis.link_vertices",
"asset.scada_devices",
)
def _affects_materialized_views(command: DatabaseCommand) -> bool:
statement = command.sql.lower()
return any(source in statement for source in _MATERIALIZED_VIEW_SOURCES)
def execute_command(name: str, command: DatabaseCommand) -> ChangeSet:
"""Apply a model mutation without the removed database undo/redo journal."""
write(name, command.sql)
if _affects_materialized_views(command) and not is_project_transaction_active(name):
refresh_materialized_views(name)
return ChangeSet.from_list(command.changes)