Commit Graph

5899 Commits

Author SHA1 Message Date
Jussi Saurio
59363a1be3 Translate Expr::SubqueryResult into bytecode 2025-10-27 16:01:39 +02:00
Jussi Saurio
bc2a7c79f9 Add TODO comment about subquery positions we don't support yet 2025-10-27 16:01:39 +02:00
Jussi Saurio
8fecd82311 Emit non from clause subqueries in translation 2025-10-27 16:01:39 +02:00
Jussi Saurio
bf66999f64 Add emit_non_from_clause_subquery() method 2025-10-27 16:01:39 +02:00
Jussi Saurio
8e1987bd5d Rename emit_subqueries() to emit_from_clause_subqueries() to disambiguate 2025-10-27 16:01:39 +02:00
Jussi Saurio
58caf32fe2 Add plan_subqueries_from_where_clause() method and use it in Select planning 2025-10-27 16:01:39 +02:00
Jussi Saurio
c54988192e Add SelectPlan::is_correlated() method 2025-10-27 16:01:39 +02:00
Jussi Saurio
9b62687c41 Change unwrap_parens() to return Parenthesized as is, if it contains multiple values 2025-10-27 16:01:39 +02:00
Jussi Saurio
580333ddd3 Add NonFromClauseSubquery struct and add a Vec of them to SelectPlan 2025-10-27 16:01:39 +02:00
Jussi Saurio
609d9957c1 Add new QueryDestination variants for subquery types 2025-10-27 16:01:39 +02:00
Jussi Saurio
5bd6e033e6 Rename emit_subquery() to emit_from_clause_subquery() to disambiguate 2025-10-27 16:01:39 +02:00
Jussi Saurio
5eb74ce8e6 AST: Add Expr::SubqueryResult variant and enum SubqueryType 2025-10-27 16:01:39 +02:00
Jussi Saurio
e7aa7ee2ff ProgramBuilder: add a few utility methods needed for correlated subqueries 2025-10-27 14:03:41 +02:00
Jussi Saurio
5c05383cc1 Implement union for ColumnUsedMask 2025-10-27 13:57:56 +02:00
Jussi Saurio
3a1d6d8879 Improve error messages in translate_expr()
The current error messages are misleading, as the user may encounter
these errors in expressions outside the WHERE clause, too.
2025-10-27 13:51:59 +02:00
Jussi Saurio
de81af29e5 find_table_by_internal_id() returns whether table is an outer query reference
Unfortunately, our current translation machinery is unable to know for sure
whether a subquery reference to an outer table 't1' has opened a table cursor,
an index cursor, or both.

For this reason, return a flag from `TableReferences::find_table_by_internal_id()`
that tells the caller whether the table is an outer query reference, and further
commits will have some additional logic to decide which cursor a subquery will
read from when referencing a table from the outer query.
2025-10-27 13:47:49 +02:00
Pekka Enberg
7d035f27d8 Merge 'Strict numeric cast for op_must_be_int' from bit-aloo
closes: #3302

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #3771
2025-10-26 16:42:35 +02:00
Pekka Enberg
6603f5318a Merge 'core/vdbe: Reuse cursor in op_open_write()' from Pekka Enberg
This optimization reuses an existing cursor when op_open_write() is
called on the same table/index (same root_page). This is safe because
the cursor position doesn't matter - op_rewind() is always called after
op_open_write() to position the cursor at the beginning of the
table/index before any operations are performed.
This change speeds up op_open_write() by avoiding unnecessary cursor re-
initialization.

Closes #3815
2025-10-26 12:29:20 +02:00
Pekka Enberg
ca073b5ecd Merge 'core: Switch RwLock<Arc<Pager>> to ArcSwap<Pager>' from Pekka Enberg
We don't actually need the RwLock locking capabilities, just the ability
to swap the instance.

Closes #3814
2025-10-26 12:29:11 +02:00
Pekka Enberg
6020b3d1ec Merge 'Always returns Floats for sum and avg on DBSP aggregations' from Glauber Costa
Trying to return integer sometimes to match SQLite led to more problems
that I anticipated. The reason being, we can't *really* match SQLite's
behavior unless we know the type of *every* element in the sum. This is
not impossible, but it is very hard, for very little gain.
Fixes #3831

Closes #3832
2025-10-26 12:28:18 +02:00
Sumit Patel
7f8f1bc074 Update the write_varint method to use an encoded buffer of size 9 instead of 10.
The SQLite varint specification states that the varint is guaranteed to be a maximum of 9 bytes, but our version of write_varint initializes a buffer of 10 bytes. Changing the size to match the specification.
2025-10-25 16:53:59 +05:30
Glauber Costa
1ccd61088e Always returns Floats for sum and avg on DBSP aggregations
Trying to return integer sometimes to match SQLite led to more problems
that I anticipated. The reason being, we can't *really* match SQLite's
behavior unless we know the type of *every* element in the sum. This is
not impossible, but it is very hard, for very little gain.

