SQLiteHooks.mod 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. MODULE SQLiteHooks;
  2. (* Connection hooks:
  3. busy handler/timeout, progress, trace_v2, update/commit/rollback,
  4. authorizer and sqlite3_db_handle. *)
  5. FROM SYSTEM IMPORT ADDRESS, ADR;
  6. FROM CStr IMPORT CString, Copy;
  7. FROM SQLite IMPORT
  8. SQLiteOk, SQLiteRow, SQLiteDone, SQLiteAuth,
  9. SQLiteOpenReadWrite, SQLiteOpenCreate, SQLiteOpenMemory,
  10. SQLiteOpInsert, SQLiteOpUpdate, SQLiteOpDelete,
  11. SQLiteAuthOk, SQLiteAuthDeny, SQLiteAuthDelete,
  12. SQLiteTraceStmt,
  13. sqlite3_open_v2, sqlite3_exec, sqlite3_close_v2,
  14. sqlite3_prepare_v2, sqlite3_step, sqlite3_column_int,
  15. sqlite3_finalize, sqlite3_free,
  16. sqlite3_busy_timeout, sqlite3_busy_handler, sqlite3_interrupt,
  17. sqlite3_progress_handler, sqlite3_trace_v2,
  18. sqlite3_update_hook, sqlite3_commit_hook, sqlite3_rollback_hook,
  19. sqlite3_set_authorizer, sqlite3_db_handle;
  20. VAR
  21. ExitCode : INTEGER;
  22. db, stmt, prev : ADDRESS;
  23. rc : INTEGER;
  24. ok : BOOLEAN;
  25. progressCalls, traceCalls, updateCalls : INTEGER;
  26. commitCalls, rollbackCalls, authCalls : INTEGER;
  27. lastOp : INTEGER;
  28. lastRowid : LONGINT;
  29. hookBad : BOOLEAN;
  30. tableBuf : ARRAY [0..31] OF CHAR;
  31. PROCEDURE ExecSQL (sql : ARRAY OF CHAR) : BOOLEAN;
  32. VAR
  33. errmsg : ADDRESS;
  34. good : BOOLEAN;
  35. BEGIN
  36. errmsg := NIL;
  37. good := sqlite3_exec(db, sql, NIL, NIL, errmsg) = SQLiteOk;
  38. IF errmsg # NIL THEN
  39. sqlite3_free(errmsg)
  40. END;
  41. RETURN good
  42. END ExecSQL;
  43. PROCEDURE QueryInt (sql : ARRAY OF CHAR; want : INTEGER) : BOOLEAN;
  44. VAR
  45. s : ADDRESS;
  46. code, got : INTEGER;
  47. good : BOOLEAN;
  48. BEGIN
  49. s := NIL; got := -777; good := FALSE;
  50. code := sqlite3_prepare_v2(db, sql, -1, s, NIL);
  51. IF (code = SQLiteOk) AND (s # NIL) THEN
  52. IF sqlite3_step(s) = SQLiteRow THEN
  53. got := sqlite3_column_int(s, 0);
  54. IF sqlite3_step(s) = SQLiteDone THEN good := TRUE END
  55. END;
  56. IF sqlite3_finalize(s) # SQLiteOk THEN good := FALSE END
  57. END;
  58. RETURN good AND (got = want)
  59. END QueryInt;
  60. PROCEDURE BusyFn (arg : ADDRESS; count : INTEGER) : INTEGER;
  61. BEGIN
  62. RETURN 0
  63. END BusyFn;
  64. PROCEDURE ProgressFn (arg : ADDRESS) : INTEGER;
  65. BEGIN
  66. progressCalls := progressCalls + 1;
  67. RETURN 0
  68. END ProgressFn;
  69. PROCEDURE TraceFn (mask : CARDINAL; ctx, sql, expanded : ADDRESS) : INTEGER;
  70. BEGIN
  71. traceCalls := traceCalls + 1;
  72. RETURN 0
  73. END TraceFn;
  74. PROCEDURE UpdateFn (arg : ADDRESS; op : INTEGER;
  75. dbName, tableName : ADDRESS; rowid : LONGINT);
  76. VAR
  77. s : CString;
  78. BEGIN
  79. updateCalls := updateCalls + 1;
  80. lastOp := op;
  81. lastRowid := rowid;
  82. s := tableName;
  83. IF (s = NIL) OR (Copy(s, tableBuf) = 0) OR (tableBuf[0] # 'h') THEN
  84. hookBad := TRUE
  85. END
  86. END UpdateFn;
  87. PROCEDURE CommitFn (arg : ADDRESS) : INTEGER;
  88. BEGIN
  89. commitCalls := commitCalls + 1;
  90. RETURN 0
  91. END CommitFn;
  92. PROCEDURE RollbackFn (arg : ADDRESS);
  93. BEGIN
  94. rollbackCalls := rollbackCalls + 1
  95. END RollbackFn;
  96. PROCEDURE AuthFn (arg : ADDRESS; action : INTEGER;
  97. dbName, tableName, colName, triggerName : ADDRESS) : INTEGER;
  98. BEGIN
  99. authCalls := authCalls + 1;
  100. IF action = SQLiteAuthDelete THEN
  101. RETURN SQLiteAuthDeny
  102. ELSE
  103. RETURN SQLiteAuthOk
  104. END
  105. END AuthFn;
  106. BEGIN
  107. ExitCode := 1;
  108. db := NIL; stmt := NIL; prev := NIL;
  109. progressCalls := 0; traceCalls := 0; updateCalls := 0;
  110. commitCalls := 0; rollbackCalls := 0; authCalls := 0;
  111. lastOp := 0; lastRowid := 0; hookBad := FALSE;
  112. ok := FALSE;
  113. IF (sqlite3_open_v2(":memory:", db,
  114. SQLiteOpenReadWrite + SQLiteOpenCreate + SQLiteOpenMemory,
  115. NIL) = SQLiteOk) AND (db # NIL) THEN
  116. IF ExecSQL("CREATE TABLE h(id INTEGER PRIMARY KEY, v TEXT);") THEN
  117. ok := TRUE
  118. END;
  119. IF ok THEN
  120. ok := (sqlite3_busy_timeout(db, 100) = SQLiteOk)
  121. AND (sqlite3_busy_handler(db, ADR(BusyFn), NIL) = SQLiteOk)
  122. AND (sqlite3_busy_handler(db, NIL, NIL) = SQLiteOk)
  123. END;
  124. (* sqlite3_db_handle returns the owning connection. *)
  125. IF ok THEN
  126. stmt := NIL;
  127. rc := sqlite3_prepare_v2(db, "SELECT 1;", -1, stmt, NIL);
  128. ok := (rc = SQLiteOk) AND (stmt # NIL)
  129. AND (sqlite3_db_handle(stmt) = db);
  130. IF stmt # NIL THEN
  131. IF sqlite3_finalize(stmt) # SQLiteOk THEN ok := FALSE END;
  132. stmt := NIL
  133. END
  134. END;
  135. (* Progress handler runs during a recursive query. *)
  136. IF ok THEN
  137. progressCalls := 0;
  138. sqlite3_progress_handler(db, 1, ADR(ProgressFn), NIL);
  139. ok := QueryInt("SELECT 1;", 1) AND (progressCalls > 0);
  140. sqlite3_progress_handler(db, 0, NIL, NIL)
  141. END;
  142. (* Trace callback sees prepared statements. *)
  143. IF ok THEN
  144. traceCalls := 0;
  145. ok := sqlite3_trace_v2(db, SQLiteTraceStmt,
  146. ADR(TraceFn), NIL) = SQLiteOk
  147. END;
  148. IF ok THEN ok := QueryInt("SELECT 1;", 1) AND (traceCalls > 0) END;
  149. IF ok THEN
  150. ok := sqlite3_trace_v2(db, 0, NIL, NIL) = SQLiteOk
  151. END;
  152. (* Update hook fires for INSERT, UPDATE and DELETE. *)
  153. IF ok THEN
  154. updateCalls := 0; hookBad := FALSE;
  155. prev := sqlite3_update_hook(db, ADR(UpdateFn), NIL)
  156. END;
  157. IF ok THEN
  158. ok := ExecSQL("INSERT INTO h(v) VALUES ('a');")
  159. AND (updateCalls = 1) AND (lastOp = SQLiteOpInsert)
  160. END;
  161. IF ok THEN
  162. ok := ExecSQL("UPDATE h SET v = 'b' WHERE id = 1;")
  163. AND (updateCalls = 2) AND (lastOp = SQLiteOpUpdate)
  164. END;
  165. IF ok THEN
  166. ok := ExecSQL("DELETE FROM h WHERE id = 1;")
  167. AND (updateCalls = 3) AND (lastOp = SQLiteOpDelete)
  168. AND (NOT hookBad)
  169. END;
  170. IF ok THEN prev := sqlite3_update_hook(db, NIL, NIL) END;
  171. (* Commit and rollback hooks. *)
  172. IF ok THEN
  173. commitCalls := 0; rollbackCalls := 0;
  174. prev := sqlite3_commit_hook(db, ADR(CommitFn), NIL);
  175. sqlite3_rollback_hook(db, ADR(RollbackFn), NIL)
  176. END;
  177. IF ok THEN
  178. ok := ExecSQL("BEGIN; INSERT INTO h(v) VALUES ('c'); COMMIT;")
  179. AND (commitCalls = 1)
  180. END;
  181. IF ok THEN
  182. ok := ExecSQL("BEGIN; INSERT INTO h(v) VALUES ('d'); ROLLBACK;")
  183. AND (rollbackCalls = 1)
  184. END;
  185. IF ok THEN
  186. prev := sqlite3_commit_hook(db, NIL, NIL);
  187. sqlite3_rollback_hook(db, NIL, NIL)
  188. END;
  189. (* Authorizer: allow SELECT, deny DELETE. *)
  190. IF ok THEN
  191. authCalls := 0;
  192. ok := sqlite3_set_authorizer(db, ADR(AuthFn), NIL) = SQLiteOk
  193. END;
  194. IF ok THEN
  195. stmt := NIL;
  196. rc := sqlite3_prepare_v2(db, "DELETE FROM h;", -1, stmt, NIL);
  197. ok := (rc = SQLiteAuth) AND (stmt = NIL) AND (authCalls > 0);
  198. IF stmt # NIL THEN
  199. IF sqlite3_finalize(stmt) # SQLiteOk THEN ok := FALSE END;
  200. stmt := NIL
  201. END
  202. END;
  203. IF ok THEN ok := QueryInt("SELECT count(*) FROM h;", 1) END;
  204. IF ok THEN
  205. ok := sqlite3_set_authorizer(db, NIL, NIL) = SQLiteOk
  206. END;
  207. IF ok THEN ok := ExecSQL("DELETE FROM h;") END;
  208. IF ok THEN sqlite3_interrupt(db) END;
  209. IF stmt # NIL THEN
  210. IF sqlite3_finalize(stmt) # SQLiteOk THEN ok := FALSE END;
  211. stmt := NIL
  212. END
  213. END;
  214. IF db # NIL THEN
  215. IF sqlite3_close_v2(db) # SQLiteOk THEN ok := FALSE END
  216. END;
  217. IF ok THEN ExitCode := 42 END
  218. END SQLiteHooks.