From 6c9481296b2aa8c48ccdc0719cc7f26e9cad624a Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby <pierre-antoine.rouby@inrae.fr> Date: Wed, 20 Sep 2023 15:21:46 +0200 Subject: [PATCH] doc: dev: Add SQL section with examples. --- doc/dev/documentation.org | 339 ++++++++++++++++++++++++++++---------- 1 file changed, 254 insertions(+), 85 deletions(-) diff --git a/doc/dev/documentation.org b/doc/dev/documentation.org index e245b959..56b45032 100644 --- a/doc/dev/documentation.org +++ b/doc/dev/documentation.org @@ -139,101 +139,270 @@ https://doc.qt.io/qt-5/model-view-programming.html ** TODO Model -- Model de donnée Python -- Correspond à une sauvegarde SQL +The model is a set of Python classes. In Pamhyr2, this classes must +respect some constraint. Each model class must inherits +=Model.Tools.SQLSubModel= abstract class, except the =Model.Study= +class who inherits =Model.Tools.SQLModel= (see [[SQL]]). + +The model entry point is the Study class. It contains infomation about +the study: their name, description, time system, and so on. Their +contains a River object too. This river object inherits the network +graph and contains a list of =RiverNode= and a list of =RiverReach= +(an edge who contains a source node, and destination node). +=RiverReach= contrains geometry, so, the river network (node and edge) +associated with the geometry forms the basis of the model, and the +other components are linked to one of these basic components. #+name: graph-model #+header: :results drawer #+header: :exports results -#+header: :post attr_wrap(width="16cm", data=*this*, name="graph-model", caption="Pamhyr2 model class dependencies", float="t") +#+header: :post attr_wrap(width="16cm", data=*this*, name="graph-model", caption="Pamhyr2 model class dependencies (A -> B means A can contain references to B)", float="t") #+begin_src dot :file "images/graph-model.png" :cache no digraph { - bgcolor="transparent"; - node[colorscheme=set19,shape=box,style="filled",fillcolor="2"]; - - study[label="Study"]; - river[label="River"]; - - subgraph cluster00 { - label="Network" - rnode[label="RiverNode"]; - redge[label="RiverReach"]; - } - - frictionlist[label="FrictionList"]; - - subgraph cluster01 { - label="Stricklers"; - stricklers[label="Stricklers"]; - stricklerslist[label="StricklersList"]; - } - subgraph cluster02 { - label="BoundaryCondition"; - boundaryconditionlist[label="BoundaryConditionList"]; - boundarycondition[label="BoundaryCondition"]; - } - subgraph cluster03 { - label="LateralContribution"; - lateralcontributionlist[label="LateralContributionList"]; - lateralcontribution[label="LateralContribution"]; - } - subgraph cluster04 { - label="InitialConditions"; - initialconditionsdict[label="InitialConditionsDict"]; - initialconditions[label="InitialConditions"]; - } - - solverparameterslist[label="SolverParametersList"]; - - subgraph cluster05 { - label="Sediment"; - sedimentlayerlist[label="SedimentLayerList"]; - sedimentlayer[label="SedimentLayer"]; - layer[label="Layer"]; - } - - subgraph cluster06 { - label="Greometry" - georeach[label="Reach"]; - geocrosssection[label="Cross-section"]; - geopoint[label="Point"]; - } + bgcolor="transparent"; + node[colorscheme=set19,shape=box,style="filled",fillcolor="2"]; + + subgraph cluster0 { + style=dashed; + study[label="Study"]; + river[label="River"]; + + subgraph cluster00 { + style=solid; + label="Network" + rnode[label="RiverNode"]; + redge[label="RiverReach"]; + } + + subgraph cluster06 { + style=solid; + label="Greometry" + georeach[label="Reach"]; + geocrosssection[label="Cross-section"]; + geopoint[label="Point"]; + } + } + + //subgraph cluster1 { + // style=dashed; + frictionlist[label="FrictionList"]; + + subgraph cluster01 { + style=solid; + label="Stricklers"; + stricklers[label="Stricklers"]; + stricklerslist[label="StricklersList"]; + } + subgraph cluster02 { + style=solid; + label="BoundaryCondition"; + boundaryconditionlist[label="BoundaryConditionList"]; + boundarycondition[label="BoundaryCondition"]; + } + subgraph cluster03 { + style=solid; + label="LateralContribution"; + lateralcontributionlist[label="LateralContributionList"]; + lateralcontribution[label="LateralContribution"]; + } + subgraph cluster04 { + style=solid; + label="InitialConditions"; + initialconditionsdict[label="InitialConditionsDict"]; + initialconditions[label="InitialConditions"]; + } + + solverparameterslist[label="SolverParametersList"]; + + subgraph cluster05 { + style=solid; + label="Sediment"; + sedimentlayerlist[label="SedimentLayerList"]; + sedimentlayer[label="SedimentLayer"]; + layer[label="Layer"]; + } + //} + + subgraph cluster2 { + style=dashed; + label="Results" + results[label="Results"] + rriver[label="River"]; + rreach[label="Reach"]; + rcrosssection[label="Cross-section"]; + } + + study -> river; + river -> rnode; + river -> redge; + redge -> rnode; + river -> boundaryconditionlist -> boundarycondition -> rnode; + river -> lateralcontributionlist -> lateralcontribution -> redge; + river -> initialconditionsdict -> initialconditions; + initialconditions -> redge; + river -> stricklerslist -> stricklers; + river -> solverparameterslist; + river -> sedimentlayerlist -> sedimentlayer -> layer; + redge -> frictionlist -> stricklers; + redge -> georeach -> geocrosssection -> geopoint; + geocrosssection -> sedimentlayer; + geopoint -> sedimentlayer; + + results -> study; + results -> rriver; + rriver -> river; + rriver -> rreach; + rreach -> georeach; + rreach -> rcrosssection; + rcrosssection -> geocrosssection; + } +#+end_src - subgraph cluster07 { - label="Results" - results[label="Results"] - rriver[label="River"]; - rreach[label="Reach"]; - rcrosssection[label="Cross-section"]; - } +*** TODO SQL + +The model must be export to a database file to create a study save +file. This file use SQLite3[fn:sqlite] format and the extention +=.pamhyr=. So, each model componante must be register into this study +file. To create, update, set and get information into SQLite database +we use SQL command. The database use version number and some +modification could be perform to update database. For each model +componante, correspond one or more SQL table to store information. To +normalize the interaction with database we made two classes, SQLModel +and SQLSubModel. The Study class use SQLModel because is the top of +the model hierachy. The rest of model class inherits to SQLSubModel. + +A class who inherits SQLSubModel, must implement some methods: +- =_sql_create=: Class method to create the database scheme +- =_sql_update=: Class method to update the database scheme if necessary +- =_sql_load=: Class method to load data from DB +- =_sql_save=: Method to save current object into DB + +Class method take in arguments: The class (=cls=), a function to +execute SQL command into the database (=execute=). In addition, the +update method take the previous version of database, load method take +an optional arguments =data= if additional infomation ar needed, and +who can contains whatever you want. The method save take in arguments +the current object (=self=), a function to execute SQL command into +the database (=execute=), and optional data (=data=). + +The class who inherits SQLSubModel can also define an class attribute +=_sub_classes= to set a formal class dependencies into database. This +attribute is use at database creation to create all table, and at +update to update all the database table. Let see examples of +SQLSubModel usage for two classes Foo and Bar with Foo contains list +of Bar (Listing [[sql-bar]] and [[sql-foo]]). + +#+NAME: sql-bar +#+CAPTION: Exemple of class Bar inherits SQLSubModel. +#+begin_src python :python python3 :results output :noweb yes + from Model.Tools.PamhyrDB import SQLSubModel + + class Bar(SQLSubModel): + _id_cnt = 0 + def __init__(self, id = -1, x = 0, y = 0): + self._x = x + self._y = y + if id == -1: + self.id = Bar._id_cnt + 1 + else: + self.id = id + Bar._id_cnt = max(id, Bar._id_cnt+1) + + @classmethod + def _sql_create(cls, execute): + execute(""" + CREATE TABLE bar ( + id INTEGER NOT NULL PRIMARY KEY, + x INTEGER NOT NULL, + foo_id INTEGER NOT NULL, + FOREIGN KEY(foo_id) REFERENCES foo(id), + )""") + return True + + @classmethod + def _sql_update(cls, execute, version): + # If version is lesser than 0.0.2, add column to bar table + major, minor, release = version.strip().split(".") + if major == minor == "0": + if int(release) < 2: + execute("ALTER TABLE bar ADD COLUMN y INTEGER") + return True + + @classmethod + def _sql_load(cls, execute, data = None): + new = [] + table = execute( + f"SELECT id, x, y FROM bar WHERE foo_id = {data['id']}" + ) + for row in table: + bar = cls( + id = row[0], x = row[1], y = row[2], + ) + new.append(bar) + return new + + def _sql_save(self, execute, data = None): + execute("INSERT INTO bar (id,x,y,foo_id) VALUES " + + f"({self.id}, {self._x}, {self._y}, {data['id']})") +#+end_src - study -> river; - river -> rnode; - river -> redge; - redge -> rnode; - river -> boundaryconditionlist -> boundarycondition -> rnode; - river -> lateralcontributionlist -> lateralcontribution -> redge; - river -> initialconditionsdict -> initialconditions; - initialconditions -> redge; - river -> stricklerslist -> stricklers; - river -> solverparameterslist; - river -> sedimentlayerlist -> sedimentlayer -> layer; - redge -> frictionlist -> stricklers; - redge -> georeach -> geocrosssection -> geopoint; - geocrosssection -> sedimentlayer; - geopoint -> sedimentlayer; - - results -> study[style="dashed"]; - results -> rriver; - rriver -> river[style="dashed"]; - rriver -> rreach; - rreach -> georeach[style="dashed"]; - rreach -> rcrosssection; - rcrosssection -> geocrosssection[style="dashed"]; - } +#+NAME: sql-foo +#+CAPTION: Exemple of class Foo inherits SQLSubModel. +#+begin_src python :python python3 :results output :noweb yes + class Foo(SQLSubModel): + _id_cnt = 0 + _sub_classes = [Bar] + + def __init__(self, id = -1, name = ""): + self._name = name + self._bar = [] + # ... + + @classmethod + def _sql_create(cls, execute): + execute(""" + CREATE TABLE foo ( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + ) + """) + return cls._create_submodel(execute) + + @classmethod + def _sql_update(cls, excute, version): + return cls._update_submodel(execute, version) + + @classmethod + def _sql_load(cls, execute, data = None): + new = [] + table = execute( + "SELECT id, name FROM foo" + ) + for row in table: + foo = cls( + id = row[0], + name = row[1], + ) + data = { + "id": row[0], # Current Foo ID + } + foo._bar = Bar._sql_load(execute, data=data) + new.append(foo) + return new + + def _sql_save(self, execute, data = None): + execute(f"DELETE FROM foo WHERE id = {self.id}") + execute(f"DELETE FROM bar WHERE foo_id = {self.id}") + # Save new data + execute(f"INSERT INTO bar (id,name) VALUES ({self.id}, {self._name})") + data = {"id": self.id} + for bar in self._bar: + bar._sql_save(execute, data=data) #+end_src -*** TODO SQL study file +[fn:sqlite] The SQLite web site: https://www.sqlite.org/index.html +(last access 2023-09-20) + *** TODO List class *** TODO Dict class ** TODO View -- GitLab