mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-31 13:54:27 +01:00
Merge 'Fix crash in Next opcode if cursor stack has no pages' from Jussi Saurio
Closes #2924 Unsure if this fix is that great, but it does fix the issue described in #2924 -- added minimal regression test to illustrate the behavior This crash requires a pretty specific set of circumstances: - 3-way join with two innermost being left joins - nullable seek key on the innermost table: * middle table gets nulled out because no matches with the outermost table * hence when we seek the innermost table using middle table values, the seek key is null, so `Insn::IsNull` entirely skips the innermost table Perhaps a bytecode plan illustrates this better: ```sql turso> explain select a.x, b.x, c.x from a left join b on a.y=b.x left join c on b.y=c.x; addr opcode p1 p2 p3 p4 p5 comment ---- ----------------- ---- ---- ---- ------------- -- ------- 0 Init 0 34 0 0 Start at 34 1 OpenRead 0 2 0 0 table=a, root=2, iDb=0 2 OpenRead 1 4 0 0 table=b, root=4, iDb=0 3 OpenRead 2 5 0 0 index=sqlite_autoindex_b_1, root=5, iDb=0 4 OpenRead 3 7 0 0 index=sqlite_autoindex_c_1, root=7, iDb=0 5 Rewind 0 33 0 0 Rewind table a 6 Integer 0 4 0 0 r[4]=0 7 Column 0 1 6 0 r[6]=a.y 8 IsNull 6 28 0 0 if (r[6]==NULL) goto 28 9 SeekGE 2 28 6 0 key=[6..6] 10 IdxGT 2 28 6 0 key=[6..6] 11 DeferredSeek 2 1 0 0 12 Integer 1 4 0 0 r[4]=1 13 Integer 0 5 0 0 r[5]=0 14 Column 1 1 7 0 r[7]=b.y -- if b.y is NULL, we skip the entire table loop between insns 16-23 -- except when we call NullRow and then Goto to re-enter that loop in order to -- return NULL values for the table 15 IsNull 7 24 0 0 if (r[7]==NULL) goto 24 16 SeekGE 3 24 7 0 key=[7..7] 17 IdxGT 3 24 7 0 key=[7..7] 18 Integer 1 5 0 0 r[5]=1 19 Column 0 0 1 0 r[1]=a.x 20 Column 1 0 2 0 r[2]=b.x 21 Column 3 0 3 0 r[3]=sqlite_autoindex_c_1.x 22 ResultRow 1 3 0 0 output=r[1..3] 23 Next 3 17 0 0 24 IfPos 5 27 0 0 r[5]>0 -> r[5]-=0, goto 27 25 NullRow 3 0 0 0 Set cursor 3 to a (pseudo) NULL row 26 Goto 0 18 0 0 27 Next 2 10 0 0 28 IfPos 4 32 0 0 r[4]>0 -> r[4]-=0, goto 32 29 NullRow 1 0 0 0 Set cursor 1 to a (pseudo) NULL row 30 NullRow 2 0 0 0 Set cursor 2 to a (pseudo) NULL row 31 Goto 0 12 0 0 32 Next 0 6 0 0 33 Halt 0 0 0 0 34 Transaction 0 0 3 0 iDb=0 write=false 35 Goto 0 1 0 0 ``` Reviewed-by: Preston Thorpe <preston@turso.tech> Closes #2967
This commit is contained in:
@@ -1213,6 +1213,10 @@ impl BTreeCursor {
|
||||
}
|
||||
None => return Ok(IOResult::Done(false)),
|
||||
}
|
||||
} else if self.stack.current_page == -1 {
|
||||
// This can happen in nested left joins. See:
|
||||
// https://github.com/tursodatabase/turso/issues/2924
|
||||
return Ok(IOResult::Done(false));
|
||||
}
|
||||
loop {
|
||||
let mem_page = self.stack.top_ref();
|
||||
@@ -4274,7 +4278,6 @@ impl BTreeCursor {
|
||||
if self.valid_state == CursorValidState::Invalid {
|
||||
return Ok(IOResult::Done(false));
|
||||
}
|
||||
|
||||
loop {
|
||||
match self.advance_state {
|
||||
AdvanceState::Start => {
|
||||
|
||||
@@ -318,4 +318,14 @@ do_execsql_test_on_specific_db {:memory:} left-join-seek-key-regression-test {
|
||||
CREATE TABLE u (x INTEGER PRIMARY KEY);
|
||||
INSERT INTO t VALUES (1);
|
||||
SELECT * FROM t LEFT JOIN u ON false WHERE u.x = 1;
|
||||
} {}
|
||||
} {}
|
||||
|
||||
# regression test for issue 2924: calling Next on a cursor that hasn't moved yet
|
||||
do_execsql_test_on_specific_db {:memory:} next-crash {
|
||||
create table a(x int primary key,y);
|
||||
create table b(x int primary key,y);
|
||||
create table c(x int primary key,y);
|
||||
insert into a values (1,1),(2,2);
|
||||
select a.x, b.x, c.x from a left join b on a.y=b.x left join c on b.y=c.x;
|
||||
} {1||
|
||||
2||}
|
||||
|
||||
Reference in New Issue
Block a user