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("e_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}