dm-hibernate-migrations 1.0.0

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