Add support for row-value comparisons, including IN operators, and

row-value updates in the UPDATE statement.

FossilOrigin-Name: ddb5f0558c44569913d22781ab78f3e9b58d7aea
This commit is contained in:
drh 2016-09-07 19:54:24 +00:00
commit f78dcd1b96
32 changed files with 3647 additions and 604 deletions

View File

@ -1,5 +1,5 @@
C Add\sthe\sext/misc/memvfs.c\sextension\sthat\simplements\sa\sVFS\sfor\sread-only\ndatabase\sfiles\scontained\sin\smemory.
D 2016-09-07T18:11:11.252
C Add\ssupport\sfor\srow-value\scomparisons,\sincluding\sIN\soperators,\sand\nrow-value\supdates\sin\sthe\sUPDATE\sstatement.
D 2016-09-07T19:54:24.437
F Makefile.in cfd8fb987cd7a6af046daa87daa146d5aad0e088
F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
F Makefile.msc 5017381e4853b1472e01d5bb926be1268eba429c
@ -339,7 +339,7 @@ F src/ctime.c e77f3dc297b4b65c96da78b4ae4272fdfae863d7
F src/date.c 95c9a8d00767e7221a8e9a31f4e913fc8029bf6b
F src/dbstat.c 19ee7a4e89979d4df8e44cfac7a8f905ec89b77d
F src/delete.c 76c084f0265f4a3cd1ecf17eee112a94f1ccbc05
F src/expr.c 9c5eca8602f6c496e8d4eefefe2aae3d831dd510
F src/expr.c 028c34005cb804abe8f73453ac08baa44f4b63f9
F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
F src/fkey.c e2be0968c1adc679c87e467aa5b4f167588f38a8
F src/func.c 29cc9acb170ec1387b9f63eb52cd85f8de96c771
@ -347,6 +347,7 @@ F src/global.c c45ea22aff29334f6a9ec549235ac3357c970015
F src/hash.c 55b5fb474100cee0b901edaf203e26c970940f36
F src/hash.h ab34c5c54a9e9de2e790b24349ba5aab3dbb4fd4
F src/hwtime.h 747c1bbe9df21a92e9c50f3bbec1de841dc5e5da
F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71
F src/insert.c 3edb5a1bda44df13531fedfcde5fbcc2fc04c222
F src/legacy.c 75d3023be8f0d2b99d60f905090341a03358c58e
F src/loadext.c dd7a2b77902cc66c22555aef02e1a682554b7aec
@ -374,7 +375,7 @@ F src/os_win.c 520f23475f1de530c435d30b67b7b15fe90874b0
F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
F src/pager.c bf5b71bde3e9b6110e7d6990607db881f6a471a2
F src/pager.h 966d2769e76ae347c8a32c4165faf6e6cb64546d
F src/parse.y ed6990c2d41eb0302eda90d5009c51fec792c850
F src/parse.y 0e0b6d46a990d01e4ca1e9d7e1d2d9b5a98f6bcb
F src/pcache.c 5583c8ade4b05075a60ba953ef471d1c1a9c05df
F src/pcache.h 2cedcd8407eb23017d92790b112186886e179490
F src/pcache1.c 4bb7a6a5300c67d0b033d25adb509c120c03e812
@ -383,14 +384,14 @@ F src/pragma.h 64c78a648751b9f4f297276c4eb7507b14b4628c
F src/prepare.c 0fcf16eaacc90c1059055519a76b75b516a59a88
F src/printf.c a5f0ca08ddede803c241266abb46356ec748ded1
F src/random.c ba2679f80ec82c4190062d756f22d0c358180696
F src/resolve.c d67b9a5cc33339256e2088c5a722745fc2ff5219
F src/resolve.c 24f40fd0c3475821d1ad762a3f2c3455cc839b42
F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac
F src/select.c fda7fd24b4d8e75479ae581329db0a0b0bf76633
F src/select.c 244f9cc5e4662987cd2ef5c22d1b7027560f3425
F src/shell.c de7c7e98846cacbfbe062cbd98bca899dfb720e3
F src/sqlite.h.in 4a030e254e204570444b34bf7d40fb4a5416089e
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h 8648034aa702469afb553231677306cc6492a1ae
F src/sqliteInt.h 153099746007418dfa03a99c8355c8d47b0a1cc9
F src/sqliteInt.h c9e010a79ab4ed7bdc910a24d8f08f3c6d5f822c
F src/sqliteLimit.h c0373387c287c8d0932510b5547ecde31b5da247
F src/status.c a9e66593dfb28a9e746cba7153f84d49c1ddc4b1
F src/table.c 5226df15ab9179b9ed558d89575ea0ce37b03fc9
@ -445,19 +446,19 @@ F src/test_windirent.h 7edc57e2faa727026dbd5d010dd0e2e665d5aa01
F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9
F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c
F src/tokenize.c 78c8085bc7af1922aa687f0f4bbd716821330de5
F src/treeview.c 70329ef46b86f94b16a98aacced793e0692217b5
F src/treeview.c f51b75a28b377adde9f79bc3deb6c7770bcf97c0
F src/trigger.c 11e20b3b12c847b3b9055594c0f1631266bb53fc
F src/update.c 8179e699dbd45b92934fd02d3d8e3732e8da8802
F src/utf.c 699001c79f28e48e9bcdf8a463da029ea660540c
F src/util.c 810ec3f22e2d1b62e66c30fe3621ebdedd23584d
F src/vacuum.c 913970b9d86dd6c2b8063ef1af421880f1464ec3
F src/vdbe.c 751dd0a177615388f95123d723ce82395b938ce9
F src/vdbe.c 3148d5d47816c5ad2ed3c62beb3086cbbcaab107
F src/vdbe.h 67bc551f7faf04c33493892e4b378aada823ed10
F src/vdbeInt.h c59381049af5c7751a83456c39b80d1a6fde1f9d
F src/vdbeapi.c a32d61b7dd05e6890d8fd44d2805f55e2f5ba9f3
F src/vdbeaux.c 83458783d241cfd6691141c8a105832ee50258e5
F src/vdbeblob.c 3e82a797b60c3b9fed7b8de8c539ca7607874937
F src/vdbemem.c 1ecaa5ee0caff07255f25d04e8dc88befb6f88d1
F src/vdbemem.c e67dc6d8177fd1830efb5d15e17793408251a187
F src/vdbesort.c 91fda3909326860382b0ca8aa251e609c6a9d62c
F src/vdbetrace.c 41963d5376f0349842b5fc4aaaaacd7d9cdc0834
F src/vtab.c e02cacb5c7ae742631edeb9ae9f53d399f093fd8
@ -465,10 +466,10 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
F src/wal.c 02eeecc265f6ffd0597378f5d8ae9070b62a406a
F src/wal.h 6dd221ed384afdc204bc61e25c23ef7fd5a511f2
F src/walker.c 2d2cc7fb0f320f7f415215d7247f3c584141ac09
F src/where.c 4bbc3f1dcda64c96ed5f0ffe51be7c0d7adb6c62
F src/whereInt.h e5b939701a7ceffc5a3a8188a37f9746416ebcd0
F src/wherecode.c 99707d11907c71d289ee9553d2d1a22f1fd8ba41
F src/whereexpr.c 98ce9f3b8d92b6f741b9f3498f9619695739256e
F src/where.c edbd73a87ba2e186928e9bfc14348b1bbb2628c5
F src/whereInt.h 14dd243e13b81cbb0a66063d38b70f93a7d6e613
F src/wherecode.c d172dcf99932ba698dd304edc9a368cd52b4b2e5
F src/whereexpr.c e3db778ed205e982f31960896db71c50612ae009
F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
F test/affinity2.test a6d901b436328bd67a79b41bb0ac2663918fe3bd
F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
@ -645,7 +646,7 @@ F test/e_createtable.test d4c6059d44dcd4b636de9aae322766062b471844
F test/e_delete.test ab39084f26ae1f033c940b70ebdbbd523dc4962e
F test/e_droptrigger.test 3cd080807622c13e5bbb61fc9a57bd7754da2412
F test/e_dropview.test 0c9f7f60989164a70a67a9d9c26d1083bc808306
F test/e_expr.test 03a84a6fa9bd3472112d6bd4599f5269f5f74803
F test/e_expr.test 1ffa8866d38e7becc76893a8829e9432050e5716
F test/e_fkey.test a1783fe1f759e1990e6a11adfcf0702dac4d0707
F test/e_fts3.test 5c02288842e4f941896fd44afdef564dd5fc1459
F test/e_insert.test 3de217e95094d3d165992a6de1164bbc4bd92dc7
@ -824,7 +825,7 @@ F test/hook.test 3b7b99d0eece6d279812c2aef6fa08bdfabc633e
F test/icu.test 73956798bace8982909c00476b216714a6d0559a
F test/ieee754.test 806fc0ce7f305f57e3331eaceeddcfec9339e607
F test/imposter1.test c3f1db2d3db2c24611a6596a3fc0ffc14f1466c8
F test/in.test 61d940ced6817bee66e4e0b09d5bc8608f57134b
F test/in.test 20c5529986998949908f889c8208b2cd894b2cc9
F test/in2.test 5d4c61d17493c832f7d2d32bef785119e87bde75
F test/in3.test 3cbf58c87f4052cee3a58b37b6389777505aa0c0
F test/in4.test d2b38cba404bc4320f4fe1b595b3d163f212c068
@ -1020,6 +1021,16 @@ F test/rollbackfault.test 0e646aeab8840c399cfbfa43daab46fd609cf04a
F test/rowallock.test 3f88ec6819489d0b2341c7a7528ae17c053ab7cc
F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81
F test/rowid.test 5b7509f384f4f6fae1af3c8c104c8ca299fea18d
F test/rowvalue.test 753eb744b7efeb5ac643d35d6e1e5066452ccf79
F test/rowvalue2.test 060d238b7e5639a7c5630cb5e63e311b44efef2b
F test/rowvalue3.test 01399b7bf150b0d41abce76c18072da777c2500c
F test/rowvalue4.test 4b556d7de161a0dd8cff095c336e913986398bea
F test/rowvalue5.test c1adfb2ea104e181f70d55bbd80d803b9917b22b
F test/rowvalue6.test d19b54feb604d5601f8614b15e214e0774c01087
F test/rowvalue7.test 5d06ff19d9e6969e574a2e662a531dd0c67801a8
F test/rowvalue8.test 5900eddad9e2c3c2e26f1a95f74aafc1232ee5e0
F test/rowvalue9.test e24f9eb02baffc6a67b6eed9e40d4c612c98079d
F test/rowvaluefault.test 7b16485e3f2b371f3e3d05455b8ded6d0c090244
F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798
F test/run-wordcount.sh 891e89c4c2d16e629cd45951d4ed899ad12afc09
F test/savepoint.test c671fdbd34cd3bfe1518a777526ada595180cf8d
@ -1043,7 +1054,7 @@ F test/select3.test 2ce595f8fb8e2ac10071d3b4e424cadd4634a054
F test/select4.test 5389d9895968d1196c457d59b3ee6515d771d328
F test/select5.test e758b8ef94f69b111df4cb819008856655dcd535
F test/select6.test 39eac4a5c03650b2b473c532882273283ee8b7a0
F test/select7.test 95e370c42d47c3c52377d05e9ffc01ccff7c1f61
F test/select7.test f659f231489349e8c5734e610803d7654207318f
F test/select8.test 8c8f5ae43894c891efc5755ed905467d1d67ad5d
F test/select9.test aebc2bb0c3bc44606125033cbcaac2c8d1f33a95
F test/selectA.test 101e722370ac6e84978c2958b8931c78b10a1709
@ -1111,7 +1122,7 @@ F test/statfault.test f525a7bf633e50afd027700e9a486090684b1ac1
F test/stmt.test 64844332db69cf1a735fcb3e11548557fc95392f
F test/subquery.test d7268d193dd33d5505df965399d3a594e76ae13f
F test/subquery2.test 438f8a7da1457277b22e4176510f7659b286995f
F test/subselect.test d24fd8757daf97dafd2e889c73ea4c4272dcf4e4
F test/subselect.test 0966aa8e720224dbd6a5e769a3ec2a723e332303
F test/substr.test 18f57c4ca8a598805c4d64e304c418734d843c1a
F test/subtype1.test 7fe09496352f97053af1437150751be2d0a0cae8
F test/superlock.test ec94f0556b6488d97f71c79f9061ae08d9ab8f12
@ -1131,7 +1142,7 @@ F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30
F test/temptable2.test cd396beb41117a5302fff61767c35fa4270a0d5e
F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637
F test/temptrigger.test 8ec228b0db5d7ebc4ee9b458fc28cb9e7873f5e1
F test/tester.tcl 949b4a73bd0324b7c796818d0d6a6715712932b3
F test/tester.tcl 4ce5afd5e192db4cae178e1a983b060e0f08c5d6
F test/thread001.test 9f22fd3525a307ff42a326b6bc7b0465be1745a5
F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58
F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7
@ -1314,7 +1325,7 @@ F test/tt3_lookaside1.c 0377e202c3c2a50d688cb65ba203afeda6fafeb9
F test/tt3_stress.c c57d804716165811d979d4a719e05baccd79277f
F test/tt3_vacuum.c 1753f45917699c9c1f66b64c717a717c9379f776
F test/types.test bf816ce73c7dfcfe26b700c19f97ef4050d194ff
F test/types2.test 3555aacf8ed8dc883356e59efc314707e6247a84
F test/types2.test 1aeb81976841a91eef292723649b5c4fe3bc3cac
F test/types3.test 99e009491a54f4dc02c06bdbc0c5eea56ae3e25a
F test/unique.test 93f8b2ef5ea51b9495f8d6493429b1fd0f465264
F test/unique2.test 3674e9f2a3f1fbbfd4772ac74b7a97090d0f77d2
@ -1426,7 +1437,7 @@ F test/zerodamage.test e59a56443d6298ecf7435f618f0b27654f0c849e
F tool/GetFile.cs a15e08acb5dd7539b75ba23501581d7c2b462cb5
F tool/GetTclKit.bat 629d87562e0487c386db630033931d12d62e6372
F tool/Replace.cs 02c67258801c2fb5f63231e0ac0f220b4b36ba91
F tool/addopcodes.tcl 2b089684eb8b7d0db64cf9d8e6d2fe1b6d279e8d
F tool/addopcodes.tcl 10c889c4a65ec6c5604e4a47306fa77ff57ae189
F tool/build-all-msvc.bat 3e4e4043b53f1aede4308e0d2567bbd773614630 x
F tool/build-shell.sh 950f47c6174f1eea171319438b93ba67ff5bf367
F tool/cg_anno.tcl 692ce4b8693d59e3a3de77ca97f4139ecfa641b0 x
@ -1512,7 +1523,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P d8451fe84d09db6ec7e1bd5f0708ea1b5e85f3d6
R 40828d7c09ecd4e3a6b5df17d516ba22
P 12b7782a9af91eab913e159149cb28b3f5a6557c 7f2c5c9ee3628c968306a5ab2e5a9a761f1b8055
R 751b4792e244c0de84e03a1b48f5b06d
T +closed 7f2c5c9ee3628c968306a5ab2e5a9a761f1b8055
U drh
Z f7bf016242cf820d90b7abae39af230e
Z 8852ee27cbc1c9d45a2ebb8e532166b1

View File

@ -1 +1 @@
12b7782a9af91eab913e159149cb28b3f5a6557c
ddb5f0558c44569913d22781ab78f3e9b58d7aea

1217
src/expr.c

File diff suppressed because it is too large Load Diff

107
src/in-operator.md Normal file
View File

@ -0,0 +1,107 @@
IN-Operator Implementation Notes
================================
## Definitions:
An IN operator has one of the following formats:
>
x IN (y1,y2,y3,...,yN)
x IN (subquery)
The "x" is referred to as the LHS (left-hand side). The list or subquery
on the right is called the RHS (right-hand side). If the RHS is a list
it must be a non-empty list. But if the RHS is a subquery, it can be an
empty set.
The LHS can be a scalar (a single quantity) or a vector (a list of
two or or more values) or a subquery that returns one or more columns.
We use the term "vector" to mean an actually list of values or a
subquery that returns two or more columns. An isolated value or
a subquery that returns a single columns is called a scalar.
The RHS can be a subquery that returns a single column, a subquery
that returns two or more columns, or a list of scalars. It is not
currently support for the RHS to be a list of vectors.
The number of columns for LHS must match the number of columns for
the RHS. If the RHS is a list of values, then the LHS must be a
scalar. If the RHS is a subquery returning N columns, then the LHS
must be a vector of size N.
NULL values can occur in either or both of the LHS and RHS.
If the LHS contains only
NULL values then we say that it is a "total-NULL". If the LHS contains
some NULL values and some non-NULL values, then it is a "partial-NULL".
For a scalar, there is no difference between a partial-NULL and a total-NULL.
The RHS is a partial-NULL if any row contains a NULL value. The RHS is
a total-NULL if it contains one or more rows that contain only NULL values.
The LHS is called "non-NULL" if it contains no NULL values. The RHS is
called "non-NULL" if it contains no NULL values in any row.
The result of an IN operator is one of TRUE, FALSE, or NULL. A NULL result
means that it cannot be determined if the LHS is contained in the RHS due
to the presence of NULL values. In some contexts (for example, when the IN
operator occurs in a WHERE clause)
the system only needs a binary result: TRUE or NOT-TRUE. One can also
to define a binary result of FALSE and NOT-FALSE, but
it turns out that no extra optimizations are possible in that case, so if
the FALSE/NOT-FALSE binary is needed, we have to compute the three-state
TRUE/FALSE/NULL result and then combine the TRUE and NULL values into
NOT-FALSE.
A "NOT IN" operator is computed by first computing the equivalent IN
operator, then interchanging the TRUE and FALSE results.
## Simple Full-Scan Algorithm
The following algorithm always compute the correct answer. However, this
algorithm is suboptimal, especially if there are many rows on the RHS.
1. Set the null-flag to false
2. For each row in the RHS:
<ol type='a'>
<li> Compare the LHS against the RHS
<li> If the LHS exactly matches the RHS, immediately return TRUE
<li> If the comparison result is NULL, set the null-flag to true
</ol>
3. If the null-flag is true, return NULL.
4. Return FALSE
## Optimized Algorithm
The following procedure computes the same answer as the simple full-scan
algorithm, though it does so with less work in the common case. This
is the algorithm that is implemented in SQLite.
1. If the RHS is a constant list of length 1 or 2, then rewrite the
IN operator as a simple expression. Implement
x IN (y1,y2)
as if it were
x=y1 OR x=y2
This is the INDEX_NOOP optimization and is only undertaken if the
IN operator is used for membership testing. If the IN operator is
driving a loop, then skip this step entirely.
2. Check the LHS to see if it is a partial-NULL and if it is, jump
ahead to step 5.
3. Do a binary search of the RHS using the LHS as a probe. If
an exact match is found, return TRUE.
4. If the RHS is non-NULL then return FALSE.
5. If we do not need to distinguish between FALSE and NULL,
then return FALSE.
6. For each row in the RHS, compare that row against the LHS and
if the result is NULL, immediately return NULL. In the case
of a scalar IN operator, we only need to look at the very first
row the RHS because for a scalar RHS, all NULLs will always come
first. If the RHS is empty, this step is a no-op.
7. Return FALSE.

View File

@ -789,10 +789,16 @@ setlist(A) ::= setlist(A) COMMA nm(X) EQ expr(Y). {
A = sqlite3ExprListAppend(pParse, A, Y.pExpr);
sqlite3ExprListSetName(pParse, A, &X, 1);
}
setlist(A) ::= setlist(A) COMMA LP idlist(X) RP EQ expr(Y). {
A = sqlite3ExprListAppendVector(pParse, A, X, Y.pExpr);
}
setlist(A) ::= nm(X) EQ expr(Y). {
A = sqlite3ExprListAppend(pParse, 0, Y.pExpr);
sqlite3ExprListSetName(pParse, A, &X, 1);
}
setlist(A) ::= LP idlist(X) RP EQ expr(Y). {
A = sqlite3ExprListAppendVector(pParse, 0, X, Y.pExpr);
}
////////////////////////// The INSERT command /////////////////////////////////
//
@ -946,6 +952,17 @@ term(A) ::= CTIME_KW(OP). {
}
}
expr(A) ::= LP(L) nexprlist(X) COMMA expr(Y) RP(R). {
ExprList *pList = sqlite3ExprListAppend(pParse, X, Y.pExpr);
A.pExpr = sqlite3PExpr(pParse, TK_VECTOR, 0, 0, 0);
if( A.pExpr ){
A.pExpr->x.pList = pList;
spanSet(&A, &L, &R);
}else{
sqlite3ExprListDelete(pParse->db, pList);
}
}
expr(A) ::= expr(A) AND(OP) expr(Y). {spanBinaryExpr(pParse,@OP,&A,&Y);}
expr(A) ::= expr(A) OR(OP) expr(Y). {spanBinaryExpr(pParse,@OP,&A,&Y);}
expr(A) ::= expr(A) LT|GT|GE|LE(OP) expr(Y).

View File

@ -776,6 +776,33 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
notValid(pParse, pNC, "parameters", NC_IsCheck|NC_PartIdx|NC_IdxExpr);
break;
}
case TK_EQ:
case TK_NE:
case TK_LT:
case TK_LE:
case TK_GT:
case TK_GE:
case TK_IS:
case TK_ISNOT: {
int nLeft, nRight;
if( pParse->db->mallocFailed ) break;
assert( pExpr->pRight!=0 );
assert( pExpr->pLeft!=0 );
nLeft = sqlite3ExprVectorSize(pExpr->pLeft);
nRight = sqlite3ExprVectorSize(pExpr->pRight);
if( nLeft!=nRight ){
testcase( pExpr->op==TK_EQ );
testcase( pExpr->op==TK_NE );
testcase( pExpr->op==TK_LT );
testcase( pExpr->op==TK_LE );
testcase( pExpr->op==TK_GT );
testcase( pExpr->op==TK_GE );
testcase( pExpr->op==TK_IS );
testcase( pExpr->op==TK_ISNOT );
sqlite3ErrorMsg(pParse, "row value misused");
}
break;
}
}
return (pParse->nErr || pParse->db->mallocFailed) ? WRC_Abort : WRC_Continue;
}

View File

