Sfoglia il codice sorgente

bindings: add SQLite helpers and prepare_v3

Eric Streit 1 giorno fa
parent
commit
d3188b06d9

+ 36 - 4
bindings/sqlite3/README.md

@@ -17,6 +17,7 @@ sqlite3_close_v2
 
 (* prepared statements *)
 sqlite3_prepare_v2
+sqlite3_prepare_v3
 sqlite3_bind_int
 sqlite3_bind_int64
 sqlite3_bind_double
@@ -124,6 +125,28 @@ SQLiteArrayAt
 SQLiteTransient
 ```
 
+Typed handle aliases are available:
+
+```modula2
+TYPE
+  DbHandle = ADDRESS;
+  StmtHandle = ADDRESS;
+  BlobHandle = ADDRESS;
+  BackupHandle = ADDRESS;
+  ContextHandle = ADDRESS;
+  ValueHandle = ADDRESS;
+```
+
+The small helper module `SQLiteUtils` provides:
+
+```text
+CStrToM2, ErrMsg, ErrStr, LibVersionStr
+StaticDestr, TransientDestr
+BindTextCopy, BindBlobCopy, PrepareOne
+ValueText, ResultTextCopy
+BackupToFile, ExecSimple, ExecWithErr
+```
+
 The matching constants include the primary result codes
 (`SQLiteOk`, `SQLiteConstraint`, ...), `SQLiteRow`, `SQLiteDone`,
 storage classes (`SQLiteInteger`, `SQLiteFloat`, `SQLiteText`,
@@ -140,6 +163,7 @@ Hook-related constants include `SQLiteOp*`, `SQLiteAuth*` and
 
 - `SQLiteSmoke.mod` - open / exec / close
 - `SQLitePrepared.mod` - prepared insert and select
+- `SQLitePrepareTail.mod` - prepare_v3 tail handling
 - `SQLiteParams.mod` - parameter names/count/index, named parameters, unbound NULL
 - `SQLiteIntrospection.mod` - sql/expanded_sql, statement state, version, limits
 - `SQLiteScalar.mod` - int, int64, double, text, NULL and column metadata
@@ -148,6 +172,7 @@ Hook-related constants include `SQLiteOp*`, `SQLiteAuth*` and
 - `SQLiteBlobErrors.mod` - read-only/bounds/invalid/expired BLOB handles
 - `SQLiteConstraints.mod` - unique/not-null/PK/check/foreign-key extended codes
 - `SQLiteBackup.mod` - full and incremental online backup
+- `SQLiteHelpers.mod` - SQLiteUtils and typed handles
 - `SQLiteCapi3c.mod` - broad C-API integration test adapted from upstream `capi3c.test`
 - `SQLiteConnection.mod` - open_v2, close_v2, changes/rowid and deferred close
 - `SQLiteCallback.mod` - exec row callbacks and user-defined scalar functions
@@ -172,15 +197,19 @@ constraint errors and authorizer behavior.
 ./run.sh
 ```
 
-The script uses `pkg-config --libs sqlite3` when available; otherwise it
-links the versioned runtime library directly:
+The script prefers `SQLITE_LIBS` from the environment, then
+`pkg-config --libs sqlite3`, then `-lsqlite3` or a runtime library found
+through `ldconfig` / compiler probing.  It falls back to common
+Linux/macOS runtime-library paths such as:
 
 ```text
 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0
+/usr/lib/aarch64-linux-gnu/libsqlite3.so.0
+/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib
 ```
 
-That fallback matters on systems with the SQLite runtime installed but
-without `libsqlite3-dev`.
+This matters on systems with the SQLite runtime installed but without
+the development package.
 
 ## Ownership notes
 
@@ -216,6 +245,9 @@ without `libsqlite3-dev`.
   `SQLiteAuthDeny` or `SQLiteAuthIgnore`.
 - `sqlite3_sql` returns borrowed SQL text; `sqlite3_expanded_sql`
   returns allocated text and must be released with `sqlite3_free`.
+- `sqlite3_prepare_v3` takes `VAR tail : ADDRESS`; copy the tail with
+  `CStr.Copy` before preparing the next statement.  `PrepareOne` in
+  `SQLiteUtils` wraps the common one-statement case.
 - When extended result codes are enabled, `sqlite3_exec` and
   `sqlite3_errcode` can report an extended code such as
   `SQLiteConstraintPrimaryKey` (1555) rather than only

+ 113 - 90
bindings/sqlite3/SQLite.def

@@ -45,12 +45,15 @@ EXPORT UNQUALIFIED
   SQLiteStmtStatusReprepare, SQLiteStmtStatusRun,
   SQLiteStmtStatusFilterMiss, SQLiteStmtStatusFilterHit,
   SQLiteStmtStatusMemused,
+  SQLitePreparePersistent, SQLitePrepareNormalize, SQLitePrepareNoVtab,
+  SQLitePrepareDontLog, SQLitePrepareFromDdl,
   sqlite3_open,
   sqlite3_open_v2,
   sqlite3_exec,
   sqlite3_close,
   sqlite3_close_v2,
   sqlite3_prepare_v2,
+  sqlite3_prepare_v3,
   sqlite3_bind_int,
   sqlite3_bind_int64,
   sqlite3_bind_double,
@@ -141,6 +144,14 @@ EXPORT UNQUALIFIED
   SQLiteArrayAt,
   SQLiteTransient;
 
+TYPE
+  DbHandle = ADDRESS;
+  StmtHandle = ADDRESS;
+  BlobHandle = ADDRESS;
+  BackupHandle = ADDRESS;
+  ContextHandle = ADDRESS;
+  ValueHandle = ADDRESS;
+
 CONST
   SQLiteOk = 0;
   SQLiteError = 1;
@@ -255,122 +266,134 @@ CONST
   SQLiteStmtStatusFilterHit = 8;
   SQLiteStmtStatusMemused = 99;
 
