瀏覽代碼

bindings: add SQLite aggregates and auxdata

Eric Streit 1 天之前
父節點
當前提交
773cadae13

+ 15 - 0
bindings/sqlite3/README.md

@@ -97,6 +97,13 @@ sqlite3_value_double
 sqlite3_value_text
 sqlite3_value_text
 sqlite3_value_blob
 sqlite3_value_blob
 sqlite3_value_bytes
 sqlite3_value_bytes
+sqlite3_value_numeric_type
+sqlite3_value_dup
+sqlite3_value_free
+sqlite3_value_pointer
+sqlite3_aggregate_context
+sqlite3_get_auxdata
+sqlite3_set_auxdata
 sqlite3_result_null
 sqlite3_result_null
 sqlite3_result_int
 sqlite3_result_int
 sqlite3_result_int64
 sqlite3_result_int64
@@ -105,6 +112,13 @@ sqlite3_result_text
 sqlite3_result_blob
 sqlite3_result_blob
 sqlite3_result_error
 sqlite3_result_error
 sqlite3_result_error_code
 sqlite3_result_error_code
+sqlite3_result_error_toobig
+sqlite3_result_error_nomem
+sqlite3_result_value
+sqlite3_result_zeroblob
+sqlite3_result_zeroblob64
+sqlite3_result_text64
+sqlite3_result_blob64
 sqlite3_user_data
 sqlite3_user_data
 sqlite3_context_db_handle
 sqlite3_context_db_handle
 
 
@@ -176,6 +190,7 @@ Hook-related constants include `SQLiteOp*`, `SQLiteAuth*` and
 - `SQLiteCapi3c.mod` - broad C-API integration test adapted from upstream `capi3c.test`
 - `SQLiteCapi3c.mod` - broad C-API integration test adapted from upstream `capi3c.test`
 - `SQLiteConnection.mod` - open_v2, close_v2, changes/rowid and deferred close
 - `SQLiteConnection.mod` - open_v2, close_v2, changes/rowid and deferred close
 - `SQLiteCallback.mod` - exec row callbacks and user-defined scalar functions
 - `SQLiteCallback.mod` - exec row callbacks and user-defined scalar functions
+- `SQLiteAggregate.mod` - aggregates, auxdata and extended UDF results
 - `SQLiteHooks.mod` - busy/progress/trace/update/commit/rollback/authorizer hooks
 - `SQLiteHooks.mod` - busy/progress/trace/update/commit/rollback/authorizer hooks
 - `showcase/SQLiteShowcase.mod` - prepared-statement walkthrough
 - `showcase/SQLiteShowcase.mod` - prepared-statement walkthrough
 - `showcase/SQLiteFeatureShowcase.mod` - broad feature walkthrough
 - `showcase/SQLiteFeatureShowcase.mod` - broad feature walkthrough

+ 43 - 0
bindings/sqlite3/SQLite.def

@@ -107,6 +107,13 @@ EXPORT UNQUALIFIED
   sqlite3_value_text,
   sqlite3_value_text,
   sqlite3_value_blob,
   sqlite3_value_blob,
   sqlite3_value_bytes,
   sqlite3_value_bytes,
+  sqlite3_value_numeric_type,
+  sqlite3_value_dup,
+  sqlite3_value_free,
+  sqlite3_value_pointer,
+  sqlite3_aggregate_context,
+  sqlite3_get_auxdata,
+  sqlite3_set_auxdata,
   sqlite3_result_null,
   sqlite3_result_null,
   sqlite3_result_int,
   sqlite3_result_int,
   sqlite3_result_int64,
   sqlite3_result_int64,
@@ -115,6 +122,13 @@ EXPORT UNQUALIFIED
   sqlite3_result_blob,
   sqlite3_result_blob,
   sqlite3_result_error,
   sqlite3_result_error,
   sqlite3_result_error_code,
   sqlite3_result_error_code,
