Aggregate functions cannot be nested, and this is validated during the
translation of aggregate function arguments. Therefore, traversing their
child expressions is unnecessary.
Handled in the same way as in `prepare_one_select_plan` for bare
function calls. In `prepare_one_select_plan`, however, resolving
external scalar functions is performed unnecessarily twice.
The new query combines multiple aggregate functions, plain columns,
arithmetic expressions, and aggregates wrapped in additional expressions.
Local run results:
```
Prepare `SELECT first_name, last_name, state, city, age + 10, LENGTH(email), UPPER(first_name), LOWE...
time: [64.535 µs 64.623 µs 64.713 µs]
Found 9 outliers among 100 measurements (9.00%)
4 (4.00%) high mild
5 (5.00%) high severe
```
The initial commits fix issues and plug gaps between ungrouped and
grouped aggregations.
The final commit consolidates the code that emits `AggStep` to prevent
future disparities between the two.
Reviewed-by: Preston Thorpe <preston@turso.tech>
Closes#2867
Some progress working on `printf` support.
(relevant issue https://github.com/tursodatabase/turso/issues/885)
Implementation of the basic substitution types cited in the `TODO`
comment on the beginning of the file (%i, %x, %X, %o, %e, %E, %c). There
are some others in the sqlite spec which I will implement in a future
PR.
I tried to pay attention to the specific behaviors from sqlite as much
as possible while testing this, but if there's something I missed please
tell me.
Also, I see this code needs to be reorganized already, I'm still
thinking on the best approach to do that without affecting the
ergonomics of new implementations, I'm still learning Rust so this is
not obvious for me right now. I'm open to suggestions about it.
Closes#2868
Because we can abort a read_page completion, this means a page can be in
the cache but be unloaded and unlocked. However, if we do not evict that
page from the page cache, we will return an unloaded page later which
will trigger assertions later on. This is worsened by the fact that page
cache is not per `Statement`, so you can abort a completion in one
Statement, and trigger some error in the next one if we don't evict the
page in these circumstances.
Also, to propagate IO errors we need to return the Error from
IOCompletions on step.
Closes#2785
Using `usize` to compute file offsets caps us at ~16GB on 32-bit
systems. For example, with 4 KiB pages we can only address up to 1048576
pages; attempting the next page overflows a 32-bit usize and can wrap
the write offset, corrupting data. Switching our I/O APIs and offset
math to u64 avoids this overflow on 32-bit targets
Closes#2791
Fixes https://github.com/tursodatabase/turso/issues/2704
The PR decrypts the page referred to by the WAL frame while reading raw
frames.
<img width="923" height="189" alt="Screenshot 2025-08-31 at 12 42 53 AM"
src="https://github.com/user-
attachments/assets/5e353cf3-aae7-4260-9378-ee2a2cde3f69" />
Reviewed-by: Avinash Sajjanshetty (@avinassh)
Closes#2762
## Changes
- Refactor sql generation to always accept a `Context` trait object so
we can query the current Generation `Opts`. This change allows us to be
more granular in generating our sql statements. It also opens
opportunities for us to add even more knobs to tweak generation as
needed. I tried to make this as generic as possible as I believe this
library can be useful for fuzz testing outside the simulator.
- Introduce `Profile` struct that aggregates the different
configurations needed to execute the simulator. With this Profile struct
we can bias sql generation towards different statements and create
predefined profiles.
`WriteHeavy` Profile:
```rust
Profile {
query: QueryProfile {
gen_opts: Opts {
// TODO: in the future tweak blob size for bigger inserts
// TODO: increase number of rows as well
table: TableOpts {
large_table: LargeTableOpts {
large_table_prob: 0.4,
..Default::default()
},
..Default::default()
},
query: QueryOpts {
insert: InsertOpts {
min_rows: NonZeroU32::new(5).unwrap(),
max_rows: NonZeroU32::new(11).unwrap(),
},
..Default::default()
},
..Default::default()
},
select_weight: 30,
insert_weight: 70,
delete_weight: 0,
update_weight: 0,
..Default::default()
},
..Default::default()
};
```
As you can see we disable the `delete` and `update` weights, decrease
`select` and increase `insert` weights. This means that we disable
updates and deletes in favor of inserting more data and checking the
validity of the database with fewer select statements.
- `Profile` and `Opts` are validated with `garde` and can generate json
schemas with `schemars` so that we can have editor integration when
creating new profiles to play with.
- Added some docs in the README explaining how you can add LSP
integration for the Json config by generating a `JsonSchema` file
Closes#2852