|
| 1 | +#include <sqlite3.h> |
| 2 | +#include <stdio.h> |
| 3 | +#include <string.h> |
| 4 | + |
| 5 | +#if 0 |
| 6 | +static void dump_frame(unsigned char *frame, size_t size){ |
| 7 | + for(int addr=0; addr<size; addr+=16){ |
| 8 | + int sum = 0; |
| 9 | + for(int i=0; i<16 && addr+1<size; i++){ |
| 10 | + sum += frame[addr+i] != 0; |
| 11 | + } |
| 12 | + if( sum ){ |
| 13 | + printf("%08x: ", addr); |
| 14 | + for(int i=0; i<16 && addr+i<size; i++){ |
| 15 | + printf("%02x ", frame[addr+i]); |
| 16 | + } |
| 17 | + printf(" |"); |
| 18 | + for(int i=0; i<16 && addr+i<size; i++){ |
| 19 | + printf("%c", frame[addr+i] ? frame[addr+i] : '.'); |
| 20 | + } |
| 21 | + printf("|"); |
| 22 | + printf("\n"); |
| 23 | + } |
| 24 | + } |
| 25 | +} |
| 26 | +#endif |
| 27 | + |
| 28 | +static int cmp_data(sqlite3 *db1, sqlite3 *db2){ |
| 29 | + sqlite3_stmt *stmt1, *stmt2; |
| 30 | + int rc; |
| 31 | + |
| 32 | + rc = sqlite3_prepare_v2(db1, "SELECT * FROM users", -1, &stmt1, 0); |
| 33 | + if( rc!=SQLITE_OK ){ |
| 34 | + fprintf(stderr, "Can't prepare statement: %s\n", sqlite3_errmsg(db1)); |
| 35 | + return 1; |
| 36 | + } |
| 37 | + |
| 38 | + rc = sqlite3_prepare_v2(db2, "SELECT * FROM users", -1, &stmt2, 0); |
| 39 | + if( rc!=SQLITE_OK ){ |
| 40 | + fprintf(stderr, "Can't prepare statement: %s\n", sqlite3_errmsg(db2)); |
| 41 | + return 1; |
| 42 | + } |
| 43 | + |
| 44 | + for(;;){ |
| 45 | + int step1 = sqlite3_step(stmt1); |
| 46 | + int step2 = sqlite3_step(stmt2); |
| 47 | + if( step1!=step2 ){ |
| 48 | + fprintf(stderr, "Step mismatch: %d != %d\n", step1, step2); |
| 49 | + return 1; |
| 50 | + } |
| 51 | + if( step1!=SQLITE_ROW ){ |
| 52 | + break; |
| 53 | + } |
| 54 | + const unsigned char *name1 = sqlite3_column_text(stmt1, 1); |
| 55 | + const unsigned char *name2 = sqlite3_column_text(stmt2, 1); |
| 56 | + if( strcmp((const char *)name1, (const char *)name2)!=0 ){ |
| 57 | + fprintf(stderr, "Data mismatch: %s != %s\n", name1, name2); |
| 58 | + return 1; |
| 59 | + } |
| 60 | + } |
| 61 | + return 0; |
| 62 | +} |
| 63 | + |
| 64 | +static int sync_db(sqlite3 *db_primary, sqlite3 *db_backup){ |
| 65 | + unsigned int max_frame; |
| 66 | + int rc; |
| 67 | + |
| 68 | + rc = libsql_wal_frame_count(db_primary, &max_frame); |
| 69 | + if( rc!=SQLITE_OK ){ |
| 70 | + fprintf(stderr, "Can't get frame count: %s\n", sqlite3_errmsg(db_primary)); |
| 71 | + return 1; |
| 72 | + } |
| 73 | + rc = libsql_wal_insert_begin(db_backup); |
| 74 | + if( rc!=SQLITE_OK ){ |
| 75 | + fprintf(stderr, "Can't begin commit: %s\n", sqlite3_errmsg(db_backup)); |
| 76 | + return 1; |
| 77 | + } |
| 78 | + for(int i=1; i<=max_frame; i++){ |
| 79 | + char frame[4096+24]; |
| 80 | + rc = libsql_wal_get_frame(db_primary, i, frame, sizeof(frame)); |
| 81 | + if( rc!=SQLITE_OK ){ |
| 82 | + fprintf(stderr, "Can't get frame: %s\n", sqlite3_errmsg(db_primary)); |
| 83 | + return 1; |
| 84 | + } |
| 85 | + rc = libsql_wal_insert_frame(db_backup, i, frame, sizeof(frame)); |
| 86 | + if( rc!=SQLITE_OK ){ |
| 87 | + fprintf(stderr, "Can't inject frame %d: %s\n", rc, sqlite3_errmsg(db_backup)); |
| 88 | + return 1; |
| 89 | + } |
| 90 | + } |
| 91 | + rc = libsql_wal_insert_end(db_backup); |
| 92 | + if( rc!=SQLITE_OK ){ |
| 93 | + fprintf(stderr, "Can't end commit: %s\n", sqlite3_errmsg(db_backup)); |
| 94 | + return 1; |
| 95 | + } |
| 96 | + return 0; |
| 97 | +} |
| 98 | + |
| 99 | +static void gen_data(sqlite3 *db){ |
| 100 | + sqlite3_exec(db, "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)", 0, 0, 0); |
| 101 | + sqlite3_exec(db, "INSERT INTO users (id, name) VALUES (1, 'John Doe')", 0, 0, 0); |
| 102 | + sqlite3_exec(db, "INSERT INTO users (id, name) VALUES (2, 'Jane Doe')", 0, 0, 0); |
| 103 | + sqlite3_exec(db, "INSERT INTO users (id, name) VALUES (3, 'Jim Beam')", 0, 0, 0); |
| 104 | +} |
| 105 | + |
| 106 | +int main(int argc, char *argv[]) |
| 107 | +{ |
| 108 | + sqlite3 *db_primary, *db_backup; |
| 109 | + int rc; |
| 110 | + |
| 111 | + rc = sqlite3_open("primary.db", &db_primary); |
| 112 | + if (rc != SQLITE_OK) { |
| 113 | + fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db_primary)); |
| 114 | + return 1; |
| 115 | + } |
| 116 | + rc = sqlite3_wal_autocheckpoint(db_primary, 0); |
| 117 | + if (rc != SQLITE_OK) { |
| 118 | + fprintf(stderr, "Can't disable checkpointing: %s\n", sqlite3_errmsg(db_primary)); |
| 119 | + return 1; |
| 120 | + } |
| 121 | + sqlite3_exec(db_primary, "PRAGMA journal_mode=WAL", NULL, NULL, NULL); |
| 122 | + |
| 123 | + gen_data(db_primary); |
| 124 | + |
| 125 | + rc = sqlite3_open("backup.db", &db_backup); |
| 126 | + if (rc != SQLITE_OK) { |
| 127 | + fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db_backup)); |
| 128 | + return 1; |
| 129 | + } |
| 130 | + rc = sqlite3_wal_autocheckpoint(db_backup, 0); |
| 131 | + if (rc != SQLITE_OK) { |
| 132 | + fprintf(stderr, "Can't disable checkpointing: %s\n", sqlite3_errmsg(db_backup)); |
| 133 | + return 1; |
| 134 | + } |
| 135 | + rc = sqlite3_exec(db_backup, "PRAGMA journal_mode=WAL", NULL, NULL, NULL); |
| 136 | + if (rc != SQLITE_OK) { |
| 137 | + fprintf(stderr, "Can't set journal mode: %s\n", sqlite3_errmsg(db_backup)); |
| 138 | + return 1; |
| 139 | + } |
| 140 | + |
| 141 | + sync_db(db_primary, db_backup); |
| 142 | + if (cmp_data(db_primary, db_backup)) { |
| 143 | + return 1; |
| 144 | + } |
| 145 | + |
| 146 | + sync_db(db_primary, db_backup); |
| 147 | + if (cmp_data(db_primary, db_backup)) { |
| 148 | + return 1; |
| 149 | + } |
| 150 | + |
| 151 | + printf("OK\n"); |
| 152 | + |
| 153 | + return 0; |
| 154 | +} |
0 commit comments