@@ -252,6 +252,16 @@ sqlite3 *close_db (sqlite3 *db) {
252252 return NULL ;
253253}
254254
255+ int close_db_v2 (sqlite3 * db ) {
256+ int counter = 0 ;
257+ if (db ) {
258+ sqlite3_exec (db , "SELECT cloudsync_terminate();" , NULL , NULL , NULL );
259+ counter = dbutils_debug_stmt (db , true);
260+ sqlite3_close (db );
261+ }
262+ return counter ;
263+ }
264+
255265bool file_delete (const char * path ) {
256266 #ifdef _WIN32
257267 if (DeleteFile (path ) == 0 ) return false;
@@ -264,6 +274,142 @@ bool file_delete (const char *path) {
264274
265275// MARK: -
266276
277+ #ifndef UNITTEST_OMIT_RLS_VALIDATION
278+ typedef struct {
279+ bool in_savepoint ;
280+ bool is_approved ;
281+ bool last_is_delete ;
282+ char * last_tbl ;
283+ void * last_pk ;
284+ int64_t last_pk_len ;
285+ int64_t last_db_version ;
286+ } unittest_payload_apply_rls_status ;
287+
288+ bool unittest_validate_changed_row (sqlite3 * db , cloudsync_context * data , char * tbl_name , void * pk , int64_t pklen ) {
289+ // verify row
290+ bool ret = false;
291+ bool vm_persistent ;
292+ sqlite3_stmt * vm = cloudsync_col_value_stmt (db , data , tbl_name , & vm_persistent );
293+ if (!vm ) goto cleanup ;
294+
295+ // bind primary key values (the return code is the pk count)
296+ int rc = pk_decode_prikey ((char * )pk , (size_t )pklen , pk_decode_bind_callback , (void * )vm );
297+ if (rc < 0 ) goto cleanup ;
298+
299+ // execute vm
300+ rc = sqlite3_step (vm );
301+ if (rc == SQLITE_DONE ) {
302+ rc = SQLITE_OK ;
303+ } else if (rc == SQLITE_ROW ) {
304+ rc = SQLITE_OK ;
305+ ret = true;
306+ }
307+
308+ cleanup :
309+ if (vm_persistent ) sqlite3_reset (vm );
310+ else sqlite3_finalize (vm );
311+
312+ return ret ;
313+ }
314+
315+ int unittest_payload_apply_reset_transaction (sqlite3 * db , unittest_payload_apply_rls_status * s , bool create_new ) {
316+ int rc = SQLITE_OK ;
317+
318+ if (s -> in_savepoint == true) {
319+ if (s -> is_approved ) rc = sqlite3_exec (db , "RELEASE unittest_payload_apply_transaction" , NULL , NULL , NULL );
320+ else rc = sqlite3_exec (db , "ROLLBACK TO unittest_payload_apply_transaction; RELEASE unittest_payload_apply_transaction" , NULL , NULL , NULL );
321+ if (rc == SQLITE_OK ) s -> in_savepoint = false;
322+ }
323+ if (create_new ) {
324+ rc = sqlite3_exec (db , "SAVEPOINT unittest_payload_apply_transaction" , NULL , NULL , NULL );
325+ if (rc == SQLITE_OK ) s -> in_savepoint = true;
326+ }
327+ return rc ;
328+ }
329+
330+ bool unittest_payload_apply_rls_callback (void * * xdata , cloudsync_pk_decode_bind_context * d , sqlite3 * db , cloudsync_context * data , int step , int rc ) {
331+ unittest_payload_apply_rls_status * s ;
332+ if (* xdata ) {
333+ s = (unittest_payload_apply_rls_status * )* xdata ;
334+ } else {
335+ s = cloudsync_memory_zeroalloc (sizeof (unittest_payload_apply_rls_status ));
336+ s -> is_approved = true;
337+ * xdata = s ;
338+ }
339+
340+ switch (step ) {
341+ case CLOUDSYNC_PAYLOAD_APPLY_WILL_APPLY : {
342+ // if the tbl name or the prikey has changed, then verify if the row is valid
343+ // must use strncmp because strings in xdata are not zero-terminated
344+ bool tbl_changed = (s -> last_tbl && (strlen (s -> last_tbl ) != (size_t )d -> tbl_len || strncmp (s -> last_tbl , d -> tbl , (size_t )d -> tbl_len ) != 0 ));
345+ bool pk_changed = (s -> last_pk && d -> pk && cloudsync_blob_compare (s -> last_pk , s -> last_pk_len , d -> pk , d -> pk_len ) != 0 );
346+ if (s -> is_approved
347+ && !s -> last_is_delete
348+ && (tbl_changed || pk_changed )) {
349+ s -> is_approved = unittest_validate_changed_row (db , data , s -> last_tbl , s -> last_pk , s -> last_pk_len );
350+ }
351+
352+ s -> last_is_delete = ((size_t )d -> col_name_len == strlen (CLOUDSYNC_TOMBSTONE_VALUE ) &&
353+ strncmp (d -> col_name , CLOUDSYNC_TOMBSTONE_VALUE , (size_t )d -> col_name_len ) == 0
354+ ) && d -> cl % 2 == 0 ;
355+
356+ // update the last_tbl value, if needed
357+ if (!s -> last_tbl ||
358+ !d -> tbl ||
359+ (strlen (s -> last_tbl ) != (size_t )d -> tbl_len ) ||
360+ strncmp (s -> last_tbl , d -> tbl , (size_t )d -> tbl_len ) != 0 ) {
361+ if (s -> last_tbl ) cloudsync_memory_free (s -> last_tbl );
362+ if (d -> tbl && d -> tbl_len > 0 ) s -> last_tbl = cloudsync_string_ndup (d -> tbl , d -> tbl_len , false);
363+ else s -> last_tbl = NULL ;
364+ }
365+
366+ // update the last_prikey and len values, if needed
367+ if (!s -> last_pk || !d -> pk || cloudsync_blob_compare (s -> last_pk , s -> last_pk_len , d -> pk , d -> pk_len ) != 0 ) {
368+ if (s -> last_pk ) cloudsync_memory_free (s -> last_pk );
369+ if (d -> pk && d -> pk_len > 0 ) {
370+ s -> last_pk = cloudsync_memory_alloc (d -> pk_len );
371+ memcpy (s -> last_pk , d -> pk , d -> pk_len );
372+ s -> last_pk_len = d -> pk_len ;
373+ } else {
374+ s -> last_pk = NULL ;
375+ s -> last_pk_len = 0 ;
376+ }
377+ }
378+
379+ // commit the previous transaction, if any
380+ // begin new transacion, if needed
381+ if (s -> last_db_version != d -> db_version ) {
382+ rc = unittest_payload_apply_reset_transaction (db , s , true);
383+ if (rc != SQLITE_OK ) printf ("unittest_payload_apply error in reset_transaction: (%d) %s\n" , rc , sqlite3_errmsg (db ));
384+
385+ // reset local variables
386+ s -> last_db_version = d -> db_version ;
387+ s -> is_approved = true;
388+ }
389+ break ;
390+ }
391+ case CLOUDSYNC_PAYLOAD_APPLY_DID_APPLY :
392+ break ;
393+ case CLOUDSYNC_PAYLOAD_APPLY_CLEANUP :
394+ if (s -> is_approved && !s -> last_is_delete ) s -> is_approved = unittest_validate_changed_row (db , data , s -> last_tbl , s -> last_pk , s -> last_pk_len );
395+ rc = unittest_payload_apply_reset_transaction (db , s , false);
396+ if (s -> last_tbl ) cloudsync_memory_free (s -> last_tbl );
397+ if (s -> last_pk ) {
398+ cloudsync_memory_free (s -> last_pk );
399+ s -> last_pk_len = 0 ;
400+ }
401+
402+ cloudsync_memory_free (s );
403+ * xdata = NULL ;
404+ break ;
405+ }
406+
407+ return s -> is_approved ;
408+ }
409+ #endif
410+
411+ // MARK: -
412+
267413#ifndef CLOUDSYNC_OMIT_PRINT_RESULT
268414int do_query_cb (void * type , int argc , char * * argv , char * * azColName ) {
269415 int query_type = 0 ;
@@ -2028,6 +2174,8 @@ sqlite3 *do_create_database_file (int i, time_t timestamp, int ntest) {
20282174 return NULL ;
20292175 }
20302176
2177+ sqlite3_exec (db , "PRAGMA journal_mode=WAL;" , NULL , NULL , NULL );
2178+
20312179 // manually load extension
20322180 sqlite3_cloudsync_init (db , NULL , NULL );
20332181
@@ -2093,8 +2241,24 @@ bool do_test_merge (int nclients, bool print_result, bool cleanup_databases) {
20932241
20942242finalize :
20952243 for (int i = 0 ; i < nclients ; ++ i ) {
2096- if (rc != SQLITE_OK && db [i ] && (sqlite3_errcode (db [i ]) != SQLITE_OK )) printf ("do_test_merge error: %s\n" , sqlite3_errmsg (db [i ]));
2097- if (db [i ]) close_db (db [i ]);
2244+ if (rc != SQLITE_OK && db [i ] && (sqlite3_errcode (db [i ]) != SQLITE_OK )) {
2245+ result = false;
2246+ printf ("do_test_merge error: %s\n" , sqlite3_errmsg (db [i ]));
2247+ }
2248+
2249+ if (db [i ]) {
2250+ if (sqlite3_get_autocommit (db [i ]) == 0 ) {
2251+ result = false;
2252+ printf ("do_test_merge error: db %d is in transaction\n" , i );
2253+ }
2254+
2255+ int counter = close_db_v2 (db [i ]);
2256+ if (counter > 0 ) {
2257+ result = false;
2258+ printf ("do_test_merge error: db %d has %d unterminated statements\n" , i , counter );
2259+ }
2260+ }
2261+
20982262 if (cleanup_databases ) {
20992263 char buf [256 ];
21002264 do_build_database_path (buf , i , timestamp , saved_counter ++ );
@@ -2171,12 +2335,12 @@ bool do_test_merge_2 (int nclients, int table_mask, bool print_result, bool clea
21712335
21722336 // deleta data in the first customer
21732337 do_delete (db [0 ], table_mask , print_result );
2174-
2338+
21752339 // merge all changes
21762340 if (do_merge (db , nclients , false) == false) {
21772341 goto finalize ;
21782342 }
2179-
2343+
21802344 // compare results
21812345 for (int i = 1 ; i < nclients ; ++ i ) {
21822346 if (table_mask & TEST_PRIKEYS ) {
@@ -2206,7 +2370,18 @@ bool do_test_merge_2 (int nclients, int table_mask, bool print_result, bool clea
22062370finalize :
22072371 for (int i = 0 ; i < nclients ; ++ i ) {
22082372 if (rc != SQLITE_OK && db [i ] && (sqlite3_errcode (db [i ]) != SQLITE_OK )) printf ("do_test_merge error: %s\n" , sqlite3_errmsg (db [i ]));
2209- if (db [i ]) close_db (db [i ]);
2373+ if (db [i ]) {
2374+ if (sqlite3_get_autocommit (db [i ]) == 0 ) {
2375+ result = false;
2376+ printf ("do_test_merge error: db %d is in transaction\n" , i );
2377+ }
2378+
2379+ int counter = close_db_v2 (db [i ]);
2380+ if (counter > 0 ) {
2381+ result = false;
2382+ printf ("do_test_merge error: db %d has %d unterminated statements\n" , i , counter );
2383+ }
2384+ }
22102385 if (cleanup_databases ) {
22112386 char buf [256 ];
22122387 do_build_database_path (buf , i , timestamp , saved_counter ++ );
@@ -2939,7 +3114,20 @@ bool do_test_fill_initial_data(int nclients, bool print_result, bool cleanup_dat
29393114finalize :
29403115 for (int i = 0 ; i < nclients ; ++ i ) {
29413116 if (rc != SQLITE_OK && db [i ] && (sqlite3_errcode (db [i ]) != SQLITE_OK )) printf ("do_test_merge error: %s\n" , sqlite3_errmsg (db [i ]));
2942- if (db [i ]) close_db (db [i ]);
3117+
3118+ if (db [i ]) {
3119+ if (sqlite3_get_autocommit (db [i ]) == 0 ) {
3120+ result = false;
3121+ printf ("do_test_merge error: db %d is in transaction\n" , i );
3122+ }
3123+
3124+ int counter = close_db_v2 (db [i ]);
3125+ if (counter > 0 ) {
3126+ result = false;
3127+ printf ("do_test_merge error: db %d has %d unterminated statements\n" , i , counter );
3128+ }
3129+ }
3130+
29433131 if (cleanup_databases ) {
29443132 char buf [256 ];
29453133 do_build_database_path (buf , i , timestamp , saved_counter ++ );
@@ -3081,6 +3269,7 @@ int main(int argc, const char * argv[]) {
30813269
30823270 // manually load extension
30833271 sqlite3_cloudsync_init (db , NULL , NULL );
3272+ cloudsync_payload_apply_callback (unittest_payload_apply_rls_callback );
30843273
30853274 printf ("Testing CloudSync version %s\n" , CLOUDSYNC_VERSION );
30863275 printf ("===============================\n" );
0 commit comments