sequel 3.11.0 → 3.12.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.
- data/CHANGELOG +70 -0
- data/Rakefile +1 -1
- data/doc/active_record.rdoc +896 -0
- data/doc/advanced_associations.rdoc +46 -31
- data/doc/association_basics.rdoc +14 -9
- data/doc/dataset_basics.rdoc +3 -3
- data/doc/migration.rdoc +1011 -0
- data/doc/model_hooks.rdoc +198 -0
- data/doc/querying.rdoc +811 -86
- data/doc/release_notes/3.12.0.txt +304 -0
- data/doc/sharding.rdoc +17 -0
- data/doc/sql.rdoc +537 -0
- data/doc/validations.rdoc +501 -0
- data/lib/sequel/adapters/jdbc.rb +19 -27
- data/lib/sequel/adapters/jdbc/postgresql.rb +0 -7
- data/lib/sequel/adapters/mysql.rb +5 -4
- data/lib/sequel/adapters/odbc.rb +3 -2
- data/lib/sequel/adapters/shared/mssql.rb +7 -6
- data/lib/sequel/adapters/shared/mysql.rb +2 -7
- data/lib/sequel/adapters/shared/postgres.rb +2 -8
- data/lib/sequel/adapters/shared/sqlite.rb +2 -5
- data/lib/sequel/adapters/sqlite.rb +4 -4
- data/lib/sequel/core.rb +0 -1
- data/lib/sequel/database.rb +2 -1060
- data/lib/sequel/database/connecting.rb +227 -0
- data/lib/sequel/database/dataset.rb +58 -0
- data/lib/sequel/database/dataset_defaults.rb +127 -0
- data/lib/sequel/database/logging.rb +62 -0
- data/lib/sequel/database/misc.rb +246 -0
- data/lib/sequel/database/query.rb +390 -0
- data/lib/sequel/database/schema_generator.rb +7 -3
- data/lib/sequel/database/schema_methods.rb +351 -7
- data/lib/sequel/dataset/actions.rb +9 -2
- data/lib/sequel/dataset/misc.rb +6 -2
- data/lib/sequel/dataset/mutation.rb +3 -11
- data/lib/sequel/dataset/query.rb +49 -6
- data/lib/sequel/exceptions.rb +3 -0
- data/lib/sequel/extensions/migration.rb +395 -113
- data/lib/sequel/extensions/schema_dumper.rb +21 -13
- data/lib/sequel/model.rb +27 -25
- data/lib/sequel/model/associations.rb +72 -34
- data/lib/sequel/model/base.rb +74 -18
- data/lib/sequel/model/errors.rb +8 -1
- data/lib/sequel/plugins/active_model.rb +8 -0
- data/lib/sequel/plugins/association_pks.rb +87 -0
- data/lib/sequel/plugins/association_proxies.rb +8 -0
- data/lib/sequel/plugins/boolean_readers.rb +12 -6
- data/lib/sequel/plugins/caching.rb +14 -7
- data/lib/sequel/plugins/class_table_inheritance.rb +15 -9
- data/lib/sequel/plugins/composition.rb +2 -1
- data/lib/sequel/plugins/force_encoding.rb +10 -7
- data/lib/sequel/plugins/hook_class_methods.rb +12 -11
- data/lib/sequel/plugins/identity_map.rb +9 -0
- data/lib/sequel/plugins/instance_hooks.rb +23 -13
- data/lib/sequel/plugins/lazy_attributes.rb +4 -1
- data/lib/sequel/plugins/many_through_many.rb +18 -4
- data/lib/sequel/plugins/nested_attributes.rb +1 -0
- data/lib/sequel/plugins/optimistic_locking.rb +1 -1
- data/lib/sequel/plugins/rcte_tree.rb +9 -8
- data/lib/sequel/plugins/schema.rb +8 -0
- data/lib/sequel/plugins/serialization.rb +1 -3
- data/lib/sequel/plugins/sharding.rb +135 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +117 -25
- data/lib/sequel/plugins/skip_create_refresh.rb +35 -0
- data/lib/sequel/plugins/string_stripper.rb +26 -0
- data/lib/sequel/plugins/tactical_eager_loading.rb +8 -0
- data/lib/sequel/plugins/timestamps.rb +15 -2
- data/lib/sequel/plugins/touch.rb +13 -0
- data/lib/sequel/plugins/update_primary_key.rb +48 -0
- data/lib/sequel/plugins/validation_class_methods.rb +8 -0
- data/lib/sequel/plugins/validation_helpers.rb +1 -1
- data/lib/sequel/sql.rb +17 -20
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +5 -5
- data/spec/core/core_sql_spec.rb +17 -1
- data/spec/core/database_spec.rb +17 -5
- data/spec/core/dataset_spec.rb +31 -8
- data/spec/core/schema_generator_spec.rb +8 -1
- data/spec/core/schema_spec.rb +13 -0
- data/spec/extensions/association_pks_spec.rb +85 -0
- data/spec/extensions/hook_class_methods_spec.rb +9 -9
- data/spec/extensions/migration_spec.rb +339 -219
- data/spec/extensions/schema_dumper_spec.rb +28 -17
- data/spec/extensions/sharding_spec.rb +272 -0
- data/spec/extensions/single_table_inheritance_spec.rb +92 -4
- data/spec/extensions/skip_create_refresh_spec.rb +17 -0
- data/spec/extensions/string_stripper_spec.rb +23 -0
- data/spec/extensions/update_primary_key_spec.rb +65 -0
- data/spec/extensions/validation_class_methods_spec.rb +5 -5
- data/spec/files/bad_down_migration/001_create_alt_basic.rb +4 -0
- data/spec/files/bad_down_migration/002_create_alt_advanced.rb +4 -0
- data/spec/files/bad_timestamped_migrations/1273253849_create_sessions.rb +9 -0
- data/spec/files/bad_timestamped_migrations/1273253851_create_nodes.rb +9 -0
- data/spec/files/bad_timestamped_migrations/1273253853_3_create_users.rb +3 -0
- data/spec/files/bad_up_migration/001_create_alt_basic.rb +4 -0
- data/spec/files/bad_up_migration/002_create_alt_advanced.rb +3 -0
- data/spec/files/convert_to_timestamp_migrations/001_create_sessions.rb +9 -0
- data/spec/files/convert_to_timestamp_migrations/002_create_nodes.rb +9 -0
- data/spec/files/convert_to_timestamp_migrations/003_3_create_users.rb +4 -0
- data/spec/files/convert_to_timestamp_migrations/1273253850_create_artists.rb +9 -0
- data/spec/files/convert_to_timestamp_migrations/1273253852_create_albums.rb +9 -0
- data/spec/files/duplicate_integer_migrations/001_create_alt_advanced.rb +4 -0
- data/spec/files/duplicate_integer_migrations/001_create_alt_basic.rb +4 -0
- data/spec/files/duplicate_timestamped_migrations/1273253849_create_sessions.rb +9 -0
- data/spec/files/duplicate_timestamped_migrations/1273253853_create_nodes.rb +9 -0
- data/spec/files/duplicate_timestamped_migrations/1273253853_create_users.rb +4 -0
- data/spec/files/integer_migrations/001_create_sessions.rb +9 -0
- data/spec/files/integer_migrations/002_create_nodes.rb +9 -0
- data/spec/files/integer_migrations/003_3_create_users.rb +4 -0
- data/spec/files/interleaved_timestamped_migrations/1273253849_create_sessions.rb +9 -0
- data/spec/files/interleaved_timestamped_migrations/1273253850_create_artists.rb +9 -0
- data/spec/files/interleaved_timestamped_migrations/1273253851_create_nodes.rb +9 -0
- data/spec/files/interleaved_timestamped_migrations/1273253852_create_albums.rb +9 -0
- data/spec/files/interleaved_timestamped_migrations/1273253853_3_create_users.rb +4 -0
- data/spec/files/missing_integer_migrations/001_create_alt_basic.rb +4 -0
- data/spec/files/missing_integer_migrations/003_create_alt_advanced.rb +4 -0
- data/spec/files/missing_timestamped_migrations/1273253849_create_sessions.rb +9 -0
- data/spec/files/missing_timestamped_migrations/1273253853_3_create_users.rb +4 -0
- data/spec/files/timestamped_migrations/1273253849_create_sessions.rb +9 -0
- data/spec/files/timestamped_migrations/1273253851_create_nodes.rb +9 -0
- data/spec/files/timestamped_migrations/1273253853_3_create_users.rb +4 -0
- data/spec/files/uppercase_timestamped_migrations/1273253849_CREATE_SESSIONS.RB +9 -0
- data/spec/files/uppercase_timestamped_migrations/1273253851_CREATE_NODES.RB +9 -0
- data/spec/files/uppercase_timestamped_migrations/1273253853_3_CREATE_USERS.RB +4 -0
- data/spec/integration/eager_loader_test.rb +20 -20
- data/spec/integration/migrator_test.rb +187 -0
- data/spec/integration/plugin_test.rb +150 -0
- data/spec/integration/schema_test.rb +13 -2
- data/spec/model/associations_spec.rb +41 -14
- data/spec/model/base_spec.rb +69 -0
- data/spec/model/eager_loading_spec.rb +7 -3
- data/spec/model/record_spec.rb +79 -4
- data/spec/model/validations_spec.rb +21 -9
- metadata +66 -5
- data/doc/schema.rdoc +0 -36
- data/lib/sequel/database/schema_sql.rb +0 -320
|
@@ -7,6 +7,12 @@ module Sequel
|
|
|
7
7
|
# they should be the last method called.
|
|
8
8
|
# ---------------------
|
|
9
9
|
|
|
10
|
+
# Action methods defined by Sequel that execute code on the database.
|
|
11
|
+
ACTION_METHODS = %w'<< [] []= all avg count columns columns! delete each
|
|
12
|
+
empty? fetch_rows first get import insert insert_multiple interval last
|
|
13
|
+
map max min multi_insert range select_hash select_map select_order_map
|
|
14
|
+
set single_record single_value sum to_csv to_hash truncate update'.map{|x| x.to_sym}
|
|
15
|
+
|
|
10
16
|
# Alias for insert, but not aliased directly so subclasses
|
|
11
17
|
# don't have to override both methods.
|
|
12
18
|
def <<(*args)
|
|
@@ -104,7 +110,7 @@ module Sequel
|
|
|
104
110
|
# Executes a select query and fetches records, passing each record to the
|
|
105
111
|
# supplied block. The yielded records should be hashes with symbol keys.
|
|
106
112
|
def fetch_rows(sql, &block)
|
|
107
|
-
raise
|
|
113
|
+
raise NotImplemented, NOTIMPL_MSG
|
|
108
114
|
end
|
|
109
115
|
|
|
110
116
|
# If a integer argument is
|
|
@@ -156,7 +162,8 @@ module Sequel
|
|
|
156
162
|
end
|
|
157
163
|
|
|
158
164
|
# Inserts multiple records into the associated table. This method can be
|
|
159
|
-
# to efficiently insert a large
|
|
165
|
+
# used to efficiently insert a large number of records into a table in a
|
|
166
|
+
# single query if the database supports it. Inserts
|
|
160
167
|
# are automatically wrapped in a transaction.
|
|
161
168
|
#
|
|
162
169
|
# This method is called with a columns array and an array of value arrays:
|
data/lib/sequel/dataset/misc.rb
CHANGED
|
@@ -49,6 +49,11 @@ module Sequel
|
|
|
49
49
|
db.servers.each{|s| yield server(s)}
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
+
# Alias of first_source_alias
|
|
53
|
+
def first_source
|
|
54
|
+
first_source_alias
|
|
55
|
+
end
|
|
56
|
+
|
|
52
57
|
# The first source (primary table) for this dataset. If the dataset doesn't
|
|
53
58
|
# have a table, raises an error. If the table is aliased, returns the aliased name.
|
|
54
59
|
def first_source_alias
|
|
@@ -66,7 +71,6 @@ module Sequel
|
|
|
66
71
|
s
|
|
67
72
|
end
|
|
68
73
|
end
|
|
69
|
-
alias first_source first_source_alias
|
|
70
74
|
|
|
71
75
|
# The first source (primary table) for this dataset. If the dataset doesn't
|
|
72
76
|
# have a table, raises an error. If the table is aliased, returns the original
|
|
@@ -116,4 +120,4 @@ module Sequel
|
|
|
116
120
|
end
|
|
117
121
|
end
|
|
118
122
|
end
|
|
119
|
-
end
|
|
123
|
+
end
|
|
@@ -5,16 +5,8 @@ module Sequel
|
|
|
5
5
|
# These methods modify the receiving dataset and should be used with care.
|
|
6
6
|
# ---------------------
|
|
7
7
|
|
|
8
|
-
# All methods that should have a ! method added that modifies
|
|
9
|
-
|
|
10
|
-
MUTATION_METHODS = %w'add_graph_aliases and cross_join distinct except exclude
|
|
11
|
-
filter for_update from from_self full_join full_outer_join graph
|
|
12
|
-
group group_and_count group_by having inner_join intersect invert join join_table left_join
|
|
13
|
-
left_outer_join limit lock_style naked natural_full_join natural_join
|
|
14
|
-
natural_left_join natural_right_join or order order_by order_more paginate qualify query
|
|
15
|
-
reverse reverse_order right_join right_outer_join select select_all select_append select_more server
|
|
16
|
-
set_defaults set_graph_aliases set_overrides unfiltered ungraphed ungrouped union
|
|
17
|
-
unlimited unordered where with with_recursive with_sql'.collect{|x| x.to_sym}
|
|
8
|
+
# All methods that should have a ! method added that modifies the receiver.
|
|
9
|
+
MUTATION_METHODS = QUERY_METHODS
|
|
18
10
|
|
|
19
11
|
# Setup mutation (e.g. filter!) methods. These operate the same as the
|
|
20
12
|
# non-! methods, but replace the options of the current dataset with the
|
|
@@ -61,4 +53,4 @@ module Sequel
|
|
|
61
53
|
self
|
|
62
54
|
end
|
|
63
55
|
end
|
|
64
|
-
end
|
|
56
|
+
end
|
data/lib/sequel/dataset/query.rb
CHANGED
|
@@ -4,6 +4,7 @@ module Sequel
|
|
|
4
4
|
# :section: Methods that return modified datasets
|
|
5
5
|
# These methods all return modified copies of the receiver.
|
|
6
6
|
# ---------------------
|
|
7
|
+
|
|
7
8
|
# The dataset options that require the removal of cached columns
|
|
8
9
|
# if changed.
|
|
9
10
|
COLUMN_CHANGE_OPTS = [:select, :sql, :from, :join].freeze
|
|
@@ -23,6 +24,17 @@ module Sequel
|
|
|
23
24
|
# if called with a block.
|
|
24
25
|
UNCONDITIONED_JOIN_TYPES = [:natural, :natural_left, :natural_right, :natural_full, :cross]
|
|
25
26
|
|
|
27
|
+
# All methods that return modified datasets with a joined table added.
|
|
28
|
+
JOIN_METHODS = (CONDITIONED_JOIN_TYPES + UNCONDITIONED_JOIN_TYPES).map{|x| "#{x}_join".to_sym} + [:join, :join_table]
|
|
29
|
+
|
|
30
|
+
# Methods that return modified datasets
|
|
31
|
+
QUERY_METHODS = %w'add_graph_aliases and distinct except exclude
|
|
32
|
+
filter for_update from from_self graph grep group group_and_count group_by having intersect invert
|
|
33
|
+
limit lock_style naked or order order_append order_by order_more order_prepend paginate qualify query
|
|
34
|
+
reverse reverse_order select select_all select_append select_more server
|
|
35
|
+
set_defaults set_graph_aliases set_overrides unfiltered ungraphed ungrouped union
|
|
36
|
+
unlimited unordered where with with_recursive with_sql'.collect{|x| x.to_sym} + JOIN_METHODS
|
|
37
|
+
|
|
26
38
|
# Adds an further filter to an existing filter using AND. If no filter
|
|
27
39
|
# exists an error is raised. This method is identical to #filter except
|
|
28
40
|
# it expects an existing filter.
|
|
@@ -208,7 +220,11 @@ module Sequel
|
|
|
208
220
|
def group(*columns)
|
|
209
221
|
clone(:group => (columns.compact.empty? ? nil : columns))
|
|
210
222
|
end
|
|
211
|
-
|
|
223
|
+
|
|
224
|
+
# Alias of group
|
|
225
|
+
def group_by(*columns)
|
|
226
|
+
group(*columns)
|
|
227
|
+
end
|
|
212
228
|
|
|
213
229
|
# Returns a dataset grouped by the given column with count by group,
|
|
214
230
|
# order by the count of records. Column aliases may be supplied, and will
|
|
@@ -260,6 +276,11 @@ module Sequel
|
|
|
260
276
|
clone(o)
|
|
261
277
|
end
|
|
262
278
|
|
|
279
|
+
# Alias of inner_join
|
|
280
|
+
def join(*args, &block)
|
|
281
|
+
inner_join(*args, &block)
|
|
282
|
+
end
|
|
283
|
+
|
|
263
284
|
# Returns a joined dataset. Uses the following arguments:
|
|
264
285
|
#
|
|
265
286
|
# * type - The type of join to do (e.g. :inner)
|
|
@@ -352,7 +373,6 @@ module Sequel
|
|
|
352
373
|
UNCONDITIONED_JOIN_TYPES.each do |jtype|
|
|
353
374
|
class_eval("def #{jtype}_join(table); raise(Sequel::Error, '#{jtype}_join does not accept join table blocks') if block_given?; join_table(:#{jtype}, table) end", __FILE__, __LINE__)
|
|
354
375
|
end
|
|
355
|
-
alias join inner_join
|
|
356
376
|
|
|
357
377
|
# If given an integer, the dataset will contain only the first l results.
|
|
358
378
|
# If given a range, it will contain only those at offsets within that
|
|
@@ -426,10 +446,19 @@ module Sequel
|
|
|
426
446
|
columns += Array(Sequel.virtual_row(&block)) if block
|
|
427
447
|
clone(:order => (columns.compact.empty?) ? nil : columns)
|
|
428
448
|
end
|
|
429
|
-
alias order_by order
|
|
430
449
|
|
|
450
|
+
# Alias of order_more, for naming consistency with order_prepend.
|
|
451
|
+
def order_append(*columns, &block)
|
|
452
|
+
order_more(*columns, &block)
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
# Alias of order
|
|
456
|
+
def order_by(*columns, &block)
|
|
457
|
+
order(*columns, &block)
|
|
458
|
+
end
|
|
459
|
+
|
|
431
460
|
# Returns a copy of the dataset with the order columns added
|
|
432
|
-
# to the existing order.
|
|
461
|
+
# to the end of the existing order.
|
|
433
462
|
#
|
|
434
463
|
# ds.order(:a).order(:b).sql #=> 'SELECT * FROM items ORDER BY b'
|
|
435
464
|
# ds.order(:a).order_more(:b).sql #=> 'SELECT * FROM items ORDER BY a, b'
|
|
@@ -438,6 +467,16 @@ module Sequel
|
|
|
438
467
|
order(*columns, &block)
|
|
439
468
|
end
|
|
440
469
|
|
|
470
|
+
# Returns a copy of the dataset with the order columns added
|
|
471
|
+
# to the beginning of the existing order.
|
|
472
|
+
#
|
|
473
|
+
# ds.order(:a).order(:b).sql #=> 'SELECT * FROM items ORDER BY b'
|
|
474
|
+
# ds.order(:a).order_prepend(:b).sql #=> 'SELECT * FROM items ORDER BY b, a'
|
|
475
|
+
def order_prepend(*columns, &block)
|
|
476
|
+
ds = order(*columns, &block)
|
|
477
|
+
@opts[:order] ? ds.order_more(*@opts[:order]) : ds
|
|
478
|
+
end
|
|
479
|
+
|
|
441
480
|
# Qualify to the given table, or first source if not table is given.
|
|
442
481
|
def qualify(table=first_source)
|
|
443
482
|
qualify_to(table)
|
|
@@ -469,10 +508,14 @@ module Sequel
|
|
|
469
508
|
|
|
470
509
|
# Returns a copy of the dataset with the order reversed. If no order is
|
|
471
510
|
# given, the existing order is inverted.
|
|
472
|
-
def
|
|
511
|
+
def reverse(*order)
|
|
473
512
|
order(*invert_order(order.empty? ? @opts[:order] : order))
|
|
474
513
|
end
|
|
475
|
-
|
|
514
|
+
|
|
515
|
+
# Alias of reverse
|
|
516
|
+
def reverse_order(*order)
|
|
517
|
+
reverse(*order)
|
|
518
|
+
end
|
|
476
519
|
|
|
477
520
|
# Returns a copy of the dataset with the columns selected changed
|
|
478
521
|
# to the given columns. This also takes a virtual row block,
|
data/lib/sequel/exceptions.rb
CHANGED
|
@@ -32,6 +32,9 @@ module Sequel
|
|
|
32
32
|
# Raised when attempting an invalid type conversion.
|
|
33
33
|
class InvalidValue < Error ; end
|
|
34
34
|
|
|
35
|
+
# Raised when the adapter adapter hasn't implemented a method such as +tables+:
|
|
36
|
+
class NotImplemented < Error; end
|
|
37
|
+
|
|
35
38
|
# Raised when the connection pool cannot acquire a database connection
|
|
36
39
|
# before the timeout.
|
|
37
40
|
class PoolTimeout < Error ; end
|
|
@@ -3,51 +3,23 @@
|
|
|
3
3
|
# to a newer version or revert to a previous version.
|
|
4
4
|
|
|
5
5
|
module Sequel
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# class CreateSessions < Sequel::Migration
|
|
10
|
-
# def up
|
|
11
|
-
# create_table :sessions do
|
|
12
|
-
# primary_key :id
|
|
13
|
-
# String :session_id, :size => 32, :unique => true
|
|
14
|
-
# DateTime :created_at
|
|
15
|
-
# text :data
|
|
16
|
-
# end
|
|
17
|
-
# end
|
|
6
|
+
# Sequel's older migration class, available for backward compatibility.
|
|
7
|
+
# Uses subclasses with up and down instance methods for each migration:
|
|
18
8
|
#
|
|
19
|
-
#
|
|
20
|
-
# # You can use raw SQL if you need to
|
|
21
|
-
# self << 'DROP TABLE sessions'
|
|
22
|
-
# end
|
|
23
|
-
# end
|
|
24
|
-
#
|
|
25
|
-
# class AlterItems < Sequel::Migration
|
|
9
|
+
# Class.new(Sequel::Migration) do
|
|
26
10
|
# def up
|
|
27
|
-
#
|
|
28
|
-
#
|
|
11
|
+
# create_table(:artists) do
|
|
12
|
+
# primary_key :id
|
|
13
|
+
# String :name
|
|
29
14
|
# end
|
|
30
15
|
# end
|
|
31
|
-
#
|
|
16
|
+
#
|
|
32
17
|
# def down
|
|
33
|
-
#
|
|
34
|
-
# drop_column :category
|
|
35
|
-
# end
|
|
18
|
+
# drop_table(:artists)
|
|
36
19
|
# end
|
|
37
20
|
# end
|
|
38
21
|
#
|
|
39
|
-
#
|
|
40
|
-
# the target database instance and the direction :up or :down, e.g.:
|
|
41
|
-
#
|
|
42
|
-
# DB = Sequel.connect('sqlite://mydb')
|
|
43
|
-
# CreateSessions.apply(DB, :up)
|
|
44
|
-
#
|
|
45
|
-
# See Sequel::Schema::Generator for the syntax to use for creating tables,
|
|
46
|
-
# and Sequel::Schema::AlterTableGenerator for the syntax to use when
|
|
47
|
-
# altering existing tables. Migrations act as a proxy for the database
|
|
48
|
-
# given in #apply, so inside #down and #up, you can act as though self
|
|
49
|
-
# refers to the database. So you can use any of the Sequel::Database
|
|
50
|
-
# instance methods directly.
|
|
22
|
+
# Part of the +migration+ extension.
|
|
51
23
|
class Migration
|
|
52
24
|
# Creates a new instance of the migration and sets the @db attribute.
|
|
53
25
|
def initialize(db)
|
|
@@ -57,15 +29,8 @@ module Sequel
|
|
|
57
29
|
# Applies the migration to the supplied database in the specified
|
|
58
30
|
# direction.
|
|
59
31
|
def self.apply(db, direction)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
when :up
|
|
63
|
-
obj.up
|
|
64
|
-
when :down
|
|
65
|
-
obj.down
|
|
66
|
-
else
|
|
67
|
-
raise ArgumentError, "Invalid migration direction specified (#{direction.inspect})"
|
|
68
|
-
end
|
|
32
|
+
raise(ArgumentError, "Invalid migration direction specified (#{direction.inspect})") unless [:up, :down].include?(direction)
|
|
33
|
+
new(db).send(direction)
|
|
69
34
|
end
|
|
70
35
|
|
|
71
36
|
# Returns the list of Migration descendants.
|
|
@@ -92,9 +57,77 @@ module Sequel
|
|
|
92
57
|
end
|
|
93
58
|
end
|
|
94
59
|
|
|
95
|
-
#
|
|
60
|
+
# Migration class used by the Sequel.migration DSL,
|
|
61
|
+
# using instances for each migration, unlike the
|
|
62
|
+
# +Migration+ class, which uses subclasses for each
|
|
63
|
+
# migration. Part of the +migration+ extension.
|
|
64
|
+
class SimpleMigration
|
|
65
|
+
# Proc used for the down action
|
|
66
|
+
attr_accessor :down
|
|
67
|
+
|
|
68
|
+
# Proc used for the up action
|
|
69
|
+
attr_accessor :up
|
|
70
|
+
|
|
71
|
+
# Apply the appropriate block on the +Database+
|
|
72
|
+
# instance using instance_eval.
|
|
73
|
+
def apply(db, direction)
|
|
74
|
+
raise(ArgumentError, "Invalid migration direction specified (#{direction.inspect})") unless [:up, :down].include?(direction)
|
|
75
|
+
if prok = send(direction)
|
|
76
|
+
db.instance_eval(&prok)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Internal class used by the Sequel.migration DSL, part of the +migration+ extension.
|
|
82
|
+
class MigrationDSL < BasicObject
|
|
83
|
+
# The underlying Migration class.
|
|
84
|
+
attr_reader :migration
|
|
85
|
+
|
|
86
|
+
def self.create(&block)
|
|
87
|
+
new(&block).migration
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Create a new migration class, and instance_eval the block.
|
|
91
|
+
def initialize(&block)
|
|
92
|
+
@migration = SimpleMigration.new
|
|
93
|
+
Migration.descendants << migration
|
|
94
|
+
instance_eval(&block)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Defines the migration's down action.
|
|
98
|
+
def down(&block)
|
|
99
|
+
migration.down = block
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Defines the migration's up action.
|
|
103
|
+
def up(&block)
|
|
104
|
+
migration.up = block
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# The preferred method for writing Sequel migrations, using a DSL:
|
|
109
|
+
#
|
|
110
|
+
# Sequel.migration do
|
|
111
|
+
# up do
|
|
112
|
+
# create_table(:artists) do
|
|
113
|
+
# primary_key :id
|
|
114
|
+
# String :name
|
|
115
|
+
# end
|
|
116
|
+
# end
|
|
117
|
+
#
|
|
118
|
+
# down do
|
|
119
|
+
# drop_table(:artists)
|
|
120
|
+
# end
|
|
121
|
+
# end
|
|
122
|
+
#
|
|
123
|
+
# Designed to be used with the +Migrator+ class, part of the +migration+ extension.
|
|
124
|
+
def self.migration(&block)
|
|
125
|
+
MigrationDSL.create(&block)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# The +Migrator+ class performs migrations based on migration files in a
|
|
96
129
|
# specified directory. The migration files should be named using the
|
|
97
|
-
# following pattern
|
|
130
|
+
# following pattern:
|
|
98
131
|
#
|
|
99
132
|
# <version>_<title>.rb
|
|
100
133
|
#
|
|
@@ -102,10 +135,21 @@ module Sequel
|
|
|
102
135
|
#
|
|
103
136
|
# 001_create_sessions.rb
|
|
104
137
|
# 002_add_data_column.rb
|
|
105
|
-
#
|
|
138
|
+
#
|
|
139
|
+
# You can also use timestamps as version numbers:
|
|
140
|
+
#
|
|
141
|
+
# 1273253850_create_sessions.rb
|
|
142
|
+
# 1273257248_add_data_column.rb
|
|
106
143
|
#
|
|
107
|
-
#
|
|
108
|
-
#
|
|
144
|
+
# If any migration filenames use timestamps as version numbers, Sequel
|
|
145
|
+
# uses the +TimestampMigrator+ to migrate, otherwise it uses the +IntegerMigrator+.
|
|
146
|
+
# The +TimestampMigrator+ can handle migrations that are run out of order
|
|
147
|
+
# as well as migrations with the same timestamp,
|
|
148
|
+
# while the +IntegerMigrator+ is more strict and raises exceptions for missing
|
|
149
|
+
# or duplicate migration files.
|
|
150
|
+
#
|
|
151
|
+
# The migration files should contain either one +Migration+
|
|
152
|
+
# subclass or one <tt>Sequel.migration</tt> call.
|
|
109
153
|
#
|
|
110
154
|
# Migrations are generally run via the sequel command line tool,
|
|
111
155
|
# using the -m and -M switches. The -m switch specifies the migration
|
|
@@ -114,10 +158,11 @@ module Sequel
|
|
|
114
158
|
# You can apply migrations using the Migrator API, as well (this is necessary
|
|
115
159
|
# if you want to specify the version from which to migrate in addition to the version
|
|
116
160
|
# to which to migrate).
|
|
117
|
-
# To apply a
|
|
161
|
+
# To apply a migrator, the +apply+ method must be invoked with the database
|
|
118
162
|
# instance, the directory of migration files and the target version. If
|
|
119
163
|
# no current version is supplied, it is read from the database. The migrator
|
|
120
|
-
# automatically creates a
|
|
164
|
+
# automatically creates a table (schema_info for integer migrations and
|
|
165
|
+
# schema_migrations for timestamped migrations). in the database to keep track
|
|
121
166
|
# of the current migration version. If no migration version is stored in the
|
|
122
167
|
# database, the version is considered to be 0. If no target version is
|
|
123
168
|
# specified, the database is migrated to the latest version available in the
|
|
@@ -127,26 +172,40 @@ module Sequel
|
|
|
127
172
|
#
|
|
128
173
|
# Sequel::Migrator.apply(DB, '.')
|
|
129
174
|
#
|
|
175
|
+
# For example, to migrate the database all the way down:
|
|
176
|
+
#
|
|
177
|
+
# Sequel::Migrator.apply(DB, '.', 0)
|
|
178
|
+
#
|
|
179
|
+
# For example, to migrate the database to version 4:
|
|
180
|
+
#
|
|
181
|
+
# Sequel::Migrator.apply(DB, '.', 4)
|
|
182
|
+
#
|
|
130
183
|
# To migrate the database from version 1 to version 5:
|
|
131
184
|
#
|
|
132
185
|
# Sequel::Migrator.apply(DB, '.', 5, 1)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
MIGRATION_FILE_PATTERN = /\A\d+_.+\.rb\z
|
|
186
|
+
#
|
|
187
|
+
# Part of the +migration+ extension.
|
|
188
|
+
class Migrator
|
|
189
|
+
MIGRATION_FILE_PATTERN = /\A\d+_.+\.rb\z/i.freeze
|
|
137
190
|
MIGRATION_SPLITTER = '_'.freeze
|
|
191
|
+
MINIMUM_TIMESTAMP = 20000101
|
|
138
192
|
|
|
139
|
-
#
|
|
193
|
+
# Exception class raised when there is an error with the migrator's
|
|
194
|
+
# file structure, database, or arguments.
|
|
195
|
+
class Error < Sequel::Error
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Wrapper for +run+, maintaining backwards API compatibility
|
|
140
199
|
def self.apply(db, directory, target = nil, current = nil)
|
|
141
200
|
run(db, directory, :target => target, :current => current)
|
|
142
201
|
end
|
|
143
202
|
|
|
144
203
|
# Migrates the supplied database using the migration files in the the specified directory. Options:
|
|
145
|
-
# * :column
|
|
146
|
-
# * :current
|
|
147
|
-
#
|
|
148
|
-
# * :table
|
|
149
|
-
# * :target
|
|
204
|
+
# * :column :: The column in the :table argument storing the migration version (default: :version).
|
|
205
|
+
# * :current :: The current version of the database. If not given, it is retrieved from the database
|
|
206
|
+
# using the :table and :column options.
|
|
207
|
+
# * :table :: The table containing the schema version (default: :schema_info).
|
|
208
|
+
# * :target :: The target version to which to migrate. If not given, migrates to the maximum version.
|
|
150
209
|
#
|
|
151
210
|
# Examples:
|
|
152
211
|
# Sequel::Migrator.run(DB, "migrations")
|
|
@@ -154,86 +213,309 @@ module Sequel
|
|
|
154
213
|
# Sequel::Migrator.run(DB, "app1/migrations", :column=> :app2_version)
|
|
155
214
|
# Sequel::Migrator.run(DB, "app2/migrations", :column => :app2_version, :table=>:schema_info2)
|
|
156
215
|
def self.run(db, directory, opts={})
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
raise(Error, "No target version available") unless target = opts[:target] || latest_migration_version(directory)
|
|
216
|
+
migrator_class(directory).new(db, directory, opts).run
|
|
217
|
+
end
|
|
160
218
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
219
|
+
# Choose the Migrator subclass to use. Uses the TimestampMigrator
|
|
220
|
+
# if the version number appears to be a unix time integer for a year
|
|
221
|
+
# after 2005, otherwise uses the IntegerMigrator.
|
|
222
|
+
def self.migrator_class(directory)
|
|
223
|
+
Dir.new(directory).each do |file|
|
|
224
|
+
next unless MIGRATION_FILE_PATTERN.match(file)
|
|
225
|
+
return TimestampMigrator if file.split(MIGRATION_SPLITTER, 2).first.to_i > MINIMUM_TIMESTAMP
|
|
226
|
+
end
|
|
227
|
+
IntegerMigrator
|
|
228
|
+
end
|
|
229
|
+
private_class_method :migrator_class
|
|
230
|
+
|
|
231
|
+
# The column to use to hold the migration version number for integer migrations or
|
|
232
|
+
# filename for timestamp migrations (defaults to :version for integer migrations and
|
|
233
|
+
# :filename for timestamp migrations)
|
|
234
|
+
attr_reader :column
|
|
235
|
+
|
|
236
|
+
# The database related to this migrator
|
|
237
|
+
attr_reader :db
|
|
238
|
+
|
|
239
|
+
# The directory for this migrator's files
|
|
240
|
+
attr_reader :directory
|
|
241
|
+
|
|
242
|
+
# The dataset for this migrator, representing the +schema_info+ table for integer
|
|
243
|
+
# migrations and the +schema_migrations+ table for timestamp migrations
|
|
244
|
+
attr_reader :ds
|
|
245
|
+
|
|
246
|
+
# All migration files in this migrator's directory
|
|
247
|
+
attr_reader :files
|
|
248
|
+
|
|
249
|
+
# The table to use to hold the applied migration data (defaults to :schema_info for
|
|
250
|
+
# integer migrations and :schema_migrations for timestamp migrations)
|
|
251
|
+
attr_reader :table
|
|
252
|
+
|
|
253
|
+
# The target version for this migrator
|
|
254
|
+
attr_reader :target
|
|
164
255
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
256
|
+
# Setup the state for the migrator
|
|
257
|
+
def initialize(db, directory, opts={})
|
|
258
|
+
raise(Error, "Must supply a valid migration path") unless File.directory?(directory)
|
|
259
|
+
@db = db
|
|
260
|
+
@directory = directory
|
|
261
|
+
@files = get_migration_files
|
|
262
|
+
@table = opts[:table] || self.class.const_get(:DEFAULT_SCHEMA_TABLE)
|
|
263
|
+
@column = opts[:column] || self.class.const_get(:DEFAULT_SCHEMA_COLUMN)
|
|
264
|
+
@ds = schema_dataset
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
private
|
|
268
|
+
|
|
269
|
+
# Remove all migration classes. Done by the migrator to ensure that
|
|
270
|
+
# the correct migration classes are picked up.
|
|
271
|
+
def remove_migration_classes
|
|
272
|
+
# Remove class definitions
|
|
273
|
+
Migration.descendants.each do |c|
|
|
274
|
+
Object.send(:remove_const, c.to_s) rescue nil
|
|
275
|
+
end
|
|
276
|
+
Migration.descendants.clear # remove any defined migration classes
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Return the integer migration version based on the filename.
|
|
280
|
+
def migration_version_from_file(filename)
|
|
281
|
+
filename.split(MIGRATION_SPLITTER, 2).first.to_i
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# The default migrator, recommended in most cases. Uses a simple incrementing
|
|
286
|
+
# version number starting with 1, where missing or duplicate migration file
|
|
287
|
+
# versions are not allowed. Part of the +migration+ extension.
|
|
288
|
+
class IntegerMigrator < Migrator
|
|
289
|
+
DEFAULT_SCHEMA_COLUMN = :version
|
|
290
|
+
DEFAULT_SCHEMA_TABLE = :schema_info
|
|
291
|
+
|
|
292
|
+
Error = Migrator::Error
|
|
293
|
+
|
|
294
|
+
# The current version for this migrator
|
|
295
|
+
attr_reader :current
|
|
296
|
+
|
|
297
|
+
# The direction of the migrator, either :up or :down
|
|
298
|
+
attr_reader :direction
|
|
299
|
+
|
|
300
|
+
# The migrations used by this migrator
|
|
301
|
+
attr_reader :migrations
|
|
302
|
+
|
|
303
|
+
# Set up all state for the migrator instance
|
|
304
|
+
def initialize(db, directory, opts={})
|
|
305
|
+
super
|
|
306
|
+
@target = opts[:target] || latest_migration_version
|
|
307
|
+
@current = opts[:current] || current_migration_version
|
|
308
|
+
@direction = current < target ? :up : :down
|
|
309
|
+
@migrations = get_migrations
|
|
310
|
+
|
|
311
|
+
raise(Error, "No current version available") unless current
|
|
312
|
+
raise(Error, "No target version available") unless target
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# Apply all migrations on the database
|
|
316
|
+
def run
|
|
317
|
+
migrations.zip(version_numbers).each do |m, v|
|
|
318
|
+
t = Time.now
|
|
319
|
+
lv = up? ? v : v + 1
|
|
320
|
+
db.log_info("Begin applying migration version #{lv}, direction: #{direction}")
|
|
321
|
+
db.transaction do
|
|
322
|
+
m.apply(db, direction)
|
|
323
|
+
set_migration_version(v)
|
|
324
|
+
end
|
|
325
|
+
db.log_info("Finished applying migration version #{lv}, direction: #{direction}, took #{sprintf('%0.6f', Time.now - t)} seconds")
|
|
168
326
|
end
|
|
169
327
|
|
|
170
328
|
target
|
|
171
329
|
end
|
|
172
330
|
|
|
331
|
+
private
|
|
332
|
+
|
|
173
333
|
# Gets the current migration version stored in the database. If no version
|
|
174
334
|
# number is stored, 0 is returned.
|
|
175
|
-
def
|
|
176
|
-
(
|
|
335
|
+
def current_migration_version
|
|
336
|
+
ds.get(column) || 0
|
|
177
337
|
end
|
|
178
338
|
|
|
179
|
-
# Returns
|
|
180
|
-
def
|
|
181
|
-
|
|
182
|
-
|
|
339
|
+
# Returns any found migration files in the supplied directory.
|
|
340
|
+
def get_migration_files
|
|
341
|
+
files = []
|
|
342
|
+
Dir.new(directory).each do |file|
|
|
343
|
+
next unless MIGRATION_FILE_PATTERN.match(file)
|
|
344
|
+
version = migration_version_from_file(file)
|
|
345
|
+
raise(Error, "Duplicate migration version: #{version}") if files[version]
|
|
346
|
+
files[version] = File.join(directory, file)
|
|
347
|
+
end
|
|
348
|
+
1.upto(files.length - 1){|i| raise(Error, "Missing migration version: #{i}") unless files[i]}
|
|
349
|
+
files
|
|
183
350
|
end
|
|
184
|
-
|
|
351
|
+
|
|
185
352
|
# Returns a list of migration classes filtered for the migration range and
|
|
186
353
|
# ordered according to the migration direction.
|
|
187
|
-
def
|
|
188
|
-
|
|
189
|
-
(current + 1)..target : (target + 1)..current
|
|
190
|
-
|
|
191
|
-
# Remove class definitions
|
|
192
|
-
Migration.descendants.each do |c|
|
|
193
|
-
Object.send(:remove_const, c.to_s) rescue nil
|
|
194
|
-
end
|
|
195
|
-
Migration.descendants.clear # remove any defined migration classes
|
|
354
|
+
def get_migrations
|
|
355
|
+
remove_migration_classes
|
|
196
356
|
|
|
197
357
|
# load migration files
|
|
198
|
-
|
|
358
|
+
files[up? ? (current + 1)..target : (target + 1)..current].compact.each{|f| load(f)}
|
|
199
359
|
|
|
200
360
|
# get migration classes
|
|
201
361
|
classes = Migration.descendants
|
|
202
|
-
|
|
203
|
-
classes
|
|
362
|
+
up? ? classes : classes.reverse
|
|
204
363
|
end
|
|
205
364
|
|
|
206
|
-
# Returns
|
|
207
|
-
def
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
files[migration_version_from_file(file)] = File.join(directory, file) if MIGRATION_FILE_PATTERN.match(file)
|
|
211
|
-
end
|
|
212
|
-
filtered = range ? files[range] : files
|
|
213
|
-
filtered ? filtered.compact : []
|
|
365
|
+
# Returns the latest version available in the specified directory.
|
|
366
|
+
def latest_migration_version
|
|
367
|
+
l = files.last
|
|
368
|
+
l ? migration_version_from_file(File.basename(l)) : nil
|
|
214
369
|
end
|
|
215
370
|
|
|
216
371
|
# Returns the dataset for the schema_info table. If no such table
|
|
217
372
|
# exists, it is automatically created.
|
|
218
|
-
def
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
db.
|
|
222
|
-
|
|
223
|
-
|
|
373
|
+
def schema_dataset
|
|
374
|
+
c = column
|
|
375
|
+
ds = db.from(table)
|
|
376
|
+
if !db.table_exists?(table)
|
|
377
|
+
db.create_table(table){Integer c, :default=>0, :null=>false}
|
|
378
|
+
elsif !ds.columns.include?(c)
|
|
379
|
+
db.alter_table(table){add_column c, Integer, :default=>0, :null=>false}
|
|
380
|
+
end
|
|
381
|
+
ds.insert(c=>0) if ds.empty?
|
|
382
|
+
raise(Error, "More than 1 row in migrator table") if ds.count > 1
|
|
383
|
+
ds
|
|
224
384
|
end
|
|
225
385
|
|
|
226
386
|
# Sets the current migration version stored in the database.
|
|
227
|
-
def
|
|
228
|
-
column
|
|
229
|
-
dataset = schema_info_dataset(db, opts)
|
|
230
|
-
dataset.send(dataset.first ? :update : :insert, column => version)
|
|
387
|
+
def set_migration_version(version)
|
|
388
|
+
ds.update(column=>version)
|
|
231
389
|
end
|
|
232
390
|
|
|
233
|
-
#
|
|
234
|
-
def
|
|
235
|
-
|
|
391
|
+
# Whether or not this is an up migration
|
|
392
|
+
def up?
|
|
393
|
+
direction == :up
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# An array of numbers corresponding to the migrations,
|
|
397
|
+
# so that each number in the array is the migration version
|
|
398
|
+
# that will be in affect after the migration is run.
|
|
399
|
+
def version_numbers
|
|
400
|
+
up? ? ((current+1)..target).to_a : (target..(current - 1)).to_a.reverse
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# The migrator used if any migration file version appears to be a timestamp.
|
|
405
|
+
# Stores filenames of migration files, and can figure out which migrations
|
|
406
|
+
# have not been applied and apply them, even if earlier migrations are added
|
|
407
|
+
# after later migrations. If you plan to do that, the responsibility is on
|
|
408
|
+
# you to make sure the migrations don't conflict. Part of the +migration+ extension.
|
|
409
|
+
class TimestampMigrator < Migrator
|
|
410
|
+
DEFAULT_SCHEMA_COLUMN = :filename
|
|
411
|
+
DEFAULT_SCHEMA_TABLE = :schema_migrations
|
|
412
|
+
|
|
413
|
+
Error = Migrator::Error
|
|
414
|
+
|
|
415
|
+
# Array of strings of applied migration filenames
|
|
416
|
+
attr_reader :applied_migrations
|
|
417
|
+
|
|
418
|
+
# Get tuples of migrations, filenames, and actions for each migration
|
|
419
|
+
attr_reader :migration_tuples
|
|
420
|
+
|
|
421
|
+
# Set up all state for the migrator instance
|
|
422
|
+
def initialize(db, directory, opts={})
|
|
423
|
+
super
|
|
424
|
+
@target = opts[:target]
|
|
425
|
+
@applied_migrations = get_applied_migrations
|
|
426
|
+
@migration_tuples = get_migration_tuples
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
# Apply all migration tuples on the database
|
|
430
|
+
def run
|
|
431
|
+
migration_tuples.each do |m, f, direction|
|
|
432
|
+
t = Time.now
|
|
433
|
+
db.log_info("Begin applying migration #{f}, direction: #{direction}")
|
|
434
|
+
db.transaction do
|
|
435
|
+
m.apply(db, direction)
|
|
436
|
+
fi = f.downcase
|
|
437
|
+
direction == :up ? ds.insert(column=>fi) : ds.filter(column=>fi).delete
|
|
438
|
+
end
|
|
439
|
+
db.log_info("Finished applying migration #{f}, direction: #{direction}, took #{sprintf('%0.6f', Time.now - t)} seconds")
|
|
440
|
+
end
|
|
441
|
+
nil
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
private
|
|
445
|
+
|
|
446
|
+
# Convert the schema_info table to the new schema_migrations table format,
|
|
447
|
+
# using the version of the schema_info table and the current migration files.
|
|
448
|
+
def convert_from_schema_info
|
|
449
|
+
v = db[IntegerMigrator::DEFAULT_SCHEMA_TABLE].get(IntegerMigrator::DEFAULT_SCHEMA_COLUMN)
|
|
450
|
+
ds = db.from(table)
|
|
451
|
+
files.each do |path|
|
|
452
|
+
f = File.basename(path)
|
|
453
|
+
if migration_version_from_file(f) <= v
|
|
454
|
+
ds.insert(column=>f)
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
# Returns filenames of all applied migrations
|
|
460
|
+
def get_applied_migrations
|
|
461
|
+
am = ds.select_order_map(column)
|
|
462
|
+
missing_migration_files = am - files.map{|f| File.basename(f).downcase}
|
|
463
|
+
raise(Error, "Applied migration files not in file system: #{missing_migration_files.join(', ')}") if missing_migration_files.length > 0
|
|
464
|
+
am
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
# Returns any migration files found in the migrator's directory.
|
|
468
|
+
def get_migration_files
|
|
469
|
+
files = []
|
|
470
|
+
Dir.new(directory).each do |file|
|
|
471
|
+
next unless MIGRATION_FILE_PATTERN.match(file)
|
|
472
|
+
files << File.join(directory, file)
|
|
473
|
+
end
|
|
474
|
+
files.sort
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
# Returns tuples of migration, filename, and direction
|
|
478
|
+
def get_migration_tuples
|
|
479
|
+
remove_migration_classes
|
|
480
|
+
up_mts = []
|
|
481
|
+
down_mts = []
|
|
482
|
+
ms = Migration.descendants
|
|
483
|
+
files.each do |path|
|
|
484
|
+
f = File.basename(path)
|
|
485
|
+
fi = f.downcase
|
|
486
|
+
if target
|
|
487
|
+
if migration_version_from_file(f) > target
|
|
488
|
+
if applied_migrations.include?(fi)
|
|
489
|
+
load(path)
|
|
490
|
+
down_mts << [ms.last, f, :down]
|
|
491
|
+
end
|
|
492
|
+
elsif !applied_migrations.include?(fi)
|
|
493
|
+
load(path)
|
|
494
|
+
up_mts << [ms.last, f, :up]
|
|
495
|
+
end
|
|
496
|
+
elsif !applied_migrations.include?(fi)
|
|
497
|
+
load(path)
|
|
498
|
+
up_mts << [ms.last, f, :up]
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
up_mts + down_mts.reverse
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
# Returns the dataset for the schema_migrations table. If no such table
|
|
505
|
+
# exists, it is automatically created.
|
|
506
|
+
def schema_dataset
|
|
507
|
+
c = column
|
|
508
|
+
ds = db.from(table)
|
|
509
|
+
if !db.table_exists?(table)
|
|
510
|
+
db.create_table(table){String c, :primary_key=>true}
|
|
511
|
+
if db.table_exists?(:schema_info) and vha = db[:schema_info].all and vha.length == 1 and
|
|
512
|
+
vha.first.keys == [:version] and vha.first.values.first.is_a?(Integer)
|
|
513
|
+
convert_from_schema_info
|
|
514
|
+
end
|
|
515
|
+
elsif !ds.columns.include?(c)
|
|
516
|
+
raise(Error, "Migrator table #{table} does not contain column #{c}")
|
|
517
|
+
end
|
|
518
|
+
ds
|
|
236
519
|
end
|
|
237
|
-
private_class_method :migration_version_from_file
|
|
238
520
|
end
|
|
239
521
|
end
|