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
|
// Remove from tables
|
||||||
self.tables.remove(&name);
|
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
|
// Remove from materialized view tracking
|
||||||
self.materialized_view_names.remove(&name);
|
self.materialized_view_names.remove(&name);
|
||||||
self.materialized_view_sql.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 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(table) = schema.get_table(&normalized_view_name) {
|
||||||
if let Some(btree_table) = table.btree() {
|
if let Some(btree_table) = table.btree() {
|
||||||
// Destroy the btree for the materialized view
|
// 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
|
// Open cursor to sqlite_schema table
|
||||||
@@ -374,7 +407,7 @@ pub fn translate_drop_view(
|
|||||||
});
|
});
|
||||||
program.preassign_label_to_next_insn(loop_start_label);
|
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
|
// Column 0 is type, Column 1 is name, Column 2 is tbl_name
|
||||||
let col0_reg = program.alloc_register();
|
let col0_reg = program.alloc_register();
|
||||||
let col1_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, 0, col0_reg);
|
||||||
program.emit_column_or_rowid(sqlite_schema_cursor_id, 1, col1_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();
|
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 {
|
program.emit_insn(Insn::Ne {
|
||||||
lhs: col0_reg,
|
lhs: col0_reg,
|
||||||
rhs: type_reg,
|
rhs: type_reg,
|
||||||
@@ -393,7 +426,6 @@ pub fn translate_drop_view(
|
|||||||
flags: CmpInsFlags::default(),
|
flags: CmpInsFlags::default(),
|
||||||
collation: program.curr_collation(),
|
collation: program.curr_collation(),
|
||||||
});
|
});
|
||||||
|
|
||||||
program.emit_insn(Insn::Ne {
|
program.emit_insn(Insn::Ne {
|
||||||
lhs: col1_reg,
|
lhs: col1_reg,
|
||||||
rhs: view_name_reg,
|
rhs: view_name_reg,
|
||||||
@@ -401,8 +433,7 @@ pub fn translate_drop_view(
|
|||||||
flags: CmpInsFlags::default(),
|
flags: CmpInsFlags::default(),
|
||||||
collation: program.curr_collation(),
|
collation: program.curr_collation(),
|
||||||
});
|
});
|
||||||
|
// Matches view - delete it
|
||||||
// Get the rowid and delete this row
|
|
||||||
program.emit_insn(Insn::RowId {
|
program.emit_insn(Insn::RowId {
|
||||||
cursor_id: sqlite_schema_cursor_id,
|
cursor_id: sqlite_schema_cursor_id,
|
||||||
dest: rowid_reg,
|
dest: rowid_reg,
|
||||||
@@ -423,6 +454,122 @@ pub fn translate_drop_view(
|
|||||||
|
|
||||||
program.preassign_label_to_next_insn(end_loop_label);
|
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
|
// Remove the view from the in-memory schema
|
||||||
program.emit_insn(Insn::DropView {
|
program.emit_insn(Insn::DropView {
|
||||||
db: 0,
|
db: 0,
|
||||||
|
|||||||
@@ -74,10 +74,10 @@ do_execsql_test_on_specific_db {:memory:} matview-filter-with-groupby {
|
|||||||
CREATE TABLE t(a INTEGER, b INTEGER);
|
CREATE TABLE t(a INTEGER, b INTEGER);
|
||||||
INSERT INTO t(a,b) VALUES (2,2), (3,3), (6,6), (7,7);
|
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 b as yourb, SUM(a) as mysum, COUNT(a) as mycount
|
SELECT b as yourb, SUM(a) as mysum, COUNT(a) as mycount
|
||||||
FROM t
|
FROM t
|
||||||
WHERE b > 2
|
WHERE b > 2
|
||||||
GROUP BY b;
|
GROUP BY b;
|
||||||
|
|
||||||
SELECT * FROM v ORDER BY yourb;
|
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 {
|
do_execsql_test_on_specific_db {:memory:} matview-insert-maintenance {
|
||||||
CREATE TABLE t(a INTEGER, b INTEGER);
|
CREATE TABLE t(a INTEGER, b INTEGER);
|
||||||
CREATE MATERIALIZED VIEW v AS
|
CREATE MATERIALIZED VIEW v AS
|
||||||
SELECT b, SUM(a) as total, COUNT(*) as cnt
|
SELECT b, SUM(a) as total, COUNT(*) as cnt
|
||||||
FROM t
|
FROM t
|
||||||
WHERE b > 2
|
WHERE b > 2
|
||||||
GROUP BY b;
|
GROUP BY b;
|
||||||
|
|
||||||
INSERT INTO t VALUES (3,3), (6,6);
|
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 {
|
do_execsql_test_on_specific_db {:memory:} matview-delete-maintenance {
|
||||||
CREATE TABLE items(id INTEGER, category TEXT, amount INTEGER);
|
CREATE TABLE items(id INTEGER, category TEXT, amount INTEGER);
|
||||||
INSERT INTO items VALUES
|
INSERT INTO items VALUES
|
||||||
(1, 'A', 10),
|
(1, 'A', 10),
|
||||||
(2, 'B', 20),
|
(2, 'B', 20),
|
||||||
(3, 'A', 30),
|
(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);
|
CREATE TABLE t(a INTEGER PRIMARY KEY, b INTEGER);
|
||||||
INSERT INTO t(a,b) VALUES (2,2), (3,3), (6,6), (7,7);
|
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 t WHERE b > 2;
|
||||||
|
|
||||||
SELECT * FROM v ORDER BY a;
|
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);
|
CREATE TABLE t(a INTEGER PRIMARY KEY, b INTEGER);
|
||||||
INSERT INTO t(a,b) VALUES (2,2), (3,3), (6,6), (7,7);
|
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 t WHERE b > 2;
|
||||||
|
|
||||||
SELECT * FROM v ORDER BY a;
|
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);
|
CREATE TABLE t(a INTEGER PRIMARY KEY, b INTEGER);
|
||||||
INSERT INTO t(a,b) VALUES (2,2), (3,3), (6,6), (7,7);
|
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 t WHERE b > 2;
|
||||||
|
|
||||||
SELECT * FROM v ORDER BY a;
|
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 {
|
do_execsql_test_on_specific_db {:memory:} matview-integer-primary-key-with-aggregation {
|
||||||
CREATE TABLE t(a INTEGER PRIMARY KEY, b INTEGER, c INTEGER);
|
CREATE TABLE t(a INTEGER PRIMARY KEY, b INTEGER, c INTEGER);
|
||||||
INSERT INTO t VALUES
|
INSERT INTO t VALUES
|
||||||
(1, 10, 100),
|
(1, 10, 100),
|
||||||
(2, 10, 200),
|
(2, 10, 200),
|
||||||
(3, 20, 300),
|
(3, 20, 300),
|
||||||
@@ -2494,3 +2494,68 @@ do_execsql_test_on_specific_db {:memory:} matview-count-distinct-global-aggregat
|
|||||||
3
|
3
|
||||||
5
|
5
|
||||||
4}
|
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