Index: src/tclsqlite.c ================================================================== --- src/tclsqlite.c +++ src/tclsqlite.c @@ -85,27 +85,57 @@ #endif /* BUILD_sqlite */ #define NUM_PREPARED_STMTS 10 #define MAX_PREPARED_STMTS 100 +/* +** Fixed string table index enumeration. These enumeration values are used as +** indexes into the pFixedStrings array. +*/ +typedef enum { + FS_NEW, FS_STEP, FS_INVERSE, FS_VALUE, FS_DESTROY, FS_NUM_FIXED_STRINGS +} FixedString; + +/* +** Fixed string table, indexed by the above enumeration. This table is +** initialized by Sqlite3_Init() and cleaned up by Sqlite3_Unload(). +*/ +static Tcl_Obj *pFixedStrings[FS_NUM_FIXED_STRINGS]; + +/* +** TCL SQL function flags. +*/ +#define FF_AGGREGATE 1 /* This is an aggregate or window function */ +#define FF_CLASS 2 /* pScript is a TclOO-style class name */ + /* Forward declaration */ typedef struct SqliteDb SqliteDb; /* -** New SQL functions can be created as TCL scripts. Each such function +** New SQL functions can be created as TCL command prefixes. Each such function ** is described by an instance of the following structure. */ typedef struct SqlFunc SqlFunc; struct SqlFunc { Tcl_Interp *interp; /* The TCL interpret to execute the function */ Tcl_Obj *pScript; /* The Tcl_Obj representation of the script */ + Tcl_Obj *pInitial; /* Constructor argument(s) to the "new" method */ SqliteDb *pDb; /* Database connection that owns this function */ - int useEvalObjv; /* True if it is safe to use Tcl_EvalObjv */ + int flags; /* Function configuration FF_* bitmask */ char *zName; /* Name of this function */ SqlFunc *pNext; /* Next function on the list of them all */ }; +/* +** Persistent aggregate/window function state data structure. +*/ +typedef struct { + int initialized; /* Nonzero if initialization has taken place */ + int window; /* Nonzero if tclSqlFuncValue() was called */ + Tcl_Obj *pObj; /* TCL state data. */ +} SqlFuncState; + /* ** New collation sequences function can be created as TCL scripts. Each such ** function is described by an instance of the following structure. */ typedef struct SqlCollate SqlCollate; @@ -424,37 +454,10 @@ } #else /* else clause for "#ifndef SQLITE_OMIT_INCRBLOB" */ #define closeIncrblobChannels(pDb) #endif -/* -** Look at the script prefix in pCmd. We will be executing this script -** after first appending one or more arguments. This routine analyzes -** the script to see if it is safe to use Tcl_EvalObjv() on the script -** rather than the more general Tcl_EvalEx(). Tcl_EvalObjv() is much -** faster. -** -** Scripts that are safe to use with Tcl_EvalObjv() consists of a -** command name followed by zero or more arguments with no [...] or $ -** or {...} or ; to be seen anywhere. Most callback scripts consist -** of just a single procedure name and they meet this requirement. -*/ -static int safeToUseEvalObjv(Tcl_Interp *interp, Tcl_Obj *pCmd){ - /* We could try to do something with Tcl_Parse(). But we will instead - ** just do a search for forbidden characters. If any of the forbidden - ** characters appear in pCmd, we will report the string as unsafe. - */ - const char *z; - int n; - z = Tcl_GetStringFromObj(pCmd, &n); - while( n-- > 0 ){ - int c = *(z++); - if( c=='$' || c=='[' || c==';' ) return 0; - } - return 1; -} - /* ** Find an SqlFunc structure with the given name. Or create a new ** one if an existing one cannot be found. Return a pointer to the ** structure. */ @@ -471,10 +474,11 @@ } } pNew->interp = pDb->interp; pNew->pDb = pDb; pNew->pScript = 0; + pNew->pInitial = 0; pNew->pNext = pDb->pFunc; pDb->pFunc = pNew; return pNew; } @@ -519,10 +523,13 @@ while( pDb->pFunc ){ SqlFunc *pFunc = pDb->pFunc; pDb->pFunc = pFunc->pNext; assert( pFunc->pDb==pDb ); Tcl_DecrRefCount(pFunc->pScript); + if( pFunc->pInitial ){ + Tcl_DecrRefCount(pFunc->pInitial); + } Tcl_Free((char*)pFunc); } while( pDb->pCollate ){ SqlCollate *pCollate = pDb->pCollate; pDb->pCollate = pCollate->pNext; @@ -896,29 +903,65 @@ Tcl_EvalObjEx(p->interp, pCmd, TCL_EVAL_DIRECT); Tcl_DecrRefCount(pCmd); return (atoi(Tcl_GetStringResult(p->interp))); } +/* Forward declaration */ +static SqlFuncState *tclSqlFuncState(sqlite3_context *context); + /* -** This routine is called to evaluate an SQL function implemented -** using TCL script. +** This function calls a TCL command prefix to implement an SQL function. +** +** The command prefix is automatically determined. If the function is a scalar +** function, or the "-state" calling convention is used, or the method is "new", +** the TCL script provided at the time the function was defined is used as the +** command prefix. If the function is an aggregate function, and the "-class" +** calling convention is used, and the method is not "new", the current state +** value is used as the command prefix. +** +** If the function is a scalar function, the next paragraph does not apply, and +** the method argument is ignored. For scalar functions, the only arguments are +** SQL function argument values. +** +** The first argument to the command prefix is the method name. If the method +** is not "new" and the "-state" calling convention is used, the second argument +** is the current state value. If the method is "new" and the "-state" calling +** convention is used, the value of the "-initial" switch (defaulting to empty +** string) is the second argument. If the method is "new" and the "-class" +** calling convention is used, the elements of the value of the "-initial" +** switch (defaulting to empty list) are the second and subsequent arguments. +** After these arguments are appended SQL function argument values, if supplied. +** +** On success, the return value is a pointer to the Tcl_Obj result. On failure, +** the return value is NULL, and an error message is stored in the SQL context. */ -static void tclSqlFunc(sqlite3_context *context, int argc, sqlite3_value**argv){ - SqlFunc *p = sqlite3_user_data(context); +static Tcl_Obj *tclSqlFuncCall( + sqlite3_context *context, + FixedString method, + int argc, + sqlite3_value **argv +){ + SqlFunc *pFunc = sqlite3_user_data(context); Tcl_Obj *pCmd; + int flags = 0; int i; int rc; - if( argc==0 ){ + /* Determine the command prefix. */ + if( (~pFunc->flags & (FF_AGGREGATE|FF_CLASS)) || method==FS_NEW ){ + pCmd = pFunc->pScript; + }else{ + pCmd = tclSqlFuncState(context)->pObj; + } + + if( !(pFunc->flags & FF_AGGREGATE) && argc==0 ){ /* If there are no arguments to the function, call Tcl_EvalObjEx on the ** script object directly. This allows the TCL compiler to generate ** bytecode for the command on the first invocation and thus make ** subsequent invocations much faster. */ - pCmd = p->pScript; Tcl_IncrRefCount(pCmd); - rc = Tcl_EvalObjEx(p->interp, pCmd, 0); - Tcl_DecrRefCount(pCmd); + flags = TCL_EVAL_DIRECT; }else{ /* If there are arguments to the function, make a shallow copy of the ** script object, lappend the arguments, then evaluate the copy. ** ** By "shallow" copy, we mean only the outer list Tcl_Obj is duplicated. @@ -927,98 +970,305 @@ ** of the list to tclCmdNameType, that alternate representation will ** be preserved and reused on the next invocation. */ Tcl_Obj **aArg; int nArg; - if( Tcl_ListObjGetElements(p->interp, p->pScript, &nArg, &aArg) ){ - sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1); - return; + if( Tcl_ListObjGetElements(pFunc->interp, pCmd, &nArg, &aArg) ){ + sqlite3_result_error(context, Tcl_GetStringResult(pFunc->interp), -1); + return 0; } pCmd = Tcl_NewListObj(nArg, aArg); Tcl_IncrRefCount(pCmd); + + /* Append aggregate/window function initial arguments. */ + if( pFunc->flags & FF_AGGREGATE ){ + /* Append method name argument. */ + if( Tcl_ListObjAppendElement(pFunc->interp, pCmd, + pFixedStrings[method]) ){ + Tcl_DecrRefCount(pCmd); + sqlite3_result_error(context, Tcl_GetStringResult(pFunc->interp), -1); + return 0; + } + + /* Append state argument or "new" constructor arguments. */ + if( !(pFunc->flags & FF_CLASS) ){ + Tcl_Obj *pState; + if( method!=FS_NEW ){ + pState = tclSqlFuncState(context)->pObj; + }else if( pFunc->pInitial ){ + pState = pFunc->pInitial; + }else{ + pState = Tcl_NewObj(); + } + if( Tcl_ListObjAppendElement(pFunc->interp, pCmd, pState) ){ + if( method==FS_NEW && !pFunc->pInitial ){ + Tcl_DecrRefCount(pState); + } + Tcl_DecrRefCount(pCmd); + sqlite3_result_error(context, Tcl_GetStringResult(pFunc->interp), -1); + return 0; + } + }else if( method==FS_NEW && pFunc->pInitial + && Tcl_ListObjAppendList(pFunc->interp, pCmd, pFunc->pInitial) ){ + Tcl_DecrRefCount(pCmd); + sqlite3_result_error(context, Tcl_GetStringResult(pFunc->interp), -1); + return 0; + } + } + + /* Append SQL arguments. */ for(i=0; i=-2147483647 && v<=2147483647 ){ pVal = Tcl_NewIntObj((int)v); }else{ pVal = Tcl_NewWideIntObj(v); } break; } case SQLITE_FLOAT: { - double r = sqlite3_value_double(pIn); - pVal = Tcl_NewDoubleObj(r); - break; - } - case SQLITE_NULL: { - pVal = Tcl_NewStringObj(p->pDb->zNull, -1); - break; - } - default: { - int bytes = sqlite3_value_bytes(pIn); - pVal = Tcl_NewStringObj((char *)sqlite3_value_text(pIn), bytes); - break; - } - } - rc = Tcl_ListObjAppendElement(p->interp, pCmd, pVal); - if( rc ){ - Tcl_DecrRefCount(pCmd); - sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1); - return; - } - } - if( !p->useEvalObjv ){ - /* Tcl_EvalObjEx() will automatically call Tcl_EvalObjv() if pCmd - ** is a list without a string representation. To prevent this from - ** happening, make sure pCmd has a valid string representation */ - Tcl_GetString(pCmd); - } - rc = Tcl_EvalObjEx(p->interp, pCmd, TCL_EVAL_DIRECT); - Tcl_DecrRefCount(pCmd); - } - - if( rc && rc!=TCL_RETURN ){ - sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1); - }else{ - Tcl_Obj *pVar = Tcl_GetObjResult(p->interp); - int n; - u8 *data; - const char *zType = (pVar->typePtr ? pVar->typePtr->name : ""); - char c = zType[0]; - if( c=='b' && strcmp(zType,"bytearray")==0 && pVar->bytes==0 ){ - /* Only return a BLOB type if the Tcl variable is a bytearray and - ** has no string representation. */ - data = Tcl_GetByteArrayFromObj(pVar, &n); - sqlite3_result_blob(context, data, n, SQLITE_TRANSIENT); - }else if( c=='b' && strcmp(zType,"boolean")==0 ){ - Tcl_GetIntFromObj(0, pVar, &n); - sqlite3_result_int(context, n); - }else if( c=='d' && strcmp(zType,"double")==0 ){ - double r; - Tcl_GetDoubleFromObj(0, pVar, &r); - sqlite3_result_double(context, r); - }else if( (c=='w' && strcmp(zType,"wideInt")==0) || - (c=='i' && strcmp(zType,"int")==0) ){ - Tcl_WideInt v; - Tcl_GetWideIntFromObj(0, pVar, &v); - sqlite3_result_int64(context, v); - }else{ - data = (unsigned char *)Tcl_GetStringFromObj(pVar, &n); - sqlite3_result_text(context, (char *)data, n, SQLITE_TRANSIENT); - } - } + pVal = Tcl_NewDoubleObj(sqlite3_value_double(argv[i])); + break; + } + case SQLITE_NULL: { + pVal = Tcl_NewStringObj(pFunc->pDb->zNull, -1); + break; + } + default: { + int bytes = sqlite3_value_bytes(argv[i]); + pVal = Tcl_NewStringObj((char *)sqlite3_value_text(argv[i]), bytes); + break; + } + } + if( Tcl_ListObjAppendElement(pFunc->interp, pCmd, pVal) ){ + Tcl_DecrRefCount(pCmd); + sqlite3_result_error(context, Tcl_GetStringResult(pFunc->interp), -1); + return 0; + } + } + } + + /* Invoke the completed command object, then deallocate it if it was a + ** temporary list object created above. */ + rc = Tcl_EvalObjEx(pFunc->interp, pCmd, flags); + Tcl_DecrRefCount(pCmd); + + if( rc && rc!=TCL_RETURN ){ + sqlite3_result_error(context, Tcl_GetStringResult(pFunc->interp), -1); + return 0; + }else{ + return Tcl_GetObjResult(pFunc->interp); + } +} + +/* +** This function converts a Tcl_Obj result to an SQL value and stores it into +** the SQL context. +*/ +static void tclSqlFuncResult( + sqlite3_context *context, + Tcl_Obj *pVar +){ + int n; + u8 *data; + const char *zType = pVar->typePtr ? pVar->typePtr->name : ""; + char c = zType[0]; + if( c=='b' && strcmp(zType,"bytearray")==0 && pVar->bytes==0 ){ + /* Only return a BLOB type if the Tcl variable is a bytearray and + ** has no string representation. */ + data = Tcl_GetByteArrayFromObj(pVar, &n); + sqlite3_result_blob(context, data, n, SQLITE_TRANSIENT); + }else if( c=='b' && strcmp(zType,"boolean")==0 ){ + Tcl_GetIntFromObj(0, pVar, &n); + sqlite3_result_int(context, n); + }else if( c=='d' && strcmp(zType,"double")==0 ){ + double r; + Tcl_GetDoubleFromObj(0, pVar, &r); + sqlite3_result_double(context, r); + }else if( (c=='w' && strcmp(zType,"wideInt")==0) || + (c=='i' && strcmp(zType,"int")==0) ){ + Tcl_WideInt v; + Tcl_GetWideIntFromObj(0, pVar, &v); + sqlite3_result_int64(context, v); + }else{ + data = (unsigned char *)Tcl_GetStringFromObj(pVar, &n); + sqlite3_result_text(context, (char *)data, n, SQLITE_TRANSIENT); + } +} + +/* + * This function returns the address of a state structure that persists for the + * duration of the execution of an aggregate or window function. + * + * If the "-state" (default) calling convention is selected, the pObj field + * points to the Tcl_Obj most recently returned by the "new", "step", or + * "inverse" methods of the TCL command prefix registered when the SQL function + * was first created. This Tcl_Obj will be used as the second argument (i.e. + * the one following the method name) the next time the command prefix is used. + * + * If the "-class" calling convention is selected, the pObj field points to the + * Tcl_Obj containing the command prefix to invoke. This command prefix is the + * result value obtained by invoking the "new" method, as with the "-state" + * calling convention. + * + * In event of error, the pObj field will be NULL, and the TCL should not be + * called again for the duration of the SQL function execution. +*/ +static SqlFuncState *tclSqlFuncState(sqlite3_context *context){ + SqlFuncState *pState = sqlite3_aggregate_context(context, sizeof(*pState)); + if( !pState->initialized ){ + pState->initialized = 1; + pState->window = 0; + pState->pObj = tclSqlFuncCall(context, FS_NEW, 0, NULL); + if( pState->pObj ){ + Tcl_IncrRefCount(pState->pObj); + } + } + return pState; +} + +/* +** Destructor for custom SQL functions defined in TCL. When execution of an SQL +** function completes, either successfully or due to error, the "destroy" method +** is invoked so that script-level cleanup may be performed. Next, the current +** state Tcl_Obj is deallocated and its pointer is set NULL so that TCL will not +** be invoked again for the current SQL function. +**/ +static void tclSqlFuncDestroy(sqlite3_context *context) +{ + SqlFuncState *pState = tclSqlFuncState(context); + if( pState->pObj ){ + tclSqlFuncCall(context, FS_DESTROY, 0, NULL); + Tcl_DecrRefCount(pState->pObj); + pState->pObj = NULL; + } +} + +/* +** Common implementation of tclSqlFuncStep() and tclSqlFuncInverse(). +*/ +static void tclSqlFuncStepOrInverse( + sqlite3_context *context, + FixedString method, + int argc, + sqlite3_value **argv +){ + SqlFuncState *pState = tclSqlFuncState(context); + if( pState->pObj ){ + Tcl_Obj *pVal = tclSqlFuncCall(context, method, argc, argv); + if( !pVal ){ + tclSqlFuncDestroy(context); + }else if( !(((SqlFunc *)sqlite3_user_data(context))->flags & FF_CLASS) ){ + Tcl_IncrRefCount(pVal); + Tcl_DecrRefCount(pState->pObj); + pState->pObj = pVal; + } + } +} + +/* +** This routine is called to evaluate a scalar SQL function implemented +** using TCL script. +*/ +static void tclSqlFuncScalar( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Tcl_Obj *pVal = tclSqlFuncCall(context, 0, argc, argv); + if( pVal ){ + tclSqlFuncResult(context, pVal); + } +} + +/* +** This routine is called for each row when it is added to the aggregate or +** window to evaluate an SQL function implemented using TCL script. +** +** If this is the first row in the group, the "new" method (i.e. first argument +** to the command prefix) is used so that initialization may occur. +** +** The "step" method is invoked using the SQL arguments. +** +** When using the "-state" calling convention, the state object is replaced with +** the result value obtained from the "step" method. +*/ +static void tclSqlFuncStep( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + tclSqlFuncStepOrInverse(context, FS_STEP, argc, argv); +} + +/* +** This routine is called for the final row of each group, or in event of error, +** or if there were no rows, to evaluate an aggregate or window SQL function +** implemented using TCL script. +** +** If there were no rows, the "new" method will be invoked first to initialize. +** +** If this is a non-window function, the "value" method is invoked. The check +** for window functions is done to avoid invoking "value" twice for the last row +** of each group. +** +** For both window and non-window aggregate functions, "destroy" is invoked. +*/ +static void tclSqlFuncFinal(sqlite3_context *context){ + SqlFuncState *pState = tclSqlFuncState(context); + if( pState->pObj ){ + if( !pState->window ){ + Tcl_Obj *pVal = tclSqlFuncCall(context, FS_VALUE, 0, NULL); + if( pVal ){ + tclSqlFuncResult(context, pVal); + } + } + tclSqlFuncDestroy(context); + } +} + +/* +** This routine is called for each row to evaluate a window SQL function +** implemented using TCL script. The "value" method is invoked. +*/ +static void tclSqlFuncValue(sqlite3_context *context){ + SqlFuncState *pState = tclSqlFuncState(context); + if( pState->pObj ){ + Tcl_Obj *pVal = tclSqlFuncCall(context, FS_VALUE, 0, NULL); + if( pVal ){ + tclSqlFuncResult(context, pVal); + }else{ + tclSqlFuncDestroy(context); + } + pState->window = 1; + } +} + +/* +** This routine is called for each row when it is removed from the window to +** evaluate an SQL function implemented using TCL script. +** +** The "inverse" method is invoked using the SQL arguments. +** +** When using the "-state" calling convention, the state object is replaced with +** the result value obtained from the "step" method. +*/ +static void tclSqlFuncInverse( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + tclSqlFuncStepOrInverse(context, FS_INVERSE, argc, argv); } #ifndef SQLITE_OMIT_AUTHORIZATION /* ** This is the authentication function. It appends the authentication @@ -2632,64 +2882,109 @@ } break; } /* - ** $db function NAME [-argcount N] [-deterministic] SCRIPT + ** $db function NAME ?SWITCHES? SCRIPT ** ** Create a new SQL function called NAME. Whenever that function is ** called, invoke SCRIPT to evaluate the function. */ case DB_FUNCTION: { - int flags = SQLITE_UTF8; + static const char *SW_strs[] = { + "-argcount", "-deterministic", "-state", "-class", "-initial", + "-scalar", "-aggregate", "-window", 0 + }; + enum SW_enum { + SW_ARGCOUNT, SW_DETERMINISTIC, SW_STATE, SW_CLASS, SW_INITIAL, + SW_SCALAR, SW_AGGREGATE, SW_WINDOW + }; + enum {TY_SCALAR, TY_AGGREGATE, TY_WINDOW} type = TY_SCALAR; + int sqlFlags = SQLITE_UTF8; + int tclFlags = 0; SqlFunc *pFunc; Tcl_Obj *pScript; + Tcl_Obj *pInitial = NULL; char *zName; int nArg = -1; int i; if( objc<4 ){ Tcl_WrongNumArgs(interp, 2, objv, "NAME ?SWITCHES? SCRIPT"); return TCL_ERROR; } for(i=3; i<(objc-1); i++){ const char *z = Tcl_GetString(objv[i]); - int n = strlen30(z); - if( n>2 && strncmp(z, "-argcount",n)==0 ){ - if( i==(objc-2) ){ - Tcl_AppendResult(interp, "option requires an argument: ", z,(char*)0); - return TCL_ERROR; - } + if( Tcl_GetIndexFromObj(interp, objv[i], SW_strs, "option", 0, &choice) ){ + return TCL_ERROR; + } + if( i==(objc-2) && (choice==SW_ARGCOUNT || choice==SW_INITIAL) ){ + Tcl_AppendResult(interp, "option requires an argument: ", z, (char *)0); + return TCL_ERROR; + } + switch( (enum SW_enum)choice ){ + case SW_ARGCOUNT: if( Tcl_GetIntFromObj(interp, objv[i+1], &nArg) ) return TCL_ERROR; if( nArg<0 ){ Tcl_AppendResult(interp, "number of arguments must be non-negative", (char*)0); return TCL_ERROR; } i++; - }else - if( n>2 && strncmp(z, "-deterministic",n)==0 ){ - flags |= SQLITE_DETERMINISTIC; - }else{ - Tcl_AppendResult(interp, "bad option \"", z, - "\": must be -argcount or -deterministic", (char*)0 - ); - return TCL_ERROR; - } + break; + case SW_INITIAL : pInitial = objv[++i] ; break; + case SW_DETERMINISTIC: sqlFlags |= SQLITE_DETERMINISTIC; break; + case SW_STATE : tclFlags &= ~FF_CLASS ; break; + case SW_CLASS : tclFlags |= FF_CLASS ; break; + case SW_SCALAR : type = TY_SCALAR ; break; + case SW_AGGREGATE : type = TY_AGGREGATE ; break; + case SW_WINDOW : type = TY_WINDOW ; break; + } + } + if( type!=TY_SCALAR ){ + tclFlags |= FF_AGGREGATE; + }else if( tclFlags&FF_CLASS ){ + Tcl_AppendResult(interp, "-class requires -window or -aggregate", + (char *)0); + return TCL_ERROR; + }else if( pInitial ){ + Tcl_AppendResult(interp, "-initial requires -window or -aggregate", + (char *)0); + return TCL_ERROR; } pScript = objv[objc-1]; zName = Tcl_GetStringFromObj(objv[2], 0); pFunc = findSqlFunc(pDb, zName); if( pFunc==0 ) return TCL_ERROR; if( pFunc->pScript ){ Tcl_DecrRefCount(pFunc->pScript); } + if( pFunc->pInitial ){ + Tcl_DecrRefCount(pFunc->pInitial); + } pFunc->pScript = pScript; Tcl_IncrRefCount(pScript); - pFunc->useEvalObjv = safeToUseEvalObjv(interp, pScript); - rc = sqlite3_create_function(pDb->db, zName, nArg, flags, - pFunc, tclSqlFunc, 0, 0); + pFunc->pInitial = pInitial; + if( pInitial ){ + Tcl_IncrRefCount(pInitial); + } + pFunc->flags = tclFlags; + switch( type ){ + case TY_SCALAR: + rc = sqlite3_create_function(pDb->db, zName, nArg, sqlFlags, + pFunc, tclSqlFuncScalar, 0, 0); + break; + case TY_AGGREGATE: + rc = sqlite3_create_function(pDb->db, zName, nArg, sqlFlags, + pFunc, 0, tclSqlFuncStep, tclSqlFuncFinal); + break; + case TY_WINDOW: + rc = sqlite3_create_window_function(pDb->db, zName, nArg, sqlFlags, + pFunc, tclSqlFuncStep, tclSqlFuncFinal, tclSqlFuncValue, + tclSqlFuncInverse, 0); + break; + } if( rc!=SQLITE_OK ){ rc = TCL_ERROR; Tcl_SetResult(interp, (char *)sqlite3_errmsg(pDb->db), TCL_VOLATILE); } break; @@ -3679,10 +3974,21 @@ ** The EXTERN macros are required by TCL in order to work on windows. */ EXTERN int Sqlite3_Init(Tcl_Interp *interp){ int rc = Tcl_InitStubs(interp, "8.4", 0) ? TCL_OK : TCL_ERROR; if( rc==TCL_OK ){ + if( !*pFixedStrings ){ +#define FIXED_STRING(i, s) \ + (pFixedStrings[(i)] = Tcl_NewStringObj((s), sizeof((s)) - 1), \ + Tcl_IncrRefCount(pFixedStrings[(i)])) + FIXED_STRING(FS_NEW , "new" ); + FIXED_STRING(FS_STEP , "step" ); + FIXED_STRING(FS_INVERSE, "inverse"); + FIXED_STRING(FS_VALUE , "value" ); + FIXED_STRING(FS_DESTROY, "destroy"); +#undef FIXED_STRING + } Tcl_CreateObjCommand(interp, "sqlite3", (Tcl_ObjCmdProc*)DbMain, 0, 0); #ifndef SQLITE_3_SUFFIX_ONLY /* The "sqlite" alias is undocumented. It is here only to support ** legacy scripts. All new scripts should use only the "sqlite3" ** command. */ @@ -3691,12 +3997,23 @@ rc = Tcl_PkgProvide(interp, "sqlite3", PACKAGE_VERSION); } return rc; } EXTERN int Tclsqlite3_Init(Tcl_Interp *interp){ return Sqlite3_Init(interp); } -EXTERN int Sqlite3_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; } -EXTERN int Tclsqlite3_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; } +EXTERN int Sqlite3_Unload(Tcl_Interp *interp, int flags){ + int i; + if( flags==TCL_UNLOAD_DETACH_FROM_PROCESS ){ + for(i=0; i