Index: main.mk ================================================================== --- main.mk +++ main.mk @@ -496,10 +496,14 @@ $(TOP)/tool/sqldiff.c sqlite3.c $(TLIBS) $(THREADLIB) dbhash$(EXE): $(TOP)/tool/dbhash.c sqlite3.c sqlite3.h $(TCCX) -o dbhash$(EXE) -DSQLITE_THREADSAFE=0 \ $(TOP)/tool/dbhash.c sqlite3.c $(TLIBS) $(THREADLIB) + +faststat1$(EXE): $(TOP)/tool/faststat1.c sqlite3.c sqlite3.h + $(TCCX) -o faststat1$(EXE) -DSQLITE_THREADSAFE=0 \ + $(TOP)/tool/faststat1.c sqlite3.c $(TLIBS) $(THREADLIB) scrub$(EXE): $(TOP)/ext/misc/scrub.c sqlite3.o $(TCC) -I. -DSCRUB_STANDALONE -o scrub$(EXE) $(TOP)/ext/misc/scrub.c sqlite3.o $(THREADLIB) srcck1$(EXE): $(TOP)/tool/srcck1.c Index: src/btree.c ================================================================== --- src/btree.c +++ src/btree.c @@ -5033,10 +5033,77 @@ } } return rc; } + +/* +** Move the cursor pCur to a location within its b-tree that is +** approximately the x/1e9*nRow entry in the table, assuming the +** table contains nRow entries. So, in other words, if x==0 move +** to the first entry and if x=1e9 move to the last entry and if +** x=5e8 move to the middle entry. The final landing spot is +** approximate. +** +** Write an estimate of the number of entries in the b-tree into +** the *pnRowEst variable. +** +** This routine works by first moving the cursor to the root of the +** b-tree, then following pointers down to a leaf, selecting a pointer +** according to x. +** +** The estimated number of entries is found by multiplying the number of +** entries on the leaf page by the number of pointers at each layer of +** non-leaf pages. +** +** Return SQLITE_OK on success or an error code if problems are encountered. +*/ +int sqlite3BtreeMovetoProportional( + BtCursor *pCur, /* Cursor to reposition */ + u32 x, /* approximate location to position the cursor */ + sqlite3_uint64 *pnRowEst /* Write estimated entry count here */ +){ + sqlite3_uint64 n = 1; + int rc; + Pgno chldPg; + u32 mx = 1000000000; + u32 perChild; + u16 rx; + MemPage *pPage; + rc = moveToRoot(pCur); + if( rc ) return rc; + pPage = pCur->apPage[0]; + while( !pPage->leaf ){ + perChild = (mx+pPage->nCell)/(pPage->nCell+1); + if( perChild<1 ) perChild = 1; + rx = x/perChild; + x %= perChild; + mx = perChild; + if( rx>=pPage->nCell ){ + chldPg = get4byte(&pPage->aData[pPage->hdrOffset+8]); + }else{ + chldPg = get4byte(findCell(pPage,rx)); + } + n *= pPage->nCell+1; + pCur->aiIdx[pCur->iPage] = rx; + rc = moveToChild(pCur, chldPg); + if( rc ) return rc; + pPage = pCur->apPage[pCur->iPage]; + } + *pnRowEst = n*pPage->nCell; + if( pPage->nCell==0 ){ + rx = 0; + }else{ + perChild = mx/pPage->nCell; + if( perChild<1 ) perChild = 1; + rx = x/perChild; + if( rx>=pPage->nCell ) rx = pPage->nCell-1; + } + pCur->aiIdx[pCur->iPage] = rx; + + return SQLITE_OK; +} /* Move the cursor so that it points to an entry near the key ** specified by pIdxKey or intKey. Return a success code. ** ** For INTKEY tables, the intKey parameter is used. pIdxKey Index: src/btree.h ================================================================== --- src/btree.h +++ src/btree.h @@ -236,10 +236,11 @@ #ifdef SQLITE_ENABLE_CURSOR_HINTS void sqlite3BtreeCursorHint(BtCursor*, int, ...); #endif int sqlite3BtreeCloseCursor(BtCursor*); +int sqlite3BtreeMovetoProportional(BtCursor*,u32,u64*); int sqlite3BtreeMovetoUnpacked( BtCursor*, UnpackedRecord *pUnKey, i64 intKey, int bias, Index: src/build.c ================================================================== --- src/build.c +++ src/build.c @@ -3582,16 +3582,25 @@ /* ** Append a new element to the given IdList. Create a new IdList if ** need be. ** ** A new IdList is returned, or NULL if malloc() fails. +** +** The zName must have been obtained from sqlite3DbMalloc(). This routine +** accepts ownership of the zName string and will ensure that it is freed +** when no longer in use. */ -IdList *sqlite3IdListAppend(sqlite3 *db, IdList *pList, Token *pToken){ +IdList *sqlite3IdListAppend( + Parse *pParse, /* Parsing context */ + IdList *pList, /* ID list to append to */ + char *zName /* Token to append */ +){ int i; + sqlite3 *db = pParse->db; if( pList==0 ){ pList = sqlite3DbMallocZero(db, sizeof(IdList) ); - if( pList==0 ) return 0; + if( pList==0 ) goto id_list_append_fail; } pList->a = sqlite3ArrayAllocate( db, pList->a, sizeof(pList->a[0]), @@ -3598,14 +3607,18 @@ &pList->nId, &i ); if( i<0 ){ sqlite3IdListDelete(db, pList); - return 0; + goto id_list_append_fail; } - pList->a[i].zName = sqlite3NameFromToken(db, pToken); + pList->a[i].zName = zName; return pList; + +id_list_append_fail: + sqlite3DbFree(db, zName); + return 0; } /* ** Delete an IdList. */ Index: src/parse.y ================================================================== --- src/parse.y +++ src/parse.y @@ -202,14 +202,18 @@ %left STAR SLASH REM. %left CONCAT. %left COLLATE. %right BITNOT. -// An IDENTIFIER can be a generic identifier, or one of several +// An "id" can be a generic identifier, or one of several // keywords. Any non-standard keyword can also be an identifier. // %token_class id ID|INDEXED. + +// A "number" can be either an integer or a floating point value +%token_class number INTEGER|FLOAT. + // The following directive causes tokens ABORT, AFTER, ASC, etc. to // fallback to ID if they will not parse as their original value. // This obviates the need for the "id" nonterminal. // @@ -252,12 +256,12 @@ 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);} -signed ::= plus_num. -signed ::= minus_num. +signed ::= PLUS|MINUS number. +signed ::= number. // "carglist" is a list of additional constraints that come after the // column name and column type in a CREATE TABLE statement. // carglist ::= carglist ccons. @@ -805,16 +809,17 @@ %type idlist_opt {IdList*} %destructor idlist_opt {sqlite3IdListDelete(pParse->db, $$);} %type idlist {IdList*} %destructor idlist {sqlite3IdListDelete(pParse->db, $$);} -idlist_opt(A) ::= . {A = 0;} +idlist_opt(A) ::= . {A = 0;} idlist_opt(A) ::= LP idlist(X) RP. {A = X;} idlist(A) ::= idlist(A) COMMA nm(Y). - {A = sqlite3IdListAppend(pParse->db,A,&Y);} + {A = sqlite3IdListAppend(pParse,A,sqlite3NameFromToken(pParse->db,&Y));} idlist(A) ::= nm(Y). - {A = sqlite3IdListAppend(pParse->db,0,&Y); /*A-overwrites-Y*/} + {A = sqlite3IdListAppend(pParse,0,sqlite3NameFromToken(pParse->db,&Y)); + /*A-overwrites-Y*/} /////////////////////////// Expression Processing ///////////////////////////// // %type expr {ExprSpan} @@ -1323,28 +1328,36 @@ %endif SQLITE_OMIT_VACUUM ///////////////////////////// The PRAGMA command ///////////////////////////// // %ifndef SQLITE_OMIT_PRAGMA -cmd ::= PRAGMA nm(X) dbnm(Z). {sqlite3Pragma(pParse,&X,&Z,0,0);} -cmd ::= PRAGMA nm(X) dbnm(Z) EQ nmnum(Y). {sqlite3Pragma(pParse,&X,&Z,&Y,0);} -cmd ::= PRAGMA nm(X) dbnm(Z) LP nmnum(Y) RP. {sqlite3Pragma(pParse,&X,&Z,&Y,0);} -cmd ::= PRAGMA nm(X) dbnm(Z) EQ minus_num(Y). - {sqlite3Pragma(pParse,&X,&Z,&Y,1);} -cmd ::= PRAGMA nm(X) dbnm(Z) LP minus_num(Y) RP. - {sqlite3Pragma(pParse,&X,&Z,&Y,1);} - -nmnum(A) ::= plus_num(A). -nmnum(A) ::= nm(A). -nmnum(A) ::= ON(A). -nmnum(A) ::= DELETE(A). -nmnum(A) ::= DEFAULT(A). -%endif SQLITE_OMIT_PRAGMA -%token_class number INTEGER|FLOAT. -plus_num(A) ::= PLUS number(X). {A = X;} -plus_num(A) ::= number(A). -minus_num(A) ::= MINUS number(X). {A = X;} +cmd ::= PRAGMA nm(X) dbnm(Z). {sqlite3Pragma(pParse,&X,&Z,0);} +cmd ::= PRAGMA nm(X) dbnm(Z) EQ pragma_arglist(Y). + {sqlite3Pragma(pParse,&X,&Z,Y);} +cmd ::= PRAGMA nm(X) dbnm(Z) LP pragma_arglist(Y) RP. + {sqlite3Pragma(pParse,&X,&Z,Y);} + +%type pragma_arglist {IdList*} +%destructor pragma_arglist {sqlite3IdListDelete(pParse->db,$$);} + +pragma_arglist(A) ::= pragma_arg(X). + { A = sqlite3IdListAppend(pParse,0,X)/*A-overwrites-X*/; } +pragma_arglist(A) ::= pragma_arglist(A) COMMA pragma_arg(Y). + { A = sqlite3IdListAppend(pParse,A,Y); } + +%type pragma_arg {char*} +%destructor pragma_arg {sqlite3DbFree(pParse->db,$$);} +pragma_arg(A) ::= number(X). + {A = sqlite3NameFromToken(pParse->db,&X);/*A-overwrites-X*/} +pragma_arg(A) ::= nm(X). + {A = sqlite3NameFromToken(pParse->db,&X);/*A-overwrites-X*/} +pragma_arg(A) ::= ON|DELETE|DEFAULT(X). + {A = sqlite3NameFromToken(pParse->db,&X);/*A-overwrites-X*/} +pragma_arg(A) ::= PLUS number(X). {A = sqlite3NameFromToken(pParse->db,&X);} +pragma_arg(A) ::= MINUS number(X). {A = sqlite3MPrintf(pParse->db,"-%T",&X);} + +%endif SQLITE_OMIT_PRAGMA //////////////////////////// The CREATE TRIGGER command ///////////////////// %ifndef SQLITE_OMIT_TRIGGER cmd ::= createkw trigger_decl(A) BEGIN trigger_cmd_list(S) END(Z). { Index: src/pragma.c ================================================================== --- src/pragma.c +++ src/pragma.c @@ -298,26 +298,21 @@ /* ** Process a pragma statement. ** ** Pragmas are of this form: ** -** PRAGMA [schema.]id [= value] -** -** The identifier might also be a string. The value is a string, and -** identifier, or a number. If minusFlag is true, then the value is -** a number that was preceded by a minus sign. +** PRAGMA [schema.]id [= value-list] ** ** If the left side is "database.id" then pId1 is the database name ** and pId2 is the id. If the left side is just "id" then pId1 is the ** id and pId2 is any empty string. */ void sqlite3Pragma( Parse *pParse, Token *pId1, /* First part of [schema.]id field */ Token *pId2, /* Second part of [schema.]id field, or NULL */ - Token *pValue, /* Token for , or NULL */ - int minusFlag /* True if a '-' sign preceded */ + IdList *pValues /* The value-list arguments. NULL if omitted */ ){ char *zLeft = 0; /* Nul-terminated UTF-8 string */ char *zRight = 0; /* Nul-terminated UTF-8 string , or NULL */ const char *zDb = 0; /* The database name */ Token *pId; /* Pointer to token */ @@ -327,18 +322,18 @@ sqlite3 *db = pParse->db; /* The database connection */ Db *pDb; /* The specific database being pragmaed */ Vdbe *v = sqlite3GetVdbe(pParse); /* Prepared statement */ const PragmaName *pPragma; /* The pragma */ - if( v==0 ) return; + if( v==0 ) goto pragma_out; sqlite3VdbeRunOnlyOnce(v); pParse->nMem = 2; /* Interpret the [schema.] part of the pragma statement. iDb is the ** index of the database this pragma is being applied to in db.aDb[]. */ iDb = sqlite3TwoPartName(pParse, pId1, pId2, &pId); - if( iDb<0 ) return; + if( iDb<0 ) goto pragma_out; pDb = &db->aDb[iDb]; /* If the temp database has been explicitly named as part of the ** pragma, make sure it is open. */ @@ -345,16 +340,12 @@ if( iDb==1 && sqlite3OpenTempDatabase(pParse) ){ return; } zLeft = sqlite3NameFromToken(db, pId); - if( !zLeft ) return; - if( minusFlag ){ - zRight = sqlite3MPrintf(db, "-%T", pValue); - }else{ - zRight = sqlite3NameFromToken(db, pValue); - } + if( !zLeft ) goto pragma_out; + if( pValues ) zRight = pValues->a[0].zName; assert( pId2 ); zDb = pId2->n>0 ? pDb->zDbSName : 0; if( sqlite3AuthCheck(pParse, SQLITE_PRAGMA, zLeft, zRight, zDb) ){ goto pragma_out; @@ -1122,10 +1113,14 @@ case PragTyp_INDEX_INFO: if( zRight ){ Index *pIdx; Table *pTab; pIdx = sqlite3FindIndex(db, zRight, zDb); + if( pIdx==0 ){ + pTab = sqlite3FindTable(db, zRight, zDb); + if( pTab && !HasRowid(pTab) ) pIdx = sqlite3PrimaryKeyIndex(pTab); + } if( pIdx ){ int i; int mx; if( pPragma->iArg ){ /* PRAGMA index_xinfo (newer version with more rows and columns) */ @@ -1368,10 +1363,137 @@ sqlite3RegisterLikeFunctions(db, sqlite3GetBoolean(zRight, 0)); } } break; + /* + ** PRAGMA est_row_cnt(,); + ** + ** Seek in through the first of rows and + ** estimate the total number of rows based on the path back up to the + ** root. + */ + case PragTyp_EST_COUNT: { + Index *pIdx; + Table *pTab = 0; + Pgno iRoot = 0; + const char *zName = 0; + int regResult; + double r; + if( (pIdx = sqlite3FindIndex(db, zRight, zDb))!=0 ){ + iRoot = pIdx->tnum; + zName = pIdx->zName; + }else if( (pTab = sqlite3FindTable(db, zRight, zDb))!=0 ){ + zName = pTab->zName; + if( HasRowid(pTab) ){ + iRoot = pTab->tnum; + }else{ + pIdx = sqlite3PrimaryKeyIndex(pTab); + iRoot = pIdx->tnum; + } + }else{ + break; + } + sqlite3TableLock(pParse, iDb, iRoot, 0, zName); + regResult = ++pParse->nMem; + if( pValues->nId>=2 ){ + const char *z = pValues->a[1].zName; + sqlite3AtoF(z, &r, sqlite3Strlen30(z), SQLITE_UTF8); + }else{ + r = 0.5; + } + if( r<0.0 ) r = 0.0; + if( r>1.0 ) r = 1.0; + sqlite3CodeVerifySchema(pParse, iDb); + pParse->nTab++; + sqlite3VdbeAddOp4Int(v, OP_OpenRead, 0, iRoot, iDb, 1); + if( pIdx ) sqlite3VdbeSetP4KeyInfo(pParse, pIdx); + sqlite3VdbeAddOp3(v, OP_EstRowCnt, 0, regResult, (int)(r*1000000000)); + sqlite3VdbeAddOp2(v, OP_ResultRow, regResult, 1); + } + break; + + /* + ** PRAGMA btree_sample(,,); + ** + ** Seek in through the first of rows and + ** then begin returning rows, one by one. A max of rows will + ** be returned. + */ + case PragTyp_BTREE_SAMPLE: { + Index *pIdx; + Table *pTab = 0; + Pgno iRoot = 0; + Pgno iLock = 0; + int nCol = 0; + const char *zName = 0; + int iLimit = 10; + int i; + int regResult; + int regLimit; + int addrTop; + int addrJmp; + int addrSkip; + double r; + if( (pIdx = sqlite3FindIndex(db, zRight, zDb))!=0 ){ + iRoot = pIdx->tnum; + iLock = pIdx->pTable->tnum; + zName = pIdx->zName; + nCol = pIdx->nColumn; + }else if( (pTab = sqlite3FindTable(db, zRight, zDb))!=0 ){ + zName = pTab->zName; + if( HasRowid(pTab) ){ + iLock = iRoot = pTab->tnum; + nCol = pTab->nCol; + }else{ + pIdx = sqlite3PrimaryKeyIndex(pTab); + iLock = iRoot = pIdx->tnum; + nCol = pIdx->nColumn; + } + }else{ + break; + } + sqlite3VdbeSetNumCols(v, nCol); + for(i=0; inId>=2 ){ + const char *z = pValues->a[1].zName; + sqlite3AtoF(z, &r, sqlite3Strlen30(z), SQLITE_UTF8); + }else{ + r = 0.5; + } + if( r<0.0 ) r = 0.0; + if( r>1.0 ) r = 1.0; + if( pValues->nId>=3 ){ + iLimit = sqlite3Atoi(pValues->a[2].zName); + } + pParse->nTab++; + sqlite3TableLock(pParse, iDb, iLock, 0, zName); + sqlite3CodeVerifySchema(pParse, iDb); + sqlite3VdbeAddOp4Int(v, OP_OpenRead, 0, iRoot, iDb, nCol); + if( pIdx ) sqlite3VdbeSetP4KeyInfo(pParse, pIdx); + regLimit = ++pParse->nMem; + regResult = pParse->nMem+1; + pParse->nMem += nCol; + sqlite3VdbeAddOp2(v, OP_Integer, iLimit, regLimit); + addrSkip = sqlite3VdbeAddOp1(v, OP_Rewind, 0); VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_EstRowCnt, 0, regResult, (int)(r*1000000000)); + addrTop = sqlite3VdbeCurrentAddr(v); + for(i=0; inSelectRow = 320; /* 4 billion rows */ computeLimitRegisters(pParse, p, addrBreak); pLimit = p->pLimit; pOffset = p->pOffset; regLimit = p->iLimit; regOffset = p->iOffset; @@ -5168,11 +5169,13 @@ } /* Set the limiter. */ iEnd = sqlite3VdbeMakeLabel(v); - p->nSelectRow = 320; /* 4 billion rows */ + if( (p->selFlags & SF_FixedLimit)==0 ){ + p->nSelectRow = 320; /* 4 billion rows */ + } computeLimitRegisters(pParse, p, iEnd); if( p->iLimit==0 && sSort.addrSortIndex>=0 ){ sqlite3VdbeChangeOpcode(v, sSort.addrSortIndex, OP_SorterOpen); sSort.sortFlags |= SORTFLAG_UseSorter; } Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -3560,11 +3560,11 @@ void sqlite3ExprListSetSpan(Parse*,ExprList*,ExprSpan*); void sqlite3ExprListDelete(sqlite3*, ExprList*); u32 sqlite3ExprListFlags(const ExprList*); int sqlite3Init(sqlite3*, char**); int sqlite3InitCallback(void*, int, char**, char**); -void sqlite3Pragma(Parse*,Token*,Token*,Token*,int); +void sqlite3Pragma(Parse*,Token*,Token*,IdList*); #ifndef SQLITE_OMIT_VIRTUALTABLE Module *sqlite3PragmaVtabRegister(sqlite3*,const char *zName); #endif void sqlite3ResetAllSchemasOfConnection(sqlite3*); void sqlite3ResetOneSchema(sqlite3*,int); @@ -3638,11 +3638,11 @@ # define sqlite3AutoincrementBegin(X) # define sqlite3AutoincrementEnd(X) #endif void sqlite3Insert(Parse*, SrcList*, Select*, IdList*, int); void *sqlite3ArrayAllocate(sqlite3*,void*,int,int*,int*); -IdList *sqlite3IdListAppend(sqlite3*, IdList*, Token*); +IdList *sqlite3IdListAppend(Parse*, IdList*, char*); int sqlite3IdListIndex(IdList*,const char*); SrcList *sqlite3SrcListEnlarge(sqlite3*, SrcList*, int, int); SrcList *sqlite3SrcListAppend(sqlite3*, SrcList*, Token*, Token*); SrcList *sqlite3SrcListAppendFromTerm(Parse*, SrcList*, Token*, Token*, Token*, Select*, Expr*, IdList*); Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -4884,10 +4884,38 @@ assert( pOp->p2>0 && pOp->p2nOp ); VdbeBranchTaken(res!=0,2); if( res ) goto jump_to_p2; break; } + +/* +** Opcode: EstRowCnt P1 P2 P3 * * * +** +** Estimate the number of entries in btree for cursor P1 do a proportional +** seek to of P3. Store the result as a floating point value in P2. +*/ +case OP_EstRowCnt: { /* out2 */ + VdbeCursor *pC; + BtCursor *pCrsr; + int rc; + sqlite3_uint64 n = 0; + + assert( pOp->p1>=0 && pOp->p1nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC->eCurType==CURTYPE_BTREE ); + pCrsr = pC->uc.pCursor; + assert( pCrsr ); + rc = sqlite3BtreeMovetoProportional(pCrsr, pOp->p3, &n); + if( rc ) goto abort_due_to_error; + pOut = out2Prerelease(p, pOp); + pOut->flags = MEM_Real; + pOut->u.r = n; + pC->nullRow = 0; + pC->deferredMoveto = 0; + pC->cacheStatus = CACHE_STALE; + break; +} /* Opcode: Next P1 P2 P3 P4 P5 ** ** Advance cursor P1 so that it points to the next key/data pair in its ** table or index. If there are no more key/value pairs then fall through Index: test/with3.test ================================================================== --- test/with3.test +++ test/with3.test @@ -58,7 +58,63 @@ x1(a) AS (values(100)) INSERT INTO t1(x) SELECT * FROM (WITH x2(y) AS (SELECT * FROM x1) SELECT y+a FROM x1, x2); SELECT * FROM t1; } {200} + +#------------------------------------------------------------------------- +# Test that the planner notices LIMIT clauses on recursive WITH queries. +# + +ifcapable analyze { + do_execsql_test 3.1.1 { + CREATE TABLE y1(a, b); + CREATE INDEX y1a ON y1(a); + + WITH cnt(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM cnt LIMIT 1000) + INSERT INTO y1 SELECT i%10, i FROM cnt; + ANALYZE; + + } + + do_eqp_test 3.1.2 { + WITH cnt(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM cnt LIMIT 1) + SELECT * FROM cnt, y1 WHERE i=a + } { + 3 0 0 {SCAN TABLE cnt} + 1 0 0 {COMPOUND SUBQUERIES 0 AND 0 (UNION ALL)} + 0 0 0 {SCAN SUBQUERY 1} + 0 1 1 {SEARCH TABLE y1 USING INDEX y1a (a=?)} + } + + do_eqp_test 3.1.3 { + WITH cnt(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM cnt LIMIT 1000000) + SELECT * FROM cnt, y1 WHERE i=a + } { + 3 0 0 {SCAN TABLE cnt} + 1 0 0 {COMPOUND SUBQUERIES 0 AND 0 (UNION ALL)} + 0 0 1 {SCAN TABLE y1} + 0 1 0 {SEARCH SUBQUERY 1 USING AUTOMATIC COVERING INDEX (i=?)} + } +} + +do_execsql_test 3.2.1 { + CREATE TABLE w1(pk INTEGER PRIMARY KEY, x INTEGER); + CREATE TABLE w2(pk INTEGER PRIMARY KEY); +} + +do_eqp_test 3.2.2 { + WITH RECURSIVE c(w,id) AS (SELECT 0, (SELECT pk FROM w2 LIMIT 1) + UNION ALL SELECT c.w + 1, x FROM w1, c LIMIT 1) + SELECT * FROM c, w2, w1 + WHERE c.id=w2.pk AND c.id=w1.pk; +} { + 2 0 0 {EXECUTE SCALAR SUBQUERY 3} + 3 0 0 {SCAN TABLE w2} + 4 0 0 {SCAN TABLE w1} + 4 1 1 {SCAN TABLE c} + 1 0 0 {COMPOUND SUBQUERIES 0 AND 0 (UNION ALL)} 0 0 0 {SCAN SUBQUERY 1} + 0 1 1 {SEARCH TABLE w2 USING INTEGER PRIMARY KEY (rowid=?)} + 0 2 2 {SEARCH TABLE w1 USING INTEGER PRIMARY KEY (rowid=?)} +} finish_test Index: test/without_rowid1.test ================================================================== --- test/without_rowid1.test +++ test/without_rowid1.test @@ -325,8 +325,27 @@ CREATE TABLE t70b( a INT CHECK( rowid!=33 ), b TEXT PRIMARY KEY ) WITHOUT ROWID; } {1 {no such column: rowid}} + +# The PRAGMA index_info and index_xinfo pragmas work on +# WITHOUT ROWID tables too, but not on rowid tables. +# +do_execsql_test 8.1 { + CREATE TABLE t80a(a TEXT, b INT, c BLOB, PRIMARY KEY(c,b)); + PRAGMA index_info(t80a); +} {} +do_execsql_test 8.2 { + PRAGMA index_xinfo(t80a); +} {} +do_execsql_test 8.3 { + CREATE TABLE t80b(a TEXT, b INT, c BLOB, PRIMARY KEY(c,b)) WITHOUT ROWID; + PRAGMA index_info(t80b); +} {0 2 c 1 1 b} +do_execsql_test 8.4 { + PRAGMA index_xinfo(t80b); +} {0 2 c 0 BINARY 1 1 1 b 0 BINARY 1 2 0 a 0 BINARY 0} + finish_test ADDED tool/faststat1.c Index: tool/faststat1.c ================================================================== --- /dev/null +++ tool/faststat1.c @@ -0,0 +1,354 @@ +/* +** 2016-10-24 +** +** 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 is a utility program that uses the est_count and btree_sample +** pragmas to try to approximate the content of the sqlite_stat1 table +** without doing a full table scan. +** +** To compile, simply link against SQLite. +** +** See the showHelp() routine below for a brief description of how to +** run the utility. +*/ +#include +#include +#include +#include +#include +#include +#include "sqlite3.h" + +/* +** All global variables are gathered into the "g" singleton. +*/ +struct GlobalVars { + const char *zArgv0; /* Name of program */ + unsigned fDebug; /* Debug flags */ + sqlite3 *db; /* The database connection */ +} g; + +/* +** Allowed values for g.fDebug +*/ +#define DEBUG_NONE 0 + + +/* +** Print an error resulting from faulting command-line arguments and +** abort the program. +*/ +static void cmdlineError(const char *zFormat, ...){ + va_list ap; + fprintf(stderr, "%s: ", g.zArgv0); + va_start(ap, zFormat); + vfprintf(stderr, zFormat, ap); + va_end(ap); + fprintf(stderr, "\n\"%s --help\" for more help\n", g.zArgv0); + exit(1); +} + +/* +** Print an error message for an error that occurs at runtime, then +** abort the program. +*/ +static void runtimeError(const char *zFormat, ...){ + va_list ap; + fprintf(stderr, "%s: ", g.zArgv0); + va_start(ap, zFormat); + vfprintf(stderr, zFormat, ap); + va_end(ap); + fprintf(stderr, "\n"); + exit(1); +} + +/* +** Return the current time in milliseconds since the julian epoch. +*/ +static sqlite3_int64 currentTime(void){ + sqlite3_vfs *pVfs = sqlite3_vfs_find(0); + sqlite3_int64 x = 0; + (void)pVfs->xCurrentTimeInt64(pVfs, &x); + return x; +} + +/* +** Prepare a new SQL statement. Print an error and abort if anything +** goes wrong. +*/ +static sqlite3_stmt *db_vprepare(const char *zFormat, va_list ap){ + char *zSql; + int rc; + sqlite3_stmt *pStmt; + + zSql = sqlite3_vmprintf(zFormat, ap); + if( zSql==0 ) runtimeError("out of memory"); + rc = sqlite3_prepare_v2(g.db, zSql, -1, &pStmt, 0); + if( rc ){ + runtimeError("SQL statement error: %s\n\"%s\"", sqlite3_errmsg(g.db), + zSql); + } + sqlite3_free(zSql); + return pStmt; +} +static sqlite3_stmt *db_prepare(const char *zFormat, ...){ + va_list ap; + sqlite3_stmt *pStmt; + va_start(ap, zFormat); + pStmt = db_vprepare(zFormat, ap); + va_end(ap); + return pStmt; +} + +/* +** Estimate the number of rows in the given table or index. +*/ +static sqlite3_int64 estEntryCount(const char *zTabIdx){ + double sum = 0.0; + int i; + int n = 0; + sqlite3_stmt *pStmt; +# define N_CNT_SAMPLE 10 + for(i=0; i<=N_CNT_SAMPLE; i++){ + pStmt = db_prepare("PRAGMA est_count(\"%w\",%g)", + zTabIdx, ((double)i)/(double)(N_CNT_SAMPLE)); + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + sum += sqlite3_column_double(pStmt, 0); + n++; + } + sqlite3_finalize(pStmt); + } + return n==0 ? 0 : (sqlite3_int64)(sum/n); +} + +/* +** Compare the i-th column of pStmt against pValue. Return true if they +** are different. +*/ +static int columnNotEqual(sqlite3_stmt *pStmt, int i, sqlite3_value *pValue){ + int n1, n2, n; + if( sqlite3_column_type(pStmt,i)!=sqlite3_value_type(pValue) ) return 1; + switch( sqlite3_column_type(pStmt,i) ){ + case SQLITE_NULL: + return 0; /* Nulls compare equal to one another in this context */ + + case SQLITE_INTEGER: + return sqlite3_column_int64(pStmt,i)!=sqlite3_value_int64(pValue); + + case SQLITE_FLOAT: + return sqlite3_column_double(pStmt,i)!=sqlite3_value_double(pValue); + + case SQLITE_BLOB: + n1 = sqlite3_column_bytes(pStmt,i); + n2 = sqlite3_value_bytes(pValue); + n = n110000 ? 100 : 20000; + pStmt = db_prepare("PRAGMA btree_sample(\"%w\",0.0,%lld)", + zIdx, n*2); + for(i=0; i<=N_SPAN; i++){ + k = 0; + while( k0" + " AND name NOT LIKE 'sqlite_%%'" + " ORDER BY name"); + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + const char *zName = (const char*)sqlite3_column_text(pStmt, 0); + analyzeTable(zName); + } + sqlite3_finalize(pStmt); + printf("ANALYZE sqlite_master;\n"); + sqlite3_close(g.db); + iTotal = currentTime() - iStart; + printf("-- elapsed time: %lld.%03lld seconds\n", iTotal/1000, iTotal%1000); + return 0; +} Index: tool/mkpragmatab.tcl ================================================================== --- tool/mkpragmatab.tcl +++ tool/mkpragmatab.tcl @@ -275,10 +275,16 @@ NAME: quick_check TYPE: INTEGRITY_CHECK FLAG: NeedSchema IF: !defined(SQLITE_OMIT_INTEGRITY_CHECK) + NAME: est_count + FLAG: NeedSchema + + NAME: btree_sample + FLAG: NeedSchema + NAME: encoding FLAG: Result0 IF: !defined(SQLITE_OMIT_UTF16) NAME: schema_version