sea-query-spanner 0.1.0

Spanner SQL dialect for sea-query
Documentation
use sea_query::{
    backend::{
        EscapeBuilder, OperLeftAssocDecider, PrecedenceDecider, QueryBuilder, QuotedBuilder,
        TableRefBuilder,
    },
    BinOper, Oper, Quote, SelectInto, SimpleExpr, SqlWriter, SubQueryStatement, Value,
};

pub struct SpannerQueryBuilder;

impl SpannerQueryBuilder {
    pub fn new() -> Self {
        Self
    }
}

impl Default for SpannerQueryBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl QuotedBuilder for SpannerQueryBuilder {
    fn quote(&self) -> Quote {
        Quote::new(b'`')
    }
}

impl EscapeBuilder for SpannerQueryBuilder {
    fn escape_string(&self, string: &str) -> String {
        string.replace('\'', "''")
    }

    fn unescape_string(&self, string: &str) -> String {
        string.replace("''", "'")
    }
}

impl TableRefBuilder for SpannerQueryBuilder {}

impl PrecedenceDecider for SpannerQueryBuilder {
    fn inner_expr_well_known_greater_precedence(
        &self,
        inner: &SimpleExpr,
        _outer_oper: &Oper,
    ) -> bool {
        matches!(
            inner,
            SimpleExpr::Column(_)
                | SimpleExpr::Tuple(_)
                | SimpleExpr::Constant(_)
                | SimpleExpr::FunctionCall(_)
                | SimpleExpr::Value(_)
                | SimpleExpr::Keyword(_)
                | SimpleExpr::Case(_)
                | SimpleExpr::SubQuery(_, _)
        )
    }
}

impl OperLeftAssocDecider for SpannerQueryBuilder {
    fn well_known_left_associative(&self, op: &BinOper) -> bool {
        matches!(
            op,
            BinOper::And
                | BinOper::Or
                | BinOper::Add
                | BinOper::Sub
                | BinOper::Mul
                | BinOper::Div
                | BinOper::Mod
        )
    }
}

impl QueryBuilder for SpannerQueryBuilder {
    fn placeholder(&self) -> (&'static str, bool) {
        ("@p", true)
    }

    fn prepare_query_statement(&self, query: &SubQueryStatement, sql: &mut impl SqlWriter) {
        match query {
            SubQueryStatement::SelectStatement(stmt) => self.prepare_select_statement(stmt, sql),
            SubQueryStatement::InsertStatement(stmt) => self.prepare_insert_statement(stmt, sql),
            SubQueryStatement::UpdateStatement(stmt) => self.prepare_update_statement(stmt, sql),
            SubQueryStatement::DeleteStatement(stmt) => self.prepare_delete_statement(stmt, sql),
            SubQueryStatement::WithStatement(stmt) => self.prepare_with_query(stmt, sql),
        }
    }

    fn prepare_select_into(&self, _: &SelectInto, _: &mut impl SqlWriter) {}

    fn prepare_value(&self, value: Value, sql: &mut impl SqlWriter) {
        sql.push_param(value, self as _);
    }
}

#[cfg(test)]
mod tests {
    use {
        super::*,
        sea_query::{Alias, Expr, Query},
    };

    #[test]
    fn test_select_basic() {
        let query = Query::select()
            .column(Alias::new("name"))
            .from(Alias::new("users"))
            .to_string(SpannerQueryBuilder);

        assert_eq!(query, r#"SELECT `name` FROM `users`"#);
    }

    #[test]
    fn test_select_with_where() {
        let query = Query::select()
            .column(Alias::new("name"))
            .from(Alias::new("users"))
            .and_where(Expr::col(Alias::new("id")).eq(1))
            .to_string(SpannerQueryBuilder);

        assert_eq!(query, r#"SELECT `name` FROM `users` WHERE `id` = 1"#);
    }

    #[test]
    fn test_insert() {
        let query = Query::insert()
            .into_table(Alias::new("users"))
            .columns([Alias::new("name"), Alias::new("email")])
            .values_panic(["Alice".into(), "alice@example.com".into()])
            .to_string(SpannerQueryBuilder);

        assert_eq!(
            query,
            r#"INSERT INTO `users` (`name`, `email`) VALUES ('Alice', 'alice@example.com')"#
        );
    }

    #[test]
    fn test_update() {
        let query = Query::update()
            .table(Alias::new("users"))
            .value(Alias::new("name"), "Bob")
            .and_where(Expr::col(Alias::new("id")).eq(1))
            .to_string(SpannerQueryBuilder);

        assert_eq!(query, r#"UPDATE `users` SET `name` = 'Bob' WHERE `id` = 1"#);
    }

    #[test]
    fn test_delete() {
        let query = Query::delete()
            .from_table(Alias::new("users"))
            .and_where(Expr::col(Alias::new("id")).eq(1))
            .to_string(SpannerQueryBuilder);

        assert_eq!(query, r#"DELETE FROM `users` WHERE `id` = 1"#);
    }

    #[test]
    fn test_placeholder() {
        let builder = SpannerQueryBuilder::new();
        assert_eq!(builder.placeholder(), ("@p", true));
    }

    #[test]
    fn test_select_with_params() {
        let (sql, values) = Query::select()
            .column(Alias::new("name"))
            .from(Alias::new("users"))
            .and_where(Expr::col(Alias::new("id")).eq(1))
            .and_where(Expr::col(Alias::new("active")).eq(true))
            .build(SpannerQueryBuilder);

        assert_eq!(
            sql,
            r#"SELECT `name` FROM `users` WHERE (`id` = @p1) AND (`active` = @p2)"#
        );
        assert_eq!(values.0.len(), 2);
    }
}