Fixes #3831
2025-10-24 14:13:53 -05:00
Pekka Enberg
f85ba9198f Merge 'Add DISTINCT support to aggregate operator' from Glauber Costa
Implements COUNT/SUM/AVG(DISTINCT) and SELECT DISTINCT for materialized
views. To do this we have to keep a list of the actual distinct values
(similarly to how we do for min/max). We then update the operator (and
issue deltas) only when there is a state transition (for example, if we
already count the value x = 1, and we see an insert for x = 1, we do
nothing).
SELECT DISTINCT (with no aggregator) is similar. We already have to keep
a list of the values anyway to power the aggregates. So we just issue
new deltas based on the transition, without updating the aggregator.

Closes #3808
2025-10-24 18:47:11 +03:00
Jussi Saurio
8c6a6f0aa1 Merge 'Fix foreign key constraint enforcement on UNIQUE indexes' from Jussi Saurio
Our foreign key constraint checks were checking for changes in PRIMARY
KEYs, but not unique indexes - which are in practice the same thing,
apart from the `INTEGER PRIMARY KEY` special case, where the PRIMARY KEY
is an alias for the rowid of the table.
Closes #3648
Closes #3652 (reimplements)

Reviewed-by: Preston Thorpe <preston@turso.tech>

Closes #3825
2025-10-24 15:14:03 +03:00
Pekka Enberg
c3fb867173 core: Switch RwLock<Arc<Pager>> to ArcSwap<Pager>
We don't actually need the RwLock locking capabilities, just the ability
to swap the instance.
2025-10-24 14:10:08 +03:00
Pekka Enberg
ae60b78d82 Merge 'Switch to SQLite's Julian date logic' from Pekka Enberg
The `julian_day_converter` crate is GPL, which is problematic for apps
embedding Turso.

Closes #3822
2025-10-24 13:38:17 +03:00
bit-aloo
b2769afffd add test 2025-10-24 16:08:15 +05:30
bit-aloo
64bbca9e12 Fix op_must_be_int to use strict numeric cast 2025-10-24 16:08:15 +05:30
Jussi Saurio
18e6a23f23 Fix foreign key constraint enforcement on UNIQUE indexes
Closes #3648

Co-authored-by: Pavan-Nambi <pavannambi999@gmail.com>
2025-10-24 11:03:55 +03:00
Pekka Enberg
827b646c24 Switch to SQLite's Julian date logic
The `julian_day_converter` crate is GPL, which is problematic for apps
embedding Turso. Switch to SQLite's Julian date logic by porting the C
code to Rust.
2025-10-24 08:31:28 +03:00
Pekka Enberg
4c59f29931 Merge 'core/storage: Fix WAL already enabled issue' from Pekka Enberg
If WAL is already enabled, let's just continue execution instead of
erroring out.

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>
Reviewed-by: Pere Diaz Bou <pere-altea@homail.com>

Closes #3819
2025-10-23 20:56:57 +03:00
Pekka Enberg
87069fde93 core/storage: Fix WAL already enabled issue
If WAL is already enabled, let's just continue execution instead of erroring out.
2025-10-23 19:35:46 +03:00
Pekka Enberg
413c582b41 core/vdbe: Reuse cursor in op_open_write()
This optimization reuses an existing cursor when op_open_write() is
called on the same table/index (same root_page). This is safe because
the cursor position doesn't matter - op_rewind() is always called
after op_open_write() to position the cursor at the beginning of the
table/index before any operations are performed.

This change speeds up op_open_write() by avoiding unnecessary cursor
re-initialization.
2025-10-23 16:34:42 +03:00
Jussi Saurio
ae22468d8b Merge 'Order by heap sort' from Nikita Sivukhin
This PR implements simple heap-sort approach for query plans like
`SELECT ... FROM t WHERE ... ORDER BY ... LIMIT N` in order to maintain
small set of top N elements in the ephemeral B-tree and avoid sort and
materialization of whole dataset.
I removed all optimizations not related to this particular change in
order to make branch lightweight.

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #3726
2025-10-23 15:00:42 +03:00
Jussi Saurio
64560a61c3 Merge 'Support statement-level rollback via anonymous savepoints' from Jussi Saurio
## Gist
This PR implements _statement subtransactions_, which means that a
single statement within an interactive transaction can individually be
rolled back.
## Background
The default constraint violation resolution strategy in SQLite is
`ABORT`, which means to rollback the statement that caused the conflict.
For example:
```sql
CREATE TABLE t(x UNIQUE);
INSERT INTO t VALUES (1);

BEGIN;
  INSERT INTO t VALUES (2),(3); -- ok
  INSERT INTO t VALUES (4),(1); -- conflict on 1, this statement should rollback
  INSERT INTO t VALUES (5); -- ok
COMMIT; -- ok

SELECT * FROM t;
1
2
3
5
```
 So far we haven't been able to support this due to lack of support for
