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