Accept Merge Request #17: (api -> master)

Merge Request: Merge api to master

Created By: @王琼钰
Accepted By: @王琼钰
URL: https://tjwater.coding.net/p/tjwatercloud/d/TJWaterServer/git/merge/17
This commit is contained in:
王琼钰
2022-09-06 21:54:51 +08:00
169 changed files with 2135 additions and 238 deletions

23
api/__init__.py Normal file
View File

@@ -0,0 +1,23 @@
from .project import have_project, create_project, delete_project
from .project import is_project_open, open_project, close_project
from .project import copy_project
from .change_set import ChangeSet
from .operation import execute_undo as undo
from .operation import execute_redo as redo
from .operation import have_snapshot, take_snapshot, pick_snapshot
from .operation import have_transaction, start_transaction, commit_transaction, abort_transaction
from .s0_base import JUNCTION, RESERVOIR, TANK, PIPE, PUMP, VALVE
from .s0_base import is_node, is_junction, is_reservoir, is_tank
from .s0_base import is_link, is_pipe, is_pump, is_valve
from .s0_base import is_curve
from .s0_base import is_pattern
from .s0_base import get_nodes, get_links, get_curves, get_patterns
from .s1_title import set_title, get_title
from .s2_junctions import add_junction, delete_junction
from .s2_junctions import get_junction_elevation, get_junction_demand, get_junction_pattern, get_junction_coord
from .s2_junctions import set_junction_elevation, set_junction_demand, set_junction_pattern, set_junction_coord

14
api/change_set.py Normal file
View File

@@ -0,0 +1,14 @@
class ChangeSet:
def __init__(self) -> None:
self.added : list[dict[str, str]] = {}
self.deleted : list[dict[str, str]] = {}
self.updated : list[dict[str, str]] = {}
def add(self, type: str, id: str) -> None:
self.added.append({ 'type': type, 'id': id })
def delete(self, type: str, id: str) -> None:
self.deleted.append({ 'type': type, 'id': id })
def update(self, type: str, id: str, property: str) -> None:
self.updated.append({ 'type': type, 'id': id, 'property': property })

3
api/connection.py Normal file
View File

@@ -0,0 +1,3 @@
import psycopg as pg
g_conn_dict : dict[str, pg.Connection] = {}

220
api/operation.py Normal file
View File

