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