Commit Graph

62 Commits

Author SHA1 Message Date
Nikita Sivukhin
05f0ee6a72 add more integration in order to properly skip backing_btree index_method 2025-10-27 17:00:26 +04:00
Nikita Sivukhin
0fb149c4c9 fix bug 2025-10-22 17:44:02 +04:00
Nikita Sivukhin
bf77862fab Merge branch 'main' into order-by-heap-sort 2025-10-22 11:44:55 +04:00
Nikita Sivukhin
4b3689e9e7 avoid doing work in case of heap-sort optimization 2025-10-15 17:27:22 +04:00
Nikita Sivukhin
af4c1e8bd4 use proper register for limit 2025-10-15 17:27:22 +04:00
Nikita Sivukhin
b065e7d380 emit Sequence column for heap-sort in order to distinguish between rows with same order by key and result columns 2025-10-15 17:27:22 +04:00
Nikita Sivukhin
5868270b06 fix clippy 2025-10-15 17:27:22 +04:00
Nikita Sivukhin
1a24139359 fix limit for order by queries with heap-sort style execution 2025-10-15 17:27:22 +04:00
Nikita Sivukhin
7c919314a9 use heap-sort style algorithm for order by ... limit k queries 2025-10-15 17:27:22 +04:00
Jussi Saurio
d42f3c7cbb Collate: compute collations properly for ORDER BY 2025-10-02 21:49:33 +03:00
Nikita Sivukhin
63a9fa8c28 fix handling of offset parameter set through variable
- before the fix db generated following plan:

turso> EXPLAIN SELECT * FROM users LIMIT ? OFFSET ?;
addr  opcode             p1    p2    p3    p4             p5  comment
----  -----------------  ----  ----  ----  -------------  --  -------
0     Init               0     16    0                    0   Start at 16
1     Variable           1     1     0                    0   r[1]=parameter(1); OFFSET expr
2     MustBeInt          1     0     0                    0
3     Variable           2     2     0                    0   r[2]=parameter(2); OFFSET expr
4     MustBeInt          2     0     0                    0
5     OffsetLimit        1     3     2                    0   if r[1]>0 then r[3]=r[1]+max(0,r[2]) else r[3]=(-1)
6     OpenRead           0     2     0                    0   table=users, root=2, iDb=0
7     Rewind             0     15    0                    0   Rewind table users
8       Variable         2     2     0                    0   r[2]=parameter(2); OFFSET expr
9       MustBeInt        2     0     0                    0
10      IfPos            2     14    1                    0   r[2]>0 -> r[2]-=1, goto 14
11      Column           0     0     4                    0   r[4]=users.x
12      ResultRow        4     1     0                    0   output=r[4]
13      DecrJumpZero     1     15    0                    0   if (--r[1]==0) goto 15
14    Next               0     8     0                    0
15    Halt               0     0     0                    0
16    Transaction        0     1     1                    0   iDb=0 tx_mode=Read
17    Goto               0     1     0                    0

- the problem here is that Variable value is re-read at step 8 - which is wrong
2025-09-26 18:05:36 +04:00
PThorpe92
58625b1c6d Use expr.is_constant instead of matching for literal directly 2025-09-23 23:08:04 -04:00
PThorpe92
376d2bf7b1 Add plumbing to add sequence column to stabilize tiebreakers in order+group by 2025-09-23 22:35:59 -04:00
TcMits
5dddc5e00b introduce OP_Explain 2025-09-12 17:31:50 +07:00
Pekka Enberg
f88f39082a core/vdbe: Fix MakeRecord affinity handling
The MakeRecord instruction now accepts an optional affinity_str
parameter that applies column-specific type conversions before creating
records. When provided, the affinity string is applied
character-by-character to each register using the existing
apply_affinity_char() function, matching SQLite's behavior.

Fixes #2040
Fixes #2041
2025-09-08 18:49:13 +03:00
Pekka Enberg
44357f93a2 Merge branch 'main' into 2025-08-21-make-limit-and-offset-expr 2025-09-04 09:54:45 +03:00
Piotr Rzysko
c383d9f16e Remove outdated comment in order_by.rs
The removed comment no longer matches the current code. The
OrderByRemapping struct and the surrounding comments are sufficient to
explain deduplication and remapping.
2025-08-28 09:49:55 +02:00
Piotr Rzysko
e33c2e0f0b Fix sorter column deduplication
Previously, the added test case failed because the last result column
was missing - a nonexistent column in the sorter was referenced.
2025-08-28 09:49:55 +02:00
bit-aloo
a3b87cd97f add review comments 2025-08-26 19:56:25 +05:30
Pekka Enberg
26ba09c45f Revert "Merge 'Remove double indirection in the Parser' from Pedro Muniz"
This reverts commit 71c1b357e4, reversing
changes made to 6bc568ff69 because it
actually makes things slower.
2025-08-26 14:58:21 +03:00
pedrocarlo
d3240844ec refactor Core to remove the double indirection 2025-08-25 22:59:31 -03:00
Levy A.
4ba1304fb9 complete parser integration 2025-08-21 15:23:59 -03:00
Levy A.
186e2f5d8e switch to new parser 2025-08-21 15:19:16 -03:00
Jussi Saurio
dd2e0ea596 Fix: always emit rowid when column is rowid alias
SQLite does not store the rowid alias column in the record at all
when it is a rowid alias, because the rowid is always stored anyway
in the record header.
2025-08-21 16:40:10 +03:00
Jussi Saurio
d2cfe06aa5 Fix DISTINCT with ORDER BY
We had a bug where we were checking for duplicates in the DISTINCT
index based on both the result column count plus any ORDER BY columns
not present in the DISTINCT clause.

