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,300 @@
1
+ require 'dm-migrations/exceptions/duplicate_migration'
2
+ require 'dm-migrations/sql'
3
+
4
+ require 'benchmark'
5
+
6
+ module DataMapper
7
+ class Migration
8
+ include SQL
9
+
10
+ # The position or version the migration belongs to
11
+ attr_reader :position
12
+
13
+ # The name of the migration
14
+ attr_reader :name
15
+
16
+ # The repository the migration operates on
17
+ attr_reader :repository
18
+
19
+ #
20
+ # Creates a new migration.
21
+ #
22
+ # @param [Symbol, String, Integer] position
23
+ # The position or version the migration belongs to.
24
+ #
25
+ # @param [Symbol] name
26
+ # The name of the migration.
27
+ #
28
+ # @param [Hash] options
29
+ # Additional options for the migration.
30
+ #
31
+ # @option options [Boolean] :verbose (true)
32
+ # Enables or disables verbose output.
33
+ #
34
+ # @option options [Symbol] :repository (:default)
35
+ # The DataMapper repository the migration will operate on.
36
+ #
37
+ def initialize(position, name, options = {}, &block)
38
+ @position = position
39
+ @name = name
40
+ @options = options
41
+ @verbose = options.fetch(:verbose, true)
42
+ @up_action = nil
43
+ @down_action = nil
44
+
45
+ @repository = if options.key?(:database)
46
+ warn 'Using the :database option with migrations is deprecated, use :repository instead'
47
+ options[:database]
48
+ else
49
+ options.fetch(:repository, :default)
50
+ end
51
+
52
+ instance_eval(&block)
53
+ end
54
+
55
+ #
56
+ # The repository the migration will operate on.
57
+ #
58
+ # @return [Symbol, nil]
59
+ # The name of the DataMapper repository the migration will run against.
60
+ #
61
+ # @deprecated Use {#repository} instead.
62
+ #
63
+ # @since 1.0.1.
64
+ #
65
+ def database
66
+ warn "Using the DataMapper::Migration#database method is deprecated, use #repository instead"
67
+ @repository
68
+ end
69
+
70
+ #
71
+ # The adapter the migration will use.
72
+ #
73
+ # @return [DataMapper::Adapter]
74
+ # The adapter the migration will operate on.
75
+ #
76
+ # @since 1.0.1
77
+ #
78
+ def adapter
79
+ setup! unless setup?
80
+
81
+ @adapter
82
+ end
83
+
84
+ # define the actions that should be performed on an up migration
85
+ def up(&block)
86
+ @up_action = block
87
+ end
88
+
89
+ # define the actions that should be performed on a down migration
90
+ def down(&block)
91
+ @down_action = block
92
+ end
93
+
94
+ # perform the migration by running the code in the #up block
95
+ def perform_up
96
+ result = nil
97
+
98
+ if needs_up?
99
+ # TODO: fix this so it only does transactions for databases that support create/drop
100
+ # database.transaction.commit do
101
+ if @up_action
102
+ say_with_time "== Performing Up Migration ##{position}: #{name}", 0 do
103
+ result = @up_action.call
104
+ end
105
+ end
106
+
107
+ update_migration_info(:up)
108
+ # end
109
+ end
110
+
111
+ result
112
+ end
113
+
114
+ # un-do the migration by running the code in the #down block
115
+ def perform_down
116
+ result = nil
117
+
118
+ if needs_down?
119
+ # TODO: fix this so it only does transactions for databases that support create/drop
120
+ # database.transaction.commit do
121
+ if @down_action
122
+ say_with_time "== Performing Down Migration ##{position}: #{name}", 0 do
123
+ result = @down_action.call
124
+ end
125
+ end
126
+
127
+ update_migration_info(:down)
128
+ # end
129
+ end
130
+
131
+ result
132
+ end
133
+
134
+ # execute raw SQL
135
+ def execute(sql, *bind_values)
136
+ say_with_time(sql) do
137
+ adapter.execute(sql, *bind_values)
138
+ end
139
+ end
140
+
141
+ def create_table(table_name, opts = {}, &block)
142
+ execute TableCreator.new(adapter, table_name, opts, &block).to_sql
143
+ end
144
+
145
+ def drop_table(table_name, opts = {})
146
+ execute "DROP TABLE #{adapter.send(:quote_name, table_name.to_s)}"
147
+ end
148
+
149
+ def modify_table(table_name, opts = {}, &block)
150
+ TableModifier.new(adapter, table_name, opts, &block).statements.each do |sql|
151
+ execute(sql)
152
+ end
153
+ end
154
+
155
+ def create_index(table_name, *columns_and_options)
156
+ if columns_and_options.last.is_a?(Hash)
157
+ opts = columns_and_options.pop
158
+ else
159
+ opts = {}
160
+ end
161
+ columns = columns_and_options.flatten
162
+
163
+ opts[:name] ||= "#{opts[:unique] ? 'unique_' : ''}index_#{table_name}_#{columns.join('_')}"
164
+
165
+ execute DataMapper::Ext::String.compress_lines(<<-SQL)
166
+ CREATE #{opts[:unique] ? 'UNIQUE ' : '' }INDEX #{quote_column_name(opts[:name])} ON
167
+ #{quote_table_name(table_name)} (#{columns.map { |c| quote_column_name(c) }.join(', ') })
168
+ SQL
169
+ end
170
+
171
+ # Orders migrations by position, so we know what order to run them in.
172
+ # First order by position, then by name, so at least the order is predictable.
173
+ def <=> other
174
+ if self.position == other.position
175
+ self.name.to_s <=> other.name.to_s
176
+ else
177
+ self.position <=> other.position
178
+ end
179
+ end
180
+
181
+ # Output some text. Optional indent level
182
+ def say(message, indent = 4)
183
+ write "#{" " * indent} #{message}"
184
+ end
185
+
186
+ # Time how long the block takes to run, and output it with the message.
187
+ def say_with_time(message, indent = 2)
188
+ say(message, indent)
189
+ result = nil
190
+ time = Benchmark.measure { result = yield }
191
+ say("-> %.4fs" % time.real, indent)
192
+ result
193
+ end
194
+
195
+ # output the given text, but only if verbose mode is on
196
+ def write(text="")
197
+ puts text if @verbose
198
+ end
199
+
200
+ # Inserts or removes a row into the `migration_info` table, so we can mark this migration as run, or un-done
201
+ def update_migration_info(direction)
202
+ save, @verbose = @verbose, false
203
+
204
+ create_migration_info_table_if_needed
205
+
206
+ if direction.to_sym == :up
207
+ execute("INSERT INTO #{migration_info_table} (#{migration_name_column}) VALUES (#{quoted_name})")
208
+ elsif direction.to_sym == :down
209
+ execute("DELETE FROM #{migration_info_table} WHERE #{migration_name_column} = #{quoted_name}")
210
+ end
211
+ @verbose = save
212
+ end
213
+
214
+ def create_migration_info_table_if_needed
215
+ save, @verbose = @verbose, false
216
+ unless migration_info_table_exists?
217
+ execute("CREATE TABLE #{migration_info_table} (#{migration_name_column} VARCHAR(255) UNIQUE)")
218
+ end
219
+ @verbose = save
220
+ end
221
+
222
+ # Quote the name of the migration for use in SQL
223
+ def quoted_name
224
+ "'#{name}'"
225
+ end
226
+
227
+ def migration_info_table_exists?
228
+ adapter.storage_exists?('migration_info')
229
+ end
230
+
231
+ # Fetch the record for this migration out of the migration_info table
232
+ def migration_record
233
+ return [] unless migration_info_table_exists?
234
+ adapter.select("SELECT #{migration_name_column} FROM #{migration_info_table} WHERE #{migration_name_column} = #{quoted_name}")
235
+ end
236
+
237
+ # True if the migration needs to be run
238
+ def needs_up?
239
+ return true unless migration_info_table_exists?
240
+ migration_record.empty?
241
+ end
242
+
243
+ # True if the migration has already been run
244
+ def needs_down?
245
+ return false unless migration_info_table_exists?
246
+ ! migration_record.empty?
247
+ end
248
+
249
+ # Quoted table name, for the adapter
250
+ def migration_info_table
251
+ @migration_info_table ||= quote_table_name('migration_info')
252
+ end
253
+
254
+ # Quoted `migration_name` column, for the adapter
255
+ def migration_name_column
256
+ @migration_name_column ||= quote_column_name('migration_name')
257
+ end
258
+
259
+ def quote_table_name(table_name)
260
+ # TODO: Fix this for 1.9 - can't use this hack to access a private method
261
+ adapter.send(:quote_name, table_name.to_s)
262
+ end
263
+
264
+ def quote_column_name(column_name)
265
+ # TODO: Fix this for 1.9 - can't use this hack to access a private method
266
+ adapter.send(:quote_name, column_name.to_s)
267
+ end
268
+
269
+ protected
270
+
271
+ #
272
+ # Determines whether the migration has been setup.
273
+ #
274
+ # @return [Boolean]
275
+ # Specifies whether the migration has been setup.
276
+ #
277
+ # @since 1.0.1
278
+ #
279
+ def setup?
280
+ !(@adapter.nil?)
281
+ end
282
+
283
+ #
284
+ # Sets up the migration.
285
+ #
286
+ # @since 1.0.1
287
+ #
288
+ def setup!
289
+ @adapter = DataMapper.repository(@repository).adapter
290
+
291
+ case @adapter.class.name
292
+ when /Sqlite/ then @adapter.extend(SQL::Sqlite)
293
+ when /Mysql/ then @adapter.extend(SQL::Mysql)
294
+ when /Postgres/ then @adapter.extend(SQL::Postgres)
295
+ else
296
+ raise(RuntimeError,"Unsupported Migration Adapter #{@adapter.class}",caller)
297
+ end
298
+ end
299
+ end
300
+ end
@@ -0,0 +1,85 @@
1
+ require 'dm-migrations/migration'
2
+
3
+ module DataMapper
4
+ module MigrationRunner
5
+ # Creates a new migration, and adds it to the list of migrations to be run.
6
+ # Migrations can be defined in any order, they will be sorted and run in the
7
+ # correct order.
8
+ #
9
+ # The order that migrations are run in is set by the first argument. It is not
10
+ # neccessary that this be unique; migrations with the same version number are
11
+ # expected to be able to be run in any order.
12
+ #
13
+ # The second argument is the name of the migration. This name is used internally
14
+ # to track if the migration has been run. It is required that this name be unique
15
+ # across all migrations.
16
+ #
17
+ # Addtionally, it accepts a number of options:
18
+ # * <tt>:database</tt> If you defined several DataMapper::database instances use this
19
+ # to choose which one to run the migration gagainst. Defaults to <tt>:default</tt>.
20
+ # Migrations are tracked individually per database.
21
+ # * <tt>:verbose</tt> true/false, defaults to true. Determines if the migration should
22
+ # output its status messages when it runs.
23
+ #
24
+ # Example of a simple migration:
25
+ #
26
+ # migration( 1, :create_people_table ) do
27
+ # up do
28
+ # create_table :people do
29
+ # column :id, Integer, :serial => true
30
+ # column :name, String, :size => 50
31
+ # column :age, Integer
32
+ # end
33
+ # end
34
+ # down do
35
+ # drop_table :people
36
+ # end
37
+ # end
38
+ #
39
+ # Its recommended that you stick with raw SQL for migrations that manipulate data. If
40
+ # you write a migration using a model, then later change the model, there's a
41
+ # possibility the migration will no longer work. Using SQL will always work.
42
+ def migration( number, name, opts = {}, &block )
43
+ raise "Migration name conflict: '#{name}'" if migrations.map { |m| m.name }.include?(name.to_s)
44
+
45
+ migrations << DataMapper::Migration.new( number, name.to_s, opts, &block )
46
+ end
47
+
48
+ # Run all migrations that need to be run. In most cases, this would be called by a
49
+ # rake task as part of a larger project, but this provides the ability to run them
50
+ # in a script or test.
51
+ #
52
+ # has an optional argument 'level' which if supplied, only performs the migrations
53
+ # with a position less than or equal to the level.
54
+ def migrate_up!(level = nil)
55
+ migrations.sort.each do |migration|
56
+ if level.nil?
57
+ migration.perform_up()
58
+ else
59
+ migration.perform_up() if migration.position <= level.to_i
60
+ end
61
+ end
62
+ end
63
+
64
+ # Run all the down steps for the migrations that have already been run.
65
+ #
66
+ # has an optional argument 'level' which, if supplied, only performs the
67
+ # down migrations with a postion greater than the level.
68
+ def migrate_down!(level = nil)
69
+ migrations.sort.reverse.each do |migration|
70
+ if level.nil?
71
+ migration.perform_down()
72
+ else
73
+ migration.perform_down() if migration.position > level.to_i
74
+ end
75
+ end
76
+ end
77
+
78
+ def migrations
79
+ @@migrations ||= []
80
+ end
81
+
82
+ end
83
+ end
84
+
85
+ include DataMapper::MigrationRunner
@@ -0,0 +1,5 @@
1
+ module SQL
2
+ class Column
3
+ attr_accessor :name, :type, :not_null, :default_value, :primary_key, :unique
4
+ end
5
+ end
@@ -0,0 +1,61 @@
1
+ require 'dm-migrations/sql/table'
2
+
3
+ module SQL
4
+ module Mysql
5
+
6
+ def supports_schema_transactions?
7
+ false
8
+ end
9
+
10
+ def table(table_name)
11
+ SQL::Mysql::Table.new(self, table_name)
12
+ end
13
+
14
+ def recreate_database
15
+ execute "DROP DATABASE #{schema_name}"
16
+ execute "CREATE DATABASE #{schema_name}"
17
+ execute "USE #{schema_name}"
18
+ end
19
+
20
+ def supports_serial?
21
+ true
22
+ end
23
+
24
+ def table_options(opts)
25
+ opt_engine = opts[:storage_engine] || storage_engine
26
+ opt_char_set = opts[:character_set] || character_set
27
+ opt_collation = opts[:collation] || collation
28
+
29
+ " ENGINE = #{opt_engine} CHARACTER SET #{opt_char_set} COLLATE #{opt_collation}"
30
+ end
31
+
32
+ def property_schema_statement(connection, schema)
33
+ if supports_serial? && schema[:serial]
34
+ statement = "#{schema[:quote_column_name]} SERIAL PRIMARY KEY"
35
+ else
36
+ super
37
+ end
38
+ end
39
+
40
+ def change_column_type_statement(name, column)
41
+ "ALTER TABLE #{quote_name(name)} MODIFY COLUMN #{column.to_sql}"
42
+ end
43
+
44
+ class Table
45
+ def initialize(adapter, table_name)
46
+ @columns = []
47
+ adapter.table_info(table_name).each do |col_struct|
48
+ @columns << SQL::Mysql::Column.new(col_struct)
49
+ end
50
+ end
51
+ end
52
+
53
+ class Column
54
+ def initialize(col_struct)
55
+ @name, @type, @default_value, @primary_key = col_struct.name, col_struct.type, col_struct.dflt_value, col_struct.pk
56
+
57
+ @not_null = col_struct.notnull == 0
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,82 @@
1
+ module SQL
2
+ module Postgres
3
+
4
+ def supports_schema_transactions?
5
+ true
6
+ end
7
+
8
+ def table(table_name)
9
+ SQL::Postgres::Table.new(self, table_name)
10
+ end
11
+
12
+ def recreate_database
13
+ execute 'DROP SCHEMA IF EXISTS test CASCADE'
14
+ execute 'CREATE SCHEMA test'
15
+ execute 'SET search_path TO test'
16
+ end
17
+
18
+ def supports_serial?
19
+ true
20
+ end
21
+
22
+ def property_schema_statement(connection, schema)
23
+ if supports_serial? && schema[:serial]
24
+ statement = "#{schema[:quote_column_name]} SERIAL PRIMARY KEY"
25
+ else
26
+ statement = super
27
+ if schema.has_key?(:sequence_name)
28
+ statement << " DEFAULT nextval('#{schema[:sequence_name]}') NOT NULL"
29
+ end
30
+ statement
31
+ end
32
+ statement
33
+ end
34
+
35
+ def table_options(opts)
36
+ ''
37
+ end
38
+
39
+ def change_column_type_statement(name, column)
40
+ "ALTER TABLE #{quote_name(name)} ALTER COLUMN #{column.to_sql}"
41
+ end
42
+
43
+ class Table < SQL::Table
44
+ def initialize(adapter, table_name)
45
+ @adapter, @name = adapter, table_name
46
+ @columns = []
47
+ adapter.query_table(table_name).each do |col_struct|
48
+ @columns << SQL::Postgres::Column.new(col_struct)
49
+ end
50
+
51
+ query_column_constraints
52
+ end
53
+
54
+ def query_column_constraints
55
+ @adapter.select(
56
+ "SELECT * FROM information_schema.table_constraints WHERE table_name='#{@name}' AND table_schema=current_schema()"
57
+ ).each do |table_constraint|
58
+ @adapter.select(
59
+ "SELECT * FROM information_schema.constraint_column_usage WHERE constraint_name='#{table_constraint.constraint_name}' AND table_schema=current_schema()"
60
+ ).each do |constrained_column|
61
+ @columns.each do |column|
62
+ if column.name == constrained_column.column_name
63
+ case table_constraint.constraint_type
64
+ when "UNIQUE" then column.unique = true
65
+ when "PRIMARY KEY" then column.primary_key = true
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ class Column < SQL::Column
75
+ def initialize(col_struct)
76
+ @name, @type, @default_value = col_struct.column_name, col_struct.data_type, col_struct.column_default
77
+
78
+ @not_null = col_struct.is_nullable != "YES"
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,51 @@
1
+ require 'dm-migrations/sql/table'
2
+
3
+ require 'fileutils'
4
+
5
+ module SQL
6
+ module Sqlite
7
+
8
+ def supports_schema_transactions?
9
+ true
10
+ end
11
+
12
+ def table(table_name)
13
+ SQL::Sqlite::Table.new(self, table_name)
14
+ end
15
+
16
+ def recreate_database
17
+ DataMapper.logger.info "Dropping #{@uri.path}"
18
+ FileUtils.rm_f(@uri.path)
19
+ # do nothing, sqlite will automatically create the database file
20
+ end
21
+
22
+ def table_options(opts)
23
+ ''
24
+ end
25
+
26
+ def supports_serial?
27
+ true
28
+ end
29
+
30
+ def change_column_type_statement(*args)
31
+ raise NotImplementedError
32
+ end
33
+
34
+ class Table < SQL::Table
35
+ def initialize(adapter, table_name)
36
+ @columns = []
37
+ adapter.table_info(table_name).each do |col_struct|
38
+ @columns << SQL::Sqlite::Column.new(col_struct)
39
+ end
40
+ end
41
+ end
42
+
43
+ class Column < SQL::Column
44
+ def initialize(col_struct)
45
+ @name, @type, @default_value, @primary_key = col_struct.name, col_struct.type, col_struct.dflt_value, col_struct.pk
46
+
47
+ @not_null = col_struct.notnull == 0
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,15 @@
1
+ require 'dm-migrations/sql/column'
2
+
3
+ module SQL
4
+ class Table
5
+ attr_accessor :name, :columns
6
+
7
+ def to_s
8
+ name
9
+ end
10
+
11
+ def column(column_name)
12
+ @columns.select { |c| c.name == column_name.to_s }.first
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,109 @@
1
+ require 'dm-core'
2
+
3
+ module SQL
4
+ class TableCreator
5
+
6
+ extend DataMapper::Property::Lookup
7
+
8
+ attr_accessor :table_name, :opts
9
+
10
+ def initialize(adapter, table_name, opts = {}, &block)
11
+ @adapter = adapter
12
+ @table_name = table_name.to_s
13
+ @opts = opts
14
+
15
+ @columns = []
16
+
17
+ self.instance_eval &block
18
+ end
19
+
20
+ def quoted_table_name
21
+ @adapter.send(:quote_name, table_name)
22
+ end
23
+
24
+ def column(name, type, opts = {})
25
+ @columns << Column.new(@adapter, name, type, opts)
26
+ end
27
+
28
+ def to_sql
29
+ "CREATE TABLE #{quoted_table_name} (#{@columns.map{ |c| c.to_sql }.join(', ')})#{@adapter.table_options(@opts)}"
30
+ end
31
+
32
+ # A helper for using the native NOW() SQL function in a default
33
+ def now
34
+ SqlExpr.new('NOW()')
35
+ end
36
+
37
+ # A helper for using the native UUID() SQL function in a default
38
+ def uuid
39
+ SqlExpr.new('UUID()')
40
+ end
41
+
42
+ class SqlExpr
43
+ attr_accessor :sql
44
+ def initialize(sql)
45
+ @sql = sql
46
+ end
47
+
48
+ def to_s
49
+ @sql.to_s
50
+ end
51
+ end
52
+
53
+ class Column
54
+ attr_accessor :name, :type
55
+
56
+ def initialize(adapter, name, type, opts = {})
57
+ @adapter = adapter
58
+ @name = name.to_s
59
+ @opts = opts
60
+ @type = build_type(type)
61
+ end
62
+
63
+ def to_sql
64
+ type
65
+ end
66
+
67
+ private
68
+
69
+ def build_type(type_class)
70
+ schema = { :name => @name, :quote_column_name => quoted_name }
71
+
72
+ [ :nullable, :nullable? ].each do |option|
73
+ next if (value = schema.delete(option)).nil?
74
+ warn "#{option.inspect} is deprecated, use :allow_nil instead"
75
+ schema[:allow_nil] = value unless schema.key?(:allow_nil)
76
+ end
77
+
78
+ unless schema.key?(:allow_nil)
79
+ schema[:allow_nil] = !schema[:not_null]
80
+ end
81
+
82
+ if type_class.kind_of?(String)
83
+ schema[:primitive] = type_class
84
+ else
85
+ type_map = @adapter.class.type_map
86
+ primitive = type_class.respond_to?(:primitive) ? type_class.primitive : type_class
87
+ options = (type_map[type_class] || type_map[primitive])
88
+
89
+ schema.update(type_class.options) if type_class.respond_to?(:options)
90
+ schema.update(options)
91
+
92
+ schema.delete(:length) if type_class == DataMapper::Property::Text
93
+ end
94
+
95
+ schema.update(@opts)
96
+
97
+ schema[:length] = schema.delete(:size) if schema.key?(:size)
98
+
99
+ @adapter.send(:with_connection) do |connection|
100
+ @adapter.property_schema_statement(connection, schema)
101
+ end
102
+ end
103
+
104
+ def quoted_name
105
+ @adapter.send(:quote_name, name)
106
+ end
107
+ end
108
+ end
109
+ end