1mod ast_literal;
2mod data_type;
3mod ddl;
4mod expr;
5mod function;
6mod operator;
7mod query;
8
9pub use {
10 ast_literal::{AstLiteral, DateTimeField, TrimWhereField},
11 data_type::DataType,
12 ddl::*,
13 expr::Expr,
14 function::{Aggregate, CountArgExpr, Function},
15 operator::*,
16 query::*,
17};
18
19use {
20 serde::{Deserialize, Serialize},
21 strum_macros::Display,
22};
23
24pub trait ToSql {
25 fn to_sql(&self) -> String;
26}
27
28pub trait ToSqlUnquoted {
29 fn to_sql_unquoted(&self) -> String;
30}
31
32#[derive(PartialEq, Debug, Clone, Eq, Hash, Serialize, Deserialize)]
33pub struct ForeignKey {
34 pub name: String,
35 pub referencing_column_name: String,
36 pub referenced_table_name: String,
37 pub referenced_column_name: String,
38 pub on_delete: ReferentialAction,
39 pub on_update: ReferentialAction,
40}
41
42#[derive(PartialEq, Debug, Clone, Eq, Hash, Serialize, Deserialize, Display)]
43pub enum ReferentialAction {
44 #[strum(to_string = "NO ACTION")]
45 NoAction,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
49pub enum Statement {
50 ShowColumns {
51 table_name: String,
52 },
53 Query(Query),
55 Insert {
57 table_name: String,
59 columns: Vec<String>,
61 source: Query,
63 },
64 Update {
66 table_name: String,
68 assignments: Vec<Assignment>,
70 selection: Option<Expr>,
72 },
73 Delete {
75 table_name: String,
77 selection: Option<Expr>,
79 },
80 CreateTable {
82 if_not_exists: bool,
83 name: String,
85 columns: Option<Vec<ColumnDef>>,
87 source: Option<Box<Query>>,
88 engine: Option<String>,
89 foreign_keys: Vec<ForeignKey>,
90 comment: Option<String>,
91 },
92 CreateFunction {
94 or_replace: bool,
95 name: String,
96 args: Vec<OperateFunctionArg>,
98 return_: Expr,
99 },
100 AlterTable {
102 name: String,
104 operation: AlterTableOperation,
105 },
106 DropTable {
108 if_exists: bool,
110 names: Vec<String>,
112 cascade: bool,
114 },
115 DropFunction {
117 if_exists: bool,
119 names: Vec<String>,
121 },
122 CreateIndex {
124 name: String,
125 table_name: String,
126 column: OrderByExpr,
127 },
128 DropIndex {
130 name: String,
131 table_name: String,
132 },
133 StartTransaction,
135 Commit,
137 Rollback,
139 ShowVariable(Variable),
141 ShowIndexes(String),
142}
143
144#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
145pub struct Assignment {
146 pub id: String,
147 pub value: Expr,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
151pub enum Variable {
152 Tables,
153 Functions,
154 Version,
155}
156
157impl ToSql for Statement {
158 fn to_sql(&self) -> String {
159 match self {
160 Statement::ShowColumns { table_name } => {
161 format!("SHOW COLUMNS FROM {table_name};")
162 }
163 Statement::Insert {
164 table_name,
165 columns,
166 source,
167 } => {
168 let columns = match columns.is_empty() {
169 true => "".to_owned(),
170 false => format!("({}) ", columns.join(", ")),
171 };
172
173 format!("INSERT INTO {table_name} {columns}{};", source.to_sql())
174 }
175 Statement::Update {
176 table_name,
177 assignments,
178 selection,
179 } => {
180 let assignments = assignments
181 .iter()
182 .map(ToSql::to_sql)
183 .collect::<Vec<_>>()
184 .join(", ");
185 match selection {
186 Some(expr) => {
187 format!(
188 r#"UPDATE "{table_name}" SET {assignments} WHERE {};"#,
189 expr.to_sql()
190 )
191 }
192 None => format!(r#"UPDATE "{table_name}" SET {assignments};"#),
193 }
194 }
195 Statement::Delete {
196 table_name,
197 selection,
198 } => match selection {
199 Some(expr) => format!(r#"DELETE FROM "{table_name}" WHERE {};"#, expr.to_sql()),
200 None => format!(r#"DELETE FROM "{table_name}";"#),
201 },
202 Statement::CreateTable {
203 if_not_exists,
204 name,
205 columns,
206 source,
207 engine,
208 foreign_keys,
209 comment,
210 } => {
211 let if_not_exists = if_not_exists.then_some("IF NOT EXISTS");
212 let body = match (source, columns) {
213 (Some(query), _) => Some(format!("AS {}", query.to_sql())),
214 (None, None) => None,
215 (None, Some(columns)) => {
216 let foreign_keys = foreign_keys.iter().map(ToSql::to_sql);
217 let body = columns
218 .iter()
219 .map(ToSql::to_sql)
220 .chain(foreign_keys)
221 .collect::<Vec<_>>()
222 .join(", ");
223
224 Some(format!("({body})"))
225 }
226 };
227 let engine = engine.as_ref().map(|engine| format!("ENGINE = {engine}"));
228 let comment = comment
229 .as_ref()
230 .map(|comment| format!("COMMENT = '{comment}'"));
231 let sql = vec![
232 Some("CREATE TABLE"),
233 if_not_exists,
234 Some(&format! {r#""{name}""#}),
235 body.as_deref(),
236 engine.as_deref(),
237 comment.as_deref(),
238 ]
239 .into_iter()
240 .flatten()
241 .collect::<Vec<&str>>()
242 .join(" ");
243
244 format!("{sql};")
245 }
246 Statement::CreateFunction {
247 or_replace,
248 name,
249 args,
250 return_,
251 ..
252 } => {
253 let or_replace = or_replace.then_some(" OR REPLACE").unwrap_or("");
254 let args = args
255 .iter()
256 .map(ToSql::to_sql)
257 .collect::<Vec<_>>()
258 .join(", ");
259 let return_ = format!(" RETURN {}", return_.to_sql());
260 format!("CREATE{or_replace} FUNCTION {name}({args}){return_};")
261 }
262 Statement::AlterTable { name, operation } => {
263 format!(r#"ALTER TABLE "{name}" {};"#, operation.to_sql())
264 }
265 Statement::DropTable {
266 if_exists,
267 names,
268 cascade,
269 } => {
270 let if_exists = if_exists.then_some("IF EXISTS").unwrap_or_default();
271 let names = names
272 .iter()
273 .map(|name| format!(r#""{name}""#))
274 .collect::<Vec<_>>()
275 .join(", ");
276 let cascade = cascade.then_some("CASCADE").unwrap_or_default();
277
278 vec!["DROP TABLE", if_exists, &names, cascade]
279 .into_iter()
280 .filter(|s| !s.is_empty())
281 .collect::<Vec<_>>()
282 .join(" ")
283 + ";"
284 }
285 Statement::DropFunction { if_exists, names } => {
286 let names = names.join(", ");
287 match if_exists {
288 true => format!("DROP FUNCTION IF EXISTS {};", names),
289 false => format!("DROP FUNCTION {};", names),
290 }
291 }
292 Statement::CreateIndex {
293 name,
294 table_name,
295 column,
296 } => {
297 format!(
298 r#"CREATE INDEX "{name}" ON "{table_name}" ({});"#,
299 column.to_sql()
300 )
301 }
302 Statement::DropIndex { name, table_name } => {
303 format!("DROP INDEX {table_name}.{name};")
304 }
305 Statement::StartTransaction => "START TRANSACTION;".to_owned(),
306 Statement::Commit => "COMMIT;".to_owned(),
307 Statement::Rollback => "ROLLBACK;".to_owned(),
308 Statement::ShowVariable(variable) => match variable {
309 Variable::Tables => "SHOW TABLES;".to_owned(),
310 Variable::Functions => "SHOW FUNCTIONS;".to_owned(),
311 Variable::Version => "SHOW VERSIONS;".to_owned(),
312 },
313 Statement::ShowIndexes(object_name) => {
314 format!(r#"SHOW INDEXES FROM "{object_name}";"#)
315 }
316 _ => "(..statement..)".to_owned(),
317 }
318 }
319}
320
321impl ToSql for Assignment {
322 fn to_sql(&self) -> String {
323 format!(r#""{}" = {}"#, self.id, self.value.to_sql())
324 }
325}
326
327impl ToSql for ForeignKey {
328 fn to_sql(&self) -> String {
329 let ForeignKey {
330 referencing_column_name,
331 referenced_table_name,
332 referenced_column_name,
333 name,
334 on_delete,
335 on_update,
336 } = self;
337
338 format!(
339 r#"CONSTRAINT "{}" FOREIGN KEY ("{}") REFERENCES "{}" ("{}") ON DELETE {} ON UPDATE {}"#,
340 name,
341 referencing_column_name,
342 referenced_table_name,
343 referenced_column_name,
344 on_delete,
345 on_update
346 )
347 }
348}
349
350#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
351pub struct Array {
352 pub elem: Vec<Expr>,
353 pub named: bool,
354}
355
356#[cfg(test)]
357mod tests {
358 use {
359 crate::ast::{
360 AlterTableOperation, Assignment, AstLiteral, BinaryOperator, ColumnDef, DataType, Expr,
361 ForeignKey, OperateFunctionArg, OrderByExpr, Query, ReferentialAction, Select,
362 SelectItem, SetExpr, Statement, TableFactor, TableWithJoins, ToSql, Values, Variable,
363 },
364 bigdecimal::BigDecimal,
365 std::str::FromStr,
366 };
367
368 #[test]
369 fn to_sql_show_columns() {
370 assert_eq!(
371 "SHOW COLUMNS FROM Bar;",
372 Statement::ShowColumns {
373 table_name: "Bar".into()
374 }
375 .to_sql()
376 )
377 }
378
379 #[test]
380 fn to_sql_insert() {
381 assert_eq!(
382 "INSERT INTO Test (id, num, name) VALUES (1, 2, 'Hello');",
383 Statement::Insert {
384 table_name: "Test".into(),
385 columns: vec!["id".to_owned(), "num".to_owned(), "name".to_owned()],
386 source: Query {
387 body: SetExpr::Values(Values(vec![vec![
388 Expr::Literal(AstLiteral::Number(BigDecimal::from_str("1").unwrap())),
389 Expr::Literal(AstLiteral::Number(BigDecimal::from_str("2").unwrap())),
390 Expr::Literal(AstLiteral::QuotedString("Hello".to_owned()))
391 ]])),
392 order_by: vec![],
393 limit: None,
394 offset: None
395 }
396 }
397 .to_sql()
398 );
399 }
400
401 #[test]
402 fn to_sql_update() {
403 assert_eq!(
404 r#"UPDATE "Foo" SET "id" = 4, "color" = 'blue';"#,
405 Statement::Update {
406 table_name: "Foo".into(),
407 assignments: vec![
408 Assignment {
409 id: "id".to_owned(),
410 value: Expr::Literal(AstLiteral::Number(
411 BigDecimal::from_str("4").unwrap()
412 ))
413 },
414 Assignment {
415 id: "color".to_owned(),
416 value: Expr::Literal(AstLiteral::QuotedString("blue".to_owned()))
417 }
418 ],
419 selection: None
420 }
421 .to_sql()
422 );
423
424 assert_eq!(
425 r#"UPDATE "Foo" SET "name" = 'first' WHERE "a" > "b";"#,
426 Statement::Update {
427 table_name: "Foo".into(),
428 assignments: vec![Assignment {
429 id: "name".to_owned(),
430 value: Expr::Literal(AstLiteral::QuotedString("first".to_owned()))
431 }],
432 selection: Some(Expr::BinaryOp {
433 left: Box::new(Expr::Identifier("a".to_owned())),
434 op: BinaryOperator::Gt,
435 right: Box::new(Expr::Identifier("b".to_owned()))
436 })
437 }
438 .to_sql()
439 )
440 }
441
442 #[test]
443 fn to_sql_delete() {
444 assert_eq!(
445 r#"DELETE FROM "Foo";"#,
446 Statement::Delete {
447 table_name: "Foo".into(),
448 selection: None
449 }
450 .to_sql()
451 );
452
453 assert_eq!(
454 r#"DELETE FROM "Foo" WHERE "item" = 'glue';"#,
455 Statement::Delete {
456 table_name: "Foo".into(),
457 selection: Some(Expr::BinaryOp {
458 left: Box::new(Expr::Identifier("item".to_owned())),
459 op: BinaryOperator::Eq,
460 right: Box::new(Expr::Literal(AstLiteral::QuotedString("glue".to_owned())))
461 })
462 }
463 .to_sql()
464 );
465 }
466
467 #[test]
468 fn to_sql_create_table() {
469 assert_eq!(
470 r#"CREATE TABLE IF NOT EXISTS "Foo";"#,
471 Statement::CreateTable {
472 if_not_exists: true,
473 name: "Foo".into(),
474 columns: None,
475 source: None,
476 engine: None,
477 foreign_keys: Vec::new(),
478 comment: None,
479 }
480 .to_sql()
481 );
482
483 assert_eq!(
484 r#"CREATE TABLE "Foo";"#,
485 Statement::CreateTable {
486 if_not_exists: false,
487 name: "Foo".into(),
488 columns: None,
489 source: None,
490 engine: None,
491 foreign_keys: Vec::new(),
492 comment: None,
493 }
494 .to_sql()
495 );
496
497 assert_eq!(
498 r#"CREATE TABLE IF NOT EXISTS "Foo" ("id" BOOLEAN NOT NULL) COMMENT = 'this is comment';"#,
499 Statement::CreateTable {
500 if_not_exists: true,
501 name: "Foo".into(),
502 columns: Some(vec![ColumnDef {
503 name: "id".to_owned(),
504 data_type: DataType::Boolean,
505 nullable: false,
506 default: None,
507 unique: None,
508 comment: None,
509 },]),
510 source: None,
511 engine: None,
512 foreign_keys: Vec::new(),
513 comment: Some("this is comment".to_owned()),
514 }
515 .to_sql()
516 );
517
518 assert_eq!(
519 r#"CREATE TABLE "Foo" ("id" INT NOT NULL, "num" INT NULL, "name" TEXT NOT NULL);"#,
520 Statement::CreateTable {
521 if_not_exists: false,
522 name: "Foo".into(),
523 columns: Some(vec![
524 ColumnDef {
525 name: "id".to_owned(),
526 data_type: DataType::Int,
527 nullable: false,
528 default: None,
529 unique: None,
530 comment: None,
531 },
532 ColumnDef {
533 name: "num".to_owned(),
534 data_type: DataType::Int,
535 nullable: true,
536 default: None,
537 unique: None,
538 comment: None,
539 },
540 ColumnDef {
541 name: "name".to_owned(),
542 data_type: DataType::Text,
543 nullable: false,
544 default: None,
545 unique: None,
546 comment: None,
547 }
548 ]),
549 source: None,
550 engine: None,
551 foreign_keys: Vec::new(),
552 comment: None,
553 }
554 .to_sql()
555 );
556 }
557
558 #[test]
559 fn to_sql_create_table_as() {
560 assert_eq!(
561 r#"CREATE TABLE "Foo" AS SELECT "id", "count" FROM "Bar";"#,
562 Statement::CreateTable {
563 if_not_exists: false,
564 name: "Foo".into(),
565 columns: None,
566 source: Some(Box::new(Query {
567 body: SetExpr::Select(Box::new(Select {
568 projection: vec![
569 SelectItem::Expr {
570 expr: Expr::Identifier("id".to_owned()),
571 label: "".to_owned()
572 },
573 SelectItem::Expr {
574 expr: Expr::Identifier("count".to_owned()),
575 label: "".to_owned()
576 }
577 ],
578 from: TableWithJoins {
579 relation: TableFactor::Table {
580 name: "Bar".to_owned(),
581 alias: None,
582 index: None
583 },
584 joins: vec![]
585 },
586 selection: None,
587 group_by: vec![],
588 having: None
589 })),
590 order_by: vec![],
591 limit: None,
592 offset: None
593 })),
594 engine: None,
595 foreign_keys: Vec::new(),
596 comment: None,
597 }
598 .to_sql()
599 );
600
601 assert_eq!(
602 r#"CREATE TABLE IF NOT EXISTS "Foo" AS VALUES (TRUE);"#,
603 Statement::CreateTable {
604 if_not_exists: true,
605 name: "Foo".into(),
606 columns: None,
607 source: Some(Box::new(Query {
608 body: SetExpr::Values(Values(vec![vec![Expr::Literal(AstLiteral::Boolean(
609 true
610 ))]])),
611 order_by: vec![],
612 limit: None,
613 offset: None
614 })),
615 engine: None,
616 foreign_keys: Vec::new(),
617 comment: None,
618 }
619 .to_sql()
620 );
621 }
622
623 #[test]
624 fn to_sql_create_table_with_engine() {
625 assert_eq!(
626 r#"CREATE TABLE "Foo" ENGINE = MEMORY;"#,
627 Statement::CreateTable {
628 if_not_exists: false,
629 name: "Foo".into(),
630 columns: None,
631 source: None,
632 engine: Some("MEMORY".to_owned()),
633 foreign_keys: Vec::new(),
634 comment: None,
635 }
636 .to_sql()
637 );
638
639 assert_eq!(
640 r#"CREATE TABLE "Foo" ("id" BOOLEAN NOT NULL) ENGINE = SLED;"#,
641 Statement::CreateTable {
642 if_not_exists: false,
643 name: "Foo".into(),
644 columns: Some(vec![ColumnDef {
645 name: "id".to_owned(),
646 data_type: DataType::Boolean,
647 nullable: false,
648 default: None,
649 unique: None,
650 comment: None,
651 },]),
652 source: None,
653 engine: Some("SLED".to_owned()),
654 foreign_keys: Vec::new(),
655 comment: None,
656 }
657 .to_sql()
658 );
659 }
660
661 #[test]
662 fn to_sql_insert_function() {
663 assert_eq!(
664 r#"CREATE FUNCTION add("num" INT DEFAULT 0) RETURN "num";"#,
665 Statement::CreateFunction {
666 or_replace: false,
667 name: "add".into(),
668 args: vec![OperateFunctionArg {
669 name: "num".into(),
670 data_type: DataType::Int,
671 default: Some(Expr::Literal(AstLiteral::Number(
672 BigDecimal::from_str("0").unwrap()
673 ))),
674 }],
675 return_: Expr::Identifier("num".to_owned())
676 }
677 .to_sql()
678 );
679 assert_eq!(
680 "CREATE OR REPLACE FUNCTION add() RETURN 1;",
681 Statement::CreateFunction {
682 or_replace: true,
683 name: "add".into(),
684 args: vec![],
685 return_: Expr::Literal(AstLiteral::Number(BigDecimal::from_str("1").unwrap()))
686 }
687 .to_sql()
688 );
689 }
690
691 #[test]
692 fn to_sql_alter_table() {
693 assert_eq!(
694 r#"ALTER TABLE "Foo" ADD COLUMN "amount" INT NOT NULL DEFAULT 10;"#,
695 Statement::AlterTable {
696 name: "Foo".into(),
697 operation: AlterTableOperation::AddColumn {
698 column_def: ColumnDef {
699 name: "amount".to_owned(),
700 data_type: DataType::Int,
701 nullable: false,
702 default: Some(Expr::Literal(AstLiteral::Number(
703 BigDecimal::from_str("10").unwrap()
704 ))),
705 unique: None,
706 comment: None,
707 }
708 }
709 }
710 .to_sql()
711 );
712
713 assert_eq!(
714 r#"ALTER TABLE "Foo" DROP COLUMN "something";"#,
715 Statement::AlterTable {
716 name: "Foo".into(),
717 operation: AlterTableOperation::DropColumn {
718 column_name: "something".to_owned(),
719 if_exists: false
720 }
721 }
722 .to_sql()
723 );
724
725 assert_eq!(
726 r#"ALTER TABLE "Foo" DROP COLUMN IF EXISTS "something";"#,
727 Statement::AlterTable {
728 name: "Foo".into(),
729 operation: AlterTableOperation::DropColumn {
730 column_name: "something".to_owned(),
731 if_exists: true
732 }
733 }
734 .to_sql()
735 );
736
737 assert_eq!(
738 r#"ALTER TABLE "Bar" RENAME COLUMN "id" TO "new_id";"#,
739 Statement::AlterTable {
740 name: "Bar".into(),
741 operation: AlterTableOperation::RenameColumn {
742 old_column_name: "id".to_owned(),
743 new_column_name: "new_id".to_owned()
744 }
745 }
746 .to_sql()
747 );
748
749 assert_eq!(
750 r#"ALTER TABLE "Foo" RENAME TO "Bar";"#,
751 Statement::AlterTable {
752 name: "Foo".to_owned(),
753 operation: AlterTableOperation::RenameTable {
754 table_name: "Bar".to_owned(),
755 }
756 }
757 .to_sql()
758 );
759 }
760
761 #[test]
762 fn to_sql_drop_table() {
763 assert_eq!(
764 r#"DROP TABLE "Test";"#,
765 Statement::DropTable {
766 if_exists: false,
767 names: vec!["Test".into()],
768 cascade: false,
769 }
770 .to_sql()
771 );
772
773 assert_eq!(
774 r#"DROP TABLE IF EXISTS "Test";"#,
775 Statement::DropTable {
776 if_exists: true,
777 names: vec!["Test".into()],
778 cascade: false,
779 }
780 .to_sql()
781 );
782
783 assert_eq!(
784 r#"DROP TABLE "Foo", "Bar";"#,
785 Statement::DropTable {
786 if_exists: false,
787 names: vec!["Foo".into(), "Bar".into(),],
788 cascade: false,
789 }
790 .to_sql()
791 );
792 }
793
794 #[test]
795 fn to_sql_delete_function() {
796 assert_eq!(
797 "DROP FUNCTION Test;",
798 Statement::DropFunction {
799 if_exists: false,
800 names: vec!["Test".into()]
801 }
802 .to_sql()
803 );
804
805 assert_eq!(
806 "DROP FUNCTION IF EXISTS Test;",
807 Statement::DropFunction {
808 if_exists: true,
809 names: vec!["Test".into()]
810 }
811 .to_sql()
812 );
813
814 assert_eq!(
815 "DROP FUNCTION Foo, Bar;",
816 Statement::DropFunction {
817 if_exists: false,
818 names: vec!["Foo".into(), "Bar".into(),]
819 }
820 .to_sql()
821 );
822 }
823
824 #[test]
825 fn to_sql_create_index() {
826 assert_eq!(
827 r#"CREATE INDEX "idx_name" ON "Test" ("LastName");"#,
828 Statement::CreateIndex {
829 name: "idx_name".into(),
830 table_name: "Test".into(),
831 column: OrderByExpr {
832 expr: Expr::Identifier("LastName".to_owned()),
833 asc: None
834 }
835 }
836 .to_sql()
837 );
838 }
839
840 #[test]
841 fn to_sql_drop_index() {
842 assert_eq!(
843 "DROP INDEX Test.idx_id;",
844 Statement::DropIndex {
845 name: "idx_id".into(),
846 table_name: "Test".into(),
847 }
848 .to_sql()
849 )
850 }
851
852 #[test]
853 fn to_sql_transaction() {
854 assert_eq!("START TRANSACTION;", Statement::StartTransaction.to_sql());
855 assert_eq!("COMMIT;", Statement::Commit.to_sql());
856 assert_eq!("ROLLBACK;", Statement::Rollback.to_sql());
857 }
858
859 #[test]
860 fn to_sql_show_variable() {
861 assert_eq!(
862 "SHOW TABLES;",
863 Statement::ShowVariable(Variable::Tables).to_sql()
864 );
865 assert_eq!(
866 "SHOW FUNCTIONS;",
867 Statement::ShowVariable(Variable::Functions).to_sql()
868 );
869 assert_eq!(
870 "SHOW VERSIONS;",
871 Statement::ShowVariable(Variable::Version).to_sql()
872 );
873 }
874
875 #[test]
876 fn to_sql_show_indexes() {
877 assert_eq!(
878 r#"SHOW INDEXES FROM "Test";"#,
879 Statement::ShowIndexes("Test".into()).to_sql()
880 );
881 }
882
883 #[test]
884 fn to_sql_assignment() {
885 assert_eq!(
886 r#""count" = 5"#,
887 Assignment {
888 id: "count".to_owned(),
889 value: Expr::Literal(AstLiteral::Number(BigDecimal::from_str("5").unwrap()))
890 }
891 .to_sql()
892 )
893 }
894
895 #[test]
896 fn to_sql_foreign_key() {
897 assert_eq!(
898 r#"CONSTRAINT "fk_id" FOREIGN KEY ("id") REFERENCES "Test" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION"#,
899 ForeignKey {
900 name: "fk_id".into(),
901 referencing_column_name: "id".into(),
902 referenced_table_name: "Test".into(),
903 referenced_column_name: "id".into(),
904 on_delete: ReferentialAction::NoAction,
905 on_update: ReferentialAction::NoAction,
906 }
907 .to_sql()
908 )
909 }
910}