Commit Graph

893 Commits

Author SHA1 Message Date
Pekka Enberg
9bbfdab5fa Revert "Merge 'Add support for sqlite_version() scalar function' from Kim Seon Woo"
This reverts commit e365c12ce0, reversing
changes made to 21bd1a961e. The pull request broke some tests:

```
thread 'main' panicked at core/vdbe/mod.rs:1713:72:
index out of bounds: the len is 3 but the index is 3
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
    while executing
"exec {*}$command"
    (procedure "evaluate_sql" line 3)
    invoked from within
"evaluate_sql $sqlite_exec $sql"
    (procedure "run_test" line 2)
    invoked from within
"run_test $::sqlite_exec $combined_sql $combined_expected_output"
    (procedure "do_execsql_test" line 5)
    invoked from within
"do_execsql_test sqlite_version {
  SELECT sqlite_version();
} {3.46.1}"
    (file "./testing/scalar-functions.test" line 434)
    invoked from within
"source $testdir/scalar-functions.test"
    (file "./testing/all.test" line 16)
make: *** [Makefile:59: test-compat] Error 1
```
2024-09-16 14:28:18 +03:00
Pekka Enberg
e365c12ce0 Merge 'Add support for sqlite_version() scalar function' from Kim Seon Woo
### sqlite
<img width="792" alt="image" src="https://github.com/user-
attachments/assets/1a9238db-d948-4583-a808-f9adfec7c534">
### limbo
<img width="809" alt="image" src="https://github.com/user-
attachments/assets/ea3e5f7e-bb3e-450d-be34-59ca00128beb">
### Changes
- Add support for `sqlite_version()` function
- Update function's explain message depending on the number of arguments

Closes #331
2024-09-16 14:18:19 +03:00
Pekka Enberg
21bd1a961e Merge 'Fix three issues with LIKE operator (#319)' from Jussi Saurio
Closes #319
1. Allow using a column as the pattern
2. Construct LIKE regexes with `^` and `$` so that eg the string
`'foobar'` does not match the pattern `'fooba'` unless the pattern
specifically has a wildcard
3. Support function expressions as the LIKE pattern

Closes #327
2024-09-16 14:17:48 +03:00
Pekka Enberg
95987f9824 Merge 'Support grouping by a function expression' from Jussi Saurio
Closes #326
2024-09-16 14:17:03 +03:00
김선우
469f345c16 Fix explain message for function depending on the number of args 2024-09-16 18:47:53 +09:00
김선우
6b40acabbc Add support for sqlite_version() scalar function 2024-09-16 18:38:42 +09:00
jussisaurio
9f1ce53d18 Merge 'implementation of scalar function sign(X)' from Jean Arhancet
This is related to the issue
https://github.com/penberg/limbo/issues/144. Add the scalar function
`sign(X)`

