diff --git a/fuzz/fuzz_targets/sql_exec.rs b/fuzz/fuzz_targets/sql_exec.rs index 71be8fe3..af0342fc 100644 --- a/fuzz/fuzz_targets/sql_exec.rs +++ b/fuzz/fuzz_targets/sql_exec.rs @@ -34,7 +34,7 @@ #![no_main] use kite_sql::binder::{command_type, CommandType}; -use kite_sql::db::{prepare_all, DataBaseBuilder, Database, Statement}; +use kite_sql::db::{DataBaseBuilder, Database, Statement}; use kite_sql::storage::lmdb::LmdbStorage; use kite_sql::types::value::DataValue; use libfuzzer_sys::fuzz_target; @@ -135,7 +135,7 @@ fn strip_slt(script: &str) -> String { } fn run_one(db: &mut Database, sql: &str) { - let Ok(statements) = prepare_all(sql) else { + let Ok(statements) = db.prepare_all(sql) else { return; }; // `split(';')` leaves one statement per chunk except for `;` inside diff --git a/src/bin/shell.rs b/src/bin/shell.rs index 9742bb48..83945998 100644 --- a/src/bin/shell.rs +++ b/src/bin/shell.rs @@ -419,6 +419,7 @@ Transaction commands: } } + // GRCOV_EXCL_START #[cfg(test)] mod tests { use super::*; @@ -557,6 +558,7 @@ Transaction commands: ); } } + // GRCOV_EXCL_STOP } #[cfg(not(target_arch = "wasm32"))] diff --git a/src/binder/aggregate.rs b/src/binder/aggregate.rs index 5fb781ab..41e0aa12 100644 --- a/src/binder/aggregate.rs +++ b/src/binder/aggregate.rs @@ -273,8 +273,21 @@ impl> Binder<'_, '_, T, A> { return Ok(()); } - HavingOrderByValidator::new(&self.context.group_by_exprs, &self.context.agg_calls) - .visit(expr, arena) + Self::validate_having_orderby_with_outputs( + expr, + &self.context.agg_calls, + &self.context.group_by_exprs, + arena, + ) + } + + pub(crate) fn validate_having_orderby_with_outputs( + expr: ExprRef, + agg_calls: &[ExprRef], + group_by_exprs: &[ExprRef], + arena: &PlanArena<'_>, + ) -> Result<(), DatabaseError> { + HavingOrderByValidator::new(group_by_exprs, agg_calls).visit(expr, arena) } } @@ -405,6 +418,7 @@ impl ExprVisitorMut for AggregateOutputBinder<'_> { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::AggregateOutputBinder; @@ -576,3 +590,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/binder/create_table.rs b/src/binder/create_table.rs index 9de51118..31d22512 100644 --- a/src/binder/create_table.rs +++ b/src/binder/create_table.rs @@ -59,6 +59,7 @@ impl> Binder<'_, '_, T, A> { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::*; @@ -125,3 +126,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/binder/distinct.rs b/src/binder/distinct.rs index 19234fbb..305eeb8a 100644 --- a/src/binder/distinct.rs +++ b/src/binder/distinct.rs @@ -124,6 +124,7 @@ impl ExprVisitorMut for DistinctOutputBinder<'_> { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::DistinctOutputBinder; @@ -213,3 +214,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/binder/expr.rs b/src/binder/expr.rs index 053dbecd..29df7e87 100644 --- a/src/binder/expr.rs +++ b/src/binder/expr.rs @@ -302,9 +302,17 @@ impl<'a, T: Transaction, A: AsRef<[(usize, LogicalType)]>> Binder<'a, '_, T, A> let (sub_query, column, correlated) = self.bind_subquery_plan_with_output(Some(&left_ty), arena, build)?; - if !self.context.is_step(&QueryBindStep::Where) { + if !matches!( + self.context.step_now(), + QueryBindStep::Where | QueryBindStep::Project + ) { + return Err(DatabaseError::UnsupportedStmt( + "quantified subqueries require WHERE or SELECT list".into(), + )); + } + if correlated && self.context.is_step(&QueryBindStep::Project) { return Err(DatabaseError::UnsupportedStmt( - "quantified subqueries can only appear in `WHERE`".to_string(), + "correlated quantified subqueries in SELECT are not supported yet".into(), )); } diff --git a/src/binder/mod.rs b/src/binder/mod.rs index aae02188..18e13ef8 100644 --- a/src/binder/mod.rs +++ b/src/binder/mod.rs @@ -52,9 +52,7 @@ mod update; mod window; #[cfg(feature = "parser")] -pub(crate) use parser::parse_statement; -#[cfg(feature = "parser")] -pub use parser::{command_type, prepare_all, CommandType, Statement}; +pub use parser::{command_type, CommandType, Statement}; #[cfg(feature = "orm")] pub use select::{BindPlanFrom, BindPlanSelectList}; #[cfg(feature = "orm")] @@ -691,6 +689,7 @@ pub(crate) fn is_valid_identifier(s: &str) -> bool { && !s.chars().all(|c| c == '_') } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] pub mod test { use crate::binder::{is_valid_identifier, Binder, BinderContext}; @@ -842,3 +841,4 @@ pub mod test { assert!(!is_valid_identifier("____")); } } +// GRCOV_EXCL_STOP diff --git a/src/binder/parser.rs b/src/binder/parser.rs index 2dbafd27..71357ab9 100644 --- a/src/binder/parser.rs +++ b/src/binder/parser.rs @@ -20,7 +20,7 @@ use super::{is_valid_identifier, Binder, CteBinding, QueryBindStep, SetOperatorK #[cfg(feature = "copy")] use crate::binder::copy::{ExtSource, FileFormat}; use crate::catalog::{ColumnCatalog, ColumnDesc, ColumnRef, TableName}; -use crate::db::{BindSource, DBTransaction, Database, DatabaseIter, TransactionIter}; +use crate::db::{BindSource, DBTransaction, Database, DatabaseIter, State, TransactionIter}; use crate::errors::{DatabaseError, SqlErrorSpan}; use crate::expression; use crate::expression::agg::AggKind; @@ -28,7 +28,7 @@ use crate::expression::visitor_mut::ExprVisitorMut; use crate::expression::window::WindowFunctionKind; use crate::expression::{AliasType, ScalarExpression, TypeCast}; use crate::iter_ext::Itertools; -use crate::parser::parse_sql; +use crate::parser::parse_sql_with_recursion_limit; use crate::planner::operator::alter_table::change_column::{DefaultChange, NotNullChange}; use crate::planner::operator::join::{JoinCondition, JoinOperator as LJoinOperator, JoinType}; use crate::planner::operator::mark_apply::MarkApplyQuantifier; @@ -42,14 +42,15 @@ use crate::storage::{Storage, Transaction}; use crate::types::value::{DataValue, Utf8Type}; use crate::types::{CharLengthUnits, ColumnId, LogicalType}; pub(super) use sqlparser::ast::{ - AlterColumnOperation, AlterTableOperation, Assignment, AssignmentTarget, BinaryOperator, - ColumnDef, ColumnOption, CreateView, DataType, DescribeAlias, Distinct, DuplicateTreatment, - Expr, FromTable, Function, FunctionArg, FunctionArgExpr, FunctionArguments, GroupByExpr, Ident, - IndexColumn, Join, JoinConstraint, JoinOperator, LimitClause, ObjectName, ObjectNamePart, - ObjectType, OrderByExpr, OrderByKind, Query, Select, SelectInto, SelectItem, - SelectItemQualifiedWildcardKind, SetExpr, SetOperator, SetQuantifier, Spanned, TableAlias, - TableConstraint, TableFactor, TableObject, TableWithJoins, TypedString, UnaryOperator, Value, - ValueWithSpan, WindowType, + AlterColumnOperation, AlterTableOperation, Assignment, AssignmentTarget, BinaryLength, + BinaryOperator, ColumnDef, ColumnOption, CreateIndex, CreateView, DataType, DescribeAlias, + Distinct, DuplicateTreatment, Expr, FromTable, Function, FunctionArg, FunctionArgExpr, + FunctionArguments, GroupByExpr, Ident, IndexColumn, IndexOption, IndexType, Join, + JoinConstraint, JoinOperator, LimitClause, ObjectName, ObjectNamePart, ObjectType, OrderByExpr, + OrderByKind, Query, Select, SelectInto, SelectItem, SelectItemQualifiedWildcardKind, SetExpr, + SetOperator, SetQuantifier, Spanned, SqliteOnConflict, TableAlias, TableConstraint, + TableFactor, TableObject, TableWithJoins, TypedString, UnaryOperator, Value, ValueWithSpan, + WindowType, }; #[cfg(feature = "copy")] pub(super) use sqlparser::ast::{CopyOption, CopySource, CopyTarget}; @@ -137,19 +138,23 @@ pub fn command_type(stmt: &Statement) -> Result { } } -/// Parses a single SQL statement into a reusable [`Statement`]. -pub(crate) fn parse_statement>(sql: T) -> Result { - let mut stmts = prepare_all(sql)?; - stmts.pop().ok_or(DatabaseError::EmptyStatement) -} +impl State { + pub(crate) fn prepare_all>( + &self, + sql: T, + ) -> Result, DatabaseError> { + let stmts = parse_sql_with_recursion_limit(sql, self.parser_recursion_limit())?; + if stmts.is_empty() { + return Err(DatabaseError::EmptyStatement); + } + Ok(stmts) + } -/// Parses one or more SQL statements into a vector of [`Statement`] values. -pub fn prepare_all>(sql: T) -> Result, DatabaseError> { - let stmts = parse_sql(sql)?; - if stmts.is_empty() { - return Err(DatabaseError::EmptyStatement); + pub(crate) fn parse_statement(&self, sql: &str) -> Result { + self.prepare_all(sql)? + .pop() + .ok_or(DatabaseError::EmptyStatement) } - Ok(stmts) } fn statement_mutates_catalog_or_statistics(statement: &Statement) -> Result { @@ -160,9 +165,17 @@ fn statement_mutates_catalog_or_statistics(statement: &Statement) -> Result Database { + /// Parses SQL statements using this database's configured parser recursion limit. + pub fn prepare_all>(&self, sql: T) -> Result, DatabaseError> { + self.state.prepare_all(sql) + } + pub fn ddl>(&mut self, sql: T) -> Result<(), DatabaseError> { let sql = sql.as_ref(); - let statements = prepare_all(sql).map_err(|err| err.with_sql_context(sql))?; + let statements = self + .state + .prepare_all(sql) + .map_err(|err| err.with_sql_context(sql))?; for statement in &statements { if !matches!(command_type(statement)?, CommandType::DDL) { @@ -186,7 +199,10 @@ impl Database { ) -> Result, { let sql = sql.as_ref(); - let statements = prepare_all(sql).map_err(|err| err.with_sql_context(sql))?; + let statements = self + .state + .prepare_all(sql) + .map_err(|err| err.with_sql_context(sql))?; self.run_mut_statements(sql, statements, consume) } @@ -228,7 +244,10 @@ impl Database { /// ``` pub fn run>(&self, sql: T) -> Result, DatabaseError> { let sql = sql.as_ref(); - let statements = prepare_all(sql).map_err(|err| err.with_sql_context(sql))?; + let statements = self + .state + .prepare_all(sql) + .map_err(|err| err.with_sql_context(sql))?; let has_catalog_mutation = statements .iter() .try_fold(false, |has_mutation, stmt| { @@ -297,7 +316,10 @@ impl<'txn, S: Storage> DBTransaction<'txn, S> { sql: T, ) -> Result>, DatabaseError> { let sql = sql.as_ref(); - let mut statements = prepare_all(sql).map_err(|err| err.with_sql_context(sql))?; + let mut statements = self + .state + .prepare_all(sql) + .map_err(|err| err.with_sql_context(sql))?; for statement in &statements { if statement_mutates_catalog_or_statistics(statement)? { return Err(DatabaseError::UnsupportedStmt( @@ -381,8 +403,9 @@ where object_type, names, if_exists, + table, .. - } => self.drop_object(*object_type, names.clone(), *if_exists)?, + } => self.drop_object(*object_type, names.clone(), *if_exists, table.clone())?, Statement::Insert(insert) => self.insert(insert)?, Statement::Update(update) => self.update(update)?, Statement::Delete(delete) => self.delete(delete)?, @@ -839,10 +862,26 @@ where Ok(()) } - fn create_index( - self, - create: sqlparser::ast::CreateIndex, - ) -> Result { + fn create_index(self, create: CreateIndex) -> Result { + if create.concurrently + || !create.include.is_empty() + || create.nulls_distinct.is_some() + || !create.with.is_empty() + || create.predicate.is_some() + || !create.alter_options.is_empty() + || create + .using + .as_ref() + .is_some_and(|ty| !matches!(ty, IndexType::BTree)) + || create + .index_options + .iter() + .any(|option| !matches!(option, IndexOption::Using(IndexType::BTree))) + { + return Err(DatabaseError::UnsupportedStmt( + "CREATE INDEX options".to_string(), + )); + } let table_name = sql_table_name(create.table_name)?; let index_name = create .name @@ -854,6 +893,15 @@ where let mut columns = Vec::with_capacity(create.columns.len()); for index_column in create.columns { + if index_column.operator_class.is_some() + || index_column.column.options.asc == Some(false) + || index_column.column.options.nulls_first.is_some() + || index_column.column.with_fill.is_some() + { + return Err(DatabaseError::UnsupportedStmt( + "CREATE INDEX column options".to_string(), + )); + } let expr = self .binder .bind_expr(&index_column.column.expr, self.arena)?; @@ -908,10 +956,16 @@ where object_type: ObjectType, mut names: Vec, if_exists: bool, + table: Option, ) -> Result { - if names.len() > 1 { + if names.len() != 1 { return Err(DatabaseError::UnsupportedStmt( - "only Drop a single `Table` or `View` is allowed".to_string(), + "only Drop a single `Table`, `View` or `Index` is allowed".to_string(), + )); + } + if table.is_some() && object_type != ObjectType::Index { + return Err(DatabaseError::UnsupportedStmt( + "DROP ON is only supported for INDEX".to_string(), )); } @@ -923,12 +977,17 @@ where .binder .bind_drop_view(sql_table_name(names.remove(0))?, if_exists), ObjectType::Index => { - let (table_name, index_name) = sql_index_name(names.remove(0))?; + let name = names.remove(0); + let (table_name, index_name) = if let Some(table) = table { + (sql_table_name(table)?, sql_object_name(name)?) + } else { + sql_index_name(name)? + }; self.binder .bind_drop_index(table_name, index_name, if_exists) } _ => Err(DatabaseError::UnsupportedStmt( - "only `Table` and `View` are allowed to be Dropped".to_string(), + "only `Table`, `View` and `Index` are allowed to be Dropped".to_string(), )), } } @@ -965,9 +1024,13 @@ where table, columns, overwrite, + or, + replace_into, source, .. } = insert; + let is_overwrite = + *overwrite || *replace_into || matches!(or, Some(SqliteOnConflict::Replace)); let table_name = match table { TableObject::TableName(table_name) => table_name.clone(), TableObject::TableFunction(_) => { @@ -981,9 +1044,9 @@ where DatabaseError::UnsupportedStmt("insert without source is not supported".to_string()) })?; if let SetExpr::Values(values) = source.body.as_ref() { - self.insert_values(table_name, columns, &values.rows, *overwrite, false) + self.insert_values(table_name, columns, &values.rows, is_overwrite, false) } else { - self.insert_query(table_name, columns, source, *overwrite) + self.insert_query(table_name, columns, source, is_overwrite) } } @@ -1143,6 +1206,9 @@ where if let TableFactor::Table { name, .. } = &to.relation { let is_joined_update = !to.joins.is_empty(); let table_name = sql_table_name(name.clone())?; + if self.binder.context.table(table_name.clone())?.is_none() { + return Err(DatabaseError::TableNotFound); + } self.binder.with_pk(table_name.clone()); let mut plan = self.binder.bind_table_ref_sql(to, self.arena)?; @@ -1643,6 +1709,24 @@ impl TryFrom<&Value> for DataValue { } } Value::SingleQuotedString(s) | Value::DoubleQuotedString(s) => s.clone().into(), + Value::HexStringLiteral(hex) => { + if hex.len() % 2 != 0 { + return Err(DatabaseError::InvalidValue( + "hex literal must contain pairs of digits".to_string(), + )); + } + let mut bytes = Vec::with_capacity(hex.len() / 2); + for pair in hex.as_bytes().chunks_exact(2) { + let Some(high) = (pair[0] as char).to_digit(16) else { + return Err(DatabaseError::InvalidValue("invalid hex digit".to_string())); + }; + let Some(low) = (pair[1] as char).to_digit(16) else { + return Err(DatabaseError::InvalidValue("invalid hex digit".to_string())); + }; + bytes.push((high * 16 + low) as u8); + } + bytes.into() + } Value::Boolean(b) => (*b).into(), Value::Null => Self::Null, v => return Err(DatabaseError::UnsupportedStmt(format!("{v:?}"))), @@ -1703,6 +1787,17 @@ impl TryFrom for LogicalType { .unwrap_or(CharLengthUnits::Characters), )) } + DataType::Varbinary(length) => { + let len = match length { + Some(BinaryLength::IntegerLength { length }) => Some(u32::try_from(length)?), + None | Some(BinaryLength::Max) => None, + }; + Ok(Self::Varbinary(len)) + } + DataType::Blob(length) | DataType::Bytes(length) => { + Ok(Self::Varbinary(length.map(u32::try_from).transpose()?)) + } + DataType::Bytea => Ok(Self::Varbinary(None)), DataType::String(_) | DataType::Text => { Ok(Self::Varchar(None, CharLengthUnits::Characters)) } @@ -1893,10 +1988,9 @@ fn single_ident_from_object_name(name: &ObjectName) -> Result Result<(TableName, String), DatabaseError> { - let table_name = name.0.first().ok_or_else(|| { - attach_span_if_absent(DatabaseError::invalid_table(name.to_string()), &name) - })?; - let index_name = name.0.get(1).ok_or(DatabaseError::InvalidIndex)?; + let [table_name, index_name] = name.0.as_slice() else { + return Err(DatabaseError::InvalidIndex); + }; Ok(( lower_name_part(table_name)?.into(), @@ -2577,6 +2671,14 @@ impl<'a, 'parent, T: Transaction, A: AsRef<[(usize, LogicalType)]>> Binder<'a, ' args.push(arena.alloc_expression(expr)) } FunctionArgExpr::Wildcard => { + if is_distinct { + return Err(attach_span_if_absent( + DatabaseError::UnsupportedStmt( + "DISTINCT cannot be used with *".to_string(), + ), + func_span, + )); + } args.push(arena.alloc_expression(Self::wildcard_expr())) } expr => { @@ -3296,6 +3398,7 @@ impl<'a, 'parent, T: Transaction, A: AsRef<[(usize, LogicalType)]>> Binder<'a, ' } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::*; @@ -3303,6 +3406,7 @@ mod tests { use crate::binder::BinderContext; use crate::db::DataBaseBuilder; use crate::errors::SqlErrorSpan; + use crate::parser::parse_sql; use crate::storage::Storage; use sqlparser::tokenizer::Location; @@ -3337,40 +3441,52 @@ mod tests { #[test] fn test_prepare_and_command_type_classification() -> Result<(), DatabaseError> { + let database = DataBaseBuilder::path(".").build_in_memory()?; assert!(matches!( - prepare_all(""), + database.prepare_all(""), Err(DatabaseError::EmptyStatement) )); assert_eq!( - command_type(&parse_statement("select 1")?)?, + command_type(&database.prepare_all("select 1")?.pop().unwrap())?, CommandType::DQL ); assert_eq!( - command_type(&parse_statement("create table t (id int primary key)")?)?, + command_type( + &database + .prepare_all("create table t (id int primary key)")? + .pop() + .unwrap() + )?, CommandType::DDL ); assert_eq!( - command_type(&parse_statement("analyze table t")?)?, + command_type(&database.prepare_all("analyze table t")?.pop().unwrap())?, CommandType::Analyze ); assert_eq!( - command_type(&parse_statement("insert into t values (1)")?)?, + command_type( + &database + .prepare_all("insert into t values (1)")? + .pop() + .unwrap() + )?, CommandType::DML ); assert_eq!( - command_type(&parse_statement("update t set id = 1")?)?, + command_type(&database.prepare_all("update t set id = 1")?.pop().unwrap())?, CommandType::DML ); assert_eq!( - command_type(&parse_statement("delete from t")?)?, + command_type(&database.prepare_all("delete from t")?.pop().unwrap())?, CommandType::DML ); assert_eq!( - command_type(&parse_statement("truncate table t")?)?, + command_type(&database.prepare_all("truncate table t")?.pop().unwrap())?, CommandType::DML ); - let err = command_type(&parse_statement("start transaction")?).unwrap_err(); + let err = + command_type(&database.prepare_all("start transaction")?.pop().unwrap()).unwrap_err(); assert_unsupported(err, "START TRANSACTION"); Ok(()) @@ -3448,7 +3564,7 @@ mod tests { "only a single ALTER TABLE operation", ); - let mut stmt = parse_statement("alter table t1 drop column c1")?; + let mut stmt = parse_sql("alter table t1 drop column c1")?.pop().unwrap(); let Statement::AlterTable(alter) = &mut stmt else { unreachable!("expected alter table statement") }; @@ -3488,6 +3604,63 @@ mod tests { Ok(()) } + #[test] + fn sql_type_binding_preserves_unsigned_widths_and_validates_precision( + ) -> Result<(), DatabaseError> { + use sqlparser::ast::{CharacterLength, ExactNumberInfo, TimezoneInfo}; + for (sql, expected) in [ + (DataType::TinyIntUnsigned(None), LogicalType::UTinyint), + (DataType::SmallIntUnsigned(None), LogicalType::USmallint), + (DataType::UnsignedInteger, LogicalType::UInteger), + (DataType::UInt64, LogicalType::UBigint), + ] { + assert_eq!(LogicalType::try_from(sql)?, expected); + } + for sql in [ + DataType::Char(Some(CharacterLength::Max)), + DataType::Varchar(Some(CharacterLength::Max)), + ] { + assert!(matches!( + LogicalType::try_from(sql), + Err(DatabaseError::UnsupportedStmt(_)) + )); + } + #[cfg(feature = "time")] + { + assert_eq!( + LogicalType::try_from(DataType::Time(Some(4), TimezoneInfo::None))?, + LogicalType::Time(Some(4)) + ); + assert_eq!( + LogicalType::try_from(DataType::Timestamp(Some(6), TimezoneInfo::WithTimeZone))?, + LogicalType::TimeStamp(Some(6), true) + ); + for sql in [ + DataType::Time(Some(5), TimezoneInfo::None), + DataType::Time(None, TimezoneInfo::WithTimeZone), + DataType::Timestamp(Some(2), TimezoneInfo::None), + DataType::Datetime(Some(3)), + ] { + assert!(matches!( + LogicalType::try_from(sql), + Err(DatabaseError::UnsupportedStmt(_)) + )); + } + } + #[cfg(feature = "decimal")] + { + assert_eq!( + LogicalType::try_from(DataType::Numeric(ExactNumberInfo::Precision(12)))?, + LogicalType::Decimal(Some(12), None) + ); + assert_eq!( + LogicalType::try_from(DataType::Dec(ExactNumberInfo::PrecisionAndScale(12, 3)))?, + LogicalType::Decimal(Some(12), Some(3)) + ); + } + Ok(()) + } + #[test] fn test_limit_parser_rejects_negative_and_non_constant_values() -> Result<(), DatabaseError> { let tables = build_t1_table()?; @@ -3618,3 +3791,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/binder/select.rs b/src/binder/select.rs index 4cb64dc1..8aa225d8 100644 --- a/src/binder/select.rs +++ b/src/binder/select.rs @@ -373,7 +373,34 @@ where } #[cfg(feature = "orm")] - pub(crate) fn having_expr(mut self, expr: ExprRef) -> Result { + pub(crate) fn having_expr(mut self, mut expr: ExprRef) -> Result { + let mut input = &self.plan; + loop { + match &input.operator { + Operator::Aggregate(op) => { + Binder::::validate_having_orderby_with_outputs( + expr, + &op.agg_calls, + &op.groupby_exprs, + self.arena, + )?; + self.binder.bind_aggregate_output_exprs_with_outputs( + &op.agg_calls, + &op.groupby_exprs, + std::iter::once(&mut expr), + self.arena, + )?; + break; + } + Operator::Filter(_) + | Operator::Sort(_) + | Operator::TopK(_) + | Operator::Limit(_) + | Operator::Window(_) => input = input.childrens.only(), + Operator::ScalarQueryInit(_) => input = input.childrens.twins().0, + _ => break, + } + } self.plan = self.binder.bind_having(self.plan, expr, self.arena)?; Ok(self) } @@ -647,6 +674,15 @@ where &[QueryBindStep::Project, QueryBindStep::Sort], self.arena, )?; + self.binder.context.step(QueryBindStep::Project); + let marker_consumers = self.select_list.iter_mut().chain( + self.orderby + .iter_mut() + .flat_map(|fields| fields.iter_mut().map(|field| &mut field.expr)), + ); + self.plan = self + .binder + .bind_mark_queries(self.plan, marker_consumers, self.arena)?; self.plan = self.binder.bind_window( self.plan, &mut self.select_list, @@ -1434,7 +1470,7 @@ impl<'a: 'b, 'b, T: Transaction, A: AsRef<[(usize, LogicalType)]>> Binder<'a, 'b pub(crate) fn bind_where_expr( &mut self, mut children: LogicalPlan, - predicate: ExprRef, + mut predicate: ExprRef, arena: &mut PlanArena, ) -> Result { children = self.bind_scalar_queries(children, &[QueryBindStep::Where], arena)?; @@ -1445,6 +1481,32 @@ impl<'a: 'b, 'b, T: Transaction, A: AsRef<[(usize, LogicalType)]>> Binder<'a, 'b )); } + if self.context.sub_queries.contains_key(&QueryBindStep::Where) { + children = self.bind_mark_queries(children, std::iter::once(&mut predicate), arena)?; + let passthrough_exprs = children + .output_schema(arena) + .iter() + .copied() + .enumerate() + .map(|(position, column)| { + arena.alloc_expression(ScalarExpression::column_expr(column, position)) + }) + .collect(); + return Ok(Self::build_project_plan( + FilterOperator::build(predicate, children, false), + passthrough_exprs, + )); + } + Ok(FilterOperator::build(predicate, children, false)) + } + + fn bind_mark_queries<'c>( + &mut self, + mut children: LogicalPlan, + exprs: impl Iterator, + arena: &mut PlanArena, + ) -> Result { + let mut output_positions = Vec::new(); if let Some(sub_queries) = self.context.sub_queries_at_now() { for sub_query in sub_queries { match sub_query { @@ -1454,9 +1516,8 @@ impl<'a: 'b, 'b, T: Transaction, A: AsRef<[(usize, LogicalType)]>> Binder<'a, 'b output_column, } => { let left_schema = children.output_schema(arena).clone(); + output_positions.push((output_column, left_schema.len())); let (plan, predicates) = Self::prepare_mark_apply( - predicate, - &output_column, left_schema.as_ref(), plan, correlated, @@ -1486,9 +1547,8 @@ impl<'a: 'b, 'b, T: Transaction, A: AsRef<[(usize, LogicalType)]>> Binder<'a, 'b ); } let left_schema = children.output_schema(arena).clone(); + output_positions.push((output_column, left_schema.len())); let (plan, predicates) = Self::prepare_mark_apply( - predicate, - &output_column, left_schema.as_ref(), plan, correlated, @@ -1506,26 +1566,17 @@ impl<'a: 'b, 'b, T: Transaction, A: AsRef<[(usize, LogicalType)]>> Binder<'a, 'b } } } - { - let passthrough_exprs = children - .output_schema(arena) - .iter() - .cloned() - .enumerate() - .map(|(position, column)| { - arena.alloc_expression(ScalarExpression::column_expr(column, position)) - }) - .collect(); - let filter = FilterOperator::build(predicate, children, false); - return Ok(LogicalPlan::new( - Operator::Project(ProjectOperator { - exprs: passthrough_exprs, - }), - Childrens::Only(Box::new(filter)), - )); + } + for expr in exprs { + for (output_column, left_len) in &output_positions { + MarkerPositionGlobalizer { + output_column, + left_len: *left_len, + } + .visit(expr, arena)?; } } - Ok(FilterOperator::build(predicate, children, false)) + Ok(children) } fn ensure_mark_apply_right_outputs( @@ -1582,10 +1633,7 @@ impl<'a: 'b, 'b, T: Transaction, A: AsRef<[(usize, LogicalType)]>> Binder<'a, 'b Ok(Vec::new()) } - #[allow(clippy::too_many_arguments)] fn prepare_mark_apply( - mut predicate: ExprRef, - output_column: &ColumnRef, left_schema: &Schema, plan: LogicalPlan, correlated: bool, @@ -1594,12 +1642,6 @@ impl<'a: 'b, 'b, T: Transaction, A: AsRef<[(usize, LogicalType)]>> Binder<'a, 'b arena: &mut PlanArena, ) -> Result<(LogicalPlan, Vec), DatabaseError> { let left_len = left_schema.len(); - MarkerPositionGlobalizer { - output_column, - left_len, - } - .visit(&mut predicate, arena)?; - let (mut plan, correlated_filters) = if correlated { Self::prepare_correlated_subquery_plan(plan, left_schema, preserve_projection, arena)? } else { @@ -2109,6 +2151,7 @@ impl<'a: 'b, 'b, T: Transaction, A: AsRef<[(usize, LogicalType)]>> Binder<'a, 'b } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::{ProjectionOutputBinder, RightSidePositionGlobalizer}; @@ -2401,3 +2444,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/catalog/column.rs b/src/catalog/column.rs index a3448b7c..05e2893e 100644 --- a/src/catalog/column.rs +++ b/src/catalog/column.rs @@ -198,6 +198,7 @@ impl ColumnCatalog { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::{ColumnCatalog, ColumnDesc}; @@ -233,6 +234,7 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP /// The descriptor of a column. #[derive(Debug, Clone, PartialEq, Eq, Hash, ReferenceSerialization)] diff --git a/src/catalog/table.rs b/src/catalog/table.rs index ac523df1..25cd77bc 100644 --- a/src/catalog/table.rs +++ b/src/catalog/table.rs @@ -74,10 +74,12 @@ impl TableCatalog { self.columns.get(id).map(|i| self.column_refs[*i]) } + // GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] pub(crate) fn get_column_id_by_name(&self, name: &str) -> Option<&ColumnId> { self.column_idxs.get(name).map(|(id, _)| id) } + // GRCOV_EXCL_STOP pub(crate) fn get_column_by_name(&self, name: &str) -> Option { self.column_idxs @@ -370,6 +372,7 @@ impl TableMeta { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::*; @@ -471,3 +474,4 @@ mod tests { } } } +// GRCOV_EXCL_STOP diff --git a/src/db.rs b/src/db.rs index 74cf4775..cef5b10c 100644 --- a/src/db.rs +++ b/src/db.rs @@ -15,7 +15,7 @@ #[cfg(any(feature = "parser", feature = "orm"))] mod prepared; #[cfg(feature = "parser")] -pub use crate::binder::{prepare_all, Statement}; +pub use crate::binder::Statement; use crate::binder::{Binder, BinderContext}; use crate::catalog::TableName; use crate::errors::DatabaseError; @@ -112,6 +112,7 @@ pub struct DataBaseBuilder { ))] path: PathBuf, histogram_buckets: Option, + parser_recursion_limit: Option, transaction_isolation: Option, #[cfg(all(not(target_arch = "wasm32"), feature = "rocksdb"))] storage_config: StorageConfig, @@ -143,6 +144,7 @@ impl DataBaseBuilder { ))] path, histogram_buckets: None, + parser_recursion_limit: None, transaction_isolation: None, #[cfg(all(not(target_arch = "wasm32"), feature = "rocksdb"))] storage_config: Default::default(), @@ -157,6 +159,14 @@ impl DataBaseBuilder { self } + /// Sets the SQL parser recursion limit. Unset uses the parser's default limit. + /// Higher limits may require a larger thread stack for deeply nested SQL. + #[cfg(feature = "parser")] + pub fn parser_recursion_limit(mut self, limit: usize) -> Self { + self.parser_recursion_limit = Some(limit); + self + } + /// Sets the transaction isolation level used by database-created transactions. pub fn transaction_isolation(mut self, isolation: TransactionIsolationLevel) -> Self { self.transaction_isolation = Some(isolation); @@ -219,7 +229,12 @@ impl DataBaseBuilder { /// Builds a database using a custom storage implementation. pub fn build_with_storage(self, storage: T) -> Result, DatabaseError> { - Self::_build::(storage, self.histogram_buckets, self.transaction_isolation) + Self::_build::( + storage, + self.histogram_buckets, + self.parser_recursion_limit, + self.transaction_isolation, + ) } /// Builds a database for the current target platform. @@ -227,7 +242,12 @@ impl DataBaseBuilder { pub fn build(self) -> Result, DatabaseError> { let storage = MemoryStorage::new(); - Self::_build::(storage, self.histogram_buckets, self.transaction_isolation) + Self::_build::( + storage, + self.histogram_buckets, + self.parser_recursion_limit, + self.transaction_isolation, + ) } /// Builds a RocksDB-backed database. @@ -235,7 +255,12 @@ impl DataBaseBuilder { pub fn build_rocksdb(self) -> Result, DatabaseError> { let storage = RocksStorage::with_config(self.path, self.storage_config)?; - Self::_build::(storage, self.histogram_buckets, self.transaction_isolation) + Self::_build::( + storage, + self.histogram_buckets, + self.parser_recursion_limit, + self.transaction_isolation, + ) } /// Builds an in-memory database. @@ -244,7 +269,12 @@ impl DataBaseBuilder { pub fn build_in_memory(self) -> Result, DatabaseError> { let storage = MemoryStorage::new(); - Self::_build::(storage, self.histogram_buckets, self.transaction_isolation) + Self::_build::( + storage, + self.histogram_buckets, + self.parser_recursion_limit, + self.transaction_isolation, + ) } /// Builds a LMDB-backed database. @@ -252,7 +282,12 @@ impl DataBaseBuilder { pub fn build_lmdb(self) -> Result, DatabaseError> { let storage = LmdbStorage::with_config(self.path, self.lmdb_config)?; - Self::_build::(storage, self.histogram_buckets, self.transaction_isolation) + Self::_build::( + storage, + self.histogram_buckets, + self.parser_recursion_limit, + self.transaction_isolation, + ) } #[cfg(all(not(target_arch = "wasm32"), feature = "rocksdb"))] @@ -264,6 +299,7 @@ impl DataBaseBuilder { Self::_build::( storage, self.histogram_buckets, + self.parser_recursion_limit, self.transaction_isolation, ) } @@ -271,6 +307,7 @@ impl DataBaseBuilder { fn _build( storage: T, histogram_buckets: Option, + parser_recursion_limit: Option, transaction_isolation: Option, ) -> Result, DatabaseError> { if matches!(histogram_buckets, Some(0)) { @@ -278,6 +315,11 @@ impl DataBaseBuilder { "histogram buckets must be >= 1".to_string(), )); } + if matches!(parser_recursion_limit, Some(0)) { + return Err(DatabaseError::InvalidValue( + "parser recursion limit must be >= 1".to_string(), + )); + } let transaction_isolation = transaction_isolation.unwrap_or_else(|| storage.default_transaction_isolation()); storage.validate_transaction_isolation(transaction_isolation)?; @@ -295,6 +337,7 @@ impl DataBaseBuilder { table_arena, optimizer_pipeline: optimizer_pipeline(), histogram_buckets, + parser_recursion_limit, _p: Default::default(), }; @@ -467,10 +510,17 @@ pub(crate) struct State { table_arena: TableArenaCell, optimizer_pipeline: HepOptimizerPipeline, histogram_buckets: Option, + #[cfg_attr(not(feature = "parser"), allow(dead_code))] + parser_recursion_limit: Option, _p: PhantomData, } impl State { + #[cfg(feature = "parser")] + pub(crate) fn parser_recursion_limit(&self) -> Option { + self.parser_recursion_limit + } + fn scala_functions(&self) -> &ScalaFunctions { &self.scala_functions } @@ -1327,6 +1377,7 @@ impl ResultIter for TransactionIter<'_, T, A> { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] pub(crate) mod test { use crate::binder::{Binder, BinderContext}; @@ -1568,7 +1619,7 @@ pub(crate) mod test { kite_sql.ddl("CREATE TABLE onecolumn (id INT PRIMARY KEY, x INT NULL)")?; kite_sql.ddl("CREATE TABLE empty (e_id INT PRIMARY KEY, x INT)")?; - let stmt = crate::binder::parse_statement( + let stmt = kite_sql.state.parse_statement( "SELECT * FROM onecolumn AS a(aid, x) JOIN empty AS b(bid, y) ON a.x = b.y", )?; let transaction = kite_sql.storage.transaction()?; @@ -1651,7 +1702,7 @@ pub(crate) mod test { kite_sql.ddl("CREATE TABLE onecolumn (id INT PRIMARY KEY, x INT NULL)")?; kite_sql.ddl("CREATE TABLE twocolumn (t_id INT PRIMARY KEY, x INT NULL, y INT NULL)")?; - let stmt = crate::binder::parse_statement( + let stmt = kite_sql.state.parse_statement( "SELECT o.x, t.y FROM onecolumn o INNER JOIN twocolumn t ON (o.x=t.x AND t.y=53)", )?; let transaction = kite_sql.storage.transaction()?; @@ -1761,7 +1812,7 @@ pub(crate) mod test { )? .done()?; - let stmt = crate::binder::parse_statement( + let stmt = kite_sql.state.parse_statement( "SELECT o.x, t.y FROM onecolumn o INNER JOIN twocolumn t ON (o.x=t.x AND t.y=53)", )?; let transaction = kite_sql.storage.transaction()?; @@ -2269,6 +2320,73 @@ pub(crate) mod test { )); } + #[test] + fn parser_recursion_limit_applies_to_sql_frontends() -> Result<(), DatabaseError> { + let expression = format!("{}7{}", "(".repeat(8), ")".repeat(8)); + let sql = format!("select {expression}"); + let default = DataBaseBuilder::path(".").build_in_memory()?; + assert_eq!(read_single_i32(default.run(&sql)?)?, 7); + assert_eq!(default.prepare_all(&sql)?.len(), 1); + + let mut db = DataBaseBuilder::path(".") + .parser_recursion_limit(256) + .build_in_memory()?; + assert_eq!(db.prepare_all(&sql)?.len(), 1); + assert_eq!(read_single_i32(db.run(&sql)?)?, 7); + db.ddl(format!("create view deep_view as {sql}"))?; + assert_eq!(read_single_i32(db.run("select * from deep_view")?)?, 7); + db.run_mut(&sql, |iter| { + assert_eq!( + iter.next_tuple(|_, row| row.values.clone())?, + Some(vec![DataValue::Int32(7)]) + ); + Ok(()) + })?; + let prepared = db.prepare_sql(&sql, &[])?; + let mut iter = db.execute(&prepared, [])?; + assert_eq!( + iter.next_tuple(|_, row| row.values.clone())?, + Some(vec![DataValue::Int32(7)]) + ); + iter.done()?; + let mut tx = db.new_transaction()?; + let mut iter = tx.run(&sql)?; + assert_eq!( + iter.next_tuple(|_, row| row.values.clone())?, + Some(vec![DataValue::Int32(7)]) + ); + iter.done()?; + let prepared = tx.prepare_sql(&sql, &[])?; + let mut iter = tx.execute(&prepared, [])?; + assert_eq!( + iter.next_tuple(|_, row| row.values.clone())?, + Some(vec![DataValue::Int32(7)]) + ); + iter.done()?; + tx.commit()?; + + let low = DataBaseBuilder::path(".") + .parser_recursion_limit(4) + .build_in_memory()?; + assert!(low.run("select 1").is_ok()); + assert!(low + .prepare_all(&sql) + .unwrap_err() + .to_string() + .contains("recursion limit exceeded")); + assert!(low + .run(&sql) + .err() + .unwrap() + .to_string() + .contains("recursion limit exceeded")); + assert!(matches!( + DataBaseBuilder::path(".").parser_recursion_limit(0).build_in_memory(), + Err(DatabaseError::InvalidValue(message)) if message == "parser recursion limit must be >= 1" + )); + Ok(()) + } + #[cfg(feature = "lmdb")] #[test] fn test_lmdb_rejects_read_committed_isolation() { @@ -2320,6 +2438,74 @@ pub(crate) mod test { Ok(()) } + #[test] + fn optimistic_checkpoint_restores_rows_indexes_and_scalar_views() -> Result<(), DatabaseError> { + let directory = TempDir::new()?; + let live_path = directory.path().join("live"); + let snapshot_path = directory.path().join("snapshot"); + let mut database = DataBaseBuilder::path(&live_path).build_optimistic()?; + database.ddl("create table checkpoint_rows(id int primary key, v int unique)")?; + database + .run("insert into checkpoint_rows values(1,10),(2,20)")? + .done()?; + database.ddl( + "create view checkpoint_view as select (select max(v) from checkpoint_rows) as v", + )?; + database.checkpoint(&snapshot_path)?; + database + .run("update checkpoint_rows set v=30 where id=2")? + .done()?; + database + .run("insert into checkpoint_rows values(3,40)")? + .done()?; + let mut snapshot = DataBaseBuilder::path(&snapshot_path).build_optimistic()?; + snapshot.load(CatalogKind::Table("checkpoint_rows".into()))?; + snapshot.load(CatalogKind::View("checkpoint_view".into()))?; + let mut rows = snapshot.run("select id,v from checkpoint_rows order by id")?; + assert_eq!( + rows.next_tuple(|_, row| row.values.clone())?, + Some(vec![DataValue::Int32(1), DataValue::Int32(10)]) + ); + assert_eq!( + rows.next_tuple(|_, row| row.values.clone())?, + Some(vec![DataValue::Int32(2), DataValue::Int32(20)]) + ); + assert!(rows.next_tuple(|_, _| ())?.is_none()); + rows.done()?; + assert_eq!( + read_single_i32(snapshot.run("select id from checkpoint_rows where v=20")?)?, + 2 + ); + assert_eq!( + read_single_i32(snapshot.run("select v from checkpoint_view")?)?, + 20 + ); + assert_eq!( + read_single_i32(database.run("select v from checkpoint_view")?)?, + 40 + ); + assert!(matches!( + snapshot + .run("insert into checkpoint_rows values(4,20)")? + .done(), + Err(DatabaseError::DuplicateUniqueValue) + )); + let occupied = directory.path().join("occupied"); + std::fs::write(&occupied, b"keep")?; + assert!( + matches!(database.checkpoint(&occupied), Err(DatabaseError::IO(ref err)) if err.kind() == ErrorKind::AlreadyExists) + ); + assert_eq!(std::fs::read(&occupied)?, b"keep"); + drop(snapshot); + let mut reopened = DataBaseBuilder::path(&snapshot_path).build_optimistic()?; + reopened.load(CatalogKind::Table("checkpoint_rows".into()))?; + assert_eq!( + read_single_i32(reopened.run("select count(*) from checkpoint_rows")?)?, + 2 + ); + Ok(()) + } + #[test] fn test_checkpoint_rejects_non_empty_target_dir() -> Result<(), DatabaseError> { let temp_dir = TempDir::new().expect("unable to create temporary working directory"); @@ -2423,3 +2609,4 @@ pub(crate) mod test { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/db/prepared.rs b/src/db/prepared.rs index 2f61233c..405639bd 100644 --- a/src/db/prepared.rs +++ b/src/db/prepared.rs @@ -1,9 +1,13 @@ use super::*; +// GRCOV_EXCL_START #[cfg(test)] use crate::planner::operator::{PhysicalOption, PlanImpl, SortOption}; +// GRCOV_EXCL_STOP use crate::planner::{ExprRef, ParamArena, PlanInput}; +// GRCOV_EXCL_START #[cfg(test)] use crate::types::index::IndexLookup; +// GRCOV_EXCL_STOP use crate::types::LogicalType; /// A bound and optimized reusable plan. @@ -49,7 +53,7 @@ impl Database { sql: &str, params: &[(usize, LogicalType)], ) -> Result, DatabaseError> { - let statement = crate::binder::parse_statement(sql)?; + let statement = self.state.parse_statement(sql)?; let transaction = self .storage .transaction_with_isolation(self.transaction_isolation)?; @@ -81,7 +85,7 @@ impl<'db, S: Storage> DBTransaction<'db, S> { sql: &str, params: &[(usize, LogicalType)], ) -> Result, DatabaseError> { - let statement = crate::binder::parse_statement(sql)?; + let statement = self.state.parse_statement(sql)?; self.state.prepare_plan(&statement, params, &self.inner) } } @@ -132,6 +136,7 @@ impl State { } } +// GRCOV_EXCL_START #[cfg(test)] mod tests { use super::*; @@ -415,6 +420,56 @@ mod tests { )?; assert!(iter.next_tuple(|_, _| ())?.is_none()); iter.done()?; + + let unions = db.prepare_sql( + "select id from parameter_order where (id=$1 or id=$2) and id>$3 order by id", + &[ + (1, LogicalType::Integer), + (2, LogicalType::Integer), + (3, LogicalType::Integer), + ], + )?; + for (first, second, lower, expected) in [ + ( + DataValue::Int32(1), + DataValue::Int32(4), + DataValue::Int32(2), + vec![DataValue::Int32(4)], + ), + ( + DataValue::Int32(4), + DataValue::Int32(1), + DataValue::Int32(0), + vec![DataValue::Int32(1), DataValue::Int32(4)], + ), + ( + DataValue::Int32(2), + DataValue::Int32(2), + DataValue::Int32(0), + vec![DataValue::Int32(2)], + ), + ( + DataValue::Null, + DataValue::Int32(3), + DataValue::Int32(0), + vec![DataValue::Int32(3)], + ), + ( + DataValue::Int32(1), + DataValue::Int32(4), + DataValue::Null, + vec![], + ), + ] { + let mut iter = db.execute(&unions, [(1, first), (2, second), (3, lower)])?; + let mut rows = Vec::new(); + while iter + .next_tuple(|_, row| rows.push(row.value_at(0).clone()))? + .is_some() + {} + iter.done()?; + assert_eq!(rows, expected); + } Ok(()) } @@ -575,3 +630,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/execution/dml/analyze.rs b/src/execution/dml/analyze.rs index 5b2bb92d..d3144016 100644 --- a/src/execution/dml/analyze.rs +++ b/src/execution/dml/analyze.rs @@ -189,9 +189,10 @@ impl fmt::Display for AnalyzeOperator { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod test { - use crate::db::{DataBaseBuilder, Database}; + use crate::db::{CatalogKind, DataBaseBuilder, Database}; use crate::errors::DatabaseError; use crate::execution::dml::analyze::DEFAULT_NUM_OF_BUCKETS; use crate::expression::range_detacher::Range; @@ -261,7 +262,48 @@ mod test { assert_eq!(statistics_meta_p_index.index_id(), 2); assert_eq!(statistics_meta_p_index.histogram().values_len(), 101); assert_eq!(statistics_meta_p_index.histogram().buckets_len(), buckets); - + let expected_frequency = + loader.collect_count(&table_name, 1, &Range::Eq(DataValue::Int32(7)))?; + + // Reload the stored parts instead of reusing ANALYZE's in-memory cache. + drop(kite_sql); + let mut reopened = DataBaseBuilder::path(temp_dir.path()).build_rocksdb()?; + reopened.load(CatalogKind::Table(table_name.clone()))?; + let loader = StatisticMetaLoader::new(reopened.state.meta_cache()); + for index in 0..=2 { + let meta = loader.load(&table_name, index)?.unwrap(); + assert_eq!(meta.index_id(), index); + assert_eq!(meta.histogram().values_len(), 101); + assert_eq!(meta.histogram().buckets_len(), buckets); + } + assert_eq!( + loader.collect_count(&table_name, 1, &Range::Eq(DataValue::Int32(7)))?, + expected_frequency + ); + let mut iter = reopened.run("select a from t1 where b=7 order by a")?; + let mut rows = Vec::new(); + while iter + .next_tuple(|_, row| rows.push(row.values[0].clone()))? + .is_some() + {} + iter.done()?; + assert_eq!(rows, [7, 27, 47, 67, 87].map(DataValue::Int32)); + + // A missing root must not publish partially reconstructed statistics. + let mut codec = TableCodec::default(); + let mut tx = reopened.storage.transaction()?; + codec.with_statistics_meta( + "t1", + 1, + None, + reopened.state.table_arena().borrow(), + |key, _| tx.remove(key), + )?; + tx.commit()?; + drop(reopened); + let mut reopened = DataBaseBuilder::path(temp_dir.path()).build_rocksdb()?; + assert!(matches!(reopened.load(CatalogKind::Table(table_name)), + Err(DatabaseError::InvalidValue(message)) if message == "statistics meta is incomplete")); Ok(()) } @@ -383,3 +425,4 @@ mod test { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/execution/dml/copy_from_file.rs b/src/execution/dml/copy_from_file.rs index 697f00b5..1d842de8 100644 --- a/src/execution/dml/copy_from_file.rs +++ b/src/execution/dml/copy_from_file.rs @@ -106,6 +106,7 @@ impl<'a, T: Transaction + 'a> ExecutorNode<'a, T> for CopyFromFile<'a> { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::*; @@ -196,3 +197,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/execution/dml/copy_to_file.rs b/src/execution/dml/copy_to_file.rs index 31e2ce0f..6fc3bf47 100644 --- a/src/execution/dml/copy_to_file.rs +++ b/src/execution/dml/copy_to_file.rs @@ -111,6 +111,7 @@ impl<'a, T: Transaction + 'a> ExecutorNode<'a, T> for CopyToFile<'a> { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::*; @@ -187,3 +188,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/execution/dql/aggregate/avg.rs b/src/execution/dql/aggregate/avg.rs index 7e4b2702..b9afbac2 100644 --- a/src/execution/dql/aggregate/avg.rs +++ b/src/execution/dql/aggregate/avg.rs @@ -20,16 +20,19 @@ use crate::types::evaluator::binary_create; use crate::types::value::DataValue; use crate::types::LogicalType; use std::borrow::Cow; +use std::collections::HashSet; pub struct AvgAccumulator { + distinct_values: Option>, inner: Option, count: usize, result: DataValue, } impl AvgAccumulator { - pub fn new() -> Self { + pub fn new(distinct: bool) -> Self { Self { + distinct_values: distinct.then(HashSet::new), inner: None, count: 0, result: DataValue::Null, @@ -40,6 +43,11 @@ impl AvgAccumulator { impl Accumulator for AvgAccumulator { fn update_value(&mut self, value: &DataValue) -> Result<(), DatabaseError> { if !value.is_null() { + if let Some(values) = &mut self.distinct_values { + if !values.insert(value.clone()) { + return Ok(()); + } + } let acc = if let Some(ref mut inner) = self.inner { inner } else { @@ -68,7 +76,7 @@ impl Accumulator for AvgAccumulator { let value = acc.result().clone().cast(&LogicalType::Double)?; let quantity = DataValue::Float64((self.count as f64).into()); let evaluator = binary_create(Cow::Owned(LogicalType::Double), BinaryOperator::Divide)?; - self.result = evaluator.binary_eval(&value, &quantity)?; + self.result = evaluator.binary_eval(Cow::Borrowed(&value), &quantity)?; Ok(()) } @@ -81,13 +89,14 @@ impl Accumulator for AvgAccumulator { } } +// GRCOV_EXCL_START #[cfg(test)] mod tests { use super::*; #[test] fn invalidate_cached_result_after_update() -> Result<(), DatabaseError> { - let mut accumulator = AvgAccumulator::new(); + let mut accumulator = AvgAccumulator::new(false); accumulator.update_value(&DataValue::Int32(2))?; accumulator.evaluate()?; let first = accumulator.result().clone(); @@ -103,14 +112,41 @@ mod tests { Ok(()) } + #[test] + fn distinct_values_ignore_nulls_and_update_cached_result() -> Result<(), DatabaseError> { + let mut accumulator = AvgAccumulator::new(true); + accumulator.update_value(&DataValue::Null)?; + accumulator.evaluate()?; + assert_eq!(accumulator.result(), &DataValue::Null); + + for value in [1, 0, 2, 2] { + accumulator.update_value(&DataValue::Int32(value))?; + } + accumulator.evaluate()?; + assert_eq!(accumulator.result(), &DataValue::Float64(1.0.into())); + + accumulator.update_value(&DataValue::Int32(2))?; + accumulator.update_value(&DataValue::Null)?; + accumulator.evaluate()?; + assert_eq!(accumulator.result(), &DataValue::Float64(1.0.into())); + + accumulator.update_value(&DataValue::Int32(3))?; + accumulator.evaluate()?; + assert_eq!( + Box::new(accumulator).result_owned(), + DataValue::Float64(1.5.into()) + ); + Ok(()) + } + #[test] fn empty_and_unsigned_results() -> Result<(), DatabaseError> { - let mut empty = AvgAccumulator::new(); + let mut empty = AvgAccumulator::new(false); empty.evaluate()?; assert_eq!(empty.result(), &DataValue::Null); assert_eq!(Box::new(empty).result_owned(), DataValue::Null); - let mut accumulator = AvgAccumulator::new(); + let mut accumulator = AvgAccumulator::new(false); accumulator.update_value(&DataValue::UInt32(2))?; accumulator.update_value(&DataValue::UInt32(4))?; accumulator.evaluate()?; @@ -121,3 +157,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/execution/dql/aggregate/hash_agg.rs b/src/execution/dql/aggregate/hash_agg.rs index 7880d79a..927913dd 100644 --- a/src/execution/dql/aggregate/hash_agg.rs +++ b/src/execution/dql/aggregate/hash_agg.rs @@ -110,6 +110,7 @@ impl<'a, T: Transaction + 'a> ExecutorNode<'a, T> for HashAggExecutor<'a> { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod test { use crate::catalog::{ColumnCatalog, ColumnDesc}; @@ -232,3 +233,4 @@ mod test { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/execution/dql/aggregate/min_max.rs b/src/execution/dql/aggregate/min_max.rs index 85de9240..bc88ffdb 100644 --- a/src/execution/dql/aggregate/min_max.rs +++ b/src/execution/dql/aggregate/min_max.rs @@ -44,7 +44,9 @@ impl Accumulator for MinMaxAccumulator { if !value.is_null() { if !self.result.is_null() { let evaluator = binary_create(Cow::Owned(value.logical_type()), self.op)?; - if let DataValue::Boolean(result) = evaluator.binary_eval(&self.result, value)? { + if let DataValue::Boolean(result) = + evaluator.binary_eval(Cow::Borrowed(&self.result), value)? + { result } else { return Err(DatabaseError::InvalidType); diff --git a/src/execution/dql/aggregate/mod.rs b/src/execution/dql/aggregate/mod.rs index 2735f699..87c685b5 100644 --- a/src/execution/dql/aggregate/mod.rs +++ b/src/execution/dql/aggregate/mod.rs @@ -64,7 +64,7 @@ pub(crate) fn create_accumulator( (AggKind::Sum, true) => Box::new(DistinctSumAccumulator::new(ty)?), (AggKind::Min, _) => Box::new(MinMaxAccumulator::new(false)), (AggKind::Max, _) => Box::new(MinMaxAccumulator::new(true)), - (AggKind::Avg, _) => Box::new(AvgAccumulator::new()), + (AggKind::Avg, distinct) => Box::new(AvgAccumulator::new(distinct)), }) } diff --git a/src/execution/dql/aggregate/stream_agg.rs b/src/execution/dql/aggregate/stream_agg.rs index a9cb91af..63b58304 100644 --- a/src/execution/dql/aggregate/stream_agg.rs +++ b/src/execution/dql/aggregate/stream_agg.rs @@ -119,6 +119,7 @@ impl<'a, T: Transaction + 'a> ExecutorNode<'a, T> for StreamAggExecutor<'a> { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::StreamAggExecutor; @@ -242,3 +243,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/execution/dql/aggregate/stream_distinct.rs b/src/execution/dql/aggregate/stream_distinct.rs index 1702fe10..da1250ee 100644 --- a/src/execution/dql/aggregate/stream_distinct.rs +++ b/src/execution/dql/aggregate/stream_distinct.rs @@ -80,6 +80,7 @@ impl<'a, T: Transaction + 'a> ExecutorNode<'a, T> for StreamDistinctExecutor<'a> } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use crate::catalog::{ColumnCatalog, ColumnDesc}; @@ -254,3 +255,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/execution/dql/aggregate/sum.rs b/src/execution/dql/aggregate/sum.rs index 9c5a9f9c..1e220167 100644 --- a/src/execution/dql/aggregate/sum.rs +++ b/src/execution/dql/aggregate/sum.rs @@ -28,10 +28,9 @@ pub struct SumAccumulator { impl SumAccumulator { pub fn new(ty: Cow<'_, LogicalType>) -> Result { - // `sum(null)` binds with the type of the NULL literal; it never sees a - // non-NULL value, so the result is NULL like in release builds. - debug_assert!(ty.is_numeric() || matches!(ty.as_ref(), LogicalType::SqlNull)); - + // The binder does not restrict SUM/AVG argument types, so `binary_create` + // decides: types without `+` (e.g. VARCHAR) return an error instead of + // panicking, and `sum(null)` uses the SqlNull evaluator. Ok(Self { result: DataValue::Null, evaluator: binary_create(ty, BinaryOperator::Plus)?, @@ -45,7 +44,9 @@ impl Accumulator for SumAccumulator { if self.result.is_null() { self.result = value.clone(); } else { - self.result = self.evaluator.binary_eval(&self.result, value)?; + self.result = self + .evaluator + .binary_eval(Cow::Borrowed(&self.result), value)?; } } diff --git a/src/execution/dql/external_sort.rs b/src/execution/dql/external_sort.rs index cd47fd57..c8a914c2 100644 --- a/src/execution/dql/external_sort.rs +++ b/src/execution/dql/external_sort.rs @@ -279,6 +279,7 @@ fn merge_pass<'on_flush>( Ok(target) } +// GRCOV_EXCL_START #[cfg(test)] mod test { use super::{finish_sort, sort_segment, Run, MERGE_FAN_IN}; @@ -507,3 +508,4 @@ mod test { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/execution/dql/join/hash_join.rs b/src/execution/dql/join/hash_join.rs index d2f42819..b34405e6 100644 --- a/src/execution/dql/join/hash_join.rs +++ b/src/execution/dql/join/hash_join.rs @@ -364,6 +364,7 @@ impl<'a, T: Transaction + 'a> ExecutorNode<'a, T> for HashJoin { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod test { use crate::catalog::{ColumnCatalog, ColumnDesc}; @@ -842,3 +843,4 @@ mod test { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/execution/dql/join/mod.rs b/src/execution/dql/join/mod.rs index b0165929..c72cae3f 100644 --- a/src/execution/dql/join/mod.rs +++ b/src/execution/dql/join/mod.rs @@ -62,6 +62,7 @@ pub fn joins_nullable(join_type: &JoinType) -> (bool, bool) { } } +// GRCOV_EXCL_START #[cfg(test)] mod tests { use super::RowBitmap; @@ -117,3 +118,4 @@ mod tests { bitmap.insert(1); } } +// GRCOV_EXCL_STOP diff --git a/src/execution/dql/join/nested_loop_join.rs b/src/execution/dql/join/nested_loop_join.rs index 72e4a9b0..e31a2d7e 100644 --- a/src/execution/dql/join/nested_loop_join.rs +++ b/src/execution/dql/join/nested_loop_join.rs @@ -355,6 +355,7 @@ impl<'a> NestedLoopJoin<'a> { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod test { use super::*; @@ -1256,3 +1257,4 @@ mod test { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/execution/dql/mark_apply.rs b/src/execution/dql/mark_apply.rs index 4d9f6c6d..761c1335 100644 --- a/src/execution/dql/mark_apply.rs +++ b/src/execution/dql/mark_apply.rs @@ -364,6 +364,7 @@ impl<'a> MarkApply<'a> { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::*; @@ -1032,3 +1033,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/execution/dql/mod.rs b/src/execution/dql/mod.rs index 0bab43cd..461d9523 100644 --- a/src/execution/dql/mod.rs +++ b/src/execution/dql/mod.rs @@ -38,6 +38,7 @@ pub(crate) mod union; pub(crate) mod values; pub(crate) mod window; +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] pub(crate) mod test { use crate::iter_ext::Itertools; @@ -49,3 +50,4 @@ pub(crate) mod test { .collect_vec() } } +// GRCOV_EXCL_STOP diff --git a/src/execution/dql/recursive_cte.rs b/src/execution/dql/recursive_cte.rs index 654ec268..eb8c06c2 100644 --- a/src/execution/dql/recursive_cte.rs +++ b/src/execution/dql/recursive_cte.rs @@ -1,5 +1,7 @@ +// GRCOV_EXCL_START #[cfg(test)] use crate::planner::PlanArena; +// GRCOV_EXCL_STOP // Copyright 2024 KipData/KiteSQL // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -320,6 +322,7 @@ impl<'a, T: Transaction + 'a> ExecutorNode<'a, T> for RecursiveScan { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::*; @@ -479,3 +482,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/execution/dql/scalar_query_init.rs b/src/execution/dql/scalar_query_init.rs index 42bd61eb..449b870d 100644 --- a/src/execution/dql/scalar_query_init.rs +++ b/src/execution/dql/scalar_query_init.rs @@ -175,6 +175,7 @@ impl<'a, T: Transaction + 'a> ExecutorNode<'a, T> for ScalarQueryInit<'a> { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::*; @@ -255,3 +256,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/execution/dql/sort.rs b/src/execution/dql/sort.rs index 402e8088..cec626f6 100644 --- a/src/execution/dql/sort.rs +++ b/src/execution/dql/sort.rs @@ -215,6 +215,7 @@ impl<'a, T: Transaction + 'a> ExecutorNode<'a, T> for Sort<'a> { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod test { use crate::catalog::{ColumnCatalog, ColumnDesc}; @@ -760,3 +761,4 @@ mod test { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/execution/dql/top_k.rs b/src/execution/dql/top_k.rs index 38ec59e5..ac925004 100644 --- a/src/execution/dql/top_k.rs +++ b/src/execution/dql/top_k.rs @@ -194,6 +194,7 @@ impl<'a, T: Transaction + 'a> ExecutorNode<'a, T> for TopK<'a> { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] #[allow(clippy::mutable_key_type)] mod test { @@ -850,3 +851,4 @@ mod test { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/execution/mod.rs b/src/execution/mod.rs index a43b2421..64c29a74 100644 --- a/src/execution/mod.rs +++ b/src/execution/mod.rs @@ -1,5 +1,7 @@ +// GRCOV_EXCL_START #[cfg(test)] use crate::planner::PlanArena; +// GRCOV_EXCL_STOP // Copyright 2024 KipData/KiteSQL // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -924,6 +926,7 @@ where } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod test_utils { use super::*; @@ -1063,3 +1066,4 @@ mod test { assert_eq!(arena.nodes.items.as_ptr(), slot); } } +// GRCOV_EXCL_STOP diff --git a/src/execution/spill/codec.rs b/src/execution/spill/codec.rs index 6dc2b89d..37ec0e17 100644 --- a/src/execution/spill/codec.rs +++ b/src/execution/spill/codec.rs @@ -257,6 +257,7 @@ impl SpillCodec for Tuple { fn estimated_dynamic_value_size(value: &DataValue) -> usize { match value { DataValue::Utf8 { value, .. } => value.capacity(), + DataValue::Binary { value, .. } => value.capacity(), DataValue::Tuple(values) => values .capacity() .saturating_mul(size_of::()) @@ -269,3 +270,174 @@ fn estimated_dynamic_value_size(value: &DataValue) -> usize { _ => 0, } } + +// GRCOV_EXCL_START +#[cfg(test)] +mod tests { + use super::*; + use crate::types::value::Utf8Type; + use crate::types::{CharLengthUnits, LogicalType}; + use std::fmt::Debug; + + fn round_trip(value: &T) -> Result<(), DatabaseError> { + let mut bytes = Vec::new(); + value.encode(&mut bytes)?; + let mut reader = bytes.as_slice(); + assert_eq!(&T::decode(&mut reader)?, value); + assert!(reader.is_empty()); + assert!(value.estimated_size() >= size_of::()); + Ok(()) + } + + fn values() -> Vec { + let values = vec![ + DataValue::Binary { + value: vec![0, 255, 128], + len: Some(8), + }, + DataValue::from(Vec::::new()), + DataValue::Null, + DataValue::Boolean(false), + DataValue::Boolean(true), + DataValue::Int8(i8::MIN), + DataValue::Int16(i16::MAX), + DataValue::Int32(i32::MIN), + DataValue::Int64(i64::MAX), + DataValue::UInt8(u8::MAX), + DataValue::UInt16(u16::MAX), + DataValue::UInt32(u32::MAX), + DataValue::UInt64(u64::MAX), + DataValue::Float32((-1.25).into()), + DataValue::Float64(2.5.into()), + DataValue::Float64(f64::INFINITY.into()), + DataValue::Utf8 { + value: "text中\0".into(), + ty: Utf8Type::Variable(Some(20)), + unit: CharLengthUnits::Characters, + }, + DataValue::Utf8 { + value: "fixed".into(), + ty: Utf8Type::Fixed(8), + unit: CharLengthUnits::Octets, + }, + DataValue::new_utf8(String::new()), + DataValue::Date32(-1), + DataValue::Date64(1_700_000_000), + DataValue::Time32(12345, 3), + DataValue::Time64(-12345, 6, false), + DataValue::Time64(12345, 6, true), + DataValue::Tuple(Vec::new()), + DataValue::Tuple(vec![ + DataValue::new_utf8("nested".into()), + DataValue::Tuple(vec![DataValue::Null, DataValue::Int32(7)]), + ]), + ]; + #[cfg(feature = "decimal")] + let values = { + let mut values = values; + values.push(DataValue::Decimal(rust_decimal::Decimal::new(-12345, 3))); + values + }; + values + } + + #[test] + fn data_value_codec_covers_all_materialized_variants() -> Result<(), DatabaseError> { + for value in values() { + round_trip(&value)?; + } + assert!(matches!( + DataValue::Parameter { + id: 1, + ty: LogicalType::Integer + } + .encode(&mut Vec::new()), + Err(DatabaseError::InvalidValue(_)) + )); + assert!(matches!( + DataValue::decode(&mut &[255][..]), + Err(DatabaseError::InvalidValue(_)) + )); + assert!(DataValue::decode(&mut &[7][..]).is_err()); + Ok(()) + } + + #[test] + fn composed_codecs_cover_containers_tuples_and_sort_rows() -> Result<(), DatabaseError> { + let values = values(); + round_trip(&Vec::::new())?; + round_trip(&values)?; + round_trip(&Option::::None)?; + round_trip(&Some(DataValue::Null))?; + round_trip(&Some(DataValue::new_utf8("owned".into())))?; + round_trip(&(Some(DataValue::Int32(3)), values.clone()))?; + for is_outer_value in [false, true] { + let reference = ScalarQueryRef { + arena_id: 1, + pos: 7, + is_outer_value, + }; + round_trip(&reference)?; + round_trip(&(reference, DataValue::Null))?; + } + for pk in [None, Some(DataValue::new_utf8("primary".into()))] { + let tuple = Tuple::new(pk, values.clone()); + round_trip(&tuple)?; + let row = SortRow { + sort_values: vec![DataValue::Int32(2), DataValue::Null], + tuple, + outer_values: vec![( + ScalarQueryRef { + arena_id: 0, + pos: 3, + is_outer_value: true, + }, + DataValue::new_utf8("scalar".into()), + )], + }; + let mut bytes = Vec::new(); + row.encode(&mut bytes)?; + let mut reader = bytes.as_slice(); + let decoded = SortRow::decode(&mut reader)?; + assert!(reader.is_empty()); + assert_eq!(decoded.sort_values, row.sort_values); + assert_eq!(decoded.tuple, row.tuple); + assert_eq!(decoded.outer_values, row.outer_values); + assert!(row.estimated_size() >= size_of::()); + } + assert!(matches!( + Option::::decode(&mut &[2][..]), + Err(DatabaseError::InvalidValue(_)) + )); + assert!(Vec::::decode(&mut &[1, 0, 0, 0][..]).is_err()); + assert!(<(DataValue, DataValue)>::decode(&mut &[0][..]).is_err()); + Ok(()) + } + + #[test] + fn scalar_reference_spill_rejects_invalid_and_truncated_markers() -> Result<(), DatabaseError> { + for is_outer_value in [false, true] { + let reference = ScalarQueryRef { + arena_id: 2, + pos: 17, + is_outer_value, + }; + let value = (reference, Some(DataValue::Null)); + let mut bytes = Vec::new(); + value.encode(&mut bytes)?; + assert_eq!( + <(ScalarQueryRef, Option)>::decode(&mut bytes.as_slice())?, + value + ); + assert!(value.estimated_size() >= size_of_val(&value)); + assert!(ScalarQueryRef::decode(&mut &bytes[..16]).is_err()); + bytes[16] = 2; + assert!(matches!( + ScalarQueryRef::decode(&mut bytes.as_slice()), + Err(DatabaseError::InvalidValue(_)) + )); + } + Ok(()) + } +} +// GRCOV_EXCL_STOP diff --git a/src/execution/spill/mod.rs b/src/execution/spill/mod.rs index 1fbb91eb..1a293485 100644 --- a/src/execution/spill/mod.rs +++ b/src/execution/spill/mod.rs @@ -81,6 +81,7 @@ impl<'on_flush, T: SpillCodec> SpillVec<'on_flush, T> { } } + // GRCOV_EXCL_START #[cfg(test)] pub(crate) fn limit(mut self, max_rows: usize, max_bytes: usize) -> Self { assert!(max_rows > 0, "spill row limit must be positive"); @@ -91,6 +92,7 @@ impl<'on_flush, T: SpillCodec> SpillVec<'on_flush, T> { } self } + // GRCOV_EXCL_STOP pub(crate) fn on_flush(mut self, on_flush: F) -> Self where @@ -408,6 +410,7 @@ fn create_spill_file() -> Result<(File, PathBuf), DatabaseError> { } } +// GRCOV_EXCL_START #[cfg(test)] mod tests { use super::*; @@ -767,3 +770,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/expression/agg.rs b/src/expression/agg.rs index 1761d03b..13198aef 100644 --- a/src/expression/agg.rs +++ b/src/expression/agg.rs @@ -47,7 +47,7 @@ impl AggKind { pub fn allow_distinct(&self) -> bool { match self { - AggKind::Avg => false, + AggKind::Avg => true, AggKind::Max => false, AggKind::Min => false, AggKind::Sum => true, diff --git a/src/expression/eq_col.rs b/src/expression/eq_col.rs index 11fd3be8..6df7123b 100644 --- a/src/expression/eq_col.rs +++ b/src/expression/eq_col.rs @@ -1,5 +1,7 @@ +// GRCOV_EXCL_START #[cfg(test)] use crate::planner::PlanArena; +// GRCOV_EXCL_STOP // Copyright 2024 KipData/KiteSQL // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -544,6 +546,7 @@ impl ExprVisitor for EqIgnoreColRefPosVisitor<'_, '_> { } } +// GRCOV_EXCL_START #[cfg(test)] mod tests { use super::*; @@ -593,6 +596,143 @@ mod tests { fn compares_every_scalar_expression_variant() -> Result<(), DatabaseError> { let table_arena = TableArenaCell::default(); let mut arena = PlanArena::new(&table_arena); + let id = arena.alloc_scalar_query_ref(false); + let other = arena.alloc_scalar_query_ref(false); + assert_case( + &mut arena, + ScalarExpression::InitValue { + id, + ty: LogicalType::Integer, + }, + ScalarExpression::InitValue { + id, + ty: LogicalType::Integer, + }, + ScalarExpression::InitValue { + id: other, + ty: LogicalType::Integer, + }, + ); + assert_case( + &mut arena, + ScalarExpression::InitValue { + id, + ty: LogicalType::Integer, + }, + ScalarExpression::InitValue { + id, + ty: LogicalType::Integer, + }, + ScalarExpression::InitValue { + id, + ty: LogicalType::Bigint, + }, + ); + assert_case( + &mut arena, + ScalarExpression::OuterValue { + id, + ty: LogicalType::Integer, + }, + ScalarExpression::OuterValue { + id, + ty: LogicalType::Integer, + }, + ScalarExpression::OuterValue { + id: other, + ty: LogicalType::Integer, + }, + ); + assert_case( + &mut arena, + ScalarExpression::OuterValue { + id, + ty: LogicalType::Integer, + }, + ScalarExpression::OuterValue { + id, + ty: LogicalType::Integer, + }, + ScalarExpression::OuterValue { + id, + ty: LogicalType::Bigint, + }, + ); + assert_case( + &mut arena, + ScalarExpression::OuterParam { + id, + ty: LogicalType::Integer, + }, + ScalarExpression::OuterParam { + id, + ty: LogicalType::Integer, + }, + ScalarExpression::OuterParam { + id: other, + ty: LogicalType::Integer, + }, + ); + assert_case( + &mut arena, + ScalarExpression::OuterParam { + id, + ty: LogicalType::Integer, + }, + ScalarExpression::OuterParam { + id, + ty: LogicalType::Integer, + }, + ScalarExpression::OuterParam { + id, + ty: LogicalType::Bigint, + }, + ); + assert_case( + &mut arena, + ScalarExpression::InitValue { + id, + ty: LogicalType::Integer, + }, + ScalarExpression::InitValue { + id, + ty: LogicalType::Integer, + }, + ScalarExpression::OuterValue { + id, + ty: LogicalType::Integer, + }, + ); + assert_case( + &mut arena, + ScalarExpression::InitValue { + id, + ty: LogicalType::Integer, + }, + ScalarExpression::InitValue { + id, + ty: LogicalType::Integer, + }, + ScalarExpression::OuterParam { + id, + ty: LogicalType::Integer, + }, + ); + assert_case( + &mut arena, + ScalarExpression::OuterValue { + id, + ty: LogicalType::Integer, + }, + ScalarExpression::OuterValue { + id, + ty: LogicalType::Integer, + }, + ScalarExpression::OuterParam { + id, + ty: LogicalType::Integer, + }, + ); let lhs_column = arena.alloc_column(ColumnCatalog::new( "c1".to_string(), @@ -1042,3 +1182,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/expression/evaluator.rs b/src/expression/evaluator.rs index 7cffc4e0..51212489 100644 --- a/src/expression/evaluator.rs +++ b/src/expression/evaluator.rs @@ -1,5 +1,7 @@ +// GRCOV_EXCL_START #[cfg(test)] use crate::planner::PlanArena; +// GRCOV_EXCL_STOP // Copyright 2024 KipData/KiteSQL // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -82,7 +84,7 @@ impl ScalarExpression { } => { let value = arena.expression(*expr).eval(arena, tuple)?; if let Some(evaluator) = evaluator { - evaluator.eval(&value).map(Cow::Owned) + evaluator.eval(value.into_owned()).map(Cow::Owned) } else { Ok(value) } @@ -107,7 +109,7 @@ impl ScalarExpression { evaluator .as_ref() .ok_or(DatabaseError::EvaluatorNotFound)? - .binary_eval(&left, &right) + .binary_eval(left, &right) .map(Cow::Owned) } ScalarExpression::IsNull { expr, negated } => { @@ -132,7 +134,7 @@ impl ScalarExpression { let mut saw_null = false; for arg in args { let arg_value = arena.expression(*arg).eval(arena, tuple)?; - match eq.binary_eval(&value, &arg_value)? { + match eq.binary_eval(Cow::Borrowed(&value), &arg_value)? { DataValue::Boolean(true) => { return Ok(Cow::Owned(DataValue::Boolean(!negated))) } @@ -177,11 +179,11 @@ impl ScalarExpression { let ge = left_evaluator .as_ref() .ok_or(DatabaseError::EvaluatorNotFound)? - .binary_eval(&value, &left)?; + .binary_eval(Cow::Borrowed(&value), &left)?; let le = right_evaluator .as_ref() .ok_or(DatabaseError::EvaluatorNotFound)? - .binary_eval(&value, &right)?; + .binary_eval(Cow::Borrowed(&value), &right)?; let is_between = match (ge, le) { (DataValue::Boolean(false), _) | (_, DataValue::Boolean(false)) => false, (DataValue::Boolean(true), DataValue::Boolean(true)) => true, @@ -352,7 +354,7 @@ impl ScalarExpression { when_value = cast_cow(when_value, &ty)?; let evaluator = binary_create(Cow::Owned(ty), BinaryOperator::Eq)?; evaluator - .binary_eval(operand_value, &when_value)? + .binary_eval(Cow::Borrowed(operand_value), &when_value)? .is_true()? } else { when_value.is_true()? @@ -413,6 +415,7 @@ fn trim_string(value: &str, trim_what: &str, trim_where: Option) trimmed.to_string() } +// GRCOV_EXCL_START #[cfg(test)] mod tests { use super::*; @@ -661,3 +664,4 @@ mod tests { assert_eq!(trim_string("hello", "", None), "hello"); } } +// GRCOV_EXCL_STOP diff --git a/src/expression/mod.rs b/src/expression/mod.rs index e84a09a6..af3629c9 100644 --- a/src/expression/mod.rs +++ b/src/expression/mod.rs @@ -1084,6 +1084,7 @@ impl fmt::Display for UnaryOperator { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod test { use crate::catalog::{ColumnCatalog, ColumnDesc}; @@ -1574,3 +1575,4 @@ mod test { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/expression/range_detacher.rs b/src/expression/range_detacher.rs index 3ac5f5fb..211e1481 100644 --- a/src/expression/range_detacher.rs +++ b/src/expression/range_detacher.rs @@ -1255,6 +1255,7 @@ impl fmt::Display for Range { } } +// GRCOV_EXCL_START #[cfg(test)] pub(crate) mod test_support { use super::*; @@ -2748,6 +2749,74 @@ mod test { Ok(()) } + #[test] + fn parameterized_range_union_keeps_residual_until_values_are_bound() -> Result<(), DatabaseError> + { + let parameter = |id| DataValue::Parameter { + id, + ty: LogicalType::Integer, + }; + let left = Range::Scope { + min: Bound::Included(parameter(1)), + max: Bound::Unbounded, + }; + let right = Range::Scope { + min: Bound::Unbounded, + max: Bound::Excluded(parameter(2)), + }; + assert_eq!( + RangeDetacher::::merge_binary( + BinaryOperator::Or, + left.clone(), + right.clone() + ), + Err((left.clone(), right.clone())), + ); + let mut ranges = Range::SortedRanges(vec![left, right]); + let low = DataValue::Int32(8); + let high = DataValue::Int32(3); + ranges.bind_parameters(&|id| match id { + 1 => Some(&low), + 2 => Some(&high), + _ => None, + })?; + assert!(!ranges.has_parameter()); + assert_eq!( + ranges, + Range::SortedRanges(vec![ + Range::Scope { + min: Bound::Included(low), + max: Bound::Unbounded + }, + Range::Scope { + min: Bound::Unbounded, + max: Bound::Excluded(high) + }, + ]) + ); + Ok(()) + } + + #[test] + fn intersection_failure_restores_inputs_after_appending_a_match() { + let scope = |min, max| Range::Scope { + min: Bound::Included(min), + max: Bound::Included(max), + }; + let left = scope(DataValue::Int32(1), DataValue::Int32(9)); + let right = vec![ + Range::Eq(DataValue::Int32(2)), + scope( + DataValue::new_utf8("a".into()), + DataValue::new_utf8("z".into()), + ), + ]; + assert_eq!( + RangeDetacher::::intersect_ranges(left.clone(), right.clone()), + Err((left, Range::SortedRanges(right))), + ); + } + #[test] fn test_range_merge_preserves_open_closed_boundaries() { let gt_one = Range::Scope { @@ -2990,3 +3059,4 @@ mod test { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/expression/simplify.rs b/src/expression/simplify.rs index ee395f7b..59aa9d9f 100644 --- a/src/expression/simplify.rs +++ b/src/expression/simplify.rs @@ -83,7 +83,7 @@ impl ExprVisitorMut for ConstantCalculator { let evaluator = binary_create(Cow::Borrowed(&ty), *op)?; let left_val = left_val.clone().cast(&ty)?; let right_val = right_val.clone().cast(&ty)?; - let value = evaluator.binary_eval(&left_val, &right_val)?; + let value = evaluator.binary_eval(Cow::Owned(left_val), &right_val)?; *expr = ScalarExpression::Constant(value); } } @@ -400,7 +400,7 @@ impl Simplify { let right = right.cast(ty).ok()?; binary_create(Cow::Borrowed(ty), op) .ok()? - .binary_eval(&left, &right) + .binary_eval(Cow::Owned(left), &right) .ok() } @@ -545,11 +545,11 @@ impl ExprRef { return None; } if let Some(evaluator) = evaluator { - evaluator.binary_eval(&left, &right) + evaluator.binary_eval(Cow::Owned(left), &right) } else { binary_create(Cow::Borrowed(ty), *op) .ok()? - .binary_eval(&left, &right) + .binary_eval(Cow::Owned(left), &right) } .ok() } diff --git a/src/optimizer/core/cm_sketch.rs b/src/optimizer/core/cm_sketch.rs index 04dd0b71..76ee3a7d 100644 --- a/src/optimizer/core/cm_sketch.rs +++ b/src/optimizer/core/cm_sketch.rs @@ -397,6 +397,7 @@ impl ReferenceSerialization for CountMinSketch { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use crate::expression::range_detacher::Range; @@ -571,3 +572,4 @@ mod tests { assert_eq!(decoded.estimate(&7), 23); } } +// GRCOV_EXCL_STOP diff --git a/src/optimizer/core/histogram.rs b/src/optimizer/core/histogram.rs index d1352a58..a2f28dcb 100644 --- a/src/optimizer/core/histogram.rs +++ b/src/optimizer/core/histogram.rs @@ -213,28 +213,28 @@ impl BoundComparator { fn lt(&self, value: &DataValue, target: &DataValue) -> Result { Ok(matches!( - self.lt.binary_eval(value, target)?, + self.lt.binary_eval(Cow::Borrowed(value), target)?, DataValue::Boolean(true) )) } fn lte(&self, value: &DataValue, target: &DataValue) -> Result { Ok(matches!( - self.lte.binary_eval(value, target)?, + self.lte.binary_eval(Cow::Borrowed(value), target)?, DataValue::Boolean(true) )) } fn gt(&self, value: &DataValue, target: &DataValue) -> Result { Ok(matches!( - self.gt.binary_eval(value, target)?, + self.gt.binary_eval(Cow::Borrowed(value), target)?, DataValue::Boolean(true) )) } fn gte(&self, value: &DataValue, target: &DataValue) -> Result { Ok(matches!( - self.gte.binary_eval(value, target)?, + self.gte.binary_eval(Cow::Borrowed(value), target)?, DataValue::Boolean(true) )) } @@ -687,6 +687,7 @@ impl HistogramMeta { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use crate::errors::DatabaseError; @@ -1565,3 +1566,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/optimizer/core/hll.rs b/src/optimizer/core/hll.rs index 66a66498..8dae20ae 100644 --- a/src/optimizer/core/hll.rs +++ b/src/optimizer/core/hll.rs @@ -123,6 +123,7 @@ impl HyperLogLog { } } +// GRCOV_EXCL_START #[cfg(test)] mod tests { use super::HyperLogLog; @@ -149,3 +150,4 @@ mod tests { assert!(error < 0.03, "estimate={estimate}, error={error}"); } } +// GRCOV_EXCL_STOP diff --git a/src/optimizer/core/kll_sketch.rs b/src/optimizer/core/kll_sketch.rs index 5a6697fb..4fa6a9ff 100644 --- a/src/optimizer/core/kll_sketch.rs +++ b/src/optimizer/core/kll_sketch.rs @@ -106,6 +106,7 @@ impl KllSketchBuilder { }) } + // GRCOV_EXCL_START #[cfg(test)] fn len(&self) -> usize { self.len @@ -120,6 +121,7 @@ impl KllSketchBuilder { fn retained_len(&self) -> usize { self.levels.iter().map(Vec::len).sum() } + // GRCOV_EXCL_STOP fn update_bounds(&mut self, value: &DataValue) -> Result<(), DatabaseError> { if let Some(min_value) = &self.min_value { @@ -344,6 +346,7 @@ fn compare_values(left: &DataValue, right: &DataValue) -> Ordering { .expect("KLL sketch values must be mutually comparable") } +// GRCOV_EXCL_START #[cfg(test)] mod tests { use super::KllSketchBuilder; @@ -449,3 +452,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/optimizer/core/statistics_meta.rs b/src/optimizer/core/statistics_meta.rs index c3ff7177..d0a3260f 100644 --- a/src/optimizer/core/statistics_meta.rs +++ b/src/optimizer/core/statistics_meta.rs @@ -154,6 +154,7 @@ impl StatisticsMeta { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use crate::errors::DatabaseError; @@ -215,3 +216,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/optimizer/core/top_n.rs b/src/optimizer/core/top_n.rs index 62773c5e..6d9656ae 100644 --- a/src/optimizer/core/top_n.rs +++ b/src/optimizer/core/top_n.rs @@ -223,6 +223,7 @@ impl ColumnTopN { } } +// GRCOV_EXCL_START #[cfg(test)] mod tests { use super::ColumnTopN; @@ -271,3 +272,4 @@ mod tests { assert_eq!(top_n.get(&DataValue::Null), None); } } +// GRCOV_EXCL_STOP diff --git a/src/optimizer/heuristic/optimizer.rs b/src/optimizer/heuristic/optimizer.rs index b0b455c9..8b853407 100644 --- a/src/optimizer/heuristic/optimizer.rs +++ b/src/optimizer/heuristic/optimizer.rs @@ -617,6 +617,7 @@ impl ImplementationRuleIndex { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use crate::binder::{Binder, BinderContext}; @@ -755,6 +756,7 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP #[derive(Clone, Default)] pub struct HepOptimizerPipeline { diff --git a/src/optimizer/plan_utils.rs b/src/optimizer/plan_utils.rs index e2aa752b..94c9f6c4 100644 --- a/src/optimizer/plan_utils.rs +++ b/src/optimizer/plan_utils.rs @@ -116,6 +116,7 @@ fn take_childrens(plan: &mut LogicalPlan) -> Childrens { mem::replace(&mut *plan.childrens, Childrens::None) } +// GRCOV_EXCL_START #[cfg(test)] mod tests { use super::*; @@ -217,3 +218,4 @@ mod tests { assert!(!wrap_child_with(&mut parent, 2, Operator::ShowView)); } } +// GRCOV_EXCL_STOP diff --git a/src/optimizer/rule/implementation/mod.rs b/src/optimizer/rule/implementation/mod.rs index f31ae4cc..c1a5879f 100644 --- a/src/optimizer/rule/implementation/mod.rs +++ b/src/optimizer/rule/implementation/mod.rs @@ -307,6 +307,7 @@ impl ImplementationRule for ImplementationRuleImpl { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use crate::binder::test::build_t1_table; @@ -653,3 +654,4 @@ mod tests { ); } } +// GRCOV_EXCL_STOP diff --git a/src/optimizer/rule/normalization/column_pruning.rs b/src/optimizer/rule/normalization/column_pruning.rs index 6653cbf9..85fb9fa8 100644 --- a/src/optimizer/rule/normalization/column_pruning.rs +++ b/src/optimizer/rule/normalization/column_pruning.rs @@ -847,6 +847,7 @@ impl NormalizationRule for ColumnPruning { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use crate::binder::test::build_t1_table; @@ -1117,3 +1118,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/optimizer/rule/normalization/combine_operators.rs b/src/optimizer/rule/normalization/combine_operators.rs index 91d1bf13..b45b8903 100644 --- a/src/optimizer/rule/normalization/combine_operators.rs +++ b/src/optimizer/rule/normalization/combine_operators.rs @@ -327,6 +327,7 @@ impl NormalizationRule for CollapseGroupByAgg { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use crate::binder::test::build_t1_table; @@ -563,3 +564,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/optimizer/rule/normalization/compilation_in_advance.rs b/src/optimizer/rule/normalization/compilation_in_advance.rs index 1fe28b46..e26a2111 100644 --- a/src/optimizer/rule/normalization/compilation_in_advance.rs +++ b/src/optimizer/rule/normalization/compilation_in_advance.rs @@ -67,6 +67,7 @@ impl NormalizationRule for EvaluatorBind { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::*; @@ -250,3 +251,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/optimizer/rule/normalization/elimination.rs b/src/optimizer/rule/normalization/elimination.rs index e37e5a2d..2e483454 100644 --- a/src/optimizer/rule/normalization/elimination.rs +++ b/src/optimizer/rule/normalization/elimination.rs @@ -490,6 +490,7 @@ pub(crate) fn covers( } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::{ @@ -1278,3 +1279,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/optimizer/rule/normalization/min_max_top_k.rs b/src/optimizer/rule/normalization/min_max_top_k.rs index 47a174c9..28bfae1c 100644 --- a/src/optimizer/rule/normalization/min_max_top_k.rs +++ b/src/optimizer/rule/normalization/min_max_top_k.rs @@ -80,6 +80,7 @@ impl NormalizationRule for MinMaxToTopK { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::MinMaxToTopK; @@ -179,3 +180,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/optimizer/rule/normalization/parameterized_index.rs b/src/optimizer/rule/normalization/parameterized_index.rs index bc726d29..8ee20085 100644 --- a/src/optimizer/rule/normalization/parameterized_index.rs +++ b/src/optimizer/rule/normalization/parameterized_index.rs @@ -573,6 +573,7 @@ mod tests { } // GRCOV_EXCL_STOP +// GRCOV_EXCL_START #[cfg(test)] mod inner_join_tests { use super::*; @@ -884,3 +885,4 @@ mod inner_join_tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/optimizer/rule/normalization/pushdown_limit.rs b/src/optimizer/rule/normalization/pushdown_limit.rs index 1bff3ee0..e33dc91c 100644 --- a/src/optimizer/rule/normalization/pushdown_limit.rs +++ b/src/optimizer/rule/normalization/pushdown_limit.rs @@ -129,6 +129,7 @@ impl NormalizationRule for PushLimitIntoScan { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use crate::binder::test::build_t1_table; @@ -233,3 +234,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/optimizer/rule/normalization/pushdown_predicates.rs b/src/optimizer/rule/normalization/pushdown_predicates.rs index 16fd7c0a..6bdef695 100644 --- a/src/optimizer/rule/normalization/pushdown_predicates.rs +++ b/src/optimizer/rule/normalization/pushdown_predicates.rs @@ -572,6 +572,7 @@ impl NormalizationRule for PushJoinPredicateIntoScan { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { @@ -1349,3 +1350,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/optimizer/rule/normalization/simplification.rs b/src/optimizer/rule/normalization/simplification.rs index 052dc486..9de97e4e 100644 --- a/src/optimizer/rule/normalization/simplification.rs +++ b/src/optimizer/rule/normalization/simplification.rs @@ -103,6 +103,7 @@ impl NormalizationRule for SimplifyFilter { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod test { use crate::binder::test::build_t1_table; @@ -687,3 +688,4 @@ mod test { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/orm/mod.rs b/src/orm/mod.rs index dc1d9a80..52c6e582 100644 --- a/src/orm/mod.rs +++ b/src/orm/mod.rs @@ -3057,7 +3057,21 @@ impl_from_data_value_by_method!(f64, double); #[cfg(feature = "decimal")] impl_from_data_value_by_method!(Decimal, decimal); -impl_to_data_value_by_clone!(bool, i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, String); +impl_to_data_value_by_clone!( + bool, + i8, + i16, + i32, + i64, + u8, + u16, + u32, + u64, + f32, + f64, + String, + Vec +); #[cfg(feature = "decimal")] impl_to_data_value_by_clone!(Decimal); @@ -3073,6 +3087,7 @@ macro_rules! impl_model_column_type { }; } +impl_model_column_type!(LogicalType::Varbinary(None); Vec); impl_model_column_type!(LogicalType::Boolean; bool); impl_model_column_type!(LogicalType::Tinyint; i8); impl_model_column_type!(LogicalType::Smallint; i16); @@ -3127,6 +3142,20 @@ mod chrono_orm { } } +impl FromDataValue for Vec { + fn logical_type() -> Option { + LogicalType::type_trans::() + } + + fn from_data_value(value: DataValue) -> Result { + if let DataValue::Binary { value, .. } = value { + Ok(value) + } else { + Err(invalid_from_data_value::(&value)) + } + } +} + impl FromDataValue for String { fn logical_type() -> Option { LogicalType::type_trans::() @@ -3425,6 +3454,7 @@ fn orm_list<'a, E: BindSource<'a>, M: Model>( #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::*; + use crate::expression::BinaryOperator; use crate::types::tuple::{Schema, SchemaView}; use std::sync::Arc; @@ -3739,6 +3769,20 @@ mod tests { Some(9) ); + assert_eq!( + Vec::::from_data_value(DataValue::from(vec![0, 255]))?, + vec![0, 255] + ); + assert_eq!( + vec![0u8, 255].to_data_value(), + DataValue::from(vec![0, 255]) + ); + assert_eq!( + as ModelColumnType>::logical_type(), + LogicalType::Varbinary(None) + ); + assert_eq!(Option::>::from_data_value(DataValue::Null)?, None); + assert!(Vec::::from_data_value(DataValue::Int32(1)).is_err()); assert_eq!(true.to_data_value(), DataValue::Boolean(true)); assert_eq!("name".to_data_value(), DataValue::from("name".to_string())); assert_eq!(Option::::None.to_data_value(), DataValue::Null); @@ -4063,6 +4107,238 @@ mod tests { Ok(()) } + #[test] + fn chained_range_and_function_helpers_preserve_values_and_null_rows( + ) -> Result<(), DatabaseError> { + let database = build_orm_unit_database()?; + let mut iter = database + .bind(|ctx| { + ctx.from::()? + .filter(|e| e.column(OrmUnitUser::age())?.between(18, 20))? + .project_tuple(|e| { + Ok(vec![ + e.column(OrmUnitUser::id())?, + e.column(OrmUnitUser::name())? + .function("upper", std::iter::empty::())?, + ]) + })? + .order_by_expr(|e| Ok(e.column(OrmUnitUser::id())?.asc()))? + .finish() + })? + .project_tuple::<(i32, String)>(); + assert_eq!(iter.next().transpose()?, Some((1, "ALICE".into()))); + assert_eq!(iter.next().transpose()?, Some((2, "BOB".into()))); + assert!(iter.next().transpose()?.is_none()); + iter.done()?; + let mut iter = database + .bind(|ctx| { + ctx.from::()? + .filter(|e| { + let age = e.column(OrmUnitUser::age())?; + age.clone().not_between(18, 20)?.or(age.is_null()) + })? + .project_scalar(OrmUnitUser::id())? + .finish() + })? + .project_value::(); + assert_eq!(iter.next().transpose()?, Some(3)); + assert!(iter.next().transpose()?.is_none()); + iter.done()?; + Ok(()) + } + + #[test] + fn insert_select_maps_columns_and_rejects_invalid_targets() -> Result<(), DatabaseError> { + let database = build_orm_unit_database()?; + database + .bind(|ctx| { + ctx.insert_select::(["name", "id", "age"], |ctx| { + ctx.from::()? + .filter(|e| e.column(OrmUnitUser::id())?.lte(2))? + .project_tuple(|e| { + Ok(vec![ + e.column(OrmUnitUser::name())?, + e.binary(e.column(OrmUnitUser::id())?, BinaryOperator::Plus, 10)?, + e.column(OrmUnitUser::age())?, + ]) + })? + .finish() + }) + })? + .done()?; + database + .bind(|ctx| { + ctx.insert_select::(std::iter::empty::(), |ctx| { + ctx.from::()? + .filter(|e| e.column(OrmUnitUser::id())?.eq(3))? + .project_tuple(|e| { + Ok(vec![ + e.binary(e.column(OrmUnitUser::id())?, BinaryOperator::Plus, 20)?, + e.column(OrmUnitUser::name())?, + e.column(OrmUnitUser::age())?, + ]) + })? + .finish() + }) + })? + .done()?; + assert!(matches!( + database.bind( + |ctx| ctx.insert_select::(["id", "name"], |ctx| { + ctx.from::()? + .project_scalar(OrmUnitUser::id())? + .finish() + }) + ), + Err(DatabaseError::ValuesLenMismatch(2, 1)) + )); + assert!(database + .bind( + |ctx| ctx.insert_select::(["missing"], |ctx| { + ctx.from::()? + .project_scalar(OrmUnitUser::id())? + .finish() + }) + ) + .is_err()); + let mut iter = + database.run("select id,name,age from orm_unit_users where id>3 order by id")?; + let mut rows = Vec::new(); + while iter + .next_tuple(|_, row| rows.push(row.values.clone()))? + .is_some() + {} + iter.done()?; + assert_eq!( + rows, + vec![ + vec![ + DataValue::Int32(11), + DataValue::new_utf8("Alice".into()), + DataValue::Int32(18) + ], + vec![ + DataValue::Int32(12), + DataValue::new_utf8("Bob".into()), + DataValue::Int32(20) + ], + vec![ + DataValue::Int32(23), + DataValue::new_utf8("Cara".into()), + DataValue::Null + ], + ] + ); + Ok(()) + } + + #[test] + fn case_helpers_infer_else_types_and_preserve_null_fallbacks() -> Result<(), DatabaseError> { + let database = build_orm_unit_database()?; + let mut rows = database + .bind(|ctx| { + ctx.from::()? + .project_tuple(|e| { + Ok(vec![ + e.column(OrmUnitUser::id())?, + e.case_when(std::iter::empty::<(bool, i32)>(), Some(7)), + e.case_value( + e.column(OrmUnitUser::id())?, + std::iter::empty::<(i32, String)>(), + Some("fallback".to_string()), + ), + e.case_when([(e.column(OrmUnitUser::age())?.gt(18)?, 1)], None::), + ]) + })? + .order_by(OrmUnitUser::id().asc())? + .finish() + })? + .project_tuple::<(i32, i32, String, Option)>(); + for expected in [ + (1, 7, "fallback".into(), None), + (2, 7, "fallback".into(), Some(1)), + (3, 7, "fallback".into(), None), + ] { + assert_eq!(rows.next().transpose()?, Some(expected)); + } + assert!(rows.next().transpose()?.is_none()); + rows.done()?; + Ok(()) + } + + #[test] + fn grouped_orm_having_reads_the_aggregate_output() -> Result<(), DatabaseError> { + let database = build_orm_unit_database()?; + let mut rows = database + .bind(|ctx| { + ctx.from::()? + .project_tuple(|e| { + Ok(vec![e.column(OrmUnitOrder::user_id())?, e.count_all()?]) + })? + .group_by(|e| e.column(OrmUnitOrder::user_id()))? + .having(|e| e.count_all()?.gt(1))? + .having(|e| e.column(OrmUnitOrder::user_id())?.eq(1))? + .order_by(OrmUnitOrder::user_id().asc())? + .finish() + })? + .project_tuple::<(i32, i32)>(); + assert_eq!(rows.next().transpose()?, Some((1, 2))); + assert!(rows.next().transpose()?.is_none()); + rows.done()?; + assert!(matches!( + database.bind(|ctx| { + ctx.from::()? + .project_tuple(|e| { + Ok(vec![e.column(OrmUnitOrder::user_id())?, e.count_all()?]) + })? + .group_by(|e| e.column(OrmUnitOrder::user_id()))? + .having(|e| e.column(OrmUnitOrder::amount())?.gt(100))? + .finish() + }), + Err(DatabaseError::AggMiss(_)) + )); + Ok(()) + } + + #[test] + fn update_assignments_preserve_unmatched_rows() -> Result<(), DatabaseError> { + let database = build_orm_unit_database()?; + assert!(matches!( + database.bind(|ctx| ctx.mutate::()?.update(|_| Ok(()))), + Err(DatabaseError::ColumnsEmpty) + )); + database + .bind(|ctx| { + ctx.mutate::()? + .filter(|e| e.column(OrmUnitUser::id())?.eq(1))? + .update(|u| { + u.set(OrmUnitUser::name(), "Alicia")?; + u.set_bound_expr( + OrmUnitUser::age(), + ScalarExpression::Constant(DataValue::Int32(25)), + ) + }) + })? + .done()?; + let mut rows = database + .bind(|ctx| { + ctx.from::()? + .project_scalars((OrmUnitUser::id(), OrmUnitUser::name(), OrmUnitUser::age()))? + .order_by(OrmUnitUser::id().asc())? + .finish() + })? + .project_tuple::<(i32, String, Option)>(); + assert_eq!( + rows.next().transpose()?, + Some((1, "Alicia".into(), Some(25))) + ); + assert_eq!(rows.next().transpose()?, Some((2, "Bob".into(), Some(20)))); + assert_eq!(rows.next().transpose()?, Some((3, "Cara".into(), None))); + assert!(rows.next().transpose()?.is_none()); + rows.done()?; + Ok(()) + } + #[test] fn query_builder_wrappers_cover_group_having_and_join_variants() -> Result<(), DatabaseError> { let database = build_orm_unit_database()?; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 1ed6d108..98c76d3c 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -30,9 +30,21 @@ const DIALECT: GenericDialect = GenericDialect {}; /// println!("{:?}", ast); /// ``` pub fn parse_sql>(sql: S) -> Result, ParserError> { - Parser::parse_sql(&DIALECT, sql.as_ref()) + parse_sql_with_recursion_limit(sql, None) } +pub(crate) fn parse_sql_with_recursion_limit>( + sql: S, + recursion_limit: Option, +) -> Result, ParserError> { + let mut parser = Parser::new(&DIALECT); + if let Some(limit) = recursion_limit { + parser = parser.with_recursion_limit(limit); + } + parser.try_with_sql(sql.as_ref())?.parse_statements() +} + +// GRCOV_EXCL_START #[cfg(test)] mod tests { use super::parse_sql; @@ -54,3 +66,4 @@ mod tests { ); } } +// GRCOV_EXCL_STOP diff --git a/src/planner/arena.rs b/src/planner/arena.rs index cf6716a7..8df83e70 100644 --- a/src/planner/arena.rs +++ b/src/planner/arena.rs @@ -1235,6 +1235,89 @@ mod tests { assert_eq!(arena.init_value(result), Some(&DataValue::Null)); } + #[test] + fn materializing_scalar_references_preserves_shared_catalog_ids() -> Result<(), DatabaseError> { + use crate::planner::operator::project::ProjectOperator; + use crate::planner::Childrens; + let catalog = TableArenaCell::default(); + let persistent = catalog.borrow_mut().alloc_scalar_query_ref(false); + let mut arena = PlanArena::new(&catalog); + let param = arena.alloc_scalar_query_ref(false); + let result = arena.alloc_scalar_query_ref(true); + let constant = arena.alloc_scalar_query_ref(false); + let exprs = [ + ScalarExpression::OuterParam { + id: param, + ty: LogicalType::Integer, + }, + ScalarExpression::OuterValue { + id: result, + ty: LogicalType::Integer, + }, + ScalarExpression::InitValue { + id: constant, + ty: LogicalType::Integer, + }, + ScalarExpression::InitValue { + id: persistent, + ty: LogicalType::Integer, + }, + ] + .into_iter() + .map(|expr| arena.alloc_expression(expr)) + .collect::>(); + let input = LogicalPlan::new( + Operator::Project(ProjectOperator { + exprs: exprs.clone(), + }), + Childrens::Only(Box::new(LogicalPlan::new(Operator::Dummy, Childrens::None))), + ); + let source = arena.alloc_expression(ScalarExpression::Constant(DataValue::Int32(5))); + let mut plan = ScalarQueryInitOperator::build( + input, + LogicalPlan::new(Operator::Dummy, Childrens::None), + exprs[1], + vec![(param, source)], + ); + arena.materialize_scalar_queries(&mut plan)?; + let expected_param = ScalarQueryRef { + arena_id: 0, + pos: 1, + is_outer_value: false, + }; + let expected_result = ScalarQueryRef { + arena_id: 0, + pos: 2, + is_outer_value: true, + }; + assert!( + matches!(arena.expression(exprs[0]), ScalarExpression::OuterParam { id, .. } if *id == expected_param) + ); + assert!( + matches!(arena.expression(exprs[1]), ScalarExpression::OuterValue { id, .. } if *id == expected_result) + ); + assert!( + matches!(arena.expression(exprs[2]), ScalarExpression::InitValue { id, .. } if id.arena_id == 0 && id.pos == 3) + ); + assert!( + matches!(arena.expression(exprs[3]), ScalarExpression::InitValue { id, .. } if *id == persistent) + ); + let Operator::ScalarQueryInit(op) = &plan.operator else { + unreachable!() + }; + assert_eq!(op.param_bindings[0].0, expected_param); + arena.reserve_scalar_query_ref(expected_result); + arena.reserve_scalar_query_ref(constant); + let mut view = ExecMetaArena::new(arena); + let allocated = view.alloc_scalar_query_ref(true); + view.reserve_scalar_query_ref(allocated); + view.set_init_value(allocated, DataValue::Null); + let view = view.boxed(); + assert_eq!(view.init_value(allocated), Some(&DataValue::Null)); + assert_eq!(view.arena_id(), 1); + Ok(()) + } + #[test] fn scalar_query_namespaces_follow_arena_parent() -> Result<(), DatabaseError> { let root = TableArenaCell::default(); @@ -1261,6 +1344,8 @@ mod tests { let mut params = ParamArena::new(&plan, &[], &[])?; assert_eq!(params.arena_id(), 2); let param_local = params.alloc_scalar_query_ref(false); + params.reserve_scalar_query_ref(param_local); + params.reserve_scalar_query_ref(local); assert_eq!( param_local, ScalarQueryRef { @@ -1280,6 +1365,11 @@ mod tests { view.set_init_value(local, DataValue::Int32(33)); assert_eq!(view.init_value(catalog), Some(&DataValue::Int32(11))); assert_eq!(view.init_value(local), Some(&DataValue::Int32(33))); + let mut boxed = view.boxed(); + let reference = boxed.alloc_scalar_query_ref(true); + boxed.reserve_scalar_query_ref(reference); + assert_eq!(reference.arena_id, 2); + assert_eq!(reference.pos, param_local.pos + 1); Ok(()) } diff --git a/src/planner/mod.rs b/src/planner/mod.rs index 855277e2..612bcba5 100644 --- a/src/planner/mod.rs +++ b/src/planner/mod.rs @@ -677,6 +677,7 @@ mod tests { } // GRCOV_EXCL_STOP +// GRCOV_EXCL_START #[cfg(test)] pub(crate) mod test { use crate::expression::ScalarExpression; @@ -718,3 +719,4 @@ pub(crate) mod test { } } } +// GRCOV_EXCL_STOP diff --git a/src/planner/operator/join.rs b/src/planner/operator/join.rs index b2e52293..7ae642cc 100644 --- a/src/planner/operator/join.rs +++ b/src/planner/operator/join.rs @@ -272,6 +272,7 @@ pub(crate) fn extract_join_keys( Ok(()) } +// GRCOV_EXCL_START #[cfg(test)] mod tests { use super::*; @@ -298,3 +299,4 @@ mod tests { assert_eq!(operator.plan_impl(), PlanImpl::NestLoopJoin); } } +// GRCOV_EXCL_STOP diff --git a/src/planner/operator/mod.rs b/src/planner/operator/mod.rs index cb8830e9..27d696ef 100644 --- a/src/planner/operator/mod.rs +++ b/src/planner/operator/mod.rs @@ -1,5 +1,7 @@ +// GRCOV_EXCL_START #[cfg(test)] use crate::planner::PlanArena; +// GRCOV_EXCL_STOP // Copyright 2024 KipData/KiteSQL // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/planner/operator/visitor.rs b/src/planner/operator/visitor.rs index b1e3b1ad..668f6768 100644 --- a/src/planner/operator/visitor.rs +++ b/src/planner/operator/visitor.rs @@ -394,6 +394,7 @@ pub fn walk_operator<'a, V: OperatorVisitor<'a>>( } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] pub(crate) mod tests { use super::*; @@ -677,3 +678,4 @@ pub(crate) mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/planner/operator/visitor_mut.rs b/src/planner/operator/visitor_mut.rs index 0255a5c2..10dee0e5 100644 --- a/src/planner/operator/visitor_mut.rs +++ b/src/planner/operator/visitor_mut.rs @@ -1,5 +1,7 @@ +// GRCOV_EXCL_START #[cfg(test)] use crate::planner::PlanArena; +// GRCOV_EXCL_STOP // Copyright 2024 KipData/KiteSQL // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -456,6 +458,7 @@ pub fn walk_mut_operator<'a, V: OperatorVisitorMut<'a>>( } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::*; @@ -539,3 +542,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/planner/plan_keeper.rs b/src/planner/plan_keeper.rs index 60e0dc2c..294f69e6 100644 --- a/src/planner/plan_keeper.rs +++ b/src/planner/plan_keeper.rs @@ -32,6 +32,7 @@ pub(crate) struct PlanKeeper<'p> { } impl<'p> PlanKeeper<'p> { + // GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] pub(crate) fn empty() -> Self { Self { @@ -39,6 +40,7 @@ impl<'p> PlanKeeper<'p> { borrowed: None, } } + // GRCOV_EXCL_STOP pub(crate) fn new(input: PlanInput<'p>) -> Self { match input { diff --git a/src/planner/scalar_query_ref.rs b/src/planner/scalar_query_ref.rs index 6b12f531..51e13769 100644 --- a/src/planner/scalar_query_ref.rs +++ b/src/planner/scalar_query_ref.rs @@ -61,6 +61,7 @@ impl ReferenceSerialization for ScalarQueryRef { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::*; @@ -102,3 +103,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/python.rs b/src/python.rs index cca38f74..91da11a5 100644 --- a/src/python.rs +++ b/src/python.rs @@ -25,7 +25,7 @@ use crate::types::tuple::{SchemaView, Tuple}; use crate::types::value::DataValue; use pyo3::exceptions::{PyRuntimeError, PyValueError}; use pyo3::prelude::*; -use pyo3::types::{PyDict, PyList, PyModule}; +use pyo3::types::{PyBytes, PyDict, PyList, PyModule}; fn to_py_err(err: impl ToString) -> PyErr { PyRuntimeError::new_err(err.to_string()) @@ -51,6 +51,7 @@ fn data_value_to_py(py: Python<'_>, value: &DataValue) -> PyResult { DataValue::UInt16(value) => value.into_py(py), DataValue::UInt32(value) => value.into_py(py), DataValue::UInt64(value) => value.into_py(py), + DataValue::Binary { value, .. } => PyBytes::new(py, value).into_any().unbind(), DataValue::Utf8 { value, .. } => value.clone().into_py(py), DataValue::Date32(_) | DataValue::Date64(_) @@ -353,6 +354,7 @@ fn kite_sql(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { Ok(()) } +// GRCOV_EXCL_START #[cfg(test)] mod tests { use super::kite_sql; @@ -449,6 +451,12 @@ stream.finish() assert streamed == [[0, 0], [1, 11]] db.ddl("drop table my_struct") +db.ddl("create table binary_rows (id int primary key, b varbinary(4))") +db.execute("insert into binary_rows values (1,x'00ff'),(2,x''),(3,null)") +assert [row["values"] for row in db.run("select b from binary_rows order by id").rows()] == [ + [b'\x00\xff'], [b''], [None], +] +db.ddl("drop table binary_rows") "# ), )?; @@ -582,3 +590,4 @@ except ValueError as exc: }) } } +// GRCOV_EXCL_STOP diff --git a/src/serdes/char_length_units.rs b/src/serdes/char_length_units.rs index 4d499b71..b5b7b19a 100644 --- a/src/serdes/char_length_units.rs +++ b/src/serdes/char_length_units.rs @@ -53,6 +53,7 @@ impl ReferenceSerialization for CharLengthUnits { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] pub(crate) mod test { use crate::errors::DatabaseError; @@ -94,3 +95,4 @@ pub(crate) mod test { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/serdes/column.rs b/src/serdes/column.rs index 94d786c9..071c5522 100644 --- a/src/serdes/column.rs +++ b/src/serdes/column.rs @@ -128,6 +128,7 @@ impl ReferenceSerialization for ColumnRelation { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] pub(crate) mod test { use crate::catalog::{ @@ -336,3 +337,4 @@ pub(crate) mod test { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/serdes/data_value.rs b/src/serdes/data_value.rs index e1c2bcde..718897f5 100644 --- a/src/serdes/data_value.rs +++ b/src/serdes/data_value.rs @@ -43,6 +43,7 @@ const TAG_TIME32: u8 = 15; const TAG_TIME64: u8 = 16; const TAG_DECIMAL: u8 = 17; const TAG_TUPLE: u8 = 18; +const TAG_BINARY: u8 = 19; impl ReferenceSerialization for Utf8Type { fn encode( @@ -144,6 +145,16 @@ impl DataValue { write_u8(writer, TAG_UINT64)?; write_u64(writer, *value) } + DataValue::Binary { value, len } => { + write_u8(writer, TAG_BINARY)?; + write_len(writer, value.len())?; + writer.write_all(value)?; + write_bool(writer, len.is_some())?; + if let Some(len) = len { + write_u32(writer, *len)?; + } + Ok(()) + } DataValue::Utf8 { value, ty, unit } => { write_u8(writer, TAG_UTF8)?; write_string(writer, value)?; @@ -200,6 +211,17 @@ impl DataValue { TAG_UINT16 => Ok(DataValue::UInt16(read_u16(reader)?)), TAG_UINT32 => Ok(DataValue::UInt32(read_u32(reader)?)), TAG_UINT64 => Ok(DataValue::UInt64(read_u64(reader)?)), + TAG_BINARY => { + let size = read_len(reader)?; + let mut value = vec![0; size]; + reader.read_exact(&mut value)?; + let len = if read_bool(reader)? { + Some(read_u32(reader)?) + } else { + None + }; + Ok(DataValue::Binary { value, len }) + } TAG_UTF8 => Ok(DataValue::Utf8 { value: read_string(reader)?, ty: read_utf8_type(reader)?, @@ -400,9 +422,10 @@ fn read_char_length_units(reader: &mut R) -> Result Result<(), DatabaseError> { let sources = vec![ + DataValue::Binary { + value: vec![0, 255, 128], + len: Some(8), + }, + DataValue::from(Vec::::new()), DataValue::Null, DataValue::Boolean(true), DataValue::Float32(OrderedFloat(32.5)), @@ -496,6 +524,10 @@ pub(crate) mod test { assert_invalid_value(&[u8::MAX], "invalid data value tag"); assert_invalid_value(&[TAG_BOOLEAN, 2], "invalid bool value"); assert_invalid_value(&[TAG_INT64, 1], "failed to fill whole buffer"); + assert_invalid_value( + &[TAG_BINARY, 2, 0, 0, 0, 255], + "failed to fill whole buffer", + ); let mut utf8 = vec![TAG_UTF8]; utf8.extend(0u32.to_le_bytes()); @@ -564,3 +596,4 @@ pub(crate) mod test { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/serdes/evaluator.rs b/src/serdes/evaluator.rs index 5fd8a466..4edd37ff 100644 --- a/src/serdes/evaluator.rs +++ b/src/serdes/evaluator.rs @@ -88,6 +88,10 @@ impl ReferenceSerialization for CastEvaluatorParams { precision.encode(writer, is_direct, reference_tables, arena)?; zone.encode(writer, is_direct, reference_tables, arena) } + CastEvaluatorParams::Binary { len } => { + 7u8.encode(writer, is_direct, reference_tables, arena)?; + len.encode(writer, is_direct, reference_tables, arena) + } CastEvaluatorParams::Tuple { evaluators } => { 6u8.encode(writer, is_direct, reference_tables, arena)?; evaluators.encode(writer, is_direct, reference_tables, arena) @@ -134,6 +138,9 @@ impl ReferenceSerialization for CastEvaluatorParams { precision: Option::::decode(reader, context, reference_tables, arena)?, zone: bool::decode(reader, context, reference_tables, arena)?, }, + 7 => CastEvaluatorParams::Binary { + len: Option::::decode(reader, context, reference_tables, arena)?, + }, 6 => CastEvaluatorParams::Tuple { evaluators: Vec::::decode( reader, @@ -228,6 +235,7 @@ impl ReferenceSerialization for CastEvaluatorRef { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::*; @@ -285,6 +293,8 @@ mod tests { fn cast_evaluator_params_serialization_roundtrips_all_variants() -> Result<(), DatabaseError> { roundtrip_cast_params(CastEvaluatorParams::Identity, 0)?; roundtrip_cast_params(CastEvaluatorParams::Unit, 1)?; + roundtrip_cast_params(CastEvaluatorParams::Binary { len: Some(8) }, 7)?; + roundtrip_cast_params(CastEvaluatorParams::Binary { len: None }, 7)?; roundtrip_cast_params( CastEvaluatorParams::String { len: Some(12), @@ -377,3 +387,4 @@ mod tests { )) } } +// GRCOV_EXCL_STOP diff --git a/src/serdes/function.rs b/src/serdes/function.rs index d528212a..d262a9b6 100644 --- a/src/serdes/function.rs +++ b/src/serdes/function.rs @@ -93,6 +93,7 @@ impl ReferenceSerialization for ArcTableFunctionImpl { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::*; @@ -180,3 +181,4 @@ mod tests { assert!(err.to_string().contains("table function not found")); } } +// GRCOV_EXCL_STOP diff --git a/src/serdes/mod.rs b/src/serdes/mod.rs index 93f068ee..3f4a4626 100644 --- a/src/serdes/mod.rs +++ b/src/serdes/mod.rs @@ -176,6 +176,7 @@ impl ReferenceTables { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use crate::errors::DatabaseError; @@ -270,3 +271,4 @@ mod tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/serdes/num.rs b/src/serdes/num.rs index 70923cb3..c2792807 100644 --- a/src/serdes/num.rs +++ b/src/serdes/num.rs @@ -84,6 +84,7 @@ impl ReferenceSerialization for usize { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] pub(crate) mod test { use crate::errors::DatabaseError; @@ -177,3 +178,4 @@ pub(crate) mod test { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/serdes/stable_hash.rs b/src/serdes/stable_hash.rs index 7a3b182b..98d72a6f 100644 --- a/src/serdes/stable_hash.rs +++ b/src/serdes/stable_hash.rs @@ -114,6 +114,7 @@ fn mix64(mut value: u64) -> u64 { value ^ (value >> 31) } +// GRCOV_EXCL_START #[cfg(test)] mod tests { use super::StableHasher; @@ -154,3 +155,4 @@ mod tests { assert_eq!(chunked.finish(), single.finish()); } } +// GRCOV_EXCL_STOP diff --git a/src/serdes/string.rs b/src/serdes/string.rs index 378f950f..0110b277 100644 --- a/src/serdes/string.rs +++ b/src/serdes/string.rs @@ -74,6 +74,7 @@ impl ReferenceSerialization for Arc { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] pub(crate) mod test { use crate::errors::DatabaseError; @@ -104,3 +105,4 @@ pub(crate) mod test { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/storage/lmdb.rs b/src/storage/lmdb.rs index 6cfa872c..8e922d4a 100644 --- a/src/storage/lmdb.rs +++ b/src/storage/lmdb.rs @@ -365,6 +365,7 @@ impl Transaction for LmdbTransaction<'_> { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use super::{LmdbConfig, LmdbStorage}; @@ -481,3 +482,4 @@ mod tests { kite_sql.ddl("create table t1 (a int primary key)").unwrap(); } } +// GRCOV_EXCL_STOP diff --git a/src/storage/memory.rs b/src/storage/memory.rs index ede8f0e0..d159c9c0 100644 --- a/src/storage/memory.rs +++ b/src/storage/memory.rs @@ -211,6 +211,7 @@ impl Transaction for MemoryTransaction { } } +// GRCOV_EXCL_START #[cfg(all(test, target_arch = "wasm32", feature = "wasm"))] mod wasm_tests { use super::*; @@ -521,3 +522,4 @@ mod native_tests { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 5b756bc0..e2eca4da 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -2209,6 +2209,7 @@ impl ViewIter<'_, T> { } } +// GRCOV_EXCL_START #[cfg(test)] pub(crate) fn check_range_rev_matches_range( tx: &mut T, @@ -3207,3 +3208,4 @@ mod test { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/storage/rocksdb.rs b/src/storage/rocksdb.rs index 7be2dba1..10458f9a 100644 --- a/src/storage/rocksdb.rs +++ b/src/storage/rocksdb.rs @@ -854,6 +854,7 @@ fn next<'a, D: rocksdb::DBAccess>( } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod test { use crate::catalog::{ColumnCatalog, ColumnDesc, TableName}; @@ -1419,3 +1420,4 @@ mod test { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/src/storage/table_codec.rs b/src/storage/table_codec.rs index 07d29733..4d9672e4 100644 --- a/src/storage/table_codec.rs +++ b/src/storage/table_codec.rs @@ -212,6 +212,7 @@ impl TableCodec { | LogicalType::UBigint | LogicalType::Char(..) | LogicalType::Varchar(..) + | LogicalType::Varbinary(..) | LogicalType::Date | LogicalType::DateTime | LogicalType::Time(..) @@ -1058,6 +1059,7 @@ impl TableCodec { } } +// GRCOV_EXCL_START #[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use crate::binder::test::build_t1_table; @@ -1866,3 +1868,4 @@ mod tests { assert_eq!(vec[2], &op("V0")); } } +// GRCOV_EXCL_STOP diff --git a/src/types/evaluator/binary.rs b/src/types/evaluator/binary.rs index 26cd8252..abb410fd 100644 --- a/src/types/evaluator/binary.rs +++ b/src/types/evaluator/binary.rs @@ -15,6 +15,7 @@ use crate::errors::DatabaseError; use crate::expression::BinaryOperator; use crate::types::evaluator::boolean::*; +use crate::types::evaluator::bytes::*; #[cfg(feature = "time")] use crate::types::evaluator::date::*; #[cfg(feature = "time")] @@ -133,6 +134,7 @@ const BINARY_BOOLEAN_BASE: u16 = BINARY_TIME64_BASE + TIMESTAMP_OPS_LEN; const BINARY_UTF8_BASE: u16 = BINARY_BOOLEAN_BASE + BOOLEAN_OPS_LEN; const BINARY_SQL_NULL: u16 = BINARY_UTF8_BASE + UTF8_OPS_LEN; const BINARY_TUPLE_BASE: u16 = BINARY_SQL_NULL + SQL_NULL_OPS_LEN; +const BINARY_BYTES_BASE: u16 = BINARY_TUPLE_BASE + TUPLE_LT_EQ_OFFSET + 1; // Evaluator positions are serialized ABI. Do not reorder or reuse existing // positions; only append new positions at the end of the current layout. @@ -181,6 +183,21 @@ pub fn binary_create( ) -> Result { let ty = ty.as_ref(); match ty { + LogicalType::Varbinary(_) => match op { + BinaryOperator::Eq => unit_binary_ref(binary_pos(BINARY_BYTES_BASE, TUPLE_EQ_OFFSET)), + BinaryOperator::NotEq => { + unit_binary_ref(binary_pos(BINARY_BYTES_BASE, TUPLE_NOT_EQ_OFFSET)) + } + BinaryOperator::Gt => unit_binary_ref(binary_pos(BINARY_BYTES_BASE, TUPLE_GT_OFFSET)), + BinaryOperator::GtEq => { + unit_binary_ref(binary_pos(BINARY_BYTES_BASE, TUPLE_GT_EQ_OFFSET)) + } + BinaryOperator::Lt => unit_binary_ref(binary_pos(BINARY_BYTES_BASE, TUPLE_LT_OFFSET)), + BinaryOperator::LtEq => { + unit_binary_ref(binary_pos(BINARY_BYTES_BASE, TUPLE_LT_EQ_OFFSET)) + } + _ => Err(DatabaseError::UnsupportedBinaryOperator(ty.clone(), op)), + }, LogicalType::Tinyint => numeric_binary_ref(BINARY_INT8_BASE, ty, op), LogicalType::Smallint => numeric_binary_ref(BINARY_INT16_BASE, ty, op), LogicalType::Integer => numeric_binary_ref(BINARY_INT32_BASE, ty, op), @@ -338,9 +355,13 @@ macro_rules! eval_numeric_binary { pub(crate) fn eval_binary( pos: u16, params: &BinaryEvaluatorParams, - left: &crate::types::value::DataValue, + left: Cow<'_, crate::types::value::DataValue>, right: &crate::types::value::DataValue, ) -> Result { + if pos == binary_pos(BINARY_UTF8_BASE, UTF8_STRING_CONCAT_OFFSET) { + return utf8_string_concat_binary_eval(left.into_owned(), right); + } + let left = left.as_ref(); match pos { BINARY_INT8_BASE..BINARY_INT16_BASE => { eval_numeric_binary!(pos, BINARY_INT8_BASE, Int8, left, right) @@ -478,9 +499,6 @@ pub(crate) fn eval_binary( x if x == binary_pos(BINARY_UTF8_BASE, UTF8_NOT_EQ_OFFSET) => { utf8_not_eq_binary_eval(left, right) } - x if x == binary_pos(BINARY_UTF8_BASE, UTF8_STRING_CONCAT_OFFSET) => { - utf8_string_concat_binary_eval(left, right) - } x if x == binary_pos(BINARY_UTF8_BASE, UTF8_LIKE_OFFSET) => { let BinaryEvaluatorParams::Like { escape_char } = params else { unreachable!() @@ -512,6 +530,24 @@ pub(crate) fn eval_binary( x if x == binary_pos(BINARY_TUPLE_BASE, TUPLE_LT_EQ_OFFSET) => { tuple_lt_eq_binary_eval(left, right) } + x if x == binary_pos(BINARY_BYTES_BASE, TUPLE_EQ_OFFSET) => { + bytes_eq_binary_eval(left, right) + } + x if x == binary_pos(BINARY_BYTES_BASE, TUPLE_NOT_EQ_OFFSET) => { + bytes_not_eq_binary_eval(left, right) + } + x if x == binary_pos(BINARY_BYTES_BASE, TUPLE_GT_OFFSET) => { + bytes_gt_binary_eval(left, right) + } + x if x == binary_pos(BINARY_BYTES_BASE, TUPLE_GT_EQ_OFFSET) => { + bytes_gt_eq_binary_eval(left, right) + } + x if x == binary_pos(BINARY_BYTES_BASE, TUPLE_LT_OFFSET) => { + bytes_lt_binary_eval(left, right) + } + x if x == binary_pos(BINARY_BYTES_BASE, TUPLE_LT_EQ_OFFSET) => { + bytes_lt_eq_binary_eval(left, right) + } _ => unreachable!("unknown binary evaluator position {pos}"), } } @@ -690,7 +726,7 @@ mod test { left: &DataValue, right: &DataValue, ) -> Result { - create(ty, op)?.binary_eval(left, right) + create(ty, op)?.binary_eval(Cow::Borrowed(left), right) } fn assert_panics(f: impl FnOnce() + std::panic::UnwindSafe) { @@ -700,7 +736,7 @@ mod test { fn assert_binary_ref_panics(pos: u16, params: BinaryEvaluatorParams, value: &DataValue) { assert_panics(|| { let evaluator = BinaryEvaluatorRef::new(pos, params); - let _ = evaluator.binary_eval(value, value).unwrap(); + let _ = evaluator.binary_eval(Cow::Borrowed(value), value).unwrap(); }); } @@ -733,21 +769,29 @@ mod test { let divide = binary_create(Cow::Owned($ty), BinaryOperator::Divide)?; for (left, right, expected) in [(5, 2, 2), (-5, 2, -2), (5, -2, -2), (-5, -2, 2)] { assert_eq!( - divide - .binary_eval(&DataValue::$variant(left), &DataValue::$variant(right))?, + divide.binary_eval( + Cow::Borrowed(&DataValue::$variant(left)), + &DataValue::$variant(right) + )?, DataValue::$variant(expected) ); } assert!(matches!( - divide.binary_eval(&DataValue::$variant(<$int>::MIN), &DataValue::$variant(-1)), + divide.binary_eval( + Cow::Borrowed(&DataValue::$variant(<$int>::MIN)), + &DataValue::$variant(-1) + ), Err(DatabaseError::OverFlow) )); assert!(matches!( - divide.binary_eval(&DataValue::$variant(1), &DataValue::$variant(0)), + divide.binary_eval( + Cow::Borrowed(&DataValue::$variant(1)), + &DataValue::$variant(0) + ), Err(DatabaseError::InvalidValue(_)) )); assert_eq!( - divide.binary_eval(&DataValue::Null, &DataValue::$variant(0))?, + divide.binary_eval(Cow::Borrowed(&DataValue::Null), &DataValue::$variant(0))?, DataValue::Null ); }}; @@ -783,9 +827,9 @@ mod test { ), ] { let divide = binary_create(Cow::Owned(ty), BinaryOperator::Divide)?; - assert_eq!(divide.binary_eval(&left, &right)?, expected); + assert_eq!(divide.binary_eval(Cow::Borrowed(&left), &right)?, expected); assert_eq!( - divide.binary_eval(&left, &DataValue::Null)?, + divide.binary_eval(Cow::Borrowed(&left), &DataValue::Null)?, DataValue::Null ); } @@ -986,7 +1030,7 @@ mod test { assert_panics(|| { let evaluator = BinaryEvaluatorRef::new(u16::MAX, BinaryEvaluatorParams::Unit); let _ = evaluator - .binary_eval(&DataValue::Int32(1), &DataValue::Int32(1)) + .binary_eval(Cow::Borrowed(&DataValue::Int32(1)), &DataValue::Int32(1)) .unwrap(); }); } diff --git a/src/types/evaluator/boolean.rs b/src/types/evaluator/boolean.rs index 2cdba135..3d4e8fd9 100644 --- a/src/types/evaluator/boolean.rs +++ b/src/types/evaluator/boolean.rs @@ -13,7 +13,7 @@ // limitations under the License. use crate::errors::DatabaseError; -use crate::types::evaluator::cast::{to_char, to_varchar}; +use crate::types::evaluator::cast::{to_char, to_varbinary, to_varchar}; use crate::types::evaluator::DataValue; use crate::types::CharLengthUnits; use ordered_float::OrderedFloat; @@ -67,34 +67,34 @@ pub fn boolean_eq_binary_eval( } crate::define_cast_evaluator!(boolean_to_tinyint_cast_eval, DataValue::Boolean(value) => { - Ok(DataValue::Int8(if *value { 1 } else { 0 })) + Ok(DataValue::Int8(if value { 1 } else { 0 })) }); crate::define_cast_evaluator!(boolean_to_utinyint_cast_eval, DataValue::Boolean(value) => { - Ok(DataValue::UInt8(if *value { 1 } else { 0 })) + Ok(DataValue::UInt8(if value { 1 } else { 0 })) }); crate::define_cast_evaluator!(boolean_to_smallint_cast_eval, DataValue::Boolean(value) => { - Ok(DataValue::Int16(if *value { 1 } else { 0 })) + Ok(DataValue::Int16(if value { 1 } else { 0 })) }); crate::define_cast_evaluator!(boolean_to_usmallint_cast_eval, DataValue::Boolean(value) => { - Ok(DataValue::UInt16(if *value { 1 } else { 0 })) + Ok(DataValue::UInt16(if value { 1 } else { 0 })) }); crate::define_cast_evaluator!(boolean_to_integer_cast_eval, DataValue::Boolean(value) => { - Ok(DataValue::Int32(if *value { 1 } else { 0 })) + Ok(DataValue::Int32(if value { 1 } else { 0 })) }); crate::define_cast_evaluator!(boolean_to_uinteger_cast_eval, DataValue::Boolean(value) => { - Ok(DataValue::UInt32(if *value { 1 } else { 0 })) + Ok(DataValue::UInt32(if value { 1 } else { 0 })) }); crate::define_cast_evaluator!(boolean_to_bigint_cast_eval, DataValue::Boolean(value) => { - Ok(DataValue::Int64(if *value { 1 } else { 0 })) + Ok(DataValue::Int64(if value { 1 } else { 0 })) }); crate::define_cast_evaluator!(boolean_to_ubigint_cast_eval, DataValue::Boolean(value) => { - Ok(DataValue::UInt64(if *value { 1 } else { 0 })) + Ok(DataValue::UInt64(if value { 1 } else { 0 })) }); crate::define_cast_evaluator!(boolean_to_float_cast_eval, DataValue::Boolean(value) => { - Ok(DataValue::Float32(OrderedFloat(if *value { 1.0 } else { 0.0 }))) + Ok(DataValue::Float32(OrderedFloat(if value { 1.0 } else { 0.0 }))) }); crate::define_cast_evaluator!(boolean_to_double_cast_eval, DataValue::Boolean(value) => { - Ok(DataValue::Float64(OrderedFloat(if *value { 1.0 } else { 0.0 }))) + Ok(DataValue::Float64(OrderedFloat(if value { 1.0 } else { 0.0 }))) }); crate::define_cast_evaluator!( boolean_to_char_cast_eval { @@ -110,6 +110,10 @@ crate::define_cast_evaluator!( }, DataValue::Boolean(value) => |this| to_varchar(value.to_string(), this.len, this.unit) ); +crate::define_cast_evaluator!( + boolean_to_varbinary_cast_eval { len: Option }, + DataValue::Boolean(value) => |this| to_varbinary(value.to_string().into_bytes(), this.len) +); pub fn boolean_not_eq_binary_eval( left: &DataValue, right: &DataValue, @@ -184,47 +188,47 @@ mod test { let value = DataValue::Boolean(true); assert_eq!( - boolean_to_tinyint_cast_eval(&value).unwrap(), + boolean_to_tinyint_cast_eval(value.clone()).unwrap(), DataValue::Int8(1) ); assert_eq!( - boolean_to_utinyint_cast_eval(&value).unwrap(), + boolean_to_utinyint_cast_eval(value.clone()).unwrap(), DataValue::UInt8(1) ); assert_eq!( - boolean_to_smallint_cast_eval(&value).unwrap(), + boolean_to_smallint_cast_eval(value.clone()).unwrap(), DataValue::Int16(1) ); assert_eq!( - boolean_to_usmallint_cast_eval(&value).unwrap(), + boolean_to_usmallint_cast_eval(value.clone()).unwrap(), DataValue::UInt16(1) ); assert_eq!( - boolean_to_integer_cast_eval(&value).unwrap(), + boolean_to_integer_cast_eval(value.clone()).unwrap(), DataValue::Int32(1) ); assert_eq!( - boolean_to_uinteger_cast_eval(&value).unwrap(), + boolean_to_uinteger_cast_eval(value.clone()).unwrap(), DataValue::UInt32(1) ); assert_eq!( - boolean_to_bigint_cast_eval(&value).unwrap(), + boolean_to_bigint_cast_eval(value.clone()).unwrap(), DataValue::Int64(1) ); assert_eq!( - boolean_to_ubigint_cast_eval(&value).unwrap(), + boolean_to_ubigint_cast_eval(value.clone()).unwrap(), DataValue::UInt64(1) ); assert_eq!( - boolean_to_float_cast_eval(&value).unwrap(), + boolean_to_float_cast_eval(value.clone()).unwrap(), DataValue::Float32(OrderedFloat(1.0)) ); assert_eq!( - boolean_to_double_cast_eval(&value).unwrap(), + boolean_to_double_cast_eval(value.clone()).unwrap(), DataValue::Float64(OrderedFloat(1.0)) ); assert_eq!( - boolean_to_char_cast_eval(4, CharLengthUnits::Characters, &value).unwrap(), + boolean_to_char_cast_eval(4, CharLengthUnits::Characters, value.clone()).unwrap(), DataValue::Utf8 { value: "true".to_string(), ty: Utf8Type::Fixed(4), @@ -232,7 +236,8 @@ mod test { } ); assert_eq!( - boolean_to_varchar_cast_eval(Some(4), CharLengthUnits::Characters, &value).unwrap(), + boolean_to_varchar_cast_eval(Some(4), CharLengthUnits::Characters, value.clone()) + .unwrap(), DataValue::Utf8 { value: "true".to_string(), ty: Utf8Type::Variable(Some(4)), @@ -240,14 +245,14 @@ mod test { } ); assert_eq!( - boolean_to_double_cast_eval(&DataValue::Boolean(false)).unwrap(), + boolean_to_double_cast_eval(DataValue::Boolean(false)).unwrap(), DataValue::Float64(OrderedFloat(0.0)) ); assert_eq!( boolean_to_varchar_cast_eval( None, CharLengthUnits::Characters, - &DataValue::Boolean(false) + DataValue::Boolean(false) ) .unwrap(), DataValue::Utf8 { diff --git a/src/types/evaluator/bytes.rs b/src/types/evaluator/bytes.rs new file mode 100644 index 00000000..94ca6348 --- /dev/null +++ b/src/types/evaluator/bytes.rs @@ -0,0 +1,43 @@ +// Copyright 2026 KipData/KiteSQL +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::errors::DatabaseError; +use crate::types::evaluator::cast::to_varbinary; +use crate::types::value::DataValue; + +macro_rules! bytes_binary { + ($name:ident, $op:tt) => { + pub fn $name(left: &DataValue, right: &DataValue) -> Result { + Ok(match (left, right) { + (DataValue::Binary { value: left, .. }, DataValue::Binary { value: right, .. }) => { + DataValue::Boolean(left $op right) + } + (DataValue::Null, _) | (_, DataValue::Null) => DataValue::Null, + _ => unreachable!(), + }) + } + }; +} + +crate::define_cast_evaluator!( + bytes_to_varbinary_cast_eval { len: Option }, + DataValue::Binary { value, .. } => |this| to_varbinary(value, this.len) +); + +bytes_binary!(bytes_eq_binary_eval, ==); +bytes_binary!(bytes_not_eq_binary_eval, !=); +bytes_binary!(bytes_gt_binary_eval, >); +bytes_binary!(bytes_gt_eq_binary_eval, >=); +bytes_binary!(bytes_lt_binary_eval, <); +bytes_binary!(bytes_lt_eq_binary_eval, <=); diff --git a/src/types/evaluator/cast.rs b/src/types/evaluator/cast.rs index c7bd0262..834077a5 100644 --- a/src/types/evaluator/cast.rs +++ b/src/types/evaluator/cast.rs @@ -14,6 +14,7 @@ use crate::errors::DatabaseError; use crate::types::evaluator::boolean::*; +use crate::types::evaluator::bytes::*; #[cfg(feature = "time")] use crate::types::evaluator::date::*; #[cfg(feature = "time")] @@ -73,6 +74,7 @@ const CAST_TIMESTAMP: u16 = 17; #[cfg(feature = "decimal")] const CAST_DECIMAL: u16 = 18; const CAST_TUPLE: u16 = 19; +const CAST_BINARY: u16 = 20; // Cast positions are serialized ABI. Type codes above must never be reordered // or reused; new cast families should append a new code and keep old positions. @@ -101,6 +103,7 @@ fn cast_type_code(ty: &LogicalType) -> u16 { #[cfg(not(feature = "decimal"))] LogicalType::Decimal(_, _) => unreachable!("DECIMAL requires the `decimal` feature"), LogicalType::Tuple(_) => CAST_TUPLE, + LogicalType::Varbinary(_) => CAST_BINARY, } } @@ -140,6 +143,12 @@ pub(crate) fn to_char( }) } +pub(crate) fn to_varbinary(value: Vec, len: Option) -> Result { + let result = DataValue::Binary { value, len }; + result.check_len(&LogicalType::Varbinary(len))?; + Ok(result) +} + pub(crate) fn to_varchar( value: String, len: Option, @@ -222,7 +231,7 @@ macro_rules! decimal_to_int_cast { macro_rules! define_cast_evaluator { ($name:ident, $pattern:pat => $body:block) => { pub fn $name( - value: &$crate::types::value::DataValue, + value: $crate::types::value::DataValue, ) -> Result<$crate::types::value::DataValue, $crate::errors::DatabaseError> { match value { $crate::types::value::DataValue::Null => Ok($crate::types::value::DataValue::Null), @@ -233,7 +242,7 @@ macro_rules! define_cast_evaluator { }; ($name:ident, $pattern:pat => |$this:ident| $body:expr) => { pub fn $name( - value: &$crate::types::value::DataValue, + value: $crate::types::value::DataValue, ) -> Result<$crate::types::value::DataValue, $crate::errors::DatabaseError> { match value { $crate::types::value::DataValue::Null => Ok($crate::types::value::DataValue::Null), @@ -247,7 +256,7 @@ macro_rules! define_cast_evaluator { }; ($name:ident, $pattern:pat => |$this:ident| $body:block) => { pub fn $name( - value: &$crate::types::value::DataValue, + value: $crate::types::value::DataValue, ) -> Result<$crate::types::value::DataValue, $crate::errors::DatabaseError> { match value { $crate::types::value::DataValue::Null => Ok($crate::types::value::DataValue::Null), @@ -262,7 +271,7 @@ macro_rules! define_cast_evaluator { ($name:ident { $($field:ident : $field_ty:ty),+ $(,)? }, $pattern:pat => |$this:ident| $body:expr) => { pub fn $name( $($field: $field_ty,)+ - value: &$crate::types::value::DataValue, + value: $crate::types::value::DataValue, ) -> Result<$crate::types::value::DataValue, $crate::errors::DatabaseError> { struct This { $($field: $field_ty),+ @@ -278,7 +287,7 @@ macro_rules! define_cast_evaluator { ($name:ident { $($field:ident : $field_ty:ty),+ $(,)? }, $pattern:pat => $body:block) => { pub fn $name( $($field: $field_ty,)+ - value: &$crate::types::value::DataValue, + value: $crate::types::value::DataValue, ) -> Result<$crate::types::value::DataValue, $crate::errors::DatabaseError> { match value { $crate::types::value::DataValue::Null => Ok($crate::types::value::DataValue::Null), @@ -294,37 +303,37 @@ macro_rules! define_integer_cast_evaluators { ($prefix:ident, $variant:ident, $src_ty:ty, $from_ty:expr) => { paste::paste! { $crate::define_cast_evaluator!([<$prefix:snake _to_boolean_cast_eval>], $crate::types::value::DataValue::$variant(value) => { - $crate::numeric_to_boolean_cast!(*value, $from_ty) + $crate::numeric_to_boolean_cast!(value, $from_ty) }); $crate::define_cast_evaluator!([<$prefix:snake _to_tinyint_cast_eval>], $crate::types::value::DataValue::$variant(value) => { - Ok($crate::types::value::DataValue::Int8(i8::try_from(*value)?)) + Ok($crate::types::value::DataValue::Int8(i8::try_from(value)?)) }); $crate::define_cast_evaluator!([<$prefix:snake _to_utinyint_cast_eval>], $crate::types::value::DataValue::$variant(value) => { - Ok($crate::types::value::DataValue::UInt8(u8::try_from(*value)?)) + Ok($crate::types::value::DataValue::UInt8(u8::try_from(value)?)) }); $crate::define_cast_evaluator!([<$prefix:snake _to_smallint_cast_eval>], $crate::types::value::DataValue::$variant(value) => { - Ok($crate::types::value::DataValue::Int16(i16::try_from(*value)?)) + Ok($crate::types::value::DataValue::Int16(i16::try_from(value)?)) }); $crate::define_cast_evaluator!([<$prefix:snake _to_usmallint_cast_eval>], $crate::types::value::DataValue::$variant(value) => { - Ok($crate::types::value::DataValue::UInt16(u16::try_from(*value)?)) + Ok($crate::types::value::DataValue::UInt16(u16::try_from(value)?)) }); $crate::define_cast_evaluator!([<$prefix:snake _to_integer_cast_eval>], $crate::types::value::DataValue::$variant(value) => { - Ok($crate::types::value::DataValue::Int32(i32::try_from(*value)?)) + Ok($crate::types::value::DataValue::Int32(i32::try_from(value)?)) }); $crate::define_cast_evaluator!([<$prefix:snake _to_uinteger_cast_eval>], $crate::types::value::DataValue::$variant(value) => { - Ok($crate::types::value::DataValue::UInt32(u32::try_from(*value)?)) + Ok($crate::types::value::DataValue::UInt32(u32::try_from(value)?)) }); $crate::define_cast_evaluator!([<$prefix:snake _to_bigint_cast_eval>], $crate::types::value::DataValue::$variant(value) => { - Ok($crate::types::value::DataValue::Int64(i64::try_from(*value)?)) + Ok($crate::types::value::DataValue::Int64(i64::try_from(value)?)) }); $crate::define_cast_evaluator!([<$prefix:snake _to_ubigint_cast_eval>], $crate::types::value::DataValue::$variant(value) => { - Ok($crate::types::value::DataValue::UInt64(u64::try_from(*value)?)) + Ok($crate::types::value::DataValue::UInt64(u64::try_from(value)?)) }); $crate::define_cast_evaluator!([<$prefix:snake _to_float_cast_eval>], $crate::types::value::DataValue::$variant(value) => { - Ok($crate::types::value::DataValue::Float32(ordered_float::OrderedFloat(*value as f32))) + Ok($crate::types::value::DataValue::Float32(ordered_float::OrderedFloat(value as f32))) }); $crate::define_cast_evaluator!([<$prefix:snake _to_double_cast_eval>], $crate::types::value::DataValue::$variant(value) => { - Ok($crate::types::value::DataValue::Float64(ordered_float::OrderedFloat(*value as f64))) + Ok($crate::types::value::DataValue::Float64(ordered_float::OrderedFloat(value as f64))) }); $crate::define_cast_evaluator!( [<$prefix:snake _to_char_cast_eval>] { @@ -344,13 +353,19 @@ macro_rules! define_integer_cast_evaluators { $crate::types::evaluator::cast::to_varchar(value.to_string(), this.len, this.unit) } ); + $crate::define_cast_evaluator!( + [<$prefix:snake _to_varbinary_cast_eval>] { len: Option }, + $crate::types::value::DataValue::$variant(value) => |this| { + $crate::types::evaluator::cast::to_varbinary(value.to_string().into_bytes(), this.len) + } + ); #[cfg(feature = "decimal")] $crate::define_cast_evaluator!( [<$prefix:snake _to_decimal_cast_eval>] { scale: Option }, $crate::types::value::DataValue::$variant(value) => |this| { - let mut decimal = rust_decimal::Decimal::from(*value); + let mut decimal = rust_decimal::Decimal::from(value); $crate::types::value::DataValue::decimal_round_i(&this.scale, &mut decimal); Ok($crate::types::value::DataValue::Decimal(decimal)) } @@ -364,7 +379,7 @@ macro_rules! define_float_cast_evaluators { ($prefix:ident, $variant:ident, $src_ty:ty, $from_ty:expr, $into_decimal:ident) => { paste::paste! { $crate::define_cast_evaluator!([<$prefix:snake _to_float_cast_eval>], $crate::types::value::DataValue::$variant(value) => { - Ok($crate::types::value::DataValue::$variant(*value)) + Ok($crate::types::value::DataValue::$variant(value)) }); $crate::define_cast_evaluator!([<$prefix:snake _to_double_cast_eval>], $crate::types::value::DataValue::$variant(value) => { Ok($crate::types::value::DataValue::Float64(ordered_float::OrderedFloat(value.0 as f64))) @@ -411,6 +426,12 @@ macro_rules! define_float_cast_evaluators { $crate::types::evaluator::cast::to_varchar(value.to_string(), this.len, this.unit) } ); + $crate::define_cast_evaluator!( + [<$prefix:snake _to_varbinary_cast_eval>] { len: Option }, + $crate::types::value::DataValue::$variant(value) => |this| { + $crate::types::evaluator::cast::to_varbinary(value.to_string().into_bytes(), this.len) + } + ); #[cfg(feature = "decimal")] $crate::define_cast_evaluator!( [<$prefix:snake _to_decimal_cast_eval>] { @@ -431,10 +452,6 @@ macro_rules! define_float_cast_evaluators { } }; } -pub fn identity_cast_eval(value: &DataValue) -> Result { - Ok(value.clone()) -} - macro_rules! cast_ref { ($from:expr, $to:expr) => {{ Ok(CastEvaluatorRef::new( @@ -551,7 +568,14 @@ pub fn cast_create( } match (from, to) { + (LogicalType::Tuple(_), LogicalType::Varbinary(_)) => { + Err(cast_fail(from.clone(), to.clone())) + } (LogicalType::SqlNull, _) => cast_ref!(from, to), + (_, LogicalType::Varbinary(len)) => Ok(CastEvaluatorRef::new( + cast_pos(from, to), + CastEvaluatorParams::Binary { len: *len }, + )), (_, LogicalType::SqlNull) => cast_ref!(from, to), (LogicalType::Boolean, LogicalType::Tinyint) => { cast_ref!(from, to) @@ -830,6 +854,13 @@ fn varchar_params(ty: &LogicalType) -> (Option, CharLengthUnits) { (*len, *unit) } +fn binary_param(params: &CastEvaluatorParams) -> Option { + let CastEvaluatorParams::Binary { len } = params else { + unreachable!("cast evaluator must have binary parameters") + }; + *len +} + fn string_param(params: &CastEvaluatorParams) -> (Option, CharLengthUnits) { let CastEvaluatorParams::String { len, unit } = params else { unreachable!("cast evaluator must have string parameters") @@ -887,6 +918,7 @@ macro_rules! eval_integer_cast_by_pos { let (len, unit) = string_param(params); [<$prefix:snake _to_varchar_cast_eval>](len, unit, $value) } + CAST_BINARY => [<$prefix:snake _to_varbinary_cast_eval>](binary_param(params), $value), #[cfg(feature = "decimal")] CAST_DECIMAL => { let (_, scale) = decimal_param(params); @@ -899,7 +931,7 @@ macro_rules! eval_integer_cast_by_pos { } impl CastEvaluatorRef { - pub fn eval(&self, value: &DataValue) -> Result { + pub fn eval(&self, value: DataValue) -> Result { let params = &self.params; let from_code = self.pos / CAST_TYPE_STRIDE; let to_code = self.pos % CAST_TYPE_STRIDE; @@ -917,7 +949,7 @@ impl CastEvaluatorRef { } if matches!(params, CastEvaluatorParams::Identity) { - return run!(identity_cast_eval); + return Ok(value); } #[cfg(not(feature = "time"))] if is_chrono_type_code(from_code) || is_chrono_type_code(to_code) { @@ -927,6 +959,41 @@ impl CastEvaluatorRef { } match (from_code, to_code) { + (CAST_BINARY, CAST_BINARY) => run!(bytes_to_varbinary_cast_eval { + len: binary_param(params) + }), + (CAST_BOOLEAN, CAST_BINARY) => run!(boolean_to_varbinary_cast_eval { + len: binary_param(params) + }), + (CAST_FLOAT, CAST_BINARY) => run!(float32_to_varbinary_cast_eval { + len: binary_param(params) + }), + (CAST_DOUBLE, CAST_BINARY) => run!(float64_to_varbinary_cast_eval { + len: binary_param(params) + }), + (CAST_CHAR | CAST_VARCHAR, CAST_BINARY) => run!(utf8_to_varbinary_cast_eval { + len: binary_param(params) + }), + #[cfg(feature = "time")] + (CAST_DATE, CAST_BINARY) => run!(date32_to_varbinary_cast_eval { + len: binary_param(params) + }), + #[cfg(feature = "time")] + (CAST_DATETIME, CAST_BINARY) => run!(date64_to_varbinary_cast_eval { + len: binary_param(params) + }), + #[cfg(feature = "time")] + (CAST_TIME, CAST_BINARY) => run!(time32_to_varbinary_cast_eval { + len: binary_param(params) + }), + #[cfg(feature = "time")] + (CAST_TIMESTAMP, CAST_BINARY) => run!(time64_to_varbinary_cast_eval { + len: binary_param(params) + }), + #[cfg(feature = "decimal")] + (CAST_DECIMAL, CAST_BINARY) => run!(decimal_to_varbinary_cast_eval { + len: binary_param(params) + }), (CAST_SQL_NULL, _) => run!(null_cast_eval), (_, CAST_SQL_NULL) => run!(to_sql_null_cast_eval), (CAST_BOOLEAN, CAST_TINYINT) => run!(boolean_to_tinyint_cast_eval), @@ -1183,7 +1250,7 @@ mod test { (LogicalType::Integer, DataValue::Int32(-12)), (LogicalType::Bigint, DataValue::Int64(-12)), ] { - assert_eq!(cast_eval(from.clone(), to, &value)?, expected); + assert_eq!(cast_eval(from.clone(), to, value.clone())?, expected); } } for (from, zero) in [ @@ -1191,11 +1258,11 @@ mod test { (LogicalType::Double, DataValue::Float64(OrderedFloat(-0.0))), ] { assert_eq!( - cast_eval(from.clone(), LogicalType::Integer, &zero)?, + cast_eval(from.clone(), LogicalType::Integer, zero.clone())?, DataValue::Int32(0) ); assert_eq!( - cast_eval(from, LogicalType::UInteger, &zero)?, + cast_eval(from, LogicalType::UInteger, zero.clone())?, DataValue::UInt32(0) ); } @@ -1258,7 +1325,7 @@ mod test { fn cast_eval( from: LogicalType, to: LogicalType, - value: &DataValue, + value: DataValue, ) -> Result { create(from, to)?.eval(value) } @@ -1270,7 +1337,7 @@ mod test { fn assert_cast_ref_panics(pos: u16, params: CastEvaluatorParams, value: DataValue) { assert_panics(|| { let evaluator = CastEvaluatorRef::new(pos, params); - let _ = evaluator.eval(&value).unwrap(); + let _ = evaluator.eval(value).unwrap(); }); } @@ -1399,8 +1466,27 @@ mod test { ]; for (to, expected) in cases { - assert_eq!(cast_eval(LogicalType::Boolean, to, &value)?, expected); + assert_eq!( + cast_eval(LogicalType::Boolean, to, value.clone())?, + expected + ); } + assert_eq!( + cast_eval( + LogicalType::Boolean, + LogicalType::Varbinary(None), + value.clone() + )?, + DataValue::from(b"true".to_vec()) + ); + assert_eq!( + cast_eval( + LogicalType::Boolean, + LogicalType::Varbinary(None), + DataValue::Null + )?, + DataValue::Null + ); Ok(()) } @@ -1441,7 +1527,7 @@ mod test { ), ]; for (to, expected) in float_cases { - assert_eq!(cast_eval(LogicalType::Float, to, &float)?, expected); + assert_eq!(cast_eval(LogicalType::Float, to, float.clone())?, expected); } let double = DataValue::Float64(OrderedFloat(1.5)); @@ -1478,8 +1564,27 @@ mod test { ), ]; for (to, expected) in double_cases { - assert_eq!(cast_eval(LogicalType::Double, to, &double)?, expected); + assert_eq!( + cast_eval(LogicalType::Double, to, double.clone())?, + expected + ); } + assert_eq!( + cast_eval( + LogicalType::Float, + LogicalType::Varbinary(None), + float.clone() + )?, + DataValue::from(b"1.5".to_vec()) + ); + assert_eq!( + cast_eval( + LogicalType::Double, + LogicalType::Varbinary(None), + double.clone() + )?, + DataValue::from(b"1.5".to_vec()) + ); Ok(()) } @@ -1530,14 +1635,41 @@ mod test { ]; for (from, value, expected) in cases { - assert_eq!(cast_eval(from, LogicalType::Double, &value)?, expected); + assert_eq!( + cast_eval(from.clone(), LogicalType::Varbinary(None), value.clone())?, + DataValue::from(value.to_string().into_bytes()) + ); + assert_eq!( + cast_eval(from, LogicalType::Double, value.clone())?, + expected + ); } + assert_eq!( + cast_eval( + LogicalType::Bigint, + LogicalType::Varbinary(None), + DataValue::Int64(-123) + )?, + DataValue::from(b"-123".to_vec()) + ); + assert_eq!( + cast_eval( + LogicalType::UBigint, + LogicalType::Varbinary(None), + DataValue::UInt64(u64::MAX) + )?, + DataValue::from(b"18446744073709551615".to_vec()) + ); + assert!(matches!( + DataValue::Int64(123).cast(&LogicalType::Varbinary(Some(2))), + Err(DatabaseError::TooLong) + )); assert_eq!( cast_eval( LogicalType::Integer, LogicalType::Char(1, CharLengthUnits::Characters), - &DataValue::Int32(9), + DataValue::Int32(9), )?, DataValue::Utf8 { value: "9".to_string(), @@ -1549,7 +1681,7 @@ mod test { cast_eval( LogicalType::UInteger, LogicalType::Varchar(Some(2), CharLengthUnits::Characters), - &DataValue::UInt32(10), + DataValue::UInt32(10), )?, DataValue::Utf8 { value: "10".to_string(), @@ -1562,7 +1694,7 @@ mod test { cast_eval( LogicalType::Smallint, LogicalType::Decimal(None, Some(1)), - &DataValue::Int16(12), + DataValue::Int16(12), )?, DataValue::Decimal(Decimal::new(120, 1)) ); @@ -1572,11 +1704,48 @@ mod test { #[test] fn test_cast_create_dispatches_utf8_casts() -> Result<(), DatabaseError> { + let from = LogicalType::Varchar(None, CharLengthUnits::Characters); + for to in [ + from.clone(), + LogicalType::Char(4, CharLengthUnits::Octets), + LogicalType::Varchar(Some(4), CharLengthUnits::Octets), + LogicalType::Varbinary(Some(4)), + ] { + let text = "中\0".to_string(); + let buffer = text.as_ptr(); + let result = create(from.clone(), to)?.eval(DataValue::from(text))?; + match result { + DataValue::Utf8 { value, .. } => { + assert_eq!(value, "中\0"); + assert_eq!(value.as_ptr(), buffer); + } + DataValue::Binary { value, .. } => { + assert_eq!(value, "中\0".as_bytes()); + assert_eq!(value.as_ptr(), buffer); + } + _ => unreachable!(), + } + } + assert_eq!( + cast_eval( + LogicalType::Varchar(None, CharLengthUnits::Characters), + LogicalType::Varbinary(Some(4)), + utf8("中\0") + )?, + DataValue::Binary { + value: "中\0".as_bytes().to_vec(), + len: Some(4) + } + ); + assert!(matches!( + utf8("中").cast(&LogicalType::Varbinary(Some(2))), + Err(DatabaseError::TooLong) + )); assert_eq!( cast_eval( LogicalType::Varchar(None, CharLengthUnits::Characters), LogicalType::Boolean, - &utf8("true") + utf8("true") )?, DataValue::Boolean(true) ); @@ -1584,7 +1753,7 @@ mod test { cast_eval( LogicalType::Varchar(None, CharLengthUnits::Characters), LogicalType::Integer, - &utf8("12") + utf8("12") )?, DataValue::Int32(12) ); @@ -1592,7 +1761,7 @@ mod test { cast_eval( LogicalType::Varchar(None, CharLengthUnits::Characters), LogicalType::Tinyint, - &utf8("12") + utf8("12") )?, DataValue::Int8(12) ); @@ -1600,7 +1769,7 @@ mod test { cast_eval( LogicalType::Varchar(None, CharLengthUnits::Characters), LogicalType::UTinyint, - &utf8("12") + utf8("12") )?, DataValue::UInt8(12) ); @@ -1608,7 +1777,7 @@ mod test { cast_eval( LogicalType::Varchar(None, CharLengthUnits::Characters), LogicalType::Smallint, - &utf8("12") + utf8("12") )?, DataValue::Int16(12) ); @@ -1616,7 +1785,7 @@ mod test { cast_eval( LogicalType::Varchar(None, CharLengthUnits::Characters), LogicalType::USmallint, - &utf8("12") + utf8("12") )?, DataValue::UInt16(12) ); @@ -1624,7 +1793,7 @@ mod test { cast_eval( LogicalType::Varchar(None, CharLengthUnits::Characters), LogicalType::UInteger, - &utf8("12") + utf8("12") )?, DataValue::UInt32(12) ); @@ -1632,7 +1801,7 @@ mod test { cast_eval( LogicalType::Varchar(None, CharLengthUnits::Characters), LogicalType::Bigint, - &utf8("12") + utf8("12") )?, DataValue::Int64(12) ); @@ -1640,7 +1809,7 @@ mod test { cast_eval( LogicalType::Varchar(None, CharLengthUnits::Characters), LogicalType::UBigint, - &utf8("12") + utf8("12") )?, DataValue::UInt64(12) ); @@ -1648,7 +1817,7 @@ mod test { cast_eval( LogicalType::Varchar(None, CharLengthUnits::Characters), LogicalType::Float, - &utf8("1.5") + utf8("1.5") )?, DataValue::Float32(OrderedFloat(1.5)) ); @@ -1656,7 +1825,7 @@ mod test { cast_eval( LogicalType::Varchar(None, CharLengthUnits::Characters), LogicalType::Double, - &utf8("1.5") + utf8("1.5") )?, DataValue::Float64(OrderedFloat(1.5)) ); @@ -1664,7 +1833,7 @@ mod test { cast_eval( LogicalType::Char(2, CharLengthUnits::Characters), LogicalType::Varchar(Some(2), CharLengthUnits::Characters), - &DataValue::Utf8 { + DataValue::Utf8 { value: "ab".to_string(), ty: Utf8Type::Fixed(2), unit: CharLengthUnits::Characters, @@ -1680,7 +1849,7 @@ mod test { cast_eval( LogicalType::Varchar(None, CharLengthUnits::Characters), LogicalType::Char(2, CharLengthUnits::Characters), - &utf8("ab"), + utf8("ab"), )?, DataValue::Utf8 { value: "ab".to_string(), @@ -1692,7 +1861,7 @@ mod test { cast_eval( LogicalType::Varchar(None, CharLengthUnits::Characters), LogicalType::Date, - &utf8("2024-01-02") + utf8("2024-01-02") )?, DataValue::Date32( chrono::NaiveDate::from_ymd_opt(2024, 1, 2) @@ -1704,7 +1873,7 @@ mod test { cast_eval( LogicalType::Varchar(None, CharLengthUnits::Characters), LogicalType::DateTime, - &utf8("2024-01-02 03:04:05"), + utf8("2024-01-02 03:04:05"), )?, DataValue::Date64( chrono::NaiveDate::from_ymd_opt(2024, 1, 2) @@ -1719,7 +1888,7 @@ mod test { cast_eval( LogicalType::Varchar(None, CharLengthUnits::Characters), LogicalType::Time(Some(0)), - &utf8("03:04:05"), + utf8("03:04:05"), )?, DataValue::Time32(DataValue::pack_time(3 * 3600 + 4 * 60 + 5, 0, 0), 0) ); @@ -1727,7 +1896,7 @@ mod test { cast_eval( LogicalType::Varchar(None, CharLengthUnits::Characters), LogicalType::TimeStamp(Some(0), true), - &utf8("2024-01-02 03:04:05+00:00"), + utf8("2024-01-02 03:04:05+00:00"), )?, DataValue::Time64( chrono::NaiveDate::from_ymd_opt(2024, 1, 2) @@ -1746,7 +1915,7 @@ mod test { cast_eval( LogicalType::Varchar(None, CharLengthUnits::Characters), LogicalType::Decimal(None, None), - &utf8("12.34"), + utf8("12.34"), )?, DataValue::Decimal(Decimal::new(1234, 2)) ); @@ -1765,7 +1934,7 @@ mod test { cast_eval( LogicalType::Date, LogicalType::Char(10, CharLengthUnits::Characters), - &date, + date.clone(), )?, DataValue::Utf8 { value: "2024-01-02".to_string(), @@ -1777,7 +1946,7 @@ mod test { cast_eval( LogicalType::Date, LogicalType::Varchar(Some(10), CharLengthUnits::Characters), - &date, + date.clone(), )?, DataValue::Utf8 { value: "2024-01-02".to_string(), @@ -1786,7 +1955,7 @@ mod test { } ); assert!(matches!( - cast_eval(LogicalType::Date, LogicalType::DateTime, &date)?, + cast_eval(LogicalType::Date, LogicalType::DateTime, date.clone())?, DataValue::Date64(_) )); @@ -1799,14 +1968,14 @@ mod test { .timestamp(), ); assert_eq!( - cast_eval(LogicalType::DateTime, LogicalType::Date, &datetime)?, + cast_eval(LogicalType::DateTime, LogicalType::Date, datetime.clone())?, date ); assert_eq!( cast_eval( LogicalType::DateTime, LogicalType::Char(19, CharLengthUnits::Characters), - &datetime, + datetime.clone(), )?, DataValue::Utf8 { value: "2024-01-02 03:04:05".to_string(), @@ -1818,7 +1987,7 @@ mod test { cast_eval( LogicalType::DateTime, LogicalType::Varchar(Some(19), CharLengthUnits::Characters), - &datetime, + datetime.clone(), )?, DataValue::Utf8 { value: "2024-01-02 03:04:05".to_string(), @@ -1827,14 +1996,18 @@ mod test { } ); assert!(matches!( - cast_eval(LogicalType::DateTime, LogicalType::Time(Some(0)), &datetime)?, + cast_eval( + LogicalType::DateTime, + LogicalType::Time(Some(0)), + datetime.clone() + )?, DataValue::Time32(_, 0) )); assert!(matches!( cast_eval( LogicalType::DateTime, LogicalType::TimeStamp(Some(0), false), - &datetime, + datetime.clone(), )?, DataValue::Time64(_, 0, false) )); @@ -1853,7 +2026,7 @@ mod test { cast_eval( LogicalType::TimeStamp(Some(3), false), LogicalType::Date, - ×tamp + timestamp.clone() )?, date ); @@ -1861,10 +2034,42 @@ mod test { cast_eval( LogicalType::TimeStamp(Some(3), false), LogicalType::Time(Some(3)), - ×tamp, + timestamp.clone(), )?, DataValue::Time32(_, 3) )); + assert_eq!( + cast_eval( + LogicalType::Date, + LogicalType::Varbinary(None), + date.clone() + )?, + DataValue::from(b"2024-01-02".to_vec()) + ); + assert_eq!( + cast_eval( + LogicalType::DateTime, + LogicalType::Varbinary(None), + datetime.clone() + )?, + DataValue::from(b"2024-01-02 03:04:05".to_vec()) + ); + assert_eq!( + cast_eval( + LogicalType::TimeStamp(Some(3), false), + LogicalType::Varbinary(None), + timestamp.clone() + )?, + DataValue::from(b"2024-01-02 03:04:05.123".to_vec()) + ); + assert_eq!( + cast_eval( + LogicalType::Time(Some(0)), + LogicalType::Varbinary(None), + DataValue::Time32(DataValue::pack_time(3 * 3600 + 4 * 60 + 5, 0, 0), 0) + )?, + DataValue::from(b"03:04:05".to_vec()) + ); let DataValue::Time64(timestamp_value, ..) = ×tamp else { unreachable!("timestamp fixture must be Time64") }; @@ -1872,7 +2077,7 @@ mod test { cast_eval( LogicalType::TimeStamp(Some(3), false), LogicalType::TimeStamp(Some(3), true), - ×tamp, + timestamp.clone(), )?, DataValue::Time64(*timestamp_value, 3, true) ); @@ -1880,7 +2085,7 @@ mod test { cast_eval( LogicalType::Time(Some(0)), LogicalType::Char(8, CharLengthUnits::Characters), - &DataValue::Time32(DataValue::pack_time(3 * 3600 + 4 * 60 + 5, 0, 0), 0), + DataValue::Time32(DataValue::pack_time(3 * 3600 + 4 * 60 + 5, 0, 0), 0), )?, DataValue::Utf8 { value: "03:04:05".to_string(), @@ -1892,7 +2097,7 @@ mod test { cast_eval( LogicalType::Time(Some(0)), LogicalType::Varchar(Some(8), CharLengthUnits::Characters), - &DataValue::Time32(DataValue::pack_time(3 * 3600 + 4 * 60 + 5, 0, 0), 0), + DataValue::Time32(DataValue::pack_time(3 * 3600 + 4 * 60 + 5, 0, 0), 0), )?, DataValue::Utf8 { value: "03:04:05".to_string(), @@ -1904,7 +2109,7 @@ mod test { cast_eval( LogicalType::TimeStamp(Some(3), false), LogicalType::Char(23, CharLengthUnits::Characters), - ×tamp, + timestamp.clone(), )?, DataValue::Utf8 { value: "2024-01-02 03:04:05.123".to_string(), @@ -1916,7 +2121,7 @@ mod test { cast_eval( LogicalType::TimeStamp(Some(3), false), LogicalType::Varchar(Some(23), CharLengthUnits::Characters), - ×tamp, + timestamp.clone(), )?, DataValue::Utf8 { value: "2024-01-02 03:04:05.123".to_string(), @@ -1931,6 +2136,40 @@ mod test { #[test] fn test_cast_create_dispatches_tuple_and_rejects_unsupported_casts() -> Result<(), DatabaseError> { + assert!(create( + LogicalType::Tuple(vec![LogicalType::Integer]), + LogicalType::Varbinary(None) + ) + .is_err()); + assert!(create(LogicalType::Varbinary(None), LogicalType::Bigint).is_err()); + let bytes = vec![0, 255]; + let buffer = bytes.as_ptr(); + let binary = create( + LogicalType::Varbinary(None), + LogicalType::Varbinary(Some(2)), + )? + .eval(DataValue::from(bytes))?; + assert_eq!(binary.binary().unwrap().as_ptr(), buffer); + assert_eq!( + binary.clone().cast(&LogicalType::Varbinary(Some(2)))?, + DataValue::Binary { + value: vec![0, 255], + len: Some(2) + } + ); + assert_eq!( + cast_eval( + LogicalType::Varbinary(None), + LogicalType::Varbinary(Some(2)), + DataValue::Null + )?, + DataValue::Null + ); + assert!(matches!( + binary.cast(&LogicalType::Varbinary(Some(1))), + Err(DatabaseError::TooLong) + )); + let tuple = DataValue::Tuple(vec![DataValue::Int32(1), utf8("2")]); assert_eq!( cast_eval( @@ -1939,7 +2178,7 @@ mod test { LogicalType::Varchar(None, CharLengthUnits::Characters), ]), LogicalType::Tuple(vec![LogicalType::Bigint, LogicalType::Integer]), - &tuple, + tuple.clone(), )?, DataValue::Tuple(vec![DataValue::Int64(1), DataValue::Int32(2)]) ); @@ -1956,7 +2195,7 @@ mod test { Err(DatabaseError::CastFail { .. }) )); assert_eq!( - create(LogicalType::Integer, LogicalType::Integer)?.eval(&DataValue::Int32(1))?, + create(LogicalType::Integer, LogicalType::Integer)?.eval(DataValue::Int32(1))?, DataValue::Int32(1) ); @@ -1970,7 +2209,7 @@ mod test { cast_eval( LogicalType::Float, LogicalType::Decimal(Some(4), Some(2)), - &DataValue::Float32(OrderedFloat(f32::NAN)), + DataValue::Float32(OrderedFloat(f32::NAN)), ), Err(DatabaseError::CastFail { .. }) )); @@ -1978,7 +2217,7 @@ mod test { cast_eval( LogicalType::Double, LogicalType::Decimal(Some(4), Some(2)), - &DataValue::Float64(OrderedFloat(f64::NAN)), + DataValue::Float64(OrderedFloat(f64::NAN)), ), Err(DatabaseError::CastFail { .. }) )); @@ -1987,6 +2226,14 @@ mod test { #[cfg(feature = "decimal")] #[test] fn test_cast_create_dispatches_decimal_casts() -> Result<(), DatabaseError> { + assert_eq!( + cast_eval( + LogicalType::Decimal(None, Some(2)), + LogicalType::Varbinary(None), + DataValue::Decimal(Decimal::new(125, 2)) + )?, + DataValue::from(b"1.25".to_vec()) + ); let value = DataValue::Decimal(Decimal::new(125, 1)); let cases = vec![ (LogicalType::Float, DataValue::Float32(OrderedFloat(12.5))), @@ -2023,7 +2270,7 @@ mod test { for (to, expected) in cases { assert_eq!( - cast_eval(LogicalType::Decimal(None, None), to, &value)?, + cast_eval(LogicalType::Decimal(None, None), to, value.clone())?, expected ); } diff --git a/src/types/evaluator/date.rs b/src/types/evaluator/date.rs index e6b71583..9df5454e 100644 --- a/src/types/evaluator/date.rs +++ b/src/types/evaluator/date.rs @@ -13,7 +13,7 @@ // limitations under the License. use crate::numeric_binary_evaluator_definition; -use crate::types::evaluator::cast::{cast_fail, to_char, to_varchar}; +use crate::types::evaluator::cast::{cast_fail, to_char, to_varbinary, to_varchar}; use crate::types::evaluator::DataValue; use crate::types::CharLengthUnits; use crate::types::LogicalType; @@ -27,7 +27,7 @@ crate::define_cast_evaluator!( }, DataValue::Date32(value) => |this| { to_char( - DataValue::format_date(*value).ok_or_else(|| { + DataValue::format_date(value).ok_or_else(|| { cast_fail(LogicalType::Date, LogicalType::Char(this.len, this.unit)) })?, this.len, @@ -42,7 +42,7 @@ crate::define_cast_evaluator!( }, DataValue::Date32(value) => |this| { to_varchar( - DataValue::format_date(*value).ok_or_else(|| { + DataValue::format_date(value).ok_or_else(|| { cast_fail(LogicalType::Date, LogicalType::Varchar(this.len, this.unit)) })?, this.len, @@ -50,8 +50,19 @@ crate::define_cast_evaluator!( ) } ); +crate::define_cast_evaluator!( + date32_to_varbinary_cast_eval { len: Option }, + DataValue::Date32(value) => |this| { + to_varbinary( + DataValue::format_date(value).ok_or_else(|| { + cast_fail(LogicalType::Date, LogicalType::Varbinary(this.len)) + })?.into_bytes(), + this.len, + ) + } +); crate::define_cast_evaluator!(date32_to_datetime_cast_eval, DataValue::Date32(value) => { - let value = NaiveDate::from_num_days_from_ce_opt(*value) + let value = NaiveDate::from_num_days_from_ce_opt(value) .ok_or_else(|| cast_fail(LogicalType::Date, LogicalType::DateTime))? .and_hms_opt(0, 0, 0) .ok_or_else(|| cast_fail(LogicalType::Date, LogicalType::DateTime))? @@ -78,7 +89,7 @@ mod test { .num_days_from_ce(), ); assert_eq!( - date32_to_char_cast_eval(10, CharLengthUnits::Characters, &value).unwrap(), + date32_to_char_cast_eval(10, CharLengthUnits::Characters, value.clone()).unwrap(), DataValue::Utf8 { value: "2024-01-02".to_string(), ty: Utf8Type::Fixed(10), @@ -86,7 +97,8 @@ mod test { } ); assert_eq!( - date32_to_varchar_cast_eval(Some(10), CharLengthUnits::Characters, &value).unwrap(), + date32_to_varchar_cast_eval(Some(10), CharLengthUnits::Characters, value.clone()) + .unwrap(), DataValue::Utf8 { value: "2024-01-02".to_string(), ty: Utf8Type::Variable(Some(10)), @@ -94,7 +106,7 @@ mod test { } ); assert_eq!( - date32_to_datetime_cast_eval(&value).unwrap(), + date32_to_datetime_cast_eval(value.clone()).unwrap(), DataValue::Date64( NaiveDate::from_ymd_opt(2024, 1, 2) .unwrap() @@ -110,15 +122,15 @@ mod test { ); let invalid = DataValue::Date32(i32::MAX); assert!(matches!( - date32_to_char_cast_eval(10, CharLengthUnits::Characters, &invalid), + date32_to_char_cast_eval(10, CharLengthUnits::Characters, invalid.clone()), Err(DatabaseError::CastFail { .. }) )); assert!(matches!( - date32_to_varchar_cast_eval(Some(10), CharLengthUnits::Characters, &invalid), + date32_to_varchar_cast_eval(Some(10), CharLengthUnits::Characters, invalid.clone()), Err(DatabaseError::CastFail { .. }) )); assert!(matches!( - date32_to_datetime_cast_eval(&invalid), + date32_to_datetime_cast_eval(invalid.clone()), Err(DatabaseError::CastFail { .. }) )); } diff --git a/src/types/evaluator/datetime.rs b/src/types/evaluator/datetime.rs index ca9daaea..ad8a448d 100644 --- a/src/types/evaluator/datetime.rs +++ b/src/types/evaluator/datetime.rs @@ -13,7 +13,7 @@ // limitations under the License. use crate::numeric_binary_evaluator_definition; -use crate::types::evaluator::cast::{cast_fail, to_char, to_varchar}; +use crate::types::evaluator::cast::{cast_fail, to_char, to_varbinary, to_varchar}; use crate::types::evaluator::DataValue; use crate::types::CharLengthUnits; use crate::types::LogicalType; @@ -27,7 +27,7 @@ crate::define_cast_evaluator!( }, DataValue::Date64(value) => |this| { to_char( - DataValue::format_datetime(*value).ok_or_else(|| { + DataValue::format_datetime(value).ok_or_else(|| { cast_fail(LogicalType::DateTime, LogicalType::Char(this.len, this.unit)) })?, this.len, @@ -42,7 +42,7 @@ crate::define_cast_evaluator!( }, DataValue::Date64(value) => |this| { to_varchar( - DataValue::format_datetime(*value).ok_or_else(|| { + DataValue::format_datetime(value).ok_or_else(|| { cast_fail(LogicalType::DateTime, LogicalType::Varchar(this.len, this.unit)) })?, this.len, @@ -50,8 +50,19 @@ crate::define_cast_evaluator!( ) } ); +crate::define_cast_evaluator!( + date64_to_varbinary_cast_eval { len: Option }, + DataValue::Date64(value) => |this| { + to_varbinary( + DataValue::format_datetime(value).ok_or_else(|| { + cast_fail(LogicalType::DateTime, LogicalType::Varbinary(this.len)) + })?.into_bytes(), + this.len, + ) + } +); crate::define_cast_evaluator!(date64_to_date_cast_eval, DataValue::Date64(value) => { - let value = DateTime::from_timestamp(*value, 0) + let value = DateTime::from_timestamp(value, 0) .ok_or_else(|| cast_fail(LogicalType::DateTime, LogicalType::Date))? .naive_utc() .date() @@ -66,7 +77,7 @@ crate::define_cast_evaluator!( }, DataValue::Date64(value) => |this| { let precision = this.precision.unwrap_or(0); - let value = DateTime::from_timestamp(*value, 0) + let value = DateTime::from_timestamp(value, 0) .map(|date_time| date_time.time().num_seconds_from_midnight()) .ok_or_else(|| { cast_fail(LogicalType::DateTime, LogicalType::Time(this.precision)) @@ -81,7 +92,7 @@ crate::define_cast_evaluator!( zone: bool }, DataValue::Date64(value) => |this| { - Ok(DataValue::Time64(*value, this.precision.unwrap_or(0), this.zone)) + Ok(DataValue::Time64(value, this.precision.unwrap_or(0), this.zone)) } ); @@ -104,7 +115,7 @@ mod test { .timestamp(), ); assert_eq!( - date64_to_char_cast_eval(19, CharLengthUnits::Characters, &value).unwrap(), + date64_to_char_cast_eval(19, CharLengthUnits::Characters, value.clone()).unwrap(), DataValue::Utf8 { value: "2024-01-02 03:04:05".to_string(), ty: Utf8Type::Fixed(19), @@ -112,7 +123,8 @@ mod test { } ); assert_eq!( - date64_to_varchar_cast_eval(Some(19), CharLengthUnits::Characters, &value).unwrap(), + date64_to_varchar_cast_eval(Some(19), CharLengthUnits::Characters, value.clone()) + .unwrap(), DataValue::Utf8 { value: "2024-01-02 03:04:05".to_string(), ty: Utf8Type::Variable(Some(19)), @@ -120,7 +132,7 @@ mod test { } ); assert_eq!( - date64_to_date_cast_eval(&value).unwrap(), + date64_to_date_cast_eval(value.clone()).unwrap(), DataValue::Date32( chrono::NaiveDate::from_ymd_opt(2024, 1, 2) .unwrap() @@ -128,11 +140,11 @@ mod test { ) ); assert_eq!( - date64_to_time_cast_eval(Some(0), &value).unwrap(), + date64_to_time_cast_eval(Some(0), value.clone()).unwrap(), DataValue::Time32(DataValue::pack_time(3 * 3600 + 4 * 60 + 5, 0, 0), 0) ); assert_eq!( - date64_to_timestamp_cast_eval(Some(0), true, &value).unwrap(), + date64_to_timestamp_cast_eval(Some(0), true, value.clone()).unwrap(), DataValue::Time64( chrono::NaiveDate::from_ymd_opt(2024, 1, 2) .unwrap() @@ -151,19 +163,19 @@ mod test { let invalid = DataValue::Date64(i64::MAX); assert!(matches!( - date64_to_char_cast_eval(19, CharLengthUnits::Characters, &invalid), + date64_to_char_cast_eval(19, CharLengthUnits::Characters, invalid.clone()), Err(DatabaseError::CastFail { .. }) )); assert!(matches!( - date64_to_varchar_cast_eval(Some(19), CharLengthUnits::Characters, &invalid), + date64_to_varchar_cast_eval(Some(19), CharLengthUnits::Characters, invalid.clone()), Err(DatabaseError::CastFail { .. }) )); assert!(matches!( - date64_to_date_cast_eval(&invalid), + date64_to_date_cast_eval(invalid.clone()), Err(DatabaseError::CastFail { .. }) )); assert!(matches!( - date64_to_time_cast_eval(Some(0), &invalid), + date64_to_time_cast_eval(Some(0), invalid.clone()), Err(DatabaseError::CastFail { .. }) )); } diff --git a/src/types/evaluator/decimal.rs b/src/types/evaluator/decimal.rs index d9f54d80..c4cf41c1 100644 --- a/src/types/evaluator/decimal.rs +++ b/src/types/evaluator/decimal.rs @@ -13,7 +13,7 @@ // limitations under the License. use crate::errors::DatabaseError; -use crate::types::evaluator::cast::{to_char, to_varchar}; +use crate::types::evaluator::cast::{to_char, to_varbinary, to_varchar}; use crate::types::evaluator::DataValue; use crate::types::CharLengthUnits; use ordered_float::OrderedFloat; @@ -60,7 +60,7 @@ crate::define_cast_evaluator!(decimal_to_double_cast_eval, DataValue::Decimal(va })?))) }); crate::define_cast_evaluator!(decimal_to_decimal_cast_eval, DataValue::Decimal(value) => { - Ok(DataValue::Decimal(*value)) + Ok(DataValue::Decimal(value)) }); crate::define_cast_evaluator!( decimal_to_char_cast_eval { @@ -76,29 +76,33 @@ crate::define_cast_evaluator!( }, DataValue::Decimal(value) => |this| to_varchar(value.to_string(), this.len, this.unit) ); +crate::define_cast_evaluator!( + decimal_to_varbinary_cast_eval { len: Option }, + DataValue::Decimal(value) => |this| to_varbinary(value.to_string().into_bytes(), this.len) +); crate::define_cast_evaluator!(decimal_to_tinyint_cast_eval, DataValue::Decimal(value) => { - Ok(DataValue::Int8(crate::decimal_to_int_cast!(*value, i8))) + Ok(DataValue::Int8(crate::decimal_to_int_cast!(value, i8))) }); crate::define_cast_evaluator!(decimal_to_smallint_cast_eval, DataValue::Decimal(value) => { - Ok(DataValue::Int16(crate::decimal_to_int_cast!(*value, i16))) + Ok(DataValue::Int16(crate::decimal_to_int_cast!(value, i16))) }); crate::define_cast_evaluator!(decimal_to_integer_cast_eval, DataValue::Decimal(value) => { - Ok(DataValue::Int32(crate::decimal_to_int_cast!(*value, i32))) + Ok(DataValue::Int32(crate::decimal_to_int_cast!(value, i32))) }); crate::define_cast_evaluator!(decimal_to_bigint_cast_eval, DataValue::Decimal(value) => { - Ok(DataValue::Int64(crate::decimal_to_int_cast!(*value, i64))) + Ok(DataValue::Int64(crate::decimal_to_int_cast!(value, i64))) }); crate::define_cast_evaluator!(decimal_to_utinyint_cast_eval, DataValue::Decimal(value) => { - Ok(DataValue::UInt8(crate::decimal_to_int_cast!(*value, u8))) + Ok(DataValue::UInt8(crate::decimal_to_int_cast!(value, u8))) }); crate::define_cast_evaluator!(decimal_to_usmallint_cast_eval, DataValue::Decimal(value) => { - Ok(DataValue::UInt16(crate::decimal_to_int_cast!(*value, u16))) + Ok(DataValue::UInt16(crate::decimal_to_int_cast!(value, u16))) }); crate::define_cast_evaluator!(decimal_to_uinteger_cast_eval, DataValue::Decimal(value) => { - Ok(DataValue::UInt32(crate::decimal_to_int_cast!(*value, u32))) + Ok(DataValue::UInt32(crate::decimal_to_int_cast!(value, u32))) }); crate::define_cast_evaluator!(decimal_to_ubigint_cast_eval, DataValue::Decimal(value) => { - Ok(DataValue::UInt64(crate::decimal_to_int_cast!(*value, u64))) + Ok(DataValue::UInt64(crate::decimal_to_int_cast!(value, u64))) }); decimal_binary!( decimal_multiply_binary_eval, @@ -150,19 +154,19 @@ mod test { let value = DataValue::Decimal(Decimal::new(125, 1)); assert_eq!( - decimal_to_float_cast_eval(&value).unwrap(), + decimal_to_float_cast_eval(value.clone()).unwrap(), DataValue::Float32(OrderedFloat(12.5)) ); assert_eq!( - decimal_to_double_cast_eval(&value).unwrap(), + decimal_to_double_cast_eval(value.clone()).unwrap(), DataValue::Float64(OrderedFloat(12.5)) ); assert_eq!( - decimal_to_decimal_cast_eval(&value).unwrap(), + decimal_to_decimal_cast_eval(value.clone()).unwrap(), DataValue::Decimal(Decimal::new(125, 1)) ); assert_eq!( - decimal_to_char_cast_eval(4, CharLengthUnits::Characters, &value).unwrap(), + decimal_to_char_cast_eval(4, CharLengthUnits::Characters, value.clone()).unwrap(), DataValue::Utf8 { value: "12.5".to_string(), ty: Utf8Type::Fixed(4), @@ -170,7 +174,8 @@ mod test { } ); assert_eq!( - decimal_to_varchar_cast_eval(Some(4), CharLengthUnits::Characters, &value).unwrap(), + decimal_to_varchar_cast_eval(Some(4), CharLengthUnits::Characters, value.clone()) + .unwrap(), DataValue::Utf8 { value: "12.5".to_string(), ty: Utf8Type::Variable(Some(4)), @@ -178,35 +183,35 @@ mod test { } ); assert_eq!( - decimal_to_tinyint_cast_eval(&value).unwrap(), + decimal_to_tinyint_cast_eval(value.clone()).unwrap(), DataValue::Int8(12) ); assert_eq!( - decimal_to_smallint_cast_eval(&value).unwrap(), + decimal_to_smallint_cast_eval(value.clone()).unwrap(), DataValue::Int16(12) ); assert_eq!( - decimal_to_integer_cast_eval(&value).unwrap(), + decimal_to_integer_cast_eval(value.clone()).unwrap(), DataValue::Int32(12) ); assert_eq!( - decimal_to_bigint_cast_eval(&value).unwrap(), + decimal_to_bigint_cast_eval(value.clone()).unwrap(), DataValue::Int64(12) ); assert_eq!( - decimal_to_utinyint_cast_eval(&value).unwrap(), + decimal_to_utinyint_cast_eval(value.clone()).unwrap(), DataValue::UInt8(12) ); assert_eq!( - decimal_to_usmallint_cast_eval(&value).unwrap(), + decimal_to_usmallint_cast_eval(value.clone()).unwrap(), DataValue::UInt16(12) ); assert_eq!( - decimal_to_uinteger_cast_eval(&value).unwrap(), + decimal_to_uinteger_cast_eval(value.clone()).unwrap(), DataValue::UInt32(12) ); assert_eq!( - decimal_to_ubigint_cast_eval(&value).unwrap(), + decimal_to_ubigint_cast_eval(value.clone()).unwrap(), DataValue::UInt64(12) ); } diff --git a/src/types/evaluator/float32.rs b/src/types/evaluator/float32.rs index b7620cc5..af4e2d69 100644 --- a/src/types/evaluator/float32.rs +++ b/src/types/evaluator/float32.rs @@ -125,47 +125,47 @@ mod test { let value = DataValue::Float32(OrderedFloat(1.5)); assert_eq!( - float32_to_float_cast_eval(&value).unwrap(), + float32_to_float_cast_eval(value.clone()).unwrap(), DataValue::Float32(OrderedFloat(1.5)) ); assert_eq!( - float32_to_double_cast_eval(&value).unwrap(), + float32_to_double_cast_eval(value.clone()).unwrap(), DataValue::Float64(OrderedFloat(1.5)) ); assert_eq!( - float32_to_tinyint_cast_eval(&value).unwrap(), + float32_to_tinyint_cast_eval(value.clone()).unwrap(), DataValue::Int8(1) ); assert_eq!( - float32_to_smallint_cast_eval(&value).unwrap(), + float32_to_smallint_cast_eval(value.clone()).unwrap(), DataValue::Int16(1) ); assert_eq!( - float32_to_integer_cast_eval(&value).unwrap(), + float32_to_integer_cast_eval(value.clone()).unwrap(), DataValue::Int32(1) ); assert_eq!( - float32_to_bigint_cast_eval(&value).unwrap(), + float32_to_bigint_cast_eval(value.clone()).unwrap(), DataValue::Int64(1) ); assert_eq!( - float32_to_utinyint_cast_eval(&value).unwrap(), + float32_to_utinyint_cast_eval(value.clone()).unwrap(), DataValue::UInt8(1) ); assert_eq!( - float32_to_usmallint_cast_eval(&value).unwrap(), + float32_to_usmallint_cast_eval(value.clone()).unwrap(), DataValue::UInt16(1) ); assert_eq!( - float32_to_uinteger_cast_eval(&value).unwrap(), + float32_to_uinteger_cast_eval(value.clone()).unwrap(), DataValue::UInt32(1) ); assert_eq!( - float32_to_ubigint_cast_eval(&value).unwrap(), + float32_to_ubigint_cast_eval(value.clone()).unwrap(), DataValue::UInt64(1) ); assert_eq!( - float32_to_char_cast_eval(3, CharLengthUnits::Characters, &value).unwrap(), + float32_to_char_cast_eval(3, CharLengthUnits::Characters, value.clone()).unwrap(), DataValue::Utf8 { value: "1.5".to_string(), ty: Utf8Type::Fixed(3), @@ -173,7 +173,8 @@ mod test { } ); assert_eq!( - float32_to_varchar_cast_eval(Some(3), CharLengthUnits::Characters, &value).unwrap(), + float32_to_varchar_cast_eval(Some(3), CharLengthUnits::Characters, value.clone()) + .unwrap(), DataValue::Utf8 { value: "1.5".to_string(), ty: Utf8Type::Variable(Some(3)), @@ -182,7 +183,7 @@ mod test { ); #[cfg(feature = "decimal")] assert_eq!( - float32_to_decimal_cast_eval(None, Some(1), &value).unwrap(), + float32_to_decimal_cast_eval(None, Some(1), value.clone()).unwrap(), DataValue::Decimal(Decimal::new(15, 1)) ); } diff --git a/src/types/evaluator/float64.rs b/src/types/evaluator/float64.rs index a1bc68d5..a4ec98c8 100644 --- a/src/types/evaluator/float64.rs +++ b/src/types/evaluator/float64.rs @@ -14,9 +14,9 @@ use crate::errors::DatabaseError; #[cfg(feature = "decimal")] -use crate::types::evaluator::cast::{cast_fail, to_char, to_varchar}; +use crate::types::evaluator::cast::{cast_fail, to_char, to_varbinary, to_varchar}; #[cfg(not(feature = "decimal"))] -use crate::types::evaluator::cast::{to_char, to_varchar}; +use crate::types::evaluator::cast::{to_char, to_varbinary, to_varchar}; use crate::types::evaluator::DataValue; use crate::types::CharLengthUnits; #[cfg(feature = "decimal")] @@ -65,7 +65,7 @@ crate::define_cast_evaluator!(float64_to_float_cast_eval, DataValue::Float64(val Ok(DataValue::Float32(ordered_float::OrderedFloat(value.0 as f32))) }); crate::define_cast_evaluator!(float64_to_double_cast_eval, DataValue::Float64(value) => { - Ok(DataValue::Float64(*value)) + Ok(DataValue::Float64(value)) }); crate::define_cast_evaluator!(float64_to_tinyint_cast_eval, DataValue::Float64(value) => { Ok(DataValue::Int8(crate::float_to_int_cast!(value.into_inner(), i8, f64)?)) @@ -105,6 +105,10 @@ crate::define_cast_evaluator!( }, DataValue::Float64(value) => |this| to_varchar(value.to_string(), this.len, this.unit) ); +crate::define_cast_evaluator!( + float64_to_varbinary_cast_eval { len: Option }, + DataValue::Float64(value) => |this| to_varbinary(value.to_string().into_bytes(), this.len) +); #[cfg(feature = "decimal")] crate::define_cast_evaluator!( float64_to_decimal_cast_eval { @@ -289,47 +293,47 @@ mod test { DataValue::Null ); assert_eq!( - float64_to_float_cast_eval(&value).unwrap(), + float64_to_float_cast_eval(value.clone()).unwrap(), DataValue::Float32(ordered_float::OrderedFloat(1.5)) ); assert_eq!( - float64_to_double_cast_eval(&value).unwrap(), + float64_to_double_cast_eval(value.clone()).unwrap(), DataValue::Float64(ordered_float::OrderedFloat(1.5)) ); assert_eq!( - float64_to_tinyint_cast_eval(&value).unwrap(), + float64_to_tinyint_cast_eval(value.clone()).unwrap(), DataValue::Int8(1) ); assert_eq!( - float64_to_smallint_cast_eval(&value).unwrap(), + float64_to_smallint_cast_eval(value.clone()).unwrap(), DataValue::Int16(1) ); assert_eq!( - float64_to_integer_cast_eval(&value).unwrap(), + float64_to_integer_cast_eval(value.clone()).unwrap(), DataValue::Int32(1) ); assert_eq!( - float64_to_bigint_cast_eval(&value).unwrap(), + float64_to_bigint_cast_eval(value.clone()).unwrap(), DataValue::Int64(1) ); assert_eq!( - float64_to_utinyint_cast_eval(&value).unwrap(), + float64_to_utinyint_cast_eval(value.clone()).unwrap(), DataValue::UInt8(1) ); assert_eq!( - float64_to_usmallint_cast_eval(&value).unwrap(), + float64_to_usmallint_cast_eval(value.clone()).unwrap(), DataValue::UInt16(1) ); assert_eq!( - float64_to_uinteger_cast_eval(&value).unwrap(), + float64_to_uinteger_cast_eval(value.clone()).unwrap(), DataValue::UInt32(1) ); assert_eq!( - float64_to_ubigint_cast_eval(&value).unwrap(), + float64_to_ubigint_cast_eval(value.clone()).unwrap(), DataValue::UInt64(1) ); assert_eq!( - float64_to_char_cast_eval(3, CharLengthUnits::Characters, &value).unwrap(), + float64_to_char_cast_eval(3, CharLengthUnits::Characters, value.clone()).unwrap(), DataValue::Utf8 { value: "1.5".to_string(), ty: Utf8Type::Fixed(3), @@ -337,7 +341,8 @@ mod test { } ); assert_eq!( - float64_to_varchar_cast_eval(Some(3), CharLengthUnits::Characters, &value).unwrap(), + float64_to_varchar_cast_eval(Some(3), CharLengthUnits::Characters, value.clone()) + .unwrap(), DataValue::Utf8 { value: "1.5".to_string(), ty: Utf8Type::Variable(Some(3)), @@ -346,7 +351,7 @@ mod test { ); #[cfg(feature = "decimal")] assert_eq!( - float64_to_decimal_cast_eval(None, Some(1), &value).unwrap(), + float64_to_decimal_cast_eval(None, Some(1), value.clone()).unwrap(), DataValue::Decimal(Decimal::new(15, 1)) ); #[cfg(feature = "decimal")] @@ -354,7 +359,7 @@ mod test { float64_to_decimal_cast_eval( Some(4), Some(2), - &DataValue::Float64(ordered_float::OrderedFloat(f64::NAN)), + DataValue::Float64(ordered_float::OrderedFloat(f64::NAN)), ), Err(DatabaseError::CastFail { .. }) )); diff --git a/src/types/evaluator/int16.rs b/src/types/evaluator/int16.rs index f74199dc..4432bb27 100644 --- a/src/types/evaluator/int16.rs +++ b/src/types/evaluator/int16.rs @@ -34,51 +34,51 @@ mod test { let value = DataValue::Int16(1); assert_eq!( - int16_to_boolean_cast_eval(&value).unwrap(), + int16_to_boolean_cast_eval(value.clone()).unwrap(), DataValue::Boolean(true) ); assert_eq!( - int16_to_tinyint_cast_eval(&value).unwrap(), + int16_to_tinyint_cast_eval(value.clone()).unwrap(), DataValue::Int8(1) ); assert_eq!( - int16_to_utinyint_cast_eval(&value).unwrap(), + int16_to_utinyint_cast_eval(value.clone()).unwrap(), DataValue::UInt8(1) ); assert_eq!( - int16_to_smallint_cast_eval(&value).unwrap(), + int16_to_smallint_cast_eval(value.clone()).unwrap(), DataValue::Int16(1) ); assert_eq!( - int16_to_usmallint_cast_eval(&value).unwrap(), + int16_to_usmallint_cast_eval(value.clone()).unwrap(), DataValue::UInt16(1) ); assert_eq!( - int16_to_integer_cast_eval(&value).unwrap(), + int16_to_integer_cast_eval(value.clone()).unwrap(), DataValue::Int32(1) ); assert_eq!( - int16_to_uinteger_cast_eval(&value).unwrap(), + int16_to_uinteger_cast_eval(value.clone()).unwrap(), DataValue::UInt32(1) ); assert_eq!( - int16_to_bigint_cast_eval(&value).unwrap(), + int16_to_bigint_cast_eval(value.clone()).unwrap(), DataValue::Int64(1) ); assert_eq!( - int16_to_ubigint_cast_eval(&value).unwrap(), + int16_to_ubigint_cast_eval(value.clone()).unwrap(), DataValue::UInt64(1) ); assert_eq!( - int16_to_float_cast_eval(&value).unwrap(), + int16_to_float_cast_eval(value.clone()).unwrap(), DataValue::Float32(OrderedFloat(1.0)) ); assert_eq!( - int16_to_double_cast_eval(&value).unwrap(), + int16_to_double_cast_eval(value.clone()).unwrap(), DataValue::Float64(OrderedFloat(1.0)) ); assert_eq!( - int16_to_char_cast_eval(1, CharLengthUnits::Characters, &value).unwrap(), + int16_to_char_cast_eval(1, CharLengthUnits::Characters, value.clone()).unwrap(), DataValue::Utf8 { value: "1".to_string(), ty: Utf8Type::Fixed(1), @@ -86,7 +86,8 @@ mod test { } ); assert_eq!( - int16_to_varchar_cast_eval(Some(1), CharLengthUnits::Characters, &value).unwrap(), + int16_to_varchar_cast_eval(Some(1), CharLengthUnits::Characters, value.clone()) + .unwrap(), DataValue::Utf8 { value: "1".to_string(), ty: Utf8Type::Variable(Some(1)), @@ -94,7 +95,7 @@ mod test { } ); assert_eq!( - int16_to_decimal_cast_eval(Some(2), &value).unwrap(), + int16_to_decimal_cast_eval(Some(2), value.clone()).unwrap(), DataValue::Decimal(Decimal::new(100, 2)) ); } diff --git a/src/types/evaluator/int32.rs b/src/types/evaluator/int32.rs index 0f43e129..32b97f5b 100644 --- a/src/types/evaluator/int32.rs +++ b/src/types/evaluator/int32.rs @@ -54,51 +54,51 @@ mod test { let value = DataValue::Int32(1); assert_eq!( - int32_to_boolean_cast_eval(&value).unwrap(), + int32_to_boolean_cast_eval(value.clone()).unwrap(), DataValue::Boolean(true) ); assert_eq!( - int32_to_tinyint_cast_eval(&value).unwrap(), + int32_to_tinyint_cast_eval(value.clone()).unwrap(), DataValue::Int8(1) ); assert_eq!( - int32_to_utinyint_cast_eval(&value).unwrap(), + int32_to_utinyint_cast_eval(value.clone()).unwrap(), DataValue::UInt8(1) ); assert_eq!( - int32_to_smallint_cast_eval(&value).unwrap(), + int32_to_smallint_cast_eval(value.clone()).unwrap(), DataValue::Int16(1) ); assert_eq!( - int32_to_usmallint_cast_eval(&value).unwrap(), + int32_to_usmallint_cast_eval(value.clone()).unwrap(), DataValue::UInt16(1) ); assert_eq!( - int32_to_integer_cast_eval(&value).unwrap(), + int32_to_integer_cast_eval(value.clone()).unwrap(), DataValue::Int32(1) ); assert_eq!( - int32_to_uinteger_cast_eval(&value).unwrap(), + int32_to_uinteger_cast_eval(value.clone()).unwrap(), DataValue::UInt32(1) ); assert_eq!( - int32_to_bigint_cast_eval(&value).unwrap(), + int32_to_bigint_cast_eval(value.clone()).unwrap(), DataValue::Int64(1) ); assert_eq!( - int32_to_ubigint_cast_eval(&value).unwrap(), + int32_to_ubigint_cast_eval(value.clone()).unwrap(), DataValue::UInt64(1) ); assert_eq!( - int32_to_float_cast_eval(&value).unwrap(), + int32_to_float_cast_eval(value.clone()).unwrap(), DataValue::Float32(OrderedFloat(1.0)) ); assert_eq!( - int32_to_double_cast_eval(&value).unwrap(), + int32_to_double_cast_eval(value.clone()).unwrap(), DataValue::Float64(OrderedFloat(1.0)) ); assert_eq!( - int32_to_char_cast_eval(1, CharLengthUnits::Characters, &value).unwrap(), + int32_to_char_cast_eval(1, CharLengthUnits::Characters, value.clone()).unwrap(), DataValue::Utf8 { value: "1".to_string(), ty: Utf8Type::Fixed(1), @@ -106,7 +106,8 @@ mod test { } ); assert_eq!( - int32_to_varchar_cast_eval(Some(1), CharLengthUnits::Characters, &value).unwrap(), + int32_to_varchar_cast_eval(Some(1), CharLengthUnits::Characters, value.clone()) + .unwrap(), DataValue::Utf8 { value: "1".to_string(), ty: Utf8Type::Variable(Some(1)), @@ -114,7 +115,7 @@ mod test { } ); assert_eq!( - int32_to_decimal_cast_eval(Some(1), &value).unwrap(), + int32_to_decimal_cast_eval(Some(1), value.clone()).unwrap(), DataValue::Decimal(Decimal::new(10, 1)) ); } diff --git a/src/types/evaluator/int64.rs b/src/types/evaluator/int64.rs index 0cd23c8c..2206f6e7 100644 --- a/src/types/evaluator/int64.rs +++ b/src/types/evaluator/int64.rs @@ -34,51 +34,51 @@ mod test { let value = DataValue::Int64(1); assert_eq!( - int64_to_boolean_cast_eval(&value).unwrap(), + int64_to_boolean_cast_eval(value.clone()).unwrap(), DataValue::Boolean(true) ); assert_eq!( - int64_to_tinyint_cast_eval(&value).unwrap(), + int64_to_tinyint_cast_eval(value.clone()).unwrap(), DataValue::Int8(1) ); assert_eq!( - int64_to_utinyint_cast_eval(&value).unwrap(), + int64_to_utinyint_cast_eval(value.clone()).unwrap(), DataValue::UInt8(1) ); assert_eq!( - int64_to_smallint_cast_eval(&value).unwrap(), + int64_to_smallint_cast_eval(value.clone()).unwrap(), DataValue::Int16(1) ); assert_eq!( - int64_to_usmallint_cast_eval(&value).unwrap(), + int64_to_usmallint_cast_eval(value.clone()).unwrap(), DataValue::UInt16(1) ); assert_eq!( - int64_to_integer_cast_eval(&value).unwrap(), + int64_to_integer_cast_eval(value.clone()).unwrap(), DataValue::Int32(1) ); assert_eq!( - int64_to_uinteger_cast_eval(&value).unwrap(), + int64_to_uinteger_cast_eval(value.clone()).unwrap(), DataValue::UInt32(1) ); assert_eq!( - int64_to_bigint_cast_eval(&value).unwrap(), + int64_to_bigint_cast_eval(value.clone()).unwrap(), DataValue::Int64(1) ); assert_eq!( - int64_to_ubigint_cast_eval(&value).unwrap(), + int64_to_ubigint_cast_eval(value.clone()).unwrap(), DataValue::UInt64(1) ); assert_eq!( - int64_to_float_cast_eval(&value).unwrap(), + int64_to_float_cast_eval(value.clone()).unwrap(), DataValue::Float32(OrderedFloat(1.0)) ); assert_eq!( - int64_to_double_cast_eval(&value).unwrap(), + int64_to_double_cast_eval(value.clone()).unwrap(), DataValue::Float64(OrderedFloat(1.0)) ); assert_eq!( - int64_to_char_cast_eval(1, CharLengthUnits::Characters, &value).unwrap(), + int64_to_char_cast_eval(1, CharLengthUnits::Characters, value.clone()).unwrap(), DataValue::Utf8 { value: "1".to_string(), ty: Utf8Type::Fixed(1), @@ -86,7 +86,8 @@ mod test { } ); assert_eq!( - int64_to_varchar_cast_eval(Some(1), CharLengthUnits::Characters, &value).unwrap(), + int64_to_varchar_cast_eval(Some(1), CharLengthUnits::Characters, value.clone()) + .unwrap(), DataValue::Utf8 { value: "1".to_string(), ty: Utf8Type::Variable(Some(1)), @@ -94,7 +95,7 @@ mod test { } ); assert_eq!( - int64_to_decimal_cast_eval(Some(2), &value).unwrap(), + int64_to_decimal_cast_eval(Some(2), value.clone()).unwrap(), DataValue::Decimal(Decimal::new(100, 2)) ); } diff --git a/src/types/evaluator/int8.rs b/src/types/evaluator/int8.rs index 5f69cc7a..b7a55756 100644 --- a/src/types/evaluator/int8.rs +++ b/src/types/evaluator/int8.rs @@ -34,51 +34,51 @@ mod test { let value = DataValue::Int8(1); assert_eq!( - int8_to_boolean_cast_eval(&value).unwrap(), + int8_to_boolean_cast_eval(value.clone()).unwrap(), DataValue::Boolean(true) ); assert_eq!( - int8_to_tinyint_cast_eval(&value).unwrap(), + int8_to_tinyint_cast_eval(value.clone()).unwrap(), DataValue::Int8(1) ); assert_eq!( - int8_to_utinyint_cast_eval(&value).unwrap(), + int8_to_utinyint_cast_eval(value.clone()).unwrap(), DataValue::UInt8(1) ); assert_eq!( - int8_to_smallint_cast_eval(&value).unwrap(), + int8_to_smallint_cast_eval(value.clone()).unwrap(), DataValue::Int16(1) ); assert_eq!( - int8_to_usmallint_cast_eval(&value).unwrap(), + int8_to_usmallint_cast_eval(value.clone()).unwrap(), DataValue::UInt16(1) ); assert_eq!( - int8_to_integer_cast_eval(&value).unwrap(), + int8_to_integer_cast_eval(value.clone()).unwrap(), DataValue::Int32(1) ); assert_eq!( - int8_to_uinteger_cast_eval(&value).unwrap(), + int8_to_uinteger_cast_eval(value.clone()).unwrap(), DataValue::UInt32(1) ); assert_eq!( - int8_to_bigint_cast_eval(&value).unwrap(), + int8_to_bigint_cast_eval(value.clone()).unwrap(), DataValue::Int64(1) ); assert_eq!( - int8_to_ubigint_cast_eval(&value).unwrap(), + int8_to_ubigint_cast_eval(value.clone()).unwrap(), DataValue::UInt64(1) ); assert_eq!( - int8_to_float_cast_eval(&value).unwrap(), + int8_to_float_cast_eval(value.clone()).unwrap(), DataValue::Float32(OrderedFloat(1.0)) ); assert_eq!( - int8_to_double_cast_eval(&value).unwrap(), + int8_to_double_cast_eval(value.clone()).unwrap(), DataValue::Float64(OrderedFloat(1.0)) ); assert_eq!( - int8_to_char_cast_eval(1, CharLengthUnits::Characters, &value).unwrap(), + int8_to_char_cast_eval(1, CharLengthUnits::Characters, value.clone()).unwrap(), DataValue::Utf8 { value: "1".to_string(), ty: Utf8Type::Fixed(1), @@ -86,7 +86,7 @@ mod test { } ); assert_eq!( - int8_to_varchar_cast_eval(Some(1), CharLengthUnits::Characters, &value).unwrap(), + int8_to_varchar_cast_eval(Some(1), CharLengthUnits::Characters, value.clone()).unwrap(), DataValue::Utf8 { value: "1".to_string(), ty: Utf8Type::Variable(Some(1)), @@ -94,7 +94,7 @@ mod test { } ); assert_eq!( - int8_to_decimal_cast_eval(Some(2), &value).unwrap(), + int8_to_decimal_cast_eval(Some(2), value.clone()).unwrap(), DataValue::Decimal(Decimal::new(100, 2)) ); } diff --git a/src/types/evaluator/mod.rs b/src/types/evaluator/mod.rs index 9b9e41a6..c71900a1 100644 --- a/src/types/evaluator/mod.rs +++ b/src/types/evaluator/mod.rs @@ -14,6 +14,7 @@ pub mod binary; pub mod boolean; +pub mod bytes; pub mod cast; #[cfg(feature = "time")] pub mod date; @@ -47,6 +48,7 @@ pub use self::unary::unary_create; use crate::errors::DatabaseError; use crate::types::value::DataValue; use crate::types::CharLengthUnits; +use std::borrow::Cow; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum BinaryEvaluatorParams { @@ -67,7 +69,7 @@ impl BinaryEvaluatorRef { pub fn binary_eval( &self, - left: &DataValue, + left: Cow<'_, DataValue>, right: &DataValue, ) -> Result { binary::eval_binary(self.pos, &self.params, left, right) @@ -118,6 +120,9 @@ pub enum CastEvaluatorParams { Tuple { evaluators: Vec, }, + Binary { + len: Option, + }, } impl CastEvaluatorRef { diff --git a/src/types/evaluator/null.rs b/src/types/evaluator/null.rs index 3c9a802d..c1a14760 100644 --- a/src/types/evaluator/null.rs +++ b/src/types/evaluator/null.rs @@ -20,10 +20,10 @@ use crate::types::evaluator::DataValue; pub fn null_binary_eval(_: &DataValue, _: &DataValue) -> Result { Ok(DataValue::Null) } -pub fn to_sql_null_cast_eval(_value: &DataValue) -> Result { +pub fn to_sql_null_cast_eval(_value: DataValue) -> Result { Ok(DataValue::Null) } -pub fn null_cast_eval(_value: &DataValue) -> Result { +pub fn null_cast_eval(_value: DataValue) -> Result { Ok(DataValue::Null) } @@ -35,10 +35,10 @@ mod test { #[test] fn test_null_cast_evaluators() { assert_eq!( - to_sql_null_cast_eval(&DataValue::Int32(1)).unwrap(), + to_sql_null_cast_eval(DataValue::Int32(1)).unwrap(), DataValue::Null ); - assert_eq!(null_cast_eval(&DataValue::Null).unwrap(), DataValue::Null); + assert_eq!(null_cast_eval(DataValue::Null).unwrap(), DataValue::Null); } } // GRCOV_EXCL_STOP diff --git a/src/types/evaluator/time32.rs b/src/types/evaluator/time32.rs index 17eeacf2..e283e60c 100644 --- a/src/types/evaluator/time32.rs +++ b/src/types/evaluator/time32.rs @@ -13,7 +13,7 @@ // limitations under the License. use crate::errors::DatabaseError; -use crate::types::evaluator::cast::{cast_fail, to_char, to_varchar}; +use crate::types::evaluator::cast::{cast_fail, to_char, to_varbinary, to_varchar}; use crate::types::evaluator::DataValue; use crate::types::value::{ONE_DAY_TO_SEC, ONE_SEC_TO_NANO}; use crate::types::CharLengthUnits; @@ -97,9 +97,9 @@ crate::define_cast_evaluator!( }, DataValue::Time32(value, precision) => |this| { to_char( - DataValue::format_time(*value, *precision).ok_or_else(|| { + DataValue::format_time(value, precision).ok_or_else(|| { cast_fail( - LogicalType::Time(Some(*precision)), + LogicalType::Time(Some(precision)), LogicalType::Char(this.len, this.unit), ) })?, @@ -115,9 +115,9 @@ crate::define_cast_evaluator!( }, DataValue::Time32(value, precision) => |this| { to_varchar( - DataValue::format_time(*value, *precision).ok_or_else(|| { + DataValue::format_time(value, precision).ok_or_else(|| { cast_fail( - LogicalType::Time(Some(*precision)), + LogicalType::Time(Some(precision)), LogicalType::Varchar(this.len, this.unit), ) })?, @@ -126,12 +126,23 @@ crate::define_cast_evaluator!( ) } ); +crate::define_cast_evaluator!( + time32_to_varbinary_cast_eval { len: Option }, + DataValue::Time32(value, precision) => |this| { + to_varbinary( + DataValue::format_time(value, precision).ok_or_else(|| { + cast_fail(LogicalType::Time(Some(precision)), LogicalType::Varbinary(this.len)) + })?.into_bytes(), + this.len, + ) + } +); crate::define_cast_evaluator!( time32_to_time_cast_eval { precision: Option }, DataValue::Time32(value, _precision) => |this| { - Ok(DataValue::Time32(*value, this.precision.unwrap_or(0))) + Ok(DataValue::Time32(value, this.precision.unwrap_or(0))) } ); time32_order_binary!(time_lt_binary_eval, is_lt); @@ -268,7 +279,7 @@ mod test { 3, ); assert_eq!( - time32_to_char_cast_eval(12, CharLengthUnits::Characters, &value).unwrap(), + time32_to_char_cast_eval(12, CharLengthUnits::Characters, value.clone()).unwrap(), DataValue::Utf8 { value: "03:04:05.123".to_string(), ty: Utf8Type::Fixed(12), @@ -276,21 +287,25 @@ mod test { } ); assert_eq!( - time32_to_varchar_cast_eval(Some(12), CharLengthUnits::Characters, &value).unwrap(), + time32_to_varchar_cast_eval(Some(12), CharLengthUnits::Characters, value.clone()) + .unwrap(), DataValue::Utf8 { value: "03:04:05.123".to_string(), ty: Utf8Type::Variable(Some(12)), unit: CharLengthUnits::Characters, } ); - assert_eq!(time32_to_time_cast_eval(Some(3), &value).unwrap(), value); + assert_eq!( + time32_to_time_cast_eval(Some(3), value.clone()).unwrap(), + value + ); let invalid = DataValue::Time32(u32::MAX, 0); assert!(matches!( - time32_to_char_cast_eval(8, CharLengthUnits::Characters, &invalid), + time32_to_char_cast_eval(8, CharLengthUnits::Characters, invalid.clone()), Err(DatabaseError::CastFail { .. }) )); assert!(matches!( - time32_to_varchar_cast_eval(Some(8), CharLengthUnits::Characters, &invalid), + time32_to_varchar_cast_eval(Some(8), CharLengthUnits::Characters, invalid.clone()), Err(DatabaseError::CastFail { .. }) )); } diff --git a/src/types/evaluator/time64.rs b/src/types/evaluator/time64.rs index 37c448e0..9abdb1f9 100644 --- a/src/types/evaluator/time64.rs +++ b/src/types/evaluator/time64.rs @@ -13,7 +13,7 @@ // limitations under the License. use crate::errors::DatabaseError; -use crate::types::evaluator::cast::{cast_fail, to_char, to_varchar}; +use crate::types::evaluator::cast::{cast_fail, to_char, to_varbinary, to_varchar}; use crate::types::evaluator::DataValue; use crate::types::CharLengthUnits; use crate::types::LogicalType; @@ -57,9 +57,9 @@ crate::define_cast_evaluator!( }, DataValue::Time64(value, precision, zone) => |this| { to_char( - DataValue::format_timestamp(*value, *precision).ok_or_else(|| { + DataValue::format_timestamp(value, precision).ok_or_else(|| { cast_fail( - LogicalType::TimeStamp(Some(*precision), *zone), + LogicalType::TimeStamp(Some(precision), zone), LogicalType::Char(this.len, this.unit), ) })?, @@ -75,9 +75,9 @@ crate::define_cast_evaluator!( }, DataValue::Time64(value, precision, zone) => |this| { to_varchar( - DataValue::format_timestamp(*value, *precision).ok_or_else(|| { + DataValue::format_timestamp(value, precision).ok_or_else(|| { cast_fail( - LogicalType::TimeStamp(Some(*precision), *zone), + LogicalType::TimeStamp(Some(precision), zone), LogicalType::Varchar(this.len, this.unit), ) })?, @@ -86,13 +86,24 @@ crate::define_cast_evaluator!( ) } ); +crate::define_cast_evaluator!( + time64_to_varbinary_cast_eval { len: Option }, + DataValue::Time64(value, precision, zone) => |this| { + to_varbinary( + DataValue::format_timestamp(value, precision).ok_or_else(|| { + cast_fail(LogicalType::TimeStamp(Some(precision), zone), LogicalType::Varbinary(this.len)) + })?.into_bytes(), + this.len, + ) + } +); crate::define_cast_evaluator!( time64_to_date_cast_eval, DataValue::Time64(value, precision, zone) => { - let value = DataValue::from_timestamp_precision(*value, *precision) + let value = DataValue::from_timestamp_precision(value, precision) .ok_or_else(|| { cast_fail( - LogicalType::TimeStamp(Some(*precision), *zone), + LogicalType::TimeStamp(Some(precision), zone), LogicalType::Date, ) })? @@ -106,10 +117,10 @@ crate::define_cast_evaluator!( crate::define_cast_evaluator!( time64_to_datetime_cast_eval, DataValue::Time64(value, precision, zone) => { - let value = DataValue::from_timestamp_precision(*value, *precision) + let value = DataValue::from_timestamp_precision(value, precision) .ok_or_else(|| { cast_fail( - LogicalType::TimeStamp(Some(*precision), *zone), + LogicalType::TimeStamp(Some(precision), zone), LogicalType::DateTime, ) })? @@ -124,7 +135,7 @@ crate::define_cast_evaluator!( }, DataValue::Time64(value, precision, zone) => |this| { let target_precision = this.precision.unwrap_or(0); - let (value, nano) = DataValue::from_timestamp_precision(*value, *precision) + let (value, nano) = DataValue::from_timestamp_precision(value, precision) .map(|date_time| { ( date_time.time().num_seconds_from_midnight(), @@ -133,7 +144,7 @@ crate::define_cast_evaluator!( }) .ok_or_else(|| { cast_fail( - LogicalType::TimeStamp(Some(*precision), *zone), + LogicalType::TimeStamp(Some(precision), zone), LogicalType::Time(this.precision), ) })?; @@ -150,7 +161,7 @@ crate::define_cast_evaluator!( zone: bool }, DataValue::Time64(value, _precision, _) => |this| { - Ok(DataValue::Time64(*value, this.precision.unwrap_or(0), this.zone)) + Ok(DataValue::Time64(value, this.precision.unwrap_or(0), this.zone)) } ); time64_binary!(time64_lt_binary_eval, <); @@ -225,7 +236,7 @@ mod test { .timestamp_millis(); let value = DataValue::Time64(timestamp, 3, false); assert_eq!( - time64_to_char_cast_eval(23, CharLengthUnits::Characters, &value).unwrap(), + time64_to_char_cast_eval(23, CharLengthUnits::Characters, value.clone()).unwrap(), DataValue::Utf8 { value: "2024-01-02 03:04:05.123".to_string(), ty: Utf8Type::Fixed(23), @@ -233,7 +244,8 @@ mod test { } ); assert_eq!( - time64_to_varchar_cast_eval(Some(23), CharLengthUnits::Characters, &value).unwrap(), + time64_to_varchar_cast_eval(Some(23), CharLengthUnits::Characters, value.clone()) + .unwrap(), DataValue::Utf8 { value: "2024-01-02 03:04:05.123".to_string(), ty: Utf8Type::Variable(Some(23)), @@ -241,7 +253,7 @@ mod test { } ); assert_eq!( - time64_to_date_cast_eval(&value).unwrap(), + time64_to_date_cast_eval(value.clone()).unwrap(), DataValue::Date32( chrono::NaiveDate::from_ymd_opt(2024, 1, 2) .unwrap() @@ -249,7 +261,7 @@ mod test { ) ); assert_eq!( - time64_to_datetime_cast_eval(&value).unwrap(), + time64_to_datetime_cast_eval(value.clone()).unwrap(), DataValue::Date64( chrono::NaiveDate::from_ymd_opt(2024, 1, 2) .unwrap() @@ -260,23 +272,28 @@ mod test { ) ); assert_eq!( - time64_to_time_cast_eval(Some(3), &value).unwrap(), + time64_to_time_cast_eval(Some(3), value.clone()).unwrap(), DataValue::Time32( DataValue::pack_time(3 * 3600 + 4 * 60 + 5, 123_000_000, 3), 3 ) ); assert_eq!( - time64_to_timestamp_cast_eval(Some(3), true, &value).unwrap(), + time64_to_timestamp_cast_eval(Some(3), true, value.clone()).unwrap(), DataValue::Time64(timestamp, 3, true) ); let invalid = DataValue::Time64(i64::MAX, 0, false); - assert!(time64_to_char_cast_eval(23, CharLengthUnits::Characters, &invalid).is_err()); - assert!(time64_to_varchar_cast_eval(None, CharLengthUnits::Characters, &invalid).is_err()); - assert!(time64_to_date_cast_eval(&invalid).is_err()); - assert!(time64_to_datetime_cast_eval(&invalid).is_err()); - assert!(time64_to_time_cast_eval(Some(0), &invalid).is_err()); + assert!( + time64_to_char_cast_eval(23, CharLengthUnits::Characters, invalid.clone()).is_err() + ); + assert!( + time64_to_varchar_cast_eval(None, CharLengthUnits::Characters, invalid.clone()) + .is_err() + ); + assert!(time64_to_date_cast_eval(invalid.clone()).is_err()); + assert!(time64_to_datetime_cast_eval(invalid.clone()).is_err()); + assert!(time64_to_time_cast_eval(Some(0), invalid.clone()).is_err()); } } // GRCOV_EXCL_STOP diff --git a/src/types/evaluator/tuple.rs b/src/types/evaluator/tuple.rs index 33dc5671..5625ba0f 100644 --- a/src/types/evaluator/tuple.rs +++ b/src/types/evaluator/tuple.rs @@ -102,14 +102,14 @@ tuple_order_binary!(tuple_lt_eq_binary_eval, is_le); pub(crate) fn eval_tuple_cast( element_evaluators: &[CastEvaluatorRef], - value: &DataValue, + value: DataValue, ) -> Result { match value { DataValue::Null => Ok(DataValue::Null), DataValue::Tuple(values) => { let mut casted = Vec::with_capacity(values.len()); - for (value, evaluator) in values.iter().zip(element_evaluators.iter()) { + for (value, evaluator) in values.into_iter().zip(element_evaluators.iter()) { casted.push(evaluator.eval(value)?); } @@ -207,7 +207,7 @@ mod test { assert_eq!( evaluator - .eval(&DataValue::Tuple(vec![ + .eval(DataValue::Tuple(vec![ DataValue::Int32(1), DataValue::Utf8 { value: "2".to_string(), @@ -218,7 +218,7 @@ mod test { .unwrap(), DataValue::Tuple(vec![DataValue::Int64(1), DataValue::Int32(2)]) ); - assert_eq!(evaluator.eval(&DataValue::Null).unwrap(), DataValue::Null); + assert_eq!(evaluator.eval(DataValue::Null).unwrap(), DataValue::Null); } } // GRCOV_EXCL_STOP diff --git a/src/types/evaluator/uint16.rs b/src/types/evaluator/uint16.rs index 06356bc8..fc434942 100644 --- a/src/types/evaluator/uint16.rs +++ b/src/types/evaluator/uint16.rs @@ -33,51 +33,51 @@ mod test { let value = DataValue::UInt16(1); assert_eq!( - uint16_to_boolean_cast_eval(&value).unwrap(), + uint16_to_boolean_cast_eval(value.clone()).unwrap(), DataValue::Boolean(true) ); assert_eq!( - uint16_to_tinyint_cast_eval(&value).unwrap(), + uint16_to_tinyint_cast_eval(value.clone()).unwrap(), DataValue::Int8(1) ); assert_eq!( - uint16_to_utinyint_cast_eval(&value).unwrap(), + uint16_to_utinyint_cast_eval(value.clone()).unwrap(), DataValue::UInt8(1) ); assert_eq!( - uint16_to_smallint_cast_eval(&value).unwrap(), + uint16_to_smallint_cast_eval(value.clone()).unwrap(), DataValue::Int16(1) ); assert_eq!( - uint16_to_usmallint_cast_eval(&value).unwrap(), + uint16_to_usmallint_cast_eval(value.clone()).unwrap(), DataValue::UInt16(1) ); assert_eq!( - uint16_to_integer_cast_eval(&value).unwrap(), + uint16_to_integer_cast_eval(value.clone()).unwrap(), DataValue::Int32(1) ); assert_eq!( - uint16_to_uinteger_cast_eval(&value).unwrap(), + uint16_to_uinteger_cast_eval(value.clone()).unwrap(), DataValue::UInt32(1) ); assert_eq!( - uint16_to_bigint_cast_eval(&value).unwrap(), + uint16_to_bigint_cast_eval(value.clone()).unwrap(), DataValue::Int64(1) ); assert_eq!( - uint16_to_ubigint_cast_eval(&value).unwrap(), + uint16_to_ubigint_cast_eval(value.clone()).unwrap(), DataValue::UInt64(1) ); assert_eq!( - uint16_to_float_cast_eval(&value).unwrap(), + uint16_to_float_cast_eval(value.clone()).unwrap(), DataValue::Float32(OrderedFloat(1.0)) ); assert_eq!( - uint16_to_double_cast_eval(&value).unwrap(), + uint16_to_double_cast_eval(value.clone()).unwrap(), DataValue::Float64(OrderedFloat(1.0)) ); assert_eq!( - uint16_to_char_cast_eval(1, CharLengthUnits::Characters, &value).unwrap(), + uint16_to_char_cast_eval(1, CharLengthUnits::Characters, value.clone()).unwrap(), DataValue::Utf8 { value: "1".to_string(), ty: Utf8Type::Fixed(1), @@ -85,7 +85,8 @@ mod test { } ); assert_eq!( - uint16_to_varchar_cast_eval(Some(1), CharLengthUnits::Characters, &value).unwrap(), + uint16_to_varchar_cast_eval(Some(1), CharLengthUnits::Characters, value.clone()) + .unwrap(), DataValue::Utf8 { value: "1".to_string(), ty: Utf8Type::Variable(Some(1)), @@ -93,7 +94,7 @@ mod test { } ); assert_eq!( - uint16_to_decimal_cast_eval(Some(2), &value).unwrap(), + uint16_to_decimal_cast_eval(Some(2), value.clone()).unwrap(), DataValue::Decimal(Decimal::new(100, 2)) ); } diff --git a/src/types/evaluator/uint32.rs b/src/types/evaluator/uint32.rs index 629316ce..64780e8e 100644 --- a/src/types/evaluator/uint32.rs +++ b/src/types/evaluator/uint32.rs @@ -33,51 +33,51 @@ mod test { let value = DataValue::UInt32(1); assert_eq!( - uint32_to_boolean_cast_eval(&value).unwrap(), + uint32_to_boolean_cast_eval(value.clone()).unwrap(), DataValue::Boolean(true) ); assert_eq!( - uint32_to_tinyint_cast_eval(&value).unwrap(), + uint32_to_tinyint_cast_eval(value.clone()).unwrap(), DataValue::Int8(1) ); assert_eq!( - uint32_to_utinyint_cast_eval(&value).unwrap(), + uint32_to_utinyint_cast_eval(value.clone()).unwrap(), DataValue::UInt8(1) ); assert_eq!( - uint32_to_smallint_cast_eval(&value).unwrap(), + uint32_to_smallint_cast_eval(value.clone()).unwrap(), DataValue::Int16(1) ); assert_eq!( - uint32_to_usmallint_cast_eval(&value).unwrap(), + uint32_to_usmallint_cast_eval(value.clone()).unwrap(), DataValue::UInt16(1) ); assert_eq!( - uint32_to_integer_cast_eval(&value).unwrap(), + uint32_to_integer_cast_eval(value.clone()).unwrap(), DataValue::Int32(1) ); assert_eq!( - uint32_to_uinteger_cast_eval(&value).unwrap(), + uint32_to_uinteger_cast_eval(value.clone()).unwrap(), DataValue::UInt32(1) ); assert_eq!( - uint32_to_bigint_cast_eval(&value).unwrap(), + uint32_to_bigint_cast_eval(value.clone()).unwrap(), DataValue::Int64(1) ); assert_eq!( - uint32_to_ubigint_cast_eval(&value).unwrap(), + uint32_to_ubigint_cast_eval(value.clone()).unwrap(), DataValue::UInt64(1) ); assert_eq!( - uint32_to_float_cast_eval(&value).unwrap(), + uint32_to_float_cast_eval(value.clone()).unwrap(), DataValue::Float32(OrderedFloat(1.0)) ); assert_eq!( - uint32_to_double_cast_eval(&value).unwrap(), + uint32_to_double_cast_eval(value.clone()).unwrap(), DataValue::Float64(OrderedFloat(1.0)) ); assert_eq!( - uint32_to_char_cast_eval(1, CharLengthUnits::Characters, &value).unwrap(), + uint32_to_char_cast_eval(1, CharLengthUnits::Characters, value.clone()).unwrap(), DataValue::Utf8 { value: "1".to_string(), ty: Utf8Type::Fixed(1), @@ -85,7 +85,8 @@ mod test { } ); assert_eq!( - uint32_to_varchar_cast_eval(Some(1), CharLengthUnits::Characters, &value).unwrap(), + uint32_to_varchar_cast_eval(Some(1), CharLengthUnits::Characters, value.clone()) + .unwrap(), DataValue::Utf8 { value: "1".to_string(), ty: Utf8Type::Variable(Some(1)), @@ -93,7 +94,7 @@ mod test { } ); assert_eq!( - uint32_to_decimal_cast_eval(Some(2), &value).unwrap(), + uint32_to_decimal_cast_eval(Some(2), value.clone()).unwrap(), DataValue::Decimal(Decimal::new(100, 2)) ); } diff --git a/src/types/evaluator/uint64.rs b/src/types/evaluator/uint64.rs index 18a12b62..dca97772 100644 --- a/src/types/evaluator/uint64.rs +++ b/src/types/evaluator/uint64.rs @@ -33,51 +33,51 @@ mod test { let value = DataValue::UInt64(1); assert_eq!( - uint64_to_boolean_cast_eval(&value).unwrap(), + uint64_to_boolean_cast_eval(value.clone()).unwrap(), DataValue::Boolean(true) ); assert_eq!( - uint64_to_tinyint_cast_eval(&value).unwrap(), + uint64_to_tinyint_cast_eval(value.clone()).unwrap(), DataValue::Int8(1) ); assert_eq!( - uint64_to_utinyint_cast_eval(&value).unwrap(), + uint64_to_utinyint_cast_eval(value.clone()).unwrap(), DataValue::UInt8(1) ); assert_eq!( - uint64_to_smallint_cast_eval(&value).unwrap(), + uint64_to_smallint_cast_eval(value.clone()).unwrap(), DataValue::Int16(1) ); assert_eq!( - uint64_to_usmallint_cast_eval(&value).unwrap(), + uint64_to_usmallint_cast_eval(value.clone()).unwrap(), DataValue::UInt16(1) ); assert_eq!( - uint64_to_integer_cast_eval(&value).unwrap(), + uint64_to_integer_cast_eval(value.clone()).unwrap(), DataValue::Int32(1) ); assert_eq!( - uint64_to_uinteger_cast_eval(&value).unwrap(), + uint64_to_uinteger_cast_eval(value.clone()).unwrap(), DataValue::UInt32(1) ); assert_eq!( - uint64_to_bigint_cast_eval(&value).unwrap(), + uint64_to_bigint_cast_eval(value.clone()).unwrap(), DataValue::Int64(1) ); assert_eq!( - uint64_to_ubigint_cast_eval(&value).unwrap(), + uint64_to_ubigint_cast_eval(value.clone()).unwrap(), DataValue::UInt64(1) ); assert_eq!( - uint64_to_float_cast_eval(&value).unwrap(), + uint64_to_float_cast_eval(value.clone()).unwrap(), DataValue::Float32(OrderedFloat(1.0)) ); assert_eq!( - uint64_to_double_cast_eval(&value).unwrap(), + uint64_to_double_cast_eval(value.clone()).unwrap(), DataValue::Float64(OrderedFloat(1.0)) ); assert_eq!( - uint64_to_char_cast_eval(1, CharLengthUnits::Characters, &value).unwrap(), + uint64_to_char_cast_eval(1, CharLengthUnits::Characters, value.clone()).unwrap(), DataValue::Utf8 { value: "1".to_string(), ty: Utf8Type::Fixed(1), @@ -85,7 +85,8 @@ mod test { } ); assert_eq!( - uint64_to_varchar_cast_eval(Some(1), CharLengthUnits::Characters, &value).unwrap(), + uint64_to_varchar_cast_eval(Some(1), CharLengthUnits::Characters, value.clone()) + .unwrap(), DataValue::Utf8 { value: "1".to_string(), ty: Utf8Type::Variable(Some(1)), @@ -93,7 +94,7 @@ mod test { } ); assert_eq!( - uint64_to_decimal_cast_eval(Some(2), &value).unwrap(), + uint64_to_decimal_cast_eval(Some(2), value.clone()).unwrap(), DataValue::Decimal(Decimal::new(100, 2)) ); } diff --git a/src/types/evaluator/uint8.rs b/src/types/evaluator/uint8.rs index 96f19e7a..56da2fa3 100644 --- a/src/types/evaluator/uint8.rs +++ b/src/types/evaluator/uint8.rs @@ -33,51 +33,51 @@ mod test { let value = DataValue::UInt8(1); assert_eq!( - uint8_to_boolean_cast_eval(&value).unwrap(), + uint8_to_boolean_cast_eval(value.clone()).unwrap(), DataValue::Boolean(true) ); assert_eq!( - uint8_to_tinyint_cast_eval(&value).unwrap(), + uint8_to_tinyint_cast_eval(value.clone()).unwrap(), DataValue::Int8(1) ); assert_eq!( - uint8_to_utinyint_cast_eval(&value).unwrap(), + uint8_to_utinyint_cast_eval(value.clone()).unwrap(), DataValue::UInt8(1) ); assert_eq!( - uint8_to_smallint_cast_eval(&value).unwrap(), + uint8_to_smallint_cast_eval(value.clone()).unwrap(), DataValue::Int16(1) ); assert_eq!( - uint8_to_usmallint_cast_eval(&value).unwrap(), + uint8_to_usmallint_cast_eval(value.clone()).unwrap(), DataValue::UInt16(1) ); assert_eq!( - uint8_to_integer_cast_eval(&value).unwrap(), + uint8_to_integer_cast_eval(value.clone()).unwrap(), DataValue::Int32(1) ); assert_eq!( - uint8_to_uinteger_cast_eval(&value).unwrap(), + uint8_to_uinteger_cast_eval(value.clone()).unwrap(), DataValue::UInt32(1) ); assert_eq!( - uint8_to_bigint_cast_eval(&value).unwrap(), + uint8_to_bigint_cast_eval(value.clone()).unwrap(), DataValue::Int64(1) ); assert_eq!( - uint8_to_ubigint_cast_eval(&value).unwrap(), + uint8_to_ubigint_cast_eval(value.clone()).unwrap(), DataValue::UInt64(1) ); assert_eq!( - uint8_to_float_cast_eval(&value).unwrap(), + uint8_to_float_cast_eval(value.clone()).unwrap(), DataValue::Float32(OrderedFloat(1.0)) ); assert_eq!( - uint8_to_double_cast_eval(&value).unwrap(), + uint8_to_double_cast_eval(value.clone()).unwrap(), DataValue::Float64(OrderedFloat(1.0)) ); assert_eq!( - uint8_to_char_cast_eval(1, CharLengthUnits::Characters, &value).unwrap(), + uint8_to_char_cast_eval(1, CharLengthUnits::Characters, value.clone()).unwrap(), DataValue::Utf8 { value: "1".to_string(), ty: Utf8Type::Fixed(1), @@ -85,7 +85,8 @@ mod test { } ); assert_eq!( - uint8_to_varchar_cast_eval(Some(1), CharLengthUnits::Characters, &value).unwrap(), + uint8_to_varchar_cast_eval(Some(1), CharLengthUnits::Characters, value.clone()) + .unwrap(), DataValue::Utf8 { value: "1".to_string(), ty: Utf8Type::Variable(Some(1)), @@ -93,7 +94,7 @@ mod test { } ); assert_eq!( - uint8_to_decimal_cast_eval(Some(2), &value).unwrap(), + uint8_to_decimal_cast_eval(Some(2), value.clone()).unwrap(), DataValue::Decimal(Decimal::new(100, 2)) ); } diff --git a/src/types/evaluator/utf8.rs b/src/types/evaluator/utf8.rs index a73aeafd..8d2fa698 100644 --- a/src/types/evaluator/utf8.rs +++ b/src/types/evaluator/utf8.rs @@ -13,7 +13,7 @@ // limitations under the License. use crate::errors::DatabaseError; -use crate::types::evaluator::cast::{to_char, to_varchar}; +use crate::types::evaluator::cast::{to_char, to_varbinary, to_varchar}; use crate::types::evaluator::DataValue; use crate::types::value::Utf8Type; use crate::types::CharLengthUnits; @@ -50,53 +50,58 @@ utf8_binary!(utf8_lt_binary_eval, |v1: &String, v2: &String| { }); crate::define_cast_evaluator!(utf8_to_boolean_cast_eval, DataValue::Utf8 { value, .. } => { - Ok(DataValue::Boolean(bool::from_str(value)?)) + Ok(DataValue::Boolean(bool::from_str(&value)?)) } ); crate::define_cast_evaluator!(utf8_to_tinyint_cast_eval, DataValue::Utf8 { value, .. } => { - Ok(DataValue::Int8(i8::from_str(value)?)) + Ok(DataValue::Int8(i8::from_str(&value)?)) }); crate::define_cast_evaluator!(utf8_to_utinyint_cast_eval, DataValue::Utf8 { value, .. } => { - Ok(DataValue::UInt8(u8::from_str(value)?)) + Ok(DataValue::UInt8(u8::from_str(&value)?)) }); crate::define_cast_evaluator!(utf8_to_smallint_cast_eval, DataValue::Utf8 { value, .. } => { - Ok(DataValue::Int16(i16::from_str(value)?)) + Ok(DataValue::Int16(i16::from_str(&value)?)) }); crate::define_cast_evaluator!(utf8_to_usmallint_cast_eval, DataValue::Utf8 { value, .. } => { - Ok(DataValue::UInt16(u16::from_str(value)?)) + Ok(DataValue::UInt16(u16::from_str(&value)?)) }); crate::define_cast_evaluator!(utf8_to_integer_cast_eval, DataValue::Utf8 { value, .. } => { - Ok(DataValue::Int32(i32::from_str(value)?)) + Ok(DataValue::Int32(i32::from_str(&value)?)) }); crate::define_cast_evaluator!(utf8_to_uinteger_cast_eval, DataValue::Utf8 { value, .. } => { - Ok(DataValue::UInt32(u32::from_str(value)?)) + Ok(DataValue::UInt32(u32::from_str(&value)?)) }); crate::define_cast_evaluator!(utf8_to_bigint_cast_eval, DataValue::Utf8 { value, .. } => { - Ok(DataValue::Int64(i64::from_str(value)?)) + Ok(DataValue::Int64(i64::from_str(&value)?)) }); crate::define_cast_evaluator!(utf8_to_ubigint_cast_eval, DataValue::Utf8 { value, .. } => { - Ok(DataValue::UInt64(u64::from_str(value)?)) + Ok(DataValue::UInt64(u64::from_str(&value)?)) }); crate::define_cast_evaluator!(utf8_to_float_cast_eval, DataValue::Utf8 { value, .. } => { - Ok(DataValue::Float32(OrderedFloat(f32::from_str(value)?))) + Ok(DataValue::Float32(OrderedFloat(f32::from_str(&value)?))) }); crate::define_cast_evaluator!(utf8_to_double_cast_eval, DataValue::Utf8 { value, .. } => { - Ok(DataValue::Float64(OrderedFloat(f64::from_str(value)?))) + Ok(DataValue::Float64(OrderedFloat(f64::from_str(&value)?))) }); crate::define_cast_evaluator!( utf8_to_char_cast_eval { len: u32, unit: CharLengthUnits }, - DataValue::Utf8 { value, .. } => |this| to_char(value.clone(), this.len, this.unit) + DataValue::Utf8 { value, .. } => |this| to_char(value, this.len, this.unit) ); crate::define_cast_evaluator!( utf8_to_varchar_cast_eval { len: Option, unit: CharLengthUnits }, - DataValue::Utf8 { value, .. } => |this| to_varchar(value.clone(), this.len, this.unit) + DataValue::Utf8 { value, .. } => |this| to_varchar(value, this.len, this.unit) ); +crate::define_cast_evaluator!( + utf8_to_varbinary_cast_eval { len: Option }, + DataValue::Utf8 { value, .. } => |this| to_varbinary(value.into_bytes(), this.len) +); + #[cfg(feature = "time")] mod chrono_cast { use super::DataValue; @@ -106,14 +111,14 @@ mod chrono_cast { crate::define_cast_evaluator!(utf8_to_date_cast_eval, DataValue::Utf8 { value, .. } => { Ok(DataValue::Date32( - NaiveDate::parse_from_str(value, crate::types::value::DATE_FMT)?.num_days_from_ce(), + NaiveDate::parse_from_str(&value, crate::types::value::DATE_FMT)?.num_days_from_ce(), )) }); crate::define_cast_evaluator!(utf8_to_datetime_cast_eval, DataValue::Utf8 { value, .. } => { - let value = NaiveDateTime::parse_from_str(value, crate::types::value::DATE_TIME_FMT) + let value = NaiveDateTime::parse_from_str(&value, crate::types::value::DATE_TIME_FMT) .or_else(|_| { - NaiveDate::parse_from_str(value, crate::types::value::DATE_FMT) + NaiveDate::parse_from_str(&value, crate::types::value::DATE_FMT) .map(|date| date.and_hms_opt(0, 0, 0).unwrap()) })? .and_utc() @@ -135,11 +140,11 @@ mod chrono_cast { }; let (value, nano) = match precision { 0 => ( - NaiveTime::parse_from_str(value, fmt) + NaiveTime::parse_from_str(&value, fmt) .map(|time| time.num_seconds_from_midnight())?, 0, ), - _ => NaiveTime::parse_from_str(value, fmt) + _ => NaiveTime::parse_from_str(&value, fmt) .map(|time| (time.num_seconds_from_midnight(), time.nanosecond()))?, }; @@ -161,15 +166,10 @@ mod chrono_cast { (3 | 6 | 9, false) => crate::types::value::TIME_STAMP_FMT_WITHOUT_ZONE, _ => crate::types::value::TIME_STAMP_FMT_WITH_ZONE, }; - let complete_value = if this.zone { - if value.contains("+") { - value.clone() - } else { - format!("{value}+00:00") - } - } else { - value.clone() - }; + let mut complete_value = value; + if this.zone && !complete_value.contains('+') { + complete_value.push_str("+00:00"); + } if !this.zone { let value = NaiveDateTime::parse_from_str(&complete_value, fmt)?.and_utc(); @@ -206,7 +206,7 @@ mod chrono_cast { pub use chrono_cast::*; #[cfg(feature = "decimal")] crate::define_cast_evaluator!(utf8_to_decimal_cast_eval, DataValue::Utf8 { value, .. } => { - Ok(DataValue::Decimal(Decimal::from_str(value)?)) + Ok(DataValue::Decimal(Decimal::from_str(&value)?)) }); utf8_binary!(utf8_lt_eq_binary_eval, |v1: &String, v2: &String| { DataValue::Boolean(v1 <= v2) @@ -217,16 +217,23 @@ utf8_binary!(utf8_eq_binary_eval, |v1: &String, v2: &String| { utf8_binary!(utf8_not_eq_binary_eval, |v1: &String, v2: &String| { DataValue::Boolean(v1 != v2) }); -utf8_binary!( - utf8_string_concat_binary_eval, - |v1: &String, v2: &String| { - DataValue::Utf8 { - value: v1.clone() + v2, - ty: Utf8Type::Variable(None), - unit: CharLengthUnits::Characters, +pub fn utf8_string_concat_binary_eval( + left: DataValue, + right: &DataValue, +) -> Result { + Ok(match (left, right) { + (DataValue::Utf8 { mut value, .. }, DataValue::Utf8 { value: right, .. }) => { + value.push_str(right); + DataValue::Utf8 { + value, + ty: Utf8Type::Variable(None), + unit: CharLengthUnits::Characters, + } } - } -); + (DataValue::Null, _) | (_, DataValue::Null) => DataValue::Null, + _ => unsafe { hint::unreachable_unchecked() }, + }) +} pub fn utf8_like_binary_eval( escape_char: Option, left: &DataValue, @@ -348,7 +355,11 @@ fn next_char_at(input: &str, index: usize) -> Option<(char, usize)> { #[cfg(all(test, not(target_arch = "wasm32")))] mod test { use super::*; + use crate::expression::BinaryOperator; + use crate::types::evaluator::binary_create; + use crate::types::LogicalType; use chrono::{Datelike, Timelike}; + use std::borrow::Cow; fn utf8(value: &str) -> DataValue { DataValue::Utf8 { @@ -364,6 +375,48 @@ mod test { #[test] fn test_utf8_binary_evaluators() { + let ty = LogicalType::Varchar(None, CharLengthUnits::Characters); + let concat = binary_create(Cow::Owned(ty.clone()), BinaryOperator::StringConcat).unwrap(); + let mut text = String::with_capacity(32); + text.push_str("ab"); + let buffer = text.as_ptr(); + let result = concat + .binary_eval(Cow::Owned(DataValue::from(text)), &utf8("cd")) + .unwrap(); + let result = concat.binary_eval(Cow::Owned(result), &utf8("中")).unwrap(); + let DataValue::Utf8 { value, .. } = result else { + unreachable!() + }; + assert_eq!(value, "abcd中"); + assert_eq!(value.as_ptr(), buffer); + + let left = utf8("ab"); + assert_eq!( + concat + .binary_eval(Cow::Borrowed(&left), &utf8("cd")) + .unwrap(), + utf8("abcd") + ); + assert_eq!(left.utf8(), Some("ab")); + assert_eq!( + concat + .binary_eval(Cow::Borrowed(&left), &DataValue::Null) + .unwrap(), + DataValue::Null + ); + assert_eq!( + concat + .binary_eval(Cow::Owned(DataValue::Null), &left) + .unwrap(), + DataValue::Null + ); + assert_eq!( + binary_create(Cow::Owned(ty), BinaryOperator::Eq) + .unwrap() + .binary_eval(Cow::Owned(left.clone()), &left) + .unwrap(), + DataValue::Boolean(true) + ); assert_eq!( utf8_gt_binary_eval(&utf8("b"), &utf8("a")).unwrap(), DataValue::Boolean(true) @@ -389,7 +442,7 @@ mod test { DataValue::Boolean(true) ); assert_eq!( - utf8_string_concat_binary_eval(&utf8("ab"), &utf8("cd")).unwrap(), + utf8_string_concat_binary_eval(utf8("ab"), &utf8("cd")).unwrap(), utf8("abcd") ); assert_eq!( @@ -467,51 +520,51 @@ mod test { #[test] fn test_utf8_cast_evaluators() { assert_eq!( - utf8_to_boolean_cast_eval(&utf8("true")).unwrap(), + utf8_to_boolean_cast_eval(utf8("true")).unwrap(), DataValue::Boolean(true) ); assert_eq!( - utf8_to_tinyint_cast_eval(&utf8("1")).unwrap(), + utf8_to_tinyint_cast_eval(utf8("1")).unwrap(), DataValue::Int8(1) ); assert_eq!( - utf8_to_utinyint_cast_eval(&utf8("1")).unwrap(), + utf8_to_utinyint_cast_eval(utf8("1")).unwrap(), DataValue::UInt8(1) ); assert_eq!( - utf8_to_smallint_cast_eval(&utf8("1")).unwrap(), + utf8_to_smallint_cast_eval(utf8("1")).unwrap(), DataValue::Int16(1) ); assert_eq!( - utf8_to_usmallint_cast_eval(&utf8("1")).unwrap(), + utf8_to_usmallint_cast_eval(utf8("1")).unwrap(), DataValue::UInt16(1) ); assert_eq!( - utf8_to_integer_cast_eval(&utf8("1")).unwrap(), + utf8_to_integer_cast_eval(utf8("1")).unwrap(), DataValue::Int32(1) ); assert_eq!( - utf8_to_uinteger_cast_eval(&utf8("1")).unwrap(), + utf8_to_uinteger_cast_eval(utf8("1")).unwrap(), DataValue::UInt32(1) ); assert_eq!( - utf8_to_bigint_cast_eval(&utf8("1")).unwrap(), + utf8_to_bigint_cast_eval(utf8("1")).unwrap(), DataValue::Int64(1) ); assert_eq!( - utf8_to_ubigint_cast_eval(&utf8("1")).unwrap(), + utf8_to_ubigint_cast_eval(utf8("1")).unwrap(), DataValue::UInt64(1) ); assert_eq!( - utf8_to_float_cast_eval(&utf8("1.5")).unwrap(), + utf8_to_float_cast_eval(utf8("1.5")).unwrap(), DataValue::Float32(OrderedFloat(1.5)) ); assert_eq!( - utf8_to_double_cast_eval(&utf8("1.5")).unwrap(), + utf8_to_double_cast_eval(utf8("1.5")).unwrap(), DataValue::Float64(OrderedFloat(1.5)) ); assert_eq!( - utf8_to_char_cast_eval(2, CharLengthUnits::Characters, &utf8("ab")).unwrap(), + utf8_to_char_cast_eval(2, CharLengthUnits::Characters, utf8("ab")).unwrap(), DataValue::Utf8 { value: "ab".to_string(), ty: Utf8Type::Fixed(2), @@ -519,7 +572,7 @@ mod test { } ); assert_eq!( - utf8_to_varchar_cast_eval(Some(2), CharLengthUnits::Characters, &utf8("ab")).unwrap(), + utf8_to_varchar_cast_eval(Some(2), CharLengthUnits::Characters, utf8("ab")).unwrap(), DataValue::Utf8 { value: "ab".to_string(), ty: Utf8Type::Variable(Some(2)), @@ -527,7 +580,7 @@ mod test { } ); assert_eq!( - utf8_to_date_cast_eval(&utf8("2024-01-02")).unwrap(), + utf8_to_date_cast_eval(utf8("2024-01-02")).unwrap(), DataValue::Date32( chrono::NaiveDate::from_ymd_opt(2024, 1, 2) .unwrap() @@ -535,7 +588,7 @@ mod test { ) ); assert_eq!( - utf8_to_datetime_cast_eval(&utf8("2024-01-02 03:04:05")).unwrap(), + utf8_to_datetime_cast_eval(utf8("2024-01-02 03:04:05")).unwrap(), DataValue::Date64( chrono::NaiveDate::from_ymd_opt(2024, 1, 2) .unwrap() @@ -546,11 +599,11 @@ mod test { ) ); assert_eq!( - utf8_to_time_cast_eval(Some(0), &utf8("03:04:05")).unwrap(), + utf8_to_time_cast_eval(Some(0), utf8("03:04:05")).unwrap(), DataValue::Time32(DataValue::pack_time(3 * 3600 + 4 * 60 + 5, 0, 0), 0) ); assert_eq!( - utf8_to_time_cast_eval(Some(3), &utf8("03:04:05.123")).unwrap(), + utf8_to_time_cast_eval(Some(3), utf8("03:04:05.123")).unwrap(), { let time = chrono::NaiveTime::parse_from_str( "03:04:05.123", @@ -564,7 +617,7 @@ mod test { } ); assert_eq!( - utf8_to_timestamp_cast_eval(Some(3), false, &utf8("2024-01-02 03:04:05.123")).unwrap(), + utf8_to_timestamp_cast_eval(Some(3), false, utf8("2024-01-02 03:04:05.123")).unwrap(), DataValue::Time64( chrono::NaiveDate::from_ymd_opt(2024, 1, 2) .unwrap() @@ -577,7 +630,7 @@ mod test { ) ); assert_eq!( - utf8_to_timestamp_cast_eval(Some(0), true, &utf8("2024-01-02 03:04:05+00:00")).unwrap(), + utf8_to_timestamp_cast_eval(Some(0), true, utf8("2024-01-02 03:04:05+00:00")).unwrap(), DataValue::Time64( chrono::NaiveDate::from_ymd_opt(2024, 1, 2) .unwrap() @@ -590,7 +643,7 @@ mod test { ) ); assert_eq!( - utf8_to_timestamp_cast_eval(Some(0), true, &utf8("2024-01-02 03:04:05")).unwrap(), + utf8_to_timestamp_cast_eval(Some(0), true, utf8("2024-01-02 03:04:05")).unwrap(), DataValue::Time64( chrono::NaiveDate::from_ymd_opt(2024, 1, 2) .unwrap() @@ -603,7 +656,7 @@ mod test { ) ); assert_eq!( - utf8_to_timestamp_cast_eval(Some(6), false, &utf8("2024-01-02 03:04:05.123456")) + utf8_to_timestamp_cast_eval(Some(6), false, utf8("2024-01-02 03:04:05.123456")) .unwrap(), DataValue::Time64( chrono::NaiveDate::from_ymd_opt(2024, 1, 2) @@ -617,12 +670,8 @@ mod test { ) ); assert_eq!( - utf8_to_timestamp_cast_eval( - Some(9), - true, - &utf8("2024-01-02 03:04:05.123456789+00:00") - ) - .unwrap(), + utf8_to_timestamp_cast_eval(Some(9), true, utf8("2024-01-02 03:04:05.123456789+00:00")) + .unwrap(), DataValue::Time64( chrono::NaiveDate::from_ymd_opt(2024, 1, 2) .unwrap() @@ -636,7 +685,7 @@ mod test { ) ); assert_eq!( - utf8_to_timestamp_cast_eval(Some(9), false, &utf8("2024-01-02 03:04:05.123456789")) + utf8_to_timestamp_cast_eval(Some(9), false, utf8("2024-01-02 03:04:05.123456789")) .unwrap(), DataValue::Time64( chrono::NaiveDate::from_ymd_opt(2024, 1, 2) @@ -651,17 +700,17 @@ mod test { ) ); assert_eq!( - utf8_to_decimal_cast_eval(&utf8("12.34")).unwrap(), + utf8_to_decimal_cast_eval(utf8("12.34")).unwrap(), DataValue::Decimal(Decimal::from_str("12.34").unwrap()) ); assert_panics(|| { - let _ = utf8_to_timestamp_cast_eval(Some(4), false, &utf8("2024-01-02 03:04:05.1234")) + let _ = utf8_to_timestamp_cast_eval(Some(4), false, utf8("2024-01-02 03:04:05.1234")) .unwrap(); }); assert_panics(|| { let _ = - utf8_to_timestamp_cast_eval(Some(4), true, &utf8("2024-01-02 03:04:05.1234+00:00")) + utf8_to_timestamp_cast_eval(Some(4), true, utf8("2024-01-02 03:04:05.1234+00:00")) .unwrap(); }); } diff --git a/src/types/mod.rs b/src/types/mod.rs index bcb0e7ea..9f6439d0 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -70,6 +70,7 @@ pub enum LogicalType { // decimal (precision, scale) Decimal(Option, Option), Tuple(Vec), + Varbinary(Option), } impl LogicalType { @@ -103,6 +104,8 @@ impl LogicalType { Some(LogicalType::Float) } else if type_id == TypeId::of::() { Some(LogicalType::Double) + } else if type_id == TypeId::of::>() { + Some(LogicalType::Varbinary(None)) } else if type_id == TypeId::of::() { Some(LogicalType::Varchar(None, CharLengthUnits::Characters)) } else { @@ -137,7 +140,7 @@ impl LogicalType { LogicalType::Float => Some(4), LogicalType::Double => Some(8), /// Note: The non-fixed length type's raw_len is None e.g. Varchar - LogicalType::Varchar(_, _) => None, + LogicalType::Varchar(_, _) | LogicalType::Varbinary(_) => None, LogicalType::Char(len, unit) => match unit { CharLengthUnits::Characters => None, CharLengthUnits::Octets => Some(*len as usize), @@ -258,6 +261,20 @@ impl LogicalType { return Ok(Cow::Owned(LogicalType::DateTime)); } } + if matches!(left, LogicalType::Varbinary(_)) && !matches!(right, LogicalType::Tuple(_)) + || matches!(right, LogicalType::Varbinary(_)) && !matches!(left, LogicalType::Tuple(_)) + { + return Ok(Cow::Owned(LogicalType::Varbinary(None))); + } + if (left.is_numeric() && matches!(right, LogicalType::Char(..) | LogicalType::Varchar(..))) + || (right.is_numeric() + && matches!(left, LogicalType::Char(..) | LogicalType::Varchar(..))) + { + return Ok(Cow::Owned(LogicalType::Varchar( + None, + CharLengthUnits::Characters, + ))); + } if let (LogicalType::Char(..), LogicalType::Varchar(..)) | (LogicalType::Varchar(..), LogicalType::Char(..)) | (LogicalType::Char(..), LogicalType::Char(..)) @@ -398,7 +415,7 @@ impl LogicalType { LogicalType::Float => matches!(to, LogicalType::Double | LogicalType::Decimal(_, _)), LogicalType::Double => matches!(to, LogicalType::Decimal(_, _)), LogicalType::Char(..) => false, - LogicalType::Varchar(..) => false, + LogicalType::Varchar(..) | LogicalType::Varbinary(..) => false, LogicalType::Date => matches!( to, LogicalType::DateTime | LogicalType::Varchar(..) | LogicalType::Char(..) @@ -435,6 +452,7 @@ impl std::fmt::Display for LogicalType { LogicalType::Double => write!(f, "Double")?, LogicalType::Char(len, units) => write!(f, "Char({len}, {units})")?, LogicalType::Varchar(len, units) => write!(f, "Varchar({len:?}, {units})")?, + LogicalType::Varbinary(len) => write!(f, "Varbinary({len:?})")?, LogicalType::Date => write!(f, "Date")?, LogicalType::DateTime => write!(f, "DateTime")?, LogicalType::TimeStamp(precision, zone) => { @@ -512,7 +530,10 @@ pub(crate) mod test { LogicalType::type_trans::(), Some(LogicalType::Varchar(None, CharLengthUnits::Characters)) ); - assert_eq!(LogicalType::type_trans::>(), None); + assert_eq!( + LogicalType::type_trans::>(), + Some(LogicalType::Varbinary(None)) + ); #[cfg(feature = "decimal")] assert_eq!( @@ -735,6 +756,23 @@ pub(crate) mod test { )?, Cow::Owned(LogicalType::Varchar(None, CharLengthUnits::Characters)) ); + for numeric in [ + LogicalType::Integer, + LogicalType::Bigint, + LogicalType::Double, + ] { + for string in [ + LogicalType::Char(2, CharLengthUnits::Characters), + LogicalType::Varchar(Some(8), CharLengthUnits::Octets), + ] { + for (left, right) in [(&numeric, &string), (&string, &numeric)] { + assert_eq!( + LogicalType::max_logical_type(left, right)?, + Cow::Owned(LogicalType::Varchar(None, CharLengthUnits::Characters)) + ); + } + } + } #[cfg(feature = "time")] { assert_eq!( @@ -949,6 +987,16 @@ pub(crate) mod test { &mut reference_tables, LogicalType::Varchar(None, CharLengthUnits::Octets), )?; + fn_assert( + &mut cursor, + &mut reference_tables, + LogicalType::Varbinary(Some(8)), + )?; + fn_assert( + &mut cursor, + &mut reference_tables, + LogicalType::Varbinary(None), + )?; fn_assert(&mut cursor, &mut reference_tables, LogicalType::Date)?; fn_assert(&mut cursor, &mut reference_tables, LogicalType::DateTime)?; fn_assert( diff --git a/src/types/serialize.rs b/src/types/serialize.rs index 18aa64ef..1357895b 100644 --- a/src/types/serialize.rs +++ b/src/types/serialize.rs @@ -92,11 +92,17 @@ pub enum TupleValueSerializableImpl { Decimal, SkipFixed(usize), SkipVariable, + Varbinary { + len: Option, + }, } impl TupleValueSerializable for TupleValueSerializableImpl { fn to_raw(&self, value: &DataValue, writer: &mut W) -> Result<(), DatabaseError> { match self { + TupleValueSerializableImpl::Varbinary { len } => { + VarbinarySerializable { len: *len }.to_raw(value, writer) + } TupleValueSerializableImpl::Boolean => BooleanSerializable.to_raw(value, writer), TupleValueSerializableImpl::Int8 => Int8Serializable.to_raw(value, writer), TupleValueSerializableImpl::Int16 => Int16Serializable.to_raw(value, writer), @@ -137,6 +143,9 @@ impl TupleValueSerializable for TupleValueSerializableImpl { fn from_raw(&self, reader: &mut Cursor<&[u8]>) -> Result { match self { + TupleValueSerializableImpl::Varbinary { len } => { + VarbinarySerializable { len: *len }.from_raw(reader) + } TupleValueSerializableImpl::Boolean => BooleanSerializable.from_raw(reader), TupleValueSerializableImpl::Int8 => Int8Serializable.from_raw(reader), TupleValueSerializableImpl::Int16 => Int16Serializable.from_raw(reader), @@ -181,6 +190,9 @@ impl TupleValueSerializable for TupleValueSerializableImpl { values: &mut std::vec::Vec, ) -> Result<(), DatabaseError> { match self { + TupleValueSerializableImpl::Varbinary { len } => { + VarbinarySerializable { len: *len }.filling_value(reader, values) + } TupleValueSerializableImpl::Boolean => { BooleanSerializable.filling_value(reader, values) } @@ -269,6 +281,11 @@ struct VarcharSerializable { unit: CharLengthUnits, } +#[derive(Debug)] +struct VarbinarySerializable { + len: Option, +} + #[derive(Debug)] struct DateSerializable; #[derive(Debug)] @@ -488,6 +505,27 @@ impl TupleValueSerializable for VarcharSerializable { } } +impl TupleValueSerializable for VarbinarySerializable { + fn to_raw(&self, value: &DataValue, writer: &mut W) -> Result<(), DatabaseError> { + let DataValue::Binary { value, .. } = value else { + unsafe { std::hint::unreachable_unchecked() } + }; + write_u32_le(writer, value.len() as u32)?; + writer.write_all(value)?; + Ok(()) + } + + fn from_raw(&self, reader: &mut Cursor<&[u8]>) -> Result { + let size = read_u32_le(reader)? as usize; + let mut value = vec![0; size]; + reader.read_exact(&mut value)?; + Ok(DataValue::Binary { + value, + len: self.len, + }) + } +} + impl_tuple_value_serializable!( DateSerializable, Date32, @@ -613,6 +651,7 @@ impl LogicalType { pub fn serializable(&self) -> TupleValueSerializableImpl { match self { + LogicalType::Varbinary(len) => TupleValueSerializableImpl::Varbinary { len: *len }, LogicalType::Boolean => TupleValueSerializableImpl::Boolean, LogicalType::Tinyint => TupleValueSerializableImpl::Int8, LogicalType::UTinyint => TupleValueSerializableImpl::UInt8, @@ -669,6 +708,17 @@ mod tests { fn test_tuple_value_serializable_impl_roundtrips_all_value_variants( ) -> Result<(), DatabaseError> { let cases = vec![ + ( + TupleValueSerializableImpl::Varbinary { len: Some(8) }, + DataValue::Binary { + value: vec![0, 255, 128], + len: Some(8), + }, + ), + ( + TupleValueSerializableImpl::Varbinary { len: None }, + DataValue::from(Vec::::new()), + ), ( TupleValueSerializableImpl::Boolean, DataValue::Boolean(true), diff --git a/src/types/value.rs b/src/types/value.rs index 7564783c..a1e8fe0f 100644 --- a/src/types/value.rs +++ b/src/types/value.rs @@ -16,7 +16,7 @@ use super::LogicalType; use crate::errors::DatabaseError; use crate::iter_ext::Itertools; use crate::storage::table_codec::{BumpBytes, NOTNULL_TAG, NULL_TAG}; -use crate::types::evaluator::cast::{cast_create, to_char, to_varchar}; +use crate::types::evaluator::cast::cast_create; use crate::types::CharLengthUnits; #[cfg(feature = "time")] use chrono::{ @@ -163,6 +163,10 @@ pub enum DataValue { #[cfg(feature = "decimal")] Decimal(Decimal), Tuple(Vec), + Binary { + value: Vec, + len: Option, + }, } /// A read-only view of a value, including an unmaterialized tuple slice. @@ -381,6 +385,8 @@ impl PartialEq for DataValue { (UInt32(_), _) => false, (UInt64(v1), UInt64(v2)) => v1.eq(v2), (UInt64(_), _) => false, + (Binary { value: v1, .. }, Binary { value: v2, .. }) => v1.eq(v2), + (Binary { .. }, _) => false, (Utf8 { value: v1, .. }, Utf8 { value: v2, .. }) => v1.eq(v2), (Utf8 { .. }, _) => false, (Null, Null) => true, @@ -473,6 +479,8 @@ impl PartialOrd for DataValue { (UInt32(_), _) => None, (UInt64(v1), UInt64(v2)) => v1.partial_cmp(v2), (UInt64(_), _) => None, + (Binary { value: v1, .. }, Binary { value: v2, .. }) => v1.partial_cmp(v2), + (Binary { .. }, _) => None, (Utf8 { value: v1, .. }, Utf8 { value: v2, .. }) => v1.partial_cmp(v2), (Utf8 { .. }, _) => None, (Null, Null) => Some(Ordering::Equal), @@ -565,6 +573,10 @@ impl Hash for DataValue { 11u8.hash(state); v.hash(state); } + Binary { value, .. } => { + 19u8.hash(state); + value.hash(state); + } Utf8 { value: v, .. } => { 12u8.hash(state); v.hash(state); @@ -709,6 +721,7 @@ impl DataValue { | DataValue::Float64(_) | DataValue::Date64(_) | DataValue::Time64(_, _, _) => 8, + DataValue::Binary { value, .. } => std::mem::size_of::() + value.len(), DataValue::Utf8 { value, ty, unit } => match (ty, unit) { (Utf8Type::Variable(_), _) => std::mem::size_of::() + value.len(), (Utf8Type::Fixed(len), CharLengthUnits::Characters) => { @@ -747,6 +760,14 @@ impl DataValue { } } + pub fn binary(&self) -> Option<&[u8]> { + if let DataValue::Binary { value, .. } = self { + Some(value) + } else { + None + } + } + pub fn new_utf8(string: String) -> Self { DataValue::Utf8 { ty: Utf8Type::Fixed(string.len() as u32), @@ -766,6 +787,9 @@ impl DataValue { #[inline] pub(crate) fn check_len(&self, logic_type: &LogicalType) -> Result<(), DatabaseError> { let is_over_len = match (logic_type, self) { + (LogicalType::Varbinary(Some(len)), DataValue::Binary { value, .. }) => { + value.len() > *len as usize + } (LogicalType::Varchar(None, _), _) => false, ( LogicalType::Varchar(Some(len), CharLengthUnits::Characters), @@ -885,6 +909,10 @@ impl DataValue { ty: Utf8Type::Variable(*len), unit: *unit, }, + LogicalType::Varbinary(len) => DataValue::Binary { + value: Vec::new(), + len: *len, + }, LogicalType::Date => { #[cfg(feature = "time")] { @@ -979,6 +1007,7 @@ impl DataValue { unit, .. } => LogicalType::Char(*len, *unit), + DataValue::Binary { len, .. } => LogicalType::Varbinary(*len), DataValue::Date32(_) => LogicalType::Date, DataValue::Date64(_) => LogicalType::DateTime, DataValue::Time32(..) => LogicalType::Time(None), @@ -1115,6 +1144,7 @@ impl DataValue { DataValue::UInt32(v) => encode_u!(b, v), DataValue::Time32(v, ..) => encode_u!(b, v), DataValue::UInt64(v) => encode_u!(b, v), + DataValue::Binary { value, .. } => Self::encode_string(b, value), DataValue::Utf8 { value: v, .. } => Self::encode_string(b, v.as_bytes()), DataValue::Boolean(v) => b.push_byte(if *v { b'1' } else { b'0' }), DataValue::Float32(f) => { @@ -1236,6 +1266,10 @@ impl DataValue { reader.read_exact(&mut b)?; Ok(DataValue::Boolean(b[0] == b'1')) } + LogicalType::Varbinary(len) => Ok(DataValue::Binary { + value: Self::decode_string(reader)?, + len: *len, + }), LogicalType::Varchar(len, unit) => Ok(DataValue::Utf8 { value: String::from_utf8(Self::decode_string(reader)?)?, ty: Utf8Type::Variable(*len), @@ -1468,15 +1502,9 @@ impl DataValue { Ok(DataValue::Parameter { id, ty: ty.clone() }) } (DataValue::Null, _) => Ok(DataValue::Null), - (DataValue::Utf8 { value, .. }, LogicalType::Char(len, unit)) => { - to_char(value, *len, *unit) - } - (DataValue::Utf8 { value, .. }, LogicalType::Varchar(len, unit)) => { - to_varchar(value, *len, *unit) - } (value, _) => { let evaluator = cast_create(&from, to)?; - evaluator.eval(&value) + evaluator.eval(value) } } } @@ -1688,6 +1716,12 @@ impl From> for DataValue { } } +impl From> for DataValue { + fn from(value: Vec) -> Self { + Self::Binary { value, len: None } + } +} + impl From for DataValue { fn from(value: String) -> Self { DataValue::Utf8 { @@ -1800,6 +1834,13 @@ impl fmt::Display for DataValue { DataValue::UInt16(e) => write!(f, "{e}")?, DataValue::UInt32(e) => write!(f, "{e}")?, DataValue::UInt64(e) => write!(f, "{e}")?, + DataValue::Binary { value, .. } => { + write!(f, "X'")?; + for byte in value { + write!(f, "{byte:02X}")?; + } + write!(f, "'")?; + } DataValue::Utf8 { value: e, .. } => write!(f, "{e}")?, DataValue::Null => write!(f, "NULL")?, DataValue::Date32(e) => { @@ -1870,6 +1911,7 @@ impl fmt::Debug for DataValue { DataValue::UInt16(_) => write!(f, "UInt16({self})"), DataValue::UInt32(_) => write!(f, "UInt32({self})"), DataValue::UInt64(_) => write!(f, "UInt64({self})"), + DataValue::Binary { .. } => write!(f, "Binary({self})"), DataValue::Utf8 { .. } => write!(f, "Utf8(\"{self}\")"), DataValue::Null => write!(f, "null"), DataValue::Date32(_) => write!(f, "Date32({self})"), @@ -1926,6 +1968,67 @@ mod test { assert_eq!(left.partial_cmp(&DataValueRef::Value(&scalar)), None); } + #[test] + fn binary_values_preserve_bytes_and_index_order() -> Result<(), DatabaseError> { + let values: Vec = [ + vec![], + vec![0], + vec![0, 0], + vec![0, 255], + vec![1], + vec![255; 8], + vec![255; 9], + ] + .into_iter() + .map(DataValue::from) + .collect(); + let mut previous = None; + for value in &values { + let mut encoded = Vec::new(); + value.memcomparable_encode(&mut encoded)?; + assert_eq!( + DataValue::memcomparable_decode( + &mut Cursor::new(&encoded), + &LogicalType::Varbinary(None) + )?, + *value + ); + if let Some(previous) = previous { + assert!(previous < encoded); + } + previous = Some(encoded); + } + let text = "中\0".to_string(); + let buffer = text.as_ptr(); + let binary = DataValue::from(text).cast(&LogicalType::Varbinary(Some(4)))?; + assert_eq!(binary.binary(), Some("中\0".as_bytes())); + assert_eq!(binary.binary().unwrap().as_ptr(), buffer); + assert_eq!(binary.logical_type(), LogicalType::Varbinary(Some(4))); + assert!(matches!( + DataValue::from("中".to_string()).cast(&LogicalType::Varbinary(Some(2))), + Err(DatabaseError::TooLong) + )); + let bytes = DataValue::from(vec![0, 255]); + assert_eq!(bytes.binary(), Some([0, 255].as_slice())); + assert_eq!(values[0].binary(), Some([].as_slice())); + assert_eq!(DataValue::Null.binary(), None); + assert_eq!(DataValue::from("bytes".to_string()).binary(), None); + assert_eq!(bytes.to_string(), "X'00FF'"); + assert_eq!(bytes.logical_type(), LogicalType::Varbinary(None)); + assert_eq!(bytes.serialized_len_hint(), 6); + let bounded = bytes.clone().cast(&LogicalType::Varbinary(Some(2)))?; + assert_eq!(bounded, bytes); + assert!(matches!( + bytes.clone().cast(&LogicalType::Varbinary(Some(1))), + Err(DatabaseError::TooLong) + )); + assert!(matches!( + bytes.cast(&LogicalType::Bigint), + Err(DatabaseError::CastFail { .. }) + )); + Ok(()) + } + fn utf8(value: &str) -> DataValue { DataValue::Utf8 { value: value.to_string(), diff --git a/src/wasm.rs b/src/wasm.rs index 9d786f24..c8986d12 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -19,7 +19,7 @@ use crate::storage::memory::MemoryStorage; use crate::types::tuple::Tuple; use crate::types::value::{DataValue, Utf8Type}; use crate::types::CharLengthUnits; -use js_sys::{Array, Object, Reflect}; +use js_sys::{Array, Object, Reflect, Uint8Array}; use wasm_bindgen::prelude::*; fn to_js_err(err: impl ToString) -> JsValue { @@ -48,6 +48,7 @@ fn data_value_to_js(value: &DataValue) -> Result { DataValue::UInt16(value) => Ok(JsValue::from_f64(*value as f64)), DataValue::UInt32(value) => Ok(JsValue::from_f64(*value as f64)), DataValue::UInt64(value) => Ok(JsValue::from_f64(*value as f64)), + DataValue::Binary { value, .. } => Ok(Uint8Array::from(value.as_slice()).into()), DataValue::Utf8 { value, ty, unit } => { let object = Object::new(); set_prop(&object, "value", JsValue::from_str(value))?; diff --git a/tests/macros-test/src/main.rs b/tests/macros-test/src/main.rs index 5aff4964..ec399e45 100644 --- a/tests/macros-test/src/main.rs +++ b/tests/macros-test/src/main.rs @@ -14,6 +14,7 @@ fn main() {} +// GRCOV_EXCL_START #[cfg(test)] mod test { use kite_sql::catalog::column::{ColumnCatalog, ColumnDesc}; @@ -33,6 +34,7 @@ mod test { use kite_sql::types::{CharLengthUnits, LogicalType}; use kite_sql::{from_tuple, scala_function, table_function, Model, Projection}; use rust_decimal::Decimal; + use std::borrow::Cow; use tempfile::TempDir; fn build_tuple(arena: &mut impl MetaArena) -> (Tuple, Schema) { @@ -3026,7 +3028,7 @@ mod test { } scala_function!(MyScalaFunction::SUM(LogicalType::Integer, LogicalType::Integer) -> LogicalType::Integer => (|v1: DataValue, v2: DataValue| { - binary_create(std::borrow::Cow::Owned(LogicalType::Integer), BinaryOperator::Plus)?.binary_eval(&v1, &v2) + binary_create(Cow::Owned(LogicalType::Integer), BinaryOperator::Plus)?.binary_eval(Cow::Owned(v1), &v2) })); scala_function!(MyOrmFunction::ADD_ONE(LogicalType::Integer) -> LogicalType::Integer => (|v1: DataValue| { @@ -3122,3 +3124,4 @@ mod test { Ok(()) } } +// GRCOV_EXCL_STOP diff --git a/tests/slt/abs.slt b/tests/slt/abs.slt deleted file mode 100644 index 30d5ef97..00000000 --- a/tests/slt/abs.slt +++ /dev/null @@ -1,29 +0,0 @@ -statement ok -create table t_abs(id int primary key, i int, b bigint, s smallint, d double, f float); - -statement ok -insert into t_abs values (0, -3, -9000000000, -7, -1.5, -2.5), (1, 4, 5, 6, 0.25, 1.5), (2, null, null, null, null, null); - -query IIIRR rowsort -select abs(i), abs(b), abs(s), abs(d), abs(f) from t_abs; ----- -3 9000000000 7 1.5 2.5 -4 5 6 0.25 1.5 -NULL NULL NULL NULL NULL - -query I rowsort -select abs(i - b) from t_abs where id < 2; ----- -1 -8999999997 - -query I -select id from t_abs where abs(i) > 3; ----- -1 - -statement error -select abs(-2147483648); - -statement ok -drop table t_abs; diff --git a/tests/slt/aggregation.slt b/tests/slt/aggregation.slt index 70bd2efa..811b89c6 100644 --- a/tests/slt/aggregation.slt +++ b/tests/slt/aggregation.slt @@ -98,6 +98,28 @@ select (select sum(null) from sn) from sn where id = 1; ---- NULL +# SUM/AVG over strings is an error, not an assertion failure. +statement ok +create table ss(id int primary key, v varchar); + +statement ok +insert into ss values (1, '10'), (2, 'x'); + +statement error +select sum(v) from ss; + +statement error +select sum(distinct v) from ss; + +statement error +select avg(v) from ss; + +statement error +select sum('a') from ss; + +statement ok +drop table ss; + # Aggregates, including SELECT aliases containing aggregates, cannot run in WHERE. statement error agg miss: aggregate functions are not allowed in WHERE select count(*) as n from sn where n is not null; @@ -121,5 +143,60 @@ select count(*) from sn where a > 0; ---- 2 +# Pruning unused aggregate outputs must retain group cardinality, including an empty group. +query I rowsort +select 7 from (select a,sum(a) as total from sn group by a) q; +---- +7 +7 + +query I +select 7 from (select sum(a) as total from sn where false) q; +---- +7 + +query I rowsort +select a from (select a,sum(a) as total from sn group by a) q; +---- +1 +2 + statement ok drop table sn + +# AVG(DISTINCT) deduplicates non-NULL values in both simple and grouped aggregates. +statement ok +create table avg_distinct_test(id int primary key, g int, v int); + +statement ok +insert into avg_distinct_test values + (1, 1, 1), (2, 1, 0), (3, 1, null), + (4, 1, 2), (5, 1, 2), (6, 2, null); + +query RR +select avg(v), avg(distinct v) from avg_distinct_test; +---- +1.25 1.0 + +query IRR +select g, avg(v), avg(distinct v) from avg_distinct_test group by g order by g; +---- +1 1.25 1.0 +2 NULL NULL + +query R +select avg(distinct v) from avg_distinct_test where false; +---- +NULL + +# DISTINCT rejects a wildcard, not an ordinary '*' string literal. +statement error DISTINCT cannot be used with * +select count(distinct *) from avg_distinct_test where false; + +query III +select count(*), count(distinct '*'), count(distinct v) from avg_distinct_test; +---- +6 1 3 + +statement ok +drop table avg_distinct_test; diff --git a/tests/slt/basic_test.slt b/tests/slt/basic_test.slt index 48ef1f7e..5fecbd8d 100644 --- a/tests/slt/basic_test.slt +++ b/tests/slt/basic_test.slt @@ -158,3 +158,48 @@ select c1 from t2; statement ok drop table t2 + +# Absolute value: numeric types, NULL and overflow. +statement ok +create table t_abs(id int primary key, i int, b bigint, s smallint, d double, f float); + +statement ok +insert into t_abs values (0, -3, -9000000000, -7, -1.5, -2.5), (1, 4, 5, 6, 0.25, 1.5), (2, null, null, null, null, null); + +query IIIRR rowsort +select abs(i), abs(b), abs(s), abs(d), abs(f) from t_abs; +---- +3 9000000000 7 1.5 2.5 +4 5 6 0.25 1.5 +NULL NULL NULL NULL NULL + +query I rowsort +select abs(i - b) from t_abs where id < 2; +---- +1 +8999999997 + +query I +select id from t_abs where abs(i) > 3; +---- +1 + +statement error +select abs(-2147483648); + +query I +select abs(cast(-7 as tinyint)); +---- +7 + +statement error over flow +select abs(cast(-128 as tinyint)); + +statement error over flow +select abs(cast(-32768 as smallint)); + +statement error over flow +select abs(cast('-9223372036854775808' as bigint)); + +statement ok +drop table t_abs; diff --git a/tests/slt/binary.slt b/tests/slt/binary.slt new file mode 100644 index 00000000..1dbae1e8 --- /dev/null +++ b/tests/slt/binary.slt @@ -0,0 +1,180 @@ +# Binary values preserve arbitrary bytes, including zero bytes and non-UTF8. +statement ok +create table binary_values (id int primary key, b varbinary(8) unique); + +statement ok +insert into binary_values values (1,x''),(2,x'00'),(3,x'0000'),(4,x'00ff'),(5,x'80'),(6,x'ff'),(7,null); + +query IT +select id,b from binary_values order by b; +---- +1 X'' +2 X'00' +3 X'0000' +4 X'00FF' +5 X'80' +6 X'FF' +7 NULL + +query I +select id from binary_values where b=x'00ff'; +---- +4 + +query I +select id from binary_values where b>=x'00' and bx'80',x'ab'=x'AB',x'00'<>x'01',x'00'<=x'00',x'01'>=x'00'; +---- +true true true true true true + +query TTTTT +select x'00ff' in (select b from binary_values),x'01' in (select b from binary_values),x'01' not in (select b from binary_values),null=x'00',x'00' in (select b from binary_values where false); +---- +true NULL NULL NULL false + +query TT +select cast(x'00ff' as varbinary(2)),cast(null as blob); +---- +X'00FF' NULL + +query T +select distinct b from binary_values where b in (x'00',x'ff') order by b desc; +---- +X'FF' +X'00' + +query I +select count(*) from binary_values a join binary_values b on a.b=b.b; +---- +6 + +statement error +insert into binary_values values (8,x'00ff'); + +statement error +insert into binary_values values (8,x'000102030405060708'); + +statement error +select cast(x'0001' as varbinary(1)); + +statement error +select x'0'; + +statement error +select x'GG'; + +statement error +select x'00'+x'01'; + +statement error +select cast(x'00' as bigint); + +query I +select count(*) from binary_values; +---- +7 + +statement ok +update binary_values set b=x'fe' where id=6; + +query I +select id from binary_values where b=x'fe'; +---- +6 + +statement ok +delete from binary_values where b=x'0000'; + +query I +select count(*) from binary_values where b=x'0000'; +---- +0 + +statement ok +create table binary_pk (b bytea primary key); + +statement ok +insert into binary_pk values (x''),(x'00'),(x'0000'),(x'ff'); + +query T +select b from binary_pk where b>=x'00' order by b; +---- +X'00' +X'0000' +X'FF' + +statement ok +drop table binary_pk; + +# Scalar conversions use their text representation, not storage/index encoding. +query TTTT +select cast(123 as varbinary),cast(-12 as varbinary(3)),cast(true as varbinary),cast('中' as varbinary(3)); +---- +X'313233' X'2D3132' X'74727565' X'E4B8AD' + +query TTT +select cast(cast('2024-01-02' as date) as varbinary),cast(cast('03:04:05' as time) as varbinary),cast(cast('1.25' as decimal(4,2)) as varbinary); +---- +X'323032342D30312D3032' X'30333A30343A3035' X'312E3235' + +query TTTT +select x'313233'=cast(123 as bigint),cast(123 as bigint)=x'313233',x'313233'='123','123'=x'313233'; +---- +true true true true + +query TTTT +select x'313233' in (cast(123 as bigint),null),123 in (x'313233',null),x'30' not in (123,null),x'313233' between 100 and 200; +---- +true true NULL true + +query TT +select x'303132' in (select cast(id as bigint) from binary_values where false),x'303132' not in (select cast(id as bigint) from binary_values where false); +---- +false true + +query TT +select x'31' in (select cast(id as bigint) from binary_values),cast(1 as bigint) in (select cast(id as varbinary) from binary_values); +---- +true true + +statement error +select cast('中' as varbinary(2)); + +statement error +select cast(123 as varbinary(2)); + +statement error +select x'31'+1; + +statement ok +create table binary_casts (id int primary key,b varbinary(4) unique); + +statement ok +insert into binary_casts values (1,123),(2,'abc'),(3,null); + +query IT +select id,b from binary_casts order by id; +---- +1 X'313233' +2 X'616263' +3 NULL + +statement error +insert into binary_casts values (4,'123'); + +query I +select id from binary_casts where b=x'313233'; +---- +1 + +statement ok +drop table binary_casts; + +statement ok +drop table binary_values; diff --git a/tests/slt/change_column.slt b/tests/slt/change_column.slt index aad116af..820c3883 100644 --- a/tests/slt/change_column.slt +++ b/tests/slt/change_column.slt @@ -197,3 +197,75 @@ value2 Integer 4 false EMPTY 808 statement ok drop table alter_users + +# Failed conversions must preserve both metadata and all original rows. +statement ok +create table alter_atomic(id int primary key, v bigint unique, label varchar(12)); + +statement ok +insert into alter_atomic values(1,10,'short'),(2,2147483648,'long-value'); + +statement error cannot alter type of indexed column +alter table alter_atomic alter column v type int; + +query TTTTTT +describe alter_atomic; +---- +id Integer 4 false PRIMARY NULL +v Bigint 8 true UNIQUE NULL +label Varchar(Some(12), CHARACTERS) variable true EMPTY NULL + +query IT +select id,label from alter_atomic where v=2147483648; +---- +2 long-value + +query II +select id,v from alter_atomic order by id; +---- +1 10 +2 2147483648 + +statement ok +drop index alter_atomic.uk_v_index; + +statement error try from int: +alter table alter_atomic alter column v type int; + +query II +select id,v from alter_atomic order by id; +---- +1 10 +2 2147483648 + +statement error too long +alter table alter_atomic alter column label type varchar(5); + +query IT +select id,label from alter_atomic order by id; +---- +1 short +2 long-value + +statement ok +insert into alter_atomic values(3,30,'still-long'); + +statement ok +update alter_atomic set v=20 where id=2; + +statement ok +alter table alter_atomic alter column v type int; + +query II +select id,v from alter_atomic where v=20; +---- +2 20 + +statement ok +create unique index alter_v on alter_atomic(v); + +statement error the column has been declared unique +insert into alter_atomic values(4,20,'duplicate'); + +statement ok +drop table alter_atomic; diff --git a/tests/slt/char.slt b/tests/slt/char.slt index 12bbe883..766e42d8 100644 --- a/tests/slt/char.slt +++ b/tests/slt/char.slt @@ -65,8 +65,52 @@ select id from t2 where +v1 is null; statement error select -v1 from t2; +# Eliminating unary + must not silently accept non-numeric/non-text types. +statement error unsupported unary operator +select +true; + +statement error unsupported unary operator +select +cast('2024-01-01' as date); + statement ok drop table t1; statement ok -drop table t2; \ No newline at end of file +drop table t2; + +# String length functions. +# char_length / octet_length: integer result, NULL in -> NULL out. + +statement ok +create table s(id int primary key, c varchar); + +statement ok +insert into s values (1, 'ab'), (2, 'abcd'), (3, null); + +query R +select avg(char_length(c)) from s; +---- +3.0 + +query I +select sum(octet_length(c)) from s; +---- +6 + +query I rowsort +select id from s where char_length(c) = 2; +---- +1 + +query I rowsort +select id from s where char_length(c) + 1 > 3; +---- +2 + +query I +select char_length(c) from s where id = 3; +---- +NULL + +statement ok +drop table s diff --git a/tests/slt/crdb/join.slt b/tests/slt/crdb/join.slt index b867d43e..ee22890c 100644 --- a/tests/slt/crdb/join.slt +++ b/tests/slt/crdb/join.slt @@ -1224,6 +1224,21 @@ select a.id,b.id from ij_a a cross join ij_b b where a.k=b.k and a.v+b.v>320 ord ---- 4 3 +# Expression equalities remain residual predicates, not hash keys. +query II +select a.id,b.id from ij_a a inner join ij_b b on a.k=b.k and a.v+75=b.v*1 order by a.id,b.id; +---- +4 1 + +query II +select a.id,b.id from ij_a a inner join ij_b b on a.k=b.k and a.v+10 0; + +statement error CREATE INDEX options +create index option_index on t (v1) include (v2); + +statement error CREATE INDEX options +create index option_index on t using hash (v1); + +statement error CREATE INDEX options +create index concurrently option_index on t (v1); + +statement error CREATE INDEX options +create unique index option_index on t (v1) nulls not distinct; + +statement error CREATE INDEX options +create index option_index on t (v1) with (fillfactor=70); + +statement error CREATE INDEX column options +create index option_index on t (v1 desc); + +statement error CREATE INDEX column options +create index option_index on t (v1 nulls first); + +statement error CREATE INDEX column options +create index option_index on t (v1 int4_ops); + +statement ok +create index option_index on t (v1); + +statement ok +drop index option_index on t; + +query IIII +select * from t; +---- +0 0 0 0 + statement ok drop index t.index_1 diff --git a/tests/slt/insert.slt b/tests/slt/insert.slt index ca48be81..4037852f 100644 --- a/tests/slt/insert.slt +++ b/tests/slt/insert.slt @@ -57,6 +57,50 @@ select * from t statement ok drop table t; +# REPLACE spellings share INSERT OVERWRITE semantics for VALUES and queries. +statement ok +create table t_replace(id int primary key, v varchar(16) default 'default') + +statement ok +insert into t_replace values (1, 'original') + +statement ok +insert or replace into t_replace values (1, 'or replace'), (2, 'new') + +query IT +select * from t_replace order by id +---- +1 or replace +2 new + +statement ok +replace into t_replace values (1, 'replace') + +statement ok +insert or replace into t_replace select 2, 'query replace' + +statement ok +replace into t_replace select 3, 'query new' + +statement ok +replace into t_replace(id) values (1) + +statement error +insert or replace into t_replace values (2, 'insert or replace') + +statement error +insert into t_replace values (2, 'duplicate') + +query IT +select * from t_replace order by id +---- +1 default +2 query replace +3 query new + +statement ok +drop table t_replace + statement ok create table t1(id int primary key, v1 bigint default 233) diff --git a/tests/slt/joined_dml.slt b/tests/slt/joined_dml.slt index 769e41d1..fb71467d 100644 --- a/tests/slt/joined_dml.slt +++ b/tests/slt/joined_dml.slt @@ -168,6 +168,27 @@ select * from joined_dml_source 8 800 9 900 +# Joined assignments read target columns even after pruning the source side. +statement ok +update joined_dml_target inner join joined_dml_source on joined_dml_target.id=joined_dml_source.id +set v=joined_dml_target.v+1 where joined_dml_target.id=5; + +query II rowsort +select * from joined_dml_target; +---- +5 506 +7 909 + +statement error joined UPDATE SET expressions can only reference target table columns +update joined_dml_target inner join joined_dml_source on joined_dml_target.id=joined_dml_source.id +set v=joined_dml_source.v; + +query II rowsort +select * from joined_dml_target; +---- +5 506 +7 909 + statement ok drop table joined_dml_target diff --git a/tests/slt/limit.slt b/tests/slt/limit.slt index 7022ef02..0b271e5f 100644 --- a/tests/slt/limit.slt +++ b/tests/slt/limit.slt @@ -24,6 +24,25 @@ select v1 from t limit 2 offset 2 3 10 +# Comma LIMIT is offset,count; BIGINT limits use the same boundary semantics. +query I +select id from t order by id limit 1,2; +---- +1 +2 + +statement error invalid limit expression +select id from t order by id limit cast(2 as bigint) offset cast(1 as bigint); + +query I +select id from t order by id limit 2147483648 offset 4; +---- +4 + +query I +select id from t order by id limit 5,0; +---- + query I select v1 from t limit 6 ---- diff --git a/tests/slt/string_length.slt b/tests/slt/string_length.slt deleted file mode 100644 index 73f7bd93..00000000 --- a/tests/slt/string_length.slt +++ /dev/null @@ -1,35 +0,0 @@ -# char_length / octet_length: integer result, NULL in -> NULL out. - -statement ok -create table s(id int primary key, c varchar); - -statement ok -insert into s values (1, 'ab'), (2, 'abcd'), (3, null); - -query R -select avg(char_length(c)) from s; ----- -3.0 - -query I -select sum(octet_length(c)) from s; ----- -6 - -query I rowsort -select id from s where char_length(c) = 2; ----- -1 - -query I rowsort -select id from s where char_length(c) + 1 > 3; ----- -2 - -query I -select char_length(c) from s where id = 3; ----- -NULL - -statement ok -drop table s diff --git a/tests/slt/subquery.slt b/tests/slt/subquery.slt index c2ed1b5c..0e3628a8 100644 --- a/tests/slt/subquery.slt +++ b/tests/slt/subquery.slt @@ -1036,6 +1036,10 @@ select id from corr_rows where b=(select max(x.b) from corr_rows x where x.idall(select b from corr_rows),5( database: &mut Database, sql: &str, ) -> Result<(), TpccError> { - let statements = prepare_all(sql)?; + let statements = database.prepare_all(sql)?; let all_ddl = statements.iter().try_fold(true, |all_ddl, statement| { Ok::<_, kite_sql::errors::DatabaseError>( all_ddl && matches!(command_type(statement)?, CommandType::DDL), diff --git a/tpcc/src/backend/sqlite.rs b/tpcc/src/backend/sqlite.rs index 5e4dd25a..3de33ef7 100644 --- a/tpcc/src/backend/sqlite.rs +++ b/tpcc/src/backend/sqlite.rs @@ -229,6 +229,7 @@ fn convert_value(value: &DataValue) -> Result { DataValue::UInt16(v) => Value::Integer(*v as i64), DataValue::UInt32(v) => Value::Integer(*v as i64), DataValue::UInt64(v) => Value::Integer(*v as i64), + DataValue::Binary { value, .. } => Value::Binary(value.clone()), DataValue::Utf8 { value, .. } => Value::String(value.clone()), DataValue::Date32(v) => Value::Integer(*v as i64), DataValue::Date64(v) => Value::String( diff --git a/tpcc/src/load.rs b/tpcc/src/load.rs index c53141ba..f889d678 100644 --- a/tpcc/src/load.rs +++ b/tpcc/src/load.rs @@ -859,6 +859,7 @@ impl Load { } } +// GRCOV_EXCL_START #[cfg(test)] mod tests { use super::{init_permutation, SqlBatch, CUST_PER_DIST, LOAD_BATCH_SIZE}; @@ -924,3 +925,4 @@ mod tests { assert!(seen.iter().skip(1).all(|seen| *seen)); } } +// GRCOV_EXCL_STOP diff --git a/tpcc/src/main.rs b/tpcc/src/main.rs index a8385514..821c4d4d 100644 --- a/tpcc/src/main.rs +++ b/tpcc/src/main.rs @@ -856,6 +856,7 @@ impl_from_tpcc_error!(rust_decimal::Error, Decimal); impl_from_tpcc_error!(chrono::ParseError, Chrono); impl_from_tpcc_error!(std::io::Error, Io); +// GRCOV_EXCL_START #[ignore] #[test] fn explain_tpcc() -> Result<(), DatabaseError> { @@ -1187,3 +1188,4 @@ fn tpcc_db_path() -> PathBuf { .unwrap() .join("kite_sql_tpcc") } +// GRCOV_EXCL_STOP