Add support for generated constraints on columns

This commit is contained in:
fabianlindfors 2021-12-28 16:01:11 +01:00
parent 101a414e00
commit fda6dc8f40
11 changed files with 70 additions and 23 deletions

View File

@ -63,14 +63,15 @@ table = "users"
[[actions.columns]] [[actions.columns]]
name = "id" name = "id"
type = "SERIAL" type = "INTEGER"
generated = "ALWAYS AS IDENTITY"
[[actions.columns]] [[actions.columns]]
name = "name" name = "name"
type = "TEXT" type = "TEXT"
``` ```
This is the equivalent of running `CREATE TABLE users (id SERIAL, name TEXT)`. This is the equivalent of running `CREATE TABLE users (id INTEGER GENERATED ALWAYS AS IDENTITY, name TEXT)`.
### Preparing your application ### Preparing your application
@ -109,7 +110,8 @@ table = "customers"
[[actions.columns]] [[actions.columns]]
name = "id" name = "id"
type = "SERIAL" type = "INTEGER"
generated = "ALWAYS AS IDENTITY"
[[actions]] [[actions]]
type = "create_table" type = "create_table"
@ -138,11 +140,12 @@ primary_key = "id"
[[actions.columns]] [[actions.columns]]
name = "id" name = "id"
type = "SERIAL" type = "INTEGER"
generated = "ALWAYS AS IDENTITY"
[[actions.columns]] [[actions.columns]]
name = "name" name = "name"
type = "SERIAL" type = "TEXT"
# Columns default to nullable # Columns default to nullable
nullable = false nullable = false
@ -161,7 +164,8 @@ primary_key = "id"
[[actions.columns]] [[actions.columns]]
name = "id" name = "id"
type = "SERIAL" type = "INTEGER"
generated = "ALWAYS AS IDENTITY"
[[actions]] [[actions]]
type = "create_table" type = "create_table"
@ -170,7 +174,8 @@ primary_key = "id"
[[actions.columns]] [[actions.columns]]
name = "id" name = "id"
type = "SERIAL" type = "INTEGER"
generated = "ALWAYS AS IDENTITY"
[[actions.columns]] [[actions.columns]]
name = "user_id" name = "user_id"
@ -343,7 +348,8 @@ primary_key = "id"
[[actions.columns]] [[actions.columns]]
name = "id" name = "id"
type = "SERIAL" type = "INTEGER"
generated = "ALWAYS AS IDENTITY"
[[actions.columns]] [[actions.columns]]
name = "name" name = "name"

View File

@ -45,11 +45,17 @@ impl Action for AddColumn {
self.column.name.to_string(), self.column.name.to_string(),
self.column.data_type.to_string(), self.column.data_type.to_string(),
]; ];
if let Some(default) = &self.column.default { if let Some(default) = &self.column.default {
definition_parts.push("DEFAULT".to_string()); definition_parts.push("DEFAULT".to_string());
definition_parts.push(default.to_string()); definition_parts.push(default.to_string());
} }
if let Some(generated) = &self.column.generated {
definition_parts.push("GENERATED".to_string());
definition_parts.push(generated.to_string());
}
// Add column as NOT NULL // Add column as NOT NULL
let query = format!( let query = format!(
" "

View File

@ -14,6 +14,8 @@ pub struct Column {
pub nullable: bool, pub nullable: bool,
pub default: Option<String>, pub default: Option<String>,
pub generated: Option<String>,
} }
fn nullable_default() -> bool { fn nullable_default() -> bool {

View File

@ -39,6 +39,11 @@ impl Action for CreateTable {
parts.push("NOT NULL".to_string()); parts.push("NOT NULL".to_string());
} }
if let Some(generated) = &column.generated {
parts.push("GENERATED".to_string());
parts.push(generated.to_string());
}
parts.join(" ") parts.join(" ")
}) })
.collect(); .collect();

View File

