From 3773c25f16645067bf58ecd99a2b6de9a3eb7062 Mon Sep 17 00:00:00 2001 From: drh Date: Thu, 28 Jun 2018 03:38:49 +0000 Subject: [PATCH 1/7] Introduce the concept of "weak fallback" in the parser. A weak fallback only occurs if a syntax error would result otherwise. FossilOrigin-Name: c41d7079fb63831bd2f009b64cbf5a423e8119e6b841bfd9bd5a2d85fba3b142 --- manifest | 21 ++++++++++++--------- manifest.uuid | 2 +- src/parse.y | 4 ++++ tool/lemon.c | 21 ++++++++++++++------- tool/lempar.c | 35 +++++++++++++++++++++++------------ 5 files changed, 54 insertions(+), 29 deletions(-) diff --git a/manifest b/manifest index abb0adcf47..fcb3dd292f 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\smissing\sVdbeCoverage()\sand\sVdbeCoverageNeverTaken()\smacros\sto\swindow.c. -D 2018-06-27T20:24:59.489 +C Introduce\sthe\sconcept\sof\s"weak\sfallback"\sin\sthe\sparser.\s\sA\sweak\sfallback\nonly\soccurs\sif\sa\ssyntax\serror\swould\sresult\sotherwise. +D 2018-06-28T03:38:49.164 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F Makefile.in 0a3a6c81e6fcb969ff9106e882f0a08547014ba463cb6beca4c4efaecc924ee6 @@ -484,7 +484,7 @@ F src/os_win.c ac29c25cde4cfb4adacc59cdec4aa45698ca0e29164ea127859585ccd9faa354 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a F src/pager.c 1bb6a57fa0465296a4d6109a1a64610a0e7adde1f3acf3ef539a9d972908ce8f F src/pager.h c571b064df842ec8f2e90855dead9acf4cbe0d1b2c05afe0ef0d0145f7fd0388 -F src/parse.y 4661ac1382c18bc67e1680ea3e44a6835ebc770667fe32ee1720c38454304165 +F src/parse.y 9517b50d35e5e495a241541b49f5512b8e9bcbac3fbfdc659215555efa5c4f40 F src/pcache.c 135ef0bc6fb2e3b7178d49ab5c9176254c8a691832c1bceb1156b2fbdd0869bd F src/pcache.h 072f94d29281cffd99e46c1539849f248c4b56ae7684c1f36626797fee375170 F src/pcache1.c 716975564c15eb6679e97f734cec1bfd6c16ac3d4010f05f1f8e509fc7d19880 @@ -1662,8 +1662,8 @@ F tool/genfkey.README cf68fddd4643bbe3ff8e31b8b6d8b0a1b85e20f4 F tool/genfkey.test 4196a8928b78f51d54ef58e99e99401ab2f0a7e5 F tool/getlock.c f4c39b651370156cae979501a7b156bdba50e7ce F tool/kvtest-speed.sh 4761a9c4b3530907562314d7757995787f7aef8f -F tool/lemon.c 33892e2a243865f73e6c6e7cecce3c6eb4bb95db4a3d9d86d146c8064feb92fd -F tool/lempar.c bf7db78e7213f1d51516710483eab506fd52bf632c7abfb3e2e9b885c90c03e1 +F tool/lemon.c 93a321bd82b5c73191e95b2f27203048bf4d054fdab4a257b7a6c9541f31828d +F tool/lempar.c c7371bad027fd516b7eaed36d03353972df35163672637cddd00f472889932f9 F tool/libvers.c caafc3b689638a1d88d44bc5f526c2278760d9b9 F tool/loadfts.c c3c64e4d5e90e8ba41159232c2189dba4be7b862 F tool/logest.c 11346aa019e2e77a00902aa7d0cabd27bd2e8cca @@ -1743,7 +1743,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 336de43a47e206fe7629072e5b8c80d4ede17ead8ef4dcf5d8da6833ff22d2f9 -R b7bd98ed0e43c8f2d4cb4f0d7b13d574 -U dan -Z 38db85555459c758350756078d19d70e +P 4383cb68a1241768ddb3f180f8cbb9ea1638f8806210b10aac1384b8a7ecdca2 +R 341c5aa0d73cc2ef03cecacf95853224 +T *branch * weak-fallback +T *sym-weak-fallback * +T -sym-exp-window-functions * +U drh +Z 196c50a9819aa6d1d98f4a403b164e6b diff --git a/manifest.uuid b/manifest.uuid index 76bb105dfe..6b74c95f01 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -4383cb68a1241768ddb3f180f8cbb9ea1638f8806210b10aac1384b8a7ecdca2 \ No newline at end of file +c41d7079fb63831bd2f009b64cbf5a423e8119e6b841bfd9bd5a2d85fba3b142 \ No newline at end of file diff --git a/src/parse.y b/src/parse.y index b253480588..02989c28a2 100644 --- a/src/parse.y +++ b/src/parse.y @@ -216,6 +216,10 @@ columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,&A,&Y);} %ifdef SQLITE_OMIT_COMPOUND_SELECT EXCEPT INTERSECT UNION %endif SQLITE_OMIT_COMPOUND_SELECT +%ifndef SQLITE_OMIT_WINDOWFUNC + CURRENT FILTER FOLLOWING ?OVER PARTITION + PRECEDING RANGE UNBOUNDED ?WINDOW +%endif SQLITE_OMIT_WINDOWFUNC REINDEX RENAME CTIME_KW IF . %wildcard ANY. diff --git a/tool/lemon.c b/tool/lemon.c index c2c9554c89..08d3f39eba 100644 --- a/tool/lemon.c +++ b/tool/lemon.c @@ -272,6 +272,7 @@ struct symbol { ** union is the correct data type for this object */ int bContent; /* True if this symbol ever carries content - if ** it is ever more than just syntax */ + int bWeakFallback; /* True for a weak fallback */ /* The following fields are used by MULTITERMINALs only */ int nsubsym; /* Number of constituent symbols in the MULTI */ struct symbol **subsym; /* Array of constituent symbols */ @@ -2209,6 +2210,7 @@ enum e_state { WAITING_FOR_DESTRUCTOR_SYMBOL, WAITING_FOR_DATATYPE_SYMBOL, WAITING_FOR_FALLBACK_ID, + WAITING_FOR_WEAK_FALLBACK_ID, WAITING_FOR_WILDCARD_ID, WAITING_FOR_CLASS_ID, WAITING_FOR_CLASS_TOKEN, @@ -2667,6 +2669,12 @@ to follow the previous rule."); } break; case WAITING_FOR_FALLBACK_ID: + if( x[0]=='?' ){ + psp->state = WAITING_FOR_WEAK_FALLBACK_ID; + break; + } + /* Fall through */ + case WAITING_FOR_WEAK_FALLBACK_ID: if( x[0]=='.' ){ psp->state = WAITING_FOR_DECL_OR_RULE; }else if( !ISUPPER(x[0]) ){ @@ -2683,6 +2691,8 @@ to follow the previous rule."); psp->errorcnt++; }else{ sp->fallback = psp->fallback; + sp->bWeakFallback = (psp->state==WAITING_FOR_WEAK_FALLBACK_ID); + psp->state = WAITING_FOR_FALLBACK_ID; psp->gp->has_fallback = 1; } } @@ -4493,6 +4503,9 @@ void ReportTable( struct symbol *p = lemp->symbols[i]; if( p->fallback==0 ){ fprintf(out, " 0, /* %10s => nothing */\n", p->name); + }else if( p->bWeakFallback ){ + fprintf(out, " %4d, /* %10s => %s (weak) */\n", + -p->fallback->index, p->name, p->fallback->name); }else{ fprintf(out, " %3d, /* %10s => %s */\n", p->fallback->index, p->name, p->fallback->name); @@ -5112,18 +5125,12 @@ struct symbol *Symbol_new(const char *x) if( sp==0 ){ sp = (struct symbol *)calloc(1, sizeof(struct symbol) ); MemoryCheck(sp); + memset(sp, 0, sizeof(*sp)); sp->name = Strsafe(x); sp->type = ISUPPER(*x) ? TERMINAL : NONTERMINAL; - sp->rule = 0; - sp->fallback = 0; sp->prec = -1; sp->assoc = UNK; - sp->firstset = 0; sp->lambda = LEMON_FALSE; - sp->destructor = 0; - sp->destLineno = 0; - sp->datatype = 0; - sp->useCnt = 0; Symbol_insert(sp,sp->name); } sp->useCnt++; diff --git a/tool/lempar.c b/tool/lempar.c index 885cefe143..d625fcb0a4 100644 --- a/tool/lempar.c +++ b/tool/lempar.c @@ -90,6 +90,7 @@ /************* Begin control #defines *****************************************/ %% /************* End control #defines *******************************************/ +#define YY_NLOOKAHEAD ((int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0]))) /* Define the yytestcase() macro to be a no-op if is not already defined ** otherwise. @@ -172,7 +173,7 @@ ** it appears. */ #ifdef YYFALLBACK -static const YYCODETYPE yyFallback[] = { +static const int yyFallback[] = { %% }; #endif /* YYFALLBACK */ @@ -519,24 +520,34 @@ static YYACTIONTYPE yy_find_shift_action( do{ i = yy_shift_ofst[stateno]; assert( i>=0 ); - assert( i+YYNTOKEN<=(int)sizeof(yy_lookahead)/sizeof(yy_lookahead[0]) ); + /*assert( i+YYNTOKEN<=YY_NLOOKAHEAD );*/ assert( iLookAhead!=YYNOCODE ); assert( iLookAhead < YYNTOKEN ); i += iLookAhead; - if( yy_lookahead[i]!=iLookAhead ){ + if( i>=YY_NLOOKAHEAD || yy_lookahead[i]!=iLookAhead + ){ #ifdef YYFALLBACK - YYCODETYPE iFallback; /* Fallback token */ + int iFallback; /* Fallback token */ if( iLookAhead %s\n", - yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); + && (iFallback = yyFallback[iLookAhead])!=0 + ){ + if( iFallback<0 && yy_default[stateno]==YY_ERROR_ACTION ){ + /* A weak fallback only happens if current lookahead is a + ** syntax error */ + iFallback = -iFallback; } + if( iFallback>0 ){ + /* A strong fallback happens regardless */ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); + } #endif - assert( yyFallback[iFallback]==0 ); /* Fallback loop must terminate */ - iLookAhead = iFallback; - continue; + assert( yyFallback[iFallback]==0 ); /* Fallback loop must terminate */ + iLookAhead = iFallback; + continue; + } } #endif #ifdef YYWILDCARD From 04a8e0cd54219d1bc3ef1701377819957aa541e6 Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 28 Jun 2018 20:05:04 +0000 Subject: [PATCH 2/7] Modifications to parse.y to better support backwards compatibility for the "window" keyword. FossilOrigin-Name: 7c4b879bdb10f30260f0fb28fcb559ab0949865d10f4ff2cf13834a326198998 --- manifest | 17 +++++++---------- manifest.uuid | 2 +- src/parse.y | 22 ++++++++++++---------- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/manifest b/manifest index fcb3dd292f..69ff0aa49e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Introduce\sthe\sconcept\sof\s"weak\sfallback"\sin\sthe\sparser.\s\sA\sweak\sfallback\nonly\soccurs\sif\sa\ssyntax\serror\swould\sresult\sotherwise. -D 2018-06-28T03:38:49.164 +C Modifications\sto\sparse.y\sto\sbetter\ssupport\sbackwards\scompatibility\sfor\sthe\n"window"\skeyword. +D 2018-06-28T20:05:04.235 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F Makefile.in 0a3a6c81e6fcb969ff9106e882f0a08547014ba463cb6beca4c4efaecc924ee6 @@ -484,7 +484,7 @@ F src/os_win.c ac29c25cde4cfb4adacc59cdec4aa45698ca0e29164ea127859585ccd9faa354 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a F src/pager.c 1bb6a57fa0465296a4d6109a1a64610a0e7adde1f3acf3ef539a9d972908ce8f F src/pager.h c571b064df842ec8f2e90855dead9acf4cbe0d1b2c05afe0ef0d0145f7fd0388 -F src/parse.y 9517b50d35e5e495a241541b49f5512b8e9bcbac3fbfdc659215555efa5c4f40 +F src/parse.y 9b5eb0e61e615832647a7a77f9e675f1da536a133426327e6812316d83f275e7 F src/pcache.c 135ef0bc6fb2e3b7178d49ab5c9176254c8a691832c1bceb1156b2fbdd0869bd F src/pcache.h 072f94d29281cffd99e46c1539849f248c4b56ae7684c1f36626797fee375170 F src/pcache1.c 716975564c15eb6679e97f734cec1bfd6c16ac3d4010f05f1f8e509fc7d19880 @@ -1743,10 +1743,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 4383cb68a1241768ddb3f180f8cbb9ea1638f8806210b10aac1384b8a7ecdca2 -R 341c5aa0d73cc2ef03cecacf95853224 -T *branch * weak-fallback -T *sym-weak-fallback * -T -sym-exp-window-functions * -U drh -Z 196c50a9819aa6d1d98f4a403b164e6b +P c41d7079fb63831bd2f009b64cbf5a423e8119e6b841bfd9bd5a2d85fba3b142 +R 4e74da2117f8f8cceb7bb5b15336dc74 +U dan +Z e15fe89d6d9f69693116ca5d610ea195 diff --git a/manifest.uuid b/manifest.uuid index 6b74c95f01..51740e4ee8 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c41d7079fb63831bd2f009b64cbf5a423e8119e6b841bfd9bd5a2d85fba3b142 \ No newline at end of file +7c4b879bdb10f30260f0fb28fcb559ab0949865d10f4ff2cf13834a326198998 \ No newline at end of file diff --git a/src/parse.y b/src/parse.y index 02989c28a2..1faa9104a1 100644 --- a/src/parse.y +++ b/src/parse.y @@ -217,8 +217,8 @@ columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,&A,&Y);} EXCEPT INTERSECT UNION %endif SQLITE_OMIT_COMPOUND_SELECT %ifndef SQLITE_OMIT_WINDOWFUNC - CURRENT FILTER FOLLOWING ?OVER PARTITION - PRECEDING RANGE UNBOUNDED ?WINDOW + CURRENT FILTER FOLLOWING OVER PARTITION + PRECEDING RANGE UNBOUNDED %endif SQLITE_OMIT_WINDOWFUNC REINDEX RENAME CTIME_KW IF . @@ -257,7 +257,7 @@ columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,&A,&Y);} // And "ids" is an identifer-or-string. // -%token_class ids ID|STRING. +%token_class ids ID|STRING. // The name of a column or table can be any of the following: // @@ -265,6 +265,7 @@ columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,&A,&Y);} nm(A) ::= id(A). nm(A) ::= STRING(A). nm(A) ::= JOIN_KW(A). +nm(A) ::= WINDOW(A). // A typetoken is really zero or more tokens that form a type name such // as can be found after the column name in a CREATE TABLE statement. @@ -280,8 +281,8 @@ typetoken(A) ::= typename(A) LP signed COMMA signed RP(Y). { A.n = (int)(&Y.z[Y.n] - A.z); } %type typename {Token} -typename(A) ::= ids(A). -typename(A) ::= typename(A) ids(Y). {A.n=Y.n+(int)(Y.z-A.z);} +typename(A) ::= nm(A). +typename(A) ::= typename(A) nm(Y). {A.n=Y.n+(int)(Y.z-A.z);} signed ::= plus_num. signed ::= minus_num. @@ -342,7 +343,7 @@ ccons ::= CHECK LP expr(X) RP. {sqlite3AddCheckConstraint(pParse,X);} ccons ::= REFERENCES nm(T) eidlist_opt(TA) refargs(R). {sqlite3CreateForeignKey(pParse,0,&T,TA,R);} ccons ::= defer_subclause(D). {sqlite3DeferForeignKey(pParse,D);} -ccons ::= COLLATE ids(C). {sqlite3AddCollateType(pParse, &C);} +ccons ::= COLLATE nm(C). {sqlite3AddCollateType(pParse, &C);} // The optional AUTOINCREMENT keyword %type autoinc {int} @@ -988,6 +989,7 @@ expr(A) ::= nm(X) DOT nm(Y) DOT nm(Z). { } term(A) ::= NULL|FLOAT|BLOB(X). {A=tokenExpr(pParse,@X,X); /*A-overwrites-X*/} term(A) ::= STRING(X). {A=tokenExpr(pParse,@X,X); /*A-overwrites-X*/} +term(A) ::= WINDOW(X). {A=tokenExpr(pParse,TK_ID,X);/*A-overwrites-X*/} term(A) ::= INTEGER(X). { A = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &X, 1); } @@ -1011,7 +1013,7 @@ expr(A) ::= VARIABLE(X). { } } } -expr(A) ::= expr(A) COLLATE ids(C). { +expr(A) ::= expr(A) COLLATE nm(C). { A = sqlite3ExprAddCollateToken(pParse, A, &C, 1); } %ifndef SQLITE_OMIT_CAST @@ -1020,7 +1022,7 @@ expr(A) ::= CAST LP expr(E) AS typetoken(T) RP. { sqlite3ExprAttachSubtrees(pParse->db, A, E, 0); } %endif SQLITE_OMIT_CAST -expr(A) ::= id(X) LP distinct(D) exprlist(Y) RP +expr(A) ::= nm(X) LP distinct(D) exprlist(Y) RP %ifndef SQLITE_OMIT_WINDOWFUNC over_opt(Z) %endif @@ -1034,7 +1036,7 @@ expr(A) ::= id(X) LP distinct(D) exprlist(Y) RP A->flags |= EP_Distinct; } } -expr(A) ::= id(X) LP STAR RP +expr(A) ::= nm(X) LP STAR RP %ifndef SQLITE_OMIT_WINDOWFUNC over_opt(Z) %endif @@ -1443,7 +1445,7 @@ eidlist(A) ::= nm(Y) collate(C) sortorder(Z). { %type collate {int} collate(C) ::= . {C = 0;} -collate(C) ::= COLLATE ids. {C = 1;} +collate(C) ::= COLLATE nm. {C = 1;} ///////////////////////////// The DROP INDEX command ///////////////////////// From 59ff42516cdd4a5c4d5e5d65b45ce7e312bf0472 Mon Sep 17 00:00:00 2001 From: dan Date: Fri, 29 Jun 2018 17:44:52 +0000 Subject: [PATCH 3/7] Instead of using a lemon %fallback directive, have the tokenizer try to figure out whether an instance of "WINDOW" should be TK_WINDOW or TK_ID. FossilOrigin-Name: 022079cb0d67be5ac0a50dd9a4d41ee55ce8df681ecd0a544170d75fc8649978 --- manifest | 19 ++++---- manifest.uuid | 2 +- src/parse.y | 22 ++++----- src/sqliteInt.h | 1 + src/tokenize.c | 56 +++++++++++++++++++++- test/window6.test | 117 ++++++++++++++++++++++++++++++++++++++++++++++ tool/lempar.c | 14 ++++++ 7 files changed, 208 insertions(+), 23 deletions(-) create mode 100644 test/window6.test diff --git a/manifest b/manifest index 69ff0aa49e..e6513a564a 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Modifications\sto\sparse.y\sto\sbetter\ssupport\sbackwards\scompatibility\sfor\sthe\n"window"\skeyword. -D 2018-06-28T20:05:04.235 +C Instead\sof\susing\sa\slemon\s%fallback\sdirective,\shave\sthe\stokenizer\stry\sto\sfigure\nout\swhether\san\sinstance\sof\s"WINDOW"\sshould\sbe\sTK_WINDOW\sor\sTK_ID. +D 2018-06-29T17:44:52.692 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F Makefile.in 0a3a6c81e6fcb969ff9106e882f0a08547014ba463cb6beca4c4efaecc924ee6 @@ -484,7 +484,7 @@ F src/os_win.c ac29c25cde4cfb4adacc59cdec4aa45698ca0e29164ea127859585ccd9faa354 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a F src/pager.c 1bb6a57fa0465296a4d6109a1a64610a0e7adde1f3acf3ef539a9d972908ce8f F src/pager.h c571b064df842ec8f2e90855dead9acf4cbe0d1b2c05afe0ef0d0145f7fd0388 -F src/parse.y 9b5eb0e61e615832647a7a77f9e675f1da536a133426327e6812316d83f275e7 +F src/parse.y 216e0e953df076ef89668614e5aab8b489e0c2c24f7ff17cf42e7bea802793a9 F src/pcache.c 135ef0bc6fb2e3b7178d49ab5c9176254c8a691832c1bceb1156b2fbdd0869bd F src/pcache.h 072f94d29281cffd99e46c1539849f248c4b56ae7684c1f36626797fee375170 F src/pcache1.c 716975564c15eb6679e97f734cec1bfd6c16ac3d4010f05f1f8e509fc7d19880 @@ -500,7 +500,7 @@ F src/shell.c.in 8578421c5fb2a972461b2a996f7173646e55e0dbd2a2eee30c8f5dc7d3dbadf F src/sqlite.h.in 243e0b1d302c237372c06002fe84f731fd22587eccd142d15de834a5296514ff F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 9887b27e69c01e79c2cbe74ef73bf01af5b5703d6a7f0a4371e386d7249cb1c7 -F src/sqliteInt.h 74728a0b20e9d2173470186a8ca1e3229c79abb19d1ac4eedc4562e8905d798d +F src/sqliteInt.h c14c850bf0c4dd3860cc5092f140ae9faf93b77737b3febf38454088e1ab3988 F src/sqliteLimit.h 1513bfb7b20378aa0041e7022d04acb73525de35b80b252f1b83fedb4de6a76b F src/status.c 46e7aec11f79dad50965a5ca5fa9de009f7d6bde08be2156f1538a0a296d4d0e F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34 @@ -558,7 +558,7 @@ F src/test_windirent.h 90dfbe95442c9762357fe128dc7ae3dc199d006de93eb33ba3972e0a9 F src/test_window.c add59ee68568868129516999f30a68e8ab2afd276e272aba4f633c9fc52c1bb1 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c -F src/tokenize.c b42aa952b05df665ed10d54f02c467159cd9fcb8cbbe5ff3c9cd74781fa2803c +F src/tokenize.c e25caaeb2ad912a378e528771d843badbaee560f2eb8c25dbface05a32c2e4cf F src/treeview.c 2c5c4bc0a443401db5fd621542150452ddf5055d38edd4eef868bc2b6bfb0260 F src/trigger.c 4ace6d1d5ba9a89822deb287317f33c810440526eafe185c2d8a48c31df1e995 F src/update.c 46dc24c6158446aaab45caee09b6d99327cb479268b83ffeb5b701823da3b67b @@ -1626,6 +1626,7 @@ F test/window3.test 47884c240d0d5234ad9c6da65452cfec1bfa69ec6f9c4158cd9750c3d88d F test/window4.tcl 7cec7e578aa9f78b7265bff8d552cda17a1d8d89f0449d0e74970a527b8846f5 F test/window4.test dcd8767869988e0d23d56bc3f8b46ec116de23127b81b5f66fd48d5529072ed1 F test/window5.test a4835b96d30eb3b81a1dbc683e333e57a614645eb6f2ae476a7ed2addf0b0f1f +F test/window6.test 97bd18d5cccd612b8a93be4017a7a26c2968a1c868a458d0d66d8d8cc62e33f8 F test/windowfault.test 97d5fc404308edb579a5a183e294ed874c844ecf01f0a28ba46df3141ebaee1f F test/with1.test 58475190cd8caaeebea8cfeb2a264ec97a0c492b8ffe9ad20cefbb23df462f96 F test/with2.test e0030e2f0267a910d6c0e4f46f2dfe941c1cc0d4f659ba69b3597728e7e8f1ab @@ -1663,7 +1664,7 @@ F tool/genfkey.test 4196a8928b78f51d54ef58e99e99401ab2f0a7e5 F tool/getlock.c f4c39b651370156cae979501a7b156bdba50e7ce F tool/kvtest-speed.sh 4761a9c4b3530907562314d7757995787f7aef8f F tool/lemon.c 93a321bd82b5c73191e95b2f27203048bf4d054fdab4a257b7a6c9541f31828d -F tool/lempar.c c7371bad027fd516b7eaed36d03353972df35163672637cddd00f472889932f9 +F tool/lempar.c afc03d26f33db130c0ca7d5942000aea7eedd638455845ca76f792818896df19 F tool/libvers.c caafc3b689638a1d88d44bc5f526c2278760d9b9 F tool/loadfts.c c3c64e4d5e90e8ba41159232c2189dba4be7b862 F tool/logest.c 11346aa019e2e77a00902aa7d0cabd27bd2e8cca @@ -1743,7 +1744,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P c41d7079fb63831bd2f009b64cbf5a423e8119e6b841bfd9bd5a2d85fba3b142 -R 4e74da2117f8f8cceb7bb5b15336dc74 +P 7c4b879bdb10f30260f0fb28fcb559ab0949865d10f4ff2cf13834a326198998 +R 53ad10aee784d2508443a129bf20d430 U dan -Z e15fe89d6d9f69693116ca5d610ea195 +Z a766b00041899c4982e4087a15ea2a82 diff --git a/manifest.uuid b/manifest.uuid index 51740e4ee8..2825dfd995 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7c4b879bdb10f30260f0fb28fcb559ab0949865d10f4ff2cf13834a326198998 \ No newline at end of file +022079cb0d67be5ac0a50dd9a4d41ee55ce8df681ecd0a544170d75fc8649978 \ No newline at end of file diff --git a/src/parse.y b/src/parse.y index 1faa9104a1..0ed0b28bad 100644 --- a/src/parse.y +++ b/src/parse.y @@ -217,8 +217,8 @@ columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,&A,&Y);} EXCEPT INTERSECT UNION %endif SQLITE_OMIT_COMPOUND_SELECT %ifndef SQLITE_OMIT_WINDOWFUNC - CURRENT FILTER FOLLOWING OVER PARTITION - PRECEDING RANGE UNBOUNDED + CURRENT FILTER FOLLOWING OVER PARTITION + PRECEDING RANGE UNBOUNDED %endif SQLITE_OMIT_WINDOWFUNC REINDEX RENAME CTIME_KW IF . @@ -257,7 +257,7 @@ columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,&A,&Y);} // And "ids" is an identifer-or-string. // -%token_class ids ID|STRING. +%token_class ids ID|STRING. // The name of a column or table can be any of the following: // @@ -265,7 +265,6 @@ columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,&A,&Y);} nm(A) ::= id(A). nm(A) ::= STRING(A). nm(A) ::= JOIN_KW(A). -nm(A) ::= WINDOW(A). // A typetoken is really zero or more tokens that form a type name such // as can be found after the column name in a CREATE TABLE statement. @@ -281,8 +280,8 @@ typetoken(A) ::= typename(A) LP signed COMMA signed RP(Y). { A.n = (int)(&Y.z[Y.n] - A.z); } %type typename {Token} -typename(A) ::= nm(A). -typename(A) ::= typename(A) nm(Y). {A.n=Y.n+(int)(Y.z-A.z);} +typename(A) ::= ids(A). +typename(A) ::= typename(A) ids(Y). {A.n=Y.n+(int)(Y.z-A.z);} signed ::= plus_num. signed ::= minus_num. @@ -343,7 +342,7 @@ ccons ::= CHECK LP expr(X) RP. {sqlite3AddCheckConstraint(pParse,X);} ccons ::= REFERENCES nm(T) eidlist_opt(TA) refargs(R). {sqlite3CreateForeignKey(pParse,0,&T,TA,R);} ccons ::= defer_subclause(D). {sqlite3DeferForeignKey(pParse,D);} -ccons ::= COLLATE nm(C). {sqlite3AddCollateType(pParse, &C);} +ccons ::= COLLATE ids(C). {sqlite3AddCollateType(pParse, &C);} // The optional AUTOINCREMENT keyword %type autoinc {int} @@ -989,7 +988,6 @@ expr(A) ::= nm(X) DOT nm(Y) DOT nm(Z). { } term(A) ::= NULL|FLOAT|BLOB(X). {A=tokenExpr(pParse,@X,X); /*A-overwrites-X*/} term(A) ::= STRING(X). {A=tokenExpr(pParse,@X,X); /*A-overwrites-X*/} -term(A) ::= WINDOW(X). {A=tokenExpr(pParse,TK_ID,X);/*A-overwrites-X*/} term(A) ::= INTEGER(X). { A = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &X, 1); } @@ -1013,7 +1011,7 @@ expr(A) ::= VARIABLE(X). { } } } -expr(A) ::= expr(A) COLLATE nm(C). { +expr(A) ::= expr(A) COLLATE ids(C). { A = sqlite3ExprAddCollateToken(pParse, A, &C, 1); } %ifndef SQLITE_OMIT_CAST @@ -1022,7 +1020,7 @@ expr(A) ::= CAST LP expr(E) AS typetoken(T) RP. { sqlite3ExprAttachSubtrees(pParse->db, A, E, 0); } %endif SQLITE_OMIT_CAST -expr(A) ::= nm(X) LP distinct(D) exprlist(Y) RP +expr(A) ::= id(X) LP distinct(D) exprlist(Y) RP %ifndef SQLITE_OMIT_WINDOWFUNC over_opt(Z) %endif @@ -1036,7 +1034,7 @@ expr(A) ::= nm(X) LP distinct(D) exprlist(Y) RP A->flags |= EP_Distinct; } } -expr(A) ::= nm(X) LP STAR RP +expr(A) ::= id(X) LP STAR RP %ifndef SQLITE_OMIT_WINDOWFUNC over_opt(Z) %endif @@ -1445,7 +1443,7 @@ eidlist(A) ::= nm(Y) collate(C) sortorder(Z). { %type collate {int} collate(C) ::= . {C = 0;} -collate(C) ::= COLLATE nm. {C = 1;} +collate(C) ::= COLLATE ids. {C = 1;} ///////////////////////////// The DROP INDEX command ///////////////////////// diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 9fab8f5590..448918a7f6 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -4299,6 +4299,7 @@ char sqlite3IndexColumnAffinity(sqlite3*, Index*, int); void sqlite3ParserFree(void*, void(*)(void*)); #endif void sqlite3Parser(void*, int, Token); +int sqlite3ParserFallback(int); #ifdef YYTRACKMAXSTACKDEPTH int sqlite3ParserStackPeak(void*); #endif diff --git a/src/tokenize.c b/src/tokenize.c index 3286f94f54..10ad25e29f 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -188,6 +188,55 @@ const char sqlite3IsEbcdicIdChar[] = { int sqlite3IsIdChar(u8 c){ return IdChar(c); } #endif +/* +** Return the id of the next token in string (*pz). Before returning, set +** (*pz) to point to the byte following the parsed token. +** +** This function assumes that any keywords that start with "w" are +** actually TK_ID. +*/ +static int windowGetToken(const unsigned char **pz){ + int ret; + const unsigned char *z = *pz; + if( z[0]=='w' || z[0]=='W' ){ + do { z++; }while( IdChar(z[0]) ); + ret = TK_ID; + }else{ + z += sqlite3GetToken(z, &ret); + } + *pz = z; + return ret; +} + +/* +** The tokenizer has just parsed the keyword WINDOW. In this case the token +** may really be the keyword (TK_WINDOW), or may be an identifier (TK_ID). +** This function determines which it is by inspecting the next two tokens +** in the input stream. Specifically, the token is TK_WINDOW if the following +** two tokens are: +** +** * TK_ID, or something else that can be used as a window name, and +** * TK_AS. +** +** Instead of using sqlite3GetToken() to parse tokens directly, this function +** uses windowGetToken(). This is to avoid recursion if the input is similar +** to "window window window window". +*/ +static void analyzeWindowKeyword(const unsigned char *z, int *tokenType){ + int t; + assert( *tokenType==TK_WINDOW ); + while( (t = windowGetToken(&z))==TK_SPACE ); + if( t!=TK_ID && t!=TK_STRING + && t!=TK_JOIN_KW && sqlite3ParserFallback(t)!=TK_ID + ){ + *tokenType = TK_ID; + }else{ + while( (t = windowGetToken(&z))==TK_SPACE ); + if( t!=TK_AS ){ + *tokenType = TK_ID; + } + } +} /* ** Return the length (in bytes) of the token that begins at z[0]. @@ -433,7 +482,12 @@ int sqlite3GetToken(const unsigned char *z, int *tokenType){ break; } *tokenType = TK_ID; - return keywordCode((char*)z, i, tokenType); + keywordCode((char*)z, i, tokenType); + if( *tokenType==TK_WINDOW ){ + assert( i==6 ); + analyzeWindowKeyword(&z[6], tokenType); + } + return i; } case CC_X: { #ifndef SQLITE_OMIT_BLOB_LITERAL diff --git a/test/window6.test b/test/window6.test new file mode 100644 index 0000000000..a6706fc521 --- /dev/null +++ b/test/window6.test @@ -0,0 +1,117 @@ +# 2018 May 8 +# +# 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 regression tests for SQLite library. Specifically, +# it tests the sqlite3_create_window_function() API. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix window6 + +ifcapable !windowfunc { + finish_test + return +} + +set setup { + CREATE TABLE %t1(%a, %b %typename); + INSERT INTO %t1 VALUES(1, 'a'); + INSERT INTO %t1 VALUES(2, 'b'); + INSERT INTO %t1 VALUES(3, 'c'); + INSERT INTO %t1 VALUES(4, 'd'); + INSERT INTO %t1 VALUES(5, 'e'); +} + +foreach {tn vars} { + 1 {} + 2 { set A(%t1) over } + 3 { set A(%a) over } + 4 { + set A(%alias) over + set A(%a) following + set A(%b) over + } + 5 { + set A(%t1) over + set A(%a) following + set A(%b) preceding + set A(%w) current + set A(%alias) filter + set A(%typename) window + } + + 6 { + set A(%a) window + } +} { + set A(%t1) t1 + set A(%a) a + set A(%b) b + set A(%w) w + set A(%alias) alias + set A(%typename) integer + eval $vars + + set MAP [array get A] + set setup_sql [string map $MAP $setup] + reset_db + execsql $setup_sql + + do_execsql_test 1.$tn.1 [string map $MAP { + SELECT group_concat(%a, '.') OVER (ORDER BY %b) FROM %t1 + }] {1 1.2 1.2.3 1.2.3.4 1.2.3.4.5} + + do_execsql_test 1.$tn.2 [string map $MAP { + SELECT sum(%a) OVER %w FROM %t1 WINDOW %w AS (ORDER BY %b) + }] {1 3 6 10 15} + + do_execsql_test 1.$tn.3 [string map $MAP { + SELECT sum(%alias.%a) OVER %w FROM %t1 %alias WINDOW %w AS (ORDER BY %b) + }] {1 3 6 10 15} + + do_execsql_test 1.$tn.4 [string map $MAP { + SELECT sum(%a) %alias FROM %t1 + }] {15} +} + + +proc winproc {args} { return "window: $args" } +db func window winproc +do_execsql_test 2.0 { + SELECT window('hello world'); +} {{window: {hello world}}} + +proc wincmp {a b} { string compare $b $a } +db collate window wincmp +do_execsql_test 3.0 { + CREATE TABLE window(x COLLATE window); + INSERT INTO window VALUES('bob'), ('alice'), ('cate'); + SELECT * FROM window ORDER BY x COLLATE window; +} {cate bob alice} +do_execsql_test 3.1 { + DROP TABLE window; + CREATE TABLE x1(x); + INSERT INTO x1 VALUES('bob'), ('alice'), ('cate'); + CREATE INDEX window ON x1(x COLLATE window); + SELECT * FROM x1 ORDER BY x COLLATE window; +} {cate bob alice} + + +do_execsql_test 4.0 { CREATE TABLE t4(x, y); } + +# do_execsql_test 4.1 { PRAGMA parser_trace = 1 } +do_execsql_test 4.1 { + SELECT * FROM t4 window, t4; +} + + +finish_test + diff --git a/tool/lempar.c b/tool/lempar.c index d625fcb0a4..feb79e3a58 100644 --- a/tool/lempar.c +++ b/tool/lempar.c @@ -1068,3 +1068,17 @@ void Parse( #endif return; } + +/* +** Return the fallback token corresponding to canonical token iToken, or +** 0 if iToken has no fallback. +*/ +int ParseFallback(int iToken){ +#ifdef YYFALLBACK + if( iToken Date: Fri, 29 Jun 2018 19:54:51 +0000 Subject: [PATCH 4/7] Improve on the previous checkin. Still a bit slow. FossilOrigin-Name: c1fb41aa7b7207b81ee1d5d32da3380b36d694033b87a2873981e0c6437ba956 --- manifest | 12 ++++++------ manifest.uuid | 2 +- src/tokenize.c | 19 +++++++++---------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/manifest b/manifest index e6513a564a..f3ee787749 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Instead\sof\susing\sa\slemon\s%fallback\sdirective,\shave\sthe\stokenizer\stry\sto\sfigure\nout\swhether\san\sinstance\sof\s"WINDOW"\sshould\sbe\sTK_WINDOW\sor\sTK_ID. -D 2018-06-29T17:44:52.692 +C Improve\son\sthe\sprevious\scheckin.\sStill\sa\sbit\sslow. +D 2018-06-29T19:54:51.681 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F Makefile.in 0a3a6c81e6fcb969ff9106e882f0a08547014ba463cb6beca4c4efaecc924ee6 @@ -558,7 +558,7 @@ F src/test_windirent.h 90dfbe95442c9762357fe128dc7ae3dc199d006de93eb33ba3972e0a9 F src/test_window.c add59ee68568868129516999f30a68e8ab2afd276e272aba4f633c9fc52c1bb1 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c -F src/tokenize.c e25caaeb2ad912a378e528771d843badbaee560f2eb8c25dbface05a32c2e4cf +F src/tokenize.c c747c0bd913957db6c8d04b990dc89cd7e386403ef32fe23f77182f83fe1c422 F src/treeview.c 2c5c4bc0a443401db5fd621542150452ddf5055d38edd4eef868bc2b6bfb0260 F src/trigger.c 4ace6d1d5ba9a89822deb287317f33c810440526eafe185c2d8a48c31df1e995 F src/update.c 46dc24c6158446aaab45caee09b6d99327cb479268b83ffeb5b701823da3b67b @@ -1744,7 +1744,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 7c4b879bdb10f30260f0fb28fcb559ab0949865d10f4ff2cf13834a326198998 -R 53ad10aee784d2508443a129bf20d430 +P 022079cb0d67be5ac0a50dd9a4d41ee55ce8df681ecd0a544170d75fc8649978 +R 80db462d4d1bca4df50e900a3c59e228 U dan -Z a766b00041899c4982e4087a15ea2a82 +Z d426a5a866f464dd5bcd7eefbf14a41b diff --git a/manifest.uuid b/manifest.uuid index 2825dfd995..0463aaf42f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -022079cb0d67be5ac0a50dd9a4d41ee55ce8df681ecd0a544170d75fc8649978 \ No newline at end of file +c1fb41aa7b7207b81ee1d5d32da3380b36d694033b87a2873981e0c6437ba956 \ No newline at end of file diff --git a/src/tokenize.c b/src/tokenize.c index 10ad25e29f..cfd631344b 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -222,20 +222,21 @@ static int windowGetToken(const unsigned char **pz){ ** uses windowGetToken(). This is to avoid recursion if the input is similar ** to "window window window window". */ -static void analyzeWindowKeyword(const unsigned char *z, int *tokenType){ +static int analyzeWindowKeyword(const unsigned char *z){ int t; - assert( *tokenType==TK_WINDOW ); + int ret = TK_WINDOW; while( (t = windowGetToken(&z))==TK_SPACE ); if( t!=TK_ID && t!=TK_STRING && t!=TK_JOIN_KW && sqlite3ParserFallback(t)!=TK_ID ){ - *tokenType = TK_ID; + ret = TK_ID; }else{ while( (t = windowGetToken(&z))==TK_SPACE ); if( t!=TK_AS ){ - *tokenType = TK_ID; + ret = TK_ID; } } + return ret; } /* @@ -482,12 +483,7 @@ int sqlite3GetToken(const unsigned char *z, int *tokenType){ break; } *tokenType = TK_ID; - keywordCode((char*)z, i, tokenType); - if( *tokenType==TK_WINDOW ){ - assert( i==6 ); - analyzeWindowKeyword(&z[6], tokenType); - } - return i; + return keywordCode((char*)z, i, tokenType); } case CC_X: { #ifndef SQLITE_OMIT_BLOB_LITERAL @@ -594,6 +590,9 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ } zSql += n; }else{ + if( tokenType==TK_WINDOW ){ + tokenType = analyzeWindowKeyword((const u8*)&zSql[6]); + } pParse->sLastToken.z = zSql; pParse->sLastToken.n = n; sqlite3Parser(pEngine, tokenType, pParse->sLastToken); From d437ac0c58e42ad3cb8bb71986af1a6e5ded5464 Mon Sep 17 00:00:00 2001 From: dan Date: Fri, 29 Jun 2018 20:21:24 +0000 Subject: [PATCH 5/7] Further tweaks to sqlite3RunParser(). FossilOrigin-Name: eef61ffab7fa36b126f57bf7028dd35c67ed4617c47145be059f91e58023b0a4 --- manifest | 12 +++++----- manifest.uuid | 2 +- src/tokenize.c | 60 +++++++++++++++++++++++++------------------------- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/manifest b/manifest index f3ee787749..92a7f4571f 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Improve\son\sthe\sprevious\scheckin.\sStill\sa\sbit\sslow. -D 2018-06-29T19:54:51.681 +C Further\stweaks\sto\ssqlite3RunParser(). +D 2018-06-29T20:21:24.547 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F Makefile.in 0a3a6c81e6fcb969ff9106e882f0a08547014ba463cb6beca4c4efaecc924ee6 @@ -558,7 +558,7 @@ F src/test_windirent.h 90dfbe95442c9762357fe128dc7ae3dc199d006de93eb33ba3972e0a9 F src/test_window.c add59ee68568868129516999f30a68e8ab2afd276e272aba4f633c9fc52c1bb1 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c -F src/tokenize.c c747c0bd913957db6c8d04b990dc89cd7e386403ef32fe23f77182f83fe1c422 +F src/tokenize.c 1a774b6a5763356075611829db77fcdd71d19e8b62e04ada2465452d035fa29a F src/treeview.c 2c5c4bc0a443401db5fd621542150452ddf5055d38edd4eef868bc2b6bfb0260 F src/trigger.c 4ace6d1d5ba9a89822deb287317f33c810440526eafe185c2d8a48c31df1e995 F src/update.c 46dc24c6158446aaab45caee09b6d99327cb479268b83ffeb5b701823da3b67b @@ -1744,7 +1744,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 022079cb0d67be5ac0a50dd9a4d41ee55ce8df681ecd0a544170d75fc8649978 -R 80db462d4d1bca4df50e900a3c59e228 +P c1fb41aa7b7207b81ee1d5d32da3380b36d694033b87a2873981e0c6437ba956 +R c80a7a4c6f0c2361268be820ff770081 U dan -Z d426a5a866f464dd5bcd7eefbf14a41b +Z f55b75e859bb2a1b9b8dde585520a141 diff --git a/manifest.uuid b/manifest.uuid index 0463aaf42f..babf03de84 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c1fb41aa7b7207b81ee1d5d32da3380b36d694033b87a2873981e0c6437ba956 \ No newline at end of file +eef61ffab7fa36b126f57bf7028dd35c67ed4617c47145be059f91e58023b0a4 \ No newline at end of file diff --git a/src/tokenize.c b/src/tokenize.c index cfd631344b..c9f586a7cb 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -559,24 +559,11 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ assert( pParse->nVar==0 ); assert( pParse->pVList==0 ); while( 1 ){ - if( zSql[0]!=0 ){ - n = sqlite3GetToken((u8*)zSql, &tokenType); - mxSqlLen -= n; - if( mxSqlLen<0 ){ - pParse->rc = SQLITE_TOOBIG; - break; - } - }else{ - /* Upon reaching the end of input, call the parser two more times - ** with tokens TK_SEMI and 0, in that order. */ - if( lastTokenParsed==TK_SEMI ){ - tokenType = 0; - }else if( lastTokenParsed==0 ){ - break; - }else{ - tokenType = TK_SEMI; - } - n = 0; + n = sqlite3GetToken((u8*)zSql, &tokenType); + mxSqlLen -= n; + if( mxSqlLen<0 ){ + pParse->rc = SQLITE_TOOBIG; + break; } if( tokenType>=TK_SPACE ){ assert( tokenType==TK_SPACE || tokenType==TK_ILLEGAL ); @@ -584,22 +571,35 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ pParse->rc = SQLITE_INTERRUPT; break; } - if( tokenType==TK_ILLEGAL ){ + if( tokenType==TK_SPACE ){ + zSql += n; + continue; + } + if( zSql[0]==0 ){ + /* Upon reaching the end of input, call the parser two more times + ** with tokens TK_SEMI and 0, in that order. */ + if( lastTokenParsed==TK_SEMI ){ + tokenType = 0; + }else if( lastTokenParsed==0 ){ + break; + }else{ + tokenType = TK_SEMI; + } + n = 0; + }else{ sqlite3ErrorMsg(pParse, "unrecognized token: \"%.*s\"", n, zSql); break; } - zSql += n; - }else{ - if( tokenType==TK_WINDOW ){ - tokenType = analyzeWindowKeyword((const u8*)&zSql[6]); - } - pParse->sLastToken.z = zSql; - pParse->sLastToken.n = n; - sqlite3Parser(pEngine, tokenType, pParse->sLastToken); - lastTokenParsed = tokenType; - zSql += n; - if( pParse->rc!=SQLITE_OK || db->mallocFailed ) break; } + else if( tokenType==TK_WINDOW ){ + tokenType = analyzeWindowKeyword((const u8*)&zSql[6]); + } + pParse->sLastToken.z = zSql; + pParse->sLastToken.n = n; + sqlite3Parser(pEngine, tokenType, pParse->sLastToken); + lastTokenParsed = tokenType; + zSql += n; + if( pParse->rc!=SQLITE_OK || db->mallocFailed ) break; } assert( nErr==0 ); #ifdef YYTRACKMAXSTACKDEPTH From 34a7d790ba7cf95d96b3d6616d98ac977f698202 Mon Sep 17 00:00:00 2001 From: dan Date: Fri, 29 Jun 2018 20:43:33 +0000 Subject: [PATCH 6/7] Further performance related tweaks for sqlite3RunParser(). FossilOrigin-Name: 5eb4776598f5bba7ef21a2c58c03105544da73d642d7ffc146f84eff1993d71e --- manifest | 16 ++-- manifest.uuid | 2 +- src/parse.y | 197 +++++++++++++++++++++++++------------------------ src/resolve.c | 4 + src/tokenize.c | 18 ++++- 5 files changed, 129 insertions(+), 108 deletions(-) diff --git a/manifest b/manifest index 92a7f4571f..a487454ad8 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Further\stweaks\sto\ssqlite3RunParser(). -D 2018-06-29T20:21:24.547 +C Further\sperformance\srelated\stweaks\sfor\ssqlite3RunParser(). +D 2018-06-29T20:43:33.493 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F Makefile.in 0a3a6c81e6fcb969ff9106e882f0a08547014ba463cb6beca4c4efaecc924ee6 @@ -484,7 +484,7 @@ F src/os_win.c ac29c25cde4cfb4adacc59cdec4aa45698ca0e29164ea127859585ccd9faa354 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a F src/pager.c 1bb6a57fa0465296a4d6109a1a64610a0e7adde1f3acf3ef539a9d972908ce8f F src/pager.h c571b064df842ec8f2e90855dead9acf4cbe0d1b2c05afe0ef0d0145f7fd0388 -F src/parse.y 216e0e953df076ef89668614e5aab8b489e0c2c24f7ff17cf42e7bea802793a9 +F src/parse.y 5a53ee98a8bc76c526ae9b4a05cc4912f6b7b2e2a601a8fccc38b7b3b7830f97 F src/pcache.c 135ef0bc6fb2e3b7178d49ab5c9176254c8a691832c1bceb1156b2fbdd0869bd F src/pcache.h 072f94d29281cffd99e46c1539849f248c4b56ae7684c1f36626797fee375170 F src/pcache1.c 716975564c15eb6679e97f734cec1bfd6c16ac3d4010f05f1f8e509fc7d19880 @@ -493,7 +493,7 @@ F src/pragma.h bb83728944b42f6d409c77f5838a8edbdb0fe83046c5496ffc9602b40340a324 F src/prepare.c e966ecc97c3671ff0e96227c8c877b83f2d33ea371ee190bbf1698b36b5605c0 F src/printf.c 7f6f3cba8e0c49c19e30a1ff4e9aeda6e06814dcbad4b664a69e1b6cb6e7e365 F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384 -F src/resolve.c 43b97be1b1bfbe7aa6a0e8608f5a022eac8cd2d727bdf4d0287371ba32218e7b +F src/resolve.c d8359c59c683fc33324c3a0abe0d393304861f05dd2e0d47e5974a501bcf4682 F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac F src/select.c 82aa95aa1cfeadef5ecae1126afff1efca64c49050bbf7116425a8a1d4fc5e6c F src/shell.c.in 8578421c5fb2a972461b2a996f7173646e55e0dbd2a2eee30c8f5dc7d3dbadfd @@ -558,7 +558,7 @@ F src/test_windirent.h 90dfbe95442c9762357fe128dc7ae3dc199d006de93eb33ba3972e0a9 F src/test_window.c add59ee68568868129516999f30a68e8ab2afd276e272aba4f633c9fc52c1bb1 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c -F src/tokenize.c 1a774b6a5763356075611829db77fcdd71d19e8b62e04ada2465452d035fa29a +F src/tokenize.c a9b97ce736f958390e51ed9f03a152403a2b77d54e6d5729f1ee1f365878d507 F src/treeview.c 2c5c4bc0a443401db5fd621542150452ddf5055d38edd4eef868bc2b6bfb0260 F src/trigger.c 4ace6d1d5ba9a89822deb287317f33c810440526eafe185c2d8a48c31df1e995 F src/update.c 46dc24c6158446aaab45caee09b6d99327cb479268b83ffeb5b701823da3b67b @@ -1744,7 +1744,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P c1fb41aa7b7207b81ee1d5d32da3380b36d694033b87a2873981e0c6437ba956 -R c80a7a4c6f0c2361268be820ff770081 +P eef61ffab7fa36b126f57bf7028dd35c67ed4617c47145be059f91e58023b0a4 +R 6ded36ebdbd58ad68c3d8e5fd0ba8d3c U dan -Z f55b75e859bb2a1b9b8dde585520a141 +Z d818b642c548723f779df4a6cae08814 diff --git a/manifest.uuid b/manifest.uuid index babf03de84..afa47307eb 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -eef61ffab7fa36b126f57bf7028dd35c67ed4617c47145be059f91e58023b0a4 \ No newline at end of file +5eb4776598f5bba7ef21a2c58c03105544da73d642d7ffc146f84eff1993d71e \ No newline at end of file diff --git a/src/parse.y b/src/parse.y index 0ed0b28bad..5097314329 100644 --- a/src/parse.y +++ b/src/parse.y @@ -1046,102 +1046,6 @@ term(A) ::= CTIME_KW(OP). { A = sqlite3ExprFunction(pParse, 0, &OP); } -%ifndef SQLITE_OMIT_WINDOWFUNC - -%type windowdefn_opt {Window*} -%destructor windowdefn_opt {sqlite3WindowDelete(pParse->db, $$);} -windowdefn_opt(A) ::= . { A = 0; } -windowdefn_opt(A) ::= WINDOW windowdefn_list(B). { A = B; } - -%type windowdefn_list {Window*} -%destructor windowdefn_list {sqlite3WindowDelete(pParse->db, $$);} -windowdefn_list(A) ::= windowdefn(Z). { A = Z; } -windowdefn_list(A) ::= windowdefn_list(Y) COMMA windowdefn(Z). { - if( Z ) Z->pNextWin = Y; - A = Z; -} - -%type windowdefn {Window*} -%destructor windowdefn {sqlite3WindowDelete(pParse->db, $$);} -windowdefn(A) ::= nm(X) AS window(Y). { - if( Y ){ - Y->zName = sqlite3DbStrNDup(pParse->db, X.z, X.n); - } - A = Y; -} - -%type over_opt {Window*} -%destructor over_opt {sqlite3WindowDelete(pParse->db, $$);} - -%type window {Window*} -%destructor window {sqlite3WindowDelete(pParse->db, $$);} - -%type frame_opt {Window*} -%destructor frame_opt {sqlite3WindowDelete(pParse->db, $$);} - -%type window_or_nm {Window*} -%destructor window_or_nm { -sqlite3WindowDelete(pParse->db, $$);} - -%type part_opt {ExprList*} -%destructor part_opt {sqlite3ExprListDelete(pParse->db, $$);} - -%type filter_opt {Expr*} -%destructor filter_opt {sqlite3ExprDelete(pParse->db, $$);} - -%type range_or_rows {int} - -%type frame_bound {struct FrameBound} -%destructor frame_bound {sqlite3ExprDelete(pParse->db, $$.pExpr);} - -over_opt(A) ::= . { A = 0; } -over_opt(A) ::= filter_opt(W) OVER window_or_nm(Z). { - A = Z; - if( A ) A->pFilter = W; -} - -window_or_nm(A) ::= window(Z). {A = Z;} -window_or_nm(A) ::= nm(Z). { - A = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window)); - if( A ){ - A->zName = sqlite3DbStrNDup(pParse->db, Z.z, Z.n); - } -} - -window(A) ::= LP part_opt(X) orderby_opt(Y) frame_opt(Z) RP. { - A = Z; - if( A ){ - A->pPartition = X; - A->pOrderBy = Y; - } -} - -part_opt(A) ::= PARTITION BY exprlist(X). { A = X; } -part_opt(A) ::= . { A = 0; } -filter_opt(A) ::= . { A = 0; } -filter_opt(A) ::= FILTER LP WHERE expr(X) RP. { A = X; } - -frame_opt(A) ::= . { - A = sqlite3WindowAlloc(pParse, TK_RANGE, TK_UNBOUNDED, 0, TK_CURRENT, 0); -} -frame_opt(A) ::= range_or_rows(X) frame_bound(Y). { - A = sqlite3WindowAlloc(pParse, X, Y.eType, Y.pExpr, TK_CURRENT, 0); -} -frame_opt(A) ::= range_or_rows(X) BETWEEN frame_bound(Y) AND frame_bound(Z). { - A = sqlite3WindowAlloc(pParse, X, Y.eType, Y.pExpr, Z.eType, Z.pExpr); -} - -range_or_rows(A) ::= RANGE. { A = TK_RANGE; } -range_or_rows(A) ::= ROWS. { A = TK_ROWS; } - -frame_bound(A) ::= UNBOUNDED PRECEDING. { A.eType = TK_UNBOUNDED; A.pExpr = 0; } -frame_bound(A) ::= expr(X) PRECEDING. { A.eType = TK_PRECEDING; A.pExpr = X; } -frame_bound(A) ::= CURRENT ROW. { A.eType = TK_CURRENT ; A.pExpr = 0; } -frame_bound(A) ::= expr(X) FOLLOWING. { A.eType = TK_FOLLOWING; A.pExpr = X; } -frame_bound(A) ::= UNBOUNDED FOLLOWING. { A.eType = TK_UNBOUNDED; A.pExpr = 0; } - -%endif // SQLITE_OMIT_WINDOWFUNC - expr(A) ::= LP nexprlist(X) COMMA expr(Y) RP. { ExprList *pList = sqlite3ExprListAppend(pParse, X, Y); A = sqlite3PExpr(pParse, TK_VECTOR, 0, 0); @@ -1693,3 +1597,104 @@ wqlist(A) ::= wqlist(A) COMMA nm(X) eidlist_opt(Y) AS LP select(Z) RP. { A = sqlite3WithAdd(pParse, A, &X, Y, Z); } %endif SQLITE_OMIT_CTE + +//////////////////////// WINDOW FUNCTION EXPRESSIONS ///////////////////////// +// These must be at the end of this file. Specifically, the windowdefn_opt +// rule must be the very last in the file. This causes the integer value +// assigned to the TK_WINDOW token to be larger than all other tokens that +// may be output by the tokenizer except TK_SPACE and TK_ILLEGAL. +// +%ifndef SQLITE_OMIT_WINDOWFUNC +%type windowdefn_list {Window*} +%destructor windowdefn_list {sqlite3WindowDelete(pParse->db, $$);} +windowdefn_list(A) ::= windowdefn(Z). { A = Z; } +windowdefn_list(A) ::= windowdefn_list(Y) COMMA windowdefn(Z). { + if( Z ) Z->pNextWin = Y; + A = Z; +} + +%type windowdefn {Window*} +%destructor windowdefn {sqlite3WindowDelete(pParse->db, $$);} +windowdefn(A) ::= nm(X) AS window(Y). { + if( Y ){ + Y->zName = sqlite3DbStrNDup(pParse->db, X.z, X.n); + } + A = Y; +} + +%type over_opt {Window*} +%destructor over_opt {sqlite3WindowDelete(pParse->db, $$);} + +%type window {Window*} +%destructor window {sqlite3WindowDelete(pParse->db, $$);} + +%type frame_opt {Window*} +%destructor frame_opt {sqlite3WindowDelete(pParse->db, $$);} + +%type window_or_nm {Window*} +%destructor window_or_nm { +sqlite3WindowDelete(pParse->db, $$);} + +%type part_opt {ExprList*} +%destructor part_opt {sqlite3ExprListDelete(pParse->db, $$);} + +%type filter_opt {Expr*} +%destructor filter_opt {sqlite3ExprDelete(pParse->db, $$);} + +%type range_or_rows {int} + +%type frame_bound {struct FrameBound} +%destructor frame_bound {sqlite3ExprDelete(pParse->db, $$.pExpr);} + +over_opt(A) ::= . { A = 0; } +over_opt(A) ::= filter_opt(W) OVER window_or_nm(Z). { + A = Z; + if( A ) A->pFilter = W; +} + +window_or_nm(A) ::= window(Z). {A = Z;} +window_or_nm(A) ::= nm(Z). { + A = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window)); + if( A ){ + A->zName = sqlite3DbStrNDup(pParse->db, Z.z, Z.n); + } +} + +window(A) ::= LP part_opt(X) orderby_opt(Y) frame_opt(Z) RP. { + A = Z; + if( A ){ + A->pPartition = X; + A->pOrderBy = Y; + } +} + +part_opt(A) ::= PARTITION BY exprlist(X). { A = X; } +part_opt(A) ::= . { A = 0; } +filter_opt(A) ::= . { A = 0; } +filter_opt(A) ::= FILTER LP WHERE expr(X) RP. { A = X; } + +frame_opt(A) ::= . { + A = sqlite3WindowAlloc(pParse, TK_RANGE, TK_UNBOUNDED, 0, TK_CURRENT, 0); +} +frame_opt(A) ::= range_or_rows(X) frame_bound(Y). { + A = sqlite3WindowAlloc(pParse, X, Y.eType, Y.pExpr, TK_CURRENT, 0); +} +frame_opt(A) ::= range_or_rows(X) BETWEEN frame_bound(Y) AND frame_bound(Z). { + A = sqlite3WindowAlloc(pParse, X, Y.eType, Y.pExpr, Z.eType, Z.pExpr); +} + +range_or_rows(A) ::= RANGE. { A = TK_RANGE; } +range_or_rows(A) ::= ROWS. { A = TK_ROWS; } + +frame_bound(A) ::= UNBOUNDED PRECEDING. { A.eType = TK_UNBOUNDED; A.pExpr = 0; } +frame_bound(A) ::= expr(X) PRECEDING. { A.eType = TK_PRECEDING; A.pExpr = X; } +frame_bound(A) ::= CURRENT ROW. { A.eType = TK_CURRENT ; A.pExpr = 0; } +frame_bound(A) ::= expr(X) FOLLOWING. { A.eType = TK_FOLLOWING; A.pExpr = X; } +frame_bound(A) ::= UNBOUNDED FOLLOWING. { A.eType = TK_UNBOUNDED; A.pExpr = 0; } + +%type windowdefn_opt {Window*} +%destructor windowdefn_opt {sqlite3WindowDelete(pParse->db, $$);} +windowdefn_opt(A) ::= . { A = 0; } +windowdefn_opt(A) ::= WINDOW windowdefn_list(B). { A = B; } +%endif // SQLITE_OMIT_WINDOWFUNC + diff --git a/src/resolve.c b/src/resolve.c index 60453c17a0..b6389eba6d 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -798,7 +798,11 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ pNC->nErr++; } if( is_agg ){ +#ifndef SQLITE_OMIT_WINDOWFUNC pNC->ncFlags &= ~(pExpr->pWin ? NC_AllowWin : NC_AllowAgg); +#else + pNC->ncFlags &= ~NC_AllowAgg; +#endif } sqlite3WalkExprList(pWalker, pList); if( is_agg ){ diff --git a/src/tokenize.c b/src/tokenize.c index c9f586a7cb..72f160d59b 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -188,6 +188,7 @@ const char sqlite3IsEbcdicIdChar[] = { int sqlite3IsIdChar(u8 c){ return IdChar(c); } #endif +#ifndef SQLITE_OMIT_WINDOWFUNC /* ** Return the id of the next token in string (*pz). Before returning, set ** (*pz) to point to the byte following the parsed token. @@ -207,7 +208,9 @@ static int windowGetToken(const unsigned char **pz){ *pz = z; return ret; } +#endif // SQLITE_OMIT_WINDOWFUNC +#ifndef SQLITE_OMIT_WINDOWFUNC /* ** The tokenizer has just parsed the keyword WINDOW. In this case the token ** may really be the keyword (TK_WINDOW), or may be an identifier (TK_ID). @@ -238,6 +241,7 @@ static int analyzeWindowKeyword(const unsigned char *z){ } return ret; } +#endif // SQLITE_OMIT_WINDOWFUNC /* ** Return the length (in bytes) of the token that begins at z[0]. @@ -565,8 +569,15 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ pParse->rc = SQLITE_TOOBIG; break; } +#ifndef SQLITE_OMIT_WINDOWFUNC + if( tokenType>=TK_WINDOW ){ + assert( tokenType==TK_SPACE + || tokenType==TK_ILLEGAL || tokenType==TK_WINDOW + ); +#else if( tokenType>=TK_SPACE ){ assert( tokenType==TK_SPACE || tokenType==TK_ILLEGAL ); +#endif // SQLITE_OMIT_WINDOWFUNC if( db->u1.isInterrupted ){ pParse->rc = SQLITE_INTERRUPT; break; @@ -586,14 +597,15 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ tokenType = TK_SEMI; } n = 0; +#ifndef SQLITE_OMIT_WINDOWFUNC + }else if( tokenType==TK_WINDOW ){ + tokenType = analyzeWindowKeyword((const u8*)&zSql[6]); +#endif // SQLITE_OMIT_WINDOWFUNC }else{ sqlite3ErrorMsg(pParse, "unrecognized token: \"%.*s\"", n, zSql); break; } } - else if( tokenType==TK_WINDOW ){ - tokenType = analyzeWindowKeyword((const u8*)&zSql[6]); - } pParse->sLastToken.z = zSql; pParse->sLastToken.n = n; sqlite3Parser(pEngine, tokenType, pParse->sLastToken); From 6e2210e0a161a7d29e1ad066265de51efcb9cd64 Mon Sep 17 00:00:00 2001 From: dan Date: Sat, 30 Jun 2018 18:54:56 +0000 Subject: [PATCH 7/7] Have the tokenizer handle fallback for tokens "OVER" and "FILTER" in the same way as it does for "WINDOW". FossilOrigin-Name: 12d819e1c17d8036900352b0989c4bfcbc34193c3735bb9af7ab051f0f129d3d --- manifest | 17 ++++---- manifest.uuid | 2 +- src/parse.y | 33 ++++++++------- src/tokenize.c | 101 +++++++++++++++++++++++++++++----------------- test/window6.test | 58 ++++++++++++++++++-------- 5 files changed, 133 insertions(+), 78 deletions(-) diff --git a/manifest b/manifest index a487454ad8..ae9a219479 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Further\sperformance\srelated\stweaks\sfor\ssqlite3RunParser(). -D 2018-06-29T20:43:33.493 +C Have\sthe\stokenizer\shandle\sfallback\sfor\stokens\s"OVER"\sand\s"FILTER"\sin\sthe\ssame\nway\sas\sit\sdoes\sfor\s"WINDOW". +D 2018-06-30T18:54:56.070 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F Makefile.in 0a3a6c81e6fcb969ff9106e882f0a08547014ba463cb6beca4c4efaecc924ee6 @@ -484,7 +484,7 @@ F src/os_win.c ac29c25cde4cfb4adacc59cdec4aa45698ca0e29164ea127859585ccd9faa354 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a F src/pager.c 1bb6a57fa0465296a4d6109a1a64610a0e7adde1f3acf3ef539a9d972908ce8f F src/pager.h c571b064df842ec8f2e90855dead9acf4cbe0d1b2c05afe0ef0d0145f7fd0388 -F src/parse.y 5a53ee98a8bc76c526ae9b4a05cc4912f6b7b2e2a601a8fccc38b7b3b7830f97 +F src/parse.y 9b57f1d0d3d7578ab2917e07ff5d8def4b0aac571113dd7b7cb8108e7194d025 F src/pcache.c 135ef0bc6fb2e3b7178d49ab5c9176254c8a691832c1bceb1156b2fbdd0869bd F src/pcache.h 072f94d29281cffd99e46c1539849f248c4b56ae7684c1f36626797fee375170 F src/pcache1.c 716975564c15eb6679e97f734cec1bfd6c16ac3d4010f05f1f8e509fc7d19880 @@ -558,7 +558,7 @@ F src/test_windirent.h 90dfbe95442c9762357fe128dc7ae3dc199d006de93eb33ba3972e0a9 F src/test_window.c add59ee68568868129516999f30a68e8ab2afd276e272aba4f633c9fc52c1bb1 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c -F src/tokenize.c a9b97ce736f958390e51ed9f03a152403a2b77d54e6d5729f1ee1f365878d507 +F src/tokenize.c 0e3e0462f7da08bf95b3da1dca4c01cd2c3ca1d988ed0f9d2f66334a975e4020 F src/treeview.c 2c5c4bc0a443401db5fd621542150452ddf5055d38edd4eef868bc2b6bfb0260 F src/trigger.c 4ace6d1d5ba9a89822deb287317f33c810440526eafe185c2d8a48c31df1e995 F src/update.c 46dc24c6158446aaab45caee09b6d99327cb479268b83ffeb5b701823da3b67b @@ -1626,7 +1626,7 @@ F test/window3.test 47884c240d0d5234ad9c6da65452cfec1bfa69ec6f9c4158cd9750c3d88d F test/window4.tcl 7cec7e578aa9f78b7265bff8d552cda17a1d8d89f0449d0e74970a527b8846f5 F test/window4.test dcd8767869988e0d23d56bc3f8b46ec116de23127b81b5f66fd48d5529072ed1 F test/window5.test a4835b96d30eb3b81a1dbc683e333e57a614645eb6f2ae476a7ed2addf0b0f1f -F test/window6.test 97bd18d5cccd612b8a93be4017a7a26c2968a1c868a458d0d66d8d8cc62e33f8 +F test/window6.test fb4e464993630c3d6023e906aa6705667062600a8341085b7fd8c201f4c2f411 F test/windowfault.test 97d5fc404308edb579a5a183e294ed874c844ecf01f0a28ba46df3141ebaee1f F test/with1.test 58475190cd8caaeebea8cfeb2a264ec97a0c492b8ffe9ad20cefbb23df462f96 F test/with2.test e0030e2f0267a910d6c0e4f46f2dfe941c1cc0d4f659ba69b3597728e7e8f1ab @@ -1744,7 +1744,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P eef61ffab7fa36b126f57bf7028dd35c67ed4617c47145be059f91e58023b0a4 -R 6ded36ebdbd58ad68c3d8e5fd0ba8d3c +P 5eb4776598f5bba7ef21a2c58c03105544da73d642d7ffc146f84eff1993d71e +R 020addcbc17f30026d883f2e135b0a63 +T +closed a3e9e62c6cafac5b5f103edcff77085b463085fca60c515aa16723c3d0006d44 U dan -Z d818b642c548723f779df4a6cae08814 +Z f871d44af5dded308446b721ea515004 diff --git a/manifest.uuid b/manifest.uuid index afa47307eb..01c135be3b 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -5eb4776598f5bba7ef21a2c58c03105544da73d642d7ffc146f84eff1993d71e \ No newline at end of file +12d819e1c17d8036900352b0989c4bfcbc34193c3735bb9af7ab051f0f129d3d \ No newline at end of file diff --git a/src/parse.y b/src/parse.y index 5097314329..42efe6fb6f 100644 --- a/src/parse.y +++ b/src/parse.y @@ -217,8 +217,7 @@ columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,&A,&Y);} EXCEPT INTERSECT UNION %endif SQLITE_OMIT_COMPOUND_SELECT %ifndef SQLITE_OMIT_WINDOWFUNC - CURRENT FILTER FOLLOWING OVER PARTITION - PRECEDING RANGE UNBOUNDED + CURRENT FOLLOWING PARTITION PRECEDING RANGE UNBOUNDED %endif SQLITE_OMIT_WINDOWFUNC REINDEX RENAME CTIME_KW IF . @@ -1599,10 +1598,10 @@ wqlist(A) ::= wqlist(A) COMMA nm(X) eidlist_opt(Y) AS LP select(Z) RP. { %endif SQLITE_OMIT_CTE //////////////////////// WINDOW FUNCTION EXPRESSIONS ///////////////////////// -// These must be at the end of this file. Specifically, the windowdefn_opt -// rule must be the very last in the file. This causes the integer value -// assigned to the TK_WINDOW token to be larger than all other tokens that -// may be output by the tokenizer except TK_SPACE and TK_ILLEGAL. +// These must be at the end of this file. Specifically, the rules that +// introduce tokens WINDOW, OVER and FILTER must appear last. This causes +// the integer values assigned to these tokens to be larger than all other +// tokens that may be output by the tokenizer except TK_SPACE and TK_ILLEGAL. // %ifndef SQLITE_OMIT_WINDOWFUNC %type windowdefn_list {Window*} @@ -1622,9 +1621,6 @@ windowdefn(A) ::= nm(X) AS window(Y). { A = Y; } -%type over_opt {Window*} -%destructor over_opt {sqlite3WindowDelete(pParse->db, $$);} - %type window {Window*} %destructor window {sqlite3WindowDelete(pParse->db, $$);} @@ -1646,12 +1642,6 @@ sqlite3WindowDelete(pParse->db, $$);} %type frame_bound {struct FrameBound} %destructor frame_bound {sqlite3ExprDelete(pParse->db, $$.pExpr);} -over_opt(A) ::= . { A = 0; } -over_opt(A) ::= filter_opt(W) OVER window_or_nm(Z). { - A = Z; - if( A ) A->pFilter = W; -} - window_or_nm(A) ::= window(Z). {A = Z;} window_or_nm(A) ::= nm(Z). { A = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window)); @@ -1670,8 +1660,6 @@ window(A) ::= LP part_opt(X) orderby_opt(Y) frame_opt(Z) RP. { part_opt(A) ::= PARTITION BY exprlist(X). { A = X; } part_opt(A) ::= . { A = 0; } -filter_opt(A) ::= . { A = 0; } -filter_opt(A) ::= FILTER LP WHERE expr(X) RP. { A = X; } frame_opt(A) ::= . { A = sqlite3WindowAlloc(pParse, TK_RANGE, TK_UNBOUNDED, 0, TK_CURRENT, 0); @@ -1696,5 +1684,16 @@ frame_bound(A) ::= UNBOUNDED FOLLOWING. { A.eType = TK_UNBOUNDED; A.pExpr = 0; } %destructor windowdefn_opt {sqlite3WindowDelete(pParse->db, $$);} windowdefn_opt(A) ::= . { A = 0; } windowdefn_opt(A) ::= WINDOW windowdefn_list(B). { A = B; } + +%type over_opt {Window*} +%destructor over_opt {sqlite3WindowDelete(pParse->db, $$);} +over_opt(A) ::= . { A = 0; } +over_opt(A) ::= filter_opt(W) OVER window_or_nm(Z). { + A = Z; + if( A ) A->pFilter = W; +} + +filter_opt(A) ::= . { A = 0; } +filter_opt(A) ::= FILTER LP WHERE expr(X) RP. { A = X; } %endif // SQLITE_OMIT_WINDOWFUNC diff --git a/src/tokenize.c b/src/tokenize.c index 72f160d59b..15678ed698 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -192,54 +192,76 @@ int sqlite3IsIdChar(u8 c){ return IdChar(c); } /* ** Return the id of the next token in string (*pz). Before returning, set ** (*pz) to point to the byte following the parsed token. -** -** This function assumes that any keywords that start with "w" are -** actually TK_ID. */ -static int windowGetToken(const unsigned char **pz){ - int ret; +static int getToken(const unsigned char **pz){ const unsigned char *z = *pz; - if( z[0]=='w' || z[0]=='W' ){ - do { z++; }while( IdChar(z[0]) ); - ret = TK_ID; - }else{ - z += sqlite3GetToken(z, &ret); + int t; /* Token type to return */ + do { + z += sqlite3GetToken(z, &t); + }while( t==TK_SPACE ); + if( t==TK_ID + || t==TK_STRING + || t==TK_JOIN_KW + || t==TK_WINDOW + || t==TK_OVER + || sqlite3ParserFallback(t)==TK_ID + ){ + t = TK_ID; } *pz = z; - return ret; + return t; } -#endif // SQLITE_OMIT_WINDOWFUNC -#ifndef SQLITE_OMIT_WINDOWFUNC /* -** The tokenizer has just parsed the keyword WINDOW. In this case the token -** may really be the keyword (TK_WINDOW), or may be an identifier (TK_ID). -** This function determines which it is by inspecting the next two tokens -** in the input stream. Specifically, the token is TK_WINDOW if the following -** two tokens are: +** The following three functions are called immediately after the tokenizer +** reads the keywords WINDOW, OVER and FILTER, respectively, to determine +** whether the token should be treated as a keyword or an SQL identifier. +** This cannot be handled by the usual lemon %fallback method, due to +** the ambiguity in some constructions. e.g. ** -** * TK_ID, or something else that can be used as a window name, and -** * TK_AS. +** SELECT sum(x) OVER ... ** -** Instead of using sqlite3GetToken() to parse tokens directly, this function -** uses windowGetToken(). This is to avoid recursion if the input is similar -** to "window window window window". +** In the above, "OVER" might be a keyword, or it might be an alias for the +** sum(x) expression. If a "%fallback ID OVER" directive were added to +** grammar, then SQLite would always treat "OVER" as an alias, making it +** impossible to call a window-function without a FILTER clause. +** +** WINDOW is treated as a keyword if: +** +** * the following token is an identifier, or a keyword that can fallback +** to being an identifier, and +** * the token after than one is TK_AS. +** +** OVER is a keyword if: +** +** * the previous token was TK_RP, and +** * the next token is either TK_LP or an identifier. +** +** FILTER is a keyword if: +** +** * the previous token was TK_RP, and +** * the next token is TK_LP. */ static int analyzeWindowKeyword(const unsigned char *z){ int t; - int ret = TK_WINDOW; - while( (t = windowGetToken(&z))==TK_SPACE ); - if( t!=TK_ID && t!=TK_STRING - && t!=TK_JOIN_KW && sqlite3ParserFallback(t)!=TK_ID - ){ - ret = TK_ID; - }else{ - while( (t = windowGetToken(&z))==TK_SPACE ); - if( t!=TK_AS ){ - ret = TK_ID; - } + t = getToken(&z); + if( t!=TK_ID ) return TK_ID; + t = getToken(&z); + if( t!=TK_AS ) return TK_ID; + return TK_WINDOW; +} +static int analyzeOverKeyword(const unsigned char *z, int lastToken){ + if( lastToken==TK_RP ){ + int t = getToken(&z); + if( t==TK_LP || t==TK_ID ) return TK_OVER; } - return ret; + return TK_ID; +} +static int analyzeFilterKeyword(const unsigned char *z, int lastToken){ + if( lastToken==TK_RP && getToken(&z)==TK_LP ){ + return TK_FILTER; + } + return TK_ID; } #endif // SQLITE_OMIT_WINDOWFUNC @@ -571,7 +593,7 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ } #ifndef SQLITE_OMIT_WINDOWFUNC if( tokenType>=TK_WINDOW ){ - assert( tokenType==TK_SPACE + assert( tokenType==TK_SPACE || tokenType==TK_OVER || tokenType==TK_FILTER || tokenType==TK_ILLEGAL || tokenType==TK_WINDOW ); #else @@ -599,7 +621,14 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ n = 0; #ifndef SQLITE_OMIT_WINDOWFUNC }else if( tokenType==TK_WINDOW ){ + assert( n==6 ); tokenType = analyzeWindowKeyword((const u8*)&zSql[6]); + }else if( tokenType==TK_OVER ){ + assert( n==4 ); + tokenType = analyzeOverKeyword((const u8*)&zSql[4], lastTokenParsed); + }else if( tokenType==TK_FILTER ){ + assert( n==6 ); + tokenType = analyzeFilterKeyword((const u8*)&zSql[6], lastTokenParsed); #endif // SQLITE_OMIT_WINDOWFUNC }else{ sqlite3ErrorMsg(pParse, "unrecognized token: \"%.*s\"", n, zSql); diff --git a/test/window6.test b/test/window6.test index a6706fc521..69b597077e 100644 --- a/test/window6.test +++ b/test/window6.test @@ -22,7 +22,7 @@ ifcapable !windowfunc { } set setup { - CREATE TABLE %t1(%a, %b %typename); + CREATE TABLE %t1(%x, %y %typename); INSERT INTO %t1 VALUES(1, 'a'); INSERT INTO %t1 VALUES(2, 'b'); INSERT INTO %t1 VALUES(3, 'c'); @@ -33,28 +33,28 @@ set setup { foreach {tn vars} { 1 {} 2 { set A(%t1) over } - 3 { set A(%a) over } + 3 { set A(%x) over } 4 { set A(%alias) over - set A(%a) following - set A(%b) over + set A(%x) following + set A(%y) over } 5 { - set A(%t1) over - set A(%a) following - set A(%b) preceding + set A(%t1) over + set A(%x) following + set A(%y) preceding set A(%w) current - set A(%alias) filter - set A(%typename) window + set A(%alias) filter + set A(%typename) window } 6 { - set A(%a) window + set A(%x) window } } { set A(%t1) t1 - set A(%a) a - set A(%b) b + set A(%x) x + set A(%y) y set A(%w) w set A(%alias) alias set A(%typename) integer @@ -66,19 +66,19 @@ foreach {tn vars} { execsql $setup_sql do_execsql_test 1.$tn.1 [string map $MAP { - SELECT group_concat(%a, '.') OVER (ORDER BY %b) FROM %t1 + SELECT group_concat(%x, '.') OVER (ORDER BY %y) FROM %t1 }] {1 1.2 1.2.3 1.2.3.4 1.2.3.4.5} do_execsql_test 1.$tn.2 [string map $MAP { - SELECT sum(%a) OVER %w FROM %t1 WINDOW %w AS (ORDER BY %b) + SELECT sum(%x) OVER %w FROM %t1 WINDOW %w AS (ORDER BY %y) }] {1 3 6 10 15} do_execsql_test 1.$tn.3 [string map $MAP { - SELECT sum(%alias.%a) OVER %w FROM %t1 %alias WINDOW %w AS (ORDER BY %b) + SELECT sum(%alias.%x) OVER %w FROM %t1 %alias WINDOW %w AS (ORDER BY %y) }] {1 3 6 10 15} do_execsql_test 1.$tn.4 [string map $MAP { - SELECT sum(%a) %alias FROM %t1 + SELECT sum(%x) %alias FROM %t1 }] {15} } @@ -112,6 +112,32 @@ do_execsql_test 4.1 { SELECT * FROM t4 window, t4; } +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 5.0 { + CREATE TABLE over(x, over); + CREATE TABLE window(x, window); + INSERT INTO over VALUES(1, 2), (3, 4), (5, 6); + INSERT INTO window VALUES(1, 2), (3, 4), (5, 6); + SELECT sum(x) over FROM over +} {9} + +do_execsql_test 5.1 { + SELECT sum(x) over over FROM over WINDOW over AS () +} {9 9 9} + +do_execsql_test 5.2 { + SELECT sum(over) over over over FROM over over WINDOW over AS (ORDER BY over) +} {2 6 12} + +do_execsql_test 5.3 { + SELECT sum(over) over over over FROM over over WINDOW over AS (ORDER BY over); +} {2 6 12} + +do_execsql_test 5.4 { + SELECT sum(window) OVER window window FROM window window window window AS (ORDER BY window); +} {2 6 12} finish_test