mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-07 02:04:21 +01:00
use virtual root page for sqlite_schema
This commit is contained in:
29
cli/app.rs
29
cli/app.rs
@@ -1126,15 +1126,6 @@ impl Limbo {
|
||||
}
|
||||
|
||||
fn display_schema(&mut self, table: Option<&str>) -> anyhow::Result<()> {
|
||||
if !self.conn.is_db_initialized() {
|
||||
if let Some(table_name) = table {
|
||||
self.write_fmt(format_args!("-- Error: Table '{table_name}' not found."))?;
|
||||
} else {
|
||||
self.writeln("-- No tables or indexes found in the database.")?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match table {
|
||||
Some(table_spec) => {
|
||||
// Parse table name to handle database prefixes (e.g., "db.table")
|
||||
@@ -1188,15 +1179,6 @@ impl Limbo {
|
||||
}
|
||||
|
||||
fn display_indexes(&mut self, maybe_table: Option<String>) -> anyhow::Result<()> {
|
||||
if !self.conn.is_db_initialized() {
|
||||
if let Some(tbl_name) = &maybe_table {
|
||||
self.write_fmt(format_args!("-- Error: Table '{tbl_name}' not found."))?;
|
||||
} else {
|
||||
self.writeln("-- No indexes found in the database.")?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let sql = match maybe_table {
|
||||
Some(ref tbl_name) => format!(
|
||||
"SELECT name FROM sqlite_schema WHERE type='index' AND tbl_name = '{tbl_name}' ORDER BY 1"
|
||||
@@ -1245,17 +1227,6 @@ impl Limbo {
|
||||
}
|
||||
|
||||
fn display_tables(&mut self, pattern: Option<&str>) -> anyhow::Result<()> {
|
||||
if !self.conn.is_db_initialized() {
|
||||
if let Some(pattern) = pattern {
|
||||
self.write_fmt(format_args!(
|
||||
"-- Error: Tables with pattern '{pattern}' not found."
|
||||
))?;
|
||||
} else {
|
||||
self.writeln("-- No tables found in the database.")?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let sql = match pattern {
|
||||
Some(pattern) => format!(
|
||||
"SELECT name FROM sqlite_schema WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name LIKE '{pattern}' ORDER BY 1"
|
||||
|
||||
@@ -40,40 +40,18 @@ impl<'a> ImportFile<'a> {
|
||||
args.table
|
||||
);
|
||||
|
||||
let mut table_exists = false;
|
||||
if self.conn.is_db_initialized() {
|
||||
table_exists = 'check: {
|
||||
match self.conn.query(table_check_query) {
|
||||
Ok(rows) => {
|
||||
if let Some(mut rows) = rows {
|
||||
loop {
|
||||
match rows.step() {
|
||||
Ok(turso_core::StepResult::Row) => {
|
||||
break 'check true;
|
||||
}
|
||||
Ok(turso_core::StepResult::Done) => break 'check false,
|
||||
Ok(turso_core::StepResult::IO) => {
|
||||
if let Err(e) = rows.run_once() {
|
||||
let _ = self.writer.write_all(
|
||||
format!("Error checking table existence: {e:?}\n")
|
||||
.as_bytes(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Ok(
|
||||
turso_core::StepResult::Interrupt
|
||||
| turso_core::StepResult::Busy,
|
||||
) => {
|
||||
if let Err(e) = rows.run_once() {
|
||||
let _ = self.writer.write_all(
|
||||
format!("Error checking table existence: {e:?}\n")
|
||||
.as_bytes(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let table_exists = 'check: {
|
||||
match self.conn.query(table_check_query) {
|
||||
Ok(rows) => {
|
||||
if let Some(mut rows) = rows {
|
||||
loop {
|
||||
match rows.step() {
|
||||
Ok(turso_core::StepResult::Row) => {
|
||||
break 'check true;
|
||||
}
|
||||
Ok(turso_core::StepResult::Done) => break 'check false,
|
||||
Ok(turso_core::StepResult::IO) => {
|
||||
if let Err(e) = rows.run_once() {
|
||||
let _ = self.writer.write_all(
|
||||
format!("Error checking table existence: {e:?}\n")
|
||||
.as_bytes(),
|
||||
@@ -81,19 +59,38 @@ impl<'a> ImportFile<'a> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Ok(
|
||||
turso_core::StepResult::Interrupt
|
||||
| turso_core::StepResult::Busy,
|
||||
) => {
|
||||
if let Err(e) = rows.run_once() {
|
||||
let _ = self.writer.write_all(
|
||||
format!("Error checking table existence: {e:?}\n")
|
||||
.as_bytes(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = self.writer.write_all(
|
||||
format!("Error checking table existence: {e:?}\n")
|
||||
.as_bytes(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = self.writer.write_all(
|
||||
format!("Error checking table existence: {e:?}\n").as_bytes(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
false
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = self
|
||||
.writer
|
||||
.write_all(format!("Error checking table existence: {e:?}\n").as_bytes());
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
let file = match File::open(args.file) {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
|
||||
@@ -413,6 +413,8 @@ pub enum CursorContext {
|
||||
/// In the future, we may expand these general validity states
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum CursorValidState {
|
||||
/// Cursor does not point to a valid entry, and Btree will never yield a record.
|
||||
Invalid,
|
||||
/// Cursor is pointing a to an existing location/cell in the Btree
|
||||
Valid,
|
||||
/// Cursor may be pointing to a non-existent location/cell. This can happen after balancing operations
|
||||
@@ -571,6 +573,11 @@ impl BTreeCursor {
|
||||
root_page: usize,
|
||||
num_columns: usize,
|
||||
) -> Self {
|
||||
let valid_state = if root_page == 1 && !pager.db_state.is_initialized() {
|
||||
CursorValidState::Invalid
|
||||
} else {
|
||||
CursorValidState::Valid
|
||||
};
|
||||
let usable_space = pager.usable_space();
|
||||
Self {
|
||||
mv_cursor,
|
||||
@@ -595,7 +602,7 @@ impl BTreeCursor {
|
||||
index_info: None,
|
||||
count: 0,
|
||||
context: None,
|
||||
valid_state: CursorValidState::Valid,
|
||||
valid_state,
|
||||
seek_state: CursorSeekState::Start,
|
||||
read_overflow_state: RefCell::new(None),
|
||||
record_cursor: RefCell::new(RecordCursor::with_capacity(num_columns)),
|
||||
@@ -4156,6 +4163,9 @@ impl BTreeCursor {
|
||||
|
||||
#[instrument(skip_all, level = Level::DEBUG)]
|
||||
pub fn rewind(&mut self) -> Result<IOResult<()>> {
|
||||
if self.valid_state == CursorValidState::Invalid {
|
||||
return Ok(IOResult::Done(()));
|
||||
}
|
||||
loop {
|
||||
match self.rewind_state {
|
||||
RewindState::Start => {
|
||||
@@ -4190,6 +4200,10 @@ impl BTreeCursor {
|
||||
|
||||
#[instrument(skip_all, level = Level::DEBUG)]
|
||||
pub fn next(&mut self) -> Result<IOResult<bool>> {
|
||||
if self.valid_state == CursorValidState::Invalid {
|
||||
return Ok(IOResult::Done(false));
|
||||
}
|
||||
|
||||
loop {
|
||||
match self.advance_state {
|
||||
AdvanceState::Start => {
|
||||
@@ -4398,6 +4412,9 @@ impl BTreeCursor {
|
||||
match state {
|
||||
InsertState::Start => {
|
||||
match (&self.valid_state, self.is_write_in_progress()) {
|
||||
(CursorValidState::Invalid, _) => {
|
||||
panic!("trying to insert with invalid BTreeCursor");
|
||||
}
|
||||
(CursorValidState::Valid, _) => {
|
||||
// consider the current position valid unless the caller explicitly asks us to seek.
|
||||
}
|
||||
@@ -7511,6 +7528,24 @@ mod tests {
|
||||
(pager, page2.get().get().id, db, conn)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn btree_with_virtual_page_1() -> Result<()> {
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
let io: Arc<dyn IO> = Arc::new(MemoryIO::new());
|
||||
let db = Database::open_file(io.clone(), ":memory:", false, false).unwrap();
|
||||
let conn = db.connect().unwrap();
|
||||
let pager = conn.pager.borrow().clone();
|
||||
|
||||
let mut cursor = BTreeCursor::new(None, pager, 1, 5);
|
||||
let result = cursor.rewind()?;
|
||||
assert!(matches!(result, IOResult::Done(_)));
|
||||
let result = cursor.next()?;
|
||||
assert!(matches!(result, IOResult::Done(has_next_record) if !has_next_record));
|
||||
let result = cursor.record()?;
|
||||
assert!(matches!(result, IOResult::Done(record) if record.is_none()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn btree_test_overflow_pages_are_cleared_on_overwrite() {
|
||||
// Create a database with a table
|
||||
|
||||
Reference in New Issue
Block a user