diff --git a/manifest b/manifest index ea4e76fd56..d61ee006e2 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sthe\sability\sto\sgenerate\sassembly\slisting\sfiles\susing\sthe\sMSVC\smakefile. -D 2013-09-06T21:41:11.484 +C Merge\supdates\sfrom\strunk. +D 2013-09-06T22:27:55.935 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 5e41da95d92656a5004b03d3576e8b226858a28e F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -157,8 +157,8 @@ F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b F sqlite3.1 6be1ad09113570e1fc8dcaff84c9b0b337db5ffc F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a F src/alter.c 2af0330bb1b601af7a7789bf7229675fd772a083 -F src/analyze.c 128f85d9bd7e79e93b8b27aa2bb54a43ad0c00f3 -F src/attach.c fea00cab11c854646a27641a263f5876569a51f9 +F src/analyze.c 7da9d6a0d04a3662d110a28f4a7e7b73a6171e75 +F src/attach.c eeb8d9d2c791caa0a54a835170ea898e96c2802d F src/auth.c 523da7fb4979469955d822ff9298352d6b31de34 F src/backup.c 2f1987981139bd2f6d8c728d64bf09fb387443c3 F src/bitvec.c 19a4ba637bd85f8f63fc8c9bae5ade9fb05ec1cb @@ -167,25 +167,25 @@ F src/btree.c b9b57df546df2636294bfb21a986f5707b417df2 F src/btree.h bfe0e8c5759b4ec77b0d18390064a6ef3cdffaaf F src/btreeInt.h 51cf220a9b9223354770883e93a859dc377aa27f F src/build.c f63e8929c7f89c0074fbc74929bc946ea117b2f8 -F src/callback.c d7e46f40c3cf53c43550b7da7a1d0479910b62cc +F src/callback.c f99a8957ba2adf369645fac0db09ad8adcf1caa2 F src/complete.c dc1d136c0feee03c2f7550bafc0d29075e36deac F src/ctime.c ea4b7f3623a0fcb1146e7f245d7410033e86859c F src/date.c 067a81c9942c497aafd2c260e13add8a7d0c7dd4 -F src/delete.c 2317c814866d9aa71fea16b3faf4fdd4d6a49b94 -F src/expr.c 4d89bd03a04fcdb5ff71d86b4e0cc7d3230797b8 +F src/delete.c 2dc64ca360b7d7da481183ea920a813a0c203c97 +F src/expr.c 33ae44812d25799000dbcfd54bb2142b30cdd049 F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb -F src/fkey.c 914a6bbd987d857c41ac9d244efa6641f36faadb -F src/func.c 1f24db7b0f19e28170ffa5d5887b842e4d9a48d9 +F src/fkey.c be866cd8c4fa6cae98ba33109578fd1a3311ee5b +F src/func.c 8eed739f11acd20fb89a82bf0ad60f75b0a8f907 F src/global.c 5caf4deab621abb45b4c607aad1bd21c20aac759 F src/hash.c ac3470bbf1ca4ae4e306a8ecb0fdf1731810ffe4 F src/hash.h 8890a25af81fb85a9ad7790d32eedab4b994da22 F src/hwtime.h d32741c8f4df852c7d959236615444e2b1063b08 -F src/insert.c a66bcdc956145369c1a876709f47f69476973e15 +F src/insert.c a271771db927873c850da8928d6ee9fc058fb9fe F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d F src/legacy.c 0df0b1550b9cc1f58229644735e317ac89131f12 F src/lempar.c cdf0a000315332fc9b50b62f3b5e22e080a0952b F src/loadext.c 867c7b330b740c6c917af9956b13b81d0a048303 -F src/main.c c4c781b06faa2ce4040c32a75fd931a07ba7054e +F src/main.c 35931467ec026b05babb279cb8a573e62f6fe1a3 F src/malloc.c fe085aa851b666b7c375c1ff957643dc20a04bf6 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c 437c7c4af964895d4650f29881df63535caaa1fa @@ -216,12 +216,12 @@ F src/printf.c da9119eb31a187a4b99f60aa4a225141c0ebb74b F src/random.c 0b2dbc37fdfbfa6bd455b091dfcef5bdb32dba68 F src/resolve.c 9d53899cc6e1f4ec0b4632d07e97d57827bf63b9 F src/rowset.c 64655f1a627c9c212d9ab497899e7424a34222e0 -F src/select.c 8b148eb851f384412aea57091659d14b369918ca -F src/shell.c dbe064d404bb497acd8a44c066cd6b8460a71236 +F src/select.c 9239586282bd146ec5843a2cde7d54cd7816cf78 +F src/shell.c d920a891ca09b8bd262cced7fb0ab9d723f7a747 F src/sqlite.h.in ec40aa958a270416fb04b4f72210357bf163d2c5 F src/sqlite3.rc fea433eb0a59f4c9393c8e6d76a6e2596b1fe0c0 F src/sqlite3ext.h 886f5a34de171002ad46fae8c36a7d8051c190fc -F src/sqliteInt.h 600086a5082e2291b0aeeefcfbb546f2bbda67b2 +F src/sqliteInt.h 32e959dba9899360ce4ae3cd6cc77b4a8df277e8 F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d F src/status.c 7ac05a5c7017d0b9f0b4bcd701228b784f987158 F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e @@ -273,24 +273,24 @@ F src/test_vfstrace.c 34b544e80ba7fb77be15395a609c669df2e660a2 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/tokenize.c 70061085a51f2f4fc15ece94f32c03bcb78e63b2 F src/trigger.c 5c0ea9b8755e7c5e1a700f3e27ac4f8d92dd221e -F src/update.c 7d9d38e4f341ada7d79035ea969cdefb8b9014d1 +F src/update.c f5182157f5d0d0a97bc5f5e3c9bdba0dfbe08f08 F src/utf.c 6fc6c88d50448c469c5c196acf21617a24f90269 F src/util.c f566b5138099a2df8533b190d0dcc74b7dfbe0c9 F src/vacuum.c d9c5759f4c5a438bb43c2086f72c5d2edabc36c8 -F src/vdbe.c 2b1cd2c7e3d74e6aa99ce2f538bfdd07a00dc2f1 +F src/vdbe.c a393a94c0d77b86c7c4ad2cfb43ec4ba278d9596 F src/vdbe.h 4f554b5627f26710c4c36d919110a3fc611ca5c4 F src/vdbeInt.h cbe71b8b36d8b3bba5709cc3f436c7e3b47b7b08 F src/vdbeapi.c 96b24b946cf21894f63d9393e821baa2f0a80979 -F src/vdbeaux.c c7fe2695e256dbf254113c4fe90d3ec9aabe3bbe +F src/vdbeaux.c 88beca92f2ed0bbe2c6f87946d0e999a6807ea1b F src/vdbeblob.c 5dc79627775bd9a9b494dd956e26297946417d69 -F src/vdbemem.c 75ad7fd8f068a8baaba1cf3a84714ef7d3b7a2b8 +F src/vdbemem.c 817ce21ab4ca57f902619bb8fef3f8a51bbd0ed8 F src/vdbesort.c 3937e06b2a0e354500e17dc206ef4c35770a5017 F src/vdbetrace.c e7ec40e1999ff3c6414424365d5941178966dcbc -F src/vtab.c 165ce0e797c2cd23badb104c9f2ae9042d6d942c +F src/vtab.c 5a423b042eb1402ef77697d03d6a67378d97bc8d F src/wal.c 7dc3966ef98b74422267e7e6e46e07ff6c6eb1b4 F src/wal.h df01efe09c5cb8c8e391ff1715cca294f89668a4 F src/walker.c 4fa43583d0a84b48f93b1e88f11adf2065be4e73 -F src/where.c 83f6106e0edfa23d1258c617cd58e3125277a363 +F src/where.c 06c249a5137575ecf4d527e5a1dadb1087e4375c F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2 F test/aggnested.test 45c0201e28045ad38a530b5a144b73cd4aa2cfd6 @@ -308,8 +308,9 @@ F test/analyze5.test 765c4e284aa69ca172772aa940946f55629bc8c4 F test/analyze6.test 19151da2c4e918905d2081b74ac5c4d47fc850ab F test/analyze7.test bb1409afc9e8629e414387ef048b8e0e3e0bdc4f F test/analyze8.test 093d15c1c888eed5034304a98c992f7360130b88 -F test/analyze9.test 2bc3fb7b0ba7954e2513e69af8608ce5da201418 +F test/analyze9.test 4a48461d71a7b4a958570c580495a97e842e2700 F test/analyzeA.test 1a5c40079894847976d983ca39c707aaa44b6944 +F test/analyzeB.test 8bf35ee0a548aea831bf56762cb8e7fdb1db083d F test/async.test 1d0e056ba1bb9729283a0f22718d3a25e82c277b F test/async2.test c0a9bd20816d7d6a2ceca7b8c03d3d69c28ffb8b F test/async3.test d73a062002376d7edc1fe3edff493edbec1fc2f7 @@ -461,6 +462,7 @@ F test/fkey3.test 5ec899d12b13bcf1e9ef40eff7fb692fdb91392e F test/fkey4.test 86446017011273aad8f9a99c1a65019e7bd9ca9d F test/fkey5.test 0bf64f2d19ad80433ca0b24edbf604a18b353d5f F test/fkey6.test c555f7fc45d842cc84b0d3ff93951ce2b8c25fc8 +F test/fkey7.test e31d0e71a41c1d29349a16448d6c420e2c53a8fc F test/fkey_malloc.test bb74c9cb8f8fceed03b58f8a7ef2df98520bbd51 F test/format4.test 1f0cac8ff3895e9359ed87e41aaabee982a812eb F test/fts-9fd058691.test 78b887e30ae6816df0e1fed6259de4b5a64ad33c @@ -652,7 +654,7 @@ F test/malloc6.test 2f039d9821927eacae43e1831f815e157659a151 F test/malloc7.test 7c68a32942858bc715284856c5507446bba88c3a F test/malloc8.test 9b7a3f8cb9cf0b12fff566e80a980b1767bd961d F test/malloc9.test 2307c6ee3703b0a21391f3ea92388b4b73f9105e -F test/mallocA.test 71e4b57e640c017cf2833e51fe6e8e43e8575b73 +F test/mallocA.test 1ba0367fb5434e7bc2fa4afcb30b14174d91b160 F test/mallocAll.test 98f1be74bc9f49a858bc4f361fc58e26486798be F test/mallocB.test bc475ab850cda896142ab935bbfbc74c24e51ed6 F test/mallocC.test 3dffe16532f109293ce1ccecd0c31dca55ef08c4 @@ -717,7 +719,7 @@ F test/pagesize.test 1dd51367e752e742f58e861e65ed7390603827a0 F test/pcache.test b09104b03160aca0d968d99e8cd2c5b1921a993d F test/pcache2.test a83efe2dec0d392f814bfc998def1d1833942025 F test/percentile.test b98fc868d71eb5619d42a1702e9ab91718cbed54 -F test/permutations.test c5e7ae8a18cb8a0ced38dbbc9e2463536c1de45b +F test/permutations.test 72f4f8881d388163ddbbeec0a6ed812e863ea2e6 F test/pragma.test 5e7de6c32a5d764f09437d2025f07e4917b9e178 F test/pragma2.test 224f0381f9411a78ae685cac24c13656a62021b7 F test/printf.test ec9870c4dce8686a37818e0bf1aba6e6a1863552 @@ -781,7 +783,7 @@ F test/shared9.test 5f2a8f79b4d6c7d107a01ffa1ed05ae7e6333e21 F test/sharedA.test 0cdf1a76dfa00e6beee66af5b534b1e8df2720f5 F test/shared_err.test 0079c05c97d88cfa03989b7c20a8b266983087aa F test/sharedlock.test 927a4b6da11978c82b857dbdb20a932aad732123 -F test/shell1.test 928547277d385038c696428e9d791cbbad098974 +F test/shell1.test 474ed53bb461c4ba9b6468d3a74e86eb8ee0d9d0 F test/shell2.test 037d6ad16e873354195d30bb2dc4b5321788154a F test/shell3.test 9196c42772d575685e722c92b4b39053c6ebba59 F test/shell4.test aa4eef8118b412d1a01477a53426ece169ea86a9 @@ -1042,7 +1044,7 @@ F test/walro.test 6cc247a0cc9b36aeea2057dd28a922a1cdfbd630 F test/walshared.test 0befc811dcf0b287efae21612304d15576e35417 F test/walslow.test e7be6d9888f83aa5d3d3c7c08aa9b5c28b93609a F test/walthread.test de8dbaf6d9e41481c460ba31ca61e163d7348f8e -F test/where.test da54153a4c1571ea1b95659e5bec8119edf786aa +F test/where.test 8d66dfbfd4d12816f74f854dbf67dee492c06267 F test/where2.test 76d5346f7edb2a6a0442dcf3e9d088ac6903e0b5 F test/where3.test d28c51f257e60be30f74308fa385ceeddfb54a6e F test/where4.test e9b9e2f2f98f00379e6031db6a6fca29bae782a2 @@ -1110,7 +1112,7 @@ F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381 F tool/wherecosttest.c f407dc4c79786982a475261866a161cd007947ae F tool/win/sqlite.vsix 97894c2790eda7b5bce3cc79cb2a8ec2fde9b3ac -P 94c4cdc50d2753c859e494d53cebd09edd2e5663 -R 6b551c6dae3914b271288c70374acb57 +P 6caa2cd10421a85db6740fb2ae8a42b573959562 97b10e66e98e84755aa577f8da017bf1aea2056c +R 10ba881e2c7b774138e3a52c0d93d6d1 U mistachkin -Z c5c612cbd00cecdaa9ef5d871289b004 +Z d4c0acc5b28a494c6ee6d9515debd6eb diff --git a/manifest.uuid b/manifest.uuid index aec120a35d..3928ea3cb2 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -6caa2cd10421a85db6740fb2ae8a42b573959562 \ No newline at end of file +8922be1a3e0269552e12b87fe1e5141c38a8d3f8 \ No newline at end of file diff --git a/src/analyze.c b/src/analyze.c index d70f77e779..42c81fd659 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -371,8 +371,7 @@ static void statInit( } static const FuncDef statInitFuncdef = { 1+IsStat34, /* nArg */ - SQLITE_UTF8, /* iPrefEnc */ - 0, /* flags */ + SQLITE_UTF8, /* funcFlags */ 0, /* pUserData */ 0, /* pNext */ statInit, /* xFunc */ @@ -383,24 +382,63 @@ static const FuncDef statInitFuncdef = { 0 /* pDestructor */ }; +#ifdef SQLITE_ENABLE_STAT4 +/* +** pNew and pOld are both candidate non-periodic samples selected for +** the same column (pNew->iCol==pOld->iCol). Ignoring this column and +** considering only any trailing columns and the sample hash value, this +** function returns true if sample pNew is to be preferred over pOld. +** In other words, if we assume that the cardinalities of the selected +** column for pNew and pOld are equal, is pNew to be preferred over pOld. +** +** This function assumes that for each argument sample, the contents of +** the anEq[] array from pSample->anEq[pSample->iCol+1] onwards are valid. +*/ +static int sampleIsBetterPost( + Stat4Accum *pAccum, + Stat4Sample *pNew, + Stat4Sample *pOld +){ + int nCol = pAccum->nCol; + int i; + assert( pNew->iCol==pOld->iCol ); + for(i=pNew->iCol+1; ianEq[i]>pOld->anEq[i] ) return 1; + if( pNew->anEq[i]anEq[i] ) return 0; + } + if( pNew->iHash>pOld->iHash ) return 1; + return 0; +} +#endif + #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 /* ** Return true if pNew is to be preferred over pOld. +** +** This function assumes that for each argument sample, the contents of +** the anEq[] array from pSample->anEq[pSample->iCol] onwards are valid. */ -static int sampleIsBetter(Stat4Sample *pNew, Stat4Sample *pOld){ +static int sampleIsBetter( + Stat4Accum *pAccum, + Stat4Sample *pNew, + Stat4Sample *pOld +){ tRowcnt nEqNew = pNew->anEq[pNew->iCol]; tRowcnt nEqOld = pOld->anEq[pOld->iCol]; assert( pOld->isPSample==0 && pNew->isPSample==0 ); assert( IsStat4 || (pNew->iCol==0 && pOld->iCol==0) ); - if( (nEqNew>nEqOld) - || (nEqNew==nEqOld && pNew->iColiCol) - || (nEqNew==nEqOld && pNew->iCol==pOld->iCol && pNew->iHash>pOld->iHash) - ){ - return 1; + if( (nEqNew>nEqOld) ) return 1; +#ifdef SQLITE_ENABLE_STAT4 + if( nEqNew==nEqOld ){ + if( pNew->iColiCol ) return 1; + return (pNew->iCol==pOld->iCol && sampleIsBetterPost(pAccum, pNew, pOld)); } return 0; +#else + return (nEqNew==nEqOld && pNew->iHash>pOld->iHash); +#endif } /* @@ -423,11 +461,10 @@ void sampleCopy(Stat4Accum *p, Stat4Sample *pTo, Stat4Sample *pFrom){ static void sampleInsert(Stat4Accum *p, Stat4Sample *pNew, int nEqZero){ Stat4Sample *pSample; int i; - i64 iSeq; - int iPos; assert( IsStat4 || nEqZero==0 ); +#ifdef SQLITE_ENABLE_STAT4 if( pNew->isPSample==0 ){ Stat4Sample *pUpgrade = 0; assert( pNew->anEq[pNew->iCol]>0 ); @@ -441,8 +478,9 @@ static void sampleInsert(Stat4Accum *p, Stat4Sample *pNew, int nEqZero){ Stat4Sample *pOld = &p->a[i]; if( pOld->anEq[pNew->iCol]==0 ){ if( pOld->isPSample ) return; - assert( sampleIsBetter(pNew, pOld) ); - if( pUpgrade==0 || sampleIsBetter(pOld, pUpgrade) ){ + assert( pOld->iCol>pNew->iCol ); + assert( sampleIsBetter(p, pNew, pOld) ); + if( pUpgrade==0 || sampleIsBetter(p, pOld, pUpgrade) ){ pUpgrade = pOld; } } @@ -453,6 +491,7 @@ static void sampleInsert(Stat4Accum *p, Stat4Sample *pNew, int nEqZero){ goto find_new_min; } } +#endif /* If necessary, remove sample iMin to make room for the new sample. */ if( p->nSample>=p->mxSample ){ @@ -468,36 +507,30 @@ static void sampleInsert(Stat4Accum *p, Stat4Sample *pNew, int nEqZero){ p->nSample = p->mxSample-1; } - /* Figure out where in the a[] array the new sample should be inserted. */ - iSeq = pNew->anLt[p->nCol-1]; - for(iPos=p->nSample; iPos>0; iPos--){ - if( iSeq>p->a[iPos-1].anLt[p->nCol-1] ) break; - } + /* The "rows less-than" for the rowid column must be greater than that + ** for the last sample in the p->a[] array. Otherwise, the samples would + ** be out of order. */ +#ifdef SQLITE_ENABLE_STAT4 + assert( p->nSample==0 + || pNew->anLt[p->nCol-1] > p->a[p->nSample-1].anLt[p->nCol-1] ); +#endif /* Insert the new sample */ - pSample = &p->a[iPos]; - if( iPos!=p->nSample ){ - Stat4Sample *pEnd = &p->a[p->nSample]; - tRowcnt *anEq = pEnd->anEq; - tRowcnt *anLt = pEnd->anLt; - tRowcnt *anDLt = pEnd->anDLt; - memmove(&p->a[iPos], &p->a[iPos+1], (p->nSample-iPos)*sizeof(p->a[0])); - pSample->anEq = anEq; - pSample->anDLt = anDLt; - pSample->anLt = anLt; - } - p->nSample++; + pSample = &p->a[p->nSample]; sampleCopy(p, pSample, pNew); + p->nSample++; /* Zero the first nEqZero entries in the anEq[] array. */ memset(pSample->anEq, 0, sizeof(tRowcnt)*nEqZero); +#ifdef SQLITE_ENABLE_STAT4 find_new_min: +#endif if( p->nSample>=p->mxSample ){ int iMin = -1; for(i=0; imxSample; i++){ if( p->a[i].isPSample ) continue; - if( iMin<0 || sampleIsBetter(&p->a[iMin], &p->a[i]) ){ + if( iMin<0 || sampleIsBetter(p, &p->a[iMin], &p->a[i]) ){ iMin = i; } } @@ -521,9 +554,8 @@ static void samplePushPrevious(Stat4Accum *p, int iChng){ ** into IndexSample.a[] at this point. */ for(i=(p->nCol-2); i>=iChng; i--){ Stat4Sample *pBest = &p->aBest[i]; - if( p->nSamplemxSample - || sampleIsBetter(pBest, &p->a[p->iMin]) - ){ + pBest->anEq[i] = p->current.anEq[i]; + if( p->nSamplemxSample || sampleIsBetter(p, pBest, &p->a[p->iMin]) ){ sampleInsert(p, pBest, i); } } @@ -550,7 +582,9 @@ static void samplePushPrevious(Stat4Accum *p, int iChng){ }else /* Or if it is a non-periodic sample. Add it in this case too. */ - if( p->nSamplemxSample || sampleIsBetter(&p->current, &p->a[p->iMin]) ){ + if( p->nSamplemxSample + || sampleIsBetter(p, &p->current, &p->a[p->iMin]) + ){ sampleInsert(p, &p->current, 0); } } @@ -584,8 +618,7 @@ static void statPush( assert( iChngnCol ); if( p->nRow==0 ){ - /* anEq[0] is only zero for the very first call to this function. Do - ** appropriate initialization */ + /* This is the first call to this function. Do initialization. */ for(i=0; inCol; i++) p->current.anEq[i] = 1; }else{ /* Second and subsequent calls get processed here */ @@ -625,7 +658,7 @@ static void statPush( /* Update the aBest[] array. */ for(i=0; i<(p->nCol-1); i++){ p->current.iCol = i; - if( i>=iChng || sampleIsBetter(&p->current, &p->aBest[i]) ){ + if( i>=iChng || sampleIsBetterPost(p, &p->current, &p->aBest[i]) ){ sampleCopy(p, &p->aBest[i], &p->current); } } @@ -634,8 +667,7 @@ static void statPush( } static const FuncDef statPushFuncdef = { 2+IsStat34, /* nArg */ - SQLITE_UTF8, /* iPrefEnc */ - 0, /* flags */ + SQLITE_UTF8, /* funcFlags */ 0, /* pUserData */ 0, /* pNext */ statPush, /* xFunc */ @@ -770,8 +802,7 @@ static void statGet( } static const FuncDef statGetFuncdef = { 1+IsStat34, /* nArg */ - SQLITE_UTF8, /* iPrefEnc */ - 0, /* flags */ + SQLITE_UTF8, /* funcFlags */ 0, /* pUserData */ 0, /* pNext */ statGet, /* xFunc */ diff --git a/src/attach.c b/src/attach.c index ce95ea702e..6d965ace65 100644 --- a/src/attach.c +++ b/src/attach.c @@ -379,8 +379,7 @@ attach_end: void sqlite3Detach(Parse *pParse, Expr *pDbname){ static const FuncDef detach_func = { 1, /* nArg */ - SQLITE_UTF8, /* iPrefEnc */ - 0, /* flags */ + SQLITE_UTF8, /* funcFlags */ 0, /* pUserData */ 0, /* pNext */ detachFunc, /* xFunc */ @@ -401,8 +400,7 @@ void sqlite3Detach(Parse *pParse, Expr *pDbname){ void sqlite3Attach(Parse *pParse, Expr *p, Expr *pDbname, Expr *pKey){ static const FuncDef attach_func = { 3, /* nArg */ - SQLITE_UTF8, /* iPrefEnc */ - 0, /* flags */ + SQLITE_UTF8, /* funcFlags */ 0, /* pUserData */ 0, /* pNext */ attachFunc, /* xFunc */ diff --git a/src/callback.c b/src/callback.c index d40c65cb92..66fa490894 100644 --- a/src/callback.c +++ b/src/callback.c @@ -270,9 +270,9 @@ static int matchQuality( } /* Bonus points if the text encoding matches */ - if( enc==p->iPrefEnc ){ + if( enc==(p->funcFlags & SQLITE_FUNC_ENCMASK) ){ match += 2; /* Exact encoding match */ - }else if( (enc & p->iPrefEnc & 2)!=0 ){ + }else if( (enc & p->funcFlags & 2)!=0 ){ match += 1; /* Both are UTF16, but with different byte orders */ } @@ -406,7 +406,7 @@ FuncDef *sqlite3FindFunction( (pBest = sqlite3DbMallocZero(db, sizeof(*pBest)+nName+1))!=0 ){ pBest->zName = (char *)&pBest[1]; pBest->nArg = (u16)nArg; - pBest->iPrefEnc = enc; + pBest->funcFlags = enc; memcpy(pBest->zName, zName, nName); pBest->zName[nName] = 0; sqlite3FuncDefInsert(&db->aFunc, pBest); diff --git a/src/delete.c b/src/delete.c index af64afc65c..50a35cd165 100644 --- a/src/delete.c +++ b/src/delete.c @@ -536,7 +536,7 @@ void sqlite3GenerateRowDelete( /* Do FK processing. This call checks that any FK constraints that ** refer to this table (i.e. constraints attached to other tables) ** are not violated by deleting this row. */ - sqlite3FkCheck(pParse, pTab, iOld, 0); + sqlite3FkCheck(pParse, pTab, iOld, 0, 0, 0); } /* Delete the index and table entries. Skip this step if pTab is really @@ -553,7 +553,7 @@ void sqlite3GenerateRowDelete( /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to ** handle rows (possibly in other tables) that refer via a foreign key ** to the row just deleted. */ - sqlite3FkActions(pParse, pTab, 0, iOld); + sqlite3FkActions(pParse, pTab, 0, iOld, 0, 0); /* Invoke AFTER DELETE trigger programs. */ sqlite3CodeRowTrigger(pParse, pTrigger, diff --git a/src/expr.c b/src/expr.c index aa6f275438..6587ee1f78 100644 --- a/src/expr.c +++ b/src/expr.c @@ -2634,7 +2634,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ ** IFNULL() functions. This avoids unnecessary evalation of ** arguments past the first non-NULL argument. */ - if( pDef->flags & SQLITE_FUNC_COALESCE ){ + if( pDef->funcFlags & SQLITE_FUNC_COALESCE ){ int endCoalesce = sqlite3VdbeMakeLabel(v); assert( nFarg>=2 ); sqlite3ExprCode(pParse, pFarg->a[0].pExpr, target); @@ -2658,7 +2658,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ ** or OPFLAG_TYPEOFARG respectively, to avoid unnecessary data ** loading. */ - if( (pDef->flags & (SQLITE_FUNC_LENGTH|SQLITE_FUNC_TYPEOF))!=0 ){ + if( (pDef->funcFlags & (SQLITE_FUNC_LENGTH|SQLITE_FUNC_TYPEOF))!=0 ){ u8 exprOp; assert( nFarg==1 ); assert( pFarg->a[0].pExpr!=0 ); @@ -2666,8 +2666,9 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ if( exprOp==TK_COLUMN || exprOp==TK_AGG_COLUMN ){ assert( SQLITE_FUNC_LENGTH==OPFLAG_LENGTHARG ); assert( SQLITE_FUNC_TYPEOF==OPFLAG_TYPEOFARG ); - testcase( pDef->flags==SQLITE_FUNC_LENGTH ); - pFarg->a[0].pExpr->op2 = pDef->flags; + testcase( (pDef->funcFlags&~SQLITE_FUNC_ENCMASK) + ==SQLITE_FUNC_LENGTH ); + pFarg->a[0].pExpr->op2 = pDef->funcFlags&~SQLITE_FUNC_ENCMASK; } } @@ -2700,11 +2701,11 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ if( i<32 && sqlite3ExprIsConstant(pFarg->a[i].pExpr) ){ constMask |= (1<flags & SQLITE_FUNC_NEEDCOLL)!=0 && !pColl ){ + if( (pDef->funcFlags & SQLITE_FUNC_NEEDCOLL)!=0 && !pColl ){ pColl = sqlite3ExprCollSeq(pParse, pFarg->a[i].pExpr); } } - if( pDef->flags & SQLITE_FUNC_NEEDCOLL ){ + if( pDef->funcFlags & SQLITE_FUNC_NEEDCOLL ){ if( !pColl ) pColl = db->pDfltColl; sqlite3VdbeAddOp4(v, OP_CollSeq, 0, 0, 0, (char *)pColl, P4_COLLSEQ); } diff --git a/src/fkey.c b/src/fkey.c index bb59c656f6..1947c2ee20 100644 --- a/src/fkey.c +++ b/src/fkey.c @@ -682,6 +682,70 @@ void sqlite3FkDropTable(Parse *pParse, SrcList *pName, Table *pTab){ } } + +/* +** The second argument points to an FKey object representing a foreign key +** for which pTab is the child table. An UPDATE statement against pTab +** is currently being processed. For each column of the table that is +** actually updated, the corresponding element in the aChange[] array +** is zero or greater (if a column is unmodified the corresponding element +** is set to -1). If the rowid column is modified by the UPDATE statement +** the bChngRowid argument is non-zero. +** +** This function returns true if any of the columns that are part of the +** child key for FK constraint *p are modified. +*/ +static int fkChildIsModified( + Table *pTab, /* Table being updated */ + FKey *p, /* Foreign key for which pTab is the child */ + int *aChange, /* Array indicating modified columns */ + int bChngRowid /* True if rowid is modified by this update */ +){ + int i; + for(i=0; inCol; i++){ + int iChildKey = p->aCol[i].iFrom; + if( aChange[iChildKey]>=0 ) return 1; + if( iChildKey==pTab->iPKey && bChngRowid ) return 1; + } + return 0; +} + +/* +** The second argument points to an FKey object representing a foreign key +** for which pTab is the parent table. An UPDATE statement against pTab +** is currently being processed. For each column of the table that is +** actually updated, the corresponding element in the aChange[] array +** is zero or greater (if a column is unmodified the corresponding element +** is set to -1). If the rowid column is modified by the UPDATE statement +** the bChngRowid argument is non-zero. +** +** This function returns true if any of the columns that are part of the +** parent key for FK constraint *p are modified. +*/ +static int fkParentIsModified( + Table *pTab, + FKey *p, + int *aChange, + int bChngRowid +){ + int i; + for(i=0; inCol; i++){ + char *zKey = p->aCol[i].zCol; + int iKey; + for(iKey=0; iKeynCol; iKey++){ + if( aChange[iKey]>=0 || (iKey==pTab->iPKey && bChngRowid) ){ + Column *pCol = &pTab->aCol[iKey]; + if( zKey ){ + if( 0==sqlite3StrICmp(pCol->zName, zKey) ) return 1; + }else if( pCol->colFlags & COLFLAG_PRIMKEY ){ + return 1; + } + } + } + } + return 0; +} + /* ** This function is called when inserting, deleting or updating a row of ** table pTab to generate VDBE code to perform foreign key constraint @@ -706,7 +770,9 @@ void sqlite3FkCheck( Parse *pParse, /* Parse context */ Table *pTab, /* Row is being deleted from this table */ int regOld, /* Previous row data is stored here */ - int regNew /* New row data is stored here */ + int regNew, /* New row data is stored here */ + int *aChange, /* Array indicating UPDATEd columns (or 0) */ + int bChngRowid /* True if rowid is UPDATEd */ ){ sqlite3 *db = pParse->db; /* Database handle */ FKey *pFKey; /* Used to iterate through FKs */ @@ -734,6 +800,13 @@ void sqlite3FkCheck( int i; int isIgnore = 0; + if( aChange + && sqlite3_stricmp(pTab->zName, pFKey->zTo)!=0 + && fkChildIsModified(pTab, pFKey, aChange, bChngRowid)==0 + ){ + continue; + } + /* Find the parent table of this foreign key. Also find a unique index ** on the parent key columns in the parent table. If either of these ** schema items cannot be located, set an error in pParse and return @@ -816,6 +889,10 @@ void sqlite3FkCheck( SrcList *pSrc; int *aiCol = 0; + if( aChange && fkParentIsModified(pTab, pFKey, aChange, bChngRowid)==0 ){ + continue; + } + if( !pFKey->isDeferred && !(db->flags & SQLITE_DeferFKs) && !pParse->pToplevel && !pParse->isMultiWrite ){ @@ -889,6 +966,7 @@ u32 sqlite3FkOldmask( return mask; } + /* ** This function is called before generating code to update or delete a ** row contained in table pTab. If the operation is a DELETE, then @@ -918,32 +996,16 @@ int sqlite3FkRequired( }else{ /* This is an UPDATE. Foreign key processing is only required if the ** operation modifies one or more child or parent key columns. */ - int i; FKey *p; /* Check if any child key columns are being modified. */ for(p=pTab->pFKey; p; p=p->pNextFrom){ - for(i=0; inCol; i++){ - int iChildKey = p->aCol[i].iFrom; - if( aChange[iChildKey]>=0 ) return 1; - if( iChildKey==pTab->iPKey && chngRowid ) return 1; - } + if( fkChildIsModified(pTab, p, aChange, chngRowid) ) return 1; } /* Check if any parent key columns are being modified. */ for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){ - for(i=0; inCol; i++){ - char *zKey = p->aCol[i].zCol; - int iKey; - for(iKey=0; iKeynCol; iKey++){ - Column *pCol = &pTab->aCol[iKey]; - if( (zKey ? !sqlite3StrICmp(pCol->zName, zKey) - : (pCol->colFlags & COLFLAG_PRIMKEY)!=0) ){ - if( aChange[iKey]>=0 ) return 1; - if( iKey==pTab->iPKey && chngRowid ) return 1; - } - } - } + if( fkParentIsModified(pTab, p, aChange, chngRowid) ) return 1; } } } @@ -1169,7 +1231,9 @@ void sqlite3FkActions( Parse *pParse, /* Parse context */ Table *pTab, /* Table being updated or deleted from */ ExprList *pChanges, /* Change-list for UPDATE, NULL for DELETE */ - int regOld /* Address of array containing old row */ + int regOld, /* Address of array containing old row */ + int *aChange, /* Array indicating UPDATEd columns (or 0) */ + int bChngRowid /* True if rowid is UPDATEd */ ){ /* If foreign-key support is enabled, iterate through all FKs that ** refer to table pTab. If there is an action associated with the FK @@ -1178,9 +1242,11 @@ void sqlite3FkActions( if( pParse->db->flags&SQLITE_ForeignKeys ){ FKey *pFKey; /* Iterator variable */ for(pFKey = sqlite3FkReferences(pTab); pFKey; pFKey=pFKey->pNextTo){ - Trigger *pAction = fkActionTrigger(pParse, pTab, pFKey, pChanges); - if( pAction ){ - sqlite3CodeRowTriggerDirect(pParse, pAction, pTab, regOld, OE_Abort, 0); + if( aChange==0 || fkParentIsModified(pTab, pFKey, aChange, bChngRowid) ){ + Trigger *pAct = fkActionTrigger(pParse, pTab, pFKey, pChanges); + if( pAct ){ + sqlite3CodeRowTriggerDirect(pParse, pAct, pTab, regOld, OE_Abort, 0); + } } } } diff --git a/src/func.c b/src/func.c index 7c6c6b26b0..33eba21b73 100644 --- a/src/func.c +++ b/src/func.c @@ -1693,7 +1693,7 @@ static void setLikeOptFlag(sqlite3 *db, const char *zName, u8 flagVal){ pDef = sqlite3FindFunction(db, zName, sqlite3Strlen30(zName), 2, SQLITE_UTF8, 0); if( ALWAYS(pDef) ){ - pDef->flags = flagVal; + pDef->funcFlags |= flagVal; } } @@ -1737,7 +1737,7 @@ int sqlite3IsLikeFunction(sqlite3 *db, Expr *pExpr, int *pIsNocase, char *aWc){ pDef = sqlite3FindFunction(db, pExpr->u.zToken, sqlite3Strlen30(pExpr->u.zToken), 2, SQLITE_UTF8, 0); - if( NEVER(pDef==0) || (pDef->flags & SQLITE_FUNC_LIKE)==0 ){ + if( NEVER(pDef==0) || (pDef->funcFlags & SQLITE_FUNC_LIKE)==0 ){ return 0; } @@ -1749,7 +1749,7 @@ int sqlite3IsLikeFunction(sqlite3 *db, Expr *pExpr, int *pIsNocase, char *aWc){ assert( (char*)&likeInfoAlt == (char*)&likeInfoAlt.matchAll ); assert( &((char*)&likeInfoAlt)[1] == (char*)&likeInfoAlt.matchOne ); assert( &((char*)&likeInfoAlt)[2] == (char*)&likeInfoAlt.matchSet ); - *pIsNocase = (pDef->flags & SQLITE_FUNC_CASE)==0; + *pIsNocase = (pDef->funcFlags & SQLITE_FUNC_CASE)==0; return 1; } @@ -1830,7 +1830,7 @@ void sqlite3RegisterGlobalFunctions(void){ AGGREGATE(total, 1, 0, 0, sumStep, totalFinalize ), AGGREGATE(avg, 1, 0, 0, sumStep, avgFinalize ), /* AGGREGATE(count, 0, 0, 0, countStep, countFinalize ), */ - {0,SQLITE_UTF8,SQLITE_FUNC_COUNT,0,0,0,countStep,countFinalize,"count",0,0}, + {0,SQLITE_UTF8|SQLITE_FUNC_COUNT,0,0,0,countStep,countFinalize,"count",0,0}, AGGREGATE(count, 1, 0, 0, countStep, countFinalize ), AGGREGATE(group_concat, 1, 0, 0, groupConcatStep, groupConcatFinalize), AGGREGATE(group_concat, 2, 0, 0, groupConcatStep, groupConcatFinalize), diff --git a/src/insert.c b/src/insert.c index 1c2cabb938..32cfb83fa8 100644 --- a/src/insert.c +++ b/src/insert.c @@ -1031,7 +1031,7 @@ void sqlite3Insert( sqlite3GenerateConstraintChecks(pParse, pTab, baseCur, regIns, aRegIdx, keyColumn>=0, 0, onError, endOfLoop, &isReplace ); - sqlite3FkCheck(pParse, pTab, 0, regIns); + sqlite3FkCheck(pParse, pTab, 0, regIns, 0, 0); sqlite3CompleteInsertion( pParse, pTab, baseCur, regIns, aRegIdx, 0, appendFlag, isReplace==0 ); diff --git a/src/main.c b/src/main.c index 4697009666..407de0e4da 100644 --- a/src/main.c +++ b/src/main.c @@ -1406,7 +1406,7 @@ int sqlite3CreateFunc( ** operation to continue but invalidate all precompiled statements. */ p = sqlite3FindFunction(db, zFunctionName, nName, nArg, (u8)enc, 0); - if( p && p->iPrefEnc==enc && p->nArg==nArg ){ + if( p && (p->funcFlags & SQLITE_FUNC_ENCMASK)==enc && p->nArg==nArg ){ if( db->nVdbeActive ){ sqlite3Error(db, SQLITE_BUSY, "unable to delete/modify user-function due to active statements"); @@ -1431,7 +1431,7 @@ int sqlite3CreateFunc( pDestructor->nRef++; } p->pDestructor = pDestructor; - p->flags = 0; + p->funcFlags &= SQLITE_FUNC_ENCMASK; p->xFunc = xFunc; p->xStep = xStep; p->xFinalize = xFinal; diff --git a/src/select.c b/src/select.c index 83fcf7ed40..6e409e73a5 100644 --- a/src/select.c +++ b/src/select.c @@ -3228,7 +3228,7 @@ static Table *isSimpleCount(Select *p, AggInfo *pAggInfo){ if( IsVirtual(pTab) ) return 0; if( pExpr->op!=TK_AGG_FUNCTION ) return 0; if( NEVER(pAggInfo->nFunc==0) ) return 0; - if( (pAggInfo->aFunc[0].pFunc->flags&SQLITE_FUNC_COUNT)==0 ) return 0; + if( (pAggInfo->aFunc[0].pFunc->funcFlags&SQLITE_FUNC_COUNT)==0 ) return 0; if( pExpr->flags&EP_Distinct ) return 0; return pTab; @@ -3825,7 +3825,7 @@ static void updateAccumulator(Parse *pParse, AggInfo *pAggInfo){ assert( nArg==1 ); codeDistinct(pParse, pF->iDistinct, addrNext, 1, regAgg); } - if( pF->pFunc->flags & SQLITE_FUNC_NEEDCOLL ){ + if( pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){ CollSeq *pColl = 0; struct ExprList_item *pItem; int j; diff --git a/src/shell.c b/src/shell.c index 915952cfbe..ccdb157a80 100644 --- a/src/shell.c +++ b/src/shell.c @@ -1194,7 +1194,7 @@ static int shell_exec( char **azCols = (char **)pData; /* Names of result columns */ char **azVals = &azCols[nCol]; /* Results */ int *aiTypes = (int *)&azVals[nCol]; /* Result types */ - int i; + int i, x; assert(sizeof(int) <= sizeof(char *)); /* save off ptrs to column names */ for(i=0; imode==MODE_Insert ){ + azVals[i] = ""; + }else{ + azVals[i] = (char*)sqlite3_column_text(pStmt, i); + } if( !azVals[i] && (aiTypes[i]!=SQLITE_NULL) ){ rc = SQLITE_NOMEM; break; /* from for */ diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 49264238b8..6b3fbe8518 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1065,8 +1065,7 @@ struct sqlite3 { */ struct FuncDef { i16 nArg; /* Number of arguments. -1 means unlimited */ - u8 iPrefEnc; /* Preferred text encoding (SQLITE_UTF8, 16LE, 16BE) */ - u8 flags; /* Some combination of SQLITE_FUNC_* */ + u16 funcFlags; /* Some combination of SQLITE_FUNC_* */ void *pUserData; /* User data parameter */ FuncDef *pNext; /* Next function with same name */ void (*xFunc)(sqlite3_context*,int,sqlite3_value**); /* Regular function */ @@ -1102,14 +1101,16 @@ struct FuncDestructor { ** values must correspond to OPFLAG_LENGTHARG and OPFLAG_TYPEOFARG. There ** are assert() statements in the code to verify this. */ -#define SQLITE_FUNC_LIKE 0x01 /* Candidate for the LIKE optimization */ -#define SQLITE_FUNC_CASE 0x02 /* Case-sensitive LIKE-type function */ -#define SQLITE_FUNC_EPHEM 0x04 /* Ephemeral. Delete with VDBE */ -#define SQLITE_FUNC_NEEDCOLL 0x08 /* sqlite3GetFuncCollSeq() might be called */ -#define SQLITE_FUNC_COUNT 0x10 /* Built-in count(*) aggregate */ -#define SQLITE_FUNC_COALESCE 0x20 /* Built-in coalesce() or ifnull() function */ -#define SQLITE_FUNC_LENGTH 0x40 /* Built-in length() function */ -#define SQLITE_FUNC_TYPEOF 0x80 /* Built-in typeof() function */ +#define SQLITE_FUNC_ENCMASK 0x003 /* SQLITE_UTF8, SQLITE_UTF16BE or UTF16LE */ +#define SQLITE_FUNC_LIKE 0x004 /* Candidate for the LIKE optimization */ +#define SQLITE_FUNC_CASE 0x008 /* Case-sensitive LIKE-type function */ +#define SQLITE_FUNC_EPHEM 0x010 /* Ephemeral. Delete with VDBE */ +#define SQLITE_FUNC_NEEDCOLL 0x020 /* sqlite3GetFuncCollSeq() might be called */ +#define SQLITE_FUNC_LENGTH 0x040 /* Built-in length() function */ +#define SQLITE_FUNC_TYPEOF 0x080 /* Built-in typeof() function */ +#define SQLITE_FUNC_COUNT 0x100 /* Built-in count(*) aggregate */ +#define SQLITE_FUNC_COALESCE 0x200 /* Built-in coalesce() or ifnull() */ +#define SQLITE_FUNC_UNLIKELY 0x400 /* Built-in unlikely() function */ /* ** The following three macros, FUNCTION(), LIKEFUNC() and AGGREGATE() are @@ -1137,18 +1138,18 @@ struct FuncDestructor { ** parameter. */ #define FUNCTION(zName, nArg, iArg, bNC, xFunc) \ - {nArg, SQLITE_UTF8, (bNC*SQLITE_FUNC_NEEDCOLL), \ + {nArg, SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, #zName, 0, 0} #define FUNCTION2(zName, nArg, iArg, bNC, xFunc, extraFlags) \ - {nArg, SQLITE_UTF8, (bNC*SQLITE_FUNC_NEEDCOLL)|extraFlags, \ + {nArg, SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL)|extraFlags, \ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, #zName, 0, 0} #define STR_FUNCTION(zName, nArg, pArg, bNC, xFunc) \ - {nArg, SQLITE_UTF8, bNC*SQLITE_FUNC_NEEDCOLL, \ + {nArg, SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ pArg, 0, xFunc, 0, 0, #zName, 0, 0} #define LIKEFUNC(zName, nArg, arg, flags) \ - {nArg, SQLITE_UTF8, flags, (void *)arg, 0, likeFunc, 0, 0, #zName, 0, 0} + {nArg, SQLITE_UTF8|flags, (void *)arg, 0, likeFunc, 0, 0, #zName, 0, 0} #define AGGREGATE(zName, nArg, arg, nc, xStep, xFinal) \ - {nArg, SQLITE_UTF8, nc*SQLITE_FUNC_NEEDCOLL, \ + {nArg, SQLITE_UTF8|(nc*SQLITE_FUNC_NEEDCOLL), \ SQLITE_INT_TO_PTR(arg), 0, 0, xStep,xFinal,#zName,0,0} /* @@ -3209,18 +3210,18 @@ const char *sqlite3JournalModename(int); ** provided (enforcement of FK constraints requires the triggers sub-system). */ #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) - void sqlite3FkCheck(Parse*, Table*, int, int); + void sqlite3FkCheck(Parse*, Table*, int, int, int*, int); void sqlite3FkDropTable(Parse*, SrcList *, Table*); - void sqlite3FkActions(Parse*, Table*, ExprList*, int); + void sqlite3FkActions(Parse*, Table*, ExprList*, int, int*, int); int sqlite3FkRequired(Parse*, Table*, int*, int); u32 sqlite3FkOldmask(Parse*, Table*); FKey *sqlite3FkReferences(Table *); #else - #define sqlite3FkActions(a,b,c,d) + #define sqlite3FkActions(a,b,c,d,e,f) #define sqlite3FkCheck(a,b,c,d) #define sqlite3FkDropTable(a,b,c) - #define sqlite3FkOldmask(a,b) 0 - #define sqlite3FkRequired(a,b,c,d) 0 + #define sqlite3FkOldmask(a,b) 0 + #define sqlite3FkRequired(a,b,c,d,e,f) 0 #endif #ifndef SQLITE_OMIT_FOREIGN_KEY void sqlite3FkDelete(sqlite3 *, Table*); diff --git a/src/update.c b/src/update.c index 5077f64e35..0f760a4609 100644 --- a/src/update.c +++ b/src/update.c @@ -488,7 +488,7 @@ void sqlite3Update( /* Do FK constraint checks. */ if( hasFK ){ - sqlite3FkCheck(pParse, pTab, regOldRowid, 0); + sqlite3FkCheck(pParse, pTab, regOldRowid, 0, aXRef, chngRowid); } /* Delete the index entries associated with the current record. */ @@ -502,7 +502,7 @@ void sqlite3Update( sqlite3VdbeJumpHere(v, j1); if( hasFK ){ - sqlite3FkCheck(pParse, pTab, 0, regNewRowid); + sqlite3FkCheck(pParse, pTab, 0, regNewRowid, aXRef, chngRowid); } /* Insert the new index entries and the new record. */ @@ -512,7 +512,7 @@ void sqlite3Update( ** handle rows (possibly in other tables) that refer via a foreign key ** to the row just updated. */ if( hasFK ){ - sqlite3FkActions(pParse, pTab, pChanges, regOldRowid); + sqlite3FkActions(pParse, pTab, pChanges, regOldRowid, aXRef, chngRowid); } } diff --git a/src/vdbe.c b/src/vdbe.c index 9f8bcb2168..70d06eb31f 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -1437,7 +1437,7 @@ case OP_Function: { MemSetTypeFlag(&ctx.s, MEM_Null); ctx.fErrorOrAux = 0; - if( ctx.pFunc->flags & SQLITE_FUNC_NEEDCOLL ){ + if( ctx.pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){ assert( pOp>aOp ); assert( pOp[-1].p4type==P4_COLLSEQ ); assert( pOp[-1].opcode==OP_CollSeq ); @@ -5437,7 +5437,7 @@ case OP_AggStep: { ctx.isError = 0; ctx.pColl = 0; ctx.skipFlag = 0; - if( ctx.pFunc->flags & SQLITE_FUNC_NEEDCOLL ){ + if( ctx.pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){ assert( pOp>p->aOp ); assert( pOp[-1].p4type==P4_COLLSEQ ); assert( pOp[-1].opcode==OP_CollSeq ); diff --git a/src/vdbeaux.c b/src/vdbeaux.c index ec071606a2..bb67c6da30 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -604,7 +604,7 @@ void sqlite3VdbeJumpHere(Vdbe *p, int addr){ ** the FuncDef is not ephermal, then do nothing. */ static void freeEphemeralFunction(sqlite3 *db, FuncDef *pDef){ - if( ALWAYS(pDef) && (pDef->flags & SQLITE_FUNC_EPHEM)!=0 ){ + if( ALWAYS(pDef) && (pDef->funcFlags & SQLITE_FUNC_EPHEM)!=0 ){ sqlite3DbFree(db, pDef); } } diff --git a/src/vdbemem.c b/src/vdbemem.c index dbdfc53074..95a8798712 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -1304,6 +1304,8 @@ int sqlite3Stat4ProbeSetValue( ){ int rc = SQLITE_OK; sqlite3_value *pVal = 0; + sqlite3 *db = pParse->db; + struct ValueNewStat4Ctx alloc; alloc.pParse = pParse; @@ -1315,7 +1317,7 @@ int sqlite3Stat4ProbeSetValue( pExpr = sqlite3ExprSkipCollate(pExpr); if( !pExpr ){ - pVal = valueNew(pParse->db, &alloc); + pVal = valueNew(db, &alloc); if( pVal ){ sqlite3VdbeMemSetNull((Mem*)pVal); *pbOk = 1; @@ -1327,11 +1329,11 @@ int sqlite3Stat4ProbeSetValue( int iBindVar = pExpr->iColumn; sqlite3VdbeSetVarmask(pParse->pVdbe, iBindVar); if( (v = pParse->pReprepare)!=0 ){ - pVal = valueNew(pParse->db, &alloc); + pVal = valueNew(db, &alloc); if( pVal ){ rc = sqlite3VdbeMemCopy((Mem*)pVal, &v->aVar[iBindVar-1]); if( rc==SQLITE_OK ){ - sqlite3ValueApplyAffinity(pVal, affinity, SQLITE_UTF8); + sqlite3ValueApplyAffinity(pVal, affinity, ENC(db)); } pVal->db = pParse->db; *pbOk = 1; @@ -1341,12 +1343,11 @@ int sqlite3Stat4ProbeSetValue( *pbOk = 0; } }else{ - sqlite3 *db = pParse->db; rc = valueFromExpr(db, pExpr, ENC(db), affinity, &pVal, &alloc); *pbOk = (pVal!=0); } - assert( pVal==0 || pVal->db==pParse->db ); + assert( pVal==0 || pVal->db==db ); return rc; } diff --git a/src/vtab.c b/src/vtab.c index 357a6dba2a..195aa68b25 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -1013,7 +1013,7 @@ FuncDef *sqlite3VtabOverloadFunction( memcpy(pNew->zName, pDef->zName, sqlite3Strlen30(pDef->zName)+1); pNew->xFunc = xFunc; pNew->pUserData = pArg; - pNew->flags |= SQLITE_FUNC_EPHEM; + pNew->funcFlags |= SQLITE_FUNC_EPHEM; return pNew; } diff --git a/src/where.c b/src/where.c index 235277d63d..3c8a1eaac6 100644 --- a/src/where.c +++ b/src/where.c @@ -2421,12 +2421,15 @@ static void whereKeyStats( tRowcnt *aStat /* OUT: stats written here */ ){ IndexSample *aSample = pIdx->aSample; - int iCol = pRec->nField-1; /* Index of required stats in anEq[] etc. */ + int iCol; /* Index of required stats in anEq[] etc. */ int iMin = 0; /* Smallest sample not yet tested */ int i = pIdx->nSample; /* Smallest sample larger than or equal to pRec */ int iTest; /* Next sample to test */ int res; /* Result of comparison operation */ + assert( pRec!=0 || pParse->db->mallocFailed ); + if( pRec==0 ) return; + iCol = pRec->nField - 1; assert( pIdx->nSample>0 ); assert( pRec->nField>0 && iColnSampleCol ); do{ @@ -2546,14 +2549,14 @@ static int whereRangeScanEst( Index *p = pBuilder->pNew->u.btree.pIndex; int nEq = pBuilder->pNew->u.btree.nEq; - if( nEq==pBuilder->nRecValid + if( p->nSample>0 + && nEq==pBuilder->nRecValid && nEqnSampleCol - && p->nSample && OptimizationEnabled(pParse->db, SQLITE_Stat3) ){ UnpackedRecord *pRec = pBuilder->pRec; tRowcnt a[2]; - u8 aff = p->pTable->aCol[p->aiColumn[0]].affinity; + u8 aff; /* Variable iLower will be set to the estimate of the number of rows in ** the index that are less than the lower bound of the range query. The @@ -2575,6 +2578,11 @@ static int whereRangeScanEst( tRowcnt iLower; tRowcnt iUpper; + if( nEq==p->nColumn ){ + aff = SQLITE_AFF_INTEGER; + }else{ + aff = p->pTable->aCol[p->aiColumn[nEq]].affinity; + } /* Determine iLower and iUpper using ($P) only. */ if( nEq==0 ){ iLower = 0; @@ -4060,8 +4068,11 @@ static int whereLoopResize(sqlite3 *db, WhereLoop *p, int n){ ** Transfer content from the second pLoop into the first. */ static int whereLoopXfer(sqlite3 *db, WhereLoop *pTo, WhereLoop *pFrom){ - if( whereLoopResize(db, pTo, pFrom->nLTerm) ) return SQLITE_NOMEM; whereLoopClearUnion(db, pTo); + if( whereLoopResize(db, pTo, pFrom->nLTerm) ){ + memset(&pTo->u, 0, sizeof(pTo->u)); + return SQLITE_NOMEM; + } memcpy(pTo, pFrom, WHERE_LOOP_XFER_SZ); memcpy(pTo->aLTerm, pFrom->aLTerm, pTo->nLTerm*sizeof(pTo->aLTerm[0])); if( pFrom->wsFlags & WHERE_VIRTUALTABLE ){ @@ -4175,11 +4186,11 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ ** all of (1) dependencies (2) setup-cost, (3) run-cost, and ** (4) number of output rows. */ assert( p->rSetup==pTemplate->rSetup ); - if( p->nLTermnLTerm + if( p->prereq==pTemplate->prereq + && p->nLTermnLTerm && (p->wsFlags & WHERE_INDEXED)!=0 && (pTemplate->wsFlags & WHERE_INDEXED)!=0 && p->u.btree.pIndex==pTemplate->u.btree.pIndex - && p->prereq==pTemplate->prereq ){ /* Overwrite an existing WhereLoop with an similar one that uses ** more terms of the index */ @@ -5904,7 +5915,7 @@ WhereInfo *sqlite3WhereBegin( && OptimizationEnabled(db, SQLITE_OmitNoopJoin) ){ Bitmask tabUsed = exprListTableUsage(pMaskSet, pResultSet); - if( pOrderBy ) tabUsed |= exprListTableUsage(pMaskSet, pOrderBy); + if( sWLB.pOrderBy ) tabUsed |= exprListTableUsage(pMaskSet, sWLB.pOrderBy); while( pWInfo->nLevel>=2 ){ WhereTerm *pTerm, *pEnd; pLoop = pWInfo->a[pWInfo->nLevel-1].pWLoop; diff --git a/test/analyze9.test b/test/analyze9.test index e2528709bf..604f340bf5 100644 --- a/test/analyze9.test +++ b/test/analyze9.test @@ -244,7 +244,7 @@ do_execsql_test 4.3 { FROM sqlite_stat4 ORDER BY rowid DESC LIMIT 2; } { - {2 1 1 1} {295 296 296} {120 122 125} {201 4} + {2 1 1 1} {295 295 295} {120 121 124} {201 3} {5 3 1 1} {290 290 292} {119 119 121} {200 1} } @@ -556,6 +556,398 @@ foreach {tn schema} { } } +#------------------------------------------------------------------------- +# Check that affinities are taken into account when using stat4 data to +# estimate the number of rows scanned by a rowid constraint. +# +drop_all_tables +do_test 13.1 { + execsql { + CREATE TABLE t1(a, b, c); + CREATE INDEX i1 ON t1(a); + CREATE INDEX i2 ON t1(b, c); + } + for {set i 0} {$i<100} {incr i} { + if {$i %2} {set a abc} else {set a def} + execsql { INSERT INTO t1(rowid, a, b, c) VALUES($i, $a, $i, $i) } + } + execsql ANALYZE +} {} +do_eqp_test 13.2.1 { + SELECT * FROM t1 WHERE a='abc' AND rowid<15 AND b<20 +} {/SEARCH TABLE t1 USING INDEX i1/} +do_eqp_test 13.2.2 { + SELECT * FROM t1 WHERE a='abc' AND rowid<'15' AND b<20 +} {/SEARCH TABLE t1 USING INDEX i1/} +do_eqp_test 13.3.1 { + SELECT * FROM t1 WHERE a='abc' AND rowid<100 AND b<20 +} {/SEARCH TABLE t1 USING INDEX i2/} +do_eqp_test 13.3.2 { + SELECT * FROM t1 WHERE a='abc' AND rowid<'100' AND b<20 +} {/SEARCH TABLE t1 USING INDEX i2/} + +#------------------------------------------------------------------------- +# Check also that affinities are taken into account when using stat4 data +# to estimate the number of rows scanned by any other constraint on a +# column other than the leftmost. +# +drop_all_tables +do_test 14.1 { + execsql { CREATE TABLE t1(a, b INTEGER, c) } + for {set i 0} {$i<100} {incr i} { + set c [expr $i % 3] + execsql { INSERT INTO t1 VALUES('ott', $i, $c) } + } + execsql { + CREATE INDEX i1 ON t1(a, b); + CREATE INDEX i2 ON t1(c); + ANALYZE; + } +} {} +do_eqp_test 13.2.1 { + SELECT * FROM t1 WHERE a='ott' AND b<10 AND c=1 +} {/SEARCH TABLE t1 USING INDEX i1/} +do_eqp_test 13.2.2 { + SELECT * FROM t1 WHERE a='ott' AND b<'10' AND c=1 +} {/SEARCH TABLE t1 USING INDEX i1/} + +#------------------------------------------------------------------------- +# By default, 16 non-periodic samples are collected for the stat4 table. +# The following tests attempt to verify that the most common keys are +# being collected. +# +proc check_stat4 {tn} { + db eval ANALYZE + db eval {SELECT a, b, c, d FROM t1} { + incr k($a) + incr k([list $a $b]) + incr k([list $a $b $c]) + if { [info exists k([list $a $b $c $d])]==0 } { incr nRow } + incr k([list $a $b $c $d]) + } + + set L [list] + foreach key [array names k] { + lappend L [list $k($key) $key] + } + + set nSample $nRow + if {$nSample>16} {set nSample 16} + + set nThreshold [lindex [lsort -decr -integer -index 0 $L] [expr $nSample-1] 0] + foreach key [array names k] { + if {$k($key)>$nThreshold} { + set expect($key) 1 + } + if {$k($key)==$nThreshold} { + set possible($key) 1 + } + } + + + set nPossible [expr $nSample - [llength [array names expect]]] + + #puts "EXPECT: [array names expect]" + #puts "POSSIBLE($nPossible/[array size possible]): [array names possible]" + #puts "HAVE: [db eval {SELECT test_decode(sample) FROM sqlite_stat4 WHERE idx='i1'}]" + + db eval {SELECT test_decode(sample) AS s FROM sqlite_stat4 WHERE idx='i1'} { + set seen 0 + for {set i 0} {$i<4} {incr i} { + unset -nocomplain expect([lrange $s 0 $i]) + if {[info exists possible([lrange $s 0 $i])]} { + set seen 1 + unset -nocomplain possible([lrange $s 0 $i]) + } + } + if {$seen} {incr nPossible -1} + } + if {$nPossible<0} {set nPossible 0} + + set res [list [llength [array names expect]] $nPossible] + uplevel [list do_test $tn [list set {} $res] {0 0}] +} + +drop_all_tables +do_test 14.1.1 { + execsql { + CREATE TABLE t1(a,b,c,d); + CREATE INDEX i1 ON t1(a,b,c,d); + } + for {set i 0} {$i < 160} {incr i} { + execsql { INSERT INTO t1 VALUES($i,$i,$i,$i) } + if {($i % 10)==0} { execsql { INSERT INTO t1 VALUES($i,$i,$i,$i) } } + } +} {} +check_stat4 14.1.2 + +do_test 14.2.1 { + execsql { DELETE FROM t1 } + for {set i 0} {$i < 1600} {incr i} { + execsql { INSERT INTO t1 VALUES($i/10,$i/17,$i/27,$i/37) } + } +} {} +check_stat4 14.2.2 + +do_test 14.3.1 { + for {set i 0} {$i < 10} {incr i} { + execsql { INSERT INTO t1 VALUES($i*50,$i*50,$i*50,$i*50) } + execsql { INSERT INTO t1 VALUES($i*50,$i*50,$i*50,$i*50) } + execsql { INSERT INTO t1 VALUES($i*50,$i*50,$i*50,$i*50) } + execsql { INSERT INTO t1 VALUES($i*50,$i*50,$i*50,$i*50) } + execsql { INSERT INTO t1 VALUES($i*50,$i*50,$i*50,$i*50) } + execsql { INSERT INTO t1 VALUES($i*50,$i*50,$i*50,$i*50) } + execsql { INSERT INTO t1 VALUES($i*50,$i*50,$i*50,$i*50) } + execsql { INSERT INTO t1 VALUES($i*50,$i*50,$i*50,$i*50) } + execsql { INSERT INTO t1 VALUES($i*50,$i*50,$i*50,$i*50) } + execsql { INSERT INTO t1 VALUES($i*50,$i*50,$i*50,$i*50) } + } +} {} +check_stat4 14.3.2 + +do_test 14.4.1 { + execsql {DELETE FROM t1} + for {set i 1} {$i < 160} {incr i} { + set b [expr $i % 10] + if {$b==0 || $b==2} {set b 1} + execsql { INSERT INTO t1 VALUES($i/10,$b,$i,$i) } + } +} {} +check_stat4 14.4.2 +db func lrange lrange +db func lindex lindex +do_execsql_test 14.4.3 { + SELECT lrange(test_decode(sample), 0, 1) AS s FROM sqlite_stat4 + WHERE lindex(s, 1)=='1' ORDER BY rowid +} { + {0 1} {1 1} {2 1} {3 1} + {4 1} {5 1} {6 1} {7 1} + {8 1} {9 1} {10 1} {11 1} + {12 1} {13 1} {14 1} {15 1} +} + +#------------------------------------------------------------------------- +# Test that nothing untoward happens if the stat4 table contains entries +# for indexes that do not exist. Or NULL values in the idx column. +# Or NULL values in any of the other columns. +# +drop_all_tables +do_execsql_test 15.1 { + CREATE TABLE x1(a, b, UNIQUE(a, b)); + INSERT INTO x1 VALUES(1, 2); + INSERT INTO x1 VALUES(3, 4); + INSERT INTO x1 VALUES(5, 6); + ANALYZE; + INSERT INTO sqlite_stat4 VALUES(NULL, NULL, NULL, NULL, NULL, NULL); +} +db close +sqlite3 db test.db +do_execsql_test 15.2 { SELECT * FROM x1 } {1 2 3 4 5 6} + +do_execsql_test 15.3 { + INSERT INTO sqlite_stat4 VALUES(42, 42, 42, 42, 42, 42); +} +db close +sqlite3 db test.db +do_execsql_test 15.4 { SELECT * FROM x1 } {1 2 3 4 5 6} + +do_execsql_test 15.5 { + UPDATE sqlite_stat1 SET stat = NULL; +} +db close +sqlite3 db test.db +do_execsql_test 15.6 { SELECT * FROM x1 } {1 2 3 4 5 6} + +do_execsql_test 15.7 { + ANALYZE; + UPDATE sqlite_stat1 SET tbl = 'no such tbl'; +} +db close +sqlite3 db test.db +do_execsql_test 15.8 { SELECT * FROM x1 } {1 2 3 4 5 6} + +do_execsql_test 15.9 { + ANALYZE; + UPDATE sqlite_stat4 SET neq = NULL, nlt=NULL, ndlt=NULL; +} +db close +sqlite3 db test.db +do_execsql_test 15.10 { SELECT * FROM x1 } {1 2 3 4 5 6} + +# This is just for coverage.... +do_execsql_test 15.11 { + ANALYZE; + UPDATE sqlite_stat1 SET stat = stat || ' unordered'; +} +db close +sqlite3 db test.db +do_execsql_test 15.12 { SELECT * FROM x1 } {1 2 3 4 5 6} + +#------------------------------------------------------------------------- +# Test that allocations used for sqlite_stat4 samples are included in +# the quantity returned by SQLITE_DBSTATUS_SCHEMA_USED. +# +set one [string repeat x 1000] +set two [string repeat x 2000] +do_test 16.1 { + reset_db + execsql { + CREATE TABLE t1(a, UNIQUE(a)); + INSERT INTO t1 VALUES($one); + ANALYZE; + } + set nByte [lindex [sqlite3_db_status db SCHEMA_USED 0] 1] + + reset_db + execsql { + CREATE TABLE t1(a, UNIQUE(a)); + INSERT INTO t1 VALUES($two); + ANALYZE; + } + set nByte2 [lindex [sqlite3_db_status db SCHEMA_USED 0] 1] + + expr {$nByte2 > $nByte+950 && $nByte2 < $nByte+1050} +} {1} + +#------------------------------------------------------------------------- +# Test that stat4 data may be used with partial indexes. +# +do_test 17.1 { + reset_db + execsql { + CREATE TABLE t1(a, b, c, d); + CREATE INDEX i1 ON t1(a, b) WHERE d IS NOT NULL; + INSERT INTO t1 VALUES(-1, -1, -1, NULL); + INSERT INTO t1 SELECT 2*a,2*b,2*c,d FROM t1; + INSERT INTO t1 SELECT 2*a,2*b,2*c,d FROM t1; + INSERT INTO t1 SELECT 2*a,2*b,2*c,d FROM t1; + INSERT INTO t1 SELECT 2*a,2*b,2*c,d FROM t1; + INSERT INTO t1 SELECT 2*a,2*b,2*c,d FROM t1; + INSERT INTO t1 SELECT 2*a,2*b,2*c,d FROM t1; + } + + for {set i 0} {$i < 32} {incr i} { + if {$i<8} {set b 0} else { set b $i } + execsql { INSERT INTO t1 VALUES($i%2, $b, $i/2, 'abc') } + } + execsql {ANALYZE main.t1} +} {} + +do_catchsql_test 17.1.2 { + ANALYZE temp.t1; +} {1 {no such table: temp.t1}} + +do_eqp_test 17.2 { + SELECT * FROM t1 WHERE d IS NOT NULL AND a=0 AND b=10 AND c=10; +} {/USING INDEX i1/} +do_eqp_test 17.3 { + SELECT * FROM t1 WHERE d IS NOT NULL AND a=0 AND b=0 AND c=10; +} {/USING INDEX i1/} + +do_execsql_test 17.4 { + CREATE INDEX i2 ON t1(c); + ANALYZE main.i2; +} +do_eqp_test 17.5 { + SELECT * FROM t1 WHERE d IS NOT NULL AND a=0 AND b=10 AND c=10; +} {/USING INDEX i1/} +do_eqp_test 17.6 { + SELECT * FROM t1 WHERE d IS NOT NULL AND a=0 AND b=0 AND c=10; +} {/USING INDEX i2/} + +#------------------------------------------------------------------------- +# +do_test 18.1 { + reset_db + execsql { + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a, b); + } + for {set i 0} {$i < 9} {incr i} { + execsql { + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + } + } + execsql ANALYZE + execsql { SELECT count(*) FROM sqlite_stat4 } +} {9} + +#------------------------------------------------------------------------- +# For coverage. +# +ifcapable view { + do_test 19.1 { + reset_db + execsql { + CREATE TABLE t1(x, y); + CREATE INDEX i1 ON t1(x, y); + CREATE VIEW v1 AS SELECT * FROM t1; + ANALYZE; + } + } {} +} +ifcapable auth { + proc authproc {op args} { + if {$op == "SQLITE_ANALYZE"} { return "SQLITE_DENY" } + return "SQLITE_OK" + } + do_test 19.2 { + reset_db + db auth authproc + execsql { + CREATE TABLE t1(x, y); + CREATE VIEW v1 AS SELECT * FROM t1; + } + catchsql ANALYZE + } {1 {not authorized}} +} + +#------------------------------------------------------------------------- +# +reset_db +proc r {args} { expr rand() } +db func r r +db func lrange lrange +do_test 20.1 { + execsql { + CREATE TABLE t1(a,b,c,d); + CREATE INDEX i1 ON t1(a,b,c,d); + } + for {set i 0} {$i < 16} {incr i} { + execsql { + INSERT INTO t1 VALUES($i, r(), r(), r()); + INSERT INTO t1 VALUES($i, $i, r(), r()); + INSERT INTO t1 VALUES($i, $i, $i, r()); + INSERT INTO t1 VALUES($i, $i, $i, $i); + INSERT INTO t1 VALUES($i, $i, $i, $i); + INSERT INTO t1 VALUES($i, $i, $i, r()); + INSERT INTO t1 VALUES($i, $i, r(), r()); + INSERT INTO t1 VALUES($i, r(), r(), r()); + } + } +} {} +do_execsql_test 20.2 { ANALYZE } +for {set i 0} {$i<16} {incr i} { + set val "$i $i $i $i" + do_execsql_test 20.3.$i { + SELECT count(*) FROM sqlite_stat4 + WHERE lrange(test_decode(sample), 0, 3)=$val + } {1} +} + finish_test - diff --git a/test/analyzeB.test b/test/analyzeB.test new file mode 100644 index 0000000000..2a78c18b3d --- /dev/null +++ b/test/analyzeB.test @@ -0,0 +1,683 @@ +# 2013 August 3 +# +# 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 contains automated tests used to verify that the sqlite_stat3 +# functionality is working. The tests in this file are based on a subset +# of the sqlite_stat4 tests in analyze9.test. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix analyzeB + +ifcapable !stat3 { + finish_test + return +} + +do_execsql_test 1.0 { + CREATE TABLE t1(a TEXT, b TEXT); + INSERT INTO t1 VALUES('(0)', '(0)'); + INSERT INTO t1 VALUES('(1)', '(1)'); + INSERT INTO t1 VALUES('(2)', '(2)'); + INSERT INTO t1 VALUES('(3)', '(3)'); + INSERT INTO t1 VALUES('(4)', '(4)'); + CREATE INDEX i1 ON t1(a, b); +} {} + + +do_execsql_test 1.1 { + ANALYZE; +} {} + +do_execsql_test 1.2 { + SELECT tbl,idx,nEq,nLt,nDLt,quote(sample) FROM sqlite_stat3; +} { + t1 i1 1 0 0 '(0)' + t1 i1 1 1 1 '(1)' + t1 i1 1 2 2 '(2)' + t1 i1 1 3 3 '(3)' + t1 i1 1 4 4 '(4)' +} + +if {[permutation] != "utf16"} { + do_execsql_test 1.3 { + SELECT tbl,idx,nEq,nLt,nDLt,quote(sample) FROM sqlite_stat3; + } { + t1 i1 1 0 0 '(0)' + t1 i1 1 1 1 '(1)' + t1 i1 1 2 2 '(2)' + t1 i1 1 3 3 '(3)' + t1 i1 1 4 4 '(4)' + } +} + + +#------------------------------------------------------------------------- +# This is really just to test SQL user function "test_decode". +# +reset_db +do_execsql_test 2.1 { + CREATE TABLE t1(a, b, c); + INSERT INTO t1(a) VALUES('some text'); + INSERT INTO t1(a) VALUES(14); + INSERT INTO t1(a) VALUES(NULL); + INSERT INTO t1(a) VALUES(22.0); + INSERT INTO t1(a) VALUES(x'656667'); + CREATE INDEX i1 ON t1(a, b, c); + ANALYZE; + SELECT quote(sample) FROM sqlite_stat3; +} { + NULL 14 22.0 {'some text'} X'656667' +} + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 3.1 { + CREATE TABLE t2(a, b); + CREATE INDEX i2 ON t2(a, b); + BEGIN; +} + +do_test 3.2 { + for {set i 0} {$i < 1000} {incr i} { + set a [expr $i / 10] + set b [expr int(rand() * 15.0)] + execsql { INSERT INTO t2 VALUES($a, $b) } + } + execsql COMMIT +} {} + +db func lindex lindex + +# Each value of "a" occurs exactly 10 times in the table. +# +do_execsql_test 3.3.1 { + SELECT count(*) FROM t2 GROUP BY a; +} [lrange [string repeat "10 " 100] 0 99] + +# The first element in the "nEq" list of all samples should therefore be 10. +# +do_execsql_test 3.3.2 { + ANALYZE; + SELECT nEq FROM sqlite_stat3; +} [lrange [string repeat "10 " 100] 0 23] + +#------------------------------------------------------------------------- +# +do_execsql_test 3.4 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + INSERT INTO t1 VALUES(1, 1, 'one-a'); + INSERT INTO t1 VALUES(11, 1, 'one-b'); + INSERT INTO t1 VALUES(21, 1, 'one-c'); + INSERT INTO t1 VALUES(31, 1, 'one-d'); + INSERT INTO t1 VALUES(41, 1, 'one-e'); + INSERT INTO t1 VALUES(51, 1, 'one-f'); + INSERT INTO t1 VALUES(61, 1, 'one-g'); + INSERT INTO t1 VALUES(71, 1, 'one-h'); + INSERT INTO t1 VALUES(81, 1, 'one-i'); + INSERT INTO t1 VALUES(91, 1, 'one-j'); + INSERT INTO t1 SELECT a+1,2,'two' || substr(c,4) FROM t1; + INSERT INTO t1 SELECT a+2,3,'three'||substr(c,4) FROM t1 WHERE c GLOB 'one-*'; + INSERT INTO t1 SELECT a+3,4,'four'||substr(c,4) FROM t1 WHERE c GLOB 'one-*'; + INSERT INTO t1 SELECT a+4,5,'five'||substr(c,4) FROM t1 WHERE c GLOB 'one-*'; + INSERT INTO t1 SELECT a+5,6,'six'||substr(c,4) FROM t1 WHERE c GLOB 'one-*'; + CREATE INDEX t1b ON t1(b); + ANALYZE; + SELECT c FROM t1 WHERE b=3 AND a BETWEEN 30 AND 60; +} {three-d three-e three-f} + + +#------------------------------------------------------------------------- +# These tests verify that the sample selection for stat3 appears to be +# working as designed. +# + +reset_db +db func lindex lindex +db func lrange lrange + +do_execsql_test 4.0 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a, b, c); + CREATE INDEX i1 ON t1(c, b, a); +} + + +proc insert_filler_rows_n {iStart args} { + set A(-ncopy) 1 + set A(-nval) 1 + + foreach {k v} $args { + if {[info exists A($k)]==0} { error "no such option: $k" } + set A($k) $v + } + if {[llength $args] % 2} { + error "option requires an argument: [lindex $args end]" + } + + for {set i 0} {$i < $A(-nval)} {incr i} { + set iVal [expr $iStart+$i] + for {set j 0} {$j < $A(-ncopy)} {incr j} { + execsql { INSERT INTO t1 VALUES($iVal, $iVal, $iVal) } + } + } +} + +do_test 4.1 { + execsql { BEGIN } + insert_filler_rows_n 0 -ncopy 10 -nval 19 + insert_filler_rows_n 20 -ncopy 1 -nval 100 + + execsql { + INSERT INTO t1(c, b, a) VALUES(200, 1, 'a'); + INSERT INTO t1(c, b, a) VALUES(200, 1, 'b'); + INSERT INTO t1(c, b, a) VALUES(200, 1, 'c'); + + INSERT INTO t1(c, b, a) VALUES(200, 2, 'e'); + INSERT INTO t1(c, b, a) VALUES(200, 2, 'f'); + + INSERT INTO t1(c, b, a) VALUES(201, 3, 'g'); + INSERT INTO t1(c, b, a) VALUES(201, 4, 'h'); + + ANALYZE; + SELECT count(*) FROM sqlite_stat3; + SELECT count(*) FROM t1; + } +} {24 297} + +do_execsql_test 4.2 { + SELECT neq, nlt, ndlt, sample FROM sqlite_stat3 ORDER BY rowid LIMIT 16; +} { + 10 0 0 0 + 10 10 1 1 + 10 20 2 2 + 10 30 3 3 + 10 40 4 4 + 10 50 5 5 + 10 60 6 6 + 10 70 7 7 + 10 80 8 8 + 10 90 9 9 + 10 100 10 10 + 10 110 11 11 + 10 120 12 12 + 10 130 13 13 + 10 140 14 14 + 10 150 15 15 +} + +do_execsql_test 4.3 { + SELECT neq, nlt, ndlt, sample FROM sqlite_stat3 + ORDER BY rowid DESC LIMIT 2; +} { + 2 295 120 201 + 5 290 119 200 +} + +do_execsql_test 4.4 { SELECT count(DISTINCT c) FROM t1 WHERE c<201 } 120 +do_execsql_test 4.5 { SELECT count(DISTINCT c) FROM t1 WHERE c<200 } 119 + +reset_db +do_test 4.7 { + execsql { + BEGIN; + CREATE TABLE t1(o,t INTEGER PRIMARY KEY); + CREATE INDEX i1 ON t1(o); + } + for {set i 0} {$i<10000} {incr i [expr (($i<1000)?1:10)]} { + execsql { INSERT INTO t1 VALUES('x', $i) } + } + execsql { + COMMIT; + ANALYZE; + SELECT count(*) FROM sqlite_stat3; + } +} {1} +do_execsql_test 4.8 { + SELECT sample FROM sqlite_stat3; +} {x} + + +#------------------------------------------------------------------------- +# The following would cause a crash at one point. +# +reset_db +do_execsql_test 5.1 { + PRAGMA encoding = 'utf-16'; + CREATE TABLE t0(v); + ANALYZE; +} + +#------------------------------------------------------------------------- +# This was also crashing (corrupt sqlite_stat3 table). +# +reset_db +do_execsql_test 6.1 { + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a); + CREATE INDEX i2 ON t1(b); + INSERT INTO t1 VALUES(1, 1); + INSERT INTO t1 VALUES(2, 2); + INSERT INTO t1 VALUES(3, 3); + INSERT INTO t1 VALUES(4, 4); + INSERT INTO t1 VALUES(5, 5); + ANALYZE; + PRAGMA writable_schema = 1; + CREATE TEMP TABLE x1 AS + SELECT tbl,idx,neq,nlt,ndlt,sample FROM sqlite_stat3 + ORDER BY (rowid%5), rowid; + DELETE FROM sqlite_stat3; + INSERT INTO sqlite_stat3 SELECT * FROM x1; + PRAGMA writable_schema = 0; + ANALYZE sqlite_master; +} +do_execsql_test 6.2 { + SELECT * FROM t1 WHERE a = 'abc'; +} + +#------------------------------------------------------------------------- +# The following tests experiment with adding corrupted records to the +# 'sample' column of the sqlite_stat3 table. +# +reset_db +sqlite3_db_config_lookaside db 0 0 0 + +do_execsql_test 7.1 { + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a, b); + INSERT INTO t1 VALUES(1, 1); + INSERT INTO t1 VALUES(2, 2); + INSERT INTO t1 VALUES(3, 3); + INSERT INTO t1 VALUES(4, 4); + INSERT INTO t1 VALUES(5, 5); + ANALYZE; + UPDATE sqlite_stat3 SET sample = X'' WHERE rowid = 1; + ANALYZE sqlite_master; +} + +do_execsql_test 7.2 { + UPDATE sqlite_stat3 SET sample = X'FFFF'; + ANALYZE sqlite_master; + SELECT * FROM t1 WHERE a = 1; +} {1 1} + +do_execsql_test 7.3 { + ANALYZE; + UPDATE sqlite_stat3 SET neq = '0 0 0'; + ANALYZE sqlite_master; + SELECT * FROM t1 WHERE a = 1; +} {1 1} + +do_execsql_test 7.4 { + ANALYZE; + UPDATE sqlite_stat3 SET ndlt = '0 0 0'; + ANALYZE sqlite_master; + SELECT * FROM t1 WHERE a = 3; +} {3 3} + +do_execsql_test 7.5 { + ANALYZE; + UPDATE sqlite_stat3 SET nlt = '0 0 0'; + ANALYZE sqlite_master; + SELECT * FROM t1 WHERE a = 5; +} {5 5} + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 8.1 { + CREATE TABLE t1(x TEXT); + CREATE INDEX i1 ON t1(x); + INSERT INTO t1 VALUES('1'); + INSERT INTO t1 VALUES('2'); + INSERT INTO t1 VALUES('3'); + INSERT INTO t1 VALUES('4'); + ANALYZE; +} +do_execsql_test 8.2 { + SELECT * FROM t1 WHERE x = 3; +} {3} + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 9.1 { + CREATE TABLE t1(a, b, c, d, e); + CREATE INDEX i1 ON t1(a, b, c, d); + CREATE INDEX i2 ON t1(e); +} +do_test 9.2 { + execsql BEGIN; + for {set i 0} {$i < 100} {incr i} { + execsql "INSERT INTO t1 VALUES('x', 'y', 'z', $i, [expr $i/2])" + } + for {set i 0} {$i < 20} {incr i} { + execsql "INSERT INTO t1 VALUES('x', 'y', 'z', 101, $i)" + } + for {set i 102} {$i < 200} {incr i} { + execsql "INSERT INTO t1 VALUES('x', 'y', 'z', $i, [expr $i/2])" + } + execsql COMMIT + execsql ANALYZE +} {} + +do_eqp_test 9.3.1 { + SELECT * FROM t1 WHERE a='x' AND b='y' AND c='z' AND d=101 AND e=5; +} {/t1 USING INDEX i1/} +do_eqp_test 9.3.2 { + SELECT * FROM t1 WHERE a='x' AND b='y' AND c='z' AND d=99 AND e=5; +} {/t1 USING INDEX i1/} + +set value_d [expr 101] +do_eqp_test 9.4.1 { + SELECT * FROM t1 WHERE a='x' AND b='y' AND c='z' AND d=$value_d AND e=5 +} {/t1 USING INDEX i1/} +set value_d [expr 99] +do_eqp_test 9.4.2 { + SELECT * FROM t1 WHERE a='x' AND b='y' AND c='z' AND d=$value_d AND e=5 +} {/t1 USING INDEX i1/} + +#------------------------------------------------------------------------- +# Check that the planner takes stat3 data into account when considering +# "IS NULL" and "IS NOT NULL" constraints. +# +do_execsql_test 10.1.1 { + DROP TABLE IF EXISTS t3; + CREATE TABLE t3(a, b); + CREATE INDEX t3a ON t3(a); + CREATE INDEX t3b ON t3(b); +} +do_test 10.1.2 { + for {set i 1} {$i < 100} {incr i} { + if {$i>90} { set a $i } else { set a NULL } + set b [expr $i % 5] + execsql "INSERT INTO t3 VALUES($a, $b)" + } + execsql ANALYZE +} {} +do_eqp_test 10.1.3 { + SELECT * FROM t3 WHERE a IS NULL AND b = 2 +} {/t3 USING INDEX t3b/} +do_eqp_test 10.1.4 { + SELECT * FROM t3 WHERE a IS NOT NULL AND b = 2 +} {/t3 USING INDEX t3a/} + +#------------------------------------------------------------------------- +# Check that stat3 data is used correctly with non-default collation +# sequences. +# +foreach {tn schema} { + 1 { + CREATE TABLE t4(a COLLATE nocase, b); + CREATE INDEX t4a ON t4(a); + CREATE INDEX t4b ON t4(b); + } + 2 { + CREATE TABLE t4(a, b); + CREATE INDEX t4a ON t4(a COLLATE nocase); + CREATE INDEX t4b ON t4(b); + } +} { + drop_all_tables + do_test 11.$tn.1 { execsql $schema } {} + + do_test 11.$tn.2 { + for {set i 0} {$i < 100} {incr i} { + if { ($i % 10)==0 } { set a ABC } else { set a DEF } + set b [expr $i % 5] + execsql { INSERT INTO t4 VALUES($a, $b) } + } + execsql ANALYZE + } {} + + do_eqp_test 11.$tn.3 { + SELECT * FROM t4 WHERE a = 'def' AND b = 3; + } {/t4 USING INDEX t4b/} + + if {$tn==1} { + set sql "SELECT * FROM t4 WHERE a = 'abc' AND b = 3;" + do_eqp_test 11.$tn.4 $sql {/t4 USING INDEX t4a/} + } else { + + set sql "SELECT * FROM t4 WHERE a = 'abc' COLLATE nocase AND b = 3;" + do_eqp_test 11.$tn.5 $sql {/t4 USING INDEX t4a/} + + set sql "SELECT * FROM t4 WHERE a COLLATE nocase = 'abc' AND b = 3;" + do_eqp_test 11.$tn.6 $sql {/t4 USING INDEX t4a/} + } +} + +#------------------------------------------------------------------------- +# Test that nothing untoward happens if the stat3 table contains entries +# for indexes that do not exist. Or NULL values in the idx column. +# Or NULL values in any of the other columns. +# +drop_all_tables +do_execsql_test 15.1 { + CREATE TABLE x1(a, b, UNIQUE(a, b)); + INSERT INTO x1 VALUES(1, 2); + INSERT INTO x1 VALUES(3, 4); + INSERT INTO x1 VALUES(5, 6); + ANALYZE; + INSERT INTO sqlite_stat3 VALUES(NULL, NULL, NULL, NULL, NULL, NULL); +} +db close +sqlite3 db test.db +do_execsql_test 15.2 { SELECT * FROM x1 } {1 2 3 4 5 6} + +do_execsql_test 15.3 { + INSERT INTO sqlite_stat3 VALUES(42, 42, 42, 42, 42, 42); +} +db close +sqlite3 db test.db +do_execsql_test 15.4 { SELECT * FROM x1 } {1 2 3 4 5 6} + +do_execsql_test 15.5 { + UPDATE sqlite_stat1 SET stat = NULL; +} +db close +sqlite3 db test.db +do_execsql_test 15.6 { SELECT * FROM x1 } {1 2 3 4 5 6} + +do_execsql_test 15.7 { + ANALYZE; + UPDATE sqlite_stat1 SET tbl = 'no such tbl'; +} +db close +sqlite3 db test.db +do_execsql_test 15.8 { SELECT * FROM x1 } {1 2 3 4 5 6} + +do_execsql_test 15.9 { + ANALYZE; + UPDATE sqlite_stat3 SET neq = NULL, nlt=NULL, ndlt=NULL; +} +db close +sqlite3 db test.db +do_execsql_test 15.10 { SELECT * FROM x1 } {1 2 3 4 5 6} + +# This is just for coverage.... +do_execsql_test 15.11 { + ANALYZE; + UPDATE sqlite_stat1 SET stat = stat || ' unordered'; +} +db close +sqlite3 db test.db +do_execsql_test 15.12 { SELECT * FROM x1 } {1 2 3 4 5 6} + +#------------------------------------------------------------------------- +# Test that allocations used for sqlite_stat3 samples are included in +# the quantity returned by SQLITE_DBSTATUS_SCHEMA_USED. +# +set one [string repeat x 1000] +set two [string repeat x 2000] +do_test 16.1 { + reset_db + execsql { + CREATE TABLE t1(a, UNIQUE(a)); + INSERT INTO t1 VALUES($one); + ANALYZE; + } + set nByte [lindex [sqlite3_db_status db SCHEMA_USED 0] 1] + + reset_db + execsql { + CREATE TABLE t1(a, UNIQUE(a)); + INSERT INTO t1 VALUES($two); + ANALYZE; + } + set nByte2 [lindex [sqlite3_db_status db SCHEMA_USED 0] 1] + + expr {$nByte2 > $nByte+950 && $nByte2 < $nByte+1050} +} {1} + +#------------------------------------------------------------------------- +# Test that stat3 data may be used with partial indexes. +# +do_test 17.1 { + reset_db + execsql { + CREATE TABLE t1(a, b, c, d); + CREATE INDEX i1 ON t1(a, b) WHERE d IS NOT NULL; + INSERT INTO t1 VALUES(-1, -1, -1, NULL); + INSERT INTO t1 SELECT 2*a,2*b,2*c,d FROM t1; + INSERT INTO t1 SELECT 2*a,2*b,2*c,d FROM t1; + INSERT INTO t1 SELECT 2*a,2*b,2*c,d FROM t1; + INSERT INTO t1 SELECT 2*a,2*b,2*c,d FROM t1; + INSERT INTO t1 SELECT 2*a,2*b,2*c,d FROM t1; + INSERT INTO t1 SELECT 2*a,2*b,2*c,d FROM t1; + } + + for {set i 0} {$i < 32} {incr i} { + execsql { INSERT INTO t1 VALUES($i%2, $b, $i/2, 'abc') } + } + execsql {ANALYZE main.t1} +} {} + +do_catchsql_test 17.1.2 { + ANALYZE temp.t1; +} {1 {no such table: temp.t1}} + +do_eqp_test 17.2 { + SELECT * FROM t1 WHERE d IS NOT NULL AND a=0; +} {/USING INDEX i1/} +do_eqp_test 17.3 { + SELECT * FROM t1 WHERE d IS NOT NULL AND a=0; +} {/USING INDEX i1/} + +do_execsql_test 17.4 { + CREATE INDEX i2 ON t1(c) WHERE d IS NOT NULL; + ANALYZE main.i2; +} +do_eqp_test 17.5 { + SELECT * FROM t1 WHERE d IS NOT NULL AND a=0; +} {/USING INDEX i1/} +do_eqp_test 17.6 { + SELECT * FROM t1 WHERE d IS NOT NULL AND a=0 AND b=0 AND c=10; +} {/USING INDEX i2/} + +#------------------------------------------------------------------------- +# +do_test 18.1 { + reset_db + execsql { + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a, b); + } + for {set i 0} {$i < 9} {incr i} { + execsql { + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + INSERT INTO t1 VALUES($i, 0); + } + } + execsql ANALYZE + execsql { SELECT count(*) FROM sqlite_stat3 } +} {9} + +#------------------------------------------------------------------------- +# For coverage. +# +ifcapable view { + do_test 19.1 { + reset_db + execsql { + CREATE TABLE t1(x, y); + CREATE INDEX i1 ON t1(x, y); + CREATE VIEW v1 AS SELECT * FROM t1; + ANALYZE; + } + } {} +} +ifcapable auth { + proc authproc {op args} { + if {$op == "SQLITE_ANALYZE"} { return "SQLITE_DENY" } + return "SQLITE_OK" + } + do_test 19.2 { + reset_db + db auth authproc + execsql { + CREATE TABLE t1(x, y); + CREATE VIEW v1 AS SELECT * FROM t1; + } + catchsql ANALYZE + } {1 {not authorized}} +} + +#------------------------------------------------------------------------- +# +reset_db +proc r {args} { expr rand() } +db func r r +db func lrange lrange +do_test 20.1 { + execsql { + CREATE TABLE t1(a,b,c,d); + CREATE INDEX i1 ON t1(a,b,c,d); + } + for {set i 0} {$i < 16} {incr i} { + execsql { + INSERT INTO t1 VALUES($i, r(), r(), r()); + INSERT INTO t1 VALUES($i, $i, r(), r()); + INSERT INTO t1 VALUES($i, $i, $i, r()); + INSERT INTO t1 VALUES($i, $i, $i, $i); + INSERT INTO t1 VALUES($i, $i, $i, $i); + INSERT INTO t1 VALUES($i, $i, $i, r()); + INSERT INTO t1 VALUES($i, $i, r(), r()); + INSERT INTO t1 VALUES($i, r(), r(), r()); + } + } +} {} +do_execsql_test 20.2 { ANALYZE } +for {set i 0} {$i<16} {incr i} { + set val $i + do_execsql_test 20.3.$i { + SELECT count(*) FROM sqlite_stat3 WHERE sample=$val + } {1} +} + +finish_test + diff --git a/test/fkey7.test b/test/fkey7.test new file mode 100644 index 0000000000..c2682edbe5 --- /dev/null +++ b/test/fkey7.test @@ -0,0 +1,54 @@ +# 2001 September 15 +# +# 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. +# +# This file implements tests for foreign keys. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix fkey7 + +ifcapable {!foreignkey} { + finish_test + return +} + +do_execsql_test 1.1 { + PRAGMA foreign_keys = 1; + + CREATE TABLE s1(a PRIMARY KEY, b); + CREATE TABLE par(a, b REFERENCES s1, c UNIQUE, PRIMARY KEY(a)); + + CREATE TABLE c1(a, b REFERENCES par); + CREATE TABLE c2(a, b REFERENCES par); + CREATE TABLE c3(a, b REFERENCES par(c)); +} + +proc auth {op tbl args} { + if {$op == "SQLITE_READ"} { set ::tbls($tbl) 1 } + return "SQLITE_OK" +} +db auth auth +db cache size 0 +proc do_tblsread_test {tn sql tbllist} { + array unset ::tbls + uplevel [list execsql $sql] + uplevel [list do_test $tn {lsort [array names ::tbls]} $tbllist] +} + +do_tblsread_test 1.2 { UPDATE par SET b=? WHERE a=? } {par s1} +do_tblsread_test 1.3 { UPDATE par SET a=? WHERE b=? } {c1 c2 par} +do_tblsread_test 1.4 { UPDATE par SET c=? WHERE b=? } {c3 par} +do_tblsread_test 1.5 { UPDATE par SET a=?,b=?,c=? WHERE b=? } {c1 c2 c3 par s1} + + +finish_test diff --git a/test/mallocA.test b/test/mallocA.test index 1e05723e13..61e88a61e7 100644 --- a/test/mallocA.test +++ b/test/mallocA.test @@ -97,6 +97,24 @@ do_faultsim_test 6.2 -faults oom* -body { } -test { faultsim_test_result [list 0 {1 2}] } +ifcapable stat3 { + do_test 6.3-prep { + execsql { + PRAGMA writable_schema = 1; + CREATE TABLE sqlite_stat4 AS + SELECT tbl, idx, neq, nlt, ndlt, sqlite_record(sample) AS sample + FROM sqlite_stat3; + } + } {} + do_faultsim_test 6.3 -faults oom* -body { + execsql { + ANALYZE sqlite_master; + SELECT rowid FROM t1 WHERE a='abc' AND b<'y'; + } + } -test { + faultsim_test_result [list 0 {1 2}] + } +} # Ensure that no file descriptors were leaked. do_test malloc-99.X { diff --git a/test/permutations.test b/test/permutations.test index 755565f1d7..7468b89673 100644 --- a/test/permutations.test +++ b/test/permutations.test @@ -311,6 +311,14 @@ test_suite "coverage-pager" -description { walfault.test walbak.test journal2.test tkt-9d68c883.test } +test_suite "coverage-analyze" -description { + Coverage tests for file analyze.c. +} -files { + analyze3.test analyze4.test analyze5.test analyze6.test + analyze7.test analyze8.test analyze9.test analyzeA.test + analyze.test analyzeB.test mallocA.test +} + lappend ::testsuitelist xxx #------------------------------------------------------------------------- @@ -501,7 +509,7 @@ test_suite "utf16" -description { } -files { alter.test alter3.test analyze.test analyze3.test analyze4.test analyze5.test analyze6.test - analyze7.test analyze8.test analyze9.test analyzeA.test + analyze7.test analyze8.test analyze9.test analyzeA.test analyzeB.test auth.test bind.test blob.test capi2.test capi3.test collate1.test collate2.test collate3.test collate4.test collate5.test collate6.test conflict.test date.test delete.test expr.test fkey1.test func.test diff --git a/test/shell1.test b/test/shell1.test index 25fb330de0..562f10e65a 100644 --- a/test/shell1.test +++ b/test/shell1.test @@ -722,7 +722,11 @@ do_test shell1-3-31.1 { # Test the output of the ".dump" command # do_test shell1-4.1 { + db close + forcedelete test.db + sqlite3 db test.db db eval { + PRAGMA encoding=UTF16; CREATE TABLE t1(x); INSERT INTO t1 VALUES(null), (''), (1), (2.25), ('hello'), (x'807f'); } @@ -752,6 +756,14 @@ INSERT INTO t1 VALUES(X'807f');}} # Test the output of ".mode tcl" # do_test shell1-4.3 { + db close + forcedelete test.db + sqlite3 db test.db + db eval { + PRAGMA encoding=UTF8; + CREATE TABLE t1(x); + INSERT INTO t1 VALUES(null), (''), (1), (2.25), ('hello'), (x'807f'); + } catchcmd test.db ".mode tcl\nselect * from t1;" } {0 {"" "" diff --git a/test/where.test b/test/where.test index 42170ceac7..09ad0bce6d 100644 --- a/test/where.test +++ b/test/where.test @@ -1304,4 +1304,33 @@ do_test where-17.5 { } } {42 1 43 1} +# Ticket [be84e357c035d068135f20bcfe82761bbf95006b] 2013-09-03 +# Segfault during query involving LEFT JOIN column in the ORDER BY clause. +# +do_execsql_test where-18.1 { + CREATE TABLE t181(a); + CREATE TABLE t182(b,c); + INSERT INTO t181 VALUES(1); + SELECT DISTINCT a FROM t181 LEFT JOIN t182 ON a=b ORDER BY c IS NULL; +} {1} +do_execsql_test where-18.2 { + SELECT DISTINCT a FROM t181 LEFT JOIN t182 ON a=b ORDER BY +c; +} {1} +do_execsql_test where-18.3 { + SELECT DISTINCT a FROM t181 LEFT JOIN t182 ON a=b ORDER BY c; +} {1} +do_execsql_test where-18.4 { + INSERT INTO t181 VALUES(1),(1),(1),(1); + SELECT DISTINCT a FROM t181 LEFT JOIN t182 ON a=b ORDER BY +c; +} {1} +do_execsql_test where-18.5 { + INSERT INTO t181 VALUES(2); + SELECT DISTINCT a FROM t181 LEFT JOIN t182 ON a=b ORDER BY c IS NULL, +a; +} {1 2} +do_execsql_test where-18.6 { + INSERT INTO t181 VALUES(2); + SELECT DISTINCT a FROM t181 LEFT JOIN t182 ON a=b ORDER BY +a, +c IS NULL; +} {1 2} + + finish_test