Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d5170c7
function that adds quotes to schema name if the schema name contains …
JanCaha Mar 17, 2023
257ee60
needs string with " if upper case letters, psycopg2 takes care of add…
JanCaha Mar 17, 2023
2fc6076
add workspace
JanCaha Mar 17, 2023
009eae9
parametrize tests, so that they run on two project names - adding pro…
JanCaha Mar 17, 2023
9b415db
add test info about workspace
JanCaha Mar 17, 2023
bacdf83
update test name
JanCaha Mar 22, 2023
0e17527
fix tests to properly escape schema names
JanCaha Mar 22, 2023
dca2b59
add to avoid accidental commits
JanCaha Mar 22, 2023
42d78c2
_drop_schema function
JanCaha Mar 22, 2023
d224d64
drop schema if its problematic
JanCaha Mar 22, 2023
ac2a08e
separate tests for problems with schema
JanCaha Mar 22, 2023
3142ecd
tests should explicitly check for schema presence/absence
JanCaha Mar 22, 2023
b440c4e
Merge branch 'master' into fix-90
JanCaha Mar 22, 2023
332354c
bring back test for dropping schemata
JanCaha Mar 22, 2023
190df0e
update messages
JanCaha Mar 23, 2023
9ec3609
update messages
JanCaha Mar 23, 2023
57afc69
drop both schema if the init didnt go well
JanCaha Mar 23, 2023
59ecee9
avoid issues, only drop schema if it exist
JanCaha Mar 23, 2023
2c2faa6
Merge branch 'master' into fix-90
JanCaha Mar 23, 2023
a362330
fix test
JanCaha Mar 23, 2023
a111a62
simplify the sql queries
JanCaha Mar 23, 2023
86bf7ff
fix error message
JanCaha Mar 23, 2023
a535081
drop only the base schema
JanCaha Mar 23, 2023
9955c03
Update message
JanCaha Mar 23, 2023
418e3bf
Update message
JanCaha Mar 23, 2023
584b5ba
Update message
JanCaha Mar 23, 2023
0843f4e
add prints before dropping schemas
JanCaha Mar 23, 2023
cf9f5dd
fix test
JanCaha Mar 23, 2023
d33221b
Merge branch 'master' into fix-90
JanCaha Mar 24, 2023
c1f23bd
remove settings that should not be used there ;-)
JanCaha Mar 24, 2023
b7c13cf
fix failing tests
JanCaha Mar 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 21 additions & 11 deletions dbsync.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ def _check_has_sync_file(file_path):
raise DbSyncError("The output GPKG file does not exist: " + file_path)


def _drop_schema(conn, schema_name: str) -> None:
cur = conn.cursor()
cur.execute(sql.SQL("DROP SCHEMA IF EXIST {} CASCADE").format(sql.Identifier(schema_name)))
conn.commit()