+  sqlite3_result_error_toobig,
+  sqlite3_result_error_nomem,
+  sqlite3_result_value,
+  sqlite3_result_zeroblob,
+  sqlite3_result_zeroblob64,
+  sqlite3_result_text64,
+  sqlite3_result_blob64,
   sqlite3_user_data,
   sqlite3_user_data,
   sqlite3_context_db_handle,
   sqlite3_context_db_handle,
   sqlite3_busy_timeout,
   sqlite3_busy_timeout,
@@ -408,6 +422,20 @@ PROCEDURE sqlite3_value_double (value : ValueHandle) : REAL;
 PROCEDURE sqlite3_value_text (value : ValueHandle) : CString;
 PROCEDURE sqlite3_value_text (value : ValueHandle) : CString;
 PROCEDURE sqlite3_value_blob (value : ValueHandle) : ADDRESS;
 PROCEDURE sqlite3_value_blob (value : ValueHandle) : ADDRESS;
 PROCEDURE sqlite3_value_bytes (value : ValueHandle) : INTEGER;
 PROCEDURE sqlite3_value_bytes (value : ValueHandle) : INTEGER;
+PROCEDURE sqlite3_value_numeric_type (value : ValueHandle) : INTEGER;
+PROCEDURE sqlite3_value_dup (value : ValueHandle) : ValueHandle;
+PROCEDURE sqlite3_value_free (value : ValueHandle);
+PROCEDURE sqlite3_value_pointer (value : ValueHandle;
+                                 typeName : CString) : ADDRESS;
+
+PROCEDURE sqlite3_aggregate_context (ctx : ContextHandle;
+                                     nBytes : INTEGER) : ADDRESS;
+PROCEDURE sqlite3_get_auxdata (ctx : ContextHandle;
+                               idx : INTEGER) : ADDRESS;
+PROCEDURE sqlite3_set_auxdata (ctx : ContextHandle;
+                               idx : INTEGER;
+                               data : ADDRESS;
+                               destructor : ADDRESS);
 
 
 PROCEDURE sqlite3_result_null (ctx : ContextHandle);
 PROCEDURE sqlite3_result_null (ctx : ContextHandle);
 PROCEDURE sqlite3_result_int (ctx : ContextHandle; value : INTEGER);
 PROCEDURE sqlite3_result_int (ctx : ContextHandle; value : INTEGER);
@@ -425,6 +453,21 @@ PROCEDURE sqlite3_result_error (ctx : ContextHandle;
                                 msg : ARRAY OF CHAR;
                                 msg : ARRAY OF CHAR;
                                 nBytes : INTEGER);
                                 nBytes : INTEGER);
 PROCEDURE sqlite3_result_error_code (ctx : ContextHandle; code : INTEGER);
 PROCEDURE sqlite3_result_error_code (ctx : ContextHandle; code : INTEGER);
+PROCEDURE sqlite3_result_error_toobig (ctx : ContextHandle);
+PROCEDURE sqlite3_result_error_nomem (ctx : ContextHandle);
+PROCEDURE sqlite3_result_value (ctx : ContextHandle; value : ValueHandle);
+PROCEDURE sqlite3_result_zeroblob (ctx : ContextHandle; nBytes : INTEGER);
+PROCEDURE sqlite3_result_zeroblob64 (ctx : ContextHandle;
+                                     nBytes : LONGCARD) : INTEGER;
+PROCEDURE sqlite3_result_text64 (ctx : ContextHandle;
+                                 value : ARRAY OF CHAR;
+                                 nBytes : LONGCARD;
+                                 destructor : ADDRESS;
+                                 encoding : INTEGER);
+PROCEDURE sqlite3_result_blob64 (ctx : ContextHandle;
+                                 data : ADDRESS;
+                                 nBytes : LONGCARD;
+                                 destructor : ADDRESS);
 
 
 PROCEDURE sqlite3_user_data (ctx : ContextHandle) : ADDRESS;
 PROCEDURE sqlite3_user_data (ctx : ContextHandle) : ADDRESS;
 PROCEDURE sqlite3_context_db_handle (ctx : ContextHandle) : DbHandle;
 PROCEDURE sqlite3_context_db_handle (ctx : ContextHandle) : DbHandle;

