Commit graph

185 commits

Author SHA1 Message Date
Anton Harniakou
6c8eef2fac Support DROP INDEX
This commit adds suport for DROP INDEX.
Bytecode produced by this commit differs from SQLITE's bytecode, main
reason we don't do autovacuum or repacking of pages like SQLITE does.
2025-05-04 12:13:16 +03:00
Jussi Saurio
c9eb56b54a Merge 'Read only mode' from Pedro Muniz
Closes #1413 . Basically, SQLite emits a check in a transaction to see
if it is attempting to write. If the db is in read only mode, it throws
an error, else the statement is executed. Mirroring how Rusqlite does
it, I modified the `OpenFlags` to use bitflags to better configure how
we open our VFS. This modification, will enable us to run tests against
the same database in parallel.

Closes #1433
2025-05-03 19:15:06 +03:00
Jussi Saurio
306e097950 Merge 'Fix bug: we cant remove order by terms from the head of the list' from Jussi Saurio
we had an incorrect optimization in `eliminate_orderby_like_groupby()`
where it could remove e.g. the first term of the ORDER BY if it matched
the first GROUP BY term and the result set was naturally ordered by that
term. this is invalid. see e.g.:
```sql
main branch - BAD: removes the `ORDER BY id` term because the results are naturally ordered by id.
However, this results in sorting the entire thing by last name only!

limbo> select id, last_name, count(1) from users GROUP BY 1,2 order by id, last_name desc limit 3;
┌──────┬───────────┬───────────┐
│ id   │ last_name │ count (1) │
├──────┼───────────┼───────────┤
│ 6235 │ Zuniga    │         1 │
├──────┼───────────┼───────────┤
│ 8043 │ Zuniga    │         1 │
├──────┼───────────┼───────────┤
│  944 │ Zimmerman │         1 │
└──────┴───────────┴───────────┘

after fix - GOOD:

limbo> select id, last_name, count(1) from users GROUP BY 1,2 order by id, last_name desc limit 3;
┌────┬───────────┬───────────┐
│ id │ last_name │ count (1) │
├────┼───────────┼───────────┤
│  1 │ Foster    │         1 │
├────┼───────────┼───────────┤
│  2 │ Salazar   │         1 │
├────┼───────────┼───────────┤
│  3 │ Perry     │         1 │
└────┴───────────┴───────────┘

I also refactored sorters to always use the ast `SortOrder` instead of boolean vectors, and use the `compare_immutable()` utility we use inside btrees too.

Closes #1365
2025-05-03 12:48:08 +03:00
pedrocarlo
0c22382f3c shared lock on file and throw ReadOnly error in transaction 2025-05-02 16:30:48 -03:00
Jussi Saurio
6096cfb3d8 Merge 'Add PRAGMA schema_version' from Anton Harniakou
This PR adds `PRAGMA schema_version` to get the value of the schema-
version integer at offset 40 in the database header.

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

Closes #1427
2025-05-01 10:50:02 +03:00
Jussi Saurio
a525feb7ad Merge 'Fix: allow page_size=65536' from meteorgan
Since `page_size` in `DatabaseHeader` can be 1 representing 65526 bytes,
it can't be used it directly.  Additionally, we should use `u32` instead
of `u16` or `usize` in other contexts.

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

Closes #1411
2025-05-01 10:46:19 +03:00
Jussi Saurio
7643b7666c Merge 'Fix page_count pragma' from meteorgan
This issue was introduced in #819. However, I believe the solution is
suboptimal because `pragma page_count` can never return 1, which is
inconsistent with SQLite.
<img width="442" alt="image" src="https://github.com/user-
attachments/assets/c772eae7-3e9f-4687-a94a-230deb0eb034" />
To align with SQLite's behavior, we should allocate the first page when
the first schema object is created, rather than immediately after
creating database. And it's always preferable to return an accurate page
count.

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

Closes #1407
2025-05-01 10:36:36 +03:00
Anton Harniakou
525b7fdbaa Add PRAGMA schema_version 2025-04-30 09:41:04 +03:00
Pere Diaz Bou
a30241ca91 Add state machine for op_idx_delete + DeleteState simplification
DeleteState had a bit too many unnecessary states so I removed them.
Usually we care about having a different state when I/O is triggered
requiring a state to be stored for later.

Furthermore, there was a bug with op_idx_delete where if balance is
triggered, op_idx_delete wouldn't be re-entrant. So a state machine was
added to prevent that from happening.
2025-04-29 14:58:20 +03:00
meteorgan
d2dce740f7 fix some issues about page_size 2025-04-28 16:13:07 +08:00
Pere Diaz Bou
63a94e7c62 Merge 'Emit IdxDelete instruction and some fixes on seek after deletion' from Pere Diaz Bou
Previously `DELETE FROM ...` only emitted deletes for main table, but
this is incorrect as we want to remove entries from index tables as
well.

Closes #1383
2025-04-28 09:13:54 +03:00
meteorgan
f3f09a5b7b Fix pragma page_count 2025-04-26 21:45:18 +08:00
Jussi Saurio
7137f4ab3b Merge 'Feature: Composite Primary key constraint' from Pedro Muniz
Closes #1384 . This PR implements Primary Key constraint for inserts. As
can be seen in the issue, if you created an Index with a Primary Key
constraint, it could trigger `Unique Constraint` error, but still insert
the record. Sqlite uses the opcode `NoConflict` to check if the record
already exists in the Btree. As we did not have this Opcode yet, I
implemented it. It is very similar to `NotFound` with the difference
that if any value in the Record is Null, it will immediately jump to the
offset. The added benefit of implementing this, is that now we fully
support Composite Primary Keys. Also, I think with the current
implementation, it will be trivial to implement the Unique opcode for
Insert. To support Updates, I need to understand more of the plan
optimizer to and find where we are Making the Record and opening the
autoindex.
For testing, I have written a test generator to generate many different
tables that can have a varying numbers of Primary Keys.
```sql
limbo> CREATE TABLE users (id INT, username TEXT, PRIMARY KEY (id, username));
limbo> INSERT INTO users VALUES (1, 'alice');
limbo> explain INSERT INTO users VALUES (1, 'alice');
addr  opcode             p1    p2    p3    p4             p5  comment
----  -----------------  ----  ----  ----  -------------  --  -------
0     Init               0     16    0                    0   Start at 16
1     OpenWrite          0     2     0                    0
2     Integer            1     2     0                    0   r[2]=1
3     String8            0     3     0     alice          0   r[3]='alice'
4     OpenWrite          1     3     0                    0
5     NewRowId           0     1     0                    0
6     Copy               2     5     0                    0   r[5]=r[2]
7     Copy               3     6     0                    0   r[6]=r[3]
8     Copy               1     7     0                    0   r[7]=r[1]
9     MakeRecord         5     3     8                    0   r[8]=mkrec(r[5..7])
10    NoConflict         1     12    5     2              0   key=r[5]
11    Halt               1555  0     0     users.id, users.username  0
12    IdxInsert          1     8     5                    0   key=r[8]
13    MakeRecord         2     2     4                    0   r[4]=mkrec(r[2..3])
14    Insert             0     4     1                    0
15    Halt               0     0     0                    0
16    Transaction        0     1     0                    0   write=true
17    Goto               0     1     0                    0
limbo> INSERT INTO users VALUES (1, 'alice');
  × Runtime error: UNIQUE constraint failed: users.id, users.username (19)
