mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-26 04:24:21 +01:00
Merge 'move our dbsp-based views to materialized views' from Glauber Costa
We will implement normal SQLite-style view-as-an-alias for compatibility, and will call our incremental views materialized views. Closes #2571
This commit is contained in:
@@ -94,7 +94,7 @@ pub struct IncrementalView {
|
||||
}
|
||||
|
||||
impl IncrementalView {
|
||||
/// Validate that a CREATE VIEW statement can be handled by IncrementalView
|
||||
/// Validate that a CREATE MATERIALIZED VIEW statement can be handled by IncrementalView
|
||||
/// This should be called early, before updating sqlite_master
|
||||
pub fn can_create_view(
|
||||
select: &turso_sqlite3_parser::ast::Select,
|
||||
@@ -149,7 +149,7 @@ impl IncrementalView {
|
||||
/// Check if this view has the same SQL definition as the provided SQL string
|
||||
pub fn has_same_sql(&self, sql: &str) -> bool {
|
||||
// Parse the SQL to extract just the SELECT statement
|
||||
if let Ok(Some(Cmd::Stmt(Stmt::CreateView { select, .. }))) =
|
||||
if let Ok(Some(Cmd::Stmt(Stmt::CreateMaterializedView { select, .. }))) =
|
||||
Parser::new(sql.as_bytes()).next()
|
||||
{
|
||||
// Compare the SELECT statements as SQL strings
|
||||
@@ -178,15 +178,14 @@ impl IncrementalView {
|
||||
let cmd = parser.next()?;
|
||||
let cmd = cmd.expect("View is an empty statement");
|
||||
match cmd {
|
||||
Cmd::Stmt(Stmt::CreateView {
|
||||
temporary: _,
|
||||
Cmd::Stmt(Stmt::CreateMaterializedView {
|
||||
if_not_exists: _,
|
||||
view_name,
|
||||
columns: _,
|
||||
select,
|
||||
}) => IncrementalView::from_stmt(view_name, select, schema),
|
||||
_ => Err(LimboError::ParseError(format!(
|
||||
"View is not a CREATE VIEW statement: {sql}"
|
||||
"View is not a CREATE MATERIALIZED VIEW statement: {sql}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
@@ -1018,7 +1017,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_projection_simple_columns() {
|
||||
let schema = create_test_schema();
|
||||
let sql = "CREATE VIEW v AS SELECT a, b FROM t";
|
||||
let sql = "CREATE MATERIALIZED VIEW v AS SELECT a, b FROM t";
|
||||
|
||||
let view = IncrementalView::from_sql(sql, &schema).unwrap();
|
||||
|
||||
@@ -1042,7 +1041,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_projection_arithmetic_expression() {
|
||||
let schema = create_test_schema();
|
||||
let sql = "CREATE VIEW v AS SELECT a * 2 as doubled FROM t";
|
||||
let sql = "CREATE MATERIALIZED VIEW v AS SELECT a * 2 as doubled FROM t";
|
||||
|
||||
let view = IncrementalView::from_sql(sql, &schema).unwrap();
|
||||
|
||||
@@ -1066,7 +1065,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_projection_multiple_expressions() {
|
||||
let schema = create_test_schema();
|
||||
let sql = "CREATE VIEW v AS SELECT a + b as sum, a - b as diff, c FROM t";
|
||||
let sql = "CREATE MATERIALIZED VIEW v AS SELECT a + b as sum, a - b as diff, c FROM t";
|
||||
|
||||
let view = IncrementalView::from_sql(sql, &schema).unwrap();
|
||||
|
||||
@@ -1093,7 +1092,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_projection_function_call() {
|
||||
let schema = create_test_schema();
|
||||
let sql = "CREATE VIEW v AS SELECT hex(a) as hex_a, b FROM t";
|
||||
let sql = "CREATE MATERIALIZED VIEW v AS SELECT hex(a) as hex_a, b FROM t";
|
||||
|
||||
let view = IncrementalView::from_sql(sql, &schema).unwrap();
|
||||
|
||||
@@ -1120,7 +1119,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_projection_mixed_columns_and_expressions() {
|
||||
let schema = create_test_schema();
|
||||
let sql = "CREATE VIEW v AS SELECT a, b * 2 as doubled, c, a + b + c as total FROM t";
|
||||
let sql = "CREATE MATERIALIZED VIEW v AS SELECT a, b * 2 as doubled, c, a + b + c as total FROM t";
|
||||
|
||||
let view = IncrementalView::from_sql(sql, &schema).unwrap();
|
||||
|
||||
@@ -1152,7 +1151,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_projection_complex_expression() {
|
||||
let schema = create_test_schema();
|
||||
let sql = "CREATE VIEW v AS SELECT (a * 2) + (b * 3) as weighted, c / 2 as half FROM t";
|
||||
let sql = "CREATE MATERIALIZED VIEW v AS SELECT (a * 2) + (b * 3) as weighted, c / 2 as half FROM t";
|
||||
|
||||
let view = IncrementalView::from_sql(sql, &schema).unwrap();
|
||||
|
||||
@@ -1176,7 +1175,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_projection_with_where_clause() {
|
||||
let schema = create_test_schema();
|
||||
let sql = "CREATE VIEW v AS SELECT a, a * 2 as doubled FROM t WHERE b > 2";
|
||||
let sql = "CREATE MATERIALIZED VIEW v AS SELECT a, a * 2 as doubled FROM t WHERE b > 2";
|
||||
|
||||
let view = IncrementalView::from_sql(sql, &schema).unwrap();
|
||||
|
||||
@@ -1202,7 +1201,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_projection_more_output_columns_than_input() {
|
||||
let schema = create_test_schema();
|
||||
let sql = "CREATE VIEW v AS SELECT a, b, a * 2 as doubled_a, b * 3 as tripled_b, a + b as sum, hex(c) as hex_c FROM t";
|
||||
let sql = "CREATE MATERIALIZED VIEW v AS SELECT a, b, a * 2 as doubled_a, b * 3 as tripled_b, a + b as sum, hex(c) as hex_c FROM t";
|
||||
|
||||
let view = IncrementalView::from_sql(sql, &schema).unwrap();
|
||||
|
||||
@@ -1237,7 +1236,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_aggregation_count_with_group_by() {
|
||||
let schema = create_test_schema();
|
||||
let sql = "CREATE VIEW v AS SELECT a, COUNT(*) FROM t GROUP BY a";
|
||||
let sql = "CREATE MATERIALIZED VIEW v AS SELECT a, COUNT(*) FROM t GROUP BY a";
|
||||
|
||||
let mut view = IncrementalView::from_sql(sql, &schema).unwrap();
|
||||
|
||||
@@ -1291,7 +1290,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_aggregation_sum_with_filter() {
|
||||
let schema = create_test_schema();
|
||||
let sql = "CREATE VIEW v AS SELECT SUM(b) FROM t WHERE a > 1";
|
||||
let sql = "CREATE MATERIALIZED VIEW v AS SELECT SUM(b) FROM t WHERE a > 1";
|
||||
|
||||
let mut view = IncrementalView::from_sql(sql, &schema).unwrap();
|
||||
|
||||
@@ -1329,7 +1328,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_aggregation_incremental_updates() {
|
||||
let schema = create_test_schema();
|
||||
let sql = "CREATE VIEW v AS SELECT a, COUNT(*), SUM(b) FROM t GROUP BY a";
|
||||
let sql = "CREATE MATERIALIZED VIEW v AS SELECT a, COUNT(*), SUM(b) FROM t GROUP BY a";
|
||||
|
||||
let mut view = IncrementalView::from_sql(sql, &schema).unwrap();
|
||||
|
||||
|
||||
@@ -125,6 +125,7 @@ pub fn translate_inner(
|
||||
| ast::Stmt::CreateTable { .. }
|
||||
| ast::Stmt::CreateTrigger { .. }
|
||||
| ast::Stmt::CreateView { .. }
|
||||
| ast::Stmt::CreateMaterializedView { .. }
|
||||
| ast::Stmt::CreateVirtualTable(..)
|
||||
| ast::Stmt::Delete(..)
|
||||
| ast::Stmt::DropIndex { .. }
|
||||
@@ -189,9 +190,12 @@ pub fn translate_inner(
|
||||
program,
|
||||
)?,
|
||||
ast::Stmt::CreateTrigger { .. } => bail_parse_error!("CREATE TRIGGER not supported yet"),
|
||||
ast::Stmt::CreateView {
|
||||
ast::Stmt::CreateView { .. } => {
|
||||
bail_parse_error!("CREATE VIEW not supported yet.")
|
||||
}
|
||||
ast::Stmt::CreateMaterializedView {
|
||||
view_name, select, ..
|
||||
} => view::translate_create_view(
|
||||
} => view::translate_create_materialized_view(
|
||||
schema,
|
||||
view_name.name.as_str(),
|
||||
&select,
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{Connection, Result, SymbolTable};
|
||||
use std::sync::Arc;
|
||||
use turso_sqlite3_parser::ast::{self, fmt::ToTokens};
|
||||
|
||||
pub fn translate_create_view(
|
||||
pub fn translate_create_materialized_view(
|
||||
schema: &Schema,
|
||||
view_name: &str,
|
||||
select_stmt: &ast::Select,
|
||||
@@ -19,7 +19,7 @@ pub fn translate_create_view(
|
||||
// Check if experimental views are enabled
|
||||
if !connection.experimental_views_enabled() {
|
||||
return Err(crate::LimboError::ParseError(
|
||||
"CREATE VIEW is an experimental feature. Enable with --experimental-views flag"
|
||||
"CREATE MATERIALIZED VIEW is an experimental feature. Enable with --experimental-views flag"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
@@ -40,7 +40,7 @@ pub fn translate_create_view(
|
||||
IncrementalView::can_create_view(select_stmt, schema)?;
|
||||
|
||||
// Reconstruct the SQL string
|
||||
let sql = create_view_to_str(view_name, select_stmt);
|
||||
let sql = create_materialized_view_to_str(view_name, select_stmt);
|
||||
|
||||
// Open cursor to sqlite_schema table
|
||||
let table = schema.get_btree_table(SQLITE_TABLEID).unwrap();
|
||||
@@ -78,9 +78,9 @@ pub fn translate_create_view(
|
||||
Ok(program)
|
||||
}
|
||||
|
||||
fn create_view_to_str(view_name: &str, select_stmt: &ast::Select) -> String {
|
||||
fn create_materialized_view_to_str(view_name: &str, select_stmt: &ast::Select) -> String {
|
||||
format!(
|
||||
"CREATE VIEW {} AS {}",
|
||||
"CREATE MATERIALIZED VIEW {} AS {}",
|
||||
view_name,
|
||||
select_stmt.format().unwrap()
|
||||
)
|
||||
|
||||
@@ -391,6 +391,29 @@ impl ToTokens for Stmt {
|
||||
s.append(TK_AS, None)?;
|
||||
select.to_tokens_with_context(s, context)
|
||||
}
|
||||
Self::CreateMaterializedView {
|
||||
if_not_exists,
|
||||
view_name,
|
||||
columns,
|
||||
select,
|
||||
} => {
|
||||
s.append(TK_CREATE, None)?;
|
||||
s.append(TK_MATERIALIZED, None)?;
|
||||
s.append(TK_VIEW, None)?;
|
||||
if *if_not_exists {
|
||||
s.append(TK_IF, None)?;
|
||||
s.append(TK_NOT, None)?;
|
||||
s.append(TK_EXISTS, None)?;
|
||||
}
|
||||
view_name.to_tokens_with_context(s, context)?;
|
||||
if let Some(columns) = columns {
|
||||
s.append(TK_LP, None)?;
|
||||
comma(columns, s, context)?;
|
||||
s.append(TK_RP, None)?;
|
||||
}
|
||||
s.append(TK_AS, None)?;
|
||||
select.to_tokens_with_context(s, context)
|
||||
}
|
||||
Self::CreateVirtualTable(create_virtual_table) => {
|
||||
let CreateVirtualTable {
|
||||
if_not_exists,
|
||||
|
||||
@@ -130,6 +130,17 @@ pub enum Stmt {
|
||||
/// query
|
||||
select: Box<Select>,
|
||||
},
|
||||
/// `CREATE MATERIALIZED VIEW`
|
||||
CreateMaterializedView {
|
||||
/// `IF NOT EXISTS`
|
||||
if_not_exists: bool,
|
||||
/// view name
|
||||
view_name: QualifiedName,
|
||||
/// columns
|
||||
columns: Option<Vec<IndexedColumn>>,
|
||||
/// query
|
||||
select: Box<Select>,
|
||||
},
|
||||
/// `CREATE VIRTUAL TABLE`
|
||||
CreateVirtualTable(Box<CreateVirtualTable>),
|
||||
/// `DELETE`
|
||||
|
||||
@@ -478,6 +478,11 @@ cmd ::= createkw temp(T) VIEW ifnotexists(E) fullname(Y) eidlist_opt(C)
|
||||
self.ctx.stmt = Some(Stmt::CreateView{ temporary: T, if_not_exists: E, view_name: Y, columns: C,
|
||||
select: Box::new(S) });
|
||||
}
|
||||
cmd ::= createkw MATERIALIZED VIEW ifnotexists(E) fullname(Y) eidlist_opt(C)
|
||||
AS select(S). {
|
||||
self.ctx.stmt = Some(Stmt::CreateMaterializedView{ if_not_exists: E, view_name: Y, columns: C,
|
||||
select: Box::new(S) });
|
||||
}
|
||||
cmd ::= DROP VIEW ifexists(E) fullname(X). {
|
||||
self.ctx.stmt = Some(Stmt::DropView{ if_exists: E, view_name: X });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user