sequel 3.17.0 → 3.18.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +22 -0
- data/doc/migration.rdoc +34 -0
- data/doc/release_notes/3.18.0.txt +121 -0
- data/lib/sequel/adapters/jdbc.rb +6 -1
- data/lib/sequel/adapters/mysql.rb +11 -1
- data/lib/sequel/adapters/mysql2.rb +3 -1
- data/lib/sequel/adapters/postgres.rb +1 -1
- data/lib/sequel/adapters/shared/sqlite.rb +13 -1
- data/lib/sequel/adapters/sqlite.rb +31 -26
- data/lib/sequel/connection_pool/sharded_single.rb +5 -1
- data/lib/sequel/connection_pool/sharded_threaded.rb +5 -1
- data/lib/sequel/dataset/sql.rb +0 -5
- data/lib/sequel/extensions/migration.rb +117 -1
- data/lib/sequel/extensions/to_dot.rb +137 -0
- data/lib/sequel/model/base.rb +1 -1
- data/lib/sequel/plugins/instance_hooks.rb +14 -9
- data/lib/sequel/plugins/json_serializer.rb +3 -3
- data/lib/sequel/sql.rb +1 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/sqlite_spec.rb +15 -7
- data/spec/core/connection_pool_spec.rb +17 -1
- data/spec/core/dataset_spec.rb +9 -0
- data/spec/extensions/instance_hooks_spec.rb +46 -0
- data/spec/extensions/json_serializer_spec.rb +11 -0
- data/spec/extensions/migration_spec.rb +107 -0
- data/spec/extensions/spec_helper.rb +1 -1
- data/spec/extensions/to_dot_spec.rb +152 -0
- data/spec/files/reversible_migrations/001_reversible.rb +5 -0
- data/spec/files/reversible_migrations/002_reversible.rb +5 -0
- data/spec/files/reversible_migrations/003_reversible.rb +5 -0
- data/spec/files/reversible_migrations/004_reversible.rb +5 -0
- data/spec/files/reversible_migrations/005_reversible.rb +10 -0
- data/spec/integration/migrator_test.rb +54 -0
- data/spec/model/association_reflection_spec.rb +19 -0
- metadata +13 -4
data/CHANGELOG
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
+
=== 3.18.0 (2010-12-01)
|
2
|
+
|
3
|
+
* Allow the user to control how the connection pool deals with attempts to access shards that aren't configured (jeremyevans)
|
4
|
+
|
5
|
+
* Typecast columns when creating model objects from JSON in the json_serializer plugin (jeremyevans)
|
6
|
+
|
7
|
+
* When parsing the schema for a model that uses an aliased table, use the unaliased table name (jeremyevans)
|
8
|
+
|
9
|
+
* When emulating schema methods such as drop_column on SQLite, recreate applicable indexes on the recreated table (jeremyevans)
|
10
|
+
|
11
|
+
* Only remove hook pairs that have been run successfully in the instance_hooks plugin (jeremyevans)
|
12
|
+
|
13
|
+
* Add reversible migration support to the migration extension (jeremyevans)
|
14
|
+
|
15
|
+
* Add to_dot extension, for producing visualizations of Dataset abstract syntax trees with Graphviz (jeremyevans)
|
16
|
+
|
17
|
+
* Switch to using manual type translation in the SQLite adapter (jeremyevans)
|
18
|
+
|
19
|
+
* Support :read_timeout option in the native mysql adapter (tmm1)
|
20
|
+
|
21
|
+
* Support :connect_timeout option in the native mysql and mysql2 adapters (tmm1)
|
22
|
+
|
1
23
|
=== 3.17.0 (2010-11-05)
|
2
24
|
|
3
25
|
* Ensure that the optimistic locking plugin increments the lock column when using Model#modified! (jfirebaugh)
|
data/doc/migration.rdoc
CHANGED
@@ -52,6 +52,40 @@ the change made by up. However, if you never need to be able to migrate down
|
|
52
52
|
the +down+ block. In this case, the +down+ block just reverses the changes made by up,
|
53
53
|
dropping the table.
|
54
54
|
|
55
|
+
You can simplify the migration given above by using a reversible migration with a +change+
|
56
|
+
block:
|
57
|
+
|
58
|
+
Sequel.migration do
|
59
|
+
change do
|
60
|
+
create_table(:artists) do
|
61
|
+
primary_key :id
|
62
|
+
String :name, :null=>false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
The +change+ block acts exactly like an +up+ block. The only difference is that
|
68
|
+
it will attempt to create a +down+ block for you, assuming that it knows how to
|
69
|
+
reverse the given migration. The +change+ block can usually correctly reverse
|
70
|
+
the following methods:
|
71
|
+
|
72
|
+
* +create_table+
|
73
|
+
* +add_column+
|
74
|
+
* +add_index+
|
75
|
+
* +rename_column+
|
76
|
+
* +rename_table+
|
77
|
+
* +alter_table+ (supporting the following methods in the +alter_table+ block):
|
78
|
+
* +add_column+
|
79
|
+
* +add_constraint+
|
80
|
+
* +add_foreign_key+ (with a symbol, not an array)
|
81
|
+
* +add_primary_key+ (with a symbol, not an array)
|
82
|
+
* +add_index+
|
83
|
+
* +add_full_text_index+
|
84
|
+
* +add_spatial_index+
|
85
|
+
* +rename_column+
|
86
|
+
|
87
|
+
If you use any other methods, you should create your own +down+ block.
|
88
|
+
|
55
89
|
In normal usage, when Sequel's migrator runs, it runs the +up+ blocks for all
|
56
90
|
migrations that have not yet been applied. However, you can use the <tt>-M</tt>
|
57
91
|
switch to specify the version to which to migrate, and if it is lower than the
|
@@ -0,0 +1,121 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* Reversible migration support has been added:
|
4
|
+
|
5
|
+
Sequel.migration do
|
6
|
+
change do
|
7
|
+
create_table(:artists) do
|
8
|
+
primary_key :id
|
9
|
+
String :name, :null=>false
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
The change block acts the same way as an up block, except that
|
15
|
+
it automatically creates a down block that reverses the changes.
|
16
|
+
So the above is equivalent to:
|
17
|
+
|
18
|
+
Sequel.migration do
|
19
|
+
up do
|
20
|
+
create_table(:artists) do
|
21
|
+
primary_key :id
|
22
|
+
String :name, :null=>false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
down do
|
26
|
+
drop_table :artists
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
The following methods are supported in a change block:
|
31
|
+
|
32
|
+
* create_table
|
33
|
+
* add_column
|
34
|
+
* add_index
|
35
|
+
* rename_column
|
36
|
+
* rename_table
|
37
|
+
* alter_table (supporting the following methods):
|
38
|
+
* add_column
|
39
|
+
* add_constraint
|
40
|
+
* add_foreign_key (with a symbol, not an array)
|
41
|
+
* add_primary_key (with a symbol, not an array)
|
42
|
+
* add_index
|
43
|
+
* add_full_text_index
|
44
|
+
* add_spatial_index
|
45
|
+
* rename_column
|
46
|
+
|
47
|
+
Use of an other method in a change block will result in the
|
48
|
+
creation of a down block that raises an exception.
|
49
|
+
|
50
|
+
* A to_dot extension has been added that adds a Dataset#to_dot
|
51
|
+
method, which returns a string that can be used as input to
|
52
|
+
the graphviz dot program in order to create visualizations
|
53
|
+
of the dataset's abstract syntax tree. Examples:
|
54
|
+
|
55
|
+
* http://sequel.heroku.com/images/to_dot_simple.gif
|
56
|
+
* http://sequel.heroku.com/images/to_dot_complex.gif
|
57
|
+
* http://imgpaste.com/i/lxngy.gif
|
58
|
+
|
59
|
+
Both the to_dot extension and reversible migrations support
|
60
|
+
were inspired by Aaron Patterson's recent work on ActiveRecord
|
61
|
+
and ARel.
|
62
|
+
|
63
|
+
* The user can now control how the connection pool handles attempts
|
64
|
+
to access shards that haven't been configured. The default is
|
65
|
+
still to assume the :default shard. However, you can specify a
|
66
|
+
different shard using the :servers_hash option when connecting
|
67
|
+
to the database:
|
68
|
+
|
69
|
+
DB = Sequel.connect(..., :servers_hash=>Hash.new(:some_shard))
|
70
|
+
|
71
|
+
You can also use this feature to raise an exception if an
|
72
|
+
unconfigured shard is used:
|
73
|
+
|
74
|
+
DB = Sequel.connect(..., :servers_hash=>Hash.new{raise ...})
|
75
|
+
|
76
|
+
* The mysql and mysql2 adapters now both support the :read_timeout
|
77
|
+
and :connect_timeout options. read_timeout is the timeout in
|
78
|
+
seconds for reading back results of a query, and connect_timeout
|
79
|
+
is the timeout in seconds before a connection attempt is abandoned.
|
80
|
+
|
81
|
+
= Other Improvements
|
82
|
+
|
83
|
+
* The json_serializer plugin will now typecast column values for
|
84
|
+
columns with unrestricted setter methods when parsing JSON into
|
85
|
+
model objects. It now also calls the getter method when creating
|
86
|
+
the JSON, instead of directly taking the values from the underlying
|
87
|
+
hash.
|
88
|
+
|
89
|
+
* When parsing the schema for a model with an aliased table name,
|
90
|
+
the unaliased table name is now used.
|
91
|
+
|
92
|
+
* The SQLite adapter has been updated to not rely on the native
|
93
|
+
type_translation support, since that will be removed in the next
|
94
|
+
major version of sqlite3-ruby. Sequel now implements it's own
|
95
|
+
type translation in the sqlite adapter, similarly to how the mysql
|
96
|
+
and postgres adapters handle type translation.
|
97
|
+
|
98
|
+
* On SQLite, when emulating natively unsupported schema methods such
|
99
|
+
as drop_column, Sequel will now attempt to recreate applicable
|
100
|
+
indexes on the underlying table.
|
101
|
+
|
102
|
+
* A more informative error message is now used when connecting fails
|
103
|
+
when using the jdbc adapter.
|
104
|
+
|
105
|
+
* method_missing is no longer removed from Sequel::BasicObject on
|
106
|
+
ruby 1.8. This should improve compatibility in some cases with
|
107
|
+
Rubinius.
|
108
|
+
|
109
|
+
= Backwards Compatibility
|
110
|
+
|
111
|
+
* On SQLite, Sequel no longer assumes that a plain integer in a
|
112
|
+
datetime or timestamp field represents a unix epoch time.
|
113
|
+
|
114
|
+
* Previously, saving a model object that used the instance_hooks
|
115
|
+
plugin removed all instance hooks. Now, only the applicable hooks
|
116
|
+
are removed. So if you save a new object, the update instance
|
117
|
+
hooks won't be removed. And if you save an existing object, delete
|
118
|
+
instance hooks won't be removed.
|
119
|
+
|
120
|
+
* The private Dataset#identifier_list method has been moved into the
|
121
|
+
SQLite adapter, since that is the only place it was used.
|
data/lib/sequel/adapters/jdbc.rb
CHANGED
@@ -168,7 +168,12 @@ module Sequel
|
|
168
168
|
props.setProperty("user", opts[:user])
|
169
169
|
props.setProperty("password", opts[:password])
|
170
170
|
end
|
171
|
-
|
171
|
+
begin
|
172
|
+
driver.new.connect(args[0], props)
|
173
|
+
rescue => e2
|
174
|
+
e.message << "\n#{e2.class.name}: #{e2.message}"
|
175
|
+
raise e
|
176
|
+
end
|
172
177
|
end
|
173
178
|
end
|
174
179
|
setup_connection(conn)
|
@@ -83,11 +83,15 @@ module Sequel
|
|
83
83
|
# the MySQL config file.
|
84
84
|
# * :config_local_infile - If provided, sets the Mysql::OPT_LOCAL_INFILE
|
85
85
|
# option on the connection with the given value.
|
86
|
+
# * :connect_timeout - Set the timeout in seconds before a connection
|
87
|
+
# attempt is abandoned.
|
86
88
|
# * :encoding - Set all the related character sets for this
|
87
89
|
# connection (connection, client, database, server, and results).
|
90
|
+
# * :read_timeout - Set the timeout in seconds for reading back results
|
91
|
+
# to a query.
|
88
92
|
# * :socket - Use a unix socket file instead of connecting via TCP/IP.
|
89
93
|
# * :timeout - Set the timeout in seconds before the server will
|
90
|
-
# disconnect this connection.
|
94
|
+
# disconnect this connection (a.k.a @@wait_timeout).
|
91
95
|
def connect(server)
|
92
96
|
opts = server_opts(server)
|
93
97
|
conn = Mysql.init
|
@@ -99,6 +103,12 @@ module Sequel
|
|
99
103
|
# encoding we want to use, but this can be overridden by READ_DEFAULT_GROUP.
|
100
104
|
conn.options(Mysql::SET_CHARSET_NAME, encoding)
|
101
105
|
end
|
106
|
+
if read_timeout = opts[:read_timeout] and Mysql::const_defined(Mysql::OPT_READ_TIMEOUT)
|
107
|
+
conn.options(Mysql::OPT_READ_TIMEOUT, read_timeout)
|
108
|
+
end
|
109
|
+
if connect_timeout = opts[:connect_timeout] and Mysql::const_defined(Mysql::OPT_CONNECT_TIMEOUT)
|
110
|
+
conn.options(Mysql::OPT_CONNECT_TIMEOUT, connect_timeout)
|
111
|
+
end
|
102
112
|
conn.real_connect(
|
103
113
|
opts[:host] || 'localhost',
|
104
114
|
opts[:user],
|
@@ -26,11 +26,13 @@ module Sequel
|
|
26
26
|
# the MySQL config file.
|
27
27
|
# * :config_local_infile - If provided, sets the Mysql::OPT_LOCAL_INFILE
|
28
28
|
# option on the connection with the given value.
|
29
|
+
# * :connect_timeout - Set the timeout in seconds before a connection
|
30
|
+
# attempt is abandoned.
|
29
31
|
# * :encoding - Set all the related character sets for this
|
30
32
|
# connection (connection, client, database, server, and results).
|
31
33
|
# * :socket - Use a unix socket file instead of connecting via TCP/IP.
|
32
34
|
# * :timeout - Set the timeout in seconds before the server will
|
33
|
-
# disconnect this connection.
|
35
|
+
# disconnect this connection (a.k.a. @@wait_timeout).
|
34
36
|
def connect(server)
|
35
37
|
opts = server_opts(server)
|
36
38
|
opts[:host] ||= 'localhost'
|
@@ -93,7 +93,7 @@ module Sequel
|
|
93
93
|
|
94
94
|
# Use a single proc for each type to conserve memory
|
95
95
|
PG_TYPE_PROCS = {
|
96
|
-
[16] =>
|
96
|
+
[16] => lambda{|s| s == 't'}, # boolean
|
97
97
|
[17] => lambda{|s| ::Sequel::SQL::Blob.new(Adapter.unescape_bytea(s))}, # bytea
|
98
98
|
[20, 21, 22, 23, 26] => lambda{|s| s.to_i}, # integer
|
99
99
|
[700, 701] => lambda{|s| s.to_f}, # float
|
@@ -237,12 +237,18 @@ module Sequel
|
|
237
237
|
|
238
238
|
qt = quote_schema_table(table)
|
239
239
|
bt = quote_identifier(backup_table_name(qt))
|
240
|
-
[
|
240
|
+
a = [
|
241
241
|
"CREATE TABLE #{bt}(#{def_columns_str})",
|
242
242
|
"INSERT INTO #{bt}(#{dataset.send(:identifier_list, new_columns)}) SELECT #{dataset.send(:identifier_list, old_columns)} FROM #{qt}",
|
243
243
|
"DROP TABLE #{qt}",
|
244
244
|
"ALTER TABLE #{bt} RENAME TO #{qt}"
|
245
245
|
]
|
246
|
+
indexes(table).each do |name, h|
|
247
|
+
if (h[:columns].map{|x| x.to_s} - new_columns).empty?
|
248
|
+
a << alter_table_sql(table, h.merge(:op=>:add_index, :name=>name))
|
249
|
+
end
|
250
|
+
end
|
251
|
+
a
|
246
252
|
end
|
247
253
|
|
248
254
|
# SQLite folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on input.
|
@@ -312,6 +318,7 @@ module Sequel
|
|
312
318
|
# Instance methods for datasets that connect to an SQLite database
|
313
319
|
module DatasetMethods
|
314
320
|
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct columns from join where group having compounds order limit')
|
321
|
+
COMMA_SEPARATOR = ', '.freeze
|
315
322
|
CONSTANT_MAP = {:CURRENT_DATE=>"date(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIMESTAMP=>"datetime(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIME=>"time(CURRENT_TIMESTAMP, 'localtime')".freeze}
|
316
323
|
|
317
324
|
# SQLite does not support pattern matching via regular expressions.
|
@@ -388,6 +395,11 @@ module Sequel
|
|
388
395
|
"#{expression} AS #{literal(aliaz.to_s)}"
|
389
396
|
end
|
390
397
|
|
398
|
+
# SQL fragment specifying a list of identifiers
|
399
|
+
def identifier_list(columns)
|
400
|
+
columns.map{|i| quote_identifier(i)}.join(COMMA_SEPARATOR)
|
401
|
+
end
|
402
|
+
|
391
403
|
# SQLite uses a preceding X for hex escaping strings
|
392
404
|
def literal_blob(v)
|
393
405
|
blob = ''
|
@@ -10,6 +10,23 @@ module Sequel
|
|
10
10
|
# Top level module for holding all SQLite-related modules and classes
|
11
11
|
# for Sequel.
|
12
12
|
module SQLite
|
13
|
+
UNIX_EPOCH_TIME_FORMAT = /\A\d+\z/.freeze
|
14
|
+
SQLITE_TYPES = {}
|
15
|
+
FALSE_VALUES = %w'0 false f no n'.freeze
|
16
|
+
SQLITE_TYPE_PROCS = {
|
17
|
+
%w'timestamp datetime' => proc{|v| Sequel.database_to_application_timestamp(v)},
|
18
|
+
%w'date' => proc{|v| Sequel.string_to_date(v)},
|
19
|
+
%w'time' => proc{|v| Sequel.string_to_time(v)},
|
20
|
+
%w'bit bool boolean' => proc{|v| !FALSE_VALUES.include?(v.downcase)},
|
21
|
+
%w'integer smallint mediumint int bigint' => proc{|v| Integer(v) rescue v},
|
22
|
+
%w'numeric decimal money' => proc{|v| BigDecimal.new(v) rescue v},
|
23
|
+
%w'float double real dec fixed' + ['double precision'] => proc{|v| Float(v) rescue v},
|
24
|
+
%w'blob' => proc{|v| ::Sequel::SQL::Blob.new(v)}
|
25
|
+
}
|
26
|
+
SQLITE_TYPE_PROCS.each do |k,v|
|
27
|
+
k.each{|n| SQLITE_TYPES[n] = v}
|
28
|
+
end
|
29
|
+
|
13
30
|
# Database class for SQLite databases used with Sequel and the
|
14
31
|
# ruby-sqlite3 driver.
|
15
32
|
class Database < Sequel::Database
|
@@ -35,33 +52,9 @@ module Sequel
|
|
35
52
|
opts[:database] = ':memory:' if blank_object?(opts[:database])
|
36
53
|
db = ::SQLite3::Database.new(opts[:database])
|
37
54
|
db.busy_timeout(opts.fetch(:timeout, 5000))
|
38
|
-
db.type_translation = true
|
39
55
|
|
40
56
|
connection_pragmas.each{|s| log_yield(s){db.execute_batch(s)}}
|
41
57
|
|
42
|
-
# Handle datetimes with Sequel.datetime_class
|
43
|
-
prok = proc do |t,v|
|
44
|
-
v = Time.at(v.to_i).iso8601 if UNIX_EPOCH_TIME_FORMAT.match(v)
|
45
|
-
Sequel.database_to_application_timestamp(v)
|
46
|
-
end
|
47
|
-
db.translator.add_translator("timestamp", &prok)
|
48
|
-
db.translator.add_translator("datetime", &prok)
|
49
|
-
|
50
|
-
# Handle numeric values with BigDecimal
|
51
|
-
prok = proc{|t,v| BigDecimal.new(v) rescue v}
|
52
|
-
db.translator.add_translator("numeric", &prok)
|
53
|
-
db.translator.add_translator("decimal", &prok)
|
54
|
-
db.translator.add_translator("money", &prok)
|
55
|
-
|
56
|
-
# Handle floating point values with Float
|
57
|
-
prok = proc{|t,v| Float(v) rescue v}
|
58
|
-
db.translator.add_translator("float", &prok)
|
59
|
-
db.translator.add_translator("real", &prok)
|
60
|
-
db.translator.add_translator("double precision", &prok)
|
61
|
-
|
62
|
-
# Handle blob values with Sequel::SQL::Blob
|
63
|
-
db.translator.add_translator("blob"){|t,v| ::Sequel::SQL::Blob.new(v)}
|
64
|
-
|
65
58
|
class << db
|
66
59
|
attr_reader :prepared_statements
|
67
60
|
end
|
@@ -271,11 +264,18 @@ module Sequel
|
|
271
264
|
def fetch_rows(sql)
|
272
265
|
execute(sql) do |result|
|
273
266
|
i = -1
|
274
|
-
|
267
|
+
type_procs = result.types.map{|t| SQLITE_TYPES[base_type_name(t)]}
|
268
|
+
cols = result.columns.map{|c| i+=1; [output_identifier(c), i, type_procs[i]]}
|
275
269
|
@columns = cols.map{|c| c.first}
|
276
270
|
result.each do |values|
|
277
271
|
row = {}
|
278
|
-
cols.each
|
272
|
+
cols.each do |name,i,type_proc|
|
273
|
+
v = values[i]
|
274
|
+
if type_proc && v.is_a?(String)
|
275
|
+
v = type_proc.call(v)
|
276
|
+
end
|
277
|
+
row[name] = v
|
278
|
+
end
|
279
279
|
yield row
|
280
280
|
end
|
281
281
|
end
|
@@ -297,6 +297,11 @@ module Sequel
|
|
297
297
|
|
298
298
|
private
|
299
299
|
|
300
|
+
# The base type name for a given type, without any parenthetical part.
|
301
|
+
def base_type_name(t)
|
302
|
+
(t =~ /^(.*?)\(/ ? $1 : t).downcase if t
|
303
|
+
end
|
304
|
+
|
300
305
|
# Quote the string using the adapter class method.
|
301
306
|
def literal_string(v)
|
302
307
|
"'#{::SQLite3::Database.quote(v)}'"
|
@@ -8,10 +8,14 @@ class Sequel::ShardedSingleConnectionPool < Sequel::ConnectionPool
|
|
8
8
|
# * :servers - A hash of servers to use. Keys should be symbols. If not
|
9
9
|
# present, will use a single :default server. The server name symbol will
|
10
10
|
# be passed to the connection_proc.
|
11
|
+
# * :servers_hash - The base hash to use for the servers. By default,
|
12
|
+
# Sequel uses Hash.new(:default). You can use a hash with a default proc
|
13
|
+
# that raises an error if you want to catch all cases where a nonexistent
|
14
|
+
# server is used.
|
11
15
|
def initialize(opts={}, &block)
|
12
16
|
super
|
13
17
|
@conns = {}
|
14
|
-
@servers = Hash.new(:default)
|
18
|
+
@servers = opts.fetch(:servers_hash, Hash.new(:default))
|
15
19
|
add_servers([:default])
|
16
20
|
add_servers(opts[:servers].keys) if opts[:servers]
|
17
21
|
end
|
@@ -10,11 +10,15 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
|
|
10
10
|
# * :servers - A hash of servers to use. Keys should be symbols. If not
|
11
11
|
# present, will use a single :default server. The server name symbol will
|
12
12
|
# be passed to the connection_proc.
|
13
|
+
# * :servers_hash - The base hash to use for the servers. By default,
|
14
|
+
# Sequel uses Hash.new(:default). You can use a hash with a default proc
|
15
|
+
# that raises an error if you want to catch all cases where a nonexistent
|
16
|
+
# server is used.
|
13
17
|
def initialize(opts = {}, &block)
|
14
18
|
super
|
15
19
|
@available_connections = {}
|
16
20
|
@connections_to_remove = []
|
17
|
-
@servers = Hash.new(:default)
|
21
|
+
@servers = opts.fetch(:servers_hash, Hash.new(:default))
|
18
22
|
add_servers([:default])
|
19
23
|
add_servers(opts[:servers].keys) if opts[:servers]
|
20
24
|
end
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -600,11 +600,6 @@ module Sequel
|
|
600
600
|
sprintf(".%06d", usec)
|
601
601
|
end
|
602
602
|
|
603
|
-
# SQL fragment specifying a list of identifiers
|
604
|
-
def identifier_list(columns)
|
605
|
-
columns.map{|i| quote_identifier(i)}.join(COMMA_SEPARATOR)
|
606
|
-
end
|
607
|
-
|
608
603
|
# Modify the identifier returned from the database based on the
|
609
604
|
# identifier_output_method.
|
610
605
|
def input_identifier(v)
|
@@ -80,7 +80,7 @@ module Sequel
|
|
80
80
|
|
81
81
|
# Internal class used by the Sequel.migration DSL, part of the +migration+ extension.
|
82
82
|
class MigrationDSL < BasicObject
|
83
|
-
# The underlying Migration
|
83
|
+
# The underlying Migration instance
|
84
84
|
attr_reader :migration
|
85
85
|
|
86
86
|
def self.create(&block)
|
@@ -103,6 +103,122 @@ module Sequel
|
|
103
103
|
def up(&block)
|
104
104
|
migration.up = block
|
105
105
|
end
|
106
|
+
|
107
|
+
# Creates a reversible migration. This is the same as creating
|
108
|
+
# the same block with +up+, but it also calls the block and attempts
|
109
|
+
# to create a +down+ block that will reverse the changes made by
|
110
|
+
# the block.
|
111
|
+
#
|
112
|
+
# There are no guarantees that this will work perfectly
|
113
|
+
# in all cases, but it should work for most common cases.
|
114
|
+
def change(&block)
|
115
|
+
migration.up = block
|
116
|
+
migration.down = MigrationReverser.new.reverse(&block)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Handles the reversing of reversible migrations. Basically records
|
121
|
+
# supported methods calls, translates them to reversed calls, and
|
122
|
+
# returns them in reverse order.
|
123
|
+
class MigrationReverser < Sequel::BasicObject
|
124
|
+
def initialize
|
125
|
+
@actions = []
|
126
|
+
end
|
127
|
+
|
128
|
+
# Reverse the actions for the given block. Takes the block given
|
129
|
+
# and returns a new block that reverses the actions taken by
|
130
|
+
# the given block.
|
131
|
+
def reverse(&block)
|
132
|
+
begin
|
133
|
+
instance_eval(&block)
|
134
|
+
rescue
|
135
|
+
just_raise = true
|
136
|
+
end
|
137
|
+
if just_raise
|
138
|
+
Proc.new{raise Sequel::Error, 'irreversible migration method used, you may need to write your own down method'}
|
139
|
+
else
|
140
|
+
actions = @actions.reverse
|
141
|
+
Proc.new do
|
142
|
+
actions.each do |a|
|
143
|
+
if a.last.is_a?(Proc)
|
144
|
+
pr = a.pop
|
145
|
+
send(*a, &pr)
|
146
|
+
else
|
147
|
+
send(*a)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def add_column(*args)
|
157
|
+
@actions << [:drop_column, args[0], args[1]]
|
158
|
+
end
|
159
|
+
|
160
|
+
def add_index(*args)
|
161
|
+
@actions << [:drop_index, *args]
|
162
|
+
end
|
163
|
+
|
164
|
+
def alter_table(table, &block)
|
165
|
+
@actions << [:alter_table, table, MigrationAlterTableReverser.new.reverse(&block)]
|
166
|
+
end
|
167
|
+
|
168
|
+
def create_table(*args)
|
169
|
+
@actions << [:drop_table, args.first]
|
170
|
+
end
|
171
|
+
|
172
|
+
def create_view(*args)
|
173
|
+
@actions << [:drop_view, args.first]
|
174
|
+
end
|
175
|
+
|
176
|
+
def rename_column(table, name, new_name)
|
177
|
+
@actions << [:rename_column, table, new_name, name]
|
178
|
+
end
|
179
|
+
|
180
|
+
def rename_table(table, new_name)
|
181
|
+
@actions << [:rename_table, new_name, table]
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Handles reversing an alter_table block in a reversible migration.
|
186
|
+
class MigrationAlterTableReverser < Sequel::BasicObject
|
187
|
+
def initialize
|
188
|
+
@actions = []
|
189
|
+
end
|
190
|
+
|
191
|
+
def reverse(&block)
|
192
|
+
instance_eval(&block)
|
193
|
+
actions = @actions.reverse
|
194
|
+
Proc.new{actions.each{|a| send(*a)}}
|
195
|
+
end
|
196
|
+
|
197
|
+
private
|
198
|
+
|
199
|
+
def add_column(*args)
|
200
|
+
@actions << [:drop_column, args.first]
|
201
|
+
end
|
202
|
+
|
203
|
+
def add_constraint(*args)
|
204
|
+
@actions << [:drop_constraint, args.first]
|
205
|
+
end
|
206
|
+
|
207
|
+
def add_foreign_key(*args)
|
208
|
+
raise if args.first.is_a?(Array)
|
209
|
+
@actions << [:drop_column, args.first]
|
210
|
+
end
|
211
|
+
alias add_primary_key add_foreign_key
|
212
|
+
|
213
|
+
def add_index(*args)
|
214
|
+
@actions << [:drop_index, *args]
|
215
|
+
end
|
216
|
+
alias add_full_text_index add_index
|
217
|
+
alias add_spatial_index add_index
|
218
|
+
|
219
|
+
def rename_column(name, new_name)
|
220
|
+
@actions << [:rename_column, new_name, name]
|
221
|
+
end
|
106
222
|
end
|
107
223
|
|
108
224
|
# The preferred method for writing Sequel migrations, using a DSL:
|