├── .gitignore
├── .travis.yml
├── Dockerfile
├── LICENSE
├── README.md
├── __init__.py
├── archiving.svg
├── branch.svg
├── checkout.svg
├── checkout_pg.svg
├── checkout_pg_local.svg
├── commit.svg
├── commit_msg.ui
├── docs
├── contributors.rst
├── functionality.rst
├── globals.rst
├── images
│ ├── CC-BY-SA-4-0.png
│ ├── PostGIS-logo.png
│ ├── archive_schemas.png
│ ├── archiving.png
│ ├── branch.png
│ ├── branch_group.png
│ ├── checkout.png
│ ├── checkout_pg.png
│ ├── checkout_pg_local.png
│ ├── commit.png
│ ├── commit_success.png
│ ├── commit_ui.png
│ ├── conflict_layer.png
│ ├── conflict_warning.png
│ ├── creating_mybranch.png
│ ├── diff_mode_symbology.png
│ ├── diff_mode_view.png
│ ├── eha.jpg
│ ├── empty_group.png
│ ├── full_mode_view.png
│ ├── groups_same_name.png
│ ├── help.png
│ ├── historization.png
│ ├── historize.png
│ ├── historize_warning.png
│ ├── initial_db.png
│ ├── late_by.png
│ ├── late_by_warning.png
│ ├── layers_not_same_db.png
│ ├── merge.png
│ ├── no_group_selected.png
│ ├── oslandia_logo.png
│ ├── pg_checkout.png
│ ├── pipes_table.png
│ ├── pipes_view.png
│ ├── revisions_table.png
│ ├── selected_features_local.png
│ ├── selected_features_warning.png
│ ├── sl_checkout.png
│ ├── spatialite-logo.png
│ ├── unversioned.png
│ ├── unversioned_menu.png
│ ├── update.png
│ ├── uptodate.png
│ ├── versioned.png
│ ├── versioned_branch_menu.png
│ ├── versioned_menu.png
│ ├── view.png
│ ├── view_dialog.png
│ ├── view_dialog_diff_mode.png
│ ├── working_copy.png
│ ├── working_copy_pg.png
│ └── working_copy_sl.png
├── index.rst
├── inner-workings.rst
├── introduction.rst
├── problem-handling.rst
├── requirements.rst
└── spatialfiltering.rst
├── help.svg
├── historize.svg
├── merge.svg
├── metadata.txt
├── package.py
├── plugin.py
├── revision_dialog.ui
├── test
├── __init__.py
├── abbreviation_bug_test.py
├── archiving_test.py
├── bug_in_branch_after_commit_test.py
├── composite_primary_key_db.sql
├── constraints_test.py
├── create_db_test.sh
├── epanet_test_db.sql
├── epanet_test_db_uuid.sql
├── history_creation_test.py
├── issue287_pg_dump.sql
├── issue287_test.py
├── issue287_wc.sqlite
├── issue357_test.py
├── issue358_test.py
├── issue437_test.py
├── issue437_test_db.sql
├── issue485_test.py
├── issue486_test.py
├── issue_type_test.py
├── merge_branch_test.py
├── multiple_geometry_test.py
├── partial_checkout_test.py
├── plugin_test.py
├── posgres_working_copy_bug_in_conflict_test.py
├── posgres_working_copy_bug_in_view_test.py
├── posgres_working_copy_test.py
├── postgres_distant_test.py
├── postgres_distant_uuid_test.py
├── run_tests.sh
├── tests.py
└── versioning_base_test.py
├── update.svg
├── versioningDB
├── __init__.py
├── constraints.py
├── postgresqlLocal.py
├── postgresqlServer.py
├── spatialite.py
├── sql
│ └── historize.sql
├── utils.py
├── versioning.py
└── versioningAbc.py
└── view.svg
/.gitignore:
--------------------------------------------------------------------------------
1 | # Extensions
2 | *.zip
3 | *.pyc
4 | .spyproject
5 |
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 |
3 | services:
4 | - docker
5 |
6 | before_install:
7 | - docker build . -t qgis-versioning-test
8 |
9 | script:
10 | - docker run qgis-versioning-test
11 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM debian:sid
2 |
3 | RUN apt-get update \
4 | && apt-get install -y \
5 | gdal-bin \
6 | libsqlite3-mod-spatialite \
7 | postgresql-11-postgis-2.5 \
8 | python3-psycopg2 \
9 | qgis \
10 | xvfb
11 |
12 | # to be able to connect locally
13 | RUN echo "host all all 127.0.0.1/32 trust" > /etc/postgresql/11/main/pg_hba.conf
14 |
15 | COPY . qgis-versioning
16 | WORKDIR qgis-versioning/test
17 |
18 | CMD ./run_tests.sh
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Versioning
2 | ==========
3 |
4 | Build and install the qgis plugin
5 | ---------------------------------
6 |
7 | cd
8 | git clone https://github.com/Oslandia/qgis-versioning.git
9 | cd qgis-versioning
10 | ./package.py # compresses all files into qgis_versioning.zip
11 | cd .qgis2/python/plugins/
12 | mkdir qgis-versioning
13 | cd qgis-versioning
14 | # unzip contents of directory *qgis_versioning* found in qgis_versioning.zip
15 |
16 | If you have admin acces to a local postgres/postis server, you can run the regression tests:
17 |
18 | export QGIS_PREFIX_PATH=/path/to/your/qgis/installation
19 | export PYTHONPATH=$QGIS_DIR/python:$PYTHONPATH
20 | python3 tests.py 127.0.0.1 postgres -v
21 |
22 | And if you want to run only one regression test:
23 |
24 | export QGIS_PREFIX_PATH=/path/to/your/qgis/installation
25 | export PYTHONPATH=$QGIS_DIR/python:..:$PYTHONPATH
26 | python3 plugin_test.py 127.0.0.1 postgres
27 |
28 |
29 | Use the plugin in qgis
30 | ----------------------
31 |
32 | Check that the plugin 'qgis-versioning' is activated in the plugin manager or install the versioning plugin directly in QGIS (Menu : Plugins = Manage plugins : Versioning).
33 |
34 | Load posgis layers from a scheme you want to version.
35 |
36 | Group postgis layers together. Select the group and click on the 'historize' button in the plugin toolbar (make sure the toolbar is displayed). The layers will be replaced by their view in the head revision
37 |
38 | Click on the group and then on the 'checkout' button. Choose a file to save your layers locally.
39 |
40 | Modify your layers.
41 |
42 | Click on the 'commit' icon.
43 |
44 | Documentation
45 | =======
46 |
47 | For more information on this plugin, you can go on its plugin documentation site: http://qgis-versioning.readthedocs.io/en/latest/. You can also contribute to the source code by sending pull request or open issues if you have any comments or bug to report.
48 |
49 | See also this article describing why the plugin has been built and how : [GIS Open Source versioning tool for a multi-user Distributed Environment](http://www.gogeomatics.ca/magazine/gis-open-source-versioning-tool-part-1.htm)
50 | Cet article est aussi disponible en français : http://www.gogeomatics.ca/magazine/outil-de-versionnement-a-code-source-ouvert-partie-1.htm
51 |
52 | Credits
53 | =======
54 |
55 | This plugin has been developed by Oslandia (http://www.oslandia.com).
56 |
57 | Oslandia provides support and assistance for QGIS and associated tools, including this plugin.
58 |
59 | This work has been funded by European funds.
60 | Thanks to the GIS Office of Apavil, Valcea County (Romania)
61 |
62 | This work has been also developed by eHealth Africa (http://ehealthafrica.org) for SpatiaLite 4.x support, filter selection for SpatiaLite file, diff mode and user identification improvements.
63 |
64 | License
65 | =======
66 |
67 | This work is free software and licenced under the GNU GPL version 2 or any later version.
68 | See LICENSE file.
69 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import os
4 | import sys
5 |
6 | # So we can use absolute path
7 | path_root = os.path.join(os.path.dirname(__file__))
8 | sys.path.append(path_root)
9 |
10 |
11 | def classFactory(iface):
12 | from .plugin import Plugin
13 | return Plugin(iface)
14 |
--------------------------------------------------------------------------------
/archiving.svg:
--------------------------------------------------------------------------------
1 |
2 |
122 |
--------------------------------------------------------------------------------
/checkout_pg.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
284 |
--------------------------------------------------------------------------------
/checkout_pg_local.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
286 |
--------------------------------------------------------------------------------
/commit_msg.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | CommitMsgDialog
4 |
5 |
6 |
7 | 0
8 | 0
9 | 388
10 | 334
11 |
12 |
13 |
14 | Commit versioned layers
15 |
16 |
17 |
18 |
19 | 20
20 | 280
21 | 341
22 | 32
23 |
24 |
25 |
26 | Qt::Horizontal
27 |
28 |
29 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok
30 |
31 |
32 |
33 |
34 |
35 | 40
36 | 60
37 | 321
38 | 211
39 |
40 |
41 |
42 |
43 |
44 |
45 | 40
46 | 37
47 | 121
48 | 18
49 |
50 |
51 |
52 | Commit message
53 |
54 |
55 |
56 |
57 | true
58 |
59 |
60 |
61 | 167
62 | 10
63 | 191
64 | 27
65 |
66 |
67 |
68 | false
69 |
70 |
71 | false
72 |
73 |
74 |
75 |
76 |
77 | false
78 |
79 |
80 | false
81 |
82 |
83 | -1
84 |
85 |
86 | 100
87 |
88 |
89 | 2147483643
90 |
91 |
92 | false
93 |
94 |
95 |
96 |
97 | true
98 |
99 |
100 |
101 | 40
102 | 14
103 | 91
104 | 20
105 |
106 |
107 |
108 | <html><head/><body><p>Select username of data editor</p></body></html>
109 |
110 |
111 | PG username
112 |
113 |
114 |
115 |
116 |
117 |
118 | buttonBox
119 | accepted()
120 | CommitMsgDialog
121 | accept()
122 |
123 |
124 | 248
125 | 254
126 |
127 |
128 | 157
129 | 274
130 |
131 |
132 |
133 |
134 | buttonBox
135 | rejected()
136 | CommitMsgDialog
137 | reject()
138 |
139 |
140 | 316
141 | 260
142 |
143 |
144 | 286
145 | 274
146 |
147 |
148 |
149 |
150 | pg_users_combobox
151 | currentIndexChanged(QString)
152 | CommitMsgDialog
153 | setFocus()
154 |
155 |
156 | 262
157 | 23
158 |
159 |
160 | 198
161 | 156
162 |
163 |
164 |
165 |
166 |
167 |
--------------------------------------------------------------------------------
/docs/contributors.rst:
--------------------------------------------------------------------------------
1 | .. include:: globals.rst
2 |
3 | --------------------
4 | Project contributors
5 | --------------------
6 |
7 | The following organizations have played a key role in the development of the |plugin|.
8 |
9 | SC Apavil Ramnicu Valcea
10 | ========================
11 | The GIS Office of `SC Apavil Ramnicu Valcea, Romania `_ initially funded the development of the plugin through a European development project.
12 |
13 | Oslandia |oslandia|_
14 | ====================
15 | Oslandia_ is the initial developer of the plugin and the official maintainer on `GitHub `_.
16 |
17 | eHealth Africa |eHA|_
18 | =====================
19 | eHealth Africa joined the development effort in Fall 2015 and has contributed several enhancements since.
20 |
21 | Other
22 | =====
23 |
24 | List of individual code `contributors`_.
25 |
--------------------------------------------------------------------------------
/docs/globals.rst:
--------------------------------------------------------------------------------
1 | .. |doctest| replace:: :mod:`doctest`
2 | .. |date| date:: %d-%m-%Y
3 | .. |time| date:: %H:%M
4 |
5 | .. |qg| replace:: :mod:`QGIS`
6 | .. _qg: http://qgis.org
7 |
8 | .. _contributors: https://github.com/Oslandia/qgis-versioning/graphs/contributors
9 |
10 | .. |plugin| replace:: :mod:`qgis-versioning plugin`
11 | .. _plugin: https://github.com/Oslandia/qgis-versioning
12 |
13 | .. _original page: http://www.oslandia.com/qgis-versioning-plugin-en.html
14 |
15 | .. |CC| image:: images/CC-BY-SA-4-0.png
16 | .. _CC: http://creativecommons.org/licenses/by-sa/4.0/
17 | .. |eHA| image:: images/eha.jpg
18 | .. _eHA: http://www.ehealthafrica.org/
19 | .. _oslandia: http://www.oslandia.com/
20 | .. |oslandia| image:: images/oslandia_logo.png
21 | .. |spatialite-logo| image:: images/spatialite-logo.png
22 | .. _spatialite-logo: https://www.gaia-gis.it/fossil/libspatialite/index
23 | .. |postgis-logo| image:: images/PostGIS-logo.png
24 | .. _postgis-logo: http://postgis.net/
25 | .. |selected_features_warning| image:: images/selected_features_warning.png
26 | .. |selected_features_local| image:: images/selected_features_local.png
27 |
28 | .. |archive_png| image:: images/archiving.png
29 | .. |archive_schemas_png| image:: images/archive_schemas.png
30 | .. |branch_png| image:: images/branch.png
31 | .. |merge_png| image:: images/merge.png
32 | .. |checkout_png| image:: images/checkout.png
33 | .. |checkout_pg_png| image:: images/checkout_pg.png
34 | .. |checkout_pg_local_png| image:: images/checkout_pg_local.png
35 | .. |commit_png| image:: images/commit.png
36 | .. |help_png| image:: images/help.png
37 | .. |historize_png| image:: images/historize.png
38 | .. |update_png| image:: images/update.png
39 | .. |view_png| image:: images/view.png
40 | .. |unversioned_menu_png| image:: images/unversioned_menu.png
41 | .. |historize_warning_png| image:: images/historize_warning.png
42 | .. |versioned_menu_png| image:: images/versioned_menu.png
43 | .. |versbranch_menu_png| image:: images/versioned_branch_menu.png
44 | .. |working_copy_sl_png| image:: images/working_copy_sl.png
45 | .. |working_copy_pg_png| image:: images/working_copy_pg.png
46 | .. |working_copy_png| image:: images/working_copy.png
47 | .. |no_group_selected_png| image:: images/no_group_selected.png
48 | .. |layers_not_same_db_png| image:: images/layers_not_same_db.png
49 | .. |groups_same_name_png| image:: images/groups_same_name.png
50 | .. |empty_group_png| image:: images/empty_group.png
51 | .. |view_dialog_png| image:: images/view_dialog.png
52 | .. |view_dialog_diff_mode_png| image:: images/view_dialog_diff_mode.png
53 | .. |diff_mode_symbology_png| image:: images/diff_mode_symbology.png
54 | .. |unversioned_png| image:: images/unversioned.png
55 | .. |versioned_png| image:: images/versioned.png
56 | .. |sl_checkout_png| image:: images/sl_checkout.png
57 | .. |pg_checkout_png| image:: images/pg_checkout.png
58 | .. |branch_group_png| image:: images/branch_group.png
59 | .. |full_mode_view_png| image:: images/full_mode_view.png
60 | .. |diff_mode_view_png| image:: images/diff_mode_view.png
61 | .. |initial_db_png| image:: images/initial_db.png
62 | .. |historization_png| image:: images/historization.png
63 | .. |creating_mybranch_png| image:: images/creating_mybranch.png
64 | .. |revisions_table_png| image:: images/revisions_table.png
65 | .. |pipes_view_png| image:: images/pipes_view.png
66 | .. |pipes_table_png| image:: images/pipes_table.png
67 | .. |late_by_png| image:: images/late_by.png
68 | .. |uptodate_png| image:: images/uptodate.png
69 | .. |late_by_warning_png| image:: images/late_by_warning.png
70 | .. |conflict_layer_png| image:: images/conflict_layer.png
71 | .. |conflict_warning_png| image:: images/conflict_warning.png
72 | .. |commit_ui_png| image:: images/commit_ui.png
73 | .. |commit_success_png| image:: images/commit_success.png
74 |
--------------------------------------------------------------------------------
/docs/images/CC-BY-SA-4-0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/CC-BY-SA-4-0.png
--------------------------------------------------------------------------------
/docs/images/PostGIS-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/PostGIS-logo.png
--------------------------------------------------------------------------------
/docs/images/archive_schemas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/archive_schemas.png
--------------------------------------------------------------------------------
/docs/images/archiving.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/archiving.png
--------------------------------------------------------------------------------
/docs/images/branch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/branch.png
--------------------------------------------------------------------------------
/docs/images/branch_group.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/branch_group.png
--------------------------------------------------------------------------------
/docs/images/checkout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/checkout.png
--------------------------------------------------------------------------------
/docs/images/checkout_pg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/checkout_pg.png
--------------------------------------------------------------------------------
/docs/images/checkout_pg_local.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/checkout_pg_local.png
--------------------------------------------------------------------------------
/docs/images/commit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/commit.png
--------------------------------------------------------------------------------
/docs/images/commit_success.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/commit_success.png
--------------------------------------------------------------------------------
/docs/images/commit_ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/commit_ui.png
--------------------------------------------------------------------------------
/docs/images/conflict_layer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/conflict_layer.png
--------------------------------------------------------------------------------
/docs/images/conflict_warning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/conflict_warning.png
--------------------------------------------------------------------------------
/docs/images/creating_mybranch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/creating_mybranch.png
--------------------------------------------------------------------------------
/docs/images/diff_mode_symbology.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/diff_mode_symbology.png
--------------------------------------------------------------------------------
/docs/images/diff_mode_view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/diff_mode_view.png
--------------------------------------------------------------------------------
/docs/images/eha.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/eha.jpg
--------------------------------------------------------------------------------
/docs/images/empty_group.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/empty_group.png
--------------------------------------------------------------------------------
/docs/images/full_mode_view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/full_mode_view.png
--------------------------------------------------------------------------------
/docs/images/groups_same_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/groups_same_name.png
--------------------------------------------------------------------------------
/docs/images/help.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/help.png
--------------------------------------------------------------------------------
/docs/images/historization.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/historization.png
--------------------------------------------------------------------------------
/docs/images/historize.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/historize.png
--------------------------------------------------------------------------------
/docs/images/historize_warning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/historize_warning.png
--------------------------------------------------------------------------------
/docs/images/initial_db.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/initial_db.png
--------------------------------------------------------------------------------
/docs/images/late_by.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/late_by.png
--------------------------------------------------------------------------------
/docs/images/late_by_warning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/late_by_warning.png
--------------------------------------------------------------------------------
/docs/images/layers_not_same_db.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/layers_not_same_db.png
--------------------------------------------------------------------------------
/docs/images/merge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/merge.png
--------------------------------------------------------------------------------
/docs/images/no_group_selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/no_group_selected.png
--------------------------------------------------------------------------------
/docs/images/oslandia_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/oslandia_logo.png
--------------------------------------------------------------------------------
/docs/images/pg_checkout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/pg_checkout.png
--------------------------------------------------------------------------------
/docs/images/pipes_table.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/pipes_table.png
--------------------------------------------------------------------------------
/docs/images/pipes_view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/pipes_view.png
--------------------------------------------------------------------------------
/docs/images/revisions_table.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/revisions_table.png
--------------------------------------------------------------------------------
/docs/images/selected_features_local.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/selected_features_local.png
--------------------------------------------------------------------------------
/docs/images/selected_features_warning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/selected_features_warning.png
--------------------------------------------------------------------------------
/docs/images/sl_checkout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/sl_checkout.png
--------------------------------------------------------------------------------
/docs/images/spatialite-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/spatialite-logo.png
--------------------------------------------------------------------------------
/docs/images/unversioned.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/unversioned.png
--------------------------------------------------------------------------------
/docs/images/unversioned_menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/unversioned_menu.png
--------------------------------------------------------------------------------
/docs/images/update.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/update.png
--------------------------------------------------------------------------------
/docs/images/uptodate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/uptodate.png
--------------------------------------------------------------------------------
/docs/images/versioned.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/versioned.png
--------------------------------------------------------------------------------
/docs/images/versioned_branch_menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/versioned_branch_menu.png
--------------------------------------------------------------------------------
/docs/images/versioned_menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/versioned_menu.png
--------------------------------------------------------------------------------
/docs/images/view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/view.png
--------------------------------------------------------------------------------
/docs/images/view_dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/view_dialog.png
--------------------------------------------------------------------------------
/docs/images/view_dialog_diff_mode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/view_dialog_diff_mode.png
--------------------------------------------------------------------------------
/docs/images/working_copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/working_copy.png
--------------------------------------------------------------------------------
/docs/images/working_copy_pg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/working_copy_pg.png
--------------------------------------------------------------------------------
/docs/images/working_copy_sl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/docs/images/working_copy_sl.png
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. include:: globals.rst
2 |
3 | .. meta::
4 | :DC.creator: eHealth Africa
5 | :DC.language: en
6 |
7 | -----------------------
8 | QGIS Versioning plugin
9 | -----------------------
10 |
11 | .. |postgis-logo|_
12 |
13 | .. |spatialite-logo|_
14 |
15 | This is the temporary location of the |plugin|_ documentation. Click this link for the `original page`_.
16 | See also this article describing why the plugin has been built and how: `GIS Open Source versioning tool for a multi-user Distributed Environment `_. Cet article est aussi disponible `en français`_.
17 |
18 | ----------
19 |
20 | :Date: Last generated on |date| at |time|
21 | .. |CC|_
22 |
23 | ----------
24 |
25 | .. toctree::
26 | :maxdepth: 2
27 |
28 | introduction
29 | requirements
30 | functionality
31 | spatialfiltering
32 | problem-handling
33 | inner-workings
34 | contributors
35 |
--------------------------------------------------------------------------------
/docs/inner-workings.rst:
--------------------------------------------------------------------------------
1 | .. include:: globals.rst
2 |
3 | --------------------
4 | Inner workings
5 | --------------------
6 |
7 | This section is intended to explain more technical aspects of the versioning implemented by the |plugin|.
8 |
9 | Central database
10 | ================
11 |
12 | Once a database is versioned, three operations can be performed on table rows: INSERT, DELETE and UPDATE. To be able to track history, every row is kept in the tables. Deleted rows are marked as such and updated rows are a combined insertion-deletion where the deleted and added rows are linked to one another as parent and child.
13 |
14 | A total of five columns are needed for versioning the first branch:
15 |
16 | **PRIMARY KEY**
17 | a unique identifier across the table
18 | **branch_rev_begin**
19 | revision when this record was inserted
20 | **branch_rev_end**
21 | last revision for which this record exists (i.e. revision when it was deleted minus one)
22 | **branch_parent**
23 | in case the row has been inserted as a result of an update, this field stores the id of the row that has been updated
24 | **branch_child**
25 | in case the row has been marked as deleted as a result of an update, this field stores the id of the row that has been inserted in its place.
26 |
27 | For each additional branch, four additional columns are needed (the ones with the prefix 'branch\_').
28 |
29 | .. note::
30 | A null value for *branch_rev_begin* means that a row (feature) does not belong to that branch.
31 |
32 | SQL views are used to see a snapshot of the database for a given revision number. Noting 'rev' the revision we want to see, the condition for a row to be present in the view is:
33 |
34 | (*branch_rev_end* IS NULL OR *branch_rev_end* >= rev) AND *branch_rev_begin* <= rev
35 |
36 | In the special case of the latest revision, or head revision, the condition reads:
37 |
38 | *branch_rev_end* IS NULL AND *branch_rev_begin* IS NOT NULL
39 |
40 | .. note::
41 | Since elements are not deleted (but merely marked as such) in an historized table, care must be taken with the definition of constraints, in particular the conceptual unicity of a field values.
42 |
43 | Views for revisions must be read-only and historized tables should **never** be edited directly. This is a basic principle for version control : editions must be made to working copies an then committed to the database. Please note that by default PostGIS 9.3 creates updatable views.
44 |
45 |
46 | Working copy database
47 | =====================
48 |
49 | For each versioned table in the working copy, a view is created with the suffix _view (e.g. mytable_view). Those views typically filter out the historization columns and show the head revision. A set of triggers is defined to allow operating on those views (DELETE, UPDATE and INSERT).
50 |
51 | The DELETE trigger simply marks the end revision of a given record.
52 |
53 | The INSERT trigger creates a new record and fills the *branch_rev_begin* field.
54 |
55 | The UPDATE trigger creates a new record and fills the *branch_rev_begin* and *branch_parent* fields. It then marks the parent record as deleted, and fills the *branch_rev_end* and *branch_child* fields accordingly.
56 |
--------------------------------------------------------------------------------
/docs/introduction.rst:
--------------------------------------------------------------------------------
1 | .. include:: globals.rst
2 |
3 | -------------
4 | Introduction
5 | -------------
6 |
7 | Summary
8 | =======
9 |
10 | The |plugin| provides versioning of geographical features stored in PostGIS databases. A typical use case met by the plugin involves one or more users checking out a local working copy to edit features in (potentially) offline mode using SpatiaLite and committing changes back to the PostGIS server.
11 |
12 | More information can be found in the plugin's `original page`_ page.
13 |
14 | .. note::
15 | Most of the screenshots contained in this document were made on an Ubuntu workstation. A few ones were made on a Windows workstation.
16 |
--------------------------------------------------------------------------------
/docs/problem-handling.rst:
--------------------------------------------------------------------------------
1 | .. include:: globals.rst
2 |
3 | --------------------
4 | When problems occur
5 | --------------------
6 |
7 | Multiple modifications to the same dataset amongst a number of users inevitably bring about conflicts. This section shows how editing conflicts are managed by the plugin. Specific errors or exceptions are also mentioned.
8 |
9 | .. _conflict-resolution:
10 |
11 | Conflict management
12 | ===================
13 |
14 | For users to be able to commit modifications to the database, their working copy must be up to date with the database. Take the example of two users checking out SL working copies from HEAD revision X. Each on their workstations, they are editing features in their working copy. One of the users will inevitably commit his(her) changes to the database first. This will create another entry in the *revisions* table, that is increment the HEAD revision to X + 1.
15 |
16 | When the second user tries to commit, a message will warn that the working copy needs to be updated :
17 |
18 | |late_by_warning_png|
19 |
20 | .. note::
21 | It is wise to always update a working copy before trying to commit. Updating directly by clicking |update_png| will check if the working copy is up to date. If it is, a message will let you know : |uptodate_png|
22 |
23 | Else, the working copy is updated to the latest revision, for example |late_by_png|
24 |
25 | If edits made by the first committer do not conflict with the second user's working copy, updating will proceed normally. Changes made by the other user will be merged into the second user's working copy. All is well and the second user can now commit his(her) changes.
26 |
27 | In the event there are conflicting edits, the second user will be presented with this message after updating :
28 |
29 | |conflict_warning_png|
30 |
31 | A new layer is created by the plugin for every dataset that contain errors and it is displayed in the working copy layer group. The name of that layer is made up of the original layer name to which the "_conflicts" string is appended :
32 |
33 | |conflict_layer_png|
34 |
35 | The figure above shows two conflicting features in one layer. Highlighted is the edit the second user is trying to commit. The attributes table shows the id of the conflicting feature. The conflict is resolved by deleting the unwanted entry in the conflict layer, either 'mine' or 'theirs' and saving the edits. On deletion of 'mine', the working copy edition is discarded; on deletion of 'theirs' the working copy edition is appended to the feature history (i.e. the working copy feature becomes a child of the last state of the feature in the historized database).
36 |
37 | Once the conflict table is empty, committing can proceed.
38 |
39 | .. note::
40 | On deletion of one conflict entry, both entries are removed (by a trigger) but the attribute table (and map canvas) are not refreshed. As a workaround, the user can close and re-open the attribute table to see the actual state of the conflict table.
41 |
42 | Since deleting 'mine' implies discarding one's own change, then committing will result in no change being committed.
43 |
44 | A useful tip if you deleted the wrong one (e.g. 'theirs' and you meant to delete 'mine') **and** did not save yet : CTRL-Z (undo) to the rescue.
45 |
46 | In the more general case, multiple editions can be made to the same feature. Therefore child relations must be followed to the last one in order to present the user with the latest state of a given conflicting feature.
47 |
48 | .. warning::
49 | Known bug : Updating a working copy may indicate it is up to date when in fact it is not. This may happen if for example the checkout (working copy creation) was done on trunk and then another branch was created afterwards. The working copy gets "stuck" at the latest revision of the branch it was checked out from. The only way to get around this is to checkout a fresh working copy from the desired branch. Edits made in the other working copy are still there and need to be integrated manually in the most recent working copy.
50 |
51 | Tip : clicking on the view icon |view_png| will tell you what the latest revision number is.
52 |
53 | Errors and exceptions
54 | =====================
55 |
56 | Although errors are generally managed within the plugin, specific circumstances may trigger errors or Pyhton exceptions. This section shows some of those errors and how they can be avoided or recovered from.
57 |
--------------------------------------------------------------------------------
/docs/requirements.rst:
--------------------------------------------------------------------------------
1 | .. include:: globals.rst
2 |
3 | ------------
4 | Requirements
5 | ------------
6 |
7 | This section is a list of both hard and soft requirements (aka suggestions).
8 |
9 | Hard requirements
10 | =================
11 | Requirements considered 'hard' are those you cannot really live without. The plugin may work but you may experience problems to make it work. The two basic hard requirements are about |qg| and PostgreSQL/PostGIS. Another pertains to naming conventions.
12 |
13 | In a nutshell, those hard requirements are :
14 |
15 | - QGIS 2.8+
16 | - PG 9.x (ideally 9.2+)
17 | - names asked by he plugin must begin with either a "_" (underscore) or a letter followed by any lowercase non accented letter, digit or underscore without spaces and up to 63 characters long
18 |
19 | QGIS
20 | +++++
21 |
22 | Recent versions of the plugin were tested with |qg| 2.8. It is worth mentioning that plugin versions < 0.2 were developed for older versions of |qg|, which may incidentally ship with older versions of spatialite/SQlite. As of version 0.2 (Aug 2015), `spatialite `_ version 4.x is supported by the plugin. This in turn makes any |qg| versions that come with older spatialite versions unusable with the plugin (for spatialite checkouts at least).
23 |
24 | Another key dependency of the plugin is `ogr2ogr `_. Although the plugin does not depend on the most recent features of ogr2ogr, it is wise to stick to the version bundled in |qg| 2.8+ (2.8+ because newer versions of ogr2ogr shipped in newer versions of |qg| should be backwards compatible).
25 |
26 | PostgreSQL/PostGIS
27 | ++++++++++++++++++
28 |
29 | The plugin should work on any minor revision of the PostgreSQL 9.x series. It was tested successfully on PostgreSQL 9.2 and 9.4.
30 |
31 | There are no hard requirements on PostGIS as such on the part of the plugin. Packing the most recent version supported in your PostgreSQL installation should be sufficient.
32 |
33 | Naming conventions
34 | ++++++++++++++++++
35 |
36 | Operation of the plugin is best ensured by sticking to the PostgreSQL naming rules. As suggested `here `_ :
37 |
38 | .. pull-quote::
39 | PostgreSQL uses a single data type to define all object names: the name type. A value of type name is a string of 63 or fewer characters. A name must start with a letter or an underscore; the rest of the string can contain letters, digits, and underscores.
40 |
41 | .. warning::
42 | Do NOT use empty spaces in any identifier the plugin asks you to supply.
43 |
44 | Do NOT use accented characters (e.g. German umlaut or French "accent aigu")
45 |
46 | For optimal operation, avoid using spaces in the full path name of files intended to be used by the plugin, for example spatialite files.
47 |
48 | Even though PostgreSQL object names can contain capital letters, the plugin does not currently support object names other than in lowercase letters (plus digits and underscores as mentioned above). Even though the plugin ensures some level of protection in that respect, it is best to stick to those conventions when naming a new PG checkout (see later for an explanation), a branch or any other name the plugin asks you to provide.
49 |
50 | The same applies to spatialite filenames (SL checkout).
51 |
52 | Soft requirements
53 | =================
54 | Soft requirements are more like "best practice" suggestions. As the saying goes : Your Mileage May Vary.
55 |
56 | Separate schema
57 | +++++++++++++++
58 |
59 | As will be explained in more detail later in this document, the |plugin| operates "historization" by adding columns to each table in a particular database together with a *revisions* table that holds all revision information. For a number of reasons, it is wise to isolate your geographic data in a schema **other** than the *public* schema.
60 |
61 | As mentioned `here `_ :
62 |
63 | .. pull-quote::
64 | "... store no data in the 'public' schema."
65 |
66 | The specific context of the previous quote pertains to backup and restore procedures but the advice also applies for the |plugin|.
67 |
--------------------------------------------------------------------------------
/docs/spatialfiltering.rst:
--------------------------------------------------------------------------------
1 | .. include:: globals.rst
2 |
3 | ----------------------------------
4 | Selecting features before checkout
5 | ----------------------------------
6 |
7 | Before checking out a local spatialite working copy, one can select features from the versioned tables to work on locally. Any layer in the group can have features selected. In the case of a layer with no features selected, the whole dataset will be checked out by the |plugin|. This allows users to select only those features they are interested in editing rather than the whole dataset.
8 |
9 | .. note::
10 | Feature selection prior to checkout only applies to spatialite checkouts. It has yet to be implemented for PG checkouts.
11 |
12 | Procedure
13 | =========
14 |
15 | - For each layer in the group, select features you want checked out. This can be done in a number of ways in |qg|.
16 | - When ready to checkout, click on the layer group and click on the spatialite checkout button (|checkout_png|). At that point any layer with selected features will pop this warning to let the user know a subset of features will be checked out :
17 |
18 | |selected_features_warning|
19 |
20 | - Complete the rest of the default spatialite checkout workflow and check that only a subset of features was retrieved for the layers you selected features for.
21 |
22 | In our example, only the selected polygons (yellow above) and all points (since no feature selection was performed on the point layer) were checked out :
23 |
24 | |selected_features_local|
25 |
--------------------------------------------------------------------------------
/metadata.txt:
--------------------------------------------------------------------------------
1 | # This file contains metadata for your plugin.
2 |
3 | # Mandatory items:
4 |
5 | [general]
6 | name=versioning
7 | qgisMinimumVersion=3.4
8 | description=postgis database versioning
9 | version=1.0
10 | author=Oslandia
11 | email=infos@oslandia.com
12 | about=A tool to manage data history, branches, and to work offline with your PostGIS-stored data and QGIS.
13 |
14 | homepage=https://github.com/Oslandia/qgis-versioning
15 | tracker=https://github.com/Oslandia/qgis-versioning/issues
16 | repository=https://github.com/Oslandia/qgis-versioning.git
17 | icon=historize.svg
18 |
19 | category=Database
20 |
21 |
--------------------------------------------------------------------------------
/package.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # coding=UTF-8
3 | """
4 | packaging script for the qgis_versioning project
5 |
6 | USAGE
7 | python -m qgispackage.py [-h, -i, -u] [directory],
8 |
9 | OPTIONS
10 | -h, --help
11 | print this help
12 |
13 | -i, --install [directory]
14 | install the package in the .qgis2 directory, if directory is ommited,
15 | install in the QGis plugin directory
16 |
17 | -u, --uninstall
18 | uninstall (remove) the package from .qgis2 directory
19 | """
20 |
21 | import os
22 | import zipfile
23 | import re
24 | import shutil
25 |
26 | # @todo make that work on windows
27 | qgis_plugin_dir = os.path.join(os.path.expanduser('~'), ".qgis2", "python", "plugins")
28 |
29 | def uninstall(install_dir):
30 | target_dir = os.path.join(install_dir, "qgis_versioning")
31 | if os.path.isdir(target_dir):
32 | shutil.rmtree(target_dir)
33 |
34 | def install(install_dir, zip_filename):
35 | uninstall(install_dir)
36 | with zipfile.ZipFile(zip_filename, "r") as z:
37 | z.extractall(install_dir)
38 | print("installed in", install_dir)
39 |
40 | def zip_(zip_filename):
41 | """the zip file include tests"""
42 | qgis_versioning_dir = os.path.abspath(os.path.dirname(__file__))
43 | with zipfile.ZipFile(zip_filename, 'w') as package:
44 | for root, dirs, files in os.walk(qgis_versioning_dir):
45 | if not re.match(r".*(test_data|doc|tmp).*", root):
46 | for file_ in files:
47 | if re.match(r".*\.(py|txt|ui|svg|png|insat|sat|qml|sql|sqlite)$", file_) \
48 | and not re.match(r"(package.py)", file_):
49 | fake_root = root.replace(qgis_versioning_dir, "qgis_versioning")
50 | package.write(os.path.join(root, file_),
51 | os.path.join(fake_root, file_))
52 |
53 |
54 | if __name__ == "__main__":
55 | import getopt
56 | import sys
57 |
58 | try:
59 | optlist, args = getopt.getopt(sys.argv[1:],
60 | "hiu",
61 | ["help", "install", "uninstall"])
62 | except Exception as e:
63 | sys.stderr.write(str(e)+"\n")
64 | exit(1)
65 |
66 | optlist = dict(optlist)
67 |
68 | if "-h" in optlist or "--help" in optlist:
69 | help(sys.modules[__name__])
70 | exit(0)
71 |
72 | zip_filename = os.path.join(os.path.dirname(__file__), "qgis_versioning.zip")
73 | zip_(zip_filename)
74 | install_dir = qgis_plugin_dir if len(args)==0 else args[0]
75 |
76 | if "-u" in optlist or "--uninstall" in optlist:
77 | uninstall(install_dir)
78 |
79 | if "-i" in optlist or "--install" in optlist:
80 | install(install_dir, zip_filename)
81 |
82 |
--------------------------------------------------------------------------------
/revision_dialog.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | view_dlg
4 |
5 |
6 |
7 | 0
8 | 0
9 | 751
10 | 418
11 |
12 |
13 |
14 |
15 | 0
16 | 0
17 |
18 |
19 |
20 | Revisions selection
21 |
22 |
23 | false
24 |
25 |
26 | -
27 |
28 |
29 | false
30 |
31 |
32 | Check to go to diff mode for any two revisions
33 |
34 |
35 | Compare selected revisions
36 |
37 |
38 |
39 | -
40 |
41 |
42 | Select one [many] for single [multiple] revisions. Fetching may take time.
43 |
44 |
45 |
46 | -
47 |
48 |
-
49 |
50 |
51 | true
52 |
53 |
54 | true
55 |
56 |
57 | false
58 |
59 |
60 | false
61 |
62 |
63 | false
64 |
65 |
66 |
67 |
68 |
69 | -
70 |
71 |
72 | Qt::Horizontal
73 |
74 |
75 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | buttonBox
85 | accepted()
86 | view_dlg
87 | accept()
88 |
89 |
90 | 248
91 | 254
92 |
93 |
94 | 157
95 | 274
96 |
97 |
98 |
99 |
100 | buttonBox
101 | rejected()
102 | view_dlg
103 | reject()
104 |
105 |
106 | 316
107 | 260
108 |
109 |
110 | 286
111 | 274
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/test/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/test/__init__.py
--------------------------------------------------------------------------------
/test/abbreviation_bug_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | from versioningDB import versioning
5 | from sqlite3 import dbapi2
6 | import psycopg2
7 | import os
8 | import tempfile
9 |
10 | longname = '_this_is_a_very_long_name_that should_be_trunctated_if_buggy'
11 | another_longname = ('this_is_another_edited_very_long_name_that '
12 | 'should_be_trunctated_if_buggy')
13 | new_longname = 'newly inserted with long name'
14 |
15 |
16 | def test(host, pguser):
17 | pg_conn_info = "dbname=epanet_test_db host=" + host + " user=" + pguser
18 | tmp_dir = tempfile.gettempdir()
19 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
20 |
21 | sqlite_test_filename = tmp_dir+"/abbreviation_test.sqlite"
22 | if os.path.isfile(sqlite_test_filename):
23 | os.remove(sqlite_test_filename)
24 |
25 | spversioning = versioning.spatialite(sqlite_test_filename, pg_conn_info)
26 | # create the test database
27 | os.system("dropdb --if-exists -h " + host + " -U "+pguser
28 | + " epanet_test_db")
29 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_db")
30 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -f "
31 | + test_data_dir + "/epanet_test_db.sql")
32 |
33 | # delete existing data
34 | pcon = psycopg2.connect(pg_conn_info)
35 | pcur = pcon.cursor()
36 | for i in range(10):
37 | pcur.execute("""
38 | INSERT INTO epanet.junctions
39 | (demand_pattern_id, elevation, geom)
40 | VALUES
41 | ('{demand_pattern_id}', {elev},
42 | ST_GeometryFromText('POINT({x} {y})',2154));
43 | """.format(
44 | demand_pattern_id=str(i+2)
45 | + longname,
46 | elev=float(i),
47 | x=float(i+1),
48 | y=float(i+1)
49 | ))
50 | pcon.commit()
51 | versioning.historize(pg_conn_info, 'epanet')
52 |
53 | spversioning.checkout(["epanet_trunk_rev_head.junctions",
54 | "epanet_trunk_rev_head.pipes"])
55 | assert(os.path.isfile(sqlite_test_filename)
56 | and "sqlite file must exist at this point")
57 |
58 | scon = dbapi2.connect(sqlite_test_filename)
59 | scon.enable_load_extension(True)
60 | scon.execute("SELECT load_extension('mod_spatialite')")
61 | scur = scon.cursor()
62 | scur.execute("SELECT id, demand_pattern_id from junctions")
63 |
64 | for rec in scur:
65 | if rec[0] > 2:
66 | assert rec[1].find(longname) != -1
67 |
68 | scur.execute(f"""
69 | update junctions_view
70 | set demand_pattern_id='{another_longname}' where ogc_fid > 8""")
71 |
72 | scur.execute(f"""
73 | insert into junctions_view(id, demand_pattern_id, elevation, geom)
74 | select 13, '{new_longname}', elevation, geom
75 | from junctions_view where ogc_fid=4""")
76 | scon.commit()
77 |
78 | spversioning.commit('a commit msg')
79 |
80 | pcur.execute("""select versioning_id, demand_pattern_id
81 | from epanet_trunk_rev_head.junctions""")
82 | for row in pcur:
83 | print(row)
84 | if row[0] > 8:
85 | assert row[1].find(another_longname) != -1\
86 | or row[1].find(new_longname) != -1
87 |
88 |
89 | if __name__ == "__main__":
90 | if len(sys.argv) != 3:
91 | print("Usage: python3 versioning_base_test.py host pguser")
92 | else:
93 | test(*sys.argv[1:])
94 |
--------------------------------------------------------------------------------
/test/archiving_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import sys
5 | from versioningDB import versioning
6 | import psycopg2
7 | import os
8 |
9 |
10 | def printTab(pcur, schema, table):
11 | pk = 'versioning_id'
12 | try:
13 | pk = versioning.pg_pk(pcur, schema, table)
14 | except:
15 | pass
16 |
17 | print("\n**********************************")
18 | print(schema+"."+table)
19 | pcur.execute("""SELECT column_name FROM information_schema.columns WHERE
20 | table_schema = '{schema}' AND table_name = '{table}'""".format(schema=schema,
21 | table=table))
22 | cols = ",".join([i[0] for i in pcur.fetchall()])
23 | print(cols)
24 |
25 | pcur.execute("""SELECT * FROM {schema}.{table} ORDER BY {pk}""".format(schema=schema, table=table, pk=pk))
26 |
27 | rows = pcur.fetchall()
28 | for row in rows:
29 | r = ', '.join([str(l) for l in list(row)])
30 | print(r)
31 | print("**********************************\n")
32 |
33 | def test(host, pguser):
34 | pg_conn_info = "dbname=epanet_test_db host=" + host + " user=" + pguser
35 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
36 |
37 | # create the test database
38 |
39 | os.system("dropdb --if-exists -h " + host + " -U "+pguser+" epanet_test_db")
40 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_db")
41 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -c 'CREATE EXTENSION postgis'")
42 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -f "+test_data_dir+"/epanet_test_db.sql")
43 | versioning.historize("dbname=epanet_test_db host={} user={}".format(host,pguser), "epanet")
44 |
45 | # chechout
46 | #tables = ['epanet_trunk_rev_head.junctions','epanet_trunk_rev_head.pipes']
47 | tables = ['epanet_trunk_rev_head.junctions', 'epanet_trunk_rev_head.pipes']
48 | pgversioning = versioning.pgServer(pg_conn_info, 'epanet_working_copy')
49 | pgversioning.checkout(tables)
50 |
51 | pcur = versioning.Db(psycopg2.connect(pg_conn_info))
52 |
53 |
54 | pcur.execute("INSERT INTO epanet_working_copy.pipes_view(id, start_node, end_node, geom) VALUES ('2','1','2',ST_GeometryFromText('LINESTRING(1 1,0 1)',2154))")
55 | pcur.commit()
56 | pgversioning.commit("rev 1")
57 | pcur.execute("INSERT INTO epanet_working_copy.pipes_view(id, start_node, end_node, geom) VALUES ('3','1','2',ST_GeometryFromText('LINESTRING(1 -1,0 1)',2154))")
58 | pcur.commit()
59 | pgversioning.commit("rev 2")
60 | pcur.execute("INSERT INTO epanet_working_copy.pipes_view(id, start_node, end_node, geom) VALUES ('4','1','2',ST_GeometryFromText('LINESTRING(1 -1,0 1)',2154))")
61 | pcur.commit()
62 | pgversioning.commit("rev 3")
63 | pcur.execute("INSERT INTO epanet_working_copy.pipes_view(id, start_node, end_node, geom) VALUES ('5','1','2',ST_GeometryFromText('LINESTRING(1 -1,0 1)',2154))")
64 | pcur.commit()
65 | pgversioning.commit("rev 4")
66 | pcur.execute("DELETE FROM epanet_working_copy.pipes_view S WHERE versioning_id = 5")
67 | pcur.commit()
68 | pgversioning.commit("rev 5")
69 | pcur.execute("INSERT INTO epanet_working_copy.pipes_view(id, start_node, end_node, geom) VALUES ('6','1','2',ST_GeometryFromText('LINESTRING(1 -1,0 1)',2154))")
70 | pcur.commit()
71 | pgversioning.commit("rev 6")
72 | pcur.execute("UPDATE epanet_working_copy.pipes_view SET length = 4 WHERE versioning_id = 3")
73 | pcur.commit()
74 | pgversioning.commit("rev 7")
75 | pcur.execute("UPDATE epanet_working_copy.pipes_view SET length = 4 WHERE versioning_id = 1")
76 | pcur.commit()
77 | pgversioning.commit("rev 8")
78 | pcur.execute("INSERT INTO epanet_working_copy.pipes_view(id, start_node, end_node, geom) VALUES ('7','1','2',ST_GeometryFromText('LINESTRING(1 -1,0 1)',2154))")
79 | pcur.commit()
80 | pgversioning.commit("rev 9")
81 | pcur.execute("INSERT INTO epanet_working_copy.pipes_view(id, start_node, end_node, geom) VALUES ('8','1','2',ST_GeometryFromText('LINESTRING(1 -1,0 1)',2154))")
82 | pcur.commit()
83 | pgversioning.commit("rev 10")
84 | pcur.execute("DELETE FROM epanet_working_copy.pipes_view S WHERE versioning_id = 7")
85 | pcur.commit()
86 | pgversioning.commit("rev 11")
87 | pcur.execute("INSERT INTO epanet_working_copy.pipes_view(id, start_node, end_node, geom) VALUES ('9','1','2',ST_GeometryFromText('LINESTRING(1 -1,0 1)',2154))")
88 | pcur.commit()
89 | pgversioning.commit("rev 12")
90 |
91 | pcur.execute("SELECT * FROM epanet.pipes ORDER BY versioning_id")
92 | end = pcur.fetchall()
93 |
94 | printTab(pcur, 'epanet', 'pipes')
95 | pcur.execute("SELECT count(*) FROM epanet.pipes")
96 | [ret] = pcur.fetchone()
97 | assert(ret == 11)
98 |
99 | versioning.archive(pg_conn_info, 'epanet', 7)
100 | printTab(pcur, 'epanet', 'pipes')
101 | pcur.execute("SELECT count(*) FROM epanet.pipes")
102 | [ret] = pcur.fetchone()
103 | assert(ret == 9)
104 | pcur.execute("SELECT versioning_id FROM epanet.pipes ORDER BY versioning_id")
105 | assert([i[0] for i in pcur.fetchall()] == [1, 2, 4, 6, 7, 8, 9, 10, 11])
106 | printTab(pcur, 'epanet_archive', 'pipes')
107 | pcur.execute("SELECT count(*) FROM epanet_archive.pipes")
108 | [ret] = pcur.fetchone()
109 | assert(ret == 2)
110 | pcur.execute("SELECT versioning_id FROM epanet_archive.pipes ORDER BY versioning_id")
111 | assert([i[0] for i in pcur.fetchall()] == [3, 5])
112 |
113 | versioning.archive(pg_conn_info, 'epanet', 11)
114 | printTab(pcur, 'epanet', 'pipes')
115 | pcur.execute("SELECT count(*) FROM epanet.pipes")
116 | [ret] = pcur.fetchone()
117 | assert(ret == 7)
118 | pcur.execute("SELECT versioning_id FROM epanet.pipes ORDER BY versioning_id")
119 | assert([i[0] for i in pcur.fetchall()] == [2, 4, 6, 8, 9, 10, 11])
120 | printTab(pcur, 'epanet_archive', 'pipes')
121 | pcur.execute("SELECT count(*) FROM epanet_archive.pipes")
122 | [ret] = pcur.fetchone()
123 | assert(ret == 4)
124 | pcur.execute("SELECT versioning_id FROM epanet_archive.pipes ORDER BY versioning_id")
125 | assert([i[0] for i in pcur.fetchall()] == [1, 3, 5, 7])
126 |
127 | # view
128 | printTab(pcur, 'epanet_archive', 'pipes_all')
129 | pcur.execute("SELECT count(*) FROM epanet_archive.pipes_all")
130 | [ret] = pcur.fetchone()
131 | assert(ret == 11)
132 | pcur.execute("SELECT * FROM epanet_archive.pipes_all ORDER BY versioning_id")
133 | endv = pcur.fetchall()
134 | assert(end==endv)
135 |
136 | pcur.close()
137 | if __name__ == "__main__":
138 | if len(sys.argv) != 3:
139 | print("Usage: python3 archiving_test.py host pguser")
140 | else:
141 | test(*sys.argv[1:])
142 |
--------------------------------------------------------------------------------
/test/bug_in_branch_after_commit_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | from versioningDB import versioning
5 | from sqlite3 import dbapi2
6 | import os
7 | import tempfile
8 |
9 |
10 | def test(host, pguser):
11 | pg_conn_info = "dbname=epanet_test_db host=" + host + " user=" + pguser
12 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
13 | tmp_dir = tempfile.gettempdir()
14 |
15 |
16 | # create the test database
17 | os.system("dropdb --if-exists -h " + host + " -U "+pguser+" epanet_test_db")
18 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_db")
19 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -f "+test_data_dir+"/epanet_test_db.sql")
20 |
21 | versioning.historize(pg_conn_info,"epanet")
22 |
23 | # try the update
24 | wc = tmp_dir+"/bug_in_branch_after_commit_wc.sqlite"
25 | if os.path.isfile(wc): os.remove(wc)
26 |
27 | spversioning = versioning.spatialite(wc, pg_conn_info)
28 | spversioning.checkout(['epanet_trunk_rev_head.junctions', 'epanet_trunk_rev_head.pipes'])
29 |
30 | scur = versioning.Db( dbapi2.connect( wc ) )
31 |
32 | scur.execute("SELECT * FROM pipes")
33 | scur.execute("UPDATE pipes_view SET length = 1 WHERE OGC_FID = 1")
34 | scur.commit()
35 |
36 | spversioning.commit('test')
37 |
38 | versioning.add_branch(pg_conn_info,"epanet","mybranch","add 'branch")
39 |
40 |
41 | if __name__ == "__main__":
42 | if len(sys.argv) != 3:
43 | print("Usage: python3 versioning_base_test.py host pguser")
44 | else:
45 | test(*sys.argv[1:])
46 |
--------------------------------------------------------------------------------
/test/composite_primary_key_db.sql:
--------------------------------------------------------------------------------
1 | CREATE EXTENSION IF NOT EXISTS postgis;
2 | CREATE SCHEMA myschema;
3 | CREATE TABLE myschema.referenced (
4 | id1 integer,
5 | id2 integer,
6 | name varchar,
7 | geom geometry('POINT', 2154),
8 | PRIMARY KEY (id1, id2)
9 | );
10 |
11 | CREATE TABLE myschema.referencing (
12 | id integer PRIMARY KEY,
13 | fkid1 integer,
14 | fkid2 integer,
15 | name varchar,
16 | geom geometry('POINT', 2154),
17 | FOREIGN KEY (fkid1, fkid2) REFERENCES myschema.referenced (id1, id2)
18 | );
19 |
20 | INSERT INTO myschema.referenced (id1, id2, name, geom) VALUES (1,18, 'toto', ST_GeometryFromText('POINT(0 0)',2154));
21 | INSERT INTO myschema.referenced (id1, id2, name, geom) VALUES (42,4, 'titi', ST_GeometryFromText('POINT(0 0)',2154));
22 |
23 | INSERT INTO myschema.referencing (id, fkid1, fkid2, name, geom)
24 | VALUES (16, 1,18, 'fk_toto', ST_GeometryFromText('POINT(0 0)',2154));
25 |
--------------------------------------------------------------------------------
/test/create_db_test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | SCRIPT_DIR=$(dirname $(readlink -f $0))
4 |
5 | for db in epanet_test_db composite_primary_key_db;
6 | do
7 | dropdb --if-exists -h 127.0.01 -U postgres $db
8 | createdb -h 127.0.01 -U postgres $db
9 | psql -h 127.0.01 -U postgres $db -f $SCRIPT_DIR/$db.sql
10 | done
11 |
12 | EMPTY_DB="qgis_versioning_empty_db"
13 | dropdb --if-exists -h 127.0.01 -U postgres $EMPTY_DB
14 | createdb -h 127.0.01 -U postgres $EMPTY_DB
15 | psql -h 127.0.01 -U postgres $EMPTY_DB -c 'CREATE EXTENSION postgis'
16 |
17 |
--------------------------------------------------------------------------------
/test/epanet_test_db.sql:
--------------------------------------------------------------------------------
1 | CREATE EXTENSION IF NOT EXISTS postgis;
2 | CREATE SCHEMA epanet;
3 |
4 | CREATE TABLE epanet.junctions (
5 | id serial PRIMARY KEY,
6 | elevation float,
7 | base_demand_flow float,
8 | demand_pattern_id varchar,
9 | geom geometry('POINT',2154)
10 | );
11 |
12 | CREATE TABLE epanet.pipes (
13 | id serial PRIMARY KEY,
14 | start_node integer references epanet.junctions(id),
15 | end_node integer references epanet.junctions(id),
16 | length float,
17 | diameter float,
18 | roughness float,
19 | minor_loss_coefficient float,
20 | status varchar,
21 | geom geometry('LINESTRING',2154)
22 | );
23 |
24 | -- INSERT DATA (Use to identify the data insertion block in test, do not remove this line!!!)
25 |
26 | INSERT INTO epanet.junctions
27 | (elevation, geom)
28 | VALUES
29 | (0,ST_GeometryFromText('POINT(1 0)',2154));
30 |
31 | INSERT INTO epanet.junctions
32 | (elevation, geom)
33 | VALUES
34 | (1,ST_GeometryFromText('POINT(0 1)',2154));
35 |
36 |
37 | INSERT INTO epanet.pipes
38 | (start_node, end_node, length, diameter, geom)
39 | VALUES
40 | (1,2,1,2,ST_GeometryFromText('LINESTRING(1 0,0 1)',2154));
41 |
--------------------------------------------------------------------------------
/test/epanet_test_db_uuid.sql:
--------------------------------------------------------------------------------
1 | CREATE EXTENSION IF NOT EXISTS postgis;
2 | CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
3 | CREATE SCHEMA epanet;
4 |
5 | CREATE TABLE epanet.junctions (
6 | gid uuid DEFAULT uuid_generate_v4 (),
7 | jid serial PRIMARY KEY,
8 | id varchar,
9 | elevation float,
10 | base_demand_flow float,
11 | demand_pattern_id varchar,
12 | geom geometry('POINT',2154)
13 | );
14 |
15 | INSERT INTO epanet.junctions
16 | (id, elevation, geom)
17 | VALUES
18 | ('0',0,ST_GeometryFromText('POINT(1 0)',2154));
19 |
20 | INSERT INTO epanet.junctions
21 | (id, elevation, geom)
22 | VALUES
23 | ('1',1,ST_GeometryFromText('POINT(0 1)',2154));
24 |
25 | CREATE TABLE epanet.pipes (
26 | gid uuid DEFAULT uuid_generate_v4 (),
27 | pid serial PRIMARY KEY,
28 | id varchar,
29 | start_node varchar,
30 | end_node varchar,
31 | length float,
32 | diameter float,
33 | roughness float,
34 | minor_loss_coefficient float,
35 | status varchar,
36 | geom geometry('LINESTRING',2154)
37 | );
38 |
39 | INSERT INTO epanet.pipes
40 | (id, start_node, end_node, length, diameter, geom)
41 | VALUES
42 | ('0','0','1',1,2,ST_GeometryFromText('LINESTRING(1 0,0 1)',2154));
43 |
44 |
--------------------------------------------------------------------------------
/test/history_creation_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | from versioningDB import versioning
5 | import psycopg2
6 | import os
7 | import tempfile
8 |
9 |
10 | def test(host, pguser):
11 | pg_conn_info = "dbname=epanet_test_db host=" + host + " user=" + pguser
12 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
13 | tmp_dir = tempfile.gettempdir()
14 |
15 | # create the test database
16 | os.system("dropdb --if-exists -h " + host + " -U "+pguser+" epanet_test_db")
17 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_db")
18 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -c 'CREATE EXTENSION postgis'")
19 |
20 | pcur = versioning.Db(psycopg2.connect(pg_conn_info))
21 | pcur.execute("CREATE SCHEMA epanet")
22 | pcur.execute("""
23 | CREATE TABLE epanet.junctions (
24 | hid serial PRIMARY KEY,
25 | id varchar,
26 | elevation float,
27 | base_demand_flow float,
28 | demand_pattern_id varchar,
29 | geom geometry('POINT',2154)
30 | )""")
31 |
32 | pcur.execute("""
33 | INSERT INTO epanet.junctions
34 | (id, elevation, geom)
35 | VALUES
36 | ('0',0,ST_GeometryFromText('POINT(1 0)',2154))""")
37 |
38 | pcur.execute("""
39 | INSERT INTO epanet.junctions
40 | (id, elevation, geom)
41 | VALUES
42 | ('1',1,ST_GeometryFromText('POINT(0 1)',2154))""")
43 |
44 | pcur.execute("""
45 | CREATE TABLE epanet.pipes (
46 | hid serial PRIMARY KEY,
47 | id varchar,
48 | start_node varchar,
49 | end_node varchar,
50 | length float,
51 | diameter float,
52 | roughness float,
53 | minor_loss_coefficient float,
54 | status varchar,
55 | geom geometry('LINESTRING',2154)
56 | )""")
57 |
58 | pcur.execute("""
59 | INSERT INTO epanet.pipes
60 | (id, start_node, end_node, length, diameter, geom)
61 | VALUES
62 | ('0','0','1',1,2,ST_GeometryFromText('LINESTRING(1 0,0 1)',2154))""")
63 |
64 | pcur.commit()
65 | pcur.close()
66 |
67 | versioning.historize( pg_conn_info, 'epanet' )
68 |
69 | failed = False
70 | try:
71 | versioning.add_branch( pg_conn_info, 'epanet', 'trunk' )
72 | except:
73 | failed = True
74 | assert( failed )
75 |
76 | failed = False
77 | try:
78 | versioning.add_branch( pg_conn_info, 'epanet', 'mybranch', 'message', 'toto' )
79 | except:
80 | failed = True
81 | assert( failed )
82 |
83 | versioning.add_branch( pg_conn_info, 'epanet', 'mybranch', 'test msg' )
84 |
85 |
86 | pcur = versioning.Db(psycopg2.connect(pg_conn_info))
87 | pcur.execute("SELECT * FROM epanet_mybranch_rev_head.junctions")
88 | assert( len(pcur.fetchall()) == 2 )
89 | pcur.execute("SELECT * FROM epanet_mybranch_rev_head.pipes")
90 | assert( len(pcur.fetchall()) == 1 )
91 |
92 | ##versioning.add_revision_view( pg_conn_info, 'epanet', 'mybranch', 2)
93 | ##pcur.execute("SELECT * FROM epanet_mybranch_rev_2.junctions")
94 | ##assert( len(pcur.fetchall()) == 2 )
95 | ##pcur.execute("SELECT * FROM epanet_mybranch_rev_2.pipes")
96 | ##assert( len(pcur.fetchall()) == 1 )
97 |
98 | select_str, where_str = versioning.rev_view_str( pg_conn_info, 'epanet', 'junctions','mybranch', 2)
99 | pcur.execute(select_str + " WHERE " + where_str)
100 | assert( len(pcur.fetchall()) == 2 )
101 | select_str, where_str = versioning.rev_view_str( pg_conn_info, 'epanet', 'pipes','mybranch', 2)
102 | pcur.execute(select_str + " WHERE " + where_str)
103 | assert( len(pcur.fetchall()) == 1 )
104 |
105 | pcur.close()
106 |
107 | if __name__ == "__main__":
108 | if len(sys.argv) != 3:
109 | print("Usage: python3 versioning_base_test.py host pguser")
110 | else:
111 | test(*sys.argv[1:])
112 |
--------------------------------------------------------------------------------
/test/issue287_pg_dump.sql:
--------------------------------------------------------------------------------
1 | --
2 | -- PostgreSQL database dump
3 | --
4 |
5 | SET statement_timeout = 0;
6 | SET client_encoding = 'UTF8';
7 | SET standard_conforming_strings = on;
8 | SET check_function_bodies = false;
9 | SET client_min_messages = warning;
10 |
11 | --
12 | -- Name: epanet; Type: SCHEMA; Schema: -
13 | --
14 |
15 | CREATE SCHEMA epanet;
16 |
17 |
18 | --
19 | -- Name: epanet_trunk_rev_head; Type: SCHEMA; Schema: -
20 | --
21 |
22 | CREATE SCHEMA epanet_trunk_rev_head;
23 |
24 |
25 |
26 | --
27 | -- Name: plpgsql; Type: EXTENSION; Schema: -; Owner:
28 | --
29 |
30 | CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;
31 |
32 |
33 | --
34 | -- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner:
35 | --
36 |
37 | COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language';
38 |
39 |
40 | --
41 | -- Name: postgis; Type: EXTENSION; Schema: -; Owner:
42 | --
43 |
44 | CREATE EXTENSION IF NOT EXISTS postgis WITH SCHEMA public;
45 |
46 |
47 | --
48 | -- Name: EXTENSION postgis; Type: COMMENT; Schema: -; Owner:
49 | --
50 |
51 | COMMENT ON EXTENSION postgis IS 'PostGIS geometry, geography, and raster spatial types and functions';
52 |
53 |
54 | SET search_path = epanet, pg_catalog;
55 |
56 | SET default_tablespace = '';
57 |
58 | SET default_with_oids = false;
59 |
60 | --
61 | -- Name: junctions; Type: TABLE; Schema: epanet; Tablespace:
62 | --
63 |
64 | CREATE TABLE junctions (
65 | id character varying,
66 | elevation double precision,
67 | base_demand_flow double precision,
68 | demand_pattern_id character varying,
69 | geom public.geometry(Point,2154),
70 | hid integer NOT NULL,
71 | trunk_rev_begin integer,
72 | trunk_rev_end integer,
73 | trunk_parent integer,
74 | trunk_child integer
75 | );
76 |
77 |
78 | --
79 | -- Name: junctions_hid_seq; Type: SEQUENCE; Schema: epanet
80 | --
81 |
82 | CREATE SEQUENCE junctions_hid_seq
83 | START WITH 1
84 | INCREMENT BY 1
85 | NO MINVALUE
86 | NO MAXVALUE
87 | CACHE 1;
88 |
89 |
90 | --
91 | -- Name: junctions_hid_seq; Type: SEQUENCE OWNED BY; Schema: epanet
92 | --
93 |
94 | ALTER SEQUENCE junctions_hid_seq OWNED BY junctions.hid;
95 |
96 |
97 | --
98 | -- Name: pipes; Type: TABLE; Schema: epanet; Tablespace:
99 | --
100 |
101 | CREATE TABLE pipes (
102 | id character varying,
103 | start_node character varying,
104 | end_node character varying,
105 | length double precision,
106 | diameter double precision,
107 | roughness double precision,
108 | minor_loss_coefficient double precision,
109 | status character varying,
110 | GEOMETRY public.geometry(LineString,2154),
111 | hid integer NOT NULL,
112 | trunk_rev_begin integer,
113 | trunk_rev_end integer,
114 | trunk_parent integer,
115 | trunk_child integer
116 | );
117 |
118 |
119 | --
120 | -- Name: pipes_hid_seq; Type: SEQUENCE; Schema: epanet
121 | --
122 |
123 | CREATE SEQUENCE pipes_hid_seq
124 | START WITH 1
125 | INCREMENT BY 1
126 | NO MINVALUE
127 | NO MAXVALUE
128 | CACHE 1;
129 |
130 |
131 | --
132 | -- Name: pipes_hid_seq; Type: SEQUENCE OWNED BY; Schema: epanet
133 | --
134 |
135 | ALTER SEQUENCE pipes_hid_seq OWNED BY pipes.hid;
136 |
137 |
138 | --
139 | -- Name: revisions; Type: TABLE; Schema: epanet; Tablespace:
140 | --
141 |
142 | CREATE TABLE revisions (
143 | rev integer NOT NULL,
144 | commit_msg character varying,
145 | branch character varying DEFAULT 'trunk'::character varying,
146 | date timestamp without time zone DEFAULT now(),
147 | author character varying
148 | );
149 |
150 |
151 | --
152 | -- Name: revisions_rev_seq; Type: SEQUENCE; Schema: epanet
153 | --
154 |
155 | CREATE SEQUENCE revisions_rev_seq
156 | START WITH 1
157 | INCREMENT BY 1
158 | NO MINVALUE
159 | NO MAXVALUE
160 | CACHE 1;
161 |
162 |
163 | --
164 | -- Name: revisions_rev_seq; Type: SEQUENCE OWNED BY; Schema: epanet
165 | --
166 |
167 | ALTER SEQUENCE revisions_rev_seq OWNED BY revisions.rev;
168 |
169 | --
170 | -- Name: versioning_constraints; Type: TABLE; Schema: epanet;
171 | --
172 |
173 | CREATE TABLE epanet.versioning_constraints (
174 | table_from character varying,
175 | columns_from character varying[],
176 | defaults_from character varying[],
177 | table_to character varying,
178 | columns_to character varying[],
179 | updtype character(1),
180 | deltype character(1)
181 | );
182 |
183 |
184 | SET search_path = epanet_trunk_rev_head, pg_catalog;
185 |
186 | --
187 | -- Name: junctions; Type: VIEW; Schema: epanet_trunk_rev_head
188 | --
189 |
190 | CREATE VIEW junctions AS
191 | SELECT junctions.hid, junctions.id, junctions.elevation, junctions.base_demand_flow, junctions.demand_pattern_id, junctions.geom FROM epanet.junctions WHERE ((junctions.trunk_rev_end IS NULL) AND (junctions.trunk_rev_begin IS NOT NULL));
192 |
193 |
194 | --
195 | -- Name: pipes; Type: VIEW; Schema: epanet_trunk_rev_head
196 | --
197 |
198 | CREATE VIEW pipes AS
199 | SELECT pipes.hid, pipes.id, pipes.start_node, pipes.end_node, pipes.length, pipes.diameter, pipes.roughness, pipes.minor_loss_coefficient, pipes.status, pipes.GEOMETRY FROM epanet.pipes WHERE ((pipes.trunk_rev_end IS NULL) AND (pipes.trunk_rev_begin IS NOT NULL));
200 |
201 |
202 | SET search_path = epanet, pg_catalog;
203 |
204 | --
205 | -- Name: hid; Type: DEFAULT; Schema: epanet
206 | --
207 |
208 | ALTER TABLE ONLY junctions ALTER COLUMN hid SET DEFAULT nextval('junctions_hid_seq'::regclass);
209 |
210 |
211 | --
212 | -- Name: hid; Type: DEFAULT; Schema: epanet
213 | --
214 |
215 | ALTER TABLE ONLY pipes ALTER COLUMN hid SET DEFAULT nextval('pipes_hid_seq'::regclass);
216 |
217 |
218 | --
219 | -- Name: rev; Type: DEFAULT; Schema: epanet
220 | --
221 |
222 | ALTER TABLE ONLY revisions ALTER COLUMN rev SET DEFAULT nextval('revisions_rev_seq'::regclass);
223 |
224 |
225 | --
226 | -- Data for Name: junctions; Type: TABLE DATA; Schema: epanet
227 | --
228 |
229 | COPY junctions (id, elevation, base_demand_flow, demand_pattern_id, geom, hid, trunk_rev_begin, trunk_rev_end, trunk_parent, trunk_child) FROM stdin;
230 | 0 0 \N \N 01010000206A080000000000000000F03F0000000000000000 1 1 \N \N \N
231 | 1 1 \N \N 01010000206A0800000000000000000000000000000000F03F 2 1 \N \N \N
232 | \.
233 |
234 |
235 | --
236 | -- Name: junctions_hid_seq; Type: SEQUENCE SET; Schema: epanet
237 | --
238 |
239 | SELECT pg_catalog.setval('junctions_hid_seq', 2, true);
240 |
241 |
242 | --
243 | -- Data for Name: pipes; Type: TABLE DATA; Schema: epanet
244 | --
245 |
246 | COPY pipes (id, start_node, end_node, length, diameter, roughness, minor_loss_coefficient, status, GEOMETRY, hid, trunk_rev_begin, trunk_rev_end, trunk_parent, trunk_child) FROM stdin;
247 | 4 2 3 \N \N \N \N \N 01020000206A08000002000000B411C210B508D9BF1B0E49744D1DE53F9C84E785AEE3E53F4EA96C73A15FDDBF 4 3 \N \N \N
248 | 0 0 1 1 2 \N \N \N 01020000206A08000002000000BC139FE342F9DC3F56F191C62EFEE3BF2276308E5E83E1BF541DDC72A203D83F 5 3 \N 1 \N
249 | 0 0 1 1 2 \N \N \N 01020000206A08000002000000F8F3853CA84AF83FA6A0C22CC87CD83FF0E70B795095E03F2AA8300B321FF63F 3 2 2 1 5
250 | 0 0 1 1 2 \N \N \N 01020000206A08000002000000000000000000F03F00000000000000000000000000000000000000000000F03F 1 1 2 \N \N
251 | 1 3 3 \N \N \N \N \N 01020000206A08000002000000C28885E799B3E0BFE946DBC885D5E83FB4CB7990B55AE63F28A5CE11B68FE2BF 2 2 3 \N \N
252 | \.
253 |
254 |
255 | --
256 | -- Name: pipes_hid_seq; Type: SEQUENCE SET; Schema: epanet
257 | --
258 |
259 | SELECT pg_catalog.setval('pipes_hid_seq', 5, true);
260 |
261 |
262 | --
263 | -- Data for Name: revisions; Type: TABLE DATA; Schema: epanet
264 | --
265 |
266 | COPY revisions (rev, commit_msg, branch, date, author) FROM stdin;
267 | 1 initial commit trunk 2014-01-22 16:00:44.707214 \N
268 | 2 test trunk 2014-01-22 16:20:28.420197 vmo
269 | 3 test trunk 2014-01-22 16:21:28.685928 vmo
270 | 4 test trunk 2014-01-22 16:22:42.132123 vmo
271 | \.
272 |
273 |
274 | --
275 | -- Name: revisions_rev_seq; Type: SEQUENCE SET; Schema: epanet
276 | --
277 |
278 | SELECT pg_catalog.setval('revisions_rev_seq', 1, false);
279 |
280 | --
281 | -- Data for Name: versioning_constraints; Type: TABLE DATA; Schema: epanet;
282 | --
283 |
284 | COPY epanet.versioning_constraints (table_from, columns_from, defaults_from, table_to, columns_to, updtype, deltype) FROM stdin;
285 | junctions {id} {nextval('epanet.junctions_id_seq'::regclass)} \N \N
286 | pipes {id} {nextval('epanet.pipes_id_seq'::regclass)} \N \N
287 | pipes {start_node} {NULL} junctions {id} a a
288 | pipes {end_node} {NULL} junctions {id} a a
289 | \.
290 |
291 |
292 | SET search_path = public, pg_catalog;
293 |
294 | --
295 | -- Data for Name: spatial_ref_sys; Type: TABLE DATA; Schema: public
296 | --
297 |
298 | COPY spatial_ref_sys (srid, auth_name, auth_srid, srtext, proj4text) FROM stdin;
299 | \.
300 |
301 |
302 | SET search_path = epanet, pg_catalog;
303 |
304 | --
305 | -- Name: junctions_pkey; Type: CONSTRAINT; Schema: epanet; Tablespace:
306 | --
307 |
308 | ALTER TABLE ONLY junctions
309 | ADD CONSTRAINT junctions_pkey PRIMARY KEY (hid);
310 |
311 |
312 | --
313 | -- Name: pipes_pkey; Type: CONSTRAINT; Schema: epanet; Tablespace:
314 | --
315 |
316 | ALTER TABLE ONLY pipes
317 | ADD CONSTRAINT pipes_pkey PRIMARY KEY (hid);
318 |
319 |
320 | --
321 | -- Name: revisions_pkey; Type: CONSTRAINT; Schema: epanet; Tablespace:
322 | --
323 |
324 | ALTER TABLE ONLY revisions
325 | ADD CONSTRAINT revisions_pkey PRIMARY KEY (rev);
326 |
327 |
328 | --
329 | -- Name: junctions_trunk_child_fkey; Type: FK CONSTRAINT; Schema: epanet
330 | --
331 |
332 | ALTER TABLE ONLY junctions
333 | ADD CONSTRAINT junctions_trunk_child_fkey FOREIGN KEY (trunk_child) REFERENCES junctions(hid);
334 |
335 |
336 | --
337 | -- Name: junctions_trunk_parent_fkey; Type: FK CONSTRAINT; Schema: epanet
338 | --
339 |
340 | ALTER TABLE ONLY junctions
341 | ADD CONSTRAINT junctions_trunk_parent_fkey FOREIGN KEY (trunk_parent) REFERENCES junctions(hid);
342 |
343 |
344 | --
345 | -- Name: junctions_trunk_rev_begin_fkey; Type: FK CONSTRAINT; Schema: epanet
346 | --
347 |
348 | ALTER TABLE ONLY junctions
349 | ADD CONSTRAINT junctions_trunk_rev_begin_fkey FOREIGN KEY (trunk_rev_begin) REFERENCES revisions(rev);
350 |
351 |
352 | --
353 | -- Name: junctions_trunk_rev_end_fkey; Type: FK CONSTRAINT; Schema: epanet
354 | --
355 |
356 | ALTER TABLE ONLY junctions
357 | ADD CONSTRAINT junctions_trunk_rev_end_fkey FOREIGN KEY (trunk_rev_end) REFERENCES revisions(rev);
358 |
359 |
360 | --
361 | -- Name: pipes_trunk_child_fkey; Type: FK CONSTRAINT; Schema: epanet
362 | --
363 |
364 | ALTER TABLE ONLY pipes
365 | ADD CONSTRAINT pipes_trunk_child_fkey FOREIGN KEY (trunk_child) REFERENCES pipes(hid);
366 |
367 |
368 | --
369 | -- Name: pipes_trunk_parent_fkey; Type: FK CONSTRAINT; Schema: epanet
370 | --
371 |
372 | ALTER TABLE ONLY pipes
373 | ADD CONSTRAINT pipes_trunk_parent_fkey FOREIGN KEY (trunk_parent) REFERENCES pipes(hid);
374 |
375 |
376 | --
377 | -- Name: pipes_trunk_rev_begin_fkey; Type: FK CONSTRAINT; Schema: epanet
378 | --
379 |
380 | ALTER TABLE ONLY pipes
381 | ADD CONSTRAINT pipes_trunk_rev_begin_fkey FOREIGN KEY (trunk_rev_begin) REFERENCES revisions(rev);
382 |
383 |
384 | --
385 | -- Name: pipes_trunk_rev_end_fkey; Type: FK CONSTRAINT; Schema: epanet
386 | --
387 |
388 | ALTER TABLE ONLY pipes
389 | ADD CONSTRAINT pipes_trunk_rev_end_fkey FOREIGN KEY (trunk_rev_end) REFERENCES revisions(rev);
390 |
391 |
392 | --
393 | -- Name: public; Type: ACL; Schema: -; Owner: postgres
394 | --
395 |
396 | REVOKE ALL ON SCHEMA public FROM PUBLIC;
397 | REVOKE ALL ON SCHEMA public FROM postgres;
398 | GRANT ALL ON SCHEMA public TO postgres;
399 | GRANT ALL ON SCHEMA public TO PUBLIC;
400 |
401 |
402 | --
403 | -- PostgreSQL database dump complete
404 | --
405 |
406 |
--------------------------------------------------------------------------------
/test/issue287_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | from versioningDB import versioning
5 | import os
6 | import shutil
7 | import tempfile
8 |
9 |
10 | def test(host, pguser):
11 | pg_conn_info = "dbname=epanet_test_db host=" + host + " user=" + pguser
12 |
13 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
14 | tmp_dir = tempfile.gettempdir()
15 |
16 | # create the test database
17 | os.system("dropdb --if-exists -h " + host + " -U "+pguser+" epanet_test_db")
18 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_db")
19 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -c 'CREATE EXTENSION postgis'")
20 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -f "+test_data_dir+"/issue287_pg_dump.sql")
21 |
22 | # try the update
23 | sqlite_test_filename = os.path.join(tmp_dir, "issue287_wc.sqlite")
24 | shutil.copyfile(os.path.join(test_data_dir, "issue287_wc.sqlite"), sqlite_test_filename)
25 | spversioning = versioning.spatialite(sqlite_test_filename, pg_conn_info)
26 | spversioning.update()
27 | spversioning.commit("test message")
28 |
29 | if __name__ == "__main__":
30 | if len(sys.argv) != 3:
31 | print("Usage: python3 versioning_base_test.py host pguser")
32 | else:
33 | test(*sys.argv[1:])
34 |
--------------------------------------------------------------------------------
/test/issue287_wc.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Oslandia/qgis-versioning/f6d5aa61f778bfa05c9470837845fa8f5f631b5f/test/issue287_wc.sqlite
--------------------------------------------------------------------------------
/test/issue357_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | from sqlite3 import dbapi2
5 | from versioningDB import versioning
6 | import os
7 | import tempfile
8 |
9 |
10 | def test(host, pguser):
11 | pg_conn_info = "dbname=epanet_test_db host=" + host + " user=" + pguser
12 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
13 | tmp_dir = tempfile.gettempdir()
14 |
15 | # create the test database
16 | os.system("dropdb --if-exists -h " + host + " -U "+pguser+" epanet_test_db")
17 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_db")
18 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -c 'CREATE EXTENSION postgis'")
19 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -f "+test_data_dir+"/epanet_test_db.sql")
20 | versioning.historize("dbname=epanet_test_db host={} user={}".format(host,pguser), "epanet")
21 |
22 | # try the update
23 | wc = [os.path.join(tmp_dir, "issue357_wc0.sqlite"), os.path.join(tmp_dir, "issue357_wc1.sqlite")]
24 | spversioning0 = versioning.spatialite(wc[0], pg_conn_info)
25 | spversioning1 = versioning.spatialite(wc[1], pg_conn_info)
26 | for i, f in enumerate(wc):
27 | if os.path.isfile(f): os.remove(f)
28 | sp = spversioning0 if i == 0 else spversioning1
29 | sp.checkout(['epanet_trunk_rev_head.junctions', 'epanet_trunk_rev_head.pipes'])
30 |
31 | scur = []
32 | for f in wc: scur.append(versioning.Db( dbapi2.connect( f ) ))
33 |
34 | scur[0].execute("INSERT INTO pipes_view(id, start_node, end_node, geom) VALUES ('2','1','2',GeomFromText('LINESTRING(1 1,0 1)',2154))")
35 | scur[0].execute("INSERT INTO pipes_view(id, start_node, end_node, geom) VALUES ('3','1','2',GeomFromText('LINESTRING(1 -1,0 1)',2154))")
36 | scur[0].commit()
37 |
38 |
39 | spversioning0.commit( 'commit 1 wc0')
40 | spversioning1.update( )
41 |
42 | scur[0].execute("UPDATE pipes_view SET length = 1")
43 | scur[0].commit()
44 | scur[1].execute("UPDATE pipes_view SET length = 2")
45 | scur[1].execute("UPDATE pipes_view SET length = 3")
46 | scur[1].commit()
47 |
48 | spversioning0.commit( "commit 2 wc0" )
49 | scur[0].execute("SELECT OGC_FID,length,trunk_rev_begin,trunk_rev_end,trunk_parent,trunk_child FROM pipes")
50 | print('################')
51 | for r in scur[0].fetchall():
52 | print(r)
53 |
54 | scur[0].execute("UPDATE pipes_view SET length = 2")
55 | scur[0].execute("DELETE FROM pipes_view WHERE OGC_FID = 6")
56 | scur[0].commit()
57 | spversioning0.commit( "commit 3 wc0" )
58 |
59 | scur[0].execute("SELECT OGC_FID,length,trunk_rev_begin,trunk_rev_end,trunk_parent,trunk_child FROM pipes")
60 | print('################')
61 | for r in scur[0].fetchall():
62 | print(r)
63 |
64 | spversioning1.update( )
65 |
66 | scur[1].execute("SELECT OGC_FID,length,trunk_rev_begin,trunk_rev_end,trunk_parent,trunk_child FROM pipes_diff")
67 | print('################ diff')
68 | for r in scur[1].fetchall():
69 | print(r)
70 |
71 | scur[1].execute("SELECT conflict_id FROM pipes_conflicts")
72 | assert( len(scur[1].fetchall()) == 6 ) # there must be conflicts
73 |
74 | scur[1].execute("SELECT conflict_id,origin,action,OGC_FID,trunk_parent,trunk_child FROM pipes_conflicts")
75 | print('################')
76 | for r in scur[1].fetchall():
77 | print(r)
78 |
79 | scur[1].execute("DELETE FROM pipes_conflicts WHERE origin='theirs' AND conflict_id=1")
80 | scur[1].commit()
81 | scur[1].execute("SELECT conflict_id FROM pipes_conflicts")
82 | assert( len(scur[1].fetchall()) == 4 ) # there must be two removed entries
83 |
84 | scur[1].execute("SELECT conflict_id,origin,action,OGC_FID,trunk_parent,trunk_child FROM pipes_conflicts")
85 | print('################')
86 | for r in scur[1].fetchall():
87 | print(r)
88 |
89 | scur[1].execute("DELETE FROM pipes_conflicts WHERE origin='mine' AND OGC_FID = 11")
90 | scur[1].execute("DELETE FROM pipes_conflicts WHERE origin='theirs'")
91 | scur[1].commit()
92 | scur[1].execute("SELECT conflict_id FROM pipes_conflicts")
93 | assert( len(scur[1].fetchall()) == 0 ) # there must be no conflict
94 |
95 |
96 | scur[1].execute("SELECT OGC_FID,length,trunk_rev_begin,trunk_rev_end,trunk_parent,trunk_child FROM pipes")
97 | print('################')
98 | for r in scur[1].fetchall():
99 | print(r)
100 |
101 | if __name__ == "__main__":
102 | if len(sys.argv) != 3:
103 | print("Usage: python3 versioning_base_test.py host pguser")
104 | else:
105 | test(*sys.argv[1:])
106 |
--------------------------------------------------------------------------------
/test/issue358_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | from versioningDB import versioning
5 | from sqlite3 import dbapi2
6 | import os
7 | import tempfile
8 |
9 |
10 | def test(host, pguser):
11 | pg_conn_info = "dbname=epanet_test_db host=" + host + " user=" + pguser
12 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
13 | tmp_dir = tempfile.gettempdir()
14 |
15 | # create the test database
16 | os.system("dropdb --if-exists -h " + host + " -U "+pguser+" epanet_test_db")
17 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_db")
18 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -c 'CREATE EXTENSION postgis'")
19 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -f "+test_data_dir+"/epanet_test_db.sql")
20 | versioning.historize("dbname=epanet_test_db host={} user={}".format(host,pguser), "epanet")
21 |
22 | # try the update
23 | wc = tmp_dir+"/issue358_wc.sqlite"
24 | if os.path.isfile(wc): os.remove(wc)
25 | spversioning = versioning.spatialite(wc, pg_conn_info)
26 | spversioning.checkout(['epanet_trunk_rev_head.junctions', 'epanet_trunk_rev_head.pipes'])
27 |
28 | scur = versioning.Db( dbapi2.connect( wc ) )
29 |
30 | scur.execute("SELECT * FROM pipes")
31 | assert( len(scur.fetchall()) == 1 )
32 | scur.execute("UPDATE pipes_view SET length = 1 WHERE OGC_FID = 1")
33 | scur.execute("SELECT * FROM pipes")
34 | assert( len(scur.fetchall()) == 2 )
35 | scur.execute("UPDATE pipes_view SET length = 2 WHERE OGC_FID = 2")
36 | scur.execute("SELECT * FROM pipes")
37 | assert( len(scur.fetchall()) == 2 )
38 |
39 | if __name__ == "__main__":
40 | if len(sys.argv) != 3:
41 | print("Usage: python3 versioning_base_test.py host pguser")
42 | else:
43 | test(*sys.argv[1:])
44 |
--------------------------------------------------------------------------------
/test/issue437_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | from versioningDB import versioning
5 | from sqlite3 import dbapi2
6 | import os
7 | import tempfile
8 |
9 |
10 | def test(host, pguser):
11 | pg_conn_info = "dbname=epanet_test_db host=" + host + " user=" + pguser
12 |
13 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
14 | tmp_dir = tempfile.gettempdir()
15 |
16 | # create the test database
17 | os.system("dropdb --if-exists -h " + host + " -U "+pguser+" epanet_test_db")
18 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_db")
19 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -c 'CREATE EXTENSION postgis'")
20 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -f "+test_data_dir+"/epanet_test_db.sql")
21 | versioning.historize("dbname=epanet_test_db host={} user={}".format(host,pguser), "epanet")
22 |
23 | # try the update
24 | wc = [os.path.join(tmp_dir,"issue437_wc0.sqlite"), os.path.join(tmp_dir,"issue437_wc1.sqlite")]
25 | spversioning0 = versioning.spatialite(wc[0], pg_conn_info)
26 | spversioning1 = versioning.spatialite(wc[1], pg_conn_info)
27 | for i, f in enumerate(wc):
28 | if os.path.isfile(f): os.remove(f)
29 | sp = spversioning0 if i == 0 else spversioning1
30 | sp.checkout(['epanet_trunk_rev_head.junctions', 'epanet_trunk_rev_head.pipes'])
31 |
32 | scur = []
33 | for f in wc: scur.append(versioning.Db( dbapi2.connect( f ) ))
34 |
35 | scur[0].execute("INSERT INTO pipes_view(id, start_node, end_node, geom) VALUES ('2','1','2',GeomFromText('LINESTRING(1 1,0 1)',2154))")
36 | scur[0].execute("INSERT INTO pipes_view(id, start_node, end_node, geom) VALUES ('3','1','2',GeomFromText('LINESTRING(1 -1,0 1)',2154))")
37 | scur[0].commit()
38 |
39 |
40 | spversioning0.commit( 'commit 1 wc0')
41 | spversioning1.update( )
42 |
43 | scur[0].execute("UPDATE pipes_view SET length = 1")
44 | scur[0].commit()
45 | scur[1].execute("UPDATE pipes_view SET length = 2")
46 | scur[1].execute("UPDATE pipes_view SET length = 3")
47 | scur[1].commit()
48 |
49 | spversioning0.commit( "commit 2 wc0" )
50 | scur[0].execute("SELECT OGC_FID,length,trunk_rev_begin,trunk_rev_end,trunk_parent,trunk_child FROM pipes")
51 | print('################')
52 | for r in scur[0].fetchall():
53 | print(r)
54 |
55 | scur[0].execute("UPDATE pipes_view SET length = 2")
56 | scur[0].execute("DELETE FROM pipes_view WHERE OGC_FID = 6")
57 | scur[0].commit()
58 | spversioning0.commit( "commit 3 wc0" )
59 |
60 | scur[0].execute("SELECT OGC_FID,length,trunk_rev_begin,trunk_rev_end,trunk_parent,trunk_child FROM pipes")
61 | print('################')
62 | for r in scur[0].fetchall():
63 | print(r)
64 |
65 | spversioning1.update( )
66 |
67 | scur[1].execute("SELECT OGC_FID,length,trunk_rev_begin,trunk_rev_end,trunk_parent,trunk_child FROM pipes_diff")
68 | print('################ diff')
69 | for r in scur[1].fetchall():
70 | print(r)
71 |
72 | scur[1].execute("SELECT conflict_id FROM pipes_conflicts")
73 | assert( len(scur[1].fetchall()) == 6 ) # there must be conflicts
74 |
75 | scur[1].execute("SELECT conflict_id,origin,action,OGC_FID,trunk_parent,trunk_child FROM pipes_conflicts")
76 | print('################')
77 | for r in scur[1].fetchall():
78 | print(r)
79 |
80 | scur[1].execute("DELETE FROM pipes_conflicts WHERE origin='theirs' AND conflict_id=1")
81 | scur[1].commit()
82 | scur[1].execute("SELECT conflict_id FROM pipes_conflicts")
83 | assert( len(scur[1].fetchall()) == 4 ) # there must be two removed entries
84 |
85 | scur[1].execute("SELECT conflict_id,origin,action,OGC_FID,trunk_parent,trunk_child FROM pipes_conflicts")
86 | print('################')
87 | for r in scur[1].fetchall():
88 | print(r)
89 |
90 | scur[1].execute("DELETE FROM pipes_conflicts WHERE origin='mine' AND OGC_FID = 11")
91 | scur[1].execute("DELETE FROM pipes_conflicts WHERE origin='theirs'")
92 | scur[1].commit()
93 | scur[1].execute("SELECT conflict_id FROM pipes_conflicts")
94 | assert( len(scur[1].fetchall()) == 0 ) # there must be no conflict
95 |
96 |
97 | scur[1].execute("SELECT OGC_FID,length,trunk_rev_begin,trunk_rev_end,trunk_parent,trunk_child FROM pipes")
98 | print('################')
99 | for r in scur[1].fetchall():
100 | print(r)
101 |
102 | if __name__ == "__main__":
103 | if len(sys.argv) != 3:
104 | print("Usage: python3 versioning_base_test.py host pguser")
105 | else:
106 | test(*sys.argv[1:])
107 |
--------------------------------------------------------------------------------
/test/issue437_test_db.sql:
--------------------------------------------------------------------------------
1 | CREATE EXTENSION IF NOT EXISTS postgis;
2 | CREATE SCHEMA epanet;
3 |
4 | CREATE TABLE epanet.junctions (
5 | id varchar,
6 | elevation float,
7 | base_demand_flow float,
8 | demand_pattern_id varchar,
9 | geom geometry('POINT',2154),
10 | ult_data timestamp
11 | );
12 |
13 | INSERT INTO epanet.junctions
14 | (id, elevation, geom)
15 | VALUES
16 | ('0',0,ST_GeometryFromText('POINT(1 0)',2154));
17 |
18 | INSERT INTO epanet.junctions
19 | (id, elevation, geom)
20 | VALUES
21 | ('1',1,ST_GeometryFromText('POINT(0 1)',2154));
22 |
23 | CREATE TABLE epanet.pipes (
24 | id varchar,
25 | start_node varchar,
26 | end_node varchar,
27 | length float,
28 | diameter float,
29 | roughness float,
30 | minor_loss_coefficient float,
31 | status varchar,
32 | geom geometry('LINESTRING',2154),
33 | ult_data timestamp
34 | );
35 |
36 | INSERT INTO epanet.pipes
37 | (id, start_node, end_node, length, diameter, geom)
38 | VALUES
39 | ('0','0','1',1,2,ST_GeometryFromText('LINESTRING(1 0,0 1)',2154));
40 |
41 | CREATE TABLE epanet.revisions(
42 | rev serial PRIMARY KEY,
43 | commit_msg varchar,
44 | branch varchar DEFAULT 'trunk',
45 | date timestamp DEFAULT current_timestamp,
46 | author varchar);
47 | INSERT INTO epanet.revisions VALUES (1,'initial commit','trunk');
48 |
49 | ALTER TABLE epanet.junctions
50 | ADD COLUMN hid serial PRIMARY KEY,
51 | ADD COLUMN trunk_rev_begin integer REFERENCES epanet.revisions(rev),
52 | ADD COLUMN trunk_rev_end integer REFERENCES epanet.revisions(rev),
53 | ADD COLUMN trunk_parent integer REFERENCES epanet.junctions(hid),
54 | ADD COLUMN trunk_child integer REFERENCES epanet.junctions(hid);
55 |
56 | ALTER TABLE epanet.pipes
57 | ADD COLUMN hid serial PRIMARY KEY,
58 | ADD COLUMN trunk_rev_begin integer REFERENCES epanet.revisions(rev),
59 | ADD COLUMN trunk_rev_end integer REFERENCES epanet.revisions(rev),
60 | ADD COLUMN trunk_parent integer REFERENCES epanet.pipes(hid),
61 | ADD COLUMN trunk_child integer REFERENCES epanet.pipes(hid);
62 |
63 | UPDATE epanet.junctions SET trunk_rev_begin = 1;
64 |
65 | UPDATE epanet.pipes SET trunk_rev_begin = 1;
66 |
67 | CREATE SCHEMA epanet_trunk_rev_head;
68 |
69 | CREATE VIEW epanet_trunk_rev_head.junctions
70 | AS SELECT hid, id, elevation, base_demand_flow, demand_pattern_id, geom
71 | FROM epanet.junctions
72 | WHERE trunk_rev_end IS NULL AND trunk_rev_begin IS NOT NULL;
73 |
74 | CREATE VIEW epanet_trunk_rev_head.pipes
75 | AS SELECT hid, id, start_node, end_node, length, diameter, roughness, minor_loss_coefficient, status, geom
76 | FROM epanet.pipes
77 | WHERE trunk_rev_end IS NULL AND trunk_rev_begin IS NOT NULL;
78 |
79 |
--------------------------------------------------------------------------------
/test/issue485_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | from versioningDB import versioning
5 | from sqlite3 import dbapi2
6 | import psycopg2
7 | import os
8 | import tempfile
9 |
10 |
11 | def test(host, pguser):
12 | pg_conn_info = "dbname=epanet_test_db host=" + host + " user=" + pguser
13 |
14 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
15 | tmp_dir = tempfile.gettempdir()
16 |
17 | # create the test database
18 | os.system("dropdb --if-exists -h " + host + " -U "+pguser+" epanet_test_db")
19 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_db")
20 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -c 'CREATE EXTENSION postgis'")
21 |
22 | pcur = versioning.Db(psycopg2.connect(pg_conn_info))
23 | pcur.execute("CREATE SCHEMA epanet")
24 | pcur.execute("""
25 | CREATE TABLE epanet.junctions (
26 | id serial PRIMARY KEY,
27 | elevation float,
28 | base_demand_flow float,
29 | demand_pattern_id varchar,
30 | geometry geometry('POINT',2154),
31 | geometry_schematic geometry('POLYGON',2154)
32 | )""")
33 |
34 | pcur.execute("""
35 | INSERT INTO epanet.junctions
36 | (elevation, geometry, geometry_schematic)
37 | VALUES
38 | (0,ST_GeometryFromText('POINT(0 0)',2154),
39 | ST_GeometryFromText('POLYGON((-1 -1,1 -1,1 1,-1 1,-1 -1))',2154))""")
40 |
41 | pcur.execute("""
42 | INSERT INTO epanet.junctions
43 | (elevation, geometry, geometry_schematic)
44 | VALUES
45 | (1,ST_GeometryFromText('POINT(0 1)',2154),
46 | ST_GeometryFromText('POLYGON((0 0,2 0,2 2,0 2,0 0))',2154))""")
47 |
48 | pcur.execute("""
49 | CREATE TABLE epanet.pipes (
50 | id serial PRIMARY KEY,
51 | start_node varchar,
52 | end_node varchar,
53 | length float,
54 | diameter float,
55 | roughness float,
56 | minor_loss_coefficient float,
57 | status varchar,
58 | geometry geometry('LINESTRING',2154)
59 | )""")
60 |
61 | pcur.execute("""
62 | INSERT INTO epanet.pipes
63 | (start_node, end_node, length, diameter, geometry)
64 | VALUES
65 | (1,2,1,2,ST_GeometryFromText('LINESTRING(1 0,0 1)',2154))""")
66 |
67 | pcur.commit()
68 | pcur.close()
69 |
70 | versioning.historize( pg_conn_info, 'epanet' )
71 |
72 | failed = False
73 | try:
74 | versioning.add_branch( pg_conn_info, 'epanet', 'trunk' )
75 | except:
76 | failed = True
77 | assert( failed )
78 |
79 | failed = False
80 | try:
81 | versioning.add_branch( pg_conn_info, 'epanet', 'mybranch', 'message', 'toto' )
82 | except:
83 | failed = True
84 | assert( failed )
85 |
86 | versioning.add_branch( pg_conn_info, 'epanet', 'mybranch', 'test msg' )
87 |
88 |
89 | pcur = versioning.Db(psycopg2.connect(pg_conn_info))
90 | pcur.execute("SELECT * FROM epanet_mybranch_rev_head.junctions")
91 | assert( len(pcur.fetchall()) == 2 )
92 | pcur.execute("SELECT * FROM epanet_mybranch_rev_head.pipes")
93 | assert( len(pcur.fetchall()) == 1 )
94 |
95 | ##versioning.add_revision_view( pg_conn_info, 'epanet', 'mybranch', 2)
96 | ##pcur.execute("SELECT * FROM epanet_mybranch_rev_2.junctions")
97 | ##assert( len(pcur.fetchall()) == 2 )
98 | ##pcur.execute("SELECT * FROM epanet_mybranch_rev_2.pipes")
99 | ##assert( len(pcur.fetchall()) == 1 )
100 |
101 | select_and_where_str = versioning.rev_view_str( pg_conn_info, 'epanet', 'junctions','mybranch', 2)
102 | print(select_and_where_str[0] + " WHERE " + select_and_where_str[1])
103 | pcur.execute(select_and_where_str[0] + " WHERE " + select_and_where_str[1])
104 | assert( len(pcur.fetchall()) == 2 )
105 | select_and_where_str = versioning.rev_view_str( pg_conn_info, 'epanet', 'pipes','mybranch', 2)
106 | print(select_and_where_str[0] + " WHERE " + select_and_where_str[1])
107 | pcur.execute(select_and_where_str[0] + " WHERE " + select_and_where_str[1])
108 | assert( len(pcur.fetchall()) == 1 )
109 |
110 | pcur.execute("SELECT ST_AsText(geometry), ST_AsText(geometry_schematic) FROM epanet.junctions")
111 | res = pcur.fetchall()
112 | assert( res[0][0] == 'POINT(0 0)' )
113 | assert( res[1][1] == 'POLYGON((0 0,2 0,2 2,0 2,0 0))' )
114 |
115 |
116 | wc = tmp_dir+'/wc_multiple_geometry_test.sqlite'
117 | if os.path.isfile(wc): os.remove(wc)
118 | spversioning = versioning.spatialite(wc, pg_conn_info)
119 | spversioning.checkout( ['epanet_trunk_rev_head.pipes','epanet_trunk_rev_head.junctions'] )
120 |
121 |
122 | scur = versioning.Db( dbapi2.connect(wc) )
123 | scur.execute("UPDATE junctions_view SET GEOMETRY = GeometryFromText('POINT(3 3)',2154) WHERE OGC_FID = 1")
124 | scur.commit()
125 | scur.close()
126 | spversioning.commit( 'moved a junction' )
127 |
128 | pcur.execute("SELECT ST_AsText(geometry), ST_AsText(geometry_schematic) FROM epanet_trunk_rev_head.junctions ORDER BY versioning_id DESC")
129 | res = pcur.fetchall()
130 | for r in res: print(r)
131 | print("res={}".format(res[0][0]))
132 | assert( res[0][0] == 'POINT(3 3)' )
133 | assert( res[0][1] == 'POLYGON((-1 -1,1 -1,1 1,-1 1,-1 -1))' )
134 |
135 | pcur.close()
136 |
137 | # now we branch from head
138 | versioning.add_branch( pg_conn_info, 'epanet', 'b1', 'add branch b1' )
139 |
140 | pcur = versioning.Db(psycopg2.connect(pg_conn_info))
141 | pcur.execute("SELECT versioning_id, trunk_rev_begin, trunk_rev_end, b1_rev_begin, b1_rev_end FROM epanet.junctions ORDER BY versioning_id")
142 | for r in pcur.fetchall(): print(r)
143 | pcur.close()
144 |
145 | # edit a little with a new wc
146 | os.remove(wc)
147 | spversioning.checkout( ['epanet_b1_rev_head.junctions'] )
148 |
149 | scur = versioning.Db( dbapi2.connect(wc) )
150 | scur.execute("UPDATE junctions_view SET GEOMETRY = GeometryFromText('POINT(4 4)',2154) WHERE OGC_FID = 3")
151 | scur.commit()
152 | scur.execute("PRAGMA table_info(junctions_view)")
153 | print("-----------------")
154 | for r in scur.fetchall(): print(r)
155 | scur.close()
156 |
157 | spversioning.commit( 'moved a junction')
158 |
159 | pcur = versioning.Db(psycopg2.connect(pg_conn_info))
160 | pcur.execute("SELECT versioning_id, trunk_rev_begin, trunk_rev_end, b1_rev_begin, b1_rev_end FROM epanet.junctions ORDER BY versioning_id")
161 | print("-----------------")
162 | for r in pcur.fetchall(): print(r)
163 | pcur.close()
164 |
165 | if __name__ == "__main__":
166 | if len(sys.argv) != 3:
167 | print("Usage: python3 versioning_base_test.py host pguser")
168 | else:
169 | test(*sys.argv[1:])
170 |
--------------------------------------------------------------------------------
/test/issue486_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import sys
3 | sys.path.insert(0, '..')
4 |
5 | from versioningDB import versioning
6 | from sqlite3 import dbapi2
7 | import psycopg2
8 | import os
9 | import shutil
10 | import tempfile
11 |
12 | def test(host, pguser):
13 | pg_conn_info = "dbname=epanet_test_db host=" + host + " user=" + pguser
14 |
15 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
16 | tmp_dir = tempfile.gettempdir()
17 |
18 | # create the test database
19 | os.system("dropdb --if-exists -h " + host + " -U "+pguser+" epanet_test_db")
20 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_db")
21 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -c 'CREATE EXTENSION postgis'")
22 |
23 | pcur = versioning.Db(psycopg2.connect(pg_conn_info))
24 | pcur.execute("CREATE SCHEMA epanet")
25 | pcur.execute("""
26 | CREATE TABLE epanet.junctions (
27 | hid serial PRIMARY KEY,
28 | id varchar,
29 | elevation float,
30 | base_demand_flow float,
31 | demand_pattern_id varchar,
32 | printmap integer[],
33 | geometry geometry('POINT',2154),
34 | geometry_schematic geometry('POLYGON',2154)
35 | )""")
36 |
37 | pcur.execute("""
38 | INSERT INTO epanet.junctions
39 | (id, elevation, printmap, geometry, geometry_schematic)
40 | VALUES
41 | ('0',0,'{1,2,3}',ST_GeometryFromText('POINT(0 0)',2154),
42 | ST_GeometryFromText('POLYGON((-1 -1,1 -1,1 1,-1 1,-1 -1))',2154))""")
43 |
44 | pcur.execute("""
45 | INSERT INTO epanet.junctions
46 | (id, elevation, printmap, geometry, geometry_schematic)
47 | VALUES
48 | ('1',1,'{}',ST_GeometryFromText('POINT(0 1)',2154),
49 | ST_GeometryFromText('POLYGON((0 0,2 0,2 2,0 2,0 0))',2154))""")
50 |
51 | pcur.execute("""
52 | CREATE TABLE epanet.pipes (
53 | hid serial PRIMARY KEY,
54 | id varchar,
55 | start_node varchar,
56 | end_node varchar,
57 | length float,
58 | diameter float,
59 | roughness float,
60 | minor_loss_coefficient float,
61 | status varchar,
62 | geometry geometry('LINESTRING',2154)
63 | )""")
64 |
65 | pcur.execute("""
66 | INSERT INTO epanet.pipes
67 | (id, start_node, end_node, length, diameter, geometry)
68 | VALUES
69 | ('0','0','1',1,2,ST_GeometryFromText('LINESTRING(1 0,0 1)',2154))""")
70 |
71 | pcur.commit()
72 | pcur.close()
73 |
74 | versioning.historize( pg_conn_info, 'epanet' )
75 |
76 | pcur = versioning.Db(psycopg2.connect(pg_conn_info))
77 |
78 | pcur.execute("SELECT ST_AsText(geometry), ST_AsText(geometry_schematic) FROM epanet_trunk_rev_head.junctions")
79 | res = pcur.fetchall()
80 | assert( res[0][0] == 'POINT(0 0)' )
81 | assert( res[1][1] == 'POLYGON((0 0,2 0,2 2,0 2,0 0))' )
82 |
83 |
84 | wc = tmp_dir+'/wc_multiple_geometry_test.sqlite'
85 | if os.path.isfile(wc): os.remove(wc)
86 | spversioning = versioning.spatialite(wc, pg_conn_info)
87 | spversioning.checkout( ['epanet_trunk_rev_head.pipes','epanet_trunk_rev_head.junctions'] )
88 |
89 |
90 | scur = versioning.Db( dbapi2.connect(wc) )
91 | scur.execute("UPDATE junctions_view SET GEOMETRY = GeometryFromText('POINT(3 3)',2154) WHERE OGC_FID = 1")
92 | scur.commit()
93 | scur.execute("SELECT * from junctions_view")
94 | print("--------------")
95 | for res in scur.fetchall(): print(res)
96 | scur.close()
97 | spversioning.commit( 'moved a junction' )
98 |
99 | pcur.execute("SELECT ST_AsText(geometry), ST_AsText(geometry_schematic), printmap FROM epanet_trunk_rev_head.junctions ORDER BY versioning_id DESC")
100 | res = pcur.fetchall()
101 | for r in res: print(r)
102 | assert( res[0][0] == 'POINT(3 3)' )
103 | assert( res[0][1] == 'POLYGON((-1 -1,1 -1,1 1,-1 1,-1 -1))' )
104 | assert( res[0][2] == [1, 2, 3] )
105 |
106 | pcur.close()
107 |
108 | if __name__ == "__main__":
109 | if len(sys.argv) != 3:
110 | print("Usage: python3 versioning_base_test.py host pguser")
111 | else:
112 | test(*sys.argv[1:])
113 |
--------------------------------------------------------------------------------
/test/issue_type_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | from versioningDB import versioning
5 | from sqlite3 import dbapi2
6 | import os
7 | import tempfile
8 | import psycopg2
9 |
10 |
11 | def test(host, pguser):
12 |
13 | dbname = "epanet_test_db"
14 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
15 | sql_file = os.path.join(test_data_dir, "epanet_test_db.sql")
16 | tmp_dir = tempfile.gettempdir()
17 |
18 | # create the test database
19 | os.system(f"dropdb --if-exists -h {host} -U {pguser} {dbname}")
20 | os.system(f"createdb -h {host} -U {pguser} {dbname}")
21 | os.system(f"psql -h {host} -U {pguser} {dbname} -f {sql_file}")
22 |
23 | pg_conn_info = f"dbname={dbname} host={host} user={pguser}"
24 |
25 | pcon = psycopg2.connect(pg_conn_info)
26 | pcur = pcon.cursor()
27 | pcur.execute("CREATE TYPE type_example AS ENUM('TEST1', 'TEST2')")
28 | pcur.execute("ALTER TABLE epanet.junctions "
29 | "ADD COLUMN type_field type_example;")
30 | pcon.commit()
31 |
32 | versioning.historize(pg_conn_info, "epanet")
33 |
34 | # try the update
35 | wc = tmp_dir+"/issue_type.sqlite"
36 | if os.path.isfile(wc):
37 | os.remove(wc)
38 |
39 | spversioning = versioning.spatialite(wc, pg_conn_info)
40 | spversioning.checkout(['epanet_trunk_rev_head.junctions'])
41 |
42 | scur = versioning.Db(dbapi2.connect(wc))
43 |
44 | # scur.execute("SELECT * FROM pipes")
45 | # assert( len(scur.fetchall()) == 1 )
46 | scur.execute("UPDATE junctions_view "
47 | "SET type_field = 'TEST1' WHERE OGC_FID = 1")
48 | scur.commit()
49 |
50 | spversioning.commit("test type")
51 |
52 | pcon = psycopg2.connect(pg_conn_info)
53 | pcur = pcon.cursor()
54 | pcur.execute("SELECT type_field FROM epanet.junctions "
55 | "WHERE id = 1 AND trunk_rev_end IS NULL")
56 |
57 | res = pcur.fetchall()
58 | assert(len(res) == 1)
59 | assert(res[0][0] == "TEST1")
60 |
61 |
62 | if __name__ == "__main__":
63 | if len(sys.argv) != 3:
64 | print("Usage: python3 issue_type_test.py host pguser")
65 | else:
66 | test(*sys.argv[1:])
67 |
--------------------------------------------------------------------------------
/test/merge_branch_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import sys
5 | from versioningDB import versioning
6 | import psycopg2
7 | import os
8 |
9 |
10 | def test(host, pguser):
11 | pg_conn_info = "dbname=epanet_test_db host=" + host + " user=" + pguser
12 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
13 |
14 | # create the test database
15 |
16 | os.system("dropdb --if-exists -h " + host +
17 | " -U "+pguser+" epanet_test_db")
18 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_db")
19 | os.system("psql -h " + host + " -U "+pguser +
20 | " epanet_test_db -c 'CREATE EXTENSION postgis'")
21 | os.system("psql -h " + host + " -U "+pguser +
22 | " epanet_test_db -f "+test_data_dir+"/epanet_test_db.sql")
23 | versioning.historize("dbname=epanet_test_db host={} user={}".format(host,pguser), "epanet")
24 |
25 | # branch
26 | versioning.add_branch(pg_conn_info, "epanet", "mybranch", "add 'branch")
27 |
28 | # chechout from branch : epanet_brwcs_rev_head
29 | #tables = ['epanet_trunk_rev_head.junctions','epanet_trunk_rev_head.pipes']
30 | tables = ['epanet_mybranch_rev_head.junctions',
31 | 'epanet_mybranch_rev_head.pipes']
32 | pgversioning = versioning.pgServer(pg_conn_info, 'epanet_brwcs_rev_head')
33 | pgversioning.checkout(tables)
34 |
35 | pcur = versioning.Db(psycopg2.connect(pg_conn_info))
36 |
37 | # insert into epanet_brwcs_rev_head
38 | pcur.execute("INSERT INTO epanet_brwcs_rev_head.pipes_view(id, start_node, end_node, geom) VALUES ('2','1','2',ST_GeometryFromText('LINESTRING(1 1,0 1)',2154))")
39 | pcur.execute("INSERT INTO epanet_brwcs_rev_head.pipes_view(id, start_node, end_node, geom) VALUES ('3','1','2',ST_GeometryFromText('LINESTRING(1 -1,0 1)',2154))")
40 | pcur.execute("DELETE FROM epanet_brwcs_rev_head.pipes_view WHERE id=3")
41 | pcur.commit()
42 |
43 | pgversioning.commit("commit", "postgres")
44 |
45 | versioning.merge(pg_conn_info, "epanet", "mybranch")
46 |
47 | pcur.execute("SELECT max(rev) FROM epanet.revisions")
48 | assert(pcur.fetchone()[0] == 4)
49 |
50 | pcur.execute(
51 | "SELECT rev, commit_msg, branch FROM epanet.revisions WHERE rev=4")
52 | assert(pcur.fetchall() == [
53 | (4, 'Merge branch mybranch into trunk', 'trunk')])
54 |
55 | pcur.execute(
56 | "SELECT versioning_id, trunk_rev_begin, trunk_rev_end, mybranch_rev_begin,mybranch_rev_end FROM epanet.pipes")
57 | assert(pcur.fetchall() == [(1, 1, None, 2, None), (2, 3, None, 3, None)])
58 |
59 | pcur.close()
60 |
61 |
62 | if __name__ == "__main__":
63 | if len(sys.argv) != 3:
64 | print("Usage: python3 merge_branch_test.py host pguser")
65 | else:
66 | test(*sys.argv[1:])
67 |
--------------------------------------------------------------------------------
/test/multiple_geometry_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | from versioningDB import versioning
5 | from sqlite3 import dbapi2
6 | import psycopg2
7 | import os
8 | import tempfile
9 |
10 |
11 | def test(host, pguser):
12 | pg_conn_info = "dbname=epanet_test_db host=" + host + " user=" + pguser
13 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
14 | tmp_dir = tempfile.gettempdir()
15 |
16 | # create the test database
17 |
18 | os.system("dropdb --if-exists -h " + host + " -U "+pguser+" epanet_test_db")
19 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_db")
20 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -c 'CREATE EXTENSION postgis'")
21 |
22 | pcur = versioning.Db(psycopg2.connect(pg_conn_info))
23 | pcur.execute("CREATE SCHEMA epanet")
24 | pcur.execute("""
25 | CREATE TABLE epanet.junctions (
26 | hid serial PRIMARY KEY,
27 | id varchar,
28 | elevation float,
29 | base_demand_flow float,
30 | demand_pattern_id varchar,
31 | geometry geometry('POINT',2154),
32 | geometry_schematic geometry('POLYGON',2154)
33 | )""")
34 |
35 | pcur.execute("""
36 | INSERT INTO epanet.junctions
37 | (id, elevation, geometry, geometry_schematic)
38 | VALUES
39 | ('0',0,ST_GeometryFromText('POINT(0 0)',2154),
40 | ST_GeometryFromText('POLYGON((-1 -1,1 -1,1 1,-1 1,-1 -1))',2154))""")
41 |
42 | pcur.execute("""
43 | INSERT INTO epanet.junctions
44 | (id, elevation, geometry, geometry_schematic)
45 | VALUES
46 | ('1',1,ST_GeometryFromText('POINT(0 1)',2154),
47 | ST_GeometryFromText('POLYGON((0 0,2 0,2 2,0 2,0 0))',2154))""")
48 |
49 | pcur.execute("""
50 | CREATE TABLE epanet.pipes (
51 | hid serial PRIMARY KEY,
52 | id varchar,
53 | start_node varchar,
54 | end_node varchar,
55 | length float,
56 | diameter float,
57 | roughness float,
58 | minor_loss_coefficient float,
59 | status varchar,
60 | geometry geometry('LINESTRING',2154)
61 | )""")
62 |
63 | pcur.execute("""
64 | INSERT INTO epanet.pipes
65 | (id, start_node, end_node, length, diameter, geometry)
66 | VALUES
67 | ('0','0','1',1,2,ST_GeometryFromText('LINESTRING(1 0,0 1)',2154))""")
68 |
69 | pcur.commit()
70 | pcur.close()
71 |
72 | versioning.historize( pg_conn_info, 'epanet' )
73 |
74 | failed = False
75 | try:
76 | versioning.add_branch( pg_conn_info, 'epanet', 'trunk' )
77 | except:
78 | failed = True
79 | assert( failed )
80 |
81 | failed = False
82 | try:
83 | versioning.add_branch( pg_conn_info, 'epanet', 'mybranch', 'message', 'toto' )
84 | except:
85 | failed = True
86 | assert( failed )
87 |
88 | versioning.add_branch( pg_conn_info, 'epanet', 'mybranch', 'test msg' )
89 |
90 |
91 | pcur = versioning.Db(psycopg2.connect(pg_conn_info))
92 | pcur.execute("SELECT * FROM epanet_mybranch_rev_head.junctions")
93 | assert( len(pcur.fetchall()) == 2 )
94 | pcur.execute("SELECT * FROM epanet_mybranch_rev_head.pipes")
95 | assert( len(pcur.fetchall()) == 1 )
96 |
97 | ##versioning.add_revision_view( pg_conn_info, 'epanet', 'mybranch', 2)
98 | ##pcur.execute("SELECT * FROM epanet_mybranch_rev_2.junctions")
99 | ##assert( len(pcur.fetchall()) == 2 )
100 | ##pcur.execute("SELECT * FROM epanet_mybranch_rev_2.pipes")
101 | ##assert( len(pcur.fetchall()) == 1 )
102 |
103 | select_and_where_str = versioning.rev_view_str( pg_conn_info, 'epanet', 'junctions','mybranch', 2)
104 | #print(select_and_where_str[0] + " WHERE " + select_and_where_str[1])
105 | pcur.execute(select_and_where_str[0] + " WHERE " + select_and_where_str[1])
106 | assert( len(pcur.fetchall()) == 2 )
107 | select_and_where_str = versioning.rev_view_str( pg_conn_info, 'epanet', 'pipes','mybranch', 2)
108 | #print(select_and_where_str[0] + " WHERE " + select_and_where_str[1])
109 | pcur.execute(select_and_where_str[0] + " WHERE " + select_and_where_str[1])
110 | assert( len(pcur.fetchall()) == 1 )
111 |
112 | ##pcur.execute("SELECT ST_AsText(geometry), ST_AsText(geometry_schematic) FROM epanet_mybranch_rev_2.junctions")
113 | pcur.execute("SELECT ST_AsText(geometry), ST_AsText(geometry_schematic) FROM epanet.junctions")
114 | res = pcur.fetchall()
115 | assert( res[0][0] == 'POINT(0 0)' )
116 | assert( res[1][1] == 'POLYGON((0 0,2 0,2 2,0 2,0 0))' )
117 |
118 |
119 | wc = os.path.join(tmp_dir, 'wc_multiple_geometry_test.sqlite')
120 | spversioning = versioning.spatialite(wc, pg_conn_info)
121 | if os.path.isfile(wc): os.remove(wc)
122 | spversioning.checkout( ['epanet_trunk_rev_head.pipes','epanet_trunk_rev_head.junctions'] )
123 |
124 |
125 | scur = versioning.Db( dbapi2.connect(wc) )
126 | scur.execute("UPDATE junctions_view SET GEOMETRY = GeometryFromText('POINT(3 3)',2154)")
127 | scur.commit()
128 | scur.close()
129 |
130 | spversioning.commit( 'a commit msg' )
131 |
132 | pcur.execute("SELECT ST_AsText(geometry), ST_AsText(geometry_schematic) FROM epanet_trunk_rev_head.junctions")
133 | res = pcur.fetchall()
134 | for r in res: print(r)
135 | assert( res[0][0] == 'POINT(3 3)' )
136 | assert( res[1][1] == 'POLYGON((0 0,2 0,2 2,0 2,0 0))' )
137 | pcur.close()
138 |
139 | if __name__ == "__main__":
140 | if len(sys.argv) != 3:
141 | print("Usage: python3 versioning_base_test.py host pguser")
142 | else:
143 | test(*sys.argv[1:])
144 |
--------------------------------------------------------------------------------
/test/partial_checkout_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | from versioningDB import versioning
5 | from sqlite3 import dbapi2
6 | import psycopg2
7 | import os
8 | import tempfile
9 |
10 | tmp_dir = tempfile.gettempdir()
11 | sqlite_test_filename = os.path.join(tmp_dir, "partial_checkout_test.sqlite")
12 |
13 |
14 | class PartialCheckoutTest:
15 |
16 | def __init__(self, host, pguser, schema):
17 |
18 | self.schema = schema
19 | self.cur = None
20 | self.con = None
21 | self.versioning = None
22 |
23 | self.pg_conn_info = f"dbname=epanet_test_db host={host} user={pguser}"
24 | self.pg_conn_info_cpy = f"dbname=epanet_test_copy_db host={host} user={pguser}"
25 |
26 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
27 |
28 | # create the test database
29 | os.system(f"dropdb --if-exists -h {host} -U {pguser} epanet_test_db")
30 | os.system(f"dropdb --if-exists -h {host} -U {pguser} epanet_test_copy_db")
31 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_db")
32 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_copy_db")
33 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -f "
34 | + test_data_dir + "/epanet_test_db.sql")
35 |
36 | self.pcon = psycopg2.connect(self.pg_conn_info)
37 | self.pcur = self.pcon.cursor()
38 | for i in range(10):
39 | self.pcur.execute("""
40 | INSERT INTO epanet.junctions
41 | (id, elevation, geom)
42 | VALUES
43 | ('{id}', {elev}, ST_GeometryFromText('POINT({x} {y})',2154));
44 | """.format(
45 | id=i+3,
46 | elev=float(i),
47 | x=float(i+1),
48 | y=float(i+1)
49 | ))
50 | self.pcon.commit()
51 |
52 | versioning.historize(self.pg_conn_info, 'epanet')
53 |
54 | def __del__(self):
55 | if self.con:
56 | self.con.close()
57 |
58 | if self.pcon:
59 | self.pcon.close()
60 |
61 | def checkout(self, tables, feature_list):
62 | self.versioning.checkout(tables, feature_list)
63 |
64 | def test_select(self):
65 |
66 | self.checkout(["epanet_trunk_rev_head.junctions",
67 | "epanet_trunk_rev_head.pipes"], [[1, 2, 3], []])
68 |
69 | self.cur.execute("SELECT elevation from {}.junctions_view".format(
70 | self.schema))
71 | assert([res[0] for res in self.cur.fetchall()] == [0., 1., 0.])
72 |
73 | def test_referenced(self):
74 | """ checkout table, its referenced table and the referenced
75 | features must appear"""
76 |
77 | self.checkout(["epanet_trunk_rev_head.pipes"], [[1]])
78 | self.cur.execute("SELECT id from {}.pipes_view order by id".format(
79 | self.schema))
80 | assert([res[0] for res in self.cur.fetchall()] == [1])
81 |
82 | self.cur.execute("SELECT id from {}.junctions_view order by id".format(
83 | self.schema))
84 | assert([res[0] for res in self.cur.fetchall()] == [1, 2])
85 |
86 | def test_referenced_union(self):
87 | """ checkout table, its referenced table and the referenced
88 | features must appear, plus the one already selected"""
89 |
90 | self.checkout(["epanet_trunk_rev_head.pipes",
91 | "epanet_trunk_rev_head.junctions"], [[1], [6, 7]])
92 | self.cur.execute("SELECT id from {}.pipes_view order by id".format(
93 | self.schema))
94 | assert([res[0] for res in self.cur.fetchall()] == [1])
95 |
96 | self.cur.execute("SELECT id from {}.junctions_view order by id".format(
97 | self.schema))
98 | assert([res[0] for res in self.cur.fetchall()] == [1, 2, 6, 7])
99 |
100 | def test_referencing(self):
101 | """ checkout table, its referencing table and the referencing
102 | features must appear"""
103 |
104 | self.checkout(["epanet_trunk_rev_head.junctions"], [[1, 6, 7]])
105 | self.cur.execute("SELECT id from {}.junctions_view order by id".format(
106 | self.schema))
107 | assert([res[0] for res in self.cur.fetchall()] == [1, 6, 7])
108 |
109 | self.cur.execute("SELECT id from {}.pipes_view order by id".format(
110 | self.schema))
111 | assert([res[0] for res in self.cur.fetchall()] == [1])
112 |
113 | def test_duplicate_pkey_on_insert(self):
114 |
115 | self.checkout(["epanet_trunk_rev_head.junctions"], [[6, 7, 8]])
116 | self.cur.execute("SELECT id from {}.junctions_view order by id".format(
117 | self.schema))
118 | assert([res[0] for res in self.cur.fetchall()] == [6, 7, 8])
119 |
120 | self.cur.execute("""
121 | INSERT INTO {}.junctions_view (id, elevation, geom) VALUES
122 | (4, 40, ST_GeometryFromText('POINT(4 4)',2154)),
123 | (5, 50, ST_GeometryFromText('POINT(5 5)',2154))""".format(
124 | self.schema))
125 | self.con.commit()
126 |
127 | self.cur.execute("SELECT id from {}.junctions_view order by id".format(
128 | self.schema))
129 | assert([res[0] for res in self.cur.fetchall()] == [4, 5, 6, 7, 8])
130 |
131 | self.con.rollback()
132 |
133 | try:
134 | self.versioning.commit("commit msg")
135 | assert(False and "Commit must fail unique constraint")
136 | except RuntimeError as e:
137 | print(e)
138 | self.con.rollback()
139 |
140 | # Check we have only one current instance of feature with id 5
141 | # and one with id 4 after commit
142 | self.pcur.execute("""SELECT COUNT(*)
143 | FROM epanet.junctions WHERE id = 5 AND trunk_rev_end IS NULL """)
144 | assert(self.pcur.fetchone()[0] == 1)
145 |
146 | self.pcur.execute("""SELECT COUNT(*)
147 | FROM epanet.junctions WHERE id = 4 AND trunk_rev_end IS NULL """)
148 | assert(self.pcur.fetchone()[0] == 1)
149 |
150 | def test_duplicate_pkey_on_update(self):
151 |
152 | self.checkout(["epanet_trunk_rev_head.junctions"], [[6, 7, 8]])
153 | self.cur.execute("SELECT id from {}.junctions_view order by id".format(
154 | self.schema))
155 | assert([res[0] for res in self.cur.fetchall()] == [6, 7, 8])
156 |
157 | # Modify id with existing unique id
158 | self.cur.execute("""
159 | UPDATE {}.junctions_view SET id = 3 WHERE id = 6""".format(
160 | self.schema))
161 | self.cur.execute("""
162 | UPDATE {}.junctions_view SET id = 4 WHERE id = 7""".format(
163 | self.schema))
164 | self.con.commit()
165 |
166 | # Modify anything but id
167 | self.cur.execute("""
168 | UPDATE {}.junctions_view SET elevation = 80 WHERE id = 8""".format(
169 | self.schema))
170 | self.con.commit()
171 |
172 | self.cur.execute("SELECT id from {}.junctions_view order by id".format(
173 | self.schema))
174 | assert([res[0] for res in self.cur.fetchall()] == [3, 4, 8])
175 |
176 | self.con.rollback()
177 |
178 | try:
179 | self.versioning.commit("commit msg")
180 | assert(False and "Commit must fail unique constraint")
181 | except RuntimeError as e:
182 | print(e)
183 | self.con.rollback()
184 |
185 | # Check we have only one current instance of feature with id 3
186 | # and one with id 4 after commit
187 | self.pcur.execute("""SELECT COUNT(*)
188 | FROM epanet.junctions WHERE id = 3 AND trunk_rev_end IS NULL """)
189 | assert(self.pcur.fetchone()[0] == 1)
190 |
191 | self.pcur.execute("""SELECT COUNT(*)
192 | FROM epanet.junctions WHERE id = 4 AND trunk_rev_end IS NULL """)
193 | assert(self.pcur.fetchone()[0] == 1)
194 |
195 |
196 | class SpatialitePartialCheckoutTest(PartialCheckoutTest):
197 |
198 | def __init__(self, host, pguser):
199 | super().__init__(host, pguser, "main")
200 |
201 | if os.path.isfile(sqlite_test_filename):
202 | os.remove(sqlite_test_filename)
203 |
204 | self.versioning = versioning.spatialite(sqlite_test_filename,
205 | self.pg_conn_info)
206 |
207 | def checkout(self, tables, feature_list):
208 |
209 | super().checkout(tables, feature_list)
210 |
211 | self.con = dbapi2.connect(sqlite_test_filename)
212 | self.con.enable_load_extension(True)
213 | self.con.execute("SELECT load_extension('mod_spatialite')")
214 | self.cur = self.con.cursor()
215 |
216 |
217 | class PgServerPartialCheckoutTest(PartialCheckoutTest):
218 |
219 | def __init__(self, host, pguser):
220 |
221 | wc_schema = "epanet_workingcopy"
222 | super().__init__(host, pguser, wc_schema)
223 |
224 | self.versioning = versioning.pgServer(self.pg_conn_info,
225 | wc_schema)
226 |
227 | def checkout(self, tables, feature_list):
228 | super().checkout(tables, feature_list)
229 | self.con = self.pcon
230 | self.cur = self.pcur
231 |
232 |
233 | class PgLocalPartialCheckoutTest(PartialCheckoutTest):
234 |
235 | def __init__(self, host, pguser):
236 |
237 | wc_schema = "epanet_workingcopy"
238 | super().__init__(host, pguser, wc_schema)
239 |
240 | self.versioning = versioning.pgLocal(
241 | self.pg_conn_info, wc_schema, self.pg_conn_info_cpy)
242 |
243 | def checkout(self, tables, feature_list):
244 | super().checkout(tables, feature_list)
245 |
246 | self.con = psycopg2.connect(self.pg_conn_info_cpy)
247 | self.cur = self.con.cursor()
248 |
249 |
250 | def test(host, pguser):
251 |
252 | # loop on the 3 ways of checkout (sqlite, pgserver, pglocal)
253 | for test_class in [SpatialitePartialCheckoutTest,
254 | PgLocalPartialCheckoutTest,
255 | PgServerPartialCheckoutTest]:
256 |
257 | test = test_class(host, pguser)
258 | test.test_select()
259 | del test
260 |
261 | test = test_class(host, pguser)
262 | test.test_referenced()
263 | del test
264 |
265 | test = test_class(host, pguser)
266 | test.test_referenced_union()
267 | del test
268 |
269 | test = test_class(host, pguser)
270 | test.test_referencing()
271 | del test
272 |
273 | test = test_class(host, pguser)
274 | test.test_duplicate_pkey_on_insert()
275 | del test
276 |
277 | test = test_class(host, pguser)
278 | test.test_duplicate_pkey_on_update()
279 | del test
280 |
281 |
282 | if __name__ == "__main__":
283 | if len(sys.argv) != 3:
284 | print("Usage: python3 versioning_base_test.py host pguser")
285 | else:
286 | test(*sys.argv[1:])
287 |
--------------------------------------------------------------------------------
/test/plugin_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from PyQt5.QtWidgets import QMessageBox
4 | from qgis.core import (QgsApplication, QgsVectorLayer, QgsProject)
5 | import sys
6 | import os
7 | import plugin
8 | import psycopg2
9 |
10 | dbname = "epanet_test_db"
11 | wc_dbname = "epanet_test_wc_db"
12 | schema = "epanet"
13 | wcs = "epanet_wc"
14 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
15 | sql_file = os.path.join(test_data_dir, "epanet_test_db.sql")
16 |
17 | # Monkey path GUI stuff
18 | # This not ideal to monkey patch too much things. It will be better to put
19 | # most of the GUI things in methods and to monkey patch these methods like
20 | # it's done with selectDatabase
21 |
22 |
23 | class EmptyObject(object):
24 | def __getattr__(self, name):
25 | return EmptyObject()
26 |
27 | def __call__(self, *args):
28 | return EmptyObject()
29 |
30 |
31 | def generate_tempfile(*args):
32 | return ("/tmp/plugin_test_file.sqlite", None)
33 |
34 |
35 | def warning(*args):
36 | print(args[2])
37 | return QMessageBox.Ok
38 |
39 |
40 | class QLineEdit:
41 |
42 | def __init__(*args):
43 | pass
44 |
45 | def text(self):
46 | return wcs
47 |
48 |
49 | def return_wc_database():
50 | return wc_dbname
51 |
52 |
53 | iface = EmptyObject()
54 | iface.mainWindow = EmptyObject()
55 | iface.layerTreeView = EmptyObject()
56 | plugin.QDialog = EmptyObject()
57 | plugin.uic.loadUi = EmptyObject()
58 | plugin.QFileDialog.getSaveFileName = generate_tempfile
59 | plugin.QMessageBox.warning = warning
60 | plugin.QVBoxLayout = EmptyObject()
61 | plugin.QDialogButtonBox = EmptyObject()
62 | plugin.QDialogButtonBox.Cancel = 0
63 | plugin.QDialogButtonBox.Ok = 0
64 | plugin.QLineEdit = QLineEdit
65 |
66 |
67 | class PluginTest:
68 |
69 | def __init__(self, host, pguser):
70 |
71 | self.host = host
72 | self.pguser = pguser
73 |
74 | # create the test database
75 | os.system(f"psql -h {host} -U {pguser} {dbname} -f {sql_file}")
76 |
77 | pg_conn_info = f"dbname={dbname} host={host} user={pguser}"
78 | pcon = psycopg2.connect(pg_conn_info)
79 | pcur = pcon.cursor()
80 | pcur.execute("""
81 | INSERT INTO epanet.junctions (id, elevation, geom)
82 | VALUES (33, 30, ST_GeometryFromText('POINT(3 3)',2154));
83 | """)
84 | pcur.execute("""
85 | INSERT INTO epanet.junctions (id, elevation, geom)
86 | VALUES (44, 40, ST_GeometryFromText('POINT(4 4)',2154));
87 | """)
88 | pcon.commit()
89 | pcon.close()
90 |
91 | # Initialize project
92 | layer_source = f"""host='{host}' dbname='{dbname}' user='{pguser}'
93 | srid=2154 table="epanet"."junctions" (geom) sql="""
94 | j_layer = QgsVectorLayer(layer_source, "junctions", "postgres")
95 | assert(j_layer and j_layer.isValid() and
96 | j_layer.featureCount() == 4)
97 | assert(QgsProject.instance().addMapLayer(j_layer, False))
98 |
99 | root = QgsProject.instance().layerTreeRoot()
100 | group = root.addGroup("epanet_group")
101 | group.addLayer(j_layer)
102 |
103 | self.versioning_plugin = plugin.Plugin(iface)
104 | self.versioning_plugin.current_layers = [j_layer]
105 | self.versioning_plugin.current_group = group
106 |
107 | self.historize()
108 |
109 | def historize(self):
110 |
111 | root = QgsProject.instance().layerTreeRoot()
112 |
113 | # historize
114 | self.versioning_plugin.historize()
115 | assert(len(root.children()) == 1)
116 | group = root.children()[0]
117 | assert(group.name() == "trunk revision head")
118 | j_layer = group.children()[0].layer()
119 | assert(j_layer.name() == "junctions")
120 |
121 | self.versioning_plugin.current_layers = [j_layer]
122 | self.versioning_plugin.current_group = group
123 |
124 | def test_checkout(self):
125 |
126 | root = QgsProject.instance().layerTreeRoot()
127 |
128 | # checkout
129 | self.checkout()
130 | assert(len(root.children()) == 2)
131 | group = root.children()[1]
132 | assert(group.name() == self.get_working_name())
133 |
134 | j_layer = group.children()[0].layer()
135 | assert(j_layer.name() == "junctions")
136 | assert(j_layer.featureCount() == 4)
137 |
138 | root.takeChild(group)
139 |
140 | def test_checkout_w_selected_features(self):
141 |
142 | root = QgsProject.instance().layerTreeRoot()
143 |
144 | # select the 2 last features
145 | group = root.children()[0]
146 | j_layer = group.children()[0].layer()
147 | assert(j_layer.name() == "junctions")
148 |
149 | for feat in j_layer.getFeatures("id > 30"):
150 | j_layer.select(feat.id())
151 |
152 | # checkout
153 | self.checkout()
154 | assert(len(root.children()) == 2)
155 | group = root.children()[1]
156 | assert(group.name() == self.get_working_name())
157 |
158 | j_layer = group.children()[0].layer()
159 | assert(j_layer.name() == "junctions")
160 | fids = [feature['id'] for feature in j_layer.getFeatures()]
161 | print(f"fids={fids}")
162 | assert(fids == [33, 44])
163 |
164 | root.takeChild(group)
165 |
166 | def __del__(self):
167 | QgsProject.instance().clear()
168 |
169 | for schema in ['epanet', 'epanet_trunk_rev_head']:
170 | os.system("psql -h {} -U {} {} -c 'DROP SCHEMA {} CASCADE'".format(
171 | self.host, self.pguser, dbname, schema))
172 |
173 | def checkout(self):
174 | raise Exception("Must be overrided")
175 |
176 | def get_working_name(self):
177 | raise Exception("Must be overrided")
178 |
179 |
180 | class SpatialitePluginTest(PluginTest):
181 |
182 | def __init__(self, host, pguser):
183 | super().__init__(host, pguser)
184 |
185 | def checkout(self):
186 | self.versioning_plugin.checkout()
187 |
188 | def get_working_name(self):
189 | return "working copy"
190 |
191 |
192 | class PgServerPluginTest(PluginTest):
193 |
194 | def __init__(self, host, pguser):
195 | super().__init__(host, pguser)
196 |
197 | def checkout(self):
198 | self.versioning_plugin.checkout_pg()
199 |
200 | def get_working_name(self):
201 | return wcs
202 |
203 | def __del__(self):
204 | super().__del__()
205 | os.system("psql -h {} -U {} {} "
206 | "-c 'DROP SCHEMA {} CASCADE'".format(
207 | self.host, self.pguser, dbname, self.get_working_name()))
208 |
209 |
210 | class PgLocalPluginTest(PluginTest):
211 |
212 | def __init__(self, host, pguser):
213 |
214 | super().__init__(host, pguser)
215 |
216 | # Monkey patch the GUI to return database name
217 | self.versioning_plugin.selectDatabase = return_wc_database
218 |
219 | def checkout(self):
220 | self.versioning_plugin.checkout_pg_distant()
221 |
222 | def get_working_name(self):
223 | return "epanet_trunk_rev_head"
224 |
225 | def __del__(self):
226 | super().__del__()
227 | os.system("psql -h {} -U {} {} "
228 | "-c 'DROP SCHEMA {} CASCADE'".format(
229 | self.host, self.pguser, wc_dbname,
230 | self.get_working_name()))
231 |
232 |
233 | def test(host, pguser):
234 |
235 | # create the test database
236 | os.system(f"dropdb --if-exists -h {host} -U {pguser} {wc_dbname}")
237 | os.system(f"createdb -h {host} -U {pguser} {wc_dbname}")
238 | os.system(f"dropdb --if-exists -h {host} -U {pguser} {dbname}")
239 | os.system(f"createdb -h {host} -U {pguser} {dbname}")
240 |
241 | qgs = QgsApplication([], False)
242 | qgs.initQgis()
243 |
244 | for test_class in [SpatialitePluginTest,
245 | PgLocalPluginTest,
246 | PgServerPluginTest]:
247 |
248 | test = test_class(host, pguser)
249 | test.test_checkout()
250 | del test
251 |
252 | test = test_class(host, pguser)
253 | test.test_checkout_w_selected_features()
254 | del test
255 |
256 | qgs.exitQgis()
257 |
258 |
259 | if __name__ == "__main__":
260 | if len(sys.argv) != 3:
261 | print("Usage: python3 versioning_base_test.py host pguser")
262 | else:
263 | test(*sys.argv[1:])
264 |
--------------------------------------------------------------------------------
/test/posgres_working_copy_bug_in_conflict_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | from versioningDB import versioning
5 | import psycopg2
6 | import os
7 |
8 |
9 | def prtTab( cur, tab ):
10 | print("--- ",tab," ---")
11 | cur.execute("SELECT versioning_id, trunk_rev_begin, trunk_rev_end, trunk_parent, trunk_child, length FROM "+tab)
12 | for r in cur.fetchall():
13 | t = []
14 | for i in r: t.append(str(i))
15 | print('\t| '.join(t))
16 |
17 | def prtHid( cur, tab ):
18 | print("--- ",tab," ---")
19 | cur.execute("SELECT versioning_id FROM "+tab)
20 | for [r] in cur.fetchall(): print(r)
21 |
22 | def test(host, pguser):
23 | pg_conn_info = "dbname=epanet_test_db host=" + host + " user=" + pguser
24 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
25 |
26 | # create the test database
27 |
28 | for resolution in ['theirs','mine']:
29 | os.system("dropdb --if-exists -h " + host + " -U "+pguser+" epanet_test_db")
30 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_db")
31 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -c 'CREATE EXTENSION postgis'")
32 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -f "+test_data_dir+"/epanet_test_db.sql")
33 | versioning.historize("dbname=epanet_test_db host={} user={}".format(host,pguser), "epanet")
34 |
35 | pcur = versioning.Db(psycopg2.connect(pg_conn_info))
36 |
37 | tables = ['epanet_trunk_rev_head.junctions', 'epanet_trunk_rev_head.pipes']
38 | pgversioning1 = versioning.pgServer(pg_conn_info, 'wc1')
39 | pgversioning2 = versioning.pgServer(pg_conn_info, 'wc2')
40 | pgversioning1.checkout(tables)
41 | pgversioning2.checkout(tables)
42 | print("checkout done")
43 |
44 | pcur.execute("UPDATE wc1.pipes_view SET length = 4 WHERE versioning_id = 1")
45 | prtTab( pcur, "wc1.pipes_diff")
46 | pcur.commit()
47 | #pcur.close()
48 | pgversioning1.commit("msg1")
49 |
50 | #pcur = versioning.Db(psycopg2.connect(pg_conn_info))
51 |
52 | print("commited")
53 | pcur.execute("UPDATE wc2.pipes_view SET length = 5 WHERE versioning_id = 1")
54 | prtTab( pcur, "wc2.pipes_diff")
55 | pcur.commit()
56 | pgversioning2.update()
57 | print("updated")
58 | prtTab( pcur, "wc2.pipes_diff")
59 | prtTab( pcur, "wc2.pipes_conflicts")
60 |
61 | pcur.execute("SELECT COUNT(*) FROM wc2.pipes_conflicts WHERE origin = 'mine'")
62 | assert( 1 == pcur.fetchone()[0] )
63 | pcur.execute("SELECT COUNT(*) FROM wc2.pipes_conflicts WHERE origin = 'theirs'")
64 | assert( 1 == pcur.fetchone()[0] )
65 |
66 | pcur.execute("DELETE FROM wc2.pipes_conflicts WHERE origin = '"+resolution+"'")
67 | prtTab( pcur, "wc2.pipes_conflicts")
68 |
69 | pcur.execute("SELECT COUNT(*) FROM wc2.pipes_conflicts")
70 | assert( 0 == pcur.fetchone()[0] )
71 | pcur.close()
72 |
73 |
74 | if __name__ == "__main__":
75 | if len(sys.argv) != 3:
76 | print("Usage: python3 versioning_base_test.py host pguser")
77 | else:
78 | test(*sys.argv[1:])
79 |
--------------------------------------------------------------------------------
/test/posgres_working_copy_bug_in_view_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | from versioningDB import versioning
5 | import psycopg2
6 | import os
7 |
8 |
9 | def prtTab( cur, tab ):
10 | print("--- ",tab," ---")
11 | cur.execute("SELECT versioning_id, trunk_rev_begin, trunk_rev_end, trunk_parent, trunk_child, length FROM "+tab)
12 | for r in cur.fetchall():
13 | t = []
14 | for i in r: t.append(str(i))
15 | print('\t| '.join(t))
16 |
17 | def prtHid( cur, tab ):
18 | print("--- ",tab," ---")
19 | cur.execute("SELECT versioning_id FROM "+tab)
20 | for [r] in cur.fetchall(): print(r)
21 |
22 | def test(host, pguser):
23 | pg_conn_info = "dbname=epanet_test_db host=" + host + " user=" + pguser
24 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
25 |
26 | # create the test database
27 | os.system("dropdb --if-exists -h " + host + " -U "+pguser+" epanet_test_db")
28 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_db")
29 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -c 'CREATE EXTENSION postgis'")
30 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -f "+test_data_dir+"/epanet_test_db.sql")
31 | versioning.historize("dbname=epanet_test_db host={} user={}".format(host,pguser), "epanet")
32 |
33 | # checkout
34 | pgversioning = versioning.pgServer(pg_conn_info, 'epanet_working_copy')
35 | pgversioning.checkout(['epanet_trunk_rev_head.junctions','epanet_trunk_rev_head.pipes'])
36 |
37 | pcur = versioning.Db(psycopg2.connect(pg_conn_info))
38 |
39 | pcur.execute("UPDATE epanet_working_copy.pipes_view SET length = 4 WHERE id = 1")
40 | prtTab(pcur, 'epanet_working_copy.pipes_diff')
41 |
42 | prtHid( pcur, 'epanet_working_copy.pipes_view')
43 | pcur.execute("SElECT COUNT(id) FROM epanet_working_copy.pipes_view")
44 | assert(1 == pcur.fetchone()[0])
45 |
46 | if __name__ == "__main__":
47 | if len(sys.argv) != 3:
48 | print("Usage: python3 versioning_base_test.py host pguser")
49 | else:
50 | test(*sys.argv[1:])
51 |
--------------------------------------------------------------------------------
/test/posgres_working_copy_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | from versioningDB import versioning
5 | import psycopg2
6 | import os
7 |
8 |
9 | def prtTab( cur, tab ):
10 | print("--- ",tab," ---")
11 | cur.execute("SELECT versioning_id, trunk_rev_begin, trunk_rev_end, trunk_parent, trunk_child, length FROM "+tab)
12 | for r in cur.fetchall():
13 | t = []
14 | for i in r: t.append(str(i))
15 | print('\t| '.join(t))
16 |
17 | def prtHid( cur, tab ):
18 | print("--- ",tab," ---")
19 | cur.execute("SELECT versioning_id FROM "+tab)
20 | for [r] in cur.fetchall(): print(r)
21 |
22 | def test(host, pguser):
23 | pg_conn_info = "dbname=epanet_test_db host=" + host + " user=" + pguser
24 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
25 |
26 | # create the test database
27 |
28 | os.system("dropdb --if-exists -h " + host + " -U "+pguser+" epanet_test_db")
29 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_db")
30 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -c 'CREATE EXTENSION postgis'")
31 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -f "+test_data_dir+"/epanet_test_db.sql")
32 | versioning.historize("dbname=epanet_test_db host={} user={}".format(host,pguser), "epanet")
33 |
34 | # chechout
35 | #tables = ['epanet_trunk_rev_head.junctions','epanet_trunk_rev_head.pipes']
36 | tables = ['epanet_trunk_rev_head.junctions', 'epanet_trunk_rev_head.pipes']
37 | pgversioning1 = versioning.pgServer(pg_conn_info, 'epanet_working_copy')
38 | pgversioning2 = versioning.pgServer(pg_conn_info, 'epanet_working_copy_cflt')
39 | pgversioning1.checkout(tables)
40 |
41 | pgversioning2.checkout(tables)
42 |
43 | pcur = versioning.Db(psycopg2.connect(pg_conn_info))
44 |
45 |
46 | pcur.execute("INSERT INTO epanet_working_copy.pipes_view(id, start_node, end_node, geom) VALUES ('2','1','2',ST_GeometryFromText('LINESTRING(1 1,0 1)',2154))")
47 | pcur.execute("INSERT INTO epanet_working_copy.pipes_view(id, start_node, end_node, geom) VALUES ('3','1','2',ST_GeometryFromText('LINESTRING(1 -1,0 1)',2154))")
48 | pcur.commit()
49 |
50 |
51 | prtHid(pcur, 'epanet_working_copy.pipes_view')
52 |
53 | pcur.execute("SELECT versioning_id FROM epanet_working_copy.pipes_view")
54 | assert( len(pcur.fetchall()) == 3 )
55 | pcur.execute("SELECT versioning_id FROM epanet_working_copy.pipes_diff")
56 | assert( len(pcur.fetchall()) == 2 )
57 | pcur.execute("SELECT versioning_id FROM epanet.pipes")
58 | assert( len(pcur.fetchall()) == 1 )
59 |
60 |
61 | prtTab(pcur, 'epanet.pipes')
62 | prtTab(pcur, 'epanet_working_copy.pipes_diff')
63 | pcur.execute("UPDATE epanet_working_copy.pipes_view SET length = 4 WHERE versioning_id = 1")
64 | prtTab(pcur, 'epanet_working_copy.pipes_diff')
65 | pcur.execute("UPDATE epanet_working_copy.pipes_view SET length = 5 WHERE versioning_id = 4")
66 | prtTab(pcur, 'epanet_working_copy.pipes_diff')
67 |
68 | pcur.execute("DELETE FROM epanet_working_copy.pipes_view WHERE versioning_id = 4")
69 | prtTab(pcur, 'epanet_working_copy.pipes_diff')
70 | pcur.commit()
71 |
72 | pgversioning1.commit("test commit msg")
73 | prtTab(pcur, 'epanet.pipes')
74 |
75 | pcur.execute("SELECT trunk_rev_end FROM epanet.pipes WHERE versioning_id = 1")
76 | assert( 1 == pcur.fetchone()[0] )
77 | pcur.execute("SELECT COUNT(*) FROM epanet.pipes WHERE trunk_rev_begin = 2")
78 | assert( 2 == pcur.fetchone()[0] )
79 |
80 |
81 | # modify the second working copy to create conflict
82 | prtTab(pcur, 'epanet.pipes')
83 | pcur.execute("SELECT * FROM epanet_working_copy_cflt.initial_revision")
84 | print('-- epanet_working_copy_cflt.initial_revision ---')
85 | for r in pcur.fetchall(): print(r)
86 |
87 | prtHid(pcur, 'epanet_working_copy_cflt.pipes_view')
88 | prtTab(pcur, 'epanet_working_copy_cflt.pipes_diff')
89 | pcur.execute("UPDATE epanet_working_copy_cflt.pipes_view SET length = 8 WHERE versioning_id = 1")
90 | pcur.commit()
91 | prtTab(pcur, 'epanet.pipes')
92 | prtTab(pcur, 'epanet_working_copy_cflt.pipes_diff')
93 | pcur.execute("SELECT COUNT(*) FROM epanet_working_copy_cflt.pipes_diff")
94 | for l in pcur.con.notices: print(l)
95 | assert( 2 == pcur.fetchone()[0] )
96 |
97 |
98 | pcur.execute("INSERT INTO epanet_working_copy_cflt.pipes_view(id, start_node, end_node, geom) VALUES (4,'1','2',ST_GeometryFromText('LINESTRING(1 -1,0 1)',2154))")
99 | prtTab(pcur, 'epanet_working_copy_cflt.pipes_diff')
100 | pcur.commit()
101 | pgversioning2.update( )
102 | prtTab(pcur, 'epanet_working_copy_cflt.pipes_diff')
103 | prtTab(pcur, 'epanet_working_copy_cflt.pipes_update_diff')
104 |
105 | pcur.execute("SELECT COUNT(*) FROM epanet_working_copy_cflt.pipes_conflicts")
106 | assert( 2 == pcur.fetchone()[0] )
107 | pcur.execute("SELECT COUNT(*) FROM epanet_working_copy_cflt.pipes_conflicts WHERE origin = 'mine'")
108 | assert( 1 == pcur.fetchone()[0] )
109 | pcur.execute("SELECT COUNT(*) FROM epanet_working_copy_cflt.pipes_conflicts WHERE origin = 'theirs'")
110 | assert( 1 == pcur.fetchone()[0] )
111 |
112 | prtTab(pcur, 'epanet_working_copy_cflt.pipes_conflicts')
113 |
114 | pcur.execute("DELETE FROM epanet_working_copy_cflt.pipes_conflicts WHERE origin = 'theirs'")
115 | pcur.execute("SELECT COUNT(*) FROM epanet_working_copy_cflt.pipes_conflicts")
116 | assert( 0 == pcur.fetchone()[0] )
117 | prtTab(pcur, 'epanet_working_copy_cflt.pipes_diff')
118 | prtTab(pcur, 'epanet_working_copy_cflt.pipes_conflicts')
119 | pcur.commit()
120 |
121 | pgversioning2.commit("second test commit msg")
122 |
123 |
124 | pcur.execute("SELECT * FROM epanet_working_copy_cflt.initial_revision")
125 | print('-- epanet_working_copy_cflt.initial_revision ---')
126 | for r in pcur.fetchall(): print(r)
127 |
128 | prtHid(pcur, 'epanet_working_copy_cflt.pipes_view')
129 | prtTab(pcur, 'epanet_working_copy_cflt.pipes_diff')
130 |
131 | pcur.execute("UPDATE epanet_working_copy_cflt.pipes_view SET length = 8")
132 | prtTab(pcur, 'epanet_working_copy_cflt.pipes_diff')
133 | pcur.commit()
134 |
135 | pgversioning2.commit("third test commit msg")
136 |
137 |
138 | prtTab(pcur, 'epanet_working_copy_cflt.pipes_diff')
139 | pcur.execute("UPDATE epanet_working_copy_cflt.pipes_view SET length = 12")
140 | pcur.commit()
141 |
142 | if __name__ == "__main__":
143 | if len(sys.argv) != 3:
144 | print("Usage: python3 versioning_base_test.py host pguser")
145 | else:
146 | test(*sys.argv[1:])
147 |
--------------------------------------------------------------------------------
/test/postgres_distant_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | from versioningDB import versioning
5 | from sqlite3 import dbapi2
6 | import psycopg2
7 | import os
8 | import tempfile
9 |
10 |
11 | def prtTab( cur, tab ):
12 | print("--- ",tab," ---")
13 | cur.execute("SELECT ogc_fid, trunk_rev_begin, trunk_rev_end, trunk_parent, trunk_child, length FROM "+tab)
14 | for r in cur.fetchall():
15 | t = []
16 | for i in r: t.append(str(i))
17 | print('\t| '.join(t))
18 |
19 | def prtHid( cur, tab ):
20 | print("--- ",tab," ---")
21 | cur.execute("SELECT ogc_fid FROM "+tab)
22 | for [r] in cur.fetchall(): print(r)
23 |
24 | def test(host, pguser):
25 |
26 | pg_conn_info = "dbname=epanet_test_db host=" + host + " user=" + pguser
27 | pg_conn_info_cpy = "dbname=epanet_test_copy_db host=" + host + " user=" + pguser
28 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
29 | tmp_dir = tempfile.gettempdir()
30 |
31 | # create the test database
32 |
33 | os.system("dropdb --if-exists -h " + host + " -U "+pguser+" epanet_test_db")
34 | os.system("dropdb --if-exists -h " + host + " -U "+pguser+" epanet_test_copy_db")
35 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_db")
36 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_copy_db")
37 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -f "+test_data_dir+"/epanet_test_db.sql")
38 | versioning.historize("dbname=epanet_test_db host={} user={}".format(host,pguser), "epanet")
39 |
40 | # chechout
41 | #tables = ['epanet_trunk_rev_head.junctions','epanet_trunk_rev_head.pipes']
42 | tables = ['epanet_trunk_rev_head.junctions', 'epanet_trunk_rev_head.pipes']
43 | pgversioning = versioning.pgLocal(pg_conn_info, 'epanet_trunk_rev_head', pg_conn_info_cpy)
44 | pgversioning.checkout(tables)
45 |
46 | pcurcpy = versioning.Db(psycopg2.connect(pg_conn_info_cpy))
47 | pcur = versioning.Db(psycopg2.connect(pg_conn_info))
48 |
49 | pcurcpy.execute("INSERT INTO epanet_trunk_rev_head.pipes_view(id, start_node, end_node, geom) VALUES ('2','1','2',ST_GeometryFromText('LINESTRING(1 1,0 1)',2154))")
50 | pcurcpy.execute("INSERT INTO epanet_trunk_rev_head.pipes_view(id, start_node, end_node, geom) VALUES ('3','1','2',ST_GeometryFromText('LINESTRING(1 -1,0 1)',2154))")
51 | pcurcpy.commit()
52 |
53 |
54 | prtHid(pcurcpy, 'epanet_trunk_rev_head.pipes_view')
55 |
56 | pcurcpy.execute("SELECT * FROM epanet_trunk_rev_head.pipes_view")
57 | assert( len(pcurcpy.fetchall()) == 3 )
58 | pcur.execute("SELECT * FROM epanet.pipes")
59 | assert( len(pcur.fetchall()) == 1 )
60 | pgversioning.commit('INSERT')
61 | pcur.execute("SELECT * FROM epanet.pipes")
62 | assert( len(pcur.fetchall()) == 3 )
63 |
64 | pcurcpy.execute("UPDATE epanet_trunk_rev_head.pipes_view SET start_node = '2' WHERE id = '1'")
65 | pcurcpy.commit()
66 | pcurcpy.execute("SELECT * FROM epanet_trunk_rev_head.pipes_view")
67 | assert( len(pcurcpy.fetchall()) == 3 )
68 | pcur.execute("SELECT * FROM epanet.pipes")
69 | assert( len(pcur.fetchall())== 3 )
70 | pgversioning.commit('UPDATE')
71 | pcur.execute("SELECT * FROM epanet.pipes")
72 | assert( len(pcur.fetchall()) == 4 )
73 |
74 | pcurcpy.execute("DELETE FROM epanet_trunk_rev_head.pipes_view WHERE id = '2'")
75 | pcurcpy.commit()
76 | pcurcpy.execute("SELECT * FROM epanet_trunk_rev_head.pipes_view")
77 | assert( len(pcurcpy.fetchall()) == 2 )
78 | pcur.execute("SELECT * FROM epanet.pipes")
79 | assert( len(pcur.fetchall()) == 4 )
80 | pgversioning.commit('DELETE')
81 | pcur.execute("SELECT * FROM epanet.pipes")
82 | assert( len(pcur.fetchall()) == 4 )
83 |
84 | sqlite_test_filename1 = os.path.join(tmp_dir, "versioning_base_test1.sqlite")
85 | if os.path.isfile(sqlite_test_filename1): os.remove(sqlite_test_filename1)
86 | spversioning1 = versioning.spatialite(sqlite_test_filename1, pg_conn_info)
87 | spversioning1.checkout( ['epanet_trunk_rev_head.pipes','epanet_trunk_rev_head.junctions'] )
88 | scon = dbapi2.connect(sqlite_test_filename1)
89 | scon.enable_load_extension(True)
90 | scon.execute("SELECT load_extension('mod_spatialite')")
91 | scur = scon.cursor()
92 | scur.execute("INSERT INTO pipes_view(id, start_node, end_node, geom) VALUES (4, 1, 2,ST_GeometryFromText('LINESTRING(2 0, 0 2)',2154))")
93 | scon.commit()
94 | spversioning1.commit("sp commit")
95 |
96 | pgversioning.update( )
97 | pcur.execute("SELECT * FROM epanet.pipes")
98 | assert( len(pcur.fetchall()) == 5 )
99 | pcurcpy.execute("SELECT * FROM epanet_trunk_rev_head.pipes")
100 | assert( len(pcurcpy.fetchall()) == 5 )
101 |
102 | pcur.execute("SELECT * FROM epanet_trunk_rev_head.pipes")
103 | assert( len(pcur.fetchall()) == 3 )
104 | pcurcpy.execute("SELECT * FROM epanet_trunk_rev_head.pipes_view")
105 | assert( len(pcurcpy.fetchall()) == 3 )
106 |
107 | pcur.execute("SELECT versioning_id FROM epanet_trunk_rev_head.pipes ORDER BY versioning_id")
108 | ret = pcur.fetchall()
109 |
110 | assert([i[0] for i in ret] == [3, 4, 5])
111 | pcurcpy.execute("SELECT ogc_fid FROM epanet_trunk_rev_head.pipes_view ORDER BY ogc_fid")
112 | ret = pcurcpy.fetchall()
113 | assert([i[0] for i in ret] == [3, 4, 5])
114 |
115 | pcurcpy.execute("INSERT INTO epanet_trunk_rev_head.pipes_view(id, start_node, end_node, geom) VALUES (5,'1','2',ST_GeometryFromText('LINESTRING(3 2,0 1)',2154))")
116 | pcurcpy.commit()
117 | pgversioning.commit('INSERT AFTER UPDATE')
118 |
119 |
120 | pcurcpy.close()
121 | pcur.close()
122 |
123 | if __name__ == "__main__":
124 | if len(sys.argv) != 3:
125 | print("Usage: python3 postgres_distant_test.py host pguser")
126 | else:
127 | test(*sys.argv[1:])
128 |
--------------------------------------------------------------------------------
/test/postgres_distant_uuid_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | from versioningDB import versioning
5 | import psycopg2
6 | import os
7 | import tempfile
8 |
9 |
10 | def prtTab( cur, tab ):
11 | print("--- ",tab," ---")
12 | cur.execute("SELECT ogc_fid, trunk_rev_begin, trunk_rev_end, trunk_parent, trunk_child, length FROM "+tab)
13 | for r in cur.fetchall():
14 | t = []
15 | for i in r: t.append(str(i))
16 | print('\t| '.join(t))
17 |
18 | def prtHid( cur, tab ):
19 | print("--- ",tab," ---")
20 | cur.execute("SELECT ogc_fid FROM "+tab)
21 | for [r] in cur.fetchall(): print(r)
22 |
23 | def test(host, pguser):
24 | pg_conn_info = "dbname=epanet_test_db host=" + host + " user=" + pguser
25 | pg_conn_info_cpy = "dbname=epanet_test_copy_db host=" + host + " user=" + pguser
26 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
27 | tmp_dir = tempfile.gettempdir()
28 |
29 | # create the test database
30 |
31 | os.system("dropdb --if-exists -h " + host + " -U "+pguser+" epanet_test_db")
32 | os.system("dropdb --if-exists -h " + host + " -U "+pguser+" epanet_test_copy_db")
33 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_db")
34 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_copy_db")
35 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -c 'CREATE EXTENSION postgis'")
36 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_copy_db -c 'CREATE EXTENSION postgis'")
37 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -f "+test_data_dir+"/epanet_test_db.sql")
38 | versioning.historize("dbname=epanet_test_db host={} user={}".format(host,pguser), "epanet")
39 |
40 | # checkout
41 | tables = ['epanet_trunk_rev_head.junctions', 'epanet_trunk_rev_head.pipes']
42 |
43 | pgversioning = versioning.pgLocal(pg_conn_info, 'epanet_trunk_rev_head', pg_conn_info_cpy)
44 | pgversioning.checkout(tables)
45 |
46 |
47 | pcurcpy = versioning.Db(psycopg2.connect(pg_conn_info_cpy))
48 | pcur = versioning.Db(psycopg2.connect(pg_conn_info))
49 |
50 |
51 | pcurcpy.execute("INSERT INTO epanet_trunk_rev_head.pipes_view(id, start_node, end_node, geom) VALUES ('2','1','2',ST_GeometryFromText('LINESTRING(1 1,0 1)',2154))")
52 | pcurcpy.execute("INSERT INTO epanet_trunk_rev_head.pipes_view(id, start_node, end_node, geom) VALUES ('3','1','2',ST_GeometryFromText('LINESTRING(1 -1,0 1)',2154))")
53 | pcurcpy.commit()
54 |
55 |
56 | prtHid(pcurcpy, 'epanet_trunk_rev_head.pipes_view')
57 |
58 | pcurcpy.execute("SELECT * FROM epanet_trunk_rev_head.pipes_view")
59 | assert( len(pcurcpy.fetchall()) == 3 )
60 | pcur.execute("SELECT * FROM epanet.pipes")
61 | assert( len(pcur.fetchall()) == 1 )
62 | pgversioning.commit('INSERT')
63 | pcur.execute("SELECT * FROM epanet.pipes")
64 | assert( len(pcur.fetchall()) == 3 )
65 |
66 | pcurcpy.execute("UPDATE epanet_trunk_rev_head.pipes_view SET start_node = 2 WHERE id = 1")
67 | pcurcpy.commit()
68 | pcurcpy.execute("SELECT * FROM epanet_trunk_rev_head.pipes_view")
69 | assert( len(pcurcpy.fetchall()) == 3 )
70 | pcur.execute("SELECT * FROM epanet.pipes")
71 | assert( len(pcur.fetchall())== 3 )
72 | pgversioning.commit('UPDATE')
73 | pcur.execute("SELECT * FROM epanet.pipes")
74 | assert( len(pcur.fetchall()) == 4 )
75 |
76 | pcurcpy.close()
77 | pcur.close()
78 |
79 | if __name__ == "__main__":
80 | if len(sys.argv) != 3:
81 | print("Usage: python3 postgres_distant_uuid_test.py host pguser")
82 | else:
83 | test(*sys.argv[1:])
84 |
--------------------------------------------------------------------------------
/test/run_tests.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | export DISPLAY=:99
4 | Xvfb :99&
5 |
6 | service postgresql start
7 |
8 | python3 tests.py 127.0.0.1 postgres -v
9 |
--------------------------------------------------------------------------------
/test/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # coding = utf-8
4 |
5 | import os
6 | import sys
7 |
8 | from subprocess import Popen, PIPE
9 |
10 |
11 | def test(host, pguser, verbose=True):
12 | __current_dir = os.path.abspath(os.path.dirname(__file__))
13 | plugin_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
14 |
15 | tests = [os.path.join(__current_dir,file_)
16 | for file_ in os.listdir(__current_dir)
17 | if file_[-8:]=="_test.py"]
18 |
19 | failed = 0
20 | env = os.environ.copy()
21 | env['PYTHONPATH'] = plugin_dir + (":" + env['PYTHONPATH'] if 'PYTHONPATH'
22 | in env else "")
23 | for i, test in enumerate(tests):
24 | sys.stdout.write("% 4d/%d %s %s"%(
25 | i+1, len(tests), test, "."*max(0, (80-len(test)))))
26 | sys.stdout.flush()
27 | child = Popen([sys.executable, test, host, pguser], stdout=PIPE,
28 | stderr=PIPE, env=env)
29 | out, err = child.communicate()
30 | if child.returncode:
31 | sys.stdout.write("failed\n")
32 | if verbose:
33 | sys.stdout.write(err.decode("utf-8"))
34 | failed += 1
35 | else:
36 | sys.stdout.write("ok\n")
37 |
38 | if failed:
39 | sys.stderr.write("%d/%d test failed\n"%(failed, len(tests)))
40 | raise RuntimeError("%d/%d test failed\n"%(failed, len(tests)))
41 | else:
42 | sys.stdout.write("%d/%d test passed (%d%%)\n"%(
43 | len(tests)-failed,
44 | len(tests),
45 | int((100.*(len(tests)-failed))/len(tests))))
46 |
47 |
48 | if __name__=="__main__":
49 | if len(sys.argv) <= 2 or len(sys.argv) > 4:
50 | print("Usage: python3 tests.py HOST PGUSER [-v]")
51 | exit(0)
52 |
53 | verbose = False
54 | if len(sys.argv) == 4 and sys.argv[3] == '-v':
55 | verbose = True
56 |
57 | test(sys.argv[1], sys.argv[2], verbose)
58 |
--------------------------------------------------------------------------------
/test/versioning_base_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | from versioningDB.versioning import diff_rev_view_str
5 | from versioningDB import versioning
6 | from sqlite3 import dbapi2
7 | import psycopg2
8 | import os
9 | import tempfile
10 |
11 |
12 | def test(host, pguser):
13 | pg_conn_info = "dbname=epanet_test_db host=" + host + " user=" + pguser
14 | tmp_dir = tempfile.gettempdir()
15 | test_data_dir = os.path.dirname(os.path.realpath(__file__))
16 |
17 | sqlite_test_filename1 = os.path.join(tmp_dir, "versioning_base_test1.sqlite")
18 | sqlite_test_filename2 = os.path.join(tmp_dir, "versioning_base_test2.sqlite")
19 | sqlite_test_filename3 = os.path.join(tmp_dir, "versioning_base_test3.sqlite")
20 | sqlite_test_filename4 = os.path.join(tmp_dir, "versioning_base_test4.sqlite")
21 | sqlite_test_filename5 = os.path.join(tmp_dir, "versioning_base_test5.sqlite")
22 | if os.path.isfile(sqlite_test_filename1): os.remove(sqlite_test_filename1)
23 | if os.path.isfile(sqlite_test_filename2): os.remove(sqlite_test_filename2)
24 | if os.path.isfile(sqlite_test_filename3): os.remove(sqlite_test_filename3)
25 | if os.path.isfile(sqlite_test_filename4): os.remove(sqlite_test_filename4)
26 | if os.path.isfile(sqlite_test_filename5): os.remove(sqlite_test_filename5)
27 |
28 | # create the test database
29 |
30 | os.system("dropdb --if-exists -h " + host + " -U "+pguser+" epanet_test_db")
31 | os.system("createdb -h " + host + " -U "+pguser+" epanet_test_db")
32 | os.system("psql -h " + host + " -U "+pguser+" epanet_test_db -f "+test_data_dir+"/epanet_test_db.sql")
33 |
34 | versioning.historize("dbname=epanet_test_db host={} user={}".format(host,pguser), "epanet")
35 |
36 | spversioning1 = versioning.spatialite(sqlite_test_filename1, pg_conn_info)
37 | spversioning2 = versioning.spatialite(sqlite_test_filename2, pg_conn_info)
38 | spversioning3 = versioning.spatialite(sqlite_test_filename3, pg_conn_info)
39 | spversioning4 = versioning.spatialite(sqlite_test_filename4, pg_conn_info)
40 | spversioning5 = versioning.spatialite(sqlite_test_filename5, pg_conn_info)
41 | # chechout two tables
42 |
43 | try:
44 | spversioning1.checkout(["epanet_trunk_rev_head.junctions","epanet.pipes"])
45 | assert(False and "checkout from schema withouti suffix _branch_rev_head should not be successfull")
46 | except RuntimeError:
47 | pass
48 |
49 | assert( not os.path.isfile(sqlite_test_filename1) and "sqlite file must not exist at this point" )
50 | spversioning1.checkout(["epanet_trunk_rev_head.junctions","epanet_trunk_rev_head.pipes"])
51 | assert( os.path.isfile(sqlite_test_filename1) and "sqlite file must exist at this point" )
52 |
53 | try:
54 | spversioning1.checkout(["epanet_trunk_rev_head.junctions","epanet_trunk_rev_head.pipes"])
55 | assert(False and "trying to checkout on an existing file must fail")
56 | except RuntimeError:
57 | pass
58 |
59 | # edit one table and commit changes; rev = 2
60 |
61 | scon = dbapi2.connect(sqlite_test_filename1)
62 | scon.enable_load_extension(True)
63 | scon.execute("SELECT load_extension('mod_spatialite')")
64 | scur = scon.cursor()
65 | scur.execute("UPDATE junctions_view SET elevation = '8' WHERE id = '2'")
66 | scon.commit()
67 | scur.execute("SELECT COUNT(*) FROM junctions")
68 | assert( scur.fetchone()[0] == 3 )
69 | scon.close()
70 | spversioning1.commit('first edit commit')
71 | pcon = psycopg2.connect(pg_conn_info)
72 | pcur = pcon.cursor()
73 | pcur.execute("SELECT COUNT(*) FROM epanet.junctions")
74 | assert( pcur.fetchone()[0] == 3 )
75 | pcur.execute("SELECT COUNT(*) FROM epanet.revisions")
76 | assert( pcur.fetchone()[0] == 2 )
77 |
78 | # add revision : edit one table and commit changes; rev = 3
79 |
80 | spversioning2.checkout(["epanet_trunk_rev_head.junctions", "epanet_trunk_rev_head.pipes"])
81 |
82 | scon = dbapi2.connect(sqlite_test_filename2)
83 | scon.enable_load_extension(True)
84 | scon.execute("SELECT load_extension('mod_spatialite')")
85 | scur = scon.cursor()
86 | scur.execute("UPDATE junctions_view SET elevation = '22' WHERE id = '2'")
87 | scon.commit()
88 | #scur.execute("SELECT COUNT(*) FROM junctions")
89 | #assert( scur.fetchone()[0] == 3 )
90 | scon.close()
91 | spversioning2.commit('second edit commit')
92 |
93 | # add revision : insert one junction and commit changes; rev = 4
94 |
95 | spversioning3.checkout(["epanet_trunk_rev_head.junctions"])
96 |
97 | scon = dbapi2.connect(sqlite_test_filename3)
98 | scon.enable_load_extension(True)
99 | scon.execute("SELECT load_extension('mod_spatialite')")
100 | scur = scon.cursor()
101 | scur.execute("INSERT INTO junctions_view(id, elevation, geom) VALUES ('10','100',GeomFromText('POINT(2 0)',2154))")
102 | scon.commit()
103 | #scur.execute("SELECT COUNT(*) FROM junctions")
104 | #assert( scur.fetchone()[0] == 3 )
105 | scon.close()
106 | spversioning3.commit('insert commit')
107 |
108 | # add revision : delete one junction and commit changes; rev = 5
109 |
110 | spversioning4.checkout(["epanet_trunk_rev_head.junctions", "epanet_trunk_rev_head.pipes"])
111 |
112 | scon = dbapi2.connect(sqlite_test_filename4)
113 | scur = scon.cursor()
114 |
115 | # remove pipes so wen can delete referenced junctions
116 | scur.execute("DELETE FROM pipes_view")
117 | scon.commit()
118 | scur.execute("SELECT COUNT(*) FROM pipes_view")
119 | assert(scur.fetchone()[0]==0)
120 |
121 | scur.execute("DELETE FROM junctions_view WHERE id = 1")
122 | scon.commit()
123 | #scur.execute("SELECT COUNT(*) FROM junctions")
124 | #assert( scur.fetchone()[0] == 3 )
125 | scon.close()
126 | spversioning4.commit('delete id=1 commit')
127 |
128 | select_str = diff_rev_view_str(pg_conn_info, 'epanet', 'junctions','trunk', 1,2)
129 | pcur.execute(select_str)
130 | res = pcur.fetchall()
131 | assert(res[0][0] == 'u')
132 | #print("fetchall 1 vs 2 = " + str(res))
133 | #fetchall 1 vs 2 = [
134 | #('u', 3, '1', 8.0, None, None, '01010000206A0800000000000000000000000000000000F03F', 2, 2, 2, 4)]
135 |
136 | select_str = diff_rev_view_str(pg_conn_info, 'epanet', 'junctions','trunk', 1,3)
137 | pcur.execute(select_str)
138 |
139 | res = pcur.fetchall()
140 | assert(res[0][0] == 'i')
141 | assert(res[1][0] == 'u')
142 | #print("fetchall 1 vs 3 = " + str(res))
143 | #fetchall 1 vs 3 = [
144 | #('u', 4, '1', 22.0, None, None, '01010000206A0800000000000000000000000000000000F03F', 3, None, 3, None),
145 | #('i', 3, '1', 8.0, None, None, '01010000206A0800000000000000000000000000000000F03F', 2, 2, 2, 4)]
146 |
147 | select_str = diff_rev_view_str(pg_conn_info, 'epanet', 'junctions','trunk', 1,4)
148 | pcur.execute(select_str)
149 | res = pcur.fetchall()
150 | assert(res[0][0] == 'i')
151 | assert(res[1][0] == 'i')
152 | assert(res[2][0] == 'u')
153 | assert(res[3][0] == 'a') # object is in intermediate state; will be deleted in rev 5
154 | #print("fetchall 1 vs 4 = " + str(res))
155 | #fetchall 1 vs 4 = [
156 | #('u', 4, '1', 22.0, None, None, '01010000206A0800000000000000000000000000000000F03F', 3, None, 3, None),
157 | #('i', 3, '1', 8.0, None, None, '01010000206A0800000000000000000000000000000000F03F', 2, 2, 2, 4),
158 | #('a', 5, '10', 100.0, None, None, '01010000206A08000000000000000000400000000000000000', 4, None, None, None),
159 | #('i', 1, '0', 0.0, None, None, '01010000206A080000000000000000F03F0000000000000000', 1, 4, None, None)]
160 |
161 | select_str = diff_rev_view_str(pg_conn_info, 'epanet', 'junctions','trunk', 1,5)
162 | pcur.execute(select_str)
163 | res = pcur.fetchall()
164 | assert(res[0][0] == 'd')
165 | assert(res[1][0] == 'i')
166 | assert(res[2][0] == 'u')
167 | assert(res[3][0] == 'a')
168 | #print("fetchall 1 vs 5 = " + str(res))
169 | #fetchall 1 vs 5 = [
170 | #('u', 4, '1', 22.0, None, None, '01010000206A0800000000000000000000000000000000F03F', 3, None, 3, None),
171 | #('i', 3, '1', 8.0, None, None, '01010000206A0800000000000000000000000000000000F03F', 2, 2, 2, 4),
172 | #('a', 5, '10', 100.0, None, None, '01010000206A08000000000000000000400000000000000000', 4, None, None, None),
173 | #('d', 1, '0', 0.0, None, None, '01010000206A080000000000000000F03F0000000000000000', 1, 4, None, None)]
174 |
175 | # add revision : edit one table then delete and commit changes; rev = 6
176 |
177 | spversioning5.checkout(["epanet_trunk_rev_head.junctions", "epanet_trunk_rev_head.pipes"])
178 |
179 | scon = dbapi2.connect(sqlite_test_filename5)
180 | scur = scon.cursor()
181 | scon.enable_load_extension(True)
182 | scon.execute("SELECT load_extension('mod_spatialite')")
183 | scur.execute("UPDATE junctions_view SET elevation = '22' WHERE id = '1'")
184 | scur.execute("DELETE FROM junctions_view WHERE id = '1'")
185 | scon.commit()
186 | #scur.execute("SELECT COUNT(*) FROM junctions")
187 | #assert( scur.fetchone()[0] == 3 )
188 | scon.close()
189 | spversioning5.commit('update and delete commit')
190 |
191 |
192 | if __name__ == "__main__":
193 | if len(sys.argv) != 3:
194 | print("Usage: python3 versioning_base_test.py host pguser")
195 | else:
196 | test(*sys.argv[1:])
197 |
--------------------------------------------------------------------------------
/versioningDB/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
--------------------------------------------------------------------------------
/versioningDB/constraints.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | /***************************************************************************
4 | versioning
5 | A QGIS plugin
6 | postgis database versioning
7 | -------------------
8 | begin : 2018-06-14
9 | copyright : (C) 2018 by Oslandia
10 | email : infos@oslandia.com
11 | ***************************************************************************/
12 |
13 | /***************************************************************************
14 | * *
15 | * This program is free software; you can redistribute it and/or modify *
16 | * it under the terms of the GNU General Public License as published by *
17 | * the Free Software Foundation; either version 2 of the License, or *
18 | * (at your option) any later version. *
19 | * *
20 | ***************************************************************************/
21 | """
22 |
23 | from .utils import get_pkeys
24 |
25 |
26 | class Constraint:
27 |
28 | def __init__(self, table_from, columns_from, defaults_from, table_to,
29 | columns_to, updtype, deltype):
30 | """ Construct a unique or foreign key constraint
31 |
32 | :param table_from: referencing table
33 | :param columns_from: referencing columns
34 | :param defaults_from: default values
35 | :param table_to: referenced table (None if constraint is unique key)
36 | :param columns_to: referenced columns (empty if constraint is
37 | unique key)
38 | :param updtype: update type (cascade, set default, set null, restrict)
39 | :param deltype: delete type (cascade, set default, set null, restrict)
40 |
41 | """
42 | assert(not columns_to or len(columns_from) == len(columns_to))
43 |
44 | self.table_from = table_from
45 | self.columns_from = columns_from
46 | self.defaults_from = defaults_from
47 | self.table_to = table_to
48 | self.columns_to = columns_to
49 | self.updtype = updtype
50 | self.deltype = deltype
51 |
52 | def get_q_table_from(self, schema):
53 | """ Build and return fully qualified table from
54 |
55 | :param schema: table schema
56 | :returns: fully qualified table name
57 | :rtype: str
58 |
59 | """
60 | return ((schema + "." + self.table_from) if schema
61 | else self.table_from)
62 |
63 | def get_q_table_to(self, schema):
64 | """ Build and return fully qualified table to
65 |
66 | :param schema: table schema
67 | :returns: fully qualified table name
68 | :rtype: str
69 |
70 | """
71 |
72 | return ((schema + "." + self.table_to) if schema
73 | else self.table_to)
74 |
75 |
76 | class ConstraintBuilder:
77 |
78 | def __init__(self, b_cur, wc_cur, b_schema, wc_schema):
79 | """ Constructor to build unique and foreign key constraint
80 |
81 | :param b_cur: base cursor (must be opened and valid)
82 | :param wc_cur: working copy cursor (must be opened and valid)
83 | :param b_schema: base schema
84 | :param wc_schema: working copy schema
85 |
86 | """
87 | self.b_cur = b_cur
88 | self.wc_cur = wc_cur
89 | self.b_schema = b_schema
90 | self.wc_schema = wc_schema
91 |
92 | b_cur.execute(f"""
93 | SELECT table_from, columns_from, defaults_from, table_to,
94 | columns_to, updtype, deltype
95 | FROM {b_schema}.versioning_constraints
96 | """)
97 |
98 | # build two dict to speed access to constraint from
99 | # referencing and referenced table
100 | self.referencing_constraints = {}
101 | self.referenced_constraints = {}
102 |
103 | # Build trigger upon this contraints and setup on view
104 | for (table_from, columns_from, defaults_from, table_to, columns_to,
105 | updtype, deltype) in b_cur.fetchall():
106 |
107 | constraint = Constraint(table_from, columns_from, defaults_from,
108 | table_to, columns_to, updtype, deltype)
109 |
110 | self.referencing_constraints.setdefault(table_from, []).append(
111 | constraint)
112 |
113 | if table_to:
114 | self.referenced_constraints.setdefault(table_to, []).append(
115 | constraint)
116 |
117 | def get_referencing_constraint(self, method, table):
118 | """ Build and return unique and foreign key referencing constraints
119 | sql for given table
120 |
121 | :param method: insert, update or delete
122 | :param table: the referencing table for which we need to build
123 | constraints
124 |
125 | """
126 |
127 | sql_constraint = ""
128 | if (table not in self.referencing_constraints
129 | or method not in ['insert', 'update']):
130 | return sql_constraint
131 |
132 | for constraint in self.referencing_constraints.get(table, []):
133 |
134 | # unique constraint
135 | if not constraint.table_to:
136 |
137 | q_table_from = constraint.get_q_table_from(self.wc_schema)
138 |
139 | # check if unique keys already exist
140 | when_filter = "(SELECT COUNT(*) FROM {}_view WHERE {}) != 0".format(
141 | q_table_from,
142 | " AND ".join(["{0} = NEW.{0}".format(column) for column in constraint.columns_from]))
143 |
144 | # check if unique keys have been modified
145 | if method == 'update':
146 | when_filter += " AND " + " AND ".join(["NEW.{0} != OLD.{0}".format(column)
147 | for column in constraint.columns_from])
148 |
149 | keys = ",".join(constraint.columns_from)
150 |
151 | # postgres requests
152 | if self.wc_cur.isPostgres():
153 |
154 | sql_constraint += f"""IF {when_filter} THEN
155 | RAISE EXCEPTION 'Fail {q_table_from} {keys} unique constraint';
156 | END IF;
157 | """
158 |
159 | # spatialite requests
160 | else:
161 |
162 | sql_constraint += f'SELECT RAISE(FAIL, "Fail {q_table_from} {keys} unique constraint") WHERE {when_filter};'
163 |
164 | # foreign key constraint
165 | else:
166 |
167 | q_table_to = constraint.get_q_table_to(self.wc_schema)
168 |
169 | # check if referenced keys exists
170 | when_filter = "(SELECT COUNT(*) FROM {}_view WHERE {}) = 0".format(
171 | q_table_to, " AND ".join(
172 | [f"(NEW.{column_from} IS NULL "
173 | f"OR {column_to} = NEW.{column_from})"
174 | for column_to, column_from
175 | in zip(constraint.columns_to, constraint.columns_from)]))
176 |
177 | keys = ",".join(constraint.columns_from)
178 |
179 | # postgres requests
180 | if self.wc_cur.db_type == 'pg : ':
181 |
182 | sql_constraint += f"""IF {when_filter} THEN
183 | RAISE EXCEPTION 'Fail {keys} foreign key constraint';
184 | END IF;"""
185 |
186 | # spatialite requests
187 | else:
188 |
189 | sql_constraint += f'SELECT RAISE(FAIL, "Fail {keys} foreign key constraint") WHERE {when_filter};'
190 |
191 | return sql_constraint
192 |
193 | def get_referenced_constraint(self, method, table):
194 | """ Build and return foreign key referenced constraints sql for given table
195 |
196 | :param method: insert, update or delete
197 | :param table: the referenced table for which we need to build
198 | constraints
199 |
200 | """
201 |
202 | sql_constraint = ""
203 | if table not in self.referenced_constraints or method not in ['delete', 'update']:
204 | return sql_constraint
205 |
206 | for constraint in self.referenced_constraints.get(table, []):
207 |
208 | # check if referenced keys have been modified
209 | where = None
210 | if method == 'update':
211 | where = "({})".format(
212 | " OR ".join(["NEW.{0} != OLD.{0}".format(column)
213 | for column in constraint.columns_to]))
214 | else:
215 | where = "True"
216 |
217 | action_type = (constraint.updtype if method == 'update'
218 | else constraint.deltype)
219 |
220 | q_table_from = constraint.get_q_table_from(self.wc_schema)
221 |
222 | col_where = " AND ".join([f"{column_from} = OLD.{column_to}"
223 | for column_from, column_to in
224 | zip(constraint.columns_from,
225 | constraint.columns_to)])
226 |
227 | # cascade
228 | if action_type == 'c':
229 | where += " AND " + col_where
230 |
231 | if method == 'update':
232 | updated_fields = ",".join(
233 | [f"{column_from} = NEW.{column_to}"
234 | for column_from, column_to
235 | in zip(constraint.columns_from,
236 | constraint.columns_to)])
237 |
238 | sql_constraint += f"""
239 | UPDATE {q_table_from}_view
240 | SET {updated_fields} WHERE {where};"""
241 | else:
242 | sql_constraint += f"""
243 | DELETE FROM {q_table_from}_view WHERE {where};"""
244 |
245 | # set null or set default
246 | elif action_type == 'n' or action_type == 'd':
247 | where += " AND " + col_where
248 |
249 | updated_fields = ",".join(
250 | ["{} = {}".format(
251 | column_from,
252 | "NULL" if (action_type == 'n'
253 | or default_from is None)
254 | else default_from)
255 | for column_from, column_to, default_from
256 | in zip(constraint.columns_from,
257 | constraint.columns_to,
258 | constraint.defaults_from)])
259 |
260 | sql_constraint += f"""UPDATE {q_table_from}_view
261 | SET {updated_fields} WHERE {col_where};"""
262 |
263 | # fail
264 | else:
265 |
266 | where += f""" AND (SELECT COUNT(*) FROM {q_table_from}_view
267 | WHERE {col_where}) > 0"""
268 |
269 | keys_label = ",".join(constraint.columns_to) + (" is" if len(constraint.columns_to) == 1 else " are")
270 | sql_constraint += (f"""IF {where} THEN RAISE EXCEPTION '{keys_label} still referenced by {q_table_from}'; END IF;"""
271 | if self.wc_cur.db_type == 'pg : '
272 | else f"""SELECT RAISE(FAIL, "{keys_label} still referenced by {q_table_from}") WHERE {where};""")
273 |
274 | return sql_constraint
275 |
276 |
277 | def check_unique_constraints(b_cur, wc_cur, wc_schema):
278 |
279 | wc_cur.execute("SELECT rev, branch, table_schema, table_name "
280 | f"FROM {wc_schema}.initial_revision")
281 | versioned_layers = wc_cur.fetchall()
282 |
283 | errors = []
284 | for [rev, branch, b_schema, table] in versioned_layers:
285 |
286 | # Spatialite
287 | if wc_cur.isSpatialite():
288 | table_w_revs = f"{table}"
289 | vid = "ogc_fid"
290 |
291 | # PgServer
292 | elif b_cur is wc_cur:
293 | table_w_revs = f"{wc_schema}.{table}_diff"
294 | vid = "versioning_id"
295 |
296 | # PgLocal
297 | else:
298 | table_w_revs = f"{wc_schema}.{table}"
299 | vid = "ogc_fid"
300 |
301 | pkeys = get_pkeys(b_cur, b_schema, table)
302 | pkey_list = ",".join(["trev." + pkey for pkey in pkeys])
303 | new_pkeys_filter = " OR ".join(["trev.{0} != trev2.{0}".format(pkey)
304 | for pkey in pkeys])
305 |
306 | wc_cur.execute(f"""
307 | -- INSERTED PKEY
308 | SELECT {pkey_list}
309 | FROM {table_w_revs} trev
310 | WHERE {branch}_rev_end is NULL
311 | AND {branch}_parent is NULL
312 | AND {branch}_rev_begin > {rev}
313 | UNION
314 | -- UPDATED PKEY
315 | SELECT {pkey_list}
316 | FROM {table_w_revs} trev, {table_w_revs} trev2
317 | WHERE trev.{branch}_parent IS NOT NULL
318 | AND trev.{branch}_rev_begin > 1
319 | AND trev.{branch}_parent = trev2.{vid}
320 | AND ({new_pkeys_filter})
321 | """)
322 |
323 | new_keys = wc_cur.fetchall()
324 |
325 | if not new_keys:
326 | continue
327 |
328 | # search in database if there are some working copy new key that
329 | # already exist.
330 | new_key_list = ",".join(["({})".format(
331 | ",".join([str(int_key)for int_key in new_key]))
332 | for new_key in new_keys])
333 |
334 | b_cur.execute(f"""
335 | SELECT {pkey_list}
336 | FROM {b_schema}.{table} trev
337 | WHERE {branch}_rev_end is NULL
338 | AND {branch}_parent is NULL
339 | INTERSECT
340 | SELECT *
341 | FROM (VALUES {new_key_list}) AS new_keys""")
342 |
343 | def to_string(rec):
344 | return " and ".join(
345 | [f"{pkey}={value}" for pkey, value in zip(pkeys, rec)])
346 |
347 | errors += [" {}.{} : {}".format(wc_schema, table, to_string(res))
348 | for res in b_cur.fetchall()]
349 |
350 | if errors:
351 | raise RuntimeError("Some new or updated row violate the primary key"
352 | " constraint in base database :\n{}".format(
353 | "\n".join(errors)))
354 |
--------------------------------------------------------------------------------
/versioningDB/sql/historize.sql:
--------------------------------------------------------------------------------
1 | -- Create table to store PRIMARY KEY and FOREIGN KEY constraints
2 | -- before deleting them
3 | CREATE TABLE {schema}.versioning_constraints (
4 | table_from varchar,
5 | columns_from varchar[],
6 | defaults_from varchar[],
7 | table_to varchar,
8 | columns_to varchar[],
9 | updtype char,
10 | deltype char);
11 |
12 | -- populate constraints table
13 | INSERT INTO {schema}.versioning_constraints
14 | SELECT (SELECT relname FROM pg_class WHERE oid = conrelid::regclass) AS table_from,
15 |
16 | (SELECT array_agg(att.attname)
17 | FROM (SELECT unnest(conkey) AS key) AS keys,
18 | pg_attribute att WHERE att.attrelid = conrelid AND att.attnum = keys.key) AS columns_from,
19 |
20 | (SELECT array_agg(adef.adsrc)
21 | FROM (SELECT unnest(conkey) AS key) AS keys
22 | LEFT JOIN pg_attrdef adef ON adef.adrelid = conrelid
23 | AND adef.adnum = keys.key ) AS defaults_from,
24 |
25 | (SELECT relname FROM pg_class WHERE oid = confrelid::regclass) as table_to,
26 |
27 | (SELECT array_agg(att.attname)
28 | FROM (SELECT unnest(confkey) AS key) AS keys,
29 | pg_attribute att WHERE att.attrelid = confrelid AND att.attnum = keys.key) AS columns_to,
30 |
31 | c.confupdtype as updtype,
32 | c.confdeltype as deltype
33 | FROM pg_constraint c
34 | JOIN pg_namespace n ON n.oid = c.connamespace
35 | WHERE contype IN ('f', 'p ')
36 | AND n.nspname = '{schema}';
37 |
38 | -- Drop foreign keys and primary keys
39 | DO
40 | $do$
41 | DECLARE
42 | c record;
43 | BEGIN
44 | FOR c IN
45 | SELECT pgc.conname as name, pgc.conrelid::regclass as table
46 | FROM pg_constraint pgc, pg_namespace pgn
47 | WHERE pgn.oid = pgc.connamespace
48 | AND pgc.contype IN ('f', 'p ')
49 | AND pgn.nspname = '{schema}'
50 | ORDER BY pgc.contype ASC -- foreign keys first
51 | LOOP
52 | EXECUTE 'ALTER TABLE ' || c.table || ' DROP CONSTRAINT ' || c.name;
53 | END LOOP;
54 | END
55 | $do$;
56 |
57 | -- Add the versioning_id primary key
58 | DO
59 | $$
60 | DECLARE
61 | rec record;
62 | BEGIN
63 | FOR rec IN
64 | SELECT schemaname, tablename
65 | FROM pg_catalog.pg_tables
66 | WHERE schemaname = '{schema}'
67 | AND tablename != 'versioning_constraints'
68 | LOOP
69 | EXECUTE 'ALTER TABLE ' || rec.schemaname || '.' || rec.tablename
70 | || ' ADD COLUMN versioning_id SERIAL PRIMARY KEY';
71 | END LOOP;
72 | END
73 | $$;
74 |
75 | -- Create revisions table
76 | CREATE TABLE {schema}.revisions (
77 | rev serial PRIMARY KEY,
78 | commit_msg varchar,
79 | branch varchar DEFAULT 'trunk',
80 | date timestamp DEFAULT current_timestamp,
81 | author varchar);
82 |
83 |
--------------------------------------------------------------------------------
/versioningDB/versioningAbc.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import absolute_import
4 |
5 | from .postgresqlServer import pgVersioningServer
6 | from .spatialite import spVersioning
7 | from .postgresqlLocal import pgVersioningLocal
8 |
9 | TYPE = ('postgres', 'spatialite', 'pgDistant')
10 | CONNECTIONS = {'postgres': 2, 'spatialite': 2, 'pgDistant': 3}
11 |
12 | class versioningAbc(object):
13 |
14 | def __init__(self, connection, typebase):
15 | assert(isinstance(connection, list) and
16 | typebase in TYPE and
17 | len(connection) == CONNECTIONS[typebase])
18 |
19 | self.typebase = typebase
20 | # spatialite : [sqlite_filename, pg_conn_info]
21 | # postgres : [pg_conn_info, working_copy_schema]
22 | # pgDistant : [pg_conn_info, working_copy_schema, pg_conn_info_out]
23 | self.connection = connection
24 | if self.typebase == 'spatialite':
25 | self.ver = spVersioning()
26 | elif self.typebase == 'pgDistant':
27 | self.ver = pgVersioningLocal()
28 | else:
29 | self.ver = pgVersioningServer()
30 |
31 | def revision(self):
32 | return self.ver.revision(self.connection)
33 |
34 | def late(self ):
35 | return self.ver.late(self.connection)
36 |
37 | def update(self):
38 | self.ver.update(self.connection)
39 |
40 | def checkout(self, pg_table_names, selected_feature_lists = []):
41 | self.ver.checkout(self.connection, pg_table_names, selected_feature_lists)
42 |
43 | def unresolved_conflicts(self):
44 | return self.ver.unresolved_conflicts(self.connection)
45 |
46 | def commit(self, commit_msg, commit_user = ''):
47 | return self.ver.commit(self.connection, commit_msg, commit_user)
48 |
--------------------------------------------------------------------------------