mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-17 08:34:19 +01:00
Fix: Drop internal DBSP table when dropping materialized view
This commit is contained in:
committed by
Martin Mauch
parent
2c49c47300
commit
4d1fdd951b
@@ -250,6 +250,12 @@ impl Schema {
|
||||
// Remove from tables
|
||||
self.tables.remove(&name);
|
||||
|
||||
// Remove DBSP state table and its indexes from in-memory schema
|
||||
use crate::incremental::compiler::DBSP_CIRCUIT_VERSION;
|
||||
let dbsp_table_name = format!("{DBSP_TABLE_PREFIX}{DBSP_CIRCUIT_VERSION}_{name}");
|
||||
self.tables.remove(&dbsp_table_name);
|
||||
self.remove_indices_for_table(&dbsp_table_name);
|
||||
|
||||
// Remove from materialized view tracking
|
||||
self.materialized_view_names.remove(&name);
|
||||
self.materialized_view_sql.remove(&name);
|
||||
|
||||
@@ -326,7 +326,8 @@ pub fn translate_drop_view(
|
||||
}
|
||||
|
||||
// If this is a materialized view, we need to destroy its btree as well
|
||||
if is_materialized_view {
|
||||
// and also clean up the associated DBSP state table and index
|
||||
let dbsp_table_name = if is_materialized_view {
|
||||
if let Some(table) = schema.get_table(&normalized_view_name) {
|
||||
if let Some(btree_table) = table.btree() {
|
||||
// Destroy the btree for the materialized view
|
||||
@@ -337,6 +338,38 @@ pub fn translate_drop_view(
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the DBSP state table name
|
||||
use crate::incremental::compiler::DBSP_CIRCUIT_VERSION;
|
||||
Some(format!(
|
||||
"{DBSP_TABLE_PREFIX}{DBSP_CIRCUIT_VERSION}_{normalized_view_name}"
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Destroy DBSP state table and index btrees if this is a materialized view
|
||||
if let Some(ref dbsp_table_name) = dbsp_table_name {
|
||||
// Destroy DBSP indexes first
|
||||
let dbsp_indexes: Vec<_> = schema.get_indices(dbsp_table_name).collect();
|
||||
for index in dbsp_indexes {
|
||||
program.emit_insn(Insn::Destroy {
|
||||
root: index.root_page,
|
||||
former_root_reg: 0, // No autovacuum
|
||||
is_temp: 0,
|
||||
});
|
||||
}
|
||||
|
||||
// Destroy DBSP state table btree
|
||||
if let Some(dbsp_table) = schema.get_table(dbsp_table_name) {
|
||||
if let Some(dbsp_btree_table) = dbsp_table.btree() {
|
||||
program.emit_insn(Insn::Destroy {
|
||||
root: dbsp_btree_table.root_page,
|
||||
former_root_reg: 0, // No autovacuum
|
||||
is_temp: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Open cursor to sqlite_schema table
|
||||
@@ -374,7 +407,7 @@ pub fn translate_drop_view(
|
||||
});
|
||||
program.preassign_label_to_next_insn(loop_start_label);
|
||||
|
||||
// Check if this row is the view we're looking for
|
||||
// Check if this row should be deleted
|
||||
// Column 0 is type, Column 1 is name, Column 2 is tbl_name
|
||||
let col0_reg = program.alloc_register();
|
||||
let col1_reg = program.alloc_register();
|
||||
@@ -382,10 +415,10 @@ pub fn translate_drop_view(
|
||||
program.emit_column_or_rowid(sqlite_schema_cursor_id, 0, col0_reg);
|
||||
program.emit_column_or_rowid(sqlite_schema_cursor_id, 1, col1_reg);
|
||||
|
||||
// Check if type == 'view' and name == view_name
|
||||
// Check if this row matches the view, DBSP table, or DBSP index
|
||||
let skip_delete_label = program.allocate_label();
|
||||
|
||||
// Both regular and materialized views are stored as type='view' in sqlite_schema
|
||||
// Check if this is the view entry (type='view' and name=view_name)
|
||||
program.emit_insn(Insn::Ne {
|
||||
lhs: col0_reg,
|
||||
rhs: type_reg,
|
||||
@@ -393,7 +426,6 @@ pub fn translate_drop_view(
|
||||
flags: CmpInsFlags::default(),
|
||||
collation: program.curr_collation(),
|
||||
});
|
||||
|
||||
program.emit_insn(Insn::Ne {
|
||||
lhs: col1_reg,
|
||||
rhs: view_name_reg,
|
||||
@@ -401,8 +433,7 @@ pub fn translate_drop_view(
|
||||
flags: CmpInsFlags::default(),
|
||||
collation: program.curr_collation(),
|
||||
});
|
||||
|
||||
// Get the rowid and delete this row
|
||||
// Matches view - delete it
|
||||
program.emit_insn(Insn::RowId {
|
||||
cursor_id: sqlite_schema_cursor_id,
|
||||
dest: rowid_reg,
|
||||
@@ -423,6 +454,122 @@ pub fn translate_drop_view(
|
||||
|
||||
program.preassign_label_to_next_insn(end_loop_label);
|
||||
|
||||
// If this is a materialized view, delete DBSP table and index entries in a second pass
|
||||
// We do this in a separate loop to ensure we catch all entries even if they come
|
||||
// in different orders in sqlite_schema
|
||||
if let Some(ref dbsp_table_name) = dbsp_table_name {
|
||||
// Set up registers for DBSP table name and types (outside the loop for efficiency)
|
||||
let dbsp_table_name_reg_2 = program.alloc_register();
|
||||
program.emit_insn(Insn::String8 {
|
||||
dest: dbsp_table_name_reg_2,
|
||||
value: dbsp_table_name.clone(),
|
||||
});
|
||||
let table_type_reg_2 = program.alloc_register();
|
||||
program.emit_insn(Insn::String8 {
|
||||
dest: table_type_reg_2,
|
||||
value: "table".to_string(),
|
||||
});
|
||||
let index_type_reg_2 = program.alloc_register();
|
||||
program.emit_insn(Insn::String8 {
|
||||
dest: index_type_reg_2,
|
||||
value: "index".to_string(),
|
||||
});
|
||||
let dbsp_index_name_reg_2 = program.alloc_register();
|
||||
let dbsp_index_name_2 =
|
||||
format!("{PRIMARY_KEY_AUTOMATIC_INDEX_NAME_PREFIX}{dbsp_table_name}_1");
|
||||
program.emit_insn(Insn::String8 {
|
||||
dest: dbsp_index_name_reg_2,
|
||||
value: dbsp_index_name_2.clone(),
|
||||
});
|
||||
|
||||
// Allocate column registers once (outside the loop)
|
||||
let dbsp_col0_reg = program.alloc_register();
|
||||
let dbsp_col1_reg = program.alloc_register();
|
||||
|
||||
// Second pass: delete DBSP table and index entries
|
||||
let dbsp_end_loop_label = program.allocate_label();
|
||||
let dbsp_loop_start_label = program.allocate_label();
|
||||
|
||||
program.emit_insn(Insn::Rewind {
|
||||
cursor_id: sqlite_schema_cursor_id,
|
||||
pc_if_empty: dbsp_end_loop_label,
|
||||
});
|
||||
program.preassign_label_to_next_insn(dbsp_loop_start_label);
|
||||
|
||||
// Read columns for this row (reusing the same registers)
|
||||
program.emit_column_or_rowid(sqlite_schema_cursor_id, 0, dbsp_col0_reg);
|
||||
program.emit_column_or_rowid(sqlite_schema_cursor_id, 1, dbsp_col1_reg);
|
||||
|
||||
let dbsp_skip_delete_label = program.allocate_label();
|
||||
|
||||
// Check if this is the DBSP table entry (type='table' and name=dbsp_table_name)
|
||||
let check_dbsp_index_label = program.allocate_label();
|
||||
program.emit_insn(Insn::Ne {
|
||||
lhs: dbsp_col0_reg,
|
||||
rhs: table_type_reg_2,
|
||||
target_pc: check_dbsp_index_label,
|
||||
flags: CmpInsFlags::default(),
|
||||
collation: program.curr_collation(),
|
||||
});
|
||||
program.emit_insn(Insn::Ne {
|
||||
lhs: dbsp_col1_reg,
|
||||
rhs: dbsp_table_name_reg_2,
|
||||
target_pc: check_dbsp_index_label,
|
||||
flags: CmpInsFlags::default(),
|
||||
collation: program.curr_collation(),
|
||||
});
|
||||
// Matches DBSP table - delete it
|
||||
program.emit_insn(Insn::RowId {
|
||||
cursor_id: sqlite_schema_cursor_id,
|
||||
dest: rowid_reg,
|
||||
});
|
||||
program.emit_insn(Insn::Delete {
|
||||
cursor_id: sqlite_schema_cursor_id,
|
||||
table_name: "sqlite_schema".to_string(),
|
||||
is_part_of_update: false,
|
||||
});
|
||||
program.emit_insn(Insn::Goto {
|
||||
target_pc: dbsp_skip_delete_label,
|
||||
});
|
||||
|
||||
// Check if this is the DBSP index entry (type='index' and name=dbsp_index_name)
|
||||
program.preassign_label_to_next_insn(check_dbsp_index_label);
|
||||
program.emit_insn(Insn::Ne {
|
||||
lhs: dbsp_col0_reg,
|
||||
rhs: index_type_reg_2,
|
||||
target_pc: dbsp_skip_delete_label,
|
||||
flags: CmpInsFlags::default(),
|
||||
collation: program.curr_collation(),
|
||||
});
|
||||
program.emit_insn(Insn::Ne {
|
||||
lhs: dbsp_col1_reg,
|
||||
rhs: dbsp_index_name_reg_2,
|
||||
target_pc: dbsp_skip_delete_label,
|
||||
flags: CmpInsFlags::default(),
|
||||
collation: program.curr_collation(),
|
||||
});
|
||||
// Matches DBSP index - delete it
|
||||
program.emit_insn(Insn::RowId {
|
||||
cursor_id: sqlite_schema_cursor_id,
|
||||
dest: rowid_reg,
|
||||
});
|
||||
program.emit_insn(Insn::Delete {
|
||||
cursor_id: sqlite_schema_cursor_id,
|
||||
table_name: "sqlite_schema".to_string(),
|
||||
is_part_of_update: false,
|
||||
});
|
||||
|
||||
program.resolve_label(dbsp_skip_delete_label, program.offset());
|
||||
|
||||
// Move to next row
|
||||
program.emit_insn(Insn::Next {
|
||||
cursor_id: sqlite_schema_cursor_id,
|
||||
pc_if_next: dbsp_loop_start_label,
|
||||
});
|
||||
|
||||
program.preassign_label_to_next_insn(dbsp_end_loop_label);
|
||||
}
|
||||
|
||||
// Remove the view from the in-memory schema
|
||||
program.emit_insn(Insn::DropView {
|
||||
db: 0,
|
||||
|
||||
@@ -74,10 +74,10 @@ do_execsql_test_on_specific_db {:memory:} matview-filter-with-groupby {
|
||||
CREATE TABLE t(a INTEGER, b INTEGER);
|
||||
INSERT INTO t(a,b) VALUES (2,2), (3,3), (6,6), (7,7);
|
||||
|
||||
CREATE MATERIALIZED VIEW v AS
|
||||
SELECT b as yourb, SUM(a) as mysum, COUNT(a) as mycount
|
||||
FROM t
|
||||
WHERE b > 2
|
||||
CREATE MATERIALIZED VIEW v AS
|
||||
SELECT b as yourb, SUM(a) as mysum, COUNT(a) as mycount
|
||||
FROM t
|
||||
WHERE b > 2
|
||||
GROUP BY b;
|
||||
|
||||
SELECT * FROM v ORDER BY yourb;
|
||||
@@ -87,10 +87,10 @@ do_execsql_test_on_specific_db {:memory:} matview-filter-with-groupby {
|
||||
|
||||
do_execsql_test_on_specific_db {:memory:} matview-insert-maintenance {
|
||||
CREATE TABLE t(a INTEGER, b INTEGER);
|
||||
CREATE MATERIALIZED VIEW v AS
|
||||
SELECT b, SUM(a) as total, COUNT(*) as cnt
|
||||
FROM t
|
||||
WHERE b > 2
|
||||
CREATE MATERIALIZED VIEW v AS
|
||||
SELECT b, SUM(a) as total, COUNT(*) as cnt
|
||||
FROM t
|
||||
WHERE b > 2
|
||||
GROUP BY b;
|
||||
|
||||
INSERT INTO t VALUES (3,3), (6,6);
|
||||
@@ -110,7 +110,7 @@ do_execsql_test_on_specific_db {:memory:} matview-insert-maintenance {
|
||||
|
||||
do_execsql_test_on_specific_db {:memory:} matview-delete-maintenance {
|
||||
CREATE TABLE items(id INTEGER, category TEXT, amount INTEGER);
|
||||
INSERT INTO items VALUES
|
||||
INSERT INTO items VALUES
|
||||
(1, 'A', 10),
|
||||
(2, 'B', 20),
|
||||
(3, 'A', 30),
|
||||
@@ -166,7 +166,7 @@ do_execsql_test_on_specific_db {:memory:} matview-integer-primary-key-basic {
|
||||
CREATE TABLE t(a INTEGER PRIMARY KEY, b INTEGER);
|
||||
INSERT INTO t(a,b) VALUES (2,2), (3,3), (6,6), (7,7);
|
||||
|
||||
CREATE MATERIALIZED VIEW v AS
|
||||
CREATE MATERIALIZED VIEW v AS
|
||||
SELECT * FROM t WHERE b > 2;
|
||||
|
||||
SELECT * FROM v ORDER BY a;
|
||||
@@ -178,7 +178,7 @@ do_execsql_test_on_specific_db {:memory:} matview-integer-primary-key-update-row
|
||||
CREATE TABLE t(a INTEGER PRIMARY KEY, b INTEGER);
|
||||
INSERT INTO t(a,b) VALUES (2,2), (3,3), (6,6), (7,7);
|
||||
|
||||
CREATE MATERIALIZED VIEW v AS
|
||||
CREATE MATERIALIZED VIEW v AS
|
||||
SELECT * FROM t WHERE b > 2;
|
||||
|
||||
SELECT * FROM v ORDER BY a;
|
||||
@@ -202,7 +202,7 @@ do_execsql_test_on_specific_db {:memory:} matview-integer-primary-key-update-val
|
||||
CREATE TABLE t(a INTEGER PRIMARY KEY, b INTEGER);
|
||||
INSERT INTO t(a,b) VALUES (2,2), (3,3), (6,6), (7,7);
|
||||
|
||||
CREATE MATERIALIZED VIEW v AS
|
||||
CREATE MATERIALIZED VIEW v AS
|
||||
SELECT * FROM t WHERE b > 2;
|
||||
|
||||
SELECT * FROM v ORDER BY a;
|
||||
@@ -223,7 +223,7 @@ do_execsql_test_on_specific_db {:memory:} matview-integer-primary-key-update-val
|
||||
|
||||
do_execsql_test_on_specific_db {:memory:} matview-integer-primary-key-with-aggregation {
|
||||
CREATE TABLE t(a INTEGER PRIMARY KEY, b INTEGER, c INTEGER);
|
||||
INSERT INTO t VALUES
|
||||
INSERT INTO t VALUES
|
||||
(1, 10, 100),
|
||||
(2, 10, 200),
|
||||
(3, 20, 300),
|
||||
@@ -2494,3 +2494,68 @@ do_execsql_test_on_specific_db {:memory:} matview-count-distinct-global-aggregat
|
||||
3
|
||||
5
|
||||
4}
|
||||
|
||||
# Test that dropping a materialized view cleans up the DBSP state table
|
||||
do_execsql_test_on_specific_db {:memory:} matview-drop-cleans-up-dbsp-table {
|
||||
CREATE TABLE t(id INTEGER PRIMARY KEY, val INTEGER);
|
||||
INSERT INTO t VALUES (1, 10), (2, 20), (3, 30);
|
||||
|
||||
CREATE MATERIALIZED VIEW v AS
|
||||
SELECT val, COUNT(*) as cnt
|
||||
FROM t
|
||||
GROUP BY val;
|
||||
|
||||
-- Verify the view exists
|
||||
SELECT COUNT(*) FROM sqlite_schema WHERE type='view' AND name='v';
|
||||
|
||||
-- Verify the DBSP state table exists (name pattern: __turso_internal_dbsp_state_v*_v)
|
||||
SELECT COUNT(*) FROM sqlite_schema WHERE type='table' AND name LIKE '__turso_internal_dbsp_state_v%_v';
|
||||
|
||||
-- Verify the DBSP state index exists
|
||||
SELECT COUNT(*) FROM sqlite_schema WHERE type='index' AND name LIKE 'sqlite_autoindex___turso_internal_dbsp_state_v%_v_1';
|
||||
|
||||
-- Drop the materialized view
|
||||
DROP VIEW v;
|
||||
|
||||
-- Verify the view is gone
|
||||
SELECT COUNT(*) FROM sqlite_schema WHERE type='view' AND name='v';
|
||||
|
||||
-- Verify the DBSP state table is gone
|
||||
SELECT COUNT(*) FROM sqlite_schema WHERE type='table' AND name LIKE '__turso_internal_dbsp_state_v%_v';
|
||||
|
||||
-- Verify the DBSP state index is gone
|
||||
SELECT COUNT(*) FROM sqlite_schema WHERE type='index' AND name LIKE 'sqlite_autoindex___turso_internal_dbsp_state_v%_v_1';
|
||||
} {1
|
||||
1
|
||||
1
|
||||
0
|
||||
0
|
||||
0}
|
||||
|
||||
# Test that a materialized view can be recreated after dropping
|
||||
do_execsql_test_on_specific_db {:memory:} matview-recreate-after-drop {
|
||||
CREATE TABLE data(x INTEGER, y INTEGER);
|
||||
INSERT INTO data VALUES (1, 10), (2, 20), (3, 30);
|
||||
|
||||
CREATE MATERIALIZED VIEW mv AS
|
||||
SELECT x, SUM(y) as total
|
||||
FROM data
|
||||
GROUP BY x;
|
||||
|
||||
SELECT * FROM mv ORDER BY x;
|
||||
|
||||
-- Drop the view
|
||||
DROP VIEW mv;
|
||||
|
||||
-- Verify it can be recreated with a different definition
|
||||
CREATE MATERIALIZED VIEW mv AS
|
||||
SELECT x + 1 as modified_x, y * 2 as doubled_y
|
||||
FROM data
|
||||
WHERE x > 1;
|
||||
|
||||
SELECT * FROM mv ORDER BY modified_x;
|
||||
} {1|10.0
|
||||
2|20.0
|
||||
3|30.0
|
||||
3|40
|
||||
4|60}
|
||||
|
||||
Reference in New Issue
Block a user