MODULE SQLiteCapi3c; (* Broad C-API integration test, adapted from upstream sqlite-master/test/capi3c.test (public domain). Exercises open/exec/prepare_v2, bind, step, column metadata, scalar/BLOB column access, changes/rowid, autocommit, errors and close-with-outstanding-statement behavior. *) FROM SYSTEM IMPORT ADDRESS; FROM CStr IMPORT CString, Copy; FROM SQLite IMPORT SQLiteOk, SQLiteRow, SQLiteDone, SQLiteBusy, SQLiteInteger, SQLiteFloat, SQLiteText, SQLiteBlob, SQLiteNull, SQLiteOpenReadWrite, SQLiteOpenCreate, SQLiteOpenMemory, sqlite3_open_v2, sqlite3_close, sqlite3_exec, sqlite3_prepare_v2, sqlite3_step, sqlite3_finalize, sqlite3_bind_int, sqlite3_bind_text, sqlite3_column_count, sqlite3_column_name, sqlite3_column_decltype, sqlite3_column_type, sqlite3_column_int, sqlite3_column_int64, sqlite3_column_double, sqlite3_column_text, sqlite3_column_blob, sqlite3_column_bytes, sqlite3_changes, sqlite3_last_insert_rowid, sqlite3_errcode, sqlite3_errmsg, sqlite3_get_autocommit, sqlite3_free, SQLiteTransient; VAR ExitCode : INTEGER; db, stmt : ADDRESS; rc : INTEGER; ok : BOOLEAN; PROCEDURE SameText (a, b : ARRAY OF CHAR) : BOOLEAN; VAR i : INTEGER; BEGIN i := 0; WHILE (a[i] # CHR(0)) AND (a[i] = b[i]) DO i := i + 1 END; RETURN (a[i] = CHR(0)) AND (b[i] = CHR(0)) END SameText; PROCEDURE TextIs (s : CString; want : ARRAY OF CHAR) : BOOLEAN; VAR buf : ARRAY [0..127] OF CHAR; BEGIN IF s = NIL THEN RETURN FALSE END; IF Copy(s, buf) = 0 THEN RETURN FALSE END; RETURN SameText(buf, want) END TextIs; PROCEDURE ExecSQL (sql : ARRAY OF CHAR) : BOOLEAN; VAR errmsg : ADDRESS; good : BOOLEAN; BEGIN errmsg := NIL; good := sqlite3_exec(db, sql, NIL, NIL, errmsg) = SQLiteOk; IF errmsg # NIL THEN sqlite3_free(errmsg) END; RETURN good END ExecSQL; PROCEDURE Finish (VAR s : ADDRESS) : BOOLEAN; VAR good : BOOLEAN; BEGIN good := TRUE; IF s # NIL THEN IF sqlite3_finalize(s) # SQLiteOk THEN good := FALSE END; s := NIL END; RETURN good END Finish; BEGIN ExitCode := 1; db := NIL; stmt := NIL; ok := FALSE; IF (sqlite3_open_v2(":memory:", db, SQLiteOpenReadWrite + SQLiteOpenCreate + SQLiteOpenMemory, NIL) = SQLiteOk) AND (db # NIL) THEN ok := ExecSQL("CREATE TABLE t(a VARINT, b BLOB, c VARCHAR(16));") AND ExecSQL("INSERT INTO t VALUES(1, X'0102', 3);") AND ExecSQL("INSERT INTO t VALUES('one', 'two', NULL);") AND ExecSQL("INSERT INTO t VALUES(1.2, 1.3, 1.4);") END; (* Prepared SELECT and column metadata. *) IF ok THEN stmt := NIL; rc := sqlite3_prepare_v2(db, "SELECT * FROM t;", -1, stmt, NIL); ok := (rc = SQLiteOk) AND (stmt # NIL) AND (sqlite3_column_count(stmt) = 3) AND TextIs(sqlite3_column_name(stmt, 0), "a") AND TextIs(sqlite3_column_name(stmt, 1), "b") AND TextIs(sqlite3_column_name(stmt, 2), "c") AND TextIs(sqlite3_column_decltype(stmt, 0), "VARINT") AND TextIs(sqlite3_column_decltype(stmt, 1), "BLOB") AND TextIs(sqlite3_column_decltype(stmt, 2), "VARCHAR(16)") END; (* Integer + BLOB + integer row. *) IF ok THEN ok := sqlite3_step(stmt) = SQLiteRow; IF ok THEN ok := (sqlite3_column_type(stmt, 0) = SQLiteInteger) AND (sqlite3_column_int(stmt, 0) = 1) AND (sqlite3_column_int64(stmt, 0) = 1) AND (sqlite3_column_double(stmt, 0) = 1.0) AND (sqlite3_column_type(stmt, 1) = SQLiteBlob) AND (sqlite3_column_blob(stmt, 1) # NIL) AND (sqlite3_column_bytes(stmt, 1) = 2) AND (sqlite3_column_type(stmt, 2) = SQLiteText) AND (sqlite3_column_int(stmt, 2) = 3) AND TextIs(sqlite3_column_text(stmt, 2), "3") END END; (* Text + text + NULL row. *) IF ok THEN ok := sqlite3_step(stmt) = SQLiteRow; IF ok THEN ok := (sqlite3_column_type(stmt, 0) = SQLiteText) AND TextIs(sqlite3_column_text(stmt, 0), "one") AND (sqlite3_column_type(stmt, 1) = SQLiteText) AND (sqlite3_column_bytes(stmt, 1) = 3) AND TextIs(sqlite3_column_text(stmt, 1), "two") AND (sqlite3_column_type(stmt, 2) = SQLiteNull) AND (sqlite3_column_int(stmt, 2) = 0) END END; (* Real + real + real row. *) IF ok THEN ok := sqlite3_step(stmt) = SQLiteRow; IF ok THEN ok := (sqlite3_column_type(stmt, 0) = SQLiteFloat) AND (sqlite3_column_double(stmt, 0) = 1.2) AND (sqlite3_column_type(stmt, 1) = SQLiteFloat) AND (sqlite3_column_double(stmt, 1) = 1.3) AND (sqlite3_column_type(stmt, 2) = SQLiteText) AND (sqlite3_column_double(stmt, 2) = 1.4) AND TextIs(sqlite3_column_text(stmt, 2), "1.4") END END; IF ok THEN ok := sqlite3_step(stmt) = SQLiteDone END; IF NOT Finish(stmt) THEN ok := FALSE END; (* Bound insert and change counters. *) IF ok THEN stmt := NIL; rc := sqlite3_prepare_v2(db, "INSERT INTO t(a,c) VALUES(?,?);", -1, stmt, NIL); ok := (rc = SQLiteOk) AND (stmt # NIL) AND (sqlite3_bind_int(stmt, 1, 7) = SQLiteOk) AND (sqlite3_bind_text(stmt, 2, "seven", -1, SQLiteTransient()) = SQLiteOk); IF ok THEN ok := sqlite3_step(stmt) = SQLiteDone END; IF ok THEN ok := (sqlite3_changes(db) = 1) AND (sqlite3_last_insert_rowid(db) = 4) END; IF NOT Finish(stmt) THEN ok := FALSE END END; (* Autocommit state. *) IF ok THEN ok := (sqlite3_get_autocommit(db) = 1) AND ExecSQL("BEGIN;") AND (sqlite3_get_autocommit(db) = 0) AND ExecSQL("COMMIT;") AND (sqlite3_get_autocommit(db) = 1) END; (* A failed prepare leaves an error and a message. *) IF ok THEN stmt := NIL; rc := sqlite3_prepare_v2(db, "SELECT namex FROM sqlite_master;", -1, stmt, NIL); ok := (rc # SQLiteOk) AND (stmt = NIL) AND (sqlite3_errcode(db) # SQLiteOk) AND (sqlite3_errmsg(db) # NIL); IF NOT Finish(stmt) THEN ok := FALSE END END; (* close() must fail while a statement is outstanding. *) IF ok THEN stmt := NIL; rc := sqlite3_prepare_v2(db, "SELECT 1;", -1, stmt, NIL); ok := (rc = SQLiteOk) AND (stmt # NIL) AND (sqlite3_close(db) = SQLiteBusy); IF NOT Finish(stmt) THEN ok := FALSE END END; IF db # NIL THEN IF sqlite3_close(db) # SQLiteOk THEN ok := FALSE END; db := NIL END; IF ok THEN ExitCode := 42 END END SQLiteCapi3c.