Skip to content

Commit

Permalink
feat: implement backend compatible with Google Bigquery
Browse files Browse the repository at this point in the history
  • Loading branch information
andyquinterom committed May 9, 2023
1 parent 6d68992 commit ee7dc8d
Show file tree
Hide file tree
Showing 13 changed files with 2,582 additions and 1 deletion.
8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -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"
Expand Down
25 changes: 25 additions & 0 deletions src/backend/bigquery/foreign_key.rs
Original file line number Diff line number Diff line change
@@ -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")
}
}
25 changes: 25 additions & 0 deletions src/backend/bigquery/index.rs
Original file line number Diff line number Diff line change
@@ -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<u32>, _sql: &mut dyn SqlWriter) {}
}
26 changes: 26 additions & 0 deletions src/backend/bigquery/mod.rs
Original file line number Diff line number Diff line change
@@ -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 {}
73 changes: 73 additions & 0 deletions src/backend/bigquery/query.rs
Original file line number Diff line number Diff line change
@@ -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");
}
}
199 changes: 199 additions & 0 deletions src/backend/bigquery/table.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
6 changes: 6 additions & 0 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
Loading

0 comments on commit ee7dc8d

Please sign in to comment.