@@ -0,0 +1,220 @@
from psycopg.rows import dict_row, Row
from .connection import g_conn_dict as conn
def _get_current_transaction(name: str) -> Row | None:
with conn[name].cursor(row_factory=dict_row) as cur:
cur.execute(f"select * from transaction_operation")
return cur.fetchone()
def _get_current_transaction_id(name: str) -> int:
row = _get_current_transaction(name)
return int(row['id'])
def _remove_transaction(name: str) -> None:
with conn[name].cursor() as cur:
cur.execute(f"delete from transaction_operation")
def _remove_operation(name: str, id: int) -> None:
with conn[name].cursor(row_factory=dict_row) as cur:
# can not be >= to cascade delete since there is a tree !
cur.execute(f"delete from transaction_operation where id = {id}") # this should not happen
cur.execute(f"delete from snapshot_operation where id = {id}") # this may happen
cur.execute(f"delete from operation where id = {id}")
def _get_parents(name: str, id: int) -> list[int]:
ids = [id]
with conn[name].cursor(row_factory=dict_row) as cur:
while ids[-1] != 0:
cur.execute(f"select parent from operation where id = {ids[-1]}")
ids.append(int(cur.fetchone()['parent']))
return ids
def _get_current_operation(name: str) -> int:
with conn[name].cursor(row_factory=dict_row) as cur:
cur.execute(f"select id from current_operation")
return int(cur.fetchone()['id'])
def _update_current_operation(name: str, old_id: int, id: int) -> None:
with conn[name].cursor() as cur:
cur.execute(f"update current_operation set id = {id} where id = {old_id}")
def _add_redo_undo(name: str, redo: str, undo: str) -> int:
with conn[name].cursor(row_factory=dict_row) as cur:
parent = _get_current_operation(name)
cur.execute(f"insert into operation (id, redo, undo, parent) values (default, '{redo}', '{undo}', {parent})")
cur.execute("select max(id) from operation")
return int(cur.fetchone()['max'])
# execute curr undo
def _query_undo(name: str, id: str) -> dict[str, str]:
with conn[name].cursor(row_factory=dict_row) as cur:
cur.execute(f"select undo, parent from operation where id = {id}")
return cur.fetchone()
# execute next redo
def _query_redo_child(name: str, id: str) -> str:
with conn[name].cursor(row_factory=dict_row) as cur:
cur.execute(f"select redo_child from operation where id = {id}")
return cur.fetchone()['redo_child']
def _query_redo(name: str, id: str) -> dict[str, str]:
with conn[name].cursor(row_factory=dict_row) as cur:
cur.execute(f"select redo from operation where id = {id}")
return cur.fetchone()['redo']
def _set_redo_child(name: str, id: int, child: int | str) -> None:
with conn[name].cursor() as cur:
cur.execute(f"update operation set redo_child = {child} where id = {id}")
def _execute(name: str, sql: str) -> None:
with conn[name].cursor() as cur:
sql = sql.replace("\"", "\'")
cur.execute(sql)
def add_operation(name: str, redo: str, undo: str) -> None:
curr = _add_redo_undo(name, redo, undo)
old = _get_current_operation(name)
_update_current_operation(name, old, curr)
def execute_undo(name: str, discard: bool = False) -> None:
curr = _get_current_operation(name)
# transaction control
if have_transaction(name):
tran = _get_current_transaction(name)
if tran != None and int(tran['id']) >= 0:
if bool(tran['strict']): # strict mode disallow undo
print("Do not allow to undo in strict transaction mode!")
return
elif int(tran['id']) >= curr: # normal mode disallow undo start point, and there is foreign key constraint
print("Do not allow to undo transaction start point!")
return
row = _query_undo(name, curr)
undo = row['undo']
if undo == '':
print("nothing to undo!")
return
parent = int(row['parent'])
_set_redo_child(name, parent, 'NULL' if discard else curr)
_execute(name, undo)
_update_current_operation(name, curr, parent)
if discard:
_remove_operation(name, curr)
def execute_redo(name: str) -> None:
curr = _get_current_operation(name)
redoChild = _query_redo_child(name, curr)
if redoChild == None:
print("nothing to redo!")
return
child = int(redoChild)
redo = _query_redo(name, child)
_execute(name, redo)
_update_current_operation(name, curr, child)
# snapshot support to checkout between different version of database
# snapshot is persistent
# since redo always remember the recently undo path
def have_snapshot(name: str, tag: str) -> bool:
with conn[name].cursor(row_factory=dict_row) as cur:
cur.execute(f"select id from snapshot_operation where tag = '{tag}'")
return cur.rowcount > 0
def take_snapshot(name: str, tag: str) -> None:
if tag == None or tag == '':
print('Non empty tag is expected!')
return
curr = _get_current_operation(name)
with conn[name].cursor() as cur:
cur.execute(f"insert into snapshot_operation (id, tag) values ({curr}, '{tag}')")
def pick_snapshot(name: str, tag: str) -> None:
if tag == None or tag == '':
print('Non empty tag is expected!')
return
if not have_snapshot(name, tag):
print('No such snapshot!')
return
curr = _get_current_operation(name)
curr_parents = _get_parents(name, curr)
with conn[name].cursor(row_factory=dict_row) as cur:
cur.execute(f"select id from snapshot_operation where tag = '{tag}'")
target = int(cur.fetchone()['id'])
if target in curr_parents: # target -> curr
for i in range(curr_parents.index(target)):
execute_undo(name)
else:
target_parents = _get_parents(name, target)
if curr in target_parents: # curr -> target
for i in range(target_parents.index(curr)):
execute_redo(name)
else:
ancestor_index = -1
while curr_parents[ancestor_index] == target_parents[ancestor_index]:
ancestor_index -= 1
# ancestor -> curr
ancestor = curr_parents[ancestor_index + 1] # ancestor_index + 1 is common parent
for i in range(curr_parents.index(ancestor)):
execute_undo(name)
# ancestor -> redo, need assign redo_child
while target_parents[ancestor_index] != target:
cur.execute(f"update operation set redo_child = '{target_parents[ancestor_index]}' where id = '{target_parents[ancestor_index + 1]}'")
execute_redo(name)
ancestor_index -= 1
cur.execute(f"update operation set redo_child = '{target}' where id = '{target_parents[1]}'")
execute_redo(name)
# transaction is volatile, commit/abort will destroy transaction.
# can not redo an aborted transaction.
# but can undo a committed transaction... inconsistent...
# it may remove snapshot if it is in aborted transaction
def have_transaction(name: str) -> bool:
with conn[name].cursor(row_factory=dict_row) as cur:
cur.execute(f"select * from transaction_operation")
return cur.rowcount > 0
def start_transaction(name: str, strict: bool = False) -> None:
if have_transaction(name):
print("Only support single transaction now, please commit/abort current transaction!")
return
curr = _get_current_operation(name)
with conn[name].cursor() as cur:
cur.execute(f"insert into transaction_operation (id, strict) values ({curr}, {strict});")
def commit_transaction(name: str) -> None:
if not have_transaction(name):
print("No active transaction!")
return
_remove_transaction(name)
def abort_transaction(name: str) -> None:
if not have_transaction(name):
print("No active transaction!")
return
tran = _get_current_transaction_id(name)
curr = _get_current_operation(name)
curr_parents = _get_parents(name, curr)
for i in range(curr_parents.index(tran)):
execute_undo(name, True)
_remove_transaction(name)

33
api/project.py Normal file
View File

@@ -0,0 +1,33 @@
import psycopg as pg
from .connection import g_conn_dict as conn
# no undo/redo
def have_project(name: str) -> bool:
with pg.connect(conninfo="dbname=postgres", autocommit=True) as conn:
with conn.cursor() as cur:
cur.execute(f"select * from pg_database where datname = '{name}'")
return cur.rowcount > 0
def copy_project(source: str, new: str) -> None:
with pg.connect(conninfo="dbname=postgres", autocommit=True) as conn:
with conn.cursor() as cur:
cur.execute(f"create database {new} with template = {source}")
def create_project(name: str) -> None:
return copy_project('project', name)
def delete_project(name: str) -> None:
with pg.connect(conninfo="dbname=postgres", autocommit=True) as conn:
with conn.cursor() as cur:
cur.execute(f"drop database {name}")
def open_project(name: str) -> None:
conn[name] = pg.connect(conninfo=f"dbname={name}", autocommit=True)
def is_project_open(name: str) -> bool:
return name in conn
def close_project(name: str) -> None:
conn[name].close()
del conn[name]

75
api/s0_base.py Normal file
View File