@ -17,12 +17,14 @@ fn add_column() {
data_type: "SERIAL".to_string(), data_type: "SERIAL".to_string(),
nullable: true, nullable: true,
default: None, default: None,
generated: None,
}, },
Column { Column {
name: "name".to_string(), name: "name".to_string(),
data_type: "TEXT".to_string(), data_type: "TEXT".to_string(),
nullable: false, nullable: false,
default: None, default: None,
generated: None,
}, },
], ],
}); });
@ -34,6 +36,7 @@ fn add_column() {
data_type: "TEXT".to_string(), data_type: "TEXT".to_string(),
nullable: false, nullable: false,
default: None, default: None,
generated: None,
}, },
up: Some("(STRING_TO_ARRAY(name, ' '))[1]".to_string()), up: Some("(STRING_TO_ARRAY(name, ' '))[1]".to_string()),
}) })
@ -44,6 +47,7 @@ fn add_column() {
data_type: "TEXT".to_string(), data_type: "TEXT".to_string(),
nullable: false, nullable: false,
default: None, default: None,
generated: None,
}, },
up: Some("(STRING_TO_ARRAY(name, ' '))[2]".to_string()), up: Some("(STRING_TO_ARRAY(name, ' '))[2]".to_string()),
}); });
@ -128,9 +132,10 @@ fn add_column_nullable() {
foreign_keys: vec![], foreign_keys: vec![],
columns: vec![Column { columns: vec![Column {
name: "id".to_string(), name: "id".to_string(),
data_type: "SERIAL".to_string(), data_type: "INTEGER".to_string(),
nullable: true, nullable: true,
default: None, default: None,
generated: None,
}], }],
}); });
let add_name_column = Migration::new("add_nullable_name_column", None).with_action(AddColumn { let add_name_column = Migration::new("add_nullable_name_column", None).with_action(AddColumn {
@ -140,6 +145,7 @@ fn add_column_nullable() {
data_type: "TEXT".to_string(), data_type: "TEXT".to_string(),
nullable: true, nullable: true,
default: None, default: None,
generated: None,
}, },
up: None, up: None,
}); });
@ -223,9 +229,10 @@ fn add_column_with_default() {
foreign_keys: vec![], foreign_keys: vec![],
columns: vec![Column { columns: vec![Column {
name: "id".to_string(), name: "id".to_string(),
data_type: "SERIAL".to_string(), data_type: "INTEGER".to_string(),
nullable: true, nullable: true,
default: None, default: None,
generated: None,
}], }],
}); });
let add_name_column = let add_name_column =
@ -236,6 +243,7 @@ fn add_column_with_default() {
data_type: "TEXT".to_string(), data_type: "TEXT".to_string(),
nullable: false, nullable: false,
default: Some("'DEFAULT'".to_string()), default: Some("'DEFAULT'".to_string()),
generated: None,
}, },
up: None, up: None,
}); });

View File

@ -14,15 +14,17 @@ fn add_index() {
columns: vec![ columns: vec![
Column { Column {
name: "id".to_string(), name: "id".to_string(),
data_type: "SERIAL".to_string(), data_type: "INTEGER".to_string(),
nullable: true, // Will be ignored by Postgres as the column is a SERIAL nullable: true,
default: None, default: None,
generated: None,
}, },
Column { Column {
name: "name".to_string(), name: "name".to_string(),
data_type: "TEXT".to_string(), data_type: "TEXT".to_string(),
nullable: false, nullable: false,
default: None, default: None,
generated: None,
}, },
], ],
}); });

View File

@ -14,15 +14,17 @@ fn alter_column_data() {
columns: vec![ columns: vec![
Column { Column {
name: "id".to_string(), name: "id".to_string(),
data_type: "SERIAL".to_string(), data_type: "INTEGER".to_string(),
nullable: true, nullable: true,
default: None, default: None,
generated: None,
}, },
Column { Column {
name: "name".to_string(), name: "name".to_string(),
data_type: "TEXT".to_string(), data_type: "TEXT".to_string(),
nullable: false, nullable: false,
default: None, default: None,
generated: None,
}, },
], ],
}); });
@ -116,15 +118,17 @@ fn alter_column_set_not_null() {
columns: vec![ columns: vec![
Column { Column {
name: "id".to_string(), name: "id".to_string(),
data_type: "SERIAL".to_string(), data_type: "INTEGER".to_string(),
nullable: true, nullable: true,
default: None, default: None,
generated: None,
}, },
Column { Column {
name: "name".to_string(), name: "name".to_string(),
data_type: "TEXT".to_string(), data_type: "TEXT".to_string(),
nullable: true, nullable: true,
default: None, default: None,
generated: None,
}, },
], ],
}); });
@ -218,15 +222,17 @@ fn alter_column_rename() {
columns: vec![ columns: vec![
Column { Column {
name: "id".to_string(), name: "id".to_string(),
data_type: "SERIAL".to_string(), data_type: "INTEGER".to_string(),
nullable: true, nullable: true,
default: None, default: None,
generated: None,
}, },
Column { Column {
name: "name".to_string(), name: "name".to_string(),
data_type: "TEXT".to_string(), data_type: "TEXT".to_string(),
nullable: true, nullable: true,
default: None, default: None,
generated: None,
}, },
], ],
}); });
@ -303,15 +309,17 @@ fn alter_column_multiple() {
columns: vec![ columns: vec![
Column { Column {
name: "id".to_string(), name: "id".to_string(),
data_type: "SERIAL".to_string(), data_type: "INTEGER".to_string(),
nullable: true, nullable: true,
default: None, default: None,
generated: None,
}, },
Column { Column {
name: "counter".to_string(), name: "counter".to_string(),
data_type: "INTEGER".to_string(), data_type: "INTEGER".to_string(),
nullable: false, nullable: false,
default: None, default: None,
generated: None,
}, },
], ],
}); });

View File

