Skip to main content

teaql_sql/
dialect.rs

1use teaql_core::{
2    AggregateFunction, BinaryOp, DataType, DeleteCommand, EntityDescriptor, Expr, ExprFunction,
3    OrderBy, PropertyDescriptor, RecoverCommand, SelectQuery, SortDirection,
4    Value,
5};
6
7use crate::{CompiledQuery, DatabaseKind, SqlCompileError};
8
9const SQL_KEYWORDS: &[&str] = &[
10    "all", "alter", "and", "as", "asc", "between", "by", "case", "create", "delete", "desc",
11    "distinct", "drop", "exists", "false", "from", "group", "having", "in", "insert", "into", "is",
12    "join", "like", "limit", "not", "null", "offset", "on", "or", "order", "select", "set",
13    "table", "true", "type", "union", "update", "values", "where",
14];
15
16pub fn quote_identifier_if_needed(ident: &str, quote: char) -> String {
17    if is_wrapped_identifier(ident) {
18        return ident.to_owned();
19    }
20    if needs_quoted_identifier(ident) {
21        let quote_string = quote.to_string();
22        let escaped = ident.replace(quote, &(quote_string.clone() + &quote_string));
23        return format!("{quote}{escaped}{quote}");
24    }
25    ident.to_owned()
26}
27
28fn is_wrapped_identifier(ident: &str) -> bool {
29    (ident.starts_with('"') && ident.ends_with('"'))
30        || (ident.starts_with('`') && ident.ends_with('`'))
31        || (ident.starts_with('[') && ident.ends_with(']'))
32}
33
34fn needs_quoted_identifier(ident: &str) -> bool {
35    if ident.is_empty()
36        || SQL_KEYWORDS
37            .binary_search(&ident.to_ascii_lowercase().as_str())
38            .is_ok()
39    {
40        return true;
41    }
42    let mut chars = ident.chars();
43    match chars.next() {
44        Some(first) if first == '_' || first.is_ascii_alphabetic() => {}
45        _ => return true,
46    }
47    chars.any(|ch| ch != '_' && !ch.is_ascii_alphanumeric())
48}
49
50
51pub trait SqlDialect {
52    fn kind(&self) -> DatabaseKind;
53    fn quote_ident(&self, ident: &str) -> String;
54    fn placeholder(&self, index: usize) -> String;
55
56    fn schema_setup_sqls(&self) -> &'static [&'static str] {
57        &[]
58    }
59
60    fn schema_type_sql(
61        &self,
62        data_type: DataType,
63        _property: &PropertyDescriptor,
64    ) -> Result<&'static str, SqlCompileError> {
65        match data_type {
66            DataType::Bool => Ok("BOOLEAN"),
67            DataType::I64 | DataType::U64 => Ok("INTEGER"),
68            DataType::F64 => Ok("REAL"),
69            DataType::Decimal => Ok("NUMERIC"),
70            DataType::Text | DataType::Json | DataType::Date | DataType::Timestamp => Ok("TEXT"),
71        }
72    }
73
74    fn column_definition_sql(
75        &self,
76        property: &PropertyDescriptor,
77    ) -> Result<String, SqlCompileError> {
78        let mut parts = vec![
79            self.quote_ident(&property.column_name),
80            self.schema_type_sql(property.data_type, property)?
81                .to_owned(),
82        ];
83
84        if property.is_id {
85            parts.push("PRIMARY KEY".to_owned());
86        }
87        if property.is_id || !property.nullable {
88            parts.push("NOT NULL".to_owned());
89        }
90
91        Ok(parts.join(" "))
92    }
93
94    fn compile_create_table(&self, entity: &EntityDescriptor) -> Result<String, SqlCompileError> {
95        let columns = entity
96            .properties
97            .iter()
98            .map(|property| self.column_definition_sql(property))
99            .collect::<Result<Vec<_>, _>>()?
100            .join(", ");
101        Ok(format!(
102            "CREATE TABLE IF NOT EXISTS {} ({columns})",
103            self.quote_ident(&entity.table_name)
104        ))
105    }
106
107    fn compile_add_column(
108        &self,
109        entity: &EntityDescriptor,
110        property: &PropertyDescriptor,
111    ) -> Result<String, SqlCompileError> {
112        Ok(format!(
113            "ALTER TABLE {} ADD COLUMN {}",
114            self.quote_ident(&entity.table_name),
115            self.column_definition_sql(property)?
116        ))
117    }
118
119    fn compile_select(
120        &self,
121        entity: &EntityDescriptor,
122        query: &SelectQuery,
123    ) -> Result<CompiledQuery, SqlCompileError> {
124        let mut params = Vec::new();
125        let sql = self.compile_select_sql(entity, query, &mut params)?;
126        Ok(CompiledQuery {
127            sql,
128            params,
129            comment: query.comment.clone(),
130        })
131    }
132
133    fn compile_select_sql(
134        &self,
135        entity: &EntityDescriptor,
136        query: &SelectQuery,
137        params: &mut Vec<Value>,
138    ) -> Result<String, SqlCompileError> {
139        if let Some(raw_sql) = &query.raw_sql {
140            return Ok(raw_sql.clone());
141        }
142
143        let projection = if query.aggregates.is_empty() {
144            self.select_projection(entity, query, params)?
145        } else {
146            self.aggregate_projection(entity, query, params)?
147        };
148
149        let mut sql = format!(
150            "SELECT {projection} FROM {}",
151            self.quote_ident(&entity.table_name)
152        );
153
154        let mut where_parts = Vec::new();
155        if let Some(filter) = &query.filter {
156            where_parts.push(self.compile_expr(entity, filter, params)?);
157        }
158        
159        if let Some(search_text) = &query.search_with_text {
160            let mut or_parts = Vec::new();
161            let like_value = format!("%{}%", search_text);
162            for property in &entity.properties {
163                if property.data_type == teaql_core::DataType::Text {
164                    params.push(teaql_core::Value::from(like_value.clone()));
165                    or_parts.push(format!("{} LIKE {}", self.quote_ident(&property.column_name), self.placeholder(params.len())));
166                }
167            }
168            if !or_parts.is_empty() {
169                where_parts.push(format!("({})", or_parts.join(" OR ")));
170            }
171        }
172        
173        where_parts.extend(query.raw_sql_search_criteria.iter().cloned());
174        if !where_parts.is_empty() {
175            sql.push_str(" WHERE ");
176            sql.push_str(&where_parts.join(" AND "));
177        }
178
179        if !query.group_by.is_empty() {
180            let group_by = query
181                .group_by
182                .iter()
183                .map(|field| self.column_sql(entity, field))
184                .collect::<Result<Vec<_>, _>>()?
185                .join(", ");
186            sql.push_str(" GROUP BY ");
187            sql.push_str(&group_by);
188        }
189
190        if let Some(having) = &query.having {
191            let having_sql = self.compile_expr(entity, having, params)?;
192            sql.push_str(" HAVING ");
193            sql.push_str(&having_sql);
194        }
195
196        if !query.order_by.is_empty() {
197            let order_by = query
198                .order_by
199                .iter()
200                .map(|order| self.order_by_sql(entity, order, params))
201                .collect::<Result<Vec<_>, _>>()?
202                .join(", ");
203            sql.push_str(" ORDER BY ");
204            sql.push_str(&order_by);
205        }
206
207        if let Some(slice) = query.slice {
208            if let Some(limit) = slice.limit {
209                sql.push_str(&format!(" LIMIT {limit}"));
210            }
211            if slice.offset > 0 {
212                sql.push_str(&format!(" OFFSET {}", slice.offset));
213            }
214        }
215
216        Ok(sql)
217    }
218
219    fn compile_insert(
220        &self,
221        entity: &EntityDescriptor,
222        command: &teaql_core::InsertCommand,
223    ) -> Result<CompiledQuery, SqlCompileError> {
224        let mut columns = Vec::new();
225        let mut placeholders = Vec::new();
226        let mut params = Vec::new();
227
228        for property in &entity.properties {
229            if let Some(value) = command.values.get(&property.name) {
230                columns.push(self.quote_ident(&property.column_name));
231                params.push(value.clone());
232                placeholders.push(self.placeholder(params.len()));
233            }
234        }
235
236        if columns.is_empty() {
237            return Err(SqlCompileError::EmptyMutation("insert".to_owned()));
238        }
239
240        Ok(CompiledQuery {
241            sql: format!(
242                "INSERT INTO {} ({}) VALUES ({})",
243                self.quote_ident(&entity.table_name),
244                columns.join(", "),
245                placeholders.join(", ")
246            ),
247            params,
248            comment: None,
249        })
250    }
251
252    fn compile_batch_insert(
253        &self,
254        entity: &EntityDescriptor,
255        command: &teaql_core::BatchInsertCommand,
256    ) -> Result<CompiledQuery, SqlCompileError> {
257        if command.batch_values.is_empty() {
258            return Err(SqlCompileError::EmptyMutation("batch_insert".to_owned()));
259        }
260        
261        let mut columns = Vec::new();
262        let first_record = &command.batch_values[0];
263        
264        for property in &entity.properties {
265            if first_record.contains_key(&property.name) {
266                columns.push(property.clone());
267            }
268        }
269        
270        if columns.is_empty() {
271            return Err(SqlCompileError::EmptyMutation("batch_insert".to_owned()));
272        }
273        
274        let column_names: Vec<String> = columns.iter().map(|p| self.quote_ident(&p.column_name)).collect();
275        let mut params = Vec::new();
276        let mut values_clauses = Vec::new();
277        
278        for record in &command.batch_values {
279            let mut row_placeholders = Vec::new();
280            for property in &columns {
281                let value = record.get(&property.name).cloned().unwrap_or(teaql_core::Value::Null);
282                params.push(value);
283                row_placeholders.push(self.placeholder(params.len()));
284            }
285            values_clauses.push(format!("({})", row_placeholders.join(", ")));
286        }
287        
288        Ok(CompiledQuery {
289            sql: format!(
290                "INSERT INTO {} ({}) VALUES {}",
291                self.quote_ident(&entity.table_name),
292                column_names.join(", "),
293                values_clauses.join(", ")
294            ),
295            params,
296            comment: None,
297        })
298    }
299
300    fn compile_update(
301        &self,
302        entity: &EntityDescriptor,
303        command: &teaql_core::UpdateCommand,
304    ) -> Result<CompiledQuery, SqlCompileError> {
305        let id_property = entity
306            .id_property()
307            .ok_or_else(|| SqlCompileError::MissingIdProperty(entity.name.clone()))?;
308        let mut assignments = Vec::new();
309        let mut params = Vec::new();
310
311        for property in &entity.properties {
312            if property.is_id {
313                continue;
314            }
315            if property.is_version && command.expected_version.is_some() {
316                continue;
317            }
318            if let Some(value) = command.values.get(&property.name) {
319                params.push(value.clone());
320                assignments.push(format!(
321                    "{} = {}",
322                    self.quote_ident(&property.column_name),
323                    self.placeholder(params.len())
324                ));
325            }
326        }
327
328        if let Some(expected_version) = command.expected_version {
329            let version_property = entity
330                .version_property()
331                .ok_or_else(|| SqlCompileError::MissingVersionProperty(entity.name.clone()))?;
332            params.push(Value::I64(expected_version + 1));
333            assignments.push(format!(
334                "{} = {}",
335                self.quote_ident(&version_property.column_name),
336                self.placeholder(params.len())
337            ));
338        }
339
340        if assignments.is_empty() {
341            return Err(SqlCompileError::EmptyMutation("update".to_owned()));
342        }
343
344        params.push(command.id.clone());
345        let mut predicates = vec![format!(
346            "{} = {}",
347            self.quote_ident(&id_property.column_name),
348            self.placeholder(params.len())
349        )];
350
351        if let Some(expected_version) = command.expected_version {
352            let version_property = entity
353                .version_property()
354                .ok_or_else(|| SqlCompileError::MissingVersionProperty(entity.name.clone()))?;
355            params.push(Value::I64(expected_version));
356            predicates.push(format!(
357                "{} = {}",
358                self.quote_ident(&version_property.column_name),
359                self.placeholder(params.len())
360            ));
361        }
362
363        Ok(CompiledQuery {
364            sql: format!(
365                "UPDATE {} SET {} WHERE {}",
366                self.quote_ident(&entity.table_name),
367                assignments.join(", "),
368                predicates.join(" AND ")
369            ),
370            params,
371            comment: None,
372        })
373    }
374
375    fn compile_batch_update(
376        &self,
377        entity: &EntityDescriptor,
378        command: &teaql_core::BatchUpdateCommand,
379    ) -> Result<CompiledQuery, SqlCompileError> {
380        if command.batch_values.is_empty() {
381            return Err(SqlCompileError::EmptyMutation("batch_update".to_owned()));
382        }
383        
384        let id_property = entity
385            .id_property()
386            .ok_or_else(|| SqlCompileError::MissingIdProperty(entity.name.clone()))?;
387            
388        let mut params = Vec::new();
389        let mut set_clauses = Vec::new();
390        
391        // Build CASE statement for each updated field
392        for field_name in &command.update_fields {
393            let property = entity.property_by_name(field_name)
394                .ok_or_else(|| SqlCompileError::UnknownField(field_name.clone()))?;
395                
396            let mut case_parts = Vec::new();
397            case_parts.push(format!("CASE {}", self.quote_ident(&id_property.column_name)));
398            
399            for (i, record) in command.batch_values.iter().enumerate() {
400                let id = &command.batch_ids[i];
401                let val = record.get(field_name).cloned().unwrap_or(teaql_core::Value::Null);
402                
403                params.push(id.clone());
404                let id_ph = self.placeholder(params.len());
405                
406                params.push(val);
407                let val_ph = self.placeholder(params.len());
408                
409                case_parts.push(format!("WHEN {} THEN {}", id_ph, val_ph));
410            }
411            
412            case_parts.push(format!("ELSE {} END", self.quote_ident(&property.column_name)));
413            set_clauses.push(format!("{} = {}", self.quote_ident(&property.column_name), case_parts.join(" ")));
414        }
415        
416        let mut has_versions = false;
417        if let Some(version_property) = entity.version_property() {
418            let mut case_parts = Vec::new();
419            case_parts.push(format!("CASE {}", self.quote_ident(&id_property.column_name)));
420            
421            for (i, exp_ver_opt) in command.batch_expected_versions.iter().enumerate() {
422                if let Some(exp_ver) = exp_ver_opt {
423                    has_versions = true;
424                    let id = &command.batch_ids[i];
425                    
426                    params.push(id.clone());
427                    let id_ph = self.placeholder(params.len());
428                    
429                    params.push(teaql_core::Value::I64(*exp_ver + 1));
430                    let val_ph = self.placeholder(params.len());
431                    
432                    case_parts.push(format!("WHEN {} THEN {}", id_ph, val_ph));
433                }
434            }
435            
436            if has_versions {
437                case_parts.push(format!("ELSE {} END", self.quote_ident(&version_property.column_name)));
438                set_clauses.push(format!("{} = {}", self.quote_ident(&version_property.column_name), case_parts.join(" ")));
439            }
440        }
441        
442        if set_clauses.is_empty() {
443            return Err(SqlCompileError::EmptyMutation("batch_update".to_owned()));
444        }
445        
446        let mut in_placeholders = Vec::new();
447        for id in &command.batch_ids {
448            params.push(id.clone());
449            in_placeholders.push(self.placeholder(params.len()));
450        }
451        let mut predicates = vec![format!(
452            "{} IN ({})",
453            self.quote_ident(&id_property.column_name),
454            in_placeholders.join(", ")
455        )];
456        
457        if has_versions {
458            let version_property = entity.version_property().unwrap();
459            let mut case_parts = Vec::new();
460            case_parts.push(format!("CASE {}", self.quote_ident(&id_property.column_name)));
461            
462            for (i, exp_ver_opt) in command.batch_expected_versions.iter().enumerate() {
463                if let Some(exp_ver) = exp_ver_opt {
464                    let id = &command.batch_ids[i];
465                    
466                    params.push(id.clone());
467                    let id_ph = self.placeholder(params.len());
468                    
469                    params.push(teaql_core::Value::I64(*exp_ver));
470                    let val_ph = self.placeholder(params.len());
471                    
472                    case_parts.push(format!("WHEN {} THEN {}", id_ph, val_ph));
473                }
474            }
475            case_parts.push(format!("ELSE {} END", self.quote_ident(&version_property.column_name)));
476            
477            predicates.push(format!(
478                "{} = {}",
479                self.quote_ident(&version_property.column_name),
480                case_parts.join(" ")
481            ));
482        }
483
484        Ok(CompiledQuery {
485            sql: format!(
486                "UPDATE {} SET {} WHERE {}",
487                self.quote_ident(&entity.table_name),
488                set_clauses.join(", "),
489                predicates.join(" AND ")
490            ),
491            params,
492            comment: None,
493        })
494    }
495
496    fn compile_delete(
497        &self,
498        entity: &EntityDescriptor,
499        command: &DeleteCommand,
500    ) -> Result<CompiledQuery, SqlCompileError> {
501        let id_property = entity
502            .id_property()
503            .ok_or_else(|| SqlCompileError::MissingIdProperty(entity.name.clone()))?;
504        let mut params = Vec::new();
505
506        if command.soft_delete {
507            let version_property = entity
508                .version_property()
509                .ok_or_else(|| SqlCompileError::MissingVersionProperty(entity.name.clone()))?;
510            params.push(match command.expected_version {
511                Some(version) => Value::I64(-(version + 1)),
512                None => Value::I64(-1),
513            });
514
515            params.push(command.id.clone());
516            let mut predicates = vec![format!(
517                "{} = {}",
518                self.quote_ident(&id_property.column_name),
519                self.placeholder(params.len())
520            )];
521
522            if let Some(expected_version) = command.expected_version {
523                params.push(Value::I64(expected_version));
524                predicates.push(format!(
525                    "{} = {}",
526                    self.quote_ident(&version_property.column_name),
527                    self.placeholder(params.len())
528                ));
529            }
530
531            return Ok(CompiledQuery {
532                sql: format!(
533                    "UPDATE {} SET {} = {} WHERE {}",
534                    self.quote_ident(&entity.table_name),
535                    self.quote_ident(&version_property.column_name),
536                    self.placeholder(1),
537                    predicates.join(" AND ")
538                ),
539                params,
540                comment: None,
541            });
542        }
543
544        params.push(command.id.clone());
545        let mut predicates = vec![format!(
546            "{} = {}",
547            self.quote_ident(&id_property.column_name),
548            self.placeholder(params.len())
549        )];
550
551        if let Some(expected_version) = command.expected_version {
552            let version_property = entity
553                .version_property()
554                .ok_or_else(|| SqlCompileError::MissingVersionProperty(entity.name.clone()))?;
555            params.push(Value::I64(expected_version));
556            predicates.push(format!(
557                "{} = {}",
558                self.quote_ident(&version_property.column_name),
559                self.placeholder(params.len())
560            ));
561        }
562
563        Ok(CompiledQuery {
564            sql: format!(
565                "DELETE FROM {} WHERE {}",
566                self.quote_ident(&entity.table_name),
567                predicates.join(" AND ")
568            ),
569            params,
570            comment: None,
571        })
572    }
573
574    fn compile_recover(
575        &self,
576        entity: &EntityDescriptor,
577        command: &RecoverCommand,
578    ) -> Result<CompiledQuery, SqlCompileError> {
579        if command.expected_version >= 0 {
580            return Err(SqlCompileError::InvalidRecoverVersion(
581                command.expected_version,
582            ));
583        }
584
585        let id_property = entity
586            .id_property()
587            .ok_or_else(|| SqlCompileError::MissingIdProperty(entity.name.clone()))?;
588        let version_property = entity
589            .version_property()
590            .ok_or_else(|| SqlCompileError::MissingVersionProperty(entity.name.clone()))?;
591        let params = vec![
592            Value::I64(-command.expected_version + 1),
593            command.id.clone(),
594            Value::I64(command.expected_version),
595        ];
596
597        Ok(CompiledQuery {
598            sql: format!(
599                "UPDATE {} SET {} = {} WHERE {} = {} AND {} = {}",
600                self.quote_ident(&entity.table_name),
601                self.quote_ident(&version_property.column_name),
602                self.placeholder(1),
603                self.quote_ident(&id_property.column_name),
604                self.placeholder(2),
605                self.quote_ident(&version_property.column_name),
606                self.placeholder(3),
607            ),
608            params,
609            comment: None,
610        })
611    }
612
613    fn column_sql(
614        &self,
615        entity: &EntityDescriptor,
616        field: &str,
617    ) -> Result<String, SqlCompileError> {
618        let property = entity
619            .property_by_name(field)
620            .ok_or_else(|| SqlCompileError::UnknownField(field.to_owned()))?;
621        Ok(self.quote_ident(&property.column_name))
622    }
623
624    fn order_by_sql(
625        &self,
626        entity: &EntityDescriptor,
627        order_by: &OrderBy,
628        params: &mut Vec<Value>,
629    ) -> Result<String, SqlCompileError> {
630        let field = if let Some(expr) = &order_by.expr {
631            self.compile_expr(entity, expr, params)?
632        } else {
633            self.column_sql(entity, &order_by.field)?
634        };
635        let direction = match order_by.direction {
636            SortDirection::Asc => "ASC",
637            SortDirection::Desc => "DESC",
638        };
639        Ok(format!("{field} {direction}"))
640    }
641
642    fn select_projection(
643        &self,
644        entity: &EntityDescriptor,
645        query: &SelectQuery,
646        params: &mut Vec<Value>,
647    ) -> Result<String, SqlCompileError> {
648        let property_projection = |property: &PropertyDescriptor| {
649            let column = self.quote_ident(&property.column_name);
650            if property.column_name == property.name {
651                column
652            } else {
653                format!("{column} AS {}", self.quote_ident(&property.name))
654            }
655        };
656
657        if query.projection.is_empty()
658            && query.expr_projection.is_empty()
659            && query.raw_projections.is_empty()
660            && query.dynamic_properties.is_empty()
661        {
662            return Ok(entity
663                .properties
664                .iter()
665                .map(property_projection)
666                .collect::<Vec<_>>()
667                .join(", "));
668        }
669        let mut parts = query
670            .projection
671            .iter()
672            .map(|field| {
673                let property = entity
674                    .property_by_name(field)
675                    .ok_or_else(|| SqlCompileError::UnknownField(field.to_owned()))?;
676                Ok(property_projection(property))
677            })
678            .collect::<Result<Vec<_>, _>>()?;
679        for projection in &query.expr_projection {
680            let expr = self.compile_expr(entity, &projection.expr, params)?;
681            parts.push(format!("{expr} AS {}", self.quote_ident(&projection.alias)));
682        }
683        for projection in query
684            .raw_projections
685            .iter()
686            .chain(query.dynamic_properties.iter())
687        {
688            parts.push(format!(
689                "{} AS {}",
690                projection.raw_sql_segment,
691                self.quote_ident(&projection.property_name)
692            ));
693        }
694        Ok(parts.join(", "))
695    }
696
697    fn aggregate_projection(
698        &self,
699        entity: &EntityDescriptor,
700        query: &SelectQuery,
701        params: &mut Vec<Value>,
702    ) -> Result<String, SqlCompileError> {
703        let mut parts = Vec::new();
704        for field in query.group_by.iter().chain(query.projection.iter()) {
705            let column = self.column_sql(entity, field)?;
706            if !parts.contains(&column) {
707                parts.push(column);
708            }
709        }
710        for projection in &query.expr_projection {
711            let expr = self.compile_expr(entity, &projection.expr, params)?;
712            let aliased = format!("{expr} AS {}", self.quote_ident(&projection.alias));
713            if !parts.contains(&aliased) {
714                parts.push(aliased);
715            }
716        }
717        for projection in query
718            .raw_projections
719            .iter()
720            .chain(query.dynamic_properties.iter())
721        {
722            let aliased = format!(
723                "{} AS {}",
724                projection.raw_sql_segment,
725                self.quote_ident(&projection.property_name)
726            );
727            if !parts.contains(&aliased) {
728                parts.push(aliased);
729            }
730        }
731        parts.extend(
732            query
733                .aggregates
734                .iter()
735                .map(|aggregate| {
736                    let field = if aggregate.function == AggregateFunction::Count
737                        && aggregate.field == "*"
738                    {
739                        "*".to_owned()
740                    } else {
741                        self.column_sql(entity, &aggregate.field)?
742                    };
743                    let call = self.aggregate_call_sql(aggregate.function, &field);
744                    Ok(format!("{call} AS {}", self.quote_ident(&aggregate.alias)))
745                })
746                .collect::<Result<Vec<_>, _>>()?,
747        );
748        Ok(parts.join(", "))
749    }
750
751    fn aggregate_call_sql(&self, function: AggregateFunction, field: &str) -> String {
752        let function_sql = self.aggregate_function_sql(function);
753        format!("{function_sql}({field})")
754    }
755
756    fn aggregate_function_sql(&self, function: AggregateFunction) -> &'static str {
757        match function {
758            AggregateFunction::Count => "COUNT",
759            AggregateFunction::Sum => "SUM",
760            AggregateFunction::Avg => "AVG",
761            AggregateFunction::Min => "MIN",
762            AggregateFunction::Max => "MAX",
763            AggregateFunction::Stddev => "STDDEV",
764            AggregateFunction::StddevPop => "STDDEV_POP",
765            AggregateFunction::VarSamp => "VAR_SAMP",
766            AggregateFunction::VarPop => "VAR_POP",
767            AggregateFunction::BitAnd => "BIT_AND",
768            AggregateFunction::BitOr => "BIT_OR",
769            AggregateFunction::BitXor => "BIT_XOR",
770        }
771    }
772
773    fn compile_expr(
774        &self,
775        entity: &EntityDescriptor,
776        expr: &Expr,
777        params: &mut Vec<Value>,
778    ) -> Result<String, SqlCompileError> {
779        match expr {
780            Expr::Column(name) => self.column_sql(entity, name),
781            Expr::Value(value) => {
782                params.push(value.clone());
783                Ok(self.placeholder(params.len()))
784            }
785            Expr::Function { function, args } => {
786                self.compile_function(entity, *function, args, params)
787            }
788            Expr::Binary { left, op, right } => {
789                if matches!(
790                    op,
791                    BinaryOp::In | BinaryOp::NotIn | BinaryOp::InLarge | BinaryOp::NotInLarge
792                ) {
793                    return self.compile_in(entity, left, *op, right, params);
794                }
795                let lhs = self.compile_expr(entity, left, params)?;
796                let rhs = self.compile_expr(entity, right, params)?;
797                let op = match op {
798                    BinaryOp::Eq => "=",
799                    BinaryOp::Ne => "!=",
800                    BinaryOp::Gt => ">",
801                    BinaryOp::Gte => ">=",
802                    BinaryOp::Lt => "<",
803                    BinaryOp::Lte => "<=",
804                    BinaryOp::Like => "LIKE",
805                    BinaryOp::NotLike => "NOT LIKE",
806                    BinaryOp::In | BinaryOp::NotIn | BinaryOp::InLarge | BinaryOp::NotInLarge => {
807                        unreachable!()
808                    }
809                };
810                Ok(format!("({lhs} {op} {rhs})"))
811            }
812            Expr::SubQuery {
813                left,
814                op,
815                entity: sub_entity,
816                query,
817            } => self.compile_subquery(entity, left, *op, sub_entity, query, params),
818            Expr::Between { expr, lower, upper } => {
819                let expr = self.compile_expr(entity, expr, params)?;
820                let lower = self.compile_expr(entity, lower, params)?;
821                let upper = self.compile_expr(entity, upper, params)?;
822                Ok(format!("({expr} BETWEEN {lower} AND {upper})"))
823            }
824            Expr::IsNull(expr) => {
825                let expr = self.compile_expr(entity, expr, params)?;
826                Ok(format!("({expr} IS NULL)"))
827            }
828            Expr::IsNotNull(expr) => {
829                let expr = self.compile_expr(entity, expr, params)?;
830                Ok(format!("({expr} IS NOT NULL)"))
831            }
832            Expr::And(parts) => self.compile_joined(entity, parts, "AND", params),
833            Expr::Or(parts) => self.compile_joined(entity, parts, "OR", params),
834            Expr::Not(expr) => {
835                let expr = self.compile_expr(entity, expr, params)?;
836                Ok(format!("(NOT {expr})"))
837            }
838        }
839    }
840
841    fn compile_function(
842        &self,
843        entity: &EntityDescriptor,
844        function: ExprFunction,
845        args: &[Expr],
846        params: &mut Vec<Value>,
847    ) -> Result<String, SqlCompileError> {
848        match function {
849            ExprFunction::Soundex => {
850                let [arg] = args else {
851                    return Err(SqlCompileError::InvalidFunctionArguments(
852                        "SOUNDEX expects exactly one argument".to_owned(),
853                    ));
854                };
855                let arg = self.compile_expr(entity, arg, params)?;
856                Ok(format!("SOUNDEX({arg})"))
857            }
858            ExprFunction::Gbk => self.compile_gbk_function(entity, args, params),
859            ExprFunction::Count if args.is_empty() => Ok("COUNT(*)".to_owned()),
860            ExprFunction::Count => self.compile_single_arg_function(entity, "COUNT", args, params),
861            ExprFunction::Sum => self.compile_single_arg_function(entity, "SUM", args, params),
862            ExprFunction::Avg => self.compile_single_arg_function(entity, "AVG", args, params),
863            ExprFunction::Min => self.compile_single_arg_function(entity, "MIN", args, params),
864            ExprFunction::Max => self.compile_single_arg_function(entity, "MAX", args, params),
865            ExprFunction::Stddev => {
866                self.compile_single_arg_function(entity, "STDDEV", args, params)
867            }
868            ExprFunction::StddevPop => {
869                self.compile_single_arg_function(entity, "STDDEV_POP", args, params)
870            }
871            ExprFunction::VarSamp => {
872                self.compile_single_arg_function(entity, "VAR_SAMP", args, params)
873            }
874            ExprFunction::VarPop => {
875                self.compile_single_arg_function(entity, "VAR_POP", args, params)
876            }
877            ExprFunction::BitAnd => {
878                self.compile_single_arg_function(entity, "BIT_AND", args, params)
879            }
880            ExprFunction::BitOr => self.compile_single_arg_function(entity, "BIT_OR", args, params),
881            ExprFunction::BitXor => {
882                self.compile_single_arg_function(entity, "BIT_XOR", args, params)
883            }
884        }
885    }
886
887    fn compile_single_arg_function(
888        &self,
889        entity: &EntityDescriptor,
890        function: &str,
891        args: &[Expr],
892        params: &mut Vec<Value>,
893    ) -> Result<String, SqlCompileError> {
894        let [arg] = args else {
895            return Err(SqlCompileError::InvalidFunctionArguments(format!(
896                "{function} expects exactly one argument"
897            )));
898        };
899        let arg = self.compile_expr(entity, arg, params)?;
900        Ok(format!("{function}({arg})"))
901    }
902
903    /// Compile a GBK sort expression. The default implementation returns an error
904    /// because GBK encoding conversion is dialect-specific. PostgreSQL dialects
905    /// should override this to use `convert_to(arg, 'GBK')`.
906    fn compile_gbk_function(
907        &self,
908        entity: &EntityDescriptor,
909        args: &[Expr],
910        params: &mut Vec<Value>,
911    ) -> Result<String, SqlCompileError> {
912        let [arg] = args else {
913            return Err(SqlCompileError::InvalidFunctionArguments(
914                "GBK expects exactly one argument".to_owned(),
915            ));
916        };
917        // Default: pass through the column as-is (no GBK conversion).
918        // Dialects with GBK support (e.g. PostgreSQL) should override this method.
919        let arg = self.compile_expr(entity, arg, params)?;
920        Ok(arg)
921    }
922
923    fn compile_subquery(
924        &self,
925        entity: &EntityDescriptor,
926        left: &Expr,
927        op: BinaryOp,
928        sub_entity: &EntityDescriptor,
929        query: &SelectQuery,
930        params: &mut Vec<Value>,
931    ) -> Result<String, SqlCompileError> {
932        let lhs = self.compile_expr(entity, left, params)?;
933        let operator = match op {
934            BinaryOp::In | BinaryOp::InLarge => "IN",
935            BinaryOp::NotIn | BinaryOp::NotInLarge => "NOT IN",
936            _ => return Err(SqlCompileError::InvalidSubQueryOperator(format!("{op:?}"))),
937        };
938        let subquery = self.compile_select_sql(sub_entity, query, params)?;
939        Ok(format!("({lhs} {operator} ({subquery}))"))
940    }
941
942    fn compile_joined(
943        &self,
944        entity: &EntityDescriptor,
945        parts: &[Expr],
946        joiner: &str,
947        params: &mut Vec<Value>,
948    ) -> Result<String, SqlCompileError> {
949        let compiled = parts
950            .iter()
951            .map(|part| self.compile_expr(entity, part, params))
952            .collect::<Result<Vec<_>, _>>()?;
953        Ok(format!("({})", compiled.join(&format!(" {joiner} "))))
954    }
955
956    fn compile_in(
957        &self,
958        entity: &EntityDescriptor,
959        left: &Expr,
960        op: BinaryOp,
961        right: &Expr,
962        params: &mut Vec<Value>,
963    ) -> Result<String, SqlCompileError> {
964        let lhs = self.compile_expr(entity, left, params)?;
965        let operator = match op {
966            BinaryOp::In | BinaryOp::InLarge => "IN",
967            BinaryOp::NotIn | BinaryOp::NotInLarge => "NOT IN",
968            _ => unreachable!(),
969        };
970        match right {
971            Expr::Value(Value::List(values)) => {
972                if values.is_empty() {
973                    return Err(SqlCompileError::EmptyInList);
974                }
975                let mut placeholders = Vec::with_capacity(values.len());
976                for value in values {
977                    params.push(value.clone());
978                    placeholders.push(self.placeholder(params.len()));
979                }
980                Ok(format!("({lhs} {operator} ({}))", placeholders.join(", ")))
981            }
982            _ => {
983                let rhs = self.compile_expr(entity, right, params)?;
984                Ok(format!("({lhs} {operator} ({rhs}))"))
985            }
986        }
987    }
988}