@@ -0,0 +1,75 @@
from psycopg.rows import dict_row, Row
from .connection import g_conn_dict as conn
_NODE = "_node"
_LINK = "_link"
_CURVE = "_curve"
_PATTERN = "_pattern"
JUNCTION = "JUNCTION"
RESERVOIR = "RESERVOIR"
TANK = "TANK"
PIPE = "PIPE"
PUMP = "PUMP"
VALVE = "VALVE"
def _get_from(name: str, id: str, base_type: str) -> Row | None:
with conn[name].cursor(row_factory=dict_row) as cur:
cur.execute(f"select * from {base_type} where id = '{id}'")
return cur.fetchone()
def is_node(name: str, id: str) -> bool:
return _get_from(name, id, _NODE) != None
def is_junction(name: str, id: str) -> bool:
row = _get_from(name, id, _NODE)
return row != None and row['type'] == JUNCTION
def is_reservoir(name: str, id: str) -> bool:
row = _get_from(name, id, _NODE)
return row != None and row['type'] == RESERVOIR
def is_tank(name: str, id: str) -> bool:
row = _get_from(name, id, _NODE)
return row != None and row['type'] == TANK
def is_link(name: str, id: str) -> bool:
return _get_from(name, id, _LINK) != {}
def is_pipe(name: str, id: str) -> bool:
row = _get_from(name, id, _LINK)
return row != None and row['type'] == PIPE
def is_pump(name: str, id: str) -> bool:
row = _get_from(name, id, _LINK)
return row != None and row['type'] == PUMP
def is_valve(name: str, id: str) -> bool:
row = _get_from(name, id, _LINK)
return row != None and row['type'] == VALVE
def is_curve(name: str, id: str) -> bool:
return _get_from(name, id, _CURVE) != None
def is_pattern(name: str, id: str) -> bool:
return _get_from(name, id, _PATTERN) != None
def _get_all(name: str, base_type: str) -> list[str]:
ids : list[str] = []
with conn[name].cursor(row_factory=dict_row) as cur:
cur.execute(f"select id from {base_type} order by id")
for record in cur:
ids.append(record['id'])
return ids
def get_nodes(name: str) -> list[str]:
return _get_all(name, _NODE)
def get_links(name: str) -> list[str]:
return _get_all(name, _LINK)
def get_curves(name: str) -> list[str]:
return _get_all(name, _CURVE)
def get_patterns(name: str) -> list[str]:
return _get_all(name, _PATTERN)

24
api/s1_title.py Normal file
View File

@@ -0,0 +1,24 @@
from psycopg.rows import dict_row
from .operation import *
from .connection import g_conn_dict as conn
from .change_set import ChangeSet
def get_title(name: str) -> str:
with conn[name].cursor(row_factory=dict_row) as cur:
cur.execute(f"select * from title")
return cur.fetchone()['value']
def set_title(name: str, value: str) -> ChangeSet:
old = get_title(name)
with conn[name].cursor() as cur:
sql = f"update title set value = '{value}'"
cur.execute(sql)
redo = sql.replace("'", '"')
undo = f'update title set value = "{old}"'
add_operation(name, redo, undo)
change = ChangeSet()
change.update('title', 'null', 'value')
return change

188
api/s2_junctions.py Normal file
View File

@@ -0,0 +1,188 @@
from psycopg.rows import dict_row, Row
from .connection import g_conn_dict as conn
from .s0_base import *
from .operation import *
from .change_set import ChangeSet
def add_junction(name: str, id: str, x: float, y: float, elevation: float) -> ChangeSet:
if is_node(name, id):
return
with conn[name].cursor() as cur:
sql = f"insert into _node (id, type) values ('{id}', 'JUNCTION');"
sql += f" insert into junctions (id, elevation) values ('{id}', {elevation});"
sql += f" insert into coordinates (node, coord) values ('{id}', '({x}, {y})');"
cur.execute(sql)
redo = sql.replace("'", '"')
undo = f'delete from coordinates where node = "{id}";'
undo += f' delete from junctions where id = "{id}";'
undo += f' delete from _node where id = "{id}";'
add_operation(name, redo, undo)
change = ChangeSet()
change.add('junction', id)
return change
def delete_junction(name: str, id: str) -> ChangeSet:
if not is_junction(name, id):
return
with conn[name].cursor(row_factory=dict_row) as cur:
cur.execute(f"select * from junctions where id = '{id}'")
row = cur.fetchone()
if row == None:
return
elevation = row['elevation']
demand = 'NULL' if row['demand'] == None else row['demand']
pattern = 'NULL' if row['pattern'] == None else row['pattern']
pattern = f'"{pattern}"' if pattern != 'NULL' else pattern
cur.execute(f"select * from coordinates where id = '{id}'")
row = cur.fetchone()
if row == None:
return
coord = row['coord']
sql = f"delete from coordinates where node = '{id}';"
sql += f" delete from junctions where id = '{id}';"
sql += f" delete from _node where id = '{id}';"
cur.execute(sql)
redo = sql.replace("'", '"')
undo = f'insert into _node (id, type) values ("{id}", "{type}");'
undo += f" insert into junctions (id, elevation, demand, pattern) values ('{id}', {elevation}, {demand}, {pattern});"
undo += f" insert into coordinates (node, coord) values ('{id}', '{coord}');"
add_operation(name, redo, undo)
change = ChangeSet()
change.delete('junction', id)
return change
def _get_junction(name: str, id: str) -> Row | None:
with conn[name].cursor(row_factory=dict_row) as cur:
cur.execute(f"select elevation, demand, pattern from junctions where id = '{id}'")
return cur.fetchone()
def get_junction_elevation(name: str, id: str) -> float | None:
row = _get_junction(name, id)
return float(row['elevation']) if row != None else None
def get_junction_demand(name: str, id: str) -> float | str | None:
row = _get_junction(name, id)
if row != None:
return float(row['demand']) if row['demand'] != None else 'NULL'
else:
return None
def get_junction_pattern(name: str, id: str) -> str | None:
row = _get_junction(name, id)
if row != None:
return row['pattern'] if row['pattern'] != None else 'NULL'
else:
return None
def _to_point(coord: str) -> dict[str, float]:
coord = coord.removeprefix('(')
coord = coord.removesuffix(')')
coord = coord.split(',')
return { 'x': float(coord[0]), 'y': float(coord[1]) }
def get_junction_coord(name: str, id: str) -> dict[str, float] | None:
with conn[name].cursor(row_factory=dict_row) as cur:
cur.execute(f"select * from coordinates where node = '{id}'")
row = cur.fetchone()
if row == None:
return None
coord = str(row['coord'])
return _to_point(coord)
def set_junction_elevation(name: str, id: str, elevation: float) -> ChangeSet:
if not is_junction(name, id):
return
old = get_junction_elevation(name, id)
if old == None:
return
with conn[name].cursor() as cur:
sql = f"update junctions set elevation = {elevation} where id = '{id}'"
cur.execute(sql)
redo = sql.replace("'", '"')
undo = f'update junctions set elevation = {old} where id = "{id}"'
add_operation(name, redo, undo)
change = ChangeSet()
change.update('junction', id, 'elevation')
return change
def set_junction_demand(name: str, id: str, demand: float) -> ChangeSet:
if not is_junction(name, id):
return
old = get_junction_demand(name, id)
if old == None:
return
with conn[name].cursor() as cur:
sql = f"update junctions set demand = {demand} where id = '{id}'"
cur.execute(sql)
redo = sql.replace("'", '"')
undo = f'update junctions set demand = {old} where id = "{id}"'
add_operation(name, redo, undo)
change = ChangeSet()
change.update('junction', id, 'demand')
return change
def set_junction_pattern(name: str, id: str, pattern: str) -> ChangeSet:
if not is_junction(name, id):
return
if not is_pattern(name, id):
return
old = get_junction_pattern(name, id)
if old == None:
return
old = f'"{old}"' if old != 'NULL' else old
with conn[name].cursor() as cur:
sql = f"update junctions set pattern = '{pattern}' where id = '{id}'"
cur.execute(sql)
redo = sql.replace("'", '"')
undo = f'update junctions set pattern = {old} where id = "{id}"'
add_operation(name, redo, undo)
change = ChangeSet()
change.update('junction', id, 'pattern')
return change
def set_junction_coord(name: str, id: str, x: float, y: float) -> ChangeSet:
if not is_junction(name, id):
return
old = get_junction_coord(name, id)
if old == None:
return
old_x, old_y = old['x'], old['y']
with conn[name].cursor() as cur:
sql = f"update coordinates set coord = '({x},{y})' where node = '{id}'"
cur.execute(sql)
redo = sql.replace("'", '"')
undo = f'update coordinates set coord = "({old_x},{old_y})" where node = "{id}"'
add_operation(name, redo, undo)
change = ChangeSet()
change.update('junction', id, 'coord')
return change

184
new_demo.py Normal file
View File

@@ -0,0 +1,184 @@
from tjnetwork_new import *
def demo_snapshot():
p = "demo_snapshot"
print(p)
if is_project_open(p):
close_project(p)
if have_project(p):
delete_project(p)
create_project(p)
open_project(p)
add_junction(p, 'j-1', 10.0, 20.0, 30.0)
add_junction(p, 'j-2', 10.0, 20.0, 30.0)
add_junction(p, 'j-3', 10.0, 20.0, 30.0)
add_junction(p, 'j-4', 10.0, 20.0, 30.0)
take_snapshot(p, "1-2-3-4")
undo(p)
undo(p)
undo(p)
undo(p)
add_junction(p, 'j-5', 10.0, 20.0, 30.0)
add_junction(p, 'j-6', 10.0, 20.0, 30.0)
add_junction(p, 'j-7', 10.0, 20.0, 30.0)
add_junction(p, 'j-8', 10.0, 20.0, 30.0)
take_snapshot(p, "5-6-7-8")
print("before checkout, it should be 5, 6, 7, 8")
print(get_nodes(p))
pick_snapshot(p, "1-2-3-4")
print("after checkout, it should be 1, 2, 3, 4")
print(get_nodes(p))
close_project(p)
# delete_project(p)
def demo_transaction():
p = "demo_transaction"
print(p)
if is_project_open(p):
close_project(p)
if have_project(p):
delete_project(p)
create_project(p)
open_project(p)
add_junction(p, 'j-1', 10.0, 20.0, 30.0)
take_snapshot(p, "1")
add_junction(p, 'j-2', 10.0, 20.0, 30.0)
take_snapshot(p, "2")
start_transaction(p)
add_junction(p, 'j-3', 10.0, 20.0, 30.0)
take_snapshot(p, "3")
add_junction(p, 'j-4', 10.0, 20.0, 30.0)
take_snapshot(p, "4")
print("before rollback, it should be 1, 2, 3, 4")
print(get_nodes(p))
print("after rollback, it should be 1, 2")
abort_transaction(p)
print(get_nodes(p))
print(f"have snapshot 1: {have_snapshot(p, '1')}")
print(f"have snapshot 2: {have_snapshot(p, '2')}")
print(f"have snapshot 3: {have_snapshot(p, '3')}")
print(f"have snapshot 4: {have_snapshot(p, '4')}")
close_project(p)
# delete_project(p)
def demo_1_title():
p = "demo_1_title"
print(p)
if is_project_open(p):
close_project(p)
if have_project(p):
delete_project(p)
create_project(p)
open_project(p)
print(get_title(p)) #
set_title(p, "title")
print(get_title(p)) # title
set_title(p, "test")
print(get_title(p)) # test
undo(p)
print(get_title(p)) # title
undo(p)
print(get_title(p)) #
close_project(p)
# delete_project(p)
def demo_2_junctions():
p = "demo_2_junctions"
print(p)
if is_project_open(p):
close_project(p)
if have_project(p):
delete_project(p)
create_project(p)
open_project(p)
j = 'j-1'
print(get_junction_coord(p, j)) # None
print(get_junction_elevation(p, j)) # None
print(get_junction_demand(p, j)) # None
print(get_junction_pattern(p, j)) # None
add_junction(p, j, 10.0, 20.0, 30.0)
print(get_junction_coord(p, j)) # {'x': 10.0, 'y': 20.0}
print(get_junction_elevation(p, j)) # 30.0
print(get_junction_demand(p, j)) # NULL
print(get_junction_pattern(p, j)) # NULL
set_junction_demand(p, j, 100.0)
print(get_junction_demand(p, j)) # 100.0
undo(p)
print(get_junction_demand(p, j)) # NULL
undo(p)
print(get_junction_coord(p, j)) # None
print(get_junction_elevation(p, j)) # None
print(get_junction_demand(p, j)) # None
print(get_junction_pattern(p, j)) # None
add_junction(p, j, 10.0, 20.0, 30.0)
print(get_junction_coord(p, j)) # {'x': 10.0, 'y': 20.0}
print(get_junction_elevation(p, j)) # 30.0
print(get_junction_demand(p, j)) # NULL
print(get_junction_pattern(p, j)) # NULL
set_junction_coord(p, j, 100.0, 200.0)
print(get_junction_coord(p, j)) # {'x': 100.0, 'y': 200.0}
undo(p)
print(get_junction_coord(p, j)) # {'x': 10.0, 'y': 20.0}
redo(p)
print(get_junction_coord(p, j)) # {'x': 100.0, 'y': 200.0}
close_project(p)
# delete_project(p)
if __name__ == "__main__":
demo_snapshot()
demo_transaction()
demo_1_title()
demo_2_junctions()
pass

