From fda6dc8f4033cf502a9082a319580c634bcdf5ec Mon Sep 17 00:00:00 2001 From: fabianlindfors Date: Tue, 28 Dec 2021 16:01:11 +0100 Subject: [PATCH] Add support for generated constraints on columns --- README.md | 22 ++++++++++++++-------- src/migrations/add_column.rs | 6 ++++++ src/migrations/common.rs | 2 ++ src/migrations/create_table.rs | 5 +++++ tests/add_column.rs | 12 ++++++++++-- tests/add_index.rs | 6 ++++-- tests/alter_column.rs | 16 ++++++++++++---- tests/create_table.rs | 12 +++++++++--- tests/remove_column.rs | 10 ++++++---- tests/remove_table.rs | 1 + tests/rename_table.rs | 1 + 11 files changed, 70 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index f6afc25..a78b02a 100644 --- a/README.md +++ b/README.md @@ -63,14 +63,15 @@ table = "users" [[actions.columns]] name = "id" - type = "SERIAL" + type = "INTEGER" + generated = "ALWAYS AS IDENTITY" [[actions.columns]] name = "name" 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 @@ -109,7 +110,8 @@ table = "customers" [[actions.columns]] name = "id" - type = "SERIAL" + type = "INTEGER" + generated = "ALWAYS AS IDENTITY" [[actions]] type = "create_table" @@ -138,11 +140,12 @@ primary_key = "id" [[actions.columns]] name = "id" - type = "SERIAL" + type = "INTEGER" + generated = "ALWAYS AS IDENTITY" [[actions.columns]] name = "name" - type = "SERIAL" + type = "TEXT" # Columns default to nullable nullable = false @@ -161,7 +164,8 @@ primary_key = "id" [[actions.columns]] name = "id" - type = "SERIAL" + type = "INTEGER" + generated = "ALWAYS AS IDENTITY" [[actions]] type = "create_table" @@ -170,7 +174,8 @@ primary_key = "id" [[actions.columns]] name = "id" - type = "SERIAL" + type = "INTEGER" + generated = "ALWAYS AS IDENTITY" [[actions.columns]] name = "user_id" @@ -343,7 +348,8 @@ primary_key = "id" [[actions.columns]] name = "id" - type = "SERIAL" + type = "INTEGER" + generated = "ALWAYS AS IDENTITY" [[actions.columns]] name = "name" diff --git a/src/migrations/add_column.rs b/src/migrations/add_column.rs index aee1870..12f1c8d 100644 --- a/src/migrations/add_column.rs +++ b/src/migrations/add_column.rs @@ -45,11 +45,17 @@ impl Action for AddColumn { self.column.name.to_string(), self.column.data_type.to_string(), ]; + if let Some(default) = &self.column.default { 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 let query = format!( " diff --git a/src/migrations/common.rs b/src/migrations/common.rs index 7c91023..7ac4114 100644 --- a/src/migrations/common.rs +++ b/src/migrations/common.rs @@ -14,6 +14,8 @@ pub struct Column { pub nullable: bool, pub default: Option, + + pub generated: Option, } fn nullable_default() -> bool { diff --git a/src/migrations/create_table.rs b/src/migrations/create_table.rs index ed965e9..49bf036 100644 --- a/src/migrations/create_table.rs +++ b/src/migrations/create_table.rs @@ -39,6 +39,11 @@ impl Action for CreateTable { parts.push("NOT NULL".to_string()); } + if let Some(generated) = &column.generated { + parts.push("GENERATED".to_string()); + parts.push(generated.to_string()); + } + parts.join(" ") }) .collect(); diff --git a/tests/add_column.rs b/tests/add_column.rs index 857adf3..4d32bce 100644 --- a/tests/add_column.rs +++ b/tests/add_column.rs @@ -17,12 +17,14 @@ fn add_column() { data_type: "SERIAL".to_string(), nullable: true, default: None, + generated: None, }, Column { name: "name".to_string(), data_type: "TEXT".to_string(), nullable: false, default: None, + generated: None, }, ], }); @@ -34,6 +36,7 @@ fn add_column() { data_type: "TEXT".to_string(), nullable: false, default: None, + generated: None, }, up: Some("(STRING_TO_ARRAY(name, ' '))[1]".to_string()), }) @@ -44,6 +47,7 @@ fn add_column() { data_type: "TEXT".to_string(), nullable: false, default: None, + generated: None, }, up: Some("(STRING_TO_ARRAY(name, ' '))[2]".to_string()), }); @@ -128,9 +132,10 @@ fn add_column_nullable() { foreign_keys: vec![], columns: vec![Column { name: "id".to_string(), - data_type: "SERIAL".to_string(), + data_type: "INTEGER".to_string(), nullable: true, default: None, + generated: None, }], }); 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(), nullable: true, default: None, + generated: None, }, up: None, }); @@ -223,9 +229,10 @@ fn add_column_with_default() { foreign_keys: vec![], columns: vec![Column { name: "id".to_string(), - data_type: "SERIAL".to_string(), + data_type: "INTEGER".to_string(), nullable: true, default: None, + generated: None, }], }); let add_name_column = @@ -236,6 +243,7 @@ fn add_column_with_default() { data_type: "TEXT".to_string(), nullable: false, default: Some("'DEFAULT'".to_string()), + generated: None, }, up: None, }); diff --git a/tests/add_index.rs b/tests/add_index.rs index 4268a76..b4b9144 100644 --- a/tests/add_index.rs +++ b/tests/add_index.rs @@ -14,15 +14,17 @@ fn add_index() { columns: vec![ Column { name: "id".to_string(), - data_type: "SERIAL".to_string(), - nullable: true, // Will be ignored by Postgres as the column is a SERIAL + data_type: "INTEGER".to_string(), + nullable: true, default: None, + generated: None, }, Column { name: "name".to_string(), data_type: "TEXT".to_string(), nullable: false, default: None, + generated: None, }, ], }); diff --git a/tests/alter_column.rs b/tests/alter_column.rs index 6e8a78d..dcf3b67 100644 --- a/tests/alter_column.rs +++ b/tests/alter_column.rs @@ -14,15 +14,17 @@ fn alter_column_data() { columns: vec![ Column { name: "id".to_string(), - data_type: "SERIAL".to_string(), + data_type: "INTEGER".to_string(), nullable: true, default: None, + generated: None, }, Column { name: "name".to_string(), data_type: "TEXT".to_string(), nullable: false, default: None, + generated: None, }, ], }); @@ -116,15 +118,17 @@ fn alter_column_set_not_null() { columns: vec![ Column { name: "id".to_string(), - data_type: "SERIAL".to_string(), + data_type: "INTEGER".to_string(), nullable: true, default: None, + generated: None, }, Column { name: "name".to_string(), data_type: "TEXT".to_string(), nullable: true, default: None, + generated: None, }, ], }); @@ -218,15 +222,17 @@ fn alter_column_rename() { columns: vec![ Column { name: "id".to_string(), - data_type: "SERIAL".to_string(), + data_type: "INTEGER".to_string(), nullable: true, default: None, + generated: None, }, Column { name: "name".to_string(), data_type: "TEXT".to_string(), nullable: true, default: None, + generated: None, }, ], }); @@ -303,15 +309,17 @@ fn alter_column_multiple() { columns: vec![ Column { name: "id".to_string(), - data_type: "SERIAL".to_string(), + data_type: "INTEGER".to_string(), nullable: true, default: None, + generated: None, }, Column { name: "counter".to_string(), data_type: "INTEGER".to_string(), nullable: false, default: None, + generated: None, }, ], }); diff --git a/tests/create_table.rs b/tests/create_table.rs index 27edb9c..aec7970 100644 --- a/tests/create_table.rs +++ b/tests/create_table.rs @@ -17,21 +17,24 @@ fn create_table() { columns: vec![ Column { name: "id".to_string(), - data_type: "SERIAL".to_string(), - nullable: true, // Will be ignored by Postgres as the column is a SERIAL + data_type: "INTEGER".to_string(), + nullable: true, default: None, + generated: Some("ALWAYS AS IDENTITY".to_string()), }, Column { name: "name".to_string(), data_type: "TEXT".to_string(), nullable: true, default: None, + generated: None, }, Column { name: "created_at".to_string(), data_type: "TIMESTAMP".to_string(), nullable: false, default: Some("NOW()".to_string()), + generated: None, }, ], }); @@ -72,7 +75,7 @@ fn create_table() { // id column let id_row = &result[0]; assert_eq!("id", id_row.get::<_, String>("column_name")); - assert!(id_row.get::<_, Option>("column_default").is_some()); + assert!(id_row.get::<_, Option>("column_default").is_none()); assert_eq!("NO", id_row.get::<_, String>("is_nullable")); assert_eq!("integer", id_row.get::<_, String>("data_type")); @@ -133,6 +136,7 @@ fn create_table_with_foreign_keys() { data_type: "SERIAL".to_string(), nullable: true, // Will be ignored by Postgres as the column is a SERIAL default: None, + generated: None, }], }); @@ -151,12 +155,14 @@ fn create_table_with_foreign_keys() { data_type: "SERIAL".to_string(), nullable: true, default: None, + generated: None, }, Column { name: "user_id".to_string(), data_type: "INTEGER".to_string(), nullable: false, default: None, + generated: None, }, ], }); diff --git a/tests/remove_column.rs b/tests/remove_column.rs index 15e049c..6c8e645 100644 --- a/tests/remove_column.rs +++ b/tests/remove_column.rs @@ -14,15 +14,17 @@ fn remove_column() { columns: vec![ Column { name: "id".to_string(), - data_type: "SERIAL".to_string(), - nullable: true, // Will be ignored by Postgres as the column is a SERIAL + data_type: "INTEGER".to_string(), + nullable: true, default: None, + generated: None, }, Column { name: "name".to_string(), data_type: "TEXT".to_string(), nullable: false, default: None, + generated: None, }, ], }); @@ -53,7 +55,7 @@ fn remove_column() { // Insert using old schema and ensure it can be retrieved through new schema old_db - .simple_query("INSERT INTO users(name) VALUES ('John Doe')") + .simple_query("INSERT INTO users(id, name) VALUES (1, 'John Doe')") .unwrap(); let results = new_db .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 new_db - .simple_query("INSERT INTO users DEFAULT VALUES") + .simple_query("INSERT INTO users(id) VALUES (2)") .unwrap(); let result = old_db .query_opt("SELECT name FROM users WHERE id = 2", &[]) diff --git a/tests/remove_table.rs b/tests/remove_table.rs index f2fd44a..f6f530f 100644 --- a/tests/remove_table.rs +++ b/tests/remove_table.rs @@ -16,6 +16,7 @@ fn remove_table() { data_type: "INTEGER".to_string(), nullable: false, default: None, + generated: None, }], }); let remove_table_migration = diff --git a/tests/rename_table.rs b/tests/rename_table.rs index 066169c..670e715 100644 --- a/tests/rename_table.rs +++ b/tests/rename_table.rs @@ -16,6 +16,7 @@ fn rename_table() { data_type: "INTEGER".to_string(), nullable: false, default: None, + generated: None, }], }); let rename_table_migration = Migration::new("rename_users_table_to_customers", None)