qail_core/transpiler/
traits.rs

1//! Transpiler traits and utilities.
2
3/// SQL reserved words that must be quoted when used as identifiers.
4pub const RESERVED_WORDS: &[&str] = &[
5    "order", "group", "user", "table", "select", "from", "where", "join",
6    "left", "right", "inner", "outer", "on", "and", "or", "not", "null",
7    "true", "false", "limit", "offset", "as", "in", "is", "like", "between",
8    "having", "union", "all", "distinct", "case", "when", "then", "else", "end",
9    "create", "alter", "drop", "insert", "update", "delete", "index", "key",
10    "primary", "foreign", "references", "default", "constraint", "check",
11];
12
13/// Escape an identifier if it's a reserved word or contains special chars.
14/// Returns the identifier quoted with double quotes if needed.
15pub fn escape_identifier(name: &str) -> String {
16    let lower = name.to_lowercase();
17    let needs_escaping = RESERVED_WORDS.contains(&lower.as_str())
18        || name.chars().any(|c| !c.is_alphanumeric() && c != '_')
19        || name.chars().next().map(|c| c.is_numeric()).unwrap_or(false);
20    
21    if needs_escaping {
22        format!("\"{}\"", name.replace('"', "\"\""))
23    } else {
24        name.to_string()
25    }
26}
27
28/// Trait for dialect-specific SQL generation.
29pub trait SqlGenerator {
30    /// Quote an identifier (table or column name).
31    fn quote_identifier(&self, name: &str) -> String;
32    /// Generate the parameter placeholder (e.g., $1, ?, @p1) for a given index.
33    fn placeholder(&self, index: usize) -> String;
34    /// Get the fuzzy matching operator (ILIKE vs LIKE).
35    fn fuzzy_operator(&self) -> &str;
36    /// Get the boolean literal (true/false vs 1/0).
37    fn bool_literal(&self, val: bool) -> String;
38    /// Generate string concatenation expression (e.g. 'a' || 'b' vs CONCAT('a', 'b')).
39    fn string_concat(&self, parts: &[&str]) -> String;
40    /// Generate LIMIT/OFFSET clause.
41    fn limit_offset(&self, limit: Option<usize>, offset: Option<usize>) -> String;
42    /// Generate JSON access syntax.
43    /// path components are the keys to traverse.
44    /// Default implementation returns "col"."key1"."key2" (Standard SQL composite).
45    fn json_access(&self, col: &str, path: &[&str]) -> String {
46        let mut parts = vec![self.quote_identifier(col)];
47        for key in path {
48            parts.push(self.quote_identifier(key));
49        }
50        parts.join(".")
51    }
52    /// Generate JSON/Array contains expression.
53    /// Default implementation returns Postgres-compatible `col @> value`.
54    fn json_contains(&self, col: &str, value: &str) -> String {
55        format!("{} @> {}", col, value)
56    }
57    /// Generate JSON key exists expression.
58    /// Default implementation returns Postgres-compatible `col ? 'key'`.
59    fn json_key_exists(&self, col: &str, key: &str) -> String {
60        format!("{} ? {}", col, key)
61    }
62    
63    /// JSON_EXISTS - check if path exists in JSON (Postgres 17+, SQL/JSON standard)
64    fn json_exists(&self, col: &str, path: &str) -> String {
65        format!("JSON_EXISTS({}, '{}')", col, path)
66    }
67    
68    /// JSON_QUERY - extract JSON object/array at path (Postgres 17+, SQL/JSON standard)
69    fn json_query(&self, col: &str, path: &str) -> String {
70        format!("JSON_QUERY({}, '{}')", col, path)
71    }
72    
73    /// JSON_VALUE - extract scalar value at path (Postgres 17+, SQL/JSON standard)
74    fn json_value(&self, col: &str, path: &str) -> String {
75        format!("JSON_VALUE({}, '{}')", col, path)
76    }
77}