@ -17,21 +17,24 @@ fn create_table() {
columns: vec![ columns: vec![
Column { Column {
name: "id".to_string(), name: "id".to_string(),
data_type: "SERIAL".to_string(), data_type: "INTEGER".to_string(),
nullable: true, // Will be ignored by Postgres as the column is a SERIAL nullable: true,
default: None, default: None,
generated: Some("ALWAYS AS IDENTITY".to_string()),
}, },
Column { Column {
name: "name".to_string(), name: "name".to_string(),
data_type: "TEXT".to_string(), data_type: "TEXT".to_string(),
nullable: true, nullable: true,
default: None, default: None,
generated: None,
}, },
Column { Column {
name: "created_at".to_string(), name: "created_at".to_string(),
data_type: "TIMESTAMP".to_string(), data_type: "TIMESTAMP".to_string(),
nullable: false, nullable: false,
default: Some("NOW()".to_string()), default: Some("NOW()".to_string()),
generated: None,
}, },
], ],
}); });
@ -72,7 +75,7 @@ fn create_table() {
// id column // id column
let id_row = &result[0]; let id_row = &result[0];
assert_eq!("id", id_row.get::<_, String>("column_name")); assert_eq!("id", id_row.get::<_, String>("column_name"));
assert!(id_row.get::<_, Option<String>>("column_default").is_some()); assert!(id_row.get::<_, Option<String>>("column_default").is_none());
assert_eq!("NO", id_row.get::<_, String>("is_nullable")); assert_eq!("NO", id_row.get::<_, String>("is_nullable"));
assert_eq!("integer", id_row.get::<_, String>("data_type")); assert_eq!("integer", id_row.get::<_, String>("data_type"));
@ -133,6 +136,7 @@ fn create_table_with_foreign_keys() {
data_type: "SERIAL".to_string(), data_type: "SERIAL".to_string(),
nullable: true, // Will be ignored by Postgres as the column is a SERIAL nullable: true, // Will be ignored by Postgres as the column is a SERIAL
default: None, default: None,
generated: None,
}], }],
}); });
@ -151,12 +155,14 @@ fn create_table_with_foreign_keys() {
data_type: "SERIAL".to_string(), data_type: "SERIAL".to_string(),
nullable: true, nullable: true,
default: None, default: None,
generated: None,
}, },
Column { Column {
name: "user_id".to_string(), name: "user_id".to_string(),
data_type: "INTEGER".to_string(), data_type: "INTEGER".to_string(),
nullable: false, nullable: false,
default: None, default: None,
generated: None,
}, },
], ],
}); });

View File

@ -14,15 +14,17 @@ fn remove_column() {
columns: vec![ columns: vec![
Column { Column {
name: "id".to_string(), name: "id".to_string(),
data_type: "SERIAL".to_string(), data_type: "INTEGER".to_string(),
nullable: true, // Will be ignored by Postgres as the column is a SERIAL nullable: true,
default: None, default: None,
generated: None,
}, },
Column { Column {
name: "name".to_string(), name: "name".to_string(),
data_type: "TEXT".to_string(), data_type: "TEXT".to_string(),
nullable: false, nullable: false,
default: None, default: None,
generated: None,
}, },
], ],
}); });
@ -53,7 +55,7 @@ fn remove_column() {
// Insert using old schema and ensure it can be retrieved through new schema // Insert using old schema and ensure it can be retrieved through new schema
old_db old_db
.simple_query("INSERT INTO users(name) VALUES ('John Doe')") .simple_query("INSERT INTO users(id, name) VALUES (1, 'John Doe')")
.unwrap(); .unwrap();
let results = new_db let results = new_db
.query("SELECT id FROM users WHERE id = 1", &[]) .query("SELECT id FROM users WHERE id = 1", &[])
@ -66,7 +68,7 @@ fn remove_column() {
// Insert using new schema and ensure the down function is correctly applied // Insert using new schema and ensure the down function is correctly applied
new_db new_db
.simple_query("INSERT INTO users DEFAULT VALUES") .simple_query("INSERT INTO users(id) VALUES (2)")
.unwrap(); .unwrap();
let result = old_db let result = old_db
.query_opt("SELECT name FROM users WHERE id = 2", &[]) .query_opt("SELECT name FROM users WHERE id = 2", &[])

View File

@ -16,6 +16,7 @@ fn remove_table() {
data_type: "INTEGER".to_string(), data_type: "INTEGER".to_string(),
nullable: false, nullable: false,
default: None, default: None,
generated: None,
}], }],
}); });
let remove_table_migration = let remove_table_migration =

View File

@ -16,6 +16,7 @@ fn rename_table() {
data_type: "INTEGER".to_string(), data_type: "INTEGER".to_string(),
nullable: false, nullable: false,
default: None, default: None,
generated: None,
}], }],
}); });
let rename_table_migration = Migration::new("rename_users_table_to_customers", None) let rename_table_migration = Migration::new("rename_users_table_to_customers", None)