From: drh <> Date: Fri, 28 Nov 2025 13:04:31 +0000 (+0000) Subject: A better fix for the issue with RETURNING triggers on eponymous X-Git-Tag: version-3.51.1~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=158436f53db8000464f26ca704e899bdf3fff452;p=thirdparty%2Fsqlite.git A better fix for the issue with RETURNING triggers on eponymous virtual tables and SQLITE_SCHEMA errors. FossilOrigin-Name: 40ddaca3fb752425c26570365a9f31820786d21d043c1c0a4b49746ff9bc0782 --- diff --git a/manifest b/manifest index a0e4f8759b..f766cd67dc 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sa\sbuffer\soverread\sin\sfts5\sthat\smight\soccur\swhile\sprocessing\sa\scorrupt\sdb. -D 2025-11-28T12:36:04.399 +C A\sbetter\sfix\sfor\sthe\sissue\swith\sRETURNING\striggers\son\seponymous\nvirtual\stables\sand\sSQLITE_SCHEMA\serrors. +D 2025-11-28T13:04:31.843 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -679,7 +679,7 @@ F src/btree.c cb5b8ceb9baa02a63a2f83dec09c4153e1cfbdf9c2adef5c62c26d2160eeb067 F src/btree.h e823c46d87f63d904d735a24b76146d19f51f04445ea561f71cc3382fd1307f0 F src/btreeInt.h 9c0f9ea5c9b5f4dcaea18111d43efe95f2ac276cd86d770dce10fd99ccc93886 F src/build.c 611e07299d72ff04bbcb9e7109183467e30925d203c3e121ef9bb3cf6876289b -F src/callback.c afa59adfaa483f668260ce69f740c8273dee3e6fee9106846e0499ebdd1ac076 +F src/callback.c 3605bbf02bd7ed46c79cd48346db4a32fc51d67624400539c0532f4eead804ad F src/carray.c ff6081a31878fc34df8fa1052a9cbf17ddc22652544dcb3e2326886ed1053b55 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e F src/date.c e19e0cfff9a41bfdd884c655755f6f00bca4c1a22272b56e0dd6667b7ea893a2 @@ -691,8 +691,8 @@ F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 F src/fkey.c 928ed2517e8732113d2b9821aa37af639688d752f4ea9ac6e0e393d713eeb76f F src/func.c 0b802107498048d3dcac0b757720bcb8506507ce02159e213ab8161458eb293b F src/global.c a19e4b1ca1335f560e9560e590fc13081e21f670643367f99cb9e8f9dc7d615b -F src/hash.c dff10fa89d4a8280b764df9599b29342ea98cba2b868dccb5dfb3c419841a3f0 -F src/hash.h c5f4a02aaf1dca835a68b51c8b906af265c6ca3d8d53d255e0453f0abce561be +F src/hash.c 03c8c0f4be9e8bcb6de65aa26d34a61d48a9430747084a69f9469fbb00ea52ca +F src/hash.h 46b92795a95bfefb210f52f0c316e9d7cdbcdd7e7fcfb0d8be796d3a5767cddf F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c dfd311b0ac2d4f6359e62013db67799757f4d2cc56cca5c10f4888acfbbfa3fd @@ -756,7 +756,7 @@ F src/test8.c 206d8f3cc73950d252906656e2646b5de0d580b07187b635fcb3edd8c2c5fbc0 F src/test9.c df9ddc7db6ef1b8cf745866ee229090779728bcbe660c7f297d3127ab21d92af F src/test_autoext.c 14d4bbd3d0bd1eec0f6d16b29e28cf1e2d0b020d454835f0721a5f68121ac10f F src/test_backup.c a2bfd90d2ff2511b8635507bdb30fa9b605ade19c16b533066cae3077f5bdb72 -F src/test_bestindex.c 3401bee51665cbf7f9ed2552b5795452a8b86365e4c9ece745b54155a55670c6 +F src/test_bestindex.c a9428931bec06de830b2630f57a7b1f2711761269f04df62b7aa1affcbce15bb F src/test_blob.c 77b994e17f2c87055f44fd96c9a206c5a7155bae2cda2769af60c2f3582f962c F src/test_btree.c 28283787d32b8fa953eb77412ad0de2c9895260e4e5bd5a94b3c7411664f90d5 F src/test_config.c 18aa596d37de1d5968c439fd58ebf38bc4d9c9d1db63621504e241fde375cecd @@ -812,7 +812,7 @@ F src/vdbemem.c 48e562ff27e6386eb8613207ac27d3d98c1f67fdc4775a1ab13759d2c2a1c021 F src/vdbesort.c b69220f4ea9ffea5fdef34d968c60305444eea909252a81933b54c296d9cca70 F src/vdbetrace.c 49e689f751505839742f4a243a1a566e57d5c9eaf0d33bbaa26e2de3febf7b41 F src/vdbevtab.c fc46b9cbd759dc013f0b3724549cc0d71379183c667df3a5988f7e2f1bd485f3 -F src/vtab.c 828221bdbeaaa6d62126ee6d07fd4ec0d09dcaea846f87ad01944d8b7e548859 +F src/vtab.c 5437ce986db2f70e639ce8a3fe68dcdfe64b0f1abb14eaebecdabd5e0766cc68 F src/vxworks.h 9d18819c5235b49c2340a8a4d48195ec5d5afb637b152406de95a9436beeaeab F src/wal.c 505a98fbc599a971d92cb90371cf54546c404cd61e04fd093e7b0c8ff978f9b6 F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452 @@ -914,7 +914,7 @@ F test/bestindexA.test e1b5def6b190797cacf008e6815ffb78fb30261999030d60a728d572e F test/bestindexB.test 328b97b69cd1a20928d5997f9ecb04d2e00f1d18e19ab27f9e9adb44d7bc51ce F test/bestindexC.test 95b4a527b1a5d07951d731604a6d4cf7e5a806b39cea0e7819d4c9667e11c3fc F test/bestindexD.test 6a8f6f84990bcf17dfa59652a1f935beddb7afd96f8302830fbc86b0a13df3c3 -F test/bestindexE.test f0c7105d1e7facaa8f5e6c498849cc6dcadd533b35ea9e5e1176e54a9a2d70f1 +F test/bestindexE.test 297f3ea8500a8f3c17d6f78e55bdfee089064c6144ee84a110bd005a03338f49 F test/between.test e7587149796101cbe8d5f8abae8d2a7b87f04d8226610aa1091615005dcf4d54 F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59 F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc @@ -1527,7 +1527,7 @@ F test/reservebytes.test 6163640b5a5120c0dee6591481e673a0fa0bf0d12d4da7513bad692 F test/resetdb.test 54c06f18bc832ac6d6319e5ab23d5c8dd49fdbeec7c696d791682a8006bd5fc3 F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb F test/returning1.test cd32517148948859db214dd814354597dd40e7489259590fac1a4f7bf44deb97 -F test/returningfault.test ae4c4b5e8745813287a359d9ccdb9d5c883c2e68afb18fb0767937d5de5692a4 +F test/returningfault.test 5f9649d05680357ab077fa6bad574a3eb3f6e4858a6880b25171be516a4efcda F test/rollback.test 952c4d805bca96adc2be76f621ea22115fe40b330015af36fcc8028c8547fcee F test/rollback2.test 3f3a4e20401825017df7e7671e9f31b6de5fae5620c2b9b49917f52f8c160a8f F test/rollbackfault.test 0e646aeab8840c399cfbfa43daab46fd609cf04a @@ -2171,9 +2171,11 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 0c836387518dee719eaac8992c63da955630ac073a5e4ba4d887160dff42b898 -Q +8b0cbc18be3c6f2501b102757af6be98c48044a296104cca7bce822ac2304515 -R 1c559fa01e2045a73634bbe221e92967 +P 712e31c59254f82e62a19e41d3b6ac15391dd6e9df966cacadfa9ae79415b243 +Q +6a9fdde109865b23888f099d066721404e8b853f3dacd55ce08c8bbda2491ec3 +Q +bf399992cb98e5d5f002a90b521328d5c2f113ebab8601653452d78222077bde +Q +dc569288dd63754269e14be7a9937c882531685a3e9caec25f86ec8c01eb9583 +R e6faec25512909b375a6de433bcc999d U drh -Z a5ce86195e7ae4027ea13f269316d3e3 +Z 6bdf6810679e96e0bba6c4bccb37d033 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 3b055bb81f..cfd00cb0f3 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -712e31c59254f82e62a19e41d3b6ac15391dd6e9df966cacadfa9ae79415b243 +40ddaca3fb752425c26570365a9f31820786d21d043c1c0a4b49746ff9bc0782 diff --git a/src/callback.c b/src/callback.c index 9ea3616b38..e6418097f6 100644 --- a/src/callback.c +++ b/src/callback.c @@ -504,17 +504,10 @@ void sqlite3SchemaClear(void *p){ temp2 = pSchema->trigHash; sqlite3HashInit(&pSchema->trigHash); sqlite3HashClear(&pSchema->idxHash); - for(pElem=sqliteHashFirst(&temp2); pElem; ){ - HashElem *pNext = sqliteHashNext(pElem); - Trigger *pTrig = (Trigger*)sqliteHashData(pElem); - if( pTrig->bReturning ){ - /* Do not remove RETURNING triggers from the temp-triggers hash */ - sqlite3HashTransfer(&pSchema->trigHash, &temp2, pElem); - }else{ - sqlite3DeleteTrigger(&xdb, pTrig); - } - pElem = pNext; + for(pElem=sqliteHashFirst(&temp2); pElem; pElem=sqliteHashNext(pElem)){ + sqlite3DeleteTrigger(&xdb, (Trigger*)sqliteHashData(pElem)); } + sqlite3HashClear(&temp2); sqlite3HashInit(&pSchema->tblHash); for(pElem=sqliteHashFirst(&temp1); pElem; pElem=sqliteHashNext(pElem)){ diff --git a/src/hash.c b/src/hash.c index 670df16d81..35df65808e 100644 --- a/src/hash.c +++ b/src/hash.c @@ -271,25 +271,3 @@ void *sqlite3HashInsert(Hash *pH, const char *pKey, void *data){ return 0; } -/* -** Parameter pElem is currently part of hash table pFrom. Add it to hash -** table pTo. -** -** This procedure corrupts hash table pFrom. Specifically, it removes element -** pElem from the list of all elements in the hash table, but may leave -** a pointer to it in one of the hash buckets of pFrom. This is not a problem -** because this function is only called if hash table pFrom will be cleared -** before any further lookups or inserts are attempted. -*/ -void sqlite3HashTransfer(Hash *pTo, Hash *pFrom, HashElem *pElem){ - if( pElem->prev ){ - pElem->prev->next = pElem->next; - }else{ - assert( pFrom->first==pElem ); - pFrom->first = pElem->next; - } - pTo->count++; - insertElement(pTo, pTo->ht ? &pTo->ht[pElem->h % pTo->htsize] : 0, pElem); -} - - diff --git a/src/hash.h b/src/hash.h index 4cb1fbe47b..cff65d6e50 100644 --- a/src/hash.h +++ b/src/hash.h @@ -70,7 +70,6 @@ void sqlite3HashInit(Hash*); void *sqlite3HashInsert(Hash*, const char *pKey, void *pData); void *sqlite3HashFind(const Hash*, const char *pKey); void sqlite3HashClear(Hash*); -void sqlite3HashTransfer(Hash*, Hash*, HashElem *pElem); /* ** Macros for looping over all elements of a hash table. The idiom is diff --git a/src/test_bestindex.c b/src/test_bestindex.c index 2f9203d85e..f6b5db0fbe 100644 --- a/src/test_bestindex.c +++ b/src/test_bestindex.c @@ -101,6 +101,7 @@ typedef struct tcl_vtab tcl_vtab; typedef struct tcl_cursor tcl_cursor; typedef struct TestFindFunction TestFindFunction; +typedef struct TestVtabContext TestVtabContext; /* ** A fs virtual-table object @@ -125,6 +126,10 @@ struct TestFindFunction { TestFindFunction *pNext; }; +struct TestVtabContext { + Tcl_Interp *interp; + Tcl_Obj *pDefault; +}; /* ** Dequote string z in place. @@ -178,25 +183,33 @@ static int tclConnect( sqlite3_vtab **ppVtab, char **pzErr ){ - Tcl_Interp *interp = (Tcl_Interp*)pAux; + TestVtabContext *pCtx = (TestVtabContext*)pAux; + Tcl_Interp *interp = pCtx->interp; tcl_vtab *pTab = 0; char *zCmd = 0; Tcl_Obj *pScript = 0; int rc = SQLITE_OK; - if( argc!=4 ){ + if( argc!=4 && (argc!=3 || pCtx->pDefault==0) ){ *pzErr = sqlite3_mprintf("wrong number of arguments"); return SQLITE_ERROR; } - zCmd = sqlite3_malloc64(strlen(argv[3])+1); + if( argc==4 ){ + zCmd = sqlite3_malloc64(strlen(argv[3])+1); + } pTab = (tcl_vtab*)sqlite3_malloc64(sizeof(tcl_vtab)); - if( zCmd && pTab ){ - memcpy(zCmd, argv[3], strlen(argv[3])+1); - tclDequote(zCmd); + if( (zCmd || argc==3) && pTab ){ memset(pTab, 0, sizeof(tcl_vtab)); - pTab->pCmd = Tcl_NewStringObj(zCmd, -1); + if( zCmd ){ + memcpy(zCmd, argv[3], strlen(argv[3])+1); + tclDequote(zCmd); + pTab->pCmd = Tcl_NewStringObj(zCmd, -1); + }else{ + pTab->pCmd = Tcl_DuplicateObj(pCtx->pDefault); + } + pTab->interp = interp; pTab->db = db; Tcl_IncrRefCount(pTab->pCmd); @@ -208,7 +221,11 @@ static int tclConnect( rc = Tcl_EvalObjEx(interp, pScript, TCL_EVAL_GLOBAL); if( rc!=TCL_OK ){ *pzErr = sqlite3_mprintf("%s", Tcl_GetStringResult(interp)); - rc = SQLITE_ERROR; + if( sqlite3_stricmp(*pzErr, "database schema has changed")==0 ){ + rc = SQLITE_SCHEMA; + }else{ + rc = SQLITE_ERROR; + } }else{ rc = sqlite3_declare_vtab(db, Tcl_GetStringResult(interp)); if( rc!=SQLITE_OK ){ @@ -802,6 +819,39 @@ static int tclFindFunction( return iRet; } +static int tclUpdate( + sqlite3_vtab *tab, + int nArg, + sqlite3_value **apVal, + sqlite3_int64 *piRowid +){ + tcl_vtab *pTab = (tcl_vtab*)tab; + Tcl_Interp *interp = pTab->interp; + Tcl_Obj *pEval = Tcl_DuplicateObj(pTab->pCmd); + Tcl_Obj *pRes = 0; + int rc = TCL_OK; + + Tcl_IncrRefCount(pEval); + Tcl_ListObjAppendElement(interp, pEval, Tcl_NewStringObj("xUpdate",-1)); + + rc = Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL); + Tcl_DecrRefCount(pEval); + + if( rc==TCL_OK ){ + Tcl_Obj *pRes = Tcl_GetObjResult(interp); + Tcl_WideInt v; + rc = Tcl_GetWideIntFromObj(interp, pRes, &v); + *piRowid = (sqlite3_int64)v; + } + + if( rc!=TCL_OK ){ + tab->zErrMsg = sqlite3_mprintf("%s", Tcl_GetStringResult(pTab->interp)); + return rc; + } + + return SQLITE_OK; +} + /* ** A virtual table module that provides read-only access to a ** Tcl global variable namespace. @@ -833,12 +883,47 @@ static sqlite3_module tclModule = { 0, /* xShadowName */ 0 /* xIntegrity */ }; +static sqlite3_module tclModuleUpdate = { + 0, /* iVersion */ + tclConnect, + tclConnect, + tclBestIndex, + tclDisconnect, + tclDisconnect, + tclOpen, /* xOpen - open a cursor */ + tclClose, /* xClose - close a cursor */ + tclFilter, /* xFilter - configure scan constraints */ + tclNext, /* xNext - advance a cursor */ + tclEof, /* xEof - check for end of scan */ + tclColumn, /* xColumn - read data */ + tclRowid, /* xRowid - read data */ + tclUpdate, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + tclFindFunction, /* xFindFunction */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadowName */ + 0 /* xIntegrity */ +}; /* ** Decode a pointer to an sqlite3 object. */ extern int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb); +static void delTestVtabCtx(void *p){ + TestVtabContext *pCtx = (TestVtabContext*)p; + if( pCtx->pDefault ){ + Tcl_DecrRefCount(pCtx->pDefault); + } + ckfree(pCtx); +} + /* ** Register the echo virtual table module. */ @@ -849,13 +934,25 @@ static int SQLITE_TCLAPI register_tcl_module( Tcl_Obj *CONST objv[] /* Command arguments */ ){ sqlite3 *db; - if( objc!=2 ){ - Tcl_WrongNumArgs(interp, 1, objv, "DB"); + if( objc!=2 && objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB ?DEFAULT-CMD?"); return TCL_ERROR; } if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; #ifndef SQLITE_OMIT_VIRTUALTABLE - sqlite3_create_module(db, "tcl", &tclModule, (void *)interp); + { + sqlite3_module *pMod = &tclModule; + TestVtabContext *pCtx = (TestVtabContext*)ckalloc(sizeof(TestVtabContext)); + pCtx->interp = interp; + pCtx->pDefault = 0; + if( objc==3 ){ + pCtx->pDefault = objv[2]; + Tcl_IncrRefCount(pCtx->pDefault); + } + + if( objc==3 ){ pMod = &tclModuleUpdate; } + sqlite3_create_module_v2(db, "tcl", pMod, (void*)pCtx, delTestVtabCtx); + } #endif return TCL_OK; } diff --git a/src/vtab.c b/src/vtab.c index e40f60873a..ed4b0afaf4 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -1279,9 +1279,12 @@ int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){ addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName)); addModuleArgument(pParse, pTab, 0); addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName)); + db->nSchemaLock++; rc = vtabCallConstructor(db, pTab, pMod, pModule->xConnect, &zErr); + db->nSchemaLock--; if( rc ){ sqlite3ErrorMsg(pParse, "%s", zErr); + pParse->rc = rc; sqlite3DbFree(db, zErr); sqlite3VtabEponymousTableClear(db, pMod); } diff --git a/test/bestindexE.test b/test/bestindexE.test index 48397fc7b3..d225d74aef 100644 --- a/test/bestindexE.test +++ b/test/bestindexE.test @@ -13,7 +13,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl -set testprefix bestindex1 +set testprefix bestindexE ifcapable !vtab { finish_test @@ -124,7 +124,68 @@ do_bestindex_test 2.2 { {Customer: oid=?} } +#-------------------------------------------------------------------------- +reset_db +register_tcl_module db eponymous_cmd +proc eponymous_cmd {method args} { + switch -- $method { + xConnect { + db eval { SELECT * FROM sqlite_schema } + return "CREATE TABLE t1 (a, b)" + } + xBestIndex { + return "idxnum 555" + } + + xFilter { + return [list sql {SELECT 123, 'A', 'B'}] + } + + xUpdate { + return 123 + } + + } + + return {} +} + +do_execsql_test 3.1.0 { + PRAGMA table_info = tcl +} { + 0 a {} 0 {} 0 1 b {} 0 {} 0 +} +do_execsql_test 3.1.1 { + SELECT rowid, * FROM tcl +} {123 A B} +do_execsql_test 3.1.2 { + INSERT INTO tcl VALUES('i', 'ii') RETURNING *; +} {i ii} +do_test 3.1.3 { + db last_insert_rowid +} {123} + +do_execsql_test 3.1.4 { + CREATE TABLE x1(x); +} + +db close +sqlite3 db test.db +register_tcl_module db eponymous_cmd +sqlite3 db2 test.db + +# Load the schema into connection [db] +do_execsql_test 3.2.1 { SELECT * FROM x1 } + +# Modify the schema on disk +do_execsql_test -db db2 3.2.2 { CREATE TABLE x2(x) } + +# Insert with RETURNING trigger on eponymous table. +do_execsql_test 3.2.3 { + INSERT INTO tcl VALUES('i', 'ii') RETURNING *; +} {i ii} finish_test + diff --git a/test/returningfault.test b/test/returningfault.test index 8bf6fbfe06..b0177e6dc8 100644 --- a/test/returningfault.test +++ b/test/returningfault.test @@ -14,13 +14,15 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl source $testdir/malloc_common.tcl +set ::testprefix returningfault + do_execsql_test 1.0 { CREATE TABLE t1 (b); } {} faultsim_save_and_close -do_faultsim_test pagerfault-1 -faults oom-t* -prep { +do_faultsim_test 1 -faults oom-t* -prep { faultsim_restore_and_reopen } -body { execsql { @@ -32,5 +34,52 @@ do_faultsim_test pagerfault-1 -faults oom-t* -prep { faultsim_test_result {1 {sub-select returns 5 columns - expected 1}} } +ifcapable vtab { + reset_db + do_execsql_test 2.0 { + CREATE TABLE t1(x); + } + + proc eponymous_cmd {method args} { + switch -- $method { + xConnect { + db eval { SELECT * FROM sqlite_schema } + return "CREATE TABLE t1 (a, b)" + } + + xBestIndex { + return "idxnum 555" + } + + xFilter { + return [list sql {SELECT 123, 'A', 'B'}] + } + + xUpdate { + return 123 + } + + } + + return {} + } + + faultsim_save_and_close + + do_faultsim_test 2 -faults oom* -prep { + faultsim_restore_and_reopen + register_tcl_module db eponymous_cmd + db eval { SELECT * FROM t1 } + sqlite3 db2 test.db + db2 eval { CREATE TABLE t2(y) } + db2 close + } -body { + db eval { + INSERT INTO tcl VALUES('hello', 'world') RETURNING * + } + } -test { + faultsim_test_result {0 {hello world}} {1 {vtable constructor failed: tcl}} + } +} finish_test