fraiseql_db/dialect/
sqlserver.rs1use std::borrow::Cow;
4
5use super::trait_def::{RowViewColumnType, SqlDialect, UnsupportedOperator};
6
7pub 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}