Skip to main content

sql_orm_sqlserver/
compiler.rs

1use crate::quoting::{
2    quote_column_ref, quote_identifier, quote_table_ref, quote_table_reference, quote_table_source,
3};
4use sql_orm_core::{ColumnMetadata, ColumnValue, EntityMetadata, OrmError, SqlValue};
5use sql_orm_query::{
6    AggregateExpr, AggregateOrderBy, AggregatePredicate, AggregateProjection, AggregateQuery,
7    BinaryOp, CompiledQuery, CountQuery, DeleteQuery, ExistsQuery, Expr, InsertQuery, Join,
8    JoinType, OrderBy, Pagination, Predicate, Query, SelectProjection, SelectQuery, SortDirection,
9    TableRef, UnaryOp, UpdateQuery,
10};
11use std::collections::BTreeSet;
12
13#[derive(Debug, Default)]
14struct ParameterBuilder {
15    params: Vec<SqlValue>,
16}
17
18impl ParameterBuilder {
19    fn push(&mut self, value: SqlValue) -> String {
20        self.params.push(value);
21        format!("@P{}", self.params.len())
22    }
23
24    fn finish_read_only(self, sql: String) -> CompiledQuery {
25        CompiledQuery::read_only(sql, self.params)
26    }
27
28    fn finish_write(self, sql: String) -> CompiledQuery {
29        CompiledQuery::write(sql, self.params)
30    }
31}
32
33impl crate::SqlServerCompiler {
34    pub fn compile_query(query: &Query) -> Result<CompiledQuery, OrmError> {
35        match query {
36            Query::Select(query) => Self::compile_select(query),
37            Query::Aggregate(query) => Self::compile_aggregate(query),
38            Query::Exists(query) => Self::compile_exists(query),
39            Query::Insert(query) => Self::compile_insert(query),
40            Query::Update(query) => Self::compile_update(query),
41            Query::Delete(query) => Self::compile_delete(query),
42            Query::Count(query) => Self::compile_count(query),
43        }
44    }
45
46    pub fn compile_select(query: &SelectQuery) -> Result<CompiledQuery, OrmError> {
47        let mut parameters = ParameterBuilder::default();
48        let projection = compile_projection(&query.projection, &mut parameters)?;
49        let mut sql = format!(
50            "SELECT {projection} FROM {}",
51            quote_table_source(&query.from)?
52        );
53        sql.push_str(&compile_joins(&query.from, &query.joins, &mut parameters)?);
54
55        if let Some(predicate) = &query.predicate {
56            let predicate = compile_predicate(predicate, &mut parameters)?;
57            sql.push_str(" WHERE ");
58            sql.push_str(&predicate);
59        }
60
61        if !query.order_by.is_empty() {
62            sql.push_str(" ORDER BY ");
63            sql.push_str(&compile_order_by(&query.order_by)?);
64        }
65
66        if let Some(pagination) = query.pagination {
67            if query.order_by.is_empty() {
68                return Err(OrmError::compile(
69                    "SQL Server pagination requires ORDER BY before OFFSET/FETCH",
70                ));
71            }
72
73            sql.push(' ');
74            sql.push_str(&compile_pagination(pagination, &mut parameters));
75        }
76
77        Ok(parameters.finish_read_only(sql))
78    }
79
80    pub fn compile_insert(query: &InsertQuery) -> Result<CompiledQuery, OrmError> {
81        if query.values.is_empty() {
82            return Err(OrmError::compile(
83                "SQL Server insert compilation requires at least one value",
84            ));
85        }
86        validate_insert_query(query)?;
87
88        let mut parameters = ParameterBuilder::default();
89        let (columns, values) = compile_column_values(&query.values, &mut parameters)?;
90        let sql = format!(
91            "INSERT INTO {} ({columns}) OUTPUT INSERTED.* VALUES ({values})",
92            quote_table_ref(&query.into)?,
93        );
94
95        Ok(parameters.finish_write(sql))
96    }
97
98    pub fn compile_update(query: &UpdateQuery) -> Result<CompiledQuery, OrmError> {
99        if query.changes.is_empty() {
100            return Err(OrmError::compile(
101                "SQL Server update compilation requires at least one change",
102            ));
103        }
104        if query.predicate.is_none() && !query.allow_all_rows {
105            return Err(OrmError::compile(
106                "SQL Server update compilation requires a WHERE predicate or explicit allow_all_rows()",
107            ));
108        }
109        validate_update_query(query)?;
110
111        let mut parameters = ParameterBuilder::default();
112        let assignments = compile_assignments(&query.changes, &mut parameters)?;
113        let mut sql = format!(
114            "UPDATE {} SET {assignments} OUTPUT INSERTED.*",
115            quote_table_ref(&query.table)?,
116        );
117
118        if let Some(predicate) = &query.predicate {
119            let predicate = compile_predicate(predicate, &mut parameters)?;
120            sql.push_str(" WHERE ");
121            sql.push_str(&predicate);
122        }
123
124        Ok(parameters.finish_write(sql))
125    }
126
127    pub fn compile_delete(query: &DeleteQuery) -> Result<CompiledQuery, OrmError> {
128        if query.predicate.is_none() && !query.allow_all_rows {
129            return Err(OrmError::compile(
130                "SQL Server delete compilation requires a WHERE predicate or explicit allow_all_rows()",
131            ));
132        }
133
134        let mut parameters = ParameterBuilder::default();
135        let mut sql = format!("DELETE FROM {}", quote_table_ref(&query.from)?);
136
137        if let Some(predicate) = &query.predicate {
138            let predicate = compile_predicate(predicate, &mut parameters)?;
139            sql.push_str(" WHERE ");
140            sql.push_str(&predicate);
141        }
142
143        Ok(parameters.finish_write(sql))
144    }
145
146    pub fn compile_count(query: &CountQuery) -> Result<CompiledQuery, OrmError> {
147        let mut parameters = ParameterBuilder::default();
148        let mut sql = format!(
149            "SELECT COUNT(*) AS {} FROM {}",
150            quote_identifier("count")?,
151            quote_table_source(&query.from)?,
152        );
153
154        if let Some(predicate) = &query.predicate {
155            let predicate = compile_predicate(predicate, &mut parameters)?;
156            sql.push_str(" WHERE ");
157            sql.push_str(&predicate);
158        }
159
160        Ok(parameters.finish_read_only(sql))
161    }
162
163    pub fn compile_exists(query: &ExistsQuery) -> Result<CompiledQuery, OrmError> {
164        let mut parameters = ParameterBuilder::default();
165        let mut subquery = format!("SELECT 1 FROM {}", quote_table_source(&query.from)?);
166        subquery.push_str(&compile_joins(&query.from, &query.joins, &mut parameters)?);
167
168        if let Some(predicate) = &query.predicate {
169            let predicate = compile_predicate(predicate, &mut parameters)?;
170            subquery.push_str(" WHERE ");
171            subquery.push_str(&predicate);
172        }
173
174        let sql = format!(
175            "SELECT CASE WHEN EXISTS ({subquery}) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END AS {}",
176            quote_identifier("exists")?
177        );
178
179        Ok(parameters.finish_read_only(sql))
180    }
181
182    pub fn compile_aggregate(query: &AggregateQuery) -> Result<CompiledQuery, OrmError> {
183        validate_aggregate_query(query)?;
184
185        let mut parameters = ParameterBuilder::default();
186        let projection =
187            compile_aggregate_projection(&query.projection, &query.group_by, &mut parameters)?;
188        let mut sql = format!(
189            "SELECT {projection} FROM {}",
190            quote_table_source(&query.from)?
191        );
192        sql.push_str(&compile_joins(&query.from, &query.joins, &mut parameters)?);
193
194        if let Some(predicate) = &query.predicate {
195            let predicate = compile_predicate(predicate, &mut parameters)?;
196            sql.push_str(" WHERE ");
197            sql.push_str(&predicate);
198        }
199
200        if !query.group_by.is_empty() {
201            sql.push_str(" GROUP BY ");
202            sql.push_str(&compile_group_by(&query.group_by, &mut parameters)?);
203        }
204
205        if let Some(having) = &query.having {
206            let having = compile_aggregate_predicate(having, &query.group_by, &mut parameters)?;
207            sql.push_str(" HAVING ");
208            sql.push_str(&having);
209        }
210
211        if !query.order_by.is_empty() {
212            sql.push_str(" ORDER BY ");
213            sql.push_str(&compile_aggregate_order_by(
214                &query.order_by,
215                &query.group_by,
216                &mut parameters,
217            )?);
218        }
219
220        if let Some(pagination) = query.pagination {
221            if query.order_by.is_empty() {
222                return Err(OrmError::compile(
223                    "SQL Server aggregate pagination requires ORDER BY before OFFSET/FETCH",
224                ));
225            }
226
227            sql.push(' ');
228            sql.push_str(&compile_pagination(pagination, &mut parameters));
229        }
230
231        Ok(parameters.finish_read_only(sql))
232    }
233}
234
235fn validate_insert_query(query: &InsertQuery) -> Result<(), OrmError> {
236    let Some(metadata) = query.entity else {
237        return Err(OrmError::compile(
238            "SQL Server insert compilation requires entity metadata",
239        ));
240    };
241
242    if metadata.schema != query.into.schema || metadata.table != query.into.table {
243        return Err(OrmError::compile(format!(
244            "SQL Server insert target [{}].[{}] does not match entity metadata [{}].[{}]",
245            query.into.schema, query.into.table, metadata.schema, metadata.table
246        )));
247    }
248
249    let mut seen_columns = BTreeSet::new();
250    for value in &query.values {
251        if !seen_columns.insert(value.column_name) {
252            return Err(OrmError::compile(format!(
253                "SQL Server insert column `{}` is duplicated",
254                value.column_name
255            )));
256        }
257
258        let column = metadata.column(value.column_name).ok_or_else(|| {
259            OrmError::compile(format!(
260                "SQL Server insert column `{}` is not defined on entity `{}`",
261                value.column_name, metadata.rust_name
262            ))
263        })?;
264        validate_insert_column(metadata, column)?;
265    }
266
267    Ok(())
268}
269
270fn validate_insert_column(
271    metadata: &EntityMetadata,
272    column: &ColumnMetadata,
273) -> Result<(), OrmError> {
274    if column.rowversion {
275        return Err(OrmError::compile(format!(
276            "SQL Server insert column `{}` on entity `{}` is rowversion and cannot be inserted",
277            column.column_name, metadata.rust_name
278        )));
279    }
280    if column.is_computed() {
281        return Err(OrmError::compile(format!(
282            "SQL Server insert column `{}` on entity `{}` is computed and cannot be inserted",
283            column.column_name, metadata.rust_name
284        )));
285    }
286    if column.primary_key && column.identity.is_some() {
287        return Err(OrmError::compile(format!(
288            "SQL Server insert column `{}` on entity `{}` is an identity primary key and cannot be inserted",
289            column.column_name, metadata.rust_name
290        )));
291    }
292    if !column.insertable {
293        return Err(OrmError::compile(format!(
294            "SQL Server insert column `{}` on entity `{}` is not insertable",
295            column.column_name, metadata.rust_name
296        )));
297    }
298
299    Ok(())
300}
301
302fn validate_update_query(query: &UpdateQuery) -> Result<(), OrmError> {
303    let Some(metadata) = query.entity else {
304        return Err(OrmError::compile(
305            "SQL Server update compilation requires entity metadata",
306        ));
307    };
308
309    if metadata.schema != query.table.schema || metadata.table != query.table.table {
310        return Err(OrmError::compile(format!(
311            "SQL Server update target [{}].[{}] does not match entity metadata [{}].[{}]",
312            query.table.schema, query.table.table, metadata.schema, metadata.table
313        )));
314    }
315
316    let mut seen_columns = BTreeSet::new();
317    for change in &query.changes {
318        if !seen_columns.insert(change.column_name) {
319            return Err(OrmError::compile(format!(
320                "SQL Server update column `{}` is duplicated",
321                change.column_name
322            )));
323        }
324
325        let column = metadata.column(change.column_name).ok_or_else(|| {
326            OrmError::compile(format!(
327                "SQL Server update column `{}` is not defined on entity `{}`",
328                change.column_name, metadata.rust_name
329            ))
330        })?;
331        validate_update_column(metadata, column)?;
332    }
333
334    Ok(())
335}
336
337fn validate_update_column(
338    metadata: &EntityMetadata,
339    column: &ColumnMetadata,
340) -> Result<(), OrmError> {
341    if column.primary_key {
342        return Err(OrmError::compile(format!(
343            "SQL Server update column `{}` on entity `{}` is a primary key and cannot be updated",
344            column.column_name, metadata.rust_name
345        )));
346    }
347    if column.rowversion {
348        return Err(OrmError::compile(format!(
349            "SQL Server update column `{}` on entity `{}` is rowversion and cannot be updated",
350            column.column_name, metadata.rust_name
351        )));
352    }
353    if column.is_computed() {
354        return Err(OrmError::compile(format!(
355            "SQL Server update column `{}` on entity `{}` is computed and cannot be updated",
356            column.column_name, metadata.rust_name
357        )));
358    }
359    if !column.updatable {
360        return Err(OrmError::compile(format!(
361            "SQL Server update column `{}` on entity `{}` is not updatable",
362            column.column_name, metadata.rust_name
363        )));
364    }
365
366    Ok(())
367}
368
369fn validate_aggregate_query(query: &AggregateQuery) -> Result<(), OrmError> {
370    if query.projection.is_empty() {
371        return Err(OrmError::compile(
372            "SQL Server aggregate query compilation requires at least one projection",
373        ));
374    }
375
376    validate_aggregate_projection(&query.projection, &query.group_by)?;
377
378    if let Some(having) = &query.having {
379        validate_aggregate_predicate(having, &query.group_by)?;
380    }
381
382    for order in &query.order_by {
383        validate_aggregate_expr(&order.expr, &query.group_by)?;
384    }
385
386    Ok(())
387}
388
389fn compile_joins(
390    from: &TableRef,
391    joins: &[Join],
392    parameters: &mut ParameterBuilder,
393) -> Result<String, OrmError> {
394    let mut compiled = String::new();
395    let mut seen_tables = vec![*from];
396
397    for join in joins {
398        if seen_tables.contains(&join.table) {
399            return Err(OrmError::compile(
400                "SQL Server join compilation requires aliases for repeated table sources",
401            ));
402        }
403
404        seen_tables.push(join.table);
405        compiled.push(' ');
406        compiled.push_str(match join.join_type {
407            JoinType::Inner => "INNER JOIN ",
408            JoinType::Left => "LEFT JOIN ",
409        });
410        compiled.push_str(&quote_table_source(&join.table)?);
411        compiled.push_str(" ON ");
412        compiled.push_str(&compile_predicate(&join.on, parameters)?);
413    }
414
415    Ok(compiled)
416}
417
418fn compile_projection(
419    projection: &[SelectProjection],
420    parameters: &mut ParameterBuilder,
421) -> Result<String, OrmError> {
422    if projection.is_empty() {
423        return Ok("*".to_string());
424    }
425
426    let mut aliases = BTreeSet::new();
427    let parts = projection
428        .iter()
429        .map(|projection| {
430            let alias = projection.alias.as_deref().ok_or_else(|| {
431                OrmError::compile("SQL Server projection expressions require an explicit alias")
432            })?;
433            if alias.trim().is_empty() {
434                return Err(OrmError::compile(
435                    "SQL Server projection alias cannot be empty",
436                ));
437            }
438            if !aliases.insert(alias) {
439                return Err(OrmError::compile(format!(
440                    "SQL Server projection alias `{alias}` is duplicated"
441                )));
442            }
443
444            Ok(format!(
445                "{} AS {}",
446                compile_expr(&projection.expr, parameters)?,
447                quote_identifier(alias)?
448            ))
449        })
450        .collect::<Result<Vec<_>, _>>()?;
451    Ok(parts.join(", "))
452}
453
454fn compile_aggregate_projection(
455    projection: &[AggregateProjection],
456    group_by: &[Expr],
457    parameters: &mut ParameterBuilder,
458) -> Result<String, OrmError> {
459    let mut aliases = BTreeSet::new();
460    let parts = projection
461        .iter()
462        .map(|projection| {
463            if projection.alias.trim().is_empty() {
464                return Err(OrmError::compile(
465                    "SQL Server aggregate projection alias cannot be empty",
466                ));
467            }
468            if !aliases.insert(projection.alias) {
469                return Err(OrmError::compile(format!(
470                    "SQL Server aggregate projection alias `{}` is duplicated",
471                    projection.alias
472                )));
473            }
474
475            Ok(format!(
476                "{} AS {}",
477                compile_aggregate_expr(&projection.expr, group_by, parameters)?,
478                quote_identifier(projection.alias)?
479            ))
480        })
481        .collect::<Result<Vec<_>, OrmError>>()?;
482    Ok(parts.join(", "))
483}
484
485fn validate_aggregate_projection(
486    projection: &[AggregateProjection],
487    group_by: &[Expr],
488) -> Result<(), OrmError> {
489    let mut aliases = BTreeSet::new();
490
491    for projection in projection {
492        if projection.alias.trim().is_empty() {
493            return Err(OrmError::compile(
494                "SQL Server aggregate projection alias cannot be empty",
495            ));
496        }
497        if !aliases.insert(projection.alias) {
498            return Err(OrmError::compile(format!(
499                "SQL Server aggregate projection alias `{}` is duplicated",
500                projection.alias
501            )));
502        }
503
504        validate_aggregate_expr(&projection.expr, group_by)?;
505    }
506
507    Ok(())
508}
509
510fn validate_aggregate_expr(expr: &AggregateExpr, group_by: &[Expr]) -> Result<(), OrmError> {
511    match expr {
512        AggregateExpr::GroupKey(expr) => validate_group_key(expr, group_by),
513        AggregateExpr::CountAll
514        | AggregateExpr::Count(_)
515        | AggregateExpr::Sum(_)
516        | AggregateExpr::Avg(_)
517        | AggregateExpr::Min(_)
518        | AggregateExpr::Max(_) => Ok(()),
519    }
520}
521
522fn validate_aggregate_predicate(
523    predicate: &AggregatePredicate,
524    group_by: &[Expr],
525) -> Result<(), OrmError> {
526    match predicate {
527        AggregatePredicate::Eq(left, right)
528        | AggregatePredicate::Ne(left, right)
529        | AggregatePredicate::Gt(left, right)
530        | AggregatePredicate::Gte(left, right)
531        | AggregatePredicate::Lt(left, right)
532        | AggregatePredicate::Lte(left, right) => {
533            validate_aggregate_expr(left, group_by)?;
534            validate_non_aggregate_expr_in_grouped_context(right, group_by)
535        }
536        AggregatePredicate::And(predicates) | AggregatePredicate::Or(predicates) => {
537            if predicates.is_empty() {
538                return Err(OrmError::compile(
539                    "aggregate logical predicate compilation requires at least one child predicate",
540                ));
541            }
542
543            for predicate in predicates {
544                validate_aggregate_predicate(predicate, group_by)?;
545            }
546            Ok(())
547        }
548        AggregatePredicate::Not(predicate) => validate_aggregate_predicate(predicate, group_by),
549    }
550}
551
552fn validate_non_aggregate_expr_in_grouped_context(
553    expr: &Expr,
554    group_by: &[Expr],
555) -> Result<(), OrmError> {
556    match expr {
557        Expr::Column(_) => validate_group_key(expr, group_by),
558        Expr::Value(_) => Ok(()),
559        Expr::Binary { left, right, .. } => {
560            validate_non_aggregate_expr_in_grouped_context(left, group_by)?;
561            validate_non_aggregate_expr_in_grouped_context(right, group_by)
562        }
563        Expr::Unary { expr, .. } => validate_non_aggregate_expr_in_grouped_context(expr, group_by),
564        Expr::Function { args, .. } | Expr::UnsafeFunction { args, .. } => {
565            for arg in args {
566                validate_non_aggregate_expr_in_grouped_context(arg, group_by)?;
567            }
568            Ok(())
569        }
570    }
571}
572
573fn compile_group_by(
574    group_by: &[Expr],
575    parameters: &mut ParameterBuilder,
576) -> Result<String, OrmError> {
577    let parts = group_by
578        .iter()
579        .map(|expr| compile_expr(expr, parameters))
580        .collect::<Result<Vec<_>, _>>()?;
581    Ok(parts.join(", "))
582}
583
584fn compile_aggregate_expr(
585    expr: &AggregateExpr,
586    group_by: &[Expr],
587    parameters: &mut ParameterBuilder,
588) -> Result<String, OrmError> {
589    match expr {
590        AggregateExpr::GroupKey(expr) => {
591            validate_group_key(expr, group_by)?;
592            compile_expr(expr, parameters)
593        }
594        AggregateExpr::CountAll => Ok("COUNT(*)".to_string()),
595        AggregateExpr::Count(expr) => Ok(format!("COUNT({})", compile_expr(expr, parameters)?)),
596        AggregateExpr::Sum(expr) => Ok(format!("SUM({})", compile_expr(expr, parameters)?)),
597        AggregateExpr::Avg(expr) => Ok(format!("AVG({})", compile_expr(expr, parameters)?)),
598        AggregateExpr::Min(expr) => Ok(format!("MIN({})", compile_expr(expr, parameters)?)),
599        AggregateExpr::Max(expr) => Ok(format!("MAX({})", compile_expr(expr, parameters)?)),
600    }
601}
602
603fn validate_group_key(expr: &Expr, group_by: &[Expr]) -> Result<(), OrmError> {
604    if group_by.iter().any(|group_key| group_key == expr) {
605        return Ok(());
606    }
607
608    Err(OrmError::compile(
609        "SQL Server aggregate group key projection must appear in GROUP BY",
610    ))
611}
612
613fn compile_expr(expr: &Expr, parameters: &mut ParameterBuilder) -> Result<String, OrmError> {
614    match expr {
615        Expr::Column(column) => quote_column_ref(column),
616        Expr::Value(value) => Ok(parameters.push(value.clone())),
617        Expr::Binary { left, op, right } => Ok(format!(
618            "({} {} {})",
619            compile_expr(left, parameters)?,
620            compile_binary_op(*op),
621            compile_expr(right, parameters)?,
622        )),
623        Expr::Unary { op, expr } => Ok(format!(
624            "({} {})",
625            compile_unary_op(*op),
626            compile_expr(expr, parameters)?,
627        )),
628        Expr::Function { function, args } => {
629            let args = args
630                .iter()
631                .map(|arg| compile_expr(arg, parameters))
632                .collect::<Result<Vec<_>, _>>()?;
633
634            Ok(format!("{}({})", function.sql_name(), args.join(", ")))
635        }
636        Expr::UnsafeFunction { name, args } => {
637            validate_unsafe_function_name(name)?;
638
639            let args = args
640                .iter()
641                .map(|arg| compile_expr(arg, parameters))
642                .collect::<Result<Vec<_>, _>>()?;
643
644            Ok(format!("{name}({})", args.join(", ")))
645        }
646    }
647}
648
649fn validate_unsafe_function_name(name: &str) -> Result<(), OrmError> {
650    if name.trim().is_empty() {
651        return Err(OrmError::compile(
652            "unsafe SQL function name cannot be empty",
653        ));
654    }
655
656    let mut chars = name.chars();
657    let Some(first) = chars.next() else {
658        return Err(OrmError::compile(
659            "unsafe SQL function name cannot be empty",
660        ));
661    };
662
663    if !is_sql_identifier_start(first) || !chars.all(is_sql_identifier_continue) {
664        return Err(OrmError::compile(format!(
665            "unsafe SQL function name `{name}` must be a single unquoted SQL identifier"
666        )));
667    }
668
669    Ok(())
670}
671
672fn is_sql_identifier_start(ch: char) -> bool {
673    ch == '_' || ch.is_ascii_alphabetic()
674}
675
676fn is_sql_identifier_continue(ch: char) -> bool {
677    is_sql_identifier_start(ch) || ch.is_ascii_digit()
678}
679
680fn compile_predicate(
681    predicate: &Predicate,
682    parameters: &mut ParameterBuilder,
683) -> Result<String, OrmError> {
684    match predicate {
685        Predicate::Eq(left, right) => compile_comparison(left, "=", right, parameters),
686        Predicate::Ne(left, right) => compile_comparison(left, "<>", right, parameters),
687        Predicate::Gt(left, right) => compile_comparison(left, ">", right, parameters),
688        Predicate::Gte(left, right) => compile_comparison(left, ">=", right, parameters),
689        Predicate::Lt(left, right) => compile_comparison(left, "<", right, parameters),
690        Predicate::Lte(left, right) => compile_comparison(left, "<=", right, parameters),
691        Predicate::Like(left, right) => compile_comparison(left, "LIKE", right, parameters),
692        Predicate::LikeEscaped(left, right, escape) => {
693            compile_like_escaped(left, right, *escape, parameters)
694        }
695        Predicate::IsNull(expr) => Ok(format!("({} IS NULL)", compile_expr(expr, parameters)?)),
696        Predicate::IsNotNull(expr) => {
697            Ok(format!("({} IS NOT NULL)", compile_expr(expr, parameters)?))
698        }
699        Predicate::And(predicates) => compile_logical("AND", predicates, parameters),
700        Predicate::Or(predicates) => compile_logical("OR", predicates, parameters),
701        Predicate::Not(predicate) => Ok(format!(
702            "(NOT {})",
703            compile_predicate(predicate, parameters)?
704        )),
705    }
706}
707
708fn compile_aggregate_predicate(
709    predicate: &AggregatePredicate,
710    group_by: &[Expr],
711    parameters: &mut ParameterBuilder,
712) -> Result<String, OrmError> {
713    match predicate {
714        AggregatePredicate::Eq(left, right) => {
715            compile_aggregate_comparison(left, "=", right, group_by, parameters)
716        }
717        AggregatePredicate::Ne(left, right) => {
718            compile_aggregate_comparison(left, "<>", right, group_by, parameters)
719        }
720        AggregatePredicate::Gt(left, right) => {
721            compile_aggregate_comparison(left, ">", right, group_by, parameters)
722        }
723        AggregatePredicate::Gte(left, right) => {
724            compile_aggregate_comparison(left, ">=", right, group_by, parameters)
725        }
726        AggregatePredicate::Lt(left, right) => {
727            compile_aggregate_comparison(left, "<", right, group_by, parameters)
728        }
729        AggregatePredicate::Lte(left, right) => {
730            compile_aggregate_comparison(left, "<=", right, group_by, parameters)
731        }
732        AggregatePredicate::And(predicates) => {
733            compile_aggregate_logical("AND", predicates, group_by, parameters)
734        }
735        AggregatePredicate::Or(predicates) => {
736            compile_aggregate_logical("OR", predicates, group_by, parameters)
737        }
738        AggregatePredicate::Not(predicate) => Ok(format!(
739            "(NOT {})",
740            compile_aggregate_predicate(predicate, group_by, parameters)?
741        )),
742    }
743}
744
745fn compile_aggregate_comparison(
746    left: &AggregateExpr,
747    operator: &str,
748    right: &Expr,
749    group_by: &[Expr],
750    parameters: &mut ParameterBuilder,
751) -> Result<String, OrmError> {
752    Ok(format!(
753        "({} {operator} {})",
754        compile_aggregate_expr(left, group_by, parameters)?,
755        compile_expr(right, parameters)?,
756    ))
757}
758
759fn compile_aggregate_logical(
760    operator: &str,
761    predicates: &[AggregatePredicate],
762    group_by: &[Expr],
763    parameters: &mut ParameterBuilder,
764) -> Result<String, OrmError> {
765    if predicates.is_empty() {
766        return Err(OrmError::compile(
767            "aggregate logical predicate compilation requires at least one child predicate",
768        ));
769    }
770
771    let compiled = predicates
772        .iter()
773        .map(|predicate| compile_aggregate_predicate(predicate, group_by, parameters))
774        .collect::<Result<Vec<_>, _>>()?;
775
776    Ok(format!("({})", compiled.join(&format!(" {operator} "))))
777}
778
779fn compile_comparison(
780    left: &Expr,
781    operator: &str,
782    right: &Expr,
783    parameters: &mut ParameterBuilder,
784) -> Result<String, OrmError> {
785    Ok(format!(
786        "({} {operator} {})",
787        compile_expr(left, parameters)?,
788        compile_expr(right, parameters)?,
789    ))
790}
791
792fn compile_like_escaped(
793    left: &Expr,
794    right: &Expr,
795    escape: char,
796    parameters: &mut ParameterBuilder,
797) -> Result<String, OrmError> {
798    validate_like_escape_char(escape)?;
799    Ok(format!(
800        "({} LIKE {} ESCAPE {})",
801        compile_expr(left, parameters)?,
802        compile_expr(right, parameters)?,
803        quote_like_escape_literal(escape),
804    ))
805}
806
807fn validate_like_escape_char(escape: char) -> Result<(), OrmError> {
808    if !escape.is_ascii() || escape == '\'' || escape.is_ascii_alphanumeric() {
809        return Err(OrmError::compile(
810            "SQL Server LIKE ESCAPE character must be a single non-alphanumeric ASCII character other than quote",
811        ));
812    }
813
814    Ok(())
815}
816
817fn quote_like_escape_literal(escape: char) -> String {
818    format!("N'{escape}'")
819}
820
821fn compile_logical(
822    operator: &str,
823    predicates: &[Predicate],
824    parameters: &mut ParameterBuilder,
825) -> Result<String, OrmError> {
826    if predicates.is_empty() {
827        return Err(OrmError::compile(
828            "logical predicate compilation requires at least one child predicate",
829        ));
830    }
831
832    let compiled = predicates
833        .iter()
834        .map(|predicate| compile_predicate(predicate, parameters))
835        .collect::<Result<Vec<_>, _>>()?;
836
837    Ok(format!("({})", compiled.join(&format!(" {operator} "))))
838}
839
840fn compile_order_by(order_by: &[OrderBy]) -> Result<String, OrmError> {
841    let parts = order_by
842        .iter()
843        .map(|order| {
844            Ok(format!(
845                "{}.{} {}",
846                quote_table_reference(&order.table)?,
847                quote_identifier(order.column_name)?,
848                match order.direction {
849                    SortDirection::Asc => "ASC",
850                    SortDirection::Desc => "DESC",
851                },
852            ))
853        })
854        .collect::<Result<Vec<_>, OrmError>>()?;
855
856    Ok(parts.join(", "))
857}
858
859fn compile_aggregate_order_by(
860    order_by: &[AggregateOrderBy],
861    group_by: &[Expr],
862    parameters: &mut ParameterBuilder,
863) -> Result<String, OrmError> {
864    let parts = order_by
865        .iter()
866        .map(|order| {
867            Ok(format!(
868                "{} {}",
869                compile_aggregate_expr(&order.expr, group_by, parameters)?,
870                match order.direction {
871                    SortDirection::Asc => "ASC",
872                    SortDirection::Desc => "DESC",
873                },
874            ))
875        })
876        .collect::<Result<Vec<_>, OrmError>>()?;
877
878    Ok(parts.join(", "))
879}
880
881fn compile_pagination(pagination: Pagination, parameters: &mut ParameterBuilder) -> String {
882    let offset = parameters.push(SqlValue::I64(pagination.offset as i64));
883    let limit = parameters.push(SqlValue::I64(pagination.limit as i64));
884
885    format!("OFFSET {offset} ROWS FETCH NEXT {limit} ROWS ONLY")
886}
887
888fn compile_column_values(
889    values: &[ColumnValue],
890    parameters: &mut ParameterBuilder,
891) -> Result<(String, String), OrmError> {
892    let mut columns = Vec::with_capacity(values.len());
893    let mut placeholders = Vec::with_capacity(values.len());
894
895    for value in values {
896        columns.push(quote_identifier(value.column_name)?);
897        placeholders.push(parameters.push(value.value.clone()));
898    }
899
900    Ok((columns.join(", "), placeholders.join(", ")))
901}
902
903fn compile_assignments(
904    changes: &[ColumnValue],
905    parameters: &mut ParameterBuilder,
906) -> Result<String, OrmError> {
907    let assignments = changes
908        .iter()
909        .map(|change| {
910            Ok(format!(
911                "{} = {}",
912                quote_identifier(change.column_name)?,
913                parameters.push(change.value.clone()),
914            ))
915        })
916        .collect::<Result<Vec<_>, OrmError>>()?;
917
918    Ok(assignments.join(", "))
919}
920
921fn compile_binary_op(op: BinaryOp) -> &'static str {
922    match op {
923        BinaryOp::Add => "+",
924        BinaryOp::Subtract => "-",
925        BinaryOp::Multiply => "*",
926        BinaryOp::Divide => "/",
927    }
928}
929
930fn compile_unary_op(op: UnaryOp) -> &'static str {
931    match op {
932        UnaryOp::Negate => "-",
933    }
934}
935
936#[cfg(test)]
937mod tests {
938    use super::super::SqlServerCompiler;
939    use sql_orm_core::{
940        Changeset, ColumnMetadata, ColumnValue, Entity, EntityColumn, EntityMetadata,
941        IdentityMetadata, Insertable, OrmErrorKind, PrimaryKeyMetadata, SqlServerType, SqlValue,
942    };
943    use sql_orm_query::{
944        AggregateExpr, AggregateOrderBy, AggregatePredicate, AggregateProjection, AggregateQuery,
945        BinaryOp, CountQuery, DeleteQuery, ExistsQuery, Expr, InsertQuery, OrderBy, Pagination,
946        Predicate, Query, QueryExecution, SelectProjection, SelectQuery, SqlFunction, TableRef,
947        UnaryOp, UpdateQuery,
948    };
949
950    #[allow(dead_code)]
951    struct Customer;
952
953    #[allow(dead_code)]
954    struct Order;
955
956    static CUSTOMER_COLUMNS: [ColumnMetadata; 7] = [
957        ColumnMetadata {
958            rust_field: "id",
959            column_name: "id",
960            renamed_from: None,
961            sql_type: SqlServerType::BigInt,
962            nullable: false,
963            primary_key: true,
964            identity: Some(IdentityMetadata::new(1, 1)),
965            default_sql: None,
966            computed_sql: None,
967            rowversion: false,
968            insertable: false,
969            updatable: false,
970            max_length: None,
971            precision: None,
972            scale: None,
973        },
974        ColumnMetadata {
975            rust_field: "email",
976            column_name: "email",
977            renamed_from: None,
978            sql_type: SqlServerType::NVarChar,
979            nullable: false,
980            primary_key: false,
981            identity: None,
982            default_sql: None,
983            computed_sql: None,
984            rowversion: false,
985            insertable: true,
986            updatable: true,
987            max_length: Some(160),
988            precision: None,
989            scale: None,
990        },
991        ColumnMetadata {
992            rust_field: "active",
993            column_name: "active",
994            renamed_from: None,
995            sql_type: SqlServerType::Bit,
996            nullable: false,
997            primary_key: false,
998            identity: None,
999            default_sql: Some("1"),
1000            computed_sql: None,
1001            rowversion: false,
1002            insertable: true,
1003            updatable: true,
1004            max_length: None,
1005            precision: None,
1006            scale: None,
1007        },
1008        ColumnMetadata {
1009            rust_field: "created_at",
1010            column_name: "created_at",
1011            renamed_from: None,
1012            sql_type: SqlServerType::DateTime2,
1013            nullable: false,
1014            primary_key: false,
1015            identity: None,
1016            default_sql: Some("SYSUTCDATETIME()"),
1017            computed_sql: None,
1018            rowversion: false,
1019            insertable: true,
1020            updatable: true,
1021            max_length: None,
1022            precision: None,
1023            scale: None,
1024        },
1025        ColumnMetadata {
1026            rust_field: "version",
1027            column_name: "version",
1028            renamed_from: None,
1029            sql_type: SqlServerType::RowVersion,
1030            nullable: false,
1031            primary_key: false,
1032            identity: None,
1033            default_sql: None,
1034            computed_sql: None,
1035            rowversion: true,
1036            insertable: false,
1037            updatable: false,
1038            max_length: None,
1039            precision: None,
1040            scale: None,
1041        },
1042        ColumnMetadata {
1043            rust_field: "email_domain",
1044            column_name: "email_domain",
1045            renamed_from: None,
1046            sql_type: SqlServerType::NVarChar,
1047            nullable: false,
1048            primary_key: false,
1049            identity: None,
1050            default_sql: None,
1051            computed_sql: Some("RIGHT([email], CHARINDEX('@', REVERSE([email])) - 1)"),
1052            rowversion: false,
1053            insertable: false,
1054            updatable: false,
1055            max_length: Some(160),
1056            precision: None,
1057            scale: None,
1058        },
1059        ColumnMetadata {
1060            rust_field: "created_by_runtime",
1061            column_name: "created_by_runtime",
1062            renamed_from: None,
1063            sql_type: SqlServerType::NVarChar,
1064            nullable: false,
1065            primary_key: false,
1066            identity: None,
1067            default_sql: None,
1068            computed_sql: None,
1069            rowversion: false,
1070            insertable: false,
1071            updatable: false,
1072            max_length: Some(120),
1073            precision: None,
1074            scale: None,
1075        },
1076    ];
1077
1078    static CUSTOMER_METADATA: EntityMetadata = EntityMetadata {
1079        rust_name: "Customer",
1080        schema: "sales",
1081        table: "customers",
1082        renamed_from: None,
1083        columns: &CUSTOMER_COLUMNS,
1084        primary_key: PrimaryKeyMetadata::new(Some("pk_customers"), &["id"]),
1085        indexes: &[],
1086        foreign_keys: &[],
1087        navigations: &[],
1088    };
1089
1090    impl Entity for Customer {
1091        fn metadata() -> &'static EntityMetadata {
1092            &CUSTOMER_METADATA
1093        }
1094    }
1095
1096    static ORDER_COLUMNS: [ColumnMetadata; 3] = [
1097        ColumnMetadata {
1098            rust_field: "id",
1099            column_name: "id",
1100            renamed_from: None,
1101            sql_type: SqlServerType::BigInt,
1102            nullable: false,
1103            primary_key: true,
1104            identity: Some(IdentityMetadata::new(1, 1)),
1105            default_sql: None,
1106            computed_sql: None,
1107            rowversion: false,
1108            insertable: false,
1109            updatable: false,
1110            max_length: None,
1111            precision: None,
1112            scale: None,
1113        },
1114        ColumnMetadata {
1115            rust_field: "customer_id",
1116            column_name: "customer_id",
1117            renamed_from: None,
1118            sql_type: SqlServerType::BigInt,
1119            nullable: false,
1120            primary_key: false,
1121            identity: None,
1122            default_sql: None,
1123            computed_sql: None,
1124            rowversion: false,
1125            insertable: true,
1126            updatable: true,
1127            max_length: None,
1128            precision: None,
1129            scale: None,
1130        },
1131        ColumnMetadata {
1132            rust_field: "total_cents",
1133            column_name: "total_cents",
1134            renamed_from: None,
1135            sql_type: SqlServerType::BigInt,
1136            nullable: false,
1137            primary_key: false,
1138            identity: None,
1139            default_sql: None,
1140            computed_sql: None,
1141            rowversion: false,
1142            insertable: true,
1143            updatable: true,
1144            max_length: None,
1145            precision: None,
1146            scale: None,
1147        },
1148    ];
1149
1150    static ORDER_METADATA: EntityMetadata = EntityMetadata {
1151        rust_name: "Order",
1152        schema: "sales",
1153        table: "orders",
1154        renamed_from: None,
1155        columns: &ORDER_COLUMNS,
1156        primary_key: PrimaryKeyMetadata::new(Some("pk_orders"), &["id"]),
1157        indexes: &[],
1158        foreign_keys: &[],
1159        navigations: &[],
1160    };
1161
1162    impl Entity for Order {
1163        fn metadata() -> &'static EntityMetadata {
1164            &ORDER_METADATA
1165        }
1166    }
1167
1168    #[allow(non_upper_case_globals)]
1169    impl Customer {
1170        const id: EntityColumn<Customer> = EntityColumn::new("id", "id");
1171        const email: EntityColumn<Customer> = EntityColumn::new("email", "email");
1172        const active: EntityColumn<Customer> = EntityColumn::new("active", "active");
1173        const created_at: EntityColumn<Customer> = EntityColumn::new("created_at", "created_at");
1174    }
1175
1176    #[allow(non_upper_case_globals)]
1177    impl Order {
1178        const customer_id: EntityColumn<Order> = EntityColumn::new("customer_id", "customer_id");
1179        const total_cents: EntityColumn<Order> = EntityColumn::new("total_cents", "total_cents");
1180    }
1181
1182    struct NewCustomer {
1183        email: String,
1184        active: bool,
1185    }
1186
1187    impl Insertable<Customer> for NewCustomer {
1188        fn values(&self) -> Vec<ColumnValue> {
1189            vec![
1190                ColumnValue::new("email", SqlValue::String(self.email.clone())),
1191                ColumnValue::new("active", SqlValue::Bool(self.active)),
1192            ]
1193        }
1194    }
1195
1196    struct UpdateCustomer {
1197        email: Option<String>,
1198        active: Option<bool>,
1199    }
1200
1201    impl Changeset<Customer> for UpdateCustomer {
1202        fn changes(&self) -> Vec<ColumnValue> {
1203            let mut changes = Vec::new();
1204
1205            if let Some(email) = &self.email {
1206                changes.push(ColumnValue::new("email", SqlValue::String(email.clone())));
1207            }
1208
1209            if let Some(active) = self.active {
1210                changes.push(ColumnValue::new("active", SqlValue::Bool(active)));
1211            }
1212
1213            changes
1214        }
1215    }
1216
1217    #[test]
1218    fn compiles_select_with_predicates_order_and_pagination() {
1219        let query = SelectQuery::from_entity::<Customer>()
1220            .select(vec![Expr::from(Customer::id), Expr::from(Customer::email)])
1221            .filter(Predicate::eq(
1222                Expr::from(Customer::active),
1223                Expr::value(SqlValue::Bool(true)),
1224            ))
1225            .filter(Predicate::like(
1226                Expr::from(Customer::email),
1227                Expr::value(SqlValue::String("%@example.com".to_string())),
1228            ))
1229            .order_by(OrderBy::desc(Customer::created_at))
1230            .paginate(Pagination::page(2, 20));
1231
1232        let compiled = SqlServerCompiler::compile_select(&query).unwrap();
1233
1234        assert_eq!(
1235            compiled.sql,
1236            "SELECT [sales].[customers].[id] AS [id], [sales].[customers].[email] AS [email] FROM [sales].[customers] WHERE (([sales].[customers].[active] = @P1) AND ([sales].[customers].[email] LIKE @P2)) ORDER BY [sales].[customers].[created_at] DESC OFFSET @P3 ROWS FETCH NEXT @P4 ROWS ONLY"
1237        );
1238        assert_eq!(
1239            compiled.params,
1240            vec![
1241                SqlValue::Bool(true),
1242                SqlValue::String("%@example.com".to_string()),
1243                SqlValue::I64(20),
1244                SqlValue::I64(20),
1245            ]
1246        );
1247    }
1248
1249    #[test]
1250    fn compiles_escaped_like_predicate_with_escape_clause() {
1251        let query = SelectQuery::from_entity::<Customer>().filter(Predicate::like_escaped(
1252            Expr::from(Customer::email),
1253            Expr::value(SqlValue::String(r"%a\%\_b\[c\]\\d%".to_string())),
1254            '\\',
1255        ));
1256
1257        let compiled = SqlServerCompiler::compile_select(&query).unwrap();
1258
1259        assert_eq!(
1260            compiled.sql,
1261            r"SELECT * FROM [sales].[customers] WHERE ([sales].[customers].[email] LIKE @P1 ESCAPE N'\')"
1262        );
1263        assert_eq!(
1264            compiled.params,
1265            vec![SqlValue::String(r"%a\%\_b\[c\]\\d%".to_string())]
1266        );
1267    }
1268
1269    #[test]
1270    fn rejects_unsafe_like_escape_characters() {
1271        let query = SelectQuery::from_entity::<Customer>().filter(Predicate::like_escaped(
1272            Expr::from(Customer::email),
1273            Expr::value(SqlValue::String("%literal%".to_string())),
1274            '\'',
1275        ));
1276
1277        let error = SqlServerCompiler::compile_select(&query).unwrap_err();
1278
1279        assert_eq!(error.kind(), OrmErrorKind::Compile);
1280        assert_eq!(
1281            error.message(),
1282            "SQL Server LIKE ESCAPE character must be a single non-alphanumeric ASCII character other than quote"
1283        );
1284    }
1285
1286    #[test]
1287    fn compiles_select_without_projection_as_star() {
1288        let compiled =
1289            SqlServerCompiler::compile_select(&SelectQuery::from_entity::<Customer>()).unwrap();
1290
1291        assert_eq!(compiled.sql, "SELECT * FROM [sales].[customers]");
1292        assert!(compiled.params.is_empty());
1293    }
1294
1295    #[test]
1296    fn rejects_pagination_without_order_by() {
1297        let error = SqlServerCompiler::compile_select(
1298            &SelectQuery::from_entity::<Customer>().paginate(Pagination::page(1, 10)),
1299        )
1300        .unwrap_err();
1301
1302        assert_eq!(error.kind(), OrmErrorKind::Compile);
1303        assert_eq!(
1304            error.message(),
1305            "SQL Server pagination requires ORDER BY before OFFSET/FETCH"
1306        );
1307    }
1308
1309    #[test]
1310    fn compiles_explicit_joins_to_sql() {
1311        let query = SelectQuery::from_entity::<Customer>()
1312            .select(vec![
1313                Expr::from(Customer::email),
1314                Expr::from(Order::total_cents),
1315            ])
1316            .inner_join::<Order>(Predicate::eq(
1317                Expr::from(Customer::id),
1318                Expr::from(Order::customer_id),
1319            ))
1320            .filter(Predicate::gt(
1321                Expr::from(Order::total_cents),
1322                Expr::value(SqlValue::I64(1000)),
1323            ))
1324            .order_by(OrderBy::desc(Order::total_cents))
1325            .paginate(Pagination::page(1, 10));
1326
1327        let compiled = SqlServerCompiler::compile_select(&query).unwrap();
1328
1329        assert_eq!(
1330            compiled.sql,
1331            "SELECT [sales].[customers].[email] AS [email], [sales].[orders].[total_cents] AS [total_cents] FROM [sales].[customers] INNER JOIN [sales].[orders] ON ([sales].[customers].[id] = [sales].[orders].[customer_id]) WHERE ([sales].[orders].[total_cents] > @P1) ORDER BY [sales].[orders].[total_cents] DESC OFFSET @P2 ROWS FETCH NEXT @P3 ROWS ONLY"
1332        );
1333        assert_eq!(
1334            compiled.params,
1335            vec![SqlValue::I64(1000), SqlValue::I64(0), SqlValue::I64(10)]
1336        );
1337    }
1338
1339    #[test]
1340    fn rejects_duplicate_unaliased_joined_tables() {
1341        let error = SqlServerCompiler::compile_select(
1342            &SelectQuery::from_entity::<Customer>().inner_join::<Customer>(Predicate::eq(
1343                Expr::from(Customer::id),
1344                Expr::from(Customer::id),
1345            )),
1346        )
1347        .unwrap_err();
1348
1349        assert_eq!(
1350            error.message(),
1351            "SQL Server join compilation requires aliases for repeated table sources"
1352        );
1353    }
1354
1355    #[test]
1356    fn compiles_aliased_selects_with_repeated_joined_tables() {
1357        let query = SelectQuery::from_entity_as::<Customer>("c")
1358            .select(vec![
1359                Expr::column_as(Customer::email, "c"),
1360                Expr::column_as(Order::total_cents, "created_orders"),
1361            ])
1362            .inner_join_as::<Order>(
1363                "created_orders",
1364                Predicate::eq(
1365                    Expr::column_as(Customer::id, "c"),
1366                    Expr::column_as(Order::customer_id, "created_orders"),
1367                ),
1368            )
1369            .left_join_as::<Order>(
1370                "completed_orders",
1371                Predicate::gte(
1372                    Expr::column_as(Order::total_cents, "completed_orders"),
1373                    Expr::value(SqlValue::I64(5000)),
1374                ),
1375            )
1376            .filter(Predicate::gt(
1377                Expr::column_as(Order::total_cents, "created_orders"),
1378                Expr::value(SqlValue::I64(1000)),
1379            ))
1380            .order_by(OrderBy::new(
1381                TableRef::for_entity_as::<Order>("completed_orders"),
1382                "total_cents",
1383                sql_orm_query::SortDirection::Desc,
1384            ))
1385            .paginate(Pagination::page(1, 10));
1386
1387        let compiled = SqlServerCompiler::compile_select(&query).unwrap();
1388
1389        assert_eq!(
1390            compiled.sql,
1391            "SELECT [c].[email] AS [email], [created_orders].[total_cents] AS [total_cents] FROM [sales].[customers] AS [c] INNER JOIN [sales].[orders] AS [created_orders] ON ([c].[id] = [created_orders].[customer_id]) LEFT JOIN [sales].[orders] AS [completed_orders] ON ([completed_orders].[total_cents] >= @P1) WHERE ([created_orders].[total_cents] > @P2) ORDER BY [completed_orders].[total_cents] DESC OFFSET @P3 ROWS FETCH NEXT @P4 ROWS ONLY"
1392        );
1393        assert_eq!(
1394            compiled.params,
1395            vec![
1396                SqlValue::I64(5000),
1397                SqlValue::I64(1000),
1398                SqlValue::I64(0),
1399                SqlValue::I64(10),
1400            ]
1401        );
1402    }
1403
1404    #[test]
1405    fn compiles_aliased_count_query() {
1406        let query = CountQuery::from_entity_as::<Customer>("c").filter(Predicate::eq(
1407            Expr::column_as(Customer::active, "c"),
1408            Expr::value(SqlValue::Bool(true)),
1409        ));
1410
1411        let compiled = SqlServerCompiler::compile_count(&query).unwrap();
1412
1413        assert_eq!(
1414            compiled.sql,
1415            "SELECT COUNT(*) AS [count] FROM [sales].[customers] AS [c] WHERE ([c].[active] = @P1)"
1416        );
1417        assert_eq!(compiled.params, vec![SqlValue::Bool(true)]);
1418    }
1419
1420    #[test]
1421    fn rejects_empty_table_aliases() {
1422        let error = SqlServerCompiler::compile_select(
1423            &SelectQuery::from_entity_as::<Customer>("").inner_join_as::<Order>(
1424                "o",
1425                Predicate::eq(
1426                    Expr::column_as(Customer::id, ""),
1427                    Expr::column_as(Order::customer_id, "o"),
1428                ),
1429            ),
1430        )
1431        .unwrap_err();
1432
1433        assert_eq!(error.message(), "SQL Server identifier cannot be empty");
1434    }
1435
1436    #[test]
1437    fn compiles_insert_with_output_inserted_and_parameter_order() {
1438        let query = InsertQuery::for_entity::<Customer, _>(&NewCustomer {
1439            email: "ana@example.com".to_string(),
1440            active: true,
1441        });
1442
1443        let compiled = SqlServerCompiler::compile_insert(&query).unwrap();
1444
1445        assert_eq!(
1446            compiled.sql,
1447            "INSERT INTO [sales].[customers] ([email], [active]) OUTPUT INSERTED.* VALUES (@P1, @P2)"
1448        );
1449        assert_eq!(
1450            compiled.params,
1451            vec![
1452                SqlValue::String("ana@example.com".to_string()),
1453                SqlValue::Bool(true),
1454            ]
1455        );
1456    }
1457
1458    #[test]
1459    fn rejects_invalid_insert_columns_against_entity_metadata() {
1460        let missing_metadata_error = SqlServerCompiler::compile_insert(&InsertQuery {
1461            into: TableRef::for_entity::<Customer>(),
1462            values: vec![ColumnValue::new(
1463                "email",
1464                SqlValue::String("ana@example.com".to_string()),
1465            )],
1466            entity: None,
1467        })
1468        .unwrap_err();
1469        assert_eq!(
1470            missing_metadata_error.message(),
1471            "SQL Server insert compilation requires entity metadata"
1472        );
1473
1474        let unknown_column_error = SqlServerCompiler::compile_insert(&InsertQuery {
1475            into: TableRef::for_entity::<Customer>(),
1476            values: vec![ColumnValue::new(
1477                "not_a_column",
1478                SqlValue::String("value".to_string()),
1479            )],
1480            entity: Some(Customer::metadata()),
1481        })
1482        .unwrap_err();
1483        assert_eq!(
1484            unknown_column_error.message(),
1485            "SQL Server insert column `not_a_column` is not defined on entity `Customer`"
1486        );
1487
1488        let duplicate_column_error = SqlServerCompiler::compile_insert(&InsertQuery {
1489            into: TableRef::for_entity::<Customer>(),
1490            values: vec![
1491                ColumnValue::new("email", SqlValue::String("first@example.com".to_string())),
1492                ColumnValue::new("email", SqlValue::String("second@example.com".to_string())),
1493            ],
1494            entity: Some(Customer::metadata()),
1495        })
1496        .unwrap_err();
1497        assert_eq!(
1498            duplicate_column_error.message(),
1499            "SQL Server insert column `email` is duplicated"
1500        );
1501
1502        let identity_pk_error = SqlServerCompiler::compile_insert(&InsertQuery {
1503            into: TableRef::for_entity::<Customer>(),
1504            values: vec![ColumnValue::new("id", SqlValue::I64(7))],
1505            entity: Some(Customer::metadata()),
1506        })
1507        .unwrap_err();
1508        assert_eq!(
1509            identity_pk_error.message(),
1510            "SQL Server insert column `id` on entity `Customer` is an identity primary key and cannot be inserted"
1511        );
1512
1513        let non_insertable_error = SqlServerCompiler::compile_insert(&InsertQuery {
1514            into: TableRef::for_entity::<Customer>(),
1515            values: vec![ColumnValue::new(
1516                "created_by_runtime",
1517                SqlValue::String("system".to_string()),
1518            )],
1519            entity: Some(Customer::metadata()),
1520        })
1521        .unwrap_err();
1522        assert_eq!(
1523            non_insertable_error.message(),
1524            "SQL Server insert column `created_by_runtime` on entity `Customer` is not insertable"
1525        );
1526
1527        let rowversion_error = SqlServerCompiler::compile_insert(&InsertQuery {
1528            into: TableRef::for_entity::<Customer>(),
1529            values: vec![ColumnValue::new("version", SqlValue::Bytes(vec![1, 2, 3]))],
1530            entity: Some(Customer::metadata()),
1531        })
1532        .unwrap_err();
1533        assert_eq!(
1534            rowversion_error.message(),
1535            "SQL Server insert column `version` on entity `Customer` is rowversion and cannot be inserted"
1536        );
1537
1538        let computed_error = SqlServerCompiler::compile_insert(&InsertQuery {
1539            into: TableRef::for_entity::<Customer>(),
1540            values: vec![ColumnValue::new(
1541                "email_domain",
1542                SqlValue::String("example.com".to_string()),
1543            )],
1544            entity: Some(Customer::metadata()),
1545        })
1546        .unwrap_err();
1547        assert_eq!(
1548            computed_error.message(),
1549            "SQL Server insert column `email_domain` on entity `Customer` is computed and cannot be inserted"
1550        );
1551    }
1552
1553    #[test]
1554    fn compiles_update_with_output_inserted_and_where_clause() {
1555        let query = UpdateQuery::for_entity::<Customer, _>(&UpdateCustomer {
1556            email: Some("ana.maria@example.com".to_string()),
1557            active: Some(false),
1558        })
1559        .filter(Predicate::eq(
1560            Expr::from(Customer::id),
1561            Expr::value(SqlValue::I64(7)),
1562        ));
1563
1564        let compiled = SqlServerCompiler::compile_update(&query).unwrap();
1565
1566        assert_eq!(
1567            compiled.sql,
1568            "UPDATE [sales].[customers] SET [email] = @P1, [active] = @P2 OUTPUT INSERTED.* WHERE ([sales].[customers].[id] = @P3)"
1569        );
1570        assert_eq!(
1571            compiled.params,
1572            vec![
1573                SqlValue::String("ana.maria@example.com".to_string()),
1574                SqlValue::Bool(false),
1575                SqlValue::I64(7),
1576            ]
1577        );
1578    }
1579
1580    #[test]
1581    fn rejects_update_and_delete_without_predicate_unless_explicitly_allowed() {
1582        let update = UpdateQuery::for_entity::<Customer, _>(&UpdateCustomer {
1583            email: Some("all@example.com".to_string()),
1584            active: Some(true),
1585        });
1586        let update_error = SqlServerCompiler::compile_update(&update).unwrap_err();
1587
1588        assert_eq!(
1589            update_error.message(),
1590            "SQL Server update compilation requires a WHERE predicate or explicit allow_all_rows()"
1591        );
1592
1593        let compiled_update =
1594            SqlServerCompiler::compile_update(&update.clone().allow_all_rows()).unwrap();
1595        assert_eq!(
1596            compiled_update.sql,
1597            "UPDATE [sales].[customers] SET [email] = @P1, [active] = @P2 OUTPUT INSERTED.*"
1598        );
1599        assert_eq!(
1600            compiled_update.params,
1601            vec![
1602                SqlValue::String("all@example.com".to_string()),
1603                SqlValue::Bool(true),
1604            ]
1605        );
1606
1607        let delete = DeleteQuery::from_entity::<Customer>();
1608        let delete_error = SqlServerCompiler::compile_delete(&delete).unwrap_err();
1609
1610        assert_eq!(
1611            delete_error.message(),
1612            "SQL Server delete compilation requires a WHERE predicate or explicit allow_all_rows()"
1613        );
1614
1615        let compiled_delete = SqlServerCompiler::compile_delete(&delete.allow_all_rows()).unwrap();
1616        assert_eq!(compiled_delete.sql, "DELETE FROM [sales].[customers]");
1617        assert!(compiled_delete.params.is_empty());
1618    }
1619
1620    #[test]
1621    fn rejects_invalid_update_columns_against_entity_metadata() {
1622        let predicate = Predicate::eq(Expr::from(Customer::id), Expr::value(SqlValue::I64(7)));
1623
1624        let missing_metadata_error = SqlServerCompiler::compile_update(&UpdateQuery {
1625            table: TableRef::for_entity::<Customer>(),
1626            changes: vec![ColumnValue::new(
1627                "email",
1628                SqlValue::String("ana@example.com".to_string()),
1629            )],
1630            predicate: Some(predicate.clone()),
1631            allow_all_rows: false,
1632            entity: None,
1633        })
1634        .unwrap_err();
1635        assert_eq!(
1636            missing_metadata_error.message(),
1637            "SQL Server update compilation requires entity metadata"
1638        );
1639
1640        let target_mismatch_error = SqlServerCompiler::compile_update(&UpdateQuery {
1641            table: TableRef::new("sales", "other_customers"),
1642            changes: vec![ColumnValue::new(
1643                "email",
1644                SqlValue::String("ana@example.com".to_string()),
1645            )],
1646            predicate: Some(predicate.clone()),
1647            allow_all_rows: false,
1648            entity: Some(Customer::metadata()),
1649        })
1650        .unwrap_err();
1651        assert_eq!(
1652            target_mismatch_error.message(),
1653            "SQL Server update target [sales].[other_customers] does not match entity metadata [sales].[customers]"
1654        );
1655
1656        let unknown_column_error = SqlServerCompiler::compile_update(&UpdateQuery {
1657            table: TableRef::for_entity::<Customer>(),
1658            changes: vec![ColumnValue::new(
1659                "not_a_column",
1660                SqlValue::String("value".to_string()),
1661            )],
1662            predicate: Some(predicate.clone()),
1663            allow_all_rows: false,
1664            entity: Some(Customer::metadata()),
1665        })
1666        .unwrap_err();
1667        assert_eq!(
1668            unknown_column_error.message(),
1669            "SQL Server update column `not_a_column` is not defined on entity `Customer`"
1670        );
1671
1672        let duplicate_column_error = SqlServerCompiler::compile_update(&UpdateQuery {
1673            table: TableRef::for_entity::<Customer>(),
1674            changes: vec![
1675                ColumnValue::new("email", SqlValue::String("first@example.com".to_string())),
1676                ColumnValue::new("email", SqlValue::String("second@example.com".to_string())),
1677            ],
1678            predicate: Some(predicate.clone()),
1679            allow_all_rows: false,
1680            entity: Some(Customer::metadata()),
1681        })
1682        .unwrap_err();
1683        assert_eq!(
1684            duplicate_column_error.message(),
1685            "SQL Server update column `email` is duplicated"
1686        );
1687
1688        let primary_key_error = SqlServerCompiler::compile_update(&UpdateQuery {
1689            table: TableRef::for_entity::<Customer>(),
1690            changes: vec![ColumnValue::new("id", SqlValue::I64(8))],
1691            predicate: Some(predicate.clone()),
1692            allow_all_rows: false,
1693            entity: Some(Customer::metadata()),
1694        })
1695        .unwrap_err();
1696        assert_eq!(
1697            primary_key_error.message(),
1698            "SQL Server update column `id` on entity `Customer` is a primary key and cannot be updated"
1699        );
1700
1701        let non_updatable_error = SqlServerCompiler::compile_update(&UpdateQuery {
1702            table: TableRef::for_entity::<Customer>(),
1703            changes: vec![ColumnValue::new(
1704                "created_by_runtime",
1705                SqlValue::String("system".to_string()),
1706            )],
1707            predicate: Some(predicate.clone()),
1708            allow_all_rows: false,
1709            entity: Some(Customer::metadata()),
1710        })
1711        .unwrap_err();
1712        assert_eq!(
1713            non_updatable_error.message(),
1714            "SQL Server update column `created_by_runtime` on entity `Customer` is not updatable"
1715        );
1716
1717        let rowversion_error = SqlServerCompiler::compile_update(&UpdateQuery {
1718            table: TableRef::for_entity::<Customer>(),
1719            changes: vec![ColumnValue::new("version", SqlValue::Bytes(vec![1, 2, 3]))],
1720            predicate: Some(predicate.clone()),
1721            allow_all_rows: false,
1722            entity: Some(Customer::metadata()),
1723        })
1724        .unwrap_err();
1725        assert_eq!(
1726            rowversion_error.message(),
1727            "SQL Server update column `version` on entity `Customer` is rowversion and cannot be updated"
1728        );
1729
1730        let computed_error = SqlServerCompiler::compile_update(&UpdateQuery {
1731            table: TableRef::for_entity::<Customer>(),
1732            changes: vec![ColumnValue::new(
1733                "email_domain",
1734                SqlValue::String("example.com".to_string()),
1735            )],
1736            predicate: Some(predicate),
1737            allow_all_rows: false,
1738            entity: Some(Customer::metadata()),
1739        })
1740        .unwrap_err();
1741        assert_eq!(
1742            computed_error.message(),
1743            "SQL Server update column `email_domain` on entity `Customer` is computed and cannot be updated"
1744        );
1745    }
1746
1747    #[test]
1748    fn compiles_delete_and_count_queries() {
1749        let delete = DeleteQuery::from_entity::<Customer>().filter(Predicate::eq(
1750            Expr::from(Customer::id),
1751            Expr::value(SqlValue::I64(7)),
1752        ));
1753        let count = CountQuery::from_entity::<Customer>().filter(Predicate::eq(
1754            Expr::from(Customer::active),
1755            Expr::value(SqlValue::Bool(true)),
1756        ));
1757
1758        let compiled_delete = SqlServerCompiler::compile_delete(&delete).unwrap();
1759        let compiled_count = SqlServerCompiler::compile_count(&count).unwrap();
1760
1761        assert_eq!(
1762            compiled_delete.sql,
1763            "DELETE FROM [sales].[customers] WHERE ([sales].[customers].[id] = @P1)"
1764        );
1765        assert_eq!(compiled_delete.params, vec![SqlValue::I64(7)]);
1766        assert_eq!(
1767            compiled_count.sql,
1768            "SELECT COUNT(*) AS [count] FROM [sales].[customers] WHERE ([sales].[customers].[active] = @P1)"
1769        );
1770        assert_eq!(compiled_count.params, vec![SqlValue::Bool(true)]);
1771    }
1772
1773    #[test]
1774    fn compiles_exists_query_with_join_and_parameter_order() {
1775        let query = ExistsQuery::from_entity::<Customer>()
1776            .inner_join::<Order>(Predicate::eq(
1777                Expr::from(Customer::id),
1778                Expr::from(Order::customer_id),
1779            ))
1780            .filter(Predicate::eq(
1781                Expr::from(Customer::active),
1782                Expr::value(SqlValue::Bool(true)),
1783            ))
1784            .filter(Predicate::gt(
1785                Expr::from(Order::total_cents),
1786                Expr::value(SqlValue::I64(1000)),
1787            ));
1788
1789        let compiled = SqlServerCompiler::compile_exists(&query).unwrap();
1790
1791        assert_eq!(
1792            compiled.sql,
1793            "SELECT CASE WHEN EXISTS (SELECT 1 FROM [sales].[customers] INNER JOIN [sales].[orders] ON ([sales].[customers].[id] = [sales].[orders].[customer_id]) WHERE (([sales].[customers].[active] = @P1) AND ([sales].[orders].[total_cents] > @P2))) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END AS [exists]"
1794        );
1795        assert_eq!(
1796            compiled.params,
1797            vec![SqlValue::Bool(true), SqlValue::I64(1000)]
1798        );
1799    }
1800
1801    #[test]
1802    fn compiles_query_enum_through_single_entry_point() {
1803        let query = Query::Count(CountQuery::from_entity::<Customer>().filter(Predicate::eq(
1804            Expr::from(Customer::active),
1805            Expr::value(SqlValue::Bool(true)),
1806        )));
1807
1808        let compiled = SqlServerCompiler::compile_query(&query).unwrap();
1809
1810        assert_eq!(
1811            compiled.sql,
1812            "SELECT COUNT(*) AS [count] FROM [sales].[customers] WHERE ([sales].[customers].[active] = @P1)"
1813        );
1814        assert_eq!(compiled.params, vec![SqlValue::Bool(true)]);
1815        assert_eq!(compiled.execution, QueryExecution::ReadOnly);
1816
1817        let exists_query = Query::Exists(Box::new(ExistsQuery::from_entity::<Customer>().filter(
1818            Predicate::eq(
1819                Expr::from(Customer::active),
1820                Expr::value(SqlValue::Bool(true)),
1821            ),
1822        )));
1823        let compiled_exists = SqlServerCompiler::compile_query(&exists_query).unwrap();
1824        assert_eq!(
1825            compiled_exists.sql,
1826            "SELECT CASE WHEN EXISTS (SELECT 1 FROM [sales].[customers] WHERE ([sales].[customers].[active] = @P1)) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END AS [exists]"
1827        );
1828        assert_eq!(compiled_exists.params, vec![SqlValue::Bool(true)]);
1829        assert_eq!(compiled_exists.execution, QueryExecution::ReadOnly);
1830    }
1831
1832    #[test]
1833    fn compiles_aggregate_query_through_single_entry_point() {
1834        let query = Query::Aggregate(Box::new(
1835            AggregateQuery::from_entity::<Order>()
1836                .project(vec![AggregateProjection::count_as("order_count")])
1837                .filter(Predicate::gt(
1838                    Expr::from(Order::total_cents),
1839                    Expr::value(SqlValue::I64(1000)),
1840                )),
1841        ));
1842
1843        let compiled = SqlServerCompiler::compile_query(&query).unwrap();
1844
1845        assert_eq!(
1846            compiled.sql,
1847            "SELECT COUNT(*) AS [order_count] FROM [sales].[orders] WHERE ([sales].[orders].[total_cents] > @P1)"
1848        );
1849        assert_eq!(compiled.params, vec![SqlValue::I64(1000)]);
1850    }
1851
1852    #[test]
1853    fn compiles_grouped_aggregate_query_with_having_and_parameter_order() {
1854        let query = AggregateQuery::from_entity::<Order>()
1855            .inner_join::<Customer>(Predicate::eq(
1856                Expr::from(Order::customer_id),
1857                Expr::from(Customer::id),
1858            ))
1859            .filter(Predicate::eq(
1860                Expr::from(Customer::active),
1861                Expr::value(SqlValue::Bool(true)),
1862            ))
1863            .group_by(vec![Expr::from(Order::customer_id)])
1864            .project(vec![
1865                AggregateProjection::group_key(Order::customer_id),
1866                AggregateProjection::count_as("order_count"),
1867                AggregateProjection::sum_as(Order::total_cents, "total_cents"),
1868                AggregateProjection::avg_as(Order::total_cents, "average_cents"),
1869                AggregateProjection::min_as(Order::total_cents, "min_cents"),
1870                AggregateProjection::max_as(Order::total_cents, "max_cents"),
1871            ])
1872            .having(AggregatePredicate::gt(
1873                AggregateExpr::count_all(),
1874                Expr::value(SqlValue::I64(1)),
1875            ))
1876            .order_by(AggregateOrderBy::desc(AggregateExpr::sum(Expr::from(
1877                Order::total_cents,
1878            ))))
1879            .paginate(Pagination::page(1, 10));
1880
1881        let compiled = SqlServerCompiler::compile_aggregate(&query).unwrap();
1882
1883        assert_eq!(
1884            compiled.sql,
1885            "SELECT [sales].[orders].[customer_id] AS [customer_id], COUNT(*) AS [order_count], SUM([sales].[orders].[total_cents]) AS [total_cents], AVG([sales].[orders].[total_cents]) AS [average_cents], MIN([sales].[orders].[total_cents]) AS [min_cents], MAX([sales].[orders].[total_cents]) AS [max_cents] FROM [sales].[orders] INNER JOIN [sales].[customers] ON ([sales].[orders].[customer_id] = [sales].[customers].[id]) WHERE ([sales].[customers].[active] = @P1) GROUP BY [sales].[orders].[customer_id] HAVING (COUNT(*) > @P2) ORDER BY SUM([sales].[orders].[total_cents]) DESC OFFSET @P3 ROWS FETCH NEXT @P4 ROWS ONLY"
1886        );
1887        assert_eq!(
1888            compiled.params,
1889            vec![
1890                SqlValue::Bool(true),
1891                SqlValue::I64(1),
1892                SqlValue::I64(0),
1893                SqlValue::I64(10),
1894            ]
1895        );
1896    }
1897
1898    #[test]
1899    fn rejects_invalid_aggregate_queries() {
1900        let empty_projection_error =
1901            SqlServerCompiler::compile_aggregate(&AggregateQuery::from_entity::<Order>())
1902                .unwrap_err();
1903        assert_eq!(
1904            empty_projection_error.message(),
1905            "SQL Server aggregate query compilation requires at least one projection"
1906        );
1907
1908        let duplicate_alias_error = SqlServerCompiler::compile_aggregate(
1909            &AggregateQuery::from_entity::<Order>().project(vec![
1910                AggregateProjection::count_as("value"),
1911                AggregateProjection::sum_as(Order::total_cents, "value"),
1912            ]),
1913        )
1914        .unwrap_err();
1915        assert_eq!(
1916            duplicate_alias_error.message(),
1917            "SQL Server aggregate projection alias `value` is duplicated"
1918        );
1919
1920        let missing_group_key_error = SqlServerCompiler::compile_aggregate(
1921            &AggregateQuery::from_entity::<Order>()
1922                .project(vec![AggregateProjection::group_key(Order::customer_id)]),
1923        )
1924        .unwrap_err();
1925        assert_eq!(
1926            missing_group_key_error.message(),
1927            "SQL Server aggregate group key projection must appear in GROUP BY"
1928        );
1929
1930        let empty_alias_error = SqlServerCompiler::compile_aggregate(
1931            &AggregateQuery::from_entity::<Order>().project(vec![AggregateProjection::expr_as(
1932                AggregateExpr::count_all(),
1933                " ",
1934            )]),
1935        )
1936        .unwrap_err();
1937        assert_eq!(
1938            empty_alias_error.message(),
1939            "SQL Server aggregate projection alias cannot be empty"
1940        );
1941
1942        let ungrouped_having_column_error = SqlServerCompiler::compile_aggregate(
1943            &AggregateQuery::from_entity::<Order>()
1944                .group_by(vec![Expr::from(Order::customer_id)])
1945                .project(vec![
1946                    AggregateProjection::group_key(Order::customer_id),
1947                    AggregateProjection::count_as("order_count"),
1948                ])
1949                .having(AggregatePredicate::gt(
1950                    AggregateExpr::count_all(),
1951                    Expr::from(Order::total_cents),
1952                )),
1953        )
1954        .unwrap_err();
1955        assert_eq!(
1956            ungrouped_having_column_error.message(),
1957            "SQL Server aggregate group key projection must appear in GROUP BY"
1958        );
1959
1960        let ungrouped_order_key_error = SqlServerCompiler::compile_aggregate(
1961            &AggregateQuery::from_entity::<Order>()
1962                .group_by(vec![Expr::from(Order::customer_id)])
1963                .project(vec![
1964                    AggregateProjection::group_key(Order::customer_id),
1965                    AggregateProjection::count_as("order_count"),
1966                ])
1967                .order_by(AggregateOrderBy::asc(AggregateExpr::group_key(
1968                    Order::total_cents,
1969                ))),
1970        )
1971        .unwrap_err();
1972        assert_eq!(
1973            ungrouped_order_key_error.message(),
1974            "SQL Server aggregate group key projection must appear in GROUP BY"
1975        );
1976    }
1977
1978    #[test]
1979    fn compiles_functions_null_checks_and_unary_binary_exprs() {
1980        let query = SelectQuery {
1981            from: TableRef::new("sales", "customers"),
1982            joins: vec![],
1983            projection: vec![SelectProjection::expr_as(
1984                Expr::function(
1985                    SqlFunction::Lower,
1986                    vec![Expr::binary(
1987                        Expr::from(Customer::email),
1988                        BinaryOp::Add,
1989                        Expr::value(SqlValue::String("@example.com".to_string())),
1990                    )],
1991                ),
1992                "email_lower",
1993            )],
1994            predicate: Some(Predicate::and(vec![
1995                Predicate::is_not_null(Expr::from(Customer::email)),
1996                Predicate::negate(Predicate::is_null(Expr::unary(
1997                    UnaryOp::Negate,
1998                    Expr::value(SqlValue::I64(1)),
1999                ))),
2000            ])),
2001            order_by: vec![],
2002            pagination: None,
2003        };
2004
2005        let compiled = SqlServerCompiler::compile_select(&query).unwrap();
2006
2007        assert_eq!(
2008            compiled.sql,
2009            "SELECT LOWER(([sales].[customers].[email] + @P1)) AS [email_lower] FROM [sales].[customers] WHERE (([sales].[customers].[email] IS NOT NULL) AND (NOT ((- @P2) IS NULL)))"
2010        );
2011        assert_eq!(
2012            compiled.params,
2013            vec![
2014                SqlValue::String("@example.com".to_string()),
2015                SqlValue::I64(1),
2016            ]
2017        );
2018    }
2019
2020    #[test]
2021    fn compiles_explicit_unsafe_function_only_with_identifier_name() {
2022        let compiled = SqlServerCompiler::compile_select(
2023            &SelectQuery::from_entity::<Customer>().select(vec![SelectProjection::expr_as(
2024                Expr::unsafe_function("SOUNDEX", vec![Expr::from(Customer::email)]),
2025                "email_soundex",
2026            )]),
2027        )
2028        .unwrap();
2029
2030        assert_eq!(
2031            compiled.sql,
2032            "SELECT SOUNDEX([sales].[customers].[email]) AS [email_soundex] FROM [sales].[customers]"
2033        );
2034
2035        let error = SqlServerCompiler::compile_select(
2036            &SelectQuery::from_entity::<Customer>().select(vec![SelectProjection::expr_as(
2037                Expr::unsafe_function("LOWER); DROP TABLE [sales].[customers];--", vec![]),
2038                "bad",
2039            )]),
2040        )
2041        .unwrap_err();
2042
2043        assert_eq!(
2044            error.message(),
2045            "unsafe SQL function name `LOWER); DROP TABLE [sales].[customers];--` must be a single unquoted SQL identifier"
2046        );
2047    }
2048
2049    #[test]
2050    fn rejects_projection_expression_without_alias() {
2051        let error = SqlServerCompiler::compile_select(
2052            &SelectQuery::from_entity::<Customer>().select(vec![SelectProjection::expr(
2053                Expr::function(SqlFunction::Lower, vec![Expr::from(Customer::email)]),
2054            )]),
2055        )
2056        .unwrap_err();
2057
2058        assert_eq!(
2059            error.message(),
2060            "SQL Server projection expressions require an explicit alias"
2061        );
2062    }
2063
2064    #[test]
2065    fn rejects_empty_or_duplicate_projection_aliases() {
2066        let empty_alias_error =
2067            SqlServerCompiler::compile_select(&SelectQuery::from_entity::<Customer>().select(
2068                vec![SelectProjection::expr_as(Expr::from(Customer::email), "")],
2069            ))
2070            .unwrap_err();
2071
2072        assert_eq!(
2073            empty_alias_error.message(),
2074            "SQL Server projection alias cannot be empty"
2075        );
2076
2077        let duplicate_alias_error = SqlServerCompiler::compile_select(
2078            &SelectQuery::from_entity::<Customer>().select(vec![
2079                SelectProjection::column(Customer::id),
2080                SelectProjection::expr_as(Expr::from(Customer::email), "id"),
2081            ]),
2082        )
2083        .unwrap_err();
2084
2085        assert_eq!(
2086            duplicate_alias_error.message(),
2087            "SQL Server projection alias `id` is duplicated"
2088        );
2089    }
2090
2091    #[test]
2092    fn rejects_empty_updates_and_empty_logical_predicates() {
2093        let empty_update = UpdateQuery::for_entity::<Customer, _>(&UpdateCustomer {
2094            email: None,
2095            active: None,
2096        });
2097        let update_error = SqlServerCompiler::compile_update(&empty_update).unwrap_err();
2098
2099        assert_eq!(
2100            update_error.message(),
2101            "SQL Server update compilation requires at least one change"
2102        );
2103
2104        let predicate_error = SqlServerCompiler::compile_select(
2105            &SelectQuery::from_entity::<Customer>().filter(Predicate::and(vec![])),
2106        )
2107        .unwrap_err();
2108
2109        assert_eq!(
2110            predicate_error.message(),
2111            "logical predicate compilation requires at least one child predicate"
2112        );
2113    }
2114}