From: drh Date: Wed, 19 Aug 2015 13:54:20 +0000 (+0000) Subject: Virtual table modules with a null xCreate method act as eponymous-only modules - X-Git-Tag: version-3.9.0~215^2~6 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=398f872d1f8d264068bc58ab9947cb7aa6a42427;p=thirdparty%2Fsqlite.git Virtual table modules with a null xCreate method act as eponymous-only modules - they cannot be used in a CREATE VIRTUAL TABLE statement. Add the series.c extension that implements a postgres-like generate_series virtual table to demonstrate this capability. FossilOrigin-Name: c58426dbd5ea8b8440ebcc1214f79fa63d658216 --- diff --git a/Makefile.in b/Makefile.in index a0f536cb23..096c0cc284 100644 --- a/Makefile.in +++ b/Makefile.in @@ -420,6 +420,7 @@ TESTSRC += \ $(TOP)/ext/misc/nextchar.c \ $(TOP)/ext/misc/percentile.c \ $(TOP)/ext/misc/regexp.c \ + $(TOP)/ext/misc/series.c \ $(TOP)/ext/misc/spellfix.c \ $(TOP)/ext/misc/totype.c \ $(TOP)/ext/misc/wholenumber.c diff --git a/Makefile.msc b/Makefile.msc index 22d3fb523e..56fcbcf8ba 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -1086,6 +1086,7 @@ TESTEXT = \ $(TOP)\ext\misc\nextchar.c \ $(TOP)\ext\misc\percentile.c \ $(TOP)\ext\misc\regexp.c \ + $(TOP)\ext\misc\series.c \ $(TOP)\ext\misc\spellfix.c \ $(TOP)\ext\misc\totype.c \ $(TOP)\ext\misc\wholenumber.c diff --git a/ext/misc/series.c b/ext/misc/series.c new file mode 100644 index 0000000000..93e9cd163b --- /dev/null +++ b/ext/misc/series.c @@ -0,0 +1,290 @@ +/* +** 2015-08-18 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file implements a virtual table that tries to replicate the +** behavior of the generate_series() table-valued-function in Postgres. +** +** Example: +** +** SELECT * FROM generate_series WHERE start=1 AND stop=9 AND step=2 +** +** Results in: +** +** 1 3 5 7 9 +** +*/ +#include "sqlite3ext.h" +SQLITE_EXTENSION_INIT1 +#include +#include + +#ifndef SQLITE_OMIT_VIRTUALTABLE + + +/* A series cursor object */ +typedef struct series_cursor series_cursor; +struct series_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + sqlite3_int64 iValue; /* Current value */ + sqlite3_int64 mnValue; /* Mimimum value */ + sqlite3_int64 mxValue; /* Maximum value */ + sqlite3_int64 iStep; /* How much to increment on each step */ +}; + +/* Methods for the series module */ +static int seriesConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + sqlite3_vtab *pNew; + pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) ); + if( pNew==0 ) return SQLITE_NOMEM; + +#define SERIES_COLUMN_VALUE 0 +#define SERIES_COLUMN_START 1 +#define SERIES_COLUMN_STOP 2 +#define SERIES_COLUMN_STEP 3 + + sqlite3_declare_vtab(db, + "CREATE TABLE x(value,start hidden,stop hidden,step hidden)"); + memset(pNew, 0, sizeof(*pNew)); + return SQLITE_OK; +} + +static int seriesDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** Open a new series cursor. +*/ +static int seriesOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + series_cursor *pCur; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* +** Close a series cursor. +*/ +static int seriesClose(sqlite3_vtab_cursor *cur){ + sqlite3_free(cur); + return SQLITE_OK; +} + + +/* +** Advance a cursor to its next row of output +*/ +static int seriesNext(sqlite3_vtab_cursor *cur){ + series_cursor *pCur = (series_cursor*)cur; + pCur->iValue += pCur->iStep; + return SQLITE_OK; +} + +/* +** Return the value associated with a series. +*/ +static int seriesColumn( + sqlite3_vtab_cursor *cur, + sqlite3_context *ctx, + int i +){ + series_cursor *pCur = (series_cursor*)cur; + sqlite3_int64 x; + switch( i ){ + case 0: x = pCur->iValue; break; + case 1: x = pCur->mnValue; break; + case 2: x = pCur->mxValue; break; + case 3: x = pCur->iStep; break; + } + sqlite3_result_int64(ctx, x); + return SQLITE_OK; +} + +/* +** The rowid. +*/ +static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + series_cursor *pCur = (series_cursor*)cur; + *pRowid = pCur->iValue; + return SQLITE_OK; +} + +/* +** Return TRUE if the last row has been output. +*/ +static int seriesEof(sqlite3_vtab_cursor *cur){ + series_cursor *pCur = (series_cursor*)cur; + return pCur->iValue>pCur->mxValue; +} + +/* +** Called to "rewind" a cursor back to the beginning so that +** it starts its output over again. Always called at least once +** prior to any seriesColumn, seriesRowid, or seriesEof call. +** +** idxNum is a bitmask showing which constraints are available: +** +** 1: start=VALUE +** 2: stop=VALUE +** 4: step=VALUE +** +*/ +static int seriesFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + series_cursor *pCur = (series_cursor *)pVtabCursor; + int i = 0; + if( idxNum & 1 ){ + pCur->mnValue = sqlite3_value_int64(argv[i++]); + }else{ + pCur->mnValue = 0; + } + pCur->iValue = pCur->mnValue; + if( idxNum & 2 ){ + pCur->mxValue = sqlite3_value_int64(argv[i++]); + }else{ + pCur->mxValue = 0xffffffff; + } + if( idxNum & 4 ){ + pCur->iStep = sqlite3_value_int64(argv[i++]); + }else{ + pCur->iStep = 1; + } + return SQLITE_OK; +} + +/* +** Search for terms of these forms: +** +** (1) start = $value +** (2) stop = $value +** (4) step = $value +** +** idxNum is an ORed combination of 1, 2, 4. +*/ +static int seriesBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + int i; + int idxNum = 0; + int startIdx = -1; + int stopIdx = -1; + int stepIdx = -1; + int nArg = 0; + + const struct sqlite3_index_constraint *pConstraint; + pConstraint = pIdxInfo->aConstraint; + for(i=0; inConstraint; i++, pConstraint++){ + if( pConstraint->usable==0 ) continue; + if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + switch( pConstraint->iColumn ){ + case SERIES_COLUMN_START: + startIdx = i; + idxNum |= 1; + break; + case SERIES_COLUMN_STOP: + stopIdx = i; + idxNum |= 2; + break; + case SERIES_COLUMN_STEP: + stepIdx = i; + idxNum |= 4; + break; + } + } + pIdxInfo->idxNum = idxNum; + if( startIdx>=0 ){ + pIdxInfo->aConstraintUsage[startIdx].argvIndex = ++nArg; + pIdxInfo->aConstraintUsage[startIdx].omit = 1; + } + if( stopIdx>=0 ){ + pIdxInfo->aConstraintUsage[stopIdx].argvIndex = ++nArg; + pIdxInfo->aConstraintUsage[stopIdx].omit = 1; + } + if( stepIdx>=0 ){ + pIdxInfo->aConstraintUsage[stepIdx].argvIndex = ++nArg; + pIdxInfo->aConstraintUsage[stepIdx].omit = 1; + } + if( pIdxInfo->nOrderBy==1 + && pIdxInfo->aOrderBy[0].desc==0 + ){ + pIdxInfo->orderByConsumed = 1; + } + if( (idxNum & 3)==3 ){ + /* Both start= and stop= boundaries are available. This is the + ** the preferred case */ + pIdxInfo->estimatedCost = (double)1; + }else{ + /* If either boundary is missing, we have to generate a huge span + ** of numbers. Make this case very expensive so that the query + ** planner will work hard to avoid it. */ + pIdxInfo->estimatedCost = (double)2000000000; + } + return SQLITE_OK; +} + +/* +** A virtual table module that provides read-only access to a +** Tcl global variable namespace. +*/ +static sqlite3_module seriesModule = { + 0, /* iVersion */ + 0, /* xCreate */ + seriesConnect, + seriesBestIndex, + seriesDisconnect, + 0, /* xDestroy */ + seriesOpen, /* xOpen - open a cursor */ + seriesClose, /* xClose - close a cursor */ + seriesFilter, /* xFilter - configure scan constraints */ + seriesNext, /* xNext - advance a cursor */ + seriesEof, /* xEof - check for end of scan */ + seriesColumn, /* xColumn - read data */ + seriesRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ +}; + +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +#ifdef _WIN32 +__declspec(dllexport) +#endif +int sqlite3_series_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); +#ifndef SQLITE_OMIT_VIRTUALTABLE + rc = sqlite3_create_module(db, "generate_series", &seriesModule, 0); +#endif + return rc; +} diff --git a/main.mk b/main.mk index f3bdae6a83..7ddc808ad2 100644 --- a/main.mk +++ b/main.mk @@ -300,6 +300,7 @@ TESTSRC += \ $(TOP)/ext/misc/nextchar.c \ $(TOP)/ext/misc/percentile.c \ $(TOP)/ext/misc/regexp.c \ + $(TOP)/ext/misc/series.c \ $(TOP)/ext/misc/spellfix.c \ $(TOP)/ext/misc/totype.c \ $(TOP)/ext/misc/wholenumber.c \ diff --git a/manifest b/manifest index 3b447c0494..94df9ac4dd 100644 --- a/manifest +++ b/manifest @@ -1,9 +1,9 @@ -C Merge\schanges\sfrom\strunk. -D 2015-08-19T12:52:51.679 +C Virtual\stable\smodules\swith\sa\snull\sxCreate\smethod\sact\sas\seponymous-only\smodules\s-\nthey\scannot\sbe\sused\sin\sa\sCREATE\sVIRTUAL\sTABLE\sstatement.\s\sAdd\sthe\sseries.c\nextension\sthat\simplements\sa\spostgres-like\sgenerate_series\svirtual\stable\sto\ndemonstrate\sthis\scapability. +D 2015-08-19T13:54:20.227 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f -F Makefile.in 2fc9ca6bf5949d415801c007ed3004a4bdb7c380 +F Makefile.in 4f663b6b4954b9b1eb0e6f08387688a93b57542d F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 -F Makefile.msc 5f7861c62c41fe8e3205ef14b90ebed28fa21f1b +F Makefile.msc cf63e11a5395cf887515ac7b78e2057dfe442fcd F Makefile.vxworks e1b65dea203f054e71653415bd8f96dcaed47858 F README.md 8ecc12493ff9f820cdea6520a9016001cb2e59b7 F VERSION ccfc4d1576dbfdeece0a4372a2e6a2e37d3e7975 @@ -196,6 +196,7 @@ F ext/misc/nextchar.c 35c8b8baacb96d92abbb34a83a997b797075b342 F ext/misc/percentile.c bcbee3c061b884eccb80e21651daaae8e1e43c63 F ext/misc/regexp.c af92cdaa5058fcec1451e49becc7ba44dba023dc F ext/misc/rot13.c 1ac6f95f99b575907b9b09c81a349114cf9be45a +F ext/misc/series.c c2be7ee41963cd2fcc1d7a226f5348fbe5f4f657 F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52 F ext/misc/spellfix.c 86998fb73aefb7b5dc346ba8a58912f312da4996 F ext/misc/totype.c 4a167594e791abeed95e0a8db028822b5e8fe512 @@ -256,7 +257,7 @@ F ext/userauth/userauth.c 5fa3bdb492f481bbc1709fc83c91ebd13460c69e F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60 -F main.mk 73167b34b0e67c0be32c1da2d988a376851c9ab1 +F main.mk 702135e71d4438ea38c64b22fd6545a0fd87425c F mkopcodec.awk c2ff431854d702cdd2d779c9c0d1f58fa16fa4ea F mkopcodeh.awk 0e7f04a8eb90f92259e47d80110e4e98d7ce337a F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83 @@ -345,7 +346,7 @@ F src/sqliteLimit.h 216557999cb45f2e3578ed53ebefe228d779cb46 F src/status.c f266ad8a2892d659b74f0f50cb6a88b6e7c12179 F src/table.c 51b46b2a62d1b3a959633d593b89bab5e2c9155e F src/tclsqlite.c d9439b6a910985b7fff43ba6756bcef00de22649 -F src/test1.c d339ae9b9baf9221c657c9628c9061d88bd831f6 +F src/test1.c c12ed85c22ac95f87f79de2ec9553334d115f71e F src/test2.c 577961fe48961b2f2e5c8b56ee50c3f459d3359d F src/test3.c 64d2afdd68feac1bb5e2ffb8226c8c639f798622 F src/test4.c d168f83cc78d02e8d35567bb5630e40dcd85ac1e @@ -407,7 +408,7 @@ F src/vdbeblob.c 4f2e8e075d238392df98c5e03a64342465b03f90 F src/vdbemem.c ae38a0d35ae71cf604381a887c170466ba518090 F src/vdbesort.c f5009e7a35e3065635d8918b9a31f498a499976b F src/vdbetrace.c 8befe829faff6d9e6f6e4dee5a7d3f85cc85f1a0 -F src/vtab.c fddb32423d824831fbc42b8358ffadc2437edd97 +F src/vtab.c 1e3405f78e9f248bdee6ef7a8903fadaa7222f9c F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb F src/wal.c 6fb6b68969e4692593c2552c4e7bff5882de2cb8 F src/wal.h df01efe09c5cb8c8e391ff1715cca294f89668a4 @@ -1030,6 +1031,7 @@ F test/superlock.test 1cde669f68d2dd37d6c9bd35eee1d95491ae3fc2 F test/sync.test a34cd43e98b7fb84eabbf38f7ed8f7349b3f3d85 F test/syscall.test d2fdaad713f103ac611fe7ef9b724c7b69f8149c F test/sysfault.test fa776e60bf46bdd3ae69f0b73e46ee3977a58ae6 +F test/tabfunc01.test 239e336a556c92c6f81431f4f144f16311184880 F test/table.test 33bf0d1fd07f304582695184b8e6feb017303816 F test/tableapi.test 2674633fa95d80da917571ebdd759a14d9819126 F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930 @@ -1374,7 +1376,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P c1f43a7799a9298abea01b2f8531fc7cdadc4594 c573b0a1aa3ba509234f07520fa94d008bcbb330 -R 04d75ba2dfa13388a455ec59c68a148d +P dddd792dedf0c73ebe74b4ff8d303e6216c16b6a +R 5f16282689e1192bf9396f5e61d5d753 U drh -Z d53ae36484d8828c21b5f9a23ee68eb5 +Z 970ed95f4f43231f807975375b2847aa diff --git a/manifest.uuid b/manifest.uuid index 0f7de90c02..59ff6ef355 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -dddd792dedf0c73ebe74b4ff8d303e6216c16b6a \ No newline at end of file +c58426dbd5ea8b8440ebcc1214f79fa63d658216 \ No newline at end of file diff --git a/src/test1.c b/src/test1.c index ef9783a3c0..539c674d9a 100644 --- a/src/test1.c +++ b/src/test1.c @@ -6380,6 +6380,7 @@ static int tclLoadStaticExtensionCmd( extern int sqlite3_nextchar_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_percentile_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_regexp_init(sqlite3*,char**,const sqlite3_api_routines*); + extern int sqlite3_series_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_spellfix_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_totype_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_wholenumber_init(sqlite3*,char**,const sqlite3_api_routines*); @@ -6400,6 +6401,7 @@ static int tclLoadStaticExtensionCmd( { "nextchar", sqlite3_nextchar_init }, { "percentile", sqlite3_percentile_init }, { "regexp", sqlite3_regexp_init }, + { "series", sqlite3_series_init }, { "spellfix", sqlite3_spellfix_init }, { "totype", sqlite3_totype_init }, { "wholenumber", sqlite3_wholenumber_init }, diff --git a/src/vtab.c b/src/vtab.c index 23fd8b0fb4..cd1222f706 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -699,7 +699,7 @@ int sqlite3VtabCallCreate(sqlite3 *db, int iDb, const char *zTab, char **pzErr){ ** invoke it now. If the module has not been registered, return an ** error. Otherwise, do nothing. */ - if( !pMod ){ + if( pMod==0 || pMod->pModule->xCreate==0 ){ *pzErr = sqlite3MPrintf(db, "no such module: %s", zMod); rc = SQLITE_ERROR; }else{ @@ -1109,7 +1109,7 @@ int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){ int rc; sqlite3 *db = pParse->db; if( pMod->pEpoTab ) return 1; - if( pModule->xCreate!=pModule->xConnect ) return 0; + if( pModule->xCreate!=0 && pModule->xCreate!=pModule->xConnect ) return 0; nName = sqlite3Strlen30(pMod->zName) + 1; pTab = sqlite3DbMallocZero(db, sizeof(Table) + nName); if( pTab==0 ) return 0; diff --git a/test/tabfunc01.test b/test/tabfunc01.test new file mode 100644 index 0000000000..3b672e91d7 --- /dev/null +++ b/test/tabfunc01.test @@ -0,0 +1,36 @@ +# 2015-08-19 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# This file implements tests for table-valued-functions implemented using +# eponymous virtual tables. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix tabfunc01 + +ifcapable !vtab { + finish_test + return +} +load_static_extension db series + +do_execsql_test tabfunc01-1.1 { + SELECT *, '|' FROM generate_series WHERE start=1 AND stop=9 AND step=2; +} {1 | 3 | 5 | 7 | 9 |} +do_execsql_test tabfunc01-1.2 { + SELECT *, '|' FROM generate_series LIMIT 5; +} {0 | 1 | 2 | 3 | 4 |} +do_catchsql_test tabfunc01-1.3 { + CREATE VIRTUAL TABLE t1 USING generate_series; +} {} + +finish_test