Closes #328
2024-09-15 16:03:09 +03:00
JeanArhancet
d03a734f21 feat: add sign function 2024-09-15 14:57:01 +02:00
jussisaurio
3f0162c76c Support grouping by a function expression 2024-09-15 09:58:46 +03:00
jussisaurio
6b8cd02f71 Fix function expressions in like pattern not working 2024-09-15 09:57:54 +03:00
jussisaurio
234c56ca81 Fix two issues with LIKE operator (#319) 2024-09-15 09:57:54 +03:00
jussisaurio
d4971a4706 Merge 'cargo clippy --fix --allow-dirty && cargo fmt' from Jussi Saurio
Closes #325
2024-09-15 09:56:02 +03:00
jussisaurio
b6e88ca883 cargo clippy --fix --allow-dirty && cargo fmt 2024-09-15 09:35:39 +03:00
jussisaurio
26d88a788b Merge 'Add support for concat_ws scalar function' from Kim Seon Woo
Adding support for `concat_ws`
<img width="1604" alt="image" src="https://github.com/user-
attachments/assets/8281d0c1-c338-4695-aa17-3b86f16625d3">
## Related issue
https://github.com/penberg/limbo/issues/144

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

Closes #324
2024-09-15 09:09:40 +03:00
김선우
8b3e4da8da Apply comments
- Specify the number of arguments using arg_count
- Add test for concat_ws
2024-09-15 10:04:30 +09:00
김선우
fc07ca9d73 Add support for concat_ws 2024-09-14 23:47:45 +09:00
Pekka Enberg
51e3fe487a Update CHANGELOG.md 2024-09-14 16:36:40 +03:00
Pekka Enberg
2395ed2b71 Merge 'core,btree: support overflow pages' from Pere Diaz Bou
This PR adds support for traversing overflow payloads and inserting them
too. The test added is a simple one and must be tested with higher
degree of stress in the future.

Closes #322
2024-09-14 16:35:02 +03:00
Pekka Enberg
f39120dbb1 Merge 'GROUP BY support' from Jussi Saurio
This PR implements GROUP BY (and also ordering by the groups or the
aggregate expressions). See `groupby.test` for the kinds of things that
are now supported.
This PR is a rabbit hole and insanely big, sorry.
---
I thought about how to explain how GROUP BY works in SQLite bytecode,
and opted to go for just adding a bunch of Insn comments, so here's an
example that uses both GROUP BY and ORDER BY:
**LIMBO**
```
limbo> explain select u.first_name, sum(u.age) from users u group by u.first_name order by sum(u.age);
addr  opcode             p1    p2    p3    p4             p5  comment
----  -----------------  ----  ----  ----  -------------  --  -------
0     Init               0     56    0                    0   Start at 56
1     SorterOpen         0     1     0     k(1,B)         0   cursor=0
2     SorterOpen         1     2     0     k(1,B)         0   cursor=1
3     Integer            0     2     0                    0   r[2]=0; clear group by abort flag
4     Null               0     4     0                    0   r[4]=NULL; initialize group by comparison registers to NULL
5     Gosub              8     45    0                    0   ; go to clear accumulator subroutine
6     OpenReadAsync      2     2     0                    0   table=u, root=2
7     OpenReadAwait      0     0     0                    0
8     RewindAsync        2     0     0                    0
9     RewindAwait        2     38    0                    0   Rewind table u
10      Column           2     1     10                   0   r[10]=u.first_name
11      Column           2     9     11                   0   r[11]=u.age
12      MakeRecord       10    2     7                    0   r[7]=mkrec(r[10..11])
13      SorterInsert     1     7     0     0              0   key=r[7]
14    NextAsync          2     0     0                    0
15    NextAwait          2     9     0                    0
16    OpenPseudo         3     7     2                    0   2 columns in r[7]
17    SorterSort         1     32    0                    0
18      SorterData       1     7     3                    0   r[7]=data
19      Column           3     0     12                   0   r[12]=cursor 3.u.first_name
20      Compare          4     12    1                    0   r[4..4]==r[12..12]
21      Jump             22    26    22                   0   ; start new group if comparison is not equal
22      Move             12    4     1                    0   r[4..4]=r[12..12]
23      Gosub            9     36    0                    0   ; check if ended group had data, and output if so
24      IfPos            2     55    0                    0   r[2]>0 -> r[2]-=0, goto 55; check abort flag
25      Gosub            8     45    0                    0   ; goto clear accumulator subroutine
26      Column           3     1     13                   0   r[13]=cursor 3.u.age
27      AggStep          0     13    6     sum            0   accum=r[6] step(r[13])
28      If               3     30    0                    0   if r[3] goto 30; don't emit group columns if continuing existing group
29      Column           3     0     5                    0   r[5]=cursor 3.u.first_name
30      Integer          1     3     0                    0   r[3]=1; indicate data in accumulator
31    SorterNext         1     18    0                    0
32    Gosub              9     36    0                    0   ; emit row for final group
33    Goto               0     48    0                    0   ; group by finished
34    Integer            1     2     0                    0   r[2]=1
35    Return             9     0     0                    0
36    IfPos              3     38    0                    0   r[3]>0 -> r[3]-=0, goto 38; output group by row subroutine start
37    Return             9     0     0                    0
38    AggFinal           0     6     0     sum            0   accum=r[6]
39    Copy               5     15    0                    0   r[15]=r[5]
40    Copy               6     16    0                    0   r[16]=r[6]
41    Copy               6     14    0                    0   r[14]=r[6]
42    MakeRecord         14    3     1                    0   r[1]=mkrec(r[14..16])
43    SorterInsert       0     1     0     0              0   key=r[1]
44    Return             9     0     0                    0
45    Null               0     5     6                    0   r[5..6]=NULL; clear accumulator subroutine start
46    Integer            0     3     0                    0   r[3]=0
47    Return             8     0     0                    0
48    OpenPseudo         4     1     3                    0   3 columns in r[1]
49    SorterSort         0     55    0                    0
50      SorterData       0     1     4                    0   r[1]=data
51      Column           4     1     17                   0   r[17]=cursor 4.sum
52      Column           4     2     18                   0   r[18]=cursor 4.u.first_name
53      ResultRow        17    2     0                    0   output=r[17..18]
54    SorterNext         0     50    0                    0
55    Halt               0     0     0                    0
56    Transaction        0     0     0                    0
57    Goto               0     1     0                    0
```
**SQLITE3**:
```
sqlite> explain select u.first_name, sum(u.age) from users u group by u.first_name order by sum(u.age);
addr  opcode         p1    p2    p3    p4             p5  comment
----  -------------  ----  ----  ----  -------------  --  -------------
0     Init           0     52    0                    0   Start at 52
1     SorterOpen     1     4     0     k(1,B)         0
2     SorterOpen     2     2     0     k(1,B)         0
3     Integer        0     2     0                    0   r[2]=0; clear abort flag
4     Null           0     5     5                    0   r[5..5]=NULL
5     Gosub          4     41    0                    0
6     OpenRead       0     2     0     10             0   root=2 iDb=0; users
7     Rewind         0     13    0                    0
8       Column         0     1     10                   0   r[10]= cursor 0 column 1
9       Column         0     9     11                   0   r[11]= cursor 0 column 9
10      MakeRecord     10    2     12                   0   r[12]=mkrec(r[10..11])
11      SorterInsert   2     12    0                    0   key=r[12]
12    Next           0     8     0                    1
13    OpenPseudo     3     12    2                    0   2 columns in r[12]
14    SorterSort     2     44    0                    0   GROUP BY sort
15      SorterData     2     12    3                    0   r[12]=data
16      Column         3     0     6                    0   r[6]= cursor 3 column 0
17      Compare        5     6     1     k(1,B)         0   r[5] <-> r[6]
18      Jump           19    23    19                   0
19      Move           6     5     1                    0   r[5]=r[6]
20      Gosub          3     33    0                    0   output one row
21      IfPos          2     44    0                    0   if r[2]>0 then r[2]-=0, goto 44; check abort flag
22      Gosub          4     41    0                    0   reset accumulator
23      Column         3     1     13                   0   r[13]=users.age
24      AggStep        0     13    9     sum(1)         1   accum=r[9] step(r[13])
25      If             1     27    0                    0
26      Column         3     0     7                    0   r[7]=users.first_name
27      Integer        1     1     0                    0   r[1]=1; indicate data in accumulator
28    SorterNext     2     15    0                    0
29    Gosub          3     33    0                    0   output final row
30    Goto           0     44    0                    0
31    Integer        1     2     0                    0   r[2]=1; set abort flag
32    Return         3     0     0                    0
33    IfPos          1     35    0                    0   if r[1]>0 then r[1]-=0, goto 35; Groupby result generator entry point
34    Return         3     0     0                    0
35    AggFinal       9     1     0     sum(1)         0   accum=r[9] N=1
36    Copy           7     15    0                    0   r[15]=r[7]
37    Copy           9     14    0                    0   r[14]=r[9]
38    MakeRecord     14    2     17                   0   r[17]=mkrec(r[14..15])
39    SorterInsert   1     17    14    2              0   key=r[17]
40    Return         3     0     0                    0   end groupby result generator
41    Null           0     7     9                    0   r[7..9]=NULL
42    Integer        0     1     0                    0   r[1]=0; indicate accumulator empty
43    Return         4     0     0                    0
44    OpenPseudo     4     18    4                    0   4 columns in r[18]
45    SorterSort     1     51    0                    0
46      SorterData     1     18    4                    0   r[18]=data
47      Column         4     0     16                   0   r[16]=sum(u.age)
48      Column         4     1     15                   0   r[15]=u.first_name
49      ResultRow      15    2     0                    0   output=r[15..16]
50    SorterNext     1     46    0                    0
51    Halt           0     0     0                    0
52    Transaction    0     0     2     0              1   usesStmtJournal=0
53    Goto           0     1     0                    0
```
As you can see the bytecodes are fairly close in this scenario.
SQLite opts to use an ephemeral index in certain cases (e.g. when you
use `LIMIT` or have multiple grouping columns). Will not implement those
branching strategies as part of this PR
---
Example operator tree:
```
limbo> explain query plan select u.first_name, p.name, sum(u.age) from users u join products p on u.id = p.id group by u.first_name, p.name order by p.name limit 10;

QUERY PLAN
`--TAKE 10
   `--SORT p.name ASC
   |  `--PROJECT u.first_name, p.name, sum (u.age)
   |  |  `--AGGREGATE Sum(u.age)
   |  |  |  `--JOIN
   |  |  |  |  |--SCAN users AS u
   |  |  |  |  `--SEEK products.rowid ON rowid=u.id
```

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

Closes #309
2024-09-14 16:33:09 +03:00
jussisaurio
a108dea825 GROUP BY 2024-09-14 16:14:45 +03:00
jussisaurio
ba3de2af58 Merge 'Pass FuncCtx to Insn::Function to keep track of arg count' from Jussi Saurio
https://github.com/penberg/limbo/pull/321 recently fixed an error with
register allocation order concerning binary expressions in
`translate_condition_expr`. I noticed we had the same bug in
`translate_expr` as well, and this led into noticing that our
implementation of `Insn::Function` diverges from SQLite in that it
doesn't provide a context object that keeps track of how many arguments
the function call has.
Not having the arg count available at runtime means that e.g.:
```
do_execsql_test length-date-binary-expr {
  select length(date('now')) = 10;
} {1}
```
Returned 0 instead of 1, because at runtime another register was already
allocated, and `date()` was implemented like this:
```
                    Func::Scalar(ScalarFunc::Date) => {
                        let result = exec_date(&state.registers[*start_reg..]); // all registers starting from start_reg
                        state.registers[*dest] = result;
                        state.pc += 1;
                    }
```
SQlite bytecode engine docs for Function say:
> Invoke a user function (P4 is a pointer to an sqlite3_context object
that contains a pointer to the function to be run) with arguments taken
from register P2 and successors. The number of arguments is in the
sqlite3_context object that P4 points to.
Accordingly, this PR implements a `FuncCtx` struct that contains a
`Func` and `arg_count` (usize).

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

Closes #323
2024-09-14 16:12:25 +03:00
jussisaurio
0839211a49 Pass FuncCtx to Insn::Function to keep track of arg count 2024-09-14 12:38:14 +03:00
Pere Diaz Bou
c7270dfb58 env_logger try_init 2024-09-14 08:58:20 +02:00
Pekka Enberg
f67b915855 Merge 'Allocate the right-hand-side register for a binary expression after translating the left-hand-side' from RJ Barman
I found a bug in queries using a like function in the where clause, ex:
`SELECT first_name, last_name FROM users WHERE like('Jas%', first_name)
= 1`.  That panicked with the message:
```
thread 'main' panicked at core/vdbe/mod.rs:1226:33:
internal error: entered unreachable code: Like on non-text registers
```
This was caused by an off-by-one error in the vdbe code for executing a
`ScalarFunc::Like`. However, this only happened in where-like-fn
queries. Queries using the like operator (ex: `SELECT first_name,
last_name FROM users WHERE first_name LIKE 'Jas%'`) did not have this
problem.
I did some digging around, looked at the explains for these queries from
both limbo and sqlite, and it turns out, for binary expressions, limbo
positions the arguments in the register differently, which is the
ultimate root cause of this problem.
For the where-like-fn query, before execution limbo's registers look
like this:
```
[Null, Null, Integer(1), Text("Jas%"), Text("Jason"), Null, Null]
                  ^the rhs 1  ^pattern         ^haystack str
```
Sqlite's look look something like this:
```
[Null, Null, Text("Jas%"), Text("Jason"), Integer(1), Null, Null]
                   ^pattern        ^haystack str  ^the rhs 1
```
Ultimately limbo's execution of scalar like function was looking in
positions 2 and 3 always, but because we stored the right-hand-side
before the like-fn arguments, there was an off-by-one error, and the
non-text register it was finding was the `Integer(1)`.
This PR changes the binary expression translation to allocate the right-
hand-side register *after* translating the left-hand-side, fixing the
off-by-one and matching sqlite's register layout.

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

Closes #321
2024-09-14 08:29:43 +03:00
Pere Diaz Bou
6f30b67ec4 fix overflow range read 2024-09-13 21:34:45 +02:00
Pere Diaz Bou
c69e7db3eb test sequential writes with overflow pages 2024-09-13 21:34:30 +02:00
Pere Diaz Bou
e9bc4b04a7 overflow pages support 2024-09-13 20:32:33 +02:00
Pere Diaz Bou
54615d752d improve debuggabily of string comparisons 2024-09-13 20:32:02 +02:00
Pere Diaz Bou
dd6285560c test adding a page that will overflow 2024-09-13 11:39:18 +02:00
Pere Diaz Bou
4316a5f4c3 simplify filling a cell 2024-09-13 11:39:03 +02:00
Pekka Enberg
59baa209f9 Merge 'core: core btree splitting' from Pere Diaz Bou
This pr adds a new crate to increase expressivity of tests with complex
things like btree node splitting.
To run tests:
```bash
cd core_tester
cargo test test_sequential_write -- --nocapture
```
This prs improves btree balancing with simple page splitting and some
minor refactors.

Closes #316
2024-09-13 10:29:43 +03:00
Pere Diaz Bou
270193b953 use rustqlite 2024-09-13 09:15:55 +02:00
Pere Diaz Bou
acd0298ce9 rename core tester in cargo.toml 2024-09-13 07:56:21 +02:00
Pere Diaz Bou
77be4a1757 rename core_tester -> test 2024-09-13 07:55:07 +02:00
Pere Diaz Bou
9d1bb4d4ea get_mem_page 2024-09-13 07:24:34 +02:00
rjhallsted
dc3d3a492a Fix register positions of binary expression arguments to fix where-like-fn case 2024-09-12 12:03:27 -07:00
Pekka Enberg
b7926dfe7c Merge 'Cache LIKE regexes' from RJ Barman
This PR adds a regex cache to `ProgramState` so that we ca re-use
already constructed regexes while processing LIKE expressions. I didn't
find anywhere else that seemed like a good fit to put an execution-time
only cache like this, so let me know if there's a better spot.
To best match sqlite, I added the constant mask into the `Function`
instruction (this indicates whether the first argument to the function
was determined to be constant at compile time), and decide whether to
use the cache based on its value. I've left the value for
`constant_mask` as 0 on every other kind of `Function` instruction. That
seemed to be the safest choice, as that appears to be what has been
implicitly done up to this point. Happy to change that if you'd advise
otherwise.

Fixes #168
Closes #320
2024-09-12 16:57:57 +03:00
rjhallsted
e67f1e910e Functionally meaningless change to get cargo fmt to play nice in ci 2024-09-10 14:40:57 -07:00
rjhallsted
9791e2074f Fix cargo fmt complaint 2024-09-10 14:32:25 -07:00
rjhallsted
960560dffc Use the same comment for Expr::Like case 2024-09-10 14:26:21 -07:00
rjhallsted
a038d1f700 Add comment on why constant_mask is 1 in ScalarFunc::Like case 2024-09-10 14:24:32 -07:00
rjhallsted
e1b134dd88 Fix the spot that constant_mask was being set to 1 2024-09-10 14:08:15 -07:00
rjhallsted
6ac78dfb03 Cache constructed LIKE regexes if FUNCTION P1 is set 2024-09-10 13:54:52 -07:00
rjhallsted
9f18fdbfd2 Remove unecessary clone when executing LIKE function 2024-09-10 11:25:52 -07:00
Pekka Enberg
c3a57d4c24 Merge 'Add missing assertion to substring() test' from Lauri Virtanen
Closes #317
2024-09-10 16:25:01 +03:00
Lauri Virtanen
8048b4e655 Add missing assertion to substring() test 2024-09-08 19:31:27 +03:00
Pekka Enberg
0d04f0717f scripts/merge-pr.py: Don't wrap code blocks 2024-09-08 08:59:24 +03:00
Pere Diaz Bou
1ea496a169 core_tester: sequential btree write test
Signed-off-by: Pere Diaz Bou <pere-altea@hotmail.com>
2024-09-05 20:50:30 +02:00
Pere Diaz Bou
d87f9c9774 core: multiple level btree page split
Signed-off-by: Pere Diaz Bou <pere-altea@hotmail.com>
2024-09-05 20:50:30 +02:00
Pekka Enberg
ec94770d08 scripts/merge-pr.py: Wrap merge commit message to 72 columns 2024-09-05 19:26:03 +03:00