dm-hibernate-migrations 1.0.0

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.
Files changed (40) hide show
  1. data/lib/dm-migrations.rb +3 -0
  2. data/lib/dm-migrations/adapters/dm-do-adapter.rb +284 -0
  3. data/lib/dm-migrations/adapters/dm-mysql-adapter.rb +283 -0
  4. data/lib/dm-migrations/adapters/dm-oracle-adapter.rb +321 -0
  5. data/lib/dm-migrations/adapters/dm-postgres-adapter.rb +159 -0
  6. data/lib/dm-migrations/adapters/dm-sqlite-adapter.rb +96 -0
  7. data/lib/dm-migrations/adapters/dm-sqlserver-adapter.rb +177 -0
  8. data/lib/dm-migrations/adapters/dm-yaml-adapter.rb +23 -0
  9. data/lib/dm-migrations/auto_migration.rb +237 -0
  10. data/lib/dm-migrations/migration.rb +217 -0
  11. data/lib/dm-migrations/migration_runner.rb +85 -0
  12. data/lib/dm-migrations/sql.rb +5 -0
  13. data/lib/dm-migrations/sql/column.rb +5 -0
  14. data/lib/dm-migrations/sql/mysql.rb +53 -0
  15. data/lib/dm-migrations/sql/postgres.rb +78 -0
  16. data/lib/dm-migrations/sql/sqlite.rb +45 -0
  17. data/lib/dm-migrations/sql/table.rb +15 -0
  18. data/lib/dm-migrations/sql/table_creator.rb +102 -0
  19. data/lib/dm-migrations/sql/table_modifier.rb +51 -0
  20. data/lib/spec/example/migration_example_group.rb +73 -0
  21. data/lib/spec/matchers/migration_matchers.rb +106 -0
  22. data/spec/integration/auto_migration_spec.rb +506 -0
  23. data/spec/integration/migration_runner_spec.rb +89 -0
  24. data/spec/integration/migration_spec.rb +138 -0
  25. data/spec/integration/sql_spec.rb +190 -0
  26. data/spec/isolated/require_after_setup_spec.rb +30 -0
  27. data/spec/isolated/require_before_setup_spec.rb +30 -0
  28. data/spec/isolated/require_spec.rb +25 -0
  29. data/spec/rcov.opts +6 -0
  30. data/spec/spec.opts +4 -0
  31. data/spec/spec_helper.rb +16 -0
  32. data/spec/unit/migration_spec.rb +453 -0
  33. data/spec/unit/sql/column_spec.rb +14 -0
  34. data/spec/unit/sql/postgres_spec.rb +97 -0
  35. data/spec/unit/sql/sqlite_extensions_spec.rb +108 -0
  36. data/spec/unit/sql/table_creator_spec.rb +94 -0
  37. data/spec/unit/sql/table_modifier_spec.rb +49 -0
  38. data/spec/unit/sql/table_spec.rb +28 -0
  39. data/spec/unit/sql_spec.rb +7 -0
  40. metadata +157 -0
