dm-migrations 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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