Commit Graph

1028 Commits

Author SHA1 Message Date
Jussi Saurio
72058da9dc Merge 'bindings/javascript: Add pragma() support' from Anton Harniakou
This PR adds column names to the ouput of js pragma function.

Reviewed-by: Diego Reis (@el-yawd)
Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #1608
2025-06-10 18:24:13 +03:00
Jussi Saurio
4794b022a5 Merge 'Fix incorrect handling of OR clauses in HAVING' from Jussi Saurio
thanks to @pedrocarlo for reminding me we have this old-ass bug I never
bothered to fix

Closes #1708
2025-06-10 18:14:39 +03:00
Jussi Saurio
3d50822284 Merge 'fix: Incorrect placeholder label in where clause translation' from Pedro Muniz
Closes #1705

Closes #1707
2025-06-10 18:14:04 +03:00
Jussi Saurio
85972fd744 Merge 'Fix rowid to_sql_string' from Pedro Muniz
Addresses the panic encountered here:
https://github.com/tursodatabase/limbo/pull/1690 . Sorry about that.

Closes #1693
2025-06-10 18:11:51 +03:00
Jussi Saurio
18dd87eff1 Fix incorrect handling of OR clauses in HAVING 2025-06-10 18:02:14 +03:00
pedrocarlo
80c480517a incorrect placeholder label in where clause translation 2025-06-10 12:00:19 -03:00
pedrocarlo
36f60e4dd1 Fix rowid to_sql_string printing 2025-06-10 10:48:05 -03:00
Jussi Saurio
547ca6cf2a Fix incorrect usage of indexes with non-contiguous columns
Due to the left-prefix rule of indexes, for an index key to be usable,
it needs to:

- Use the columns in contiguous order (0, 1, 2...)
  * eg if WHERE refers to cols 0 and 2, only 0 can be used
- Stop at the first range operator
  * eg if WHERE: col1 = 5 AND col2 > 5 AND col3 = 5, only col1 and col2
    can be used.

