1mod column;
2mod data_type;
3mod definition;
4mod foreign_key;
5mod index;
6mod sequence;
7mod table;
8mod table_definition;
9mod table_name;
10
11pub use column::{Column, ColumnOptions};
12pub use data_type::DataType;
13pub use definition::Definition;
14pub use foreign_key::ForeignKey;
15pub use index::{Index, IndexOptions};
16pub use table::{PrimaryKey, Table, TableOptions, Timestamps};
17pub use table_definition::TableDefinition;
18pub use table_name::TableName;
19
20#[derive(Copy, Clone, Debug)]
21pub enum Dialect {
22 Sqlite,
23}
24
25pub trait SqlUp {
26 fn sqlite_up(&self) -> Option<String> {
27 None
28 }
29
30 fn sqlite(&self) -> Option<String> {
31 self.sql_up(Dialect::Sqlite)
32 }
33
34 fn sql_up(&self, dialect: Dialect) -> Option<String> {
35 match dialect {
36 Dialect::Sqlite => self.sqlite_up(),
37 }
38 }
39}
40
41pub trait SqlDown {
42 fn sqlite_down(&self) -> Option<String> {
43 None
44 }
45
46 fn sql_down(&self, dialect: Dialect) -> Option<String> {
47 match dialect {
48 Dialect::Sqlite => self.sqlite_down(),
49 }
50 }
51}
52
53#[cfg(test)]
54macro_rules! create_tests {
55 ($($name:ident: $value:expr,)*) => {
56 $(
57 #[test]
58 fn $name() {
59 let (input, expected) = $value;
60 assert_eq!(input.sql_up(Dialect::Sqlite).unwrap().as_str(), expected.trim());
61 }
62 )*
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 create_tests!(
71 test_table: (
72 Definition::table("users", vec![
73 TableDefinition::Column(Column::new("name")),
74 ]), r#"
75 CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP)
76 "#),
77 test_default_values_for_column: (Definition::column("users", DataType::String.column("name")), r#"
78 ALTER TABLE users ADD COLUMN name STRING
79 "#),
80 test_default_not_null_column: (Definition::column("users", Column::new("name").not_null().to_owned()), r#"
81 ALTER TABLE users ADD COLUMN name STRING NOT NULL
82 "#),
83 test_index: (Definition::index("users", Index::new(vec!["name".to_string()])), r#"
84 CREATE INDEX name ON users (name)
85 "#),
86 test_foreign_key: (Definition::foreign_key("users", ForeignKey::new("other")), r#"
87 ALTER TABLE users ADD FOREIGN KEY (other_id) REFERENCES other (id)
88 "#),
89 );
90
91 #[test]
92 fn integration_test() {
93 let definition = vec![
94 Definition::table_no_timestamps(
95 "changeloglock",
96 vec![
97 TableDefinition::column(
98 DataType::Bool
99 .column("locked")
100 .not_null()
101 .default(serde_value::Value::Bool(false))
102 .to_owned(),
103 ),
104 TableDefinition::column(
105 DataType::Text.column("locked_by").not_null().to_owned(),
106 ),
107 TableDefinition::column(
108 DataType::Datetime
109 .column("locked_at")
110 .not_null()
111 .default_raw("CURRENT_TIMESTAMP".to_string())
112 .to_owned(),
113 ),
114 ],
115 ),
116 Definition::statement(
117 "INSERT INTO changeloglock (id, locked, locked_by) VALUES (1, false, '')",
118 ),
119 Definition::table_no_timestamps(
120 "migrations",
121 vec![
122 TableDefinition::column(
123 DataType::Text.column("identifier").not_null().to_owned(),
124 ),
125 TableDefinition::column(
126 DataType::Datetime
127 .column("applied_at")
128 .not_null()
129 .default_raw("CURRENT_TIMESTAMP".to_string())
130 .to_owned(),
131 ),
132 ],
133 ),
134 ];
135 assert_eq!(definition.iter().sql_up(Dialect::Sqlite).unwrap().as_str(), vec![
136 "CREATE TABLE IF NOT EXISTS changeloglock (id INTEGER PRIMARY KEY AUTOINCREMENT, locked BOOLEAN NOT NULL DEFAULT FALSE, locked_by TEXT NOT NULL, locked_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP);",
137 "INSERT INTO changeloglock (id, locked, locked_by) VALUES (1, false, '');",
138 "CREATE TABLE IF NOT EXISTS migrations (id INTEGER PRIMARY KEY AUTOINCREMENT, identifier TEXT NOT NULL, applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP)"
139 ].join(" ").trim());
140
141 let connection = rusqlite::Connection::open_in_memory().unwrap();
142 connection.execute_batch(definition.iter().sqlite().unwrap().as_str()).unwrap();
143 let mut stmt = connection.prepare("SELECT name FROM sqlite_master WHERE type='table'").unwrap();
144 let tables = stmt.query_map([], |row| row.get(0)).unwrap().collect::<Result<Vec<String>, _>>().unwrap();
145 assert_eq!(tables, vec!["changeloglock", "sqlite_sequence", "migrations"]);
146
147 let mut stmt = connection.prepare("SELECT * FROM changeloglock").unwrap();
148 let rows = stmt.query_map([], |row| {
149 Ok((row.get(0)?, row.get(1)?, row.get(2)?))
150 }).unwrap().collect::<Result<Vec<(i64, bool, String)>, _>>().unwrap();
151 assert_eq!(rows, vec![(1, false, "".to_string())]);
152 }
153}