@ -88,7 +88,7 @@ static void clearSelect(sqlite3 *db, Select *p, int bFree){
void sqlite3SelectDestInit(SelectDest *pDest, int eDest, int iParm){
pDest->eDest = (u8)eDest;
pDest->iSDParm = iParm;
pDest->affSdst = 0;
pDest->zAffSdst = 0;
pDest->iSdst = 0;
pDest->nSdst = 0;
}
@ -659,30 +659,6 @@ static void codeDistinct(
sqlite3ReleaseTempReg(pParse, r1);
}
#ifndef SQLITE_OMIT_SUBQUERY
/*
** Generate an error message when a SELECT is used within a subexpression
** (example: "a IN (SELECT * FROM table)") but it has more than 1 result
** column. We do this in a subroutine because the error used to occur
** in multiple places. (The error only occurs in one place now, but we
** retain the subroutine to minimize code disruption.)
*/
static int checkForMultiColumnSelectError(
Parse *pParse, /* Parse context. */
SelectDest *pDest, /* Destination of SELECT results */
int nExpr /* Number of result columns returned by SELECT */
){
int eDest = pDest->eDest;
if( nExpr>1 && (eDest==SRT_Mem || eDest==SRT_Set) ){
sqlite3ErrorMsg(pParse, "only a single result allowed for "
"a SELECT that is part of an expression");
return 1;
}else{
return 0;
}
}
#endif
/*
** This routine generates the code for the inside of the inner loop
** of a SELECT.
@ -892,19 +868,19 @@ static void selectInnerLoop(
** item into the set table with bogus data.
*/
case SRT_Set: {
assert( nResultCol==1 );
pDest->affSdst =
sqlite3CompareAffinity(pEList->a[0].pExpr, pDest->affSdst);
if( pSort ){
/* At first glance you would think we could optimize out the
** ORDER BY in this case since the order of entries in the set
** does not matter. But there might be a LIMIT clause, in which
** case the order does matter */
pushOntoSorter(pParse, pSort, p, regResult, regResult, 1, nPrefixReg);
pushOntoSorter(
pParse, pSort, p, regResult, regResult, nResultCol, nPrefixReg);
}else{
int r1 = sqlite3GetTempReg(pParse);
sqlite3VdbeAddOp4(v, OP_MakeRecord, regResult,1,r1, &pDest->affSdst, 1);
sqlite3ExprCacheAffinityChange(pParse, regResult, 1);
assert( sqlite3Strlen30(pDest->zAffSdst)==nResultCol );
sqlite3VdbeAddOp4(v, OP_MakeRecord, regResult, nResultCol,
r1, pDest->zAffSdst, nResultCol);
sqlite3ExprCacheAffinityChange(pParse, regResult, nResultCol);
sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1);
sqlite3ReleaseTempReg(pParse, r1);
}
@ -920,13 +896,14 @@ static void selectInnerLoop(
}
/* If this is a scalar select that is part of an expression, then
** store the results in the appropriate memory cell and break out
** of the scan loop.
** store the results in the appropriate memory cell or array of
** memory cells and break out of the scan loop.
*/
case SRT_Mem: {
assert( nResultCol==1 );
assert( nResultCol==pDest->nSdst );
if( pSort ){
pushOntoSorter(pParse, pSort, p, regResult, regResult, 1, nPrefixReg);
pushOntoSorter(
pParse, pSort, p, regResult, regResult, nResultCol, nPrefixReg);
}else{
assert( regResult==iParm );
/* The LIMIT clause will jump out of the loop for us */
@ -1241,14 +1218,14 @@ static void generateSortTail(
sqlite3VdbeResolveLabel(v, pSort->labelBkOut);
}
iTab = pSort->iECursor;
if( eDest==SRT_Output || eDest==SRT_Coroutine ){
if( eDest==SRT_Output || eDest==SRT_Coroutine || eDest==SRT_Mem ){
regRowid = 0;
regRow = pDest->iSdst;
nSortData = nColumn;
}else{
regRowid = sqlite3GetTempReg(pParse);
regRow = sqlite3GetTempReg(pParse);
nSortData = 1;
regRow = sqlite3GetTempRange(pParse, nColumn);
nSortData = nColumn;
}
nKey = pOrderBy->nExpr - pSort->nOBSat;
if( pSort->sortFlags & SORTFLAG_UseSorter ){
@ -1283,16 +1260,14 @@ static void generateSortTail(
}
#ifndef SQLITE_OMIT_SUBQUERY
case SRT_Set: {
assert( nColumn==1 );
sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, 1, regRowid,
&pDest->affSdst, 1);
sqlite3ExprCacheAffinityChange(pParse, regRow, 1);
assert( nColumn==sqlite3Strlen30(pDest->zAffSdst) );
sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, nColumn, regRowid,
pDest->zAffSdst, nColumn);
sqlite3ExprCacheAffinityChange(pParse, regRow, nColumn);
sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, regRowid);
break;
}
case SRT_Mem: {
assert( nColumn==1 );
sqlite3ExprCodeMove(pParse, regRow, iParm, 1);
/* The LIMIT clause will terminate the loop for us */
break;
}
@ -1311,7 +1286,11 @@ static void generateSortTail(
}
}
if( regRowid ){
sqlite3ReleaseTempReg(pParse, regRow);
if( eDest==SRT_Set ){
sqlite3ReleaseTempRange(pParse, regRow, nColumn);
}else{
sqlite3ReleaseTempReg(pParse, regRow);
}
sqlite3ReleaseTempReg(pParse, regRowid);
}
/* The bottom of the loop
@ -1813,7 +1792,7 @@ Table *sqlite3ResultSetOfSelect(Parse *pParse, Select *pSelect){
*/
static SQLITE_NOINLINE Vdbe *allocVdbe(Parse *pParse){
Vdbe *v = pParse->pVdbe = sqlite3VdbeCreate(pParse);
if( v ) sqlite3VdbeAddOp0(v, OP_Init);
if( v ) sqlite3VdbeAddOp2(v, OP_Init, 0, 1);
if( pParse->pToplevel==0
&& OptimizationEnabled(pParse->db,SQLITE_FactorOutConst)
){
@ -2652,18 +2631,15 @@ static int generateOutputSubroutine(
}
#ifndef SQLITE_OMIT_SUBQUERY
/* If we are creating a set for an "expr IN (SELECT ...)" construct,
** then there should be a single item on the stack. Write this
** item into the set table with bogus data.
/* If we are creating a set for an "expr IN (SELECT ...)".
*/
case SRT_Set: {
int r1;
assert( pIn->nSdst==1 || pParse->nErr>0 );
pDest->affSdst =
sqlite3CompareAffinity(p->pEList->a[0].pExpr, pDest->affSdst);
testcase( pIn->nSdst>1 );
r1 = sqlite3GetTempReg(pParse);
sqlite3VdbeAddOp4(v, OP_MakeRecord, pIn->iSdst, 1, r1, &pDest->affSdst,1);
sqlite3ExprCacheAffinityChange(pParse, pIn->iSdst, 1);
sqlite3VdbeAddOp4(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst,
r1, pDest->zAffSdst, pIn->nSdst);
sqlite3ExprCacheAffinityChange(pParse, pIn->iSdst, pIn->nSdst);
sqlite3VdbeAddOp2(v, OP_IdxInsert, pDest->iSDParm, r1);
sqlite3ReleaseTempReg(pParse, r1);
break;
@ -4897,16 +4873,6 @@ int sqlite3Select(
}
#endif
/* If writing to memory or generating a set
** only a single column may be output.
*/
#ifndef SQLITE_OMIT_SUBQUERY
if( checkForMultiColumnSelectError(pParse, pDest, p->pEList->nExpr) ){
goto select_end;
}
#endif
/* Try to flatten subqueries in the FROM clause up into the main query
*/
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)

View File

@ -1733,6 +1733,7 @@ struct CollSeq {
** operator is NULL. It is added to certain comparison operators to
** prove that the operands are always NOT NULL.
*/
#define SQLITE_KEEPNULL 0x08 /* Used by vector == or <> */
#define SQLITE_JUMPIFNULL 0x10 /* jumps if either operand is NULL */
#define SQLITE_STOREP2 0x20 /* Store result in reg[P2] rather than jump */
#define SQLITE_NULLEQ 0x80 /* NULL=NULL */
@ -2297,9 +2298,11 @@ struct Expr {
int iTable; /* TK_COLUMN: cursor number of table holding column
** TK_REGISTER: register number
** TK_TRIGGER: 1 -> new, 0 -> old
** EP_Unlikely: 134217728 times likelihood */
** EP_Unlikely: 134217728 times likelihood
** TK_SELECT: 1st register of result vector */
ynVar iColumn; /* TK_COLUMN: column index. -1 for rowid.
** TK_VARIABLE: variable number (always >= 1). */
** TK_VARIABLE: variable number (always >= 1).
** TK_SELECT_COLUMN: column of the result vector */
i16 iAgg; /* Which entry in pAggInfo->aCol[] or ->aFunc[] */
i16 iRightJoinTable; /* If EP_FromJoin, the right table of the join */
u8 op2; /* TK_REGISTER: original value of Expr.op
@ -2780,7 +2783,7 @@ struct Select {
*/
struct SelectDest {
u8 eDest; /* How to dispose of the results. On of SRT_* above. */
char affSdst; /* Affinity used when eDest==SRT_Set */
char *zAffSdst; /* Affinity used when eDest==SRT_Set */
int iSDParm; /* A parameter used by the eDest disposal method */
int iSdst; /* Base register where results are written */
int nSdst; /* Number of registers allocated */
@ -3512,6 +3515,7 @@ char *sqlite3VMPrintf(sqlite3*,const char*, va_list);
#if defined(SQLITE_DEBUG)
void sqlite3TreeViewExpr(TreeView*, const Expr*, u8);
void sqlite3TreeViewBareExprList(TreeView*, const ExprList*, const char*);
void sqlite3TreeViewExprList(TreeView*, const ExprList*, u8, const char*);
void sqlite3TreeViewSelect(TreeView*, const Select*, u8);
void sqlite3TreeViewWith(TreeView*, const With*, u8);
@ -3543,6 +3547,7 @@ Expr *sqlite3ExprFunction(Parse*,ExprList*, Token*);
void sqlite3ExprAssignVarNumber(Parse*, Expr*);
void sqlite3ExprDelete(sqlite3*, Expr*);
ExprList *sqlite3ExprListAppend(Parse*,ExprList*,Expr*);
ExprList *sqlite3ExprListAppendVector(Parse*,ExprList*,IdList*,Expr*);
void sqlite3ExprListSetSortOrder(ExprList*,int);
void sqlite3ExprListSetName(Parse*,ExprList*,Token*,int);
void sqlite3ExprListSetSpan(Parse*,ExprList*,ExprSpan*);
@ -3878,6 +3883,7 @@ const char *sqlite3IndexAffinityStr(sqlite3*, Index*);
void sqlite3TableAffinity(Vdbe*, Table*, int);
char sqlite3CompareAffinity(Expr *pExpr, char aff2);
int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity);
char sqlite3TableColumnAffinity(Table*,int);
char sqlite3ExprAffinity(Expr *pExpr);
int sqlite3Atoi64(const char*, i64*, int, u8);
int sqlite3DecOrHexToI64(const char*, i64*);
@ -3943,7 +3949,7 @@ void sqlite3AlterRenameTable(Parse*, SrcList*, Token*);
int sqlite3GetToken(const unsigned char *, int *);
void sqlite3NestedParse(Parse*, const char*, ...);
void sqlite3ExpirePreparedStatements(sqlite3*);
int sqlite3CodeSubselect(Parse *, Expr *, int, int);
int sqlite3CodeSubselect(Parse*, Expr *, int, int);
void sqlite3SelectPrep(Parse*, Select*, NameContext*);
void sqlite3SelectWrongNumTermsError(Parse *pParse, Select *p);
int sqlite3MatchSpanName(const char*, const char*, const char*, const char*);
@ -3998,12 +4004,20 @@ Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int);
void sqlite3BackupRestart(sqlite3_backup *);
void sqlite3BackupUpdate(sqlite3_backup *, Pgno, const u8 *);
#ifndef SQLITE_OMIT_SUBQUERY
int sqlite3ExprCheckIN(Parse*, Expr*);
#else
# define sqlite3ExprCheckIN(x,y) SQLITE_OK
#endif
#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
void sqlite3AnalyzeFunctions(void);
int sqlite3Stat4ProbeSetValue(Parse*,Index*,UnpackedRecord**,Expr*,u8,int,int*);
int sqlite3Stat4ProbeSetValue(
Parse*,Index*,UnpackedRecord**,Expr*,int,int,int*);
int sqlite3Stat4ValueFromExpr(Parse*, Expr*, u8, sqlite3_value**);
void sqlite3Stat4ProbeFree(UnpackedRecord*);
int sqlite3Stat4Column(sqlite3*, const void*, int, int, sqlite3_value**);
char sqlite3IndexColumnAffinity(sqlite3*, Index*, int);
#endif
/*
@ -4156,7 +4170,7 @@ const char *sqlite3JournalModename(int);
#define IN_INDEX_NOOP_OK 0x0001 /* OK to return IN_INDEX_NOOP */
#define IN_INDEX_MEMBERSHIP 0x0002 /* IN operator used for membership test */
#define IN_INDEX_LOOP 0x0004 /* IN operator used as a loop */
int sqlite3FindInIndex(Parse *, Expr *, u32, int*);
int sqlite3FindInIndex(Parse *, Expr *, u32, int*, int*);
int sqlite3JournalOpen(sqlite3_vfs *, const char *, sqlite3_file *, int, int);
int sqlite3JournalSize(sqlite3_vfs *);
@ -4261,4 +4275,9 @@ int sqlite3ThreadJoin(SQLiteThread*, void**);
int sqlite3DbstatRegister(sqlite3*);
#endif
int sqlite3ExprVectorSize(Expr *pExpr);
int sqlite3ExprIsVector(Expr *pExpr);
Expr *sqlite3VectorFieldSubexpr(Expr*, int);
Expr *sqlite3ExprForVectorField(Parse*,Expr*,int);
#endif /* SQLITEINT_H */

View File

@ -451,6 +451,15 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){
sqlite3TreeViewExpr(pView, pExpr->pRight, 0);
break;
}
case TK_VECTOR: {
sqlite3TreeViewBareExprList(pView, pExpr->x.pList, "VECTOR");
break;
}
case TK_SELECT_COLUMN: {
sqlite3TreeViewLine(pView, "SELECT-COLUMN %d", pExpr->iColumn);
sqlite3TreeViewSelect(pView, pExpr->pLeft->x.pSelect, 0);
break;
}
default: {
sqlite3TreeViewLine(pView, "op=%d", pExpr->op);
break;
@ -467,21 +476,20 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){
sqlite3TreeViewPop(pView);
}
/*
** Generate a human-readable explanation of an expression list.
*/
void sqlite3TreeViewExprList(
void sqlite3TreeViewBareExprList(
TreeView *pView,
const ExprList *pList,
u8 moreToFollow,
const char *zLabel
){
int i;
pView = sqlite3TreeViewPush(pView, moreToFollow);
if( zLabel==0 || zLabel[0]==0 ) zLabel = "LIST";
if( pList==0 ){
sqlite3TreeViewLine(pView, "%s (empty)", zLabel);
}else{
int i;
sqlite3TreeViewLine(pView, "%s", zLabel);
for(i=0; i<pList->nExpr; i++){
int j = pList->a[i].u.x.iOrderByCol;
@ -493,6 +501,15 @@ void sqlite3TreeViewExprList(
if( j ) sqlite3TreeViewPop(pView);
}
}
}
void sqlite3TreeViewExprList(
TreeView *pView,
const ExprList *pList,
u8 moreToFollow,
const char *zLabel
){
pView = sqlite3TreeViewPush(pView, moreToFollow);
sqlite3TreeViewBareExprList(pView, pList, zLabel);
sqlite3TreeViewPop(pView);
}

View File

@ -573,7 +573,7 @@ int sqlite3VdbeExec(
sqlite3 *db = p->db; /* The database */
u8 resetSchemaOnFault = 0; /* Reset schema after an error if positive */
u8 encoding = ENC(db); /* The database encoding */
int iCompare = 0; /* Result of last OP_Compare operation */
int iCompare = 0; /* Result of last comparison */
unsigned nVmStep = 0; /* Number of virtual machine steps */
#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
unsigned nProgressLimit = 0;/* Invoke xProgress() when nVmStep reaches this */
@ -905,7 +905,7 @@ case OP_Yield: { /* in1, jump */
}
/* Opcode: HaltIfNull P1 P2 P3 P4 P5
** Synopsis: if r[P3]=null halt
** Synopsis: if r[P3]=null halt
**
** Check the value in register P3. If it is NULL then Halt using
** parameter P1, P2, and P4 as if this were a Halt instruction. If the
@ -1118,7 +1118,7 @@ case OP_String: { /* out2 */
}
/* Opcode: Null P1 P2 P3 * *
** Synopsis: r[P2..P3]=NULL
** Synopsis: r[P2..P3]=NULL
**
** Write a NULL into registers P2. If P3 greater than P2, then also write
** NULL into register P3 and every register in between P2 and P3. If P3
@ -1147,7 +1147,7 @@ case OP_Null: { /* out2 */
}
/* Opcode: SoftNull P1 * * * *
** Synopsis: r[P1]=NULL
** Synopsis: r[P1]=NULL
**
** Set register P1 to have the value NULL as seen by the OP_MakeRecord
** instruction, but do not free any string or blob memory associated with
@ -1200,7 +1200,7 @@ case OP_Variable: { /* out2 */
}
/* Opcode: Move P1 P2 P3 * *
** Synopsis: r[P2@P3]=r[P1@P3]
** Synopsis: r[P2@P3]=r[P1@P3]
**
** Move the P3 values in register P1..P1+P3-1 over into
** registers P2..P2+P3-1. Registers P1..P1+P3-1 are
@ -1310,7 +1310,7 @@ case OP_IntCopy: { /* out2 */
}
/* Opcode: ResultRow P1 P2 * * *
** Synopsis: output=r[P1@P2]
** Synopsis: output=r[P1@P2]
**
** The registers P1 through P1+P2-1 contain a single row of
** results. This opcode causes the sqlite3_step() call to terminate
@ -1443,14 +1443,14 @@ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */
}
/* Opcode: Add P1 P2 P3 * *
** Synopsis: r[P3]=r[P1]+r[P2]
** Synopsis: r[P3]=r[P1]+r[P2]
**
** Add the value in register P1 to the value in register P2
** and store the result in register P3.
** If either input is NULL, the result is NULL.
*/
/* Opcode: Multiply P1 P2 P3 * *
** Synopsis: r[P3]=r[P1]*r[P2]
** Synopsis: r[P3]=r[P1]*r[P2]
**
**
** Multiply the value in register P1 by the value in register P2
@ -1458,14 +1458,14 @@ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */
** If either input is NULL, the result is NULL.
*/
/* Opcode: Subtract P1 P2 P3 * *
** Synopsis: r[P3]=r[P2]-r[P1]
** Synopsis: r[P3]=r[P2]-r[P1]
**
** Subtract the value in register P1 from the value in register P2
** and store the result in register P3.
** If either input is NULL, the result is NULL.
*/
/* Opcode: Divide P1 P2 P3 * *
** Synopsis: r[P3]=r[P2]/r[P1]
** Synopsis: r[P3]=r[P2]/r[P1]
**
** Divide the value in register P1 by the value in register P2
** and store the result in register P3 (P3=P2/P1). If the value in
@ -1473,7 +1473,7 @@ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */
** NULL, the result is NULL.
*/
/* Opcode: Remainder P1 P2 P3 * *
** Synopsis: r[P3]=r[P2]%r[P1]
** Synopsis: r[P3]=r[P2]%r[P1]
**
** Compute the remainder after integer register P2 is divided by
** register P1 and store the result in register P3.
@ -1706,21 +1706,21 @@ case OP_Function: {
}
/* Opcode: BitAnd P1 P2 P3 * *
** Synopsis: r[P3]=r[P1]&r[P2]
** Synopsis: r[P3]=r[P1]&r[P2]
**
** Take the bit-wise AND of the values in register P1 and P2 and
** store the result in register P3.
** If either input is NULL, the result is NULL.
*/
/* Opcode: BitOr P1 P2 P3 * *
** Synopsis: r[P3]=r[P1]|r[P2]
** Synopsis: r[P3]=r[P1]|r[P2]
**
** Take the bit-wise OR of the values in register P1 and P2 and
** store the result in register P3.
** If either input is NULL, the result is NULL.
*/
/* Opcode: ShiftLeft P1 P2 P3 * *
** Synopsis: r[P3]=r[P2]<<r[P1]
** Synopsis: r[P3]=r[P2]<<r[P1]
**
** Shift the integer value in register P2 to the left by the
** number of bits specified by the integer in register P1.
@ -1728,7 +1728,7 @@ case OP_Function: {
** If either input is NULL, the result is NULL.
*/
/* Opcode: ShiftRight P1 P2 P3 * *
** Synopsis: r[P3]=r[P2]>>r[P1]
** Synopsis: r[P3]=r[P2]>>r[P1]
**
** Shift the integer value in register P2 to the right by the
** number of bits specified by the integer in register P1.
@ -1788,7 +1788,7 @@ case OP_ShiftRight: { /* same as TK_RSHIFT, in1, in2, out3 */
}
/* Opcode: AddImm P1 P2 * * *
** Synopsis: r[P1]=r[P1]+P2
** Synopsis: r[P1]=r[P1]+P2
**
** Add the constant P2 to the value in register P1.
** The result is always an integer.
@ -1880,14 +1880,61 @@ case OP_Cast: { /* in1 */
}
#endif /* SQLITE_OMIT_CAST */
/* Opcode: Eq P1 P2 P3 P4 P5
** Synopsis: IF r[P3]==r[P1]
**
** Compare the values in register P1 and P3. If reg(P3)==reg(P1) then
** jump to address P2. Or if the SQLITE_STOREP2 flag is set in P5, then
** store the result of comparison in register P2.
**
** The SQLITE_AFF_MASK portion of P5 must be an affinity character -
** SQLITE_AFF_TEXT, SQLITE_AFF_INTEGER, and so forth. An attempt is made
** to coerce both inputs according to this affinity before the
** comparison is made. If the SQLITE_AFF_MASK is 0x00, then numeric
** affinity is used. Note that the affinity conversions are stored
** back into the input registers P1 and P3. So this opcode can cause
** persistent changes to registers P1 and P3.
**
** Once any conversions have taken place, and neither value is NULL,
** the values are compared. If both values are blobs then memcmp() is
** used to determine the results of the comparison. If both values
** are text, then the appropriate collating function specified in
** P4 is used to do the comparison. If P4 is not specified then
** memcmp() is used to compare text string. If both values are
** numeric, then a numeric comparison is used. If the two values
** are of different types, then numbers are considered less than
** strings and strings are considered less than blobs.
**
** If SQLITE_NULLEQ is set in P5 then the result of comparison is always either
** true or false and is never NULL. If both operands are NULL then the result
** of comparison is true. If either operand is NULL then the result is false.
** If neither operand is NULL the result is the same as it would be if
** the SQLITE_NULLEQ flag were omitted from P5.
**
** If both SQLITE_STOREP2 and SQLITE_KEEPNULL flags are set then the
** content of r[P2] is only changed if the new value is NULL or 0 (false).
** In other words, a prior r[P2] value will not be overwritten by 1 (true).
*/
/* Opcode: Ne P1 P2 P3 P4 P5
** Synopsis: IF r[P3]!=r[P1]
**
** This works just like the Eq opcode except that the jump is taken if
** the operands in registers P1 and P3 are not equal. See the Eq opcode for
** additional information.
**
** If both SQLITE_STOREP2 and SQLITE_KEEPNULL flags are set then the
** content of r[P2] is only changed if the new value is NULL or 1 (true).
** In other words, a prior r[P2] value will not be overwritten by 0 (false).
*/
/* Opcode: Lt P1 P2 P3 P4 P5
** Synopsis: IF r[P3]<r[P1]
**
** Compare the values in register P1 and P3. If reg(P3)<reg(P1) then
** jump to address P2.
** jump to address P2. Or if the SQLITE_STOREP2 flag is set in P5 store
** the result of comparison (0 or 1 or NULL) into register P2.
**
** If the SQLITE_JUMPIFNULL bit of P5 is set and either reg(P1) or
** reg(P3) is NULL then take the jump. If the SQLITE_JUMPIFNULL
** reg(P3) is NULL then the take the jump. If the SQLITE_JUMPIFNULL
** bit is clear then fall through if either operand is NULL.
**
** The SQLITE_AFF_MASK portion of P5 must be an affinity character -
@ -1907,39 +1954,6 @@ case OP_Cast: { /* in1 */
** numeric, then a numeric comparison is used. If the two values
** are of different types, then numbers are considered less than
** strings and strings are considered less than blobs.
**
** If the SQLITE_STOREP2 bit of P5 is set, then do not jump. Instead,
** store a boolean result (either 0, or 1, or NULL) in register P2.
**
** If the SQLITE_NULLEQ bit is set in P5, then NULL values are considered
** equal to one another, provided that they do not have their MEM_Cleared
** bit set.
*/
/* Opcode: Ne P1 P2 P3 P4 P5
** Synopsis: IF r[P3]!=r[P1]
**
** This works just like the Lt opcode except that the jump is taken if
** the operands in registers P1 and P3 are not equal. See the Lt opcode for
** additional information.
**
** If SQLITE_NULLEQ is set in P5 then the result of comparison is always either
** true or false and is never NULL. If both operands are NULL then the result
** of comparison is false. If either operand is NULL then the result is true.
** If neither operand is NULL the result is the same as it would be if
** the SQLITE_NULLEQ flag were omitted from P5.
*/
/* Opcode: Eq P1 P2 P3 P4 P5
** Synopsis: IF r[P3]==r[P1]
**
** This works just like the Lt opcode except that the jump is taken if
** the operands in registers P1 and P3 are equal.
** See the Lt opcode for additional information.
**
** If SQLITE_NULLEQ is set in P5 then the result of comparison is always either
** true or false and is never NULL. If both operands are NULL then the result
** of comparison is true. If either operand is NULL then the result is false.
** If neither operand is NULL the result is the same as it would be if
** the SQLITE_NULLEQ flag were omitted from P5.
*/
/* Opcode: Le P1 P2 P3 P4 P5
** Synopsis: IF r[P3]<=r[P1]
@ -1968,7 +1982,7 @@ case OP_Lt: /* same as TK_LT, jump, in1, in3 */
case OP_Le: /* same as TK_LE, jump, in1, in3 */
case OP_Gt: /* same as TK_GT, jump, in1, in3 */
case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
int res; /* Result of the comparison of pIn1 against pIn3 */
int res, res2; /* Result of the comparison of pIn1 against pIn3 */
char affinity; /* Affinity to use for comparison */
u16 flags1; /* Copy of initial value of pIn1->flags */
u16 flags3; /* Copy of initial value of pIn3->flags */
@ -1991,9 +2005,9 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
&& (flags3&MEM_Null)!=0
&& (flags3&MEM_Cleared)==0
){
res = 0; /* Results are equal */
res = 0; /* Operands are equal */
}else{
res = 1; /* Results are not equal */
res = 1; /* Operands are not equal */
}
}else{
/* SQLITE_NULLEQ is clear and at least one operand is NULL,
@ -2002,6 +2016,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
*/
if( pOp->p5 & SQLITE_STOREP2 ){
pOut = &aMem[pOp->p2];
iCompare = 1; /* Operands are not equal */
memAboutToChange(p, pOut);
MemSetTypeFlag(pOut, MEM_Null);
REGISTER_TRACE(pOp->p2, pOut);
@ -2055,12 +2070,12 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
res = sqlite3MemCompare(pIn3, pIn1, pOp->p4.pColl);
}
switch( pOp->opcode ){
case OP_Eq: res = res==0; break;
case OP_Ne: res = res!=0; break;
case OP_Lt: res = res<0; break;
case OP_Le: res = res<=0; break;
case OP_Gt: res = res>0; break;
default: res = res>=0; break;
case OP_Eq: res2 = res==0; break;
case OP_Ne: res2 = res; break;
case OP_Lt: res2 = res<0; break;
case OP_Le: res2 = res<=0; break;
case OP_Gt: res2 = res>0; break;
default: res2 = res>=0; break;
}
/* Undo any changes made by applyAffinity() to the input registers. */
@ -2071,19 +2086,55 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
if( pOp->p5 & SQLITE_STOREP2 ){
pOut = &aMem[pOp->p2];
iCompare = res;
res2 = res2!=0; /* For this path res2 must be exactly 0 or 1 */
if( (pOp->p5 & SQLITE_KEEPNULL)!=0 ){
/* The KEEPNULL flag prevents OP_Eq from overwriting a NULL with 1
** and prevents OP_Ne from overwriting NULL with 0. This flag
** is only used in contexts where either:
** (1) op==OP_Eq && (r[P2]==NULL || r[P2]==0)
** (2) op==OP_Ne && (r[P2]==NULL || r[P2]==1)
** Therefore it is not necessary to check the content of r[P2] for
** NULL. */
assert( pOp->opcode==OP_Ne || pOp->opcode==OP_Eq );
assert( res2==0 || res2==1 );
testcase( res2==0 && pOp->opcode==OP_Eq );
testcase( res2==1 && pOp->opcode==OP_Eq );
testcase( res2==0 && pOp->opcode==OP_Ne );
testcase( res2==1 && pOp->opcode==OP_Ne );
if( (pOp->opcode==OP_Eq)==res2 ) break;
}
memAboutToChange(p, pOut);
MemSetTypeFlag(pOut, MEM_Int);
pOut->u.i = res;
pOut->u.i = res2;
REGISTER_TRACE(pOp->p2, pOut);
}else{
VdbeBranchTaken(res!=0, (pOp->p5 & SQLITE_NULLEQ)?2:3);
if( res ){
if( res2 ){
goto jump_to_p2;
}
}
break;
}
/* Opcode: ElseNotEq * P2 * * *
**
** This opcode must immediately follow an Lt or Gt comparison operator.
** If the operands in that previous comparison had been used with an Eq
** operator and if the result of that Eq would be NULL or false (0), then
** then jump to P2. If the result of comparing the two previous operands
** using Eq would have been true (1), then fall through.
*/
case OP_ElseNotEq: { /* same as TK_ESCAPE, jump */
assert( pOp>aOp );
assert( pOp[-1].opcode==OP_Lt || pOp[-1].opcode==OP_Gt );
assert( pOp[-1].p5 & SQLITE_STOREP2 );
VdbeBranchTaken(iCompare!=0, 2);
if( iCompare!=0 ) goto jump_to_p2;
break;
}
/* Opcode: Permutation * * * P4 *
**
** Set the permutation used by the OP_Compare operator to be the array
@ -2333,7 +2384,7 @@ case OP_IfNot: { /* jump, in1 */
}
/* Opcode: IsNull P1 P2 * * *
** Synopsis: if r[P1]==NULL goto P2
** Synopsis: if r[P1]==NULL goto P2
**
** Jump to P2 if the value in register P1 is NULL.
*/
@ -2361,7 +2412,7 @@ case OP_NotNull: { /* same as TK_NOTNULL, jump, in1 */
}
/* Opcode: Column P1 P2 P3 P4 P5
** Synopsis: r[P3]=PX
** Synopsis: r[P3]=PX
**
** Interpret the data that cursor P1 points to as a structure built using
** the MakeRecord instruction. (See the MakeRecord opcode for additional
@ -3871,7 +3922,6 @@ seek_not_found:
}
break;
}
/* Opcode: Found P1 P2 P3 P4 *
** Synopsis: key=r[P3@P4]
@ -4304,7 +4354,7 @@ case OP_NewRowid: { /* out2 */
** for indices is OP_IdxInsert.
*/
/* Opcode: InsertInt P1 P2 P3 P4 P5
** Synopsis: intkey=P3 data=r[P2]
** Synopsis: intkey=P3 data=r[P2]
**
** This works exactly like OP_Insert except that the key is the
** integer value P3, not the value of the integer stored in register P3.
@ -4535,7 +4585,7 @@ case OP_ResetCount: {
}
/* Opcode: SorterCompare P1 P2 P3 P4
** Synopsis: if key(P1)!=trim(r[P3],P4) goto P2
** Synopsis: if key(P1)!=trim(r[P3],P4) goto P2
**
** P1 is a sorter cursor. This instruction compares a prefix of the
** record blob in register P3 against a prefix of the entry that
@ -5062,7 +5112,7 @@ case OP_IdxDelete: {
}
/* Opcode: Seek P1 * P3 P4 *
** Synopsis: Move P3 to P1.rowid
** Synopsis: Move P3 to P1.rowid
**
** P1 is an open index cursor and P3 is a cursor on the corresponding
** table. This opcode does a deferred seek of the P3 table cursor
@ -5569,7 +5619,7 @@ case OP_IntegrityCk: {
#endif /* SQLITE_OMIT_INTEGRITY_CHECK */
/* Opcode: RowSetAdd P1 P2 * * *
** Synopsis: rowset(P1)=r[P2]
** Synopsis: rowset(P1)=r[P2]
**
** Insert the integer value held by register P2 into a boolean index
** held in register P1.
@ -5589,7 +5639,7 @@ case OP_RowSetAdd: { /* in1, in2 */
}
/* Opcode: RowSetRead P1 P2 P3 * *
** Synopsis: r[P3]=rowset(P1)
** Synopsis: r[P3]=rowset(P1)
**
** Extract the smallest value from boolean index P1 and put that value into
** register P3. Or, if boolean index P1 is initially empty, leave P3
@ -6768,7 +6818,7 @@ case OP_MaxPgcnt: { /* out2 */
/* Opcode: Init * P2 * P4 *
** Synopsis: Start at P2
** Synopsis: Start at P2
**
** Programs contain a single instance of this opcode as the very first
** opcode.
@ -6828,8 +6878,8 @@ case OP_Init: { /* jump */
}
#endif /* SQLITE_DEBUG */
#endif /* SQLITE_OMIT_TRACE */
if( pOp->p2 ) goto jump_to_p2;
break;
assert( pOp->p2>0 );
goto jump_to_p2;
}
#ifdef SQLITE_ENABLE_CURSOR_HINTS

