ardm-migrations 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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