limbo> INSERT INTO users VALUES (1, 'bob');
limbo> INSERT INTO users VALUES (1, 'bob');
  × Runtime error: UNIQUE constraint failed: users.id, users.username (19)
```

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

Closes #1393
2025-04-24 23:25:30 +03:00
pedrocarlo
2e147b20a8 Adjustments and explicitely just emitting NoConflict on unique indexes 2025-04-24 13:13:39 -03:00
Pere Diaz Bou
3ba5c2349f add corrupt error if no matching record found for idxdelete
a
2025-04-24 16:29:05 +02:00
Pere Diaz Bou
b7970a286d implement IdxDelete
clippy

revert op_idx_ge changes

fmt

fmt again

rever op_idx_gt changes
2025-04-24 16:23:34 +02:00
Jussi Saurio
2e8042510e Merge 'Pragma page size reading' from Anton Harniakou
1) Fix a bug where cli pretty mode would not print pragma results;
2) Add ability to read page_size using PRAGMA page_size;

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

Closes #1394
2025-04-24 11:08:55 +03:00
Jussi Saurio
3798b4aa8b use SortOrder in sorters always 2025-04-24 10:34:06 +03:00
Anton Harniakou
0a69ea0138 Support reading db page size using PRAGMA page_size 2025-04-24 10:12:02 +03:00
pedrocarlo
b6036cc79d Primary key constraint working 2025-04-23 16:44:13 -03:00
Levy A.
f1ee92bf2d numeric types overhaul 2025-04-23 08:34:58 -03:00
Jussi Saurio
f256fb46fd remove print spam from index insert 2025-04-21 14:59:13 +03:00
PThorpe92
d53c60e071
Prevent double allocations for VFilter args in vdbe 2025-04-17 14:01:45 -04:00
PThorpe92
853af16946
Implement xBestIndex for virtual table api to improve query planning 2025-04-17 13:53:27 -04:00
Jussi Saurio
1189b7a288 codegen: add support for descending indexes 2025-04-16 13:58:12 +03:00
Jussi Saurio
b1073da4a5 btree: add support fo descending indexes 2025-04-16 13:58:12 +03:00
Jussi Saurio
13a703d636 Merge 'Add BeginSubrtn, NotFound and Affinity bytecodes' from Diego Reis
I'm working on an optimization of `WHERE .. IN (..)` statements that
requires these bytecodes.
```sh
sqlite> explain select * from users where first_name in ('alice', 'bob', 'charlie');
addr  opcode         p1    p2    p3    p4             p5  comment
----  -------------  ----  ----  ----  -------------  --  -------------
0     Init           0     35    0                    0   Start at 35
1     OpenRead       0     2     0     10             0   root=2 iDb=0; users
2     Rewind         0     34    0                    0
3       Noop           0     0     0                    0   begin IN expr
4       BeginSubrtn    0     1     0                    0   r[1]=NULL <---- Here
5         Once           0     17    0                    0
6         OpenEphemeral  1     1     0     k(1,B)         0   nColumn=1; RHS of IN operator
7         String8        0     2     0     alice          0   r[2]='alice'
8         MakeRecord     2     1     3     B              0   r[3]=mkrec(r[2])
9         IdxInsert      1     3     2     1              0   key=r[3]
10        String8        0     2     0     bob            0   r[2]='bob'
11        MakeRecord     2     1     3     B              0   r[3]=mkrec(r[2])
12        IdxInsert      1     3     2     1              0   key=r[3]
13        String8        0     2     0     charlie        0   r[2]='charlie'
14        MakeRecord     2     1     3     B              0   r[3]=mkrec(r[2])
15        IdxInsert      1     3     2     1              0   key=r[3]
16        NullRow        1     0     0                    0
17      Return         1     5     1                    0
18      Column         0     1     4                    0   r[4]= cursor 0 column 1
19      IsNull         4     33    0                    0   if r[4]==NULL goto 33
20      Affinity       4     1     0     B              0   affinity(r[4]) <---- Here
21      NotFound       1     33    4     1              0   key=r[4]; end IN expr <---- Here
22      Rowid          0     5     0                    0   r[5]=users.rowid
23      Column         0     1     6                    0   r[6]= cursor 0 column 1
24      Column         0     2     7                    0   r[7]= cursor 0 column 2
25      Column         0     3     8                    0   r[8]= cursor 0 column 3
26      Column         0     4     9                    0   r[9]= cursor 0 column 4
27      Column         0     5     10                   0   r[10]= cursor 0 column 5
28      Column         0     6     11                   0   r[11]= cursor 0 column 6
29      Column         0     7     12                   0   r[12]= cursor 0 column 7
30      Column         0     8     13                   0   r[13]= cursor 0 column 8
31      Column         0     9     14                   0   r[14]= cursor 0 column 9
32      ResultRow      5     10    0                    0   output=r[5..14]
33    Next           0     3     0                    1
34    Halt           0     0     0                    0
35    Transaction    0     0     3     0              1   usesStmtJournal=0
36    Goto           0     1     0                    0
```
EDIT: [Found](https://sqlite.org/opcode.html#Found) and
[NoConflict](https://sqlite.org/opcode.html#NoConflict) can be easily
derived from `NotFound` but I wanted to be concise, I could do it in
another PR :)

Closes #1345
2025-04-15 20:25:55 +03:00
Diego Reis
58efb90467 core: Add Affinity bytecode
Apply affinities to a range of P2 registers starting with P1.

P4 is a string that is P2 characters long. The N-th character of the string indicates the column affinity that should be used for the N-th memory cell in the range.
2025-04-15 09:52:04 -03:00
Diego Reis
c5161311fc core/vdbe: Add NotFound bytecode
If P4==0 then register P3 holds a blob constructed by MakeRecord. If P4>0 then register P3 is the first of P4 registers that form an unpacked record.

Cursor P1 is on an index btree. If the record identified by P3 and P4 is not the prefix of any entry in P1 then a jump is made to P2. If P1 does contain an entry whose prefix matches the P3/P4 record then control falls through to the next instruction and P1 is left pointing at the matching entry.

This operation leaves the cursor in a state where it cannot be advanced in either direction. In other words, the Next and Prev opcodes do not work after this operation.
2025-04-15 09:52:04 -03:00
Jussi Saurio
e299a0e77e vdbe: add Insn::IdxRowId 2025-04-15 15:13:18 +03:00
Jussi Saurio
cc8f89e8e0 Merge 'Fix Unary Negate Operation on Blobs' from Pedro Muniz
Fixing stuff that appears in Fuzz testing.
# Before
<img width="668" alt="image" src="https://github.com/user-
attachments/assets/f1f59b63-5173-4932-98b2-774803cb8a8e" />
```
limbo> EXPLAIN SELECT -x'';
addr  opcode             p1    p2    p3    p4             p5  comment
----  -----------------  ----  ----  ----  -------------  --  -------
0     Init               0     5     0                    0   Start at 5
1     Blob               0     2     0                    0   r[2]= (len=0)
2     Multiply           3     2     1                    0   r[1]=r[3]*r[2]
3     ResultRow          1     1     0                    0   output=r[1]
4     Halt               0     0     0                    0
5     Integer            -1    3     0                    0   r[3]=-1
6     Goto               0     1     0                    0
```
# After
<img width="175" alt="image" src="https://github.com/user-
attachments/assets/9f361dc3-b243-4d69-bdd2-d6a2bbc0bf20" />
```
limbo> EXPLAIN SELECT -x'';
addr  opcode             p1    p2    p3    p4             p5  comment
----  -----------------  ----  ----  ----  -------------  --  -------
0     Init               0     5     0                    0   Start at 5
1     Blob               0     2     0                    0   r[2]= (len=0)
2     Subtract           3     2     1                    0   r[1]=r[3]-r[2]
3     ResultRow          1     1     0                    0   output=r[1]
4     Halt               0     0     0                    0
5     Integer            0     3     0                    0   r[3]=0
6     Goto               0     1     0                    0
```
# Sqlite
```
sqlite> SELECT -x'';
0
sqlite> EXPLAIN SELECT -x'';
addr  opcode         p1    p2    p3    p4             p5  comment
----  -------------  ----  ----  ----  -------------  --  -------------
0     Init           0     4     0                    0   Start at 4
1     Subtract       3     2     1                    0   r[1]=r[2]-r[3]
2     ResultRow      1     1     0                    0   output=r[1]
3     Halt           0     0     0                    0
4     Integer        0     2     0                    0   r[2]=0
5     Blob           0     3     0                    0   r[3]= (len=0)
6     Goto           0     1     0                    0
```

Closes #1333
2025-04-15 14:42:54 +03:00
Jussi Saurio
6463448fdc Merge 'Fix incompatibility AND Operation' from Pedro Muniz
Sqlite reference implementation: https://github.com/sqlite/sqlite/blob/8
37dc09bce7de8971c7488b70cf5da93c60fbed0/src/vdbe.c#L2558
We did not support blobs before and our ordering of the match statements
were incorrect when one of the arguments was NULL

Closes #1337
2025-04-15 14:37:29 +03:00
Jussi Saurio
5a38b38e71 Merge 'Feature: VDestroy for Dropping Virtual Tables' from Pedro Muniz
Reviewed-by: Preston Thorpe (@PThorpe92)

Closes #1274
2025-04-15 14:34:30 +03:00
pedrocarlo
e1ddf5ffcc Fix Unary Negate Operation on Blobs 2025-04-14 12:05:00 -03:00
Jussi Saurio
d20782350d Merge 'support modifiers for julianday()' from meteorgan
Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #1321
2025-04-14 11:52:43 +03:00
Jussi Saurio
23f8fffe12 Add Insn::OpenAutoindex, which is just an alias for OpenEphemeral 2025-04-14 11:23:37 +03:00
Jussi Saurio
9dadc58194 Add support for Insn::Once 2025-04-14 11:23:37 +03:00
Jussi Saurio
d286a56e15 refactor: fold Async/Await insns into a single instruction 2025-04-14 09:40:20 +03:00
pedrocarlo
af456513d1 Fix incompatibility AND Expression 2025-04-13 22:38:43 -03:00
pedrocarlo
2181de79de add destroy function to vtab 2025-04-13 17:06:12 -03:00
pedrocarlo
000d8756ec Implment VDestroy opcode 2025-04-13 17:06:12 -03:00
Diego Reis
fd79ad2644 core/vdbe: Change is_btree to is_table in OpenEphemeral 2025-04-13 11:15:01 -03:00
Diego Reis
4c315e1bb6 core/vdbe: Update OpenEphemeral to use CreateBtreeFlags 2025-04-13 11:13:25 -03:00
Diego Reis
035e6dcef4 core/vdbe: Fix logic error during btree creation
I do thing we should change this 1,2 flag to 0,1 or just an enum, to be more rustacean. The current state can be very misleading
2025-04-13 11:10:06 -03:00
Diego Reis
61c324cca5 core/vdbe: Add missing work to get cursor and transient table usable 2025-04-13 11:10:06 -03:00
Diego Reis
bcac1fe778 core/vdbe: Rename page_io to db_file in OpenEphemeral 2025-04-13 11:10:06 -03:00
Diego Reis
d9bf383507 core/io: Untie MemoryIO's lifetime of the IO layer 2025-04-13 11:10:06 -03:00
Diego Reis
79f8b83cbe Fix dumb clippy errors 2025-04-13 11:10:06 -03:00
Diego Reis
66e12e1c2d core/vdbe: Create OpenEphemeral bytecode
"Open a new cursor P1 to a transient table. The cursor is always opened read/write even if the main database is read-only. The ephemeral table is deleted automatically when the cursor is closed.

If the cursor P1 is already opened on an ephemeral table, the table is cleared (all content is erased)."

There is still some work to do, but this is a basic setup
2025-04-13 11:10:06 -03:00
Jussi Saurio
8e601959ad Merge 'Fuzz fix some operations' from Pedro Muniz
Me and @diegoreis42 were late at night, running the new fuzzer and saw
some errors with `Bit-Not` and `Not` operations. Added some tests as
well for those fixes.

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

Closes #1328
2025-04-13 17:02:19 +03:00