Skip to content

Database defined UNIQUE or FOREIGN KEY constraint causes rebase to fail #210

Open
@leorudczenko

Description

@leorudczenko

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 version 3.9.18
  • pygeodiff version 2.0.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions