Skip to content

Commit be13dc1

Browse files
committed
feat: implement backend compatible with Google Bigquery
1 parent 6d68992 commit be13dc1

File tree

13 files changed

+2580
-1
lines changed

13 files changed

+2580
-1
lines changed

Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ pretty_assertions = { version = "1" }
5555
backend-mysql = []
5656
backend-postgres = []
5757
backend-sqlite = []
58-
default = ["derive", "backend-mysql", "backend-postgres", "backend-sqlite"]
58+
backend-bigquery = []
59+
default = ["derive", "backend-mysql", "backend-postgres", "backend-sqlite", "backend-bigquery"]
5960
derive = ["sea-query-derive"]
6061
attr = ["sea-query-attr"]
6162
hashable-value = ["derivative", "ordered-float"]
@@ -87,6 +88,11 @@ name = "test-mysql"
8788
path = "tests/mysql/mod.rs"
8889
required-features = ["tests-cfg", "backend-mysql"]
8990

91+
[[test]]
92+
name = "test-bigquery"
93+
path = "tests/bigquery/mod.rs"
94+
required-features = ["tests-cfg", "backend-bigquery"]
95+
9096
[[test]]
9197
name = "test-postgres"
9298
path = "tests/postgres/mod.rs"

src/backend/bigquery/foreign_key.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use super::*;
2+
3+
impl ForeignKeyBuilder for BigQueryQueryBuilder {
4+
fn prepare_table_ref_fk_stmt(&self, _table_ref: &TableRef, _sql: &mut dyn SqlWriter) {
5+
panic!("Not supported")
6+
}
7+
8+
fn prepare_foreign_key_drop_statement_internal(
9+
&self,
10+
_drop: &ForeignKeyDropStatement,
11+
_sql: &mut dyn SqlWriter,
12+
_mode: Mode,
13+
) {
14+
panic!("Not supported")
15+
}
16+
17+
fn prepare_foreign_key_create_statement_internal(
18+
&self,
19+
_create: &ForeignKeyCreateStatement,
20+
_sql: &mut dyn SqlWriter,
21+
_mode: Mode,
22+
) {
23+
panic!("Not supported")
24+
}
25+
}

src/backend/bigquery/index.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use super::*;
2+
3+
impl IndexBuilder for BigQueryQueryBuilder {
4+
fn prepare_index_create_statement(
5+
&self,
6+
_create: &IndexCreateStatement,
7+
_sql: &mut dyn SqlWriter,
8+
) {
9+
panic!("Not supported");
10+
}
11+
12+
fn prepare_table_ref_index_stmt(&self, _table_ref: &TableRef, _sql: &mut dyn SqlWriter) {
13+
panic!("Not supported");
14+
}
15+
16+
fn prepare_index_drop_statement(&self, _drop: &IndexDropStatement, _sql: &mut dyn SqlWriter) {
17+
panic!("Not supported");
18+
}
19+
20+
fn prepare_index_prefix(&self, _create: &IndexCreateStatement, _sql: &mut dyn SqlWriter) {
21+
panic!("Not supported");
22+
}
23+
24+
fn write_column_index_prefix(&self, _col_prefix: &Option<u32>, _sql: &mut dyn SqlWriter) {}
25+
}

src/backend/bigquery/mod.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
pub(crate) mod foreign_key;
2+
pub(crate) mod index;
3+
pub(crate) mod query;
4+
pub(crate) mod table;
5+
6+
use super::*;
7+
8+
/// BigQuery query builder.
9+
#[derive(Default, Debug)]
10+
pub struct BigQueryQueryBuilder;
11+
12+
const QUOTE: Quote = Quote(b'`', b'`');
13+
14+
impl GenericBuilder for BigQueryQueryBuilder {}
15+
16+
impl SchemaBuilder for BigQueryQueryBuilder {}
17+
18+
impl QuotedBuilder for BigQueryQueryBuilder {
19+
fn quote(&self) -> Quote {
20+
QUOTE
21+
}
22+
}
23+
24+
impl EscapeBuilder for BigQueryQueryBuilder {}
25+
26+
impl TableRefBuilder for BigQueryQueryBuilder {}

