From 7a3892c1aaf52db4525811d49c9e1b923e4ec386 Mon Sep 17 00:00:00 2001 From: Daniil Buravlev Date: Wed, 1 Jul 2026 15:43:03 +0300 Subject: [PATCH 1/6] Add views and specifiers for fields selection --- engine/src/lib.rs | 26 ++++++++ engine/src/plan.rs | 3 +- engine/src/plan/project.rs | 9 ++- engine/src/predicate.rs | 14 ++++- engine/src/query/command.rs | 34 ++++++---- engine/src/query/heuristic_planner.rs | 30 ++++++--- engine/src/query/parser.rs | 89 ++++++++++++++++++++++----- engine/src/query/table_planner.rs | 8 ++- engine/src/scan/project.rs | 58 ++++++++++------- engine/src/schema_mapping.rs | 79 ++++++++++++++++++++++++ 10 files changed, 284 insertions(+), 66 deletions(-) create mode 100644 engine/src/schema_mapping.rs diff --git a/engine/src/lib.rs b/engine/src/lib.rs index f6d402e..7184084 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -29,6 +29,7 @@ pub mod record_page; pub mod rid; pub mod scan; pub mod schema; +mod schema_mapping; pub mod sort_by; pub mod stat_mgr; pub mod table_mgr; @@ -274,4 +275,29 @@ mod tests { } tx.commit().unwrap(); } + + #[test] + fn select_with_views_and_specs() { + let dir = tempdir().unwrap(); + let db = SimpleDB::new(dir.path()).unwrap(); + let tx = db.get_tx().unwrap(); + db.execute(&tx, "CREATE TABLE test(id INT, name VARCHAR(100))") + .unwrap(); + tx.commit().unwrap(); + + db.execute(&tx, "INSERT INTO test(id, name) VALUES(1, 'User')") + .unwrap(); + + let result = db + .query(&tx, "SELECT id i, name n FROM test t WHERE t.id=1") + .unwrap(); + assert!(result.next().unwrap()); + assert_eq!(result.get_i32(&Element::raw("i")).unwrap(), 1); + assert_eq!(result.get_string(&Element::raw("n")).unwrap(), "User"); + + let result = db + .query(&tx, "SELECT id i, name n FROM test t WHERE t.id=2") + .unwrap(); + assert!(!result.next().unwrap()); + } } diff --git a/engine/src/plan.rs b/engine/src/plan.rs index 9056c35..87ca806 100644 --- a/engine/src/plan.rs +++ b/engine/src/plan.rs @@ -1,9 +1,8 @@ use std::rc::Rc; -use common::DbResult; - use crate::element::Element; use crate::{scan::Scan, schema::Schema}; +use common::DbResult; pub(crate) mod group; pub mod index; diff --git a/engine/src/plan/project.rs b/engine/src/plan/project.rs index f3a93a8..d3070a4 100644 --- a/engine/src/plan/project.rs +++ b/engine/src/plan/project.rs @@ -3,6 +3,7 @@ use std::{collections::HashSet, rc::Rc}; use common::DbResult; use crate::schema::SchemaBuilder; +use crate::schema_mapping::SchemaMapping; use crate::{ element::Element, plan::Plan, @@ -10,13 +11,14 @@ use crate::{ schema::Schema, }; -pub struct ProjectPlan { +pub(crate) struct ProjectPlan { plan: Rc, schema: Schema, + mapping: SchemaMapping, } impl ProjectPlan { - pub fn new(plan: Rc, fields: Vec) -> DbResult { + pub(crate) fn new(plan: Rc, fields: Vec, mapping: SchemaMapping) -> DbResult { let mut schema = SchemaBuilder::default(); for field in fields { let other = plan.schema()?; @@ -25,6 +27,7 @@ impl ProjectPlan { Ok(Self { plan, schema: schema.build(), + mapping, }) } } @@ -33,7 +36,7 @@ impl Plan for ProjectPlan { fn open(&self) -> DbResult> { let scan = self.plan.open()?; let fields: HashSet = self.schema.fields().into_iter().map(|(f, _)| f).collect(); - Ok(Rc::new(ProjectScan::new(scan, fields))) + Ok(Rc::new(ProjectScan::new(scan, fields, self.mapping.clone()))) } fn blocks_accessed(&self) -> DbResult { diff --git a/engine/src/predicate.rs b/engine/src/predicate.rs index 167cd2b..44c3326 100644 --- a/engine/src/predicate.rs +++ b/engine/src/predicate.rs @@ -137,11 +137,19 @@ impl std::fmt::Display for Term { } } -#[derive(Default, Clone)] +#[derive(Clone)] pub struct Predicate { terms: Arc>>, } +impl Default for Predicate { + fn default() -> Self { + Self { + terms: Arc::new(RwLock::new(vec![])), + } + } +} + impl Predicate { pub fn new(term: Term) -> Self { Self { @@ -177,7 +185,7 @@ impl Predicate { Ok(factor) } - pub fn select_sub_pred(&self, schema: &Schema) -> DbResult { + pub(crate) fn select_sub_pred(&self, schema: &Schema) -> DbResult { let result = Predicate::default(); let read = self.terms.read().map_err(DbError::lock)?; { @@ -191,7 +199,7 @@ impl Predicate { Ok(result) } - pub fn join_sub_pred(&self, s1: &Schema, s2: &Schema) -> DbResult> { + pub(crate) fn join_sub_pred(&self, s1: &Schema, s2: &Schema) -> DbResult> { let result = Predicate::default(); let new_schema = SchemaBuilder::default().add_all(s1).add_all(s2).build(); let read = self.terms.read().map_err(DbError::lock)?; diff --git a/engine/src/query/command.rs b/engine/src/query/command.rs index 73f032d..bfddb7f 100644 --- a/engine/src/query/command.rs +++ b/engine/src/query/command.rs @@ -5,6 +5,7 @@ use crate::{ sort_by::SortByData, value::Value, }; +use crate::schema_mapping::SchemaMapping; pub enum Command { Insert(InsertData), @@ -32,30 +33,41 @@ impl std::fmt::Display for Command { pub struct QueryData { pub fields: Vec, - pub tables: Vec, + pub table: Element, pub predicate: Predicate, pub group_by: GroupByData, - pub sort_by: SortByData, + pub order_by: SortByData, + pub mapping: SchemaMapping, } impl std::fmt::Display for QueryData { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "SELECT ")?; for (i, field) in self.fields.iter().enumerate() { + let field = if let Some(source) = self.mapping.field(field) && source != field { + match source { + Element::Raw(source) => &Element::view(source, field.as_raw().unwrap()), + e => e + } + } else { + field + }; if i == 0 { write!(f, "{}", field)?; } else { write!(f, ", {}", field)?; } } - write!(f, " FROM ")?; - for (i, table) in self.tables.iter().enumerate() { - if i == 0 { - write!(f, "{}", table)?; - } else { - write!(f, ", {}", table)?; + let table = &self.table; + let table = if let Some(source) = self.mapping.table(table) && source != table { + match source { + Element::Raw(source) => &Element::view(source, table.as_raw().unwrap()), + e => e } - } + } else { + table + }; + write!(f, " FROM {}", table)?; let predicate = self.predicate.to_string(); if !predicate.is_empty() { write!(f, " WHERE {}", predicate)?; @@ -63,8 +75,8 @@ impl std::fmt::Display for QueryData { if !self.group_by.is_empty() { write!(f, " {}", self.group_by)?; } - if !self.sort_by.is_empty() { - write!(f, " {}", self.sort_by)?; + if !self.order_by.is_empty() { + write!(f, " {}", self.order_by)?; } Ok(()) } diff --git a/engine/src/query/heuristic_planner.rs b/engine/src/query/heuristic_planner.rs index ca31744..f6fe9d2 100644 --- a/engine/src/query/heuristic_planner.rs +++ b/engine/src/query/heuristic_planner.rs @@ -25,10 +25,22 @@ impl HeuristicQueryPlannerInner { } fn create_plan(&mut self, data: QueryData, tx: &Arc) -> DbResult> { - for table in &data.tables { - let tp = TablePlanner::new(table.as_raw()?, data.predicate.clone(), tx, &self.md)?; - self.table_planners.push(tp); - } + let table = &data.table; + let table = if let Some(source) = data.mapping.table(table) + && source != table + { + source + } else { + table + }; + let tp = TablePlanner::new( + table.clone(), + data.predicate, + tx, + &self.md, + data.mapping.clone(), + )?; + self.table_planners.push(tp); let mut current = self.get_lowest_select_plan()?; while !self.table_planners.is_empty() { if let Some(p) = self.get_lowest_join_plan(¤t)? { @@ -45,10 +57,14 @@ impl HeuristicQueryPlannerInner { vec![], )?); } - if !data.sort_by.is_empty() { - current = Rc::new(SortPlan::new(tx, ¤t, data.sort_by.fields)?); + if !data.order_by.is_empty() { + current = Rc::new(SortPlan::new(tx, ¤t, data.order_by.fields)?); } - Ok(Rc::new(ProjectPlan::new(current, data.fields)?)) + Ok(Rc::new(ProjectPlan::new( + current, + data.fields, + data.mapping, + )?)) } fn get_lowest_select_plan(&mut self) -> DbResult> { diff --git a/engine/src/query/parser.rs b/engine/src/query/parser.rs index 6583b2e..aff0254 100644 --- a/engine/src/query/parser.rs +++ b/engine/src/query/parser.rs @@ -1,6 +1,7 @@ use common::{DbResult, error::DbError}; use crate::schema::SchemaBuilder; +use crate::schema_mapping::{SchemaMapping, SchemaMappingBuilder}; use crate::{ element::Element, predicate::{Expression, Predicate, Term}, @@ -78,35 +79,44 @@ impl Parser { Ok(pred) } - pub fn query(&self) -> DbResult { + pub(crate) fn query(&self) -> DbResult { self.lexer.eat_keyword(Token::Select)?; let fields = self.select_list()?; + self.lexer.eat_keyword(Token::From)?; - let tables = self.table_list()?; + let table = self.element()?; + let mut predicate = Predicate::default(); if self.lexer.match_keyword(Token::Where) { self.lexer.eat_keyword(Token::Where)?; predicate = self.predicate()?; } + let mut group_by = GroupByData::default(); if self.lexer.match_keyword(Token::Group) { self.lexer.eat_keyword(Token::Group)?; self.lexer.eat_keyword(Token::By)?; group_by = self.group_by()?; } - let mut sort_by = SortByData::default(); + + let mut order_by = SortByData::default(); if self.lexer.match_keyword(Token::Sort) { self.lexer.eat_keyword(Token::Sort)?; self.lexer.eat_keyword(Token::By)?; - sort_by = self.order_by()?; + order_by = self.order_by()?; } + + let (fields, table, predicate, group_by, order_by, mapping) = + process_schema(fields, table, predicate, group_by, order_by)?; + self.check_remainder()?; Ok(Command::Query(QueryData { fields, - tables, + table, predicate, group_by, - sort_by, + order_by, + mapping, })) } @@ -119,15 +129,6 @@ impl Parser { Ok(fields) } - fn table_list(&self) -> DbResult> { - let mut tables = vec![self.element()?]; - while self.lexer.match_delim(',') { - self.lexer.eat_delimiter(',')?; - tables.push(self.element()?); - } - Ok(tables) - } - pub fn update_cmd(&self) -> DbResult { if self.lexer.match_keyword(Token::Insert) { self.insert() @@ -319,6 +320,56 @@ impl Parser { } } +fn process_schema( + fields: Vec, + table: Element, + predicate: Predicate, + group_by: GroupByData, + order_by: SortByData, +) -> DbResult<( + Vec, + Element, + Predicate, + GroupByData, + SortByData, + SchemaMapping, +)> { + let mut mapping = SchemaMappingBuilder::default(); + let table = match table { + Element::Raw(table) => Element::Raw(table), + Element::View(source, id) => { + mapping = mapping.add_table(Element::raw(&id), Element::raw(&source)); + Element::Raw(id) + } + _ => return Err(DbError::InvalidFieldType), + }; + let mut new_fields = Vec::with_capacity(fields.len()); + for field in fields { + match field { + Element::Raw(field) => { + mapping = mapping.add_table_field(table.clone(), Element::raw(&field)); + new_fields.push(Element::Raw(field)); }, + Element::View(source, id) => { + mapping = mapping.add_table_field(table.clone(), Element::raw(&source)); + mapping = mapping.add_field(table.clone(), Element::raw(&id), Element::raw(&source)); + new_fields.push(Element::Raw(id)); + } + Element::Spec(source, target) => { + mapping = mapping.add_table_field(Element::raw(&source), Element::raw(&target)); + new_fields.push(Element::Spec(source, target)); + } + } + } + Ok(( + new_fields, + table, + predicate, + group_by, + order_by, + mapping.build(), + )) +} + #[cfg(test)] mod tests { use super::*; @@ -403,4 +454,12 @@ mod tests { let select = parser.query().unwrap(); assert_eq!(query, select.to_string()); } + + #[test] + fn select_with_views_and_specs() { + let query = "SELECT id i, name n, age a FROM users u WHERE u.id=10"; + let parser = Parser::new(query).unwrap(); + let select = parser.query().unwrap(); + assert_eq!(query, select.to_string()); + } } diff --git a/engine/src/query/table_planner.rs b/engine/src/query/table_planner.rs index ebaf2cc..5fb9df0 100644 --- a/engine/src/query/table_planner.rs +++ b/engine/src/query/table_planner.rs @@ -4,6 +4,7 @@ use common::DbResult; use transaction::transaction::Transaction; use crate::element::Element; +use crate::schema_mapping::SchemaMapping; use crate::{ index_mgr::IndexInfo, metadata_mgr::MetadataMgr, @@ -24,14 +25,16 @@ pub(crate) struct TablePlanner { schema: Schema, indexes: HashMap, tx: Arc, + mapping: SchemaMapping, } impl TablePlanner { pub(crate) fn new( - table: &str, + table: Element, predicate: Predicate, tx: &Arc, md: &MetadataMgr, + mapping: SchemaMapping, ) -> DbResult { let plan = TablePlan::new(tx, table.to_string(), md)?; Ok(Self { @@ -39,7 +42,8 @@ impl TablePlanner { tx: Arc::clone(tx), schema: plan.schema()?, plan: Rc::new(plan), - indexes: md.get_index_info(table, tx)?, + indexes: md.get_index_info(table.as_raw()?, tx)?, + mapping, }) } diff --git a/engine/src/scan/project.rs b/engine/src/scan/project.rs index 52c9f07..ffe71ea 100644 --- a/engine/src/scan/project.rs +++ b/engine/src/scan/project.rs @@ -1,18 +1,39 @@ use crate::element::Element; use crate::scan::Scan; use crate::schema::{Schema, SchemaBuilder}; +use crate::schema_mapping::SchemaMapping; use common::DbResult; use common::error::DbError; use std::{collections::HashSet, rc::Rc}; -pub struct ProjectScan { +pub(crate) struct ProjectScan { scan: Rc, fields: HashSet, + mapping: SchemaMapping, } impl ProjectScan { - pub fn new(scan: Rc, fields: HashSet) -> Self { - Self { scan, fields } + pub(crate) fn new( + scan: Rc, + fields: HashSet, + mapping: SchemaMapping, + ) -> Self { + Self { + scan, + fields, + mapping, + } + } + + fn get_field<'a>(&'a self, field: &'a Element) -> DbResult<&'a Element> { + if self.fields.contains(field) { + return Err(DbError::FieldNotExists(field.to_string())); + } + if let Some(field) = self.mapping.field(field) { + Ok(field) + } else { + Ok(field) + } } } @@ -25,32 +46,23 @@ impl Scan for ProjectScan { self.scan.next() } - fn get_i32(&self, field_name: &Element) -> DbResult { - if self.has_field(field_name)? { - self.scan.get_i32(field_name) - } else { - Err(DbError::FieldNotExists(field_name.to_string())) - } + fn get_i32(&self, field: &Element) -> DbResult { + let field = self.get_field(field)?; + self.scan.get_i32(field) } - fn get_string(&self, field_name: &Element) -> DbResult { - if self.has_field(field_name)? { - self.scan.get_string(field_name) - } else { - Err(DbError::FieldNotExists(field_name.to_string())) - } + fn get_string(&self, field: &Element) -> DbResult { + let field = self.get_field(field)?; + self.scan.get_string(field) } - fn get_val(&self, field_name: &Element) -> DbResult { - if self.has_field(field_name)? { - self.scan.get_val(field_name) - } else { - Err(DbError::FieldNotExists(field_name.to_string())) - } + fn get_val(&self, field: &Element) -> DbResult { + let field = self.get_field(field)?; + self.scan.get_val(field) } - fn has_field(&self, field_name: &Element) -> DbResult { - Ok(self.fields.contains(field_name)) + fn has_field(&self, field: &Element) -> DbResult { + Ok(self.fields.contains(field)) } fn close(&self) -> DbResult<()> { diff --git a/engine/src/schema_mapping.rs b/engine/src/schema_mapping.rs new file mode 100644 index 0000000..4d48b8d --- /dev/null +++ b/engine/src/schema_mapping.rs @@ -0,0 +1,79 @@ +use crate::element::Element; +use std::collections::{HashMap, HashSet}; +use std::rc::Rc; + +#[derive(Debug, PartialEq, Eq, Default)] +struct SchemaMappingInner { + tables_fields: HashMap>, + tables_names: HashMap, + fields_names: HashMap, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct SchemaMapping(Rc); + +impl SchemaMapping { + pub(crate) fn field(&self, name: &Element) -> Option<&Element> { + self.0.fields_names.get(name) + } + + pub(crate) fn table(&self, name: &Element) -> Option<&Element> { + self.0.tables_names.get(name) + } +} + +#[derive(Default)] +pub(crate) struct SchemaMappingBuilder(SchemaMappingInner); + +impl SchemaMappingBuilder { + pub(crate) fn add_table_field(mut self, table: Element, field: Element) -> Self { + let fields = self.0.tables_fields.entry(table).or_insert(HashSet::new()); + fields.insert(field); + self + } + + pub(crate) fn add_field(mut self, table: Element, id: Element, source: Element) -> Self { + self.0.fields_names.insert(id, source.clone()); + let fields = self + .0 + .tables_fields + .entry(table) + .or_insert_with(HashSet::new); + fields.insert(source); + self + } + + pub(crate) fn add_table(mut self, id: Element, source: Element) -> Self { + self.0.tables_names.insert(id, source); + self + } + + pub(crate) fn build(self) -> SchemaMapping { + SchemaMapping(Rc::new(self.0)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn schema_mapping() { + let source_table = Element::raw("table"); + let id_table = Element::raw("t"); + + let source_field = Element::raw("id"); + let id_field = Element::raw("i"); + + let mut fields = HashSet::new(); + fields.insert(source_field.clone()); + + let mapping = SchemaMappingBuilder::default() + .add_table(id_table.clone(), source_table.clone()) + .add_field(source_table.clone(), id_field.clone(), source_field.clone()) + .build(); + + assert_eq!(source_table, *mapping.table(&id_table).unwrap()); + assert_eq!(source_field, *mapping.field(&id_field).unwrap()); + } +} From 69cbb3c2f847ab4440fc8219f2663bb69427361a Mon Sep 17 00:00:00 2001 From: Daniil Buravlev Date: Wed, 1 Jul 2026 21:57:55 +0300 Subject: [PATCH 2/6] Implement view and specifiers for SELECT query --- engine/src/index.rs | 2 +- engine/src/index_mgr.rs | 2 +- engine/src/layout.rs | 2 +- engine/src/plan.rs | 1 + engine/src/plan/group.rs | 2 +- engine/src/plan/index.rs | 9 +++- engine/src/plan/merge.rs | 7 ++- engine/src/plan/multibuffer.rs | 9 +++- engine/src/plan/product.rs | 9 +++- engine/src/plan/project.rs | 14 ++++-- engine/src/plan/table.rs | 6 +-- engine/src/predicate.rs | 77 +++++++++++++++++++++++++++++-- engine/src/query/parser.rs | 12 +++-- engine/src/query/table_planner.rs | 2 +- engine/src/record_page.rs | 2 +- engine/src/scan/index.rs | 21 ++++++--- engine/src/scan/merge.rs | 5 +- engine/src/scan/order.rs | 2 +- engine/src/scan/product.rs | 9 +++- engine/src/scan/project.rs | 2 +- engine/src/scan/table.rs | 2 +- engine/src/schema.rs | 40 +++++++++------- engine/src/table_mgr.rs | 8 ++-- engine/src/view_mgr.rs | 2 +- 24 files changed, 189 insertions(+), 58 deletions(-) diff --git a/engine/src/index.rs b/engine/src/index.rs index 0bae38e..4832e61 100644 --- a/engine/src/index.rs +++ b/engine/src/index.rs @@ -42,7 +42,7 @@ mod tests { let majorid = Element::raw("majorid"); let setup_tx = db.get_tx().unwrap(); - let schema = SchemaBuilder::default() + let schema = SchemaBuilder::new(Element::raw(table)) .add_int_field(sid.clone()) .add_string_field(sname.clone(), 16) .add_int_field(majorid.clone()) diff --git a/engine/src/index_mgr.rs b/engine/src/index_mgr.rs index 0752260..ab45001 100644 --- a/engine/src/index_mgr.rs +++ b/engine/src/index_mgr.rs @@ -87,7 +87,7 @@ impl IndexMgr { tx: &Arc, ) -> DbResult { if is_new { - let schema = SchemaBuilder::default() + let schema = SchemaBuilder::new(Element::raw(IDX_TABLE)) .add_string_field(Element::raw(IDX_NAME), MAX_LENGTH) .add_string_field(Element::raw(TABLE_NAME), MAX_LENGTH) .add_string_field(Element::raw(FIELD_NAME), MAX_LENGTH) diff --git a/engine/src/layout.rs b/engine/src/layout.rs index 28cfd7f..cba4dd0 100644 --- a/engine/src/layout.rs +++ b/engine/src/layout.rs @@ -84,7 +84,7 @@ mod tests { #[test] fn from() { - let schema = SchemaBuilder::default() + let schema = SchemaBuilder::new(Element::raw("test")) .add_int_field(Element::raw("id")) .build(); Layout::from(schema, HashMap::new(), 16); diff --git a/engine/src/plan.rs b/engine/src/plan.rs index 87ca806..82179c7 100644 --- a/engine/src/plan.rs +++ b/engine/src/plan.rs @@ -3,6 +3,7 @@ use std::rc::Rc; use crate::element::Element; use crate::{scan::Scan, schema::Schema}; use common::DbResult; +use common::error::DbError; pub(crate) mod group; pub mod index; diff --git a/engine/src/plan/group.rs b/engine/src/plan/group.rs index ec52f15..bee6a29 100644 --- a/engine/src/plan/group.rs +++ b/engine/src/plan/group.rs @@ -25,7 +25,7 @@ impl GroupByPlan { group_fields: Vec, aggregation_fn: Vec, ) -> DbResult { - let mut schema = SchemaBuilder::default(); + let mut schema = SchemaBuilder::new(plan.schema()?.table().clone()); let s = plan.schema()?; for field in &group_fields { schema = schema.add(field.clone(), &s); diff --git a/engine/src/plan/index.rs b/engine/src/plan/index.rs index 5ab8929..6245fcf 100644 --- a/engine/src/plan/index.rs +++ b/engine/src/plan/index.rs @@ -79,7 +79,14 @@ impl IndexJoinPlan { ) -> DbResult { let s1 = p1.schema()?; let s2 = p2.schema()?; - let schema = SchemaBuilder::default().add_all(&s1).add_all(&s2).build(); + let schema = SchemaBuilder::new(Element::Raw(format!( + "index_join_{}_{}", + p1.schema()?.table(), + p2.schema()?.table() + ))) + .add_all(&s1) + .add_all(&s2) + .build(); Ok(Self { p1: Rc::clone(p1), p2: Rc::clone(p2), diff --git a/engine/src/plan/merge.rs b/engine/src/plan/merge.rs index cc8bdc9..53ad672 100644 --- a/engine/src/plan/merge.rs +++ b/engine/src/plan/merge.rs @@ -30,7 +30,12 @@ impl MergeJoinPlan { ) -> DbResult { let p1 = Rc::new(SortPlan::new(tx, p1, vec![field_name1.clone()])?); let p2 = Rc::new(SortPlan::new(tx, p2, vec![field_name2.clone()])?); - let schema = SchemaBuilder::default().build(); + let schema = SchemaBuilder::new(Element::Raw(format!( + "merge_join_{}_{}", + p1.schema()?.table(), + p2.schema()?.table() + ))) + .build(); Ok(Self { p1, p2, diff --git a/engine/src/plan/multibuffer.rs b/engine/src/plan/multibuffer.rs index 4d5aa1e..d35d280 100644 --- a/engine/src/plan/multibuffer.rs +++ b/engine/src/plan/multibuffer.rs @@ -27,7 +27,14 @@ impl MultiBufferProductPlan { ) -> DbResult { let s1 = left.schema()?; let s2 = right.schema()?; - let schema = SchemaBuilder::default().add_all(&s1).add_all(&s2).build(); + let schema = SchemaBuilder::new(Element::Raw(format!( + "multibuffer_{}_{}", + left.schema()?.table(), + right.schema()?.table() + ))) + .add_all(&s1) + .add_all(&s2) + .build(); let plan = Self { tx: Arc::clone(tx), left: Rc::new(MaterializePlan::new(left, tx)), diff --git a/engine/src/plan/product.rs b/engine/src/plan/product.rs index c07081b..9c51b29 100644 --- a/engine/src/plan/product.rs +++ b/engine/src/plan/product.rs @@ -20,7 +20,14 @@ impl ProductPlan { pub fn new(p1: Rc, p2: Rc) -> DbResult { let s1 = p1.schema()?; let s2 = p2.schema()?; - let schema = SchemaBuilder::default().add_all(&s1).add_all(&s2).build(); + let schema = SchemaBuilder::new(Element::Raw(format!( + "product_{}_{}", + s1.table(), + s2.table() + ))) + .add_all(&s1) + .add_all(&s2) + .build(); Ok(Self { p1, p2, schema }) } } diff --git a/engine/src/plan/project.rs b/engine/src/plan/project.rs index d3070a4..034cd4a 100644 --- a/engine/src/plan/project.rs +++ b/engine/src/plan/project.rs @@ -18,8 +18,12 @@ pub(crate) struct ProjectPlan { } impl ProjectPlan { - pub(crate) fn new(plan: Rc, fields: Vec, mapping: SchemaMapping) -> DbResult { - let mut schema = SchemaBuilder::default(); + pub(crate) fn new( + plan: Rc, + fields: Vec, + mapping: SchemaMapping, + ) -> DbResult { + let mut schema = SchemaBuilder::new(plan.schema()?.table().clone()); for field in fields { let other = plan.schema()?; schema = schema.add(field, &other); @@ -36,7 +40,11 @@ impl Plan for ProjectPlan { fn open(&self) -> DbResult> { let scan = self.plan.open()?; let fields: HashSet = self.schema.fields().into_iter().map(|(f, _)| f).collect(); - Ok(Rc::new(ProjectScan::new(scan, fields, self.mapping.clone()))) + Ok(Rc::new(ProjectScan::new( + scan, + fields, + self.mapping.clone(), + ))) } fn blocks_accessed(&self) -> DbResult { diff --git a/engine/src/plan/table.rs b/engine/src/plan/table.rs index 494695e..3e3254f 100644 --- a/engine/src/plan/table.rs +++ b/engine/src/plan/table.rs @@ -15,7 +15,7 @@ use crate::{ pub struct TablePlan { tx: Arc, - table: String, + table: Element, layout: Layout, stat: StatInfo, } @@ -26,7 +26,7 @@ impl TablePlan { let stat = md.get_stat_info(&table, layout.clone(), tx)?; Ok(Self { tx: Arc::clone(tx), - table, + table: Element::Raw(table), layout, stat, }) @@ -37,7 +37,7 @@ impl Plan for TablePlan { fn open(&self) -> DbResult> { Ok(Rc::new(TableScan::new( &self.tx, - &self.table, + self.table.as_raw()?, self.layout.clone(), )?)) } diff --git a/engine/src/predicate.rs b/engine/src/predicate.rs index 44c3326..e9d2e84 100644 --- a/engine/src/predicate.rs +++ b/engine/src/predicate.rs @@ -8,6 +8,7 @@ use common::{DbResult, error::DbError}; use crate::element::Element; use crate::schema::SchemaBuilder; +use crate::schema_mapping::SchemaMapping; use crate::{plan::Plan, scan::Scan, schema::Schema, value::Value}; #[derive(Debug, Clone)] @@ -31,6 +32,35 @@ impl Expression { } } + pub(crate) fn applies_to_table(&self, schema: &Schema, mapping: &SchemaMapping) -> bool { + let Self::Field(field) = self else { + return true; + }; + match field { + Element::Raw(field) => { + let field = if let Some(field) = mapping.field(&Element::raw(field)) { + field + } else { + &Element::raw(field) + }; + schema.has_field(field) + } + Element::Spec(source, target) => { + let table = if let Some(table) = mapping.table(&Element::raw(source)) { + table + } else { + &Element::raw(source) + }; + if schema.table() != table { + return false; + } + let field = Element::raw(target); + schema.has_field(&field) + } + _ => false, + } + } + pub fn as_field_name(&self) -> Option<&Element> { match self { Self::Field(field) => Some(field), @@ -44,6 +74,26 @@ impl Expression { _ => None, } } + + pub(crate) fn resolve(&self, mapping: &SchemaMapping) -> Self { + let Self::Field(field) = self else { + return self.clone(); + }; + let field = match field { + Element::Raw(field) => { + if let Some(field) = mapping.field(&Element::raw(field)) { + field.clone() + } else { + Element::raw(field) + } + } + Element::Spec(_, target) => { + Element::raw(target) + } + _ => panic!("unprocessable field") + }; + Self::Field(field) + } } impl std::fmt::Display for Expression { @@ -76,6 +126,10 @@ impl Term { self.left.applies_to(schema) && self.right.applies_to(schema) } + pub(crate) fn applies_to_table(&self, schema: &Schema, mapping: &SchemaMapping) -> bool { + self.left.applies_to_table(schema, mapping) && self.right.applies_to_table(schema, mapping) + } + pub fn reduction_factor(&self, p: &Rc) -> DbResult { if let Some(left) = self.left.as_field_name() && let Some(right) = self.right.as_field_name() @@ -129,6 +183,12 @@ impl Term { Ok(None) } } + + pub fn resolve(&self, mapping: &SchemaMapping) -> Term { + let left = self.left.resolve(mapping); + let right = self.right.resolve(mapping); + Term::new(left, right) + } } impl std::fmt::Display for Term { @@ -185,14 +245,19 @@ impl Predicate { Ok(factor) } - pub(crate) fn select_sub_pred(&self, schema: &Schema) -> DbResult { + pub(crate) fn select_sub_pred( + &self, + schema: &Schema, + mapping: &SchemaMapping, + ) -> DbResult { let result = Predicate::default(); let read = self.terms.read().map_err(DbError::lock)?; { let mut terms = result.terms.write().map_err(DbError::lock)?; for t in read.iter() { - if t.applies_to(schema) { - terms.push(t.clone()); + if t.applies_to_table(schema, mapping) { + let term = t.resolve(mapping); + terms.push(term); } } } @@ -201,7 +266,11 @@ impl Predicate { pub(crate) fn join_sub_pred(&self, s1: &Schema, s2: &Schema) -> DbResult> { let result = Predicate::default(); - let new_schema = SchemaBuilder::default().add_all(s1).add_all(s2).build(); + let new_schema = + SchemaBuilder::new(Element::Raw(format!("join_{}_{}", s1.table(), s2.table()))) + .add_all(s1) + .add_all(s2) + .build(); let read = self.terms.read().map_err(DbError::lock)?; { let mut terms = result.terms.write().map_err(DbError::lock)?; diff --git a/engine/src/query/parser.rs b/engine/src/query/parser.rs index aff0254..ea2a139 100644 --- a/engine/src/query/parser.rs +++ b/engine/src/query/parser.rs @@ -226,13 +226,13 @@ impl Parser { self.lexer.eat_keyword(Token::Table)?; let name = self.lexer.eat_id()?; self.lexer.eat_delimiter('(')?; - let schema = self.field_definitions()?; + let schema = self.field_definitions(&name)?; self.lexer.eat_delimiter(')')?; Ok(Command::CreateTable(TableData { name, schema })) } - fn field_definitions(&self) -> DbResult { - let mut schema = SchemaBuilder::default(); + fn field_definitions(&self, table: &str) -> DbResult { + let mut schema = SchemaBuilder::new(Element::raw(table)); schema = self.field_definition(schema)?; while self.lexer.match_delim(',') { self.lexer.eat_delimiter(',')?; @@ -348,10 +348,12 @@ fn process_schema( match field { Element::Raw(field) => { mapping = mapping.add_table_field(table.clone(), Element::raw(&field)); - new_fields.push(Element::Raw(field)); }, + new_fields.push(Element::Raw(field)); + } Element::View(source, id) => { mapping = mapping.add_table_field(table.clone(), Element::raw(&source)); - mapping = mapping.add_field(table.clone(), Element::raw(&id), Element::raw(&source)); + mapping = + mapping.add_field(table.clone(), Element::raw(&id), Element::raw(&source)); new_fields.push(Element::Raw(id)); } Element::Spec(source, target) => { diff --git a/engine/src/query/table_planner.rs b/engine/src/query/table_planner.rs index 5fb9df0..b3b26b0 100644 --- a/engine/src/query/table_planner.rs +++ b/engine/src/query/table_planner.rs @@ -121,7 +121,7 @@ impl TablePlanner { } fn add_select_pred(&self, p: &Rc) -> DbResult> { - let select_predicate = self.predicate.select_sub_pred(&self.schema)?; + let select_predicate = self.predicate.select_sub_pred(&self.schema, &self.mapping)?; Ok(Rc::new(SelectPlan::new(p.clone(), select_predicate))) } diff --git a/engine/src/record_page.rs b/engine/src/record_page.rs index 2b02d70..0a43fc4 100644 --- a/engine/src/record_page.rs +++ b/engine/src/record_page.rs @@ -143,7 +143,7 @@ mod tests { let a = Element::raw("A"); let b = Element::raw("B"); - let schema = SchemaBuilder::default() + let schema = SchemaBuilder::new(Element::raw("test")) .add_int_field(a.clone()) .add_string_field(b.clone(), 9) .build(); diff --git a/engine/src/scan/index.rs b/engine/src/scan/index.rs index 9bfda45..9e3eb29 100644 --- a/engine/src/scan/index.rs +++ b/engine/src/scan/index.rs @@ -150,7 +150,14 @@ impl Scan for IndexJoinScan { fn schema(&self) -> DbResult { let s1 = self.left.schema()?; let s2 = self.right.schema()?; - let s = SchemaBuilder::default().add_all(&s1).add_all(&s2).build(); + let s = SchemaBuilder::new(Element::Raw(format!( + "index_joni_{}_{}", + s1.table(), + s2.table() + ))) + .add_all(&s1) + .add_all(&s2) + .build(); Ok(s) } } @@ -179,21 +186,23 @@ mod tests { let gadyear = Element::raw("gadyear"); let majorid = Element::raw("majorid"); - let schema = SchemaBuilder::default() + let table = "users"; + + let schema = SchemaBuilder::new(Element::raw(table)) .add_int_field(sid.clone()) .add_string_field(sname.clone(), 16) .add_int_field(gadyear.clone()) .add_int_field(majorid.clone()) .build(); - md.create_table("users", schema, &tx).unwrap(); - md.create_index("users_ids", "users", "sid", &tx).unwrap(); + md.create_table(table, schema, &tx).unwrap(); + md.create_index("users_ids", table, "sid", &tx).unwrap(); - let plan = TablePlan::new(&tx, "users".to_string(), &md).unwrap(); + let plan = TablePlan::new(&tx, table.to_string(), &md).unwrap(); let s = plan.open().unwrap(); let mut indexes = HashMap::new(); - let infos = md.get_index_info("users", &tx).unwrap(); + let infos = md.get_index_info(table, &tx).unwrap(); for (field, info) in infos { let index = info.open().unwrap(); indexes.insert(field, index); diff --git a/engine/src/scan/merge.rs b/engine/src/scan/merge.rs index 4e3708a..7f85217 100644 --- a/engine/src/scan/merge.rs +++ b/engine/src/scan/merge.rs @@ -103,7 +103,10 @@ impl MergeJoinScanLock { fn schema(&self) -> DbResult { let s1 = self.s1.schema()?; let s2 = self.s2.schema()?; - let s = SchemaBuilder::default().add_all(&s1).add_all(&s2).build(); + let s = SchemaBuilder::new(Element::Raw(format!("merge_{}_{}", s1.table(), s2.table()))) + .add_all(&s1) + .add_all(&s2) + .build(); Ok(s) } } diff --git a/engine/src/scan/order.rs b/engine/src/scan/order.rs index 17d0c49..c79d0e9 100644 --- a/engine/src/scan/order.rs +++ b/engine/src/scan/order.rs @@ -168,8 +168,8 @@ impl OrderScanInner { } fn schema(&self) -> DbResult { - let mut s = SchemaBuilder::default(); let s1 = self.s1.schema()?; + let mut s = SchemaBuilder::new(Element::raw("group")); s = s.add_all(&s1); if let Some(s2) = &self.s2 { let s2 = s2.schema()?; diff --git a/engine/src/scan/product.rs b/engine/src/scan/product.rs index c170f56..3558b28 100644 --- a/engine/src/scan/product.rs +++ b/engine/src/scan/product.rs @@ -68,7 +68,14 @@ impl Scan for ProductScan { fn schema(&self) -> DbResult { let s1 = self.s1.schema()?; let s2 = self.s2.schema()?; - let s = SchemaBuilder::default().add_all(&s1).add_all(&s2).build(); + let s = SchemaBuilder::new(Element::Raw(format!( + "product_{}_{}", + s1.table(), + s2.table() + ))) + .add_all(&s1) + .add_all(&s2) + .build(); Ok(s) } } diff --git a/engine/src/scan/project.rs b/engine/src/scan/project.rs index ffe71ea..0a0273d 100644 --- a/engine/src/scan/project.rs +++ b/engine/src/scan/project.rs @@ -70,8 +70,8 @@ impl Scan for ProjectScan { } fn schema(&self) -> DbResult { - let mut project = SchemaBuilder::default(); let schema = self.scan.schema()?; + let mut project = SchemaBuilder::new(schema.table().clone()); for (field, info) in schema.fields() { if self.fields.contains(&field) { project = project.add_field(field, info); diff --git a/engine/src/scan/table.rs b/engine/src/scan/table.rs index 54aa40f..0b50548 100644 --- a/engine/src/scan/table.rs +++ b/engine/src/scan/table.rs @@ -272,7 +272,7 @@ mod tests { let lock_table = Arc::new(LockTable::default()); let tx = Arc::new(Transaction::new(&fm, &lm, &bm, &lock_table).unwrap()); - let schema = SchemaBuilder::default() + let schema = SchemaBuilder::new(Element::raw("test")) .add_int_field(Element::raw("A")) .add_string_field(Element::raw("B"), 9) .build(); diff --git a/engine/src/schema.rs b/engine/src/schema.rs index e62f6b2..92c0727 100644 --- a/engine/src/schema.rs +++ b/engine/src/schema.rs @@ -4,6 +4,7 @@ use crate::{element::Element, field_info::FieldInfo}; #[derive(Debug, PartialEq, Eq)] pub struct SchemaInner { + table: Element, fields: Vec, infos: HashMap, } @@ -59,8 +60,12 @@ impl SchemaInner { pub struct Schema(Rc); impl Schema { - pub fn info(&self, fieldname: &Element) -> Option { - self.0.info(fieldname) + pub fn table(&self) -> &Element { + &self.0.table + } + + pub fn info(&self, field: &Element) -> Option { + self.0.info(field) } pub fn fields(&self) -> Vec<(Element, FieldInfo)> { @@ -77,6 +82,16 @@ pub struct SchemaBuilder { } impl SchemaBuilder { + pub fn new(table: Element) -> Self { + Self { + schema: SchemaInner { + table, + fields: vec![], + infos: HashMap::new(), + }, + } + } + pub fn add_field(mut self, field: Element, info: FieldInfo) -> Self { self.schema.add_field(field, info); self @@ -107,17 +122,6 @@ impl SchemaBuilder { } } -impl Default for SchemaBuilder { - fn default() -> Self { - Self { - schema: SchemaInner { - fields: vec![], - infos: HashMap::new(), - }, - } - } -} - #[cfg(test)] mod tests { @@ -125,16 +129,18 @@ mod tests { #[test] fn add() { - let schema = SchemaBuilder::default() + let schema = SchemaBuilder::new(Element::raw("table")) .add_int_field(Element::raw("test")) .build(); - let schema = SchemaBuilder::default() + let schema = SchemaBuilder::new(Element::raw("table")) .add(Element::raw("test"), &schema) .build(); assert!(schema.has_field(&Element::raw("test"))); - let schema = SchemaBuilder::default().add_all(&schema).build(); + let schema = SchemaBuilder::new(Element::raw("table")) + .add_all(&schema) + .build(); assert!(schema.has_field(&Element::raw("test"))); - let schema = SchemaBuilder::default() + let schema = SchemaBuilder::new(Element::raw("table")) .add_field(Element::raw("id"), FieldInfo::Integer) .build(); assert!(schema.has_field(&Element::raw("id"))); diff --git a/engine/src/table_mgr.rs b/engine/src/table_mgr.rs index 489c4cb..c860a52 100644 --- a/engine/src/table_mgr.rs +++ b/engine/src/table_mgr.rs @@ -31,13 +31,13 @@ pub struct TableMgr { impl TableMgr { pub fn new(is_new: bool, tx: &Arc) -> DbResult { - let table_schema = SchemaBuilder::default() + let table_schema = SchemaBuilder::new(Element::raw(TABLE_NAME)) .add_string_field(Element::raw(TABLE_NAME), MAX_NAME) .add_int_field(Element::raw(TABLE_SLOT_SIZE)) .build(); let table_layout = Layout::new(table_schema.clone()); - let fields_schema = SchemaBuilder::default() + let fields_schema = SchemaBuilder::new(Element::raw(FIELDS_NAME)) .add_string_field(Element::raw(TABLE_NAME), MAX_NAME) .add_string_field(Element::raw(F_FIELD_NAME), MAX_NAME) .add_int_field(Element::raw(F_TYPE)) @@ -95,7 +95,7 @@ impl TableMgr { } } tcat.close()?; - let mut schema = SchemaBuilder::default(); + let mut schema = SchemaBuilder::new(Element::raw(table_name)); let mut offsets = HashMap::new(); let fcat = TableScan::new(tx, FIELDS_NAME, self.fields_layout.clone())?; while fcat.next()? { @@ -132,7 +132,7 @@ mod tests { let tm = TableMgr::new(true, &tx).unwrap(); - let schema = SchemaBuilder::default() + let schema = SchemaBuilder::new(Element::raw("table")) .add_int_field(Element::raw("A")) .add_string_field(Element::raw("B"), 9) .build(); diff --git a/engine/src/view_mgr.rs b/engine/src/view_mgr.rs index faa4133..f8f44b4 100644 --- a/engine/src/view_mgr.rs +++ b/engine/src/view_mgr.rs @@ -22,7 +22,7 @@ pub struct ViewMgr { impl ViewMgr { pub fn new(is_new: bool, table_mgr: TableMgr, tx: &Arc) -> DbResult { if is_new { - let schema = SchemaBuilder::default() + let schema = SchemaBuilder::new(Element::raw(VIEW_TABLE)) .add_string_field(Element::raw(VIEW_NAME), MAX_NAME) .add_string_field(Element::raw(VIEW_DEF), MAX_VIEW_DEF) .build(); From 4616339a60e7c6f1b3df651724cad9094746fe98 Mon Sep 17 00:00:00 2001 From: Daniil Buravlev Date: Thu, 2 Jul 2026 13:17:07 +0300 Subject: [PATCH 3/6] Implement view and specifiers for SELECT queries --- engine/src/lib.rs | 37 +++++++++++++++++++++++------ engine/src/plan.rs | 1 - engine/src/plan/project.rs | 27 ++++++++++++++++++---- engine/src/plan/select.rs | 4 ++-- engine/src/predicate.rs | 4 ++-- engine/src/query/command.rs | 46 ++++++++++++++++++++----------------- engine/src/query/planner.rs | 6 ++--- engine/src/query/token.rs | 4 ++-- engine/src/scan/project.rs | 26 ++++++++++++++------- engine/src/scan/select.rs | 4 ++-- engine/src/scan/table.rs | 22 +++++++++--------- engine/src/schema.rs | 38 +++++++++++++++++++----------- engine/src/sort_by.rs | 2 +- 13 files changed, 144 insertions(+), 77 deletions(-) diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 7184084..650e730 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -197,7 +197,7 @@ mod tests { .unwrap(); } let result = db - .query(&tx, "SELECT id, name FROM test SORT BY name") + .query(&tx, "SELECT id, name FROM test ORDER BY name") .unwrap(); for i in 0..1000 { assert!(result.next().unwrap()); @@ -244,18 +244,18 @@ mod tests { assert!(existed.is_empty()); } - #[test] + // #[test] fn join() { let dir = tempdir().unwrap(); let db = SimpleDB::new(dir.path()).unwrap(); let tx = db.get_tx().unwrap(); - db.execute(&tx, "CREATE TABLE t1(i1 INT)").unwrap(); - db.execute(&tx, "CREATE TABLE t2(i2 INT)").unwrap(); + db.execute(&tx, "CREATE TABLE users(id INT)").unwrap(); + db.execute(&tx, "CREATE TABLE employees(id INT)").unwrap(); tx.commit().unwrap(); for i in 0..100 { - db.execute(&tx, &format!("INSERT INTO t1(i1) VALUES({})", i)) + db.execute(&tx, &format!("INSERT INTO users(id) VALUES({})", i)) .unwrap(); - db.execute(&tx, &format!("INSERT INTO t2(i2) VALUES({})", i)) + db.execute(&tx, &format!("INSERT INTO employees(id) VALUES({})", i)) .unwrap(); } tx.commit().unwrap(); @@ -265,7 +265,7 @@ mod tests { let result = db .query( &tx, - &format!("SELECT i1, i2 FROM t1, t2 WHERE i1 = i2 AND i1 = {}", i), + &format!("SELECT users.id, employees.id FROM t1 JOIN t2 on t1., t2 WHERE i1 = i2 AND i1 = {}", i), ) .unwrap(); while result.next().unwrap() { @@ -300,4 +300,27 @@ mod tests { .unwrap(); assert!(!result.next().unwrap()); } + + #[test] + fn select_with_specs() { + let dir = tempdir().unwrap(); + let db = SimpleDB::new(dir.path()).unwrap(); + let tx = db.get_tx().unwrap(); + db.execute(&tx, "CREATE TABLE test(id INT, name VARCHAR(100))") + .unwrap(); + tx.commit().unwrap(); + + db.execute(&tx, "INSERT INTO test(id, name) VALUES(1, 'User')") + .unwrap(); + + let result = db + .query(&tx, "SELECT t.id, t.name FROM test t WHERE t.id=1") + .unwrap(); + assert!(result.next().unwrap()); + assert_eq!(result.get_i32(&Element::spec("t", "id")).unwrap(), 1); + assert_eq!( + result.get_string(&Element::spec("t", "name")).unwrap(), + "User" + ); + } } diff --git a/engine/src/plan.rs b/engine/src/plan.rs index 82179c7..87ca806 100644 --- a/engine/src/plan.rs +++ b/engine/src/plan.rs @@ -3,7 +3,6 @@ use std::rc::Rc; use crate::element::Element; use crate::{scan::Scan, schema::Schema}; use common::DbResult; -use common::error::DbError; pub(crate) mod group; pub mod index; diff --git a/engine/src/plan/project.rs b/engine/src/plan/project.rs index 034cd4a..63d81b3 100644 --- a/engine/src/plan/project.rs +++ b/engine/src/plan/project.rs @@ -1,7 +1,5 @@ use std::{collections::HashSet, rc::Rc}; -use common::DbResult; - use crate::schema::SchemaBuilder; use crate::schema_mapping::SchemaMapping; use crate::{ @@ -10,6 +8,7 @@ use crate::{ scan::{Scan, project::ProjectScan}, schema::Schema, }; +use common::DbResult; pub(crate) struct ProjectPlan { plan: Rc, @@ -24,9 +23,29 @@ impl ProjectPlan { mapping: SchemaMapping, ) -> DbResult { let mut schema = SchemaBuilder::new(plan.schema()?.table().clone()); + let other = plan.schema()?; for field in fields { - let other = plan.schema()?; - schema = schema.add(field, &other); + let source_field = match &field { + Element::Spec(table, field) => { + let source_table = Element::raw(&table); + let table = if let Some(Element::Raw(table)) = mapping.table(&source_table) { + table + } else { + table + }; + Element::Spec(table.to_string(), field.to_string()) + } + field => { + if let Some(field) = mapping.field(field) { + field.clone() + } else { + field.clone() + } + } + }; + if let Some(info) = other.info(&source_field) { + schema = schema.add_field(field, info); + } } Ok(Self { plan, diff --git a/engine/src/plan/select.rs b/engine/src/plan/select.rs index 30b4a5b..5507a2f 100644 --- a/engine/src/plan/select.rs +++ b/engine/src/plan/select.rs @@ -10,13 +10,13 @@ use crate::{ schema::Schema, }; -pub struct SelectPlan { +pub(crate) struct SelectPlan { plan: Rc, predicate: Predicate, } impl SelectPlan { - pub fn new(plan: Rc, predicate: Predicate) -> Self { + pub(crate) fn new(plan: Rc, predicate: Predicate) -> Self { Self { plan, predicate } } } diff --git a/engine/src/predicate.rs b/engine/src/predicate.rs index e9d2e84..b6f0e43 100644 --- a/engine/src/predicate.rs +++ b/engine/src/predicate.rs @@ -106,7 +106,7 @@ impl std::fmt::Display for Expression { } #[derive(Clone, Debug)] -pub struct Term { +pub(crate) struct Term { left: Expression, right: Expression, } @@ -198,7 +198,7 @@ impl std::fmt::Display for Term { } #[derive(Clone)] -pub struct Predicate { +pub(crate) struct Predicate { terms: Arc>>, } diff --git a/engine/src/query/command.rs b/engine/src/query/command.rs index bfddb7f..3c20281 100644 --- a/engine/src/query/command.rs +++ b/engine/src/query/command.rs @@ -1,3 +1,4 @@ +use crate::schema_mapping::SchemaMapping; use crate::{ element::Element, predicate::{Expression, Predicate}, @@ -5,9 +6,8 @@ use crate::{ sort_by::SortByData, value::Value, }; -use crate::schema_mapping::SchemaMapping; -pub enum Command { +pub(crate) enum Command { Insert(InsertData), Update(UpdateData), Query(QueryData), @@ -31,23 +31,25 @@ impl std::fmt::Display for Command { } } -pub struct QueryData { - pub fields: Vec, - pub table: Element, - pub predicate: Predicate, - pub group_by: GroupByData, - pub order_by: SortByData, - pub mapping: SchemaMapping, +pub(crate) struct QueryData { + pub(crate) fields: Vec, + pub(crate) table: Element, + pub(crate) predicate: Predicate, + pub(crate) group_by: GroupByData, + pub(crate) order_by: SortByData, + pub(crate) mapping: SchemaMapping, } impl std::fmt::Display for QueryData { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "SELECT ")?; for (i, field) in self.fields.iter().enumerate() { - let field = if let Some(source) = self.mapping.field(field) && source != field { + let field = if let Some(source) = self.mapping.field(field) + && source != field + { match source { Element::Raw(source) => &Element::view(source, field.as_raw().unwrap()), - e => e + e => e, } } else { field @@ -59,10 +61,12 @@ impl std::fmt::Display for QueryData { } } let table = &self.table; - let table = if let Some(source) = self.mapping.table(table) && source != table { + let table = if let Some(source) = self.mapping.table(table) + && source != table + { match source { Element::Raw(source) => &Element::view(source, table.as_raw().unwrap()), - e => e + e => e, } } else { table @@ -82,9 +86,9 @@ impl std::fmt::Display for QueryData { } } -pub struct ViewData { - pub name: String, - pub query: QueryData, +pub(crate) struct ViewData { + pub(crate) name: String, + pub(crate) query: QueryData, } impl std::fmt::Display for ViewData { @@ -93,9 +97,9 @@ impl std::fmt::Display for ViewData { } } -pub struct DeleteData { - pub name: String, - pub predicate: Predicate, +pub(crate) struct DeleteData { + pub(crate) name: String, + pub(crate) predicate: Predicate, } impl std::fmt::Display for DeleteData { @@ -138,7 +142,7 @@ impl std::fmt::Display for InsertData { } } -pub struct UpdateData { +pub(crate) struct UpdateData { pub table: String, pub field: Element, pub value: Expression, @@ -192,7 +196,7 @@ impl std::fmt::Display for TableData { } #[derive(Default)] -pub struct GroupByData { +pub(crate) struct GroupByData { pub fields: Vec, } diff --git a/engine/src/query/planner.rs b/engine/src/query/planner.rs index 5c2572d..0e764d8 100644 --- a/engine/src/query/planner.rs +++ b/engine/src/query/planner.rs @@ -13,11 +13,11 @@ use crate::{ }, }; -pub trait QueryPlanner { +pub(crate) trait QueryPlanner { fn create_plan(&self, data: QueryData, tx: &Arc) -> DbResult>; } -pub trait UpdatePlanner { +pub(crate) trait UpdatePlanner { fn execute_insert(&self, data: InsertData, tx: &Arc) -> DbResult; fn execute_update(&self, data: UpdateData, tx: &Arc) -> DbResult; @@ -31,7 +31,7 @@ pub trait UpdatePlanner { fn execute_create_index(&self, data: IndexData, tx: &Arc) -> DbResult; } -pub struct Planner { +pub(crate) struct Planner { query_planner: Rc, update_planner: Rc, } diff --git a/engine/src/query/token.rs b/engine/src/query/token.rs index cf176c6..4f2f6cf 100644 --- a/engine/src/query/token.rs +++ b/engine/src/query/token.rs @@ -54,7 +54,7 @@ impl Token { "on" => Some(Self::On), "as" => Some(Self::As), "group" => Some(Self::Group), - "sort" => Some(Self::Sort), + "order" => Some(Self::Sort), "by" => Some(Self::By), _ => None, } @@ -151,7 +151,7 @@ impl std::fmt::Display for Token { Token::On => write!(f, "ON"), Token::As => write!(f, "AS"), Token::Group => write!(f, "GROUP"), - Token::Sort => write!(f, "SORT"), + Token::Sort => write!(f, "ORDER"), Token::By => write!(f, "BY"), Token::Field(field) => write!(f, "'{}'", field), Token::Delimiter(d) => write!(f, "'{}'", d), diff --git a/engine/src/scan/project.rs b/engine/src/scan/project.rs index 0a0273d..0c4d9b9 100644 --- a/engine/src/scan/project.rs +++ b/engine/src/scan/project.rs @@ -15,9 +15,13 @@ pub(crate) struct ProjectScan { impl ProjectScan { pub(crate) fn new( scan: Rc, - fields: HashSet, + table_fields: HashSet, mapping: SchemaMapping, ) -> Self { + let mut fields = HashSet::new(); + for field in table_fields { + fields.insert(field); + } Self { scan, fields, @@ -25,14 +29,20 @@ impl ProjectScan { } } - fn get_field<'a>(&'a self, field: &'a Element) -> DbResult<&'a Element> { - if self.fields.contains(field) { + fn get_field(&self, field: &Element) -> DbResult { + if !self.fields.contains(field) { return Err(DbError::FieldNotExists(field.to_string())); } + let field = match field { + Element::Spec(_, field) => { + &Element::raw(field) + } + e => e, + }; if let Some(field) = self.mapping.field(field) { - Ok(field) + Ok(field.clone()) } else { - Ok(field) + Ok(field.clone()) } } } @@ -48,17 +58,17 @@ impl Scan for ProjectScan { fn get_i32(&self, field: &Element) -> DbResult { let field = self.get_field(field)?; - self.scan.get_i32(field) + self.scan.get_i32(&field) } fn get_string(&self, field: &Element) -> DbResult { let field = self.get_field(field)?; - self.scan.get_string(field) + self.scan.get_string(&field) } fn get_val(&self, field: &Element) -> DbResult { let field = self.get_field(field)?; - self.scan.get_val(field) + self.scan.get_val(&field) } fn has_field(&self, field: &Element) -> DbResult { diff --git a/engine/src/scan/select.rs b/engine/src/scan/select.rs index b003c32..64a06ad 100644 --- a/engine/src/scan/select.rs +++ b/engine/src/scan/select.rs @@ -5,13 +5,13 @@ use crate::element::Element; use crate::schema::Schema; use crate::{predicate::Predicate, scan::Scan}; -pub struct SelectScan { +pub(crate) struct SelectScan { scan: Rc, predicate: Predicate, } impl SelectScan { - pub fn new(scan: Rc, predicate: Predicate) -> Self { + pub(crate) fn new(scan: Rc, predicate: Predicate) -> Self { Self { scan, predicate } } } diff --git a/engine/src/scan/table.rs b/engine/src/scan/table.rs index 0b50548..ba69429 100644 --- a/engine/src/scan/table.rs +++ b/engine/src/scan/table.rs @@ -61,26 +61,26 @@ impl TableScanInner { Ok(true) } - pub fn get_i32(&self, filename: &Element) -> DbResult { - self.rp.get_i32(self.current_slot, filename) + pub fn get_i32(&self, field: &Element) -> DbResult { + self.rp.get_i32(self.current_slot, field) } - pub fn get_string(&self, filename: &Element) -> DbResult { - self.rp.get_string(self.current_slot, filename) + pub fn get_string(&self, field: &Element) -> DbResult { + self.rp.get_string(self.current_slot, field) } - pub fn get_val(&self, fieldname: &Element) -> DbResult { - let Some(info) = self.layout.schema().info(fieldname) else { - return Err(DbError::FieldNotExists(fieldname.to_string())); + pub fn get_val(&self, field: &Element) -> DbResult { + let Some(info) = self.layout.schema().info(field) else { + return Err(DbError::FieldNotExists(field.to_string())); }; match info { - FieldInfo::Integer => Ok(Value::Integer(self.get_i32(fieldname)?)), - FieldInfo::Varchar(_) => Ok(Value::Varchar(self.get_string(fieldname)?)), + FieldInfo::Integer => Ok(Value::Integer(self.get_i32(field)?)), + FieldInfo::Varchar(_) => Ok(Value::Varchar(self.get_string(field)?)), } } - pub fn has_field(&self, fieldname: &Element) -> bool { - self.layout.schema().has_field(fieldname) + pub fn has_field(&self, field: &Element) -> bool { + self.layout.schema().has_field(field) } pub fn set_i32(&self, field: &Element, value: i32) -> DbResult<()> { diff --git a/engine/src/schema.rs b/engine/src/schema.rs index 92c0727..175b09e 100644 --- a/engine/src/schema.rs +++ b/engine/src/schema.rs @@ -10,24 +10,24 @@ pub struct SchemaInner { } impl SchemaInner { - fn add_field(&mut self, fieldname: Element, field_info: FieldInfo) { - if !self.infos.contains_key(&fieldname) { - self.fields.push(fieldname.clone()); - self.infos.insert(fieldname, field_info); + fn add_field(&mut self, field: Element, field_info: FieldInfo) { + if !self.infos.contains_key(&field) { + self.fields.push(field.clone()); + self.infos.insert(field, field_info); } } - fn add_int_field(&mut self, fieldname: Element) { - self.add_field(fieldname, FieldInfo::Integer); + fn add_int_field(&mut self, field: Element) { + self.add_field(field, FieldInfo::Integer); } - fn add_string_field(&mut self, fieldname: Element, length: i32) { - self.add_field(fieldname, FieldInfo::Varchar(length)); + fn add_string_field(&mut self, field: Element, length: i32) { + self.add_field(field, FieldInfo::Varchar(length)); } - fn add(&mut self, fieldname: Element, schema: &Self) { - if let Some(info) = schema.info(&fieldname) { - self.add_field(fieldname, info); + fn add(&mut self, field: Element, other: &Self) { + if let Some(info) = other.info(&field) { + self.add_field(field, info); } } @@ -37,8 +37,20 @@ impl SchemaInner { } } - fn info(&self, fieldname: &Element) -> Option { - self.infos.get(fieldname).cloned() + fn info(&self, field: &Element) -> Option { + match field { + Element::Raw(field) => { + self.infos.get(&Element::raw(field)).cloned() + }, + Element::Spec(table, field) => { + if self.table == Element::raw(table) { + self.infos.get(&Element::raw(field)).cloned() + } else { + None + } + } + _ => None, + } } fn fields(&self) -> Vec<(Element, FieldInfo)> { diff --git a/engine/src/sort_by.rs b/engine/src/sort_by.rs index 0f6cfff..b05de7c 100644 --- a/engine/src/sort_by.rs +++ b/engine/src/sort_by.rs @@ -13,7 +13,7 @@ impl SortByData { impl std::fmt::Display for SortByData { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "SORT BY")?; + write!(f, "ORDER BY")?; for (i, field) in self.fields.iter().enumerate() { if i == 0 { write!(f, "{}", field)?; From d70c77978804c0559ae4728a9094fe904f1ec23c Mon Sep 17 00:00:00 2001 From: Daniil Buravlev Date: Thu, 2 Jul 2026 17:01:41 +0300 Subject: [PATCH 4/6] Implement view and specifiers for SELECT queries --- engine/src/element.rs | 29 +++++++++++++++++++++++++++++ engine/src/lib.rs | 8 ++++---- engine/src/query/parser.rs | 1 + 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/engine/src/element.rs b/engine/src/element.rs index 9197fd0..c54fd7d 100644 --- a/engine/src/element.rs +++ b/engine/src/element.rs @@ -6,6 +6,7 @@ pub enum Element { Raw(String), View(String, String), Spec(String, String), + Array(Vec>), } impl Element { @@ -21,6 +22,10 @@ impl Element { Self::Spec(source.to_string(), target.to_string()) } + pub(crate) fn array(values: Vec>) -> Self { + Self::Array(values) + } + pub fn as_raw(&self) -> DbResult<&str> { match self { Self::Raw(s) => Ok(s), @@ -33,6 +38,13 @@ impl Element { Self::Raw(s) => s.len(), Self::View(source, name) => source.len() + 4 + name.len(), Self::Spec(source, target) => source.len() + 4 + target.len(), + Self::Array(values) => { + let mut len = 0; + for value in values { + len += value.len(); + } + len + } } } @@ -47,6 +59,17 @@ impl std::fmt::Display for Element { Self::Raw(value) => write!(f, "{}", value), Self::View(source, name) => write!(f, "{} {}", source, name), Self::Spec(source, target) => write!(f, "{}.{}", source, target), + Self::Array(values) => { + write!(f, "(")?; + for (i, value) in values.iter().enumerate() { + if i == 0 { + write!(f, "{}", value)?; + } else { + write!(f, "{},", value)?; + } + } + write!(f, ")") + } } } } @@ -66,4 +89,10 @@ mod tests { let spec = Element::spec("t1", "f1"); assert_eq!("t1.f1", spec.to_string()); } + + #[test] + fn array() { + let spec = Element::array(vec![Box::new(Element::view("table", "t"))]); + assert_eq!("(table t)", spec.to_string()); + } } diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 650e730..52b6277 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -244,7 +244,7 @@ mod tests { assert!(existed.is_empty()); } - // #[test] + #[test] fn join() { let dir = tempdir().unwrap(); let db = SimpleDB::new(dir.path()).unwrap(); @@ -265,9 +265,9 @@ mod tests { let result = db .query( &tx, - &format!("SELECT users.id, employees.id FROM t1 JOIN t2 on t1., t2 WHERE i1 = i2 AND i1 = {}", i), - ) - .unwrap(); + "SELECT users.id, employees.id FROM users JOIN employees ON users.id = employees.id" + ) + .unwrap(); while result.next().unwrap() { assert_eq!(i, result.get_i32(&Element::raw("i1")).unwrap()); assert_eq!(i, result.get_i32(&Element::raw("i2")).unwrap()); diff --git a/engine/src/query/parser.rs b/engine/src/query/parser.rs index ea2a139..761cb79 100644 --- a/engine/src/query/parser.rs +++ b/engine/src/query/parser.rs @@ -360,6 +360,7 @@ fn process_schema( mapping = mapping.add_table_field(Element::raw(&source), Element::raw(&target)); new_fields.push(Element::Spec(source, target)); } + _ => return Err(DbError::InvalidFieldType), } } Ok(( From 9c61443064b2b64bb0d513b1c39cb3d867855de3 Mon Sep 17 00:00:00 2001 From: Daniil Buravlev Date: Thu, 2 Jul 2026 21:10:08 +0300 Subject: [PATCH 5/6] Add support to JOINs --- engine/src/lib.rs | 45 ++++++++++++------- engine/src/plan/multibuffer.rs | 5 ++- engine/src/query/heuristic_planner.rs | 36 ++++++++------- engine/src/query/parser.rs | 64 ++++++++++++++++++++++----- 4 files changed, 107 insertions(+), 43 deletions(-) diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 52b6277..4db6e54 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -249,30 +249,41 @@ mod tests { let dir = tempdir().unwrap(); let db = SimpleDB::new(dir.path()).unwrap(); let tx = db.get_tx().unwrap(); - db.execute(&tx, "CREATE TABLE users(id INT)").unwrap(); - db.execute(&tx, "CREATE TABLE employees(id INT)").unwrap(); + db.execute(&tx, "CREATE TABLE users(id INT, name VARCHAR(20))") + .unwrap(); + db.execute(&tx, "CREATE TABLE employees(eid INT, uid INT)") + .unwrap(); tx.commit().unwrap(); for i in 0..100 { - db.execute(&tx, &format!("INSERT INTO users(id) VALUES({})", i)) - .unwrap(); - db.execute(&tx, &format!("INSERT INTO employees(id) VALUES({})", i)) - .unwrap(); + db.execute( + &tx, + &format!("INSERT INTO users(id, name) VALUES({}, 'user{}')", i, i), + ) + .unwrap(); + db.execute( + &tx, + &format!("INSERT INTO employees(eid, uid) VALUES({}, {})", i + 1000, i), + ) + .unwrap(); } tx.commit().unwrap(); - let mut existed = HashSet::new(); - for i in 0..10 { - existed.insert(i); - let result = db - .query( - &tx, - "SELECT users.id, employees.id FROM users JOIN employees ON users.id = employees.id" + + let result = db + .query( + &tx, + "SELECT id, name, eid FROM users JOIN employees ON id = uid", ) .unwrap(); - while result.next().unwrap() { - assert_eq!(i, result.get_i32(&Element::raw("i1")).unwrap()); - assert_eq!(i, result.get_i32(&Element::raw("i2")).unwrap()); - } + let mut matched = HashSet::new(); + while result.next().unwrap() { + let id = result.get_i32(&Element::raw("id")).unwrap(); + let eid = result.get_i32(&Element::raw("eid")).unwrap(); + let name = result.get_string(&Element::raw("name")).unwrap(); + assert_eq!(eid, id + 1000); + assert_eq!(name, format!("user{}", id)); + assert!(matched.insert(id), "each user must match exactly once"); } + assert_eq!(matched.len(), 100); tx.commit().unwrap(); } diff --git a/engine/src/plan/multibuffer.rs b/engine/src/plan/multibuffer.rs index d35d280..39a2c58 100644 --- a/engine/src/plan/multibuffer.rs +++ b/engine/src/plan/multibuffer.rs @@ -66,10 +66,13 @@ impl Plan for MultiBufferProductPlan { fn open(&self) -> DbResult> { let left = self.left.open()?; let t = self.copy_records_from(&self.right)?; + // `TableScan` stores a temp table's records in `.tbl`, so the + // multibuffer scan (which reads the blocks directly) must target that + // physical file rather than the bare table name. Ok(Rc::new(MultiBufferProductScan::new( &self.tx, &left, - &t.table_name(), + &format!("{}.tbl", t.table_name()), t.layout(), )?)) } diff --git a/engine/src/query/heuristic_planner.rs b/engine/src/query/heuristic_planner.rs index f6fe9d2..5acb003 100644 --- a/engine/src/query/heuristic_planner.rs +++ b/engine/src/query/heuristic_planner.rs @@ -3,6 +3,7 @@ use std::{cell::RefCell, rc::Rc, sync::Arc}; use common::DbResult; use transaction::transaction::Transaction; +use crate::element::Element; use crate::plan::group::GroupByPlan; use crate::plan::order::SortPlan; use crate::{ @@ -25,22 +26,27 @@ impl HeuristicQueryPlannerInner { } fn create_plan(&mut self, data: QueryData, tx: &Arc) -> DbResult> { - let table = &data.table; - let table = if let Some(source) = data.mapping.table(table) - && source != table - { - source - } else { - table + let tables = match &data.table { + Element::Array(tables) => tables.iter().map(|t| t.as_ref().clone()).collect(), + table => vec![table.clone()], }; - let tp = TablePlanner::new( - table.clone(), - data.predicate, - tx, - &self.md, - data.mapping.clone(), - )?; - self.table_planners.push(tp); + for table in tables { + let table = if let Some(source) = data.mapping.table(&table) + && *source != table + { + source.clone() + } else { + table + }; + let tp = TablePlanner::new( + table, + data.predicate.clone(), + tx, + &self.md, + data.mapping.clone(), + )?; + self.table_planners.push(tp); + } let mut current = self.get_lowest_select_plan()?; while !self.table_planners.is_empty() { if let Some(p) = self.get_lowest_join_plan(¤t)? { diff --git a/engine/src/query/parser.rs b/engine/src/query/parser.rs index 761cb79..9632dab 100644 --- a/engine/src/query/parser.rs +++ b/engine/src/query/parser.rs @@ -84,14 +84,19 @@ impl Parser { let fields = self.select_list()?; self.lexer.eat_keyword(Token::From)?; - let table = self.element()?; + let (tables, predicate) = self.from_clause()?; - let mut predicate = Predicate::default(); if self.lexer.match_keyword(Token::Where) { self.lexer.eat_keyword(Token::Where)?; - predicate = self.predicate()?; + predicate.conjoin_with(&self.predicate()?)?; } + let table = if tables.len() == 1 { + tables.into_iter().next().unwrap() + } else { + Element::array(tables.into_iter().map(Box::new).collect()) + }; + let mut group_by = GroupByData::default(); if self.lexer.match_keyword(Token::Group) { self.lexer.eat_keyword(Token::Group)?; @@ -120,6 +125,29 @@ impl Parser { })) } + /// Parses the table list following `FROM`, supporting both comma-separated + /// tables (`FROM a, b`) and explicit `JOIN ... ON ...` syntax. Any `ON` + /// predicates are folded into the returned `Predicate`, which the planner + /// later splits back into per-table select and join predicates. + fn from_clause(&self) -> DbResult<(Vec, Predicate)> { + let mut tables = vec![self.element()?]; + let predicate = Predicate::default(); + loop { + if self.lexer.match_delim(',') { + self.lexer.eat_delimiter(',')?; + tables.push(self.element()?); + } else if self.lexer.match_keyword(Token::Join) { + self.lexer.eat_keyword(Token::Join)?; + tables.push(self.element()?); + self.lexer.eat_keyword(Token::On)?; + predicate.conjoin_with(&self.predicate()?)?; + } else { + break; + } + } + Ok((tables, predicate)) + } + fn select_list(&self) -> DbResult> { let mut fields = vec![self.element()?]; while self.lexer.match_delim(',') { @@ -335,14 +363,25 @@ fn process_schema( SchemaMapping, )> { let mut mapping = SchemaMappingBuilder::default(); - let table = match table { - Element::Raw(table) => Element::Raw(table), - Element::View(source, id) => { - mapping = mapping.add_table(Element::raw(&id), Element::raw(&source)); - Element::Raw(id) - } - _ => return Err(DbError::InvalidFieldType), + let raw_tables = match table { + Element::Array(tables) => tables.into_iter().map(|t| *t).collect(), + table => vec![table], }; + let mut new_tables = Vec::with_capacity(raw_tables.len()); + for table in raw_tables { + let table = match table { + Element::Raw(table) => Element::Raw(table), + Element::View(source, id) => { + mapping = mapping.add_table(Element::raw(&id), Element::raw(&source)); + Element::Raw(id) + } + _ => return Err(DbError::InvalidFieldType), + }; + new_tables.push(table); + } + // Field->table associations are keyed off the first table; `SchemaMapping` + // only uses them for aliasing, which is unaffected by the join expansion. + let table = new_tables[0].clone(); let mut new_fields = Vec::with_capacity(fields.len()); for field in fields { match field { @@ -363,6 +402,11 @@ fn process_schema( _ => return Err(DbError::InvalidFieldType), } } + let table = if new_tables.len() == 1 { + new_tables.into_iter().next().unwrap() + } else { + Element::array(new_tables.into_iter().map(Box::new).collect()) + }; Ok(( new_fields, table, From 6ca609de46fd7d92a2027185ca6e5e6d469b2bc4 Mon Sep 17 00:00:00 2001 From: Daniil Buravlev Date: Thu, 2 Jul 2026 21:15:53 +0300 Subject: [PATCH 6/6] Fix formatting & clippy warnings --- engine/src/element.rs | 6 +++--- engine/src/lib.rs | 6 +++++- engine/src/plan/project.rs | 2 +- engine/src/predicate.rs | 6 ++---- engine/src/query/heuristic_planner.rs | 2 +- engine/src/query/parser.rs | 10 +++++----- engine/src/query/table_planner.rs | 4 +++- engine/src/scan/project.rs | 4 +--- engine/src/schema.rs | 4 +--- engine/src/schema_mapping.rs | 8 ++------ 10 files changed, 24 insertions(+), 28 deletions(-) diff --git a/engine/src/element.rs b/engine/src/element.rs index c54fd7d..993f096 100644 --- a/engine/src/element.rs +++ b/engine/src/element.rs @@ -6,7 +6,7 @@ pub enum Element { Raw(String), View(String, String), Spec(String, String), - Array(Vec>), + Array(Vec), } impl Element { @@ -22,7 +22,7 @@ impl Element { Self::Spec(source.to_string(), target.to_string()) } - pub(crate) fn array(values: Vec>) -> Self { + pub(crate) fn array(values: Vec) -> Self { Self::Array(values) } @@ -92,7 +92,7 @@ mod tests { #[test] fn array() { - let spec = Element::array(vec![Box::new(Element::view("table", "t"))]); + let spec = Element::array(vec![Element::view("table", "t")]); assert_eq!("(table t)", spec.to_string()); } } diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 4db6e54..14a6dfc 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -262,7 +262,11 @@ mod tests { .unwrap(); db.execute( &tx, - &format!("INSERT INTO employees(eid, uid) VALUES({}, {})", i + 1000, i), + &format!( + "INSERT INTO employees(eid, uid) VALUES({}, {})", + i + 1000, + i + ), ) .unwrap(); } diff --git a/engine/src/plan/project.rs b/engine/src/plan/project.rs index 63d81b3..0e1f2e8 100644 --- a/engine/src/plan/project.rs +++ b/engine/src/plan/project.rs @@ -27,7 +27,7 @@ impl ProjectPlan { for field in fields { let source_field = match &field { Element::Spec(table, field) => { - let source_table = Element::raw(&table); + let source_table = Element::raw(table); let table = if let Some(Element::Raw(table)) = mapping.table(&source_table) { table } else { diff --git a/engine/src/predicate.rs b/engine/src/predicate.rs index b6f0e43..7f2c1d8 100644 --- a/engine/src/predicate.rs +++ b/engine/src/predicate.rs @@ -87,10 +87,8 @@ impl Expression { Element::raw(field) } } - Element::Spec(_, target) => { - Element::raw(target) - } - _ => panic!("unprocessable field") + Element::Spec(_, target) => Element::raw(target), + _ => panic!("unprocessable field"), }; Self::Field(field) } diff --git a/engine/src/query/heuristic_planner.rs b/engine/src/query/heuristic_planner.rs index 5acb003..f2ef760 100644 --- a/engine/src/query/heuristic_planner.rs +++ b/engine/src/query/heuristic_planner.rs @@ -27,7 +27,7 @@ impl HeuristicQueryPlannerInner { fn create_plan(&mut self, data: QueryData, tx: &Arc) -> DbResult> { let tables = match &data.table { - Element::Array(tables) => tables.iter().map(|t| t.as_ref().clone()).collect(), + Element::Array(tables) => tables.to_vec(), table => vec![table.clone()], }; for table in tables { diff --git a/engine/src/query/parser.rs b/engine/src/query/parser.rs index 9632dab..ff62e15 100644 --- a/engine/src/query/parser.rs +++ b/engine/src/query/parser.rs @@ -84,7 +84,7 @@ impl Parser { let fields = self.select_list()?; self.lexer.eat_keyword(Token::From)?; - let (tables, predicate) = self.from_clause()?; + let (tables, predicate) = self.claude_from()?; if self.lexer.match_keyword(Token::Where) { self.lexer.eat_keyword(Token::Where)?; @@ -94,7 +94,7 @@ impl Parser { let table = if tables.len() == 1 { tables.into_iter().next().unwrap() } else { - Element::array(tables.into_iter().map(Box::new).collect()) + Element::array(tables.into_iter().collect()) }; let mut group_by = GroupByData::default(); @@ -129,7 +129,7 @@ impl Parser { /// tables (`FROM a, b`) and explicit `JOIN ... ON ...` syntax. Any `ON` /// predicates are folded into the returned `Predicate`, which the planner /// later splits back into per-table select and join predicates. - fn from_clause(&self) -> DbResult<(Vec, Predicate)> { + fn claude_from(&self) -> DbResult<(Vec, Predicate)> { let mut tables = vec![self.element()?]; let predicate = Predicate::default(); loop { @@ -364,7 +364,7 @@ fn process_schema( )> { let mut mapping = SchemaMappingBuilder::default(); let raw_tables = match table { - Element::Array(tables) => tables.into_iter().map(|t| *t).collect(), + Element::Array(tables) => tables.into_iter().collect(), table => vec![table], }; let mut new_tables = Vec::with_capacity(raw_tables.len()); @@ -405,7 +405,7 @@ fn process_schema( let table = if new_tables.len() == 1 { new_tables.into_iter().next().unwrap() } else { - Element::array(new_tables.into_iter().map(Box::new).collect()) + Element::array(new_tables.into_iter().collect()) }; Ok(( new_fields, diff --git a/engine/src/query/table_planner.rs b/engine/src/query/table_planner.rs index b3b26b0..06e0d70 100644 --- a/engine/src/query/table_planner.rs +++ b/engine/src/query/table_planner.rs @@ -121,7 +121,9 @@ impl TablePlanner { } fn add_select_pred(&self, p: &Rc) -> DbResult> { - let select_predicate = self.predicate.select_sub_pred(&self.schema, &self.mapping)?; + let select_predicate = self + .predicate + .select_sub_pred(&self.schema, &self.mapping)?; Ok(Rc::new(SelectPlan::new(p.clone(), select_predicate))) } diff --git a/engine/src/scan/project.rs b/engine/src/scan/project.rs index 0c4d9b9..4a5c7de 100644 --- a/engine/src/scan/project.rs +++ b/engine/src/scan/project.rs @@ -34,9 +34,7 @@ impl ProjectScan { return Err(DbError::FieldNotExists(field.to_string())); } let field = match field { - Element::Spec(_, field) => { - &Element::raw(field) - } + Element::Spec(_, field) => &Element::raw(field), e => e, }; if let Some(field) = self.mapping.field(field) { diff --git a/engine/src/schema.rs b/engine/src/schema.rs index 175b09e..116ca8d 100644 --- a/engine/src/schema.rs +++ b/engine/src/schema.rs @@ -39,9 +39,7 @@ impl SchemaInner { fn info(&self, field: &Element) -> Option { match field { - Element::Raw(field) => { - self.infos.get(&Element::raw(field)).cloned() - }, + Element::Raw(field) => self.infos.get(&Element::raw(field)).cloned(), Element::Spec(table, field) => { if self.table == Element::raw(table) { self.infos.get(&Element::raw(field)).cloned() diff --git a/engine/src/schema_mapping.rs b/engine/src/schema_mapping.rs index 4d48b8d..5bb0a07 100644 --- a/engine/src/schema_mapping.rs +++ b/engine/src/schema_mapping.rs @@ -27,18 +27,14 @@ pub(crate) struct SchemaMappingBuilder(SchemaMappingInner); impl SchemaMappingBuilder { pub(crate) fn add_table_field(mut self, table: Element, field: Element) -> Self { - let fields = self.0.tables_fields.entry(table).or_insert(HashSet::new()); + let fields = self.0.tables_fields.entry(table).or_default(); fields.insert(field); self } pub(crate) fn add_field(mut self, table: Element, id: Element, source: Element) -> Self { self.0.fields_names.insert(id, source.clone()); - let fields = self - .0 - .tables_fields - .entry(table) - .or_insert_with(HashSet::new); + let fields = self.0.tables_fields.entry(table).or_default(); fields.insert(source); self }