Description
This is a follow-up from MerginMaps/mobile#2933. After further investigation into this bug, we have found that the bug exists in the rebase
function from geodiff
.
Summary
Our database/GeoPackage structure requires the use of the UNIQUE
constraint on certain fields, specifically for primary/foreign keys. When trying to rebase a GeoPackage file with a UNIQUE
constraint on any column, it fails with this error: pygeodiff.geodifflib.GeoDiffLibError: rebase
. This error is then what leads to the strange conflict bug here: MerginMaps/mobile#2933.
To Reproduce
We have created a Python script to re-produce this error repeatedly, using a minimal table definition with 2 fields:
"""
geodiff_unique_rebase_bug.py
"""
import sqlite3
from pathlib import Path
import pygeodiff
import etlhelper as etl
def main() -> None:
# Define GeoPackage filepaths
gpkg_dir = Path(__file__).parent
base = gpkg_dir / "base.gpkg"
ours = gpkg_dir / "ours.gpkg"
theirs = gpkg_dir / "theirs.gpkg"
reset_gpkg_files(base, ours, theirs)
create_gpkg_files(base, ours, theirs)
geodiff_rebase(gpkg_dir, base, ours, theirs)
def reset_gpkg_files(base: Path, ours: Path, theirs: Path) -> None:
"""
Delete the GeoPackage files if they already exist.
"""
for gpkg in [base, ours, theirs]:
gpkg.unlink(missing_ok=True)
def create_gpkg_files(base: Path, ours: Path, theirs: Path) -> None:
"""
Create 3 GeoPackage files at the given filepaths.
All 3 of the files will have the same table created in them.
But, for the file 'ours' and 'theirs', they will each have 1 row inserted into the table too.
These rows will abide by the database table constraints.
"""
# UNIQUE constraint in this CREATE TABLE statement causes geodiff.rebase to fail
# If you re-run this script, but remove the UNIQUE constraint, it will succeed
create_table = """
CREATE TABLE test (
"fid" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"name" TEXT UNIQUE
)
"""
# Add empty table to all 3
for gpkg in [base, ours, theirs]:
with sqlite3.connect(gpkg) as conn:
etl.execute(create_table, conn)
# Add 1 row to ours and theirs
if gpkg.stem == "ours":
etl.load("test", conn, [{"name": "our_change"}])
elif gpkg.stem == "theirs":
etl.load("test", conn, [{"name": "their_change"}])
def geodiff_rebase(gpkg_dir: Path, base: Path, ours: Path, theirs: Path) -> None:
"""
Call geodiff.rebase.
When it works, you should end up with both of the inserted rows in the file 'ours'.
When it fails, you should see 'pygeodiff.geodifflib.GeoDiffLibError: rebase' and no conflict file is created.
"""
geodiff = pygeodiff.GeoDiff()
geodiff.rebase(str(base), str(theirs), str(ours), str(gpkg_dir / "conflict.gpkg"))
print(f"Rebased files successfully into: {ours}")
if __name__ == "__main__":
main()
Running the Script
To run the script, you need to install the following Python libraries:
pygeodiff
etlhelper
Run the script with:
python geodiff_unique_rebase_bug.py
Results
The script will fail at the rebase
step, demonstrating the bug.
If you then remove the UNIQUE
constraint from the test
table definition, it will allow the script to run to completion. In this circumstance, the GeoPackage ours
will end up with 2 rows as expected.
Software Versions
python
version3.9.18
pygeodiff
version2.0.2