Просмотр исходного кода

bindings: add broad SQLite C API test

Eric Streit 1 день назад
Родитель
Сommit
d516a555f2

+ 1 - 0
bindings/sqlite3/README.md

@@ -148,6 +148,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
+- `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
 - `SQLiteHooks.mod` - busy/progress/trace/update/commit/rollback/authorizer hooks

+ 4 - 2
bindings/sqlite3/TEST-INVENTORY.md

@@ -7,7 +7,8 @@ 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` and `tests/SQLiteBackup.mod`.
+`tests/SQLiteConstraints.mod`, `tests/SQLiteBackup.mod` and the broad
+`tests/SQLiteCapi3c.mod` integration test.
 
 This note records which existing tests are useful for hardening the V3
 SQLite binding under `bindings/sqlite3`, and which APIs they would drive.
@@ -16,7 +17,7 @@ SQLite binding under `bindings/sqlite3`, and which APIs they would drive.
 
 | Source | Count | Notes |
 |---|---:|---|
-| `bindings/sqlite3/tests` | 13 | Current V3 tests: smoke, prepared, params, introspection, scalar, errors, BLOB, BLOB errors, constraints, backup, connection, callback, hooks |
+| `bindings/sqlite3/tests` | 14 | Current V3 tests: smoke, prepared, params, 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 |
@@ -37,6 +38,7 @@ harness.
 - `SQLiteBlobErrors.mod`
 - `SQLiteConstraints.mod`
 - `SQLiteBackup.mod`
+- `SQLiteCapi3c.mod`
 - `SQLiteConnection.mod`
 - `SQLiteCallback.mod`
 - `SQLiteHooks.mod`

+ 1 - 0
bindings/sqlite3/run.sh

@@ -46,6 +46,7 @@ build_and_run SQLiteBlob
 build_and_run SQLiteBlobErrors
 build_and_run SQLiteConstraints
 build_and_run SQLiteBackup
+build_and_run SQLiteCapi3c
 build_and_run SQLiteConnection
 build_and_run SQLiteCallback
 build_and_run SQLiteHooks

+ 201 - 0
bindings/sqlite3/tests/SQLiteCapi3c.mod

