use std::borrow::Cow;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RowViewColumnType {
Text,
Int32,
Int64,
Float64,
Boolean,
Uuid,
Timestamptz,
Json,
Date,
}
#[derive(Debug)]
pub struct UnsupportedOperator {
pub dialect: &'static str,
pub operator: &'static str,
}
impl std::fmt::Display for UnsupportedOperator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"operator `{}` is not supported by the {} dialect",
self.operator, self.dialect
)
}
}
impl std::error::Error for UnsupportedOperator {}
pub trait SqlDialect: Send + Sync + 'static {
fn name(&self) -> &'static str;
fn quote_identifier(&self, name: &str) -> String;
fn json_extract_scalar(&self, column: &str, path: &[String]) -> String;
fn placeholder(&self, n: usize) -> String;
fn cast_to_numeric<'a>(&self, expr: &'a str) -> Cow<'a, str> {
Cow::Borrowed(expr)
}
fn cast_to_boolean<'a>(&self, expr: &'a str) -> Cow<'a, str> {
Cow::Borrowed(expr)
}
fn cast_param_numeric<'a>(&self, placeholder: &'a str) -> Cow<'a, str> {
Cow::Borrowed(placeholder)
}
fn cast_native_param(&self, placeholder: &str, _native_type: &str) -> String {
placeholder.to_string()
}
fn like_sql(&self, lhs: &str, rhs: &str) -> String {
format!("{lhs} LIKE {rhs}")
}
fn ilike_sql(&self, lhs: &str, rhs: &str) -> String {
format!("LOWER({lhs}) LIKE LOWER({rhs})")
}
fn concat_sql(&self, parts: &[&str]) -> String {
parts.join(" || ")
}
fn always_false(&self) -> &'static str {
"FALSE"
}
fn always_true(&self) -> &'static str {
"TRUE"
}
fn neq_operator(&self) -> &'static str {
"!="
}
fn json_array_length(&self, expr: &str) -> String;
fn array_contains_sql(&self, _lhs: &str, _rhs: &str) -> Result<String, UnsupportedOperator> {
Err(UnsupportedOperator {
dialect: self.name(),
operator: "ArrayContains",
})
}
fn array_contained_by_sql(
&self,
_lhs: &str,
_rhs: &str,
) -> Result<String, UnsupportedOperator> {
Err(UnsupportedOperator {
dialect: self.name(),
operator: "ArrayContainedBy",
})
}
fn array_overlaps_sql(&self, _lhs: &str, _rhs: &str) -> Result<String, UnsupportedOperator> {
Err(UnsupportedOperator {
dialect: self.name(),
operator: "ArrayOverlaps",
})
}
fn fts_matches_sql(&self, _expr: &str, _param: &str) -> Result<String, UnsupportedOperator> {
Err(UnsupportedOperator {
dialect: self.name(),
operator: "Matches",
})
}
fn fts_plain_query_sql(
&self,
_expr: &str,
_param: &str,
) -> Result<String, UnsupportedOperator> {
Err(UnsupportedOperator {
dialect: self.name(),
operator: "PlainQuery",
})
}
fn fts_phrase_query_sql(
&self,
_expr: &str,
_param: &str,
) -> Result<String, UnsupportedOperator> {
Err(UnsupportedOperator {
dialect: self.name(),
operator: "PhraseQuery",
})
}
fn fts_websearch_query_sql(
&self,
_expr: &str,
_param: &str,
) -> Result<String, UnsupportedOperator> {
Err(UnsupportedOperator {
dialect: self.name(),
operator: "WebsearchQuery",
})
}
fn regex_sql(
&self,
_lhs: &str,
_rhs: &str,
_case_insensitive: bool,
_negate: bool,
) -> Result<String, UnsupportedOperator> {
Err(UnsupportedOperator {
dialect: self.name(),
operator: "Regex",
})
}
fn vector_distance_sql(
&self,
_pg_op: &str,
_lhs: &str,
_rhs: &str,
) -> Result<String, UnsupportedOperator> {
Err(UnsupportedOperator {
dialect: self.name(),
operator: "VectorDistance",
})
}
fn jaccard_distance_sql(&self, _lhs: &str, _rhs: &str) -> Result<String, UnsupportedOperator> {
Err(UnsupportedOperator {
dialect: self.name(),
operator: "JaccardDistance",
})
}
fn inet_check_sql(&self, _lhs: &str, _check_name: &str) -> Result<String, UnsupportedOperator> {
Err(UnsupportedOperator {
dialect: self.name(),
operator: "InetCheck",
})
}
fn inet_binary_sql(
&self,
_pg_op: &str,
_lhs: &str,
_rhs: &str,
) -> Result<String, UnsupportedOperator> {
Err(UnsupportedOperator {
dialect: self.name(),
operator: "InetBinaryOp",
})
}
fn ltree_binary_sql(
&self,
_pg_op: &str,
_lhs: &str,
_rhs: &str,
_rhs_type: &str,
) -> Result<String, UnsupportedOperator> {
Err(UnsupportedOperator {
dialect: self.name(),
operator: "LTreeBinaryOp",
})
}
fn ltree_any_lquery_sql(
&self,
_lhs: &str,
_placeholders: &[String],
) -> Result<String, UnsupportedOperator> {
Err(UnsupportedOperator {
dialect: self.name(),
operator: "MatchesAnyLquery",
})
}
fn ltree_depth_sql(
&self,
_op: &str,
_lhs: &str,
_rhs: &str,
) -> Result<String, UnsupportedOperator> {
Err(UnsupportedOperator {
dialect: self.name(),
operator: "LTreeDepth",
})
}
fn ltree_lca_sql(
&self,
_lhs: &str,
_placeholders: &[String],
) -> Result<String, UnsupportedOperator> {
Err(UnsupportedOperator {
dialect: self.name(),
operator: "Lca",
})
}
fn row_view_column_expr(
&self,
json_column: &str,
field_name: &str,
col_type: &RowViewColumnType,
) -> String {
let _ = (json_column, field_name, col_type);
panic!("{} dialect has not implemented row_view_column_expr", self.name())
}
fn create_row_view_ddl(
&self,
view_name: &str,
source_table: &str,
columns: &[(String, String)],
) -> String {
let quoted_view = self.quote_identifier(view_name);
let quoted_table = self.quote_identifier(source_table);
let col_list: Vec<String> = columns
.iter()
.map(|(alias, expr)| format!("{expr} AS {}", self.quote_identifier(alias)))
.collect();
format!(
"CREATE OR REPLACE VIEW {quoted_view} AS\nSELECT\n {}\nFROM {quoted_table};",
col_list.join(",\n ")
)
}
fn generate_extended_sql(
&self,
operator: &crate::filters::ExtendedOperator,
_field_sql: &str,
_params: &mut Vec<serde_json::Value>,
) -> fraiseql_error::Result<String> {
Err(fraiseql_error::FraiseQLError::validation(format!(
"Extended operator {operator} is not supported by the {} dialect",
self.name()
)))
}
}