|
|
@@ -0,0 +1,145 @@
|
|
|
+MODULE test_blob ;
|
|
|
+
|
|
|
+(*
|
|
|
+ m2SQLITE test: incremental BLOB I/O and the online backup API.
|
|
|
+ Covers open/read/write/reopen/bytes/close, the read-only write
|
|
|
+ error, direct backup paging, and the BackupToFile helper with
|
|
|
+ a temp file that is removed afterwards.
|
|
|
+*)
|
|
|
+
|
|
|
+FROM SYSTEM IMPORT ADDRESS, ADR;
|
|
|
+FROM SQLite IMPORT DbHandle, StmtHandle, BlobHandle, BackupHandle,
|
|
|
+ SQLiteOk, SQLiteRow, SQLiteDone, SQLiteReadOnly,
|
|
|
+ sqlite3_open, sqlite3_close,
|
|
|
+ sqlite3_prepare_v2, sqlite3_step, sqlite3_finalize,
|
|
|
+ sqlite3_last_insert_rowid,
|
|
|
+ sqlite3_column_int,
|
|
|
+ sqlite3_blob_open, sqlite3_blob_reopen, sqlite3_blob_close,
|
|
|
+ sqlite3_blob_bytes, sqlite3_blob_read, sqlite3_blob_write,
|
|
|
+ sqlite3_backup_init, sqlite3_backup_step, sqlite3_backup_finish,
|
|
|
+ sqlite3_backup_remaining, sqlite3_backup_pagecount;
|
|
|
+FROM SQLiteUtils IMPORT ErrMsg, ExecSimple, BackupToFile;
|
|
|
+FROM libc IMPORT printf, unlink;
|
|
|
+
|
|
|
+VAR
|
|
|
+ db, db2: DbHandle; stmt: StmtHandle;
|
|
|
+ bh, bh2: BlobHandle; bk: BackupHandle;
|
|
|
+ row1, row2: LONGINT;
|
|
|
+ wbuf: ARRAY [0..15] OF CHAR;
|
|
|
+ rbuf: ARRAY [0..255] OF CHAR;
|
|
|
+ path: ARRAY [0..63] OF CHAR;
|
|
|
+ i: INTEGER;
|
|
|
+
|
|
|
+PROCEDURE fail (what: ARRAY OF CHAR);
|
|
|
+VAR e: ARRAY [0..255] OF CHAR;
|
|
|
+BEGIN
|
|
|
+ ErrMsg(db, e);
|
|
|
+ printf("FAIL %s: %s\n", what, e);
|
|
|
+ HALT(1)
|
|
|
+END fail;
|
|
|
+
|
|
|
+PROCEDURE check (rc: INTEGER; what: ARRAY OF CHAR);
|
|
|
+BEGIN
|
|
|
+ IF rc # SQLiteOk THEN fail(what) END
|
|
|
+END check;
|
|
|
+
|
|
|
+PROCEDURE RemoveFile (name: ARRAY OF CHAR);
|
|
|
+VAR buf: ARRAY [0..255] OF CHAR; n: INTEGER; rc: INTEGER;
|
|
|
+BEGIN
|
|
|
+ n := 0;
|
|
|
+ WHILE (VAL(CARDINAL, n) <= HIGH(buf))
|
|
|
+ AND (VAL(CARDINAL, n) <= HIGH(name))
|
|
|
+ AND (name[n] # 0C) DO
|
|
|
+ buf[n] := name[n];
|
|
|
+ INC(n)
|
|
|
+ END;
|
|
|
+ IF VAL(CARDINAL, n) <= HIGH(buf) THEN buf[n] := 0C END;
|
|
|
+ rc := unlink(ADR(buf))
|
|
|
+END RemoveFile;
|
|
|
+
|
|
|
+PROCEDURE countFiles (h: DbHandle; what: ARRAY OF CHAR) : INTEGER;
|
|
|
+VAR st: StmtHandle; n, rc: INTEGER;
|
|
|
+BEGIN
|
|
|
+ rc := sqlite3_prepare_v2(h, "SELECT COUNT(*) FROM files;",
|
|
|
+ -1, st, NIL);
|
|
|
+ IF rc # SQLiteOk THEN fail(what) END;
|
|
|
+ IF sqlite3_step(st) # SQLiteRow THEN fail(what) END;
|
|
|
+ n := sqlite3_column_int(st, 0);
|
|
|
+ IF sqlite3_finalize(st) # SQLiteOk THEN fail(what) END;
|
|
|
+ RETURN n
|
|
|
+END countFiles;
|
|
|
+
|
|
|
+BEGIN
|
|
|
+ FOR i := 0 TO 15 DO
|
|
|
+ wbuf[i] := CHR(ORD('A') + VAL(CARDINAL, i))
|
|
|
+ END;
|
|
|
+ check(sqlite3_open(":memory:", db), "open");
|
|
|
+ check(ExecSimple(db, "CREATE TABLE files(name TEXT, data BLOB);"),
|
|
|
+ "create");
|
|
|
+ check(ExecSimple(db, "INSERT INTO files VALUES('a', zeroblob(256));"),
|
|
|
+ "seed1");
|
|
|
+ row1 := sqlite3_last_insert_rowid(db);
|
|
|
+ check(ExecSimple(db, "INSERT INTO files VALUES('b', zeroblob(16));"),
|
|
|
+ "seed2");
|
|
|
+ row2 := sqlite3_last_insert_rowid(db);
|
|
|
+
|
|
|
+ (* read-write handle, size check, write, read back *)
|
|
|
+ check(sqlite3_blob_open(db, "main", "files", "data", row1, 1, bh),
|
|
|
+ "blob open");
|
|
|
+ IF sqlite3_blob_bytes(bh) # 256 THEN fail("blob bytes") END;
|
|
|
+ check(sqlite3_blob_write(bh, ADR(wbuf), 16, 10), "blob write");
|
|
|
+ check(sqlite3_blob_read(bh, ADR(rbuf), 16, 10), "blob read");
|
|
|
+ FOR i := 0 TO 15 DO
|
|
|
+ IF rbuf[i] # wbuf[i] THEN fail("blob roundtrip") END
|
|
|
+ END;
|
|
|
+ printf("blob roundtrip ok\n");
|
|
|
+
|
|
|
+ (* move the handle to the second row *)
|
|
|
+ check(sqlite3_blob_reopen(bh, row2), "blob reopen");
|
|
|
+ IF sqlite3_blob_bytes(bh) # 16 THEN fail("reopen bytes") END;
|
|
|
+ check(sqlite3_blob_close(bh), "blob close");
|
|
|
+
|
|
|
+ (* read-only handle must refuse writes *)
|
|
|
+ check(sqlite3_blob_open(db, "main", "files", "data", row1, 0, bh2),
|
|
|
+ "ro open");
|
|
|
+ IF sqlite3_blob_write(bh2, ADR(wbuf), 1, 0) # SQLiteReadOnly THEN
|
|
|
+ fail("ro write accepted")
|
|
|
+ END;
|
|
|
+ printf("readonly guard ok\n");
|
|
|
+ (* close relays the handle's pending write error, but the
|
|
|
+ handle is released regardless *)
|
|
|
+ i := sqlite3_blob_close(bh2);
|
|
|
+ printf("ro close rc %d\n", i);
|
|
|
+
|
|
|
+ (* direct backup API into a second memory database *)
|
|
|
+ check(sqlite3_open(":memory:", db2), "open dst");
|
|
|
+ bk := sqlite3_backup_init(db2, "main", db, "main");
|
|
|
+ IF bk = NIL THEN fail("backup init") END;
|
|
|
+ i := sqlite3_backup_step(bk, -1);
|
|
|
+ printf("backup step %d remaining %d pages %d\n",
|
|
|
+ i, sqlite3_backup_remaining(bk),
|
|
|
+ sqlite3_backup_pagecount(bk));
|
|
|
+ IF i # SQLiteDone THEN fail("backup step") END;
|
|
|
+ IF sqlite3_backup_remaining(bk) # 0 THEN fail("remaining") END;
|
|
|
+ IF sqlite3_backup_pagecount(bk) <= 0 THEN fail("pagecount") END;
|
|
|
+ check(sqlite3_backup_finish(bk), "backup finish");
|
|
|
+ IF countFiles(db2, "verify") # 2 THEN fail("backup content") END;
|
|
|
+ printf("backup copy ok\n");
|
|
|
+ check(sqlite3_close(db2), "close dst");
|
|
|
+
|
|
|
+ (* helper to a temp file, verified and removed *)
|
|
|
+ path[0] := '/'; path[1] := 't'; path[2] := 'm'; path[3] := 'p';
|
|
|
+ path[4] := '/'; path[5] := 'm'; path[6] := '2'; path[7] := 's';
|
|
|
+ path[8] := 'q'; path[9] := 'l'; path[10] := 'i'; path[11] := 't';
|
|
|
+ path[12] := 'e'; path[13] := '_'; path[14] := 'b'; path[15] := 'k';
|
|
|
+ path[16] := '.'; path[17] := 'd'; path[18] := 'b'; path[19] := 0C;
|
|
|
+ check(BackupToFile(db, path), "backup file");
|
|
|
+ check(sqlite3_open(path, db2), "open file");
|
|
|
+ IF countFiles(db2, "file content") # 2 THEN fail("file rows") END;
|
|
|
+ check(sqlite3_close(db2), "close file");
|
|
|
+ RemoveFile(path);
|
|
|
+ printf("backup file ok\n");
|
|
|
+
|
|
|
+ check(sqlite3_close(db), "close");
|
|
|
+ printf("PASS test_blob\n")
|
|
|
+END test_blob.
|