View File

@ -1520,9 +1520,9 @@ static int stat4ValueFromExpr(
** structures intended to be compared against sample index keys stored
** in the sqlite_stat4 table.
**
** A single call to this function attempts to populates field iVal (leftmost
** is 0 etc.) of the unpacked record with a value extracted from expression
** pExpr. Extraction of values is possible if:
** A single call to this function populates zero or more fields of the
** record starting with field iVal (fields are numbered from left to
** right starting with 0). A single field is populated if:
**
** * (pExpr==0). In this case the value is assumed to be an SQL NULL,
**
@ -1531,10 +1531,14 @@ static int stat4ValueFromExpr(
** * The sqlite3ValueFromExpr() function is able to extract a value
** from the expression (i.e. the expression is a literal value).
**
** If a value can be extracted, the affinity passed as the 5th argument
** is applied to it before it is copied into the UnpackedRecord. Output
** parameter *pbOk is set to true if a value is extracted, or false
** otherwise.
** Or, if pExpr is a TK_VECTOR, one field is populated for each of the
** vector components that match either of the two latter criteria listed
** above.
**
** Before any value is appended to the record, the affinity of the
** corresponding column within index pIdx is applied to it. Before
** this function returns, output parameter *pnExtract is set to the
** number of values appended to the record.
**
** When this function is called, *ppRec must either point to an object
** allocated by an earlier call to this function, or must be NULL. If it
@ -1550,22 +1554,33 @@ int sqlite3Stat4ProbeSetValue(
Index *pIdx, /* Index being probed */
UnpackedRecord **ppRec, /* IN/OUT: Probe record */
Expr *pExpr, /* The expression to extract a value from */
u8 affinity, /* Affinity to use */
int nElem, /* Maximum number of values to append */
int iVal, /* Array element to populate */
int *pbOk /* OUT: True if value was extracted */
int *pnExtract /* OUT: Values appended to the record */
){
int rc;
sqlite3_value *pVal = 0;
struct ValueNewStat4Ctx alloc;
int rc = SQLITE_OK;
int nExtract = 0;
alloc.pParse = pParse;
alloc.pIdx = pIdx;
alloc.ppRec = ppRec;
alloc.iVal = iVal;
if( pExpr==0 || pExpr->op!=TK_SELECT ){
int i;
struct ValueNewStat4Ctx alloc;
rc = stat4ValueFromExpr(pParse, pExpr, affinity, &alloc, &pVal);
assert( pVal==0 || pVal->db==pParse->db );
*pbOk = (pVal!=0);
alloc.pParse = pParse;
alloc.pIdx = pIdx;
alloc.ppRec = ppRec;
for(i=0; i<nElem; i++){
sqlite3_value *pVal = 0;
Expr *pElem = (pExpr ? sqlite3VectorFieldSubexpr(pExpr, i) : 0);
u8 aff = sqlite3IndexColumnAffinity(pParse->db, pIdx, iVal+i);
alloc.iVal = iVal+i;
rc = stat4ValueFromExpr(pParse, pElem, aff, &alloc, &pVal);
if( !pVal ) break;
nExtract++;
}
}
*pnExtract = nExtract;
return rc;
}

View File

@ -826,7 +826,8 @@ static sqlite3_index_info *allocateIndexInfo(
WhereClause *pWC,
Bitmask mUnusable, /* Ignore terms with these prereqs */
struct SrcList_item *pSrc,
ExprList *pOrderBy
ExprList *pOrderBy,
u16 *pmNoOmit /* Mask of terms not to omit */
){
int i, j;
int nTerm;
@ -836,6 +837,7 @@ static sqlite3_index_info *allocateIndexInfo(
WhereTerm *pTerm;
int nOrderBy;
sqlite3_index_info *pIdxInfo;
u16 mNoOmit = 0;
/* Count the number of possible WHERE clause constraints referring
** to this virtual table */
@ -924,6 +926,15 @@ static sqlite3_index_info *allocateIndexInfo(
assert( WO_GE==SQLITE_INDEX_CONSTRAINT_GE );
assert( WO_MATCH==SQLITE_INDEX_CONSTRAINT_MATCH );
assert( pTerm->eOperator & (WO_IN|WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE|WO_MATCH) );
if( op & (WO_LT|WO_LE|WO_GT|WO_GE)
&& sqlite3ExprIsVector(pTerm->pExpr->pRight)
){
if( i<16 ) mNoOmit |= (1 << i);
if( op==WO_LT ) pIdxCons[j].op = WO_LE;
if( op==WO_GT ) pIdxCons[j].op = WO_GE;
}
j++;
}
for(i=0; i<nOrderBy; i++){
@ -932,6 +943,7 @@ static sqlite3_index_info *allocateIndexInfo(
pIdxOrderBy[i].desc = pOrderBy->a[i].sortOrder;
}
*pmNoOmit = mNoOmit;
return pIdxInfo;
}
@ -1207,7 +1219,7 @@ static LogEst whereRangeAdjust(WhereTerm *pTerm, LogEst nNew){
/*
** Return the affinity for a single column of an index.
*/
static char sqlite3IndexColumnAffinity(sqlite3 *db, Index *pIdx, int iCol){
char sqlite3IndexColumnAffinity(sqlite3 *db, Index *pIdx, int iCol){
assert( iCol>=0 && iCol<pIdx->nColumn );
if( !pIdx->zColAff ){
if( sqlite3IndexAffinityStr(db, pIdx)==0 ) return SQLITE_AFF_BLOB;
@ -1384,7 +1396,8 @@ static int whereRangeScanEst(
if( nEq==pBuilder->nRecValid ){
UnpackedRecord *pRec = pBuilder->pRec;
tRowcnt a[2];
u8 aff;
int nBtm = pLoop->u.btree.nBtm;
int nTop = pLoop->u.btree.nTop;
/* 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
@ -1414,8 +1427,6 @@ static int whereRangeScanEst(
testcase( pRec->nField!=pBuilder->nRecValid );
pRec->nField = pBuilder->nRecValid;
}
aff = sqlite3IndexColumnAffinity(pParse->db, p, nEq);
assert( nEq!=p->nKeyCol || aff==SQLITE_AFF_INTEGER );
/* Determine iLower and iUpper using ($P) only. */
if( nEq==0 ){
iLower = 0;
@ -1434,17 +1445,20 @@ static int whereRangeScanEst(
if( p->aSortOrder[nEq] ){
/* The roles of pLower and pUpper are swapped for a DESC index */
SWAP(WhereTerm*, pLower, pUpper);
SWAP(int, nBtm, nTop);
}
/* If possible, improve on the iLower estimate using ($P:$L). */
if( pLower ){
int bOk; /* True if value is extracted from pExpr */
int n; /* Values extracted from pExpr */
Expr *pExpr = pLower->pExpr->pRight;
rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, aff, nEq, &bOk);
if( rc==SQLITE_OK && bOk ){
rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, nBtm, nEq, &n);
if( rc==SQLITE_OK && n ){
tRowcnt iNew;
u16 mask = WO_GT|WO_LE;
if( sqlite3ExprVectorSize(pExpr)>n ) mask = (WO_LE|WO_LT);
iLwrIdx = whereKeyStats(pParse, p, pRec, 0, a);
iNew = a[0] + ((pLower->eOperator & (WO_GT|WO_LE)) ? a[1] : 0);
iNew = a[0] + ((pLower->eOperator & mask) ? a[1] : 0);
if( iNew>iLower ) iLower = iNew;
nOut--;
pLower = 0;
@ -1453,13 +1467,15 @@ static int whereRangeScanEst(
/* If possible, improve on the iUpper estimate using ($P:$U). */
if( pUpper ){
int bOk; /* True if value is extracted from pExpr */
int n; /* Values extracted from pExpr */
Expr *pExpr = pUpper->pExpr->pRight;
rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, aff, nEq, &bOk);
if( rc==SQLITE_OK && bOk ){
rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, nTop, nEq, &n);
if( rc==SQLITE_OK && n ){
tRowcnt iNew;
u16 mask = WO_GT|WO_LE;
if( sqlite3ExprVectorSize(pExpr)>n ) mask = (WO_LE|WO_LT);
iUprIdx = whereKeyStats(pParse, p, pRec, 1, a);
iNew = a[0] + ((pUpper->eOperator & (WO_GT|WO_LE)) ? a[1] : 0);
iNew = a[0] + ((pUpper->eOperator & mask) ? a[1] : 0);
if( iNew<iUpper ) iUpper = iNew;
nOut--;
pUpper = 0;
@ -1549,7 +1565,6 @@ static int whereEqualScanEst(
Index *p = pBuilder->pNew->u.btree.pIndex;
int nEq = pBuilder->pNew->u.btree.nEq;
UnpackedRecord *pRec = pBuilder->pRec;
u8 aff; /* Column affinity */
int rc; /* Subfunction return code */
tRowcnt a[2]; /* Statistics */
int bOk;
@ -1573,8 +1588,7 @@ static int whereEqualScanEst(
return SQLITE_OK;
}
aff = sqlite3IndexColumnAffinity(pParse->db, p, nEq-1);
rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, aff, nEq-1, &bOk);
rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, 1, nEq-1, &bOk);
pBuilder->pRec = pRec;
if( rc!=SQLITE_OK ) return rc;
if( bOk==0 ) return SQLITE_NOTFOUND;
@ -1663,9 +1677,14 @@ static void whereTermPrint(WhereTerm *pTerm, int iTerm){
sqlite3_snprintf(sizeof(zLeft),zLeft,"left=%d", pTerm->leftCursor);
}
sqlite3DebugPrintf(
"TERM-%-3d %p %s %-12s prob=%-3d op=0x%03x wtFlags=0x%04x\n",
"TERM-%-3d %p %s %-12s prob=%-3d op=0x%03x wtFlags=0x%04x",
iTerm, pTerm, zType, zLeft, pTerm->truthProb,
pTerm->eOperator, pTerm->wtFlags);
if( pTerm->iField ){
sqlite3DebugPrintf(" iField=%d\n", pTerm->iField);
}else{
sqlite3DebugPrintf("\n");
}
sqlite3TreeViewExpr(0, pTerm->pExpr, 0);
}
}
@ -2187,6 +2206,72 @@ static void whereLoopOutputAdjust(
if( pLoop->nOut > nRow-iReduce ) pLoop->nOut = nRow - iReduce;
}
/*
** Term pTerm is a vector range comparison operation. The first comparison
** in the vector can be optimized using column nEq of the index. This
** function returns the total number of vector elements that can be used
** as part of the range comparison.
**
** For example, if the query is:
**
** WHERE a = ? AND (b, c, d) > (?, ?, ?)
**
** and the index:
**
** CREATE INDEX ... ON (a, b, c, d, e)
**
** then this function would be invoked with nEq=1. The value returned in
** this case is 3.
*/
int whereRangeVectorLen(
Parse *pParse, /* Parsing context */
int iCur, /* Cursor open on pIdx */
Index *pIdx, /* The index to be used for a inequality constraint */
int nEq, /* Number of prior equality constraints on same index */
WhereTerm *pTerm /* The vector inequality constraint */
){
int nCmp = sqlite3ExprVectorSize(pTerm->pExpr->pLeft);
int i;
nCmp = MIN(nCmp, (pIdx->nColumn - nEq));
for(i=1; i<nCmp; i++){
/* Test if comparison i of pTerm is compatible with column (i+nEq)
** of the index. If not, exit the loop. */
char aff; /* Comparison affinity */
char idxaff = 0; /* Indexed columns affinity */
CollSeq *pColl; /* Comparison collation sequence */
Expr *pLhs = pTerm->pExpr->pLeft->x.pList->a[i].pExpr;
Expr *pRhs = pTerm->pExpr->pRight;
if( pRhs->flags & EP_xIsSelect ){
pRhs = pRhs->x.pSelect->pEList->a[i].pExpr;
}else{
pRhs = pRhs->x.pList->a[i].pExpr;
}
/* Check that the LHS of the comparison is a column reference to
** the right column of the right source table. And that the sort
** order of the index column is the same as the sort order of the
** leftmost index column. */
if( pLhs->op!=TK_COLUMN
|| pLhs->iTable!=iCur
|| pLhs->iColumn!=pIdx->aiColumn[i+nEq]
|| pIdx->aSortOrder[i+nEq]!=pIdx->aSortOrder[nEq]
){
break;
}
testcase( pLhs->iColumn==XN_ROWID );
aff = sqlite3CompareAffinity(pRhs, sqlite3ExprAffinity(pLhs));
idxaff = sqlite3TableColumnAffinity(pIdx->pTable, pLhs->iColumn);
if( aff!=idxaff ) break;
pColl = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs);
if( pColl==0 ) break;
if( sqlite3StrICmp(pColl->zName, pIdx->azColl[i+nEq]) ) break;
}
return i;
}
/*
** Adjust the cost C by the costMult facter T. This only occurs if
** compiled with -DSQLITE_ENABLE_COSTMULT
@ -2225,6 +2310,8 @@ static int whereLoopAddBtreeIndex(
Bitmask saved_prereq; /* Original value of pNew->prereq */
u16 saved_nLTerm; /* Original value of pNew->nLTerm */
u16 saved_nEq; /* Original value of pNew->u.btree.nEq */
u16 saved_nBtm; /* Original value of pNew->u.btree.nBtm */
u16 saved_nTop; /* Original value of pNew->u.btree.nTop */
u16 saved_nSkip; /* Original value of pNew->nSkip */
u32 saved_wsFlags; /* Original value of pNew->wsFlags */
LogEst saved_nOut; /* Original value of pNew->nOut */
@ -2241,6 +2328,7 @@ static int whereLoopAddBtreeIndex(
if( pNew->wsFlags & WHERE_BTM_LIMIT ){
opMask = WO_LT|WO_LE;
}else{
assert( pNew->u.btree.nBtm==0 );
opMask = WO_EQ|WO_IN|WO_GT|WO_GE|WO_LT|WO_LE|WO_ISNULL|WO_IS;
}
if( pProbe->bUnordered ) opMask &= ~(WO_GT|WO_GE|WO_LT|WO_LE);
@ -2248,6 +2336,8 @@ static int whereLoopAddBtreeIndex(
assert( pNew->u.btree.nEq<pProbe->nColumn );
saved_nEq = pNew->u.btree.nEq;
saved_nBtm = pNew->u.btree.nBtm;
saved_nTop = pNew->u.btree.nTop;
saved_nSkip = pNew->nSkip;
saved_nLTerm = pNew->nLTerm;
saved_wsFlags = pNew->wsFlags;
@ -2291,6 +2381,8 @@ static int whereLoopAddBtreeIndex(
pNew->wsFlags = saved_wsFlags;
pNew->u.btree.nEq = saved_nEq;
pNew->u.btree.nBtm = saved_nBtm;
pNew->u.btree.nTop = saved_nTop;
pNew->nLTerm = saved_nLTerm;
if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */
pNew->aLTerm[pNew->nLTerm++] = pTerm;
@ -2307,14 +2399,23 @@ static int whereLoopAddBtreeIndex(
pNew->wsFlags |= WHERE_COLUMN_IN;
if( ExprHasProperty(pExpr, EP_xIsSelect) ){
/* "x IN (SELECT ...)": TUNING: the SELECT returns 25 rows */
int i;
nIn = 46; assert( 46==sqlite3LogEst(25) );
/* The expression may actually be of the form (x, y) IN (SELECT...).
** In this case there is a separate term for each of (x) and (y).
** However, the nIn multiplier should only be applied once, not once
** for each such term. The following loop checks that pTerm is the
** first such term in use, and sets nIn back to 0 if it is not. */
for(i=0; i<pNew->nLTerm-1; i++){
if( pNew->aLTerm[i]->pExpr==pExpr ) nIn = 0;
}
}else if( ALWAYS(pExpr->x.pList && pExpr->x.pList->nExpr) ){
/* "x IN (value, value, ...)" */
nIn = sqlite3LogEst(pExpr->x.pList->nExpr);
assert( nIn>0 ); /* RHS always has 2 or more terms... The parser
** changes "x IN (?)" into "x=?". */
}
assert( nIn>0 ); /* RHS always has 2 or more terms... The parser
** changes "x IN (?)" into "x=?". */
}else if( eOp & (WO_EQ|WO_IS) ){
int iCol = pProbe->aiColumn[saved_nEq];
pNew->wsFlags |= WHERE_COLUMN_EQ;
@ -2334,6 +2435,9 @@ static int whereLoopAddBtreeIndex(
testcase( eOp & WO_GT );
testcase( eOp & WO_GE );
pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_BTM_LIMIT;
pNew->u.btree.nBtm = whereRangeVectorLen(
pParse, pSrc->iCursor, pProbe, saved_nEq, pTerm
);
pBtm = pTerm;
pTop = 0;
if( pTerm->wtFlags & TERM_LIKEOPT ){
@ -2346,12 +2450,16 @@ static int whereLoopAddBtreeIndex(
if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */
pNew->aLTerm[pNew->nLTerm++] = pTop;
pNew->wsFlags |= WHERE_TOP_LIMIT;
pNew->u.btree.nTop = 1;
}
}else{
assert( eOp & (WO_LT|WO_LE) );
testcase( eOp & WO_LT );
testcase( eOp & WO_LE );
pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_TOP_LIMIT;
pNew->u.btree.nTop = whereRangeVectorLen(
pParse, pSrc->iCursor, pProbe, saved_nEq, pTerm
);
pTop = pTerm;
pBtm = (pNew->wsFlags & WHERE_BTM_LIMIT)!=0 ?
pNew->aLTerm[pNew->nLTerm-2] : 0;
@ -2451,6 +2559,8 @@ static int whereLoopAddBtreeIndex(
}
pNew->prereq = saved_prereq;
pNew->u.btree.nEq = saved_nEq;
pNew->u.btree.nBtm = saved_nBtm;
pNew->u.btree.nTop = saved_nTop;
pNew->nSkip = saved_nSkip;
pNew->wsFlags = saved_wsFlags;
pNew->nOut = saved_nOut;
@ -2572,7 +2682,7 @@ static int whereUsablePartialIndex(int iTab, WhereClause *pWC, Expr *pWhere){
/*
** Add all WhereLoop objects for a single table of the join where the table
** is idenfied by pBuilder->pNew->iTab. That table is guaranteed to be
** is identified by pBuilder->pNew->iTab. That table is guaranteed to be
** a b-tree table, not a virtual table.
**
** The costs (WhereLoop.rRun) of the b-tree loops added by this function
@ -2726,6 +2836,8 @@ static int whereLoopAddBtree(
}
rSize = pProbe->aiRowLogEst[0];
pNew->u.btree.nEq = 0;
pNew->u.btree.nBtm = 0;
pNew->u.btree.nTop = 0;
pNew->nSkip = 0;
pNew->nLTerm = 0;
pNew->iSortIdx = 0;
@ -2854,6 +2966,7 @@ static int whereLoopAddVirtualOne(
Bitmask mUsable, /* Mask of usable tables */
u16 mExclude, /* Exclude terms using these operators */
sqlite3_index_info *pIdxInfo, /* Populated object for xBestIndex */
u16 mNoOmit, /* Do not omit these constraints */
int *pbIn /* OUT: True if plan uses an IN(...) op */
){
WhereClause *pWC = pBuilder->pWC;
@ -2942,6 +3055,7 @@ static int whereLoopAddVirtualOne(
}
}
}
pNew->u.vtab.omitMask &= ~mNoOmit;
pNew->nLTerm = mxTerm+1;
assert( pNew->nLTerm<=pNew->nLSlot );
@ -3015,6 +3129,7 @@ static int whereLoopAddVirtual(
int bIn; /* True if plan uses IN(...) operator */
WhereLoop *pNew;
Bitmask mBest; /* Tables used by best possible plan */
u16 mNoOmit;
assert( (mPrereq & mUnusable)==0 );
pWInfo = pBuilder->pWInfo;
@ -3023,7 +3138,8 @@ static int whereLoopAddVirtual(
pNew = pBuilder->pNew;
pSrc = &pWInfo->pTabList->a[pNew->iTab];
assert( IsVirtual(pSrc->pTab) );
p = allocateIndexInfo(pParse, pWC, mUnusable, pSrc, pBuilder->pOrderBy);
p = allocateIndexInfo(pParse, pWC, mUnusable, pSrc, pBuilder->pOrderBy,
&mNoOmit);
if( p==0 ) return SQLITE_NOMEM_BKPT;
pNew->rSetup = 0;
pNew->wsFlags = WHERE_VIRTUALTABLE;
@ -3037,7 +3153,7 @@ static int whereLoopAddVirtual(
/* First call xBestIndex() with all constraints usable. */
WHERETRACE(0x40, (" VirtualOne: all usable\n"));
rc = whereLoopAddVirtualOne(pBuilder, mPrereq, ALLBITS, 0, p, &bIn);
rc = whereLoopAddVirtualOne(pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn);
/* If the call to xBestIndex() with all terms enabled produced a plan
** that does not require any source tables (IOW: a plan with mBest==0),
@ -3054,7 +3170,8 @@ static int whereLoopAddVirtual(
** xBestIndex again, this time with IN(...) terms disabled. */
if( bIn ){
WHERETRACE(0x40, (" VirtualOne: all usable w/o IN\n"));
rc = whereLoopAddVirtualOne(pBuilder, mPrereq, ALLBITS, WO_IN, p, &bIn);
rc = whereLoopAddVirtualOne(
pBuilder, mPrereq, ALLBITS, WO_IN, p, mNoOmit, &bIn);
assert( bIn==0 );
mBestNoIn = pNew->prereq & ~mPrereq;
if( mBestNoIn==0 ){
@ -3080,7 +3197,8 @@ static int whereLoopAddVirtual(
if( mNext==mBest || mNext==mBestNoIn ) continue;
WHERETRACE(0x40, (" VirtualOne: mPrev=%04llx mNext=%04llx\n",
(sqlite3_uint64)mPrev, (sqlite3_uint64)mNext));
rc = whereLoopAddVirtualOne(pBuilder, mPrereq, mNext|mPrereq, 0, p, &bIn);
rc = whereLoopAddVirtualOne(
pBuilder, mPrereq, mNext|mPrereq, 0, p, mNoOmit, &bIn);
if( pNew->prereq==mPrereq ){
seenZero = 1;
if( bIn==0 ) seenZeroNoIN = 1;
@ -3092,7 +3210,8 @@ static int whereLoopAddVirtual(
** usable), make a call here with all source tables disabled */
if( rc==SQLITE_OK && seenZero==0 ){
WHERETRACE(0x40, (" VirtualOne: all disabled\n"));
rc = whereLoopAddVirtualOne(pBuilder, mPrereq, mPrereq, 0, p, &bIn);
rc = whereLoopAddVirtualOne(
pBuilder, mPrereq, mPrereq, 0, p, mNoOmit, &bIn);
if( bIn==0 ) seenZeroNoIN = 1;
}
@ -3101,7 +3220,8 @@ static int whereLoopAddVirtual(
** operator, make a final call to obtain one here. */
if( rc==SQLITE_OK && seenZeroNoIN==0 ){
WHERETRACE(0x40, (" VirtualOne: all disabled and w/o IN\n"));
rc = whereLoopAddVirtualOne(pBuilder, mPrereq, mPrereq, WO_IN, p, &bIn);
rc = whereLoopAddVirtualOne(
pBuilder, mPrereq, mPrereq, WO_IN, p, mNoOmit, &bIn);
}
}
@ -3445,20 +3565,42 @@ static i8 wherePathSatisfiesOrderBy(
rev = revSet = 0;
distinctColumns = 0;
for(j=0; j<nColumn; j++){
u8 bOnce; /* True to run the ORDER BY search loop */
u8 bOnce = 1; /* True to run the ORDER BY search loop */
/* Skip over == and IS and ISNULL terms.
** (Also skip IN terms when doing WHERE_ORDERBY_LIMIT processing)
*/
if( j<pLoop->u.btree.nEq
&& pLoop->nSkip==0
&& ((i = pLoop->aLTerm[j]->eOperator) & eqOpMask)!=0
){
if( i & WO_ISNULL ){
testcase( isOrderDistinct );
isOrderDistinct = 0;
assert( j>=pLoop->u.btree.nEq
|| (pLoop->aLTerm[j]==0)==(j<pLoop->nSkip)
);
if( j<pLoop->u.btree.nEq && j>=pLoop->nSkip ){
u16 eOp = pLoop->aLTerm[j]->eOperator;
/* Skip over == and IS and ISNULL terms. (Also skip IN terms when
** doing WHERE_ORDERBY_LIMIT processing).
**
** If the current term is a column of an ((?,?) IN (SELECT...))
** expression for which the SELECT returns more than one column,
** check that it is the only column used by this loop. Otherwise,
** if it is one of two or more, none of the columns can be
** considered to match an ORDER BY term. */
if( (eOp & eqOpMask)!=0 ){
if( eOp & WO_ISNULL ){
testcase( isOrderDistinct );
isOrderDistinct = 0;
}
continue;
}else if( ALWAYS(eOp & WO_IN) ){
/* ALWAYS() justification: eOp is an equality operator due to the
** j<pLoop->u.btree.nEq constraint above. Any equality other
** than WO_IN is captured by the previous "if". So this one
** always has to be WO_IN. */
Expr *pX = pLoop->aLTerm[j]->pExpr;
for(i=j+1; i<pLoop->u.btree.nEq; i++){
if( pLoop->aLTerm[i]->pExpr==pX ){
assert( (pLoop->aLTerm[i]->eOperator & WO_IN) );
bOnce = 0;
break;
}
}
}
continue;
}
/* Get the column number in the table (iColumn) and sort order
@ -3487,7 +3629,6 @@ static i8 wherePathSatisfiesOrderBy(
/* Find the ORDER BY term that corresponds to the j-th column
** of the index and mark that ORDER BY term off
*/
bOnce = 1;
isMatch = 0;
for(i=0; bOnce && i<nOrderBy; i++){
if( MASKBIT(i) & obSat ) continue;
@ -4681,10 +4822,12 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
sqlite3VdbeResolveLabel(v, pLevel->addrNxt);
for(j=pLevel->u.in.nIn, pIn=&pLevel->u.in.aInLoop[j-1]; j>0; j--, pIn--){
sqlite3VdbeJumpHere(v, pIn->addrInTop+1);
sqlite3VdbeAddOp2(v, pIn->eEndLoopOp, pIn->iCur, pIn->addrInTop);
VdbeCoverage(v);
VdbeCoverageIf(v, pIn->eEndLoopOp==OP_PrevIfOpen);
VdbeCoverageIf(v, pIn->eEndLoopOp==OP_NextIfOpen);
if( pIn->eEndLoopOp!=OP_Noop ){
sqlite3VdbeAddOp2(v, pIn->eEndLoopOp, pIn->iCur, pIn->addrInTop);
VdbeCoverage(v);
VdbeCoverageIf(v, pIn->eEndLoopOp==OP_PrevIfOpen);
VdbeCoverageIf(v, pIn->eEndLoopOp==OP_NextIfOpen);
}
sqlite3VdbeJumpHere(v, pIn->addrInTop-1);
}
}

View File

@ -122,6 +122,8 @@ struct WhereLoop {
union {
struct { /* Information for internal btree tables */
u16 nEq; /* Number of equality constraints */
u16 nBtm; /* Size of BTM vector */
u16 nTop; /* Size of TOP vector */
Index *pIndex; /* Index used, or NULL */
} btree;
struct { /* Information for virtual tables */
@ -246,6 +248,7 @@ struct WhereTerm {
Expr *pExpr; /* Pointer to the subexpression that is this term */
int iParent; /* Disable pWC->a[iParent] when this term disabled */
int leftCursor; /* Cursor number of X in "X <op> <expr>" */
int iField; /* Field in (?,?,?) IN (SELECT...) vector */
union {
int leftColumn; /* Column number of X in "X <op> <expr>" */
WhereOrInfo *pOrInfo; /* Extra information if (eOperator & WO_OR)!=0 */

View File

@ -21,6 +21,17 @@
#include "whereInt.h"
#ifndef SQLITE_OMIT_EXPLAIN
/*
** Return the name of the i-th column of the pIdx index.
*/
static const char *explainIndexColumnName(Index *pIdx, int i){
i = pIdx->aiColumn[i];
if( i==XN_EXPR ) return "<expr>";
if( i==XN_ROWID ) return "rowid";
return pIdx->pTable->aCol[i].zName;
}
/*
** This routine is a helper for explainIndexRange() below
**
@ -31,24 +42,32 @@
*/
static void explainAppendTerm(
StrAccum *pStr, /* The text expression being built */
int iTerm, /* Index of this term. First is zero */
const char *zColumn, /* Name of the column */
Index *pIdx, /* Index to read column names from */
int nTerm, /* Number of terms */
int iTerm, /* Zero-based index of first term. */
int bAnd, /* Non-zero to append " AND " */
const char *zOp /* Name of the operator */
){
if( iTerm ) sqlite3StrAccumAppend(pStr, " AND ", 5);
sqlite3StrAccumAppendAll(pStr, zColumn);
sqlite3StrAccumAppend(pStr, zOp, 1);
sqlite3StrAccumAppend(pStr, "?", 1);
}
int i;
/*
** Return the name of the i-th column of the pIdx index.
*/
static const char *explainIndexColumnName(Index *pIdx, int i){
i = pIdx->aiColumn[i];
if( i==XN_EXPR ) return "<expr>";
if( i==XN_ROWID ) return "rowid";
return pIdx->pTable->aCol[i].zName;
assert( nTerm>=1 );
if( bAnd ) sqlite3StrAccumAppend(pStr, " AND ", 5);
if( nTerm>1 ) sqlite3StrAccumAppend(pStr, "(", 1);
for(i=0; i<nTerm; i++){
if( i ) sqlite3StrAccumAppend(pStr, ",", 1);
sqlite3StrAccumAppendAll(pStr, explainIndexColumnName(pIdx, iTerm+i));
}
if( nTerm>1 ) sqlite3StrAccumAppend(pStr, ")", 1);
sqlite3StrAccumAppend(pStr, zOp, 1);
if( nTerm>1 ) sqlite3StrAccumAppend(pStr, "(", 1);
for(i=0; i<nTerm; i++){
if( i ) sqlite3StrAccumAppend(pStr, ",", 1);
sqlite3StrAccumAppend(pStr, "?", 1);
}
if( nTerm>1 ) sqlite3StrAccumAppend(pStr, ")", 1);
}
/*
@ -81,12 +100,11 @@ static void explainIndexRange(StrAccum *pStr, WhereLoop *pLoop){
j = i;
if( pLoop->wsFlags&WHERE_BTM_LIMIT ){
const char *z = explainIndexColumnName(pIndex, i);
explainAppendTerm(pStr, i++, z, ">");
explainAppendTerm(pStr, pIndex, pLoop->u.btree.nBtm, j, i, ">");
i = 1;
}
if( pLoop->wsFlags&WHERE_TOP_LIMIT ){
const char *z = explainIndexColumnName(pIndex, j);
explainAppendTerm(pStr, i, z, "<");
explainAppendTerm(pStr, pIndex, pLoop->u.btree.nTop, j, i, "<");
}
sqlite3StrAccumAppend(pStr, ")", 1);
}
@ -276,7 +294,7 @@ void sqlite3WhereAddScanStatus(
*/
static void disableTerm(WhereLevel *pLevel, WhereTerm *pTerm){
int nLoop = 0;
while( pTerm
while( ALWAYS(pTerm!=0)
&& (pTerm->wtFlags & TERM_CODED)==0
&& (pLevel->iLeftJoin==0 || ExprHasProperty(pTerm->pExpr, EP_FromJoin))
&& (pLevel->notReady & pTerm->prereqAll)==0
@ -332,16 +350,46 @@ static void codeApplyAffinity(Parse *pParse, int base, int n, char *zAff){
}
}
/*
** Expression pRight, which is the RHS of a comparison operation, is
** either a vector of n elements or, if n==1, a scalar expression.
** Before the comparison operation, affinity zAff is to be applied
** to the pRight values. This function modifies characters within the
** affinity string to SQLITE_AFF_BLOB if either:
**
** * the comparison will be performed with no affinity, or
** * the affinity change in zAff is guaranteed not to change the value.
*/
static void updateRangeAffinityStr(
Parse *pParse, /* Parse context */
Expr *pRight, /* RHS of comparison */
int n, /* Number of vector elements in comparison */
char *zAff /* Affinity string to modify */
){
int i;
for(i=0; i<n; i++){
Expr *p = sqlite3VectorFieldSubexpr(pRight, i);
if( sqlite3CompareAffinity(p, zAff[i])==SQLITE_AFF_BLOB
|| sqlite3ExprNeedsNoAffinityChange(p, zAff[i])
){
zAff[i] = SQLITE_AFF_BLOB;
}
}
}
/*
** Generate code for a single equality term of the WHERE clause. An equality
** term can be either X=expr or X IN (...). pTerm is the term to be
** coded.
**
** The current value for the constraint is left in register iReg.
** The current value for the constraint is left in a register, the index
** of which is returned. An attempt is made store the result in iTarget but
** this is only guaranteed for TK_ISNULL and TK_IN constraints. If the
** constraint is a TK_EQ or TK_IS, then the current value might be left in
** some other register and it is the caller's responsibility to compensate.
**
** For a constraint of the form X=expr, the expression is evaluated and its
** result is left on the stack. For constraints of the form X IN (...)
** For a constraint of the form X=expr, the expression is evaluated in
** straight-line code. For constraints of the form X IN (...)
** this routine sets up a loop that will iterate over all values of X.
*/
static int codeEqualityTerm(
@ -356,6 +404,7 @@ static int codeEqualityTerm(
Vdbe *v = pParse->pVdbe;
int iReg; /* Register holding results */
assert( pLevel->pWLoop->aLTerm[iEq]==pTerm );
assert( iTarget>0 );
if( pX->op==TK_EQ || pX->op==TK_IS ){
iReg = sqlite3ExprCodeTarget(pParse, pX->pRight, iTarget);
@ -364,10 +413,13 @@ static int codeEqualityTerm(
sqlite3VdbeAddOp2(v, OP_Null, 0, iReg);
#ifndef SQLITE_OMIT_SUBQUERY
}else{
int eType;
int eType = IN_INDEX_NOOP;
int iTab;
struct InLoop *pIn;
WhereLoop *pLoop = pLevel->pWLoop;
int i;
int nEq = 0;
int *aiMap = 0;
if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0
&& pLoop->u.btree.pIndex!=0
@ -379,7 +431,75 @@ static int codeEqualityTerm(
}
assert( pX->op==TK_IN );
iReg = iTarget;
eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0);
for(i=0; i<iEq; i++){
if( pLoop->aLTerm[i] && pLoop->aLTerm[i]->pExpr==pX ){
disableTerm(pLevel, pTerm);
return iTarget;
}
}
for(i=iEq;i<pLoop->nLTerm; i++){
if( ALWAYS(pLoop->aLTerm[i]) && pLoop->aLTerm[i]->pExpr==pX ) nEq++;
}
if( (pX->flags & EP_xIsSelect)==0 || pX->x.pSelect->pEList->nExpr==1 ){
eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, 0);
}else{
Select *pSelect = pX->x.pSelect;
sqlite3 *db = pParse->db;
ExprList *pOrigRhs = pSelect->pEList;
ExprList *pOrigLhs = pX->pLeft->x.pList;
ExprList *pRhs = 0; /* New Select.pEList for RHS */
ExprList *pLhs = 0; /* New pX->pLeft vector */
for(i=iEq;i<pLoop->nLTerm; i++){
if( pLoop->aLTerm[i]->pExpr==pX ){
int iField = pLoop->aLTerm[i]->iField - 1;
Expr *pNewRhs = sqlite3ExprDup(db, pOrigRhs->a[iField].pExpr, 0);
Expr *pNewLhs = sqlite3ExprDup(db, pOrigLhs->a[iField].pExpr, 0);
pRhs = sqlite3ExprListAppend(pParse, pRhs, pNewRhs);
pLhs = sqlite3ExprListAppend(pParse, pLhs, pNewLhs);
}
}
if( !db->mallocFailed ){
Expr *pLeft = pX->pLeft;
if( pSelect->pOrderBy ){
/* If the SELECT statement has an ORDER BY clause, zero the
** iOrderByCol variables. These are set to non-zero when an
** ORDER BY term exactly matches one of the terms of the
** result-set. Since the result-set of the SELECT statement may
** have been modified or reordered, these variables are no longer
** set correctly. Since setting them is just an optimization,
** it's easiest just to zero them here. */
ExprList *pOrderBy = pSelect->pOrderBy;
for(i=0; i<pOrderBy->nExpr; i++){
pOrderBy->a[i].u.x.iOrderByCol = 0;
}
}
/* Take care here not to generate a TK_VECTOR containing only a
** single value. Since the parser never creates such a vector, some
** of the subroutines do not handle this case. */
if( pLhs->nExpr==1 ){
pX->pLeft = pLhs->a[0].pExpr;
}else{
pLeft->x.pList = pLhs;
aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int) * nEq);
testcase( aiMap==0 );
}
pSelect->pEList = pRhs;
eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap);
testcase( aiMap!=0 && aiMap[0]!=0 );
pSelect->pEList = pOrigRhs;
pLeft->x.pList = pOrigLhs;
pX->pLeft = pLeft;
}
sqlite3ExprListDelete(pParse->db, pLhs);
sqlite3ExprListDelete(pParse->db, pRhs);
}
if( eType==IN_INDEX_INDEX_DESC ){
testcase( bRev );
bRev = !bRev;
@ -389,28 +509,46 @@ static int codeEqualityTerm(
VdbeCoverageIf(v, bRev);
VdbeCoverageIf(v, !bRev);
assert( (pLoop->wsFlags & WHERE_MULTI_OR)==0 );
pLoop->wsFlags |= WHERE_IN_ABLE;
if( pLevel->u.in.nIn==0 ){
pLevel->addrNxt = sqlite3VdbeMakeLabel(v);
}
pLevel->u.in.nIn++;
i = pLevel->u.in.nIn;
pLevel->u.in.nIn += nEq;
pLevel->u.in.aInLoop =
sqlite3DbReallocOrFree(pParse->db, pLevel->u.in.aInLoop,
sizeof(pLevel->u.in.aInLoop[0])*pLevel->u.in.nIn);
pIn = pLevel->u.in.aInLoop;
if( pIn ){
pIn += pLevel->u.in.nIn - 1;
pIn->iCur = iTab;
if( eType==IN_INDEX_ROWID ){
pIn->addrInTop = sqlite3VdbeAddOp2(v, OP_Rowid, iTab, iReg);
}else{
pIn->addrInTop = sqlite3VdbeAddOp3(v, OP_Column, iTab, 0, iReg);
int iMap = 0; /* Index in aiMap[] */
pIn += i;
for(i=iEq;i<pLoop->nLTerm; i++){
int iOut = iReg;
if( pLoop->aLTerm[i]->pExpr==pX ){
if( eType==IN_INDEX_ROWID ){
assert( nEq==1 && i==iEq );
pIn->addrInTop = sqlite3VdbeAddOp2(v, OP_Rowid, iTab, iReg);
}else{
int iCol = aiMap ? aiMap[iMap++] : 0;
iOut = iReg + i - iEq;
pIn->addrInTop = sqlite3VdbeAddOp3(v,OP_Column,iTab, iCol, iOut);
}
sqlite3VdbeAddOp1(v, OP_IsNull, iOut); VdbeCoverage(v);
if( i==iEq ){
pIn->iCur = iTab;
pIn->eEndLoopOp = bRev ? OP_PrevIfOpen : OP_NextIfOpen;
}else{
pIn->eEndLoopOp = OP_Noop;
}
pIn++;
}
}
pIn->eEndLoopOp = bRev ? OP_PrevIfOpen : OP_NextIfOpen;
sqlite3VdbeAddOp1(v, OP_IsNull, iReg); VdbeCoverage(v);
}else{
pLevel->u.in.nIn = 0;
}
sqlite3DbFree(pParse->db, aiMap);
#endif
}
disableTerm(pLevel, pTerm);
@ -536,9 +674,15 @@ static int codeAllEqualityTerms(
sqlite3VdbeAddOp2(v, OP_SCopy, r1, regBase+j);
}
}
testcase( pTerm->eOperator & WO_ISNULL );
testcase( pTerm->eOperator & WO_IN );
if( (pTerm->eOperator & (WO_ISNULL|WO_IN))==0 ){
if( pTerm->eOperator & WO_IN ){
if( pTerm->pExpr->flags & EP_xIsSelect ){
/* No affinity ever needs to be (or should be) applied to a value
** from the RHS of an "? IN (SELECT ...)" expression. The
** sqlite3FindInIndex() routine has already ensured that the
** affinity of the comparison has been applied to the value. */
if( zAff ) zAff[j] = SQLITE_AFF_BLOB;
}
}else if( (pTerm->eOperator & WO_ISNULL)==0 ){
Expr *pRight = pTerm->pExpr->pRight;
if( (pTerm->wtFlags & TERM_IS)==0 && sqlite3ExprCanBeNull(pRight) ){
sqlite3VdbeAddOp2(v, OP_IsNull, regBase+j, pLevel->addrBrk);
@ -861,6 +1005,39 @@ static void codeDeferredSeek(
}
}
/*
** If the expression passed as the second argument is a vector, generate
** code to write the first nReg elements of the vector into an array
** of registers starting with iReg.
**
** If the expression is not a vector, then nReg must be passed 1. In
** this case, generate code to evaluate the expression and leave the
** result in register iReg.
*/
static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){
assert( nReg>0 );
if( sqlite3ExprIsVector(p) ){
#ifndef SQLITE_OMIT_SUBQUERY
if( (p->flags & EP_xIsSelect) ){
Vdbe *v = pParse->pVdbe;
int iSelect = sqlite3CodeSubselect(pParse, p, 0, 0);
sqlite3VdbeAddOp3(v, OP_Copy, iSelect, iReg, nReg-1);
}else
#endif
{
int i;
ExprList *pList = p->x.pList;
assert( nReg<=pList->nExpr );
for(i=0; i<nReg; i++){
sqlite3ExprCode(pParse, pList->a[i].pExpr, iReg+i);
}
}
}else{
assert( nReg==1 );
sqlite3ExprCode(pParse, p, iReg);
}
}
/*
** Generate code for the start of the iLevel-th loop in the WHERE clause
** implementation described by pWInfo.
@ -956,7 +1133,8 @@ Bitmask sqlite3WhereCodeOneLoopStart(
codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, iTarget);
addrNotFound = pLevel->addrNxt;
}else{
sqlite3ExprCode(pParse, pTerm->pExpr->pRight, iTarget);
Expr *pRight = pTerm->pExpr->pRight;
codeExprOrVector(pParse, pRight, iTarget, 1);
}
}
sqlite3VdbeAddOp2(v, OP_Integer, pLoop->u.vtab.idxNum, iReg);
@ -1070,6 +1248,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
if( pStart ){
Expr *pX; /* The expression that defines the start bound */
int r1, rTemp; /* Registers for holding the start boundary */
int op; /* Cursor seek operation */
/* The following constant maps TK_xx codes into corresponding
** seek opcodes. It depends on a particular ordering of TK_xx
@ -1089,8 +1268,16 @@ Bitmask sqlite3WhereCodeOneLoopStart(
pX = pStart->pExpr;
assert( pX!=0 );
testcase( pStart->leftCursor!=iCur ); /* transitive constraints */
r1 = sqlite3ExprCodeTemp(pParse, pX->pRight, &rTemp);
sqlite3VdbeAddOp3(v, aMoveOp[pX->op-TK_GT], iCur, addrBrk, r1);
if( sqlite3ExprIsVector(pX->pRight) ){
r1 = rTemp = sqlite3GetTempReg(pParse);
codeExprOrVector(pParse, pX->pRight, r1, 1);
op = aMoveOp[(pX->op - TK_GT) | 0x0001];
}else{
r1 = sqlite3ExprCodeTemp(pParse, pX->pRight, &rTemp);
disableTerm(pLevel, pStart);
op = aMoveOp[(pX->op - TK_GT)];
}
sqlite3VdbeAddOp3(v, op, iCur, addrBrk, r1);
VdbeComment((v, "pk"));
VdbeCoverageIf(v, pX->op==TK_GT);
VdbeCoverageIf(v, pX->op==TK_LE);
@ -1098,7 +1285,6 @@ Bitmask sqlite3WhereCodeOneLoopStart(
VdbeCoverageIf(v, pX->op==TK_GE);
sqlite3ExprCacheAffinityChange(pParse, r1, 1);
sqlite3ReleaseTempReg(pParse, rTemp);
disableTerm(pLevel, pStart);
}else{
sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iCur, addrBrk);
VdbeCoverageIf(v, bRev==0);
@ -1112,13 +1298,17 @@ Bitmask sqlite3WhereCodeOneLoopStart(
testcase( pEnd->leftCursor!=iCur ); /* Transitive constraints */
testcase( pEnd->wtFlags & TERM_VIRTUAL );
memEndValue = ++pParse->nMem;
sqlite3ExprCode(pParse, pX->pRight, memEndValue);
if( pX->op==TK_LT || pX->op==TK_GT ){
codeExprOrVector(pParse, pX->pRight, memEndValue, 1);
if( 0==sqlite3ExprIsVector(pX->pRight)
&& (pX->op==TK_LT || pX->op==TK_GT)
){
testOp = bRev ? OP_Le : OP_Ge;
}else{
testOp = bRev ? OP_Lt : OP_Gt;
}
disableTerm(pLevel, pEnd);
if( 0==sqlite3ExprIsVector(pX->pRight) ){
disableTerm(pLevel, pEnd);
}
}
start = sqlite3VdbeCurrentAddr(v);
pLevel->op = bRev ? OP_Prev : OP_Next;
@ -1185,6 +1375,8 @@ Bitmask sqlite3WhereCodeOneLoopStart(
OP_IdxLT, /* 3: (end_constraints && bRev && endEq) */
};
u16 nEq = pLoop->u.btree.nEq; /* Number of == or IN terms */
u16 nBtm = pLoop->u.btree.nBtm; /* Length of BTM vector */
u16 nTop = pLoop->u.btree.nTop; /* Length of TOP vector */
int regBase; /* Base register holding constraint values */
WhereTerm *pRangeStart = 0; /* Inequality constraint at range start */
WhereTerm *pRangeEnd = 0; /* Inequality constraint at range end */
@ -1197,7 +1389,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
int nExtraReg = 0; /* Number of extra registers needed */
int op; /* Instruction opcode */
char *zStartAff; /* Affinity for start of range constraint */
char cEndAff = 0; /* Affinity for end of range constraint */
char *zEndAff = 0; /* Affinity for end of range constraint */
u8 bSeekPastNull = 0; /* True to seek past initial nulls */
u8 bStopAtNull = 0; /* Add condition to terminate at NULLs */
@ -1231,14 +1423,14 @@ Bitmask sqlite3WhereCodeOneLoopStart(
j = nEq;
if( pLoop->wsFlags & WHERE_BTM_LIMIT ){
pRangeStart = pLoop->aLTerm[j++];
nExtraReg = 1;
nExtraReg = MAX(nExtraReg, pLoop->u.btree.nBtm);
/* Like optimization range constraints always occur in pairs */
assert( (pRangeStart->wtFlags & TERM_LIKEOPT)==0 ||
(pLoop->wsFlags & WHERE_TOP_LIMIT)!=0 );
}
if( pLoop->wsFlags & WHERE_TOP_LIMIT ){
pRangeEnd = pLoop->aLTerm[j++];
nExtraReg = 1;
nExtraReg = MAX(nExtraReg, pLoop->u.btree.nTop);
#ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS
if( (pRangeEnd->wtFlags & TERM_LIKEOPT)!=0 ){
assert( pRangeStart!=0 ); /* LIKE opt constraints */
@ -1274,6 +1466,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
){
SWAP(WhereTerm *, pRangeEnd, pRangeStart);
SWAP(u8, bSeekPastNull, bStopAtNull);
SWAP(u8, nBtm, nTop);
}
/* Generate code to evaluate all constraint terms using == or IN
@ -1283,7 +1476,9 @@ Bitmask sqlite3WhereCodeOneLoopStart(
codeCursorHint(pTabItem, pWInfo, pLevel, pRangeEnd);
regBase = codeAllEqualityTerms(pParse,pLevel,bRev,nExtraReg,&zStartAff);
assert( zStartAff==0 || sqlite3Strlen30(zStartAff)>=nEq );
if( zStartAff ) cEndAff = zStartAff[nEq];
if( zStartAff && nTop ){
zEndAff = sqlite3DbStrDup(db, &zStartAff[nEq]);
}
addrNxt = pLevel->addrNxt;
testcase( pRangeStart && (pRangeStart->eOperator & WO_LE)!=0 );
@ -1298,7 +1493,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
nConstraint = nEq;
if( pRangeStart ){
Expr *pRight = pRangeStart->pExpr->pRight;
sqlite3ExprCode(pParse, pRight, regBase+nEq);
codeExprOrVector(pParse, pRight, regBase+nEq, nBtm);
whereLikeOptimizationStringFixup(v, pLevel, pRangeStart);
if( (pRangeStart->wtFlags & TERM_VNULL)==0
&& sqlite3ExprCanBeNull(pRight)
@ -1307,18 +1502,15 @@ Bitmask sqlite3WhereCodeOneLoopStart(
VdbeCoverage(v);
}
if( zStartAff ){
if( sqlite3CompareAffinity(pRight, zStartAff[nEq])==SQLITE_AFF_BLOB){
/* Since the comparison is to be performed with no conversions
** applied to the operands, set the affinity to apply to pRight to
** SQLITE_AFF_BLOB. */
zStartAff[nEq] = SQLITE_AFF_BLOB;
}
if( sqlite3ExprNeedsNoAffinityChange(pRight, zStartAff[nEq]) ){
zStartAff[nEq] = SQLITE_AFF_BLOB;
}
updateRangeAffinityStr(pParse, pRight, nBtm, &zStartAff[nEq]);
}
nConstraint++;
nConstraint += nBtm;
testcase( pRangeStart->wtFlags & TERM_VIRTUAL );
if( sqlite3ExprIsVector(pRight)==0 ){
disableTerm(pLevel, pRangeStart);
}else{
startEq = 1;
}
bSeekPastNull = 0;
}else if( bSeekPastNull ){
sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq);
@ -1351,7 +1543,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
if( pRangeEnd ){
Expr *pRight = pRangeEnd->pExpr->pRight;
sqlite3ExprCacheRemove(pParse, regBase+nEq, 1);
sqlite3ExprCode(pParse, pRight, regBase+nEq);
codeExprOrVector(pParse, pRight, regBase+nEq, nTop);
whereLikeOptimizationStringFixup(v, pLevel, pRangeEnd);
if( (pRangeEnd->wtFlags & TERM_VNULL)==0
&& sqlite3ExprCanBeNull(pRight)
@ -1359,19 +1551,27 @@ Bitmask sqlite3WhereCodeOneLoopStart(
sqlite3VdbeAddOp2(v, OP_IsNull, regBase+nEq, addrNxt);
VdbeCoverage(v);
}
if( sqlite3CompareAffinity(pRight, cEndAff)!=SQLITE_AFF_BLOB
&& !sqlite3ExprNeedsNoAffinityChange(pRight, cEndAff)
){
codeApplyAffinity(pParse, regBase+nEq, 1, &cEndAff);
if( zEndAff ){
updateRangeAffinityStr(pParse, pRight, nTop, zEndAff);
codeApplyAffinity(pParse, regBase+nEq, nTop, zEndAff);
}else{
assert( pParse->db->mallocFailed );
}
nConstraint++;
nConstraint += nTop;
testcase( pRangeEnd->wtFlags & TERM_VIRTUAL );
if( sqlite3ExprIsVector(pRight)==0 ){
disableTerm(pLevel, pRangeEnd);
}else{
endEq = 1;
}
}else if( bStopAtNull ){
sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq);
endEq = 0;
nConstraint++;
}
sqlite3DbFree(db, zStartAff);
sqlite3DbFree(db, zEndAff);
/* Top of the loop body */
pLevel->p2 = sqlite3VdbeCurrentAddr(v);
@ -1387,8 +1587,6 @@ Bitmask sqlite3WhereCodeOneLoopStart(
}
/* Seek the table cursor, if required */
disableTerm(pLevel, pRangeStart);
disableTerm(pLevel, pRangeEnd);
if( omitTable ){
/* pIdx is a covering index. No need to access the main table. */
}else if( HasRowid(pIdx->pTable) ){
@ -1412,9 +1610,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
iRowidReg, pPk->nKeyCol); VdbeCoverage(v);
}
/* Record the instruction used to terminate the loop. Disable
** WHERE clause terms made redundant by the index range scan.
*/
/* Record the instruction used to terminate the loop. */
if( pLoop->wsFlags & WHERE_ONEROW ){
pLevel->op = OP_Noop;
}else if( bRev ){
@ -1491,7 +1687,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
u16 wctrlFlags; /* Flags for sub-WHERE clause */
Expr *pAndExpr = 0; /* An ".. AND (...)" expression */
Table *pTab = pTabItem->pTab;
pTerm = pLoop->aLTerm[0];
assert( pTerm!=0 );
assert( pTerm->eOperator & WO_OR );

View File

@ -95,7 +95,7 @@ static int whereClauseInsert(WhereClause *pWC, Expr *p, u16 wtFlags){
/*
** Return TRUE if the given operator is one of the operators that is
** allowed for an indexable WHERE clause term. The allowed operators are
** "=", "<", ">", "<=", ">=", "IN", and "IS NULL"
** "=", "<", ">", "<=", ">=", "IN", "IS", and "IS NULL"
*/
static int allowedOp(int op){
assert( TK_GT>TK_EQ && TK_GT<TK_GE );
@ -823,7 +823,8 @@ static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){
** in any index. Return TRUE (1) if pExpr is an indexed term and return
** FALSE (0) if not. If TRUE is returned, also set *piCur to the cursor
** number of the table that is indexed and *piColumn to the column number
** of the column that is indexed, or -2 if an expression is being indexed.
** of the column that is indexed, or XN_EXPR (-2) if an expression is being
** indexed.
**
** If pExpr is a TK_COLUMN column reference, then this routine always returns
** true even if that particular column is not indexed, because the column
@ -831,6 +832,7 @@ static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){
*/
static int exprMightBeIndexed(
SrcList *pFrom, /* The FROM clause */
int op, /* The specific comparison operator */
Bitmask mPrereq, /* Bitmask of FROM clause terms referenced by pExpr */
Expr *pExpr, /* An operand of a comparison operator */
int *piCur, /* Write the referenced table cursor number here */
@ -839,6 +841,17 @@ static int exprMightBeIndexed(
Index *pIdx;
int i;
int iCur;
/* If this expression is a vector to the left or right of a
** inequality constraint (>, <, >= or <=), perform the processing
** on the first element of the vector. */
assert( TK_GT+1==TK_LE && TK_GT+2==TK_LT && TK_GT+3==TK_GE );
assert( TK_IS<TK_GE && TK_ISNULL<TK_GE && TK_IN<TK_GE );
assert( op<=TK_GE );
if( pExpr->op==TK_VECTOR && (op>=TK_GT && ALWAYS(op<=TK_GE)) ){
pExpr = pExpr->x.pList->a[0].pExpr;
}
if( pExpr->op==TK_COLUMN ){
*piCur = pExpr->iTable;
*piColumn = pExpr->iColumn;
@ -851,10 +864,10 @@ static int exprMightBeIndexed(
for(pIdx=pFrom->a[i].pTab->pIndex; pIdx; pIdx=pIdx->pNext){
if( pIdx->aColExpr==0 ) continue;
for(i=0; i<pIdx->nKeyCol; i++){
if( pIdx->aiColumn[i]!=(-2) ) continue;
if( pIdx->aiColumn[i]!=XN_EXPR ) continue;
if( sqlite3ExprCompare(pExpr, pIdx->aColExpr->a[i].pExpr, iCur)==0 ){
*piCur = iCur;
*piColumn = -2;
*piColumn = XN_EXPR;
return 1;
}
}
@ -911,6 +924,7 @@ static void exprAnalyze(
op = pExpr->op;
if( op==TK_IN ){
assert( pExpr->pRight==0 );
if( sqlite3ExprCheckIN(pParse, pExpr) ) return;
if( ExprHasProperty(pExpr, EP_xIsSelect) ){
pTerm->prereqRight = exprSelectUsage(pMaskSet, pExpr->x.pSelect);
}else{
@ -937,18 +951,26 @@ static void exprAnalyze(
Expr *pLeft = sqlite3ExprSkipCollate(pExpr->pLeft);
Expr *pRight = sqlite3ExprSkipCollate(pExpr->pRight);
u16 opMask = (pTerm->prereqRight & prereqLeft)==0 ? WO_ALL : WO_EQUIV;
if( exprMightBeIndexed(pSrc, prereqLeft, pLeft, &iCur, &iColumn) ){
if( pTerm->iField>0 ){
assert( op==TK_IN );
assert( pLeft->op==TK_VECTOR );
pLeft = pLeft->x.pList->a[pTerm->iField-1].pExpr;
}
if( exprMightBeIndexed(pSrc, op, prereqLeft, pLeft, &iCur, &iColumn) ){
pTerm->leftCursor = iCur;
pTerm->u.leftColumn = iColumn;
pTerm->eOperator = operatorMask(op) & opMask;
}
if( op==TK_IS ) pTerm->wtFlags |= TERM_IS;
if( pRight
&& exprMightBeIndexed(pSrc, pTerm->prereqRight, pRight, &iCur, &iColumn)
&& exprMightBeIndexed(pSrc, op, pTerm->prereqRight, pRight, &iCur,&iColumn)
){
WhereTerm *pNew;
Expr *pDup;
u16 eExtraOp = 0; /* Extra bits for pNew->eOperator */
assert( pTerm->iField==0 );
if( pTerm->leftCursor>=0 ){
int idxNew;
pDup = sqlite3ExprDup(db, pExpr, 0);
@ -1152,6 +1174,59 @@ static void exprAnalyze(
}
#endif /* SQLITE_OMIT_VIRTUALTABLE */
/* If there is a vector == or IS term - e.g. "(a, b) == (?, ?)" - create
** new terms for each component comparison - "a = ?" and "b = ?". The
** new terms completely replace the original vector comparison, which is
** no longer used.
**
** This is only required if at least one side of the comparison operation
** is not a sub-select. */
if( pWC->op==TK_AND
&& (pExpr->op==TK_EQ || pExpr->op==TK_IS)
&& sqlite3ExprIsVector(pExpr->pLeft)
&& ( (pExpr->pLeft->flags & EP_xIsSelect)==0
|| (pExpr->pRight->flags & EP_xIsSelect)==0
)){
int nLeft = sqlite3ExprVectorSize(pExpr->pLeft);
int i;
assert( nLeft==sqlite3ExprVectorSize(pExpr->pRight) );
for(i=0; i<nLeft; i++){
int idxNew;
Expr *pNew;
Expr *pLeft = sqlite3ExprForVectorField(pParse, pExpr->pLeft, i);
Expr *pRight = sqlite3ExprForVectorField(pParse, pExpr->pRight, i);
pNew = sqlite3PExpr(pParse, pExpr->op, pLeft, pRight, 0);
idxNew = whereClauseInsert(pWC, pNew, TERM_DYNAMIC);
exprAnalyze(pSrc, pWC, idxNew);
}
pTerm = &pWC->a[idxTerm];
pTerm->wtFlags = TERM_CODED|TERM_VIRTUAL; /* Disable the original */
pTerm->eOperator = 0;
}
/* If there is a vector IN term - e.g. "(a, b) IN (SELECT ...)" - create
** a virtual term for each vector component. The expression object
** used by each such virtual term is pExpr (the full vector IN(...)
** expression). The WhereTerm.iField variable identifies the index within
** the vector on the LHS that the virtual term represents.
**
** This only works if the RHS is a simple SELECT, not a compound
*/
if( pWC->op==TK_AND && pExpr->op==TK_IN && pTerm->iField==0
&& pExpr->pLeft->op==TK_VECTOR
&& pExpr->x.pSelect->pPrior==0
){
int i;
for(i=0; i<sqlite3ExprVectorSize(pExpr->pLeft); i++){
int idxNew;
idxNew = whereClauseInsert(pWC, pExpr, TERM_VIRTUAL);
pWC->a[idxNew].iField = i+1;
exprAnalyze(pSrc, pWC, idxNew);
markTermAsChild(pWC, idxNew, idxTerm);
}
}
#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
/* When sqlite_stat3 histogram data is available an operator of the
** form "x IS NOT NULL" can sometimes be evaluated more efficiently

View File

@ -847,6 +847,9 @@ foreach {tn x expr res nEval} {
3 5 "x() >= 5 AND x() <= 5" 1 2
4 5 "x() BETWEEN 5 AND 5" 1 1
5 9 "(x(),8) >= (9,7) AND (x(),8)<=(9,10)" 1 2
6 9 "(x(),8) BETWEEN (9,7) AND (9,10)" 1 1
} {
do_test e_expr-13.1.$tn {
set ::xcount 0
@ -1809,7 +1812,7 @@ do_expr_test e_expr-35.1.6 {
# The following block tests that errors are returned in a bunch of cases
# where a subquery returns more than one column.
#
set M {only a single result allowed for a SELECT that is part of an expression}
set M {/1 {sub-select returns [23] columns - expected 1}/}
foreach {tn sql} {
1 { SELECT (SELECT * FROM t2 UNION SELECT a+1, b+1 FROM t2) }
2 { SELECT (SELECT * FROM t2 UNION SELECT a+1, b+1 FROM t2 ORDER BY 1) }
@ -1818,7 +1821,7 @@ foreach {tn sql} {
5 { SELECT (SELECT * FROM t2) }
6 { SELECT (SELECT * FROM (SELECT 1, 2, 3)) }
} {
do_catchsql_test e_expr-35.2.$tn $sql [list 1 $M]
do_catchsql_test e_expr-35.2.$tn $sql $M
}
# EVIDENCE-OF: R-35764-28041 The result of the expression is the value

View File

@ -314,7 +314,7 @@ do_test in-9.4 {
catchsql {
SELECT b FROM t1 WHERE a NOT IN tb;
}
} {1 {only a single result allowed for a SELECT that is part of an expression}}
} {1 {sub-select returns 2 columns - expected 1}}
# IN clauses in CHECK constraints. Ticket #1645
#
@ -391,28 +391,28 @@ do_test in-12.2 {
SELECT a, b FROM t3 UNION ALL SELECT a, b FROM t2
);
}
} {1 {only a single result allowed for a SELECT that is part of an expression}}
} {1 {sub-select returns 2 columns - expected 1}}
do_test in-12.3 {
catchsql {
SELECT * FROM t2 WHERE a IN (
SELECT a, b FROM t3 UNION SELECT a, b FROM t2
);
}
} {1 {only a single result allowed for a SELECT that is part of an expression}}
} {1 {sub-select returns 2 columns - expected 1}}
do_test in-12.4 {
catchsql {
SELECT * FROM t2 WHERE a IN (
SELECT a, b FROM t3 EXCEPT SELECT a, b FROM t2
);
}
} {1 {only a single result allowed for a SELECT that is part of an expression}}
} {1 {sub-select returns 2 columns - expected 1}}
do_test in-12.5 {
catchsql {
SELECT * FROM t2 WHERE a IN (
SELECT a, b FROM t3 INTERSECT SELECT a, b FROM t2
);
}
} {1 {only a single result allowed for a SELECT that is part of an expression}}
} {1 {sub-select returns 2 columns - expected 1}}
do_test in-12.6 {
catchsql {
SELECT * FROM t2 WHERE a IN (
@ -478,7 +478,7 @@ do_test in-12.14 {
SELECT a, b FROM t3 UNION ALL SELECT a, b FROM t2
);
}
} {1 {only a single result allowed for a SELECT that is part of an expression}}
} {1 {sub-select returns 2 columns - expected 1}}
do_test in-12.15 {
catchsql {
SELECT * FROM t2 WHERE a IN (
@ -629,11 +629,12 @@ do_test in-13.14 {
}
} {}
breakpoint
do_test in-13.15 {
catchsql {
SELECT 0 WHERE (SELECT 0,0) OR (0 IN (1,2));
}
} {1 {only a single result allowed for a SELECT that is part of an expression}}
} {1 {sub-select returns 2 columns - expected 1}}
do_test in-13.X {

259
test/rowvalue.test Normal file
View File

@ -0,0 +1,259 @@
# 2016 June 17
#
# 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. The
# focus of this file is testing the SELECT statement.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set ::testprefix rowvalue
do_execsql_test 0.0 {
CREATE TABLE one(o);
INSERT INTO one VALUES(1);
}
foreach {tn v1 v2 eq ne is isnot} {
1 "1, 2, 3" "1, 2, 3" 1 0 1 0
2 "1, 0, 3" "1, 2, 3" 0 1 0 1
3 "1, 2, NULL" "1, 2, 3" {} {} 0 1
4 "1, 2, NULL" "1, 2, NULL" {} {} 1 0
5 "NULL, NULL, NULL" "NULL, NULL, NULL" {} {} 1 0
6 "1, NULL, 1" "1, 1, 1" {} {} 0 1
7 "1, NULL, 1" "1, 1, 2" 0 1 0 1
} {
do_execsql_test 1.$tn.eq "SELECT ($v1) == ($v2)" [list $eq]
do_execsql_test 1.$tn.ne "SELECT ($v1) != ($v2)" [list $ne]
do_execsql_test 1.$tn.is "SELECT ($v1) IS ($v2)" [list $is]
do_execsql_test 1.$tn.isnot "SELECT ($v1) IS NOT ($v2)" [list $isnot]
do_execsql_test 1.$tn.2.eq "SELECT (SELECT $v1) == (SELECT $v2)" [list $eq]
do_execsql_test 1.$tn.2.ne "SELECT (SELECT $v1) != (SELECT $v2)" [list $ne]
}
foreach {tn v1 v2 lt gt le ge} {
1 "(1, 1, 3)" "(1, 2, 3)" 1 0 1 0
2 "(1, 2, 3)" "(1, 2, 3)" 0 0 1 1
3 "(1, 3, 3)" "(1, 2, 3)" 0 1 0 1
4 "(1, NULL, 3)" "(1, 2, 3)" {} {} {} {}
5 "(1, 3, 3)" "(1, NULL, 3)" {} {} {} {}
6 "(1, NULL, 3)" "(1, NULL, 3)" {} {} {} {}
} {
foreach {tn2 expr res} [list \
2.$tn.lt "$v1 < $v2" $lt \
2.$tn.gt "$v1 > $v2" $gt \
2.$tn.le "$v1 <= $v2" $le \
2.$tn.ge "$v1 >= $v2" $ge \
] {
do_execsql_test $tn2 "SELECT $expr" [list $res]
set map(0) [list]
set map() [list]
set map(1) [list 1]
do_execsql_test $tn2.where1 "SELECT * FROM one WHERE $expr" $map($res)
set map(0) [list 1]
set map() [list]
set map(1) [list]
do_execsql_test $tn2.where2 "SELECT * FROM one WHERE NOT $expr" $map($res)
}
}
do_execsql_test 3.0 {
CREATE TABLE t1(x, y);
INSERT INTO t1 VALUES(1, 1);
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t1 VALUES(2, 3);
INSERT INTO t1 VALUES(2, 4);
INSERT INTO t1 VALUES(3, 5);
INSERT INTO t1 VALUES(3, 6);
}
foreach {tn r order} {
1 "(1, 1)" "ORDER BY y"
2 "(1, 1)" "ORDER BY x, y"
3 "(1, 2)" "ORDER BY x, y DESC"
4 "(3, 6)" "ORDER BY x DESC, y DESC"
5 "((3, 5))" "ORDER BY x DESC, y"
6 "(SELECT 3, 5)" "ORDER BY x DESC, y"
} {
do_execsql_test 3.$tn.1 "SELECT $r == (SELECT x,y FROM t1 $order)" 1
do_execsql_test 3.$tn.2 "SELECT $r == (SELECT * FROM t1 $order)" 1
do_execsql_test 3.$tn.3 "
SELECT (SELECT * FROM t1 $order) == (SELECT * FROM t1 $order)
" 1
do_execsql_test 3.$tn.4 "
SELECT (SELECT 0, 0) == (SELECT * FROM t1 $order)
" 0
}
foreach {tn expr res} {
1 {(2, 2) BETWEEN (2, 2) AND (3, 3)} 1
2 {(2, 2) BETWEEN (2, NULL) AND (3, 3)} {}
3 {(2, 2) BETWEEN (3, NULL) AND (3, 3)} 0
} {
do_execsql_test 4.$tn "SELECT $expr" [list $res]
}
foreach {tn expr res} {
1 {(2, 4) IN (SELECT * FROM t1)} 1
2 {(3, 4) IN (SELECT * FROM t1)} 0
3 {(NULL, 4) IN (SELECT * FROM t1)} {}
4 {(NULL, 0) IN (SELECT * FROM t1)} 0
5 {(NULL, 4) NOT IN (SELECT * FROM t1)} {}
6 {(NULL, 0) NOT IN (SELECT * FROM t1)} 1
} {
do_execsql_test 5.$tn "SELECT $expr" [list $res]
}
do_execsql_test 6.0 {
CREATE TABLE hh(a, b, c);
INSERT INTO hh VALUES('abc', 1, 'i');
INSERT INTO hh VALUES('ABC', 1, 'ii');
INSERT INTO hh VALUES('def', 2, 'iii');
INSERT INTO hh VALUES('DEF', 2, 'iv');
INSERT INTO hh VALUES('GHI', 3, 'v');
INSERT INTO hh VALUES('ghi', 3, 'vi');
CREATE INDEX hh_ab ON hh(a, b);
}
do_execsql_test 6.1 {
SELECT c FROM hh WHERE (a, b) = (SELECT 'abc', 1);
} {i}
do_execsql_test 6.2 {
SELECT c FROM hh WHERE (a, b) = (SELECT 'abc' COLLATE nocase, 1);
} {i}
do_execsql_test 6.3 {
SELECT c FROM hh WHERE a = (SELECT 'abc' COLLATE nocase) AND b = (SELECT 1);
} {i}
do_execsql_test 6.4 {
SELECT c FROM hh WHERE +a = (SELECT 'abc' COLLATE nocase) AND b = (SELECT 1);
} {i}
do_execsql_test 6.5 {
SELECT c FROM hh WHERE a = (SELECT 'abc') COLLATE nocase AND b = (SELECT 1);
} {i ii}
do_catchsql_test 6.6 {
SELECT c FROM hh WHERE (a, b) = (SELECT 'abc', 1) COLLATE nocase;
} {1 {row value misused}}
do_catchsql_test 6.7 {
SELECT c FROM hh WHERE (a, b) = 1;
} {1 {row value misused}}
do_execsql_test 6.8 {
SELECT c FROM hh WHERE (a COLLATE nocase, b) = (SELECT 'def', 2);
} {iii iv}
do_execsql_test 6.9 {
SELECT c FROM hh WHERE (a COLLATE nocase, b) IS NOT (SELECT 'def', 2);
} {i ii v vi}
do_execsql_test 6.10 {
SELECT c FROM hh WHERE (b, a) = (SELECT 2, 'def');
} {iii}
do_execsql_test 7.0 {
CREATE TABLE xy(i INTEGER PRIMARY KEY, j, k);
INSERT INTO xy VALUES(1, 1, 1);
INSERT INTO xy VALUES(2, 2, 2);
INSERT INTO xy VALUES(3, 3, 3);
INSERT INTO xy VALUES(4, 4, 4);
}
foreach {tn sql res eqp} {
1 "SELECT * FROM xy WHERE (i, j) IS (2, 2)" {2 2 2}
"0 0 0 {SEARCH TABLE xy USING INTEGER PRIMARY KEY (rowid=?)}"
2 "SELECT * FROM xy WHERE (k, j) < (2, 3)" {1 1 1 2 2 2}
"0 0 0 {SCAN TABLE xy}"
3 "SELECT * FROM xy WHERE (i, j) < (2, 3)" {1 1 1 2 2 2}
"0 0 0 {SEARCH TABLE xy USING INTEGER PRIMARY KEY (rowid<?)}"
4 "SELECT * FROM xy WHERE (i, j) > (2, 1)" {2 2 2 3 3 3 4 4 4}
"0 0 0 {SEARCH TABLE xy USING INTEGER PRIMARY KEY (rowid>?)}"
5 "SELECT * FROM xy WHERE (i, j) > ('2', 1)" {2 2 2 3 3 3 4 4 4}
"0 0 0 {SEARCH TABLE xy USING INTEGER PRIMARY KEY (rowid>?)}"
} {
do_eqp_test 7.$tn.1 $sql $eqp
do_execsql_test 7.$tn.2 $sql $res
}
do_execsql_test 8.0 {
CREATE TABLE j1(a);
}
do_execsql_test 8.1 {
SELECT * FROM j1 WHERE (select min(a) FROM j1) IN (?, ?, ?)
}
do_execsql_test 9.0 {
CREATE TABLE t2(a INTEGER PRIMARY KEY, b, c);
INSERT INTO t2 VALUES(1, 1, 1);
INSERT INTO t2 VALUES(2, 2, 2);
INSERT INTO t2 VALUES(3, 3, 3);
INSERT INTO t2 VALUES(4, 4, 4);
INSERT INTO t2 VALUES(5, 5, 5);
}
foreach {tn q res} {
1 "(a, b) > (2, 1)" {2 3 4 5}
2 "(a, b) > (2, 2)" {3 4 5}
3 "(a, b) < (4, 5)" {1 2 3 4}
4 "(a, b) < (4, 3)" {1 2 3}
} {
do_execsql_test 9.$tn "SELECT c FROM t2 WHERE $q" $res
}
do_execsql_test 10.0 {
CREATE TABLE dual(dummy); INSERT INTO dual(dummy) VALUES('X');
CREATE TABLE t3(a TEXT,b TEXT,c TEXT,d TEXT,e TEXT,f TEXT);
CREATE INDEX t3x ON t3(b,c,d,e,f);
SELECT a FROM t3
WHERE (c,d) IN (SELECT 'c','d' FROM dual)
AND (a,b,e) IN (SELECT 'a','b','d' FROM dual);
}
do_catchsql_test 11.1 {
CREATE TABLE t11(a);
SELECT * FROM t11 WHERE (a,a)<=1;
} {1 {row value misused}}
do_catchsql_test 11.2 {
SELECT * FROM t11 WHERE (a,a)<1;
} {1 {row value misused}}
do_catchsql_test 11.3 {
SELECT * FROM t11 WHERE (a,a)>=1;
} {1 {row value misused}}
do_catchsql_test 11.4 {
SELECT * FROM t11 WHERE (a,a)>1;
} {1 {row value misused}}
do_catchsql_test 11.5 {
SELECT * FROM t11 WHERE (a,a)==1;
} {1 {row value misused}}
do_catchsql_test 11.6 {
SELECT * FROM t11 WHERE (a,a)<>1;
} {1 {row value misused}}
do_catchsql_test 11.7 {
SELECT * FROM t11 WHERE (a,a) IS 1;
} {1 {row value misused}}
do_catchsql_test 11.8 {
SELECT * FROM t11 WHERE (a,a) IS NOT 1;
} {1 {row value misused}}
finish_test

279
test/rowvalue2.test Normal file
View File

@ -0,0 +1,279 @@
# 2016 June 17
#
# 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. The
# focus of this file is testing the SELECT statement.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set ::testprefix rowvalue2
do_execsql_test 1.0 {
CREATE TABLE t1(a, b, c);
INSERT INTO t1 VALUES(0, 0, 0);
INSERT INTO t1 VALUES(0, 1, 1);
INSERT INTO t1 VALUES(1, 0, 2);
INSERT INTO t1 VALUES(1, 1, 3);
CREATE INDEX i1 ON t1(a, b);
}
do_execsql_test 1.1.1 { SELECT c FROM t1 WHERE (a, b) >= (1, 0) } {2 3}
do_execsql_test 1.1.2 { SELECT c FROM t1 WHERE (a, b) > (1, 0) } {3}
#-------------------------------------------------------------------------
do_execsql_test 2.0.1 {
CREATE TABLE t2(a INTEGER, b INTEGER, c INTEGER, d INTEGER);
CREATE INDEX i2 ON t2(a, b, c);
}
do_test 2.0.2 {
foreach a {0 1 2 3} {
foreach b {0 1 2 3} {
foreach c {0 1 2 3} {
execsql { INSERT INTO t2 VALUES($a, $b, $c, $c + $b*4 + $a*16); }
}}}
} {}
do_execsql_test 2.1 {
SELECT d FROM t2 WHERE (a, b) > (2, 2);
} [db eval { SELECT d FROM t2 WHERE a>2 OR (a=2 AND b>2) }]
do_execsql_test 2.2 {
SELECT d FROM t2 WHERE (a, b) >= (2, 2);
} [db eval { SELECT d FROM t2 WHERE a>2 OR (a=2 AND b>=2) }]
do_execsql_test 2.3 {
SELECT d FROM t2 WHERE a=1 AND (b, c) >= (1, 2);
} [db eval { SELECT d FROM t2 WHERE +a=1 AND (b>1 OR (b==1 AND c>=2)) }]
do_execsql_test 2.4 {
SELECT d FROM t2 WHERE a=1 AND (b, c) > (1, 2);
} [db eval { SELECT d FROM t2 WHERE +a=1 AND (b>1 OR (b==1 AND c>2)) }]
#-------------------------------------------------------------------------
set words {
airfare airfield airfields airflow airfoil
airfoils airframe airframes airily airing
airings airless airlift airlifts airline
airliner airlines airlock airlocks airmail
airmails airman airmen airplane airplanes
arraignment arraignments arraigns arrange arranged
arrangement arrangements arranger arrangers arranges
arranging arrant array arrayed arrays
arrears arrest arrested arrester arresters
arresting arrestingly arrestor arrestors arrests
edifices edit edited editing edition
editions editor editorial editorially editorials
editors edits educable educate educated
educates educating education educational educationally
educations educator educators eel eelgrass
}
do_test 3.0 {
execsql { CREATE TABLE t3(a, b, c, w); }
foreach w $words {
set a [string range $w 0 2]
set b [string range $w 3 5]
set c [string range $w 6 end]
execsql { INSERT INTO t3 VALUES($a, $b, $c, $w) }
}
} {}
foreach {tn idx} {
IDX1 {}
IDX2 { CREATE INDEX i3 ON t3(a, b, c); }
IDX3 { CREATE INDEX i3 ON t3(a, b); }
IDX4 { CREATE INDEX i3 ON t3(a); }
} {
execsql { DROP INDEX IF EXISTS i3 }
execsql $idx
foreach w $words {
set a [string range $w 0 2]
set b [string range $w 3 5]
set c [string range $w 6 end]
foreach op [list > >= < <= == IS] {
do_execsql_test 3.1.$tn.$w.$op [subst -novar {
SELECT rowid FROM t3 WHERE (a, b, c) [set op] ($a, $b, $c)
ORDER BY +rowid
}] [db eval [subst -novar {
SELECT rowid FROM t3 WHERE w [set op] $w ORDER BY +rowid
}]]
do_execsql_test 3.1.$tn.$w.$op.subselect [subst -novar {
SELECT rowid FROM t3 WHERE (a, b, c) [set op] (
SELECT a, b, c FROM t3 WHERE w = $w
)
ORDER BY +rowid
}] [db eval [subst -novar {
SELECT rowid FROM t3 WHERE w [set op] $w ORDER BY +rowid
}]]
}
}
}
#-------------------------------------------------------------------------
#
do_execsql_test 4.0 {
CREATE TABLE t4(a, b, c);
INSERT INTO t4 VALUES(NULL, NULL, NULL);
INSERT INTO t4 VALUES(NULL, NULL, 0);
INSERT INTO t4 VALUES(NULL, NULL, 1);
INSERT INTO t4 VALUES(NULL, 0, NULL);
INSERT INTO t4 VALUES(NULL, 0, 0);
INSERT INTO t4 VALUES(NULL, 0, 1);
INSERT INTO t4 VALUES(NULL, 1, NULL);
INSERT INTO t4 VALUES(NULL, 1, 0);
INSERT INTO t4 VALUES(NULL, 1, 1);
INSERT INTO t4 VALUES( 0, NULL, NULL);
INSERT INTO t4 VALUES( 0, NULL, 0);
INSERT INTO t4 VALUES( 0, NULL, 1);
INSERT INTO t4 VALUES( 0, 0, NULL);
INSERT INTO t4 VALUES( 0, 0, 0);
INSERT INTO t4 VALUES( 0, 0, 1);
INSERT INTO t4 VALUES( 0, 1, NULL);
INSERT INTO t4 VALUES( 0, 1, 0);
INSERT INTO t4 VALUES( 0, 1, 1);
INSERT INTO t4 VALUES( 1, NULL, NULL);
INSERT INTO t4 VALUES( 1, NULL, 0);
INSERT INTO t4 VALUES( 1, NULL, 1);
INSERT INTO t4 VALUES( 1, 0, NULL);
INSERT INTO t4 VALUES( 1, 0, 0);
INSERT INTO t4 VALUES( 1, 0, 1);
INSERT INTO t4 VALUES( 1, 1, NULL);
INSERT INTO t4 VALUES( 1, 1, 0);
INSERT INTO t4 VALUES( 1, 1, 1);
}
proc make_expr1 {cList vList op} {
return "([join $cList ,]) $op ([join $vList ,])"
}
proc make_expr3 {cList vList op} {
set n [llength $cList]
set aList [list]
foreach c [lrange $cList 0 end-1] v [lrange $vList 0 end-1] {
lappend aList "$c == $v"
}
lappend aList "[lindex $cList end] $op [lindex $vList end]"
return "([join $aList { AND }])"
}
proc make_expr2 {cList vList op} {
set ret ""
switch -- $op {
== - IS {
set aList [list]
foreach c $cList v $vList { lappend aList "($c $op $v)" }
set ret [join $aList " AND "]
}
< - > {
set oList [list]
for {set i 0} {$i < [llength $cList]} {incr i} {
lappend oList [make_expr3 [lrange $cList 0 $i] [lrange $vList 0 $i] $op]
}
set ret [join $oList " OR "]
}
<= - >= {
set o2 [string range $op 0 0]
set oList [list]
for {set i 0} {$i < [llength $cList]-1} {incr i} {
lappend oList [make_expr3 [lrange $cList 0 $i] [lrange $vList 0 $i] $o2]
}
lappend oList [make_expr3 $cList $vList $op]
set ret [join $oList " OR "]
}
default {
error "Unknown op: $op"
}
}
set ret
}
foreach {tn idx} {
IDX1 {}
IDX2 { CREATE INDEX i4 ON t4(a, b, c); }
IDX3 { CREATE INDEX i4 ON t4(a, b); }
IDX4 { CREATE INDEX i4 ON t4(a); }
} {
execsql { DROP INDEX IF EXISTS i4 }
execsql $idx
foreach {tn2 vector} {
1 {0 0 0}
2 {1 1 1}
3 {0 0 NULL}
4 {0 NULL 0}
5 {NULL 0 0}
6 {1 1 NULL}
7 {1 NULL 1}
8 {NULL 1 1}
} {
foreach op { IS == < <= > >= } {
set e1 [make_expr1 {a b c} $vector $op]
set e2 [make_expr2 {a b c} $vector $op]
do_execsql_test 4.$tn.$tn2.$op \
"SELECT rowid FROM t4 WHERE $e2 ORDER BY +rowid" [
db eval "SELECT rowid FROM t4 WHERE $e1 ORDER BY +rowid"
]
}
}
}
do_execsql_test 5.0 {
CREATE TABLE r1(a TEXT, iB TEXT);
CREATE TABLE r2(x TEXT, zY INTEGER);
CREATE INDEX r1ab ON r1(a, iB);
INSERT INTO r1 VALUES(35, 35);
INSERT INTO r2 VALUES(35, 36);
INSERT INTO r2 VALUES(35, 4);
INSERT INTO r2 VALUES(35, 35);
} {}
foreach {tn lhs rhs} {
1 {x +zY} {a iB}
2 {x zY} {a iB}
3 {x zY} {a +iB}
4 {+x zY} {a iB}
5 {x zY} {+a iB}
} {
foreach op { IS == < <= > >= } {
set e1 [make_expr1 $lhs $rhs $op]
set e2 [make_expr2 $lhs $rhs $op]
do_execsql_test 5.$tn.$op \
"SELECT * FROM r1, r2 WHERE $e2 ORDER BY iB" [db eval \
"SELECT * FROM r1, r2 WHERE $e1 ORDER BY iB"
]
}
}
finish_test

206
test/rowvalue3.test Normal file
View File

@ -0,0 +1,206 @@
# 2016 June 17
#
# 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. The
# focus of this file is testing "(...) IN (SELECT ...)" expressions
# where the SELECT statement returns more than one column.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set ::testprefix rowvalue3
do_execsql_test 1.0 {
CREATE TABLE t1(a, b, c);
CREATE INDEX i1 ON t1(a, b);
INSERT INTO t1 VALUES(1, 2, 3);
INSERT INTO t1 VALUES(4, 5, 6);
INSERT INTO t1 VALUES(7, 8, 9);
}
foreach {tn sql res} {
1 "SELECT 1 WHERE (4, 5) IN (SELECT a, b FROM t1)" 1
2 "SELECT 1 WHERE (5, 5) IN (SELECT a, b FROM t1)" {}
3 "SELECT 1 WHERE (5, 4) IN (SELECT a, b FROM t1)" {}
4 "SELECT 1 WHERE (5, 4) IN (SELECT b, a FROM t1)" 1
5 "SELECT 1 WHERE (SELECT a, b FROM t1 WHERE c=6) IN (SELECT a, b FROM t1)" 1
6 "SELECT (5, 4) IN (SELECT a, b FROM t1)" 0
7 "SELECT 1 WHERE (5, 4) IN (SELECT +b, +a FROM t1)" 1
8 "SELECT (5, 4) IN (SELECT +b, +a FROM t1)" 1
9 "SELECT (1, 2) IN (SELECT rowid, b FROM t1)" 1
10 "SELECT 1 WHERE (1, 2) IN (SELECT rowid, b FROM t1)" 1
11 "SELECT 1 WHERE (1, NULL) IN (SELECT rowid, b FROM t1)" {}
12 "SELECT 1 FROM t1 WHERE (a, b) = (SELECT +a, +b FROM t1)" {1}
} {
do_execsql_test 1.$tn $sql $res
}
#-------------------------------------------------------------------------
do_execsql_test 2.0 {
CREATE TABLE z1(x, y, z);
CREATE TABLE kk(a, b);
INSERT INTO z1 VALUES('a', 'b', 'c');
INSERT INTO z1 VALUES('d', 'e', 'f');
INSERT INTO z1 VALUES('g', 'h', 'i');
-- INSERT INTO kk VALUES('y', 'y');
INSERT INTO kk VALUES('d', 'e');
-- INSERT INTO kk VALUES('x', 'x');
}
foreach {tn idx} {
1 { }
2 { CREATE INDEX z1idx ON z1(x, y) }
3 { CREATE UNIQUE INDEX z1idx ON z1(x, y) }
4 { CREATE INDEX z1idx ON kk(a, b) }
} {
execsql "DROP INDEX IF EXISTS z1idx"
execsql $idx
do_execsql_test 2.$tn.1 {
SELECT * FROM z1 WHERE x IN (SELECT a FROM kk)
} {d e f}
do_execsql_test 2.$tn.2 {
SELECT * FROM z1 WHERE (x,y) IN (SELECT a, b FROM kk)
} {d e f}
do_execsql_test 2.$tn.3 {
SELECT * FROM z1 WHERE (x, +y) IN (SELECT a, b FROM kk)
} {d e f}
do_execsql_test 2.$tn.4 {
SELECT * FROM z1 WHERE (x, +y) IN (SELECT a, b||'x' FROM kk)
} {}
do_execsql_test 2.$tn.5 {
SELECT * FROM z1 WHERE (+x, y) IN (SELECT a, b FROM kk)
} {d e f}
}
#-------------------------------------------------------------------------
#
do_execsql_test 3.0 {
CREATE TABLE c1(a, b, c, d);
INSERT INTO c1(rowid, a, b) VALUES(1, NULL, 1);
INSERT INTO c1(rowid, a, b) VALUES(2, 2, NULL);
INSERT INTO c1(rowid, a, b) VALUES(3, 2, 2);
INSERT INTO c1(rowid, a, b) VALUES(4, 3, 3);
INSERT INTO c1(rowid, a, b, c, d) VALUES(101, 'a', 'b', 1, 1);
INSERT INTO c1(rowid, a, b, c, d) VALUES(102, 'a', 'b', 1, 2);
INSERT INTO c1(rowid, a, b, c, d) VALUES(103, 'a', 'b', 1, 3);
INSERT INTO c1(rowid, a, b, c, d) VALUES(104, 'a', 'b', 2, 1);
INSERT INTO c1(rowid, a, b, c, d) VALUES(105, 'a', 'b', 2, 2);
INSERT INTO c1(rowid, a, b, c, d) VALUES(106, 'a', 'b', 2, 3);
INSERT INTO c1(rowid, a, b, c, d) VALUES(107, 'a', 'b', 3, 1);
INSERT INTO c1(rowid, a, b, c, d) VALUES(108, 'a', 'b', 3, 2);
INSERT INTO c1(rowid, a, b, c, d) VALUES(109, 'a', 'b', 3, 3);
}
foreach {tn idx} {
1 { }
2 { CREATE INDEX c1ab ON c1(a, b); }
3 { CREATE INDEX c1ba ON c1(b, a); }
4 { CREATE INDEX c1cd ON c1(c, d); }
5 { CREATE INDEX c1dc ON c1(d, c); }
} {
drop_all_indexes
foreach {tn2 sql res} {
1 "SELECT (1, 2) IN (SELECT a, b FROM c1)" {0}
2 "SELECT (1, 1) IN (SELECT a, b FROM c1)" {{}}
3 "SELECT (2, 1) IN (SELECT a, b FROM c1)" {{}}
4 "SELECT (2, 2) IN (SELECT a, b FROM c1)" {1}
5 "SELECT c, d FROM c1 WHERE (c, d) IN (SELECT d, c FROM c1)"
{ 1 1 1 2 1 3 2 1 2 2 2 3 3 1 3 2 3 3 }
6 "SELECT c, d FROM c1 WHERE (c,d) IN (SELECT d, c FROM c1) ORDER BY c DESC"
{ 3 1 3 2 3 3 2 1 2 2 2 3 1 1 1 2 1 3 }
7 {
SELECT c, d FROM c1 WHERE (c,d) IN (SELECT d, c FROM c1)
ORDER BY c DESC, d ASC
} { 3 1 3 2 3 3 2 1 2 2 2 3 1 1 1 2 1 3 }
8 {
SELECT c, d FROM c1 WHERE (c,d) IN (SELECT d, c FROM c1)
ORDER BY c ASC, d DESC
} { 1 3 1 2 1 1 2 3 2 2 2 1 3 3 3 2 3 1 }
9 {
SELECT c, d FROM c1 WHERE (c,d) IN (SELECT d, c FROM c1)
ORDER BY c ASC, d ASC
} { 1 1 1 2 1 3 2 1 2 2 2 3 3 1 3 2 3 3 }
10 {
SELECT c, d FROM c1 WHERE (c,d) IN (SELECT d, c FROM c1)
ORDER BY c DESC, d DESC
} { 3 3 3 2 3 1 2 3 2 2 2 1 1 3 1 2 1 1 }
} {
do_execsql_test 3.$tn.$tn2 $sql $res
}
}
#-------------------------------------------------------------------------
do_execsql_test 4.0 {
CREATE TABLE hh(a, b, c);
INSERT INTO hh VALUES('a', 'a', 1);
INSERT INTO hh VALUES('a', 'b', 2);
INSERT INTO hh VALUES('b', 'a', 3);
INSERT INTO hh VALUES('b', 'b', 4);
CREATE TABLE k1(x, y);
INSERT INTO k1 VALUES('a', 'a');
INSERT INTO k1 VALUES('b', 'b');
INSERT INTO k1 VALUES('a', 'b');
INSERT INTO k1 VALUES('b', 'a');
}
foreach {tn idx} {
1 { }
2 { CREATE INDEX h1 ON hh(a, b); }
3 { CREATE UNIQUE INDEX k1idx ON k1(x, y) }
4 { CREATE UNIQUE INDEX k1idx ON k1(x, y DESC) }
5 {
CREATE INDEX h1 ON hh(a, b);
CREATE UNIQUE INDEX k1idx ON k1(x, y);
}
6 {
CREATE INDEX h1 ON hh(a, b);
CREATE UNIQUE INDEX k1idx ON k1(x, y DESC);
}
} {
drop_all_indexes
execsql $idx
foreach {tn2 orderby res} {
1 "a ASC, b ASC" {1 2 3 4}
2 "a ASC, b DESC" {2 1 4 3}
3 "a DESC, b ASC" {3 4 1 2}
4 "a DESC, b DESC" {4 3 2 1}
} {
do_execsql_test 4.$tn.$tn2 "
SELECT c FROM hh WHERE (a, b) in (SELECT x, y FROM k1) ORDER BY $orderby
" $res
}
}
#-------------------------------------------------------------------------
finish_test

313
test/rowvalue4.test Normal file
View File

@ -0,0 +1,313 @@
# 2016 July 29
#
# 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. The
# focus of this file is syntax errors involving row-value constructors
# and sub-selects that return multiple arguments.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set ::testprefix rowvalue4
#-------------------------------------------------------------------------
# Test some error conditions:
#
# * row values used where they are not supported,
# * row values or sub-selects that contain/return the wrong number
# of elements.
#
do_execsql_test 1.0 {
CREATE TABLE t1(a, b, c);
CREATE INDEX t1bac ON t1(b, a, c);
}
foreach {tn e} {
1 "(1, 2, 3)"
2 "1 + (1, 2)"
3 "(1,2,3) == (1, 2)"
} {
do_catchsql_test 1.$tn "SELECT $e" {1 {row value misused}}
}
foreach {tn s error} {
1 "SELECT * FROM t1 WHERE a = (1, 2)" {row value misused}
2 "SELECT * FROM t1 WHERE b = (1, 2)" {row value misused}
3 "SELECT * FROM t1 WHERE NOT (b = (1, 2))" {row value misused}
4 "SELECT * FROM t1 LIMIT (1, 2)" {row value misused}
5 "SELECT (a, b) IN (SELECT * FROM t1) FROM t1"
{sub-select returns 3 columns - expected 2}
6 "SELECT * FROM t1 WHERE (a, b) IN (SELECT * FROM t1)"
{sub-select returns 3 columns - expected 2}
7 "SELECT * FROM t1 WHERE (c, c) <= 1" {row value misused}
8 "SELECT * FROM t1 WHERE (b, b) <= 1" {row value misused}
} {
do_catchsql_test 2.$tn "$s" [list 1 $error]
}
#-------------------------------------------------------------------------
do_execsql_test 2.0 {
CREATE TABLE t2(a, b, c, d);
INSERT INTO t2 VALUES(1, 1, 1, 1);
INSERT INTO t2 VALUES(1, 1, 2, 2);
INSERT INTO t2 VALUES(1, 1, 3, 3);
INSERT INTO t2 VALUES(1, 2, 1, 4);
INSERT INTO t2 VALUES(1, 2, 2, 5);
INSERT INTO t2 VALUES(1, 2, 3, 6);
INSERT INTO t2 VALUES(1, 3, 1, 7);
INSERT INTO t2 VALUES(1, 3, 2, 8);
INSERT INTO t2 VALUES(1, 3, 3, 9);
INSERT INTO t2 VALUES(2, 1, 1, 10);
INSERT INTO t2 VALUES(2, 1, 2, 11);
INSERT INTO t2 VALUES(2, 1, 3, 12);
INSERT INTO t2 VALUES(2, 2, 1, 13);
INSERT INTO t2 VALUES(2, 2, 2, 14);
INSERT INTO t2 VALUES(2, 2, 3, 15);
INSERT INTO t2 VALUES(2, 3, 1, 16);
INSERT INTO t2 VALUES(2, 3, 2, 17);
INSERT INTO t2 VALUES(2, 3, 3, 18);
INSERT INTO t2 VALUES(3, 1, 1, 19);
INSERT INTO t2 VALUES(3, 1, 2, 20);
INSERT INTO t2 VALUES(3, 1, 3, 21);
INSERT INTO t2 VALUES(3, 2, 1, 22);
INSERT INTO t2 VALUES(3, 2, 2, 23);
INSERT INTO t2 VALUES(3, 2, 3, 24);
INSERT INTO t2 VALUES(3, 3, 1, 25);
INSERT INTO t2 VALUES(3, 3, 2, 26);
INSERT INTO t2 VALUES(3, 3, 3, 27);
}
foreach {nm idx} {
idx1 {}
idx2 { CREATE INDEX t2abc ON t2(a, b, c); }
idx3 { CREATE INDEX t2abc ON t2(a, b DESC, c); }
idx4 { CREATE INDEX t2abc ON t2(a DESC, b DESC, c DESC); }
idx5 { CREATE INDEX t2abc ON t2(a ASC, b ASC, c ASC); }
idx6 { CREATE INDEX t2abc ON t2(a DESC, b, c); }
idx7 { CREATE INDEX t2abc ON t2(a DESC, b DESC) }
idx8 { CREATE INDEX t2abc ON t2(c, b, a); }
idx9 { CREATE INDEX t2d ON t2(d); }
idx10 { CREATE INDEX t2abc ON t2(a DESC, b, c DESC); }
} {
drop_all_indexes
execsql $idx
foreach {tn where res} {
1 "(a, b, c) < (2, 2, 2)" {1 2 3 4 5 6 7 8 9 10 11 12 13}
2 "(a, b, c) <= (2, 2, 2)" {1 2 3 4 5 6 7 8 9 10 11 12 13 14}
3 "(a, b, c) > (2, 2, 2)" {15 16 17 18 19 20 21 22 23 24 25 26 27}
4 "(a, b, c) >= (2, 2, 2)" {14 15 16 17 18 19 20 21 22 23 24 25 26 27}
5 "(a, b, c) >= (2, 2, NULL)" {16 17 18 19 20 21 22 23 24 25 26 27}
6 "(a, b, c) <= (2, 2, NULL)" {1 2 3 4 5 6 7 8 9 10 11 12}
7 "(a, b, c) >= (2, NULL, NULL)" {19 20 21 22 23 24 25 26 27}
8 "(a, b, c) <= (2, NULL, NULL)" {1 2 3 4 5 6 7 8 9}
9 "(a, b, c) < (SELECT a, b, c FROM t2 WHERE d=14)"
{1 2 3 4 5 6 7 8 9 10 11 12 13}
10 "(a, b, c) = (SELECT a, b, c FROM t2 WHERE d=14)" 14
11 "a = 2 AND (b, c) > (2, 2)" {15 16 17 18}
12 "a = 2 AND (b, c) < (3, 3) AND (b, c) > (1, 1)" {11 12 13 14 15 16 17}
} {
set result [db eval "SELECT d FROM t2 WHERE $where"]
do_test 2.1.$nm.$tn { lsort -integer $result } $res
}
foreach {tn e res} {
1 "(2, 1) IN (SELECT a, b FROM t2)" 1
2 "(2, 1) IN (SELECT a, b FROM t2 ORDER BY d)" 1
3 "(2, 1) IN (SELECT a, b FROM t2 ORDER BY d LIMIT 9)" 0
4 "(2, 1) IN (SELECT a, b FROM t2 ORDER BY d LIMIT 10)" 1
5 "(3, 3) = (SELECT a, b FROM t2 ORDER BY d DESC LIMIT 1)" 1
6 "(3, 3) = (SELECT a, b FROM t2 ORDER BY d ASC LIMIT 1)" 0
7 "(1, NULL) = (SELECT a, b FROM t2 ORDER BY d ASC LIMIT 1)" {{}}
8 "(3, 1) = (SELECT b, c FROM t2 ORDER BY d DESC LIMIT 1 OFFSET 2)" 1
9 "(3, 1) = (SELECT b, c FROM t2 ORDER BY d ASC LIMIT 1 OFFSET 2)" 0
10 "(1, NULL) = (SELECT b, c FROM t2 ORDER BY d ASC LIMIT 1 OFFSET 2)" {{}}
11 "(3, 3) = (SELECT max(a), max(b) FROM t2)" 1
12 "(3, 1) = (SELECT max(a), min(b) FROM t2)" 1
13 "(NULL, NULL) = (SELECT max(a), min(b) FROM t2)" {{}}
14 "(2, 1) IN (SELECT a, b FROM t2 ORDER BY d LIMIT 5 OFFSET 11)" 1
15 "(2, 1) IN (SELECT a, b FROM t2 ORDER BY d LIMIT 5 OFFSET 12)" 0
} {
do_execsql_test 2.2.$nm.$tn "SELECT $e" $res
}
}
ifcapable stat4 {
do_execsql_test 3.0 {
CREATE TABLE c1(a, b, c, d);
INSERT INTO c1(a, b) VALUES(1, 'a');
INSERT INTO c1(a, b) VALUES(1, 'b');
INSERT INTO c1(a, b) VALUES(1, 'c');
INSERT INTO c1(a, b) VALUES(1, 'd');
INSERT INTO c1(a, b) VALUES(1, 'e');
INSERT INTO c1(a, b) VALUES(1, 'f');
INSERT INTO c1(a, b) VALUES(1, 'g');
INSERT INTO c1(a, b) VALUES(1, 'h');
INSERT INTO c1(a, b) VALUES(1, 'i');
INSERT INTO c1(a, b) VALUES(1, 'j');
INSERT INTO c1(a, b) VALUES(1, 'k');
INSERT INTO c1(a, b) VALUES(1, 'l');
INSERT INTO c1(a, b) VALUES(1, 'm');
INSERT INTO c1(a, b) VALUES(1, 'n');
INSERT INTO c1(a, b) VALUES(1, 'o');
INSERT INTO c1(a, b) VALUES(1, 'p');
INSERT INTO c1(a, b) VALUES(2, 'a');
INSERT INTO c1(a, b) VALUES(2, 'b');
INSERT INTO c1(a, b) VALUES(2, 'c');
INSERT INTO c1(a, b) VALUES(2, 'd');
INSERT INTO c1(a, b) VALUES(2, 'e');
INSERT INTO c1(a, b) VALUES(2, 'f');
INSERT INTO c1(a, b) VALUES(2, 'g');
INSERT INTO c1(a, b) VALUES(2, 'h');
INSERT INTO c1(c, d) SELECT a, b FROM c1;
CREATE INDEX c1ab ON c1(a, b);
CREATE INDEX c1cd ON c1(c, d);
ANALYZE;
}
do_eqp_test 3.1.1 { SELECT * FROM c1 WHERE a=1 AND c=2 } {
0 0 0 {SEARCH TABLE c1 USING INDEX c1cd (c=?)}
}
do_eqp_test 3.1.2 { SELECT * FROM c1 WHERE a=1 AND b>'d' AND c=2 } {
0 0 0 {SEARCH TABLE c1 USING INDEX c1cd (c=?)}
}
do_eqp_test 3.1.3 { SELECT * FROM c1 WHERE a=1 AND b>'l' AND c=2 } {
0 0 0 {SEARCH TABLE c1 USING INDEX c1ab (a=? AND b>?)}
}
do_eqp_test 3.2.1 { SELECT * FROM c1 WHERE a=1 AND c>1 } {
0 0 0 {SEARCH TABLE c1 USING INDEX c1cd (c>?)}
}
do_eqp_test 3.2.2 { SELECT * FROM c1 WHERE a=1 AND c>0 } {
0 0 0 {SEARCH TABLE c1 USING INDEX c1ab (a=?)}
}
do_eqp_test 3.2.3 { SELECT * FROM c1 WHERE a=1 AND c>=1 } {
0 0 0 {SEARCH TABLE c1 USING INDEX c1ab (a=?)}
}
do_eqp_test 3.2.4 { SELECT * FROM c1 WHERE a=1 AND (c, d)>(1, 'c') } {
0 0 0 {SEARCH TABLE c1 USING INDEX c1ab (a=?)}
}
do_eqp_test 3.2.5 { SELECT * FROM c1 WHERE a=1 AND (c, d)>(1, 'o') } {
0 0 0 {SEARCH TABLE c1 USING INDEX c1cd ((c,d)>(?,?))}
}
do_eqp_test 3.2.6 { SELECT * FROM c1 WHERE a=1 AND (c, +b)>(1, 'c') } {
0 0 0 {SEARCH TABLE c1 USING INDEX c1ab (a=?)}
}
}
#------------------------------------------------------------------------
do_execsql_test 5.0 {
CREATE TABLE d1(x, y);
CREATE TABLE d2(a, b, c);
CREATE INDEX d2ab ON d2(a, b);
CREATE INDEX d2c ON d2(c);
WITH i(i) AS (
VALUES(1) UNION ALL SELECT i+1 FROM i WHERE i<1000
)
INSERT INTO d2 SELECT i/3, i%3, i/3 FROM i;
ANALYZE;
}
do_eqp_test 5.1 {
SELECT * FROM d2 WHERE
(a, b) IN (SELECT x, y FROM d1) AND
(c) IN (SELECT y FROM d1)
} {
0 0 0 {SEARCH TABLE d2 USING INDEX d2ab (a=? AND b=?)}
0 0 0 {EXECUTE LIST SUBQUERY 1}
1 0 0 {SCAN TABLE d1}
0 0 0 {EXECUTE LIST SUBQUERY 2}
2 0 0 {SCAN TABLE d1}
}
do_execsql_test 6.0 {
CREATE TABLE e1(a, b, c, d, e);
CREATE INDEX e1ab ON e1(a, b);
CREATE INDEX e1cde ON e1(c, d, e);
}
do_eqp_test 6.1 {
SELECT * FROM e1 WHERE (a, b) > (?, ?)
} {
0 0 0 {SEARCH TABLE e1 USING INDEX e1ab ((a,b)>(?,?))}
}
do_eqp_test 6.2 {
SELECT * FROM e1 WHERE (a, b) < (?, ?)
} {
0 0 0 {SEARCH TABLE e1 USING INDEX e1ab ((a,b)<(?,?))}
}
do_eqp_test 6.3 {
SELECT * FROM e1 WHERE c = ? AND (d, e) > (?, ?)
} {
0 0 0 {SEARCH TABLE e1 USING INDEX e1cde (c=? AND (d,e)>(?,?))}
}
do_eqp_test 6.4 {
SELECT * FROM e1 WHERE c = ? AND (d, e) < (?, ?)
} {
0 0 0 {SEARCH TABLE e1 USING INDEX e1cde (c=? AND (d,e)<(?,?))}
}
do_eqp_test 6.5 {
SELECT * FROM e1 WHERE (d, e) BETWEEN (?, ?) AND (?, ?) AND c = ?
} {
0 0 0
{SEARCH TABLE e1 USING INDEX e1cde (c=? AND (d,e)>(?,?) AND (d,e)<(?,?))}
}
#-------------------------------------------------------------------------
do_execsql_test 7.1 {
CREATE TABLE f1(a, b, c);
CREATE INDEX f1ab ON f1(a, b);
}
do_catchsql_test 7.2 {
SELECT (a COLLATE nocase, b) IN (SELECT a, b FROM f1) FROM f1;
} {0 {}}
do_catchsql_test 7.3 {
SELECT (a COLLATE nose, b) IN (SELECT a, b FROM f1) FROM f1;
} {1 {no such collation sequence: nose}}
do_catchsql_test 7.4 {
SELECT * FROM f1 WHERE (?, ? COLLATE nose) > (a, b);
} {1 {no such collation sequence: nose}}
#-------------------------------------------------------------------------
drop_all_tables
do_execsql_test 8.1 {
CREATE TABLE c1(x, y);
CREATE TABLE c2(a, b, c);
CREATE INDEX c2ab ON c2(a, b);
CREATE INDEX c2c ON c2(c);
CREATE TABLE c3(d);
}
do_catchsql_test 8.2 {
SELECT * FROM c2 CROSS JOIN c3 WHERE
( (a, b) == (SELECT x, y FROM c1) AND c3.d = c ) OR
( c == (SELECT x, y FROM c1) AND c3.d = c )
} {1 {row value misused}}
finish_test

110
test/rowvalue5.test Normal file
View File

@ -0,0 +1,110 @@
# 2016 July 29
#
# 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. The
# focus of this file is syntax errors involving row-values and
# virtual tables.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set ::testprefix rowvalue5
proc vtab_command {method args} {
switch -- $method {
xConnect {
return "CREATE TABLE t1(a, b, c, d, expr)"
}
xBestIndex {
set COL(0) a
set COL(1) b
set COL(2) c
set COL(3) d
set COL(4) expr
set OP(eq) =
set OP(ne) !=
set OP(gt) >
set OP(le) <=
set OP(lt) <
set OP(ge) >=
set OP(match) MATCH
set OP(like) LIKE
set OP(glob) GLOB
set OP(regexp) REGEXP
set clist [lindex $args 0]
set ret [list]
set elist [list]
set i 0
foreach c $clist {
array set C $c
if {$C(usable)} {
lappend ret omit $i
lappend elist "$COL($C(column)) $OP($C(op)) %$i%"
}
incr i
}
lappend ret idxstr [join $elist " AND "]
#puts "xBestIndex: $ret"
return $ret
}
xFilter {
foreach {idxnum idxstr arglist} $args {}
set i 0
set ee $idxstr
foreach a $arglist {
if {[string is double $a]==0} {
set a "'[string map {' ''} $a]'"
}
set ee [string map [list "%$i%" $a] $ee]
incr i
}
set ee [string map [list "'" "''"] $ee]
set ret [list sql "SELECT 1, 'a', 'b', 'c', 'd', '$ee'"]
#puts "xFilter: $ret"
return $ret
}
}
return {}
}
register_tcl_module db
do_execsql_test 1.0 {
CREATE VIRTUAL TABLE x1 USING tcl(vtab_command);
} {}
foreach {tn where res} {
1 "1" {{}}
2 "a=1" {{a = 1}}
3 "a=1 AND 4 = b" {{a = 1 AND b = 4}}
4 "c>'hello'" {{c > 'hello'}}
5 "c<='hel''lo'" {{c <= 'hel''lo'}}
6 "(a, b) = (SELECT 9, 10)" {{a = 9 AND b = 10}}
7 "(+a, b) = (SELECT 'a', 'b')" {{b = 'b'}}
8 "(a, +b) = (SELECT 'a', 'b')" {{a = 'a'}}
11 "(+a, b) IN (SELECT 'a', 'b')" {{b = 'b'}}
12 "(a, +b) IN (SELECT 'a', 'b')" {{a = 'a'}}
13 "(a, b) < ('d', 'e')" {{a <= 'd'}}
14 "(a, b) < ('a', 'c')" {{a <= 'a'}}
15 "(a, b) <= ('a', 'b')" {{a <= 'a'}}
16 "(a, b) < ('a', 'b')" {}
} {
do_execsql_test 1.$tn "SELECT expr FROM x1 WHERE $where" $res
}
finish_test

36
test/rowvalue6.test Normal file
View File

@ -0,0 +1,36 @@
# 2016-08-18
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
# The focus of this file is handling of NULL values in row-value IN
# expressions.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set ::testprefix rowvalue6
do_execsql_test 1.1 {
CREATE TABLE t1(a,b,c);
CREATE INDEX t1x1 ON t1(a,b);
INSERT INTO t1 VALUES(1,NULL,200);
CREATE TABLE t2(x,y,z);
INSERT INTO t2 VALUES(1,NULL,55);
SELECT c FROM t1 WHERE (a,b) IN (SELECT x,y FROM t2 WHERE z==55);
} {}
do_execsql_test 1.2 {
INSERT INTO t1 VALUES(2,3,400);
INSERT INTO t2 VALUES(2,3,55);
SELECT c FROM t1 WHERE (a,b) IN (SELECT x,y FROM t2 WHERE z==55);
} {400}
finish_test

58
test/rowvalue7.test Normal file
View File

@ -0,0 +1,58 @@
# 2016-08-18
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
# The focus of this file is vector assignments in the SET clause of
# an UPDATE statement.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set ::testprefix rowvalue7
do_execsql_test 1.1 {
CREATE TABLE t1(a,b,c,d);
CREATE INDEX t1x ON t1(a,b);
INSERT INTO t1(a,b,c,d) VALUES(1,2,0,0),(3,4,0,0),(5,6,0,0);
CREATE TABLE t2(w,x,y,z);
CREATE INDEX t2x ON t2(w,x);
INSERT INTO t2(w,x,y,z) VALUES(1,2,11,22),(8,9,88,99),(3,5,33,55),(5,6,55,66);
SELECT *,'|' FROM t1 ORDER BY a;
} {1 2 0 0 | 3 4 0 0 | 5 6 0 0 |}
do_execsql_test 1.2 {
UPDATE t1 SET (c,d) = (SELECT y,z FROM t2 WHERE (w,x)=(a,b));
SELECT *,'|' FROM t1 ORDER BY a;
} {1 2 11 22 | 3 4 {} {} | 5 6 55 66 |}
do_execsql_test 1.3 {
UPDATE t1 SET (c,d) = (SELECT y,z FROM t2 WHERE w=a);
SELECT *,'|' FROM t1 ORDER BY a;
} {1 2 11 22 | 3 4 33 55 | 5 6 55 66 |}
do_execsql_test 1.4 {
UPDATE t1 SET (c) = 99 WHERE a=3;
SELECT *,'|' FROM t1 ORDER BY a;
} {1 2 11 22 | 3 4 99 55 | 5 6 55 66 |}
do_execsql_test 1.5 {
UPDATE t1 SET b = 8, (c,d) = (SELECT 123,456) WHERE a=3;
SELECT *,'|' FROM t1 ORDER BY a;
} {1 2 11 22 | 3 8 123 456 | 5 6 55 66 |}
do_catchsql_test 2.1 {
UPDATE t1 SET (c,d) = (SELECT x,y,z FROM t2 WHERE w=a);
} {1 {2 columns assigned 3 values}}
do_catchsql_test 2.2 {
UPDATE t1 SET (b,c,d) = (SELECT x,y FROM t2 WHERE w=a);
} {1 {3 columns assigned 2 values}}
finish_test

59
test/rowvalue8.test Normal file
View File

@ -0,0 +1,59 @@
# 2016-08-22
#
# 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.
#
#***********************************************************************
# Use of row values in CASE statements.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set ::testprefix rowvalue8
do_execsql_test 1.1 {
CREATE TABLE t1(a INTEGER PRIMARY KEY,b,c,d);
INSERT INTO t1(a,b,c,d) VALUES
(1,1,2,3),
(2,2,3,4),
(3,1,2,4),
(4,2,3,5),
(5,3,4,6),
(6,4,5,9);
SELECT a, CASE (b,c) WHEN (1,2) THEN 'aleph'
WHEN (2,3) THEN 'bet'
WHEN (3,4) THEN 'gimel'
ELSE '-' END,
'|'
FROM t1
ORDER BY a;
} {1 aleph | 2 bet | 3 aleph | 4 bet | 5 gimel | 6 - |}
do_execsql_test 1.2 {
SELECT a, CASE (b,c,d) WHEN (1,2,3) THEN 'aleph'
WHEN (2,3,4) THEN 'bet'
WHEN (3,4,6) THEN 'gimel'
ELSE '-' END,
'|'
FROM t1
ORDER BY a;
} {1 aleph | 2 bet | 3 - | 4 - | 5 gimel | 6 - |}
do_execsql_test 2.1 {
CREATE TABLE t2(x INTEGER PRIMARY KEY, y);
INSERT INTO t2(x,y) VALUES(1,6),(2,5),(3,4),(4,3),(5,2),(6,1);
SELECT x, CASE (SELECT b,c FROM t1 WHERE a=y)
WHEN (1,2) THEN 'aleph'
WHEN (2,3) THEN 'bet'
WHEN (3,4) THEN 'gimel'
ELSE '-' END,
'|'
FROM t2
ORDER BY +x;
} {1 - | 2 gimel | 3 bet | 4 aleph | 5 bet | 6 aleph |}
finish_test

302
test/rowvalue9.test Normal file
View File

@ -0,0 +1,302 @@
# 2016 September 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 implements regression tests for SQLite library. The
# focus of this file is testing SQL statements that use row value
# constructors.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set ::testprefix rowvalue9
# Tests:
#
# 1.*: Test that affinities are handled correctly by various row-value
# operations without indexes.
#
# 2.*: Test an affinity bug that came up during testing.
#
# 3.*: Test a row-value version of the bug tested by 2.*.
#
# 4.*: Test that affinities are handled correctly by various row-value
# operations with assorted indexes.
#
do_execsql_test 1.0.1 {
CREATE TABLE a1(c, b INTEGER, a TEXT, PRIMARY KEY(a, b));
INSERT INTO a1 (rowid, c, b, a) VALUES(3, '0x03', 1, 1);
INSERT INTO a1 (rowid, c, b, a) VALUES(14, '0x0E', 2, 2);
INSERT INTO a1 (rowid, c, b, a) VALUES(15, '0x0F', 3, 3);
INSERT INTO a1 (rowid, c, b, a) VALUES(92, '0x5C', 4, 4);
CREATE TABLE a2(x BLOB, y BLOB);
INSERT INTO a2(x, y) VALUES(1, 1);
INSERT INTO a2(x, y) VALUES(2, '2');
INSERT INTO a2(x, y) VALUES('3', 3);
INSERT INTO a2(x, y) VALUES('4', '4');
}
do_execsql_test 1.0.2 {
SELECT x, typeof(x), y, typeof(y) FROM a2 ORDER BY rowid
} {
1 integer 1 integer
2 integer 2 text
3 text 3 integer
4 text 4 text
}
do_execsql_test 1.1.1 {
SELECT (SELECT rowid FROM a1 WHERE a=x AND b=y) FROM a2
} {{} {} 15 92}
do_execsql_test 1.1.2 {
SELECT (SELECT rowid FROM a1 WHERE (a, b) = (x, y)) FROM a2
} {{} {} 15 92}
do_execsql_test 1.2.3 {
SELECT a1.rowid FROM a1, a2 WHERE a=x AND b=y;
} {15 92}
do_execsql_test 1.2.4 {
SELECT a1.rowid FROM a1, a2 WHERE (a, b) = (x, y)
} {15 92}
do_execsql_test 1.3.1 {
SELECT a1.rowid FROM a1, a2 WHERE coalesce(NULL,x)=a AND coalesce(NULL,y)=b
} {3 14 15 92}
do_execsql_test 1.3.2 {
SELECT a1.rowid FROM a1, a2
WHERE (coalesce(NULL,x), coalesce(NULL,y)) = (a, b)
} {3 14 15 92}
do_execsql_test 1.4.1 {
SELECT a1.rowid FROM a1, a2 WHERE +x=a AND +y=b
} {3 14 15 92}
do_execsql_test 1.4.2 {
SELECT a1.rowid FROM a1, a2 WHERE (+x, +y) = (a, b)
} {3 14 15 92}
do_execsql_test 1.5.1 {
SELECT (SELECT rowid FROM a1 WHERE a=+x AND b=+y) FROM a2
} {3 14 15 92}
do_execsql_test 1.5.2 {
SELECT (SELECT rowid FROM a1 WHERE (a, b) = (+x, +y)) FROM a2
} {3 14 15 92}
do_execsql_test 1.5.3 {
SELECT (SELECT rowid FROM a1 WHERE (+x, +y) = (a, b)) FROM a2
} {3 14 15 92}
do_execsql_test 1.6.1 {
SELECT a1.rowid FROM a1 WHERE (a, b) IN (SELECT x, y FROM a2)
} {15 92}
do_execsql_test 1.6.2 {
SELECT a1.rowid FROM a1, a2 WHERE EXISTS (
SELECT 1 FROM a1 WHERE a=x AND b=y
)
} {3 14 15 92 3 14 15 92}
# Test that [199df416] is fixed.
#
do_execsql_test 2.1 {
CREATE TABLE b1(a TEXT);
CREATE TABLE b2(x BLOB);
INSERT INTO b1 VALUES(1);
INSERT INTO b2 VALUES(1);
}
do_execsql_test 2.2 { SELECT * FROM b1, b2 WHERE a=x; } {}
do_execsql_test 2.3 { SELECT * FROM b1 WHERE a IN (SELECT x FROM b2) } {}
do_execsql_test 2.4 { CREATE UNIQUE INDEX b1a ON b1(a); }
do_execsql_test 2.5 { SELECT * FROM b1 WHERE a IN (SELECT x FROM b2) } {}
# Test that a multi-column version of the query that revealed problem
# [199df416] also works.
#
do_execsql_test 3.1 {
CREATE TABLE c1(a INTEGER, b TEXT);
INSERT INTO c1 VALUES(1, 1);
CREATE TABLE c2(x BLOB, y BLOB);
INSERT INTO c2 VALUES(1, 1);
}
do_execsql_test 3.2 {
SELECT * FROM c1 WHERE (a, b) IN (SELECT x, y FROM c2)
} {}
do_execsql_test 3.3 {
CREATE UNIQUE INDEX c1ab ON c1(a, b);
SELECT * FROM c1 WHERE (a, b) IN (SELECT x, y FROM c2)
} {}
do_execsql_test 3.4 {
SELECT * FROM c1 WHERE (a, +b) IN (SELECT x, y FROM c2)
} {}
do_execsql_test 3.5 {
SELECT c1.rowid FROM c1 WHERE b = (SELECT y FROM c2);
} {}
do_execsql_test 3.6 {
SELECT c1.rowid FROM c1 WHERE (a, b) = (SELECT x, y FROM c2);
} {}
#-------------------------------------------------------------------------
#
do_execsql_test 4.0 {
CREATE TABLE d1(a TEXT, b INTEGER, c NUMERIC);
CREATE TABLE d2(x BLOB, y BLOB);
INSERT INTO d1 VALUES(1, 1, 1);
INSERT INTO d1 VALUES(2, 2, 2);
INSERT INTO d1 VALUES(3, 3, 3);
INSERT INTO d1 VALUES(4, 4, 4);
INSERT INTO d2 VALUES (1, 1);
INSERT INTO d2 VALUES (2, '2');
INSERT INTO d2 VALUES ('3', 3);
INSERT INTO d2 VALUES ('4', '4');
}
foreach {tn idx} {
1 {}
2 { CREATE INDEX idx ON d1(a) }
3 { CREATE INDEX idx ON d1(a, c) }
4 { CREATE INDEX idx ON d1(c) }
5 { CREATE INDEX idx ON d1(c, a) }
6 {
CREATE INDEX idx ON d1(c, a) ;
CREATE INDEX idx1 ON d2(x, y);
}
7 {
CREATE INDEX idx ON d1(c, a) ;
CREATE UNIQUE INDEX idx2 ON d2(x, y) ;
}
8 {
CREATE INDEX idx ON d1(c) ;
CREATE UNIQUE INDEX idx2 ON d2(x);
}
} {
execsql { DROP INDEX IF EXISTS idx }
execsql { DROP INDEX IF EXISTS idx2 }
execsql { DROP INDEX IF EXISTS idx3 }
execsql $idx
do_execsql_test 4.$tn.1 {
SELECT rowid FROM d1 WHERE (a, c) IN (SELECT x, y FROM d2);
} {3 4}
do_execsql_test 4.$tn.2 {
SELECT rowid FROM d1 WHERE (c, a) IN (SELECT x, y FROM d2);
} {2 4}
do_execsql_test 4.$tn.3 {
SELECT rowid FROM d1 WHERE (+c, a) IN (SELECT x, y FROM d2);
} {2}
do_execsql_test 4.$tn.4 {
SELECT rowid FROM d1 WHERE (c, a) = (
SELECT x, y FROM d2 WHERE d2.rowid=d1.rowid
);
} {2 4}
do_execsql_test 4.$tn.5 {
SELECT d1.rowid FROM d1, d2 WHERE a = y;
} {2 4}
do_execsql_test 4.$tn.6 {
SELECT d1.rowid FROM d1 WHERE a = (
SELECT y FROM d2 where d2.rowid=d1.rowid
);
} {2 4}
}
do_execsql_test 5.0 {
CREATE TABLE e1(a TEXT, c NUMERIC);
CREATE TABLE e2(x BLOB, y BLOB);
INSERT INTO e1 VALUES(2, 2);
INSERT INTO e2 VALUES ('2', 2);
INSERT INTO e2 VALUES ('2', '2');
INSERT INTO e2 VALUES ('2', '2.0');
CREATE INDEX e1c ON e1(c);
}
do_execsql_test 5.1 {
SELECT rowid FROM e1 WHERE (a, c) IN (SELECT x, y FROM e2);
} {1}
do_execsql_test 5.2 {
SELECT rowid FROM e2 WHERE rowid IN (SELECT +c FROM e1);
} {2}
do_execsql_test 5.3 {
SELECT rowid FROM e2 WHERE rowid IN (SELECT 0+c FROM e1);
} {2}
#-------------------------------------------------------------------------
#
do_execsql_test 6.0 {
CREATE TABLE f1(a, b);
CREATE TABLE f2(c, d);
CREATE TABLE f3(e, f);
}
do_execsql_test 6.1 {
SELECT * FROM f3 WHERE (e, f) IN (
SELECT a, b FROM f1 UNION ALL SELECT c, d FROM f2
);
}
do_execsql_test 6.2 {
CREATE INDEX f3e ON f3(e);
SELECT * FROM f3 WHERE (e, f) IN (
SELECT a, b FROM f1 UNION ALL SELECT c, d FROM f2
);
}
#-------------------------------------------------------------------------
#
do_execsql_test 7.0 {
CREATE TABLE g1(a, b);
INSERT INTO g1 VALUES
(1, 1), (1, 2), (1, 3), (1, 'i'), (1, 'j'),
(1, 6), (1, 7), (1, 8), (1, 9), (1, 10),
(1, 4), (1, 5);
CREATE TABLE g2(x, y);
CREATE INDEX g2x ON g2(x);
INSERT INTO g2 VALUES(1, 4);
INSERT INTO g2 VALUES(1, 5);
}
do_execsql_test 7.1 {
SELECT * FROM g2 WHERE (x, y) IN (
SELECT a, b FROM g1 ORDER BY +a, +b LIMIT 10
);
} { 1 4 1 5 }
do_execsql_test 7.2 {
SELECT * FROM g2 WHERE (x, y) IN (
SELECT a, b FROM g1 ORDER BY a, b LIMIT 10
);
} { 1 4 1 5 }
do_execsql_test 7.3 {
SELECT * FROM g2 WHERE (x, y) IN (
SELECT a, b FROM g1 ORDER BY 1, 2 LIMIT 10
);
} { 1 4 1 5 }
finish_test

72
test/rowvaluefault.test Normal file
View File

@ -0,0 +1,72 @@
# 2016 June 17
#
# 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.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/malloc_common.tcl
set ::testprefix rowvaluefault
do_execsql_test 1.0 {
CREATE TABLE xyz(one, two, thr, fou);
INSERT INTO xyz VALUES('A', 'A', 'A', 1);
INSERT INTO xyz VALUES('B', 'B', 'B', 2);
INSERT INTO xyz VALUES('C', 'C', 'C', 3);
INSERT INTO xyz VALUES('D', 'D', 'D', 4);
CREATE UNIQUE INDEX xyz_one_two ON xyz(one, two);
}
do_faultsim_test 1 -faults oom* -body {
execsql { SELECT fou FROM xyz WHERE (one, two, thr) = ('B', 'B', 'B') }
} -test {
faultsim_test_result {0 2}
}
do_faultsim_test 2 -faults oom* -body {
execsql { SELECT fou FROM xyz WHERE (two, thr) IS ('C', 'C') }
} -test {
faultsim_test_result {0 3}
}
do_faultsim_test 3 -faults oom* -body {
execsql { SELECT fou FROM xyz WHERE (one, two, thr) > ('B', 'B', 'B') }
} -test {
faultsim_test_result {0 {3 4}}
}
do_faultsim_test 4 -faults oom* -body {
execsql { SELECT fou FROM xyz WHERE (one, two) IN (SELECT one, two FROM xyz) }
} -test {
faultsim_test_result {0 {1 2 3 4}}
}
do_faultsim_test 5 -faults oom* -body {
execsql {
SELECT fou FROM xyz
WHERE (one, two, thr) IN (SELECT one, two, thr FROM xyz)
}
} -test {
faultsim_test_result {0 {1 2 3 4}}
}
do_faultsim_test 6 -faults oom* -body {
execsql {
SELECT fou FROM xyz
WHERE (one, two, thr) BETWEEN ('B', 'B', 'B') AND ('C', 'C', 'C') }
} -test {
faultsim_test_result {0 {2 3}}
}
finish_test

View File

@ -114,26 +114,22 @@ ifcapable {subquery && compound} {
CREATE TABLE t2(a,b);
SELECT 5 IN (SELECT a,b FROM t2);
}
} [list 1 \
{only a single result allowed for a SELECT that is part of an expression}]
} {1 {sub-select returns 2 columns - expected 1}}
do_test select7-5.2 {
catchsql {
SELECT 5 IN (SELECT * FROM t2);
}
} [list 1 \
{only a single result allowed for a SELECT that is part of an expression}]
} {1 {sub-select returns 2 columns - expected 1}}
do_test select7-5.3 {
catchsql {
SELECT 5 IN (SELECT a,b FROM t2 UNION SELECT b,a FROM t2);
}
} [list 1 \
{only a single result allowed for a SELECT that is part of an expression}]
} {1 {sub-select returns 2 columns - expected 1}}
do_test select7-5.4 {
catchsql {
SELECT 5 IN (SELECT * FROM t2 UNION SELECT * FROM t2);
}
} [list 1 \
{only a single result allowed for a SELECT that is part of an expression}]
} {1 {sub-select returns 2 columns - expected 1}}
}
# Verify that an error occurs if you have too many terms on a

View File

@ -40,7 +40,7 @@ do_test subselect-1.1 {
do_test subselect-1.2 {
set v [catch {execsql {SELECT * FROM t1 WHERE a = (SELECT * FROM t1)}} msg]
lappend v $msg
} {1 {only a single result allowed for a SELECT that is part of an expression}}
} {1 {row value misused}}
# A subselect without an aggregate.
#

View File

@ -25,6 +25,7 @@
# copy_file FROM TO
# delete_file FILENAME
# drop_all_tables ?DB?
# drop_all_indexes ?DB?
# forcecopy FROM TO
# forcedelete FILENAME
#
@ -1289,9 +1290,9 @@ proc explain_i {sql {db db}} {
set D ""
}
foreach opcode {
Seek SeekGe SeekGt SeekLe SeekLt NotFound Last Rewind
Seek SeekGE SeekGT SeekLE SeekLT NotFound Last Rewind
NoConflict Next Prev VNext VPrev VFilter
SorterSort SorterNext
SorterSort SorterNext NextIfOpen
} {
set color($opcode) $B
}
@ -1312,9 +1313,15 @@ proc explain_i {sql {db db}} {
set bSeenGoto 1
}
if {$opcode=="Once"} {
for {set i $addr} {$i<$p2} {incr i} {
set star($i) $addr
}
}
if {$opcode=="Next" || $opcode=="Prev"
|| $opcode=="VNext" || $opcode=="VPrev"
|| $opcode=="SorterNext"
|| $opcode=="SorterNext" || $opcode=="NextIfOpen"
} {
for {set i $p2} {$i<$addr} {incr i} {
incr x($i) 2
@ -1338,6 +1345,12 @@ proc explain_i {sql {db db}} {
}
set I [string repeat " " $x($addr)]
if {[info exists star($addr)]} {
set ii [expr $x($star($addr))]
append I " "
set I [string replace $I $ii $ii *]
}
set col ""
catch { set col $color($opcode) }
@ -1939,6 +1952,16 @@ proc drop_all_tables {{db db}} {
}
}
# Drop all auxiliary indexes from the main database opened by handle [db].
#
proc drop_all_indexes {{db db}} {
set L [$db eval {
SELECT name FROM sqlite_master WHERE type='index' AND sql LIKE 'create%'
}]
foreach idx $L { $db eval "DROP INDEX $idx" }
}
#-------------------------------------------------------------------------
# If a test script is executed with global variable $::G(perm:name) set to
# "wal", then the tests are run in WAL mode. Otherwise, they should be run

View File

@ -333,7 +333,7 @@ ifcapable subquery {
test_boolset types2-8.8 {o IN (SELECT t FROM t4)} {7}
test_boolset types2-8.9 {i IN (SELECT o FROM t4)} {9 10 11 12}
test_boolset types2-8.6 {n IN (SELECT o FROM t4)} {9 10 11 12}
test_boolset types2-8.7 {t IN (SELECT o FROM t4)} {9 11}
test_boolset types2-8.7 {t IN (SELECT o FROM t4)} {}
test_boolset types2-8.8 {o IN (SELECT o FROM t4)} {9 10}
}

View File

@ -37,6 +37,8 @@ set extras {
UMINUS
UPLUS
REGISTER
VECTOR
SELECT_COLUMN
ASTERISK
SPAN
SPACE