pg2any_lib 0.11.1

PostgreSQL to Any database library with Change Data Capture (CDC) and logical replication support
Documentation
use crate::destinations::dialect::SqlDialect;
use pg_walstream::ColumnValue;

#[derive(Default, Debug, Clone, Copy)]
pub struct MySqlDialect;

impl SqlDialect for MySqlDialect {
    fn quote_identifier(&self, ident: &str, out: &mut String) {
        out.reserve(ident.len() + 2);
        out.push('`');
        if ident.contains('`') {
            for ch in ident.chars() {
                if ch == '`' {
                    out.push('`');
                }
                out.push(ch);
            }
        } else {
            out.push_str(ident);
        }
        out.push('`');
    }

    fn qualify_table(&self, schema: &str, table: &str, out: &mut String) {
        self.quote_identifier(schema, out);
        out.push('.');
        self.quote_identifier(table, out);
    }

    fn render_hex_literal(&self, bytes: &[u8], out: &mut String) {
        out.push_str("X'");
        crate::destinations::dialect::push_hex_ascii(out, bytes);
        out.push('\'');
    }

    fn render_value(&self, value: &ColumnValue, out: &mut String) {
        match value {
            ColumnValue::Null => out.push_str("NULL"),
            ColumnValue::Text(_) => match value.as_str() {
                Some(s) => {
                    if s == "t" {
                        out.push('1');
                        return;
                    }
                    if s == "f" {
                        out.push('0');
                        return;
                    }
                    out.reserve(s.len() + 2);
                    out.push('\'');
                    let needs_escape = s.contains(['\'', '\\']);
                    if needs_escape {
                        for ch in s.chars() {
                            match ch {
                                '\'' => out.push_str("''"),
                                '\\' => out.push_str("\\\\"),
                                _ => out.push(ch),
                            }
                        }
                    } else {
                        out.push_str(s);
                    }
                    out.push('\'');
                }
                None => self.render_hex_literal(value.as_bytes(), out),
            },
            ColumnValue::Binary(_) => self.render_hex_literal(value.as_bytes(), out),
        }
    }

    fn truncate_table_sql(&self, schema: &str, table: &str) -> Option<String> {
        let mut sql = String::new();
        sql.push_str("TRUNCATE TABLE ");
        self.qualify_table(schema, table, &mut sql);
        sql.push(';');
        Some(sql)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn s(f: impl Fn(&mut String)) -> String {
        let mut o = String::new();
        f(&mut o);
        o
    }

    #[test]
    fn quote_identifier() {
        assert_eq!(s(|o| MySqlDialect.quote_identifier("users", o)), "`users`");
        assert_eq!(
            s(|o| MySqlDialect.quote_identifier("back`tick", o)),
            "`back``tick`"
        );
        assert_eq!(
            s(|o| MySqlDialect.quote_identifier("bra]cket", o)),
            "`bra]cket`"
        );
        assert_eq!(
            s(|o| MySqlDialect.quote_identifier("double\"quote", o)),
            "`double\"quote`"
        );
    }

    #[test]
    fn qualify_table() {
        assert_eq!(
            s(|o| MySqlDialect.qualify_table("public", "users", o)),
            "`public`.`users`"
        );
        assert_eq!(
            s(|o| MySqlDialect.qualify_table("custom", "items", o)),
            "`custom`.`items`"
        );
    }

    #[test]
    fn render_hex_literal() {
        assert_eq!(s(|o| MySqlDialect.render_hex_literal(&[], o)), "X''");
        assert_eq!(
            s(|o| MySqlDialect.render_hex_literal(&[0xde, 0xad, 0xbe, 0xef], o)),
            "X'deadbeef'"
        );
    }

    #[test]
    fn render_value() {
        use bytes::Bytes;
        assert_eq!(
            s(|o| MySqlDialect.render_value(&ColumnValue::Null, o)),
            "NULL"
        );
        assert_eq!(
            s(|o| MySqlDialect.render_value(&ColumnValue::text("t"), o)),
            "1"
        );
        assert_eq!(
            s(|o| MySqlDialect.render_value(&ColumnValue::text("f"), o)),
            "0"
        );
        assert_eq!(
            s(|o| MySqlDialect.render_value(&ColumnValue::text("hello"), o)),
            "'hello'"
        );
        assert_eq!(
            s(|o| MySqlDialect.render_value(&ColumnValue::text("o'reilly"), o)),
            "'o''reilly'"
        );
        assert_eq!(
            s(|o| MySqlDialect.render_value(&ColumnValue::text("back\\slash"), o)),
            "'back\\\\slash'"
        );
        assert_eq!(
            s(|o| MySqlDialect.render_value(
                &ColumnValue::Binary(Bytes::from_static(&[0x00, 0xff, 0xab])),
                o
            )),
            "X'00ffab'"
        );
    }

    #[test]
    fn truncate_table_sql() {
        assert_eq!(
            MySqlDialect
                .truncate_table_sql("public", "users")
                .as_deref(),
            Some("TRUNCATE TABLE `public`.`users`;")
        );
    }
}