ardm-migrations 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +35 -0
- data/.travis.yml +11 -0
- data/Gemfile +53 -0
- data/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/Rakefile +4 -0
- data/ardm-migrations.gemspec +27 -0
- data/db/migrations/1_create_people_table.rb +12 -0
- data/db/migrations/2_add_dob_to_people.rb +13 -0
- data/db/migrations/config.rb +4 -0
- data/examples/Rakefile +144 -0
- data/examples/sample_migration.rb +58 -0
- data/examples/sample_migration_spec.rb +50 -0
- data/lib/ardm-migrations.rb +1 -0
- data/lib/dm-migrations/adapters/dm-do-adapter.rb +295 -0
- data/lib/dm-migrations/adapters/dm-mysql-adapter.rb +299 -0
- data/lib/dm-migrations/adapters/dm-oracle-adapter.rb +332 -0
- data/lib/dm-migrations/adapters/dm-postgres-adapter.rb +159 -0
- data/lib/dm-migrations/adapters/dm-sqlite-adapter.rb +96 -0
- data/lib/dm-migrations/adapters/dm-sqlserver-adapter.rb +177 -0
- data/lib/dm-migrations/adapters/dm-yaml-adapter.rb +23 -0
- data/lib/dm-migrations/auto_migration.rb +239 -0
- data/lib/dm-migrations/exceptions/duplicate_migration.rb +6 -0
- data/lib/dm-migrations/migration.rb +300 -0
- data/lib/dm-migrations/migration_runner.rb +85 -0
- data/lib/dm-migrations/sql/column.rb +5 -0
- data/lib/dm-migrations/sql/mysql.rb +61 -0
- data/lib/dm-migrations/sql/postgres.rb +82 -0
- data/lib/dm-migrations/sql/sqlite.rb +51 -0
- data/lib/dm-migrations/sql/table.rb +15 -0
- data/lib/dm-migrations/sql/table_creator.rb +109 -0
- data/lib/dm-migrations/sql/table_modifier.rb +57 -0
- data/lib/dm-migrations/sql.rb +5 -0
- data/lib/dm-migrations/version.rb +5 -0
- data/lib/dm-migrations.rb +3 -0
- data/lib/spec/example/migration_example_group.rb +73 -0
- data/lib/spec/matchers/migration_matchers.rb +106 -0
- data/spec/integration/auto_migration_spec.rb +553 -0
- data/spec/integration/auto_upgrade_spec.rb +40 -0
- data/spec/integration/migration_runner_spec.rb +89 -0
- data/spec/integration/migration_spec.rb +157 -0
- data/spec/integration/sql_spec.rb +250 -0
- data/spec/isolated/require_after_setup_spec.rb +30 -0
- data/spec/isolated/require_before_setup_spec.rb +30 -0
- data/spec/isolated/require_spec.rb +25 -0
- data/spec/rcov.opts +6 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/unit/migration_spec.rb +453 -0
- data/spec/unit/sql/column_spec.rb +14 -0
- data/spec/unit/sql/postgres_spec.rb +97 -0
- data/spec/unit/sql/sqlite_extensions_spec.rb +108 -0
- data/spec/unit/sql/table_creator_spec.rb +94 -0
- data/spec/unit/sql/table_modifier_spec.rb +49 -0
- data/spec/unit/sql/table_spec.rb +28 -0
- data/spec/unit/sql_spec.rb +7 -0
- data/tasks/spec.rake +38 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- 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,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,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
|