AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3 4#include <stdio.h>5#include <stdlib.h>6#include <string.h>7 8/* Helper: assert rc==SQLITE_OK, else include sqlite3_errmsg in message */9static void assert_sqlite_ok_rc(int rc, sqlite3 *db, const char *msg){10 if( rc!=SQLITE_OK ){11 char buf[512];12 snprintf(buf, sizeof(buf), "%s (rc=%d, err=%s)", msg, rc,13 db ? sqlite3_errmsg(db) : "(null)");14 TEST_FAIL_MESSAGE(buf);15 }16}17 18static void exec_sql(sqlite3 *db, const char *sql){19 char *errmsg = NULL;20 int rc = sqlite3_exec(db, sql, 0, 0, &errmsg);21 if( rc!=SQLITE_OK ){22 char buf[512];23 snprintf(buf, sizeof(buf), "SQL error: %s; rc=%d; sql: %s",24 errmsg ? errmsg : "(null)", rc, sql);25 sqlite3_free(errmsg);26 TEST_FAIL_MESSAGE(buf);27 }28}29 30static int int_query(sqlite3 *db, const char *sql){31 sqlite3_stmt *stmt = NULL;32 int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);33 assert_sqlite_ok_rc(rc, db, "prepare failed");34 rc = sqlite3_step(stmt);35 if( rc!=SQLITE_ROW ){36 char buf[256];37 snprintf(buf, sizeof(buf), "Expected one row from: %s (rc=%d)", sql, rc);38 sqlite3_finalize(stmt);39 TEST_FAIL_MESSAGE(buf);40 }41 int v = sqlite3_column_int(stmt, 0);42 rc = sqlite3_finalize(stmt);43 assert_sqlite_ok_rc(rc, db, "finalize failed");44 return v;45}46 47static int pragma_page_size(sqlite3 *db){48 return int_query(db, "PRAGMA page_size");49}50 51void setUp(void) {52 /* Nothing needed */53}54void tearDown(void) {55 /* Nothing needed */56}57 58/* Test 1: Basic full backup from non-empty source to empty destination */59void test_sqlite3_backup_step_basic_copy_done(void){60 sqlite3 *src = NULL, *dst = NULL;61 int rc;62 63 assert_sqlite_ok_rc(sqlite3_open(":memory:", &src), src, "open src");64 assert_sqlite_ok_rc(sqlite3_open(":memory:", &dst), dst, "open dst");65 66 /* Prepare source with data */67 exec_sql(src, "CREATE TABLE t(a INTEGER PRIMARY KEY, b TEXT);"68 "INSERT INTO t(b) VALUES ('alpha'),('beta'),('gamma');");69 70 sqlite3_backup *p = sqlite3_backup_init(dst, "main", src, "main");71 TEST_ASSERT_NOT_NULL_MESSAGE(p, "sqlite3_backup_init returned NULL");72 73 rc = sqlite3_backup_step(p, -1);74 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_DONE, rc, "Expected SQLITE_DONE");75 76 /* Check page counts and remaining */77 TEST_ASSERT_EQUAL_INT(0, sqlite3_backup_remaining(p));78 TEST_ASSERT_TRUE_MESSAGE(sqlite3_backup_pagecount(p) >= 1, "Expected pagecount >= 1");79 80 rc = sqlite3_backup_finish(p);81 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);82 83 /* Verify data copied */84 int cnt = int_query(dst, "SELECT count(*) FROM t");85 TEST_ASSERT_EQUAL_INT(3, cnt);86 87 sqlite3_close(dst);88 sqlite3_close(src);89}90 91/* Test 2: Incremental backup with small nPage, ensure eventual DONE and remaining decreases */92void test_sqlite3_backup_step_partial_then_resume(void){93 sqlite3 *src = NULL, *dst = NULL;94 int rc;95 96 assert_sqlite_ok_rc(sqlite3_open(":memory:", &src), src, "open src");97 assert_sqlite_ok_rc(sqlite3_open(":memory:", &dst), dst, "open dst");98 99 /* Fill source with enough rows. Even if it fits in one page, the test tolerates DONE early. */100 exec_sql(src, "CREATE TABLE t(a INTEGER PRIMARY KEY, b BLOB);");101 /* Insert rows with some payload to possibly spill into multiple pages */102 exec_sql(src, "WITH RECURSIVE c(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM c WHERE x<200)"103 "INSERT INTO t(a,b) SELECT x, randomblob(200) FROM c;");104 105 sqlite3_backup *p = sqlite3_backup_init(dst, "main", src, "main");106 TEST_ASSERT_NOT_NULL(p);107 108 int lastRemaining = -1;109 int loops = 0;110 for(;;){111 rc = sqlite3_backup_step(p, 1); /* copy at most one page per call */112 if( rc==SQLITE_DONE ){113 break;114 }115 TEST_ASSERT_TRUE_MESSAGE(rc==SQLITE_OK || rc==SQLITE_BUSY, "Unexpected rc from partial step");116 117 /* Track remaining pages if available */118 int rem = sqlite3_backup_remaining(p);119 int pc = sqlite3_backup_pagecount(p);120 TEST_ASSERT_TRUE_MESSAGE(pc >= 0, "pagecount should be >= 0");121 if( lastRemaining>=0 ){122 TEST_ASSERT_TRUE_MESSAGE(rem <= lastRemaining, "remaining did not decrease or stay the same");123 }124 lastRemaining = rem;125 126 /* Avoid infinite loop in pathological cases */127 loops++;128 TEST_ASSERT_TRUE_MESSAGE(loops < 100000, "Too many iterations without DONE");129 }130 131 rc = sqlite3_backup_finish(p);132 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);133 134 /* Verify data copied */135 int srcCnt = int_query(src, "SELECT count(*) FROM t");136 int dstCnt = int_query(dst, "SELECT count(*) FROM t");137 TEST_ASSERT_EQUAL_INT(srcCnt, dstCnt);138 139 sqlite3_close(dst);140 sqlite3_close(src);141}142 143/* Test 3: SQLITE_BUSY when source holds a write transaction, then success after rollback */144void test_sqlite3_backup_step_busy_when_src_write_txn(void){145 sqlite3 *src = NULL, *dst = NULL;146 int rc;147 148 assert_sqlite_ok_rc(sqlite3_open(":memory:", &src), src, "open src");149 assert_sqlite_ok_rc(sqlite3_open(":memory:", &dst), dst, "open dst");150 151 exec_sql(src, "CREATE TABLE t(a); INSERT INTO t VALUES (1),(2),(3);");152 153 /* Begin a write transaction on source to force BUSY */154 exec_sql(src, "BEGIN IMMEDIATE");155 156 sqlite3_backup *p = sqlite3_backup_init(dst, "main", src, "main");157 TEST_ASSERT_NOT_NULL(p);158 159 rc = sqlite3_backup_step(p, 1);160 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_BUSY, rc, "Expected SQLITE_BUSY with source write transaction");161 162 /* Rollback the write transaction and try again */163 exec_sql(src, "ROLLBACK");164 165 rc = sqlite3_backup_step(p, -1);166 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_DONE, rc, "Expected SQLITE_DONE after releasing write txn");167 168 rc = sqlite3_backup_finish(p);169 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);170 171 /* Verify data copied */172 int cnt = int_query(dst, "SELECT count(*) FROM t");173 TEST_ASSERT_EQUAL_INT(3, cnt);174 175 sqlite3_close(dst);176 sqlite3_close(src);177}178 179/* Test 4: SQLITE_READONLY when destination is memdb with fixed page size different from source */180void test_sqlite3_backup_step_memdb_pgsize_mismatch_readonly(void){181 sqlite3 *src = NULL, *dst = NULL;182 int rc;183 184 assert_sqlite_ok_rc(sqlite3_open(":memory:", &src), src, "open src");185 assert_sqlite_ok_rc(sqlite3_open(":memory:", &dst), dst, "open dst");186 187 /* Set source page size to 1024 and create content (fixes page size) */188 exec_sql(src, "PRAGMA page_size=1024;");189 exec_sql(src, "CREATE TABLE s(x); INSERT INTO s VALUES (1);");190 191 /* Set destination page size to 4096 and create any object to fix it */192 exec_sql(dst, "PRAGMA page_size=4096;");193 exec_sql(dst, "CREATE TABLE d(y); INSERT INTO d VALUES (99);");194 195 /* Sanity: ensure different page sizes */196 int pgsSrc = pragma_page_size(src);197 int pgsDst = pragma_page_size(dst);198 TEST_ASSERT_NOT_EQUAL(pgsSrc, pgsDst);199 200 sqlite3_backup *p = sqlite3_backup_init(dst, "main", src, "main");201 TEST_ASSERT_NOT_NULL(p);202 203 rc = sqlite3_backup_step(p, -1);204 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_READONLY, rc,205 "Expected SQLITE_READONLY for memdb destination with differing page size");206 207 rc = sqlite3_backup_finish(p);208 TEST_ASSERT_EQUAL_INT(SQLITE_READONLY, rc);209 210 /* Verify destination content unaffected (table d still there with 1 row) */211 int cnt = int_query(dst, "SELECT count(*) FROM d");212 TEST_ASSERT_EQUAL_INT(1, cnt);213 214 sqlite3_close(dst);215 sqlite3_close(src);216}217 218/* Test 5: Backup from empty source database */219void test_sqlite3_backup_step_empty_source(void){220 sqlite3 *src = NULL, *dst = NULL;221 int rc;222 223 assert_sqlite_ok_rc(sqlite3_open(":memory:", &src), src, "open src");224 assert_sqlite_ok_rc(sqlite3_open(":memory:", &dst), dst, "open dst");225 226 /* Source remains empty (no schema changes) */227 228 sqlite3_backup *p = sqlite3_backup_init(dst, "main", src, "main");229 TEST_ASSERT_NOT_NULL(p);230 231 rc = sqlite3_backup_step(p, -1);232 TEST_ASSERT_EQUAL_INT(SQLITE_DONE, rc);233 234 TEST_ASSERT_EQUAL_INT(0, sqlite3_backup_remaining(p));235 TEST_ASSERT_TRUE(sqlite3_backup_pagecount(p) >= 0);236 237 rc = sqlite3_backup_finish(p);238 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);239 240 /* Destination should have empty schema */241 int sm = int_query(dst, "SELECT count(*) FROM sqlite_master WHERE type IN ('table','index','trigger','view')");242 TEST_ASSERT_EQUAL_INT(0, sm);243 244 sqlite3_close(dst);245 sqlite3_close(src);246}247 248/* Test 6: Destination page size is set to source page size for empty destination */249void test_sqlite3_backup_step_sets_dest_pagesize(void){250 sqlite3 *src = NULL, *dst = NULL;251 int rc;252 253 assert_sqlite_ok_rc(sqlite3_open(":memory:", &src), src, "open src");254 assert_sqlite_ok_rc(sqlite3_open(":memory:", &dst), dst, "open dst");255 256 /* Set source page size to 1024 and create data */257 exec_sql(src, "PRAGMA page_size=1024;");258 exec_sql(src, "CREATE TABLE s(a); INSERT INTO s VALUES (10),(20);");259 260 /* Destination left empty so its page size can be changed by backup_step */261 int destBefore = pragma_page_size(dst);262 263 sqlite3_backup *p = sqlite3_backup_init(dst, "main", src, "main");264 TEST_ASSERT_NOT_NULL(p);265 266 rc = sqlite3_backup_step(p, -1);267 TEST_ASSERT_EQUAL_INT(SQLITE_DONE, rc);268 269 rc = sqlite3_backup_finish(p);270 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);271 272 int pgsSrc = pragma_page_size(src);273 int pgsDst = pragma_page_size(dst);274 275 TEST_ASSERT_NOT_EQUAL_MESSAGE(-1, destBefore, "Invalid initial dest page size");276 TEST_ASSERT_EQUAL_INT_MESSAGE(pgsSrc, 1024, "Expected source page size 1024");277 TEST_ASSERT_EQUAL_INT_MESSAGE(pgsDst, pgsSrc, "Destination page size should match source");278 279 sqlite3_close(dst);280 sqlite3_close(src);281}282 283int main(void){284 UNITY_BEGIN();285 RUN_TEST(test_sqlite3_backup_step_basic_copy_done);286 RUN_TEST(test_sqlite3_backup_step_partial_then_resume);287 RUN_TEST(test_sqlite3_backup_step_busy_when_src_write_txn);288 RUN_TEST(test_sqlite3_backup_step_memdb_pgsize_mismatch_readonly);289 RUN_TEST(test_sqlite3_backup_step_empty_source);290 RUN_TEST(test_sqlite3_backup_step_sets_dest_pagesize);291 return UNITY_END();292}