|
|
@@ -0,0 +1,240 @@
|
|
|
+MODULE SQLiteHooks;
|
|
|
+(* Connection hooks:
|
|
|
+ busy handler/timeout, progress, trace_v2, update/commit/rollback,
|
|
|
+ authorizer and sqlite3_db_handle. *)
|
|
|
+
|
|
|
+FROM SYSTEM IMPORT ADDRESS, ADR;
|
|
|
+FROM CStr IMPORT CString, Copy;
|
|
|
+FROM SQLite IMPORT
|
|
|
+ SQLiteOk, SQLiteRow, SQLiteDone, SQLiteAuth,
|
|
|
+ SQLiteOpenReadWrite, SQLiteOpenCreate, SQLiteOpenMemory,
|
|
|
+ SQLiteOpInsert, SQLiteOpUpdate, SQLiteOpDelete,
|
|
|
+ SQLiteAuthOk, SQLiteAuthDeny, SQLiteAuthDelete,
|
|
|
+ SQLiteTraceStmt,
|
|
|
+ sqlite3_open_v2, sqlite3_exec, sqlite3_close_v2,
|
|
|
+ sqlite3_prepare_v2, sqlite3_step, sqlite3_column_int,
|
|
|
+ sqlite3_finalize, sqlite3_free,
|
|
|
+ sqlite3_busy_timeout, sqlite3_busy_handler, sqlite3_interrupt,
|
|
|
+ sqlite3_progress_handler, sqlite3_trace_v2,
|
|
|
+ sqlite3_update_hook, sqlite3_commit_hook, sqlite3_rollback_hook,
|
|
|
+ sqlite3_set_authorizer, sqlite3_db_handle;
|
|
|
+
|
|
|
+VAR
|
|
|
+ ExitCode : INTEGER;
|
|
|
+ db, stmt, prev : ADDRESS;
|
|
|
+ rc : INTEGER;
|
|
|
+ ok : BOOLEAN;
|
|
|
+ progressCalls, traceCalls, updateCalls : INTEGER;
|
|
|
+ commitCalls, rollbackCalls, authCalls : INTEGER;
|
|
|
+ lastOp : INTEGER;
|
|
|
+ lastRowid : LONGINT;
|
|
|
+ hookBad : BOOLEAN;
|
|
|
+ tableBuf : ARRAY [0..31] OF CHAR;
|
|
|
+
|
|
|
+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 QueryInt (sql : ARRAY OF CHAR; want : INTEGER) : BOOLEAN;
|
|
|
+VAR
|
|
|
+ s : ADDRESS;
|
|
|
+ code, got : INTEGER;
|
|
|
+ good : BOOLEAN;
|
|
|
+BEGIN
|
|
|
+ s := NIL; got := -777; good := FALSE;
|
|
|
+ code := sqlite3_prepare_v2(db, sql, -1, s, NIL);
|
|
|
+ IF (code = SQLiteOk) AND (s # NIL) THEN
|
|
|
+ IF sqlite3_step(s) = SQLiteRow THEN
|
|
|
+ got := sqlite3_column_int(s, 0);
|
|
|
+ IF sqlite3_step(s) = SQLiteDone THEN good := TRUE END
|
|
|
+ END;
|
|
|
+ IF sqlite3_finalize(s) # SQLiteOk THEN good := FALSE END
|
|
|
+ END;
|
|
|
+ RETURN good AND (got = want)
|
|
|
+END QueryInt;
|
|
|
+
|
|
|
+PROCEDURE BusyFn (arg : ADDRESS; count : INTEGER) : INTEGER;
|
|
|
+BEGIN
|
|
|
+ RETURN 0
|
|
|
+END BusyFn;
|
|
|
+
|
|
|
+PROCEDURE ProgressFn (arg : ADDRESS) : INTEGER;
|
|
|
+BEGIN
|
|
|
+ progressCalls := progressCalls + 1;
|
|
|
+ RETURN 0
|
|
|
+END ProgressFn;
|
|
|
+
|
|
|
+PROCEDURE TraceFn (mask : CARDINAL; ctx, sql, expanded : ADDRESS) : INTEGER;
|
|
|
+BEGIN
|
|
|
+ traceCalls := traceCalls + 1;
|
|
|
+ RETURN 0
|
|
|
+END TraceFn;
|
|
|
+
|
|
|
+PROCEDURE UpdateFn (arg : ADDRESS; op : INTEGER;
|
|
|
+ dbName, tableName : ADDRESS; rowid : LONGINT);
|
|
|
+VAR
|
|
|
+ s : CString;
|
|
|
+BEGIN
|
|
|
+ updateCalls := updateCalls + 1;
|
|
|
+ lastOp := op;
|
|
|
+ lastRowid := rowid;
|
|
|
+ s := tableName;
|
|
|
+ IF (s = NIL) OR (Copy(s, tableBuf) = 0) OR (tableBuf[0] # 'h') THEN
|
|
|
+ hookBad := TRUE
|
|
|
+ END
|
|
|
+END UpdateFn;
|
|
|
+
|
|
|
+PROCEDURE CommitFn (arg : ADDRESS) : INTEGER;
|
|
|
+BEGIN
|
|
|
+ commitCalls := commitCalls + 1;
|
|
|
+ RETURN 0
|
|
|
+END CommitFn;
|
|
|
+
|
|
|
+PROCEDURE RollbackFn (arg : ADDRESS);
|
|
|
+BEGIN
|
|
|
+ rollbackCalls := rollbackCalls + 1
|
|
|
+END RollbackFn;
|
|
|
+
|
|
|
+PROCEDURE AuthFn (arg : ADDRESS; action : INTEGER;
|
|
|
+ dbName, tableName, colName, triggerName : ADDRESS) : INTEGER;
|
|
|
+BEGIN
|
|
|
+ authCalls := authCalls + 1;
|
|
|
+ IF action = SQLiteAuthDelete THEN
|
|
|
+ RETURN SQLiteAuthDeny
|
|
|
+ ELSE
|
|
|
+ RETURN SQLiteAuthOk
|
|
|
+ END
|
|
|
+END AuthFn;
|
|
|
+
|
|
|
+BEGIN
|
|
|
+ ExitCode := 1;
|
|
|
+ db := NIL; stmt := NIL; prev := NIL;
|
|
|
+ progressCalls := 0; traceCalls := 0; updateCalls := 0;
|
|
|
+ commitCalls := 0; rollbackCalls := 0; authCalls := 0;
|
|
|
+ lastOp := 0; lastRowid := 0; hookBad := FALSE;
|
|
|
+ ok := FALSE;
|
|
|
+
|
|
|
+ IF (sqlite3_open_v2(":memory:", db,
|
|
|
+ SQLiteOpenReadWrite + SQLiteOpenCreate + SQLiteOpenMemory,
|
|
|
+ NIL) = SQLiteOk) AND (db # NIL) THEN
|
|
|
+ IF ExecSQL("CREATE TABLE h(id INTEGER PRIMARY KEY, v TEXT);") THEN
|
|
|
+ ok := TRUE
|
|
|
+ END;
|
|
|
+
|
|
|
+ IF ok THEN
|
|
|
+ ok := (sqlite3_busy_timeout(db, 100) = SQLiteOk)
|
|
|
+ AND (sqlite3_busy_handler(db, ADR(BusyFn), NIL) = SQLiteOk)
|
|
|
+ AND (sqlite3_busy_handler(db, NIL, NIL) = SQLiteOk)
|
|
|
+ END;
|
|
|
+
|
|
|
+ (* sqlite3_db_handle returns the owning connection. *)
|
|
|
+ IF ok THEN
|
|
|
+ stmt := NIL;
|
|
|
+ rc := sqlite3_prepare_v2(db, "SELECT 1;", -1, stmt, NIL);
|
|
|
+ ok := (rc = SQLiteOk) AND (stmt # NIL)
|
|
|
+ AND (sqlite3_db_handle(stmt) = db);
|
|
|
+ IF stmt # NIL THEN
|
|
|
+ IF sqlite3_finalize(stmt) # SQLiteOk THEN ok := FALSE END;
|
|
|
+ stmt := NIL
|
|
|
+ END
|
|
|
+ END;
|
|
|
+
|
|
|
+ (* Progress handler runs during a recursive query. *)
|
|
|
+ IF ok THEN
|
|
|
+ progressCalls := 0;
|
|
|
+ sqlite3_progress_handler(db, 1, ADR(ProgressFn), NIL);
|
|
|
+ ok := QueryInt("SELECT 1;", 1) AND (progressCalls > 0);
|
|
|
+ sqlite3_progress_handler(db, 0, NIL, NIL)
|
|
|
+ END;
|
|
|
+
|
|
|
+ (* Trace callback sees prepared statements. *)
|
|
|
+ IF ok THEN
|
|
|
+ traceCalls := 0;
|
|
|
+ ok := sqlite3_trace_v2(db, SQLiteTraceStmt,
|
|
|
+ ADR(TraceFn), NIL) = SQLiteOk
|
|
|
+ END;
|
|
|
+ IF ok THEN ok := QueryInt("SELECT 1;", 1) AND (traceCalls > 0) END;
|
|
|
+ IF ok THEN
|
|
|
+ ok := sqlite3_trace_v2(db, 0, NIL, NIL) = SQLiteOk
|
|
|
+ END;
|
|
|
+
|
|
|
+ (* Update hook fires for INSERT, UPDATE and DELETE. *)
|
|
|
+ IF ok THEN
|
|
|
+ updateCalls := 0; hookBad := FALSE;
|
|
|
+ prev := sqlite3_update_hook(db, ADR(UpdateFn), NIL)
|
|
|
+ END;
|
|
|
+ IF ok THEN
|
|
|
+ ok := ExecSQL("INSERT INTO h(v) VALUES ('a');")
|
|
|
+ AND (updateCalls = 1) AND (lastOp = SQLiteOpInsert)
|
|
|
+ END;
|
|
|
+ IF ok THEN
|
|
|
+ ok := ExecSQL("UPDATE h SET v = 'b' WHERE id = 1;")
|
|
|
+ AND (updateCalls = 2) AND (lastOp = SQLiteOpUpdate)
|
|
|
+ END;
|
|
|
+ IF ok THEN
|
|
|
+ ok := ExecSQL("DELETE FROM h WHERE id = 1;")
|
|
|
+ AND (updateCalls = 3) AND (lastOp = SQLiteOpDelete)
|
|
|
+ AND (NOT hookBad)
|
|
|
+ END;
|
|
|
+ IF ok THEN prev := sqlite3_update_hook(db, NIL, NIL) END;
|
|
|
+
|
|
|
+ (* Commit and rollback hooks. *)
|
|
|
+ IF ok THEN
|
|
|
+ commitCalls := 0; rollbackCalls := 0;
|
|
|
+ prev := sqlite3_commit_hook(db, ADR(CommitFn), NIL);
|
|
|
+ sqlite3_rollback_hook(db, ADR(RollbackFn), NIL)
|
|
|
+ END;
|
|
|
+ IF ok THEN
|
|
|
+ ok := ExecSQL("BEGIN; INSERT INTO h(v) VALUES ('c'); COMMIT;")
|
|
|
+ AND (commitCalls = 1)
|
|
|
+ END;
|
|
|
+ IF ok THEN
|
|
|
+ ok := ExecSQL("BEGIN; INSERT INTO h(v) VALUES ('d'); ROLLBACK;")
|
|
|
+ AND (rollbackCalls = 1)
|
|
|
+ END;
|
|
|
+ IF ok THEN
|
|
|
+ prev := sqlite3_commit_hook(db, NIL, NIL);
|
|
|
+ sqlite3_rollback_hook(db, NIL, NIL)
|
|
|
+ END;
|
|
|
+
|
|
|
+ (* Authorizer: allow SELECT, deny DELETE. *)
|
|
|
+ IF ok THEN
|
|
|
+ authCalls := 0;
|
|
|
+ ok := sqlite3_set_authorizer(db, ADR(AuthFn), NIL) = SQLiteOk
|
|
|
+ END;
|
|
|
+ IF ok THEN
|
|
|
+ stmt := NIL;
|
|
|
+ rc := sqlite3_prepare_v2(db, "DELETE FROM h;", -1, stmt, NIL);
|
|
|
+ ok := (rc = SQLiteAuth) AND (stmt = NIL) AND (authCalls > 0);
|
|
|
+ IF stmt # NIL THEN
|
|
|
+ IF sqlite3_finalize(stmt) # SQLiteOk THEN ok := FALSE END;
|
|
|
+ stmt := NIL
|
|
|
+ END
|
|
|
+ END;
|
|
|
+ IF ok THEN ok := QueryInt("SELECT count(*) FROM h;", 1) END;
|
|
|
+ IF ok THEN
|
|
|
+ ok := sqlite3_set_authorizer(db, NIL, NIL) = SQLiteOk
|
|
|
+ END;
|
|
|
+ IF ok THEN ok := ExecSQL("DELETE FROM h;") END;
|
|
|
+
|
|
|
+ IF ok THEN sqlite3_interrupt(db) END;
|
|
|
+ IF stmt # NIL THEN
|
|
|
+ IF sqlite3_finalize(stmt) # SQLiteOk THEN ok := FALSE END;
|
|
|
+ stmt := NIL
|
|
|
+ END
|
|
|
+ END;
|
|
|
+
|
|
|
+ IF db # NIL THEN
|
|
|
+ IF sqlite3_close_v2(db) # SQLiteOk THEN ok := FALSE END
|
|
|
+ END;
|
|
|
+ IF ok THEN ExitCode := 42 END
|
|
|
+END SQLiteHooks.
|