This is wrong, so fix it by only using the result columns for the
dedupe check.
2025-08-15 15:49:55 +03:00
Jussi Saurio
a99c8a8ca0 Simplify ORDER BY sorter column remapping
In case an ORDER BY column exactly matches a result column in the SELECT,
the insertion of the result column into the ORDER BY sorter can be skipped
because it's already necessarily inserted as a sorting column.

For this reason we have a mapping to know what index a given result column
has in the order by sorter.

This commit makes that mapping much simpler.
2025-08-15 15:48:41 +03:00
meteorgan
6262ff4267 support offset for values 2025-08-01 00:46:46 +08:00
Levy A.
ffd6844b5b refactor: remove PseudoTable from Table
the only reason for `PseudoTable` to exist, is to provide column
information for `PseudoCursor` creation. this should not be part of the
schema.
2025-06-30 14:31:58 -03:00
Levy A.
3907b387b3 cargo fix 2025-06-30 14:01:47 -03:00
Levy A.
afc55b27f0 refactor: remove unnecessary column definitions for PseudoTable
the only information that matters in the amount of column
2025-06-30 13:54:29 -03:00
Pekka Enberg
725c3e4ddc Rename limbo_sqlite3_parser crate to turso_sqlite3_parser 2025-06-29 12:34:46 +03:00
Levy A.
15e0cab8d8 refactor+fix: precompute default values from schema 2025-06-11 14:18:39 -03:00
Jussi Saurio
cc405dea7e Use new TableReferences struct everywhere 2025-05-29 11:44:56 +03:00
Jussi Saurio
77ce4780d9 Fix ProgramBuilder::cursor_ref not having unique keys
Currently we have this:

program.alloc_cursor_id(Option<String>, CursorType)`

where the String is the table's name or alias ('users' or 'u' in
the query).

This is problematic because this can happen:

`SELECT * FROM t WHERE EXISTS (SELECT * FROM t)`

There are two cursors, both with identifier 't'. This causes a bug
where the program will use the same cursor for both the main query
and the subquery, since they are keyed by 't'.

Instead introduce `CursorKey`, which is a combination of:

1. `TableInternalId`, and
2. index name (Option<String> -- in case of index cursors.

This should provide key uniqueness for cursors:

`SELECT * FROM t WHERE EXISTS (SELECT * FROM t)`

here the first 't' will have a different `TableInternalId` than the
second `t`, so there is no clash.
2025-05-29 00:59:24 +03:00
Jussi Saurio
4e9d9a2470 Fix LIMIT handling
Currently we have some usages of LIMIT where the actual limit counter
is initialized next to the DecrJumpZero instruction, and then
`program.mark_last_insn_constant()` is used to hoist the counter
initialization to the beginning of the program.

This is very fragile, and already FROM clause subquery handling works
around this with a hack (removed in this PR), and (upcoming) WHERE clause
subqueries would also run into problems because of this, because the LIMIT
might need to be initialized once for every iteration of the subquery.

This PR removes those usages for LIMIT, and LIMIT processing is now more
intuitive:

- limit counter is now initialized at the start of the query processing
- a function init_limit() is extracted to do this for select/update/delete
2025-05-27 21:12:22 +03:00
Jussi Saurio
7c07c09300 Add stable internal_id property to TableReference
Currently our "table id"/"table no"/"table idx" references always
use the direct index of the `TableReference` in the plan, e.g. in
`SelectPlan::table_references`. For example:

```rust
Expr::Column { table: 0, column: 3, .. }
```

refers to the 0'th table in the `table_references` list.

This is a fragile approach because it assumes the table_references
list is stable for the lifetime of the query processing. This has so
far been the case, but there exist certain query transformations,
e.g. subquery unnesting, that may fold new table references from
a subquery (which has its own table ref list) into the table reference
list of the parent.

If such a transformation is made, then potentially all of the Expr::Column
references to tables will become invalid. Consider this example:

```sql
-- Assume tables: users(id, age), orders(user_id, amount)

