Commit Graph

4835 Commits

Author SHA1 Message Date
Jussi Saurio
7ededb8b97 Merge 'Fix ProgramBuilder::cursor_ref not having unique keys' from Jussi Saurio
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<Arc<Index>>` -- 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.
---
These `CursorKey`s are only required when the program needs to retrieve
the cursor ID later:
`program.resolve_cursor_id(key)`
So, there are now two methods for allocating cursors:
`program.alloc_cursor_id_keyed(key, cursor_type); // needs to be
retrieved later with same key`
`program.alloc_cursor_id(cursor_type); // does not need to be retrieved
later`

Reviewed-by: Preston Thorpe (@PThorpe92)

Closes #1604
2025-05-29 10:53:24 +03:00
Jussi Saurio
592ba41137 Add assertion forbidding duplicate cursor keys 2025-05-29 01:04:45 +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
85316d8419 Merge 'clear page cache on transaction failure' from Pere Diaz Bou
This is the first step towards rollback, since we still don't spill
pages with WAL, we can simply invalidate page cache in case of failure.

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

Closes #1599
2025-05-28 23:14:44 +03:00
Jussi Saurio
1d7824badd Merge 'Fix serialize() unreachable panic' from Krishna Vishal
Closes: https://github.com/tursodatabase/limbo/issues/1602
Fix unreachable panic when calling serialize on Value::Integer(0)
 by handling `SerialTypeKind` `ConstInt0` and `ConstInt1` in
`Record::serialize()`
Changed `test_serialize_integers` to reflect this change.

Reviewed-by: Pere Diaz Bou <pere-altea@homail.com>

Closes #1603
2025-05-28 23:12:32 +03:00
krishvishal
fb1d53b0ec Fix test. off by one. 2025-05-29 00:22:38 +05:30
krishvishal
5b57efd894 A couple more tests to test this case. 2025-05-29 00:10:59 +05:30
krishvishal
e3bc78f7e4 Fix unreachable panic when calling serialize on Value::Integer(0)
by handling `SerialTypeKind` `ConstInt0` and `ConstInt1` in
`Record::serialize()`

- Changed `test_serialize_integers` to accomodate this change.
2025-05-29 00:08:37 +05:30
Jussi Saurio
35948c6dbb Merge 'Btree: fix cursor record state not being updated in insert_into_page()' from Jussi Saurio
overwrite_cell() requires that the cursor state is pointing to a valid
record, but this was not currently set properly.

Reviewed-by: Pere Diaz Bou <pere-altea@homail.com>

Closes #1598
2025-05-28 17:10:44 +03:00
Pere Diaz Bou
28bd24b7d4 clear page cache on transaction failure
This is the first step towards rollback, since we still don't spill
pages with WAL, we can simply invalidate page cache in case of failure.
2025-05-28 15:54:28 +02:00
Jussi Saurio
dad1e6293b Btree: fix cursor record state not being updated in insert_into_page()
overwrite_cell() requires that the cursor state is pointing to a valid
record, but this was not currently set properly.
2025-05-28 16:54:00 +03:00
Pekka Enberg
0ae46e815c Limbo 0.0.21 2025-05-28 12:42:03 +03:00
Pekka Enberg
6112bd855c Update CHANGELOG.md 2025-05-28 12:41:54 +03:00
Pekka Enberg
6b93c9acdc Limbo 0.0.21-pre.2 2025-05-28 12:23:13 +03:00
Pekka Enberg
8870dc38aa github: Switch JavaScript publish to Github actions
With Blacksmith runners, it errors out as follows:

```
npm notice Publishing to https://registry.npmjs.org/ with tag next and public access
npm notice publish Signed provenance statement with source and build information from GitHub Actions
npm notice publish Provenance statement published to transparency log: https://search.sigstore.dev/?logIndex=221582002
npm error code E422
npm error 422 Unprocessable Entity - PUT https://registry.npmjs.org/@tursodatabase%2flimbo - Error verifying sigstore provenance bundle: Unsupported GitHub Actions runner environment: "self-hosted". Only "github-hosted" runners are supported when publishing with provenance.
npm error A complete log of this run can be found in: /home/runner/.npm/_logs/2025-05-28T08_42_29_830Z-debug-0.log
Error: Process completed with exit code 1.
```
2025-05-28 12:22:17 +03:00
Pekka Enberg
80d7571f3e Limbo 0.0.21-pre.1 2025-05-28 11:17:08 +03:00
Jussi Saurio
7ab243dc4e Merge 'Make WhereTerm::consumed a Cell<bool>' from Jussi Saurio
Currently in the main translation logic after planning and optimization,
we don't _really_ need to pass a `&mut Vec<WhereTerm>` around anymore,
except for the fact that virtual table constraint resolution is done ad-
hoc in `init_loop()`.
Even there, the only thing we mutate is `WhereTerm::consumed` which is a
boolean indicating that the term has been "used up" by the optimizer and
shouldn't be evaluated as a normal where clause condition anymore.
In the upcoming branch for WHERE clause subqueries, I want to store
immutable references to WHERE clause expressions in `Resolver`, but this
is unfortunately not possible if we still use the aforementioned mutable
references.
Hence, we can temporarily make `WhereTerm::consumed` a `Cell<bool>`
which allows us to pass an immutable reference to `init_loop()`, and the
`Cell` can be removed once the virtual table constraint resolution is
moved to an earlier part of the query processing pipeline.

Closes #1597
2025-05-28 11:14:40 +03:00
Jussi Saurio
2a7d675e71 Merge 'Use lifetimes in walk_expr() to guarantee that child expr has same lifetime as parent expr' from Jussi Saurio
Closes #1596
2025-05-28 11:14:10 +03:00
Jussi Saurio
73e806ad84 Make WhereTerm::consumed a Cell<bool>
Currently in the main translation logic after planning and optimization,
we don't _really_ need to pass a &mut Vec<WhereTerm> around anymore, except
for the fact that virtual table constraint resolution is done ad-hoc in
`init_loop()`. Even there, the only thing we mutate is `WhereTerm::consumed`
which is a boolean indicating that the term has been "used up" by the optimizer
and shouldn't be evaluated as a normal where clause condition anymore.

In the upcoming branch for WHERE clause subqueries, I want to store immutable
references to WHERE clause expressions in `Resolver`, but this is unfortunately
not possible if we still use the aforementioned mutable references.

Hence, we can temporarily make `WhereTerm::consumed` a `Cell<bool>` which allows
us to pass an immutable reference to `init_loop()`, and the `Cell` can be removed
once the virtual table constraint resolution is moved to an earlier part of the
query processing pipeline.
2025-05-28 11:02:39 +03:00
Jussi Saurio
51605ad2a4 Use lifetimes in walk_expr() to guarantee that child expr has same lifetime as parent expr 2025-05-28 10:56:30 +03:00
Jussi Saurio
7241e0503e Merge 'Fix LIMIT handling' from Jussi Saurio
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

Closes #1591
2025-05-28 09:53:51 +03:00
Jussi Saurio
a9ae1af75c Fix: init_limit() in wrong place for Delete 2025-05-27 21:26:31 +03:00
Jussi Saurio
3c587b91b5 Add comment on init_limit() 2025-05-27 21:19:28 +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
8abe5efe99 Merge 'Add Schema reference to Resolver - needed for adhoc subquery planning' from Jussi Saurio
enabler for WHERE clause subquery ad-hoc planning&translation

Closes #1589
2025-05-27 20:19:45 +03:00
Jussi Saurio
ad0f2bb399 Merge 'Small VDBE insn tweaks' from Jussi Saurio
1. allow calling op_null with Insn::BeginSubrtn
    - BeginSubrtn is identical to Null, but named differently so that
its use in context is clearer
2. Insn::Return: add possibility to fallthrough on non-integer values as
per sqlite spec

Closes #1588
2025-05-27 20:19:31 +03:00
Jussi Saurio
d67e22c557 Merge 'Use the SetCookie opcode to implement user_version pragma' from meteorgan
1. implement the `SetCookie` opcode for `user_version`
2. reimplement `PRAGMA user_version=N` functionality using the
`SetCookie` opcode
**sqlite**
```
sqlite> explain pragma user_version=10;
addr  opcode         p1    p2    p3    p4             p5  comment
----  -------------  ----  ----  ----  -------------  --  -------------
0     Init           0     5     0                    0   Start at 5
1     Expire         1     1     0                    0
2     Transaction    0     1     0                    0
3     SetCookie      0     6     10                   1
4     Halt           0     0     0                    0
5     Goto           0     1     0                    0
```
**limbo**
```
limbo>  explain pragma user_version=10;
addr  opcode             p1    p2    p3    p4             p5  comment
----  -----------------  ----  ----  ----  -------------  --  -------
0     Init               0     3     0                    0   Start at 3
1     SetCookie          0     6     10                   1
2     Halt               0     0     0                    0
3     Transaction        0     1     0                    0   write=true
4     Goto               0     1     0                    0
```
To fully align the opcodes with SQLite, we still need to implement the
`Expire` opcode, which I plan to address in the next PR.

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

Closes #1590
2025-05-27 20:19:13 +03:00
meteorgan
86249d9c28 add more tests for pragma user_version 2025-05-28 00:47:09 +08:00
meteorgan
2f82762ca2 add function parse_signed_number 2025-05-28 00:33:41 +08:00
meteorgan
d9d3a5ecbb Use the SetCookie opcode to implement user_version pragma 2025-05-28 00:31:11 +08:00
Jussi Saurio
d2a287f67f Add Schema reference to Resolver - needed for adhoc subquery planning 2025-05-27 19:12:47 +03:00
Jussi Saurio
6914d61180 allow calling op_null with Insn::BeginSubrtn 2025-05-27 19:09:15 +03:00
Jussi Saurio
70965f4b28 Insn::Return: add possibility to fallthrough on non-integer values as per sqlite spec 2025-05-27 19:09:10 +03:00
Pekka Enberg
ac97ac36a6 Fix broken build in sqlite3 tests
Thanks for the heads up Pedro!
2025-05-27 18:44:41 +03:00
Pekka Enberg
8d7f20b7d2 Merge 'Add libsql_wal_get_frame() API' from Pekka Enberg
This pull request implements the `libsql_wal_get_frame()` API. To do
that, we also introduce a `wait_for_completion()` API in I/O dispatcher.

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

Closes #1533
2025-05-27 18:17:32 +03:00
Pekka Enberg
99926c5f99 sqlite3/tests: Clippy is not happy 2025-05-27 18:16:54 +03:00
Pekka Enberg
edfa7402f0 sqlite3/test: Use tempfile in read frame test case
...make test runs idempotent, as suggested by Jussi.
2025-05-27 16:45:02 +03:00
Pekka Enberg
59d28eac93 core: Switch Completion "is_completed" to use Cell
Suggested by Jussi
2025-05-27 14:05:07 +03:00
Pekka Enberg
3250560eb8 sqlite3: Add libsql_wal_get_frame() API 2025-05-27 13:47:40 +03:00
Pekka Enberg
05df548b10 core/io: Add wait_for_completion() to I/O dispatcher 2025-05-27 13:47:40 +03:00
Jussi Saurio
a88e1c38f3 Merge 'Fix bug: op_vopen should replace cursor slot, not add new one' from Jussi Saurio
Found this when reviewing #1528 locally and this was crashing
```sql
INSERT INTO t SELECT * FROM generate_series(1,10,1);
```
Reason was that `op_vopen` was not replacing the already allocated
cursor slot, but using `.insert()`

Reviewed-by: Pere Diaz Bou <pere-altea@homail.com>

Closes #1583
2025-05-27 12:50:11 +03:00
Pere Diaz Bou
312bb5205a Merge 'Reset idx delete state after successful finish' from Pere Diaz Bou
If we don't reset the state of `IdxDelete`, next `IdxDelete` will start
in `Deleting` state which is completely wrong since it should seek from
the start.

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

Closes #1584
2025-05-27 11:31:25 +02:00
Pere Diaz Bou
a5a8a52a07 reset-idx-delete-state 2025-05-27 10:47:21 +02:00
Pekka Enberg
eca9a5b703 core/io: Switch to Arc<Completion> 2025-05-27 11:28:49 +03:00
Jussi Saurio
f035e076ca Merge 'bind/js: Add support for bind() method and reduce boilerplate' from Diego Reis
EDIT: This PR also adds support for the `pluck()` logic in all methods

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

Closes #1582
2025-05-27 11:01:27 +03:00
Jussi Saurio
b843ad0a58 Add INSERT INTO ... SELECT * FROM generate_series() regression test 2025-05-27 10:54:55 +03:00
Jussi Saurio
360b1fcdae Fix bug: op_vopen should replace cursor slot, not add new one 2025-05-27 10:52:36 +03:00
Jussi Saurio
b72b99c973 Merge 'feature: INSERT INTO <table> SELECT' from Pedro Muniz
Closes #1528 .
- Modified `translate_select` so that the caller can define if the
statement is top-level statement or a subquery.
- Refactored `translate_insert` to offload the translation of multi-row
VALUES and SELECT statements to `translate_select`
- I did not try to change much of `populate_column_registers` as I did
not want to break `translate_virtual_table_insert`. Ideally, I would
want to unite this remaining logic folding `populate_column_registers`
into `populate_columns_multiple_rows` and the
`translate_virtual_table_insert` into `translate_insert`. But, I think
this may be best suited for a separate PR.
## TODO
- ~Tests~ - *Done*
- ~Need to emit a temp table when we are selecting and inserting into
the Same Table -
https://github.com/sqlite/sqlite/blob/master/src/insert.c#L1369~ -
*Done*
- Optimization when table have the exact same schema - open an Issue
about it
- Virtual Tables do not benefit yet from this feature - open an Issue
about it

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

Closes #1566
2025-05-27 10:50:26 +03:00
Jussi Saurio
1c4b5c0333 Merge 'Clear test db's WAL too in clone_test_db.sh' from Jussi Saurio
Before #1570 , we never read anything from the on-disk WAL at startup,
so the `test_wal_frame_count()` test assertion `frame_count == 0`would
always pass, regardless of whether there was anything in the WAL or not
Now that the actual bug is fixed, the current test isn't idempotent
because the `clone_test_db.sh` command does not clear the wal, just the
db file

Closes #1581
2025-05-27 10:04:02 +03:00
Pekka Enberg
21535018aa core: Don't pass page to begin_read_wal_frame()
Make `begin_read_wal_frame()` a bit more generic by not requiring a page
to be passed.
2025-05-27 10:02:36 +03:00