src/backend/bigquery/query.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use super::*;
2+
3+
impl QueryBuilder for BigQueryQueryBuilder {
4+
fn placeholder(&self) -> (&str, bool) {
5+
("$", true)
6+
}
7+
8+
fn prepare_select_lock(&self, _select_lock: &LockClause, _sql: &mut dyn SqlWriter) {
9+
// SQLite doesn't supports row locking
10+
}
11+
12+
fn if_null_function(&self) -> &str {
13+
"COALESCE"
14+
}
15+
16+
fn prepare_sub_query_oper(&self, oper: &SubQueryOper, sql: &mut dyn SqlWriter) {
17+
write!(
18+
sql,
19+
"{}",
20+
match oper {
21+
SubQueryOper::Exists => "EXISTS",
22+
SubQueryOper::Any => panic!("Operator 'ANY' doesnot support"),
23+
SubQueryOper::Some => panic!("Operator 'SOME' doesnot support"),
24+
SubQueryOper::All => panic!("Operator 'ALL' doesnot support"),
25+
// Should add custom operator options. In the case of BigQuery, ARRAY, Scalar subquery
26+
}
27+
)
28+
.unwrap();
29+
}
30+
31+
fn prepare_union_statement(
32+
&self,
33+
union_type: UnionType,
34+
select_statement: &SelectStatement,
35+
sql: &mut dyn SqlWriter,
36+
) {
37+
match union_type {
38+
UnionType::Intersect => write!(sql, " INTERSECT ").unwrap(),
39+
UnionType::Distinct => write!(sql, " UNION DISTINCT ").unwrap(),
40+
UnionType::Except => write!(sql, " EXCEPT ").unwrap(),
41+
UnionType::All => write!(sql, " UNION ALL ").unwrap(),
42+
}
43+
self.prepare_select_statement(select_statement, sql);
44+
}
45+
46+
fn prepare_query_statement(&self, query: &SubQueryStatement, sql: &mut dyn SqlWriter) {
47+
query.prepare_statement(self, sql);
48+
}
49+
50+
fn prepare_order_expr(&self, order_expr: &OrderExpr, sql: &mut dyn SqlWriter) {
51+
if !matches!(order_expr.order, Order::Field(_)) {
52+
self.prepare_simple_expr(&order_expr.expr, sql);
53+
}
54+
self.prepare_order(order_expr, sql);
55+
match order_expr.nulls {
56+
None => (),
57+
Some(NullOrdering::Last) => write!(sql, " NULLS LAST").unwrap(),
58+
Some(NullOrdering::First) => write!(sql, " NULLS FIRST").unwrap(),
59+
}
60+
}
61+
62+
fn prepare_value(&self, value: &Value, sql: &mut dyn SqlWriter) {
63+
sql.push_param(value.clone(), self as _);
64+
}
65+
66+
fn char_length_function(&self) -> &str {
67+
"CHAR_LENGTH"
68+
}
69+
70+
fn insert_default_values(&self, _: u32, _sql: &mut dyn SqlWriter) {
71+
panic!("BigQuery does not support default values");
72+
}
73+
}