-- Get total amount spent per user on orders over $100
SELECT u.id, sub.total
FROM users u JOIN
     (SELECT user_id, SUM(amount) as total
      FROM orders o
      WHERE o.amount > 100
      GROUP BY o.user_id) sub
WHERE u.id = sub.user_id

-- Before subquery unnesting:
-- Main query table_references: [users, sub]
-- u.id refers to table 0, column 0
-- sub.total refers to table 1, column 1
--
-- Subquery table_references: [orders]
-- o.user_id refers to table 0, column 0
-- o.amount refers to table 0, column 1
--
-- After unnesting and folding subquery tables into main query,
-- the query might look like this:

SELECT u.id, SUM(o.amount) as total
FROM users u JOIN orders o ON u.id = o.user_id
WHERE o.amount > 100
GROUP BY u.id;

-- Main query table_references: [users, orders]
-- u.id refers to table index 0 (correct)
-- o.amount refers to table index 0 (incorrect, should be 1)
-- o.user_id refers to table index 0 (incorrect, should be 1)
```

We could ofc traverse every expression in the subquery and rewrite
the table indexes to be correct, but if we instead use stable identifiers
for each table reference, then all the column references will continue
to be correct.

Hence, this PR introduces a `TableInternalId` used in `TableReference`
as well as `Expr::Column` and `Expr::Rowid` so that this kind of query
transformations can happen with less pain.
2025-05-25 20:26:17 +03:00
Jussi Saurio
f6443ae742 Support LIMIT with UNION ALL 2025-05-24 13:12:41 +03:00
Jussi Saurio
0c4c451d2a rename 2025-05-22 16:51:03 +03:00
Jussi Saurio
6ed5412bde extract method 2025-05-22 16:51:03 +03:00
Jussi Saurio
f3ea9a603a add support for SELECT DISTINCT 2025-05-22 16:51:03 +03:00
pedrocarlo
4a3119786e refactor BtreeCursor and Sorter to accept Vec of collations 2025-05-19 15:22:55 -03:00
pedrocarlo
bf1fe9e0b3 Actually fixed group by and order by collation 2025-05-19 15:22:15 -03:00
pedrocarlo
f8854f180a Added collation to create table columns 2025-05-19 15:22:14 -03:00
pedrocarlo
5f2216cf8e modify explain for MakeRecord to show index name 2025-05-14 13:30:39 -03:00
pedrocarlo
bb158a5433 add unique field to Column 2025-05-14 11:34:11 -03:00
Jussi Saurio
37097e01ae GROUP BY: refactor logic to support cases where no sorting is needed 2025-05-08 12:39:26 +03:00
Jussi Saurio
306e097950 Merge 'Fix bug: we cant remove order by terms from the head of the list' from Jussi Saurio
we had an incorrect optimization in `eliminate_orderby_like_groupby()`
where it could remove e.g. the first term of the ORDER BY if it matched
the first GROUP BY term and the result set was naturally ordered by that
term. this is invalid. see e.g.:
```sql
main branch - BAD: removes the `ORDER BY id` term because the results are naturally ordered by id.
However, this results in sorting the entire thing by last name only!

limbo> select id, last_name, count(1) from users GROUP BY 1,2 order by id, last_name desc limit 3;
┌──────┬───────────┬───────────┐
│ id   │ last_name │ count (1) │
├──────┼───────────┼───────────┤
│ 6235 │ Zuniga    │         1 │
├──────┼───────────┼───────────┤
│ 8043 │ Zuniga    │         1 │
├──────┼───────────┼───────────┤
│  944 │ Zimmerman │         1 │
└──────┴───────────┴───────────┘

after fix - GOOD:

limbo> select id, last_name, count(1) from users GROUP BY 1,2 order by id, last_name desc limit 3;
┌────┬───────────┬───────────┐
│ id │ last_name │ count (1) │
├────┼───────────┼───────────┤
│  1 │ Foster    │         1 │
├────┼───────────┼───────────┤
│  2 │ Salazar   │         1 │
├────┼───────────┼───────────┤
│  3 │ Perry     │         1 │
└────┴───────────┴───────────┘

I also refactored sorters to always use the ast `SortOrder` instead of boolean vectors, and use the `compare_immutable()` utility we use inside btrees too.

Closes #1365
2025-05-03 12:48:08 +03:00
Jussi Saurio
029e5eddde Fix existing resolve_label() calls to work with new system 2025-04-24 11:05:21 +03:00
Jussi Saurio
3798b4aa8b use SortOrder in sorters always 2025-04-24 10:34:06 +03:00
Jussi Saurio
89e48a16db Add affinity() function to Column 2025-02-18 10:56:30 +02:00