Skip to main content

fraiseql_db/dialect/
sqlserver.rs

1//! SQL Server SQL dialect implementation.
2
3use std::borrow::Cow;
4
5use super::trait_def::{RowViewColumnType, SqlDialect, UnsupportedOperator};
6
7/// SQL Server dialect for [`GenericWhereGenerator`].
8///
9/// [`GenericWhereGenerator`]: crate::where_generator::GenericWhereGenerator
10pub struct SqlServerDialect;
11
12impl SqlDialect for SqlServerDialect {
13    fn name(&self) -> &'static str {
14        "SQL Server"
15    }
16
17    fn quote_identifier(&self, name: &str) -> String {
18        format!("[{}]", name.replace(']', "]]"))
19    }
20
21    fn json_extract_scalar(&self, column: &str, path: &[String]) -> String {
22        let json_path = crate::path_escape::escape_sqlserver_json_path(path);
23        format!("JSON_VALUE({column}, '{json_path}')")
24    }
25
26    fn placeholder(&self, n: usize) -> String {
27        format!("@p{n}")
28    }
29
30    fn cast_to_numeric<'a>(&self, expr: &'a str) -> Cow<'a, str> {
31        Cow::Owned(format!("CAST({expr} AS FLOAT)"))
32    }
33
34    fn like_sql(&self, lhs: &str, rhs: &str) -> String {
35        format!("{lhs} LIKE {rhs} COLLATE Latin1_General_CS_AS")
36    }
37
38    fn ilike_sql(&self, lhs: &str, rhs: &str) -> String {
39        format!("{lhs} LIKE {rhs} COLLATE Latin1_General_CI_AI")
40    }
41
42    fn concat_sql(&self, parts: &[&str]) -> String {
43        parts.join(" + ")
44    }
45
46    fn always_false(&self) -> &'static str {
47        "1=0"
48    }
49
50    fn always_true(&self) -> &'static str {
51        "1=1"
52    }
53
54    fn neq_operator(&self) -> &'static str {
55        "<>"
56    }
57
58    fn json_array_length(&self, expr: &str) -> String {
59        format!("(SELECT COUNT(*) FROM OPENJSON({expr}))")
60    }
61
62    fn array_contains_sql(&self, lhs: &str, rhs: &str) -> Result<String, UnsupportedOperator> {
63        Ok(format!("EXISTS (SELECT 1 FROM OPENJSON({lhs}) WHERE value = {rhs})"))
64    }
65
66    fn row_view_column_expr(
67        &self,
68        json_column: &str,
69        field_name: &str,
70        col_type: &RowViewColumnType,
71    ) -> String {
72        let tsql_type = match col_type {
73            RowViewColumnType::Text => "NVARCHAR(MAX)",
74            RowViewColumnType::Int32 => "INT",
75            RowViewColumnType::Int64 => "BIGINT",
76            RowViewColumnType::Float64 => "FLOAT",
77            RowViewColumnType::Boolean => "BIT",
78            RowViewColumnType::Uuid => "UNIQUEIDENTIFIER",
79            RowViewColumnType::Timestamptz => "DATETIMEOFFSET",
80            RowViewColumnType::Date => "DATE",
81            RowViewColumnType::Json => "NVARCHAR(MAX)",
82        };
83        format!("CAST(JSON_VALUE({json_column}, '$.{field_name}') AS {tsql_type})")
84    }
85
86    fn create_row_view_ddl(
87        &self,
88        view_name: &str,
89        source_table: &str,
90        columns: &[(String, String)],
91    ) -> String {
92        let quoted_view = self.quote_identifier(view_name);
93        let quoted_table = self.quote_identifier(source_table);
94        let col_list: Vec<String> = columns
95            .iter()
96            .map(|(alias, expr)| format!("{expr} AS {}", self.quote_identifier(alias)))
97            .collect();
98        format!(
99            "CREATE OR ALTER VIEW {quoted_view} AS\nSELECT\n  {}\nFROM {quoted_table};",
100            col_list.join(",\n  ")
101        )
102    }
103
104    fn fts_matches_sql(&self, expr: &str, param: &str) -> Result<String, UnsupportedOperator> {
105        Ok(format!("CONTAINS({expr}, {param})"))
106    }
107
108    fn fts_plain_query_sql(&self, expr: &str, param: &str) -> Result<String, UnsupportedOperator> {
109        Ok(format!("CONTAINS({expr}, {param})"))
110    }
111
112    fn fts_phrase_query_sql(&self, expr: &str, param: &str) -> Result<String, UnsupportedOperator> {
113        Ok(format!("FREETEXT({expr}, {param})"))
114    }
115}