1use super::ir::{AuthoringIR, IRArgument, IRMutation, IRQuery, MutationOperation};
36use crate::error::Result;
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
40#[non_exhaustive]
41pub enum DatabaseTarget {
42 #[default]
44 PostgreSQL,
45 MySQL,
47 SQLite,
49 SQLServer,
51}
52
53impl DatabaseTarget {
54 fn placeholder(self, index: usize) -> String {
56 match self {
57 Self::PostgreSQL => format!("${index}"),
58 Self::MySQL | Self::SQLite => "?".to_string(),
59 Self::SQLServer => format!("@p{index}"),
60 }
61 }
62
63 fn quote_identifier(self, name: &str) -> String {
65 match self {
66 Self::PostgreSQL | Self::SQLite => format!("\"{name}\""),
67 Self::MySQL => format!("`{name}`"),
68 Self::SQLServer => format!("[{name}]"),
69 }
70 }
71
72 fn jsonb_extract(self, column: &str, path: &str) -> String {
74 match self {
75 Self::PostgreSQL => format!("{column}->'{path}'"),
76 Self::MySQL => format!("JSON_EXTRACT({column}, '$.{path}')"),
77 Self::SQLite => format!("json_extract({column}, '$.{path}')"),
78 Self::SQLServer => format!("JSON_VALUE({column}, '$.{path}')"),
79 }
80 }
81
82 fn limit_offset(self, limit: Option<&str>, offset: Option<&str>) -> String {
84 match self {
85 Self::PostgreSQL | Self::SQLite | Self::MySQL => {
86 let mut parts = Vec::new();
87 if let Some(lim) = limit {
88 parts.push(format!("LIMIT {lim}"));
89 }
90 if let Some(off) = offset {
91 parts.push(format!("OFFSET {off}"));
92 }
93 parts.join(" ")
94 },
95 Self::SQLServer => {
96 let mut parts = Vec::new();
98 if let Some(off) = offset {
99 parts.push(format!("OFFSET {off} ROWS"));
100 if let Some(lim) = limit {
101 parts.push(format!("FETCH NEXT {lim} ROWS ONLY"));
102 }
103 } else if let Some(lim) = limit {
104 parts.push(format!("TOP {lim}"));
106 }
107 parts.join(" ")
108 },
109 }
110 }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115#[non_exhaustive]
116pub enum TemplateKind {
117 Query,
119 Insert,
121 Update,
123 Delete,
125 Custom,
127}
128
129#[derive(Debug, Clone)]
131pub struct SqlTemplate {
132 pub name: String,
134 pub kind: TemplateKind,
136 pub template: String,
138 pub parameters: Vec<String>,
140 pub supports_where: bool,
142 pub supports_order_by: bool,
144 pub supports_pagination: bool,
146}
147
148impl SqlTemplate {
149 const fn query(name: String, template: String, parameters: Vec<String>) -> Self {
151 Self {
152 name,
153 kind: TemplateKind::Query,
154 template,
155 parameters,
156 supports_where: false,
157 supports_order_by: false,
158 supports_pagination: false,
159 }
160 }
161
162 #[must_use]
164 pub const fn with_where(mut self) -> Self {
165 self.supports_where = true;
166 self
167 }
168
169 #[must_use]
171 pub const fn with_order_by(mut self) -> Self {
172 self.supports_order_by = true;
173 self
174 }
175
176 #[must_use]
178 pub const fn with_pagination(mut self) -> Self {
179 self.supports_pagination = true;
180 self
181 }
182}
183
184pub struct SqlTemplateGenerator {
186 target: DatabaseTarget,
187}
188
189impl SqlTemplateGenerator {
190 #[must_use]
192 pub const fn new(target: DatabaseTarget) -> Self {
193 Self { target }
194 }
195
196 pub fn generate(&self, ir: &AuthoringIR) -> Result<Vec<SqlTemplate>> {
210 let mut templates = Vec::new();
211
212 for query in &ir.queries {
214 templates.push(self.generate_query_template(query));
215 }
216
217 for mutation in &ir.mutations {
219 templates.push(self.generate_mutation_template(mutation));
220 }
221
222 Ok(templates)
223 }
224
225 fn generate_query_template(&self, query: &IRQuery) -> SqlTemplate {
227 let table = query.sql_source.as_deref().unwrap_or(&query.return_type);
228 let quoted_table = self.target.quote_identifier(table);
229
230 let parameters: Vec<String> = query.arguments.iter().map(|a| a.name.clone()).collect();
232
233 let mut template = format!("SELECT data FROM {quoted_table}");
235
236 if query.auto_params.has_where || !query.arguments.is_empty() {
238 template.push_str(" {{where_clause}}");
239 }
240
241 if query.auto_params.has_order_by {
243 template.push_str(" {{order_by}}");
244 }
245
246 if query.auto_params.has_limit || query.auto_params.has_offset {
248 template.push_str(" {{pagination}}");
249 }
250
251 let mut sql_template = SqlTemplate::query(query.name.clone(), template, parameters);
252
253 if query.auto_params.has_where || !query.arguments.is_empty() {
254 sql_template = sql_template.with_where();
255 }
256 if query.auto_params.has_order_by {
257 sql_template = sql_template.with_order_by();
258 }
259 if query.auto_params.has_limit || query.auto_params.has_offset {
260 sql_template = sql_template.with_pagination();
261 }
262
263 sql_template
264 }
265
266 fn generate_mutation_template(&self, mutation: &IRMutation) -> SqlTemplate {
268 let table = mutation.return_type.to_lowercase();
270 let quoted_table = self.target.quote_identifier(&table);
271
272 let parameters: Vec<String> = mutation.arguments.iter().map(|a| a.name.clone()).collect();
274
275 let (template, kind) = match mutation.operation {
276 MutationOperation::Create => {
277 self.generate_insert_template("ed_table, &mutation.arguments)
278 },
279 MutationOperation::Update => {
280 self.generate_update_template("ed_table, &mutation.arguments)
281 },
282 MutationOperation::Delete => {
283 self.generate_delete_template("ed_table, &mutation.arguments)
284 },
285 MutationOperation::Custom => {
286 (format!("-- Custom mutation: {}", mutation.name), TemplateKind::Custom)
287 },
288 };
289
290 SqlTemplate {
291 name: mutation.name.clone(),
292 kind,
293 template,
294 parameters,
295 supports_where: false,
296 supports_order_by: false,
297 supports_pagination: false,
298 }
299 }
300
301 fn generate_insert_template(
303 &self,
304 quoted_table: &str,
305 arguments: &[IRArgument],
306 ) -> (String, TemplateKind) {
307 if arguments.is_empty() {
308 return (
309 format!("INSERT INTO {quoted_table} (data) VALUES ({{data}}) RETURNING data"),
310 TemplateKind::Insert,
311 );
312 }
313
314 let has_input = arguments.iter().any(|a| a.name == "input");
316
317 if has_input {
318 (
320 format!("INSERT INTO {quoted_table} (data) VALUES ({{input}}) RETURNING data"),
321 TemplateKind::Insert,
322 )
323 } else {
324 let columns: Vec<&str> = arguments.iter().map(|a| a.name.as_str()).collect();
326 let placeholders: Vec<String> =
327 (1..=columns.len()).map(|i| self.target.placeholder(i)).collect();
328
329 (
330 format!(
331 "INSERT INTO {quoted_table} ({}) VALUES ({}) RETURNING data",
332 columns.join(", "),
333 placeholders.join(", ")
334 ),
335 TemplateKind::Insert,
336 )
337 }
338 }
339
340 fn generate_update_template(
342 &self,
343 quoted_table: &str,
344 arguments: &[IRArgument],
345 ) -> (String, TemplateKind) {
346 let id_arg = arguments.iter().find(|a| a.name == "id" || a.name == "where");
348
349 let input_arg = arguments.iter().find(|a| a.name == "input" || a.name == "data");
351
352 match (id_arg, input_arg) {
353 (Some(id), Some(_)) => (
354 format!(
355 "UPDATE {quoted_table} SET data = {{input}} WHERE {} = {{{}}} RETURNING data",
356 self.target.jsonb_extract("data", "id"),
357 id.name
358 ),
359 TemplateKind::Update,
360 ),
361 (Some(id), None) => {
362 let set_clauses: Vec<String> = arguments
364 .iter()
365 .filter(|a| a.name != "id" && a.name != "where")
366 .map(|a| {
367 format!("data = jsonb_set(data, '{{{{{}}}}}', {{{}}})", a.name, a.name)
368 })
369 .collect();
370
371 if set_clauses.is_empty() {
372 (
373 format!(
374 "UPDATE {quoted_table} SET data = {{data}} WHERE {} = {{{}}} RETURNING data",
375 self.target.jsonb_extract("data", "id"),
376 id.name
377 ),
378 TemplateKind::Update,
379 )
380 } else {
381 (
382 format!(
383 "UPDATE {quoted_table} SET {} WHERE {} = {{{}}} RETURNING data",
384 set_clauses.join(", "),
385 self.target.jsonb_extract("data", "id"),
386 id.name
387 ),
388 TemplateKind::Update,
389 )
390 }
391 },
392 _ => (
393 format!(
394 "UPDATE {quoted_table} SET data = {{data}} WHERE {{where_clause}} RETURNING data"
395 ),
396 TemplateKind::Update,
397 ),
398 }
399 }
400
401 fn generate_delete_template(
403 &self,
404 quoted_table: &str,
405 arguments: &[IRArgument],
406 ) -> (String, TemplateKind) {
407 let id_arg = arguments.iter().find(|a| a.name == "id" || a.name == "where");
409
410 if let Some(id) = id_arg {
411 (
412 format!(
413 "DELETE FROM {quoted_table} WHERE {} = {{{}}} RETURNING data",
414 self.target.jsonb_extract("data", "id"),
415 id.name
416 ),
417 TemplateKind::Delete,
418 )
419 } else {
420 (
421 format!("DELETE FROM {quoted_table} WHERE {{where_clause}} RETURNING data"),
422 TemplateKind::Delete,
423 )
424 }
425 }
426
427 #[must_use]
429 pub const fn target(&self) -> DatabaseTarget {
430 self.target
431 }
432
433 pub fn expand_template(
441 &self,
442 template: &SqlTemplate,
443 params: &std::collections::HashMap<String, serde_json::Value>,
444 where_clause: Option<&str>,
445 order_by: Option<&str>,
446 limit: Option<u64>,
447 offset: Option<u64>,
448 ) -> String {
449 let mut sql = template.template.clone();
450
451 for (i, param_name) in template.parameters.iter().enumerate() {
453 let placeholder = format!("{{{param_name}}}");
454 if let Some(value) = params.get(param_name) {
455 let replacement = self.value_to_sql(value);
456 sql = sql.replace(&placeholder, &replacement);
457 } else {
458 sql = sql.replace(&placeholder, &self.target.placeholder(i + 1));
460 }
461 }
462
463 if template.supports_where {
465 let where_sql = where_clause.map(|w| format!("WHERE {w}")).unwrap_or_default();
466 sql = sql.replace("{{where_clause}}", &where_sql);
467 }
468
469 if template.supports_order_by {
471 let order_sql = order_by.map(|o| format!("ORDER BY {o}")).unwrap_or_default();
472 sql = sql.replace("{{order_by}}", &order_sql);
473 }
474
475 if template.supports_pagination {
477 let limit_str = limit.map(|l| l.to_string());
478 let offset_str = offset.map(|o| o.to_string());
479 let pagination_sql =
480 self.target.limit_offset(limit_str.as_deref(), offset_str.as_deref());
481 sql = sql.replace("{{pagination}}", &pagination_sql);
482 }
483
484 sql = sql.replace("{{where_clause}}", "");
486 sql = sql.replace("{{order_by}}", "");
487 sql = sql.replace("{{pagination}}", "");
488
489 sql.split_whitespace().collect::<Vec<_>>().join(" ")
491 }
492
493 fn value_to_sql(&self, value: &serde_json::Value) -> String {
495 match value {
496 serde_json::Value::Null => "NULL".to_string(),
497 serde_json::Value::Bool(b) => if *b { "TRUE" } else { "FALSE" }.to_string(),
498 serde_json::Value::Number(n) => n.to_string(),
499 serde_json::Value::String(s) => format!("'{}'", s.replace('\'', "''")),
500 serde_json::Value::Array(_) | serde_json::Value::Object(_) => {
501 format!("'{}'", value.to_string().replace('\'', "''"))
502 },
503 }
504 }
505}
506
507#[cfg(test)]
508mod tests {
509 #![allow(clippy::unwrap_used)] use super::{
512 super::ir::{AutoParams, IRArgument},
513 *,
514 };
515
516 #[test]
517 fn test_sql_template_generator_new() {
518 let generator = SqlTemplateGenerator::new(DatabaseTarget::PostgreSQL);
519 assert_eq!(generator.target(), DatabaseTarget::PostgreSQL);
520 }
521
522 #[test]
523 fn test_database_target_equality() {
524 assert_eq!(DatabaseTarget::PostgreSQL, DatabaseTarget::PostgreSQL);
525 assert_ne!(DatabaseTarget::PostgreSQL, DatabaseTarget::MySQL);
526 }
527
528 #[test]
529 fn test_database_target_placeholder() {
530 assert_eq!(DatabaseTarget::PostgreSQL.placeholder(1), "$1");
531 assert_eq!(DatabaseTarget::PostgreSQL.placeholder(5), "$5");
532 assert_eq!(DatabaseTarget::MySQL.placeholder(1), "?");
533 assert_eq!(DatabaseTarget::SQLite.placeholder(1), "?");
534 assert_eq!(DatabaseTarget::SQLServer.placeholder(1), "@p1");
535 }
536
537 #[test]
538 fn test_database_target_quote_identifier() {
539 assert_eq!(DatabaseTarget::PostgreSQL.quote_identifier("users"), "\"users\"");
540 assert_eq!(DatabaseTarget::MySQL.quote_identifier("users"), "`users`");
541 assert_eq!(DatabaseTarget::SQLite.quote_identifier("users"), "\"users\"");
542 assert_eq!(DatabaseTarget::SQLServer.quote_identifier("users"), "[users]");
543 }
544
545 #[test]
546 fn test_database_target_jsonb_extract() {
547 assert_eq!(DatabaseTarget::PostgreSQL.jsonb_extract("data", "name"), "data->'name'");
548 assert_eq!(
549 DatabaseTarget::MySQL.jsonb_extract("data", "name"),
550 "JSON_EXTRACT(data, '$.name')"
551 );
552 assert_eq!(
553 DatabaseTarget::SQLite.jsonb_extract("data", "name"),
554 "json_extract(data, '$.name')"
555 );
556 assert_eq!(
557 DatabaseTarget::SQLServer.jsonb_extract("data", "name"),
558 "JSON_VALUE(data, '$.name')"
559 );
560 }
561
562 #[test]
563 fn test_database_target_limit_offset() {
564 assert_eq!(DatabaseTarget::PostgreSQL.limit_offset(Some("10"), None), "LIMIT 10");
566 assert_eq!(
567 DatabaseTarget::PostgreSQL.limit_offset(Some("10"), Some("5")),
568 "LIMIT 10 OFFSET 5"
569 );
570 assert_eq!(DatabaseTarget::PostgreSQL.limit_offset(None, Some("5")), "OFFSET 5");
571
572 assert_eq!(DatabaseTarget::SQLServer.limit_offset(Some("10"), None), "TOP 10");
574 assert_eq!(
575 DatabaseTarget::SQLServer.limit_offset(Some("10"), Some("5")),
576 "OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY"
577 );
578 }
579
580 #[test]
581 fn test_generate_query_template_basic() {
582 let generator = SqlTemplateGenerator::new(DatabaseTarget::PostgreSQL);
583
584 let query = IRQuery {
585 name: "users".to_string(),
586 return_type: "User".to_string(),
587 returns_list: true,
588 nullable: false,
589 arguments: vec![],
590 sql_source: Some("v_user".to_string()),
591 description: None,
592 auto_params: AutoParams::default(),
593 };
594
595 let template = generator.generate_query_template(&query);
596
597 assert_eq!(template.name, "users");
598 assert_eq!(template.kind, TemplateKind::Query);
599 assert_eq!(template.template, "SELECT data FROM \"v_user\"");
600 assert!(!template.supports_where);
601 assert!(!template.supports_order_by);
602 assert!(!template.supports_pagination);
603 }
604
605 #[test]
606 fn test_generate_query_template_with_auto_params() {
607 let generator = SqlTemplateGenerator::new(DatabaseTarget::PostgreSQL);
608
609 let query = IRQuery {
610 name: "users".to_string(),
611 return_type: "User".to_string(),
612 returns_list: true,
613 nullable: false,
614 arguments: vec![],
615 sql_source: Some("v_user".to_string()),
616 description: None,
617 auto_params: AutoParams {
618 has_where: true,
619 has_order_by: true,
620 has_limit: true,
621 has_offset: true,
622 },
623 };
624
625 let template = generator.generate_query_template(&query);
626
627 assert!(template.template.contains("{{where_clause}}"));
628 assert!(template.template.contains("{{order_by}}"));
629 assert!(template.template.contains("{{pagination}}"));
630 assert!(template.supports_where);
631 assert!(template.supports_order_by);
632 assert!(template.supports_pagination);
633 }
634
635 #[test]
636 fn test_generate_query_template_with_arguments() {
637 let generator = SqlTemplateGenerator::new(DatabaseTarget::PostgreSQL);
638
639 let query = IRQuery {
640 name: "user".to_string(),
641 return_type: "User".to_string(),
642 returns_list: false,
643 nullable: true,
644 arguments: vec![IRArgument {
645 name: "id".to_string(),
646 arg_type: "ID!".to_string(),
647 nullable: false,
648 default_value: None,
649 description: None,
650 }],
651 sql_source: Some("v_user".to_string()),
652 description: None,
653 auto_params: AutoParams::default(),
654 };
655
656 let template = generator.generate_query_template(&query);
657
658 assert_eq!(template.parameters, vec!["id"]);
659 assert!(template.supports_where); }
661
662 #[test]
663 fn test_generate_mutation_template_insert() {
664 let generator = SqlTemplateGenerator::new(DatabaseTarget::PostgreSQL);
665
666 let mutation = IRMutation {
667 name: "createUser".to_string(),
668 return_type: "User".to_string(),
669 nullable: false,
670 arguments: vec![IRArgument {
671 name: "input".to_string(),
672 arg_type: "CreateUserInput!".to_string(),
673 nullable: false,
674 default_value: None,
675 description: None,
676 }],
677 description: None,
678 operation: MutationOperation::Create,
679 };
680
681 let template = generator.generate_mutation_template(&mutation);
682
683 assert_eq!(template.name, "createUser");
684 assert_eq!(template.kind, TemplateKind::Insert);
685 assert!(template.template.contains("INSERT INTO"));
686 assert!(template.template.contains("{input}"));
687 assert!(template.template.contains("RETURNING data"));
688 }
689
690 #[test]
691 fn test_generate_mutation_template_update() {
692 let generator = SqlTemplateGenerator::new(DatabaseTarget::PostgreSQL);
693
694 let mutation = IRMutation {
695 name: "updateUser".to_string(),
696 return_type: "User".to_string(),
697 nullable: false,
698 arguments: vec![
699 IRArgument {
700 name: "id".to_string(),
701 arg_type: "ID!".to_string(),
702 nullable: false,
703 default_value: None,
704 description: None,
705 },
706 IRArgument {
707 name: "input".to_string(),
708 arg_type: "UpdateUserInput!".to_string(),
709 nullable: false,
710 default_value: None,
711 description: None,
712 },
713 ],
714 description: None,
715 operation: MutationOperation::Update,
716 };
717
718 let template = generator.generate_mutation_template(&mutation);
719
720 assert_eq!(template.name, "updateUser");
721 assert_eq!(template.kind, TemplateKind::Update);
722 assert!(template.template.contains("UPDATE"));
723 assert!(template.template.contains("{id}"));
724 assert!(template.template.contains("RETURNING data"));
725 }
726
727 #[test]
728 fn test_generate_mutation_template_delete() {
729 let generator = SqlTemplateGenerator::new(DatabaseTarget::PostgreSQL);
730
731 let mutation = IRMutation {
732 name: "deleteUser".to_string(),
733 return_type: "User".to_string(),
734 nullable: false,
735 arguments: vec![IRArgument {
736 name: "id".to_string(),
737 arg_type: "ID!".to_string(),
738 nullable: false,
739 default_value: None,
740 description: None,
741 }],
742 description: None,
743 operation: MutationOperation::Delete,
744 };
745
746 let template = generator.generate_mutation_template(&mutation);
747
748 assert_eq!(template.name, "deleteUser");
749 assert_eq!(template.kind, TemplateKind::Delete);
750 assert!(template.template.contains("DELETE FROM"));
751 assert!(template.template.contains("{id}"));
752 }
753
754 #[test]
755 fn test_generate_all_templates() {
756 let generator = SqlTemplateGenerator::new(DatabaseTarget::PostgreSQL);
757 let mut ir = AuthoringIR::new();
758
759 ir.queries.push(IRQuery {
760 name: "users".to_string(),
761 return_type: "User".to_string(),
762 returns_list: true,
763 nullable: false,
764 arguments: vec![],
765 sql_source: Some("v_user".to_string()),
766 description: None,
767 auto_params: AutoParams::default(),
768 });
769
770 ir.mutations.push(IRMutation {
771 name: "createUser".to_string(),
772 return_type: "User".to_string(),
773 nullable: false,
774 arguments: vec![],
775 description: None,
776 operation: MutationOperation::Create,
777 });
778
779 let templates = generator.generate(&ir).unwrap();
780
781 assert_eq!(templates.len(), 2);
782 assert_eq!(templates[0].name, "users");
783 assert_eq!(templates[1].name, "createUser");
784 }
785
786 #[test]
787 fn test_expand_template_basic() {
788 let generator = SqlTemplateGenerator::new(DatabaseTarget::PostgreSQL);
789
790 let template = SqlTemplate::query(
791 "users".to_string(),
792 "SELECT data FROM \"v_user\"".to_string(),
793 vec![],
794 );
795
796 let params = std::collections::HashMap::new();
797 let sql = generator.expand_template(&template, ¶ms, None, None, None, None);
798
799 assert_eq!(sql, "SELECT data FROM \"v_user\"");
800 }
801
802 #[test]
803 fn test_expand_template_with_where() {
804 let generator = SqlTemplateGenerator::new(DatabaseTarget::PostgreSQL);
805
806 let template = SqlTemplate::query(
807 "users".to_string(),
808 "SELECT data FROM \"v_user\" {{where_clause}}".to_string(),
809 vec![],
810 )
811 .with_where();
812
813 let params = std::collections::HashMap::new();
814 let sql = generator.expand_template(
815 &template,
816 ¶ms,
817 Some("data->>'status' = 'active'"),
818 None,
819 None,
820 None,
821 );
822
823 assert_eq!(sql, "SELECT data FROM \"v_user\" WHERE data->>'status' = 'active'");
824 }
825
826 #[test]
827 fn test_expand_template_with_pagination() {
828 let generator = SqlTemplateGenerator::new(DatabaseTarget::PostgreSQL);
829
830 let template = SqlTemplate::query(
831 "users".to_string(),
832 "SELECT data FROM \"v_user\" {{pagination}}".to_string(),
833 vec![],
834 )
835 .with_pagination();
836
837 let params = std::collections::HashMap::new();
838 let sql = generator.expand_template(&template, ¶ms, None, None, Some(10), Some(5));
839
840 assert_eq!(sql, "SELECT data FROM \"v_user\" LIMIT 10 OFFSET 5");
841 }
842
843 #[test]
844 fn test_expand_template_with_order_by() {
845 let generator = SqlTemplateGenerator::new(DatabaseTarget::PostgreSQL);
846
847 let template = SqlTemplate::query(
848 "users".to_string(),
849 "SELECT data FROM \"v_user\" {{order_by}}".to_string(),
850 vec![],
851 )
852 .with_order_by();
853
854 let params = std::collections::HashMap::new();
855 let sql = generator.expand_template(
856 &template,
857 ¶ms,
858 None,
859 Some("data->>'name' ASC"),
860 None,
861 None,
862 );
863
864 assert_eq!(sql, "SELECT data FROM \"v_user\" ORDER BY data->>'name' ASC");
865 }
866
867 #[test]
868 fn test_value_to_sql() {
869 let generator = SqlTemplateGenerator::new(DatabaseTarget::PostgreSQL);
870
871 assert_eq!(generator.value_to_sql(&serde_json::Value::Null), "NULL");
872 assert_eq!(generator.value_to_sql(&serde_json::json!(true)), "TRUE");
873 assert_eq!(generator.value_to_sql(&serde_json::json!(false)), "FALSE");
874 assert_eq!(generator.value_to_sql(&serde_json::json!(42)), "42");
875 assert_eq!(generator.value_to_sql(&serde_json::json!(1.5)), "1.5");
876 assert_eq!(generator.value_to_sql(&serde_json::json!("hello")), "'hello'");
877 assert_eq!(generator.value_to_sql(&serde_json::json!("it's")), "'it''s'"); }
879
880 #[test]
881 fn test_mysql_query_template() {
882 let generator = SqlTemplateGenerator::new(DatabaseTarget::MySQL);
883
884 let query = IRQuery {
885 name: "users".to_string(),
886 return_type: "User".to_string(),
887 returns_list: true,
888 nullable: false,
889 arguments: vec![],
890 sql_source: Some("v_user".to_string()),
891 description: None,
892 auto_params: AutoParams::default(),
893 };
894
895 let template = generator.generate_query_template(&query);
896
897 assert!(template.template.contains("`v_user`"));
899 }
900
901 #[test]
902 fn test_sqlserver_query_template() {
903 let generator = SqlTemplateGenerator::new(DatabaseTarget::SQLServer);
904
905 let query = IRQuery {
906 name: "users".to_string(),
907 return_type: "User".to_string(),
908 returns_list: true,
909 nullable: false,
910 arguments: vec![],
911 sql_source: Some("v_user".to_string()),
912 description: None,
913 auto_params: AutoParams::default(),
914 };
915
916 let template = generator.generate_query_template(&query);
917
918 assert!(template.template.contains("[v_user]"));
920 }
921}