This wasn't properly tested, and resulted in simulator failures. Added
some regression tests for this behavior.
2025-06-10 15:21:26 +03:00
Jussi Saurio
844461d20b update and delete fixes 2025-06-10 14:16:26 +03:00
Jussi Saurio
2bac140d73 Remove SeekOp::EQ and encode eq_only in LE&GE - needed for iteration direction aware equality seeks 2025-06-10 14:16:26 +03:00
Anton Harniakou
8471704e00 Don't use hard-coded column names 2025-06-09 10:40:04 +03:00
Anton Harniakou
d802075ea9 Resolve merge conflict: Add columns names to result set for pragma statement output 2025-06-09 10:40:04 +03:00
Jussi Saurio
18e6987904 Remove plan.to_sql_string() from optimize_plan() as it panics on TODOs 2025-06-09 09:45:06 +03:00
Jussi Saurio
2075e5f3eb Fix UPDATE always inserting only nulls into non-unique indexes 2025-06-09 08:51:23 +03:00
Jussi Saurio
8ffe6208a3 Merge 'Minor: use use_eq_ignore_ascii_case in some places' from Anton Harniakou
Use `eq_ignore_ascii_case` because it's cooler 😎 than `x.to_lowercase()
== y.to_lowercase()`.

Closes #1678
2025-06-09 08:29:56 +03:00
Jussi Saurio
eec7c0529c Merge 'Beginnings of AUTOVACUUM' from Zaid Humayun
This PR adds the beginnings of
[AUTOVACUUM](https://www.sqlite.org/lang_vacuum.html) to Limbo. It adds
a feature flag called `omit_autovacuum` which is analogous to
`SQLITE_OMIT_AUTOVACUUM`. It is off by default, same as SQLite.
It introduces the concept of [pointer map pages](https://www.sqlite.org/
fileformat.html#pointer_map_or_ptrmap_pages) which are reverse index
pages used to map pages to their parents. This is used to swap pages
(when a table is deleted for instance) to keep root pages clustered at
the beginning of the file. It's also used while creating a table to
ensure that root pages are clustered at the beginning (although, this
isn't completely implemented yet)
Finally, it also adds a couple of missing instructions like `Int64` that
are required for `PRAGMA` commands related to `auto_vacuum` settings
<img width="1512" alt="Screenshot 2025-05-28 at 8 47 51 PM"
src="https://github.com/user-
attachments/assets/d52eb74f-5b79-4d52-9401-1bdc2dcc304d" />

Closes #1600
2025-06-09 08:20:24 +03:00
Jussi Saurio
51637ccad2 Merge 'Reverse Parse Limbo ast and Plans' from Pedro Muniz
This PR implements the `ToSqlString` trait to most of the `ast` structs
and to the `SelectPlan`, `UpdatePlan`, `DeletePlan`,
`CompoundSelectPlan`.
Inside the files in the `to_sql_string` folder, I annotated many `TODOs`
with things that seem to diverge from SQLite syntax. The most egregious
by far was that Create Trigger statements do not use the standard
`delete`, `select`, `update`, and `insert` statements. The parser uses
different structs for those statements only in Create Trigger. E.g
`ast::TriggerCmdUpdate` instead of `ast::Update` and so on.
Also, as this iteration of reverse parsing is not particularly efficient
in the number of string allocations it does. I tested different methods
of achieving this by using `format!`, pushing directly to a `String`, or
just pushing to `Vec<String>` and joining all the string with a space
separator. I focused mainly on trying to get the syntax to print
correctly without major hurdles in understanding the code.
Lastly, I intend in the future to use this code in the simulator to
expand the its available syntax.

Closes #1619
2025-06-09 08:14:19 +03:00
Zaid Humayun
e994adfb40 Persisting database header and pointer map page to cache
This commit ensures that the metadata in the database header and the pointer map pages allocated are correctly persisted to the page cache. This was not being done earlier.
2025-06-06 23:14:25 +05:30
Zaid Humayun
5827a33517 Beginnings of AUTOVACUUM
This commit introduces AUTOVACUUM to Limbo. It introduces the concept of ptrmap pages and also adds some additional instructions that are required to make AUTOVACUUM PRAGMA work
2025-06-06 23:14:22 +05:30
Anton Harniakou
bd2becf45e Use eq_ignore_ascii_case to for case insensitive compare 2025-06-06 17:01:52 +03:00
meteorgan
a242bac340 Fix: ensure PRAGMA cache_size changes persist only for current session 2025-06-05 16:55:41 +08:00
pedrocarlo
3c1b984b78 use table_references for PlanContext 2025-06-04 12:06:43 -03:00
pedrocarlo
ebee9516ba clippy 2025-06-04 12:06:43 -03:00
pedrocarlo
5f379fe2d6 when no context is needed use Display Impl 2025-06-04 12:06:43 -03:00
pedrocarlo
bfc8cb6d4c move display and to_sql_string impls to separate modules for plan 2025-06-04 12:06:43 -03:00
pedrocarlo
f90bebbfbc small fix and remove dbg 2025-06-04 12:06:43 -03:00
pedrocarlo
fa0dff9843 Fix rebase changes 2025-06-04 12:06:43 -03:00
pedrocarlo
a96577529e impl ToSqlString for Update Plan 2025-06-04 12:06:43 -03:00
pedrocarlo
d243d1015c impl ToSqlString for Delete Plan 2025-06-04 12:06:43 -03:00
pedrocarlo
ff5aa17769 impl ToSqlString for CompoundSelect Plan 2025-06-04 12:06:43 -03:00
pedrocarlo
51014d01c3 impl ToSqlString for SelectPlan 2025-06-04 12:06:43 -03:00
Jussi Saurio
2087393d22 Merge 'Write database header via normal pager route' from meteorgan
Closes: #1613

Closes #1634
2025-06-04 09:39:14 +03:00
Pekka Enberg
c6ef19396d Merge 'Add support for pragma table-valued functions' from Piotr Rżysko
This PR adds support for table-valued functions for PRAGMAs (see the
[PRAGMA functions section](https://www.sqlite.org/pragma.html)).
Additionally, it introduces built-in table-valued functions. I
considered using extensions for this, but there are several reasons in
favor of a dedicated mechanism:
* It simplifies the use of internal functions, structs, etc. For
example, when implementing `json_each` and `json_tree`, direct access to
internals was necessary:
https://github.com/tursodatabase/limbo/pull/1088
* It avoids FFI overhead. [Benchmarks](https://github.com/piotrrzysko/li
mbo/blob/pragma_vtabs_bench/core/benches/pragma_benchmarks.rs) on my
hardware show that `pragma_table_info()` implemented as an extension is
2.5× slower than the built-in version.

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

Closes #1642
2025-06-04 09:08:10 +03:00
meteorgan
f2bf6251cd write database header via normal pager route 2025-06-03 22:06:08 +08:00
Jussi Saurio
31b37332d5 all index cursors must be opened when DELETE does an index seek too 2025-06-03 15:18:45 +03:00
Jussi Saurio
06626f72eb Fix cursors not being opened for indexes in DELETE 2025-06-03 14:45:01 +03:00
Jussi Saurio
ea301de726 Merge 'Pass input string to translate function' from Pedro Muniz
In preparation for `CREATE VIEW`, we need to have the original sql query
that was used to create the view. I'm using the scanner's offset to
slice into the original input, trimming the newlines, and passing it to
the translate function.

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

Closes #1621
2025-06-02 17:43:11 +03:00
pedrocarlo
9dc6638313 cleaner approach for opening indexes 2025-06-02 01:13:14 -03:00
Piotr Rzysko
4d35e36b77 Introduce virtual table types 2025-06-01 07:45:57 +02:00
pedrocarlo
bc563266b3 add instrumentation to more functions for debugging + adjust how cursors are opened 2025-05-30 20:35:50 -03:00
pedrocarlo
b73200de86 pass input string to translate function 2025-05-30 11:20:36 -03:00
Jussi Saurio
819a6138d0 Merge 'Fix: aggregate regs must be initialized as NULL at the start' from Jussi Saurio
Again found when fuzzing nested where clause subqueries:
Aggregate registers need to be NULLed at the start because the same
registers might be reused on another invocation of a subquery, and if
they are not NULLed, the 2nd invocation of the same subquery will have
values left over from the first invocation.

Reviewed-by: Preston Thorpe (@PThorpe92)

Closes #1614
2025-05-30 09:39:37 +03:00
Jussi Saurio
f8257df77b Fix: aggregate regs must be initialized as NULL at the start 2025-05-29 18:44:53 +03:00
Jussi Saurio
211b511189 Fix join optimizer tests 2025-05-29 11:44:56 +03:00
Jussi Saurio
cc405dea7e Use new TableReferences struct everywhere 2025-05-29 11:44:56 +03:00
Jussi Saurio
124b38a262 plan.rs: add new datastructures
- TableReferences struct, which holds both:
     - joined_tables, and
     - outer_query_refs

- JoinedTable:
     - this is just a rename of the previous TableReference struct

- OuterQueryReference
     - this is to distinguish from JoinedTable those cases where
       e.g. a subquery refers to an outer query's table, or a CTE
       refers to a previous CTE.

Both JoinedTable and OuterQueryReference can be referred to by expressions,
but only JoinedTables are considered for join ordering optimization and so
forth.

This commit does not compile.
2025-05-29 11:03:09 +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
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
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