dm-migrations 0.10.2 → 1.0.0.rc1
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.
- data/.gitignore +36 -0
- data/Gemfile +141 -0
- data/Rakefile +2 -3
- data/VERSION +1 -1
- data/dm-migrations.gemspec +50 -18
- data/lib/dm-migrations.rb +2 -0
- data/lib/dm-migrations/adapters/dm-do-adapter.rb +276 -0
- data/lib/dm-migrations/adapters/dm-mysql-adapter.rb +283 -0
- data/lib/dm-migrations/adapters/dm-oracle-adapter.rb +321 -0
- data/lib/dm-migrations/adapters/dm-postgres-adapter.rb +159 -0
- data/lib/dm-migrations/adapters/dm-sqlite-adapter.rb +96 -0
- data/lib/dm-migrations/adapters/dm-sqlserver-adapter.rb +177 -0
- data/lib/dm-migrations/adapters/dm-yaml-adapter.rb +23 -0
- data/lib/dm-migrations/auto_migration.rb +238 -0
- data/lib/dm-migrations/migration.rb +3 -3
- data/lib/dm-migrations/sql.rb +2 -2
- data/lib/dm-migrations/sql/mysql.rb +3 -3
- data/lib/dm-migrations/sql/{postgresql.rb → postgres.rb} +3 -3
- data/lib/dm-migrations/sql/{sqlite3.rb → sqlite.rb} +3 -3
- data/spec/integration/auto_migration_spec.rb +506 -0
- data/spec/integration/migration_runner_spec.rb +12 -2
- data/spec/integration/migration_spec.rb +28 -14
- data/spec/integration/sql_spec.rb +22 -21
- data/spec/isolated/require_after_setup_spec.rb +30 -0
- data/spec/isolated/require_before_setup_spec.rb +30 -0
- data/spec/isolated/require_spec.rb +25 -0
- data/spec/spec_helper.rb +10 -25
- data/spec/unit/migration_spec.rb +320 -319
- data/spec/unit/sql/{postgresql_spec.rb → postgres_spec.rb} +17 -17
- data/spec/unit/sql/{sqlite3_extensions_spec.rb → sqlite_extensions_spec.rb} +14 -14
- data/tasks/local_gemfile.rake +18 -0
- data/tasks/spec.rake +0 -3
- metadata +72 -32
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'dm-migrations/auto_migration'
|
2
|
+
require 'dm-migrations/adapters/dm-do-adapter'
|
3
|
+
|
4
|
+
module DataMapper
|
5
|
+
module Migrations
|
6
|
+
module PostgresAdapter
|
7
|
+
|
8
|
+
include DataObjectsAdapter
|
9
|
+
|
10
|
+
# @api private
|
11
|
+
def self.included(base)
|
12
|
+
base.extend DataObjectsAdapter::ClassMethods
|
13
|
+
base.extend ClassMethods
|
14
|
+
end
|
15
|
+
|
16
|
+
# @api semipublic
|
17
|
+
def upgrade_model_storage(model)
|
18
|
+
without_notices { super }
|
19
|
+
end
|
20
|
+
|
21
|
+
# @api semipublic
|
22
|
+
def create_model_storage(model)
|
23
|
+
without_notices { super }
|
24
|
+
end
|
25
|
+
|
26
|
+
# @api semipublic
|
27
|
+
def destroy_model_storage(model)
|
28
|
+
if supports_drop_table_if_exists?
|
29
|
+
without_notices { super }
|
30
|
+
else
|
31
|
+
super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module SQL #:nodoc:
|
36
|
+
# private ## This cannot be private for current migrations
|
37
|
+
|
38
|
+
# @api private
|
39
|
+
def supports_drop_table_if_exists?
|
40
|
+
@supports_drop_table_if_exists ||= postgres_version >= '8.2'
|
41
|
+
end
|
42
|
+
|
43
|
+
# @api private
|
44
|
+
def schema_name
|
45
|
+
@schema_name ||= select('SELECT current_schema()').first.freeze
|
46
|
+
end
|
47
|
+
|
48
|
+
# @api private
|
49
|
+
def postgres_version
|
50
|
+
@postgres_version ||= select('SELECT version()').first.split[1].freeze
|
51
|
+
end
|
52
|
+
|
53
|
+
# @api private
|
54
|
+
def without_notices
|
55
|
+
# execute the block with NOTICE messages disabled
|
56
|
+
begin
|
57
|
+
execute('SET client_min_messages = warning')
|
58
|
+
yield
|
59
|
+
ensure
|
60
|
+
execute('RESET client_min_messages')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# @api private
|
65
|
+
def property_schema_hash(property)
|
66
|
+
schema = super
|
67
|
+
|
68
|
+
primitive = property.primitive
|
69
|
+
|
70
|
+
# Postgres does not support precision and scale for Float
|
71
|
+
if primitive == Float
|
72
|
+
schema.delete(:precision)
|
73
|
+
schema.delete(:scale)
|
74
|
+
end
|
75
|
+
|
76
|
+
if property.kind_of?(Property::Integer)
|
77
|
+
min = property.min
|
78
|
+
max = property.max
|
79
|
+
|
80
|
+
schema[:primitive] = integer_column_statement(min..max) if min && max
|
81
|
+
end
|
82
|
+
|
83
|
+
if schema[:serial]
|
84
|
+
schema[:primitive] = serial_column_statement(min..max)
|
85
|
+
end
|
86
|
+
|
87
|
+
schema
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# Return SQL statement for the integer column
|
93
|
+
#
|
94
|
+
# @param [Range] range
|
95
|
+
# the min/max allowed integers
|
96
|
+
#
|
97
|
+
# @return [String]
|
98
|
+
# the statement to create the integer column
|
99
|
+
#
|
100
|
+
# @api private
|
101
|
+
def integer_column_statement(range)
|
102
|
+
min = range.first
|
103
|
+
max = range.last
|
104
|
+
|
105
|
+
smallint = 2**15
|
106
|
+
integer = 2**31
|
107
|
+
bigint = 2**63
|
108
|
+
|
109
|
+
if min >= -smallint && max < smallint then 'SMALLINT'
|
110
|
+
elsif min >= -integer && max < integer then 'INTEGER'
|
111
|
+
elsif min >= -bigint && max < bigint then 'BIGINT'
|
112
|
+
else
|
113
|
+
raise ArgumentError, "min #{min} and max #{max} exceeds supported range"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Return SQL statement for the serial column
|
118
|
+
#
|
119
|
+
# @param [Integer] max
|
120
|
+
# the max allowed integer
|
121
|
+
#
|
122
|
+
# @return [String]
|
123
|
+
# the statement to create the serial column
|
124
|
+
#
|
125
|
+
# @api private
|
126
|
+
def serial_column_statement(range)
|
127
|
+
max = range.last
|
128
|
+
|
129
|
+
if max.nil? || max < 2**31 then 'SERIAL'
|
130
|
+
elsif max < 2**63 then 'BIGSERIAL'
|
131
|
+
else
|
132
|
+
raise ArgumentError, "min #{range.first} and max #{max} exceeds supported range"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end # module SQL
|
136
|
+
|
137
|
+
include SQL
|
138
|
+
|
139
|
+
module ClassMethods
|
140
|
+
# Types for PostgreSQL databases.
|
141
|
+
#
|
142
|
+
# @return [Hash] types for PostgreSQL databases.
|
143
|
+
#
|
144
|
+
# @api private
|
145
|
+
def type_map
|
146
|
+
precision = Property::Numeric::DEFAULT_PRECISION
|
147
|
+
scale = Property::Decimal::DEFAULT_SCALE
|
148
|
+
|
149
|
+
@type_map ||= super.merge(
|
150
|
+
Property::Binary => { :primitive => 'BYTEA' },
|
151
|
+
BigDecimal => { :primitive => 'NUMERIC', :precision => precision, :scale => scale },
|
152
|
+
Float => { :primitive => 'DOUBLE PRECISION' }
|
153
|
+
).freeze
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'dm-migrations/auto_migration'
|
2
|
+
require 'dm-migrations/adapters/dm-do-adapter'
|
3
|
+
|
4
|
+
module DataMapper
|
5
|
+
module Migrations
|
6
|
+
module SqliteAdapter
|
7
|
+
|
8
|
+
include DataObjectsAdapter
|
9
|
+
|
10
|
+
# @api private
|
11
|
+
def self.included(base)
|
12
|
+
base.extend DataObjectsAdapter::ClassMethods
|
13
|
+
base.extend ClassMethods
|
14
|
+
end
|
15
|
+
|
16
|
+
# @api semipublic
|
17
|
+
def storage_exists?(storage_name)
|
18
|
+
table_info(storage_name).any?
|
19
|
+
end
|
20
|
+
|
21
|
+
# @api semipublic
|
22
|
+
def field_exists?(storage_name, column_name)
|
23
|
+
table_info(storage_name).any? do |row|
|
24
|
+
row.name == column_name
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module SQL #:nodoc:
|
29
|
+
# private ## This cannot be private for current migrations
|
30
|
+
|
31
|
+
# @api private
|
32
|
+
def supports_serial?
|
33
|
+
@supports_serial ||= sqlite_version >= '3.1.0'
|
34
|
+
end
|
35
|
+
|
36
|
+
# @api private
|
37
|
+
def supports_drop_table_if_exists?
|
38
|
+
@supports_drop_table_if_exists ||= sqlite_version >= '3.3.0'
|
39
|
+
end
|
40
|
+
|
41
|
+
# @api private
|
42
|
+
def table_info(table_name)
|
43
|
+
select("PRAGMA table_info(#{quote_name(table_name)})")
|
44
|
+
end
|
45
|
+
|
46
|
+
# @api private
|
47
|
+
def create_table_statement(connection, model, properties)
|
48
|
+
statement = <<-SQL.compress_lines
|
49
|
+
CREATE TABLE #{quote_name(model.storage_name(name))}
|
50
|
+
(#{properties.map { |property| property_schema_statement(connection, property_schema_hash(property)) }.join(', ')}
|
51
|
+
SQL
|
52
|
+
|
53
|
+
# skip adding the primary key if one of the columns is serial. In
|
54
|
+
# SQLite the serial column must be the primary key, so it has already
|
55
|
+
# been defined
|
56
|
+
unless properties.any? { |property| property.serial? }
|
57
|
+
statement << ", PRIMARY KEY(#{properties.key.map { |property| quote_name(property.field) }.join(', ')})"
|
58
|
+
end
|
59
|
+
|
60
|
+
statement << ')'
|
61
|
+
statement
|
62
|
+
end
|
63
|
+
|
64
|
+
# @api private
|
65
|
+
def property_schema_statement(connection, schema)
|
66
|
+
statement = super
|
67
|
+
|
68
|
+
if supports_serial? && schema[:serial]
|
69
|
+
statement << ' PRIMARY KEY AUTOINCREMENT'
|
70
|
+
end
|
71
|
+
|
72
|
+
statement
|
73
|
+
end
|
74
|
+
|
75
|
+
# @api private
|
76
|
+
def sqlite_version
|
77
|
+
@sqlite_version ||= select('SELECT sqlite_version(*)').first.freeze
|
78
|
+
end
|
79
|
+
end # module SQL
|
80
|
+
|
81
|
+
include SQL
|
82
|
+
|
83
|
+
module ClassMethods
|
84
|
+
# Types for SQLite 3 databases.
|
85
|
+
#
|
86
|
+
# @return [Hash] types for SQLite 3 databases.
|
87
|
+
#
|
88
|
+
# @api private
|
89
|
+
def type_map
|
90
|
+
@type_map ||= super.merge(Class => { :primitive => 'VARCHAR' }).freeze
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'dm-migrations/auto_migration'
|
2
|
+
require 'dm-migrations/adapters/dm-do-adapter'
|
3
|
+
|
4
|
+
module DataMapper
|
5
|
+
module Migrations
|
6
|
+
module SqlserverAdapter
|
7
|
+
|
8
|
+
DEFAULT_CHARACTER_SET = 'utf8'.freeze
|
9
|
+
|
10
|
+
include DataObjectsAdapter
|
11
|
+
|
12
|
+
# @api private
|
13
|
+
def self.included(base)
|
14
|
+
base.extend DataObjectsAdapter::ClassMethods
|
15
|
+
base.extend ClassMethods
|
16
|
+
end
|
17
|
+
|
18
|
+
# @api semipublic
|
19
|
+
def storage_exists?(storage_name)
|
20
|
+
select("SELECT name FROM sysobjects WHERE name LIKE ?", storage_name).first == storage_name
|
21
|
+
end
|
22
|
+
|
23
|
+
# @api semipublic
|
24
|
+
def field_exists?(storage_name, field_name)
|
25
|
+
result = select("SELECT c.name FROM sysobjects as o JOIN syscolumns AS c ON o.id = c.id WHERE o.name = #{quote_name(storage_name)} AND c.name LIKE ?", field_name).first
|
26
|
+
result ? result.field == field_name : false
|
27
|
+
end
|
28
|
+
|
29
|
+
module SQL #:nodoc:
|
30
|
+
# private ## This cannot be private for current migrations
|
31
|
+
|
32
|
+
# @api private
|
33
|
+
def supports_serial?
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
# @api private
|
38
|
+
def supports_drop_table_if_exists?
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
# @api private
|
43
|
+
def schema_name
|
44
|
+
# TODO: is there a cleaner way to find out the current DB we are connected to?
|
45
|
+
@options[:path].split('/').last
|
46
|
+
end
|
47
|
+
|
48
|
+
# TODO: update dkubb/dm-more/dm-migrations to use schema_name and remove this
|
49
|
+
|
50
|
+
alias db_name schema_name
|
51
|
+
|
52
|
+
# @api private
|
53
|
+
def create_table_statement(connection, model, properties)
|
54
|
+
statement = <<-SQL.compress_lines
|
55
|
+
CREATE TABLE #{quote_name(model.storage_name(name))}
|
56
|
+
(#{properties.map { |property| property_schema_statement(connection, property_schema_hash(property)) }.join(', ')}
|
57
|
+
SQL
|
58
|
+
|
59
|
+
unless properties.any? { |property| property.serial? }
|
60
|
+
statement << ", PRIMARY KEY(#{properties.key.map { |property| quote_name(property.field) }.join(', ')})"
|
61
|
+
end
|
62
|
+
|
63
|
+
statement << ')'
|
64
|
+
statement
|
65
|
+
end
|
66
|
+
|
67
|
+
# @api private
|
68
|
+
def property_schema_hash(property)
|
69
|
+
schema = super
|
70
|
+
|
71
|
+
if property.kind_of?(Property::Integer)
|
72
|
+
min = property.min
|
73
|
+
max = property.max
|
74
|
+
|
75
|
+
schema[:primitive] = integer_column_statement(min..max) if min && max
|
76
|
+
end
|
77
|
+
|
78
|
+
if schema[:primitive] == 'TEXT'
|
79
|
+
schema.delete(:default)
|
80
|
+
end
|
81
|
+
|
82
|
+
schema
|
83
|
+
end
|
84
|
+
|
85
|
+
# @api private
|
86
|
+
def property_schema_statement(connection, schema)
|
87
|
+
if supports_serial? && schema[:serial]
|
88
|
+
statement = quote_name(schema[:name])
|
89
|
+
statement << " #{schema[:primitive]}"
|
90
|
+
|
91
|
+
length = schema[:length]
|
92
|
+
|
93
|
+
if schema[:precision] && schema[:scale]
|
94
|
+
statement << "(#{[ :precision, :scale ].map { |key| connection.quote_value(schema[key]) }.join(', ')})"
|
95
|
+
elsif length
|
96
|
+
statement << "(#{connection.quote_value(length)})"
|
97
|
+
end
|
98
|
+
|
99
|
+
statement << ' IDENTITY'
|
100
|
+
else
|
101
|
+
statement = super
|
102
|
+
end
|
103
|
+
|
104
|
+
statement
|
105
|
+
end
|
106
|
+
|
107
|
+
# @api private
|
108
|
+
def character_set
|
109
|
+
@character_set ||= show_variable('character_set_connection') || DEFAULT_CHARACTER_SET
|
110
|
+
end
|
111
|
+
|
112
|
+
# @api private
|
113
|
+
def collation
|
114
|
+
@collation ||= show_variable('collation_connection') || DEFAULT_COLLATION
|
115
|
+
end
|
116
|
+
|
117
|
+
# @api private
|
118
|
+
def show_variable(name)
|
119
|
+
raise "SqlserverAdapter#show_variable: Not implemented"
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
# Return SQL statement for the integer column
|
125
|
+
#
|
126
|
+
# @param [Range] range
|
127
|
+
# the min/max allowed integers
|
128
|
+
#
|
129
|
+
# @return [String]
|
130
|
+
# the statement to create the integer column
|
131
|
+
#
|
132
|
+
# @api private
|
133
|
+
def integer_column_statement(range)
|
134
|
+
min = range.first
|
135
|
+
max = range.last
|
136
|
+
|
137
|
+
smallint = 2**15
|
138
|
+
integer = 2**31
|
139
|
+
bigint = 2**63
|
140
|
+
|
141
|
+
if min >= 0 && max < 2**8 then 'TINYINT'
|
142
|
+
elsif min >= -smallint && max < smallint then 'SMALLINT'
|
143
|
+
elsif min >= -integer && max < integer then 'INT'
|
144
|
+
elsif min >= -bigint && max < bigint then 'BIGINT'
|
145
|
+
else
|
146
|
+
raise ArgumentError, "min #{min} and max #{max} exceeds supported range"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end # module SQL
|
151
|
+
|
152
|
+
include SQL
|
153
|
+
|
154
|
+
module ClassMethods
|
155
|
+
# Types for Sqlserver databases.
|
156
|
+
#
|
157
|
+
# @return [Hash] types for Sqlserver databases.
|
158
|
+
#
|
159
|
+
# @api private
|
160
|
+
def type_map
|
161
|
+
length = Property::String::DEFAULT_LENGTH
|
162
|
+
precision = Property::Numeric::DEFAULT_PRECISION
|
163
|
+
scale = Property::Decimal::DEFAULT_SCALE
|
164
|
+
|
165
|
+
@type_map ||= super.merge(
|
166
|
+
DateTime => { :primitive => 'DATETIME' },
|
167
|
+
Date => { :primitive => 'SMALLDATETIME' },
|
168
|
+
Time => { :primitive => 'SMALLDATETIME' },
|
169
|
+
TrueClass => { :primitive => 'BIT', },
|
170
|
+
Property::Text => { :primitive => 'NVARCHAR', :length => 'max' }
|
171
|
+
).freeze
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'dm-migrations/auto_migration'
|
2
|
+
require 'dm-migrations/adapters/dm-do-adapter'
|
3
|
+
|
4
|
+
module DataMapper
|
5
|
+
module Migrations
|
6
|
+
module YamlAdapter
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
DataMapper.extend(Migrations::SingletonMethods)
|
10
|
+
[ :Repository, :Model ].each do |name|
|
11
|
+
DataMapper.const_get(name).send(:include, Migrations.const_get(name))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# @api semipublic
|
16
|
+
def destroy_model_storage(model)
|
17
|
+
yaml_file(model).unlink if yaml_file(model).file?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|