From d82c6d08a686f2fcc35c324259bf0dada8775372 Mon Sep 17 00:00:00 2001 From: andres Date: Tue, 9 May 2023 14:54:36 -0500 Subject: [PATCH] feat: implement backend compatible with Google Bigquery --- Cargo.toml | 8 +- src/backend/bigquery/foreign_key.rs | 25 + src/backend/bigquery/index.rs | 25 + src/backend/bigquery/mod.rs | 26 + src/backend/bigquery/query.rs | 73 ++ src/backend/bigquery/table.rs | 199 ++++ src/backend/mod.rs | 6 + src/table/column.rs | 6 + tests/bigquery/foreign_key.rs | 22 + tests/bigquery/index.rs | 19 + tests/bigquery/mod.rs | 10 + tests/bigquery/query.rs | 1665 +++++++++++++++++++++++++++ tests/bigquery/table.rs | 476 ++++++++ 13 files changed, 2559 insertions(+), 1 deletion(-) create mode 100644 src/backend/bigquery/foreign_key.rs create mode 100644 src/backend/bigquery/index.rs create mode 100644 src/backend/bigquery/mod.rs create mode 100644 src/backend/bigquery/query.rs create mode 100644 src/backend/bigquery/table.rs create mode 100644 tests/bigquery/foreign_key.rs create mode 100644 tests/bigquery/index.rs create mode 100644 tests/bigquery/mod.rs create mode 100644 tests/bigquery/query.rs create mode 100644 tests/bigquery/table.rs diff --git a/Cargo.toml b/Cargo.toml index 5666a2dbe..4c4489574 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,8 @@ pretty_assertions = { version = "1" } backend-mysql = [] backend-postgres = [] backend-sqlite = [] -default = ["derive", "backend-mysql", "backend-postgres", "backend-sqlite"] +backend-bigquery = [] +default = ["derive", "backend-mysql", "backend-postgres", "backend-sqlite", "backend-bigquery"] derive = ["sea-query-derive"] attr = ["sea-query-attr"] hashable-value = ["derivative", "ordered-float"] @@ -87,6 +88,11 @@ name = "test-mysql" path = "tests/mysql/mod.rs" required-features = ["tests-cfg", "backend-mysql"] +[[test]] +name = "test-bigquery" +path = "tests/bigquery/mod.rs" +required-features = ["tests-cfg", "backend-bigquery"] + [[test]] name = "test-postgres" path = "tests/postgres/mod.rs" diff --git a/src/backend/bigquery/foreign_key.rs b/src/backend/bigquery/foreign_key.rs new file mode 100644 index 000000000..5fb94ba58 --- /dev/null +++ b/src/backend/bigquery/foreign_key.rs @@ -0,0 +1,25 @@ +use super::*; + +impl ForeignKeyBuilder for BigQueryQueryBuilder { + fn prepare_table_ref_fk_stmt(&self, _table_ref: &TableRef, _sql: &mut dyn SqlWriter) { + panic!("Not supported") + } + + fn prepare_foreign_key_drop_statement_internal( + &self, + _drop: &ForeignKeyDropStatement, + _sql: &mut dyn SqlWriter, + _mode: Mode, + ) { + panic!("Not supported") + } + + fn prepare_foreign_key_create_statement_internal( + &self, + _create: &ForeignKeyCreateStatement, + _sql: &mut dyn SqlWriter, + _mode: Mode, + ) { + panic!("Not supported") + } +} diff --git a/src/backend/bigquery/index.rs b/src/backend/bigquery/index.rs new file mode 100644 index 000000000..d87368269 --- /dev/null +++ b/src/backend/bigquery/index.rs @@ -0,0 +1,25 @@ +use super::*; + +impl IndexBuilder for BigQueryQueryBuilder { + fn prepare_index_create_statement( + &self, + _create: &IndexCreateStatement, + _sql: &mut dyn SqlWriter, + ) { + panic!("Not supported"); + } + + fn prepare_table_ref_index_stmt(&self, _table_ref: &TableRef, _sql: &mut dyn SqlWriter) { + panic!("Not supported"); + } + + fn prepare_index_drop_statement(&self, _drop: &IndexDropStatement, _sql: &mut dyn SqlWriter) { + panic!("Not supported"); + } + + fn prepare_index_prefix(&self, _create: &IndexCreateStatement, _sql: &mut dyn SqlWriter) { + panic!("Not supported"); + } + + fn write_column_index_prefix(&self, _col_prefix: &Option, _sql: &mut dyn SqlWriter) {} +} diff --git a/src/backend/bigquery/mod.rs b/src/backend/bigquery/mod.rs new file mode 100644 index 000000000..399eda67e --- /dev/null +++ b/src/backend/bigquery/mod.rs @@ -0,0 +1,26 @@ +pub(crate) mod foreign_key; +pub(crate) mod index; +pub(crate) mod query; +pub(crate) mod table; + +use super::*; + +/// BigQuery query builder. +#[derive(Default, Debug)] +pub struct BigQueryQueryBuilder; + +const QUOTE: Quote = Quote(b'`', b'`'); + +impl GenericBuilder for BigQueryQueryBuilder {} + +impl SchemaBuilder for BigQueryQueryBuilder {} + +impl QuotedBuilder for BigQueryQueryBuilder { + fn quote(&self) -> Quote { + QUOTE + } +} + +impl EscapeBuilder for BigQueryQueryBuilder {} + +impl TableRefBuilder for BigQueryQueryBuilder {} diff --git a/src/backend/bigquery/query.rs b/src/backend/bigquery/query.rs new file mode 100644 index 000000000..6fb9c3d9e --- /dev/null +++ b/src/backend/bigquery/query.rs @@ -0,0 +1,73 @@ +use super::*; + +impl QueryBuilder for BigQueryQueryBuilder { + fn placeholder(&self) -> (&str, bool) { + ("$", true) + } + + fn prepare_select_lock(&self, _select_lock: &LockClause, _sql: &mut dyn SqlWriter) { + // SQLite doesn't supports row locking + } + + fn if_null_function(&self) -> &str { + "COALESCE" + } + + fn prepare_sub_query_oper(&self, oper: &SubQueryOper, sql: &mut dyn SqlWriter) { + write!( + sql, + "{}", + match oper { + SubQueryOper::Exists => "EXISTS", + SubQueryOper::Any => panic!("Operator 'ANY' doesnot support"), + SubQueryOper::Some => panic!("Operator 'SOME' doesnot support"), + SubQueryOper::All => panic!("Operator 'ALL' doesnot support"), + // Should add custom operator options. In the case of BigQuery, ARRAY, Scalar subquery + } + ) + .unwrap(); + } + + fn prepare_union_statement( + &self, + union_type: UnionType, + select_statement: &SelectStatement, + sql: &mut dyn SqlWriter, + ) { + match union_type { + UnionType::Intersect => write!(sql, " INTERSECT ").unwrap(), + UnionType::Distinct => write!(sql, " UNION DISTINCT ").unwrap(), + UnionType::Except => write!(sql, " EXCEPT ").unwrap(), + UnionType::All => write!(sql, " UNION ALL ").unwrap(), + } + self.prepare_select_statement(select_statement, sql); + } + + fn prepare_query_statement(&self, query: &SubQueryStatement, sql: &mut dyn SqlWriter) { + query.prepare_statement(self, sql); + } + + fn prepare_order_expr(&self, order_expr: &OrderExpr, sql: &mut dyn SqlWriter) { + if !matches!(order_expr.order, Order::Field(_)) { + self.prepare_simple_expr(&order_expr.expr, sql); + } + self.prepare_order(order_expr, sql); + match order_expr.nulls { + None => (), + Some(NullOrdering::Last) => write!(sql, " NULLS LAST").unwrap(), + Some(NullOrdering::First) => write!(sql, " NULLS FIRST").unwrap(), + } + } + + fn prepare_value(&self, value: &Value, sql: &mut dyn SqlWriter) { + sql.push_param(value.clone(), self as _); + } + + fn char_length_function(&self) -> &str { + "CHAR_LENGTH" + } + + fn insert_default_values(&self, _: u32, _sql: &mut dyn SqlWriter) { + panic!("BigQuery does not support default values"); + } +} diff --git a/src/backend/bigquery/table.rs b/src/backend/bigquery/table.rs new file mode 100644 index 000000000..93e4b1218 --- /dev/null +++ b/src/backend/bigquery/table.rs @@ -0,0 +1,199 @@ +use super::*; + +impl TableBuilder for BigQueryQueryBuilder { + 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() { + if let ColumnSpec::PrimaryKey = column_spec { + continue; + } + if let ColumnSpec::AutoIncrement = column_spec { + continue; + } + if let ColumnSpec::Comment(_) = column_spec { + continue; + } + write!(sql, " ").unwrap(); + self.prepare_column_spec(column_spec, 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!("STRING({length})"), + None => "STRING".into(), + }, + ColumnType::String(length) => match length { + Some(length) => format!("STRING({length})"), + None => "STRING".into(), + }, + ColumnType::Text => "STRING".into(), + ColumnType::TinyInteger | ColumnType::TinyUnsigned => "INT64".into(), + ColumnType::SmallInteger | ColumnType::SmallUnsigned => "INT64".into(), + ColumnType::Integer | ColumnType::Unsigned => "INT64".into(), + ColumnType::BigInteger | ColumnType::BigUnsigned => "INT64".into(), + ColumnType::Float => "FLOAT64".into(), + ColumnType::Double => "FLOAT64".into(), + ColumnType::Decimal(precision) | ColumnType::Money(precision) => match precision { + Some((precision, scale)) => match scale { + 0..=9 if precision.max(&1) <= precision && precision <= &(scale + 29) => + format!("NUMERIC({precision}, {scale})"), + 10..=38 if precision.max(&1) <= precision && precision <= &(scale + 38) => + format!("BIGNUMERIC({precision}, {scale})"), + _ => panic!("Invalid precision and scale for NUMERIC type"), + }, + None => "BIGNUMERIC".into(), + }, + ColumnType::DateTime => "DATETIME".into(), + ColumnType::Timestamp => "TIMESTAMP".into(), + ColumnType::TimestampWithTimeZone => "TIMESTAMP".into(), + ColumnType::Time => "TIME".into(), + ColumnType::Date => "DATE".into(), + ColumnType::Interval(_, _) => "INTERVAL".into(), + ColumnType::Binary(blob_size) => match blob_size { + BlobSize::Blob(Some(length)) => format!("BYTES({length})"), + _ => "BYTES".into(), + }, + ColumnType::VarBinary(length) => format!("BYTES({length})"), + ColumnType::Boolean => "BOOL".into(), + ColumnType::Json => "JSON".into(), + ColumnType::JsonBinary => "JSON".into(), + ColumnType::Uuid => "STRING(36)".into(), + ColumnType::Custom(iden) => iden.to_string(), + ColumnType::Enum { .. } => "STRING".into(), + ColumnType::Array(col_type) => { + let mut sql = String::new(); + self.prepare_column_type(col_type, &mut sql); + format!("ARRAY<{sql}>") + } + ColumnType::Cidr => unimplemented!("Cidr is not available in BigQuery."), + ColumnType::Inet => unimplemented!("Inet is not available in BigQuery."), + ColumnType::MacAddr => unimplemented!("MacAddr is not available in BigQuery."), + ColumnType::Year(_) => unimplemented!("Year is not available in BigQuery."), + ColumnType::Bit(_) => unimplemented!("Bit is not available in BigQuery."), + ColumnType::VarBit(_) => unimplemented!("VarBit is not available in BigQuery."), + } + ) + .unwrap() + } + + fn column_spec_auto_increment_keyword(&self) -> &str { + panic!("BigQuery does not support auto increment"); + } + + fn prepare_table_drop_opt(&self, _drop_opt: &TableDropOpt, _sql: &mut dyn SqlWriter) { + panic!("BigQuery does not support table drop option"); + } + + 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(); + self.prepare_column_def(column, sql); + } + TableAlterOption::ModifyColumn(column_def) => { + if let Some(types) = &column_def.types { + write!(sql, "ALTER COLUMN ").unwrap(); + column_def.name.prepare(sql.as_writer(), self.quote()); + write!(sql, " SET DATA TYPE ").unwrap(); + self.prepare_column_type(types, sql); + } + let first = column_def.types.is_none(); + + column_def.spec.iter().fold(first, |first, column_spec| { + if !first { + write!(sql, ", ").unwrap(); + } + match column_spec { + ColumnSpec::AutoIncrement => {} + ColumnSpec::Null => { + write!(sql, "ALTER COLUMN ").unwrap(); + column_def.name.prepare(sql.as_writer(), self.quote()); + write!(sql, " DROP NOT NULL").unwrap(); + } + ColumnSpec::NotNull => { + panic!("BigQuery doesn't support changing to REQUIRED") + } + ColumnSpec::Default(v) => { + write!(sql, "ALTER COLUMN ").unwrap(); + column_def.name.prepare(sql.as_writer(), self.quote()); + write!(sql, " SET DEFAULT ").unwrap(); + QueryBuilder::prepare_simple_expr(self, v, sql); + } + ColumnSpec::UniqueKey => { + panic!("BigQuery doesn't support adding unique constraint") + } + ColumnSpec::PrimaryKey => { + panic!("BigQuery doesn't support adding primary key constraint") + } + ColumnSpec::Check(_check) => { + panic!("BigQuery doesn't support adding check constraint") + } + ColumnSpec::Generated { .. } => {} + ColumnSpec::Extra(string) => write!(sql, "{string}").unwrap(), + ColumnSpec::Comment(_) => {} + } + false + }); + } + 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(col_name) => { + write!(sql, "DROP COLUMN ").unwrap(); + col_name.prepare(sql.as_writer(), self.quote()); + } + TableAlterOption::DropForeignKey(_) => { + panic!("BigQuery does not support modification of foreign key constraints to existing tables"); + } + TableAlterOption::AddForeignKey(_) => { + panic!("BigQuery does not support modification of foreign key constraints to existing tables"); + } + } + false + }); + } + + fn prepare_table_rename_statement( + &self, + rename: &TableRenameStatement, + sql: &mut dyn SqlWriter, + ) { + write!(sql, "ALTER TABLE ").unwrap(); + if let Some(from_name) = &rename.from_name { + self.prepare_table_ref_table_stmt(from_name, sql); + } + write!(sql, " RENAME TO ").unwrap(); + if let Some(to_name) = &rename.to_name { + self.prepare_table_ref_table_stmt(to_name, sql); + } + } +} diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 03c3ec307..6be62078a 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -12,6 +12,12 @@ mod postgres; #[cfg_attr(docsrs, doc(cfg(feature = "backend-sqlite")))] mod sqlite; +#[cfg(feature = "backend-bigquery")] +#[cfg_attr(docsrs, doc(cfg(feature = "backend-bigquery")))] +mod bigquery; + +#[cfg(feature = "backend-bigquery")] +pub use bigquery::*; #[cfg(feature = "backend-mysql")] pub use mysql::*; #[cfg(feature = "backend-postgres")] diff --git a/src/table/column.rs b/src/table/column.rs index 4e56121b9..94c6efec9 100644 --- a/src/table/column.rs +++ b/src/table/column.rs @@ -426,6 +426,12 @@ impl ColumnDef { self } + #[cfg(feature = "backend-bigquery")] + pub fn interval_bq(&mut self) -> &mut Self { + self.types = Some(ColumnType::Interval(None, None)); + self + } + /// Set column type as timestamp pub fn timestamp(&mut self) -> &mut Self { self.types = Some(ColumnType::Timestamp); diff --git a/tests/bigquery/foreign_key.rs b/tests/bigquery/foreign_key.rs new file mode 100644 index 000000000..153744c81 --- /dev/null +++ b/tests/bigquery/foreign_key.rs @@ -0,0 +1,22 @@ +use super::*; + +#[test] +#[should_panic(expected = "Not supported")] +fn create_1() { + ForeignKey::create() + .name("FK_2e303c3a712662f1fc2a4d0aad6") + .from(Char::Table, Char::FontId) + .to(Font::Table, Font::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + .to_string(BigQueryQueryBuilder); +} + +#[test] +#[should_panic(expected = "Not supported")] +fn drop_1() { + ForeignKey::drop() + .name("FK_2e303c3a712662f1fc2a4d0aad6") + .table(Char::Table) + .to_string(BigQueryQueryBuilder); +} diff --git a/tests/bigquery/index.rs b/tests/bigquery/index.rs new file mode 100644 index 000000000..2c00b287e --- /dev/null +++ b/tests/bigquery/index.rs @@ -0,0 +1,19 @@ +use super::*; + +#[test] +#[should_panic(expected = "Not supported")] +fn create_1() { + Index::create() + .name("idx-glyph-aspect") + .table(Glyph::Table) + .col(Glyph::Aspect) + .to_string(BigQueryQueryBuilder); +} + +#[test] +#[should_panic(expected = "Not supported")] +fn drop_1() { + Index::drop() + .name("idx-glyph-aspect") + .to_string(BigQueryQueryBuilder); +} diff --git a/tests/bigquery/mod.rs b/tests/bigquery/mod.rs new file mode 100644 index 000000000..fc7388cd0 --- /dev/null +++ b/tests/bigquery/mod.rs @@ -0,0 +1,10 @@ +use sea_query::{tests_cfg::*, *}; + +mod foreign_key; +mod index; +mod query; +mod table; + +#[path = "../common.rs"] +mod common; +use common::*; diff --git a/tests/bigquery/query.rs b/tests/bigquery/query.rs new file mode 100644 index 000000000..e14eb98a1 --- /dev/null +++ b/tests/bigquery/query.rs @@ -0,0 +1,1665 @@ +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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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::Aspect]) + .from_subquery( + Query::select() + .columns([Glyph::Image, Glyph::Aspect]) + .from(Glyph::Table) + .take(), + Alias::new("subglyph") + ) + .to_string(BigQueryQueryBuilder), + r#"SELECT `aspect` 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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"SELECT `aspect` FROM `glyph` WHERE COALESCE(`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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"SELECT `aspect` FROM `glyph` WHERE COALESCE(`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(BigQueryQueryBuilder), + r#"SELECT `aspect` FROM `glyph` WHERE COALESCE(`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(BigQueryQueryBuilder), + r#"SELECT `aspect` FROM `glyph` WHERE COALESCE(`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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + [ + r#"SELECT `aspect`, MAX(`image`) FROM `glyph` GROUP BY `aspect`"#, + r#"HAVING ((`aspect` > 2) OR (`aspect` < 8))"#, + r#"OR ((`aspect` > 12) AND (`aspect` < 18))"#, + r#"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(BigQueryQueryBuilder); + + 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(BigQueryQueryBuilder); + + 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(BigQueryQueryBuilder); + + 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(BigQueryQueryBuilder); + + 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(BigQueryQueryBuilder); + + 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(BigQueryQueryBuilder); + + 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(BigQueryQueryBuilder); + + 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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder); + + 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(BigQueryQueryBuilder); + + assert_eq!(statement, r#"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(BigQueryQueryBuilder); + + 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(BigQueryQueryBuilder); + + 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(BigQueryQueryBuilder); + + 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(BigQueryQueryBuilder); + + 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(BigQueryQueryBuilder); + + 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(BigQueryQueryBuilder); + + 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(BigQueryQueryBuilder); + + 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(BigQueryQueryBuilder); + + 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(BigQueryQueryBuilder), + [ + r#"SELECT `aspect`"#, + r#"FROM `glyph`"#, + r#"WHERE COALESCE(`aspect`, 0) > 2"#, + r#"ORDER BY `image` DESC NULLS FIRST,"#, + r#"`glyph`.`aspect` ASC NULLS LAST"#, + ] + .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(BigQueryQueryBuilder), + [ + r#"SELECT `aspect`"#, + r#"FROM `glyph`"#, + r#"WHERE COALESCE(`aspect`, 0) > 2"#, + r#"ORDER BY `id` ASC NULLS FIRST,"#, + r#"`aspect` DESC NULLS LAST"#, + ] + .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(BigQueryQueryBuilder), + [ + r#"SELECT `aspect`"#, + r#"FROM `glyph`"#, + r#"WHERE COALESCE(`aspect`, 0) > 2"#, + r#"ORDER BY `glyph`.`id` ASC NULLS FIRST,"#, + r#"`glyph`.`aspect` DESC NULLS LAST"#, + ] + .join(" ") + ); +} + +#[test] +fn select_54() { + assert_eq!( + Query::select() + .distinct() + .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(BigQueryQueryBuilder), + [ + r#"SELECT DISTINCT `aspect`"#, + r#"FROM `glyph`"#, + r#"WHERE COALESCE(`aspect`, 0) > 2"#, + r#"ORDER BY `glyph`.`id` ASC NULLS FIRST,"#, + r#"`glyph`.`aspect` DESC NULLS LAST"#, + ] + .join(" ") + ); +} + +#[test] +fn select_55() { + 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(BigQueryQueryBuilder); + + assert_eq!( + statement, + r#"SELECT * FROM `character`, `font` WHERE `font`.`id` = `character`.`font_id`"# + ); +} + +#[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::Id, + Order::Field(Values(vec![ + Value::Int(Some(4)), + Value::Int(Some(5)), + Value::Int(Some(1)), + Value::Int(Some(3)) + ])) + ) + .order_by((Glyph::Table, Glyph::Aspect), Order::Asc) + .to_string(BigQueryQueryBuilder), + [ + r#"SELECT `aspect`"#, + r#"FROM `glyph`"#, + r#"WHERE COALESCE(`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_57() { + 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![ + Value::Int(Some(4)), + Value::Int(Some(5)), + Value::Int(Some(1)), + Value::Int(Some(3)) + ])) + ) + .to_string(BigQueryQueryBuilder), + [ + r#"SELECT `aspect`"#, + r#"FROM `glyph`"#, + r#"WHERE COALESCE(`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_58() { + let select = SelectStatement::new() + .columns([Glyph::Id, Glyph::Image, Glyph::Aspect]) + .from(Glyph::Table) + .to_owned(); + let cte = CommonTableExpression::new() + .query(select) + .table_name(Alias::new("cte")) + .to_owned(); + let with_clause = WithClause::new().cte(cte).to_owned(); + let select = SelectStatement::new() + .columns([Glyph::Id, Glyph::Image, Glyph::Aspect]) + .from(Alias::new("cte")) + .to_owned(); + assert_eq!( + select.with(with_clause).to_string(BigQueryQueryBuilder), + [ + r#"WITH `cte` AS"#, + r#"(SELECT `id`, `image`, `aspect`"#, + r#"FROM `glyph`)"#, + r#"SELECT `id`, `image`, `aspect` FROM `cte`"#, + ] + .join(" ") + ); +} + +#[test] +fn select_59() { + 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(BigQueryQueryBuilder), + 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_60() { + let (cust_query, cust_values) = Query::select() + .column(Character::Id) + .from(Character::Table) + .and_where(Expr::col(Character::FontSize).eq(3)) + .build(BigQueryQueryBuilder); + + let (statement, values) = Query::select() + .expr(Expr::cust_with_values(&cust_query[7..], cust_values.0)) + .limit(5) + .build(BigQueryQueryBuilder); + + assert_eq!( + statement, + r#"SELECT `id` FROM `character` WHERE `font_size` = $1 LIMIT $2"# + ); + assert_eq!(values, Values(vec![3i32.into(), 5u64.into()])); +} + +#[test] +fn select_61() { + assert_eq!( + Query::select() + .column(Char::Character) + .from(Char::Table) + .and_where(Expr::col(Char::Character).like(LikeExpr::new("A").escape('\\'))) + .build(BigQueryQueryBuilder), + ( + r#"SELECT `character` FROM `character` WHERE `character` LIKE $1 ESCAPE '\\'"# + .to_owned(), + Values(vec!["A".into()]) + ) + ); +} + +#[test] +fn select_62() { + let select = SelectStatement::new() + .column(Asterisk) + .from_values([(1i32, "hello"), (2, "world")], Alias::new("x")) + .to_owned(); + let cte = CommonTableExpression::new() + .query(select) + .table_name(Alias::new("cte")) + .to_owned(); + let with_clause = WithClause::new().cte(cte).to_owned(); + let select = SelectStatement::new() + .columns([Alias::new("column1"), Alias::new("column2")]) + .from(Alias::new("cte")) + .to_owned(); + assert_eq!( + select.with(with_clause).to_string(BigQueryQueryBuilder), + [ + r#"WITH `cte` AS"#, + r#"(SELECT * FROM (VALUES (1, 'hello'), (2, 'world')) AS `x`)"#, + r#"SELECT `column1`, `column2`"#, + r#"FROM `cte`"#, + ] + .join(" ") + ); +} + +#[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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + "INSERT INTO `glyph` (`image`) VALUES ('1970-01-01 00:00:00')" + ); +} + +#[test] +#[cfg(feature = "with-time")] +fn insert_9() { + 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(BigQueryQueryBuilder), + "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(BigQueryQueryBuilder), + "INSERT INTO `glyph` (`image`) VALUES ('00000000-0000-0000-0000-000000000000')" + ); +} + +#[test] +fn insert_from_select() { + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .or_default_values() + .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(6)); + }, + ) + .to_owned() + ) + .unwrap() + .to_owned() + .to_string(BigQueryQueryBuilder), + r#"INSERT INTO `glyph` (`aspect`, `image`) SELECT `aspect`, `image` FROM `glyph` WHERE `image` LIKE '%'"# + ); +} + +#[test] +fn insert_6() -> error::Result<()> { + let select = SelectStatement::new() + .columns([Glyph::Id, Glyph::Image, Glyph::Aspect]) + .from(Glyph::Table) + .to_owned(); + let cte = CommonTableExpression::new() + .query(select) + .column(Glyph::Id) + .column(Glyph::Image) + .column(Glyph::Aspect) + .table_name(Alias::new("cte")) + .to_owned(); + let with_clause = WithClause::new().cte(cte).to_owned(); + let select = SelectStatement::new() + .columns([Glyph::Id, Glyph::Image, Glyph::Aspect]) + .from(Alias::new("cte")) + .to_owned(); + let mut insert = Query::insert(); + insert + .into_table(Glyph::Table) + .columns([Glyph::Id, Glyph::Image, Glyph::Aspect]) + .select_from(select)?; + let sql = insert.with(with_clause).to_string(BigQueryQueryBuilder); + assert_eq!( + sql.as_str(), + [ + r#"WITH `cte` (`id`, `image`, `aspect`) AS (SELECT `id`, `image`, `aspect` FROM `glyph`)"#, + r#"INSERT INTO `glyph` (`id`, `image`, `aspect`) SELECT `id`, `image`, `aspect` FROM `cte`"#, + ].join(" ") + ); + Ok(()) +} + +#[test] +#[allow(clippy::approx_constant)] +fn insert_on_conflict_1() { + assert_eq!( + Query::insert() + .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(BigQueryQueryBuilder), + [ + r#"INSERT INTO `glyph` (`aspect`, `image`)"#, + r#"VALUES ('04108048005887010020060000204E0180400400', 3.1415)"#, + r#"ON CONFLICT (`id`) DO UPDATE SET `aspect` = `excluded`.`aspect`"#, + ] + .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(BigQueryQueryBuilder), + [ + r#"INSERT INTO `glyph` (`aspect`, `image`)"#, + r#"VALUES ('04108048005887010020060000204E0180400400', 3.1415)"#, + r#"ON CONFLICT (`id`, `aspect`) DO UPDATE SET `aspect` = `excluded`.`aspect`, `image` = `excluded`.`image`"#, + ] + .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(BigQueryQueryBuilder), + [ + r#"INSERT INTO `glyph` (`aspect`, `image`)"#, + r#"VALUES ('04108048005887010020060000204E0180400400', 3.1415)"#, + r#"ON CONFLICT (`id`, `aspect`) DO UPDATE SET `aspect` = '04108048005887010020060000204E0180400400', `image` = 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(BigQueryQueryBuilder), + [ + r#"INSERT INTO `glyph` (`aspect`, `image`)"#, + r#"VALUES ('04108048005887010020060000204E0180400400', 3.1415)"#, + r#"ON CONFLICT (`id`, `aspect`) DO UPDATE SET `image` = 1 + 2"#, + ] + .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(BigQueryQueryBuilder), + [ + r#"INSERT INTO `glyph` (`aspect`, `image`)"#, + r#"VALUES ('04108048005887010020060000204E0180400400', 3.1415)"#, + r#"ON CONFLICT (`id`, `aspect`) DO UPDATE SET `aspect` = '04108048005887010020060000204E0180400400', `image` = `excluded`.`image`"#, + ] + .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(BigQueryQueryBuilder), + [ + r#"INSERT INTO `glyph` (`aspect`, `image`)"#, + r#"VALUES ('04108048005887010020060000204E0180400400', 3.1415)"#, + r#"ON CONFLICT (`id`, `aspect`) DO UPDATE SET `aspect` = `excluded`.`aspect`, `image` = 1 + 2"#, + ] + .join(" ") + ); +} + +#[test] +#[allow(clippy::approx_constant)] +fn insert_returning_all_columns() { + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .columns([Glyph::Image, Glyph::Aspect]) + .values_panic([ + "04108048005887010020060000204E0180400400".into(), + 3.1415.into(), + ]) + .returning(Query::returning().all()) + .to_string(BigQueryQueryBuilder), + r#"INSERT INTO `glyph` (`image`, `aspect`) VALUES ('04108048005887010020060000204E0180400400', 3.1415) RETURNING *"# + ); +} + +#[test] +#[allow(clippy::approx_constant)] +fn insert_returning_specific_columns() { + assert_eq!( + Query::insert() + .into_table(Glyph::Table) + .columns([Glyph::Image, Glyph::Aspect]) + .values_panic([ + "04108048005887010020060000204E0180400400".into(), + 3.1415.into(), + ]) + .returning(Query::returning().columns([Glyph::Id, Glyph::Image])) + .to_string(BigQueryQueryBuilder), + r#"INSERT INTO `glyph` (`image`, `aspect`) VALUES ('04108048005887010020060000204E0180400400', 3.1415) RETURNING `id`, `image`"# + ); +} + +#[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)) + .to_string(BigQueryQueryBuilder), + r#"UPDATE `glyph` SET `aspect` = 2.1345, `image` = '24B0E11951B03B07F8300FD003983F03F0780060' WHERE `id` = 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)) + .to_string(BigQueryQueryBuilder), + r#"UPDATE `glyph` SET `aspect` = 60 * 24 * 24, `image` = '24B0E11951B03B07F8300FD003983F03F0780060' WHERE `id` = 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(BigQueryQueryBuilder), + r#"UPDATE `glyph` SET `aspect` = `aspect` + 1, `image` = '24B0E11951B03B07F8300FD003983F03F0780060' WHERE `id` = 1 ORDER BY `id` ASC LIMIT 1"# + ); +} + +#[test] +fn update_returning_all_columns() { + 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)) + .returning(Query::returning().all()) + .to_string(BigQueryQueryBuilder), + r#"UPDATE `glyph` SET `aspect` = 60 * 24 * 24, `image` = '24B0E11951B03B07F8300FD003983F03F0780060' WHERE `id` = 1 RETURNING *"# + ); +} + +#[test] +fn update_returning_specified_columns() { + 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)) + .returning(Query::returning().columns([Glyph::Id, Glyph::Image])) + .to_string(BigQueryQueryBuilder), + r#"UPDATE `glyph` SET `aspect` = 60 * 24 * 24, `image` = '24B0E11951B03B07F8300FD003983F03F0780060' WHERE `id` = 1 RETURNING `id`, `image`"# + ); +} + +#[test] +fn delete_1() { + assert_eq!( + Query::delete() + .from_table(Glyph::Table) + .and_where(Expr::col(Glyph::Id).eq(1)) + .to_string(BigQueryQueryBuilder), + r#"DELETE FROM `glyph` WHERE `id` = 1"# + ); +} + +#[test] +fn escape_1() { + let test = r#" "abc" "#; + assert_eq!( + BigQueryQueryBuilder.escape_string(test), + r#" \"abc\" "#.to_owned() + ); + assert_eq!( + BigQueryQueryBuilder.unescape_string(BigQueryQueryBuilder.escape_string(test).as_str()), + test + ) +} + +#[test] +fn escape_2() { + let test = "a\nb\tc"; + assert_eq!( + BigQueryQueryBuilder.escape_string(test), + "a\\nb\\tc".to_owned() + ); + assert_eq!( + BigQueryQueryBuilder.unescape_string(BigQueryQueryBuilder.escape_string(test).as_str()), + test + ); +} + +#[test] +fn escape_3() { + let test = "a\\b"; + assert_eq!( + BigQueryQueryBuilder.escape_string(test), + "a\\\\b".to_owned() + ); + assert_eq!( + BigQueryQueryBuilder.unescape_string(BigQueryQueryBuilder.escape_string(test).as_str()), + test + ); +} + +#[test] +fn escape_4() { + let test = "a\"b"; + assert_eq!( + BigQueryQueryBuilder.escape_string(test), + "a\\\"b".to_owned() + ); + assert_eq!( + BigQueryQueryBuilder.unescape_string(BigQueryQueryBuilder.escape_string(test).as_str()), + test + ) +} + +#[test] +fn delete_returning_all_columns() { + assert_eq!( + Query::delete() + .from_table(Glyph::Table) + .and_where(Expr::col(Glyph::Id).eq(1)) + .returning(Query::returning().all()) + .to_string(BigQueryQueryBuilder), + r#"DELETE FROM `glyph` WHERE `id` = 1 RETURNING *"# + ); +} + +#[test] +fn delete_returning_specific_columns() { + assert_eq!( + Query::delete() + .from_table(Glyph::Table) + .and_where(Expr::col(Glyph::Id).eq(1)) + .returning(Query::returning().columns([Glyph::Id, Glyph::Image])) + .to_string(BigQueryQueryBuilder), + r#"DELETE FROM `glyph` WHERE `id` = 1 RETURNING `id`, `image`"# + ); +} + +#[test] +fn delete_returning_specific_exprs() { + assert_eq!( + Query::delete() + .from_table(Glyph::Table) + .and_where(Expr::col(Glyph::Id).eq(1)) + .returning(Query::returning().exprs([Expr::col(Glyph::Id), Expr::col(Glyph::Image)])) + .to_string(BigQueryQueryBuilder), + r#"DELETE FROM `glyph` WHERE `id` = 1 RETURNING `id`, `image`"# + ); +} + +#[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(BigQueryQueryBuilder), + [ + r#"SELECT `character` FROM `character` UNION DISTINCT SELECT `character` FROM `character`"#, + r#"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(BigQueryQueryBuilder), + r#"SELECT ARRAY((SELECT * FROM `character`))"# + ); +} diff --git a/tests/bigquery/table.rs b/tests/bigquery/table.rs new file mode 100644 index 000000000..e23df23ca --- /dev/null +++ b/tests/bigquery/table.rs @@ -0,0 +1,476 @@ +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()) + .col(ColumnDef::new(Glyph::Aspect).double().not_null()) + .col(ColumnDef::new(Glyph::Image).text()) + .to_string(BigQueryQueryBuilder), + [ + r#"CREATE TABLE `glyph` ("#, + r#"`id` INT64 NOT NULL,"#, + r#"`aspect` FLOAT64 NOT NULL,"#, + r#"`image` STRING"#, + r#")"#, + ] + .join(" ") + ); +} + +#[test] +fn create_2() { + assert_eq!( + Table::create() + .table(Font::Table) + .col(ColumnDef::new(Font::Id).integer().not_null()) + .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(255).not_null()) + .to_string(BigQueryQueryBuilder), + [ + r#"CREATE TABLE `font` ("#, + r#"`id` INT64 NOT NULL,"#, + r#"`name` STRING NOT NULL,"#, + r#"`variant` STRING(255) NOT NULL,"#, + r#"`language` STRING(255) NOT NULL"#, + r#")"#, + ] + .join(" ") + ); +} + +// Foreign key constraints are not supported in BigQuery. +#[test] +#[should_panic(expected = "Not supported")] +fn create_3() { + assert_eq!( + Table::create() + .table(Char::Table) + .if_not_exists() + .col(ColumnDef::new(Char::Id).integer().not_null()) + .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)) + ) + .foreign_key( + ForeignKey::create() + .name("FK_2e303c3a712662f1fc2a4d0aad6") + .from(Char::Table, Char::FontId) + .to(Font::Table, Font::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + ) + .to_string(BigQueryQueryBuilder), + [ + r#"CREATE TABLE IF NOT EXISTS `character` ("#, + r#"`id` INT64 NOT NULL,"#, + r#"`font_size` INT64 NOT NULL,"#, + r#"`character` STRING(255) NOT NULL,"#, + r#"`size_w` STRING NOT NULL,"#, + r#""size_h" STRING NOT NULL,"#, + r#"`font_id` INT64 DEFAULT NULL,"#, + r#"CONSTRAINT "FK_2e303c3a712662f1fc2a4d0aad6""#, + r#"FOREIGN KEY (`font_id`) REFERENCES `font` (`id`)"#, + r#"ON DELETE CASCADE ON UPDATE CASCADE"#, + r#")"#, + ] + .join(" ") + ); +} + +#[test] +fn create_4() { + assert_eq!( + Table::create() + .table(Glyph::Table) + .col(ColumnDef::new(Glyph::Image).custom(Glyph::Aspect)) + .to_string(BigQueryQueryBuilder), + r#"CREATE TABLE `glyph` ( `image` aspect )"# + ); +} + +#[test] +fn create_5() { + assert_eq!( + Table::create() + .table(Glyph::Table) + .col(ColumnDef::new(Glyph::Image).json()) + .col(ColumnDef::new(Glyph::Aspect).json_binary()) + .to_string(BigQueryQueryBuilder), + [ + r#"CREATE TABLE `glyph` ("#, + r#"`image` JSON,"#, + r#"`aspect` JSON"#, + r#")"#, + ] + .join(" ") + ); +} + +#[test] +fn create_6() { + 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(BigQueryQueryBuilder), + [ + r#"CREATE TABLE `glyph` ("#, + r#"`id` INT64 NOT NULL ANYTHING I WANT TO SAY"#, + r#")"#, + ] + .join(" ") + ); +} + +#[test] +fn create_7() { + assert_eq!( + Table::create() + .table(Glyph::Table) + .col(ColumnDef::new(Glyph::Aspect).interval_bq().not_null()) + .to_string(BigQueryQueryBuilder), + [ + r#"CREATE TABLE `glyph` ("#, + r#"`aspect` INTERVAL NOT NULL"#, + r#")"#, + ] + .join(" ") + ); +} + +#[test] +fn create_8() { + assert_eq!( + Table::create() + .table(Glyph::Table) + .col(ColumnDef::new(Glyph::Aspect).interval_bq().not_null()) + .to_string(BigQueryQueryBuilder), + [ + r#"CREATE TABLE `glyph` ("#, + r#"`aspect` INTERVAL NOT NULL"#, + r#")"#, + ] + .join(" ") + ); +} + +#[test] +fn create_9() { + assert_eq!( + Table::create() + .table(Char::Table) + .col( + ColumnDef::new(Char::CreatedAt) + .timestamp_with_time_zone() + .not_null() + ) + .to_string(BigQueryQueryBuilder), + [ + r#"CREATE TABLE `character` ("#, + r#"`created_at` TIMESTAMP NOT NULL"#, + r#")"#, + ] + .join(" ") + ); +} + +#[test] +fn create_10() { + 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::BlobSize).blob(BlobSize::Blob(Some(32)))) + .col(ColumnDef::new(BinaryType::TinyBlob).blob(BlobSize::Tiny)) + .col(ColumnDef::new(BinaryType::Blob).blob(BlobSize::Blob(None))) + .col(ColumnDef::new(BinaryType::MediumBlob).blob(BlobSize::Medium)) + .col(ColumnDef::new(BinaryType::LongBlob).blob(BlobSize::Long)) + .to_string(BigQueryQueryBuilder), + [ + r#"CREATE TABLE `binary_type` ("#, + r#"`binlen` BYTES(32),"#, + r#"`bin` BYTES,"#, + r#"`defb` BYTES(32),"#, + r#"`tb` BYTES,"#, + r#"`b` BYTES,"#, + r#"`mb` BYTES,"#, + r#"`lb` BYTES"#, + r#")"#, + ] + .join(" ") + ); +} + +#[test] +fn create_11() { + assert_eq!( + Table::create() + .table(Char::Table) + .col(ColumnDef::new(Char::Character).binary()) + .col(ColumnDef::new(Char::FontSize).binary_len(10)) + .col(ColumnDef::new(Char::SizeW).var_binary(10)) + .to_string(BigQueryQueryBuilder), + [ + r#"CREATE TABLE `character` ("#, + r#"`character` BYTES,"#, + r#"`font_size` BYTES(10),"#, + r#"`size_w` BYTES(10)"#, + r#")"#, + ] + .join(" ") + ); +} + +#[test] +fn create_12() { + assert_eq!( + Table::create() + .table((Alias::new("schema"), Glyph::Table)) + .col(ColumnDef::new(Glyph::Image).custom(Glyph::Aspect)) + .to_string(BigQueryQueryBuilder), + [ + r#"CREATE TABLE `schema`.`glyph` ("#, + r#"`image` aspect"#, + r#")"#, + ] + .join(" ") + ); +} + +#[test] +#[should_panic(expected = "Not supported")] +fn create_13() { + assert_eq!( + Table::create() + .table(Glyph::Table) + .col(ColumnDef::new(Glyph::Image).json()) + .col(ColumnDef::new(Glyph::Aspect).json_binary()) + .index( + Index::create() + .unique() + .nulls_not_distinct() + .name("idx-glyph-aspect-image") + .table(Glyph::Table) + .col(Glyph::Aspect) + .col(Glyph::Image) + ) + .to_string(BigQueryQueryBuilder), + [ + r#"CREATE TABLE `glyph` ("#, + r#"`image` json,"#, + r#"`aspect` jsonb,"#, + r#"CONSTRAINT "idx-glyph-aspect-image" UNIQUE NULLS NOT DISTINCT (`aspect`, `image`)"#, + r#")"#, + ] + .join(" ") + ); +} + +#[test] +fn drop_1() { + assert_eq!( + Table::drop() + .table(Glyph::Table) + .table(Char::Table) + .to_string(BigQueryQueryBuilder), + r#"DROP TABLE `glyph`, `character`"# + ); +} + +#[test] +fn drop_2() { + assert_eq!( + Table::drop() + .table((Alias::new("schema1"), Glyph::Table)) + .table((Alias::new("schema2"), Char::Table)) + .to_string(BigQueryQueryBuilder), + r#"DROP TABLE `schema1`.`glyph`, `schema2`.`character`"# + ); +} + +#[test] +fn truncate_1() { + assert_eq!( + Table::truncate() + .table(Font::Table) + .to_string(BigQueryQueryBuilder), + r#"TRUNCATE TABLE `font`"# + ); +} + +#[test] +fn truncate_2() { + assert_eq!( + Table::truncate() + .table((Alias::new("schema"), Font::Table)) + .to_string(BigQueryQueryBuilder), + r#"TRUNCATE TABLE `schema`.`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(BigQueryQueryBuilder), + r#"ALTER TABLE `font` ADD COLUMN `new_col` INT64 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(BigQueryQueryBuilder), + [ + r#"ALTER TABLE `font`"#, + r#"ALTER COLUMN `new_col` SET DATA TYPE INT64,"#, + r#"ALTER COLUMN `new_col` SET DEFAULT 999"#, + ] + .join(" ") + ); +} + +#[test] +fn alter_3() { + assert_eq!( + Table::alter() + .table(Font::Table) + .rename_column(Alias::new("new_col"), Alias::new("new_column")) + .to_string(BigQueryQueryBuilder), + r#"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(BigQueryQueryBuilder), + r#"ALTER TABLE `font` DROP COLUMN `new_column`"# + ); +} + +#[test] +fn alter_5() { + assert_eq!( + Table::alter() + .table((Alias::new("schema"), Font::Table)) + .rename_column(Alias::new("new_col"), Alias::new("new_column")) + .to_string(BigQueryQueryBuilder), + r#"ALTER TABLE `schema`.`font` RENAME COLUMN `new_col` TO `new_column`"# + ); +} + +#[test] +#[should_panic(expected = "No alter option found")] +fn alter_6() { + Table::alter().to_string(BigQueryQueryBuilder); +} + +#[test] +fn alter_7() { + assert_eq!( + Table::alter() + .table(Font::Table) + .add_column(ColumnDef::new(Alias::new("new_col")).integer()) + .rename_column(Font::Name, Alias::new("name_new")) + .to_string(BigQueryQueryBuilder), + r#"ALTER TABLE `font` ADD COLUMN `new_col` INT64, RENAME COLUMN `name` TO `name_new`"# + ); +} + +#[test] +fn alter_8() { + assert_eq!( + Table::alter() + .table(Font::Table) + .modify_column(ColumnDef::new(Font::Language).null()) + .to_string(BigQueryQueryBuilder), + [ + r#"ALTER TABLE `font`"#, + r#"ALTER COLUMN `language` DROP NOT NULL"#, + ] + .join(" ") + ); +} + +#[test] +#[should_panic(expected = "BigQuery doesn't support changing to REQUIRED")] +fn alter_9() { + assert_eq!( + Table::alter() + .table(Glyph::Table) + .modify_column( + ColumnDef::new(Glyph::Aspect) + .integer() + .auto_increment() + .not_null() + .unique_key() + .primary_key() + ) + .to_string(BigQueryQueryBuilder), + [ + r#"ALTER TABLE `glyph`"#, + r#"ALTER COLUMN `aspect` TYPE serial,"#, + r#"ALTER COLUMN `aspect` SET NOT NULL,"#, + r#"ADD UNIQUE (`aspect`),"#, + r#"ADD PRIMARY KEY (`aspect`)"#, + ] + .join(" ") + ); +} + +#[test] +fn rename_1() { + assert_eq!( + Table::rename() + .table(Font::Table, Alias::new("font_new")) + .to_string(BigQueryQueryBuilder), + r#"ALTER TABLE `font` RENAME TO `font_new`"# + ); +} + +#[test] +fn rename_2() { + assert_eq!( + Table::rename() + .table( + (Alias::new("schema"), Font::Table), + (Alias::new("schema"), Alias::new("font_new")), + ) + .to_string(BigQueryQueryBuilder), + r#"ALTER TABLE `schema`.`font` RENAME TO `schema`.`font_new`"# + ); +}