src/backend/bigquery/table.rs

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
use super::*;
2+
3+
impl TableBuilder for BigQueryQueryBuilder {
4+
fn prepare_column_def(&self, column_def: &ColumnDef, sql: &mut dyn SqlWriter) {
5+
column_def.name.prepare(sql.as_writer(), self.quote());
6+
7+
if let Some(column_type) = &column_def.types {
8+
write!(sql, " ").unwrap();
9+
self.prepare_column_type(column_type, sql);
10+
}
11+
12+
for column_spec in column_def.spec.iter() {
13+
if let ColumnSpec::PrimaryKey = column_spec {
14+
continue;
15+
}
16+
if let ColumnSpec::AutoIncrement = column_spec {
17+
continue;
18+
}
19+
if let ColumnSpec::Comment(_) = column_spec {
20+
continue;
21+
}
22+
write!(sql, " ").unwrap();
23+
self.prepare_column_spec(column_spec, sql);
24+
}
25+
}
26+
27+
fn prepare_column_type(&self, column_type: &ColumnType, sql: &mut dyn SqlWriter) {
28+
write!(
29+
sql,
30+
"{}",
31+
match column_type {
32+
ColumnType::Char(length) => match length {
33+
Some(length) => format!("STRING({length})"),
34+
None => "STRING".into(),
35+
},
36+
ColumnType::String(length) => match length {
37+
Some(length) => format!("STRING({length})"),
38+
None => "STRING".into(),
39+
},
40+
ColumnType::Text => "STRING".into(),
41+
ColumnType::TinyInteger | ColumnType::TinyUnsigned => "INT64".into(),
42+
ColumnType::SmallInteger | ColumnType::SmallUnsigned => "INT64".into(),
43+
ColumnType::Integer | ColumnType::Unsigned => "INT64".into(),
44+
ColumnType::BigInteger | ColumnType::BigUnsigned => "INT64".into(),
45+
ColumnType::Float => "FLOAT64".into(),
46+
ColumnType::Double => "FLOAT64".into(),
47+
ColumnType::Decimal(precision) | ColumnType::Money(precision) => match precision {
48+
Some((precision, scale)) => match scale {
49+
0..=9 if precision.max(&1) <= precision && precision <= &(scale + 29) =>
50+
format!("NUMERIC({precision}, {scale})"),
51+
10..=38 if precision.max(&1) <= precision && precision <= &(scale + 38) =>
52+
format!("BIGNUMERIC({precision}, {scale})"),
53+
_ => panic!("Invalid precision and scale for NUMERIC type"),
54+
},
55+
None => "BIGNUMERIC".into(),
56+
},
57+
ColumnType::DateTime => "DATETIME".into(),
58+
ColumnType::Timestamp => "TIMESTAMP".into(),
59+
ColumnType::TimestampWithTimeZone => "TIMESTAMP".into(),
60+
ColumnType::Time => "TIME".into(),
61+
ColumnType::Date => "DATE".into(),
62+
ColumnType::Interval(_, _) => "INTERVAL".into(),
63+
ColumnType::Binary(blob_size) => match blob_size {
64+
BlobSize::Blob(Some(length)) => format!("BYTES({length})"),
65+
_ => "BYTES".into(),
66+
},
67+
ColumnType::VarBinary(length) => format!("BYTES({length})"),
68+
ColumnType::Boolean => "BOOL".into(),
69+
ColumnType::Json => "JSON".into(),
70+
ColumnType::JsonBinary => "JSON".into(),
71+
ColumnType::Uuid => "STRING(36)".into(),
72+
ColumnType::Custom(iden) => iden.to_string(),
73+
ColumnType::Enum { .. } => "STRING".into(),
74+
ColumnType::Array(col_type) => {
75+
let mut sql = String::new();
76+
self.prepare_column_type(col_type, &mut sql);
77+
format!("ARRAY<{sql}>")
78+
}
79+
ColumnType::Cidr => unimplemented!("Cidr is not available in BigQuery."),
80+
ColumnType::Inet => unimplemented!("Inet is not available in BigQuery."),
81+
ColumnType::MacAddr => unimplemented!("MacAddr is not available in BigQuery."),
82+
ColumnType::Year(_) => unimplemented!("Year is not available in BigQuery."),
83+
ColumnType::Bit(_) => unimplemented!("Bit is not available in BigQuery."),
84+
ColumnType::VarBit(_) => unimplemented!("VarBit is not available in BigQuery."),
85+
}
86+
)
87+
.unwrap()
88+
}
89+
90+
fn column_spec_auto_increment_keyword(&self) -> &str {
91+
panic!("BigQuery does not support auto increment");
92+
}
93+
94+
fn prepare_table_drop_opt(&self, _drop_opt: &TableDropOpt, _sql: &mut dyn SqlWriter) {
95+
panic!("BigQuery does not support table drop option");
96+
}
97+
98+
fn prepare_table_alter_statement(&self, alter: &TableAlterStatement, sql: &mut dyn SqlWriter) {
99+
if alter.options.is_empty() {
100+
panic!("No alter option found")
101+
}
102+
write!(sql, "ALTER TABLE ").unwrap();
103+
if let Some(table) = &alter.table {
104+
self.prepare_table_ref_table_stmt(table, sql);
105+
write!(sql, " ").unwrap();
106+
}
107+
alter.options.iter().fold(true, |first, option| {
108+
if !first {
109+
write!(sql, ", ").unwrap();
110+
};
111+
match option {
112+
TableAlterOption::AddColumn(AddColumnOption {
113+
column,
114+
if_not_exists: _,
115+
}) => {
116+
write!(sql, "ADD COLUMN ").unwrap();
117+
self.prepare_column_def(column, sql);
118+
}
119+
TableAlterOption::ModifyColumn(column_def) => {
120+
if let Some(types) = &column_def.types {
121+
write!(sql, "ALTER COLUMN ").unwrap();
122+
column_def.name.prepare(sql.as_writer(), self.quote());
123+
write!(sql, " SET DATA TYPE ").unwrap();
124+
self.prepare_column_type(types, sql);
125+
}
126+
let first = column_def.types.is_none();
127+
128+
column_def.spec.iter().fold(first, |first, column_spec| {
129+
if !first {
130+
write!(sql, ", ").unwrap();
131+
}
132+
match column_spec {
133+
ColumnSpec::AutoIncrement => {}
134+
ColumnSpec::Null => {
135+
write!(sql, "ALTER COLUMN ").unwrap();
136+
column_def.name.prepare(sql.as_writer(), self.quote());
137+
write!(sql, " DROP NOT NULL").unwrap();
138+
}
139+
ColumnSpec::NotNull => {
140+
panic!("BigQuery doesn't support changing to REQUIRED")
141+
}
142+
ColumnSpec::Default(v) => {
143+
write!(sql, "ALTER COLUMN ").unwrap();
144+
column_def.name.prepare(sql.as_writer(), self.quote());
145+
write!(sql, " SET DEFAULT ").unwrap();
146+
QueryBuilder::prepare_simple_expr(self, v, sql);
147+
}
148+
ColumnSpec::UniqueKey => {
149+
panic!("BigQuery doesn't support adding unique constraint")
150+
}
151+
ColumnSpec::PrimaryKey => {
152+
panic!("BigQuery doesn't support adding primary key constraint")
153+
}
154+
ColumnSpec::Check(_check) => {
155+
panic!("BigQuery doesn't support adding check constraint")
156+
}
157+
ColumnSpec::Generated { .. } => {}
158+
ColumnSpec::Extra(string) => write!(sql, "{string}").unwrap(),
159+
ColumnSpec::Comment(_) => {}
160+
}
161+
false
162+
});
163+
}
164+
TableAlterOption::RenameColumn(from_name, to_name) => {
165+
write!(sql, "RENAME COLUMN ").unwrap();
166+
from_name.prepare(sql.as_writer(), self.quote());
167+
write!(sql, " TO ").unwrap();
168+
to_name.prepare(sql.as_writer(), self.quote());
169+
}
170+
TableAlterOption::DropColumn(col_name) => {
171+
write!(sql, "DROP COLUMN ").unwrap();
172+
col_name.prepare(sql.as_writer(), self.quote());
173+
}
174+
TableAlterOption::DropForeignKey(_) => {
175+
panic!("BigQuery does not support modification of foreign key constraints to existing tables");
176+
}
177+
TableAlterOption::AddForeignKey(_) => {
178+
panic!("BigQuery does not support modification of foreign key constraints to existing tables");
179+
}
180+
}
181+
false
182+
});
183+
}
184+
185+
fn prepare_table_rename_statement(
186+
&self,
187+
rename: &TableRenameStatement,
188+
sql: &mut dyn SqlWriter,
189+
) {
190+
write!(sql, "ALTER TABLE ").unwrap();
191+
if let Some(from_name) = &rename.from_name {
192+
self.prepare_table_ref_table_stmt(from_name, sql);
193+
}
194+
write!(sql, " RENAME TO ").unwrap();
195+
if let Some(to_name) = &rename.to_name {
196+
self.prepare_table_ref_table_stmt(to_name, sql);
197+
}
198+
}
199+
}

src/backend/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ mod postgres;
1212
#[cfg_attr(docsrs, doc(cfg(feature = "backend-sqlite")))]
1313
mod sqlite;
1414

15+
#[cfg(feature = "backend-bigquery")]
16+
#[cfg_attr(docsrs, doc(cfg(feature = "backend-bigquery")))]
17+
mod bigquery;
18+
19+
#[cfg(feature = "backend-bigquery")]
20+
pub use bigquery::*;
1521
#[cfg(feature = "backend-mysql")]
1622
pub use mysql::*;
1723
#[cfg(feature = "backend-postgres")]

0 commit comments

Comments
 (0)