1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +03:00

tcl extension: UDFs may now 'break' to return an SQL NULL. Add the (eval -asdict) flag to use a dict, instead of an array, for the eval row data.

FossilOrigin-Name: 413a626b5c7902c1810142536c36e4ea8ee7c616ea82dfe1114199f9319091f7
This commit is contained in:
stephan
2025-05-31 11:02:06 +00:00
parent b504aab848
commit 10206572b6
4 changed files with 213 additions and 94 deletions

View File

@ -1,5 +1,5 @@
C Add\ssome\smissing\sUNUSED_PARAMETER()\sannotations\sto\ssquelch\sdownstream\sbuild\swarnings\swhen\susing\s-Wextra\s-pedantic. C tcl\sextension:\sUDFs\smay\snow\s'break'\sto\sreturn\san\sSQL\sNULL.\sAdd\sthe\s(eval\s-asdict)\sflag\sto\suse\sa\sdict,\sinstead\sof\san\sarray,\sfor\sthe\seval\srow\sdata.
D 2025-05-31T09:44:00.684 D 2025-05-31T11:02:06.099
F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@ -794,7 +794,7 @@ F src/sqliteInt.h bb9137b860b2416b12788f09b32384ceab96b720aae07a6e9afacc545e4361
F src/sqliteLimit.h 6d817c28a8f19af95e6f4921933b7fbbca48a962bce0eb0ec81e8bb3ef38e68b F src/sqliteLimit.h 6d817c28a8f19af95e6f4921933b7fbbca48a962bce0eb0ec81e8bb3ef38e68b
F src/status.c 0e72e4f6be6ccfde2488eb63210297e75f569f3ce9920f6c3d77590ec6ce5ffd F src/status.c 0e72e4f6be6ccfde2488eb63210297e75f569f3ce9920f6c3d77590ec6ce5ffd
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
F src/tclsqlite.c d0e63ffe7944dd223bf62066d9f982cbee1978811c7fbfd889f4ba9c5baed3d1 F src/tclsqlite.c 3c604c49e6cf4211960a9ddb9505280fd22cde32175f40884c641c0f5a286036
F src/tclsqlite.h 65e2c761446e1c9fa0342b7d2612a703483643c8b6a316d12a65b745a4727395 F src/tclsqlite.h 65e2c761446e1c9fa0342b7d2612a703483643c8b6a316d12a65b745a4727395
F src/test1.c 9b54135e5f1352f06b1d23d7c183f124c1f33de6ea8997cd801f0f215c43591d F src/test1.c 9b54135e5f1352f06b1d23d7c183f124c1f33de6ea8997cd801f0f215c43591d
F src/test2.c 62f0830958f9075692c29c6de51b495ae8969e1bef85f239ffcd9ba5fb44a5ff F src/test2.c 62f0830958f9075692c29c6de51b495ae8969e1bef85f239ffcd9ba5fb44a5ff
@ -1725,7 +1725,7 @@ F test/tabfunc01.test 8a484fe8b19fc24844f72ca1ceb7c9ae8c9a6bca000a5c6ccab5d89f5c
F test/table.test e87294bf1c80bfd7792142b84ab32ea5beb4f3f71e535d7fb263a6b2068377bf F test/table.test e87294bf1c80bfd7792142b84ab32ea5beb4f3f71e535d7fb263a6b2068377bf
F test/tableapi.test e37c33e6be2276e3a96bb54b00eea7f321277115d10e5b30fdb52a112b432750 F test/tableapi.test e37c33e6be2276e3a96bb54b00eea7f321277115d10e5b30fdb52a112b432750
F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930 F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930
F test/tclsqlite.test ad0bbd92edabe64cc91d990a0748142fe5ab962d74ac71fa3bfa94d50d2f4c87 F test/tclsqlite.test 3f697424cfc1cdc9c076ec0cadb0e700f059400a3e3ce134b7d856fc9f880e1c
F test/tempdb.test 4cdaa23ddd8acb4d79cbb1b68ccdfd09b0537aaba909ca69a876157c2a2cbd08 F test/tempdb.test 4cdaa23ddd8acb4d79cbb1b68ccdfd09b0537aaba909ca69a876157c2a2cbd08
F test/tempdb2.test 353864e96fd3ae2f70773d0ffbf8b1fe48589b02c2ec05013b540879410c3440 F test/tempdb2.test 353864e96fd3ae2f70773d0ffbf8b1fe48589b02c2ec05013b540879410c3440
F test/tempfault.test 0c0d349c9a99bf5f374655742577f8712c647900 F test/tempfault.test 0c0d349c9a99bf5f374655742577f8712c647900
@ -2207,8 +2207,8 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350
F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
P cf5b37b3a39013d8ca9de92da2289346caf52b56daff59e19b140cc586a3421f P a98a2f49355ec39c56e571c70d377675b1bd99a6d43cf9217b0eb1e081895d8e
R bf16fd55e66316305a43e4a57281b369 R b5e249a32712c6263d2ec9033caa9f81
U stephan U stephan
Z 52f694bf6ce8c40102964ca1cfc5e8c9 Z 24f79bc914343b0b71f5079a7d4e350f
# Remove this line to create a well-formed Fossil manifest. # Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
a98a2f49355ec39c56e571c70d377675b1bd99a6d43cf9217b0eb1e081895d8e 413a626b5c7902c1810142536c36e4ea8ee7c616ea82dfe1114199f9319091f7

