Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement backend compatible with Google Bigquery #637

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading