]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add test cases for the carray module.
authordan <Dan Kennedy>
Tue, 7 Oct 2025 21:02:37 +0000 (21:02 +0000)
committerdan <Dan Kennedy>
Tue, 7 Oct 2025 21:02:37 +0000 (21:02 +0000)
FossilOrigin-Name: dcfc0164a95eddb5e924c606850fe2015e8f2f516d36b380cbb79d6bdfe034fc

ext/misc/carray.c
manifest
manifest.uuid
src/test1.c
test/carray02.test [new file with mode: 0644]
test/carrayfault.test [new file with mode: 0644]

index 1287f27843130bedfc6ead070840dea724ad8f7c..df60b7db326cebe7419cf019c552abbee310706b 100644 (file)
@@ -236,10 +236,11 @@ static int carrayColumn(
           sqlite3_result_text(ctx, p[pCur->iRowid-1], -1, SQLITE_TRANSIENT);
           return SQLITE_OK;
         }
-        case CARRAY_BLOB: {
+        default: {
           const struct iovec *p = (struct iovec*)pCur->pPtr;
+          assert( pCur->eType==CARRAY_BLOB );
           sqlite3_result_blob(ctx, p[pCur->iRowid-1].iov_base,
-                               (int)p[pCur->iRowid-1].iov_len, SQLITE_TRANSIENT);
+                              (int)p[pCur->iRowid-1].iov_len, SQLITE_TRANSIENT);
           return SQLITE_OK;
         }
       }
