Skip to content

fix: actionable error when API called before cloudsync_init#47

Merged
andinux merged 3 commits intomainfrom
fix/better-uninit-error-messages
Apr 24, 2026
Merged

fix: actionable error when API called before cloudsync_init#47
andinux merged 3 commits intomainfrom
fix/better-uninit-error-messages

Conversation

@andinux
Copy link
Copy Markdown
Collaborator

@andinux andinux commented Apr 24, 2026

Summary

Six functions leaked low-level symptoms when called before any cloudsync_init('<table_name>'). All six now raise a single actionable message pointing at SELECT cloudsync_init('<table_name>').

Before / After

Function Before After
SELECT * FROM cloudsync_changes (SQLite) Error: out of memory (7)SQLITE_NOMEM returned by mistake `cloudsync has no tables configured for sync. Call SELECT
cloudsync_init('<table_name>') …`
SELECT cloudsync_db_version() (SQLite) Unable to retrieve db_version (not an error). cloudsync is not initialized: call SELECT cloudsync_init('<table_name>') …
SELECT cloudsync_db_version() (PG) Unable to retrieve db_version () (empty parens) same actionable message
SELECT cloudsync_db_version_next() (SQLite) Unable to retrieve next_db_version (not an error). same actionable message
SELECT cloudsync_db_version_next() (PG) silently returns -1 now raises the actionable error
SELECT cloudsync_set_filter('foo', …) (SQLite / PG) ten+ "no such table" / NOTICE … does not exist, skipping lines followed by generic error recreating triggers
cloudsync_set_filter: table 'foo' is not configured for sync. Call SELECT cloudsync_init('foo') first.
SELECT cloudsync_clear_filter('foo') (SQLite / PG) same pattern analogous actionable message
SELECT cloudsync_payload_apply(…) (SQLite / PG) "dbutils_settings_get_value error no such table: cloudsync_settings" then Runtime error: not an error `cloudsync is not initialized:
call SELECT cloudsync_init('<table_name>') …`

cloudsync_changes on PostgreSQL was already graceful (empty result set) — no change needed.

Implementation

  • New helper cloudsync_context_is_initialized(data) — returns true when data_version_stmt is prepared, which is only done by cloudsync_init (via cloudsync_add_dbvms
    cloudsync_context_init).
  • The guard is placed inside the error branch of each function, not before it. When the normal path succeeds, the guard never executes — so there is zero overhead on the sync hot path (e.g.
    cloudsync_db_version_next stepped by the merge triggers on every received row, and cloudsync_payload_apply on every inbound sync).
  • For cloudsync_payload_apply the guard is added once inside the shared cloudsync_payload_apply() in src/cloudsync.c, so all three callers (dbsync_payload_decode, dbsync_payload_load,
    and the PG SRF) inherit it.

Regression test

A new do_test_uninit_error_messages in test/unit.c invokes each of the six functions on a fresh database (no cloudsync_init called) and asserts that every error message contains the stable
substring "cloudsync_init". Matching on a single token rather than full text keeps the test robust to future rewordings of the user-facing string.

Test plan

  • make clean && make && make unittest — all OK (including the new Uninit Error Messages test)
  • make postgres-docker-rebuild && make postgres-docker-run-test — 0 failures (port 5432, standalone)
  • make postgres-supabase-rebuild && make postgres-supabase-run-test — 0 failures (port 54322, Supabase)
  • Manual repro of each "before" error on a fresh database — all produce the new message
  • Manual happy-path check after cloudsync_init — unchanged

Version bumped to 1.0.17.

andinux and others added 3 commits April 24, 2026 12:11
Six functions produced misleading errors when called before any
cloudsync_init('<table_name>'):

  cloudsync_changes           -> "out of memory (7)"
  cloudsync_db_version        -> "Unable to retrieve db_version (not an error)"
  cloudsync_db_version_next   -> same pattern (SQLite) / silent -1 (PG)
  cloudsync_set_filter        -> 10+ "no such table" NOTICEs + generic trigger error
  cloudsync_clear_filter      -> same as set_filter
  cloudsync_payload_apply     -> 3x "no such table: cloudsync_settings" debug lines
                                  followed by "Runtime error: not an error"

Add cloudsync_context_is_initialized() helper and guard each function
on its error branch. When the root cause is missing init, raise a
single message pointing at SELECT cloudsync_init('<table_name>').

The guard is a NULL-pointer check on the error branch only, so the
sync hot path (cloudsync_db_version_next stepped by merge triggers
on every received row) is unaffected.

cloudsync_changes on PostgreSQL was already graceful (empty result
set), so no fix was needed there.

Bumps to 1.0.17.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Asserts that every function guarded by the previous commit points the
caller at cloudsync_init when called before any table has been set up
for sync. Matches on the stable substring "cloudsync_init" rather than
the full sentence, so future rewordings of the user-facing message do
not break the test.

Covers cloudsync_changes, cloudsync_db_version, cloudsync_db_version_next,
cloudsync_set_filter, cloudsync_clear_filter, and cloudsync_payload_apply.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@andinux andinux merged commit d6bb396 into main Apr 24, 2026
28 checks passed
@andinux andinux deleted the fix/better-uninit-error-messages branch April 24, 2026 22:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant