Skip to content

Commit 65ef99b

Browse files
committed
Added new init_flags to the cloudsync_init function
1 parent 7a3d1d5 commit 65ef99b

8 files changed

Lines changed: 138 additions & 53 deletions

File tree

API.md

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ This document provides a reference for the SQLite functions provided by the `sql
55
## Index
66

77
- [Configuration Functions](#configuration-functions)
8-
- [`cloudsync_init()`](#cloudsync_inittable_name-crdt_algo-force)
8+
- [`cloudsync_init()`](#cloudsync_inittable_name-crdt_algo-init_flags)
99
- [`cloudsync_enable()`](#cloudsync_enabletable_name)
1010
- [`cloudsync_disable()`](#cloudsync_disabletable_name)
1111
- [`cloudsync_is_enabled()`](#cloudsync_is_enabledtable_name)
12+
- [`cloudsync_set_filter()`](#cloudsync_set_filtertable_name-filter_expr)
13+
- [`cloudsync_clear_filter()`](#cloudsync_clear_filtertable_name)
1214
- [`cloudsync_cleanup()`](#cloudsync_cleanuptable_name)
1315
- [`cloudsync_terminate()`](#cloudsync_terminate)
1416
- [Block-Level LWW Functions](#block-level-lww-functions)
@@ -38,7 +40,7 @@ This document provides a reference for the SQLite functions provided by the `sql
3840

3941
## Configuration Functions
4042

41-
### `cloudsync_init(table_name, [crdt_algo], [force])`
43+
### `cloudsync_init(table_name, [crdt_algo], [init_flags])`
4244

4345
**Description:** Initializes a table for `sqlite-sync` synchronization. This function is idempotent and needs to be called only once per table on each site; configurations are stored in the database and automatically loaded with the extension.
4446

@@ -62,25 +64,32 @@ For comprehensive guidelines, see the [Database Schema Recommendations](docs/sch
6264
The function supports three overloads:
6365
- `cloudsync_init(table_name)`: Uses the default 'cls' CRDT algorithm.
6466
- `cloudsync_init(table_name, crdt_algo)`: Specifies a CRDT algorithm ('cls', 'dws', 'aws', 'gos').
65-
- `cloudsync_init(table_name, crdt_algo, force)`: Specifies an algorithm and, if `force` is `true` (or `1`), skips the integer primary key check (use with caution, GUIDs are strongly recommended).
67+
- `cloudsync_init(table_name, crdt_algo, init_flags)`: Specifies an algorithm and a bitmask of initialization flags to control which schema sanity checks are skipped.
6668

6769
**Parameters:**
6870

6971
- `table_name` (TEXT): The name of the table to initialize.
70-
- `crdt_algo` (TEXT, optional): The CRDT algorithm to use. Can be "cls", "dws", "aws", "gos". Defaults to "cls".
71-
- `force` (BOOLEAN, optional): If `true` (or `1`), it skips the check that prevents the use of a single-column INTEGER primary key. Defaults to `false`. It is strongly recommended to use globally unique primary keys instead of integers.
72+
- `crdt_algo` (TEXT, optional): The CRDT algorithm to use. Can be `"cls"`, `"dws"`, `"aws"`, `"gos"`. Defaults to `"cls"`.
73+
- `init_flags` (INTEGER, optional): A bitmask of flags that control initialization behavior. Defaults to `0` (no flags). Available flags:
74+
- `0` — No flags; all sanity checks are performed (default).
75+
- `1` (`CLOUDSYNC_INIT_FLAG_SKIP_INT_PK_CHECK`) — Skip the check that prevents the use of a single-column INTEGER primary key. Use with caution; globally unique primary keys (UUID/ULID) are strongly recommended.
76+
- `2` (`CLOUDSYNC_INIT_FLAG_SKIP_NOT_NULL_DEFAULT_CHECK`) — Skip the check that requires all NOT NULL non-PK columns to have a DEFAULT value.
77+
- `4` (`CLOUDSYNC_INIT_FLAG_SKIP_NOT_NULL_PRIKEYS_CHECK`) — Skip the check that rejects NULL primary key values.
78+
- Flags can be combined with bitwise OR (e.g., `3` skips both the integer PK check and the NOT NULL default check).
7279

7380
**Returns:** None.
7481

7582
**Example:**
7683

7784
```sql
78-
-- Initialize a single table for synchronization with the Causal-Length Set (CLS) Algorithm (default)
85+
-- Initialize a table with the default CLS algorithm
7986
SELECT cloudsync_init('my_table');
8087

81-
-- Initialize a single table for synchronization with a different algorithm Delete-Wins Set (DWS)
88+
-- Initialize a table with the Delete-Wins Set algorithm
8289
SELECT cloudsync_init('my_table', 'dws');
8390

91+
-- Initialize a table with an integer primary key (skip the integer PK check)
92+
SELECT cloudsync_init('my_table', 'cls', 1);
8493
```
8594

8695
---
@@ -139,6 +148,56 @@ SELECT cloudsync_is_enabled('my_table');
139148

140149
---
141150

151+
### `cloudsync_set_filter(table_name, filter_expr)`
152+
153+
**Description:** Sets a row-level filter expression on a synchronized table. Only rows that match the filter are tracked by the sync triggers; changes to rows that do not satisfy the expression are ignored and never replicated.
154+
155+
The filter expression is a standard SQL boolean expression written using bare column names (without a table or alias prefix). The extension automatically rewrites it with `NEW.` for INSERT/UPDATE triggers and `OLD.` for DELETE triggers. The expression is evaluated inside the trigger `WHEN` clause.
156+
157+
This function stores the filter in the table's settings and immediately recreates the sync triggers to apply it. The filter persists across database reopens. Use [`cloudsync_clear_filter()`](#cloudsync_clear_filtertable_name) to remove it.
158+
159+
**Parameters:**
160+
161+
- `table_name` (TEXT): The name of the synchronized table.
162+
- `filter_expr` (TEXT): A SQL boolean expression referencing column names of the table. Only rows for which this expression evaluates to true are tracked for sync.
163+
164+
**Returns:** `1` on success.
165+
166+
**Example:**
167+
168+
```sql
169+
-- Only sync tasks that are not marked as drafts
170+
SELECT cloudsync_set_filter('tasks', "is_draft = 0");
171+
172+
-- Only sync rows belonging to a specific tenant
173+
SELECT cloudsync_set_filter('orders', "tenant_id = 'acme'");
174+
175+
-- Combine conditions
176+
SELECT cloudsync_set_filter('messages', "deleted = 0 AND type != 'ephemeral'");
177+
```
178+
179+
---
180+
181+
### `cloudsync_clear_filter(table_name)`
182+
183+
**Description:** Removes the row-level filter previously set with [`cloudsync_set_filter()`](#cloudsync_set_filtertable_name-filter_expr). After clearing, all row changes in the table are tracked and replicated regardless of column values.
184+
185+
This function updates the stored settings and immediately recreates the sync triggers without a filter condition.
186+
187+
**Parameters:**
188+
189+
- `table_name` (TEXT): The name of the synchronized table.
190+
191+
**Returns:** `1` on success.
192+
193+
**Example:**
194+
195+
```sql
196+
SELECT cloudsync_clear_filter('tasks');
197+
```
198+
199+
---
200+
142201
### `cloudsync_cleanup(table_name)`
143202

144203
**Description:** Removes the `sqlite-sync` synchronization mechanism from a specified table or all tables. This operation drops the associated `_cloudsync` metadata table and removes triggers from the target table(s). Use this function when synchronization is no longer desired for a table.

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

7+
## [1.0.9] - 2026-04-08
8+
9+
### Changed
10+
11+
- **cloudsync_init**: Replaced the `force` boolean parameter with an `init_flags` integer bitmask (`CLOUDSYNC_INIT_FLAG`), allowing fine-grained control over which schema sanity checks are skipped. Existing callers passing `0`/`false` or `1`/`true` remain compatible.
12+
- **API**: Updated `cloudsync_init` SQL signature (PostgreSQL) to accept `integer` instead of `boolean` for the third argument, enabling flag combinations via bitwise OR.
13+
14+
### Added
15+
16+
- `CLOUDSYNC_INIT_FLAG_NONE` (0), `CLOUDSYNC_INIT_FLAG_SKIP_INT_PK_CHECK` (1), `CLOUDSYNC_INIT_FLAG_SKIP_NOT_NULL_DEFAULT_CHECK` (2), `CLOUDSYNC_INIT_FLAG_SKIP_NOT_NULL_PRIKEYS_CHECK` (4) enum values.
17+
- Documentation for `cloudsync_set_filter` and `cloudsync_clear_filter` in API.md.
18+
719
## [1.0.8] - 2026-04-03
820

921
### Changed

src/cloudsync.c

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2429,7 +2429,7 @@ int cloudsync_commit_alter (cloudsync_context *data, const char *table_name) {
24292429
// init again cloudsync for the table
24302430
table_algo algo_current = dbutils_table_settings_get_algo(data, table_name);
24312431
if (algo_current == table_algo_none) algo_current = dbutils_table_settings_get_algo(data, "*");
2432-
rc = cloudsync_init_table(data, table_name, cloudsync_algo_name(algo_current), true);
2432+
rc = cloudsync_init_table(data, table_name, cloudsync_algo_name(algo_current), CLOUDSYNC_INIT_FLAG_SKIP_INT_PK_CHECK);
24332433
if (rc != DBRES_OK) goto rollback_finalize_alter;
24342434

24352435
return DBRES_OK;
@@ -3278,7 +3278,7 @@ int cloudsync_payload_save (cloudsync_context *data, const char *payload_path, i
32783278

32793279
// MARK: - Core -
32803280

3281-
int cloudsync_table_sanity_check (cloudsync_context *data, const char *name, bool skip_int_pk_check) {
3281+
int cloudsync_table_sanity_check (cloudsync_context *data, const char *name, CLOUDSYNC_INIT_FLAG init_flags) {
32823282
DEBUG_DBFUNCTION("cloudsync_table_sanity_check %s", name);
32833283
char buffer[2048];
32843284

@@ -3317,7 +3317,8 @@ int cloudsync_table_sanity_check (cloudsync_context *data, const char *name, boo
33173317
return cloudsync_set_error(data, buffer, DBRES_ERROR);
33183318
}
33193319
#endif
3320-
3320+
3321+
bool skip_int_pk_check = (init_flags & CLOUDSYNC_INIT_FLAG_SKIP_INT_PK_CHECK) != 0;
33213322
if (!skip_int_pk_check) {
33223323
if (npri_keys == 1) {
33233324
// the affinity of a column is determined by the declared type of the column,
@@ -3335,23 +3336,29 @@ int cloudsync_table_sanity_check (cloudsync_context *data, const char *name, boo
33353336

33363337
// if user declared explicit primary key(s) then make sure they are all declared as NOT NULL
33373338
#if CLOUDSYNC_CHECK_NOTNULL_PRIKEYS
3338-
if (npri_keys > 0) {
3339-
int npri_keys_notnull = database_count_pk(data, name, true, cloudsync_schema(data));
3340-
if (npri_keys_notnull < 0) return cloudsync_set_dberror(data);
3341-
if (npri_keys != npri_keys_notnull) {
3342-
snprintf(buffer, sizeof(buffer), "All primary keys must be explicitly declared as NOT NULL (table %s)", name);
3343-
return cloudsync_set_error(data, buffer, DBRES_ERROR);
3339+
bool skip_notnull_prikeys_check = (init_flags & CLOUDSYNC_INIT_FLAG_SKIP_NOT_NULL_PRIKEYS_CHECK) != 0;
3340+
if (!skip_notnull_prikeys_check) {
3341+
if (npri_keys > 0) {
3342+
int npri_keys_notnull = database_count_pk(data, name, true, cloudsync_schema(data));
3343+
if (npri_keys_notnull < 0) return cloudsync_set_dberror(data);
3344+
if (npri_keys != npri_keys_notnull) {
3345+
snprintf(buffer, sizeof(buffer), "All primary keys must be explicitly declared as NOT NULL (table %s)", name);
3346+
return cloudsync_set_error(data, buffer, DBRES_ERROR);
3347+
}
33443348
}
33453349
}
33463350
#endif
33473351

33483352
// check for columns declared as NOT NULL without a DEFAULT value.
33493353
// Otherwise, col_merge_stmt would fail if changes to other columns are inserted first.
3350-
int n_notnull_nodefault = database_count_notnull_without_default(data, name, cloudsync_schema(data));
3351-
if (n_notnull_nodefault < 0) return cloudsync_set_dberror(data);
3352-
if (n_notnull_nodefault > 0) {
3353-
snprintf(buffer, sizeof(buffer), "All non-primary key columns declared as NOT NULL must have a DEFAULT value. (table %s)", name);
3354-
return cloudsync_set_error(data, buffer, DBRES_ERROR);
3354+
bool skip_notnull_default_check = (init_flags & CLOUDSYNC_INIT_FLAG_SKIP_NOT_NULL_DEFAULT_CHECK) != 0;
3355+
if (!skip_notnull_default_check) {
3356+
int n_notnull_nodefault = database_count_notnull_without_default(data, name, cloudsync_schema(data));
3357+
if (n_notnull_nodefault < 0) return cloudsync_set_dberror(data);
3358+
if (n_notnull_nodefault > 0) {
3359+
snprintf(buffer, sizeof(buffer), "All non-primary key columns declared as NOT NULL must have a DEFAULT value. (table %s)", name);
3360+
return cloudsync_set_error(data, buffer, DBRES_ERROR);
3361+
}
33553362
}
33563363

33573364
return DBRES_OK;
@@ -3440,9 +3447,9 @@ int cloudsync_terminate (cloudsync_context *data) {
34403447
return 1;
34413448
}
34423449

3443-
int cloudsync_init_table (cloudsync_context *data, const char *table_name, const char *algo_name, bool skip_int_pk_check) {
3450+
int cloudsync_init_table (cloudsync_context *data, const char *table_name, const char *algo_name, CLOUDSYNC_INIT_FLAG init_flags) {
34443451
// sanity check table and its primary key(s)
3445-
int rc = cloudsync_table_sanity_check(data, table_name, skip_int_pk_check);
3452+
int rc = cloudsync_table_sanity_check(data, table_name, init_flags);
34463453
if (rc != DBRES_OK) return rc;
34473454

34483455
// init cloudsync_settings

src/cloudsync.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
extern "C" {
1919
#endif
2020

21-
#define CLOUDSYNC_VERSION "1.0.8"
21+
#define CLOUDSYNC_VERSION "1.0.9"
2222
#define CLOUDSYNC_MAX_TABLENAME_LEN 512
2323

2424
#define CLOUDSYNC_VALUE_NOTSET -1
@@ -29,6 +29,13 @@ extern "C" {
2929

3030
#define CLOUDSYNC_CHANGES_NCOLS 9
3131

32+
typedef enum {
33+
CLOUDSYNC_INIT_FLAG_NONE = 0,
34+
CLOUDSYNC_INIT_FLAG_SKIP_INT_PK_CHECK = 1 << 0, // 1
35+
CLOUDSYNC_INIT_FLAG_SKIP_NOT_NULL_DEFAULT_CHECK = 1 << 1, // 2
36+
CLOUDSYNC_INIT_FLAG_SKIP_NOT_NULL_PRIKEYS_CHECK = 1 << 2 // 4
37+
} CLOUDSYNC_INIT_FLAG;
38+
3239
// CRDT Algos
3340
table_algo cloudsync_algo_from_name (const char *algo_name);
3441
const char *cloudsync_algo_name (table_algo algo);
@@ -43,7 +50,7 @@ const char *cloudsync_context_init (cloudsync_context *data);
4350
void cloudsync_context_free (void *ctx);
4451

4552
// CloudSync global
46-
int cloudsync_init_table (cloudsync_context *data, const char *table_name, const char *algo_name, bool skip_int_pk_check);
53+
int cloudsync_init_table (cloudsync_context *data, const char *table_name, const char *algo_name, CLOUDSYNC_INIT_FLAG init_flags);
4754
int cloudsync_cleanup (cloudsync_context *data, const char *table_name);
4855
int cloudsync_cleanup_all (cloudsync_context *data);
4956
int cloudsync_terminate (cloudsync_context *data);

src/postgresql/cloudsync--1.0.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ RETURNS bytea
5555
AS 'MODULE_PATHNAME', 'cloudsync_init'
5656
LANGUAGE C VOLATILE;
5757

58-
CREATE OR REPLACE FUNCTION cloudsync_init(table_name text, algo text, skip_int_pk_check boolean)
58+
CREATE OR REPLACE FUNCTION cloudsync_init(table_name text, algo text, init_flags integer)
5959
RETURNS bytea
6060
AS 'MODULE_PATHNAME', 'cloudsync_init'
6161
LANGUAGE C VOLATILE;

src/postgresql/cloudsync_postgresql.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ Datum cloudsync_db_version_next (PG_FUNCTION_ARGS) {
272272

273273
// Internal helper for cloudsync_init - replicates dbsync_init logic from SQLite
274274
// Returns site_id as bytea on success, raises error on failure
275-
static bytea *cloudsync_init_internal (cloudsync_context *data, const char *table, const char *algo, bool skip_int_pk_check) {
275+
static bytea *cloudsync_init_internal (cloudsync_context *data, const char *table, const char *algo, CLOUDSYNC_INIT_FLAG init_flags) {
276276
bytea *result = NULL;
277277

278278
// Connect SPI for database operations
@@ -290,7 +290,7 @@ static bytea *cloudsync_init_internal (cloudsync_context *data, const char *tabl
290290
}
291291

292292
// Initialize table for sync
293-
rc = cloudsync_init_table(data, table, algo, skip_int_pk_check);
293+
rc = cloudsync_init_table(data, table, algo, init_flags);
294294
ereport(DEBUG1, (errmsg("cloudsync_init_internal cloudsync_init_table %d", rc)));
295295

296296
if (rc == DBRES_OK) {
@@ -332,8 +332,8 @@ static bytea *cloudsync_init_internal (cloudsync_context *data, const char *tabl
332332
return result;
333333
}
334334

335-
// cloudsync_init(table_name, [algo], [skip_int_pk_check]) - Initialize table for sync
336-
// Supports 1-3 arguments with defaults: algo=NULL, skip_int_pk_check=false
335+
// cloudsync_init(table_name, [algo], [init_flags]) - Initialize table for sync
336+
// Supports 1-3 arguments with defaults: algo=NULL, init_flags=CLOUDSYNC_INIT_FLAG_NONE
337337
PG_FUNCTION_INFO_V1(cloudsync_init);
338338
Datum cloudsync_init (PG_FUNCTION_ARGS) {
339339
if (PG_ARGISNULL(0)) {
@@ -344,7 +344,7 @@ Datum cloudsync_init (PG_FUNCTION_ARGS) {
344344

345345
// Default values
346346
const char *algo = NULL;
347-
bool skip_int_pk_check = false;
347+
int init_flags = CLOUDSYNC_INIT_FLAG_NONE;
348348

349349
// Handle optional arguments
350350
int nargs = PG_NARGS();
@@ -354,13 +354,13 @@ Datum cloudsync_init (PG_FUNCTION_ARGS) {
354354
}
355355

356356
if (nargs >= 3 && !PG_ARGISNULL(2)) {
357-
skip_int_pk_check = PG_GETARG_BOOL(2);
357+
init_flags = PG_GETARG_INT32(2);
358358
}
359359

360360
cloudsync_context *data = get_cloudsync_context();
361361

362362
// Call internal helper and return site_id as bytea
363-
bytea *result = cloudsync_init_internal(data, table, algo, skip_int_pk_check);
363+
bytea *result = cloudsync_init_internal(data, table, algo, init_flags);
364364
PG_RETURN_BYTEA_P(result);
365365
}
366366

src/sqlite/cloudsync_sqlite.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,7 @@ void dbsync_terminate (sqlite3_context *context, int argc, sqlite3_value **argv)
858858

859859
// MARK: -
860860

861-
void dbsync_init (sqlite3_context *context, const char *table, const char *algo, bool skip_int_pk_check) {
861+
void dbsync_init (sqlite3_context *context, const char *table, const char *algo, CLOUDSYNC_INIT_FLAG init_flags) {
862862
cloudsync_context *data = (cloudsync_context *)sqlite3_user_data(context);
863863

864864
int rc = database_begin_savepoint(data, "cloudsync_init");
@@ -868,7 +868,7 @@ void dbsync_init (sqlite3_context *context, const char *table, const char *algo,
868868
return;
869869
}
870870

871-
rc = cloudsync_init_table(data, table, algo, skip_int_pk_check);
871+
rc = cloudsync_init_table(data, table, algo, init_flags);
872872
if (rc == SQLITE_OK) {
873873
rc = database_commit_savepoint(data, "cloudsync_init");
874874
if (rc != SQLITE_OK) {
@@ -896,23 +896,23 @@ void dbsync_init3 (sqlite3_context *context, int argc, sqlite3_value **argv) {
896896

897897
const char *table = (const char *)database_value_text(argv[0]);
898898
const char *algo = (const char *)database_value_text(argv[1]);
899-
bool skip_int_pk_check = (bool)database_value_int(argv[2]);
900-
dbsync_init(context, table, algo, skip_int_pk_check);
899+
int init_flags = database_value_int(argv[2]);
900+
dbsync_init(context, table, algo, init_flags);
901901
}
902902

903903
void dbsync_init2 (sqlite3_context *context, int argc, sqlite3_value **argv) {
904904
DEBUG_FUNCTION("cloudsync_init2");
905905

906906
const char *table = (const char *)database_value_text(argv[0]);
907907
const char *algo = (const char *)database_value_text(argv[1]);
908-
dbsync_init(context, table, algo, false);
908+
dbsync_init(context, table, algo, CLOUDSYNC_INIT_FLAG_NONE);
909909
}
910910

911911
void dbsync_init1 (sqlite3_context *context, int argc, sqlite3_value **argv) {
912912
DEBUG_FUNCTION("cloudsync_init1");
913913

914914
const char *table = (const char *)database_value_text(argv[0]);
915-
dbsync_init(context, table, NULL, false);
915+
dbsync_init(context, table, NULL, CLOUDSYNC_INIT_FLAG_NONE);
916916
}
917917

918918
// MARK: -

0 commit comments

Comments
 (0)