diff options
Diffstat (limited to 'src/tclsqlite.c')
-rw-r--r-- | src/tclsqlite.c | 133 |
1 files changed, 85 insertions, 48 deletions
diff --git a/src/tclsqlite.c b/src/tclsqlite.c index df7f440bd..6a9434131 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -12,7 +12,7 @@ ** A TCL Interface to SQLite. Append this file to sqlite3.c and ** compile the whole thing to build a TCL-enabled version of SQLite. ** -** $Id: tclsqlite.c,v 1.205 2007/11/12 17:56:43 drh Exp $ +** $Id: tclsqlite.c,v 1.206 2007/11/13 10:30:26 danielk1977 Exp $ */ #include "tcl.h" #include <errno.h> @@ -886,6 +886,67 @@ static char *local_getline(char *zPrompt, FILE *in){ return zLine; } + +/* +** Figure out the column names for the data returned by the statement +** passed as the second argument. +** +** If parameter papColName is not NULL, then *papColName is set to point +** at an array allocated using Tcl_Alloc(). It is the callers responsibility +** to free this array using Tcl_Free(), and to decrement the reference +** count of each Tcl_Obj* member of the array. +** +** The return value of this function is the number of columns of data +** returned by pStmt (and hence the size of the *papColName array). +** +** If pArray is not NULL, then it contains the name of a Tcl array +** variable. The "*" member of this array is set to a list containing +** the names of the columns returned by the statement, 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 tcl command: +** +** set ${pArray}(*) {a b c} +*/ +static int +computeColumnNames( + Tcl_Interp *interp, + sqlite3_stmt *pStmt, /* SQL statement */ + Tcl_Obj ***papColName, /* OUT: Array of column names */ + Tcl_Obj *pArray /* Name of array variable (may be null) */ +){ + int nCol; + + /* Compute column names */ + nCol = sqlite3_column_count(pStmt); + if( papColName ){ + int i; + Tcl_Obj **apColName = (Tcl_Obj**)Tcl_Alloc( sizeof(Tcl_Obj*)*nCol ); + for(i=0; i<nCol; i++){ + apColName[i] = dbTextToObj(sqlite3_column_name(pStmt,i)); + Tcl_IncrRefCount(apColName[i]); + } + + /* If results are being stored in an array variable, then create + ** the array(*) entry for that array + */ + if( pArray ){ + Tcl_Obj *pColList = Tcl_NewObj(); + Tcl_Obj *pStar = Tcl_NewStringObj("*", -1); + Tcl_IncrRefCount(pColList); + for(i=0; i<nCol; i++){ + Tcl_ListObjAppendElement(interp, pColList, apColName[i]); + } + Tcl_IncrRefCount(pStar); + Tcl_ObjSetVar2(interp, pArray, pStar, pColList,0); + Tcl_DecrRefCount(pColList); + Tcl_DecrRefCount(pStar); + } + *papColName = apColName; + } + + return nCol; +} + /* ** The "sqlite" command below creates a new Tcl command for each ** connection it opens to an SQLite database. This routine is invoked @@ -1506,7 +1567,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ while( rc==TCL_OK && zSql[0] ){ int i; /* Loop counter */ int nVar; /* Number of bind parameters in the pStmt */ - int nCol; /* Number of columns in the result set */ + int nCol = -1; /* Number of columns in the result set */ Tcl_Obj **apColName = 0; /* Array of column names */ int len; /* String length of zSql */ @@ -1514,13 +1575,8 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ ** which matches the next sequence of SQL. */ pStmt = 0; - pPreStmt = pDb->stmtList; len = strlen(zSql); - if( pPreStmt && sqlite3_expired(pPreStmt->pStmt) ){ - flushStmtCache(pDb); - pPreStmt = 0; - } - for(; pPreStmt; pPreStmt=pPreStmt->pNext){ + for(pPreStmt = pDb->stmtList; pPreStmt; pPreStmt=pPreStmt->pNext){ int n = pPreStmt->nSql; if( len>=n && memcmp(pPreStmt->zSql, zSql, n)==0 @@ -1551,7 +1607,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ /* If no prepared statement was found. Compile the SQL text */ if( pStmt==0 ){ - if( SQLITE_OK!=sqlite3_prepare(pDb->db, zSql, -1, &pStmt, &zLeft) ){ + if( SQLITE_OK!=sqlite3_prepare_v2(pDb->db, zSql, -1, &pStmt, &zLeft) ){ Tcl_SetObjResult(interp, dbTextToObj(sqlite3_errmsg(pDb->db))); rc = TCL_ERROR; break; @@ -1625,36 +1681,20 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ } } - /* Compute column names */ - nCol = sqlite3_column_count(pStmt); - if( pScript ){ - apColName = (Tcl_Obj**)Tcl_Alloc( sizeof(Tcl_Obj*)*nCol ); - if( apColName==0 ) break; - for(i=0; i<nCol; i++){ - apColName[i] = dbTextToObj(sqlite3_column_name(pStmt,i)); - Tcl_IncrRefCount(apColName[i]); - } - } - - /* If results are being stored in an array variable, then create - ** the array(*) entry for that array - */ - if( pArray ){ - Tcl_Obj *pColList = Tcl_NewObj(); - Tcl_Obj *pStar = Tcl_NewStringObj("*", -1); - Tcl_IncrRefCount(pColList); - for(i=0; i<nCol; i++){ - Tcl_ListObjAppendElement(interp, pColList, apColName[i]); - } - Tcl_IncrRefCount(pStar); - Tcl_ObjSetVar2(interp, pArray, pStar, pColList,0); - Tcl_DecrRefCount(pColList); - Tcl_DecrRefCount(pStar); - } - /* Execute the SQL */ while( rc==TCL_OK && pStmt && SQLITE_ROW==sqlite3_step(pStmt) ){ + + /* Compute column names. This must be done after the first successful + ** call to sqlite3_step(), in case the query is recompiled and the + ** number or names of the returned columns changes. + */ + assert(!pArray||pScript); + if (nCol < 0) { + Tcl_Obj ***ap = (pScript?&apColName:0); + nCol = computeColumnNames(interp, pStmt, ap, pArray); + } + for(i=0; i<nCol; i++){ Tcl_Obj *pVal; @@ -1727,6 +1767,14 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ /* Free the column name objects */ if( pScript ){ + /* If the query returned no rows, but an array variable was + ** specified, call computeColumnNames() now to populate the + ** arrayname(*) variable. + */ + if (pArray && nCol < 0) { + Tcl_Obj ***ap = (pScript?&apColName:0); + nCol = computeColumnNames(interp, pStmt, ap, pArray); + } for(i=0; i<nCol; i++){ Tcl_DecrRefCount(apColName[i]); } @@ -1745,15 +1793,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ ** flush the statement cache and try the statement again. */ rc2 = sqlite3_reset(pStmt); - if( SQLITE_SCHEMA==rc2 ){ - /* After a schema change, flush the cache and try to run the - ** statement again - */ - flushStmtCache( pDb ); - sqlite3_finalize(pStmt); - if( pPreStmt ) Tcl_Free((char*)pPreStmt); - continue; - }else if( SQLITE_OK!=rc2 ){ + if( SQLITE_OK!=rc2 ){ /* If a run-time error occurs, report the error and stop reading ** the SQL */ @@ -1854,9 +1894,6 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ if( rc!=SQLITE_OK ){ rc = TCL_ERROR; Tcl_SetResult(interp, (char *)sqlite3_errmsg(pDb->db), TCL_VOLATILE); - }else{ - /* Must flush any cached statements */ - flushStmtCache( pDb ); } break; } |