def _check_schema_exists(conn, schema_name):
cur = conn.cursor()
cur.execute("SELECT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = %s)", (schema_name,))
Expand Down Expand Up @@ -595,7 +601,8 @@ def init(conn_cfg, mc, from_gpkg=True):
# this is not a first run of db-sync init
db_proj_info = _get_db_project_comment(conn, conn_cfg.base)
if not db_proj_info:
raise DbSyncError("Base schema exists but missing which project it belongs to")
raise DbSyncError("Base schema exists but missing which project it belongs to. "
f"This may be a result of a previously failed attempt to initialize DB sync. You can delete both schemas `{conn_cfg.base}` and `{conn_cfg.modified}` to fix this error and restart DB sync.")
if "error" in db_proj_info:
changes_gpkg_base = _compare_datasets("sqlite", "", gpkg_full_path, conn_cfg.driver,
conn_cfg.conn_info, conn_cfg.base, ignored_tables,
Expand Down Expand Up @@ -671,9 +678,11 @@ def init(conn_cfg, mc, from_gpkg=True):
print("The GPKG file, base and modified schemas are already initialized and in sync")
return # nothing to do
elif modified_schema_exists:
raise DbSyncError(f"The 'modified' schema exists but the base schema is missing: {conn_cfg.base}")
raise DbSyncError(f"The 'modified' schema exists but the base schema is missing: {conn_cfg.base}. "
f"This may be a result of a previously failed attempt to initialize DB sync. You can delete schema `{conn_cfg.modified}` in the database to fix this error and restart DB sync.")
elif base_schema_exists:
raise DbSyncError(f"The base schema exists but the modified schema is missing: {conn_cfg.modified}")
raise DbSyncError(f"The base schema exists but the modified schema is missing: {conn_cfg.modified}. "
f"This may be a result of a previously failed attempt to initialize DB sync. You can delete schema `{conn_cfg.base}` in the database to fix this error and restart DB sync.")

# initialize: we have an existing GeoPackage in our Mergin Maps project and we want to initialize database
print("The base and modified schemas do not exist yet, going to initialize them ...")
Expand All @@ -699,22 +708,23 @@ def init(conn_cfg, mc, from_gpkg=True):
raise DbSyncError('Initialization of db-sync failed due to a bug in geodiff.\n '
'Please report this problem to mergin-db-sync developers')
except DbSyncError:
# add comment to base schema before throwing exception
_set_db_project_comment(conn, conn_cfg.base, conn_cfg.mergin_project, local_version,
error='Initialization of db-sync failed due to a bug in geodiff')
print(f"Cleaning up after a failed DB sync init - dropping schemas {conn_cfg.base} and {conn_cfg.modified}.")
_drop_schema(conn_cfg.base)
_drop_schema(conn_cfg.modified)
raise

_set_db_project_comment(conn, conn_cfg.base, conn_cfg.mergin_project, local_version)
else:
if not modified_schema_exists:
raise DbSyncError("The 'modified' schema does not exist: " + conn_cfg.modified)
raise DbSyncError(f"The 'modified' schema does not exist: {conn_cfg.modified}. "
"This schema is necessary if initialization should be done from database (parameter `init-from-db`).")

if os.path.exists(gpkg_full_path) and base_schema_exists:
# make sure output gpkg is in sync with db or fail
summary_modified = _compare_datasets(conn_cfg.driver, conn_cfg.conn_info, conn_cfg.modified,
"sqlite", "", gpkg_full_path, ignored_tables)
"sqlite", "", gpkg_full_path, ignored_tables)
summary_base = _compare_datasets(conn_cfg.driver, conn_cfg.conn_info, conn_cfg.base,
"sqlite", "", gpkg_full_path, ignored_tables)
"sqlite", "", gpkg_full_path, ignored_tables)
if len(summary_base):
print(f"Local project version at {_get_project_version(work_dir)} and base schema at {db_proj_info['version']}")
_print_changes_summary(summary_base, "Base schema changes:")
Expand Down Expand Up @@ -755,8 +765,8 @@ def init(conn_cfg, mc, from_gpkg=True):
raise DbSyncError('Initialization of db-sync failed due to a bug in geodiff.\n '
'Please report this problem to mergin-db-sync developers')
except DbSyncError:
_set_db_project_comment(conn, conn_cfg.base, conn_cfg.mergin_project, local_version,
error='Initialization of db-sync failed due to a bug in geodiff')
print(f"Cleaning up after a failed DB sync init - dropping schema {conn_cfg.base}.")
_drop_schema(conn_cfg.base)
raise

# upload gpkg to Mergin Maps (client takes care of storing metadata)
Expand Down
82 changes: 72 additions & 10 deletions test/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,16 +121,6 @@ def test_init_from_gpkg(mc: MerginClient):
assert db_proj_info["name"] == config.connections[0].mergin_project
assert db_proj_info["version"] == 'v1'

# rename base schema to mimic some mismatch
cur.execute(sql.SQL("ALTER SCHEMA {} RENAME TO schema_tmp").format(sql.Identifier(db_schema_base)).as_string(conn))
conn.commit()
with pytest.raises(DbSyncError) as err:
dbsync_init(mc)
assert "The 'modified' schema exists but the base schema is missing" in str(err.value)
# and revert back
cur.execute(sql.SQL("ALTER SCHEMA schema_tmp RENAME TO {}").format(sql.Identifier(db_schema_base)).as_string(conn))
conn.commit()

# make change in GPKG and push to server to create pending changes, it should pass but not sync
shutil.copy(os.path.join(TEST_DATA_DIR, 'inserted_1_A.gpkg'), os.path.join(project_dir, 'test_sync.gpkg'))
mc.push_project(project_dir)
Expand Down Expand Up @@ -440,6 +430,7 @@ def test_recreated_project_ids(mc: MerginClient):
with pytest.raises(DbSyncError):
dbsync_status(mc)


@pytest.mark.parametrize("project_name", ['test_init_1', 'Test_Init_2', "Test 3", "Test-4"])
def test_project_names(mc: MerginClient, project_name: str):
source_gpkg_path = os.path.join(TEST_DATA_DIR, 'base.gpkg')
Expand Down Expand Up @@ -487,3 +478,74 @@ def test_project_names(mc: MerginClient, project_name: str):
assert gpkg_cur.fetchone()[3] == 100
db_proj_info = _get_db_project_comment(conn, db_schema_base)
assert db_proj_info["version"] == 'v3'


def test_init_from_gpkg_missing_schema(mc: MerginClient):
source_gpkg_path = os.path.join(TEST_DATA_DIR, 'base.gpkg')
project_name = "test_init_missing_schema"
db_schema_base = project_name + "_base"
db_schema_main = project_name + "_main"

init_sync_from_geopackage(mc, project_name, source_gpkg_path)

conn = psycopg2.connect(DB_CONNINFO)
cur = conn.cursor()

# drop base schema to mimic some mismatch
cur.execute(sql.SQL("DROP SCHEMA {} CASCADE").format(sql.Identifier(db_schema_base)))
conn.commit()

# check that removed schema does not exists
cur.execute(f"SELECT schema_name FROM information_schema.schemata WHERE schema_name = '{db_schema_base}'")
cur.fetchone() is None

with pytest.raises(DbSyncError) as err:
dbsync_init(mc)
assert "The 'modified' schema exists but the base schema is missing" in str(err.value)
assert "This may be a result of a previously failed attempt to initialize DB sync" in str(err.value)

init_sync_from_geopackage(mc, project_name, source_gpkg_path)

# drop main schema to mimic some mismatch
cur.execute(sql.SQL("DROP SCHEMA {} CASCADE").format(sql.Identifier(db_schema_main)))
conn.commit()

# check that removed schema does not exists
cur.execute(f"SELECT schema_name FROM information_schema.schemata WHERE schema_name = '{db_schema_main}'")
cur.fetchone() is None

with pytest.raises(DbSyncError) as err:
dbsync_init(mc)
assert "The base schema exists but the modified schema is missing" in str(err.value)
assert "This may be a result of a previously failed attempt to initialize DB sync" in str(err.value)


def test_init_from_gpkg_missing_comment(mc: MerginClient):
project_name = "test_init_missing_comment"
source_gpkg_path = os.path.join(TEST_DATA_DIR, 'base.gpkg')
schema_name = project_name + "_base"

init_sync_from_geopackage(mc, project_name, source_gpkg_path)

conn = psycopg2.connect(DB_CONNINFO)
cur = conn.cursor()

# sql query for schema
sql_cmd = f"SELECT schema_name FROM information_schema.schemata WHERE schema_name = '{schema_name}'"

# check that schema exists
cur.execute(sql_cmd)
cur.fetchone()[0] == schema_name

# drop base schema to mimic some mismatch
query = sql.SQL("COMMENT ON SCHEMA {} IS %s").format(sql.Identifier(schema_name))
cur.execute(query.as_string(conn), ("",))
conn.commit()

with pytest.raises(DbSyncError) as err:
dbsync_init(mc)
assert "Base schema exists but missing which project it belongs to" in str(err.value)

# check that schema does not exists anymore
cur.execute(sql_cmd)
cur.fetchone() is None
4 changes: 2 additions & 2 deletions test/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ def test_config():

_reset_config()
with pytest.raises(ConfigError, match="Config error: Only 'postgres' driver is currently supported."):
config.update({'CONNECTIONS': [{"driver": "oracle", "conn_info": "", "modified": "mergin_main", "base": "mergin_base", "mergin_project": "john/dbsync", "sync_file": "sync.gpkg", "init_from": "gpkg"}]})
config.update({'CONNECTIONS': [{"driver": "oracle", "conn_info": "", "modified": "mergin_main", "base": "mergin_base", "mergin_project": "john/dbsync", "sync_file": "sync.gpkg"}]})
validate_config(config)

_reset_config()
with pytest.raises(ConfigError, match="Config error: Name of the Mergin Maps project should be provided in the namespace/name format."):
config.update({'CONNECTIONS': [{"driver": "postgres", "conn_info": "", "modified": "mergin_main", "base": "mergin_base", "mergin_project": "dbsync", "sync_file": "sync.gpkg", "init_from": "gpkg"}]})
config.update({'CONNECTIONS': [{"driver": "postgres", "conn_info": "", "modified": "mergin_main", "base": "mergin_base", "mergin_project": "dbsync", "sync_file": "sync.gpkg"}]})
validate_config(config)