]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the new "bind_fallback" method to the "sqlite3" object in the TCL
authordrh <drh@noemail.net>
Thu, 28 Feb 2019 17:29:19 +0000 (17:29 +0000)
committerdrh <drh@noemail.net>
Thu, 28 Feb 2019 17:29:19 +0000 (17:29 +0000)
interface.

FossilOrigin-Name: c7f70b6d96338dba201e005104e7f7148c1a8cd767ab05e35b44617c4c797bc5

manifest
manifest.uuid
src/tclsqlite.c
test/tclsqlite.test

index baa572685731226345b145fc5e0071b98ba9aae4..4f2d8561114428c04560f3d62e3d4e1f53bcf116 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C New\stest\scase\sloaded\sinto\stest/fuzzdata8.db.
-D 2019-02-28T14:09:14.893
+C Add\sthe\snew\s"bind_fallback"\smethod\sto\sthe\s"sqlite3"\sobject\sin\sthe\sTCL\ninterface.
+D 2019-02-28T17:29:19.212
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F Makefile.in 1ad7263f38329c0ecea543c80f30af839ee714ea77fc391bf1a3fbb919a5b6b5
@@ -524,7 +524,7 @@ F src/sqliteInt.h f253c4ec15e577a293a462e5049f8ea1d0c7a31819b3a88acdd24698df8f4d
 F src/sqliteLimit.h 1513bfb7b20378aa0041e7022d04acb73525de35b80b252f1b83fedb4de6a76b
 F src/status.c 46e7aec11f79dad50965a5ca5fa9de009f7d6bde08be2156f1538a0a296d4d0e
 F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34
-F src/tclsqlite.c de81c50e5112a8106da871b4d2dfef7748fe7625e148f85cc89ec7499b8e4de5
+F src/tclsqlite.c cfe7f93daf9d8787f65e099efb67d7cdfc2c35236dec5d3f6758520bd3519424
 F src/test1.c 353b066e7ec761c4c715c1c20b888e0e7a0b0c0eda7f68c110e032d63713cade
 F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5
 F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644
@@ -1366,7 +1366,7 @@ F test/tabfunc01.test 20e98ffe55f35d8d33fd834ca8bf9d4b637e560af8fcd00464b4154d90
 F test/table.test eb3463b7add9f16a5bb836badf118cf391b809d09fdccd1f79684600d07ec132
 F test/tableapi.test ecbcc29c4ab62c1912c3717c48ea5c5e59f7d64e4a91034e6148bd2b82f177f4
 F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930
-F test/tclsqlite.test 0037c0ca7fd3da08202a807f7b76590019841edb9f459fcfcf52aed7212bf853
+F test/tclsqlite.test 5a06962d8f18edf4703931f6b7dacd83678d02fa5c8ced9a7958c007ad58626a
 F test/tempdb.test 4cdaa23ddd8acb4d79cbb1b68ccdfd09b0537aaba909ca69a876157c2a2cbd08
 F test/tempdb2.test 2479226e4cb96f4c663eccd2d12c077cf6bda29ca5cc69a8a58a06127105dd62
 F test/tempfault.test 0c0d349c9a99bf5f374655742577f8712c647900
@@ -1805,7 +1805,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P d5250db6322103326f0d5782ba049996d9ce8784f9e53a3112fb6f09f888f1c3
-R 76ee1637810bdaf539a762a9c646979d
+P 00ae0c6c4815366bd2f36bc054b13bc7b568dd0a3caceddf0eba4db33f010ee4
+R 448ca78435be9ef8ac1ee1c7bdd459a0
 U drh
