diff --git a/Cargo.toml b/Cargo.toml index 4c743a0d..cf94584e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ pretty_assertions = { version = "1" } [features] # STUB +backend-databend = [] backend-mysql = [] backend-postgres = [] backend-sqlite = [] @@ -119,6 +120,11 @@ name = "test-sqlite" path = "tests/sqlite/mod.rs" required-features = ["tests-cfg", "backend-sqlite"] +[[test]] +name = "test-databend" +path = "tests/databend/mod.rs" +required-features = ["tests-cfg", "backend-databend"] + [[test]] name = "option-more-parentheses" path = "tests/more-parentheses.rs" diff --git a/src/backend/databend/foreign_key.rs b/src/backend/databend/foreign_key.rs new file mode 100644 index 00000000..616b7cb2 --- /dev/null +++ b/src/backend/databend/foreign_key.rs @@ -0,0 +1,21 @@ +use super::*; + +impl ForeignKeyBuilder for DatabendQueryBuilder { + fn prepare_table_ref_fk_stmt(&self, _table_ref: &TableRef, _sql: &mut dyn SqlWriter) {} + + fn prepare_foreign_key_drop_statement_internal( + &self, + _drop: &ForeignKeyDropStatement, + _sql: &mut dyn SqlWriter, + _mode: Mode, + ) { + } + + fn prepare_foreign_key_create_statement_internal( + &self, + _create: &ForeignKeyCreateStatement, + _sql: &mut dyn SqlWriter, + _mode: Mode, + ) { + } +} diff --git a/src/backend/databend/index.rs b/src/backend/databend/index.rs new file mode 100644 index 00000000..32f66d90 --- /dev/null +++ b/src/backend/databend/index.rs @@ -0,0 +1,18 @@ +use super::*; + +impl IndexBuilder for DatabendQueryBuilder { + fn prepare_index_create_statement( + &self, + _create: &IndexCreateStatement, + _sql: &mut dyn SqlWriter, + ) { + } + + fn prepare_table_ref_index_stmt(&self, _table_ref: &TableRef, _sql: &mut dyn SqlWriter) {} + + fn prepare_index_drop_statement(&self, _drop: &IndexDropStatement, _sql: &mut dyn SqlWriter) {} + + fn prepare_index_prefix(&self, _create: &IndexCreateStatement, _sql: &mut dyn SqlWriter) {} + + fn write_column_index_prefix(&self, _col_prefix: &Option, _sql: &mut dyn SqlWriter) {} +} diff --git a/src/backend/databend/mod.rs b/src/backend/databend/mod.rs new file mode 100644 index 00000000..6f320b98 --- /dev/null +++ b/src/backend/databend/mod.rs @@ -0,0 +1,40 @@ +pub(crate) mod foreign_key; +pub(crate) mod index; +pub(crate) mod query; +pub(crate) mod table; + +use super::*; + +/// Sqlite query builder. +#[derive(Default, Debug)] +pub struct DatabendQueryBuilder; + +impl GenericBuilder for DatabendQueryBuilder {} + +impl SchemaBuilder for DatabendQueryBuilder {} + +impl QuotedBuilder for DatabendQueryBuilder { + fn quote(&self) -> Quote { + MysqlQueryBuilder.quote() + } +} + +impl EscapeBuilder for DatabendQueryBuilder {} + +impl TableRefBuilder for DatabendQueryBuilder {} + +impl PrecedenceDecider for DatabendQueryBuilder { + fn inner_expr_well_known_greater_precedence( + &self, + inner: &SimpleExpr, + outer_oper: &Oper, + ) -> bool { + common_inner_expr_well_known_greater_precedence(inner, outer_oper) + } +} + +impl OperLeftAssocDecider for DatabendQueryBuilder { + fn well_known_left_associative(&self, op: &BinOper) -> bool { + common_well_known_left_associative(op) + } +} diff --git a/src/backend/databend/query.rs b/src/backend/databend/query.rs new file mode 100644 index 00000000..3214ad00 --- /dev/null +++ b/src/backend/databend/query.rs @@ -0,0 +1,99 @@ +use super::*; + +impl QueryBuilder for DatabendQueryBuilder { + fn prepare_insert_statement(&self, insert: &InsertStatement, sql: &mut dyn SqlWriter) { + self.prepare_insert(insert.replace, sql); + + if let Some(table) = &insert.table { + write!(sql, " INTO ").unwrap(); + self.prepare_table_ref(table, sql); + } + + self.prepare_output(&insert.returning, sql); + + write!(sql, " ").unwrap(); + + if insert.default_values.is_some() && insert.columns.is_empty() && insert.source.is_none() { + let num_rows = insert.default_values.unwrap(); + self.insert_default_values(num_rows, sql); + } else { + write!(sql, "(").unwrap(); + insert.columns.iter().fold(true, |first, col| { + if !first { + write!(sql, ", ").unwrap() + } + col.prepare(sql.as_writer(), self.quote()); + false + }); + write!(sql, ")").unwrap(); + + if insert.replace { + self.prepare_on_conflict(&insert.on_conflict, sql); + } + + if let Some(source) = &insert.source { + write!(sql, " ").unwrap(); + match source { + InsertValueSource::Values(values) => { + write!(sql, "VALUES ").unwrap(); + values.iter().fold(true, |first, row| { + if !first { + write!(sql, ", ").unwrap() + } + write!(sql, "(").unwrap(); + row.iter().fold(true, |first, col| { + if !first { + write!(sql, ", ").unwrap() + } + self.prepare_simple_expr(col, sql); + false + }); + write!(sql, ")").unwrap(); + false + }); + } + InsertValueSource::Select(select_query) => { + self.prepare_select_statement(select_query, sql); + } + } + } + } + } + fn prepare_on_conflict_keywords(&self, sql: &mut dyn SqlWriter) { + write!(sql, " ON ").unwrap(); + } + fn prepare_on_conflict(&self, on_conflict: &Option, sql: &mut dyn SqlWriter) { + if let Some(on_conflict) = on_conflict { + self.prepare_on_conflict_keywords(sql); + self.prepare_on_conflict_target(&on_conflict.targets, sql); + } + } + + fn prepare_select_lock(&self, _select_lock: &LockClause, _sql: &mut dyn SqlWriter) {} + + fn prepare_query_statement(&self, query: &SubQueryStatement, sql: &mut dyn SqlWriter) { + query.prepare_statement(self, sql); + } + + fn prepare_on_conflict_condition(&self, _: &ConditionHolder, _: &mut dyn SqlWriter) {} + + fn prepare_returning(&self, _returning: &Option, _sql: &mut dyn SqlWriter) {} + + fn prepare_with_clause_recursive_options(&self, _: &WithClause, _: &mut dyn SqlWriter) { + // Sqlite doesn't support sql recursive with query 'SEARCH' and 'CYCLE' options. + } + + fn prepare_order_expr(&self, order_expr: &OrderExpr, sql: &mut dyn SqlWriter) { + MysqlQueryBuilder.prepare_order_expr(order_expr, sql) + } + + fn prepare_value(&self, value: &Value, sql: &mut dyn SqlWriter) { + let v = self.value_to_string(value); + write!(sql, "{v}").unwrap(); + // sql.push_param(value.clone(), self as _); + } + + fn insert_default_keyword(&self) -> &str { + "()" + } +} diff --git a/src/backend/databend/table.rs b/src/backend/databend/table.rs new file mode 100644 index 00000000..17e451ac --- /dev/null +++ b/src/backend/databend/table.rs @@ -0,0 +1,214 @@ +use super::*; + +impl TableBuilder for DatabendQueryBuilder { + fn prepare_table_create_statement( + &self, + create: &TableCreateStatement, + sql: &mut dyn SqlWriter, + ) { + write!(sql, "CREATE TABLE ").unwrap(); + + self.prepare_create_table_if_not_exists(create, sql); + + if let Some(table_ref) = &create.table { + self.prepare_table_ref_table_stmt(table_ref, sql); + } + + write!(sql, " ( ").unwrap(); + let mut first = true; + + create.columns.iter().for_each(|column_def| { + if !first { + write!(sql, ", ").unwrap(); + } + self.prepare_column_def(column_def, sql); + first = false; + }); + + write!(sql, " )").unwrap(); + } + + fn prepare_column_def(&self, column_def: &ColumnDef, sql: &mut dyn SqlWriter) { + column_def.name.prepare(sql.as_writer(), self.quote()); + + if let Some(column_type) = &column_def.types { + write!(sql, " ").unwrap(); + self.prepare_column_type(column_type, sql); + } + + for column_spec in column_def.spec.iter() { + let mut s = String::new(); + self.prepare_column_spec(column_spec, &mut s); + if !s.is_empty() { + write!(sql, " {s}").unwrap(); + } + } + } + + fn prepare_column_spec(&self, column_spec: &ColumnSpec, sql: &mut dyn SqlWriter) { + match column_spec { + ColumnSpec::Null => write!(sql, "NULL").unwrap(), + ColumnSpec::NotNull => write!(sql, "NOT NULL").unwrap(), + ColumnSpec::Default(value) => { + write!(sql, "DEFAULT ").unwrap(); + QueryBuilder::prepare_simple_expr(self, value, sql); + } + ColumnSpec::Generated { expr, stored } => { + self.prepare_generated_column(expr, *stored, sql) + } + ColumnSpec::Extra(string) => write!(sql, "{string}").unwrap(), + ColumnSpec::Comment(comment) => self.column_comment(comment, sql), + _ => {} + } + } + + fn prepare_column_type(&self, column_type: &ColumnType, sql: &mut dyn SqlWriter) { + write!( + sql, + "{}", + match column_type { + ColumnType::Char(length) => match length { + Some(length) => format!("char({length})"), + None => "char".into(), + }, + ColumnType::String(length) => match length { + StringLen::N(length) => format!("varchar({length})"), + _ => "varchar".into(), + }, + ColumnType::Text => "text".into(), + ColumnType::TinyInteger | ColumnType::TinyUnsigned => "tinyint".into(), + ColumnType::SmallInteger | ColumnType::SmallUnsigned => "smallint".into(), + ColumnType::Integer | ColumnType::Unsigned => "int".into(), + ColumnType::BigInteger | ColumnType::BigUnsigned => "bigint".into(), + ColumnType::Float => "float".into(), + ColumnType::Double => "double".into(), + ColumnType::Decimal(precision) => match precision { + Some((precision, scale)) => format!("decimal({precision}, {scale})"), + None => "decimal".into(), + }, + ColumnType::DateTime => "datetime".into(), + ColumnType::Timestamp => "timestamp".into(), + ColumnType::TimestampWithTimeZone => "timestamp".into(), + ColumnType::Time => "time".into(), + ColumnType::Date => "date".into(), + ColumnType::Year => "year".into(), + ColumnType::Interval(_, _) => "unsupported".into(), + ColumnType::Binary(length) => format!("binary({length})"), + ColumnType::VarBinary(length) => match length { + StringLen::N(length) => format!("varbinary({length})"), + _ => "varbinary".into(), + }, + ColumnType::Bit(length) => { + match length { + Some(length) => format!("bit({length})"), + None => "bit".into(), + } + } + ColumnType::VarBit(length) => { + format!("bit({length})") + } + ColumnType::Boolean => "bool".into(), + ColumnType::Money(precision) => match precision { + Some((precision, scale)) => format!("decimal({precision}, {scale})"), + None => "decimal".into(), + }, + ColumnType::Json => "json".into(), + ColumnType::JsonBinary => "json".into(), + ColumnType::Uuid => "binary(16)".into(), + ColumnType::Custom(iden) => iden.to_string(), + ColumnType::Enum { variants, .. } => format!( + "ENUM('{}')", + variants + .iter() + .map(|v| v.to_string()) + .collect::>() + .join("', '") + ), + ColumnType::Array(_) => unimplemented!("Array is not available in MySQL."), + ColumnType::Cidr => unimplemented!("Cidr is not available in MySQL."), + ColumnType::Inet => unimplemented!("Inet is not available in MySQL."), + ColumnType::MacAddr => unimplemented!("MacAddr is not available in MySQL."), + ColumnType::LTree => unimplemented!("LTree is not available in MySQL."), + } + ) + .unwrap(); + if matches!( + column_type, + ColumnType::TinyUnsigned + | ColumnType::SmallUnsigned + | ColumnType::Unsigned + | ColumnType::BigUnsigned + ) { + write!(sql, " ").unwrap(); + write!(sql, "UNSIGNED").unwrap(); + } + } + + fn column_spec_auto_increment_keyword(&self) -> &str { + "" + } + + fn prepare_table_drop_opt(&self, drop_opt: &TableDropOpt, sql: &mut dyn SqlWriter) { + write!( + sql, + "{}", + match drop_opt { + TableDropOpt::Restrict => "", + TableDropOpt::Cascade => " ALL", + } + ) + .unwrap(); + } + + fn prepare_table_alter_statement(&self, alter: &TableAlterStatement, sql: &mut dyn SqlWriter) { + if alter.options.is_empty() { + panic!("No alter option found") + }; + write!(sql, "ALTER TABLE ").unwrap(); + if let Some(table) = &alter.table { + self.prepare_table_ref_table_stmt(table, sql); + write!(sql, " ").unwrap(); + } + alter.options.iter().fold(true, |first, option| { + if !first { + write!(sql, ", ").unwrap(); + }; + match option { + TableAlterOption::AddColumn(AddColumnOption { + column, + if_not_exists, + }) => { + write!(sql, "ADD COLUMN ").unwrap(); + if *if_not_exists { + write!(sql, "IF NOT EXISTS ").unwrap(); + } + self.prepare_column_def(column, sql); + } + TableAlterOption::ModifyColumn(column_def) => { + write!(sql, "MODIFY COLUMN ").unwrap(); + self.prepare_column_def(column_def, sql); + } + TableAlterOption::RenameColumn(from_name, to_name) => { + write!(sql, "RENAME COLUMN ").unwrap(); + from_name.prepare(sql.as_writer(), self.quote()); + write!(sql, " TO ").unwrap(); + to_name.prepare(sql.as_writer(), self.quote()); + } + TableAlterOption::DropColumn(column_name) => { + write!(sql, "DROP COLUMN ").unwrap(); + column_name.prepare(sql.as_writer(), self.quote()); + } + _ => {} + }; + false + }); + } + + fn prepare_table_rename_statement( + &self, + rename: &TableRenameStatement, + sql: &mut dyn SqlWriter, + ) { + MysqlQueryBuilder.prepare_table_rename_statement(rename, sql) + } +} diff --git a/src/backend/mod.rs b/src/backend/mod.rs index b918e854..5c1025a6 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -2,6 +2,9 @@ use crate::*; +#[cfg(feature = "backend-databend")] +#[cfg_attr(docsrs, doc(cfg(feature = "backend-databend")))] +pub mod databend; #[cfg(feature = "backend-mysql")] #[cfg_attr(docsrs, doc(cfg(feature = "backend-mysql")))] mod mysql; @@ -12,6 +15,8 @@ mod postgres; #[cfg_attr(docsrs, doc(cfg(feature = "backend-sqlite")))] mod sqlite; +#[cfg(feature = "backend-databend")] +pub use databend::*; #[cfg(feature = "backend-mysql")] pub use mysql::*; #[cfg(feature = "backend-postgres")] diff --git a/tests/databend/mod.rs b/tests/databend/mod.rs new file mode 100644 index 00000000..7b67bf17 --- /dev/null +++ b/tests/databend/mod.rs @@ -0,0 +1,8 @@ +use sea_query::{extension::mysql::*, tests_cfg::*, *}; + +mod query; +mod table; + +#[path = "../common.rs"] +mod common; +use common::*; diff --git a/tests/databend/query.rs b/tests/databend/query.rs new file mode 100644 index 00000000..dc862132 --- /dev/null +++ b/tests/databend/query.rs @@ -0,0 +1,1498 @@ +use super::*; +use pretty_assertions::assert_eq; + +#[test] +fn select_1() { + assert_eq!( + Query::select() + .columns([Char::Character, Char::SizeW, Char::SizeH]) + .from(Char::Table) + .limit(10) + .offset(100) + .to_string(DatabendQueryBuilder), + "SELECT `character`, `size_w`, `size_h` FROM `character` LIMIT 10 OFFSET 100" + ); +} + +#[test] +fn select_2() { + assert_eq!( + Query::select() + .columns([Char::Character, Char::SizeW, Char::SizeH]) + .from(Char::Table) + .and_where(Expr::col(Char::SizeW).eq(3)) + .to_string(DatabendQueryBuilder), + "SELECT `character`, `size_w`, `size_h` FROM `character` WHERE `size_w` = 3" + ); +} + +#[test] +fn select_3() { + assert_eq!( + Query::select() + .columns([ + Char::Character, Char::SizeW, Char::SizeH + ]) + .from(Char::Table) + .and_where(Expr::col(Char::SizeW).eq(3)) + .and_where(Expr::col(Char::SizeH).eq(4)) + .to_string(DatabendQueryBuilder), + "SELECT `character`, `size_w`, `size_h` FROM `character` WHERE `size_w` = 3 AND `size_h` = 4" + ); +} + +#[test] +fn select_4() { + assert_eq!( + Query::select() + .columns([Glyph::Image]) + .from_subquery( + Query::select() + .columns([Glyph::Image, Glyph::Aspect]) + .from(Glyph::Table) + .take(), + Alias::new("subglyph") + ) + .to_string(DatabendQueryBuilder), + "SELECT `image` FROM (SELECT `image`, `aspect` FROM `glyph`) AS `subglyph`" + ); +} + +#[test] +fn select_5() { + assert_eq!( + Query::select() + .column((Glyph::Table, Glyph::Image)) + .from(Glyph::Table) + .and_where(Expr::col((Glyph::Table, Glyph::Aspect)).is_in([3, 4])) + .to_string(DatabendQueryBuilder), + "SELECT `glyph`.`image` FROM `glyph` WHERE `glyph`.`aspect` IN (3, 4)" + ); +} + +#[test] +fn select_6() { + assert_eq!( + Query::select() + .columns([Glyph::Aspect]) + .exprs([Expr::col(Glyph::Image).max()]) + .from(Glyph::Table) + .group_by_columns([Glyph::Aspect]) + .and_having(Expr::col(Glyph::Aspect).gt(2)) + .to_string(DatabendQueryBuilder), + "SELECT `aspect`, MAX(`image`) FROM `glyph` GROUP BY `aspect` HAVING `aspect` > 2" + ); +} + +#[test] +fn select_7() { + assert_eq!( + Query::select() + .columns([Glyph::Aspect]) + .from(Glyph::Table) + .and_where(Expr::expr(Expr::col(Glyph::Aspect).if_null(0)).gt(2)) + .to_string(DatabendQueryBuilder), + "SELECT `aspect` FROM `glyph` WHERE IFNULL(`aspect`, 0) > 2" + ); +} + +#[test] +fn select_8() { + assert_eq!( + Query::select() + .columns([ + Char::Character, + ]) + .from(Char::Table) + .left_join(Font::Table, Expr::col((Char::Table, Char::FontId)).equals((Font::Table, Font::Id))) + .to_string(DatabendQueryBuilder), + "SELECT `character` FROM `character` LEFT JOIN `font` ON `character`.`font_id` = `font`.`id`" + ); +} + +#[test] +fn select_9() { + assert_eq!( + Query::select() + .columns([ + Char::Character, + ]) + .from(Char::Table) + .left_join(Font::Table, Expr::col((Char::Table, Char::FontId)).equals((Font::Table, Font::Id))) + .inner_join(Glyph::Table, Expr::col((Char::Table, Char::Character)).equals((Glyph::Table, Glyph::Image))) + .to_string(DatabendQueryBuilder), + "SELECT `character` FROM `character` LEFT JOIN `font` ON `character`.`font_id` = `font`.`id` INNER JOIN `glyph` ON `character`.`character` = `glyph`.`image`" + ); +} + +#[test] +fn select_10() { + assert_eq!( + Query::select() + .columns([ + Char::Character, + ]) + .from(Char::Table) + .left_join(Font::Table, + Expr::col((Char::Table, Char::FontId)).equals((Font::Table, Font::Id)) + .and(Expr::col((Char::Table, Char::FontId)).equals((Font::Table, Font::Id))) + ) + .to_string(DatabendQueryBuilder), + "SELECT `character` FROM `character` LEFT JOIN `font` ON `character`.`font_id` = `font`.`id` AND `character`.`font_id` = `font`.`id`" + ); +} + +#[test] +fn select_11() { + assert_eq!( + Query::select() + .columns([ + Glyph::Aspect, + ]) + .from(Glyph::Table) + .and_where(Expr::expr(Expr::col(Glyph::Aspect).if_null(0)).gt(2)) + .order_by(Glyph::Image, Order::Desc) + .order_by((Glyph::Table, Glyph::Aspect), Order::Asc) + .to_string(DatabendQueryBuilder), + "SELECT `aspect` FROM `glyph` WHERE IFNULL(`aspect`, 0) > 2 ORDER BY `image` DESC, `glyph`.`aspect` ASC" + ); +} + +#[test] +fn select_12() { + assert_eq!( + Query::select() + .columns([ + Glyph::Aspect, + ]) + .from(Glyph::Table) + .and_where(Expr::expr(Expr::col(Glyph::Aspect).if_null(0)).gt(2)) + .order_by_columns([ + (Glyph::Id, Order::Asc), + (Glyph::Aspect, Order::Desc), + ]) + .to_string(DatabendQueryBuilder), + "SELECT `aspect` FROM `glyph` WHERE IFNULL(`aspect`, 0) > 2 ORDER BY `id` ASC, `aspect` DESC" + ); +} + +#[test] +fn select_13() { + assert_eq!( + Query::select() + .columns([ + Glyph::Aspect, + ]) + .from(Glyph::Table) + .and_where(Expr::expr(Expr::col(Glyph::Aspect).if_null(0)).gt(2)) + .order_by_columns([ + ((Glyph::Table, Glyph::Id), Order::Asc), + ((Glyph::Table, Glyph::Aspect), Order::Desc), + ]) + .to_string(DatabendQueryBuilder), + "SELECT `aspect` FROM `glyph` WHERE IFNULL(`aspect`, 0) > 2 ORDER BY `glyph`.`id` ASC, `glyph`.`aspect` DESC" + ); +} + +#[test] +fn select_14() { + assert_eq!( + Query::select() + .columns([ + Glyph::Id, + Glyph::Aspect, + ]) + .expr(Expr::col(Glyph::Image).max()) + .from(Glyph::Table) + .group_by_columns([ + (Glyph::Table, Glyph::Id), + (Glyph::Table, Glyph::Aspect), + ]) + .and_having(Expr::col(Glyph::Aspect).gt(2)) + .to_string(DatabendQueryBuilder), + "SELECT `id`, `aspect`, MAX(`image`) FROM `glyph` GROUP BY `glyph`.`id`, `glyph`.`aspect` HAVING `aspect` > 2" + ); +} + +#[test] +fn select_15() { + assert_eq!( + Query::select() + .columns([Char::Character]) + .from(Char::Table) + .and_where(Expr::col(Char::FontId).is_null()) + .to_string(DatabendQueryBuilder), + "SELECT `character` FROM `character` WHERE `font_id` IS NULL" + ); +} + +#[test] +fn select_16() { + assert_eq!( + Query::select() + .columns([Char::Character]) + .from(Char::Table) + .and_where(Expr::col(Char::FontId).is_null()) + .and_where(Expr::col(Char::Character).is_not_null()) + .to_string(DatabendQueryBuilder), + "SELECT `character` FROM `character` WHERE `font_id` IS NULL AND `character` IS NOT NULL" + ); +} + +#[test] +fn select_17() { + assert_eq!( + Query::select() + .columns([(Glyph::Table, Glyph::Image)]) + .from(Glyph::Table) + .and_where(Expr::col((Glyph::Table, Glyph::Aspect)).between(3, 5)) + .to_string(DatabendQueryBuilder), + "SELECT `glyph`.`image` FROM `glyph` WHERE `glyph`.`aspect` BETWEEN 3 AND 5" + ); +} + +#[test] +fn select_18() { + assert_eq!( + Query::select() + .columns([ + Glyph::Aspect, + ]) + .from(Glyph::Table) + .and_where(Expr::col(Glyph::Aspect).between(3, 5)) + .and_where(Expr::col(Glyph::Aspect).not_between(8, 10)) + .to_string(DatabendQueryBuilder), + "SELECT `aspect` FROM `glyph` WHERE (`aspect` BETWEEN 3 AND 5) AND (`aspect` NOT BETWEEN 8 AND 10)" + ); +} + +#[test] +fn select_19() { + assert_eq!( + Query::select() + .columns([Char::Character]) + .from(Char::Table) + .and_where(Expr::col(Char::Character).eq("A")) + .to_string(DatabendQueryBuilder), + "SELECT `character` FROM `character` WHERE `character` = 'A'" + ); +} + +#[test] +fn select_20() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where(Expr::col(Char::Character).like("A")) + .to_string(DatabendQueryBuilder), + "SELECT `character` FROM `character` WHERE `character` LIKE 'A'" + ); +} + +#[test] +fn select_21() { + assert_eq!( + Query::select() + .columns([ + Char::Character + ]) + .from(Char::Table) + .cond_where(any![ + Expr::col(Char::Character).like("A%"), + Expr::col(Char::Character).like("%B"), + Expr::col(Char::Character).like("%C%"), + ]) + .to_string(DatabendQueryBuilder), + "SELECT `character` FROM `character` WHERE `character` LIKE 'A%' OR `character` LIKE '%B' OR `character` LIKE '%C%'" + ); +} + +#[test] +fn select_22() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .cond_where( + Cond::all() + .add( + Cond::any() + .add(Expr::col(Char::Character).like("C")) + .add(Expr::col(Char::Character).like("D").and(Expr::col(Char::Character).like("E"))) + ) + .add( + Expr::col(Char::Character).like("F").or(Expr::col(Char::Character).like("G")) + ) + ) + .to_string(DatabendQueryBuilder), + "SELECT `character` FROM `character` WHERE (`character` LIKE 'C' OR (`character` LIKE 'D' AND `character` LIKE 'E')) AND (`character` LIKE 'F' OR `character` LIKE 'G')" + ); +} + +#[test] +fn select_23() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where_option(None) + .to_string(DatabendQueryBuilder), + "SELECT `character` FROM `character`" + ); +} + +#[test] +fn select_24() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .conditions( + true, + |x| { + x.and_where(Expr::col(Char::FontId).eq(5)); + }, + |_| () + ) + .to_string(DatabendQueryBuilder), + "SELECT `character` FROM `character` WHERE `font_id` = 5" + ); +} + +#[test] +fn select_25() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where( + Expr::col(Char::SizeW) + .mul(2) + .eq(Expr::col(Char::SizeH).div(2)) + ) + .to_string(DatabendQueryBuilder), + "SELECT `character` FROM `character` WHERE `size_w` * 2 = `size_h` / 2" + ); +} + +#[test] +fn select_26() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where( + Expr::expr(Expr::col(Char::SizeW).add(1)) + .mul(2) + .eq(Expr::expr(Expr::col(Char::SizeH).div(2)).sub(1)) + ) + .to_string(DatabendQueryBuilder), + "SELECT `character` FROM `character` WHERE (`size_w` + 1) * 2 = (`size_h` / 2) - 1" + ); +} + +#[test] +fn select_27() { + assert_eq!( + Query::select() + .columns([ + Char::Character, Char::SizeW, Char::SizeH + ]) + .from(Char::Table) + .and_where(Expr::col(Char::SizeW).eq(3)) + .and_where(Expr::col(Char::SizeH).eq(4)) + .and_where(Expr::col(Char::SizeH).eq(5)) + .to_string(DatabendQueryBuilder), + "SELECT `character`, `size_w`, `size_h` FROM `character` WHERE `size_w` = 3 AND `size_h` = 4 AND `size_h` = 5" + ); +} + +#[test] +fn select_28() { + assert_eq!( + Query::select() + .columns([ + Char::Character, Char::SizeW, Char::SizeH + ]) + .from(Char::Table) + .cond_where(any![ + Expr::col(Char::SizeW).eq(3), + Expr::col(Char::SizeH).eq(4), + Expr::col(Char::SizeH).eq(5), + ]) + .to_string(DatabendQueryBuilder), + "SELECT `character`, `size_w`, `size_h` FROM `character` WHERE `size_w` = 3 OR `size_h` = 4 OR `size_h` = 5" + ); +} + +#[test] +fn select_30() { + assert_eq!( + Query::select() + .columns([ + Char::Character, Char::SizeW, Char::SizeH + ]) + .from(Char::Table) + .and_where( + Expr::col(Char::SizeW).mul(2) + .add(Expr::col(Char::SizeH).div(3)) + .eq(4) + ) + .to_string(DatabendQueryBuilder), + "SELECT `character`, `size_w`, `size_h` FROM `character` WHERE (`size_w` * 2) + (`size_h` / 3) = 4" + ); +} + +#[test] +fn select_31() { + assert_eq!( + Query::select() + .expr((1..10_i32).fold(Expr::value(0), |expr, i| { expr.add(i) })) + .to_string(DatabendQueryBuilder), + "SELECT 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9" + ); +} + +#[test] +fn select_32() { + assert_eq!( + Query::select() + .expr_as(Expr::col(Char::Character), Alias::new("C")) + .from(Char::Table) + .to_string(DatabendQueryBuilder), + "SELECT `character` AS `C` FROM `character`" + ); +} + +#[test] +fn select_33() { + assert_eq!( + Query::select() + .column(Glyph::Image) + .from(Glyph::Table) + .and_where( + Expr::col(Glyph::Aspect) + .in_subquery(Query::select().expr(Expr::cust("3 + 2 * 2")).take()) + ) + .to_string(DatabendQueryBuilder), + "SELECT `image` FROM `glyph` WHERE `aspect` IN (SELECT 3 + 2 * 2)" + ); +} + +#[test] +fn select_34a() { + assert_eq!( + Query::select() + .column(Glyph::Aspect) + .expr(Expr::col(Glyph::Image).max()) + .from(Glyph::Table) + .group_by_columns([Glyph::Aspect]) + .cond_having(any![ + Expr::col(Glyph::Aspect) + .gt(2) + .or(Expr::col(Glyph::Aspect).lt(8)), + Expr::col(Glyph::Aspect) + .gt(12) + .and(Expr::col(Glyph::Aspect).lt(18)), + Expr::col(Glyph::Aspect).gt(32), + ]) + .to_string(DatabendQueryBuilder), + [ + "SELECT `aspect`, MAX(`image`) FROM `glyph` GROUP BY `aspect`", + "HAVING `aspect` > 2 OR `aspect` < 8", + "OR (`aspect` > 12 AND `aspect` < 18)", + "OR `aspect` > 32", + ] + .join(" ") + ); +} + +#[test] +fn select_35() { + let (statement, values) = Query::select() + .column(Glyph::Id) + .from(Glyph::Table) + .and_where(Expr::col(Glyph::Aspect).is_null()) + .build(DatabendQueryBuilder); + + assert_eq!( + statement, + r#"SELECT `id` FROM `glyph` WHERE `aspect` IS NULL"# + ); + assert_eq!(values.0, vec![]); +} + +#[test] +fn select_36() { + let (statement, values) = Query::select() + .column(Glyph::Id) + .from(Glyph::Table) + .cond_where(Cond::any().add(Expr::col(Glyph::Aspect).is_null())) + .build(DatabendQueryBuilder); + + assert_eq!( + statement, + r#"SELECT `id` FROM `glyph` WHERE `aspect` IS NULL"# + ); + assert_eq!(values.0, vec![]); +} + +#[test] +fn select_37() { + let (statement, values) = Query::select() + .column(Glyph::Id) + .from(Glyph::Table) + .cond_where(Cond::any().add(Cond::all()).add(Cond::any())) + .build(DatabendQueryBuilder); + + assert_eq!(statement, r#"SELECT `id` FROM `glyph` WHERE TRUE OR FALSE"#); + assert_eq!(values.0, vec![]); +} + +#[test] +fn select_37a() { + let (statement, values) = Query::select() + .column(Glyph::Id) + .from(Glyph::Table) + .cond_where( + Cond::all() + .add(Cond::all().not()) + .add(Cond::any().not()) + .not(), + ) + .build(DatabendQueryBuilder); + + assert_eq!( + statement, + r#"SELECT `id` FROM `glyph` WHERE NOT ((NOT TRUE) AND (NOT FALSE))"# + ); + assert_eq!(values.0, vec![]); +} + +#[test] +fn select_38() { + let (statement, values) = Query::select() + .column(Glyph::Id) + .from(Glyph::Table) + .cond_where( + Cond::any() + .add(Expr::col(Glyph::Aspect).is_null()) + .add(Expr::col(Glyph::Aspect).is_not_null()), + ) + .build(DatabendQueryBuilder); + + assert_eq!( + statement, + r#"SELECT `id` FROM `glyph` WHERE `aspect` IS NULL OR `aspect` IS NOT NULL"# + ); + assert_eq!(values.0, vec![]); +} + +#[test] +fn select_39() { + let (statement, values) = Query::select() + .column(Glyph::Id) + .from(Glyph::Table) + .cond_where( + Cond::all() + .add(Expr::col(Glyph::Aspect).is_null()) + .add(Expr::col(Glyph::Aspect).is_not_null()), + ) + .build(DatabendQueryBuilder); + + assert_eq!( + statement, + r#"SELECT `id` FROM `glyph` WHERE `aspect` IS NULL AND `aspect` IS NOT NULL"# + ); + assert_eq!(values.0, vec![]); +} + +#[test] +fn select_40() { + let statement = Query::select() + .column(Glyph::Id) + .from(Glyph::Table) + .cond_where(any![ + Expr::col(Glyph::Aspect).is_null(), + all![ + Expr::col(Glyph::Aspect).is_not_null(), + Expr::col(Glyph::Aspect).lt(8) + ] + ]) + .to_string(DatabendQueryBuilder); + + assert_eq!( + statement, + r#"SELECT `id` FROM `glyph` WHERE `aspect` IS NULL OR (`aspect` IS NOT NULL AND `aspect` < 8)"# + ); +} + +#[test] +fn select_41() { + assert_eq!( + Query::select() + .columns([Glyph::Aspect]) + .exprs([Expr::col(Glyph::Image).max()]) + .from(Glyph::Table) + .group_by_columns([Glyph::Aspect]) + .cond_having(any![Expr::col(Glyph::Aspect).gt(2)]) + .to_string(DatabendQueryBuilder), + "SELECT `aspect`, MAX(`image`) FROM `glyph` GROUP BY `aspect` HAVING `aspect` > 2" + ); +} + +#[test] +fn select_42() { + let statement = Query::select() + .column(Glyph::Id) + .from(Glyph::Table) + .cond_where( + Cond::all() + .add_option(Some(Expr::col(Glyph::Aspect).lt(8))) + .add(Expr::col(Glyph::Aspect).is_not_null()), + ) + .to_string(DatabendQueryBuilder); + + assert_eq!( + statement, + r#"SELECT `id` FROM `glyph` WHERE `aspect` < 8 AND `aspect` IS NOT NULL"# + ); +} + +#[test] +fn select_43() { + let statement = Query::select() + .column(Glyph::Id) + .from(Glyph::Table) + .cond_where(Cond::all().add_option::(None)) + .to_string(DatabendQueryBuilder); + + assert_eq!(statement, "SELECT `id` FROM `glyph` WHERE TRUE"); +} + +#[test] +fn select_44() { + let statement = Query::select() + .column(Glyph::Id) + .from(Glyph::Table) + .cond_where( + Cond::any() + .not() + .add_option(Some(Expr::col(Glyph::Aspect).lt(8))), + ) + .to_string(DatabendQueryBuilder); + + assert_eq!( + statement, + r#"SELECT `id` FROM `glyph` WHERE NOT `aspect` < 8"# + ); +} + +#[test] +fn select_45() { + let statement = Query::select() + .column(Glyph::Id) + .from(Glyph::Table) + .cond_where( + Cond::any() + .not() + .add_option(Some(Expr::col(Glyph::Aspect).lt(8))) + .add(Expr::col(Glyph::Aspect).is_not_null()), + ) + .to_string(DatabendQueryBuilder); + + assert_eq!( + statement, + r#"SELECT `id` FROM `glyph` WHERE NOT (`aspect` < 8 OR `aspect` IS NOT NULL)"# + ); +} + +#[test] +fn select_46() { + let statement = Query::select() + .column(Glyph::Id) + .from(Glyph::Table) + .cond_where( + Cond::all() + .not() + .add_option(Some(Expr::col(Glyph::Aspect).lt(8))), + ) + .to_string(DatabendQueryBuilder); + + assert_eq!( + statement, + r#"SELECT `id` FROM `glyph` WHERE NOT `aspect` < 8"# + ); +} + +#[test] +fn select_47() { + let statement = Query::select() + .column(Glyph::Id) + .from(Glyph::Table) + .cond_where( + Cond::all() + .not() + .add_option(Some(Expr::col(Glyph::Aspect).lt(8))) + .add(Expr::col(Glyph::Aspect).is_not_null()), + ) + .to_string(DatabendQueryBuilder); + + assert_eq!( + statement, + r#"SELECT `id` FROM `glyph` WHERE NOT (`aspect` < 8 AND `aspect` IS NOT NULL)"# + ); +} + +#[test] +fn select_48() { + let statement = Query::select() + .column(Glyph::Id) + .from(Glyph::Table) + .cond_where( + Cond::all().add_option(Some(ConditionExpression::SimpleExpr( + Expr::tuple([Expr::col(Glyph::Aspect).into(), Expr::value(100)]) + .lt(Expr::tuple([Expr::value(8), Expr::value(100)])), + ))), + ) + .to_string(DatabendQueryBuilder); + + assert_eq!( + statement, + r#"SELECT `id` FROM `glyph` WHERE (`aspect`, 100) < (8, 100)"# + ); +} + +#[test] +fn select_48a() { + let statement = Query::select() + .column(Glyph::Id) + .from(Glyph::Table) + .cond_where( + Cond::all().add_option(Some(ConditionExpression::SimpleExpr( + Expr::tuple([ + Expr::col(Glyph::Aspect).into(), + Expr::value(String::from("100")), + ]) + .in_tuples([(8, String::from("100"))]), + ))), + ) + .to_string(DatabendQueryBuilder); + + assert_eq!( + statement, + r#"SELECT `id` FROM `glyph` WHERE (`aspect`, '100') IN ((8, '100'))"# + ); +} + +#[test] +fn select_49() { + let statement = Query::select() + .column(Asterisk) + .from(Char::Table) + .to_string(DatabendQueryBuilder); + + assert_eq!(statement, r#"SELECT * FROM `character`"#); +} + +#[test] +fn select_50() { + let statement = Query::select() + .column((Char::Table, Asterisk)) + .column((Font::Table, Font::Name)) + .from(Char::Table) + .inner_join( + Font::Table, + Expr::col((Char::Table, Char::FontId)).equals((Font::Table, Font::Id)), + ) + .to_string(DatabendQueryBuilder); + + assert_eq!( + statement, + r#"SELECT `character`.*, `font`.`name` FROM `character` INNER JOIN `font` ON `character`.`font_id` = `font`.`id`"# + ) +} + +#[test] +fn select_51() { + assert_eq!( + Query::select() + .columns([Glyph::Aspect]) + .from(Glyph::Table) + .and_where(Expr::expr(Expr::col(Glyph::Aspect).if_null(0)).gt(2)) + .order_by_with_nulls(Glyph::Image, Order::Desc, NullOrdering::First) + .order_by_with_nulls( + (Glyph::Table, Glyph::Aspect), + Order::Asc, + NullOrdering::Last + ) + .to_string(DatabendQueryBuilder), + [ + r#"SELECT `aspect`"#, + r#"FROM `glyph`"#, + r#"WHERE IFNULL(`aspect`, 0) > 2"#, + r#"ORDER BY `image` IS NULL DESC,"#, + r#"`image` DESC,"#, + r#"`glyph`.`aspect` IS NULL ASC,"#, + r#"`glyph`.`aspect` ASC"#, + ] + .join(" ") + ); +} + +#[test] +fn select_52() { + assert_eq!( + Query::select() + .columns([Glyph::Aspect]) + .from(Glyph::Table) + .and_where(Expr::expr(Expr::col(Glyph::Aspect).if_null(0)).gt(2)) + .order_by_columns_with_nulls([ + (Glyph::Id, Order::Asc, NullOrdering::First), + (Glyph::Aspect, Order::Desc, NullOrdering::Last), + ]) + .to_string(DatabendQueryBuilder), + [ + r#"SELECT `aspect`"#, + r#"FROM `glyph`"#, + r#"WHERE IFNULL(`aspect`, 0) > 2"#, + r#"ORDER BY `id` IS NULL DESC,"#, + r#"`id` ASC,"#, + r#"`aspect` IS NULL ASC,"#, + r#"`aspect` DESC"#, + ] + .join(" ") + ); +} + +#[test] +fn select_53() { + assert_eq!( + Query::select() + .columns([Glyph::Aspect]) + .from(Glyph::Table) + .and_where(Expr::expr(Expr::col(Glyph::Aspect).if_null(0)).gt(2)) + .order_by_columns_with_nulls([ + ((Glyph::Table, Glyph::Id), Order::Asc, NullOrdering::First), + ( + (Glyph::Table, Glyph::Aspect), + Order::Desc, + NullOrdering::Last + ), + ]) + .to_string(DatabendQueryBuilder), + [ + r#"SELECT `aspect`"#, + r#"FROM `glyph`"#, + r#"WHERE IFNULL(`aspect`, 0) > 2"#, + r#"ORDER BY `glyph`.`id` IS NULL DESC,"#, + r#"`glyph`.`id` ASC,"#, + r#"`glyph`.`aspect` IS NULL ASC,"#, + r#"`glyph`.`aspect` DESC"#, + ] + .join(" ") + ); +} + +#[test] +fn select_54() { + let statement = Query::select() + .column(Asterisk) + .from(Char::Table) + .from(Font::Table) + .and_where(Expr::col((Font::Table, Font::Id)).equals((Char::Table, Char::FontId))) + .to_string(DatabendQueryBuilder); + + assert_eq!( + statement, + r#"SELECT * FROM `character`, `font` WHERE `font`.`id` = `character`.`font_id`"# + ); +} + +#[test] +fn select_55() { + assert_eq!( + Query::select() + .columns([Glyph::Aspect]) + .from(Glyph::Table) + .and_where(Expr::expr(Expr::col(Glyph::Aspect).if_null(0)).gt(2)) + .order_by( + Glyph::Id, + Order::Field(Values(vec![4.into(), 5.into(), 1.into(), 3.into()])) + ) + .order_by((Glyph::Table, Glyph::Aspect), Order::Asc) + .to_string(DatabendQueryBuilder), + [ + r#"SELECT `aspect`"#, + r#"FROM `glyph`"#, + r#"WHERE IFNULL(`aspect`, 0) > 2"#, + r#"ORDER BY CASE"#, + r#"WHEN `id`=4 THEN 0"#, + r#"WHEN `id`=5 THEN 1"#, + r#"WHEN `id`=1 THEN 2"#, + r#"WHEN `id`=3 THEN 3"#, + r#"ELSE 4 END,"#, + r#"`glyph`.`aspect` ASC"#, + ] + .join(" ") + ); +} + +#[test] +fn select_56() { + assert_eq!( + Query::select() + .columns([Glyph::Aspect]) + .from(Glyph::Table) + .and_where(Expr::expr(Expr::col(Glyph::Aspect).if_null(0)).gt(2)) + .order_by((Glyph::Table, Glyph::Aspect), Order::Asc) + .order_by( + Glyph::Id, + Order::Field(Values(vec![4.into(), 5.into(), 1.into(), 3.into()])) + ) + .to_string(DatabendQueryBuilder), + [ + r#"SELECT `aspect`"#, + r#"FROM `glyph`"#, + r#"WHERE IFNULL(`aspect`, 0) > 2"#, + r#"ORDER BY `glyph`.`aspect` ASC,"#, + r#"CASE WHEN `id`=4 THEN 0"#, + r#"WHEN `id`=5 THEN 1"#, + r#"WHEN `id`=1 THEN 2"#, + r#"WHEN `id`=3 THEN 3"#, + r#"ELSE 4 END"#, + ] + .join(" ") + ); +} + +#[test] +fn select_57() { + let query = Query::select() + .expr_as( + CaseStatement::new() + .case(Expr::col((Glyph::Table, Glyph::Aspect)).gt(0), "positive") + .case(Expr::col((Glyph::Table, Glyph::Aspect)).lt(0), "negative") + .finally("zero"), + Alias::new("polarity"), + ) + .from(Glyph::Table) + .to_owned(); + + assert_eq!( + query.to_string(DatabendQueryBuilder), + r#"SELECT (CASE WHEN (`glyph`.`aspect` > 0) THEN 'positive' WHEN (`glyph`.`aspect` < 0) THEN 'negative' ELSE 'zero' END) AS `polarity` FROM `glyph`"# + ); +} + +#[test] +fn select_58() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where(Expr::col(Char::Character).like(LikeExpr::new("A").escape('\\'))) + .build(DatabendQueryBuilder), + ( + r"SELECT `character` FROM `character` WHERE `character` LIKE 'A' ESCAPE '\\'" + .to_owned(), + Values(vec![]) + ) + ); +} + +#[test] +#[allow(clippy::approx_constant)] +fn insert_2() { + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .columns([ + Glyph::Image, + Glyph::Aspect, + ]) + .values_panic([ + "04108048005887010020060000204E0180400400".into(), + 3.1415.into(), + ]) + .to_string(DatabendQueryBuilder), + "INSERT INTO `glyph` (`image`, `aspect`) VALUES ('04108048005887010020060000204E0180400400', 3.1415)" + ); +} + +#[test] +#[allow(clippy::approx_constant)] +fn insert_3() { + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .columns([ + Glyph::Image, + Glyph::Aspect, + ]) + .values_panic([ + "04108048005887010020060000204E0180400400".into(), + 3.1415.into(), + ]) + .values_panic([ + Value::String(None).into(), + 2.1345.into(), + ]) + .to_string(DatabendQueryBuilder), + "INSERT INTO `glyph` (`image`, `aspect`) VALUES ('04108048005887010020060000204E0180400400', 3.1415), (NULL, 2.1345)" + ); +} + +#[test] +#[cfg(feature = "with-chrono")] +fn insert_4() { + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .columns([Glyph::Image]) + .values_panic([chrono::NaiveDateTime::from_timestamp_opt(0, 0) + .unwrap() + .into()]) + .to_string(DatabendQueryBuilder), + "INSERT INTO `glyph` (`image`) VALUES ('1970-01-01 00:00:00')" + ); +} + +#[test] +#[cfg(feature = "with-time")] +fn insert_8() { + use time::macros::{date, time}; + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .columns([Glyph::Image]) + .values_panic([date!(1970 - 01 - 01).with_time(time!(00:00:00)).into()]) + .to_string(DatabendQueryBuilder), + "INSERT INTO `glyph` (`image`) VALUES ('1970-01-01 00:00:00.000000')" + ); +} + +#[test] +#[cfg(feature = "with-uuid")] +fn insert_5() { + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .columns([Glyph::Image]) + .values_panic([uuid::Uuid::nil().into()]) + .to_string(DatabendQueryBuilder), + "INSERT INTO `glyph` (`image`) VALUES ('00000000-0000-0000-0000-000000000000')" + ); +} + +#[test] +fn insert_6() { + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .or_default_values() + .to_string(DatabendQueryBuilder), + "INSERT INTO `glyph` VALUES ()" + ); +} + +#[test] +fn insert_7() { + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .or_default_values() + .returning_col(Glyph::Id) + .to_string(DatabendQueryBuilder), + "INSERT INTO `glyph` VALUES ()" + ); +} + +#[test] +fn insert_from_select() { + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .columns([Glyph::Aspect, Glyph::Image]) + .select_from( + Query::select() + .column(Glyph::Aspect) + .column(Glyph::Image) + .from(Glyph::Table) + .conditions( + true, + |x| { + x.and_where(Expr::col(Glyph::Image).like("%")); + }, + |x| { + x.and_where(Expr::col(Glyph::Id).eq(6i32)); + }, + ) + .to_owned() + ) + .unwrap() + .to_owned() + .to_string(DatabendQueryBuilder), + r#"INSERT INTO `glyph` (`aspect`, `image`) SELECT `aspect`, `image` FROM `glyph` WHERE `image` LIKE '%'"# + ); +} + +#[test] +#[allow(clippy::approx_constant)] +fn insert_on_conflict_0() { + let q = Query::insert() + .replace() + .into_table(Glyph::Table) + .columns([Glyph::Aspect, Glyph::Image]) + .values_panic([ + "04108048005887010020060000204E0180400400".into(), + 3.1415.into(), + ]) + .on_conflict( + OnConflict::columns([Glyph::Id]) + .update_columns([Glyph::Aspect, Glyph::Image]) + .to_owned(), + ) + .to_owned(); + let (sql, v) = q.build(DatabendQueryBuilder); + println!("{sql},{v:#?}"); + assert_eq!( + q.to_string(DatabendQueryBuilder), + [ + r#"REPLACE INTO `glyph` (`aspect`, `image`)"#, + r#"ON (`id`)"#, + r#"VALUES ('04108048005887010020060000204E0180400400', 3.1415)"#, + ] + .join(" ") + ); +} + +#[test] +#[allow(clippy::approx_constant)] +fn insert_on_conflict_1() { + assert_eq!( + Query::insert() + .replace() + .into_table(Glyph::Table) + .columns([Glyph::Aspect, Glyph::Image]) + .values_panic([ + "04108048005887010020060000204E0180400400".into(), + 3.1415.into(), + ]) + .on_conflict( + OnConflict::column(Glyph::Id) + .update_column(Glyph::Aspect) + .to_owned() + ) + .to_string(DatabendQueryBuilder), + [ + r#"REPLACE INTO `glyph` (`aspect`, `image`) ON (`id`)"#, + r#"VALUES ('04108048005887010020060000204E0180400400', 3.1415)"#, + ] + .join(" ") + ); +} + +#[test] +#[allow(clippy::approx_constant)] +fn insert_on_conflict_2() { + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .columns([Glyph::Aspect, Glyph::Image]) + .values_panic([ + "04108048005887010020060000204E0180400400".into(), + 3.1415.into(), + ]) + .on_conflict( + OnConflict::columns([Glyph::Id, Glyph::Aspect]) + .update_columns([Glyph::Aspect, Glyph::Image]) + .to_owned() + ) + .to_string(DatabendQueryBuilder), + [ + r#"INSERT INTO `glyph` (`aspect`, `image`)"#, + r#"VALUES ('04108048005887010020060000204E0180400400', 3.1415)"#, + ] + .join(" ") + ); +} + +#[test] +#[allow(clippy::approx_constant)] +fn insert_on_conflict_3() { + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .columns([Glyph::Aspect, Glyph::Image]) + .values_panic([ + "04108048005887010020060000204E0180400400".into(), + 3.1415.into(), + ]) + .on_conflict( + OnConflict::columns([Glyph::Id, Glyph::Aspect]) + .values([ + ( + Glyph::Aspect, + "04108048005887010020060000204E0180400400".into() + ), + (Glyph::Image, 3.1415.into()), + ]) + .to_owned() + ) + .to_string(DatabendQueryBuilder), + [ + r#"INSERT INTO `glyph` (`aspect`, `image`)"#, + r#"VALUES ('04108048005887010020060000204E0180400400', 3.1415)"#, + ] + .join(" ") + ); +} + +#[test] +#[allow(clippy::approx_constant)] +fn insert_on_conflict_4() { + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .columns([Glyph::Aspect, Glyph::Image]) + .values_panic([ + "04108048005887010020060000204E0180400400".into(), + 3.1415.into(), + ]) + .on_conflict( + OnConflict::columns([Glyph::Id, Glyph::Aspect]) + .value(Glyph::Image, Expr::val(1).add(2)) + .to_owned() + ) + .to_string(DatabendQueryBuilder), + [ + r#"INSERT INTO `glyph` (`aspect`, `image`)"#, + r#"VALUES ('04108048005887010020060000204E0180400400', 3.1415)"#, + ] + .join(" ") + ); +} + +#[test] +#[allow(clippy::approx_constant)] +fn insert_on_conflict_5() { + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .columns([Glyph::Aspect, Glyph::Image]) + .values_panic([ + "04108048005887010020060000204E0180400400".into(), + 3.1415.into(), + ]) + .on_conflict( + OnConflict::columns([Glyph::Id, Glyph::Aspect]) + .value( + Glyph::Aspect, + Expr::val("04108048005887010020060000204E0180400400") + ) + .update_column(Glyph::Image) + .to_owned() + ) + .to_string(DatabendQueryBuilder), + [ + r#"INSERT INTO `glyph` (`aspect`, `image`)"#, + r#"VALUES ('04108048005887010020060000204E0180400400', 3.1415)"#, + ] + .join(" ") + ); +} + +#[test] +#[allow(clippy::approx_constant)] +fn insert_on_conflict_6() { + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .columns([Glyph::Aspect, Glyph::Image]) + .values_panic([ + "04108048005887010020060000204E0180400400".into(), + 3.1415.into(), + ]) + .on_conflict( + OnConflict::columns([Glyph::Id, Glyph::Aspect]) + .update_column(Glyph::Aspect) + .value(Glyph::Image, Expr::val(1).add(2)) + .to_owned() + ) + .to_string(DatabendQueryBuilder), + [ + r#"INSERT INTO `glyph` (`aspect`, `image`)"#, + r#"VALUES ('04108048005887010020060000204E0180400400', 3.1415)"#, + ] + .join(" ") + ); +} + +#[test] +fn update_1() { + assert_eq!( + Query::update() + .table(Glyph::Table) + .values([ + (Glyph::Aspect, 2.1345.into()), + (Glyph::Image, "24B0E11951B03B07F8300FD003983F03F0780060".into()), + ]) + .and_where(Expr::col(Glyph::Id).eq(1)) + .order_by(Glyph::Id, Order::Asc) + .limit(1) + .to_string(DatabendQueryBuilder), + "UPDATE `glyph` SET `aspect` = 2.1345, `image` = '24B0E11951B03B07F8300FD003983F03F0780060' WHERE `id` = 1 ORDER BY `id` ASC LIMIT 1" + ); +} + +#[test] +fn update_3() { + assert_eq!( + Query::update() + .table(Glyph::Table) + .value(Glyph::Aspect, Expr::cust("60 * 24 * 24")) + .values([ + (Glyph::Image, "24B0E11951B03B07F8300FD003983F03F0780060".into()), + ]) + .and_where(Expr::col(Glyph::Id).eq(1)) + .order_by(Glyph::Id, Order::Asc) + .limit(1) + .to_string(DatabendQueryBuilder), + "UPDATE `glyph` SET `aspect` = 60 * 24 * 24, `image` = '24B0E11951B03B07F8300FD003983F03F0780060' WHERE `id` = 1 ORDER BY `id` ASC LIMIT 1" + ); +} + +#[test] +fn update_4() { + assert_eq!( + Query::update() + .table(Glyph::Table) + .value(Glyph::Aspect, Expr::col(Glyph::Aspect).add(1)) + .values([ + (Glyph::Image, "24B0E11951B03B07F8300FD003983F03F0780060".into()), + ]) + .and_where(Expr::col(Glyph::Id).eq(1)) + .order_by(Glyph::Id, Order::Asc) + .limit(1) + .to_string(DatabendQueryBuilder), + "UPDATE `glyph` SET `aspect` = `aspect` + 1, `image` = '24B0E11951B03B07F8300FD003983F03F0780060' WHERE `id` = 1 ORDER BY `id` ASC LIMIT 1" + ); +} + +#[test] +fn delete_1() { + assert_eq!( + Query::delete() + .from_table(Glyph::Table) + .and_where(Expr::col(Glyph::Id).eq(1)) + .order_by(Glyph::Id, Order::Asc) + .limit(1) + .to_string(DatabendQueryBuilder), + "DELETE FROM `glyph` WHERE `id` = 1 ORDER BY `id` ASC LIMIT 1" + ); +} + +#[test] +fn escape_1() { + let test = r#" "abc" "#; + assert_eq!( + DatabendQueryBuilder.escape_string(test), + r#" \"abc\" "#.to_owned() + ); + assert_eq!( + DatabendQueryBuilder.unescape_string(DatabendQueryBuilder.escape_string(test).as_str()), + test + ); +} + +#[test] +fn escape_2() { + let test = "a\nb\tc"; + assert_eq!( + DatabendQueryBuilder.escape_string(test), + "a\\nb\\tc".to_owned() + ); + assert_eq!( + DatabendQueryBuilder.unescape_string(DatabendQueryBuilder.escape_string(test).as_str()), + test + ); +} + +#[test] +fn escape_3() { + let test = "a\\b"; + assert_eq!( + DatabendQueryBuilder.escape_string(test), + "a\\\\b".to_owned() + ); + assert_eq!( + DatabendQueryBuilder.unescape_string(DatabendQueryBuilder.escape_string(test).as_str()), + test + ); +} + +#[test] +fn escape_4() { + let test = "a\"b"; + assert_eq!( + DatabendQueryBuilder.escape_string(test), + "a\\\"b".to_owned() + ); + assert_eq!( + DatabendQueryBuilder.unescape_string(DatabendQueryBuilder.escape_string(test).as_str()), + test + ); +} + +#[test] +fn union_1() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .union( + UnionType::Distinct, + Query::select() + .column(Char::Character) + .from(Char::Table) + .left_join( + Font::Table, + Expr::col((Char::Table, Char::FontId)).equals((Font::Table, Font::Id)) + ) + .order_by((Font::Table, Font::Id), Order::Asc) + .take() + ) + .to_string(DatabendQueryBuilder), + [ + "SELECT `character` FROM `character` UNION (SELECT `character` FROM `character`", + "LEFT JOIN `font` ON `character`.`font_id` = `font`.`id` ORDER BY `font`.`id` ASC)" + ] + .join(" ") + ); +} + +#[test] +fn sub_query_with_fn() { + #[derive(Iden)] + #[iden = "ARRAY"] + pub struct ArrayFunc; + + let sub_select = Query::select() + .column(Asterisk) + .from(Char::Table) + .to_owned(); + + let select = Query::select() + .expr(Func::cust(ArrayFunc).arg(SimpleExpr::SubQuery( + None, + Box::new(sub_select.into_sub_query_statement()), + ))) + .to_owned(); + + assert_eq!( + select.to_string(DatabendQueryBuilder), + "SELECT ARRAY((SELECT * FROM `character`))" + ); +} diff --git a/tests/databend/table.rs b/tests/databend/table.rs new file mode 100644 index 00000000..bc2ece78 --- /dev/null +++ b/tests/databend/table.rs @@ -0,0 +1,365 @@ +use super::*; +use pretty_assertions::assert_eq; + +#[test] +fn create_1() { + assert_eq!( + Table::create() + .table(Glyph::Table) + .col( + ColumnDef::new(Glyph::Id) + .integer() + .not_null() + .auto_increment() + .primary_key() + ) + .col(ColumnDef::new(Glyph::Aspect).double().not_null()) + .col(ColumnDef::new(Glyph::Image).text()) + .engine("InnoDB") + .character_set("utf8mb4") + .collate("utf8mb4_unicode_ci") + .to_string(DatabendQueryBuilder), + [ + "CREATE TABLE `glyph` (", + "`id` int NOT NULL,", + "`aspect` double NOT NULL,", + "`image` text", + ")", + ] + .join(" ") + ); +} + +#[test] +fn create_2() { + assert_eq!( + Table::create() + .table(Font::Table) + .col( + ColumnDef::new(Font::Id) + .integer() + .not_null() + .auto_increment() + .primary_key() + ) + .col(ColumnDef::new(Font::Name).string().not_null()) + .col(ColumnDef::new(Font::Variant).string_len(255).not_null()) + .col(ColumnDef::new(Font::Language).string_len(1024).not_null()) + .engine("InnoDB") + .character_set("utf8mb4") + .collate("utf8mb4_unicode_ci") + .to_string(DatabendQueryBuilder), + [ + "CREATE TABLE `font` (", + "`id` int NOT NULL,", + "`name` varchar NOT NULL,", + "`variant` varchar(255) NOT NULL,", + "`language` varchar(1024) NOT NULL", + ")", + ] + .join(" ") + ); +} + +#[test] +fn create_3() { + assert_eq!( + Table::create() + .table(Char::Table) + .if_not_exists() + .col( + ColumnDef::new(Char::Id) + .integer() + .not_null() + .auto_increment() + .primary_key() + ) + .col(ColumnDef::new(Char::FontSize).integer().not_null()) + .col(ColumnDef::new(Char::Character).string_len(255).not_null()) + .col(ColumnDef::new(Char::SizeW).unsigned().not_null()) + .col(ColumnDef::new(Char::SizeH).unsigned().not_null()) + .col( + ColumnDef::new(Char::FontId) + .integer() + .default(Value::Int(None)) + ) + .col( + ColumnDef::new(Char::CreatedAt) + .timestamp() + .default(Expr::current_timestamp()) + .not_null() + ) + .foreign_key( + ForeignKey::create() + .name("FK_2e303c3a712662f1fc2a4d0aad6") + .from(Char::Table, Char::FontId) + .to(Font::Table, Font::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Restrict) + ) + .engine("InnoDB") + .character_set("utf8mb4") + .collate("utf8mb4_unicode_ci") + .to_string(DatabendQueryBuilder), + [ + "CREATE TABLE IF NOT EXISTS `character` (", + "`id` int NOT NULL,", + "`font_size` int NOT NULL,", + "`character` varchar(255) NOT NULL,", + "`size_w` int UNSIGNED NOT NULL,", + "`size_h` int UNSIGNED NOT NULL,", + "`font_id` int DEFAULT NULL,", + "`created_at` timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL )", + ] + .join(" ") + ); +} + +#[test] +fn create_4() { + assert_eq!( + Table::create() + .table(Glyph::Table) + .col( + ColumnDef::new(Glyph::Id) + .integer() + .not_null() + .extra("ANYTHING I WANT TO SAY".to_owned()) + ) + .to_string(DatabendQueryBuilder), + [ + "CREATE TABLE `glyph` (", + "`id` int NOT NULL ANYTHING I WANT TO SAY", + ")", + ] + .join(" ") + ); +} + +#[test] +fn create_5() { + assert_eq!( + Table::create() + .table(Glyph::Table) + .col(ColumnDef::new(Glyph::Id).integer().not_null()) + .index(Index::create().unique().name("idx-glyph-id").col(Glyph::Id)) + .to_string(DatabendQueryBuilder), + ["CREATE TABLE `glyph` (", "`id` int NOT NULL", ")",].join(" ") + ); +} + +#[test] +fn create_6() { + assert_eq!( + Table::create() + .table(BinaryType::Table) + .col(ColumnDef::new(BinaryType::BinaryLen).binary_len(32)) + .col(ColumnDef::new(BinaryType::Binary).binary()) + .col(ColumnDef::new(BinaryType::Blob).blob()) + .col(ColumnDef::new(BinaryType::TinyBlob).custom(MySqlType::TinyBlob)) + .col(ColumnDef::new(BinaryType::MediumBlob).custom(MySqlType::MediumBlob)) + .col(ColumnDef::new(BinaryType::LongBlob).custom(MySqlType::LongBlob)) + .to_string(DatabendQueryBuilder), + [ + "CREATE TABLE `binary_type` (", + "`binlen` binary(32),", + "`bin` binary(1),", + "`b` blob,", + "`tb` tinyblob,", + "`mb` mediumblob,", + "`lb` longblob", + ")", + ] + .join(" ") + ); +} + +#[test] +fn create_7() { + assert_eq!( + Table::create() + .table(Char::Table) + .col(ColumnDef::new(BinaryType::Blob).custom(MySqlType::Blob)) + .col(ColumnDef::new(Char::Character).binary()) + .col(ColumnDef::new(Char::FontSize).binary_len(10)) + .col(ColumnDef::new(Char::SizeW).var_binary(10)) + .to_string(DatabendQueryBuilder), + [ + "CREATE TABLE `character` (", + "`b` blob,", + "`character` binary(1),", + "`font_size` binary(10),", + "`size_w` varbinary(10)", + ")", + ] + .join(" ") + ); +} + +#[test] +fn create_8() { + assert_eq!( + Table::create() + .table(Font::Table) + .col(ColumnDef::new(Font::Variant).year()) + .to_string(DatabendQueryBuilder), + "CREATE TABLE `font` ( `variant` year )" + ); +} + +#[test] +fn create_9() { + assert_eq!( + Table::create() + .table(Glyph::Table) + .index(Index::create().name("idx-glyph-id").col(Glyph::Id)) + .to_string(DatabendQueryBuilder), + "CREATE TABLE `glyph` ( )" + ); +} + +#[test] +fn create_10() { + assert_eq!( + Table::create() + .table(Glyph::Table) + .col(ColumnDef::new(Glyph::Id).enumeration( + Alias::new("tea"), + [Alias::new("EverydayTea"), Alias::new("BreakfastTea")] + ),) + .to_string(DatabendQueryBuilder), + "CREATE TABLE `glyph` ( `id` ENUM('EverydayTea', 'BreakfastTea') )" + ); +} + +#[test] +fn drop_1() { + assert_eq!( + Table::drop() + .table(Glyph::Table) + .table(Char::Table) + .cascade() + .to_string(DatabendQueryBuilder), + "DROP TABLE `glyph`, `character` ALL" + ); +} + +#[test] +fn truncate_1() { + assert_eq!( + Table::truncate() + .table(Font::Table) + .to_string(DatabendQueryBuilder), + "TRUNCATE TABLE `font`" + ); +} + +#[test] +fn alter_1() { + assert_eq!( + Table::alter() + .table(Font::Table) + .add_column( + ColumnDef::new(Alias::new("new_col")) + .integer() + .not_null() + .default(100) + ) + .to_string(DatabendQueryBuilder), + "ALTER TABLE `font` ADD COLUMN `new_col` int NOT NULL DEFAULT 100" + ); +} + +#[test] +fn alter_2() { + assert_eq!( + Table::alter() + .table(Font::Table) + .modify_column( + ColumnDef::new(Alias::new("new_col")) + .big_integer() + .default(999) + ) + .to_string(DatabendQueryBuilder), + "ALTER TABLE `font` MODIFY COLUMN `new_col` bigint DEFAULT 999" + ); +} + +#[test] +fn alter_3() { + assert_eq!( + Table::alter() + .table(Font::Table) + .rename_column(Alias::new("new_col"), Alias::new("new_column")) + .to_string(DatabendQueryBuilder), + "ALTER TABLE `font` RENAME COLUMN `new_col` TO `new_column`" + ); +} + +#[test] +fn alter_4() { + assert_eq!( + Table::alter() + .table(Font::Table) + .drop_column(Alias::new("new_column")) + .to_string(DatabendQueryBuilder), + "ALTER TABLE `font` DROP COLUMN `new_column`" + ); +} + +#[test] +fn alter_5() { + assert_eq!( + Table::rename() + .table(Font::Table, Alias::new("font_new")) + .to_string(DatabendQueryBuilder), + "RENAME TABLE `font` TO `font_new`" + ); +} + +#[test] +fn alter_7() { + assert_eq!( + Table::alter() + .table(Font::Table) + .drop_column(Alias::new("new_column")) + .rename_column(Font::Name, Alias::new("name_new")) + .to_string(DatabendQueryBuilder), + "ALTER TABLE `font` DROP COLUMN `new_column`, RENAME COLUMN `name` TO `name_new`" + ); +} + +#[test] +fn create_with_check_constraint() { + assert_eq!( + Table::create() + .table(Glyph::Table) + .col( + ColumnDef::new(Glyph::Id) + .integer() + .not_null() + .check(Expr::col(Glyph::Id).gt(10)) + ) + .check(Expr::col(Glyph::Id).lt(20)) + .check(Expr::col(Glyph::Id).ne(15)) + .to_string(DatabendQueryBuilder), + r#"CREATE TABLE `glyph` ( `id` int NOT NULL )"#, + ); +} + +#[test] +fn alter_with_check_constraint() { + assert_eq!( + Table::alter() + .table(Glyph::Table) + .add_column( + ColumnDef::new(Glyph::Aspect) + .integer() + .not_null() + .default(101) + .check(Expr::col(Glyph::Aspect).gt(100)) + ) + .to_string(DatabendQueryBuilder), + r#"ALTER TABLE `glyph` ADD COLUMN `aspect` int NOT NULL DEFAULT 101"#, + ); +}