+ 15 - 8
bindings/sqlite3/TEST-INVENTORY.md

@@ -8,11 +8,11 @@ metadata/diagnostics, BLOB error handling and extended constraint
 codes are implemented.  Covered by `tests/SQLiteParams.mod`,
 codes are implemented.  Covered by `tests/SQLiteParams.mod`,
 `tests/SQLiteIntrospection.mod`, `tests/SQLiteBlobErrors.mod`,
 `tests/SQLiteIntrospection.mod`, `tests/SQLiteBlobErrors.mod`,
 `tests/SQLiteConstraints.mod`, `tests/SQLiteBackup.mod`,
 `tests/SQLiteConstraints.mod`, `tests/SQLiteBackup.mod`,
-`tests/SQLitePrepareTail.mod`, `tests/SQLiteHelpers.mod`, the broad
-`tests/SQLiteCapi3c.mod` integration test and
-`showcase/SQLiteFeatureShowcase.mod`.  Typed handle aliases, a
-`SQLiteUtils` helper layer and portable library discovery are also in
-place.
+`tests/SQLitePrepareTail.mod`, `tests/SQLiteHelpers.mod`,
+`tests/SQLiteAggregate.mod`, the broad `tests/SQLiteCapi3c.mod`
+integration test and `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
 This note records which existing tests are useful for hardening the V3
 SQLite binding under `bindings/sqlite3`, and which APIs they would drive.
 SQLite binding under `bindings/sqlite3`, and which APIs they would drive.
@@ -21,7 +21,7 @@ SQLite binding under `bindings/sqlite3`, and which APIs they would drive.
 
 
 | Source | Count | Notes |
 | Source | Count | Notes |
 |---|---:|---|
 |---|---:|---|
-| `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 |
+| `bindings/sqlite3/tests` | 17 | Current V3 tests: smoke, prepared, prepare-v3 tail, params, helpers, introspection, scalar, errors, BLOB, BLOB errors, constraints, backup, C API integration, connection, callback, aggregate, hooks |
 | sibling `m2SQLITE/tests` | 7 | Original GNU Modula-2 binding tests; closest existing coverage |
 | 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 |
 | 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 |
 | `compiler/tests` | 806 files | Compiler/ABI tests; not SQLite-specific, but several guard binding prerequisites |
@@ -47,6 +47,7 @@ harness.
 - `SQLiteCapi3c.mod`
 - `SQLiteCapi3c.mod`
 - `SQLiteConnection.mod`
 - `SQLiteConnection.mod`
 - `SQLiteCallback.mod`
 - `SQLiteCallback.mod`
+- `SQLiteAggregate.mod`
 - `SQLiteHooks.mod`
 - `SQLiteHooks.mod`
 
 
 ## Original m2SQLITE tests
 ## Original m2SQLITE tests
@@ -167,9 +168,15 @@ Ported from `m2SQLITE/tests/test_codes.mod` into `SQLiteConstraints.mod`.
 - `SQLiteConstraintCheck` = 275
 - `SQLiteConstraintCheck` = 275
 - optionally trigger, rowid and datatype constraints
 - optionally trigger, rowid and datatype constraints
 
 
-### 5. Later slices
+### 5. Aggregate/UDF extensions — implemented 2026-10-27
 
 
-- aggregate functions + auxdata (`test_func.mod`, `func*.test`)
+Ported into `SQLiteAggregate.mod`: aggregate context, auxdata,
+`value_numeric_type`, `value_dup/free`, `result_value`, zeroblob64,
+text64/blob64 and error-result APIs.
+
+### 6. Later slices
+
+- deeper aggregate/window-function coverage (`func*.test`, `window*.test`)
 - file-to-file backup coverage (`backup*.test`)
 - file-to-file backup coverage (`backup*.test`)
 - `sqlite3_get_table` / `free_table`
 - `sqlite3_get_table` / `free_table`
 - extension loading and WAL/checkpoint helpers
 - extension loading and WAL/checkpoint helpers

+ 1 - 0
bindings/sqlite3/run.sh

