Skip to content

Commit

Permalink
Allow passing the if_exists or if_not_exists modifiers for creating a…
Browse files Browse the repository at this point in the history
…nd altering tables in migrations. Fixes #988 (#993)
  • Loading branch information
jwoertink authored Dec 29, 2023
1 parent 9ad64b9 commit 82eef3e
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 14 deletions.
20 changes: 20 additions & 0 deletions spec/avram/migrator/alter_table_statement_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -186,4 +186,24 @@ describe Avram::Migrator::AlterTableStatement do
end
end
end

context "IF EXISTS" do
it "adds the option to the table" do
built = Avram::Migrator::AlterTableStatement.new(:users, if_exists: true).build do
add name : String?
rename :old_name, :new_name
rename_belongs_to :owner, :boss
end

built.statements.size.should eq 3

built.statements[0].should eq "ALTER TABLE IF EXISTS users RENAME COLUMN old_name TO new_name;"
built.statements[1].should eq "ALTER TABLE IF EXISTS users RENAME COLUMN owner_id TO boss_id;"

built.statements[2].should eq <<-SQL
ALTER TABLE IF EXISTS users
ADD name text;
SQL
end
end
end
12 changes: 12 additions & 0 deletions spec/avram/migrator/create_table_statement_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -236,4 +236,16 @@ describe Avram::Migrator::CreateTableStatement do
end
end
end

context "IF NOT EXISTS" do
it "adds the option to the table" do
built = Avram::Migrator::CreateTableStatement.new(:users, if_not_exists: true).build do
end

built.statements.size.should eq 1
built.statements.first.should eq <<-SQL
CREATE TABLE IF NOT EXISTS users (\n);
SQL
end
end
end
27 changes: 27 additions & 0 deletions spec/avram/migrator/migration_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,20 @@ class MigrationWithFunctionAndTrigger::V996 < Avram::Migrator::Migration::V1
end
end

class MigrationCreateTableIfNotExists::V995 < Avram::Migrator::Migration::V1
def migrate
create :fake_things, if_not_exists: true do
add foo : String
end
end

def rollback
alter :fake_things, if_exists: true do
add foo : String?
end
end
end

describe Avram::Migrator::Migration::V1 do
it "executes statements in a transaction" do
expect_raises Avram::FailedMigration do
Expand Down Expand Up @@ -113,6 +127,19 @@ describe Avram::Migrator::Migration::V1 do
sql.should contain "CREATE TRIGGER trigger_touch_updated_at"
end
end

describe "if _not_ exists" do
it "passes the option through the macro" do
migration = MigrationCreateTableIfNotExists::V995.new
migration.migrate
sql = migration.prepared_statements.join("\n")
sql.should contain "CREATE TABLE IF NOT EXISTS fake_things"

migration.rollback
sql = migration.prepared_statements.join("\n")
sql.should contain "ALTER TABLE IF EXISTS fake_things"
end
end
end

private def get_column_names(table_name)
Expand Down
15 changes: 11 additions & 4 deletions src/avram/migrator/alter_table_statement.cr
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ class Avram::Migrator::AlterTableStatement
getter fill_existing_with_statements = [] of String
getter change_type_statements = [] of String
getter change_default_statements = [] of String
private getter? if_exists : Bool = false

def initialize(@table_name : TableName)
def initialize(@table_name : TableName, *, @if_exists : Bool = false)
end

# Change the column's type from whatever it is currently to
Expand Down Expand Up @@ -116,13 +117,19 @@ class Avram::Migrator::AlterTableStatement
alter_statements + change_default_statements + change_type_statements + index_statements + fill_existing_with_statements
end

def if_exists_statement
if if_exists?
"IF EXISTS "
end
end

def alter_statements : Array(String)
alterations = renamed_rows.map do |statement|
"ALTER TABLE #{@table_name} #{statement};"
"ALTER TABLE #{if_exists_statement}#{@table_name} #{statement};"
end
unless (rows + dropped_rows).empty?
alterations << String.build do |statement|
statement << "ALTER TABLE #{@table_name}"
statement << "ALTER TABLE #{if_exists_statement}#{@table_name}"
statement << "\n"
statement << (rows + dropped_rows).join(",\n")
statement << ';'
Expand Down Expand Up @@ -249,7 +256,7 @@ class Avram::Migrator::AlterTableStatement

def add_fill_existing_with_statements(column : Symbol | String, type, value, nilable)
@fill_existing_with_statements << "UPDATE #{@table_name} SET #{column} = #{value};"
@fill_existing_with_statements << "ALTER TABLE #{@table_name} ALTER COLUMN #{column} SET NOT NULL;" unless nilable
@fill_existing_with_statements << "ALTER TABLE #{if_exists_statement}#{@table_name} ALTER COLUMN #{column} SET NOT NULL;" unless nilable
end

macro symbol_expected_error(action, name)
Expand Down
14 changes: 8 additions & 6 deletions src/avram/migrator/create_table_statement.cr
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ class Avram::Migrator::CreateTableStatement

private getter rows = [] of String
private getter constraints = [] of String
private getter? if_not_exists : Bool = false

def initialize(@table_name : TableName)
def initialize(@table_name : TableName, *, @if_not_exists : Bool = false)
end

# Accepts a block to build a table and indices using `add` and `add_index` methods.
Expand Down Expand Up @@ -54,18 +55,19 @@ class Avram::Migrator::CreateTableStatement

private def table_statement
String.build do |statement|
statement << initial_table_statement
initial_table_statement(statement)
statement << rows.join(",\n")
statement << ",\n" if !constraints.empty?
statement << constraints.join(", \n")
statement << ");"
end
end

private def initial_table_statement
<<-SQL
CREATE TABLE #{@table_name} (\n
SQL
private def initial_table_statement(io)
io << "CREATE TABLE "
io << "IF NOT EXISTS " if if_not_exists?
io << @table_name
io << " (\n"
end

macro primary_key(type_declaration)
Expand Down
8 changes: 4 additions & 4 deletions src/avram/migrator/statement_helpers.cr
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ module Avram::Migrator::StatementHelpers
include Avram::Migrator::IndexStatementHelpers
include Avram::TableFor

macro create(table_name)
statements = Avram::Migrator::CreateTableStatement.new({{ table_name }}).build do
macro create(table_name, *, if_not_exists = false)
statements = Avram::Migrator::CreateTableStatement.new({{ table_name }}, if_not_exists: {{ if_not_exists }}).build do
{{ yield }}
end.statements

Expand All @@ -18,8 +18,8 @@ module Avram::Migrator::StatementHelpers
prepared_statements << Avram::Migrator::DropTableStatement.new(table_name).build
end

macro alter(table_name)
statements = Avram::Migrator::AlterTableStatement.new({{ table_name }}).build do
macro alter(table_name, *, if_exists = false)
statements = Avram::Migrator::AlterTableStatement.new({{ table_name }}, if_exists: {{ if_exists }}).build do
{{ yield }}
end.statements

Expand Down

0 comments on commit 82eef3e

Please sign in to comment.