From 676f17a5708895bca3389873d23cb547462ee26e Mon Sep 17 00:00:00 2001 From: drh <> Date: Fri, 7 Jul 2023 16:58:20 +0000 Subject: [PATCH] Proof of concept for a new sqlite3_function_needed() interface. FossilOrigin-Name: cd67edc032983b5111ffcf6c73ca1308ef67cf2f16baa25788121827b3038d2f --- manifest | 25 +++++++++-------- manifest.uuid | 2 +- src/callback.c | 21 ++++++++++++++ src/main.c | 25 +++++++++++++++++ src/shell.c.in | 73 +++++++++++++++++++++++++++++++++++------------- src/sqlite.h.in | 48 +++++++++++++++++++++++++++++++ src/sqlite3ext.h | 4 +++ src/sqliteInt.h | 3 ++ 8 files changed, 169 insertions(+), 32 deletions(-) diff --git a/manifest b/manifest index e66f836455..8be6987b7a 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C For\sthe\s"Valgrind"\stest\sscenario,\sset\sLONGDOUBLE_TYPE=double\sas\svalgrind\ndoes\snot\scorrectly\semulated\sextended\sprecision\son\sx64. -D 2023-07-07T12:18:26.936 +C Proof\sof\sconcept\sfor\sa\snew\ssqlite3_function_needed()\sinterface. +D 2023-07-07T16:58:20.565 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -580,7 +580,7 @@ F src/btree.c c0c93b6cb4dc133b528c1290bb4ad0f2414452f9a5758ff2b106af718874f39e F src/btree.h aa354b9bad4120af71e214666b35132712b8f2ec11869cb2315c52c81fad45cc F src/btreeInt.h 3b4eff7155c0cea6971dc51f62e3529934a15a6640ec607dd42a767e379cb3a9 F src/build.c a8ae3b32d9aa9bbd2c0e97d7c0dd80def9fbca408425de1608f57ee6f47f45f4 -F src/callback.c db3a45e376deff6a16c0058163fe0ae2b73a2945f3f408ca32cf74960b28d490 +F src/callback.c 3520e686ab1d427f33cffb46d554d0a0a2a588866a195c69d1a17103b67dde18 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e F src/ctime.c 20507cc0b0a6c19cd882fcd0eaeda32ae6a4229fb4b024cfdf3183043d9b703d F src/date.c f73f203b3877cef866c60ab402aec2bf89597219b60635cf50cbe3c5e4533e94 @@ -600,7 +600,7 @@ F src/insert.c 3f0a94082d978bbdd33c38fefea15346c6c6bffb70bc645a71dc0f1f87dd3276 F src/json.c 14c474fb1249a46eb44e878e2361f36abfe686b134039b0d1883d93d61505b4a F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/loadext.c 176d6b2cb18a6ad73b133db17f6fc351c4d9a2d510deebdb76c22bde9cfd1465 -F src/main.c 512b1d45bc556edf4471a845afb7ba79e64bd5b832ab222dc195c469534cd002 +F src/main.c d869b4032a2717b24ad0fd82266ea4d79304a25d942a4b332ff307009f6f10e3 F src/malloc.c 47b82c5daad557d9b963e3873e99c22570fb470719082c6658bf64e3012f7d23 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c 3bb59158c38e05f6270e761a9f435bf19827a264c13d1631c58b84bdc96d73b2 @@ -638,11 +638,11 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 37953a5f36c60bea413c3c04efcd433b6177009f508ef2ace0494728912fe2e9 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/select.c 3ab1186290a311a8ceed1286c0e286209f7fe97b2d02c7593258004ce295dd88 -F src/shell.c.in d320d8a13636de06d777cc1eab981caca304e175464e98183cf4ea68d93db818 -F src/sqlite.h.in f999ef3642f381d69679b2516b430dbcb6c5a2a951b7f5e43dc4751b474a5774 +F src/shell.c.in d6c9ac6ab2c56eac0009ad383f6fd31a410d4cfa04ddf5875496207773fc60b1 +F src/sqlite.h.in 287bce997bc287c251558b41ffb4ec025b98e7d0c74e3bf22c4919d2edb6d44c F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 -F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4 -F src/sqliteInt.h f6c5470b7db42318a3de1115757e94b76570ad7697ac547823e01f1166756f1d +F src/sqlite3ext.h 51ee0eff1f843bd233c3441d220812964d2166f3c5e5714e7a3d847a778310c4 +F src/sqliteInt.h 3323b237e3f951a4e0297ce797fb7e65bade820f04aa90876a3cf7c1eee029eb F src/sqliteLimit.h 33b1c9baba578d34efe7dfdb43193b366111cdf41476b1e82699e14c11ee1fb6 F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@ -2043,8 +2043,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 64e6bd1c25d8e1dbfe511cba1921ff052c0fa4fe410fc9ce4435700a70cb88b1 -R 938acc948a8d28aa6e5bbb237d21a380 +P d3532eaed1cc671d8149b1bd34ead2a88fb83502a2898a5f60aea0a7daf18958 +R d4ed9da3aa6365c836a379cf03c29a8d +T *branch * need-function +T *sym-need-function * +T -sym-trunk * U drh -Z 95c647057214d80d38fbb5e9ad4c9cbc +Z e80e56952de95f04df4da782eb785cf5 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 7718a55d23..0b24384b5f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d3532eaed1cc671d8149b1bd34ead2a88fb83502a2898a5f60aea0a7daf18958 \ No newline at end of file +cd67edc032983b5111ffcf6c73ca1308ef67cf2f16baa25788121827b3038d2f \ No newline at end of file diff --git a/src/callback.c b/src/callback.c index c36d51a4ec..c9e1a892be 100644 --- a/src/callback.c +++ b/src/callback.c @@ -474,7 +474,28 @@ FuncDef *sqlite3FindFunction( if( pBest && (pBest->xSFunc || createFlag) ){ return pBest; + }else if( db->xFuncNeeded!=0 ){ + int rc; + int (*xFN)(void*,sqlite3*,const char*,int,int) = db->xFuncNeeded; + db->xFuncNeeded = 0; + rc = xFN(db->pFuncNeededArg,db,zName,nArg,enc); + db->xFuncNeeded = xFN; + pBest = 0; + bestScore = 0; + if( rc==SQLITE_OK ){ + p = (FuncDef*)sqlite3HashFind(&db->aFunc,zName); + while( p ){ + int score = matchQuality(p, nArg, enc); + if( score>bestScore ){ + pBest = p; + bestScore = score; + } + p = p->pNext; + } + return pBest; + } } + return 0; } diff --git a/src/main.c b/src/main.c index 8f21af13ee..355b0bdb95 100644 --- a/src/main.c +++ b/src/main.c @@ -1219,6 +1219,9 @@ static int sqlite3Close(sqlite3 *db, int forceZombie){ db->trace.xV2(SQLITE_TRACE_CLOSE, db->pTraceArg, db, 0); } + /* Free the function-needed payload pointer */ + if( db->xFuncNeededFree ) db->xFuncNeededFree(db->pFuncNeededArg); + /* Force xDisconnect calls on all virtual tables */ disconnectAllVtab(db); @@ -3705,6 +3708,28 @@ int sqlite3_collation_needed16( } #endif /* SQLITE_OMIT_UTF16 */ +/* +** Register a collation sequence factory callback with the database handle +** db. Replace any previously installed collation sequence factory. +*/ +int sqlite3_function_needed( + sqlite3 *db, + void *pFuncNeededArg, + int (*xFuncNeeded)(void*,sqlite3*,const char*,int,int), + void (*xFuncNeededFree)(void*) +){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + sqlite3_mutex_enter(db->mutex); + if( db->xFuncNeededFree ) db->xFuncNeededFree(db->pFuncNeededArg); + db->pFuncNeededArg = pFuncNeededArg; + db->xFuncNeeded = xFuncNeeded; + db->xFuncNeededFree = xFuncNeededFree; + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; +} + #ifndef SQLITE_OMIT_DEPRECATED /* ** This function is now an anachronism. It used to be used to recover from a diff --git a/src/shell.c.in b/src/shell.c.in index 9165f21f71..5382c2b24c 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -5383,6 +5383,57 @@ static void shellUSleepFunc( #define OPEN_DB_KEEPALIVE 0x001 /* Return after error if true */ #define OPEN_DB_ZIPFILE 0x002 /* Open as ZIP if name matches *.zip */ +/* +** Unknown function callback +*/ +static int shellUnknownFunction( + void *pUserData, + sqlite3 *db, + const char *zFunc, + int nArg, + int enc +){ + ShellState *p = (ShellState*)pUserData; + (void)enc; + (void)nArg; + if( sqlite3_stricmp(zFunc,"strtod")==0 ){ + sqlite3_create_function(db, "strtod", 1, SQLITE_UTF8, 0, + shellStrtod, 0, 0); + }else + if( sqlite3_stricmp(zFunc,"dtostr")==0 ){ + sqlite3_create_function(db, "dtostr", 1, SQLITE_UTF8, 0, + shellDtostr, 0, 0); + sqlite3_create_function(db, "dtostr", 2, SQLITE_UTF8, 0, + shellDtostr, 0, 0); + }else + if( sqlite3_stricmp(zFunc,"shell_add_schema")==0 ){ + sqlite3_create_function(db, "shell_add_schema", 3, SQLITE_UTF8, 0, + shellAddSchemaName, 0, 0); + }else + if( sqlite3_stricmp(zFunc,"shell_module_schema")==0 ){ + sqlite3_create_function(db, "shell_module_schema", 1, SQLITE_UTF8, 0, + shellModuleSchema, 0, 0); + }else + if( sqlite3_stricmp(zFunc,"shell_putsnl")==0 ){ + sqlite3_create_function(db, "shell_putsnl", 1, SQLITE_UTF8, p, + shellPutsFunc, 0, 0); + }else + if( sqlite3_stricmp(zFunc,"usleep")==0 ){ + sqlite3_create_function(db, "usleep",1,SQLITE_UTF8,0, + shellUSleepFunc, 0, 0); + } +#ifndef SQLITE_NOHAVE_SYSTEM + else + if( sqlite3_stricmp(zFunc,"edit")==0 ){ + sqlite3_create_function(db, "edit", 1, SQLITE_UTF8, 0, + editFunc, 0, 0); + sqlite3_create_function(db, "edit", 2, SQLITE_UTF8, 0, + editFunc, 0, 0); + } +#endif + return SQLITE_OK; +} + /* ** Make sure the database is open. If it is not, then open it. If ** the database fails to open, print an error message and exit. @@ -5503,27 +5554,9 @@ static void open_db(ShellState *p, int openFlags){ } #endif - sqlite3_create_function(p->db, "strtod", 1, SQLITE_UTF8, 0, - shellStrtod, 0, 0); - sqlite3_create_function(p->db, "dtostr", 1, SQLITE_UTF8, 0, - shellDtostr, 0, 0); - sqlite3_create_function(p->db, "dtostr", 2, SQLITE_UTF8, 0, - shellDtostr, 0, 0); - sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0, - shellAddSchemaName, 0, 0); - sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0, - shellModuleSchema, 0, 0); - sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p, - shellPutsFunc, 0, 0); - sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0, - shellUSleepFunc, 0, 0); -#ifndef SQLITE_NOHAVE_SYSTEM - sqlite3_create_function(p->db, "edit", 1, SQLITE_UTF8, 0, - editFunc, 0, 0); - sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0, - editFunc, 0, 0); -#endif + sqlite3_function_needed(p->db, p, shellUnknownFunction, 0); + if( p->openMode==SHELL_OPEN_ZIPFILE ){ char *zSql = sqlite3_mprintf( "CREATE VIRTUAL TABLE zip USING zipfile(%Q);", zDbFilename); diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 59d9986c52..dfe3d97de4 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -6206,6 +6206,54 @@ int sqlite3_create_collation16( int(*xCompare)(void*,int,const void*,int,const void*) ); +/* +** CAPI3REF: Function Needed Callbacks +** METHOD: sqlite3 +** +** The sqlite3_function_needed(D,P,C,X) interface registers a callback +** function C which is invoked whenever the SQL parser encounters an +** unknown SQL function. The callback may optionally register a suitable +** implementation for that unknown function (using [sqlite3_create_function()] +** or similar), in which case parsing will continue normally. In this way, +** applications with hundreds or thousands of seldom-used application defined +** SQL functions can avoid having register them at start-up and instead +** register each function if and only if it it actually gets used. +** +** ^If the callback function C has been registered using the +** sqlite3_function_needed(D,P,C,X), then whenever the SQL parser encounters +** an unknown SQL function, it will invoke the C callback with 5 arguments: +** P, D, F, N, E. P is a copy of the generic pointer P parameter to +** the original sqlite3_function_needed() call that registered the +** callback. D is the database connection. F is the name of the unknown +** function (UTF-8 encoded and zero-terminated but in an arbitrary case). +** N is the number of arguments on the F function. +** E is the preferred text encoding. +** +** If the C callback registers a new function with the correct +** name and number of arguments and if the callback returns SQLITE_OK, then +** the newly registered function is used and parsing continues. If an +** appropriate function does not get registered, or if the callback returns +** anything other than SQLITE_OK, then an "unknown function" error is generated +** as normal. +** +** Only a single function-needed callback is supported per database connection. +** Each call to sqlite3_function_needed() replaces the callback identified +** by the previous call to sqlite3_function_needed(). The function-needed +** callback mechanism may be disabled by invoking sqlite3_function_needed(D,P,C,X) +** using a NULL pointer for the C parameter. +** +** The X argument to sqlite3_function_needed(D,P,C,X) is an optional +** destructor for generic pointer parameter P. If X and C are both not NULL, +** then X(P) is invoked when the database connection closes or at the next +** call to sqlite3_function_needed(). +*/ +int sqlite3_function_needed( + sqlite3*, + void*, + int(*)(void*,sqlite3*,const char*,int,int), + void(*)(void*) +); + /* ** CAPI3REF: Collation Needed Callbacks ** METHOD: sqlite3 diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h index 19e030028a..450e8ab0ff 100644 --- a/src/sqlite3ext.h +++ b/src/sqlite3ext.h @@ -361,6 +361,8 @@ struct sqlite3_api_routines { int (*value_encoding)(sqlite3_value*); /* Version 3.41.0 and later */ int (*is_interrupted)(sqlite3*); + /* Version 3.43.0 and later */ + int (*function_needed)(sqlite3*,void*,const char*,int,int); }; /* @@ -689,6 +691,8 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_value_encoding sqlite3_api->value_encoding /* Version 3.41.0 and later */ #define sqlite3_is_interrupted sqlite3_api->is_interrupted +/* Version 3.43.0 and later */ +#define sqlite3_function_needed sqlite3_api->function_needed #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 8eae8d166a..4773c13458 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1674,6 +1674,9 @@ struct sqlite3 { int (*xWalCallback)(void *, sqlite3 *, const char *, int); void *pWalArg; #endif + int(*xFuncNeeded)(void*,sqlite3*,const char*,int,int); + void *pFuncNeededArg; + void(*xFuncNeededFree)(void*); void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*); void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*); void *pCollNeededArg; -- 2.39.5