@@ -441,42 +442,53 @@ SQLITE_API int sqlite3_carray_bind(
   int mFlags,
   void (*xDestroy)(void*)
 ){
-  carray_bind *pNew;
+  carray_bind *pNew = 0;
   int i;
+  int rc = SQLITE_OK;
+  
+  /* Ensure that the mFlags value is acceptable. */
+  assert( CARRAY_INT32==0 && CARRAY_INT64==1 && CARRAY_DOUBLE==2 );
+  assert( CARRAY_TEXT==3 && CARRAY_BLOB==4 );
+  if( mFlags<CARRAY_INT32 || mFlags>CARRAY_BLOB ){
+    rc = SQLITE_ERROR;
+    goto carray_bind_error;
+  }
+
   pNew = sqlite3_malloc64(sizeof(*pNew));
   if( pNew==0 ){
-    if( xDestroy!=SQLITE_STATIC && xDestroy!=SQLITE_TRANSIENT ){
-      xDestroy(aData);
-    }
-    return SQLITE_NOMEM;
+    rc = SQLITE_NOMEM;
+    goto carray_bind_error;
   }
+
   pNew->nData = nData;
   pNew->mFlags = mFlags;
   if( xDestroy==SQLITE_TRANSIENT ){
     sqlite3_int64 sz = nData;
-    switch( mFlags & 0x07 ){
+    switch( mFlags ){
       case CARRAY_INT32:   sz *= 4;                     break;
       case CARRAY_INT64:   sz *= 8;                     break;
       case CARRAY_DOUBLE:  sz *= 8;                     break;
       case CARRAY_TEXT:    sz *= sizeof(char*);         break;
-      case CARRAY_BLOB:    sz *= sizeof(struct iovec);  break;
+      default:             sz *= sizeof(struct iovec);  break;
     }
-    if( (mFlags & 0x07)==CARRAY_TEXT ){
+    if( mFlags==CARRAY_TEXT ){
       for(i=0; i<nData; i++){
         const char *z = ((char**)aData)[i];
         if( z ) sz += strlen(z) + 1;
       }
-    }else if( (mFlags & 0x07)==CARRAY_BLOB ){
+    }else if( mFlags==CARRAY_BLOB ){
       for(i=0; i<nData; i++){
         sz += ((struct iovec*)aData)[i].iov_len;
       }
     } 
+
     pNew->aData = sqlite3_malloc64( sz );
     if( pNew->aData==0 ){
-      sqlite3_free(pNew);
-      return SQLITE_NOMEM;
+      rc = SQLITE_NOMEM;
+      goto carray_bind_error;
     }
-    if( (mFlags & 0x07)==CARRAY_TEXT ){
+
+    if( mFlags==CARRAY_TEXT ){
       char **az = (char**)pNew->aData;
       char *z = (char*)&az[nData];
       for(i=0; i<nData; i++){
@@ -491,7 +503,7 @@ SQLITE_API int sqlite3_carray_bind(
         memcpy(z, zData, n+1);
         z += n+1;
       }
-    }else if( (mFlags & 0x07)==CARRAY_BLOB ){
+    }else if( mFlags==CARRAY_BLOB ){
       struct iovec *p = (struct iovec*)pNew->aData;
       unsigned char *z = (unsigned char*)&p[nData];
       for(i=0; i<nData; i++){
@@ -510,6 +522,13 @@ SQLITE_API int sqlite3_carray_bind(
     pNew->xDel = xDestroy;
   }
   return sqlite3_bind_pointer(pStmt, idx, pNew, "carray-bind", carrayBindDel);
+ carray_bind_error:
+  if( xDestroy!=SQLITE_STATIC && xDestroy!=SQLITE_TRANSIENT ){
+    xDestroy(aData);
+  }
+  sqlite3_free(pNew);
+  return rc;
 }
 
 
index 56446e17712115fe463805d6f44afa2bb059d1ea..7aaa4331de4dbef05b6472abd6f3c027da4c394d 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Improve\sthe\sinvariant\schecker\smodule\sso\sthat\sso\sthat\sit\sadded\s"+"\sbefore\n"column\sISNULL"\sin\squeries\swhere\sthe\sbase\squery\scontains\sa\sGROUP\sBY,\sto\nprevent\sthe\sISNULL\sterm\sfrom\sbeing\spushed\sdown\sinto\sthe\ssubquery,\ssince\nthat\scan\scause\sambiguities\sif\scolumn\sis\sUNIQUE.
-D 2025-10-07T18:06:05.110
+C Add\stest\scases\sfor\sthe\scarray\smodule.
+D 2025-10-07T21:02:37.621
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -362,7 +362,7 @@ F ext/misc/base85.c ff54cc676c6ec86231f75ecc86ea45416fcb69751dfb79690d5f5da5f7d3
 F ext/misc/basexx.c 89ad6b76558efbceb627afd5e2ef1d84b2e96d9aaf9b7ecb20e3d00b51be6fcf
 F ext/misc/blobio.c a867c4c4617f6ec223a307ebfe0eabb45e0992f74dd47722b96f3e631c0edb2a
 F ext/misc/btreeinfo.c 8f5e6da2c82ec2f06ee0216e922370a436dafdbb06ffa7a552203515ff9e7ddf
-F ext/misc/carray.c 6fd2be4dfa3e9ecf227221d92d808454e18710c123034fbb74999f6625f9143f
+F ext/misc/carray.c 9f7f838b3343660256c519f05aae05303d54a62770f64e8e4bbad255d5c78c94
 F ext/misc/carray.h 4bef8af4e9ddda024f5540cc4d456c3e4a4a7624d6315edf85dce1ce8419beb8
 F ext/misc/cksumvfs.c 9d7d0cf1a8893ac5d48922bfe9f3f217b4a61a6265f559263a02bb2001259913
 F ext/misc/closure.c 5559daf1daf742228431db929d1aa86dd535a4224cc634a81d2fd0d1e6ad7839
@@ -746,7 +746,7 @@ F src/status.c 0e72e4f6be6ccfde2488eb63210297e75f569f3ce9920f6c3d77590ec6ce5ffd
 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
 F src/tclsqlite.c 3c604c49e6cf4211960a9ddb9505280fd22cde32175f40884c641c0f5a286036
 F src/tclsqlite.h 614b3780a62522bc9f8f2b9fb22689e8009958e7aa77e572d0f3149050af348a
-F src/test1.c 82d8aab7162a758d030728cab6ae6011785234854b7d6d2aebd877f985d1016a
+F src/test1.c ce9ccd92d48e4980ebd1924be3131739cfa70ac535059474155b596cdb4a529e
 F src/test2.c 62f0830958f9075692c29c6de51b495ae8969e1bef85f239ffcd9ba5fb44a5ff
 F src/test3.c 432646f581d8af1bb495e58fc98234380250954f5d5535e507fc785eccc3987a
 F src/test4.c 0ac87fc13cdb334ab3a71823f99b6c32a6bebe5d603cd6a71d84c823d43a25a0
@@ -950,6 +950,8 @@ F test/capi3c.test 31d3a6778f2d06f2d9222bd7660c41a516d1518a059b069e96ebbeadb5a49
 F test/capi3d.test 8b778794af891b0dca3d900bd345fbc8ebd2aa2aae425a9dccdd10d5233dfbde
 F test/capi3e.test 3d49c01ef2a1a55f41d73cba2b23b5059ec460fe
 F test/carray01.test 23ed7074307c4a829ba5ff2970993a9d87db7c5cdbbe1a2cbef672d0df6d6e31
+F test/carray02.test 6df8d1bf95f7c5f41f80b81e47f14332222ac820cf3de50af10ec2fb989a903c
+F test/carrayfault.test 52ef4956acbcb7b34eb177167e7af2b5ac8d9cbb80213ef4a3a9d7efb1a1f4be
 F test/cast.test a2a3b32df86e3c0601ffa2e9f028a18796305d251801efea807092dbf374a040
 F test/cffault.test 9d6b20606afe712374952eec4f8fd74b1a8097ef
 F test/changes.test 4377d202a487f66fc2822c1bf57c46798c8b2caf7446f4f701723b1dbb6b86f6
@@ -2168,8 +2170,8 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd
 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
 F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 06b4bd2aba22c57f5a5fed606c3bee225dee6fdc13bb16cc58194040ef0d7d85
-R 453915b0b72d47b5291d37cc0a19ae2f
-U drh
-Z 1eb68a79b24ce28ad8c3cdcce7ce295e
+P b4ff920fbeef9a8590219596d73c09976da3da53c08a685be56f6b2cd2cdc70c
+R 1dda2a73403b3668fbe9a0e2c83e251e
+U dan
+Z 20b6eca0d0874b62c280c82aff95f36b
 # Remove this line to create a well-formed Fossil manifest.
index 3cdeabada1d95c386a6ac869bb11dfeef47e2d49..7c39cb764a2cc7d32d24f1d4169b2e838bda7242 100644 (file)
@@ -1 +1 @@
-b4ff920fbeef9a8590219596d73c09976da3da53c08a685be56f6b2cd2cdc70c
+dcfc0164a95eddb5e924c606850fe2015e8f2f516d36b380cbb79d6bdfe034fc
index 78c7b9f03fd4ea9ad57b7f6229b45a3617b761c3..bb2ccd383761c945f1b629f06558b54a6db5fd06 100644 (file)
@@ -4360,10 +4360,71 @@ static int SQLITE_TCLAPI test_bind_value_from_select(
 #endif
 
 #ifndef SQLITE_OMIT_VIRTUALTABLE
+
+/*
+** These two are used by the -malloc option to sqlite3_carray_bind()
+*/
+static void *testCarrayAlloc(int n){
+  u8 *pRet = (u8*)sqlite3_malloc(n+16);
+  if( pRet ){
+    pRet = &pRet[16];
+  }
+  return (void*)pRet;
+}
+static void testCarrayFree(void *p){
+  if( p ){
+    u8 *p2 = (u8*)p;
+    sqlite3_free(&p2[-16]);
+  }
+}
+
+static void delIntptr(void *p){
+  ckfree(p);
+}
+
+/*
+** bind_carray_intptr STMT IPARAM  INT0 INT1 INT2...
+*/
+static int SQLITE_TCLAPI bind_carray_intptr(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt = 0;
+  int iVar = 0;
+  int *aInt = 0;
+  int nInt = 0;
+  int ii = 0;
+  int rc = SQLITE_OK;
+
+  if( objc<3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "STMT");
+    return TCL_ERROR;
+  }
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &iVar) ) return TCL_ERROR;
+  nInt = objc - 3;
+
+  aInt = ckalloc((nInt+1) * sizeof(int));
+  for(ii=0; ii<nInt; ii++){
+    if( Tcl_GetIntFromObj(interp, objv[3+ii], &aInt[ii]) ){
+      ckfree(aInt);
+      return TCL_ERROR;
+    }
+  }
+
+  rc = sqlite3_bind_pointer(pStmt, iVar, (void*)aInt, "carray", delIntptr);
+  Tcl_SetResult(interp, (char *)t1ErrorName(rc), 0);
+
+  return TCL_OK;
+}
+
 /*
 ** sqlite3_carray_bind [options...] STMT NAME VALUE ...
 **
 ** Options:
+**    -malloc
 **    -transient
 **    -static
 **    -int32
@@ -4383,10 +4444,12 @@ static int SQLITE_TCLAPI test_carray_bind(
 ){
   sqlite3_stmt *pStmt;
   int eType = 0;   /* CARRAY_INT32 */
+  int mFlagsOverride = 0;
   int nData = 0;
   void *aData = 0;
   int isTransient = 0;
   int isStatic = 0;
+  int isMalloc = 0;               /* True to use custom xDel function */
   int idx;
   int i, j;
   int rc;
@@ -4432,6 +4495,10 @@ static int SQLITE_TCLAPI test_carray_bind(
       isStatic = 1;
       xDel = SQLITE_STATIC;
     }else
+    if( strcmp(z, "-malloc")==0 ){
+      isMalloc = 1;
+      xDel = testCarrayFree;
+    }else
     if( strcmp(z, "-int32")==0 ){
       eType = 0;  /* CARRAY_INT32 */
     }else
@@ -4447,6 +4514,12 @@ static int SQLITE_TCLAPI test_carray_bind(
     if( strcmp(z, "-blob")==0 ){
       eType = 4;  /* CARRAY_BLOB */
     }else
+    if( i<(objc-1) && strcmp(z, "-flags")==0 ){
+      i++;
+      if( Tcl_GetIntFromObj(interp, objv[i], &mFlagsOverride) ){
+        return TCL_ERROR;
+      }
+    }else
     if( strcmp(z, "--")==0 ){
       break;
     }else
@@ -4524,24 +4597,36 @@ static int SQLITE_TCLAPI test_carray_bind(
     }
     case 3: { /* TEXT */
       char **a = sqlite3_malloc( sizeof(char*)*nData );
-      if( a==0 ){ rc = SQLITE_NOMEM; goto carray_bind_done; }
-      for(j=0; j<nData; j++){
+      if( a==0 ){ 
+        rc = SQLITE_NOMEM;
+      }else{
+        memset(a, 0, sizeof(char*)*nData);
+      }
+      for(j=0; rc==SQLITE_OK && j<nData; j++){
         const char *v = Tcl_GetString(objv[i+j]);
-        a[j] = sqlite3_mprintf("%s", v);
+        if( v && strcmp(v, "NULL") ){
+          a[j] = sqlite3_mprintf("%s", v);
+          if( a[j]==0 ) rc = SQLITE_NOMEM;
+        }
       }
       aData = a;
       break;
     }
     case 4: { /* BLOB */
       struct iovec *a = sqlite3_malloc( sizeof(struct iovec)*nData );
-      if( a==0 ){ rc = SQLITE_NOMEM; goto carray_bind_done; }
-      for(j=0; j<nData; j++){
+      if( a==0 ){ 
+        rc = SQLITE_NOMEM; 
+      }else{
+        memset(a, 0, sizeof(struct iovec)*nData);
+      }
+      for(j=0; rc==SQLITE_OK && j<nData; j++){
         Tcl_Size n = 0;
         unsigned char *v = Tcl_GetByteArrayFromObj(objv[i+i], &n);
         a[j].iov_len = (size_t)n;
         a[j].iov_base = sqlite3_malloc64( n );
         if( a[j].iov_base==0 ){
           a[j].iov_len = 0;
+          rc = SQLITE_NOMEM;
         }else{
           memcpy(a[j].iov_base, v, n);
         }
@@ -4557,17 +4642,38 @@ static int SQLITE_TCLAPI test_carray_bind(
       break;
     }
   }
-  if( isStatic ){
-    aStaticData = aData;
-    nStaticData = nData;
-    eStaticType = eType;
+
+  if( rc==SQLITE_OK ){
+    if( isStatic ){
+      aStaticData = aData;
+      nStaticData = nData;
+      eStaticType = eType;
+    }
+    else if( isMalloc ){
+      int nByte = ((eType==0) ? sizeof(int) : sizeof(i64)) * nData;
+      void *aByte = testCarrayAlloc(nByte);
+      if( aByte==0 ){
+        sqlite3_free(aData);
+        rc = SQLITE_NOMEM;
+      }else{
+        memcpy(aByte, aData, nByte);
+        sqlite3_free(aData);
+        aData = aByte;
+        xDel = testCarrayFree;
+      }
+      assert( eType==0 || eType==1 || eType==2 );
+    }
+  }
+
+  if( rc==SQLITE_OK ){
+    if( mFlagsOverride==0 ) mFlagsOverride = eType;
+    rc = sqlite3_carray_bind(pStmt, idx, aData, nData, mFlagsOverride, xDel);
   }
-  rc = sqlite3_carray_bind(pStmt, idx, aData, nData, eType, xDel);
   if( isTransient ){
-    if( eType==3 ){
+    if( eType==3 && aData ){
       for(i=0; i<nData; i++) sqlite3_free(((char**)aData)[i]);
     }
-    if( eType==4 ){
+    if( eType==4 && aData ){
       for(i=0; i<nData; i++) sqlite3_free(((struct iovec*)aData)[i].iov_base);
     }
     sqlite3_free(aData);
@@ -8943,6 +9049,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
      { "sqlite3_bind_value_from_preupdate",test_bind_value_from_preupdate ,0 },
 #ifndef SQLITE_OMIT_VIRTUALTABLE
      { "sqlite3_carray_bind",           test_carray_bind   ,0 },
+     { "bind_carray_intptr",            bind_carray_intptr   ,0 },
 #endif
      { "sqlite3_bind_parameter_count",  test_bind_parameter_count, 0},
      { "sqlite3_bind_parameter_name",   test_bind_parameter_name,  0},
diff --git a/test/carray02.test b/test/carray02.test
new file mode 100644 (file)
index 0000000..07b5424
--- /dev/null
@@ -0,0 +1,165 @@
+# 2025-10-07
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# 
+# This file implements tests for CARRAY extension
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix carray02
+
+ifcapable !vtab {
+  finish_test
+  return
+}
+load_static_extension db carray
+
+proc run_stmt {stmt} {
+  set r {}
+  while {[sqlite3_step $stmt]=="SQLITE_ROW"} {
+    for {set i 0} {$i<[sqlite3_data_count $stmt]} {incr i} {
+      lappend r [sqlite3_column_text $stmt $i]
+    }
+  }
+  sqlite3_reset $stmt
+  return $r
+}
+
+# Bind some text values using sqlite3_carray_bind(). Including a NULL pointer.
+#
+set STMT [
+  sqlite3_prepare_v2 db "SELECT value, value IS NULL FROM carray(?)" -1 TAIL
+]
+do_test 1.0 {
+  sqlite3_carray_bind -transient -text $STMT 1 "abc" NULL "def"
+  run_stmt $STMT
+} {abc 0 {} 1 def 0}
+sqlite3_finalize $STMT
+
+# Pass bad values as the "flags" parameter to sqlite3_carray_bind().
+#
+set STMT [sqlite3_prepare_v2 db "SELECT value FROM carray(?)" -1 TAIL]
+do_test 2.0 {
+  list [catch {sqlite3_carray_bind -flags 5 -int32 $STMT 1 1 2 3 4} msg] $msg
+} {1 {SQL logic error}}
+do_test 2.1 {
+  list [catch {sqlite3_carray_bind -flags -1 -int32 $STMT 1 1 2 3 4} msg] $msg
+} {1 {SQL logic error}}
+sqlite3_finalize $STMT
+
+# In each of the following, carray(?) contains integer values 1 to 5, bound
+# using sqlite3_carray_bind().
+#
+foreach {tn sql res} {
+  1 { SELECT value FROM carray(?) WHERE value>2 } {3 4 5}
+  2 { 
+    WITH s(i) AS ( VALUES(1) UNION ALL VALUES(2) )
+    SELECT i, value FROM s, carray(?) WHERE i=value;
+  } {1 1 2 2}
+} {
+  do_test 2.2.$tn {
+    set STMT [sqlite3_prepare_v2 db $sql -1 TAIL]
+    sqlite3_carray_bind -int32 $STMT 1 1 2 3 4 5
+    set r [run_stmt $STMT]
+    sqlite3_finalize $STMT
+    set r
+  } $res
+}
+
+# In each of the following, ? is bound to an int array containing 1 to 5.
+# Bound using C code like:
+#
+#     sqlite3_bind_pointer(pStmt, 1, aInt, "carray", SQLITE_TRANSIENT)
+#
+foreach {tn sql res} {
+  1 { SELECT value FROM carray(?, 5) } {1 2 3 4 5}
+  2 { SELECT value FROM carray(?, 3, 'int32') } {1 2 3}
+  3 { SELECT value, pointer, count, ctype FROM carray(?, 5, 'int32') } 
+    {1 {} 5 int32 2 {} 5 int32 3 {} 5 int32 4 {} 5 int32 5 {} 5 int32}
+  4 { SELECT rowid, value FROM carray(?, 5, 'int32') } 
+    {1 1 2 2 3 3 4 4 5 5}
+} {
+  do_test 2.3.$tn {
+    set STMT [sqlite3_prepare_v2 db $sql -1 TAIL]
+    bind_carray_intptr $STMT 1   1 2 3 4 5
+    set r [run_stmt $STMT]
+    sqlite3_finalize $STMT
+    set r
+  } $res
+}
+
+# In each of the following. Both carray(?1) and carray(?2) contain integer
+# values 1 to 5. Bound by sqlite3_carray_bind().
+#
+foreach {tn sql res} {
+  1 { 
+    SELECT * FROM carray(?1) AS a, carray(?2) AS b 
+    WHERE a.value=b.value
+  } {1 1 2 2 3 3 4 4 5 5}
+
+  2 { 
+    SELECT * FROM carray(?1) AS a, carray(?2) AS b 
+    WHERE a.value=b.value AND a.value<3 AND b.value<3
+  } {1 1 2 2 3 3}
+
+  3 { 
+    SELECT * FROM carray(?1) AS a, carray(?2) AS b 
+    WHERE a.value<3 AND b.value<3 AND a.value=b.value
+  } {1 1 2 2 3 3}
+
+  4 { 
+    SELECT * FROM carray(?1) AS a, carray(?2, a.value) AS b 
+    WHERE a.value=b.value
+  } {1 1 2 2 3 3}
+
+} {
+  do_test 2.4.$tn {
+    set STMT [sqlite3_prepare_v2 db {
+      SELECT * FROM carray(?1) AS a, carray(?2) AS b WHERE a.value=b.value
+    } -1 TAIL]
+    sqlite3_carray_bind $STMT 1   1 2 3 4 5
+    sqlite3_carray_bind $STMT 2   1 2 3 4 5
+    set r [run_stmt $STMT]
+    sqlite3_finalize $STMT
+    set r
+  } {1 1 2 2 3 3 4 4 5 5}
+}
+
+# Test that not binding any pointer, or passing a value that is not a bound
+# pointer to carray() produces no rows of output.
+#
+do_execsql_test 3.0.0 {
+  SELECT * FROM carray
+} {}
+do_execsql_test 3.0.1 {
+  SELECT * FROM carray('0xFFFF', 5)
+} {}
+do_execsql_test 3.0.2 {
+  SELECT * FROM carray('0xFFFF')
+} {}
+do_execsql_test 3.0.3 {
+  CREATE TABLE t1(x);
+  INSERT INTO t1 VALUES('0xFFFF');
+  SELECT * FROM t1, carray WHERE carray.pointer = t1.x;
+} {}
+
+# Test passing an unknown datatype.
+#
+do_test 3.1 {
+  set STMT [sqlite3_prepare_v2 db {SELECT * FROM carray(?, 5, 'apples')} -1 T]
+  sqlite3_carray_bind $STMT 1   1 2 3 4 5
+  sqlite3_step $STMT
+  list [sqlite3_finalize $STMT] [sqlite3_errmsg db]
+} {SQLITE_ERROR {unknown datatype: 'apples'}}
+
+finish_test
+
+
diff --git a/test/carrayfault.test b/test/carrayfault.test
new file mode 100644 (file)
index 0000000..8561876
--- /dev/null
@@ -0,0 +1,96 @@
+# 2025-10-07
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix carrayfault
+
+
+# Check that OOM faults in sqlite3_carray_init() are handled.
+#
+do_faultsim_test 1 -faults oom* -prep {
+  sqlite3 db test.db
+} -body {
+  load_static_extension db carray
+} -test {
+  faultsim_test_result {0 {}} {1 {initialization of carray failed: }}
+}
+
+sqlite3 db test.db
+load_static_extension db carray
+
+do_execsql_test 2.0 {
+  CREATE TABLE t1(a);
+}
+
+set STMT [sqlite3_prepare_v2 db "SELECT value FROM carray(?)" -1 TAIL]
+
+# Test OOM faults in sqlite3_carray_bind() when binding an integer
+# array.
+#
+foreach {tn mem} {
+  1 -static
+  2 -transient
+  3 -malloc
+} {
+  do_faultsim_test 2.$tn -faults oom* -prep {
+  } -body {
+    sqlite3_carray_bind $::mem -int64 $::STMT 1   100 200 300 400
+  } -test {
+    faultsim_test_result {0 {}} {1 {out of memory}}
+  }
+}
+
+# Test OOM faults in sqlite3_carray_bind() when binding a text array.
+#
+do_faultsim_test 3 -faults oom* -prep {
+} -body {
+    sqlite3_carray_bind -transient -text $::STMT 1 "hello" "world"
+} -test {
+  faultsim_test_result {0 {}} {1 {initialization of carray failed: }}
+}
+
+sqlite3_finalize $STMT
+
+
+# Test OOM faults when running queries against carray.
+#
+do_faultsim_test 4 -faults oom* -prep {
+  set ::STMT [sqlite3_prepare_v2 db "SELECT value FROM carray(?)" -1 TAIL]
+  sqlite3_carray_bind -transient -text $::STMT 1 "hello" "world"
+} -body {
+    set myres [list]
+    while { "SQLITE_ROW"==[sqlite3_step $::STMT] } {
+      lappend myres [sqlite3_column_text $::STMT 1]
+    }
+    sqlite3_reset $::STMT
+} -test {
+  faultsim_test_result {0 SQLITE_OK} {0 SQLITE_NOMEM}
+  sqlite3_finalize $::STMT
+}
+
+# Test OOM faults when preparing queries against carray.
+#
+do_faultsim_test 5 -faults oom* -prep {
+  sqlite3 db test.db
+  load_static_extension db carray
+} -body {
+  execsql "SELECT value FROM carray(?)"
+} -test {
+  faultsim_test_result {0 {}}
+}
+
+
+
+sqlite3_carray_bind
+
+finish_test