diff --git a/manifest b/manifest index 6d41043211..f0269573f9 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Modify\slemon\sto\suse\smuch\sless\smemory\sfor\sits\sparser\stables.\s\sThis\sreduces\nthe\ssize\sof\sthe\slibrary\sby\s50K,\swhich\sis\simportant\sfor\san\sembedded\slibrary.\s(CVS\s389) -D 2002-02-23T19:39:47 +C Added\ssupport\sfor\suser-defined\snormal\sfunctions.\s\sSupport\sfor\suser-defined\naggregates\sis\spending.\s(CVS\s390) +D 2002-02-23T23:45:45 F Makefile.in 9fa4277413bf1d9cf91365f07d4108d7d87ed2af F Makefile.template 3372d45f8853afdb70bd30cc6fb50a3cd9069834 F README a4c0ba11354ef6ba0776b400d057c59da47a4cc0 @@ -23,11 +23,11 @@ F src/btree.c 495275fe14f3b718cf2f691dce979d4c0e1f8e5d F src/btree.h 8abeabfe6e0b1a990b64fa457592a6482f6674f3 F src/build.c 1da051784be0155ae579d47890db74f0186f9b9f F src/delete.c 950d8f9097361419f1963875f9943344b469cf02 -F src/expr.c 6b641c43941094a5d1f7a96657d8a34d07188856 -F src/hash.c 8f7c740ef2eaaa8decfa8751f2be30680b123e46 -F src/hash.h d1ce47900c7325af5e41c4feb4855c4bf2b841e7 +F src/expr.c 4f9db24c4e90585fd046703d4f91c10b453867fa +F src/hash.c cc259475e358baaf299b00a2c7370f2b03dda892 +F src/hash.h dca065dda89d4575f3176e75e9a3dc0f4b4fb8b9 F src/insert.c 164d2d5e943268a8ff0594e1947599e04df0ce11 -F src/main.c abc0732d4caa676ff8337f278b01f1f1b57538f5 +F src/main.c 0fa2298ab8980cb446dc81086ce36f905f607f70 F src/md5.c 52f677bfc590e09f71d07d7e327bd59da738d07c F src/os.c f6bc9b7ab530346bb7fef2ed39f2f1f214bc14ea F src/os.h a17596ecc7f38a228b83ecdb661fb03ce44726d6 @@ -39,9 +39,9 @@ F src/random.c 19e8e00fe0df32a742f115773f57651be327cabe F src/select.c 61d4a739956aaeb124cdf12c34c66e99ae34212c F src/shell.c cbf48bf0ca35c4e0d8a7d2a86f7724f52c525cd7 F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e -F src/sqlite.h.in f57074c84a2c112a5093ba7a9d9636aa9cacc87c -F src/sqliteInt.h 81dcdf77391471443d53e4b96ac5e78a10e9df4b -F src/table.c c89698bd5bb4b8d14722d6ee7e9be014c383d24a +F src/sqlite.h.in f1421919a4437a377fb712b98835a224482e776e +F src/sqliteInt.h b089e9226fbb88b25810d2f52285929dcf6999c6 +F src/table.c 203a09d5d0009eeeb1f670370d52b4ce163a3b52 F src/tclsqlite.c b9cf346e95291cb4c4f1bf5ac1d77db6b8ad023d F src/test1.c 33efd350dca27c52c58c553c04fd3a6a51f13c1f F src/test2.c d410dbd8a90faa466c3ab694fa0aa57f5a773aa6 @@ -50,8 +50,8 @@ F src/threadtest.c 81f0598e0f031c1bd506af337fdc1b7e8dff263f F src/tokenize.c 4b5d30590a744b9bb5605a92d1f620ab2e7e75af F src/update.c 18971d265b0341574b7e3f73116e7947ddab0997 F src/util.c f31f3d6198a0d1296a16f5a6ceec423a932cbbf6 -F src/vdbe.c 44832d804e109248e9e2abd40daee5f8ac735450 -F src/vdbe.h 002bb8cf884034bea25a9fe901a9c5e9d29bc045 +F src/vdbe.c 7d4c6e2f4861bed0ef5abd5d39153bfd401e30ff +F src/vdbe.h c3be021687ab2eb8517052a24e1df7e7355f0d77 F src/where.c 664be01b0ce9ffaecbde609afbd4d8d3e5ed1585 F test/all.test 7a8a8a7a579ed2bb4d8976d55402f21eacd58049 F test/bigrow.test 8ab252dba108f12ad64e337b0f2ff31a807ac578 @@ -81,7 +81,7 @@ F test/printf.test 3cb415073754cb8ff076f26173143c3cd293a9da F test/quick.test 6f023c7a73fc413e6d65b7a1879c79764038dc05 F test/quote.test 286db944717afa9a9bf829dd85e59185c65d5435 F test/rowid.test 4c55943300cddf73dd0f88d40a268cab14c83274 -F test/select1.test fd2936aa907559153c78edf2740ea65eb9a614f5 +F test/select1.test 7d5ae792d6dbfa2c1b6345a32b154b7ba8d24bbc F test/select2.test ed2c1882857106b85478f54f67000e14966be4c4 F test/select3.test 9469c332250a75a0ef1771fb5da62dc04ec77f18 F test/select4.test 29a2ffb187f3d8b6ca42a0a6b619e9cabe12e228 @@ -110,7 +110,7 @@ F www/arch.fig d5f9752a4dbf242e9cfffffd3f5762b6c63b3bcf F www/arch.png 82ef36db1143828a7abc88b1e308a5f55d4336f4 F www/arch.tcl 72a0c80e9054cc7025a50928d28d9c75c02c2b8b F www/c_interface.tcl 63efc40f09e2f0d8fea43d174103248b160fdf0e -F www/changes.tcl 4aee975940a59d43736cdd4cd352c51e1e6426ba +F www/changes.tcl 0c40569ef5c92af1fb211ea1f679113a44c60f19 F www/conflict.tcl 81dd21f9a679e60aae049e9dd8ab53d59570cda2 F www/crosscompile.tcl 3622ebbe518927a3854a12de51344673eb2dd060 F www/download.tcl a6d75b8b117cd33dcb090bef7e80d7556d28ebe0 @@ -125,7 +125,7 @@ F www/speed.tcl 83457b2bf6bb430900bd48ca3dd98264d9a916a5 F www/sqlite.tcl 8b5884354cb615049aed83039f8dfe1552a44279 F www/tclsqlite.tcl 829b393d1ab187fd7a5e978631b3429318885c49 F www/vdbe.tcl 2013852c27a02a091d39a766bc87cff329f21218 -P 8da0ac9a8bb859377613dd18f4f423eb49c7338b -R a10568441a27cde980e4058838f315f0 +P 67a135a051e7c96ddbfe85976539b4b8372c7026 +R fa8d1484ff86c96c0829999f970d3d5e U drh -Z 77767b7d4f55ba40f2761ab26d448e78 +Z f2c150ed8d00dcdf045fbfc7cf498205 diff --git a/manifest.uuid b/manifest.uuid index ed839134c2..5cdd6e2062 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -67a135a051e7c96ddbfe85976539b4b8372c7026 \ No newline at end of file +c490a1ff951c5d4a2de8e4f8d349189bfaef7f74 \ No newline at end of file diff --git a/src/expr.c b/src/expr.c index 40a4a388eb..d986c7c9a0 100644 --- a/src/expr.c +++ b/src/expr.c @@ -12,7 +12,7 @@ ** This file contains routines used for analyzing expressions and ** for generating VDBE code that evaluates expressions in SQLite. ** -** $Id: expr.c,v 1.42 2002/02/23 02:32:10 drh Exp $ +** $Id: expr.c,v 1.43 2002/02/23 23:45:45 drh Exp $ */ #include "sqliteInt.h" @@ -606,29 +606,40 @@ int sqliteExprCheck(Parse *pParse, Expr *pExpr, int allowAgg, int *pIsAgg){ int no_such_func = 0; int too_many_args = 0; int too_few_args = 0; + int wrong_num_args = 0; int is_agg = 0; int i; pExpr->iColumn = id; switch( id ){ - case FN_Unknown: { - no_such_func = 1; + case FN_Unknown: { + UserFunc *pUser = sqliteFindUserFunction(pParse->db, + pExpr->token.z, pExpr->token.n, n, 0); + if( pUser==0 ){ + pUser = sqliteFindUserFunction(pParse->db, + pExpr->token.z, pExpr->token.n, -1, 0); + if( pUser==0 ){ + no_such_func = 1; + }else{ + wrong_num_args = 1; + } + }else{ + is_agg = pUser->xFunc==0; + } break; } case FN_Count: { - no_such_func = !allowAgg; too_many_args = n>1; is_agg = 1; break; } case FN_Max: case FN_Min: { - too_few_args = allowAgg ? n<1 : n<2; + too_few_args = n<1; is_agg = n==1; break; } case FN_Avg: case FN_Sum: { - no_such_func = !allowAgg; too_many_args = n>1; too_few_args = n<1; is_agg = 1; @@ -652,7 +663,13 @@ int sqliteExprCheck(Parse *pParse, Expr *pExpr, int allowAgg, int *pIsAgg){ } default: break; } - if( no_such_func ){ + if( is_agg && !allowAgg ){ + sqliteSetNString(&pParse->zErrMsg, "misuse of aggregate function ", -1, + pExpr->token.z, pExpr->token.n, "()", 2, 0); + pParse->nErr++; + nErr++; + is_agg = 0; + }else if( no_such_func ){ sqliteSetNString(&pParse->zErrMsg, "no such function: ", -1, pExpr->token.z, pExpr->token.n, 0); pParse->nErr++; @@ -667,6 +684,12 @@ int sqliteExprCheck(Parse *pParse, Expr *pExpr, int allowAgg, int *pIsAgg){ pExpr->token.z, pExpr->token.n, "()", 2, 0); pParse->nErr++; nErr++; + }else if( wrong_num_args ){ + sqliteSetNString(&pParse->zErrMsg, + "wrong number of arguments to function ",-1, + pExpr->token.z, pExpr->token.n, "()", 2, 0); + pParse->nErr++; + nErr++; } if( is_agg ) pExpr->op = TK_AGG_FUNCTION; if( is_agg && pIsAgg ) *pIsAgg = 1; @@ -886,6 +909,18 @@ void sqliteExprCode(Parse *pParse, Expr *pExpr){ sqliteVdbeAddOp(v, OP_Substr, 0, 0); break; } + case FN_Unknown: { + UserFunc *pUser; + pUser = sqliteFindUserFunction(pParse->db, + pExpr->token.z, pExpr->token.n, pList->nExpr, 0); + assert( pUser!=0 ); + for(i=0; inExpr; i++){ + sqliteExprCode(pParse, pList->a[i].pExpr); + } + sqliteVdbeAddOp(v, OP_UserFunc, pList->nExpr, 0); + sqliteVdbeChangeP3(v, -1, (char*)pUser->xFunc, P3_POINTER); + break; + } default: { /* Can't happen! */ break; @@ -1245,3 +1280,52 @@ int sqliteExprAnalyzeAggregates(Parse *pParse, Expr *pExpr){ } return nErr; } + +/* +** Locate a user function given a name and a number of arguments. +** Return a pointer to the UserFunc structure that defines that +** function, or return NULL if the function does not exist. +** +** If the createFlag argument is true, then a new (blank) UserFunc +** structure is created and liked into the "db" structure if a +** no matching function previously existed. When createFlag is true +** and the nArg parameter is -1, then only a function that accepts +** any number of arguments will be returned. +** +** If createFlag is false and nArg is -1, then the first valid +** function found is returned. A function is valid if either xFunc +** or xStep is non-zero. +*/ +UserFunc *sqliteFindUserFunction( + sqlite *db, /* An open database */ + const char *zName, /* Name of the function. Not null-terminated */ + int nName, /* Number of characters in the name */ + int nArg, /* Number of arguments. -1 means any number */ + int createFlag /* Create new entry if true and does not otherwise exist */ +){ + UserFunc *pFirst, *p, *pMaybe; + pFirst = p = (UserFunc*)sqliteHashFind(&db->userFunc, zName, nName); + if( !createFlag && nArg<0 ){ + while( p && p->xFunc==0 && p->xStep==0 ){ p = p->pNext; } + return p; + } + pMaybe = 0; + while( p && p->nArg!=nArg ){ + if( p->nArg<0 && !createFlag && (p->xFunc || p->xStep) ) pMaybe = p; + p = p->pNext; + } + if( p && !createFlag && p->xFunc==0 && p->xStep==0 ){ + return 0; + } + if( p==0 && pMaybe ){ + assert( createFlag==0 ); + return pMaybe; + } + if( p==0 && createFlag ){ + p = sqliteMalloc( sizeof(*p) ); + p->nArg = nArg; + p->pNext = pFirst; + sqliteHashInsert(&db->userFunc, zName, nName, (void*)p); + } + return p; +} diff --git a/src/hash.c b/src/hash.c index 965308e4f5..ddac9c0482 100644 --- a/src/hash.c +++ b/src/hash.c @@ -12,7 +12,7 @@ ** This is the implementation of generic hash-tables ** used in SQLite. ** -** $Id: hash.c,v 1.6 2002/01/14 09:28:20 drh Exp $ +** $Id: hash.c,v 1.7 2002/02/23 23:45:45 drh Exp $ */ #include "sqliteInt.h" #include @@ -286,7 +286,7 @@ void *sqliteHashFind(const Hash *pH, const void *pKey, int nKey){ ** If the "data" parameter to this function is NULL, then the ** element corresponding to "key" is removed from the hash table. */ -void *sqliteHashInsert(Hash *pH, void *pKey, int nKey, void *data){ +void *sqliteHashInsert(Hash *pH, const void *pKey, int nKey, void *data){ int hraw; /* Raw hash value of the key */ int h; /* the hash of the key modulo hash table size */ HashElem *elem; /* Used to loop thru the element list */ @@ -320,7 +320,7 @@ void *sqliteHashInsert(Hash *pH, void *pKey, int nKey, void *data){ } memcpy((void*)new_elem->pKey, pKey, nKey); }else{ - new_elem->pKey = pKey; + new_elem->pKey = (const void*)pKey; } new_elem->nKey = nKey; pH->count++; diff --git a/src/hash.h b/src/hash.h index d5c7f62510..e51396e356 100644 --- a/src/hash.h +++ b/src/hash.h @@ -12,7 +12,7 @@ ** This is the header file for the generic hash-table implemenation ** used in SQLite. ** -** $Id: hash.h,v 1.3 2002/02/03 03:34:09 drh Exp $ +** $Id: hash.h,v 1.4 2002/02/23 23:45:45 drh Exp $ */ #ifndef _SQLITE_HASH_H_ #define _SQLITE_HASH_H_ @@ -79,7 +79,7 @@ struct HashElem { ** Access routines. To delete, insert a NULL pointer. */ void sqliteHashInit(Hash*, int keytype, int copyKey); -void *sqliteHashInsert(Hash*, void *pKey, int nKey, void *pData); +void *sqliteHashInsert(Hash*, const void *pKey, int nKey, void *pData); void *sqliteHashFind(const Hash*, const void *pKey, int nKey); void sqliteHashClear(Hash*); diff --git a/src/main.c b/src/main.c index 4969f3551f..4253ae1251 100644 --- a/src/main.c +++ b/src/main.c @@ -14,8 +14,9 @@ ** other files are for internal use by SQLite and should not be ** accessed by users of the library. ** -** $Id: main.c,v 1.61 2002/02/21 12:01:27 drh Exp $ +** $Id: main.c,v 1.62 2002/02/23 23:45:45 drh Exp $ */ +#include #include "sqliteInt.h" #include "os.h" @@ -292,6 +293,30 @@ const char sqlite_encoding[] = "UTF-8"; const char sqlite_encoding[] = "iso8859"; #endif +/* +** Implementation of the upper() and lower() SQL functions. +*/ +static void upperFunc(void *context, int argc, const char **argv){ + char *z; + int i; + if( argc<1 || argv[0]==0 ) return; + z = sqlite_set_result_string(context, argv[0], -1); + if( z==0 ) return; + for(i=0; z[i]; i++){ + if( islower(z[i]) ) z[i] = toupper(z[i]); + } +} +static void lowerFunc(void *context, int argc, const char **argv){ + char *z; + int i; + if( argc<1 || argv[0]==0 ) return; + z = sqlite_set_result_string(context, argv[0], -1); + if( z==0 ) return; + for(i=0; z[i]; i++){ + if( isupper(z[i]) ) z[i] = tolower(z[i]); + } +} + /* ** Open a new SQLite database. Construct an "sqlite" structure to define ** the state of this database and return a pointer to that structure. @@ -313,6 +338,9 @@ sqlite *sqlite_open(const char *zFilename, int mode, char **pzErrMsg){ sqliteHashInit(&db->idxHash, SQLITE_HASH_STRING, 0); sqliteHashInit(&db->tblDrop, SQLITE_HASH_POINTER, 0); sqliteHashInit(&db->idxDrop, SQLITE_HASH_POINTER, 0); + sqliteHashInit(&db->userFunc, SQLITE_HASH_STRING, 1); + sqlite_create_function(db, "upper", 1, upperFunc); + sqlite_create_function(db, "lower", 1, lowerFunc); db->onError = OE_Default; db->priorNewRowid = 0; @@ -408,11 +436,20 @@ int sqlite_last_insert_rowid(sqlite *db){ ** Close an existing SQLite database */ void sqlite_close(sqlite *db){ + HashElem *i; sqliteBtreeClose(db->pBe); clearHashTable(db, 0); if( db->pBeTemp ){ sqliteBtreeClose(db->pBeTemp); } + for(i=sqliteHashFirst(&db->userFunc); i; i=sqliteHashNext(i)){ + UserFunc *pFunc, *pNext; + for(pFunc = (UserFunc*)sqliteHashData(i); pFunc; pFunc=pNext){ + pNext = pFunc->pNext; + sqliteFree(pFunc); + } + } + sqliteHashClear(&db->userFunc); sqliteFree(db); } @@ -613,3 +650,47 @@ void sqlite_freemem(void *p){ free(p); } */ const char *sqlite_libversion(void){ return sqlite_version; } const char *sqlite_libencoding(void){ return sqlite_encoding; } + +/* +** Create new user-defined functions. The sqlite_create_function() +** routine creates a regular function and sqlite_create_aggregate() +** creates an aggregate function. +** +** Passing a NULL xFunc argument or NULL xStep and xFinalize arguments +** disables the function. Calling sqlite_create_function() with the +** same name and number of arguments as a prior call to +** sqlite_create_aggregate() disables the prior call to +** sqlite_create_aggregate(), and vice versa. +** +** If nArg is -1 it means that this function will accept any number +** of arguments, including 0. +*/ +int sqlite_create_function( + sqlite *db, /* Add the function to this database connection */ + const char *zName, /* Name of the function to add */ + int nArg, /* Number of arguments */ + void (*xFunc)(void*,int,const char**) /* Implementation of the function */ +){ + UserFunc *p; + if( db==0 || zName==0 ) return 1; + p = sqliteFindUserFunction(db, zName, strlen(zName), nArg, 1); + p->xFunc = xFunc; + p->xStep = 0; + p->xFinalize = 0; + return 0; +} +int sqlite_create_aggregate( + sqlite *db, /* Add the function to this database connection */ + const char *zName, /* Name of the function to add */ + int nArg, /* Number of arguments */ + void *(*xStep)(void*,int,const char**), /* The step function */ + void (*xFinalize)(void*,void*) /* The finalizer */ +){ + UserFunc *p; + if( db==0 || zName==0 ) return 1; + p = sqliteFindUserFunction(db, zName, strlen(zName), nArg, 1); + p->xFunc = 0; + p->xStep = xStep; + p->xFinalize = xFinalize; + return 0; +} diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 12d07ac182..14c5f55792 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -12,7 +12,7 @@ ** This header file defines the interface that the SQLite library ** presents to client programs. ** -** @(#) $Id: sqlite.h.in,v 1.25 2002/01/16 21:00:27 drh Exp $ +** @(#) $Id: sqlite.h.in,v 1.26 2002/02/23 23:45:45 drh Exp $ */ #ifndef _SQLITE_H_ #define _SQLITE_H_ @@ -362,6 +362,41 @@ int sqlite_get_table_vprintf( va_list ap /* Arguments to the format string */ ); +/* +** Use the following routines to create new user-defined functions. See +** the documentation for details. +*/ +int sqlite_create_function( + sqlite*, /* Database where the new function is registered */ + const char *zName, /* Name of the new function */ + int nArg, /* Number of arguments. -1 means any number */ + void (*xFunc)(void*,int,const char**) /* C code to implement the function */ +); +int sqlite_create_aggregate( + sqlite*, /* Database where the new function is registered */ + const char *zName, /* Name of the function */ + int nArg, /* Number of arguments */ + void *(*xStep)(void*,int,const char**), /* Called for each row */ + void (*xFinalize)(void*,void*) /* Called once to get final result */ +); + +/* +** The user function implementations call one of the following four routines +** in order to return their results. The first parameter to each of these +** routines is a copy of the first argument to xFunc() or the second argument +** to xFinalize(). The second parameter to these routines is the result +** to be returned. A NULL can be passed as the second parameter to +** sqlite_set_result_string() in order to return a NULL result. +** +** The 3rd argument to _string and _error is the number of characters to +** take from the string. If this argument is negative, then all characters +** up to and including the first '\000' are used. +*/ +char *sqlite_set_result_string(void*,const char*,int); +void sqlite_set_result_int(void*,int); +void sqlite_set_result_double(void*,double); +void sqlite_set_result_error(void*,const char*,int); + #ifdef __cplusplus } /* End of the 'extern "C"' block */ #endif diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 59d17221b3..82d7121782 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -11,7 +11,7 @@ ************************************************************************* ** Internal interface definitions for SQLite. ** -** @(#) $Id: sqliteInt.h,v 1.90 2002/02/23 02:32:10 drh Exp $ +** @(#) $Id: sqliteInt.h,v 1.91 2002/02/23 23:45:45 drh Exp $ */ #include "sqlite.h" #include "hash.h" @@ -130,7 +130,7 @@ extern int sqlite_iMallocFail; /* Fail sqliteMalloc() after this many calls */ /* ** Integer identifiers for built-in SQL functions. */ -#define FN_Unknown 0 +#define FN_Unknown 0 /* Not a built-in. Might be user defined */ #define FN_Count 1 #define FN_Min 2 #define FN_Max 3 @@ -158,6 +158,7 @@ typedef struct WhereInfo WhereInfo; typedef struct WhereLevel WhereLevel; typedef struct Select Select; typedef struct AggExpr AggExpr; +typedef struct UserFunc UserFunc; /* ** Each database is an instance of the following structure @@ -176,6 +177,7 @@ struct sqlite { Hash idxHash; /* All (named) indices indexed by name */ Hash tblDrop; /* Uncommitted DROP TABLEs */ Hash idxDrop; /* Uncommitted DROP INDEXs */ + Hash userFunc; /* User defined functions */ int lastRowid; /* ROWID of most recent insert */ int priorNewRowid; /* Last randomly generated ROWID */ int onError; /* Default conflict algorithm */ @@ -198,9 +200,18 @@ struct sqlite { #define SQLITE_ResultDetails 0x00000100 /* Details added to result set */ /* -** Current file format version +** Each user-defined function is defined by an instance of the following +** structure. A pointer to this structure is stored in the sqlite.userFunc +** hash table. When multiple functions have the same name, the hash table +** points to a linked list of these structures. */ -#define SQLITE_FileFormat 2 +struct UserFunc { + void (*xFunc)(void*,int,const char**); /* Regular function */ + void *(*xStep)(void*,int,const char**); /* Aggregate function step */ + void (*xFinalize)(void*,void*); /* Aggregate function finializer */ + int nArg; /* Number of arguments */ + UserFunc *pNext; /* Next function with same name */ +}; /* ** information about each column of an SQL table is held in an instance @@ -634,3 +645,4 @@ void sqliteEndWriteOperation(Parse*); void sqliteExprMoveStrings(Expr*, int); void sqliteExprListMoveStrings(ExprList*, int); void sqliteSelectMoveStrings(Select*, int); +UserFunc *sqliteFindUserFunction(sqlite*,const char*,int,int,int); diff --git a/src/table.c b/src/table.c index e957cf3d56..cc01f7c2be 100644 --- a/src/table.c +++ b/src/table.c @@ -17,6 +17,7 @@ ** if they are not used. */ #include +#include #include "sqlite.h" /* diff --git a/src/vdbe.c b/src/vdbe.c index acacaa91d3..5d6faa929b 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -30,7 +30,7 @@ ** But other routines are also provided to help in building up ** a program instruction by instruction. ** -** $Id: vdbe.c,v 1.121 2002/02/21 12:01:27 drh Exp $ +** $Id: vdbe.c,v 1.122 2002/02/23 23:45:45 drh Exp $ */ #include "sqliteInt.h" #include @@ -138,6 +138,16 @@ typedef struct Mem Mem; #define STK_Dyn 0x0010 /* Need to call sqliteFree() on zStack[*] */ #define STK_Static 0x0020 /* zStack[] points to a static string */ +/* +** The "context" argument for a user-defined function. +*/ +struct UserFuncContext { + Stack s; /* Small string, integer, and floating point values go here */ + char *z; /* Space for holding dynamic string results */ + int isError; /* Set to true for an error */ +}; +typedef struct UserFuncContext UserFuncContext; + /* ** An Agg structure describes an Aggregator. Each Agg consists of ** zero or more Aggregator elements (AggElem). Each AggElem contains @@ -492,6 +502,73 @@ void sqliteVdbeCompressSpace(Vdbe *p, int addr){ z[j] = 0; } +/* +** The following group or routines are employed by user-defined functions +** to return their results. +** +** The sqlite_set_result_string() routine can be used to return a string +** value or to return a NULL. To return a NULL, pass in NULL for zResult. +** A copy is made of the string before this routine returns so it is safe +** to pass in a ephemeral string. +** +** sqlite_set_result_error() works like sqlite_set_result_string() except +** that it signals a fatal error. The string argument, if any, is the +** error message. If the argument is NULL a generic substitute error message +** is used. +** +** The sqlite_set_result_int() and sqlite_set_result_double() set the return +** value of the user function to an integer or a double. +*/ +char *sqlite_set_result_string(void *context, const char *zResult, int n){ + UserFuncContext *p = (UserFuncContext*)context; + if( p->s.flags & STK_Dyn ){ + sqliteFree(p->z); + } + if( zResult==0 ){ + p->s.flags = STK_Null; + n = 0; + p->z = 0; + }else{ + if( n<0 ) n = strlen(zResult); + if( ns.z, zResult, n); + p->s.z[n] = 0; + p->s.flags = STK_Str; + p->z = p->s.z; + }else{ + p->z = sqliteMalloc( n+1 ); + if( p->z ){ + memcpy(p->z, zResult, n); + p->z[n] = 0; + } + p->s.flags = STK_Str | STK_Dyn; + } + } + p->s.n = n; + return p->z; +} +void sqlite_set_result_int(void *context, int iResult){ + UserFuncContext *p = (UserFuncContext*)context; + if( p->s.flags & STK_Dyn ){ + sqliteFree(p->z); + } + p->s.i = iResult; + p->s.flags = STK_Int; +} +void sqlite_set_result_double(void *context, double rResult){ + UserFuncContext *p = (UserFuncContext*)context; + if( p->s.flags & STK_Dyn ){ + sqliteFree(p->z); + } + p->s.r = rResult; + p->s.flags = STK_Real; +} +void sqlite_set_result_error(void *context, const char *zMsg, int n){ + UserFuncContext *p = (UserFuncContext*)context; + sqlite_set_result_string(context, zMsg, n); + p->isError = 1; +} + /* ** Reset an Agg structure. Delete all its contents. */ @@ -891,7 +968,7 @@ static char *zOpName[] = { 0, "Le", "Gt", "Ge", "IsNull", "NotNull", "Negative", "And", "Or", "Not", "Concat", "Noop", "Strlen", - "Substr", "Limit", + "Substr", "UserFunc", "UserAgg", "Limit", }; /* @@ -1694,6 +1771,47 @@ case OP_Max: { break; } +/* Opcode: UserFunc P1 * P3 +** +** Invoke a user function (P3 is a pointer to the function) with +** P1 string arguments taken from the stack. Pop all arguments from +** the stack and push back the result. +*/ +case OP_UserFunc: { + int n, i; + UserFuncContext ctx; + void (*xFunc)(void*,int,const char**); + n = pOp->p1; + VERIFY( if( n<=0 ) goto bad_instruction; ) + VERIFY( if( p->tos+1tos-n+1; i<=p->tos; i++){ + if( (aStack[i].flags & STK_Null)==0 ){ + if( Stringify(p, i) ) goto no_mem; + } + } + xFunc = (void(*)(void*,int,const char**))pOp->p3; + ctx.s.flags = STK_Null; + ctx.isError = 0; + xFunc((void*)&ctx, n, (const char**)&zStack[p->tos-n+1]); + PopStack(p, n); + VERIFY( NeedStack(p, p->tos+1); ) + p->tos++; + aStack[p->tos] = ctx.s; + if( ctx.s.flags & STK_Dyn ){ + zStack[p->tos] = ctx.z; + }else if( ctx.s.flags & STK_Str ){ + zStack[p->tos] = aStack[p->tos].z; + }else{ + zStack[p->tos] = 0; + } + if( ctx.isError ){ + sqliteSetString(pzErrMsg, + zStack[p->tos] ? zStack[p->tos] : "user function error", 0); + rc = SQLITE_ERROR; + } + break; +} + /* Opcode: BitAnd * * * ** ** Pop the top two elements from the stack. Convert both elements diff --git a/src/vdbe.h b/src/vdbe.h index 4bde7b342c..a3b66a29c6 100644 --- a/src/vdbe.h +++ b/src/vdbe.h @@ -15,7 +15,7 @@ ** or VDBE. The VDBE implements an abstract machine that runs a ** simple program to access and modify the underlying database. ** -** $Id: vdbe.h,v 1.44 2002/02/21 12:01:28 drh Exp $ +** $Id: vdbe.h,v 1.45 2002/02/23 23:45:46 drh Exp $ */ #ifndef _SQLITE_VDBE_H_ #define _SQLITE_VDBE_H_ @@ -198,13 +198,14 @@ typedef struct VdbeOp VdbeOp; #define OP_Not 113 #define OP_Concat 114 #define OP_Noop 115 - #define OP_Strlen 116 #define OP_Substr 117 +#define OP_UserFunc 118 +#define OP_UserAgg 119 -#define OP_Limit 118 +#define OP_Limit 120 -#define OP_MAX 118 +#define OP_MAX 120 /* ** Prototypes for the VDBE interface. See comments on the implementation diff --git a/test/select1.test b/test/select1.test index 399d64bdcb..b1b18bf675 100644 --- a/test/select1.test +++ b/test/select1.test @@ -11,7 +11,7 @@ # This file implements regression tests for SQLite library. The # focus of this file is testing the SELECT statement. # -# $Id: select1.test,v 1.18 2002/01/22 14:11:30 drh Exp $ +# $Id: select1.test,v 1.19 2002/02/23 23:45:47 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -183,7 +183,7 @@ do_test select1-2.19 { do_test select1-2.20 { set v [catch {execsql {SELECT SUM(min(f1)) FROM test1}} msg] lappend v $msg -} {1 {too few arguments to function min()}} +} {1 {misuse of aggregate function min()}} # WHERE clause expressions # @@ -222,7 +222,7 @@ do_test select1-3.8 { do_test select1-3.9 { set v [catch {execsql {SELECT f1 FROM test1 WHERE count(f1,f2)!=11}} msg] lappend v $msg -} {1 {no such function: count}} +} {1 {misuse of aggregate function count()}} # ORDER BY expressions # @@ -241,7 +241,7 @@ do_test select1-4.3 { do_test select1-4.4 { set v [catch {execsql {SELECT f1 FROM test1 ORDER BY min(f1)}} msg] lappend v $msg -} {1 {too few arguments to function min()}} +} {1 {misuse of aggregate function min()}} do_test select1-4.5 { catchsql { SELECT f1 FROM test1 ORDER BY 8.4; diff --git a/www/changes.tcl b/www/changes.tcl index 64e5dcd7d9..1e22c76dd8 100644 --- a/www/changes.tcl +++ b/www/changes.tcl @@ -25,6 +25,9 @@ chng {2002 Feb * (2.3.4)} {
  • Automatically generated ROWIDs are now sequential.
  • Do not allow dot-commands of the command-line shell to occur in the middle of a real SQL command.
  • +
  • Modifications to the "lemon" parser generator so that the parser tables + are 4 times smaller.
  • +
  • Added support for user-defined functions implemented in C.
  • } chng {2002 Feb 18 (2.3.3)} {