sequel 3.17.0 → 3.18.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 +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:
|