From 541939675513c52b7efbe3bc58453cbbfa57df51 Mon Sep 17 00:00:00 2001 From: Glauber Costa Date: Wed, 29 Jan 2025 09:47:27 -0500 Subject: [PATCH 1/4] start page count at 0 That's what sqlite does. For example, for an empty database: $ sqlite3 file:memory "pragma page_count" 0 --- core/storage/sqlite3_ondisk.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/storage/sqlite3_ondisk.rs b/core/storage/sqlite3_ondisk.rs index 5543fa7db..c2784a645 100644 --- a/core/storage/sqlite3_ondisk.rs +++ b/core/storage/sqlite3_ondisk.rs @@ -223,7 +223,7 @@ impl Default for DatabaseHeader { min_embed_frac: 32, min_leaf_frac: 32, change_counter: 1, - database_size: 1, + database_size: 0, freelist_trunk_page: 0, freelist_pages: 0, schema_cookie: 0, From f0660a9227a2269507eef8f305a3be8c2ecc0884 Mon Sep 17 00:00:00 2001 From: Glauber Costa Date: Wed, 29 Jan 2025 10:47:09 -0500 Subject: [PATCH 2/4] add compat statement about CreateBTree opcode It is partially supported - only on the existing database. --- COMPAT.md | 345 +++++++++++++++++++++++++++--------------------------- 1 file changed, 173 insertions(+), 172 deletions(-) diff --git a/COMPAT.md b/COMPAT.md index e459e71f2..182a751a0 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -401,178 +401,179 @@ Modifiers: ## SQLite VDBE opcodes -| Opcode | Status | -|----------------|--------| -| Add | Yes | -| AddImm | No | -| Affinity | No | -| AggFinal | Yes | -| AggStep | Yes | -| AggStep | Yes | -| And | Yes | -| AutoCommit | No | -| BitAnd | Yes | -| BitNot | Yes | -| BitOr | Yes | -| Blob | Yes | -| Checkpoint | No | -| Clear | No | -| Close | No | -| CollSeq | No | -| Column | Yes | -| Compare | Yes | -| Concat | Yes | -| Copy | Yes | -| Count | No | -| CreateIndex | No | -| CreateTable | No | -| DecrJumpZero | Yes | -| Delete | No | -| Destroy | No | -| Divide | Yes | -| DropIndex | No | -| DropTable | No | -| DropTrigger | No | -| EndCoroutine | Yes | -| Eq | Yes | -| Expire | No | -| Explain | No | -| FkCounter | No | -| FkIfZero | No | -| Found | No | -| Function | Yes | -| Ge | Yes | -| Gosub | Yes | -| Goto | Yes | -| Gt | Yes | -| Halt | Yes | -| HaltIfNull | No | -| IdxDelete | No | -| IdxGE | Yes | -| IdxInsert | No | -| IdxLT | No | -| IdxRowid | No | -| If | Yes | -| IfNeg | No | -| IfNot | Yes | -| IfPos | Yes | -| IfZero | No | -| IncrVacuum | No | -| Init | Yes | -| InitCoroutine | Yes | -| Insert | No | -| InsertAsync | Yes | -| InsertAwait | Yes | -| InsertInt | No | -| Int64 | No | -| Integer | Yes | -| IntegrityCk | No | -| IsNull | Yes | -| IsUnique | No | -| JournalMode | No | -| Jump | Yes | -| Last | No | -| Le | Yes | -| LoadAnalysis | No | -| Lt | Yes | -| MakeRecord | Yes | -| MaxPgcnt | No | -| MemMax | No | -| Move | No | -| Multiply | Yes | -| MustBeInt | Yes | -| Ne | Yes | -| NewRowid | Yes | -| Next | No | -| NextAsync | Yes | -| NextAwait | Yes | -| Noop | No | -| Not | Yes | -| NotExists | Yes | -| NotFound | No | -| NotNull | Yes | -| Null | Yes | -| NullRow | Yes | -| Once | No | -| OpenAutoindex | No | -| OpenEphemeral | No | -| OpenPseudo | Yes | -| OpenRead | Yes | -| OpenReadAsync | Yes | -| OpenWrite | No | -| OpenWriteAsync | Yes | -| OpenWriteAwait | Yes | -| Or | Yes | -| Pagecount | No | -| Param | No | -| ParseSchema | No | -| Permutation | No | -| Prev | No | -| PrevAsync | Yes | -| PrevAwait | Yes | -| Program | No | -| ReadCookie | No | -| Real | Yes | -| RealAffinity | Yes | -| Remainder | Yes | -| ResetCount | No | -| ResultRow | Yes | -| Return | Yes | -| Rewind | Yes | -| RewindAsync | Yes | -| RewindAwait | Yes | -| RowData | No | -| RowId | Yes | -| RowKey | No | -| RowSetAdd | No | -| RowSetRead | No | -| RowSetTest | No | -| Rowid | Yes | -| SCopy | No | -| Savepoint | No | -| Seek | No | -| SeekGe | Yes | -| SeekGt | Yes | -| SeekLe | No | -| SeekLt | No | -| SeekRowid | Yes | -| Sequence | No | -| SetCookie | No | -| ShiftLeft | Yes | -| ShiftRight | Yes | -| SoftNull | Yes | -| Sort | No | -| SorterCompare | No | -| SorterData | Yes | -| SorterInsert | Yes | -| SorterNext | Yes | -| SorterOpen | Yes | -| SorterSort | Yes | -| String | No | -| String8 | Yes | -| Subtract | Yes | -| TableLock | No | -| ToBlob | No | -| ToInt | No | -| ToNumeric | No | -| ToReal | No | -| ToText | No | -| Trace | No | -| Transaction | Yes | -| VBegin | No | -| VColumn | No | -| VCreate | No | -| VDestroy | No | -| VFilter | No | -| VNext | No | -| VOpen | No | -| VRename | No | -| VUpdate | No | -| Vacuum | No | -| Variable | No | -| VerifyCookie | No | -| Yield | Yes | -| ZeroOrNull | Yes | +| Opcode | Status | Comment | +|----------------|--------|---------| +| Add | Yes | | +| AddImm | No | | +| Affinity | No | | +| AggFinal | Yes | | +| AggStep | Yes | | +| AggStep | Yes | | +| And | Yes | | +| AutoCommit | No | | +| BitAnd | Yes | | +| BitNot | Yes | | +| BitOr | Yes | | +| Blob | Yes | | +| Checkpoint | No | | +| Clear | No | | +| Close | No | | +| CollSeq | No | | +| Column | Yes | | +| Compare | Yes | | +| Concat | Yes | | +| Copy | Yes | | +| Count | No | | +| CreateBTree | Partial| no temp databases | +| CreateTable | No | | +| CreateTable | No | | +| DecrJumpZero | Yes | | +| Delete | No | | +| Destroy | No | | +| Divide | Yes | | +| DropIndex | No | | +| DropTable | No | | +| DropTrigger | No | | +| EndCoroutine | Yes | | +| Eq | Yes | | +| Expire | No | | +| Explain | No | | +| FkCounter | No | | +| FkIfZero | No | | +| Found | No | | +| Function | Yes | | +| Ge | Yes | | +| Gosub | Yes | | +| Goto | Yes | | +| Gt | Yes | | +| Halt | Yes | | +| HaltIfNull | No | | +| IdxDelete | No | | +| IdxGE | Yes | | +| IdxInsert | No | | +| IdxLT | No | | +| IdxRowid | No | | +| If | Yes | | +| IfNeg | No | | +| IfNot | Yes | | +| IfPos | Yes | | +| IfZero | No | | +| IncrVacuum | No | | +| Init | Yes | | +| InitCoroutine | Yes | | +| Insert | No | | +| InsertAsync | Yes | | +| InsertAwait | Yes | | +| InsertInt | No | | +| Int64 | No | | +| Integer | Yes | | +| IntegrityCk | No | | +| IsNull | Yes | | +| IsUnique | No | | +| JournalMode | No | | +| Jump | Yes | | +| Last | No | | +| Le | Yes | | +| LoadAnalysis | No | | +| Lt | Yes | | +| MakeRecord | Yes | | +| MaxPgcnt | No | | +| MemMax | No | | +| Move | No | | +| Multiply | Yes | | +| MustBeInt | Yes | | +| Ne | Yes | | +| NewRowid | Yes | | +| Next | No | | +| NextAsync | Yes | | +| NextAwait | Yes | | +| Noop | No | | +| Not | Yes | | +| NotExists | Yes | | +| NotFound | No | | +| NotNull | Yes | | +| Null | Yes | | +| NullRow | Yes | | +| Once | No | | +| OpenAutoindex | No | | +| OpenEphemeral | No | | +| OpenPseudo | Yes | | +| OpenRead | Yes | | +| OpenReadAsync | Yes | | +| OpenWrite | No | | +| OpenWriteAsync | Yes | | +| OpenWriteAwait | Yes | | +| Or | Yes | | +| Pagecount | No | | +| Param | No | | +| ParseSchema | No | | +| Permutation | No | | +| Prev | No | | +| PrevAsync | Yes | | +| PrevAwait | Yes | | +| Program | No | | +| ReadCookie | No | | +| Real | Yes | | +| RealAffinity | Yes | | +| Remainder | Yes | | +| ResetCount | No | | +| ResultRow | Yes | | +| Return | Yes | | +| Rewind | Yes | | +| RewindAsync | Yes | | +| RewindAwait | Yes | | +| RowData | No | | +| RowId | Yes | | +| RowKey | No | | +| RowSetAdd | No | | +| RowSetRead | No | | +| RowSetTest | No | | +| Rowid | Yes | | +| SCopy | No | | +| Savepoint | No | | +| Seek | No | | +| SeekGe | Yes | | +| SeekGt | Yes | | +| SeekLe | No | | +| SeekLt | No | | +| SeekRowid | Yes | | +| Sequence | No | | +| SetCookie | No | | +| ShiftLeft | Yes | | +| ShiftRight | Yes | | +| SoftNull | Yes | | +| Sort | No | | +| SorterCompare | No | | +| SorterData | Yes | | +| SorterInsert | Yes | | +| SorterNext | Yes | | +| SorterOpen | Yes | | +| SorterSort | Yes | | +| String | No | | +| String8 | Yes | | +| Subtract | Yes | | +| TableLock | No | | +| ToBlob | No | | +| ToInt | No | | +| ToNumeric | No | | +| ToReal | No | | +| ToText | No | | +| Trace | No | | +| Transaction | Yes | | +| VBegin | No | | +| VColumn | No | | +| VCreate | No | | +| VDestroy | No | | +| VFilter | No | | +| VNext | No | | +| VOpen | No | | +| VRename | No | | +| VUpdate | No | | +| Vacuum | No | | +| Variable | No | | +| VerifyCookie | No | | +| Yield | Yes | | +| ZeroOrNull | Yes | | ## [SQLite journaling modes](https://www.sqlite.org/pragma.html#pragma_journal_mode) From e338b5e9393b2fd8d2e7cc6be93701a40f6c3054 Mon Sep 17 00:00:00 2001 From: Glauber Costa Date: Wed, 29 Jan 2025 11:00:10 -0500 Subject: [PATCH 3/4] implement the pragma page_count To do that, we also have to implement the vdbe opcode Pagecount. --- COMPAT.md | 2 +- core/translate/mod.rs | 14 ++++++++++++++ core/vdbe/explain.rs | 9 +++++++++ core/vdbe/insn.rs | 5 +++++ core/vdbe/mod.rs | 9 +++++++++ tests/integration/pragma/test_pragma_stmts.rs | 13 +++++++++++++ vendored/sqlite3-parser/src/parser/ast/mod.rs | 3 +++ 7 files changed, 54 insertions(+), 1 deletion(-) diff --git a/COMPAT.md b/COMPAT.md index 182a751a0..8618e9e29 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -504,7 +504,7 @@ Modifiers: | OpenWriteAsync | Yes | | | OpenWriteAwait | Yes | | | Or | Yes | | -| Pagecount | No | | +| Pagecount | Partial| no temp databases | | Param | No | | | ParseSchema | No | | | Permutation | No | | diff --git a/core/translate/mod.rs b/core/translate/mod.rs index 75c1f9758..a82bffd24 100644 --- a/core/translate/mod.rs +++ b/core/translate/mod.rs @@ -600,6 +600,10 @@ fn update_pragma( query_pragma("wal_checkpoint", header, program)?; Ok(()) } + PragmaName::PageCount => { + query_pragma("page_count", header, program)?; + Ok(()) + } } } @@ -649,6 +653,16 @@ fn query_pragma( count: 3, }); } + PragmaName::PageCount => { + program.emit_insn(Insn::PageCount { + db: 0, + dest: register, + }); + program.emit_insn(Insn::ResultRow { + start_reg: register, + count: 1, + }); + } } Ok(()) diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index 89967608a..8b39c393b 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -1138,6 +1138,15 @@ pub fn insn_to_str( 0, format!("r[{}]=(r[{}] || r[{}])", dest, lhs, rhs), ), + Insn::PageCount { db, dest } => ( + "Pagecount", + *db as i32, + *dest as i32, + 0, + OwnedValue::build_text(Rc::new("".to_string())), + 0, + "".to_string(), + ), }; format!( "{:<4} {:<17} {:<4} {:<4} {:<4} {:<13} {:<2} {}", diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index f17a1a354..966cf2c92 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -563,6 +563,11 @@ pub enum Insn { rhs: usize, dest: usize, }, + /// Write the current number of pages in database P1 to memory cell P2. + PageCount { + db: usize, + dest: usize, + }, } fn cast_text_to_numerical(value: &str) -> OwnedValue { diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index c472da4ec..a90e184c1 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -2396,6 +2396,15 @@ impl Program { exec_or(&state.registers[*lhs], &state.registers[*rhs]); state.pc += 1; } + Insn::PageCount { db, dest } => { + if *db > 0 { + // TODO: implement temp databases + todo!("temp databases not implemented yet"); + } + state.registers[*dest] = + OwnedValue::Integer(pager.db_header.borrow().database_size.into()); + state.pc += 1; + } } } } diff --git a/tests/integration/pragma/test_pragma_stmts.rs b/tests/integration/pragma/test_pragma_stmts.rs index 11a831d37..15c4182c0 100644 --- a/tests/integration/pragma/test_pragma_stmts.rs +++ b/tests/integration/pragma/test_pragma_stmts.rs @@ -7,6 +7,19 @@ mod tests { use rexpect::session::{spawn_command, PtySession}; use std::process; + #[test] + fn test_pragma_page_count() -> Result<(), Error> { + let mut child = spawn_command(run_cli(), Some(1000))?; + child.exp_regex("limbo>")?; // skip everything until limbo cursor appear + child.exp_regex(".?")?; + child.send_line("pragma page_count;")?; + child.exp_string("0")?; + child.exp_regex("limbo>")?; + child.send_line("create table foo(bar); pragma page_count;")?; + child.exp_string("2")?; + quit(&mut child) + } + #[test] fn test_pragma_journal_mode_wal() -> Result<(), Error> { let mut child = spawn_command(run_cli(), Some(1000))?; diff --git a/vendored/sqlite3-parser/src/parser/ast/mod.rs b/vendored/sqlite3-parser/src/parser/ast/mod.rs index b81eaa9b5..97c7f7c1d 100644 --- a/vendored/sqlite3-parser/src/parser/ast/mod.rs +++ b/vendored/sqlite3-parser/src/parser/ast/mod.rs @@ -1593,6 +1593,8 @@ pub enum PragmaName { JournalMode, /// trigger a checkpoint to run on database(s) if WAL is enabled WalCheckpoint, + /// Return the total number of pages in the database file. + PageCount, } impl FromStr for PragmaName { @@ -1603,6 +1605,7 @@ impl FromStr for PragmaName { "cache_size" => Ok(PragmaName::CacheSize), "wal_checkpoint" => Ok(PragmaName::WalCheckpoint), "journal_mode" => Ok(PragmaName::JournalMode), + "page_count" => Ok(PragmaName::PageCount), _ => Err(()), } } From 2932972f81df615c1a7f19e9ea65ddf28e224415 Mon Sep 17 00:00:00 2001 From: Glauber Costa Date: Wed, 29 Jan 2025 12:30:33 -0500 Subject: [PATCH 4/4] move database_size back to one And then, subtract one when returning --- core/storage/sqlite3_ondisk.rs | 2 +- core/vdbe/mod.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/storage/sqlite3_ondisk.rs b/core/storage/sqlite3_ondisk.rs index c2784a645..5543fa7db 100644 --- a/core/storage/sqlite3_ondisk.rs +++ b/core/storage/sqlite3_ondisk.rs @@ -223,7 +223,7 @@ impl Default for DatabaseHeader { min_embed_frac: 32, min_leaf_frac: 32, change_counter: 1, - database_size: 0, + database_size: 1, freelist_trunk_page: 0, freelist_pages: 0, schema_cookie: 0, diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index a90e184c1..3c407c0bd 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -2401,8 +2401,9 @@ impl Program { // TODO: implement temp databases todo!("temp databases not implemented yet"); } - state.registers[*dest] = - OwnedValue::Integer(pager.db_header.borrow().database_size.into()); + let pages : u32 = pager.db_header.borrow().database_size; + assert_ne!(pages, 0); + state.registers[*dest] = OwnedValue::Integer((pages - 1).into()); state.pc += 1; } }