View File

@ -49,6 +49,10 @@
# define CONST const # define CONST const
#elif !defined(Tcl_Size) #elif !defined(Tcl_Size)
typedef int Tcl_Size; typedef int Tcl_Size;
# ifndef Tcl_BounceRefCount
# define Tcl_BounceRefCount(X) Tcl_IncrRefCount(X); Tcl_DecrRefCount(X)
/* https://www.tcl-lang.org/man/tcl9.0/TclLib/Object.html */
# endif
#endif #endif
/**** End copy of tclsqlite.h ****/ /**** End copy of tclsqlite.h ****/
@ -1084,7 +1088,9 @@ static void tclSqlFunc(sqlite3_context *context, int argc, sqlite3_value**argv){
Tcl_DecrRefCount(pCmd); Tcl_DecrRefCount(pCmd);
} }
if( rc && rc!=TCL_RETURN ){ if( TCL_BREAK==rc ){
sqlite3_result_null(context);
}else if( rc && rc!=TCL_RETURN ){
sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1); sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1);
}else{ }else{
Tcl_Obj *pVar = Tcl_GetObjResult(p->interp); Tcl_Obj *pVar = Tcl_GetObjResult(p->interp);
@ -1102,7 +1108,7 @@ static void tclSqlFunc(sqlite3_context *context, int argc, sqlite3_value**argv){
}else if( (c=='b' && pVar->bytes==0 && strcmp(zType,"boolean")==0 ) }else if( (c=='b' && pVar->bytes==0 && strcmp(zType,"boolean")==0 )
|| (c=='b' && pVar->bytes==0 && strcmp(zType,"booleanString")==0 ) || (c=='b' && pVar->bytes==0 && strcmp(zType,"booleanString")==0 )
|| (c=='w' && strcmp(zType,"wideInt")==0) || (c=='w' && strcmp(zType,"wideInt")==0)
|| (c=='i' && strcmp(zType,"int")==0) || (c=='i' && strcmp(zType,"int")==0)
){ ){
eType = SQLITE_INTEGER; eType = SQLITE_INTEGER;
}else if( c=='d' && strcmp(zType,"double")==0 ){ }else if( c=='d' && strcmp(zType,"double")==0 ){
@ -1616,11 +1622,12 @@ struct DbEvalContext {
SqlPreparedStmt *pPreStmt; /* Current statement */ SqlPreparedStmt *pPreStmt; /* Current statement */
int nCol; /* Number of columns returned by pStmt */ int nCol; /* Number of columns returned by pStmt */
int evalFlags; /* Flags used */ int evalFlags; /* Flags used */
Tcl_Obj *pArray; /* Name of array variable */ Tcl_Obj *pVarName; /* Name of target array/dict variable */
Tcl_Obj **apColName; /* Array of column names */ Tcl_Obj **apColName; /* Array of column names */
}; };
#define SQLITE_EVAL_WITHOUTNULLS 0x00001 /* Unset array(*) for NULL */ #define SQLITE_EVAL_WITHOUTNULLS 0x00001 /* Unset array(*) for NULL */
#define SQLITE_EVAL_ASDICT 0x00002 /* Use dict instead of array */
/* /*
** Release any cache of column names currently held as part of ** Release any cache of column names currently held as part of
@ -1641,20 +1648,20 @@ static void dbReleaseColumnNames(DbEvalContext *p){
/* /*
** Initialize a DbEvalContext structure. ** Initialize a DbEvalContext structure.
** **
** If pArray is not NULL, then it contains the name of a Tcl array ** If pVarName is not NULL, then it contains the name of a Tcl array
** variable. The "*" member of this array is set to a list containing ** variable. The "*" member of this array is set to a list containing
** the names of the columns returned by the statement as part of each ** the names of the columns returned by the statement as part of each
** call to dbEvalStep(), in order from left to right. e.g. if the names ** call to dbEvalStep(), in order from left to right. e.g. if the names
** of the returned columns are a, b and c, it does the equivalent of the ** of the returned columns are a, b and c, it does the equivalent of the
** tcl command: ** tcl command:
** **
** set ${pArray}(*) {a b c} ** set ${pVarName}(*) {a b c}
*/ */
static void dbEvalInit( static void dbEvalInit(
DbEvalContext *p, /* Pointer to structure to initialize */ DbEvalContext *p, /* Pointer to structure to initialize */
SqliteDb *pDb, /* Database handle */ SqliteDb *pDb, /* Database handle */
Tcl_Obj *pSql, /* Object containing SQL script */ Tcl_Obj *pSql, /* Object containing SQL script */
Tcl_Obj *pArray, /* Name of Tcl array to set (*) element of */ Tcl_Obj *pVarName, /* Name of Tcl array to set (*) element of */
int evalFlags /* Flags controlling evaluation */ int evalFlags /* Flags controlling evaluation */
){ ){
memset(p, 0, sizeof(DbEvalContext)); memset(p, 0, sizeof(DbEvalContext));
@ -1662,9 +1669,9 @@ static void dbEvalInit(
p->zSql = Tcl_GetString(pSql); p->zSql = Tcl_GetString(pSql);
p->pSql = pSql; p->pSql = pSql;
Tcl_IncrRefCount(pSql); Tcl_IncrRefCount(pSql);
if( pArray ){ if( pVarName ){
p->pArray = pArray; p->pVarName = pVarName;
Tcl_IncrRefCount(pArray); Tcl_IncrRefCount(pVarName);
} }
p->evalFlags = evalFlags; p->evalFlags = evalFlags;
addDatabaseRef(p->pDb); addDatabaseRef(p->pDb);
@ -1687,7 +1694,7 @@ static void dbEvalRowInfo(
Tcl_Obj **apColName = 0; /* Array of column names */ Tcl_Obj **apColName = 0; /* Array of column names */
p->nCol = nCol = sqlite3_column_count(pStmt); p->nCol = nCol = sqlite3_column_count(pStmt);
if( nCol>0 && (papColName || p->pArray) ){ if( nCol>0 && (papColName || p->pVarName) ){
apColName = (Tcl_Obj**)Tcl_Alloc( sizeof(Tcl_Obj*)*nCol ); apColName = (Tcl_Obj**)Tcl_Alloc( sizeof(Tcl_Obj*)*nCol );
for(i=0; i<nCol; i++){ for(i=0; i<nCol; i++){
apColName[i] = Tcl_NewStringObj(sqlite3_column_name(pStmt,i), -1); apColName[i] = Tcl_NewStringObj(sqlite3_column_name(pStmt,i), -1);
@ -1696,20 +1703,35 @@ static void dbEvalRowInfo(
p->apColName = apColName; p->apColName = apColName;
} }
/* If results are being stored in an array variable, then create /* If results are being stored in a variable then create the
** the array(*) entry for that array ** array(*) or dict(*) entry for that variable.
*/ */
if( p->pArray ){ if( p->pVarName ){
Tcl_Interp *interp = p->pDb->interp; Tcl_Interp *interp = p->pDb->interp;
Tcl_Obj *pColList = Tcl_NewObj(); Tcl_Obj *pColList = Tcl_NewObj();
Tcl_Obj *pStar = Tcl_NewStringObj("*", -1); Tcl_Obj *pStar = Tcl_NewStringObj("*", -1);
Tcl_IncrRefCount(pColList);
Tcl_IncrRefCount(pStar);
for(i=0; i<nCol; i++){ for(i=0; i<nCol; i++){
Tcl_ListObjAppendElement(interp, pColList, apColName[i]); Tcl_ListObjAppendElement(interp, pColList, apColName[i]);
} }
Tcl_IncrRefCount(pStar); if( 0==(SQLITE_EVAL_ASDICT & p->evalFlags) ){
Tcl_ObjSetVar2(interp, p->pArray, pStar, pColList, 0); Tcl_ObjSetVar2(interp, p->pVarName, pStar, pColList, 0);
}else{
Tcl_Obj * pDict = Tcl_ObjGetVar2(interp, p->pVarName, NULL, 0);
if( !pDict ){
pDict = Tcl_NewDictObj();
}else if( Tcl_IsShared(pDict) ){
pDict = Tcl_DuplicateObj(pDict);
}
if( Tcl_DictObjPut(interp, pDict, pStar, pColList)==TCL_OK ){
Tcl_ObjSetVar2(interp, p->pVarName, NULL, pDict, 0);
}
Tcl_BounceRefCount(pDict);
}
Tcl_DecrRefCount(pStar); Tcl_DecrRefCount(pStar);
Tcl_DecrRefCount(pColList);
} }
} }
@ -1751,7 +1773,7 @@ static int dbEvalStep(DbEvalContext *p){
if( rcs==SQLITE_ROW ){ if( rcs==SQLITE_ROW ){
return TCL_OK; return TCL_OK;
} }
if( p->pArray ){ if( p->pVarName ){
dbEvalRowInfo(p, 0, 0); dbEvalRowInfo(p, 0, 0);
} }
rcs = sqlite3_reset(pStmt); rcs = sqlite3_reset(pStmt);
@ -1802,9 +1824,9 @@ static void dbEvalFinalize(DbEvalContext *p){
dbReleaseStmt(p->pDb, p->pPreStmt, 0); dbReleaseStmt(p->pDb, p->pPreStmt, 0);
p->pPreStmt = 0; p->pPreStmt = 0;
} }
if( p->pArray ){ if( p->pVarName ){
Tcl_DecrRefCount(p->pArray); Tcl_DecrRefCount(p->pVarName);
p->pArray = 0; p->pVarName = 0;
} }
Tcl_DecrRefCount(p->pSql); Tcl_DecrRefCount(p->pSql);
dbReleaseColumnNames(p); dbReleaseColumnNames(p);
@ -1879,7 +1901,7 @@ static int DbUseNre(void){
/* /*
** This function is part of the implementation of the command: ** This function is part of the implementation of the command:
** **
** $db eval SQL ?ARRAYNAME? SCRIPT ** $db eval SQL ?TGT-NAME? SCRIPT
*/ */
static int SQLITE_TCLAPI DbEvalNextCmd( static int SQLITE_TCLAPI DbEvalNextCmd(
ClientData data[], /* data[0] is the (DbEvalContext*) */ ClientData data[], /* data[0] is the (DbEvalContext*) */
@ -1893,8 +1915,8 @@ static int SQLITE_TCLAPI DbEvalNextCmd(
** is a pointer to a Tcl_Obj containing the script to run for each row ** is a pointer to a Tcl_Obj containing the script to run for each row
** returned by the queries encapsulated in data[0]. */ ** returned by the queries encapsulated in data[0]. */
DbEvalContext *p = (DbEvalContext *)data[0]; DbEvalContext *p = (DbEvalContext *)data[0];
Tcl_Obj *pScript = (Tcl_Obj *)data[1]; Tcl_Obj * const pScript = (Tcl_Obj *)data[1];
Tcl_Obj *pArray = p->pArray; Tcl_Obj * const pVarName = p->pVarName;
while( (rc==TCL_OK || rc==TCL_CONTINUE) && TCL_OK==(rc = dbEvalStep(p)) ){ while( (rc==TCL_OK || rc==TCL_CONTINUE) && TCL_OK==(rc = dbEvalStep(p)) ){
int i; int i;
@ -1902,15 +1924,46 @@ static int SQLITE_TCLAPI DbEvalNextCmd(
Tcl_Obj **apColName; Tcl_Obj **apColName;
dbEvalRowInfo(p, &nCol, &apColName); dbEvalRowInfo(p, &nCol, &apColName);
for(i=0; i<nCol; i++){ for(i=0; i<nCol; i++){
if( pArray==0 ){ if( pVarName==0 ){
Tcl_ObjSetVar2(interp, apColName[i], 0, dbEvalColumnValue(p,i), 0); Tcl_ObjSetVar2(interp, apColName[i], 0, dbEvalColumnValue(p,i), 0);
}else if( (p->evalFlags & SQLITE_EVAL_WITHOUTNULLS)!=0 }else if( (p->evalFlags & SQLITE_EVAL_WITHOUTNULLS)!=0
&& sqlite3_column_type(p->pPreStmt->pStmt, i)==SQLITE_NULL && sqlite3_column_type(p->pPreStmt->pStmt, i)==SQLITE_NULL
){ ){
Tcl_UnsetVar2(interp, Tcl_GetString(pArray), /* Remove NULL-containing column from the target container... */
Tcl_GetString(apColName[i]), 0); if( 0==(SQLITE_EVAL_ASDICT & p->evalFlags) ){
/* Target is an array */
Tcl_UnsetVar2(interp, Tcl_GetString(pVarName),
Tcl_GetString(apColName[i]), 0);
}else{
/* Target is a dict */
Tcl_Obj *pDict = Tcl_ObjGetVar2(interp, pVarName, NULL, 0);
if( pDict ){
if( Tcl_IsShared(pDict) ){
pDict = Tcl_DuplicateObj(pDict);
}
if( Tcl_DictObjRemove(interp, pDict, apColName[i])==TCL_OK ){
Tcl_ObjSetVar2(interp, pVarName, NULL, pDict, 0);
}
Tcl_BounceRefCount(pDict);
}
}
}else if( 0==(SQLITE_EVAL_ASDICT & p->evalFlags) ){
/* Target is an array: set target(colName) = colValue */
Tcl_ObjSetVar2(interp, pVarName, apColName[i],
dbEvalColumnValue(p,i), 0);
}else{ }else{
Tcl_ObjSetVar2(interp, pArray, apColName[i], dbEvalColumnValue(p,i), 0); /* Target is a dict: set target(colName) = colValue */
Tcl_Obj *pDict = Tcl_ObjGetVar2(interp, pVarName, NULL, 0);
if( !pDict ){
pDict = Tcl_NewDictObj();
}else if( Tcl_IsShared(pDict) ){
pDict = Tcl_DuplicateObj(pDict);
}
if( Tcl_DictObjPut(interp, pDict, apColName[i],
dbEvalColumnValue(p,i))==TCL_OK ){
Tcl_ObjSetVar2(interp, pVarName, NULL, pDict, 0);
}
Tcl_BounceRefCount(pDict);
} }
} }
@ -2019,7 +2072,7 @@ static int SQLITE_TCLAPI DbObjCmd(
"timeout", "total_changes", "trace", "timeout", "total_changes", "trace",
"trace_v2", "transaction", "unlock_notify", "trace_v2", "transaction", "unlock_notify",
"update_hook", "version", "wal_hook", "update_hook", "version", "wal_hook",
0 0
}; };
enum DB_enum { enum DB_enum {
DB_AUTHORIZER, DB_BACKUP, DB_BIND_FALLBACK, DB_AUTHORIZER, DB_BACKUP, DB_BIND_FALLBACK,
@ -2853,13 +2906,15 @@ deserialize_error:
} }
/* /*
** $db eval ?options? $sql ?array? ?{ ...code... }? ** $db eval ?options? $sql ?varName? ?{ ...code... }?
** **
** The SQL statement in $sql is evaluated. For each row, the values are ** The SQL statement in $sql is evaluated. For each row, the values
** placed in elements of the array named "array" and ...code... is executed. ** are placed in elements of the array or dict named $varName and
** If "array" and "code" are omitted, then no callback is every invoked. ** ...code... is executed. If $varName and $code are omitted, then
** If "array" is an empty string, then the values are placed in variables ** no callback is ever invoked. If $varName is an empty string,
** that have the same name as the fields extracted by the query. ** then the values are placed in variables that have the same name
** as the fields extracted by the query, and those variables are
** accessible during the eval of $code.
*/ */
case DB_EVAL: { case DB_EVAL: {
int evalFlags = 0; int evalFlags = 0;
@ -2867,8 +2922,9 @@ deserialize_error:
while( objc>3 && (zOpt = Tcl_GetString(objv[2]))!=0 && zOpt[0]=='-' ){ while( objc>3 && (zOpt = Tcl_GetString(objv[2]))!=0 && zOpt[0]=='-' ){
if( strcmp(zOpt, "-withoutnulls")==0 ){ if( strcmp(zOpt, "-withoutnulls")==0 ){
evalFlags |= SQLITE_EVAL_WITHOUTNULLS; evalFlags |= SQLITE_EVAL_WITHOUTNULLS;
} }else if( strcmp(zOpt, "-asdict")==0 ){
else{ evalFlags |= SQLITE_EVAL_ASDICT;
}else{
Tcl_AppendResult(interp, "unknown option: \"", zOpt, "\"", (void*)0); Tcl_AppendResult(interp, "unknown option: \"", zOpt, "\"", (void*)0);
return TCL_ERROR; return TCL_ERROR;
} }
@ -2876,8 +2932,8 @@ deserialize_error:
objv++; objv++;
} }
if( objc<3 || objc>5 ){ if( objc<3 || objc>5 ){
Tcl_WrongNumArgs(interp, 2, objv, Tcl_WrongNumArgs(interp, 2, objv,
"?OPTIONS? SQL ?ARRAY-NAME? ?SCRIPT?"); "?OPTIONS? SQL ?VAR-NAME? ?SCRIPT?");
return TCL_ERROR; return TCL_ERROR;
} }
@ -2903,17 +2959,17 @@ deserialize_error:
}else{ }else{
ClientData cd2[2]; ClientData cd2[2];
DbEvalContext *p; DbEvalContext *p;
Tcl_Obj *pArray = 0; Tcl_Obj *pVarName = 0;
Tcl_Obj *pScript; Tcl_Obj *pScript;
if( objc>=5 && *(char *)Tcl_GetString(objv[3]) ){ if( objc>=5 && *(char *)Tcl_GetString(objv[3]) ){
pArray = objv[3]; pVarName = objv[3];
} }
pScript = objv[objc-1]; pScript = objv[objc-1];
Tcl_IncrRefCount(pScript); Tcl_IncrRefCount(pScript);
p = (DbEvalContext *)Tcl_Alloc(sizeof(DbEvalContext)); p = (DbEvalContext *)Tcl_Alloc(sizeof(DbEvalContext));
dbEvalInit(p, pDb, objv[2], pArray, evalFlags); dbEvalInit(p, pDb, objv[2], pVarName, evalFlags);
cd2[0] = (void *)p; cd2[0] = (void *)p;
cd2[1] = (void *)pScript; cd2[1] = (void *)pScript;

View File

@ -9,7 +9,7 @@
# #
#*********************************************************************** #***********************************************************************
# This file implements regression tests for TCL interface to the # This file implements regression tests for TCL interface to the
# SQLite library. # SQLite library.
# #
# Actually, all tests are based on the TCL interface, so the main # Actually, all tests are based on the TCL interface, so the main
# interface is pretty well tested. This file contains some addition # interface is pretty well tested. This file contains some addition
@ -121,7 +121,7 @@ ifcapable {complete} {
do_test tcl-1.14 { do_test tcl-1.14 {
set v [catch {db eval} msg] set v [catch {db eval} msg]
lappend v $msg lappend v $msg
} {1 {wrong # args: should be "db eval ?OPTIONS? SQL ?ARRAY-NAME? ?SCRIPT?"}} } {1 {wrong # args: should be "db eval ?OPTIONS? SQL ?VAR-NAME? ?SCRIPT?"}}
do_test tcl-1.15 { do_test tcl-1.15 {
set v [catch {db function} msg] set v [catch {db function} msg]
lappend v $msg lappend v $msg
@ -359,6 +359,19 @@ do_test tcl-9.3 {
execsql {SELECT typeof(ret_int())} execsql {SELECT typeof(ret_int())}
} {integer} } {integer}
proc breakAsNullUdf args {
if {"1" eq [lindex $args 0]} {return -code break}
}
do_test tcl-9.4 {
db function banu breakAsNullUdf
execsql {SELECT typeof(banu()), typeof(banu(1))}
} {text null}
do_test tcl-9.5 {
db nullvalue banunull
db eval {SELECT banu(), banu(1)}
} {{} banunull}
# Recursive calls to the same user-defined function # Recursive calls to the same user-defined function
# #
ifcapable tclvar { ifcapable tclvar {
@ -465,7 +478,7 @@ do_test tcl-10.13 {
db eval {SELECT * FROM t4} db eval {SELECT * FROM t4}
} {1 2 5 6 7} } {1 2 5 6 7}
# Now test that [db transaction] commands may be nested with # Now test that [db transaction] commands may be nested with
# the expected results. # the expected results.
# #
do_test tcl-10.14 { do_test tcl-10.14 {
@ -475,7 +488,7 @@ do_test tcl-10.14 {
INSERT INTO t4 VALUES('one'); INSERT INTO t4 VALUES('one');
} }
catch { catch {
db transaction { db transaction {
db eval { INSERT INTO t4 VALUES('two') } db eval { INSERT INTO t4 VALUES('two') }
db transaction { db transaction {
@ -674,11 +687,11 @@ do_test tcl-15.5 {
} {0} } {0}
# 2017-06-26: The --withoutnulls flag to "db eval". # 2017-06-26: The -withoutnulls flag to "db eval".
# #
# In the "db eval --withoutnulls SQL ARRAY" form, NULL results cause the # In the "db eval -withoutnulls SQL TARGET" form, NULL results cause the
# corresponding array entry to be unset. The default behavior (without # corresponding target entry to be unset. The default behavior (without
# the -withoutnulls flags) is for the corresponding array value to get # the -withoutnulls flags) is for the corresponding target value to get
# the [db nullvalue] string. # the [db nullvalue] string.
# #
catch {db close} catch {db close}
@ -720,64 +733,64 @@ reset_db
proc add {a b} { return [expr $a + $b] } proc add {a b} { return [expr $a + $b] }
proc ret {a} { return $a } proc ret {a} { return $a }
db function add_i -returntype integer add db function add_i -returntype integer add
db function add_r -ret real add db function add_r -ret real add
db function add_t -return text add db function add_t -return text add
db function add_b -returntype blob add db function add_b -returntype blob add
db function add_a -returntype any add db function add_a -returntype any add
db function ret_i -returntype int ret db function ret_i -returntype int ret
db function ret_r -returntype real ret db function ret_r -returntype real ret
db function ret_t -returntype text ret db function ret_t -returntype text ret
db function ret_b -returntype blob ret db function ret_b -returntype blob ret
db function ret_a -r any ret db function ret_a -r any ret
do_execsql_test 17.0 { do_execsql_test 17.0 {
SELECT quote( add_i(2, 3) ); SELECT quote( add_i(2, 3) );
SELECT quote( add_r(2, 3) ); SELECT quote( add_r(2, 3) );
SELECT quote( add_t(2, 3) ); SELECT quote( add_t(2, 3) );
SELECT quote( add_b(2, 3) ); SELECT quote( add_b(2, 3) );
SELECT quote( add_a(2, 3) ); SELECT quote( add_a(2, 3) );
} {5 5.0 '5' X'35' 5} } {5 5.0 '5' X'35' 5}
do_execsql_test 17.1 { do_execsql_test 17.1 {
SELECT quote( add_i(2.2, 3.3) ); SELECT quote( add_i(2.2, 3.3) );
SELECT quote( add_r(2.2, 3.3) ); SELECT quote( add_r(2.2, 3.3) );
SELECT quote( add_t(2.2, 3.3) ); SELECT quote( add_t(2.2, 3.3) );
SELECT quote( add_b(2.2, 3.3) ); SELECT quote( add_b(2.2, 3.3) );
SELECT quote( add_a(2.2, 3.3) ); SELECT quote( add_a(2.2, 3.3) );
} {5.5 5.5 '5.5' X'352E35' 5.5} } {5.5 5.5 '5.5' X'352E35' 5.5}
do_execsql_test 17.2 { do_execsql_test 17.2 {
SELECT quote( ret_i(2.5) ); SELECT quote( ret_i(2.5) );
SELECT quote( ret_r(2.5) ); SELECT quote( ret_r(2.5) );
SELECT quote( ret_t(2.5) ); SELECT quote( ret_t(2.5) );
SELECT quote( ret_b(2.5) ); SELECT quote( ret_b(2.5) );
SELECT quote( ret_a(2.5) ); SELECT quote( ret_a(2.5) );
} {2.5 2.5 '2.5' X'322E35' 2.5} } {2.5 2.5 '2.5' X'322E35' 2.5}
do_execsql_test 17.3 { do_execsql_test 17.3 {
SELECT quote( ret_i('2.5') ); SELECT quote( ret_i('2.5') );
SELECT quote( ret_r('2.5') ); SELECT quote( ret_r('2.5') );
SELECT quote( ret_t('2.5') ); SELECT quote( ret_t('2.5') );
SELECT quote( ret_b('2.5') ); SELECT quote( ret_b('2.5') );
SELECT quote( ret_a('2.5') ); SELECT quote( ret_a('2.5') );
} {2.5 2.5 '2.5' X'322E35' '2.5'} } {2.5 2.5 '2.5' X'322E35' '2.5'}
do_execsql_test 17.4 { do_execsql_test 17.4 {
SELECT quote( ret_i('abc') ); SELECT quote( ret_i('abc') );
SELECT quote( ret_r('abc') ); SELECT quote( ret_r('abc') );
SELECT quote( ret_t('abc') ); SELECT quote( ret_t('abc') );
SELECT quote( ret_b('abc') ); SELECT quote( ret_b('abc') );
SELECT quote( ret_a('abc') ); SELECT quote( ret_a('abc') );
} {'abc' 'abc' 'abc' X'616263' 'abc'} } {'abc' 'abc' 'abc' X'616263' 'abc'}
do_execsql_test 17.5 { do_execsql_test 17.5 {
SELECT quote( ret_i(X'616263') ); SELECT quote( ret_i(X'616263') );
SELECT quote( ret_r(X'616263') ); SELECT quote( ret_r(X'616263') );
SELECT quote( ret_t(X'616263') ); SELECT quote( ret_t(X'616263') );
SELECT quote( ret_b(X'616263') ); SELECT quote( ret_b(X'616263') );
SELECT quote( ret_a(X'616263') ); SELECT quote( ret_a(X'616263') );
} {'abc' 'abc' 'abc' X'616263' X'616263'} } {'abc' 'abc' 'abc' X'616263' X'616263'}
do_test 17.6.1 { do_test 17.6.1 {
@ -848,21 +861,70 @@ do_catchsql_test 19.911 {
} {1 {invalid command name "bind_fallback_does_not_exist"}} } {1 {invalid command name "bind_fallback_does_not_exist"}}
db bind_fallback {} db bind_fallback {}
#------------------------------------------------------------------------- # 2025-05-05: the -asdict eval flag
#
do_test 20.0 { do_test 20.0 {
execsql {CREATE TABLE tad(a,b)}
execsql {INSERT INTO tad(a,b) VALUES('aa','bb'),('AA','BB')}
db eval -asdict {
SELECT a, b FROM tad WHERE 0
} D {}
set D
} {* {a b}}
do_test 20.1 {
unset D
set i 0
set res {}
set colNames {}
db eval -asdict {
SELECT a, b FROM tad ORDER BY a
} D {
dict set D i [incr i]
lappend res $i [dict get $D a] [dict get $D b]
if {1 == $i} {
set colNames [dict get $D *]
}
}
lappend res $colNames
unset D
set res
} {1 AA BB 2 aa bb {a b}}
do_test 20.2 {
set res {}
db eval -asdict -withoutnulls {
SELECT n, a, b FROM (
SELECT 1 as n, 'aa' as a, NULL as b
UNION ALL
SELECT 2 as n, NULL as a, 'bb' as b
)
ORDER BY n
} D {
dict unset D *
lappend res [dict values $D]
}
unset D
execsql {DROP TABLE tad}
set res
} {{1 aa} {2 bb}}
#-------------------------------------------------------------------------
do_test 21.0 {
db transaction { db transaction {
db close db close
} }
} {} } {}
do_test 20.1 { do_test 21.1 {
sqlite3 db test.db sqlite3 db test.db
set rc [catch { set rc [catch {
db eval {SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3} { db close } db eval {SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3} { db close }
} msg] } msg]
list $rc $msg list $rc $msg
} {1 {invalid command name "db"}} } {1 {invalid command name "db"}}
proc closedb {} { proc closedb {} {
db close db close
@ -874,7 +936,7 @@ sqlite3 db test.db
db func closedb closedb db func closedb closedb
db func func1 func1 db func func1 func1
do_test 20.2 { do_test 21.2 {
set rc [catch { set rc [catch {
db eval { db eval {
SELECT closedb(),func1() UNION ALL SELECT 20,30 UNION ALL SELECT 30,40 SELECT closedb(),func1() UNION ALL SELECT 20,30 UNION ALL SELECT 30,40
@ -884,9 +946,10 @@ do_test 20.2 {
} {0 {10 1 20 30 30 40}} } {0 {10 1 20 30 30 40}}
sqlite3 db :memory: sqlite3 db :memory:
do_test 21.1 { do_test 22.1 {
catch {db eval {SELECT 1 2 3;}} msg catch {db eval {SELECT 1 2 3;}} msg
db erroroffset db erroroffset
} {9} } {9}
finish_test finish_test