Index: src/tclsqlite.c ================================================================== --- src/tclsqlite.c +++ src/tclsqlite.c @@ -162,15 +162,22 @@ IncrblobChannel *pIncrblob;/* Linked list of open incrblob channels */ int nStep, nSort, nIndex; /* Statistics for most recent operation */ int nVMStep; /* Another statistic for most recent operation */ int nTransaction; /* Number of nested [transaction] methods */ int openFlags; /* Flags used to open. (SQLITE_OPEN_URI) */ + unsigned int modeFlags; /* Operating mode flags */ #ifdef SQLITE_TEST int bLegacyPrepare; /* True to use sqlite3_prepare() */ #endif }; +/* +** Valid values for SqliteDb.modeFlags +*/ +#define SQLITE_TCLMODE_UNSETNULL 0x0001 /* Unset NULL array values in */ + /* db eval ARRAY SQL */ + struct IncrblobChannel { sqlite3_blob *pBlob; /* sqlite3 blob handle */ SqliteDb *pDb; /* Associated database connection */ int iSeek; /* Current seek offset */ Tcl_Channel channel; /* Channel identifier */ @@ -1728,15 +1735,19 @@ int i; int nCol; Tcl_Obj **apColName; dbEvalRowInfo(p, &nCol, &apColName); for(i=0; ipDb->modeFlags & SQLITE_TCLMODE_UNSETNULL)!=0 + && sqlite3_column_type(p->pPreStmt->pStmt, i)==SQLITE_NULL + ){ + Tcl_UnsetVar2(interp, Tcl_GetString(pArray), + Tcl_GetString(apColName[i]), 0); }else{ - Tcl_ObjSetVar2(interp, pArray, apColName[i], pVal, 0); + Tcl_ObjSetVar2(interp, pArray, apColName[i], dbEvalColumnValue(p,i), 0); } } /* The required interpreter variables are now populated with the data ** from the current row. If using NRE, schedule callbacks to evaluate @@ -3307,10 +3318,11 @@ char *zErrMsg; int i; const char *zFile; const char *zVfs = 0; int flags; + unsigned int modeFlags = 0; Tcl_DString translatedFilename; #if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_CODEC_FROM_TCL) void *pKey = 0; int nKey = 0; #endif @@ -3397,19 +3409,28 @@ if( b ){ flags |= SQLITE_OPEN_URI; }else{ flags &= ~SQLITE_OPEN_URI; } + }else if( strcmp(zArg, "-unsetnull")==0 ){ + int b; + if( Tcl_GetBooleanFromObj(interp, objv[i+1], &b) ) return TCL_ERROR; + if( b ){ + modeFlags |= SQLITE_TCLMODE_UNSETNULL; + }else{ + modeFlags &= ~SQLITE_TCLMODE_UNSETNULL; + } }else{ Tcl_AppendResult(interp, "unknown option: ", zArg, (char*)0); return TCL_ERROR; } } if( objc<3 || (objc&1)!=1 ){ Tcl_WrongNumArgs(interp, 1, objv, "HANDLE FILENAME ?-vfs VFSNAME? ?-readonly BOOLEAN? ?-create BOOLEAN?" " ?-nomutex BOOLEAN? ?-fullmutex BOOLEAN? ?-uri BOOLEAN?" + " ?-unsetnull BOOLEAN?" #if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_CODEC_FROM_TCL) " ?-key CODECKEY?" #endif ); return TCL_ERROR; @@ -3441,10 +3462,11 @@ sqlite3_free(zErrMsg); return TCL_ERROR; } p->maxStmt = NUM_PREPARED_STMTS; p->openFlags = flags & SQLITE_OPEN_URI; + p->modeFlags = modeFlags; p->interp = interp; zArg = Tcl_GetStringFromObj(objv[1], 0); if( DbUseNre() ){ Tcl_NRCreateCommand(interp, zArg, DbObjCmdAdaptor, DbObjCmd, (char*)p, DbDeleteCmd); Index: test/tclsqlite.test ================================================================== --- test/tclsqlite.test +++ test/tclsqlite.test @@ -20,11 +20,11 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl # Check the error messages generated by tclsqlite # -set r "sqlite_orig HANDLE FILENAME ?-vfs VFSNAME? ?-readonly BOOLEAN? ?-create BOOLEAN? ?-nomutex BOOLEAN? ?-fullmutex BOOLEAN? ?-uri BOOLEAN?" +set r "sqlite_orig HANDLE FILENAME ?-vfs VFSNAME? ?-readonly BOOLEAN? ?-create BOOLEAN? ?-nomutex BOOLEAN? ?-fullmutex BOOLEAN? ?-uri BOOLEAN? ?-unsetnull BOOLEAN?" if {[sqlite3 -has-codec]} { append r " ?-key CODECKEY?" } do_test tcl-1.1 { set v [catch {sqlite3 bogus} msg] @@ -662,12 +662,47 @@ } {1} do_test tcl-15.5 { db exists {SELECT a FROM t1 WHERE a>3} } {0} - - - - - +# 2017-06-26: Different behavior with "-unsetnull 1" versus "-unsetnull 0" +# +# In the "db eval SQL ARRAY" form, NULL results cause the corresponding +# array entry to be unset with -unsetnull 1. With -unsetnull 0, the NULL +# results cause the array entry to be filled with the [db nullvalue] string. +# The -unsetnull 0 version is the default, for legacy compatibility. +# +catch {db close} +forcedelete test.db +sqlite3 db test.db +do_execsql_test tcl-16.100 { + CREATE TABLE t1(a,b); + INSERT INTO t1 VALUES(1,2),(2,NULL),(3,'xyz'); +} +do_test tcl-16.101 { + set res {} + unset -nocomplain x + db eval {SELECT * FROM t1} x { + lappend res $x(a) [array names x] + } + set res +} {1 {a b *} 2 {a b *} 3 {a b *}} +sqlite3 db test.db -unsetnull 0 +do_test tcl-16.102 { + set res {} + unset -nocomplain x + db eval {SELECT * FROM t1} x { + lappend res $x(a) [array names x] + } + set res +} {1 {a b *} 2 {a b *} 3 {a b *}} +sqlite3 db test.db -unsetnull 1 +do_test tcl-16.103 { + set res {} + unset -nocomplain x + db eval {SELECT * FROM t1} x { + lappend res $x(a) [array names x] + } + set res +} {1 {a b *} 2 {a *} 3 {a b *}} finish_test