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:
commit
f78dcd1b96
60
manifest
60
manifest
@ -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
|
||||
|
@ -1 +1 @@
|
||||
12b7782a9af91eab913e159149cb28b3f5a6557c
|
||||
ddb5f0558c44569913d22781ab78f3e9b58d7aea
|
1217
src/expr.c
1217
src/expr.c
File diff suppressed because it is too large
Load Diff
107
src/in-operator.md
Normal file
107
src/in-operator.md
Normal 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.
|
17
src/parse.y
17
src/parse.y
@ -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).
|
||||
|
@ -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;
|
||||
}
|
||||
|
94
src/select.c
94
src/select.c
@ -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)
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
196
src/vdbe.c
196
src/vdbe.c
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
231
src/where.c
231
src/where.c
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 */
|
||||
|
338
src/wherecode.c
338
src/wherecode.c
@ -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 );
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
15
test/in.test
15
test/in.test
@ -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
259
test/rowvalue.test
Normal 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
279
test/rowvalue2.test
Normal 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
206
test/rowvalue3.test
Normal 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
313
test/rowvalue4.test
Normal 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
110
test/rowvalue5.test
Normal 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
36
test/rowvalue6.test
Normal 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
58
test/rowvalue7.test
Normal 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
59
test/rowvalue8.test
Normal 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
302
test/rowvalue9.test
Normal 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
72
test/rowvaluefault.test
Normal 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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
#
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,8 @@ set extras {
|
||||
UMINUS
|
||||
UPLUS
|
||||
REGISTER
|
||||
VECTOR
|
||||
SELECT_COLUMN
|
||||
ASTERISK
|
||||
SPAN
|
||||
SPACE
|
||||
|
Loading…
Reference in New Issue
Block a user