MODULE SQLiteIntrospection; (* Statement metadata and library diagnostics: sql, expanded_sql, readonly, busy, next_stmt, stmt_status, version/sourceid, error_offset, autocommit, complete and limits. *) FROM SYSTEM IMPORT ADDRESS; FROM CStr IMPORT CString, Copy; FROM SQLite IMPORT SQLiteOk, SQLiteRow, SQLiteDone, SQLiteOpenReadWrite, SQLiteOpenCreate, SQLiteOpenMemory, SQLiteLimitLikePatternLength, SQLiteStmtStatusVmStep, sqlite3_open_v2, sqlite3_exec, sqlite3_close_v2, sqlite3_prepare_v2, sqlite3_step, sqlite3_finalize, sqlite3_bind_int, sqlite3_sql, sqlite3_expanded_sql, sqlite3_stmt_readonly, sqlite3_stmt_busy, sqlite3_next_stmt, sqlite3_stmt_status, sqlite3_libversion, sqlite3_libversion_number, sqlite3_sourceid, sqlite3_threadsafe, sqlite3_error_offset, sqlite3_get_autocommit, sqlite3_complete, sqlite3_limit, sqlite3_free; VAR ExitCode : INTEGER; db, stmt, s1, s2, p, q : 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 TextNonEmpty (s : CString) : BOOLEAN; VAR buf : ARRAY [0..127] OF CHAR; BEGIN IF s = NIL THEN RETURN FALSE END; RETURN Copy(s, buf) > 0 END TextNonEmpty; 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; s1 := NIL; s2 := NIL; p := NIL; q := NIL; ok := FALSE; IF (sqlite3_open_v2(":memory:", db, SQLiteOpenReadWrite + SQLiteOpenCreate + SQLiteOpenMemory, NIL) = SQLiteOk) AND (db # NIL) THEN ok := (sqlite3_libversion_number() > 0) AND TextNonEmpty(sqlite3_libversion()) AND TextNonEmpty(sqlite3_sourceid()) AND (sqlite3_threadsafe() > 0); IF ok THEN ok := (sqlite3_complete("SELECT 1;") = 1) AND (sqlite3_complete("SELECT 1") = 0) END; 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; IF ok THEN rc := sqlite3_limit(db, SQLiteLimitLikePatternLength, -1); ok := (rc > 0) AND (sqlite3_limit(db, SQLiteLimitLikePatternLength, 1000) = rc) AND (sqlite3_limit(db, SQLiteLimitLikePatternLength, -1) = 1000) AND (sqlite3_limit(db, SQLiteLimitLikePatternLength, rc) = 1000) END; IF ok THEN ok := ExecSQL("CREATE TABLE t(x INTEGER);") END; (* Original SQL, expanded SQL, readonly/busy and VM counters. *) IF ok THEN stmt := NIL; rc := sqlite3_prepare_v2(db, "SELECT ?;", -1, stmt, NIL); ok := (rc = SQLiteOk) AND (stmt # NIL) AND (sqlite3_stmt_readonly(stmt) = 1) AND TextIs(sqlite3_sql(stmt), "SELECT ?;") AND (sqlite3_stmt_busy(stmt) = 0); IF ok THEN ok := sqlite3_bind_int(stmt, 1, 42) = SQLiteOk END; IF ok THEN p := sqlite3_expanded_sql(stmt); ok := (p # NIL) AND TextIs(p, "SELECT 42;"); IF p # NIL THEN sqlite3_free(p); p := NIL END END; IF ok THEN ok := sqlite3_step(stmt) = SQLiteRow END; IF ok THEN ok := sqlite3_stmt_busy(stmt) = 1 END; IF ok THEN ok := sqlite3_stmt_status(stmt, SQLiteStmtStatusVmStep, 0) > 0 END; IF ok THEN ok := sqlite3_step(stmt) = SQLiteDone END; IF ok THEN ok := sqlite3_stmt_busy(stmt) = 0 END; IF ok THEN ok := (sqlite3_stmt_status(stmt, SQLiteStmtStatusVmStep, 1) > 0) AND (sqlite3_stmt_status(stmt, SQLiteStmtStatusVmStep, 0) = 0) END; IF NOT Finish(stmt) THEN ok := FALSE END END; (* A write statement is not readonly. *) IF ok THEN stmt := NIL; rc := sqlite3_prepare_v2(db, "INSERT INTO t(x) VALUES(1);", -1, stmt, NIL); ok := (rc = SQLiteOk) AND (stmt # NIL) AND (sqlite3_stmt_readonly(stmt) = 0); IF NOT Finish(stmt) THEN ok := FALSE END END; (* Syntax errors expose a byte offset. *) IF ok THEN stmt := NIL; rc := sqlite3_prepare_v2(db, "SELECT * FROM;", -1, stmt, NIL); ok := (rc # SQLiteOk) AND (stmt = NIL) AND (sqlite3_error_offset(db) >= 0); IF NOT Finish(stmt) THEN ok := FALSE END END; (* Statement iteration: exactly the two prepared statements. *) IF ok THEN rc := sqlite3_prepare_v2(db, "SELECT 1;", -1, s1, NIL); ok := (rc = SQLiteOk) AND (s1 # NIL); IF ok THEN rc := sqlite3_prepare_v2(db, "SELECT 2;", -1, s2, NIL); ok := (rc = SQLiteOk) AND (s2 # NIL) END; IF ok THEN p := sqlite3_next_stmt(db, NIL); q := sqlite3_next_stmt(db, p); ok := (p # NIL) AND (q # NIL) AND (p # q) AND (sqlite3_next_stmt(db, q) = NIL) END; IF NOT Finish(s1) THEN ok := FALSE END; IF NOT Finish(s2) THEN ok := FALSE END END END; IF NOT Finish(stmt) THEN ok := FALSE END; IF NOT Finish(s1) THEN ok := FALSE END; IF NOT Finish(s2) THEN ok := FALSE END; IF db # NIL THEN IF sqlite3_close_v2(db) # SQLiteOk THEN ok := FALSE END END; IF ok THEN ExitCode := 42 END END SQLiteIntrospection.