dm-migrations 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,107 @@
1
+
2
+ module Spec
3
+ module Matchers
4
+ module Migration
5
+
6
+ def have_table(table_name)
7
+ HaveTableMatcher.new(table_name)
8
+ end
9
+
10
+ def have_column(column_name)
11
+ HaveColumnMatcher.new(column_name)
12
+ end
13
+
14
+ def permit_null
15
+ NullableColumnMatcher.new
16
+ end
17
+
18
+ def be_primary_key
19
+ PrimaryKeyMatcher.new
20
+ end
21
+
22
+ class HaveTableMatcher
23
+
24
+ attr_accessor :table_name, :repository
25
+
26
+ def initialize(table_name)
27
+ @table_name = table_name
28
+ end
29
+
30
+ def matches?(repository)
31
+ repository.adapter.storage_exists?(table_name)
32
+ end
33
+
34
+ def failure_message
35
+ %(expected #{repository} to have table '#{table_name}')
36
+ end
37
+
38
+ def negative_failure_message
39
+ %(expected #{repository} to not have table '#{table_name}')
40
+ end
41
+
42
+ end
43
+
44
+ class HaveColumnMatcher
45
+
46
+ attr_accessor :table, :column_name
47
+
48
+ def initialize(column_name)
49
+ @column_name = column_name
50
+ end
51
+
52
+ def matches?(table)
53
+ @table = table
54
+ table.columns.map { |c| c.name }.include?(column_name.to_s)
55
+ end
56
+
57
+ def failure_message
58
+ %(expected #{table} to have column '#{column_name}')
59
+ end
60
+
61
+ def negative_failure_message
62
+ %(expected #{table} to not have column '#{column_name}')
63
+ end
64
+
65
+ end
66
+
67
+ class NullableColumnMatcher
68
+
69
+ attr_accessor :column
70
+
71
+ def matches?(column)
72
+ @column = column
73
+ ! column.not_null
74
+ end
75
+
76
+ def failure_message
77
+ %(expected #{column.name} to permit NULL)
78
+ end
79
+
80
+ def negative_failure_message
81
+ %(expected #{column.name} to be NOT NULL)
82
+ end
83
+
84
+ end
85
+
86
+ class PrimaryKeyMatcher
87
+
88
+ attr_accessor :column
89
+
90
+ def matches?(column)
91
+ @column = column
92
+ column.primary_key
93
+ end
94
+
95
+ def failure_message
96
+ %(expected #{column.name} to be PRIMARY KEY)
97
+ end
98
+
99
+ def negative_failure_message
100
+ %(expected #{column.name} to not be PRIMARY KEY)
101
+ end
102
+
103
+ end
104
+
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,110 @@
1
+ require File.dirname(__FILE__) + '/sql/sqlite3'
2
+ require File.dirname(__FILE__) + '/sql/mysql'
3
+ require File.dirname(__FILE__) + '/sql/postgresql'
4
+
5
+ module SQL
6
+
7
+ class TableCreator
8
+ attr_accessor :table_name, :opts
9
+
10
+ def initialize(adapter, table_name, opts = {}, &block)
11
+ @adapter = adapter
12
+ @table_name = table_name.to_s
13
+ @opts = opts
14
+
15
+ @columns = []
16
+
17
+ self.instance_eval &block
18
+ end
19
+
20
+ def quoted_table_name
21
+ @adapter.send(:quote_table_name, table_name)
22
+ end
23
+
24
+ def column(name, type, opts = {})
25
+ @columns << Column.new(@adapter, name, type, opts)
26
+ end
27
+
28
+ def to_sql
29
+ "CREATE TABLE #{quoted_table_name} (#{@columns.map{ |c| c.to_sql }.join(', ')})"
30
+ end
31
+
32
+ class Column
33
+ attr_accessor :name, :type
34
+
35
+ def initialize(adapter, name, type, opts = {})
36
+ @adapter = adapter
37
+ @name = name.to_s
38
+ @opts = (opts ||= {})
39
+ @type = build_type(type)
40
+ end
41
+
42
+ def build_type(type_class)
43
+ schema = {:name => @name, :quote_column_name => quoted_name}.merge(@opts)
44
+ schema.merge!(@adapter.class.type_map[type_class])
45
+ @adapter.property_schema_statement(schema)
46
+ end
47
+
48
+ def to_sql
49
+ type
50
+ end
51
+
52
+ def quoted_name
53
+ @adapter.send(:quote_column_name, name)
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ class TableModifier
60
+ attr_accessor :table_name, :opts, :statements, :adapter
61
+
62
+ def initialize(adapter, table_name, opts = {}, &block)
63
+ @adapter = adapter
64
+ @table_name = table_name.to_s
65
+ @opts = (opts ||= {})
66
+
67
+ @statements = []
68
+
69
+ self.instance_eval &block
70
+ end
71
+
72
+ def add_column(name, type, opts = {})
73
+ column = SQL::TableCreator::Column.new(@adapter, name, type, opts)
74
+ @statements << "ALTER TABLE #{quoted_table_name} ADD COLUMN #{column.to_sql}"
75
+ end
76
+
77
+ def drop_column(name)
78
+ # raise NotImplemented for SQLite3. Can't ALTER TABLE, need to copy table.
79
+ # We'd have to inspect it, and we can't, since we aren't executing any queries yet.
80
+ # Just write the sql yourself.
81
+ if name.is_a?(Array)
82
+ name.each{ |n| drop_column(n) }
83
+ else
84
+ @statements << "ALTER TABLE #{quoted_table_name} DROP COLUMN #{quote_column_name(name)}"
85
+ end
86
+ end
87
+ alias drop_columns drop_column
88
+
89
+ def rename_column(name, new_name, opts = {})
90
+ # raise NotImplemented for SQLite3
91
+ @statements << "ALTER TABLE #{quoted_table_name} RENAME COLUMN #{quote_column_name(name)} TO #{quote_column_name(new_name)}"
92
+ end
93
+
94
+ def change_column(name, type, opts = {})
95
+ # raise NotImplemented for SQLite3
96
+ @statements << "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(name)} TYPE #{type}"
97
+ end
98
+
99
+ def quote_column_name(name)
100
+ @adapter.send(:quote_column_name, name.to_s)
101
+ end
102
+
103
+ def quoted_table_name
104
+ @adapter.send(:quote_table_name, table_name)
105
+ end
106
+
107
+ end
108
+
109
+
110
+ end
@@ -0,0 +1,9 @@
1
+ module SQL
2
+
3
+ class Column
4
+
5
+ attr_accessor :name, :type, :not_null, :default_value, :primary_key, :unique
6
+
7
+ end
8
+
9
+ end
@@ -0,0 +1,52 @@
1
+ require File.dirname(__FILE__) + '/table'
2
+
3
+ module SQL
4
+ module Mysql
5
+
6
+ def supports_schema_transactions?
7
+ false
8
+ end
9
+
10
+ def table(table_name)
11
+ SQL::Mysql::Table.new(self, table_name)
12
+ end
13
+
14
+ def recreate_database
15
+ execute "DROP DATABASE #{db_name}"
16
+ execute "CREATE DATABASE #{db_name}"
17
+ execute "USE #{db_name}"
18
+ end
19
+
20
+ def supports_serial?
21
+ true
22
+ end
23
+
24
+ # TODO: move to dm-more/dm-migrations
25
+ def property_schema_statement(schema)
26
+ if supports_serial? && schema[:serial]
27
+ statement = "#{schema[:quote_column_name]} serial PRIMARY KEY"
28
+ else
29
+ super
30
+ end
31
+ end
32
+
33
+ class Table
34
+ def initialize(adapter, table_name)
35
+ @columns = []
36
+ adapter.query_table(table_name).each do |col_struct|
37
+ @columns << SQL::Mysql::Column.new(col_struct)
38
+ end
39
+ end
40
+ end
41
+
42
+ class Column
43
+ def initialize(col_struct)
44
+ @name, @type, @default_value, @primary_key = col_struct.name, col_struct.type, col_struct.dflt_value, col_struct.pk
45
+
46
+ @not_null = col_struct.notnull == 0
47
+ end
48
+ end
49
+
50
+
51
+ end
52
+ end
@@ -0,0 +1,78 @@
1
+ module SQL
2
+ module Postgresql
3
+
4
+ def supports_schema_transactions?
5
+ true
6
+ end
7
+
8
+ def table(table_name)
9
+ SQL::Postgresql::Table.new(self, table_name)
10
+ end
11
+
12
+ def drop_database
13
+ end
14
+
15
+ def recreate_database
16
+ execute "DROP SCHEMA IF EXISTS test CASCADE"
17
+ execute "CREATE SCHEMA test"
18
+ execute "SET search_path TO test"
19
+ end
20
+
21
+ def supports_serial?
22
+ true
23
+ end
24
+
25
+ def property_schema_statement(schema)
26
+ if supports_serial? && schema[:serial]
27
+ statement = "#{schema[:quote_column_name]} serial PRIMARY KEY"
28
+ else
29
+ statement = super
30
+ if schema.has_key?(:sequence_name)
31
+ statement << " DEFAULT nextval('#{schema[:sequence_name]}') NOT NULL"
32
+ end
33
+ statement
34
+ end
35
+ statement
36
+ end
37
+
38
+ class Table < SQL::Table
39
+ def initialize(adapter, table_name)
40
+ @columns = []
41
+ adapter.query_table(table_name).each do |col_struct|
42
+ @columns << SQL::Postgresql::Column.new(col_struct)
43
+ end
44
+
45
+ puts "+=+++++++++++++++++++++++++++++++++++++++"
46
+ # detect column constraints
47
+ adapter.query(
48
+ "SELECT * FROM information_schema.table_constraints WHERE table_name='#{table_name}' AND table_schema=current_schema()"
49
+ ).each do |table_constraint|
50
+ puts table_constraint.inspect
51
+ adapter.query(
52
+ "SELECT * FROM information_schema.constraint_column_usage WHERE table_name='#{table_name}' AND table_schema=current_schema()"
53
+ ).each do |constrained_column|
54
+ @columns.each do |column|
55
+ if column.name == constrained_column.column_name
56
+ case table_constraint.constraint_type
57
+ when "UNIQUE" then column.unique = true
58
+ when "PRIMARY KEY" then column.primary_key = true
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ class Column < SQL::Column
69
+ def initialize(col_struct)
70
+ @name, @type, @default_value = col_struct.column_name, col_struct.data_type, col_struct.column_default
71
+
72
+ @not_null = col_struct.is_nullable != "YES"
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,50 @@
1
+ require File.dirname(__FILE__) + '/table'
2
+
3
+ module SQL
4
+ module Sqlite3
5
+
6
+ def supports_schema_transactions?
7
+ true
8
+ end
9
+
10
+ def table(table_name)
11
+ SQL::Table.new(self, table_name)
12
+ end
13
+
14
+ def recreate_database
15
+ DataMapper.logger.info "Dropping #{@uri.path}"
16
+ system "rm #{@uri.path}"
17
+ # do nothing, sqlite will automatically create the database file
18
+ end
19
+
20
+ def supports_serial?
21
+ true
22
+ end
23
+
24
+ # TODO: move to dm-more/dm-migrations
25
+ def property_schema_statement(schema)
26
+ statement = super
27
+ statement << ' PRIMARY KEY AUTOINCREMENT' if supports_serial? && schema[:serial]
28
+ statement
29
+ end
30
+
31
+ class Table < SQL::Table
32
+ def initialize(adapter, table_name)
33
+ @columns = []
34
+ adapter.query_table(table_name).each do |col_struct|
35
+ @columns << SQL::Sqlite3::Column.new(col_struct)
36
+ end
37
+ end
38
+ end
39
+
40
+ class Column < SQL::Column
41
+ def initialize(col_struct)
42
+ @name, @type, @default_value, @primary_key = col_struct.name, col_struct.type, col_struct.dflt_value, col_struct.pk
43
+
44
+ @not_null = col_struct.notnull == 0
45
+ end
46
+ end
47
+
48
+
49
+ end
50
+ end
@@ -0,0 +1,19 @@
1
+ require File.dirname(__FILE__) + '/column'
2
+
3
+ module SQL
4
+
5
+ class Table
6
+
7
+ attr_accessor :name, :columns
8
+
9
+ def to_s
10
+ name
11
+ end
12
+
13
+ def column(column_name)
14
+ @columns.select { |c| c.name == column_name.to_s }.first
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,78 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
3
+
4
+ if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
5
+ describe 'empty migration runner' do
6
+ it "should return an empty array if no migrations have been defined" do
7
+ migrations.should be_kind_of(Array)
8
+ migrations.should have(0).item
9
+ end
10
+ end
11
+ describe 'migration runnner' do
12
+ # set up some 'global' setup and teardown tasks
13
+ before(:each) do
14
+ migration( 1, :create_people_table) { }
15
+ end
16
+
17
+ after(:each) do
18
+ @@migrations = []
19
+ end
20
+
21
+ describe '#migration' do
22
+
23
+ it 'should create a new migration object, and add it to the list of migrations' do
24
+ @@migrations.should be_kind_of(Array)
25
+ @@migrations.should have(1).item
26
+ @@migrations.first.name.should == "create_people_table"
27
+ end
28
+
29
+ it 'should allow multiple migrations to be added' do
30
+ migration( 2, :add_dob_to_people) { }
31
+ migration( 2, :add_favorite_pet_to_people) { }
32
+ migration( 3, :add_something_else_to_people) { }
33
+ @@migrations.should have(4).items
34
+ end
35
+
36
+ it 'should raise an error on adding with a duplicated name' do
37
+ lambda { migration( 1, :create_people_table) { } }.should raise_error(RuntimeError, /Migration name conflict/)
38
+ end
39
+
40
+ end
41
+
42
+ describe '#migrate_up! and #migrate_down!' do
43
+ before(:each) do
44
+ migration( 2, :add_dob_to_people) { }
45
+ migration( 2, :add_favorite_pet_to_people) { }
46
+ migration( 3, :add_something_else_to_people) { }
47
+ end
48
+
49
+ it 'calling migrate_up! should migrate up all the migrations' do
50
+ # add our expection that migrate_up should be called
51
+ @@migrations.each do |m|
52
+ m.should_receive(:perform_up)
53
+ end
54
+ migrate_up!
55
+ end
56
+
57
+ it 'calling migrate_up! with an arguement should only migrate to that level' do
58
+ @@migrations.each do |m|
59
+ if m.position <= 2
60
+ m.should_receive(:perform_up)
61
+ else
62
+ m.should_not_receive(:perform_up)
63
+ end
64
+ end
65
+ migrate_up!(2)
66
+ end
67
+
68
+ it 'calling migrate_down! should migrate down all the migrations' do
69
+ # add our expection that migrate_up should be called
70
+ @@migrations.each do |m|
71
+ m.should_receive(:perform_down)
72
+ end
73
+ migrate_down!
74
+ end
75
+
76
+ end
77
+ end
78
+ end