@@ -89,5 +89,6 @@ build_and_run SQLiteCapi3c
 build_and_run SQLiteConnection
 build_and_run SQLiteConnection
 build_and_run SQLiteCallback
 build_and_run SQLiteCallback
 build_and_run SQLiteHooks
 build_and_run SQLiteHooks
+build_and_run SQLiteAggregate
 build_and_run SQLiteShowcase showcase/SQLiteShowcase.mod
 build_and_run SQLiteShowcase showcase/SQLiteShowcase.mod
 build_and_run SQLiteFeatureShowcase showcase/SQLiteFeatureShowcase.mod
 build_and_run SQLiteFeatureShowcase showcase/SQLiteFeatureShowcase.mod

+ 283 - 0
bindings/sqlite3/tests/SQLiteAggregate.mod

@@ -0,0 +1,283 @@
+MODULE SQLiteAggregate;
+(* Aggregate functions, auxdata and the remaining UDF value/result APIs. *)
+
+FROM SYSTEM IMPORT ADDRESS, ADR;
+FROM CStr IMPORT CString, Copy;
+FROM SQLite IMPORT
+  DbHandle, StmtHandle, ContextHandle, ValueHandle,
+  SQLiteOk, SQLiteRow, SQLiteDone, SQLiteNull,
+  SQLiteInteger, SQLiteText, SQLiteTooBig, SQLiteNoMem,
+  SQLiteOpenReadWrite, SQLiteOpenCreate, SQLiteOpenMemory, SQLiteUtf8,
+  sqlite3_open_v2, sqlite3_close, sqlite3_exec,
+  sqlite3_prepare_v2, sqlite3_step, sqlite3_column_int,
+  sqlite3_column_text, sqlite3_finalize,
+  sqlite3_create_function_v2,
+  sqlite3_value_type, sqlite3_value_int64, sqlite3_value_numeric_type,
+  sqlite3_value_dup, sqlite3_value_free,
+  sqlite3_aggregate_context, sqlite3_get_auxdata, sqlite3_set_auxdata,
+  sqlite3_result_int, sqlite3_result_int64, sqlite3_result_null,
+  sqlite3_result_error, sqlite3_result_value, sqlite3_result_zeroblob,
+  sqlite3_result_zeroblob64,
+  sqlite3_result_text64, sqlite3_result_blob64,
+  sqlite3_result_error_toobig, sqlite3_result_error_nomem,
+  sqlite3_free, SQLiteArrayAt, SQLiteTransient;
+
+TYPE
+  SumPtr = POINTER TO LONGINT;
+
+VAR
+  ExitCode : INTEGER;
+  db : DbHandle;
+  stmt : StmtHandle;
+  rc : INTEGER;
+  ok : BOOLEAN;
+  bin, auxMarker : ARRAY [0..7] 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 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 : StmtHandle;
+  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 QueryText (sql : ARRAY OF CHAR; want : ARRAY OF CHAR) : BOOLEAN;
+VAR
+  s : StmtHandle;
+  code : INTEGER;
+  p : CString;
+  buf : ARRAY [0..63] OF CHAR;
+  good : BOOLEAN;
+BEGIN
+  s := NIL; good := FALSE;
+  code := sqlite3_prepare_v2(db, sql, -1, s, NIL);
+  IF (code = SQLiteOk) AND (s # NIL) THEN
+    IF sqlite3_step(s) = SQLiteRow THEN
+      p := sqlite3_column_text(s, 0);
+      IF (p # NIL) AND (Copy(p, buf) > 0) THEN
+        good := SameText(buf, want)
+      END;
+      IF sqlite3_step(s) # SQLiteDone THEN good := FALSE END
+    END;
+    IF sqlite3_finalize(s) # SQLiteOk THEN good := FALSE END
+  END;
+  RETURN good
+END QueryText;
+
+PROCEDURE QueryError (sql : ARRAY OF CHAR; want : INTEGER) : BOOLEAN;
+VAR
+  s : StmtHandle;
+  code : INTEGER;
+  good : BOOLEAN;
+BEGIN
+  s := NIL; good := FALSE;
+  code := sqlite3_prepare_v2(db, sql, -1, s, NIL);
+  IF (code = SQLiteOk) AND (s # NIL) THEN
+    good := sqlite3_step(s) = want;
+    rc := sqlite3_finalize(s)
+  END;
+  RETURN good
+END QueryError;
+
+PROCEDURE Register (name : ARRAY OF CHAR; n : INTEGER;
+                    xFunc, xStep, xFinal : ADDRESS) : BOOLEAN;
+BEGIN
+  RETURN sqlite3_create_function_v2(db, name, n, SQLiteUtf8,
+    NIL, xFunc, xStep, xFinal, NIL) = SQLiteOk
+END Register;
+
+PROCEDURE SumStep (ctx : ContextHandle; argc : INTEGER; argv : ADDRESS);
+VAR
+  p : SumPtr;
+  v : ValueHandle;
+BEGIN
+  p := VAL(SumPtr, sqlite3_aggregate_context(ctx, 8));
+  IF p # NIL THEN
+    v := SQLiteArrayAt(argv, 0);
+    IF sqlite3_value_type(v) # SQLiteNull THEN
+      p^ := p^ + sqlite3_value_int64(v)
+    END
+  END
+END SumStep;
+
+PROCEDURE SumFinal (ctx : ContextHandle);
+VAR
+  p : SumPtr;
+BEGIN
+  p := VAL(SumPtr, sqlite3_aggregate_context(ctx, 0));
+  IF p = NIL THEN
+    sqlite3_result_null(ctx)
+  ELSE
+    sqlite3_result_int64(ctx, p^)
+  END
+END SumFinal;
+
+PROCEDURE AuxFn (ctx : ContextHandle; argc : INTEGER; argv : ADDRESS);
+VAR
+  p, q : ADDRESS;
+BEGIN
+  p := ADR(auxMarker[0]);
+  sqlite3_set_auxdata(ctx, 0, p, NIL);
+  q := sqlite3_get_auxdata(ctx, 0);
+  IF q = p THEN
+    sqlite3_result_int(ctx, 1)
+  ELSE
+    sqlite3_result_int(ctx, 0)
+  END
+END AuxFn;
+
+PROCEDURE DupFn (ctx : ContextHandle; argc : INTEGER; argv : ADDRESS);
+VAR
+  v, d : ValueHandle;
+BEGIN
+  IF argc = 1 THEN
+    v := SQLiteArrayAt(argv, 0);
+    d := sqlite3_value_dup(v);
+    IF d = NIL THEN
+      sqlite3_result_error(ctx, "dup failed", -1)
+    ELSE
+      sqlite3_result_value(ctx, d);
+      sqlite3_value_free(d)
+    END
+  ELSE
+    sqlite3_result_error(ctx, "m2_dup expects 1 argument", -1)
+  END
+END DupFn;
+
+PROCEDURE NumTypeFn (ctx : ContextHandle; argc : INTEGER; argv : ADDRESS);
+BEGIN
+  IF argc = 1 THEN
+    sqlite3_result_int(ctx,
+      sqlite3_value_numeric_type(SQLiteArrayAt(argv, 0)))
+  ELSE
+    sqlite3_result_error(ctx, "m2_numtype expects 1 argument", -1)
+  END
+END NumTypeFn;
+
+PROCEDURE Zero32Fn (ctx : ContextHandle; argc : INTEGER; argv : ADDRESS);
+BEGIN
+  sqlite3_result_zeroblob(ctx, 4)
+END Zero32Fn;
+
+PROCEDURE Zero64Fn (ctx : ContextHandle; argc : INTEGER; argv : ADDRESS);
+VAR
+  code : INTEGER;
+BEGIN
+  code := sqlite3_result_zeroblob64(ctx, 4);
+  IF code # SQLiteOk THEN
+    sqlite3_result_error(ctx, "zeroblob64 failed", -1)
+  END
+END Zero64Fn;
+
+PROCEDURE Text64Fn (ctx : ContextHandle; argc : INTEGER; argv : ADDRESS);
+BEGIN
+  sqlite3_result_text64(ctx, "hi", 2, SQLiteTransient(), SQLiteUtf8)
+END Text64Fn;
+
+PROCEDURE Blob64Fn (ctx : ContextHandle; argc : INTEGER; argv : ADDRESS);
+BEGIN
+  sqlite3_result_blob64(ctx, ADR(bin[0]), 3, SQLiteTransient())
+END Blob64Fn;
+
+PROCEDURE TooBigFn (ctx : ContextHandle; argc : INTEGER; argv : ADDRESS);
+BEGIN
+  sqlite3_result_error_toobig(ctx)
+END TooBigFn;
+
+PROCEDURE NoMemFn (ctx : ContextHandle; argc : INTEGER; argv : ADDRESS);
+BEGIN
+  sqlite3_result_error_nomem(ctx)
+END NoMemFn;
+
+BEGIN
+  ExitCode := 1;
+  db := NIL; stmt := NIL; ok := FALSE;
+  bin[0] := CHR(0); bin[1] := CHR(1); bin[2] := CHR(2);
+  auxMarker[0] := 'A';
+
+  IF (sqlite3_open_v2(":memory:", db,
+        SQLiteOpenReadWrite + SQLiteOpenCreate + SQLiteOpenMemory,
+        NIL) = SQLiteOk) AND (db # NIL)
+     AND ExecSQL("CREATE TABLE nums(x INTEGER);")
+     AND ExecSQL("INSERT INTO nums VALUES(1);")
+     AND ExecSQL("INSERT INTO nums VALUES(2);")
+     AND ExecSQL("INSERT INTO nums VALUES(3);") THEN
+    ok := TRUE
+  END;
+
+  IF ok THEN
+    ok := Register("m2_sum", 1, NIL, ADR(SumStep), ADR(SumFinal))
+          AND QueryInt("SELECT m2_sum(x) FROM nums;", 6)
+  END;
+  IF ok THEN
+    ok := Register("m2_aux", 0, ADR(AuxFn), NIL, NIL)
+          AND QueryInt("SELECT m2_aux();", 1)
+  END;
+  IF ok THEN
+    ok := Register("m2_dup", 1, ADR(DupFn), NIL, NIL)
+          AND QueryText("SELECT m2_dup('abc');", "abc")
+  END;
+  IF ok THEN
+    ok := Register("m2_numtype", 1, ADR(NumTypeFn), NIL, NIL)
+          AND QueryInt("SELECT m2_numtype('42');", SQLiteInteger)
+  END;
+  IF ok THEN
+    ok := Register("m2_zero32", 0, ADR(Zero32Fn), NIL, NIL)
+          AND QueryText("SELECT hex(m2_zero32());", "00000000")
+  END;
+  IF ok THEN
+    ok := Register("m2_zero64", 0, ADR(Zero64Fn), NIL, NIL)
+          AND QueryText("SELECT hex(m2_zero64());", "00000000")
+  END;
+  IF ok THEN
+    ok := Register("m2_text64", 0, ADR(Text64Fn), NIL, NIL)
+          AND QueryText("SELECT m2_text64();", "hi")
+  END;
+  IF ok THEN
+    ok := Register("m2_blob64", 0, ADR(Blob64Fn), NIL, NIL)
+          AND QueryText("SELECT hex(m2_blob64());", "000102")
+  END;
+  IF ok THEN
+    ok := Register("m2_toobig", 0, ADR(TooBigFn), NIL, NIL)
+          AND QueryError("SELECT m2_toobig();", SQLiteTooBig)
+  END;
+  IF ok THEN
+    ok := Register("m2_nomem", 0, ADR(NoMemFn), NIL, NIL)
+          AND QueryError("SELECT m2_nomem();", SQLiteNoMem)
+  END;
+
+  IF db # NIL THEN
+    IF sqlite3_close(db) # SQLiteOk THEN ok := FALSE END
+  END;
+  IF ok THEN ExitCode := 42 END
+END SQLiteAggregate.