dm-migrations 0.10.2 → 1.0.0.rc1

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