|
@@ -0,0 +1,194 @@
|
|
|
|
|
+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.
|