polyglot-sql 0.3.11

SQL parsing, validating, formatting, and dialect translation library
Documentation
use polyglot_sql::dialects::Dialect;
use polyglot_sql::{parse, DialectType};

fn assert_clickhouse_parse(sql: &str) {
    let parsed = parse(sql, DialectType::ClickHouse).expect("ClickHouse SQL should parse");
    assert_eq!(parsed.len(), 1);
}

fn normalize_clickhouse(sql: &str) -> String {
    let dialect = Dialect::get(DialectType::ClickHouse);
    let parsed = dialect.parse(sql).expect("ClickHouse SQL should parse");
    let statement = parsed.first().expect("expected a statement");
    let transformed = dialect
        .transform(statement.clone())
        .expect("ClickHouse transform should succeed");
    dialect
        .generate_with_source(&transformed, DialectType::ClickHouse)
        .expect("ClickHouse generation should succeed")
}

fn assert_clickhouse_normalized_roundtrip(sql: &str) {
    let first = normalize_clickhouse(sql);
    let second = normalize_clickhouse(&first);

    assert_eq!(second, first);
}

#[test]
fn clickhouse_parses_array_literal_with_nested_subscript_and_cast() {
    assert_clickhouse_parse("SELECT [[1][1]]::Array(UInt32)");
}

#[test]
fn clickhouse_parses_array_literal_elements_with_type_casts() {
    assert_clickhouse_parse("SELECT [1::UInt32, 2::UInt32]::Array(UInt64)");
    assert_clickhouse_parse("SELECT [[1, 2]::Array(UInt32), [3]]::Array(Array(UInt64))");
}

#[test]
fn clickhouse_parses_nested_array_literal_subscript_chain() {
    assert_clickhouse_parse(
        "SELECT [[10, 2, 13, 15][toNullable(toLowCardinality(1))]][materialize(toLowCardinality(1))]",
    );
}

#[test]
fn clickhouse_parses_array_literal_followed_by_multiple_casts() {
    assert_clickhouse_parse("SELECT [[[1, 2, 3]::Array(UInt64)::Dynamic]]");
}

#[test]
fn clickhouse_recovers_terminal_unterminated_string_probe() {
    assert_eq!(
        normalize_clickhouse("select 'select json"),
        "SELECT 'select json'"
    );
}

#[test]
fn clickhouse_normalizes_ctas_without_repeated_parentheses() {
    let sql = "CREATE TABLE x ENGINE=Memory AS (SELECT 1)";
    let first = normalize_clickhouse(sql);
    let second = normalize_clickhouse(&first);

    assert_eq!(first, "CREATE TABLE x ENGINE=Memory AS (SELECT 1)");
    assert_eq!(second, first);
}

#[test]
fn clickhouse_preserves_table_function_ctas_source() {
    assert_eq!(
        normalize_clickhouse("CREATE TABLE t AS numbers(5)"),
        "CREATE TABLE t AS numbers(5)"
    );
}

#[test]
fn clickhouse_bare_with_totals_stays_parseable() {
    assert_eq!(
        normalize_clickhouse("SELECT count() WITH TOTALS"),
        "SELECT count() WITH TOTALS"
    );
}

#[test]
fn clickhouse_preserves_quoted_dotted_array_join_alias() {
    assert_eq!(
        normalize_clickhouse("SELECT x FROM t ARRAY JOIN s.a AS `s.a`"),
        "SELECT x FROM t ARRAY JOIN s.a AS \"s.a\""
    );
}

#[test]
fn clickhouse_preserves_quoted_database_names() {
    assert_eq!(
        normalize_clickhouse("CREATE DATABASE `this.is.a.valid.databasename`"),
        "CREATE DATABASE \"this.is.a.valid.databasename\""
    );
}

#[test]
fn clickhouse_preserves_incomplete_insert_probe_as_command() {
    assert_eq!(normalize_clickhouse("INSERT INTO t0"), "INSERT INTO t0");
}

#[test]
fn clickhouse_recovers_missing_terminal_rparen_for_extracted_subquery() {
    assert_eq!(
        normalize_clickhouse("SELECT count() FROM (SELECT 1"),
        "SELECT count() FROM (SELECT 1)"
    );
}

#[test]
fn clickhouse_preserves_sample_clause_keyword() {
    assert_eq!(
        normalize_clickhouse("SELECT count() FROM t SAMPLE 0.1"),
        "SELECT count() FROM t SAMPLE 0.1"
    );
}

#[test]
fn clickhouse_ttl_set_clause_stays_stable() {
    let sql = "CREATE TABLE t (key Int, date Date, value String) ENGINE = MergeTree() ORDER BY key TTL date + INTERVAL 2 MONTH GROUP BY key SET value = argMax(value, date)";

    assert_clickhouse_normalized_roundtrip(sql);
    assert_eq!(
        normalize_clickhouse(sql),
        "CREATE TABLE t (key Int32, date DATE, value String) ENGINE=MergeTree() ORDER BY key TTL date + INTERVAL '2' MONTH GROUP BY key SET value = argMax(value, date)"
    );
}

#[test]
fn clickhouse_preserves_enum8_custom_type_name() {
    assert_eq!(
        normalize_clickhouse("SELECT CAST(x AS Enum8('hello' = -123, 'world'))"),
        "SELECT CAST(x AS Enum8('hello' = -123, 'world'))"
    );
}

#[test]
fn clickhouse_enum8_custom_type_suffix_escapes_control_characters() {
    let sql = "SELECT CAST(x AS Enum8('a\tb' = 1, 'c\\\\d' = 2))";

    assert_clickhouse_normalized_roundtrip(sql);
}

#[test]
fn clickhouse_preserves_partial_with_probe_as_command() {
    assert_eq!(normalize_clickhouse("WITH build AS ("), "WITH build AS (");
}

#[test]
fn clickhouse_preserves_alter_update_mutation_as_command() {
    assert_eq!(
        normalize_clickhouse("ALTER TABLE tab UPDATE str = 'I am not inverted' WHERE 1"),
        "ALTER TABLE tab UPDATE str = 'I am not inverted' WHERE 1"
    );
}