@@ -0,0 +1,201 @@
+MODULE SQLiteCapi3c;
+(* Broad C-API integration test, adapted from upstream
+   sqlite-master/test/capi3c.test (public domain).
+
+   Exercises open/exec/prepare_v2, bind, step, column metadata,
+   scalar/BLOB column access, changes/rowid, autocommit, errors and
+   close-with-outstanding-statement behavior. *)
+
+FROM SYSTEM IMPORT ADDRESS;
+FROM CStr IMPORT CString, Copy;
+FROM SQLite IMPORT
+  SQLiteOk, SQLiteRow, SQLiteDone, SQLiteBusy,
+  SQLiteInteger, SQLiteFloat, SQLiteText, SQLiteBlob, SQLiteNull,
+  SQLiteOpenReadWrite, SQLiteOpenCreate, SQLiteOpenMemory,
+  sqlite3_open_v2, sqlite3_close, sqlite3_exec,
+  sqlite3_prepare_v2, sqlite3_step, sqlite3_finalize,
+  sqlite3_bind_int, sqlite3_bind_text,
+  sqlite3_column_count, sqlite3_column_name, sqlite3_column_decltype,
+  sqlite3_column_type, sqlite3_column_int, sqlite3_column_int64,
+  sqlite3_column_double, sqlite3_column_text, sqlite3_column_blob,
+  sqlite3_column_bytes,
+  sqlite3_changes, sqlite3_last_insert_rowid,
+  sqlite3_errcode, sqlite3_errmsg, sqlite3_get_autocommit,
+  sqlite3_free, SQLiteTransient;
+
+VAR
+  ExitCode : INTEGER;
+  db, stmt : 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 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; ok := FALSE;
+
+  IF (sqlite3_open_v2(":memory:", db,
+        SQLiteOpenReadWrite + SQLiteOpenCreate + SQLiteOpenMemory,
+        NIL) = SQLiteOk) AND (db # NIL) THEN
+    ok := ExecSQL("CREATE TABLE t(a VARINT, b BLOB, c VARCHAR(16));")
+          AND ExecSQL("INSERT INTO t VALUES(1, X'0102', 3);")
+          AND ExecSQL("INSERT INTO t VALUES('one', 'two', NULL);")
+          AND ExecSQL("INSERT INTO t VALUES(1.2, 1.3, 1.4);")
+  END;
+
+  (* Prepared SELECT and column metadata. *)
+  IF ok THEN
+    stmt := NIL;
+    rc := sqlite3_prepare_v2(db, "SELECT * FROM t;", -1, stmt, NIL);
+    ok := (rc = SQLiteOk) AND (stmt # NIL)
+          AND (sqlite3_column_count(stmt) = 3)
+          AND TextIs(sqlite3_column_name(stmt, 0), "a")
+          AND TextIs(sqlite3_column_name(stmt, 1), "b")
+          AND TextIs(sqlite3_column_name(stmt, 2), "c")
+          AND TextIs(sqlite3_column_decltype(stmt, 0), "VARINT")
+          AND TextIs(sqlite3_column_decltype(stmt, 1), "BLOB")
+          AND TextIs(sqlite3_column_decltype(stmt, 2), "VARCHAR(16)")
+  END;
+
+  (* Integer + BLOB + integer row. *)
+  IF ok THEN
+    ok := sqlite3_step(stmt) = SQLiteRow;
+    IF ok THEN
+      ok := (sqlite3_column_type(stmt, 0) = SQLiteInteger)
+            AND (sqlite3_column_int(stmt, 0) = 1)
+            AND (sqlite3_column_int64(stmt, 0) = 1)
+            AND (sqlite3_column_double(stmt, 0) = 1.0)
+            AND (sqlite3_column_type(stmt, 1) = SQLiteBlob)
+            AND (sqlite3_column_blob(stmt, 1) # NIL)
+            AND (sqlite3_column_bytes(stmt, 1) = 2)
+            AND (sqlite3_column_type(stmt, 2) = SQLiteText)
+            AND (sqlite3_column_int(stmt, 2) = 3)
+            AND TextIs(sqlite3_column_text(stmt, 2), "3")
+    END
+  END;
+
+  (* Text + text + NULL row. *)
+  IF ok THEN
+    ok := sqlite3_step(stmt) = SQLiteRow;
+    IF ok THEN
+      ok := (sqlite3_column_type(stmt, 0) = SQLiteText)
+            AND TextIs(sqlite3_column_text(stmt, 0), "one")
+            AND (sqlite3_column_type(stmt, 1) = SQLiteText)
+            AND (sqlite3_column_bytes(stmt, 1) = 3)
+            AND TextIs(sqlite3_column_text(stmt, 1), "two")
+            AND (sqlite3_column_type(stmt, 2) = SQLiteNull)
+            AND (sqlite3_column_int(stmt, 2) = 0)
+    END
+  END;
+
+  (* Real + real + real row. *)
+  IF ok THEN
+    ok := sqlite3_step(stmt) = SQLiteRow;
+    IF ok THEN
+      ok := (sqlite3_column_type(stmt, 0) = SQLiteFloat)
+            AND (sqlite3_column_double(stmt, 0) = 1.2)
+            AND (sqlite3_column_type(stmt, 1) = SQLiteFloat)
+            AND (sqlite3_column_double(stmt, 1) = 1.3)
+            AND (sqlite3_column_type(stmt, 2) = SQLiteText)
+            AND (sqlite3_column_double(stmt, 2) = 1.4)
+            AND TextIs(sqlite3_column_text(stmt, 2), "1.4")
+    END
+  END;
+
+  IF ok THEN ok := sqlite3_step(stmt) = SQLiteDone END;
+  IF NOT Finish(stmt) THEN ok := FALSE END;
+
+  (* Bound insert and change counters. *)
+  IF ok THEN
+    stmt := NIL;
+    rc := sqlite3_prepare_v2(db,
+      "INSERT INTO t(a,c) VALUES(?,?);", -1, stmt, NIL);
+    ok := (rc = SQLiteOk) AND (stmt # NIL)
+          AND (sqlite3_bind_int(stmt, 1, 7) = SQLiteOk)
+          AND (sqlite3_bind_text(stmt, 2, "seven", -1,
+                                 SQLiteTransient()) = SQLiteOk);
+    IF ok THEN ok := sqlite3_step(stmt) = SQLiteDone END;
+    IF ok THEN
+      ok := (sqlite3_changes(db) = 1)
+            AND (sqlite3_last_insert_rowid(db) = 4)
+    END;
+    IF NOT Finish(stmt) THEN ok := FALSE END
+  END;
+
+  (* Autocommit state. *)
+  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;
+
+  (* A failed prepare leaves an error and a message. *)
+  IF ok THEN
+    stmt := NIL;
+    rc := sqlite3_prepare_v2(db, "SELECT namex FROM sqlite_master;",
+                             -1, stmt, NIL);
+    ok := (rc # SQLiteOk) AND (stmt = NIL)
+          AND (sqlite3_errcode(db) # SQLiteOk)
+          AND (sqlite3_errmsg(db) # NIL);
+    IF NOT Finish(stmt) THEN ok := FALSE END
+  END;
+
+  (* close() must fail while a statement is outstanding. *)
+  IF ok THEN
+    stmt := NIL;
+    rc := sqlite3_prepare_v2(db, "SELECT 1;", -1, stmt, NIL);
+    ok := (rc = SQLiteOk) AND (stmt # NIL)
+          AND (sqlite3_close(db) = SQLiteBusy);
+    IF NOT Finish(stmt) THEN ok := FALSE END
+  END;
+  IF db # NIL THEN
+    IF sqlite3_close(db) # SQLiteOk THEN ok := FALSE END;
+    db := NIL
+  END;
+
+  IF ok THEN ExitCode := 42 END
+END SQLiteCapi3c.