View File

@@ -1,5 +0,0 @@
from tjnetwork_admin import *
if __name__ == "__main__":
delete_template()
create_template()

View File

@@ -0,0 +1,27 @@
-- get_title()
create function tj.get_title() returns text as
$$
declare
title text;
begin
select value into title from tj.title;
return title;
end;
$$ language plpgsql;
-- set_title()
create function tj.set_title(new_title text) returns void as
$$
declare
old_title text;
redo text;
undo text;
begin
select tj.get_title() into old_title;
update tj.title set value = new_title where value = old_title;
redo := concat('update tj.title set value = ''', new_title, ''' where value = ''', old_title, '''');
undo := concat('update tj.title set value = ''', old_title, ''' where value = ''', new_title, '''');
perform tj.add_operation(redo, undo);
end;
$$ language plpgsql;

View File

@@ -0,0 +1,109 @@
create function tj.add_operation(redo text, undo text) returns void as
$$
declare
parent_id int;
curr_id int;
begin
select id into parent_id from tj.current_operation;
insert into tj.operation (id, redo, undo, parent) values (default, redo, undo, parent_id);
select max(id) into curr_id from tj.operation;
update tj.current_operation set id = curr_id where id = parent_id;
end;
$$ language plpgsql;
create function tj.have_transaction() returns boolean as
$$
declare
tran_count int;
begin
select count(*) into tran_count from tj.transaction_operation;
return tran_count > 0;
end;
$$ language plpgsql;
create function tj.execute_undo(discard boolean) returns void as
$$
declare
curr_id int;
have_tran boolean;
tran_id int;
strict_mode boolean;
undo_sql text;
parent_id int;
begin
select id into curr_id from tj.current_operation;
select tj.have_transaction() into have_tran;
if have_tran then
select strict into strict_mode from tj.transaction_operation;
if strict_mode then
return; -- strict mode disallow undo
else
select id into tran_id from tj.transaction_operation;
if tran_id >= curr_id then
return; -- # normal mode disallow undo start point, and there is foreign key constraint
end if;
end if;
end if;
select undo into undo_sql from tj.operation where id = curr_id;
if undo_sql = '' then
return;
end if;
select parent into parent_id from tj.operation where id = curr_id;
if discard then
update tj.operation set redo_child = null where id = parent_id;
else
update tj.operation set redo_child = curr_id where id = parent_id;
end if;
execute undo_sql;
update tj.current_operation set id = parent_id where id = curr_id;
if discard then
delete from tj.transaction_operation where id = curr_id;
delete from tj.snapshot_operation where id = curr_id;
delete from tj.operation where id = curr_id;
end if;
end;
$$ language plpgsql;
create function tj.undo() returns void as
$$
declare
begin
perform tj.execute_undo(false);
end;
$$ language plpgsql;
create function tj.discard_undo() returns void as
$$
declare
begin
perform tj.execute_undo(true);
end;
$$ language plpgsql;
create function tj.redo() returns void as
$$
declare
curr_id int;
child_id int;
redo_sql text;
begin
select id into curr_id from tj.current_operation;
select redo_child into child_id from tj.operation where id = curr_id;
if child_id = null then
return;
end if;
select redo into redo_sql from tj.operation where id = child_id;
execute redo_sql;
update tj.current_operation set id = child_id where id = curr_id;
end;
$$ language plpgsql;

View File

@@ -0,0 +1,9 @@
create function tj.have_project(in_name text) returns boolean as
$$
declare
db_count int;
begin
select count(*) into db_count from pg_database where datname = in_name;
return db_count > 0;
end;
$$ language plpgsql;

View File

@@ -0,0 +1,3 @@
drop function if exists tj.set_title;
drop function if exists tj.get_title;

View File

@@ -0,0 +1,9 @@
drop function if exists tj.redo;
drop function if exists tj.discard_undo;
drop function if exists tj.undo;
drop function if exists tj.execute_undo;
drop function if exists tj.have_transaction;
drop function if exists tj.add_operation;

View File

@@ -0,0 +1,6 @@
create function xxx() returns void as
$$
declare
begin
end;
$$ language plpgsql;

View File

@@ -0,0 +1,35 @@
create type tj.node_type as enum
(
'junction'
, 'reservoir'
, 'tank'
);
create type tj.link_type as enum
(
'pipe'
, 'pump'
, 'valve'
);
create table tj.node
(
id varchar(32) primary key
, type tj.node_type not null
);
create table tj.link
(
id varchar(32) primary key
, type tj.link_type not null
);
create table tj.curve
(
id varchar(32) primary key
);
create table tj.pattern
(
id varchar(32) primary key
);

View File

@@ -0,0 +1,8 @@
-- [title]
create table tj.title
(
value text
);
insert into tj.title (value) values ('');

View File

@@ -0,0 +1,33 @@
-- [status]
create type tj.status_pipe_pump_status as enum
(
'open'
, 'closed'
);
create table tj.status_pipe
(
id varchar(32) primary key references tj.pipes(id)
, status tj.status_pipe_pump_status not null
);
create table tj.status_pump
(
id varchar(32) primary key references tj.pumps(id)
, status tj.status_pipe_pump_status not null
);
create type tj.status_valve_status as enum ('open', 'closed', 'active');
create table tj.status_valve
(
id varchar(32) primary key references tj.valves(id)
, status tj.status_valve_status not null
);
create table tj.status_link
(
id varchar(32) primary key references tj.link(id)
, setting numeric not null
);

View File

@@ -0,0 +1,7 @@
-- [patterns]
create table tj.patterns
(
id varchar(32) references tj.pattern(id) not null
, multipliers numeric not null
);

View File

@@ -0,0 +1,8 @@
-- [curves]
create table tj.curves
(
id varchar(32) references tj.curve(id) not null
, x numeric not null
, y numeric not null
);

View File

@@ -0,0 +1,30 @@
-- [controls]
create type tj.controls_1_prep as enum ('above', 'below');
-- link linkid status if node nodeid above / below value
create table tj.controls_1
(
linkid varchar(32) primary key references tj.link(id)
, status text not null -- open / closed, a pump speed setting, or a control valve setting
, nodeid varchar(32) references tj.node(id) not null
, prep tj.controls_1_prep not null
, value numeric not null
);
-- link linkid status at time time
create table tj.controls_2
(
linkid varchar(32) primary key references tj.link(id)
, status text not null -- open / closed, a pump speed setting, or a control valve setting
, time interval hour
);
-- link linkid status at clocktime clocktime am / pm
create table tj.controls_3
(
linkid varchar(32) primary key references tj.link(id)
, status text not null -- open / closed, a pump speed setting, or a control valve setting
, clocktimehour interval hour -- get am/pm from it
, clocktimemin interval minute
);

View File

@@ -0,0 +1,6 @@
-- [rules]
create table tj.rules
(
content text primary key
);

View File

@@ -0,0 +1,29 @@
-- [energy]
create type tj.energy_param as enum
(
'price'
, 'pattern'
, 'effic'
);
-- global price / pattern / effic value
create table tj.energy_global
(
param tj.energy_param not null
, value numeric not null
);
-- pump pumpid price / pattern / effic value
create table tj.energy_pump
(
id varchar(32) references tj.pumps(id) not null
, param tj.energy_param not null
, value numeric not null
);
-- demand charge value
create table tj.energy_demand_charge
(
value numeric not null
);

View File

@@ -0,0 +1,7 @@
-- [emitters]
create table tj.emitters
(
junction varchar(32) primary key references tj.junctions(id)
, coefficient numeric not null
);

View File

@@ -0,0 +1,7 @@
-- [quality]
create table tj.quality
(
node varchar(32) primary key references tj.node(id)
, initialqual numeric not null
);

View File

@@ -0,0 +1,17 @@
-- [sources]
create type tj.sources_type as enum
(
'concen'
, 'mass'
, 'flowpaced'
, 'setpoint'
);
create table tj.sources
(
node varchar(32) primary key references tj.node(id)
, type tj.sources_type not null
, strength numeric not null
, timepattern varchar(32) references tj.pattern(id)
);

View File

@@ -0,0 +1,55 @@
-- [reactions]
create type tj.reactions_order_param as enum
(
'bulk'
, 'wall'
, 'tank'
);
create table tj.reactions_order
(
key tj.reactions_order_param not null
, value numeric not null
);
create type tj.reactions_global_param as enum
(
'bulk'
, 'wall'
);
create table tj.reactions_global
(
key tj.reactions_global_param not null
, value numeric not null
);
create type tj.reactions_pipe_param as enum
(
'bulk'
, 'wall'
);
create table tj.reactions_pipe
(
key tj.reactions_pipe_param not null
, pipe varchar(32) references tj.pipes(id) not null
, value numeric not null
);
create table tj.reactions_tank
(
tank varchar(32) references tj.tanks(id) not null
, value numeric not null
);
create table tj.reactions_limiting_potential
(
value numeric not null
);
create table tj.reactions_roughness_correlation
(
value numeric not null
);

View File

@@ -0,0 +1,9 @@
-- [junctions]
create table tj.junctions
(
id varchar(32) primary key references tj.node(id)
, elevation numeric not null
, demand numeric
, pattern varchar(32) references tj.pattern(id)
);

View File

@@ -0,0 +1,16 @@
-- [mixing]
create type tj.mixing_model as enum
(
'mixed'
, '2comp'
, 'fifo'
, 'lifo'
);
create table tj.mixing
(
tank varchar(32) primary key references tj.tanks(id)
, model tj.mixing_model not null
, value numeric
);

View File

@@ -0,0 +1,9 @@
-- [times]
-- todo: constraint
create table tj.times
(
key text not null
, value text not null
);

View File

@@ -0,0 +1,9 @@
-- [report]
-- todo: constraint
create table tj.report
(
key text not null
, value text not null
);

View File

@@ -0,0 +1,9 @@
-- [options]
-- todo: constraint
create table tj.options
(
key text not null
, value text not null
);

View File

@@ -0,0 +1,10 @@
-- [coordinates]
create table tj.coordinates
(
node varchar(32) primary key references tj.node(id)
, coord point not null
);
create index tj_coordinates_spgist on tj.coordinates using spgist(coord);
create index tj_coordinates_gist on tj.coordinates using gist(coord);

View File

@@ -0,0 +1,8 @@
-- [vertices]
create table tj.vertices
(
link varchar(32) references tj.link(id) not null
, x numeric not null
, y numeric not null
);

View File

@@ -0,0 +1,9 @@
-- [labels]
create table tj.labels
(
x numeric not null
, y numeric not null
, label text not null
, anchornode varchar(32) references tj.node(id)
);

View File

@@ -0,0 +1,6 @@
-- [backdrop]
create table tj.backdrop
(
content text primary key
);

View File

@@ -0,0 +1,8 @@
-- [reservoirs]
create table tj.reservoirs
(
id varchar(32) primary key references tj.node(id)
, head numeric not null
, pattern varchar(32) references tj.pattern(id)
);

View File

@@ -0,0 +1,20 @@
-- [tanks]
create type tj.tanks_overflow as enum
(
'yes'
, 'no'
);
create table tj.tanks
(
id varchar(32) primary key references tj.node(id)
, elevation numeric not null
, initlevel numeric not null
, minlevel numeric not null
, maxlevel numeric not null
, diameter numeric not null
, minvol numeric not null
, volcurve varchar(32) references tj.curve(id)
, overflow tj.tanks_overflow
);

View File

@@ -0,0 +1,20 @@
-- [pipes]
create type tj.pipes_status as enum
(
'open'
, 'closed'
, 'cv'
);
create table tj.pipes
(
id varchar(32) primary key references tj.link(id)
, node1 varchar(32) references tj.node(id) not null
, node2 varchar(32) references tj.node(id) not null
, length numeric not null
, diameter numeric not null
, roughness numeric not null
, minorloss numeric not null
, status tj.pipes_status not null
);

View File

@@ -0,0 +1,32 @@
-- [pumps]
create table tj.pumps
(
id varchar(32) primary key references tj.link(id)
, node1 varchar(32) references tj.node(id) not null
, node2 varchar(32) references tj.node(id) not null
);
create table tj.pumps_property_power
(
id varchar primary key references tj.pumps(id)
, value varchar(32) not null
);
create table tj.pumps_property_speed
(
id varchar primary key references tj.pumps(id)
, value varchar(32) not null
);
create table tj.pumps_property_head
(
id varchar primary key references tj.pumps(id)
, head varchar(32) references tj.curve(id) not null
);
create table tj.pumps_property_pattern
(
id varchar primary key references tj.pumps(id)
, pattern varchar(32) references tj.pattern(id) not null
);

View File

@@ -0,0 +1,22 @@
-- [valves]
create type tj.valves_type as enum
(
'prv'
, 'psv'
, 'pbv'
, 'fcv'
, 'tcv'
, 'gpv'
);
create table tj.valves
(
id varchar(32) primary key references tj.link(id)
, node1 varchar(32) references tj.node(id) not null
, node2 varchar(32) references tj.node(id) not null
, diameter numeric not null
, type tj.valves_type not null
, setting numeric not null
, minorloss numeric not null
);

View File

@@ -0,0 +1,13 @@
-- [tags]
create table tj.tags_node
(
id varchar(32) primary key references tj.node(id)
, tag text not null
);
create table tj.tags_link
(
id varchar(32) primary key references tj.link(id)
, tag text not null
);

View File

@@ -0,0 +1,9 @@
-- [demands]
create table tj.demands
(
junction varchar(32) references tj.junctions(id) not null
, demand numeric not null
, pattern varchar(32) references tj.pattern(id)
, category text not null
);

View File

@@ -0,0 +1 @@
create schema tj;

View File

@@ -0,0 +1,29 @@
create table tj.operation
(
id serial primary key
, redo text not null
, undo text not null
, parent integer references tj.operation(id)
, redo_child integer references tj.operation(id)
);
insert into tj.operation (id, redo, undo) values (0, '', '');
create table tj.current_operation
(
id integer primary key references tj.operation(id)
);
insert into tj.current_operation (id) values (0);
create table tj.snapshot_operation
(
id integer primary key references tj.operation(id)
, tag text not null unique
);
create table tj.transaction_operation
(
id integer primary key references tj.operation(id)
, strict boolean not null default false
);

View File

@@ -0,0 +1,11 @@
drop table if exists tj.pattern;
drop table if exists tj.curve;
drop table if exists tj.link;
drop table if exists tj.node;
drop type if exists tj.link_type;
drop type if exists tj.node_type;

View File

@@ -0,0 +1,3 @@
-- [title]
drop table if exists tj.title;

View File

@@ -0,0 +1,13 @@
-- [status]
drop table if exists tj.status_link;
drop table if exists tj.status_valve;
drop type if exists tj.status_valve_status;
drop table if exists tj.status_pump;
drop table if exists tj.status_pipe;
drop type if exists tj.status_pipe_pump_status;

View File

@@ -0,0 +1,3 @@
-- [patterns]
drop table if exists tj.patterns;

View File

@@ -0,0 +1,3 @@
-- [curves]
drop table if exists tj.curves;

View File

@@ -0,0 +1,9 @@
-- [controls]
drop table if exists tj.controls_3;
drop table if exists tj.controls_2;
drop table if exists tj.controls_1;
drop type if exists tj.controls_1_prep;

View File

@@ -0,0 +1,3 @@
-- [rules]
drop table if exists tj.rules;

View File

@@ -0,0 +1,9 @@
-- [energy]
drop table if exists tj.energy_demand_charge;
drop table if exists tj.energy_pump;
drop table if exists tj.energy_global;
drop type if exists tj.energy_param;

View File

@@ -0,0 +1,3 @@
-- [emitters]
drop table if exists tj.emitters;

View File

@@ -0,0 +1,3 @@
-- [quality]
drop table if exists tj.quality;

View File

@@ -0,0 +1,5 @@
-- [sources]
drop table if exists tj.sources;
drop type if exists tj.sources_type;

View File

@@ -0,0 +1,19 @@
-- [reactions]
drop table if exists tj.reactions_roughness_correlation;
drop table if exists tj.reactions_limiting_potential;
drop table if exists tj.reactions_tank;
drop table if exists tj.reactions_pipe;
drop type if exists tj.reactions_pipe_param;
drop table if exists tj.reactions_global;
drop type if exists tj.reactions_global_param;
drop table if exists tj.reactions_order;
drop type if exists tj.reactions_order_param;

View File

@@ -0,0 +1,3 @@
-- [junctions]
drop table if exists tj.junctions;

View File

@@ -0,0 +1,5 @@
-- [mixing]
drop table if exists tj.mixing;
drop type if exists tj.mixing_model;

View File

@@ -0,0 +1,3 @@
-- [times]
drop table if exists tj.times;

View File

@@ -0,0 +1,3 @@
-- [report]
drop table if exists tj.report;

View File

@@ -0,0 +1,3 @@
-- [options]
drop table if exists tj.options;

View File

@@ -0,0 +1,7 @@
-- [coordinates]
drop index if exists tj_coordinates_gist;
drop index if exists tj_coordinates_spgist;
drop table if exists tj.coordinates;

View File

@@ -0,0 +1,3 @@
-- [vertices]
drop table if exists tj.vertices;

View File

@@ -0,0 +1,3 @@
-- [labels]
drop table if exists tj.labels;

View File

@@ -0,0 +1,3 @@
-- [backdrop]
drop table if exists tj.backdrop;

View File

@@ -0,0 +1,3 @@
-- [reservoirs]
drop table if exists tj.reservoirs;

View File

@@ -0,0 +1,5 @@
-- [tanks]
drop table if exists tj.tanks;
drop type if exists tj.tanks_overflow;

View File

@@ -0,0 +1,5 @@
-- [pipes]
drop table if exists tj.pipes;
drop type if exists tj.pipes_status;

View File

@@ -0,0 +1,11 @@
-- [pumps]
drop table if exists tj.pumps_property_pattern;
drop table if exists tj.pumps_property_head;
drop table if exists tj.pumps_property_speed;
drop table if exists tj.pumps_property_power;
drop table if exists tj.pumps;

View File

@@ -0,0 +1,5 @@
-- [valves]
drop table if exists tj.valves;
drop type if exists tj.valves_type;

View File

@@ -0,0 +1,5 @@
-- [tags]
drop table if exists tj.tags_link;
drop table if exists tj.tags_node;

View File

@@ -0,0 +1,3 @@
-- [demands]
drop table if exists tj.demands;

View File

@@ -0,0 +1 @@
drop schema if exists tj;

View File

@@ -0,0 +1,7 @@
drop table if exists tj.transaction_operation;
drop table if exists tj.snapshot_operation;
drop table if exists tj.current_operation;
drop table if exists tj.operation;

108
script/script/template.py Normal file
View File

@@ -0,0 +1,108 @@
import psycopg as pg
sql_create = [
"table/create/namespace.sql",
"table/create/0.base.sql",
"table/create/1.title.sql",
"table/create/2.junctions.sql",
"table/create/3.reservoirs.sql",
"table/create/4.tanks.sql",
"table/create/5.pipes.sql",
"table/create/6.pumps.sql",
"table/create/7.valves.sql",
"table/create/8.tags.sql",
"table/create/9.demands.sql",
"table/create/10.status.sql",
"table/create/11.patterns.sql",
"table/create/12.curves.sql",
"table/create/13.controls.sql",
"table/create/14.rules.sql",
"table/create/15.energy.sql",
"table/create/16.emitters.sql",
"table/create/17.quality.sql",
"table/create/18.sources.sql",
"table/create/19.reactions.sql",
"table/create/20.mixing.sql",
"table/create/21.times.sql",
"table/create/22.report.sql",
"table/create/23.options.sql",
"table/create/24.coordinates.sql",
"table/create/25.vertices.sql",
"table/create/26.labels.sql",
"table/create/27.backdrop.sql",
"table/create/28.end.sql",
"table/create/operation.sql",
"api/create/operation.sql",
"api/create/1.title.sql"
]
sql_drop = [
"api/drop/1.title.sql",
"api/drop/operation.sql",
"table/drop/operation.sql",
"table/drop/28.end.sql",
"table/drop/27.backdrop.sql",
"table/drop/26.labels.sql",
"table/drop/25.vertices.sql",
"table/drop/24.coordinates.sql",
"table/drop/23.options.sql",
"table/drop/22.report.sql",
"table/drop/21.times.sql",
"table/drop/20.mixing.sql",
"table/drop/19.reactions.sql",
"table/drop/18.sources.sql",
"table/drop/17.quality.sql",
"table/drop/16.emitters.sql",
"table/drop/15.energy.sql",
"table/drop/14.rules.sql",
"table/drop/13.controls.sql",
"table/drop/12.curves.sql",
"table/drop/11.patterns.sql",
"table/drop/10.status.sql",
"table/drop/9.demands.sql",
"table/drop/8.tags.sql",
"table/drop/7.valves.sql",
"table/drop/6.pumps.sql",
"table/drop/5.pipes.sql",
"table/drop/4.tanks.sql",
"table/drop/3.reservoirs.sql",
"table/drop/2.junctions.sql",
"table/drop/1.title.sql",
"table/drop/0.base.sql",
"table/drop/namespace.sql"
]
def create_template():
with pg.connect(conninfo="dbname=postgres", autocommit=True) as conn:
with conn.cursor() as cur:
cur.execute("create database tj_project")
with pg.connect(conninfo="dbname=tj_project") as conn:
with conn.cursor() as cur:
for sql in sql_create:
with open(sql, "r") as f:
cur.execute(f.read())
print(f'executed {sql}')
conn.commit()
def have_template():
with pg.connect(conninfo="dbname=postgres", autocommit=True) as conn:
with conn.cursor() as cur:
cur.execute("select * from pg_database where datname = 'tj_project'")
return cur.rowcount > 0
def delete_template():
with pg.connect(conninfo="dbname=tj_project") as conn:
with conn.cursor() as cur:
for sql in sql_drop:
with open(sql, "r") as f:
cur.execute(f.read())
print(f'executed {sql}')
conn.commit()
with pg.connect(conninfo="dbname=postgres", autocommit=True) as conn:
with conn.cursor() as cur:
cur.execute("drop database tj_project")
if __name__ == "__main__":
if (have_template()):
delete_template()
create_template()

View File

@@ -4,3 +4,5 @@ CREATE TABLE TITLE
(
Value TEXT
);
INSERT INTO TITLE (Value) VALUES ('');

View File

@@ -3,7 +3,7 @@
CREATE TABLE COORDINATES
(
Node VARCHAR(32) PRIMARY KEY REFERENCES _NODE(ID)
, Coord POINT NOT NULL DEFAULT(POINT(0.0, 0.0))
, Coord POINT NOT NULL
);
CREATE INDEX COORDINATES_SPGIST ON COORDINATES USING SPGIST(Coord);

Some files were not shown because too many files have changed in this diff Show More