@@ -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
@@ -0,0 +1,237 @@
1
+ require 'dm-core'
2
+
3
+ module DataMapper
4
+ module Migrations
5
+ module SingletonMethods
6
+
7
+ # destructively migrates the repository upwards to match model definitions
8
+ #
9
+ # @param [Symbol] name repository to act on, :default is the default
10
+ #
11
+ # @api public
12
+ def migrate!(repository_name = nil)
13
+ repository(repository_name).migrate!
14
+ end
15
+
16
+ # drops and recreates the repository upwards to match model definitions
17
+ #
18
+ # @param [Symbol] name repository to act on, :default is the default
19
+ #
20
+ # @api public
21
+ def auto_migrate!(repository_name = nil)
22
+ repository_execute(:auto_migrate!, repository_name)
23
+ end
24
+
25
+ # @api public
26
+ def auto_upgrade!(repository_name = nil)
27
+ repository_execute(:auto_upgrade!, repository_name)
28
+ end
29
+
30
+ private
31
+
32
+ # @api semipublic
33
+ def auto_migrate_down!(repository_name)
34
+ repository_execute(:auto_migrate_down!, repository_name)
35
+ end
36
+
37
+ # @api semipublic
38
+ def auto_migrate_up!(repository_name)
39
+ repository_execute(:auto_migrate_up!, repository_name)
40
+ end
41
+
42
+ # @api private
43
+ def repository_execute(method, repository_name)
44
+ DataMapper::Model.descendants.each do |model|
45
+ model.send(method, repository_name || model.default_repository_name)
46
+ end
47
+ end
48
+ end
49
+
50
+ module Repository
51
+ # Determine whether a particular named storage exists in this repository
52
+ #
53
+ # @param [String]
54
+ # storage_name name of the storage to test for
55
+ #
56
+ # @return [Boolean]
57
+ # true if the data-store +storage_name+ exists
58
+ #
59
+ # @api semipublic
60
+ def storage_exists?(storage_name)
61
+ adapter = self.adapter
62
+ if adapter.respond_to?(:storage_exists?)
63
+ adapter.storage_exists?(storage_name)
64
+ end
65
+ end
66
+
67
+ # @api semipublic
68
+ def upgrade_model_storage(model)
69
+ adapter = self.adapter
70
+ if adapter.respond_to?(:upgrade_model_storage)
71
+ adapter.upgrade_model_storage(model)
72
+ end
73
+ end
74
+
75
+ # @api semipublic
76
+ def create_model_storage(model)
77
+ adapter = self.adapter
78
+ if adapter.respond_to?(:create_model_storage)
79
+ adapter.create_model_storage(model)
80
+ end
81
+ end
82
+
83
+ # @api semipublic
84
+ def destroy_model_storage(model)
85
+ adapter = self.adapter
86
+ if adapter.respond_to?(:destroy_model_storage)
87
+ adapter.destroy_model_storage(model)
88
+ end
89
+ end
90
+
91
+ # Destructively automigrates the data-store to match the model.
92
+ # First migrates all models down and then up.
93
+ # REPEAT: THIS IS DESTRUCTIVE
94
+ #
95
+ # @api public
96
+ def auto_migrate!
97
+ DataMapper.auto_migrate!(name)
98
+ end
99
+
100
+ # Safely migrates the data-store to match the model
101
+ # preserving data already in the data-store
102
+ #
103
+ # @api public
104
+ def auto_upgrade!
105
+ DataMapper.auto_upgrade!(name)
106
+ end
107
+ end # module Repository
108
+
109
+ module Model
110
+
111
+ # @api private
112
+ def self.included(mod)
113
+ mod.descendants.each { |model| model.extend self }
114
+ end
115
+
116
+ # @api semipublic
117
+ def storage_exists?(repository_name = default_repository_name)
118
+ repository(repository_name).storage_exists?(storage_name(repository_name))
119
+ end
120
+
121
+ # Destructively automigrates the data-store to match the model
122
+ # REPEAT: THIS IS DESTRUCTIVE
123
+ #
124
+ # @param Symbol repository_name the repository to be migrated
125
+ #
126
+ # @api public
127
+ def auto_migrate!(repository_name = self.repository_name)
128
+ assert_valid(true)
129
+ auto_migrate_down!(repository_name)
130
+ auto_migrate_up!(repository_name)
131
+ end
132
+
133
+ # Safely migrates the data-store to match the model
134
+ # preserving data already in the data-store
135
+ #
136
+ # @param Symbol repository_name the repository to be migrated
137
+ #
138
+ # @api public
139
+ def auto_upgrade!(repository_name = self.repository_name)
140
+ assert_valid(true)
141
+ base_model = self.base_model
142
+ if base_model == self
143
+ repository(repository_name).upgrade_model_storage(self)
144
+ else
145
+ base_model.auto_upgrade!(repository_name)
146
+ end
147
+ end
148
+
149
+ # Destructively migrates the data-store down, which basically
150
+ # deletes all the models.
151
+ # REPEAT: THIS IS DESTRUCTIVE
152
+ #
153
+ # @param Symbol repository_name the repository to be migrated
154
+ #
155
+ # @api private
156
+ def auto_migrate_down!(repository_name = self.repository_name)
157
+ assert_valid(true)
158
+ base_model = self.base_model
159
+ if base_model == self
160
+ repository(repository_name).destroy_model_storage(self)
161
+ else
162
+ base_model.auto_migrate_down!(repository_name)
163
+ end
164
+ end
165
+
166
+ # Auto migrates the data-store to match the model
167
+ #
168
+ # @param Symbol repository_name the repository to be migrated
169
+ #
170
+ # @api private
171
+ def auto_migrate_up!(repository_name = self.repository_name)
172
+ assert_valid(true)
173
+ base_model = self.base_model
174
+ if base_model == self
175
+ repository(repository_name).create_model_storage(self)
176
+ else
177
+ base_model.auto_migrate_up!(repository_name)
178
+ end
179
+ end
180
+
181
+ end # module Model
182
+
183
+ def self.include_migration_api
184
+ DataMapper.extend(SingletonMethods)
185
+ [ :Repository, :Model ].each do |name|
186
+ DataMapper.const_get(name).send(:include, const_get(name))
187
+ end
188
+ DataMapper::Model.append_extensions(Model)
189
+ Adapters::AbstractAdapter.descendants.each do |adapter_class|
190
+ Adapters.include_migration_api(ActiveSupport::Inflector.demodulize(adapter_class.name))
191
+ end
192
+ end
193
+
194
+ end
195
+
196
+ module Adapters
197
+
198
+ def self.include_migration_api(const_name)
199
+ require auto_migration_extensions(const_name)
200
+ if Migrations.const_defined?(const_name)
201
+ adapter = const_get(const_name)
202
+ adapter.send(:include, migration_module(const_name))
203
+ end
204
+ rescue LoadError
205
+ # Silently ignore the fact that no adapter extensions could be required
206
+ # This means that the adapter in use doesn't support migrations
207
+ end
208
+
209
+ def self.migration_module(const_name)
210
+ Migrations.const_get(const_name)
211
+ end
212
+
213
+ class << self
214
+ private
215
+
216
+ # @api private
217
+ def auto_migration_extensions(const_name)
218
+ name = adapter_name(const_name)
219
+ name = 'do' if name == 'dataobjects'
220
+ "dm-migrations/adapters/dm-#{name}-adapter"
221
+ end
222
+
223
+ end
224
+
225
+ extendable do
226
+ # @api private
227
+ def const_added(const_name)
228
+ include_migration_api(const_name)
229
+ super
230
+ end
231
+ end
232
+
233
+ end # module Adapters
234
+
235
+ Migrations.include_migration_api
236
+
237
+ end # module DataMapper