ardm-migrations 1.2.0

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 (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