subtransactions, and have used the `ROLLBACK` strategy, which means to
rollback the entire transaction on any constraint error.
## Problem
Although PRIMARY KEY and UNIQUE constraints allow defining the conflict
resolution strategy (e.g. `id INTEGER PRIMARY KEY ON CONFLICT
ROLLBACK`), FOREIGN KEY violations do not support this: they always use
`ABORT` i.e. statement subtransaction rollback. For this reason alone it
is important to implement this mechanism now rather than later, since we
already have FOREIGN KEY support implemented.
## Details
This PR implements statement subtransactions with _anonymous
savepoints_. This means that whenever a statement begins, it will open a
new savepoint which will write "page undo images" into a temporary file
called a _subjournal_. Whenever the statement marks a page as dirty, it
will write the before-image of the page into the subjournal so that its
modifications can be undone in the event of an ABORT (statement
rollback).
- Right now, only anonymous savepoints are supported, so the explicit
`SAVEPOINT` syntax is not.
- Due to the above, there can be only one savepoint open per pager, and
this is enforced with assertions.
- The subjournal file is currently entirely in memory. If it were not,
we would either have to block on IO or refactor many usages of code to
account for potentially pending completions.
- Constraint errors no longer cause transactions to abort nor do they
cause the page cache to be cleared - instead, subjournaled pages will be
brought back into the page cache which effectively handles the same
behavior albeit more fine-grained.

Reviewed-by: Preston Thorpe <preston@turso.tech>

Closes #3792
2025-10-23 15:00:11 +03:00
Pekka Enberg
418fc90f8a Merge 'core/storage: Cache schema cookie in Pager' from Pekka Enberg
Every transaction was reading page 1 from the WAL to check the schema
cookie in op_transaction, causing unnecessary WAL lookups.
This commit caches the schema_cookie in Pager as AtomicU64, similar to
how page_size and reserved_space are already cached. The cache is
updated when the header is read/modified and invalidated in
begin_read_tx() when WAL changes are detected from other connections.
This matches SQLite's approach of caching frequently accessed header
fields to avoid repeated page 1 reads. Improves write throughput by 5%
in our benchmarks.

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #3727
2025-10-23 14:00:27 +03:00
Glauber Costa
92751e621b Add DISTINCT support to aggregate operator
Implements COUNT/SUM/AVG(DISTINCT) and SELECT DISTINCT for materialized views.
To do this we have to keep a list of the actual distinct values
(similarly to how we do for min/max). We then update the operator (and
issue deltas) only when there is a state transition (for example, if we
already count the value x = 1, and we see an insert for x = 1, we do
nothing).

SELECT DISTINCT (with no aggregator) is similar. We already have to keep
a list of the values anyway to power the aggregates. So we just issue
new deltas based on the transition, without updating the aggregator.
2025-10-22 16:32:18 -05:00
Jussi Saurio
2b73260dd9 Handle cases where DB grows or shrinks due to savepoint rollback 2025-10-22 23:40:45 +03:00
Jussi Saurio
fe51804e6b Implement crude way of making opening subtransaction conditional
We don't want something like `BEGIN IMMEDIATE` to start a subtransaction,
so instead we will open it if:

- Statement is write, AND

a) Statement has >0 table_references, or
b) The statement is an INSERT (INSERT doesn't track table_references in
   the same way as other program types)
2025-10-22 23:40:45 +03:00
Jussi Saurio
ea98d8086f Change default ON CONFLICT mode back to ABORT now that we support it 2025-10-22 23:40:45 +03:00
Jussi Saurio
e04c6c9b46 Mark pages_to_balance as dirty only after loading 2025-10-22 23:40:45 +03:00
Jussi Saurio
a14bbdecf2 Add assertion that page is loaded when pager.add_dirty() is called 2025-10-22 23:40:45 +03:00
Jussi Saurio
7376475cb3 Do not start statement subtransactions when MVCC is enabled
MVCC does not support statement-level rollback.
2025-10-22 23:40:45 +03:00
Jussi Saurio
d8cc57cf14 clippy: Remove unnecessary referencing 2025-10-22 23:40:45 +03:00
Jussi Saurio
97aad78b3f Allow dead code - SQLITE_CONSTRAINT_FOREIGNKEY is currently unused 2025-10-22 23:40:45 +03:00
Jussi Saurio
086ba8c946 VDBE: begin statement subtransaction in op_transaction 2025-10-22 23:40:45 +03:00
Jussi Saurio
904cbe535d VDBE: handle subtransaction commits/aborts in op_halt 2025-10-22 23:40:45 +03:00
Jussi Saurio
f0548c280f ProgramState: add begin_statement() and end_statement() 2025-10-22 23:40:45 +03:00
Jussi Saurio
734eeb5bab VDBE: constraint errors do not cause a tx rollback by default 2025-10-22 23:40:45 +03:00
Jussi Saurio
25f8ba0025 Pager: clear savepoints when tx rolls back 2025-10-22 23:40:45 +03:00