1use teaql_core::{
2 Aggregate, AggregateFunction, BinaryOp, DataType, DeleteCommand, EntityDescriptor, Expr,
3 ExprFunction, OrderBy, PropertyDescriptor, RecoverCommand, SelectQuery, SortDirection, Value,
4};
5
6use crate::{CompiledQuery, DatabaseKind, SqlCompileError};
7
8const SQL_KEYWORDS: &[&str] = &[
9 "all", "alter", "and", "as", "asc", "between", "by", "case", "create", "delete", "desc",
10 "distinct", "drop", "exists", "false", "from", "group", "having", "in", "insert", "into", "is",
11 "join", "like", "limit", "not", "null", "offset", "on", "or", "order", "select", "set",
12 "table", "true", "type", "union", "update", "values", "where",
13];
14
15pub fn quote_identifier_if_needed(ident: &str, quote: char) -> String {
16 if is_wrapped_identifier(ident) {
17 return ident.to_owned();
18 }
19 if needs_quoted_identifier(ident) {
20 let quote_string = quote.to_string();
21 let escaped = ident.replace(quote, &(quote_string.clone() + "e_string));
22 return format!("{quote}{escaped}{quote}");
23 }
24 ident.to_owned()
25}
26
27fn is_wrapped_identifier(ident: &str) -> bool {
28 (ident.starts_with('"') && ident.ends_with('"'))
29 || (ident.starts_with('`') && ident.ends_with('`'))
30 || (ident.starts_with('[') && ident.ends_with(']'))
31}
32
33fn needs_quoted_identifier(ident: &str) -> bool {
34 if ident.is_empty()
35 || SQL_KEYWORDS
36 .binary_search(&ident.to_ascii_lowercase().as_str())
37 .is_ok()
38 {
39 return true;
40 }
41 let mut chars = ident.chars();
42 match chars.next() {
43 Some(first) if first == '_' || first.is_ascii_alphabetic() => {}
44 _ => return true,
45 }
46 chars.any(|ch| ch != '_' && !ch.is_ascii_alphanumeric())
47}
48
49pub trait SqlDialect {
50 fn kind(&self) -> DatabaseKind;
51 fn quote_ident(&self, ident: &str) -> String;
52 fn placeholder(&self, index: usize) -> String;
53
54 fn large_in_uses_array_param(&self) -> bool {
57 false
58 }
59
60 fn prefers_small_parent_relation_probes(&self) -> bool {
61 false
62 }
63
64 fn schema_setup_sqls(&self) -> &'static [&'static str] {
65 &[]
66 }
67
68 fn schema_type_sql(
69 &self,
70 data_type: DataType,
71 _property: &PropertyDescriptor,
72 ) -> Result<&'static str, SqlCompileError> {
73 match data_type {
74 DataType::Bool => Ok("BOOLEAN"),
75 DataType::I64 | DataType::U64 => Ok("INTEGER"),
76 DataType::F64 => Ok("REAL"),
77 DataType::Decimal => Ok("NUMERIC"),
78 DataType::Text => Ok("VARCHAR(255)"),
79 DataType::LargeText | DataType::Json | DataType::Date | DataType::Timestamp => {
80 Ok("TEXT")
81 }
82 }
83 }
84
85 fn column_definition_sql(
86 &self,
87 property: &PropertyDescriptor,
88 ) -> Result<String, SqlCompileError> {
89 let mut parts = vec![
90 self.quote_ident(&property.column_name),
91 self.schema_type_sql(property.data_type, property)?
92 .to_owned(),
93 ];
94
95 if property.is_id {
96 parts.push("PRIMARY KEY".to_owned());
97 }
98 if property.is_id || !property.nullable {
99 parts.push("NOT NULL".to_owned());
100 }
101
102 Ok(parts.join(" "))
103 }
104
105 fn compile_create_table(&self, entity: &EntityDescriptor) -> Result<String, SqlCompileError> {
106 let columns = entity
107 .properties
108 .iter()
109 .map(|property| self.column_definition_sql(property))
110 .collect::<Result<Vec<_>, _>>()?
111 .join(", ");
112 Ok(format!(
113 "CREATE TABLE IF NOT EXISTS {} ({columns})",
114 self.quote_ident(&entity.table_name)
115 ))
116 }
117
118 fn schema_indexes_sqls(
119 &self,
120 entity: &EntityDescriptor,
121 ) -> Result<Vec<String>, SqlCompileError> {
122 let mut sqls = Vec::new();
123 let table_name_upper = entity.table_name.to_uppercase();
124 let quoted_table = self.quote_ident(&entity.table_name);
125
126 if let Some(version_col) = entity.properties.iter().find(|p| p.is_version) {
127 let default_id = "id".to_string();
128 let id_col = entity
129 .properties
130 .iter()
131 .find(|p| p.is_id)
132 .map(|p| &p.column_name)
133 .unwrap_or(&default_id);
134 let idx_name = format!("PK_{}_ID_VERSION", table_name_upper);
135 sqls.push(format!(
136 "CREATE UNIQUE INDEX IF NOT EXISTS {} ON {} ({}, {})",
137 self.quote_ident(&idx_name),
138 quoted_table,
139 self.quote_ident(id_col),
140 self.quote_ident(&version_col.column_name)
141 ));
142 }
143
144 for p in &entity.properties {
145 if p.name.ends_with("Id")
146 || p.name.ends_with("Time")
147 || p.name.ends_with("_time")
148 || p.name == "create_time"
149 || p.name == "update_time"
150 {
151 let idx_name = format!("IDX_{}_{}", table_name_upper, p.column_name.to_uppercase());
152 sqls.push(format!(
153 "CREATE INDEX IF NOT EXISTS {} ON {} ({})",
154 self.quote_ident(&idx_name),
155 quoted_table,
156 self.quote_ident(&p.column_name)
157 ));
158 }
159 }
160
161 Ok(sqls)
162 }
163
164 fn fallback_default_value_sql(&self, data_type: DataType) -> &'static str {
165 match data_type {
166 DataType::Bool => "FALSE",
167 DataType::I64 | DataType::U64 | DataType::F64 | DataType::Decimal => "0",
168 DataType::Text | DataType::LargeText => "''",
169 DataType::Json => "'{}'",
170 DataType::Date => "'1970-01-01'",
171 DataType::Timestamp => "'1970-01-01 00:00:00Z'",
172 }
173 }
174
175 fn compile_add_column(
176 &self,
177 entity: &EntityDescriptor,
178 property: &PropertyDescriptor,
179 ) -> Result<String, SqlCompileError> {
180 let mut def = self.column_definition_sql(property)?;
181 if !property.nullable && !property.is_id {
182 def.push_str(" DEFAULT ");
183 def.push_str(self.fallback_default_value_sql(property.data_type));
184 }
185 Ok(format!(
186 "ALTER TABLE {} ADD COLUMN {}",
187 self.quote_ident(&entity.table_name),
188 def
189 ))
190 }
191
192 fn compile_select(
193 &self,
194 entity: &EntityDescriptor,
195 query: &SelectQuery,
196 ) -> Result<CompiledQuery, SqlCompileError> {
197 let mut params = Vec::new();
198 let sql = self.compile_select_sql(entity, query, &mut params)?;
199 Ok(CompiledQuery {
200 sql,
201 params,
202 comment: query.comment.clone(),
203 })
204 }
205
206 fn compile_select_sql(
207 &self,
208 entity: &EntityDescriptor,
209 query: &SelectQuery,
210 params: &mut Vec<Value>,
211 ) -> Result<String, SqlCompileError> {
212 if let Some(raw_sql) = &query.raw_sql {
213 return Ok(raw_sql.clone());
214 }
215
216 let mut projection = self.compile_projection(entity, query, params)?;
217 let partitioned_slice = query.partition_by.as_deref().zip(query.slice);
218 if let Some((partition_by, _)) = partitioned_slice {
219 let partition_column = self.column_sql(entity, partition_by)?;
220 let window_order = if query.order_by.is_empty() {
221 String::new()
222 } else {
223 let order_by = query
224 .order_by
225 .iter()
226 .map(|order| self.order_by_sql(entity, order, params))
227 .collect::<Result<Vec<_>, _>>()?
228 .join(", ");
229 format!(" ORDER BY {order_by}")
230 };
231 let rank = self.quote_ident(teaql_core::PARTITION_RANK_PROPERTY);
232 projection.push_str(&format!(
233 ", ROW_NUMBER() OVER (PARTITION BY {partition_column}{window_order}) AS {rank}"
234 ));
235 }
236
237 let mut sql = format!(
238 "SELECT {projection} FROM {}",
239 self.quote_ident(&entity.table_name)
240 );
241
242 let mut where_parts = Vec::new();
243 if let Some(filter) = &query.filter {
244 where_parts.push(self.compile_expr(entity, filter, params)?);
245 }
246
247 if let Some(search_text) = &query.search_with_text {
248 let mut or_parts = Vec::new();
249 let like_value = format!("%{}%", search_text);
250 for property in &entity.properties {
251 if property.data_type == teaql_core::DataType::Text
252 || property.data_type == teaql_core::DataType::LargeText
253 {
254 params.push(teaql_core::Value::from(like_value.clone()));
255 or_parts.push(format!(
256 "{} LIKE {}",
257 self.quote_ident(&property.column_name),
258 self.placeholder(params.len())
259 ));
260 }
261 }
262 if !or_parts.is_empty() {
263 where_parts.push(format!("({})", or_parts.join(" OR ")));
264 }
265 }
266
267 where_parts.extend(query.raw_sql_search_criteria.iter().cloned());
268 if !where_parts.is_empty() {
269 sql.push_str(" WHERE ");
270 sql.push_str(&where_parts.join(" AND "));
271 }
272
273 if let Some((_, slice)) = partitioned_slice {
274 let rank = self.quote_ident(teaql_core::PARTITION_RANK_PROPERTY);
275 let alias = self.quote_ident("__teaql_partitioned");
276 let mut predicates = vec![format!("{rank} > {}", slice.offset)];
277 if let Some(limit) = slice.limit {
278 predicates.push(format!("{rank} <= {}", slice.offset.saturating_add(limit)));
279 }
280 return Ok(format!(
281 "SELECT * FROM ({sql}) AS {alias} WHERE {} ORDER BY {rank}",
282 predicates.join(" AND ")
283 ));
284 }
285
286 if !query.group_by.is_empty() {
287 let group_by = query
288 .group_by
289 .iter()
290 .map(|field| self.column_sql(entity, field))
291 .collect::<Result<Vec<_>, _>>()?
292 .join(", ");
293 sql.push_str(" GROUP BY ");
294 sql.push_str(&group_by);
295 }
296
297 if let Some(having) = &query.having {
298 let having_sql = self.compile_expr(entity, having, params)?;
299 sql.push_str(" HAVING ");
300 sql.push_str(&having_sql);
301 }
302
303 if !query.order_by.is_empty() {
304 let order_by = query
305 .order_by
306 .iter()
307 .map(|order| self.order_by_sql(entity, order, params))
308 .collect::<Result<Vec<_>, _>>()?
309 .join(", ");
310 sql.push_str(" ORDER BY ");
311 sql.push_str(&order_by);
312 }
313
314 if let Some(slice) = query.slice {
315 if let Some(limit) = slice.limit {
316 sql.push_str(&format!(" LIMIT {limit}"));
317 }
318 if slice.offset > 0 {
319 sql.push_str(&format!(" OFFSET {}", slice.offset));
320 }
321 }
322
323 Ok(sql)
324 }
325
326 fn compile_insert(
327 &self,
328 entity: &EntityDescriptor,
329 command: &teaql_core::InsertCommand,
330 ) -> Result<CompiledQuery, SqlCompileError> {
331 let mut columns = Vec::new();
332 let mut placeholders = Vec::new();
333 let mut params = Vec::new();
334
335 for property in &entity.properties {
336 if let Some(value) = command.values.get(&property.name) {
337 columns.push(self.quote_ident(&property.column_name));
338 let mut v = value.clone();
339 if let Value::Null = v {
340 v = Value::TypedNull(property.data_type);
341 }
342 params.push(v);
343 placeholders.push(self.placeholder(params.len()));
344 }
345 }
346
347 if columns.is_empty() {
348 return Err(SqlCompileError::EmptyMutation("insert".to_owned()));
349 }
350
351 Ok(CompiledQuery {
352 sql: format!(
353 "INSERT INTO {} ({}) VALUES ({})",
354 self.quote_ident(&entity.table_name),
355 columns.join(", "),
356 placeholders.join(", ")
357 ),
358 params,
359 comment: None,
360 })
361 }
362
363 fn compile_batch_insert(
364 &self,
365 entity: &EntityDescriptor,
366 command: &teaql_core::BatchInsertCommand,
367 ) -> Result<CompiledQuery, SqlCompileError> {
368 if command.batch_values.is_empty() {
369 return Err(SqlCompileError::EmptyMutation("batch_insert".to_owned()));
370 }
371
372 let mut columns = Vec::new();
373 let first_record = &command.batch_values[0];
374
375 for property in &entity.properties {
376 if first_record.contains_key(&property.name) {
377 columns.push(property.clone());
378 }
379 }
380
381 if columns.is_empty() {
382 return Err(SqlCompileError::EmptyMutation("batch_insert".to_owned()));
383 }
384
385 let column_names: Vec<String> = columns
386 .iter()
387 .map(|p| self.quote_ident(&p.column_name))
388 .collect();
389 let mut params = Vec::new();
390 let mut values_clauses = Vec::new();
391
392 for record in &command.batch_values {
393 let mut row_placeholders = Vec::new();
394 for property in &columns {
395 let mut value = record
396 .get(&property.name)
397 .cloned()
398 .unwrap_or(teaql_core::Value::Null);
399 if let Value::Null = value {
400 value = Value::TypedNull(property.data_type);
401 }
402 params.push(value);
403 row_placeholders.push(self.placeholder(params.len()));
404 }
405 values_clauses.push(format!("({})", row_placeholders.join(", ")));
406 }
407
408 Ok(CompiledQuery {
409 sql: format!(
410 "INSERT INTO {} ({}) VALUES {}",
411 self.quote_ident(&entity.table_name),
412 column_names.join(", "),
413 values_clauses.join(", ")
414 ),
415 params,
416 comment: None,
417 })
418 }
419
420 fn compile_update(
421 &self,
422 entity: &EntityDescriptor,
423 command: &teaql_core::UpdateCommand,
424 ) -> Result<CompiledQuery, SqlCompileError> {
425 let id_property = entity
426 .id_property()
427 .ok_or_else(|| SqlCompileError::MissingIdProperty(entity.name.clone()))?;
428 let mut assignments = Vec::new();
429 let mut params = Vec::new();
430
431 for property in &entity.properties {
432 if property.is_id {
433 continue;
434 }
435 if property.is_version && command.expected_version.is_some() {
436 continue;
437 }
438 if let Some(value) = command.values.get(&property.name) {
439 let mut v = value.clone();
440 if let Value::Null = v {
441 v = Value::TypedNull(property.data_type);
442 }
443 params.push(v);
444 assignments.push(format!(
445 "{} = {}",
446 self.quote_ident(&property.column_name),
447 self.placeholder(params.len())
448 ));
449 }
450 }
451
452 if let Some(expected_version) = command.expected_version {
453 let version_property = entity
454 .version_property()
455 .ok_or_else(|| SqlCompileError::MissingVersionProperty(entity.name.clone()))?;
456 params.push(Value::I64(expected_version + 1));
457 assignments.push(format!(
458 "{} = {}",
459 self.quote_ident(&version_property.column_name),
460 self.placeholder(params.len())
461 ));
462 }
463
464 if assignments.is_empty() {
465 return Err(SqlCompileError::EmptyMutation("update".to_owned()));
466 }
467
468 params.push(command.id.clone());
469 let mut predicates = vec![format!(
470 "{} = {}",
471 self.quote_ident(&id_property.column_name),
472 self.placeholder(params.len())
473 )];
474
475 if let Some(expected_version) = command.expected_version {
476 let version_property = entity
477 .version_property()
478 .ok_or_else(|| SqlCompileError::MissingVersionProperty(entity.name.clone()))?;
479 params.push(Value::I64(expected_version));
480 predicates.push(format!(
481 "{} = {}",
482 self.quote_ident(&version_property.column_name),
483 self.placeholder(params.len())
484 ));
485 }
486
487 Ok(CompiledQuery {
488 sql: format!(
489 "UPDATE {} SET {} WHERE {}",
490 self.quote_ident(&entity.table_name),
491 assignments.join(", "),
492 predicates.join(" AND ")
493 ),
494 params,
495 comment: None,
496 })
497 }
498
499 fn compile_batch_update(
500 &self,
501 entity: &EntityDescriptor,
502 command: &teaql_core::BatchUpdateCommand,
503 ) -> Result<CompiledQuery, SqlCompileError> {
504 if command.batch_values.is_empty() {
505 return Err(SqlCompileError::EmptyMutation("batch_update".to_owned()));
506 }
507
508 let id_property = entity
509 .id_property()
510 .ok_or_else(|| SqlCompileError::MissingIdProperty(entity.name.clone()))?;
511
512 let mut params = Vec::new();
513 let mut set_clauses = Vec::new();
514
515 for field_name in &command.update_fields {
517 let property = entity
518 .property_by_name(field_name)
519 .ok_or_else(|| SqlCompileError::UnknownField(field_name.clone()))?;
520
521 let mut case_parts = Vec::new();
522 case_parts.push(format!(
523 "CASE {}",
524 self.quote_ident(&id_property.column_name)
525 ));
526
527 for (i, record) in command.batch_values.iter().enumerate() {
528 let id = &command.batch_ids[i];
529 let mut val = record
530 .get(field_name)
531 .cloned()
532 .unwrap_or(teaql_core::Value::Null);
533 if let Value::Null = val {
534 val = Value::TypedNull(property.data_type);
535 }
536
537 params.push(id.clone());
538 let id_ph = self.placeholder(params.len());
539
540 params.push(val);
541 let val_ph = self.placeholder(params.len());
542
543 case_parts.push(format!("WHEN {} THEN {}", id_ph, val_ph));
544 }
545
546 case_parts.push(format!(
547 "ELSE {} END",
548 self.quote_ident(&property.column_name)
549 ));
550 set_clauses.push(format!(
551 "{} = {}",
552 self.quote_ident(&property.column_name),
553 case_parts.join(" ")
554 ));
555 }
556
557 let mut has_versions = false;
558 if let Some(version_property) = entity.version_property() {
559 let mut case_parts = Vec::new();
560 case_parts.push(format!(
561 "CASE {}",
562 self.quote_ident(&id_property.column_name)
563 ));
564
565 for (i, exp_ver_opt) in command.batch_expected_versions.iter().enumerate() {
566 if let Some(exp_ver) = exp_ver_opt {
567 has_versions = true;
568 let id = &command.batch_ids[i];
569
570 params.push(id.clone());
571 let id_ph = self.placeholder(params.len());
572
573 params.push(teaql_core::Value::I64(*exp_ver + 1));
574 let val_ph = self.placeholder(params.len());
575
576 case_parts.push(format!("WHEN {} THEN {}", id_ph, val_ph));
577 }
578 }
579
580 if has_versions {
581 case_parts.push(format!(
582 "ELSE {} END",
583 self.quote_ident(&version_property.column_name)
584 ));
585 set_clauses.push(format!(
586 "{} = {}",
587 self.quote_ident(&version_property.column_name),
588 case_parts.join(" ")
589 ));
590 }
591 }
592
593 if set_clauses.is_empty() {
594 return Err(SqlCompileError::EmptyMutation("batch_update".to_owned()));
595 }
596
597 let mut in_placeholders = Vec::new();
598 for id in &command.batch_ids {
599 params.push(id.clone());
600 in_placeholders.push(self.placeholder(params.len()));
601 }
602 let mut predicates = vec![format!(
603 "{} IN ({})",
604 self.quote_ident(&id_property.column_name),
605 in_placeholders.join(", ")
606 )];
607
608 if has_versions {
609 let version_property = entity.version_property().unwrap();
610 let mut case_parts = Vec::new();
611 case_parts.push(format!(
612 "CASE {}",
613 self.quote_ident(&id_property.column_name)
614 ));
615
616 for (i, exp_ver_opt) in command.batch_expected_versions.iter().enumerate() {
617 if let Some(exp_ver) = exp_ver_opt {
618 let id = &command.batch_ids[i];
619
620 params.push(id.clone());
621 let id_ph = self.placeholder(params.len());
622
623 params.push(teaql_core::Value::I64(*exp_ver));
624 let val_ph = self.placeholder(params.len());
625
626 case_parts.push(format!("WHEN {} THEN {}", id_ph, val_ph));
627 }
628 }
629 case_parts.push(format!(
630 "ELSE {} END",
631 self.quote_ident(&version_property.column_name)
632 ));
633
634 predicates.push(format!(
635 "{} = {}",
636 self.quote_ident(&version_property.column_name),
637 case_parts.join(" ")
638 ));
639 }
640
641 Ok(CompiledQuery {
642 sql: format!(
643 "UPDATE {} SET {} WHERE {}",
644 self.quote_ident(&entity.table_name),
645 set_clauses.join(", "),
646 predicates.join(" AND ")
647 ),
648 params,
649 comment: None,
650 })
651 }
652
653 fn compile_delete(
654 &self,
655 entity: &EntityDescriptor,
656 command: &DeleteCommand,
657 ) -> Result<CompiledQuery, SqlCompileError> {
658 let id_property = entity
659 .id_property()
660 .ok_or_else(|| SqlCompileError::MissingIdProperty(entity.name.clone()))?;
661 let mut params = Vec::new();
662
663 if command.soft_delete {
664 let version_property = entity
665 .version_property()
666 .ok_or_else(|| SqlCompileError::MissingVersionProperty(entity.name.clone()))?;
667 params.push(match command.expected_version {
668 Some(version) => Value::I64(-(version + 1)),
669 None => Value::I64(-1),
670 });
671
672 params.push(command.id.clone());
673 let mut predicates = vec![format!(
674 "{} = {}",
675 self.quote_ident(&id_property.column_name),
676 self.placeholder(params.len())
677 )];
678
679 if let Some(expected_version) = command.expected_version {
680 params.push(Value::I64(expected_version));
681 predicates.push(format!(
682 "{} = {}",
683 self.quote_ident(&version_property.column_name),
684 self.placeholder(params.len())
685 ));
686 }
687
688 return Ok(CompiledQuery {
689 sql: format!(
690 "UPDATE {} SET {} = {} WHERE {}",
691 self.quote_ident(&entity.table_name),
692 self.quote_ident(&version_property.column_name),
693 self.placeholder(1),
694 predicates.join(" AND ")
695 ),
696 params,
697 comment: None,
698 });
699 }
700
701 params.push(command.id.clone());
702 let mut predicates = vec![format!(
703 "{} = {}",
704 self.quote_ident(&id_property.column_name),
705 self.placeholder(params.len())
706 )];
707
708 if let Some(expected_version) = command.expected_version {
709 let version_property = entity
710 .version_property()
711 .ok_or_else(|| SqlCompileError::MissingVersionProperty(entity.name.clone()))?;
712 params.push(Value::I64(expected_version));
713 predicates.push(format!(
714 "{} = {}",
715 self.quote_ident(&version_property.column_name),
716 self.placeholder(params.len())
717 ));
718 }
719
720 Ok(CompiledQuery {
721 sql: format!(
722 "DELETE FROM {} WHERE {}",
723 self.quote_ident(&entity.table_name),
724 predicates.join(" AND ")
725 ),
726 params,
727 comment: None,
728 })
729 }
730
731 fn compile_recover(
732 &self,
733 entity: &EntityDescriptor,
734 command: &RecoverCommand,
735 ) -> Result<CompiledQuery, SqlCompileError> {
736 if command.expected_version >= 0 {
737 return Err(SqlCompileError::InvalidRecoverVersion(
738 command.expected_version,
739 ));
740 }
741
742 let id_property = entity
743 .id_property()
744 .ok_or_else(|| SqlCompileError::MissingIdProperty(entity.name.clone()))?;
745 let version_property = entity
746 .version_property()
747 .ok_or_else(|| SqlCompileError::MissingVersionProperty(entity.name.clone()))?;
748 let params = vec![
749 Value::I64(-command.expected_version + 1),
750 command.id.clone(),
751 Value::I64(command.expected_version),
752 ];
753
754 Ok(CompiledQuery {
755 sql: format!(
756 "UPDATE {} SET {} = {} WHERE {} = {} AND {} = {}",
757 self.quote_ident(&entity.table_name),
758 self.quote_ident(&version_property.column_name),
759 self.placeholder(1),
760 self.quote_ident(&id_property.column_name),
761 self.placeholder(2),
762 self.quote_ident(&version_property.column_name),
763 self.placeholder(3),
764 ),
765 params,
766 comment: None,
767 })
768 }
769
770 fn column_sql(
771 &self,
772 entity: &EntityDescriptor,
773 field: &str,
774 ) -> Result<String, SqlCompileError> {
775 let property = entity
776 .property_by_name(field)
777 .ok_or_else(|| SqlCompileError::UnknownField(field.to_owned()))?;
778 Ok(self.quote_ident(&property.column_name))
779 }
780
781 fn order_by_sql(
782 &self,
783 entity: &EntityDescriptor,
784 order_by: &OrderBy,
785 params: &mut Vec<Value>,
786 ) -> Result<String, SqlCompileError> {
787 let field = self.resolve_order_field(entity, order_by, params)?;
788 let direction = match order_by.direction {
789 SortDirection::Asc => "ASC",
790 SortDirection::Desc => "DESC",
791 };
792 Ok(format!("{field} {direction}"))
793 }
794
795 fn select_projection(
796 &self,
797 entity: &EntityDescriptor,
798 query: &SelectQuery,
799 params: &mut Vec<Value>,
800 ) -> Result<String, SqlCompileError> {
801 let property_projection = |property: &PropertyDescriptor| self.column_with_alias(property);
802
803 if query.projection.is_empty()
804 && query.expr_projection.is_empty()
805 && query.raw_projections.is_empty()
806 && query.dynamic_properties.is_empty()
807 {
808 return Ok(entity
809 .properties
810 .iter()
811 .map(property_projection)
812 .collect::<Vec<_>>()
813 .join(", "));
814 }
815 let mut seen_fields = std::collections::BTreeSet::new();
820 let mut parts = Vec::new();
821 for field in &query.projection {
822 if !seen_fields.insert(field.as_str()) {
823 continue;
824 }
825 let property = entity
826 .property_by_name(field)
827 .ok_or_else(|| SqlCompileError::UnknownField(field.to_owned()))?;
828 parts.push(property_projection(property));
829 }
830 for projection in &query.expr_projection {
831 let expr = self.compile_expr(entity, &projection.expr, params)?;
832 parts.push(format!("{expr} AS {}", self.quote_ident(&projection.alias)));
833 }
834 for projection in query
835 .raw_projections
836 .iter()
837 .chain(query.dynamic_properties.iter())
838 {
839 parts.push(format!(
840 "{} AS {}",
841 projection.raw_sql_segment,
842 self.quote_ident(&projection.property_name)
843 ));
844 }
845 Ok(parts.join(", "))
846 }
847
848 fn aggregate_projection(
849 &self,
850 entity: &EntityDescriptor,
851 query: &SelectQuery,
852 params: &mut Vec<Value>,
853 ) -> Result<String, SqlCompileError> {
854 let mut parts = Vec::new();
855 for field in &query.group_by {
860 let column = self.column_sql(entity, field)?;
861 if !parts.contains(&column) {
862 parts.push(column);
863 }
864 }
865 for projection in &query.expr_projection {
866 let expr = self.compile_expr(entity, &projection.expr, params)?;
867 let aliased = format!("{expr} AS {}", self.quote_ident(&projection.alias));
868 if !parts.contains(&aliased) {
869 parts.push(aliased);
870 }
871 }
872 for projection in query
873 .raw_projections
874 .iter()
875 .chain(query.dynamic_properties.iter())
876 {
877 let aliased = format!(
878 "{} AS {}",
879 projection.raw_sql_segment,
880 self.quote_ident(&projection.property_name)
881 );
882 if !parts.contains(&aliased) {
883 parts.push(aliased);
884 }
885 }
886 parts.extend(
887 query
888 .aggregates
889 .iter()
890 .map(|aggregate| {
891 let field = self.resolve_aggregate_field(entity, aggregate)?;
892 let call = self.aggregate_call_sql(aggregate.function, &field);
893 Ok(format!("{call} AS {}", self.quote_ident(&aggregate.alias)))
894 })
895 .collect::<Result<Vec<_>, _>>()?,
896 );
897 Ok(parts.join(", "))
898 }
899
900 fn aggregate_call_sql(&self, function: AggregateFunction, field: &str) -> String {
901 let function_sql = self.aggregate_function_sql(function);
902 format!("{function_sql}({field})")
903 }
904
905 fn aggregate_function_sql(&self, function: AggregateFunction) -> &'static str {
906 match function {
907 AggregateFunction::Count => "COUNT",
908 AggregateFunction::Sum => "SUM",
909 AggregateFunction::Avg => "AVG",
910 AggregateFunction::Min => "MIN",
911 AggregateFunction::Max => "MAX",
912 AggregateFunction::Stddev => "STDDEV",
913 AggregateFunction::StddevPop => "STDDEV_POP",
914 AggregateFunction::VarSamp => "VAR_SAMP",
915 AggregateFunction::VarPop => "VAR_POP",
916 AggregateFunction::BitAnd => "BIT_AND",
917 AggregateFunction::BitOr => "BIT_OR",
918 AggregateFunction::BitXor => "BIT_XOR",
919 }
920 }
921
922 fn compile_expr(
923 &self,
924 entity: &EntityDescriptor,
925 expr: &Expr,
926 params: &mut Vec<Value>,
927 ) -> Result<String, SqlCompileError> {
928 match expr {
929 Expr::Column(name) => self.column_sql(entity, name),
930 Expr::Value(value) => {
931 params.push(value.clone());
932 Ok(self.placeholder(params.len()))
933 }
934 Expr::Function { function, args } => {
935 self.compile_function(entity, *function, args, params)
936 }
937 Expr::Binary { left, op, right } => {
938 if matches!(
939 op,
940 BinaryOp::In | BinaryOp::NotIn | BinaryOp::InLarge | BinaryOp::NotInLarge
941 ) {
942 return self.compile_in(entity, left, *op, right, params);
943 }
944 let lhs = self.compile_expr(entity, left, params)?;
945 let rhs = self.compile_expr(entity, right, params)?;
946 let op = match op {
947 BinaryOp::Eq => "=",
948 BinaryOp::Ne => "!=",
949 BinaryOp::Gt => ">",
950 BinaryOp::Gte => ">=",
951 BinaryOp::Lt => "<",
952 BinaryOp::Lte => "<=",
953 BinaryOp::Like => "LIKE",
954 BinaryOp::NotLike => "NOT LIKE",
955 BinaryOp::In | BinaryOp::NotIn | BinaryOp::InLarge | BinaryOp::NotInLarge => {
956 unreachable!()
957 }
958 };
959 Ok(format!("({lhs} {op} {rhs})"))
960 }
961 Expr::SubQuery {
962 left,
963 op,
964 entity: sub_entity,
965 query,
966 } => self.compile_subquery(entity, left, *op, sub_entity, query, params),
967 Expr::Between { expr, lower, upper } => {
968 let expr = self.compile_expr(entity, expr, params)?;
969 let lower = self.compile_expr(entity, lower, params)?;
970 let upper = self.compile_expr(entity, upper, params)?;
971 Ok(format!("({expr} BETWEEN {lower} AND {upper})"))
972 }
973 Expr::IsNull(expr) => {
974 let expr = self.compile_expr(entity, expr, params)?;
975 Ok(format!("({expr} IS NULL)"))
976 }
977 Expr::IsNotNull(expr) => {
978 let expr = self.compile_expr(entity, expr, params)?;
979 Ok(format!("({expr} IS NOT NULL)"))
980 }
981 Expr::And(parts) => self.compile_joined(entity, parts, "AND", params),
982 Expr::Or(parts) => self.compile_joined(entity, parts, "OR", params),
983 Expr::Not(expr) => {
984 let expr = self.compile_expr(entity, expr, params)?;
985 Ok(format!("(NOT {expr})"))
986 }
987 }
988 }
989
990 fn compile_function(
991 &self,
992 entity: &EntityDescriptor,
993 function: ExprFunction,
994 args: &[Expr],
995 params: &mut Vec<Value>,
996 ) -> Result<String, SqlCompileError> {
997 match function {
998 ExprFunction::Soundex => {
999 let [arg] = args else {
1000 return Err(SqlCompileError::InvalidFunctionArguments(
1001 "SOUNDEX expects exactly one argument".to_owned(),
1002 ));
1003 };
1004 let arg = self.compile_expr(entity, arg, params)?;
1005 Ok(format!("SOUNDEX({arg})"))
1006 }
1007 ExprFunction::Gbk => self.compile_gbk_function(entity, args, params),
1008 ExprFunction::Count if args.is_empty() => Ok("COUNT(*)".to_owned()),
1009 ExprFunction::Count => self.compile_single_arg_function(entity, "COUNT", args, params),
1010 ExprFunction::Sum => self.compile_single_arg_function(entity, "SUM", args, params),
1011 ExprFunction::Avg => self.compile_single_arg_function(entity, "AVG", args, params),
1012 ExprFunction::Min => self.compile_single_arg_function(entity, "MIN", args, params),
1013 ExprFunction::Max => self.compile_single_arg_function(entity, "MAX", args, params),
1014 ExprFunction::Stddev => {
1015 self.compile_single_arg_function(entity, "STDDEV", args, params)
1016 }
1017 ExprFunction::StddevPop => {
1018 self.compile_single_arg_function(entity, "STDDEV_POP", args, params)
1019 }
1020 ExprFunction::VarSamp => {
1021 self.compile_single_arg_function(entity, "VAR_SAMP", args, params)
1022 }
1023 ExprFunction::VarPop => {
1024 self.compile_single_arg_function(entity, "VAR_POP", args, params)
1025 }
1026 ExprFunction::BitAnd => {
1027 self.compile_single_arg_function(entity, "BIT_AND", args, params)
1028 }
1029 ExprFunction::BitOr => self.compile_single_arg_function(entity, "BIT_OR", args, params),
1030 ExprFunction::BitXor => {
1031 self.compile_single_arg_function(entity, "BIT_XOR", args, params)
1032 }
1033 }
1034 }
1035
1036 fn compile_single_arg_function(
1037 &self,
1038 entity: &EntityDescriptor,
1039 function: &str,
1040 args: &[Expr],
1041 params: &mut Vec<Value>,
1042 ) -> Result<String, SqlCompileError> {
1043 let [arg] = args else {
1044 return Err(SqlCompileError::InvalidFunctionArguments(format!(
1045 "{function} expects exactly one argument"
1046 )));
1047 };
1048 let arg = self.compile_expr(entity, arg, params)?;
1049 Ok(format!("{function}({arg})"))
1050 }
1051
1052 fn compile_gbk_function(
1056 &self,
1057 entity: &EntityDescriptor,
1058 args: &[Expr],
1059 params: &mut Vec<Value>,
1060 ) -> Result<String, SqlCompileError> {
1061 let [arg] = args else {
1062 return Err(SqlCompileError::InvalidFunctionArguments(
1063 "GBK expects exactly one argument".to_owned(),
1064 ));
1065 };
1066 let arg = self.compile_expr(entity, arg, params)?;
1069 Ok(arg)
1070 }
1071
1072 fn compile_subquery(
1073 &self,
1074 entity: &EntityDescriptor,
1075 left: &Expr,
1076 op: BinaryOp,
1077 sub_entity: &EntityDescriptor,
1078 query: &SelectQuery,
1079 params: &mut Vec<Value>,
1080 ) -> Result<String, SqlCompileError> {
1081 let lhs = self.compile_expr(entity, left, params)?;
1082 let operator = match op {
1083 BinaryOp::In | BinaryOp::InLarge => "IN",
1084 BinaryOp::NotIn | BinaryOp::NotInLarge => "NOT IN",
1085 _ => return Err(SqlCompileError::InvalidSubQueryOperator(format!("{op:?}"))),
1086 };
1087 let subquery = self.compile_select_sql(sub_entity, query, params)?;
1088 Ok(format!("({lhs} {operator} ({subquery}))"))
1089 }
1090
1091 fn compile_joined(
1092 &self,
1093 entity: &EntityDescriptor,
1094 parts: &[Expr],
1095 joiner: &str,
1096 params: &mut Vec<Value>,
1097 ) -> Result<String, SqlCompileError> {
1098 let compiled = parts
1099 .iter()
1100 .map(|part| self.compile_expr(entity, part, params))
1101 .collect::<Result<Vec<_>, _>>()?;
1102 Ok(format!("({})", compiled.join(&format!(" {joiner} "))))
1103 }
1104
1105 fn compile_in(
1106 &self,
1107 entity: &EntityDescriptor,
1108 left: &Expr,
1109 op: BinaryOp,
1110 right: &Expr,
1111 params: &mut Vec<Value>,
1112 ) -> Result<String, SqlCompileError> {
1113 let lhs = self.compile_expr(entity, left, params)?;
1114 let operator = match op {
1115 BinaryOp::In | BinaryOp::InLarge => "IN",
1116 BinaryOp::NotIn | BinaryOp::NotInLarge => "NOT IN",
1117 _ => unreachable!(),
1118 };
1119 match right {
1120 Expr::Value(Value::List(values)) => {
1121 if values.is_empty() {
1122 return Err(SqlCompileError::EmptyInList);
1123 }
1124 let mut placeholders = Vec::with_capacity(values.len());
1125 for value in values {
1126 params.push(value.clone());
1127 placeholders.push(self.placeholder(params.len()));
1128 }
1129 Ok(format!("({lhs} {operator} ({}))", placeholders.join(", ")))
1130 }
1131 _ => {
1132 let rhs = self.compile_expr(entity, right, params)?;
1133 Ok(format!("({lhs} {operator} ({rhs}))"))
1134 }
1135 }
1136 }
1137
1138 fn compile_projection(
1139 &self,
1140 entity: &EntityDescriptor,
1141 query: &SelectQuery,
1142 params: &mut Vec<Value>,
1143 ) -> Result<String, SqlCompileError> {
1144 match query.aggregates.is_empty() {
1145 true => self.select_projection(entity, query, params),
1146 false => self.aggregate_projection(entity, query, params),
1147 }
1148 }
1149
1150 fn resolve_order_field(
1151 &self,
1152 entity: &EntityDescriptor,
1153 order_by: &OrderBy,
1154 params: &mut Vec<Value>,
1155 ) -> Result<String, SqlCompileError> {
1156 match &order_by.expr {
1157 Some(expr) => self.compile_expr(entity, expr, params),
1158 None => self.column_sql(entity, &order_by.field),
1159 }
1160 }
1161
1162 fn column_with_alias(&self, property: &PropertyDescriptor) -> String {
1163 let column = self.quote_ident(&property.column_name);
1164 match property.column_name == property.name {
1165 true => column,
1166 false => format!("{column} AS {}", self.quote_ident(&property.name)),
1167 }
1168 }
1169
1170 fn resolve_aggregate_field(
1171 &self,
1172 entity: &EntityDescriptor,
1173 aggregate: &Aggregate,
1174 ) -> Result<String, SqlCompileError> {
1175 match aggregate.function == AggregateFunction::Count && aggregate.field == "*" {
1176 true => Ok("*".to_owned()),
1177 false => self.column_sql(entity, &aggregate.field),
1178 }
1179 }
1180}
1181
1182#[cfg(test)]
1183mod tests {
1184 use super::*;
1185 use crate::DatabaseKind;
1186 use teaql_core::{DataType, EntityDescriptor, InsertCommand, PropertyDescriptor, Value};
1187
1188 struct TestDialect;
1189 impl crate::SqlDialect for TestDialect {
1190 fn kind(&self) -> DatabaseKind {
1191 DatabaseKind::PostgreSql
1192 }
1193 fn quote_ident(&self, ident: &str) -> String {
1194 ident.to_owned()
1195 }
1196 fn placeholder(&self, index: usize) -> String {
1197 format!("${index}")
1198 }
1199 fn schema_type_sql(
1200 &self,
1201 _data_type: DataType,
1202 _property: &PropertyDescriptor,
1203 ) -> Result<&'static str, SqlCompileError> {
1204 Ok("TEST")
1205 }
1206 }
1207
1208 #[test]
1209 fn test_regression_issue_56_typed_null_conversion() {
1210 let dialect = TestDialect;
1211 let mut entity = EntityDescriptor::new("User");
1212 entity
1213 .properties
1214 .push(PropertyDescriptor::new("paid_at", DataType::Timestamp));
1215
1216 let mut command = InsertCommand::new("User");
1217 command = command.value("paid_at", Value::Null);
1218
1219 let query = dialect.compile_insert(&entity, &command).unwrap();
1220 assert_eq!(query.params.len(), 1);
1222 assert_eq!(query.params[0], Value::TypedNull(DataType::Timestamp));
1223 }
1224}