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.
Files changed (33) hide show
  1. data/.gitignore +36 -0
  2. data/Gemfile +141 -0
  3. data/Rakefile +2 -3
  4. data/VERSION +1 -1
  5. data/dm-migrations.gemspec +50 -18
  6. data/lib/dm-migrations.rb +2 -0
  7. data/lib/dm-migrations/adapters/dm-do-adapter.rb +276 -0
  8. data/lib/dm-migrations/adapters/dm-mysql-adapter.rb +283 -0
  9. data/lib/dm-migrations/adapters/dm-oracle-adapter.rb +321 -0
  10. data/lib/dm-migrations/adapters/dm-postgres-adapter.rb +159 -0
  11. data/lib/dm-migrations/adapters/dm-sqlite-adapter.rb +96 -0
  12. data/lib/dm-migrations/adapters/dm-sqlserver-adapter.rb +177 -0
  13. data/lib/dm-migrations/adapters/dm-yaml-adapter.rb +23 -0
  14. data/lib/dm-migrations/auto_migration.rb +238 -0
  15. data/lib/dm-migrations/migration.rb +3 -3
  16. data/lib/dm-migrations/sql.rb +2 -2
  17. data/lib/dm-migrations/sql/mysql.rb +3 -3
  18. data/lib/dm-migrations/sql/{postgresql.rb → postgres.rb} +3 -3
  19. data/lib/dm-migrations/sql/{sqlite3.rb → sqlite.rb} +3 -3
  20. data/spec/integration/auto_migration_spec.rb +506 -0
  21. data/spec/integration/migration_runner_spec.rb +12 -2
  22. data/spec/integration/migration_spec.rb +28 -14
  23. data/spec/integration/sql_spec.rb +22 -21
  24. data/spec/isolated/require_after_setup_spec.rb +30 -0
  25. data/spec/isolated/require_before_setup_spec.rb +30 -0
  26. data/spec/isolated/require_spec.rb +25 -0
  27. data/spec/spec_helper.rb +10 -25
  28. data/spec/unit/migration_spec.rb +320 -319
  29. data/spec/unit/sql/{postgresql_spec.rb → postgres_spec.rb} +17 -17
  30. data/spec/unit/sql/{sqlite3_extensions_spec.rb → sqlite_extensions_spec.rb} +14 -14
  31. data/tasks/local_gemfile.rake +18 -0
  32. data/tasks/spec.rake +0 -3
  33. 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