-Z ac7f7b28caabfd1523a4f739ee67d612
+Z 8dd6bd727b5289a2522f6d5168203921
index cba481dcd3580b81b2c5f60c1dba334c1ce32ec0..228336747290c6e22e1b14aae1ca59a71924ca76 100644 (file)
@@ -1 +1 @@
-00ae0c6c4815366bd2f36bc054b13bc7b568dd0a3caceddf0eba4db33f010ee4
\ No newline at end of file
+c7f70b6d96338dba201e005104e7f7148c1a8cd767ab05e35b44617c4c797bc5
\ No newline at end of file
index 1b0086c7d532265465f34f3b5a735522a763b2cd..a78d5676c738b3ddc2ba2c7857c181fd70fac8ce 100644 (file)
@@ -159,6 +159,7 @@ struct SqliteDb {
   char *zTraceV2;            /* The trace_v2 callback routine */
   char *zProfile;            /* The profile callback routine */
   char *zProgress;           /* The progress callback routine */
+  char *zBindFallback;       /* Callback to invoke on a binding miss */
   char *zAuth;               /* The authorization callback routine */
   int disableAuth;           /* Disable the authorizer if it exists */
   char *zNull;               /* Text to substitute for an SQL NULL value */
@@ -549,6 +550,9 @@ static void SQLITE_TCLAPI DbDeleteCmd(void *db){
   if( pDb->zProfile ){
     Tcl_Free(pDb->zProfile);
   }
+  if( pDb->zBindFallback ){
+    Tcl_Free(pDb->zBindFallback);
+  }
   if( pDb->zAuth ){
     Tcl_Free(pDb->zAuth);
   }
@@ -1301,6 +1305,8 @@ static int dbPrepareAndBind(
   int iParm = 0;                  /* Next free entry in apParm */
   char c;
   int i;
+  int needResultReset = 0;        /* Need to invoke Tcl_ResetResult() */
+  int rc = SQLITE_OK;             /* Value to return */
   Tcl_Interp *interp = pDb->interp;
 
   *ppPreStmt = 0;
@@ -1388,6 +1394,25 @@ static int dbPrepareAndBind(
     const char *zVar = sqlite3_bind_parameter_name(pStmt, i);
     if( zVar!=0 && (zVar[0]=='$' || zVar[0]==':' || zVar[0]=='@') ){
       Tcl_Obj *pVar = Tcl_GetVar2Ex(interp, &zVar[1], 0, 0);
+      if( pVar==0 && pDb->zBindFallback!=0 ){
+        Tcl_Obj *pCmd;
+        int rx;
+        pCmd = Tcl_NewStringObj(pDb->zBindFallback, -1);
+        Tcl_IncrRefCount(pCmd);
+        Tcl_ListObjAppendElement(interp, pCmd, Tcl_NewStringObj(zVar,-1));
+        if( needResultReset ) Tcl_ResetResult(interp);
+        needResultReset = 1;
+        rx = Tcl_EvalObjEx(interp, pCmd, TCL_EVAL_DIRECT);
+        Tcl_DecrRefCount(pCmd);
+        if( rx==TCL_OK ){
+          pVar = Tcl_GetObjResult(interp);
+        }else if( rx==TCL_ERROR ){
+          rc = TCL_ERROR;
+          break;
+        }else{
+          pVar = 0;
+        }
+      }
       if( pVar ){
         int n;
         u8 *data;
@@ -1423,12 +1448,14 @@ static int dbPrepareAndBind(
       }else{
         sqlite3_bind_null(pStmt, i);
       }
+      if( needResultReset ) Tcl_ResetResult(pDb->interp);
     }
   }
   pPreStmt->nParm = iParm;
   *ppPreStmt = pPreStmt;
+  if( needResultReset && rc==TCL_OK ) Tcl_ResetResult(pDb->interp);
 
-  return TCL_OK;
+  return rc;
 }
 
 /*
@@ -1887,35 +1914,36 @@ static int SQLITE_TCLAPI DbObjCmd(
   int choice;
   int rc = TCL_OK;
   static const char *DB_strs[] = {
-    "authorizer",             "backup",                "busy",
-    "cache",                  "changes",               "close",
-    "collate",                "collation_needed",      "commit_hook",
-    "complete",               "copy",                  "deserialize",
-    "enable_load_extension",  "errorcode",             "eval",
-    "exists",                 "function",              "incrblob",
-    "interrupt",              "last_insert_rowid",     "nullvalue",
-    "onecolumn",              "preupdate",             "profile",
-    "progress",               "rekey",                 "restore",
-    "rollback_hook",          "serialize",             "status",
-    "timeout",                "total_changes",         "trace",
-    "trace_v2",               "transaction",           "unlock_notify",
-    "update_hook",            "version",               "wal_hook",
-    0                        
+    "authorizer",             "backup",                "bind_fallback",
+    "busy",                   "cache",                 "changes",
+    "close",                  "collate",               "collation_needed",
+    "commit_hook",            "complete",              "copy",
+    "deserialize",            "enable_load_extension", "errorcode",
+    "eval",                   "exists",                "function",
+    "incrblob",               "interrupt",             "last_insert_rowid",
+    "nullvalue",              "onecolumn",             "preupdate",
+    "profile",                "progress",              "rekey",
+    "restore",                "rollback_hook",         "serialize",
+    "status",                 "timeout",               "total_changes",
+    "trace",                  "trace_v2",              "transaction",
+    "unlock_notify",          "update_hook",           "version",
+    "wal_hook",               0                        
   };
   enum DB_enum {
-    DB_AUTHORIZER,            DB_BACKUP,               DB_BUSY,
-    DB_CACHE,                 DB_CHANGES,              DB_CLOSE,
-    DB_COLLATE,               DB_COLLATION_NEEDED,     DB_COMMIT_HOOK,
-    DB_COMPLETE,              DB_COPY,                 DB_DESERIALIZE,
-    DB_ENABLE_LOAD_EXTENSION, DB_ERRORCODE,            DB_EVAL,
-    DB_EXISTS,                DB_FUNCTION,             DB_INCRBLOB,
-    DB_INTERRUPT,             DB_LAST_INSERT_ROWID,    DB_NULLVALUE,
-    DB_ONECOLUMN,             DB_PREUPDATE,            DB_PROFILE,
-    DB_PROGRESS,              DB_REKEY,                DB_RESTORE,
-    DB_ROLLBACK_HOOK,         DB_SERIALIZE,            DB_STATUS,
-    DB_TIMEOUT,               DB_TOTAL_CHANGES,        DB_TRACE,
-    DB_TRACE_V2,              DB_TRANSACTION,          DB_UNLOCK_NOTIFY,
-    DB_UPDATE_HOOK,           DB_VERSION,              DB_WAL_HOOK
+    DB_AUTHORIZER,            DB_BACKUP,               DB_BIND_FALLBACK,
+    DB_BUSY,                  DB_CACHE,                DB_CHANGES,
+    DB_CLOSE,                 DB_COLLATE,              DB_COLLATION_NEEDED,
+    DB_COMMIT_HOOK,           DB_COMPLETE,             DB_COPY,
+    DB_DESERIALIZE,           DB_ENABLE_LOAD_EXTENSION,DB_ERRORCODE,
+    DB_EVAL,                  DB_EXISTS,               DB_FUNCTION,
+    DB_INCRBLOB,              DB_INTERRUPT,            DB_LAST_INSERT_ROWID,
+    DB_NULLVALUE,             DB_ONECOLUMN,            DB_PREUPDATE,
+    DB_PROFILE,               DB_PROGRESS,             DB_REKEY,
+    DB_RESTORE,               DB_ROLLBACK_HOOK,        DB_SERIALIZE,
+    DB_STATUS,                DB_TIMEOUT,              DB_TOTAL_CHANGES,
+    DB_TRACE,                 DB_TRACE_V2,             DB_TRANSACTION,
+    DB_UNLOCK_NOTIFY,         DB_UPDATE_HOOK,          DB_VERSION,
+    DB_WAL_HOOK             
   };
   /* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */
 
@@ -2037,6 +2065,49 @@ static int SQLITE_TCLAPI DbObjCmd(
     break;
   }
 
+  /*    $db bind_fallback ?CALLBACK?
+  **
+  ** When resolving bind parameters in an SQL statement, if the parameter
+  ** cannot be associated with a TCL variable then invoke CALLBACK with a
+  ** single argument that is the name of the parameter and use the return
+  ** value of the CALLBACK as the binding.  If CALLBACK returns something
+  ** other than TCL_OK or TCL_ERROR then bind a NULL.
+  **
+  ** If CALLBACK is an empty string, then revert to the default behavior 
+  ** which is to set the binding to NULL.
+  **
+  ** If CALLBACK returns an error, that causes the statement execution to
+  ** abort.  Hence, to configure a connection so that it throws an error
+  ** on an attempt to bind an unknown variable, do something like this:
+  **
+  **     proc bind_error {name} {error "no such variable: $name"}
+  **     db bind_fallback bind_error
+  */
+  case DB_BIND_FALLBACK: {
+    if( objc>3 ){
+      Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?");
+      return TCL_ERROR;
+    }else if( objc==2 ){
+      if( pDb->zBindFallback ){
+        Tcl_AppendResult(interp, pDb->zBindFallback, (char*)0);
+      }
+    }else{
+      char *zCallback;
+      int len;
+      if( pDb->zBindFallback ){
+        Tcl_Free(pDb->zBindFallback);
+      }
+      zCallback = Tcl_GetStringFromObj(objv[2], &len);
+      if( zCallback && len>0 ){
+        pDb->zBindFallback = Tcl_Alloc( len + 1 );
+        memcpy(pDb->zBindFallback, zCallback, len+1);
+      }else{
+        pDb->zBindFallback = 0;
+      }
+    }
+    break;
+  }
+
   /*    $db busy ?CALLBACK?
   **
   ** Invoke the given callback if an SQL statement attempts to open
index 4a674a8f2426aabf701c7f2cbacbe65f805fa4b0..319737426f9f224538169f12e3324086f471a6a3 100644 (file)
@@ -42,7 +42,7 @@ do_test tcl-1.1.1 {
 do_test tcl-1.2 {
   set v [catch {db bogus} msg]
   lappend v $msg
-} {1 {bad option "bogus": must be authorizer, backup, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, copy, deserialize, enable_load_extension, errorcode, eval, exists, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, preupdate, profile, progress, rekey, restore, rollback_hook, serialize, status, timeout, total_changes, trace, trace_v2, transaction, unlock_notify, update_hook, version, or wal_hook}}
+} {1 {bad option "bogus": must be authorizer, backup, bind_fallback, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, copy, deserialize, enable_load_extension, errorcode, eval, exists, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, preupdate, profile, progress, rekey, restore, rollback_hook, serialize, status, timeout, total_changes, trace, trace_v2, transaction, unlock_notify, update_hook, version, or wal_hook}}
 do_test tcl-1.2.1 {
   set v [catch {db cache bogus} msg]
   lappend v $msg
@@ -791,5 +791,60 @@ do_test 17.6.3 {
   list [catch { db function xyz -n object ret } msg] $msg
 } {1 {bad option "-n": must be -argcount, -deterministic or -returntype}}
 
-finish_test
+# 2019-02-28: The "bind_fallback" command.
+#
+do_test 18.100 {
+  unset -nocomplain bindings abc def ghi jkl mno e01 e02
+  set bindings(abc) [expr {1+2}]
+  set bindings(def) {hello}
+  set bindings(ghi) [expr {3.1415926*1.0}]
+  proc bind_callback {nm} {
+    global bindings
+    set n2 [string range $nm 1 end]
+    if {[info exists bindings($n2)]} {
+      return $bindings($n2)
+    }
+    if {[string match e* $n2]} {
+      error "no such variable: $nm"
+    }
+    return -code return {}
+  }
+  db bind_fallback bind_callback
+  db eval {SELECT $abc, typeof($abc), $def, typeof($def), $ghi, typeof($ghi)}
+} {3 integer hello text 3.1415926 real}
+do_test 18.110 {
+  db eval {SELECT quote(@def), typeof(@def)}
+} {X'68656C6C6F' blob}
+do_execsql_test 18.120 {
+  SELECT typeof($mno);
+} {null}
+do_catchsql_test 18.130 {
+  SELECT $e01;
+} {1 {no such variable: $e01}}
+do_test 18.140 {
+  db bind_fallback
+} {bind_callback}
+do_test 18.200 {
+  db bind_fallback {}
+  db eval {SELECT $abc, typeof($abc), $def, typeof($def), $ghi, typeof($ghi)}
+} {{} null {} null {} null}
+do_test 18.300 {
+  unset -nocomplain bindings
+  proc bind_callback {nm} {lappend ::bindings $nm}
+  db bind_fallback bind_callback
+  db eval {SELECT $abc, @def, $ghi(123), :mno}
+  set bindings
+} {{$abc} @def {$ghi(123)} :mno}
+do_test 18.900 {
+  set rc [catch {db bind_fallback a b} msg]
+  lappend rc $msg
+} {1 {wrong # args: should be "db bind_fallback ?CALLBACK?"}}
+do_test 18.910 {
+  db bind_fallback bind_fallback_does_not_exist
+} {}
+do_catchsql_test 19.911 {
+  SELECT $abc, typeof($abc), $def, typeof($def), $ghi, typeof($ghi);
+} {1 {invalid command name "bind_fallback_does_not_exist"}}
+db bind_fallback {}
 
+finish_test