Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allocate the right-hand-side register for a binary expression after translating the left-hand-side #321

Merged
merged 1 commit into from
Sep 14, 2024

Conversation

barbarj
Copy link
Contributor

@barbarj barbarj commented Sep 12, 2024

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.

Copy link
Collaborator

@jussisaurio jussisaurio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

@penberg penberg closed this in c416b5d Sep 14, 2024
@penberg penberg merged commit c416b5d into tursodatabase:main Sep 14, 2024
30 checks passed
jussisaurio added a commit that referenced this pull request Sep 14, 2024
…m Jussi Saurio

#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
@barbarj barbarj deleted the fix-scalar-like branch September 16, 2024 21:28
penberg added a commit that referenced this pull request Sep 22, 2024
…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
penberg pushed a commit that referenced this pull request Sep 22, 2024
…m Jussi Saurio

#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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants