use crate::ast::NoExt;
use crate::ast::dialect::FeatureSet;
use crate::parser::Dialect;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct BigQuery;
impl Dialect for BigQuery {
type Ext = NoExt;
fn features(&self) -> &FeatureSet {
&FeatureSet::BIGQUERY
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{Expr, SelectItem};
use crate::dialect::test_support::assert_full_grammar;
use crate::dialect::{Ansi, DuckDb, MySql, Postgres, Sqlite};
use crate::parse_with;
use crate::parser::{Dialect, Parsed};
#[test]
fn bigquery_parses_the_full_m1_select_grammar() {
assert_full_grammar(BigQuery);
}
#[test]
fn bigquery_parses_unnest_with_offset_that_every_oracle_preset_rejects() {
for sql in [
"SELECT * FROM UNNEST(arr) WITH OFFSET",
"SELECT * FROM UNNEST(arr) WITH OFFSET AS pos",
"SELECT * FROM t CROSS JOIN UNNEST(t.items) WITH OFFSET AS ord",
] {
assert!(parse_with(sql, BigQuery).is_ok(), "BigQuery parses {sql:?}");
assert!(
parse_with(sql, Ansi).is_err(),
"ANSI has no UNNEST factor, so it rejects {sql:?}",
);
assert!(
parse_with(sql, Postgres).is_err(),
"PostgreSQL parses UNNEST but rejects the WITH OFFSET tail in {sql:?}",
);
assert!(
parse_with(sql, MySql).is_err(),
"MySQL has no UNNEST factor, so it rejects {sql:?}",
);
assert!(
parse_with(sql, Sqlite).is_err(),
"SQLite has no UNNEST factor, so it rejects {sql:?}",
);
assert!(
parse_with(sql, DuckDb).is_err(),
"DuckDB parses UNNEST but rejects the WITH OFFSET tail in {sql:?}",
);
}
}
#[test]
fn bigquery_parses_bare_unnest_without_offset() {
for sql in ["SELECT * FROM UNNEST(arr)", "SELECT * FROM UNNEST(a, b)"] {
assert!(parse_with(sql, BigQuery).is_ok(), "BigQuery parses {sql:?}");
}
}
#[test]
fn bigquery_quotes_identifiers_with_backticks_not_double_quotes() {
let backtick = "SELECT `a b` FROM t";
assert!(
parse_with(backtick, BigQuery).is_ok(),
"BigQuery reads `` `a b` `` as an identifier",
);
assert!(
parse_with(backtick, Ansi).is_err(),
"ANSI has no backtick identifier quote",
);
assert!(
parse_with(backtick, Postgres).is_err(),
"PostgreSQL has no backtick identifier quote",
);
assert!(
parse_with(backtick, DuckDb).is_err(),
"DuckDB has no backtick identifier quote",
);
assert!(
parse_with(backtick, MySql).is_ok(),
"MySQL shares the backtick identifier quote",
);
assert!(
parse_with(backtick, Sqlite).is_ok(),
"SQLite shares the backtick identifier quote (MySQL compat)",
);
}
#[test]
fn bigquery_reads_double_quotes_as_strings_not_identifiers() {
let parsed = parse_with("SELECT \"hi\"", BigQuery).expect("BigQuery parses `\"hi\"`");
let SelectItem::Expr { expr, .. } = &select_projection(&parsed)[0] else {
panic!("expected a bare projection expression");
};
let Expr::Literal { literal, .. } = expr else {
panic!("BigQuery must read `\"hi\"` as a string literal, got {expr:?}");
};
assert_eq!(literal.as_str(parsed.source()).expect("string value"), "hi",);
let ansi_parsed = parse_with("SELECT \"hi\"", Ansi).expect("ANSI parses `\"hi\"`");
let SelectItem::Expr { expr, .. } = &select_projection(&ansi_parsed)[0] else {
panic!("expected a bare projection expression");
};
assert!(
!matches!(expr, Expr::Literal { .. }),
"ANSI must read `\"hi\"` as an identifier, not a string literal",
);
}
#[test]
fn bigquery_features_are_the_bigquery_preset() {
assert_eq!(BigQuery.features(), &FeatureSet::BIGQUERY);
}
fn select_projection(parsed: &Parsed) -> &[SelectItem<NoExt>] {
use crate::ast::{SetExpr, Statement};
let Statement::Query { query, .. } = &parsed.statements()[0] else {
panic!("expected a query statement");
};
let SetExpr::Select { select, .. } = &query.body else {
panic!("expected a plain SELECT body");
};
&select.projection
}
}