+  SQLitePreparePersistent = 1;
+  SQLitePrepareNormalize = 2;
+  SQLitePrepareNoVtab = 4;
+  SQLitePrepareDontLog = 16;
+  SQLitePrepareFromDdl = 32;
+
 PROCEDURE sqlite3_open (filename : ARRAY OF CHAR;
-                        VAR db : ADDRESS) : INTEGER;
+                        VAR db : DbHandle) : INTEGER;
 PROCEDURE sqlite3_open_v2 (filename : ARRAY OF CHAR;
-                           VAR db : ADDRESS;
+                           VAR db : DbHandle;
                            flags : INTEGER;
                            vfs : CString) : INTEGER;
-PROCEDURE sqlite3_exec (db : ADDRESS;
+PROCEDURE sqlite3_exec (db : DbHandle;
                         sql : ARRAY OF CHAR;
                         callback, arg : ADDRESS;
                         VAR errmsg : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_close (db : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_close_v2 (db : ADDRESS) : INTEGER;
+PROCEDURE sqlite3_close (db : DbHandle) : INTEGER;
+PROCEDURE sqlite3_close_v2 (db : DbHandle) : INTEGER;
 
-PROCEDURE sqlite3_prepare_v2 (db : ADDRESS;
+PROCEDURE sqlite3_prepare_v2 (db : DbHandle;
                               sql : ARRAY OF CHAR;
                               nBytes : INTEGER;
-                              VAR stmt : ADDRESS;
+                              VAR stmt : StmtHandle;
                               tail : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_bind_int (stmt : ADDRESS;
+PROCEDURE sqlite3_prepare_v3 (db : DbHandle;
+                              sql : ARRAY OF CHAR;
+                              nBytes : INTEGER;
+                              prepFlags : CARDINAL;
+                              VAR stmt : StmtHandle;
+                              VAR tail : ADDRESS) : INTEGER;
+PROCEDURE sqlite3_bind_int (stmt : StmtHandle;
                             idx : INTEGER;
                             value : INTEGER) : INTEGER;
-PROCEDURE sqlite3_bind_int64 (stmt : ADDRESS;
+PROCEDURE sqlite3_bind_int64 (stmt : StmtHandle;
                               idx : INTEGER;
                               value : LONGINT) : INTEGER;
-PROCEDURE sqlite3_bind_double (stmt : ADDRESS;
+PROCEDURE sqlite3_bind_double (stmt : StmtHandle;
                                idx : INTEGER;
                                value : REAL) : INTEGER;
-PROCEDURE sqlite3_bind_null (stmt : ADDRESS;
+PROCEDURE sqlite3_bind_null (stmt : StmtHandle;
                              idx : INTEGER) : INTEGER;
-PROCEDURE sqlite3_bind_blob (stmt : ADDRESS;
+PROCEDURE sqlite3_bind_blob (stmt : StmtHandle;
                              idx : INTEGER;
                              data : ADDRESS;
                              nBytes : INTEGER;
                              destructor : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_bind_zeroblob (stmt : ADDRESS;
+PROCEDURE sqlite3_bind_zeroblob (stmt : StmtHandle;
                                  idx : INTEGER;
                                  nBytes : INTEGER) : INTEGER;
-PROCEDURE sqlite3_bind_text (stmt : ADDRESS;
+PROCEDURE sqlite3_bind_text (stmt : StmtHandle;
                              idx : INTEGER;
                              value : ARRAY OF CHAR;
                              nBytes : INTEGER;
                              destructor : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_bind_parameter_count (stmt : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_bind_parameter_name (stmt : ADDRESS;
+PROCEDURE sqlite3_bind_parameter_count (stmt : StmtHandle) : INTEGER;
+PROCEDURE sqlite3_bind_parameter_name (stmt : StmtHandle;
                                        idx : INTEGER) : CString;
-PROCEDURE sqlite3_bind_parameter_index (stmt : ADDRESS;
+PROCEDURE sqlite3_bind_parameter_index (stmt : StmtHandle;
                                         name : ARRAY OF CHAR) : INTEGER;
-PROCEDURE sqlite3_step (stmt : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_sql (stmt : ADDRESS) : CString;
-PROCEDURE sqlite3_expanded_sql (stmt : ADDRESS) : CString;
-PROCEDURE sqlite3_stmt_readonly (stmt : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_stmt_busy (stmt : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_next_stmt (db, stmt : ADDRESS) : ADDRESS;
-PROCEDURE sqlite3_stmt_status (stmt : ADDRESS;
+PROCEDURE sqlite3_step (stmt : StmtHandle) : INTEGER;
+PROCEDURE sqlite3_sql (stmt : StmtHandle) : CString;
+PROCEDURE sqlite3_expanded_sql (stmt : StmtHandle) : CString;
+PROCEDURE sqlite3_stmt_readonly (stmt : StmtHandle) : INTEGER;
+PROCEDURE sqlite3_stmt_busy (stmt : StmtHandle) : INTEGER;
+PROCEDURE sqlite3_next_stmt (db : DbHandle; stmt : StmtHandle) : StmtHandle;
+PROCEDURE sqlite3_stmt_status (stmt : StmtHandle;
                                op : INTEGER;
                                resetFlag : INTEGER) : INTEGER;
-PROCEDURE sqlite3_reset (stmt : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_clear_bindings (stmt : ADDRESS) : INTEGER;
+PROCEDURE sqlite3_reset (stmt : StmtHandle) : INTEGER;
+PROCEDURE sqlite3_clear_bindings (stmt : StmtHandle) : INTEGER;
 
-PROCEDURE sqlite3_column_count (stmt : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_data_count (stmt : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_column_type (stmt : ADDRESS;
+PROCEDURE sqlite3_column_count (stmt : StmtHandle) : INTEGER;
+PROCEDURE sqlite3_data_count (stmt : StmtHandle) : INTEGER;
+PROCEDURE sqlite3_column_type (stmt : StmtHandle;
                                col : INTEGER) : INTEGER;
-PROCEDURE sqlite3_column_bytes (stmt : ADDRESS;
+PROCEDURE sqlite3_column_bytes (stmt : StmtHandle;
                                 col : INTEGER) : INTEGER;
-PROCEDURE sqlite3_column_int (stmt : ADDRESS;
+PROCEDURE sqlite3_column_int (stmt : StmtHandle;
                               col : INTEGER) : INTEGER;
-PROCEDURE sqlite3_column_int64 (stmt : ADDRESS;
+PROCEDURE sqlite3_column_int64 (stmt : StmtHandle;
                                 col : INTEGER) : LONGINT;
-PROCEDURE sqlite3_column_double (stmt : ADDRESS;
+PROCEDURE sqlite3_column_double (stmt : StmtHandle;
                                  col : INTEGER) : REAL;
-PROCEDURE sqlite3_column_blob (stmt : ADDRESS;
+PROCEDURE sqlite3_column_blob (stmt : StmtHandle;
                                col : INTEGER) : ADDRESS;
-PROCEDURE sqlite3_column_text (stmt : ADDRESS;
+PROCEDURE sqlite3_column_text (stmt : StmtHandle;
                                col : INTEGER) : CString;
-PROCEDURE sqlite3_column_name (stmt : ADDRESS;
+PROCEDURE sqlite3_column_name (stmt : StmtHandle;
                                col : INTEGER) : CString;
-PROCEDURE sqlite3_column_decltype (stmt : ADDRESS;
+PROCEDURE sqlite3_column_decltype (stmt : StmtHandle;
                                    col : INTEGER) : CString;
-PROCEDURE sqlite3_finalize (stmt : ADDRESS) : INTEGER;
+PROCEDURE sqlite3_finalize (stmt : StmtHandle) : INTEGER;
 
-PROCEDURE sqlite3_changes (db : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_total_changes (db : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_last_insert_rowid (db : ADDRESS) : LONGINT;
+PROCEDURE sqlite3_changes (db : DbHandle) : INTEGER;
+PROCEDURE sqlite3_total_changes (db : DbHandle) : INTEGER;
+PROCEDURE sqlite3_last_insert_rowid (db : DbHandle) : LONGINT;
 
-PROCEDURE sqlite3_blob_open (db : ADDRESS;
+PROCEDURE sqlite3_blob_open (db : DbHandle;
                              dbName, tableName, columnName : ARRAY OF CHAR;
                              rowid : LONGINT;
                              flags : INTEGER;
-                             VAR blob : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_blob_close (blob : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_blob_read (blob : ADDRESS;
+                             VAR blob : BlobHandle) : INTEGER;
+PROCEDURE sqlite3_blob_close (blob : BlobHandle) : INTEGER;
+PROCEDURE sqlite3_blob_read (blob : BlobHandle;
                              data : ADDRESS;
                              nBytes : INTEGER;
                              offset : INTEGER) : INTEGER;
-PROCEDURE sqlite3_blob_write (blob : ADDRESS;
+PROCEDURE sqlite3_blob_write (blob : BlobHandle;
                               data : ADDRESS;
                               nBytes : INTEGER;
                               offset : INTEGER) : INTEGER;
-PROCEDURE sqlite3_blob_bytes (blob : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_blob_reopen (blob : ADDRESS;
+PROCEDURE sqlite3_blob_bytes (blob : BlobHandle) : INTEGER;
+PROCEDURE sqlite3_blob_reopen (blob : BlobHandle;
                                rowid : LONGINT) : INTEGER;
 
-PROCEDURE sqlite3_backup_init (dst : ADDRESS;
+PROCEDURE sqlite3_backup_init (dst : DbHandle;
                                dstName : ARRAY OF CHAR;
-                               src : ADDRESS;
-                               srcName : ARRAY OF CHAR) : ADDRESS;
-PROCEDURE sqlite3_backup_step (backup : ADDRESS; pages : INTEGER) : INTEGER;
-PROCEDURE sqlite3_backup_finish (backup : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_backup_remaining (backup : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_backup_pagecount (backup : ADDRESS) : INTEGER;
+                               src : DbHandle;
+                               srcName : ARRAY OF CHAR) : BackupHandle;
+PROCEDURE sqlite3_backup_step (backup : BackupHandle; pages : INTEGER) : INTEGER;
+PROCEDURE sqlite3_backup_finish (backup : BackupHandle) : INTEGER;
+PROCEDURE sqlite3_backup_remaining (backup : BackupHandle) : INTEGER;
+PROCEDURE sqlite3_backup_pagecount (backup : BackupHandle) : INTEGER;
 
 (* User-defined scalar functions.  xFunc receives
    (sqlite3_context*, int argc, sqlite3_value** argv); use
    SQLiteArrayAt(argv, i) to reach each value. *)
-PROCEDURE sqlite3_create_function_v2 (db : ADDRESS;
+PROCEDURE sqlite3_create_function_v2 (db : DbHandle;
                                       name : ARRAY OF CHAR;
                                       nArgs : INTEGER;
                                       textRep : INTEGER;
@@ -378,70 +401,70 @@ PROCEDURE sqlite3_create_function_v2 (db : ADDRESS;
                                       xFunc, xStep, xFinal : ADDRESS;
                                       xDestroy : ADDRESS) : INTEGER;
 
-PROCEDURE sqlite3_value_type (value : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_value_int (value : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_value_int64 (value : ADDRESS) : LONGINT;
-PROCEDURE sqlite3_value_double (value : ADDRESS) : REAL;
-PROCEDURE sqlite3_value_text (value : ADDRESS) : CString;
-PROCEDURE sqlite3_value_blob (value : ADDRESS) : ADDRESS;
-PROCEDURE sqlite3_value_bytes (value : ADDRESS) : INTEGER;
+PROCEDURE sqlite3_value_type (value : ValueHandle) : INTEGER;
+PROCEDURE sqlite3_value_int (value : ValueHandle) : INTEGER;
+PROCEDURE sqlite3_value_int64 (value : ValueHandle) : LONGINT;
+PROCEDURE sqlite3_value_double (value : ValueHandle) : REAL;
+PROCEDURE sqlite3_value_text (value : ValueHandle) : CString;
+PROCEDURE sqlite3_value_blob (value : ValueHandle) : ADDRESS;
+PROCEDURE sqlite3_value_bytes (value : ValueHandle) : INTEGER;
 
-PROCEDURE sqlite3_result_null (ctx : ADDRESS);
-PROCEDURE sqlite3_result_int (ctx : ADDRESS; value : INTEGER);
-PROCEDURE sqlite3_result_int64 (ctx : ADDRESS; value : LONGINT);
-PROCEDURE sqlite3_result_double (ctx : ADDRESS; value : REAL);
-PROCEDURE sqlite3_result_text (ctx : ADDRESS;
+PROCEDURE sqlite3_result_null (ctx : ContextHandle);
+PROCEDURE sqlite3_result_int (ctx : ContextHandle; value : INTEGER);
+PROCEDURE sqlite3_result_int64 (ctx : ContextHandle; value : LONGINT);
+PROCEDURE sqlite3_result_double (ctx : ContextHandle; value : REAL);
+PROCEDURE sqlite3_result_text (ctx : ContextHandle;
                                value : ARRAY OF CHAR;
                                nBytes : INTEGER;
                                destructor : ADDRESS);
-PROCEDURE sqlite3_result_blob (ctx : ADDRESS;
+PROCEDURE sqlite3_result_blob (ctx : ContextHandle;
                                data : ADDRESS;
                                nBytes : INTEGER;
                                destructor : ADDRESS);
-PROCEDURE sqlite3_result_error (ctx : ADDRESS;
+PROCEDURE sqlite3_result_error (ctx : ContextHandle;
                                 msg : ARRAY OF CHAR;
                                 nBytes : INTEGER);
-PROCEDURE sqlite3_result_error_code (ctx : ADDRESS; code : INTEGER);
+PROCEDURE sqlite3_result_error_code (ctx : ContextHandle; code : INTEGER);
 
-PROCEDURE sqlite3_user_data (ctx : ADDRESS) : ADDRESS;
-PROCEDURE sqlite3_context_db_handle (ctx : ADDRESS) : ADDRESS;
+PROCEDURE sqlite3_user_data (ctx : ContextHandle) : ADDRESS;
+PROCEDURE sqlite3_context_db_handle (ctx : ContextHandle) : DbHandle;
 
 (* Connection hooks.  Hooks must be top-level V3 procedures. *)
-PROCEDURE sqlite3_busy_timeout (db : ADDRESS; milliseconds : INTEGER) : INTEGER;
-PROCEDURE sqlite3_busy_handler (db : ADDRESS;
+PROCEDURE sqlite3_busy_timeout (db : DbHandle; milliseconds : INTEGER) : INTEGER;
+PROCEDURE sqlite3_busy_handler (db : DbHandle;
                                 callback, arg : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_interrupt (db : ADDRESS);
-PROCEDURE sqlite3_progress_handler (db : ADDRESS;
+PROCEDURE sqlite3_interrupt (db : DbHandle);
+PROCEDURE sqlite3_progress_handler (db : DbHandle;
                                     nOps : INTEGER;
                                     callback, arg : ADDRESS);
-PROCEDURE sqlite3_trace_v2 (db : ADDRESS;
+PROCEDURE sqlite3_trace_v2 (db : DbHandle;
                             mask : CARDINAL;
                             callback, arg : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_update_hook (db : ADDRESS;
+PROCEDURE sqlite3_update_hook (db : DbHandle;
                                callback, arg : ADDRESS) : ADDRESS;
-PROCEDURE sqlite3_commit_hook (db : ADDRESS;
+PROCEDURE sqlite3_commit_hook (db : DbHandle;
                                callback, arg : ADDRESS) : ADDRESS;
-PROCEDURE sqlite3_rollback_hook (db : ADDRESS;
+PROCEDURE sqlite3_rollback_hook (db : DbHandle;
                                  callback, arg : ADDRESS);
-PROCEDURE sqlite3_set_authorizer (db : ADDRESS;
+PROCEDURE sqlite3_set_authorizer (db : DbHandle;
                                   callback, arg : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_db_handle (stmt : ADDRESS) : ADDRESS;
+PROCEDURE sqlite3_db_handle (stmt : StmtHandle) : DbHandle;
 PROCEDURE sqlite3_libversion () : CString;
 PROCEDURE sqlite3_libversion_number () : INTEGER;
 PROCEDURE sqlite3_sourceid () : CString;
 PROCEDURE sqlite3_threadsafe () : INTEGER;
-PROCEDURE sqlite3_error_offset (db : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_get_autocommit (db : ADDRESS) : INTEGER;
+PROCEDURE sqlite3_error_offset (db : DbHandle) : INTEGER;
+PROCEDURE sqlite3_get_autocommit (db : DbHandle) : INTEGER;
 PROCEDURE sqlite3_complete (sql : ARRAY OF CHAR) : INTEGER;
-PROCEDURE sqlite3_limit (db : ADDRESS;
+PROCEDURE sqlite3_limit (db : DbHandle;
                          id : INTEGER;
                          newVal : INTEGER) : INTEGER;
 
-PROCEDURE sqlite3_errmsg (db : ADDRESS) : CString;
+PROCEDURE sqlite3_errmsg (db : DbHandle) : CString;
 PROCEDURE sqlite3_errstr (code : INTEGER) : CString;
-PROCEDURE sqlite3_errcode (db : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_extended_errcode (db : ADDRESS) : INTEGER;
-PROCEDURE sqlite3_extended_result_codes (db : ADDRESS;
+PROCEDURE sqlite3_errcode (db : DbHandle) : INTEGER;
+PROCEDURE sqlite3_extended_errcode (db : DbHandle) : INTEGER;
+PROCEDURE sqlite3_extended_result_codes (db : DbHandle;
                                          onoff : INTEGER) : INTEGER;
 PROCEDURE sqlite3_free (p : ADDRESS);
 

+ 40 - 0
bindings/sqlite3/SQLiteUtils.def

@@ -0,0 +1,40 @@
+DEFINITION MODULE SQLiteUtils;
+(* Small V3 helpers over the raw SQLite binding. *)
+
+FROM SYSTEM IMPORT ADDRESS;
+FROM SQLite IMPORT DbHandle, StmtHandle, ContextHandle, ValueHandle;
+
+EXPORT UNQUALIFIED
+  CStrToM2, ErrMsg, ErrStr, LibVersionStr,
+  StaticDestr, TransientDestr,
+  BindTextCopy, BindBlobCopy, PrepareOne,
+  ValueText, ResultTextCopy,
+  BackupToFile, ExecSimple, ExecWithErr;
+
+PROCEDURE CStrToM2 (src : ADDRESS; VAR dst : ARRAY OF CHAR);
+PROCEDURE ErrMsg (db : DbHandle; VAR buf : ARRAY OF CHAR);
+PROCEDURE ErrStr (code : INTEGER; VAR buf : ARRAY OF CHAR);
+PROCEDURE LibVersionStr (VAR buf : ARRAY OF CHAR);
+
+PROCEDURE StaticDestr () : ADDRESS;
+PROCEDURE TransientDestr () : ADDRESS;
+
+PROCEDURE BindTextCopy (stmt : StmtHandle; idx : INTEGER;
+                        value : ARRAY OF CHAR) : INTEGER;
+PROCEDURE BindBlobCopy (stmt : StmtHandle; idx : INTEGER;
+                        data : ADDRESS; nBytes : INTEGER) : INTEGER;
+
+PROCEDURE PrepareOne (db : DbHandle; sql : ARRAY OF CHAR;
+                      VAR stmt : StmtHandle) : INTEGER;
+(* Prepare one statement with sqlite3_prepare_v3(..., tail). *)
+
+PROCEDURE ValueText (value : ValueHandle; VAR buf : ARRAY OF CHAR);
+PROCEDURE ResultTextCopy (ctx : ContextHandle; value : ARRAY OF CHAR);
+
+PROCEDURE BackupToFile (src : DbHandle;
+                        filename : ARRAY OF CHAR) : INTEGER;
+PROCEDURE ExecSimple (db : DbHandle; sql : ARRAY OF CHAR) : INTEGER;
+PROCEDURE ExecWithErr (db : DbHandle; sql : ARRAY OF CHAR;
+                       VAR errbuf : ARRAY OF CHAR) : INTEGER;
+
+END SQLiteUtils.

+ 142 - 0
bindings/sqlite3/SQLiteUtils.mod

@@ -0,0 +1,142 @@
+IMPLEMENTATION MODULE SQLiteUtils;
+(* Pure V3 helpers over the SQLite binding. *)
+
+FROM SYSTEM IMPORT ADDRESS;
+FROM CStr IMPORT Copy;
+FROM SQLite IMPORT
+  DbHandle, StmtHandle, ContextHandle, ValueHandle, BackupHandle,
+  SQLiteOk, SQLiteDone, SQLiteBusy, SQLiteLocked,
+  sqlite3_libversion, sqlite3_errmsg, sqlite3_errstr,
+  sqlite3_open, sqlite3_close, sqlite3_errcode,
+  sqlite3_exec, sqlite3_free,
+  sqlite3_bind_text, sqlite3_bind_blob,
+  sqlite3_prepare_v3,
+  sqlite3_value_text, sqlite3_result_text,
+  sqlite3_backup_init, sqlite3_backup_step, sqlite3_backup_finish,
+  SQLiteTransient;
+
+PROCEDURE CStrToM2 (src : ADDRESS; VAR dst : ARRAY OF CHAR);
+VAR
+  n : CARDINAL;
+BEGIN
+  n := Copy(src, dst)
+END CStrToM2;
+
+PROCEDURE ErrMsg (db : DbHandle; VAR buf : ARRAY OF CHAR);
+BEGIN
+  CStrToM2(sqlite3_errmsg(db), buf)
+END ErrMsg;
+
+PROCEDURE ErrStr (code : INTEGER; VAR buf : ARRAY OF CHAR);
+BEGIN
+  CStrToM2(sqlite3_errstr(code), buf)
+END ErrStr;
+
+PROCEDURE LibVersionStr (VAR buf : ARRAY OF CHAR);
+BEGIN
+  CStrToM2(sqlite3_libversion(), buf)
+END LibVersionStr;
+
+PROCEDURE StaticDestr () : ADDRESS;
+BEGIN
+  RETURN NIL
+END StaticDestr;
+
+PROCEDURE TransientDestr () : ADDRESS;
+BEGIN
+  RETURN SQLiteTransient()
+END TransientDestr;
+
+PROCEDURE BindTextCopy (stmt : StmtHandle; idx : INTEGER;
+                        value : ARRAY OF CHAR) : INTEGER;
+BEGIN
+  RETURN sqlite3_bind_text(stmt, idx, value, -1, TransientDestr())
+END BindTextCopy;
+
+PROCEDURE BindBlobCopy (stmt : StmtHandle; idx : INTEGER;
+                        data : ADDRESS; nBytes : INTEGER) : INTEGER;
+BEGIN
+  RETURN sqlite3_bind_blob(stmt, idx, data, nBytes, TransientDestr())
+END BindBlobCopy;
+
+PROCEDURE PrepareOne (db : DbHandle; sql : ARRAY OF CHAR;
+                      VAR stmt : StmtHandle) : INTEGER;
+VAR
+  tail : ADDRESS;
+BEGIN
+  tail := NIL;
+  RETURN sqlite3_prepare_v3(db, sql, -1, 0, stmt, tail)
+END PrepareOne;
+
+PROCEDURE ValueText (value : ValueHandle; VAR buf : ARRAY OF CHAR);
+BEGIN
+  CStrToM2(sqlite3_value_text(value), buf)
+END ValueText;
+
+PROCEDURE ResultTextCopy (ctx : ContextHandle; value : ARRAY OF CHAR);
+BEGIN
+  sqlite3_result_text(ctx, value, -1, TransientDestr())
+END ResultTextCopy;
+
+PROCEDURE BackupToFile (src : DbHandle;
+                        filename : ARRAY OF CHAR) : INTEGER;
+VAR
+  dst : DbHandle;
+  bk : BackupHandle;
+  rc, rc2, tries : INTEGER;
+BEGIN
+  rc := sqlite3_open(filename, dst);
+  IF rc # SQLiteOk THEN RETURN rc END;
+  bk := sqlite3_backup_init(dst, "main", src, "main");
+  IF bk = NIL THEN
+    rc := sqlite3_errcode(dst);
+    rc2 := sqlite3_close(dst);
+    RETURN rc
+  END;
+  tries := 0;
+  LOOP
+    rc := sqlite3_backup_step(bk, 50);
+    IF (rc # SQLiteOk) AND (rc # SQLiteBusy)
+       AND (rc # SQLiteLocked) THEN EXIT END;
+    tries := tries + 1;
+    IF tries > 100000 THEN
+      rc := SQLiteBusy;
+      EXIT
+    END
+  END;
+  IF rc = SQLiteDone THEN rc := SQLiteOk END;
+  rc2 := sqlite3_backup_finish(bk);
+  IF rc = SQLiteOk THEN rc := rc2 END;
+  rc2 := sqlite3_close(dst);
+  RETURN rc
+END BackupToFile;
+
+PROCEDURE ExecSimple (db : DbHandle; sql : ARRAY OF CHAR) : INTEGER;
+VAR
+  msg : ADDRESS;
+  rc : INTEGER;
+BEGIN
+  msg := NIL;
+  rc := sqlite3_exec(db, sql, NIL, NIL, msg);
+  IF msg # NIL THEN sqlite3_free(msg) END;
+  RETURN rc
+END ExecSimple;
+
+PROCEDURE ExecWithErr (db : DbHandle; sql : ARRAY OF CHAR;
+                       VAR errbuf : ARRAY OF CHAR) : INTEGER;
+VAR
+  msg : ADDRESS;
+  rc : INTEGER;
+BEGIN
+  msg := NIL;
+  rc := sqlite3_exec(db, sql, NIL, NIL, msg);
+  IF msg # NIL THEN
+    CStrToM2(msg, errbuf);
+    sqlite3_free(msg)
+  ELSE
+    errbuf[0] := CHR(0)
+  END;
+  RETURN rc
+END ExecWithErr;
+
+END SQLiteUtils.

+ 10 - 5
bindings/sqlite3/TEST-INVENTORY.md

@@ -7,9 +7,12 @@ Status update 2026-10-27: parameter introspection
 metadata/diagnostics, BLOB error handling and extended constraint
 codes are implemented.  Covered by `tests/SQLiteParams.mod`,
 `tests/SQLiteIntrospection.mod`, `tests/SQLiteBlobErrors.mod`,
-`tests/SQLiteConstraints.mod`, `tests/SQLiteBackup.mod`, the broad
+`tests/SQLiteConstraints.mod`, `tests/SQLiteBackup.mod`,
+`tests/SQLitePrepareTail.mod`, `tests/SQLiteHelpers.mod`, the broad
 `tests/SQLiteCapi3c.mod` integration test and
-`showcase/SQLiteFeatureShowcase.mod`.
+`showcase/SQLiteFeatureShowcase.mod`.  Typed handle aliases, a
+`SQLiteUtils` helper layer and portable library discovery are also in
+place.
 
 This note records which existing tests are useful for hardening the V3
 SQLite binding under `bindings/sqlite3`, and which APIs they would drive.
@@ -18,7 +21,7 @@ SQLite binding under `bindings/sqlite3`, and which APIs they would drive.
 
 | Source | Count | Notes |
 |---|---:|---|
-| `bindings/sqlite3/tests` | 14 | Current V3 tests: smoke, prepared, params, introspection, scalar, errors, BLOB, BLOB errors, constraints, backup, C API integration, connection, callback, hooks |
+| `bindings/sqlite3/tests` | 16 | Current V3 tests: smoke, prepared, prepare-v3 tail, params, helpers, introspection, scalar, errors, BLOB, BLOB errors, constraints, backup, C API integration, connection, callback, hooks |
 | sibling `m2SQLITE/tests` | 7 | Original GNU Modula-2 binding tests; closest existing coverage |
 | sibling `m2SQLITE/sqlite-master/test` | 1,195 `.test` files | Upstream SQLite TCL regression suite; useful as a specification |
 | `compiler/tests` | 806 files | Compiler/ABI tests; not SQLite-specific, but several guard binding prerequisites |
@@ -31,7 +34,9 @@ harness.
 
 - `SQLiteSmoke.mod`
 - `SQLitePrepared.mod`
+- `SQLitePrepareTail.mod`
 - `SQLiteParams.mod`
+- `SQLiteHelpers.mod`
 - `SQLiteIntrospection.mod`
 - `SQLiteScalar.mod`
 - `SQLiteErrors.mod`
@@ -179,8 +184,8 @@ completed in this session and the current verification state.
 
 - Replace m2SQLITE's `libc.printf`/`HALT` style with the V3 test convention:
   set `ExitCode := 42` on success.
-- m2SQLITE's `SQLiteUtils` helpers do not exist in the V3 binding; call
-  the raw `SQLite` procedures directly.
+- A subset of m2SQLITE's `SQLiteUtils` now exists in the V3 binding;
+  use it for common string, bind, exec and backup operations.
 - For a raw C pointer to a V3 array, use `ADR(a[0])`, not `ADR(a)`.
 - V3 string literals in the current compiler can be truncated for long
   SQL text.  Keep individual literals short, or build the SQL through a

+ 44 - 5
bindings/sqlite3/run.sh

@@ -1,6 +1,7 @@
 #!/bin/sh
 # SQLite binding tests and showcase.
 set -e
+trap 'rm -f /tmp/m2-sqlite-utils.db' EXIT
 cd "$(dirname "$0")"
 ROOT="$(cd ../.. && pwd)"
 M2MAKE="$ROOT/tools/m2make/b_v3/M2make"
@@ -8,12 +9,48 @@ if [ ! -x "$M2MAKE" ]; then
   echo "missing $M2MAKE; run tools/m2make/build_m2.sh" >&2
   exit 1
 fi
-SQLITE_LIBS="$(pkg-config --libs sqlite3 2>/dev/null || true)"
-if [ -z "$SQLITE_LIBS" ]; then
-  # Runtime-only installations often have no .pc/header; use the
-  # versioned shared object directly.
-  SQLITE_LIBS="/usr/lib/x86_64-linux-gnu/libsqlite3.so.0"
+# Allow an explicit override, then use pkg-config, ldconfig or a
+# platform-specific runtime library.  The .so.0 fallback matters on
+# systems with the SQLite runtime but without the development package.
+if [ -n "${SQLITE_LIBS:-}" ]; then
+  :
+elif command -v pkg-config >/dev/null 2>&1 && pkg-config --exists sqlite3; then
+  SQLITE_LIBS="$(pkg-config --libs sqlite3)"
+else
+  SQLITE_LIBS=""
+  if command -v ldconfig >/dev/null 2>&1; then
+    SQLITE_LIBS="$(ldconfig -p 2>/dev/null |
+      awk '/libsqlite3\.so\.0/ { print $NF; exit }')"
+  fi
+  if [ -z "$SQLITE_LIBS" ] && command -v cc >/dev/null 2>&1; then
+    cand="$(cc -print-file-name=libsqlite3.so 2>/dev/null || true)"
+    if [ -n "$cand" ] && [ "$cand" != "libsqlite3.so" ] && [ -f "$cand" ]; then
+      SQLITE_LIBS="$cand"
+    fi
+    if [ -z "$SQLITE_LIBS" ]; then
+      cand="$(cc -print-file-name=libsqlite3.dylib 2>/dev/null || true)"
+      if [ -n "$cand" ] && [ "$cand" != "libsqlite3.dylib" ] && [ -f "$cand" ]; then
+        SQLITE_LIBS="$cand"
+      fi
+    fi
+  fi
+  if [ -z "$SQLITE_LIBS" ]; then
+    for cand in \
+      /usr/lib/x86_64-linux-gnu/libsqlite3.so.0 \
+      /usr/lib/aarch64-linux-gnu/libsqlite3.so.0 \
+      /usr/lib64/libsqlite3.so.0 \
+      /usr/lib/libsqlite3.so.0 \
+      /usr/local/lib/libsqlite3.so.0 \
+      /opt/homebrew/opt/sqlite/lib/libsqlite3.dylib \
+      /usr/local/opt/sqlite/lib/libsqlite3.dylib; do
+      if [ -f "$cand" ]; then SQLITE_LIBS="$cand"; break; fi
+    done
+  fi
+  if [ -z "$SQLITE_LIBS" ]; then
+    SQLITE_LIBS="-lsqlite3"
+  fi
 fi
+echo "SQLite library: $SQLITE_LIBS"
 
 build_and_run() {
   name="$1"
@@ -39,6 +76,8 @@ build_and_run() {
 build_and_run SQLiteSmoke
 build_and_run SQLitePrepared
 build_and_run SQLiteParams
+build_and_run SQLitePrepareTail
+build_and_run SQLiteHelpers
 build_and_run SQLiteIntrospection
 build_and_run SQLiteScalar
 build_and_run SQLiteErrors

+ 157 - 0
bindings/sqlite3/tests/SQLiteHelpers.mod

@@ -0,0 +1,157 @@
+MODULE SQLiteHelpers;
+(* Exercise SQLiteUtils and the typed handle aliases. *)
+
+FROM SYSTEM IMPORT ADDRESS, ADR;
+FROM SQLite IMPORT
+  DbHandle, StmtHandle, ContextHandle, ValueHandle,
+  SQLiteOk, SQLiteRow, SQLiteDone, SQLiteError, SQLiteText,
+  SQLiteOpenReadWrite, SQLiteOpenCreate, SQLiteOpenMemory,
+  SQLiteUtf8,
+  sqlite3_open, sqlite3_open_v2, sqlite3_close,
+  sqlite3_step, sqlite3_finalize,
+  sqlite3_column_int, sqlite3_column_text, sqlite3_column_type,
+  sqlite3_create_function_v2,
+  sqlite3_value_type, SQLiteArrayAt, SQLiteTransient;
+FROM SQLiteUtils IMPORT
+  CStrToM2, ErrMsg, ErrStr, LibVersionStr, StaticDestr, TransientDestr,
+  BindTextCopy, BindBlobCopy, PrepareOne, ValueText, ResultTextCopy,
+  BackupToFile, ExecSimple, ExecWithErr;
+
+VAR
+  ExitCode : INTEGER;
+  db, fileDb : DbHandle;
+  stmt : StmtHandle;
+  rc : INTEGER;
+  ok : BOOLEAN;
+  bin : ARRAY [0..7] OF CHAR;
+  buf : ARRAY [0..127] OF CHAR;
+
+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 Echo (ctx : ContextHandle; argc : INTEGER; argv : ADDRESS);
+VAR
+  v : ValueHandle;
+  text : ARRAY [0..63] OF CHAR;
+BEGIN
+  IF argc = 1 THEN
+    v := SQLiteArrayAt(argv, 0);
+    IF sqlite3_value_type(v) = SQLiteText THEN
+      ValueText(v, text);
+      ResultTextCopy(ctx, text)
+    END
+  END
+END Echo;
+
+PROCEDURE QueryCount (h : DbHandle; VAR n : INTEGER) : BOOLEAN;
+VAR
+  s : StmtHandle;
+  code : INTEGER;
+  good : BOOLEAN;
+BEGIN
+  s := NIL; n := -1; good := FALSE;
+  code := PrepareOne(h, "SELECT count(*) FROM t;", s);
+  IF (code = SQLiteOk) AND (s # NIL) THEN
+    IF sqlite3_step(s) = SQLiteRow THEN
+      n := 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
+END QueryCount;
+
+BEGIN
+  ExitCode := 1;
+  db := NIL; fileDb := NIL; stmt := NIL; ok := FALSE;
+  bin[0] := CHR(0); bin[1] := CHR(1); bin[2] := CHR(2);
+
+  IF (sqlite3_open_v2(":memory:", db,
+        SQLiteOpenReadWrite + SQLiteOpenCreate + SQLiteOpenMemory,
+        NIL) = SQLiteOk) AND (db # NIL)
+     AND (ExecSimple(db, "CREATE TABLE t(a TEXT, b BLOB);") = SQLiteOk)
+     AND (PrepareOne(db, "INSERT INTO t(a,b) VALUES(?,?);", stmt) = SQLiteOk)
+     AND (BindTextCopy(stmt, 1, "hello") = SQLiteOk)
+     AND (BindBlobCopy(stmt, 2, ADR(bin[0]), 3) = SQLiteOk)
+     AND (sqlite3_step(stmt) = SQLiteDone) THEN
+    ok := sqlite3_finalize(stmt) = SQLiteOk;
+    stmt := NIL
+  END;
+
+  IF ok THEN
+    ok := (ExecWithErr(db, "INSERT INTO bogus(x) VALUES(1);", buf) # SQLiteOk)
+          AND (buf[0] # CHR(0));
+    IF ok THEN
+      ErrMsg(db, buf);
+      ok := buf[0] # CHR(0)
+    END
+  END;
+  IF ok THEN
+    ErrStr(SQLiteError, buf);
+    ok := buf[0] # CHR(0)
+  END;
+  IF ok THEN
+    LibVersionStr(buf);
+    ok := buf[0] # CHR(0)
+  END;
+  IF ok THEN
+    CStrToM2(NIL, buf);
+    ok := buf[0] = CHR(0)
+  END;
+  IF ok THEN
+    ok := (StaticDestr() = NIL) AND (TransientDestr() = SQLiteTransient())
+  END;
+
+  (* ResultTextCopy + ValueText through a UDF. *)
+  IF ok THEN
+    ok := sqlite3_create_function_v2(db, "m2_echo", 1, SQLiteUtf8,
+      NIL, ADR(Echo), NIL, NIL, NIL) = SQLiteOk
+  END;
+  IF ok THEN
+    stmt := NIL;
+    ok := (PrepareOne(db, "SELECT m2_echo('hey');", stmt) = SQLiteOk)
+          AND (stmt # NIL)
+          AND (sqlite3_step(stmt) = SQLiteRow)
+          AND (sqlite3_column_type(stmt, 0) = SQLiteText);
+    IF ok THEN
+      CStrToM2(sqlite3_column_text(stmt, 0), buf);
+      ok := SameText(buf, "hey")
+    END;
+    IF stmt # NIL THEN
+      IF sqlite3_finalize(stmt) # SQLiteOk THEN ok := FALSE END;
+      stmt := NIL
+    END
+  END;
+
+  (* BackupToFile and a file-backed verification query. *)
+  IF ok THEN
+    ok := BackupToFile(db, "/tmp/m2-sqlite-utils.db") = SQLiteOk
+  END;
+  IF ok THEN
+    fileDb := NIL;
+    ok := (sqlite3_open("/tmp/m2-sqlite-utils.db", fileDb) = SQLiteOk)
+          AND (fileDb # NIL);
+    IF ok THEN
+      rc := 0;
+      ok := QueryCount(fileDb, rc) AND (rc = 1)
+    END;
+    IF fileDb # NIL THEN
+      IF sqlite3_close(fileDb) # SQLiteOk THEN ok := FALSE END;
+      fileDb := NIL
+    END
+  END;
+
+  IF stmt # NIL THEN
+    IF sqlite3_finalize(stmt) # SQLiteOk THEN ok := FALSE END
+  END;
+  IF db # NIL THEN
+    IF sqlite3_close(db) # SQLiteOk THEN ok := FALSE END
+  END;
+  IF ok THEN ExitCode := 42 END
+END SQLiteHelpers.

+ 78 - 0
bindings/sqlite3/tests/SQLitePrepareTail.mod

@@ -0,0 +1,78 @@
+MODULE SQLitePrepareTail;
+(* sqlite3_prepare_v3: prepare one statement and reuse its tail. *)
+
+FROM SYSTEM IMPORT ADDRESS;
+FROM CStr IMPORT Copy;
+FROM SQLite IMPORT
+  DbHandle, StmtHandle,
+  SQLiteOk, SQLiteRow, SQLiteDone,
+  SQLiteOpenReadWrite, SQLiteOpenCreate, SQLiteOpenMemory,
+  SQLitePreparePersistent,
+  sqlite3_open_v2, sqlite3_close,
+  sqlite3_prepare_v3, sqlite3_step, sqlite3_column_int,
+  sqlite3_finalize;
+
+VAR
+  ExitCode : INTEGER;
+  db : DbHandle;
+  stmt : StmtHandle;
+  tail, tail2 : ADDRESS;
+  rc : INTEGER;
+  ok : BOOLEAN;
+  buf : ARRAY [0..127] OF CHAR;
+
+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;
+
+BEGIN
+  ExitCode := 1;
+  db := NIL; stmt := NIL; tail := NIL; tail2 := NIL; ok := FALSE;
+
+  IF (sqlite3_open_v2(":memory:", db,
+        SQLiteOpenReadWrite + SQLiteOpenCreate + SQLiteOpenMemory,
+        NIL) = SQLiteOk) AND (db # NIL) THEN
+    rc := sqlite3_prepare_v3(db, "SELECT 1; SELECT 2;", -1,
+                             SQLitePreparePersistent, stmt, tail);
+    ok := (rc = SQLiteOk) AND (stmt # NIL) AND (tail # NIL);
+    IF ok THEN
+      IF Copy(tail, buf) = 0 THEN
+        ok := FALSE
+      ELSIF NOT SameText(buf, " SELECT 2;") THEN
+        ok := FALSE
+      END
+    END;
+    IF ok THEN ok := sqlite3_step(stmt) = SQLiteRow END;
+    IF ok THEN ok := sqlite3_column_int(stmt, 0) = 1 END;
+    IF ok THEN ok := sqlite3_step(stmt) = SQLiteDone END;
+    IF stmt # NIL THEN
+      IF sqlite3_finalize(stmt) # SQLiteOk THEN ok := FALSE END;
+      stmt := NIL
+    END;
+
+    IF ok THEN
+      rc := sqlite3_prepare_v3(db, buf, -1, 0, stmt, tail2);
+      ok := (rc = SQLiteOk) AND (stmt # NIL);
+      IF ok THEN ok := sqlite3_step(stmt) = SQLiteRow END;
+      IF ok THEN ok := sqlite3_column_int(stmt, 0) = 2 END;
+      IF ok THEN ok := sqlite3_step(stmt) = SQLiteDone END;
+      IF stmt # NIL THEN
+        IF sqlite3_finalize(stmt) # SQLiteOk THEN ok := FALSE END;
+        stmt := NIL
+      END
+    END
+  END;
+
+  IF stmt # NIL THEN
+    IF sqlite3_finalize(stmt) # SQLiteOk THEN ok := FALSE END
+  END;
+  IF db # NIL THEN
+    IF sqlite3_close(db) # SQLiteOk THEN ok := FALSE END
+  END;
+  IF ok THEN ExitCode := 42 END
+END SQLitePrepareTail.