Pere Diaz Bou
0f631101df
core: change page idx type from usize to i64
...
MVCC is like the annoying younger cousin (I know because I was him) that
needs to be treated differently. MVCC requires us to use root_pages that
might not be allocated yet, and the plan is to use negative root_pages
for that case. Therefore, we need i64 in order to fit this change.
2025-09-29 18:38:43 +02:00
Pekka Enberg
f88f39082a
core/vdbe: Fix MakeRecord affinity handling
...
The MakeRecord instruction now accepts an optional affinity_str
parameter that applies column-specific type conversions before creating
records. When provided, the affinity string is applied
character-by-character to each register using the existing
apply_affinity_char() function, matching SQLite's behavior.
Fixes #2040
Fixes #2041
2025-09-08 18:49:13 +03:00
Alex Miller
370da9fa59
ANALYZE creates sqlite_stat1 if it doesn't exist
...
This change replaces a bail_parse_error!() when sqlite_stat1 doesn't
exist with the appropriate codegen to create the table, and handle both
cases of the table existing or not existing.
SQLite's codegen looks like:
sqlite> create table stat_test(a,b,c);
sqlite> explain analyze stat_test;
addr opcode p1 p2 p3 p4 p5 comment
---- ------------- ---- ---- ---- ------------- -- -------------
0 Init 0 40 0 0 Start at 40
1 ReadCookie 0 3 2 0
2 If 3 5 0 0
3 SetCookie 0 2 4 0
4 SetCookie 0 5 1 0
5 CreateBtree 0 2 1 0 r[2]=root iDb=0 flags=1
6 OpenWrite 0 1 0 5 0 root=1 iDb=0
7 NewRowid 0 1 0 0 r[1]=rowid
8 Blob 6 3 0 0 r[3]= (len=6)
9 Insert 0 3 1 8 intkey=r[1] data=r[3]
10 Close 0 0 0 0
11 Close 0 0 0 0
12 Null 0 4 5 0 r[4..5]=NULL
13 Noop 4 0 4 0
14 OpenWrite 3 1 0 5 0 root=1 iDb=0; sqlite_master
15 SeekRowid 3 17 1 0 intkey=r[1]
16 Rowid 3 5 0 0 r[5]= rowid of 3
17 IsNull 5 26 0 0 if r[5]==NULL goto 26
18 String8 0 6 0 table 0 r[6]='table'
19 String8 0 7 0 sqlite_stat1 0 r[7]='sqlite_stat1'
20 String8 0 8 0 sqlite_stat1 0 r[8]='sqlite_stat1'
21 Copy 2 9 0 0 r[9]=r[2]
22 String8 0 10 0 CREATE TABLE sqlite_stat1(tbl,idx,stat) 0 r[10]='CREATE TABLE sqlite_stat1(tbl,idx,stat)'
23 MakeRecord 6 5 4 BBBDB 0 r[4]=mkrec(r[6..10])
24 Delete 3 68 5 0
25 Insert 3 4 5 0 intkey=r[5] data=r[4]
26 SetCookie 0 1 2 0
27 ParseSchema 0 0 0 tbl_name='sqlite_stat1' AND type!='trigger' 0
28 OpenWrite 0 2 0 3 16 root=2 iDb=0; sqlite_stat1
29 OpenRead 5 2 0 3 0 root=2 iDb=0; stat_test
30 String8 0 18 0 stat_test 0 r[18]='stat_test'; stat_test
31 Count 5 20 0 0 r[20]=count()
32 IfNot 20 37 0 0
33 Null 0 19 0 0 r[19]=NULL
34 MakeRecord 18 3 16 BBB 0 r[16]=mkrec(r[18..20])
35 NewRowid 0 12 0 0 r[12]=rowid
36 Insert 0 16 12 8 intkey=r[12] data=r[16]
37 LoadAnalysis 0 0 0 0
38 Expire 0 0 0 0
39 Halt 0 0 0 0
40 Transaction 0 1 1 0 1 usesStmtJournal=1
41 Goto 0 1 0 0
And now Turso's looks like:
turso> create table stat_test(a,b,c);
turso> explain analyze stat_test;
addr opcode p1 p2 p3 p4 p5 comment
---- ----------------- ---- ---- ---- ------------- -- -------
0 Init 0 23 0 0 Start at 23
1 Null 0 1 0 0 r[1]=NULL
2 CreateBtree 0 2 1 0 r[2]=root iDb=0 flags=1
3 OpenWrite 0 1 0 0 root=1; iDb=0
4 NewRowid 0 3 0 0 r[3]=rowid
5 String8 0 4 0 table 0 r[4]='table'
6 String8 0 5 0 sqlite_stat1 0 r[5]='sqlite_stat1'
7 String8 0 6 0 sqlite_stat1 0 r[6]='sqlite_stat1'
8 Copy 2 7 0 0 r[7]=r[2]
9 String8 0 8 0 CREATE TABLE sqlite_stat1(tbl,idx,stat) 0 r[8]='CREATE TABLE sqlite_stat1(tbl,idx,stat)'
10 MakeRecord 4 5 9 0 r[9]=mkrec(r[4..8])
11 Insert 0 9 3 sqlite_stat1 0 intkey=r[3] data=r[9]
12 ParseSchema 0 0 0 tbl_name = 'sqlite_stat1' AND type != 'trigger' 0 tbl_name = 'sqlite_stat1' AND type != 'trigger'
13 OpenWrite 1 2 0 0 root=2; iDb=0
14 OpenRead 2 2 0 0 =stat_test, root=2, iDb=0
15 String8 0 12 0 stat_test 0 r[12]='stat_test'
16 Count 2 14 0 0
17 IfNot 14 22 0 0 if !r[14] goto 22
18 Null 0 13 0 0 r[13]=NULL
19 MakeRecord 12 3 11 0 r[11]=mkrec(r[12..14])
20 NewRowid 1 10 0 0 r[10]=rowid
21 Insert 1 11 10 sqlite_stat1 0 intkey=r[10] data=r[11]
22 Halt 0 0 0 0
23 Goto 0 1 0 0
The notable difference in size is following the same codegen difference
in CREATE TABLE, where sqlite's odd dance of adding a placeholder entry
which is immediately replaced is instead done in tursodb as just
inserting the correct row in the first place. Aside from lines 6-13 of
sqlite's vdbe being missing, there's still the lack of LoadAnalysis,
Expire, and Cookie management.
2025-08-24 13:35:39 -07:00
Alex Miller
4619890ffc
Add basic support for ANALYZE statement.
...
This permits only `ANALYZE <table_name>` to work, and all other forms
fail with a parse error (as documented in the tests).
On SQLite, ANALYZE generates:
sqlite> CREATE TABLE sqlite_stat1(tbl,idx,stat);
sqlite> CREATE TABLE iiftest(a int, b int, c int);
sqlite> EXPLAIN ANALYZE iiftest;
addr opcode p1 p2 p3 p4 p5 comment
---- ------------- ---- ---- ---- ------------- -- -------------
0 Init 0 21 0 0 Start at 21
1 Null 0 1 0 0 r[1]=NULL
2 OpenWrite 3 4 0 3 0 root=4 iDb=0; sqlite_stat1
3 Rewind 3 9 0 0
4 Column 3 0 2 0 r[2]= cursor 3 column 0
5 Ne 3 8 2 BINARY-8 81 if r[2]!=r[3] goto 8
6 Rowid 3 4 0 0 r[4]=sqlite_stat1.rowid
7 Delete 3 0 0 sqlite_stat1 2
8 Next 3 4 0 1
9 OpenWrite 0 4 0 3 0 root=4 iDb=0; sqlite_stat1
10 OpenRead 4 2 0 3 0 root=2 iDb=0; iiftest
11 String8 0 11 0 iiftest 0 r[11]='iiftest'; iiftest
12 Count 4 13 0 0 r[13]=count()
13 IfNot 13 18 0 0
14 Null 0 12 0 0 r[12]=NULL
15 MakeRecord 11 3 9 BBB 0 r[9]=mkrec(r[11..13])
16 NewRowid 0 5 0 0 r[5]=rowid
17 Insert 0 9 5 8 intkey=r[5] data=r[9]
18 LoadAnalysis 0 0 0 0
19 Expire 0 0 0 0
20 Halt 0 0 0 0
21 Transaction 0 1 9 0 1 usesStmtJournal=0
22 String8 0 3 0 iiftest 0 r[3]='iiftest'
23 Goto 0 1 0 0
Turso can now generate:
turso> create table sqlite_stat1(tbl,idx,stat);
turso> create table iiftest(a int, b int, c int);
turso> explain analyze iiftest;
addr opcode p1 p2 p3 p4 p5 comment
---- ----------------- ---- ---- ---- ------------- -- -------
0 Init 0 19 0 0 Start at 19
1 Null 0 1 0 0 r[1]=NULL
2 OpenWrite 0 2 0 0 root=2; iDb=0
3 Rewind 0 9 0 0 Rewind sqlite_stat1
4 Column 0 0 2 0 r[2]=sqlite_stat1.tbl
5 Ne 2 3 9 0 if r[2]!=r[3] goto 9
6 RowId 0 4 0 0 r[4]=sqlite_stat1.rowid
7 Delete 0 0 0 sqlite_stat1 0
8 Next 0 4 0 0
9 OpenWrite 1 2 0 0 root=2; iDb=0
10 OpenRead 2 3 0 0 =iiftest, root=3, iDb=0
11 String8 0 7 0 iiftest 0 r[7]='iiftest'
12 Count 2 9 0 0
13 IfNot 9 18 0 0 if !r[9] goto 18
14 Null 0 8 0 0 r[8]=NULL
15 MakeRecord 7 3 6 0 r[6]=mkrec(r[7..9])
16 NewRowid 1 5 0 0 r[5]=rowid
17 Insert 1 6 5 sqlite_stat1 0 intkey=r[5] data=r[6]
18 Halt 0 0 0 0
19 String8 0 3 0 iiftest 0 r[3]='iiftest'
20 Goto 0 1 0 0
Note the missing support for LoadAnalysis and Expire, but there's no
optimizer work done yet to leverage any gathered statistics yet anyway.
2025-08-22 23:18:53 -07:00