sequel 3.16.0 → 3.17.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +28 -0
- data/doc/release_notes/3.17.0.txt +58 -0
- data/lib/sequel/adapters/jdbc/as400.rb +51 -8
- data/lib/sequel/adapters/mysql.rb +4 -2
- data/lib/sequel/adapters/mysql2.rb +3 -2
- data/lib/sequel/adapters/shared/oracle.rb +9 -0
- data/lib/sequel/adapters/swift.rb +5 -2
- data/lib/sequel/adapters/swift/postgres.rb +21 -2
- data/lib/sequel/database/connecting.rb +3 -1
- data/lib/sequel/database/logging.rb +6 -1
- data/lib/sequel/database/misc.rb +3 -0
- data/lib/sequel/dataset/graph.rb +25 -12
- data/lib/sequel/model/associations.rb +18 -7
- data/lib/sequel/model/base.rb +9 -2
- data/lib/sequel/plugins/identity_map.rb +2 -1
- data/lib/sequel/plugins/many_through_many.rb +2 -2
- data/lib/sequel/plugins/nested_attributes.rb +9 -6
- data/lib/sequel/plugins/optimistic_locking.rb +1 -1
- data/lib/sequel/plugins/xml_serializer.rb +11 -3
- data/lib/sequel/version.rb +1 -1
- data/spec/core/database_spec.rb +43 -4
- data/spec/core/object_graph_spec.rb +34 -0
- data/spec/extensions/inflector_spec.rb +4 -0
- data/spec/extensions/many_through_many_spec.rb +19 -0
- data/spec/extensions/nested_attributes_spec.rb +2 -2
- data/spec/extensions/optimistic_locking_spec.rb +26 -1
- data/spec/extensions/xml_serializer_spec.rb +10 -0
- metadata +6 -4
data/CHANGELOG
CHANGED
@@ -1,3 +1,31 @@
|
|
1
|
+
=== 3.17.0 (2010-11-05)
|
2
|
+
|
3
|
+
* Ensure that the optimistic locking plugin increments the lock column when using Model#modified! (jfirebaugh)
|
4
|
+
|
5
|
+
* Correctly handle nil values in the xml_serializer plugin, instead of converting them to empty strings (george.haff) (#313)
|
6
|
+
|
7
|
+
* Use a default wait_timeout that's allowed on Windows for the mysql and mysql2 adapters (jeremyevans) (#314)
|
8
|
+
|
9
|
+
* Add support for connecting to MySQL over SSL using the :sslca, :sslkey, and related options (jeremyevans)
|
10
|
+
|
11
|
+
* Fix Database#each_server when used with jdbc or do connection strings without separate :adapter option (jeremyevans) (#312)
|
12
|
+
|
13
|
+
* Much better support in the AS400 JDBC subadapter (bhauff)
|
14
|
+
|
15
|
+
* Allow cloning of many_through_many associations (gucki, jeremyevans)
|
16
|
+
|
17
|
+
* In the nested_attributes plugin, don't make unnecessary update calls to modify associated objects that are about to be deleted (jeremyevans, gucki)
|
18
|
+
|
19
|
+
* Allow Dataset#(add|set)_graph_aliases to accept as hash values symbols and arrays with a single element (jeremyevans)
|
20
|
+
|
21
|
+
* Add Databse#views and #view_exists? to the Oracle adapter (gpheruson)
|
22
|
+
|
23
|
+
* Add Database#sql_log_level for changing the level at which SQL queries are logged (jeremyevans)
|
24
|
+
|
25
|
+
* Remove unintended use of prepared statements in swift adapter (jeremyevans)
|
26
|
+
|
27
|
+
* Fix logging in the swift PostgreSQL subadapter (jeremyevans)
|
28
|
+
|
1
29
|
=== 3.16.0 (2010-10-01)
|
2
30
|
|
3
31
|
* Support composite foreign keys for associations in the identity_map plugin (harukizaemon, jeremyevans) (#310)
|
@@ -0,0 +1,58 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* You can now change the level at which Sequel logs SQL statements,
|
4
|
+
by calling Database#sql_log_level= with the method name symbol.
|
5
|
+
The default is still :info for backwards compatibility. Previously,
|
6
|
+
you had to use a proxy logger to get similar capability.
|
7
|
+
|
8
|
+
* You can now specify graph aliases where the alias would be the same
|
9
|
+
as the table column name using just the table name symbol, instead
|
10
|
+
having to repeat the alias as the second element of an array. More
|
11
|
+
clearly:
|
12
|
+
|
13
|
+
# < 3.17.0:
|
14
|
+
DB[:a].graph(:b, :a_id=>:id).
|
15
|
+
set_graph_aliases(:c=>[:a, :c], :d=>[:b, :d])
|
16
|
+
# >= 3.17.0:
|
17
|
+
DB[:a].graph(:b, :a_id=>:id).set_graph_aliases(:c=>:a, :d=>:b)
|
18
|
+
|
19
|
+
Both of these now yield the SQL:
|
20
|
+
|
21
|
+
SELECT a.c, b.d FROM a LEFT OUTER JOIN b ON (b.a_id = a.id)
|
22
|
+
|
23
|
+
* You should now be able to connect to MySQL over SSL in the native
|
24
|
+
MySQL adapter using the :sslca, :sslkey, and related options.
|
25
|
+
|
26
|
+
* Database#views and Database#view_exists? methods were added to the
|
27
|
+
Oracle adapter, allowing you to get a an array of view name symbols
|
28
|
+
and to check whether a given view exists.
|
29
|
+
|
30
|
+
= Other Improvements
|
31
|
+
|
32
|
+
* The nested_attributes plugin now avoids unnecessary update calls
|
33
|
+
when deleting associated objects, resulting in better performance.
|
34
|
+
|
35
|
+
* The optimistic_locking plugin now increments the lock column if no
|
36
|
+
other columns were modified but the Model#modified! was called. This
|
37
|
+
means it now works correctly with the nested_attributes plugin when
|
38
|
+
no changes to the main model object are made.
|
39
|
+
|
40
|
+
* The xml_serializer plugin can now round-trip nil values correctly.
|
41
|
+
Previously, nil values would be converted into empty strings. This
|
42
|
+
is accomplished by including a nil attribute in the xml tag.
|
43
|
+
|
44
|
+
* Database#each_server now works correctly when using the jdbc and do
|
45
|
+
adapters and a connection string without a separate :adapter option.
|
46
|
+
|
47
|
+
* You can now clone many_through_many associations.
|
48
|
+
|
49
|
+
* The default wait_timeout used by the mysql and mysql2 adapters was
|
50
|
+
decreased slightly so that it works correctly with MySQL database
|
51
|
+
servers that run on Windows.
|
52
|
+
|
53
|
+
* Many improvements were made to the AS400 jdbc subadapter.
|
54
|
+
|
55
|
+
* Many improvements were made to the swift adapter and subadapters.
|
56
|
+
|
57
|
+
* Dataset#ungraphed now removes any cached graph aliases set with
|
58
|
+
set_graph_aliases or add_graph_aliases.
|
@@ -4,6 +4,10 @@ module Sequel
|
|
4
4
|
module AS400
|
5
5
|
# Instance methods for AS400 Database objects accessed via JDBC.
|
6
6
|
module DatabaseMethods
|
7
|
+
TRANSACTION_BEGIN = 'Transaction.begin'.freeze
|
8
|
+
TRANSACTION_COMMIT = 'Transaction.commit'.freeze
|
9
|
+
TRANSACTION_ROLLBACK = 'Transaction.rollback'.freeze
|
10
|
+
|
7
11
|
# AS400 uses the :as400 database type.
|
8
12
|
def database_type
|
9
13
|
:as400
|
@@ -18,28 +22,67 @@ module Sequel
|
|
18
22
|
def last_insert_id(conn, opts={})
|
19
23
|
nil
|
20
24
|
end
|
25
|
+
|
26
|
+
# AS400 supports transaction isolation levels
|
27
|
+
def supports_transaction_isolation_levels?
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Use JDBC connection's setAutoCommit to false to start transactions
|
34
|
+
def begin_transaction(conn, opts={})
|
35
|
+
set_transaction_isolation(conn, opts)
|
36
|
+
log_yield(TRANSACTION_BEGIN){conn.setAutoCommit(false)}
|
37
|
+
conn
|
38
|
+
end
|
39
|
+
|
40
|
+
# Use JDBC connection's commit method to commit transactions
|
41
|
+
def commit_transaction(conn, opts={})
|
42
|
+
log_yield(TRANSACTION_COMMIT){conn.commit}
|
43
|
+
end
|
44
|
+
|
45
|
+
# Use JDBC connection's setAutoCommit to true to enable default
|
46
|
+
# auto-commit mode
|
47
|
+
def remove_transaction(conn)
|
48
|
+
conn.setAutoCommit(true) if conn
|
49
|
+
@transactions.delete(Thread.current)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Use JDBC connection's rollback method to rollback transactions
|
53
|
+
def rollback_transaction(conn, opts={})
|
54
|
+
log_yield(TRANSACTION_ROLLBACK){conn.rollback}
|
55
|
+
end
|
21
56
|
end
|
22
57
|
|
23
58
|
# Dataset class for AS400 datasets accessed via JDBC.
|
24
59
|
class Dataset < JDBC::Dataset
|
25
60
|
WILDCARD = Sequel::LiteralString.new('*').freeze
|
26
61
|
|
27
|
-
# AS400 needs to use a couple of subselects for
|
62
|
+
# AS400 needs to use a couple of subselects for queries with offsets.
|
28
63
|
def select_sql
|
29
|
-
return super unless
|
30
|
-
|
64
|
+
return super unless o = @opts[:offset]
|
65
|
+
l = @opts[:limit]
|
31
66
|
order = @opts[:order]
|
32
67
|
dsa1 = dataset_alias(1)
|
33
68
|
dsa2 = dataset_alias(2)
|
34
69
|
rn = row_number_column
|
35
70
|
irn = Sequel::SQL::Identifier.new(rn).qualify(dsa2)
|
36
71
|
subselect_sql(unlimited.
|
37
|
-
|
38
|
-
|
72
|
+
from_self(:alias=>dsa1).
|
73
|
+
select_more(Sequel::SQL::QualifiedIdentifier.new(dsa1, WILDCARD),
|
39
74
|
Sequel::SQL::WindowFunction.new(SQL::Function.new(:ROW_NUMBER), Sequel::SQL::Window.new(:order=>order)).as(rn)).
|
40
|
-
|
41
|
-
|
42
|
-
|
75
|
+
from_self(:alias=>dsa2).
|
76
|
+
select(Sequel::SQL::QualifiedIdentifier.new(dsa2, WILDCARD)).
|
77
|
+
where(l ? ((irn > o) & (irn <= l + o)) : (irn > o))) # Leave off limit in case of limit(nil, offset)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Modify the sql to limit the number of rows returned
|
81
|
+
def select_limit_sql(sql)
|
82
|
+
if @opts[:limit]
|
83
|
+
sql << " FETCH FIRST ROW ONLY" if @opts[:limit] == 1
|
84
|
+
sql << " FETCH FIRST #{@opts[:limit]} ROWS ONLY" if @opts[:limit] > 1
|
85
|
+
end
|
43
86
|
end
|
44
87
|
|
45
88
|
def supports_window_functions?
|
@@ -93,6 +93,7 @@ module Sequel
|
|
93
93
|
conn = Mysql.init
|
94
94
|
conn.options(Mysql::READ_DEFAULT_GROUP, opts[:config_default_group] || "client")
|
95
95
|
conn.options(Mysql::OPT_LOCAL_INFILE, opts[:config_local_infile]) if opts.has_key?(:config_local_infile)
|
96
|
+
conn.ssl_set(opts[:sslkey], opts[:sslcert], opts[:sslca], opts[:sslcapath], opts[:sslcipher]) if opts[:sslca] || opts[:sslkey]
|
96
97
|
if encoding = opts[:encoding] || opts[:charset]
|
97
98
|
# Set encoding before connecting so that the mysql driver knows what
|
98
99
|
# encoding we want to use, but this can be overridden by READ_DEFAULT_GROUP.
|
@@ -116,8 +117,9 @@ module Sequel
|
|
116
117
|
# that feature.
|
117
118
|
sqls << "SET NAMES #{literal(encoding.to_s)}" if encoding
|
118
119
|
|
119
|
-
#
|
120
|
-
|
120
|
+
# Increase timeout so mysql server doesn't disconnect us
|
121
|
+
# Value used by default is maximum allowed value on Windows.
|
122
|
+
sqls << "SET @@wait_timeout = #{opts[:timeout] || 2147483}"
|
121
123
|
|
122
124
|
# By default, MySQL 'where id is null' selects the last inserted id
|
123
125
|
sqls << "SET SQL_AUTO_IS_NULL=0" unless opts[:auto_is_null]
|
@@ -46,8 +46,9 @@ module Sequel
|
|
46
46
|
sqls << "SET NAMES #{conn.escape(encoding.to_s)}"
|
47
47
|
end
|
48
48
|
|
49
|
-
#
|
50
|
-
|
49
|
+
# Increase timeout so mysql server doesn't disconnect us.
|
50
|
+
# Value used by default is maximum allowed value on Windows.
|
51
|
+
sqls << "SET @@wait_timeout = #{opts[:timeout] || 2147483}"
|
51
52
|
|
52
53
|
# By default, MySQL 'where id is null' selects the last inserted id
|
53
54
|
sqls << "SET SQL_AUTO_IS_NULL=0" unless opts[:auto_is_null]
|
@@ -30,6 +30,15 @@ module Sequel
|
|
30
30
|
from(:tab).filter(:tname =>dataset.send(:input_identifier, name), :tabtype => 'TABLE').count > 0
|
31
31
|
end
|
32
32
|
|
33
|
+
def views(opts={})
|
34
|
+
ds = from(:tab).server(opts[:server]).select(:tname).filter(:tabtype => 'VIEW')
|
35
|
+
ds.map{|r| ds.send(:output_identifier, r[:tname])}
|
36
|
+
end
|
37
|
+
|
38
|
+
def view_exists?(name)
|
39
|
+
from(:tab).filter(:tname =>dataset.send(:input_identifier, name), :tabtype => 'VIEW').count > 0
|
40
|
+
end
|
41
|
+
|
33
42
|
private
|
34
43
|
|
35
44
|
def auto_increment_sql
|
@@ -64,7 +64,7 @@ module Sequel
|
|
64
64
|
synchronize(opts[:server]) do |conn|
|
65
65
|
begin
|
66
66
|
res = nil
|
67
|
-
log_yield(sql){res = conn.
|
67
|
+
log_yield(sql){conn.execute(sql); res = conn.results}
|
68
68
|
yield res if block_given?
|
69
69
|
nil
|
70
70
|
rescue SwiftError => e
|
@@ -92,9 +92,12 @@ module Sequel
|
|
92
92
|
def execute_insert(sql, opts={})
|
93
93
|
synchronize(opts[:server]) do |conn|
|
94
94
|
begin
|
95
|
-
|
95
|
+
res = nil
|
96
|
+
log_yield(sql){conn.execute(sql); (res = conn.results).insert_id}
|
96
97
|
rescue SwiftError => e
|
97
98
|
raise_error(e)
|
99
|
+
ensure
|
100
|
+
res.finish if res
|
98
101
|
end
|
99
102
|
end
|
100
103
|
end
|
@@ -46,6 +46,21 @@ module Sequel
|
|
46
46
|
Sequel::Swift::Postgres::Dataset.new(self, opts)
|
47
47
|
end
|
48
48
|
|
49
|
+
# Run the SELECT SQL on the database and yield the rows
|
50
|
+
def execute(sql, opts={})
|
51
|
+
synchronize(opts[:server]) do |conn|
|
52
|
+
begin
|
53
|
+
conn.execute(sql)
|
54
|
+
res = conn.results
|
55
|
+
yield res if block_given?
|
56
|
+
rescue SwiftError => e
|
57
|
+
raise_error(e)
|
58
|
+
ensure
|
59
|
+
res.finish if res
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
49
64
|
# Run the DELETE/UPDATE SQL on the database and return the number
|
50
65
|
# of matched rows.
|
51
66
|
def execute_dui(sql, opts={})
|
@@ -62,8 +77,12 @@ module Sequel
|
|
62
77
|
# for the record.
|
63
78
|
def execute_insert(sql, opts={})
|
64
79
|
synchronize(opts[:server]) do |conn|
|
65
|
-
|
66
|
-
|
80
|
+
begin
|
81
|
+
conn.execute(sql)
|
82
|
+
insert_result(conn, opts[:table], opts[:values])
|
83
|
+
rescue SwiftError => e
|
84
|
+
raise_error(e)
|
85
|
+
end
|
67
86
|
end
|
68
87
|
end
|
69
88
|
|
@@ -15,6 +15,8 @@ module Sequel
|
|
15
15
|
# Raises Sequel::AdapterNotFound if the adapter
|
16
16
|
# could not be loaded.
|
17
17
|
def self.adapter_class(scheme)
|
18
|
+
return scheme if scheme.is_a?(Class)
|
19
|
+
|
18
20
|
scheme = scheme.to_s.gsub('-', '_').to_sym
|
19
21
|
|
20
22
|
unless klass = ADAPTER_MAP[scheme]
|
@@ -58,7 +60,7 @@ module Sequel
|
|
58
60
|
end
|
59
61
|
when Hash
|
60
62
|
opts = conn_string.merge(opts)
|
61
|
-
c = adapter_class(opts[:adapter] || opts['adapter'])
|
63
|
+
c = adapter_class(opts[:adapter_class] || opts[:adapter] || opts['adapter'])
|
62
64
|
else
|
63
65
|
raise Error, "Sequel::Database.connect takes either a Hash or a String, given: #{conn_string.inspect}"
|
64
66
|
end
|
@@ -12,6 +12,11 @@ module Sequel
|
|
12
12
|
# Array of SQL loggers to use for this database.
|
13
13
|
attr_accessor :loggers
|
14
14
|
|
15
|
+
# Log level at which to log SQL queries. This is actually the method
|
16
|
+
# sent to the logger, so it should be the method name symbol. The default
|
17
|
+
# is :info, it can be set to :debug to log at DEBUG level.
|
18
|
+
attr_accessor :sql_log_level
|
19
|
+
|
15
20
|
# Log a message at level info to all loggers.
|
16
21
|
def log_info(message, args=nil)
|
17
22
|
log_each(:info, args ? "#{message}; #{args.inspect}" : message)
|
@@ -51,7 +56,7 @@ module Sequel
|
|
51
56
|
# Log message with message prefixed by duration at info level, or
|
52
57
|
# warn level if duration is greater than log_warn_duration.
|
53
58
|
def log_duration(duration, message)
|
54
|
-
log_each((lwd = log_warn_duration and duration >= lwd) ? :warn :
|
59
|
+
log_each((lwd = log_warn_duration and duration >= lwd) ? :warn : sql_log_level, "(#{sprintf('%0.6fs', duration)}) #{message}")
|
55
60
|
end
|
56
61
|
|
57
62
|
# Log message at level (which should be :error, :warn, or :info)
|
data/lib/sequel/database/misc.rb
CHANGED
@@ -32,6 +32,7 @@ module Sequel
|
|
32
32
|
# :quote_identifiers :: Whether to quote identifiers
|
33
33
|
# :servers :: A hash specifying a server/shard specific options, keyed by shard symbol
|
34
34
|
# :single_threaded :: Whether to use a single-threaded connection pool
|
35
|
+
# :sql_log_level :: Method to use to log SQL to a logger, :info by default.
|
35
36
|
#
|
36
37
|
# All options given are also passed to the connection pool. If a block
|
37
38
|
# is given, it is used as the connection_proc for the ConnectionPool.
|
@@ -43,6 +44,7 @@ module Sequel
|
|
43
44
|
@opts[:disconnection_proc] ||= proc{|conn| disconnect_connection(conn)}
|
44
45
|
block ||= proc{|server| connect(server)}
|
45
46
|
@opts[:servers] = {} if @opts[:servers].is_a?(String)
|
47
|
+
@opts[:adapter_class] = self.class
|
46
48
|
|
47
49
|
@opts[:single_threaded] = @single_threaded = typecast_value_boolean(@opts.fetch(:single_threaded, @@single_threaded))
|
48
50
|
@schemas = {}
|
@@ -52,6 +54,7 @@ module Sequel
|
|
52
54
|
@identifier_input_method = nil
|
53
55
|
@identifier_output_method = nil
|
54
56
|
@quote_identifiers = nil
|
57
|
+
self.sql_log_level = @opts[:sql_log_level] ? @opts[:sql_log_level].to_sym : :info
|
55
58
|
@pool = ConnectionPool.get_pool(@opts, &block)
|
56
59
|
|
57
60
|
::Sequel::DATABASES.push(self)
|
data/lib/sequel/dataset/graph.rb
CHANGED
@@ -15,7 +15,8 @@ module Sequel
|
|
15
15
|
# # SELECT ..., table.column AS some_alias
|
16
16
|
# # => {:table=>{:column=>some_alias_value, ...}, ...}
|
17
17
|
def add_graph_aliases(graph_aliases)
|
18
|
-
|
18
|
+
columns, graph_aliases = graph_alias_columns(graph_aliases)
|
19
|
+
ds = select_more(*columns)
|
19
20
|
ds.opts[:graph_aliases] = (ds.opts[:graph_aliases] || (ds.opts[:graph][:column_aliases] rescue {}) || {}).merge(graph_aliases)
|
20
21
|
ds
|
21
22
|
end
|
@@ -183,20 +184,25 @@ module Sequel
|
|
183
184
|
# graphing is used.
|
184
185
|
#
|
185
186
|
# graph_aliases :: Should be a hash with keys being symbols of
|
186
|
-
# column aliases, and values being arrays with
|
187
|
-
#
|
188
|
-
#
|
187
|
+
# column aliases, and values being either symbols or arrays with one to three elements.
|
188
|
+
# If the value is a symbol, it is assumed to be the same as a one element
|
189
|
+
# array containing that symbol.
|
190
|
+
# The first element of the array should be the table alias symbol.
|
191
|
+
# The second should be the actual column name symbol. If the array only
|
192
|
+
# has a single element the column name symbol will be assumed to be the
|
193
|
+
# same as the corresponding hash key. If the array
|
189
194
|
# has a third element, it is used as the value returned, instead of
|
190
195
|
# table_alias.column_name.
|
191
196
|
#
|
192
197
|
# DB[:artists].graph(:albums, :artist_id=>:id).
|
193
|
-
# set_graph_aliases(:
|
198
|
+
# set_graph_aliases(:name=>:artists,
|
194
199
|
# :album_name=>[:albums, :name],
|
195
200
|
# :forty_two=>[:albums, :fourtwo, 42]).first
|
196
|
-
# # SELECT artists.name
|
201
|
+
# # SELECT artists.name, albums.name AS album_name, 42 AS forty_two ...
|
197
202
|
# # => {:artists=>{:name=>artists.name}, :albums=>{:name=>albums.name, :fourtwo=>42}}
|
198
203
|
def set_graph_aliases(graph_aliases)
|
199
|
-
|
204
|
+
columns, graph_aliases = graph_alias_columns(graph_aliases)
|
205
|
+
ds = select(*columns)
|
200
206
|
ds.opts[:graph_aliases] = graph_aliases
|
201
207
|
ds
|
202
208
|
end
|
@@ -204,18 +210,25 @@ module Sequel
|
|
204
210
|
# Remove the splitting of results into subhashes, and all metadata
|
205
211
|
# related to the current graph (if any).
|
206
212
|
def ungraphed
|
207
|
-
clone(:graph=>nil)
|
213
|
+
clone(:graph=>nil, :graph_aliases=>nil)
|
208
214
|
end
|
209
215
|
|
210
216
|
private
|
211
217
|
|
212
|
-
# Transform the hash of graph aliases
|
218
|
+
# Transform the hash of graph aliases and return a two element array
|
219
|
+
# where the first element is an array of identifiers suitable to pass to
|
220
|
+
# a select method, and the second is a new hash of preprocessed graph aliases.
|
213
221
|
def graph_alias_columns(graph_aliases)
|
214
|
-
|
215
|
-
|
216
|
-
|
222
|
+
gas = {}
|
223
|
+
identifiers = graph_aliases.collect do |col_alias, tc|
|
224
|
+
table, column, value = Array(tc)
|
225
|
+
column ||= col_alias
|
226
|
+
gas[col_alias] = [table, column]
|
227
|
+
identifier = value || SQL::QualifiedIdentifier.new(table, column)
|
228
|
+
identifier = SQL::AliasedExpression.new(identifier, col_alias) if value or column != col_alias
|
217
229
|
identifier
|
218
230
|
end
|
231
|
+
[identifiers, gas]
|
219
232
|
end
|
220
233
|
|
221
234
|
# Fetch the rows, split them into component table parts,
|
@@ -97,7 +97,7 @@ module Sequel
|
|
97
97
|
def need_associated_primary_key?
|
98
98
|
false
|
99
99
|
end
|
100
|
-
|
100
|
+
|
101
101
|
# Returns the reciprocal association variable, if one exists. The reciprocal
|
102
102
|
# association is the association in the associated class that is the opposite
|
103
103
|
# of the current association. For example, Album.many_to_one :artist and
|
@@ -128,6 +128,12 @@ module Sequel
|
|
128
128
|
:"remove_all_#{self[:name]}"
|
129
129
|
end
|
130
130
|
|
131
|
+
# Whether associated objects need to be removed from the association before
|
132
|
+
# being destroyed in order to preserve referential integrity.
|
133
|
+
def remove_before_destroy?
|
134
|
+
true
|
135
|
+
end
|
136
|
+
|
131
137
|
# Name symbol for the remove_ association method
|
132
138
|
def remove_method
|
133
139
|
:"remove_#{singularize(self[:name])}"
|
@@ -144,7 +150,7 @@ module Sequel
|
|
144
150
|
true
|
145
151
|
end
|
146
152
|
|
147
|
-
# The columns to select when loading the association
|
153
|
+
# The columns to select when loading the association.
|
148
154
|
def select
|
149
155
|
self[:select]
|
150
156
|
end
|
@@ -259,22 +265,27 @@ module Sequel
|
|
259
265
|
self[:primary_key] ||= self[:model].primary_key
|
260
266
|
end
|
261
267
|
|
262
|
-
# One to many associations set the reciprocal to self when loading associated records.
|
263
|
-
def set_reciprocal_to_self?
|
264
|
-
true
|
265
|
-
end
|
266
|
-
|
267
268
|
# Whether the reciprocal of this association returns an array of objects instead of a single object,
|
268
269
|
# false for a one_to_many association.
|
269
270
|
def reciprocal_array?
|
270
271
|
false
|
271
272
|
end
|
272
273
|
|
274
|
+
# Destroying one_to_many associated objects automatically deletes the foreign key.
|
275
|
+
def remove_before_destroy?
|
276
|
+
false
|
277
|
+
end
|
278
|
+
|
273
279
|
# The one_to_many association needs to check that an object to be removed already is associated.
|
274
280
|
def remove_should_check_existing?
|
275
281
|
true
|
276
282
|
end
|
277
283
|
|
284
|
+
# One to many associations set the reciprocal to self when loading associated records.
|
285
|
+
def set_reciprocal_to_self?
|
286
|
+
true
|
287
|
+
end
|
288
|
+
|
278
289
|
private
|
279
290
|
|
280
291
|
# The reciprocal type of a one_to_many association is a many_to_one association.
|
data/lib/sequel/model/base.rb
CHANGED
@@ -1236,7 +1236,7 @@ module Sequel
|
|
1236
1236
|
@columns_updated = @values.reject{|k, v| !columns.include?(k)}
|
1237
1237
|
changed_columns.reject!{|c| columns.include?(c)}
|
1238
1238
|
end
|
1239
|
-
|
1239
|
+
_update_columns(@columns_updated)
|
1240
1240
|
@this = nil
|
1241
1241
|
after_update
|
1242
1242
|
after_save
|
@@ -1264,7 +1264,14 @@ module Sequel
|
|
1264
1264
|
Array(primary_key).each{|x| v.delete(x) unless changed_columns.include?(x)}
|
1265
1265
|
v
|
1266
1266
|
end
|
1267
|
-
|
1267
|
+
|
1268
|
+
# Call _update with the given columns, if any are present.
|
1269
|
+
# Plugins can override this method in order to update with
|
1270
|
+
# additional columns, even when the column hash is initially empty.
|
1271
|
+
def _update_columns(columns)
|
1272
|
+
_update(columns) unless columns.empty?
|
1273
|
+
end
|
1274
|
+
|
1268
1275
|
# Update this instance's dataset with the supplied column hash.
|
1269
1276
|
def _update(columns)
|
1270
1277
|
n = _update_dataset.update(columns)
|
@@ -24,7 +24,8 @@ module Sequel
|
|
24
24
|
#
|
25
25
|
# The identity_map plugin is not compatible with the standard eager loading of
|
26
26
|
# many_to_many and many_through_many associations. If you want to use the identity_map plugin,
|
27
|
-
# you should use +eager_graph+ instead of +eager+ for those associations.
|
27
|
+
# you should use +eager_graph+ instead of +eager+ for those associations. It is also
|
28
|
+
# not compatible with the eager loading in the +rcte_tree+ plugin.
|
28
29
|
#
|
29
30
|
# Usage:
|
30
31
|
#
|
@@ -141,8 +141,8 @@ module Sequel
|
|
141
141
|
# array of symbols for a composite key association.
|
142
142
|
# * :uniq - Adds a after_load callback that makes the array of objects unique.
|
143
143
|
def many_through_many(name, through, opts={}, &block)
|
144
|
-
associate(:many_through_many, name, opts.merge(:through=>through), &block)
|
145
|
-
end
|
144
|
+
associate(:many_through_many, name, opts.merge(through.is_a?(Hash) ? through : {:through=>through}), &block)
|
145
|
+
end
|
146
146
|
|
147
147
|
private
|
148
148
|
|
@@ -123,15 +123,18 @@ module Sequel
|
|
123
123
|
end
|
124
124
|
|
125
125
|
# Remove the matching associated object from the current object.
|
126
|
-
# If the :destroy option is given, destroy the object after disassociating it
|
126
|
+
# If the :destroy option is given, destroy the object after disassociating it
|
127
|
+
# (unless destroying the object would automatically disassociate it).
|
127
128
|
# Returns the object removed, if it exists.
|
128
129
|
def nested_attributes_remove(reflection, pk, opts={})
|
129
130
|
if obj = nested_attributes_find(reflection, pk)
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
131
|
+
if !opts[:destroy] || reflection.remove_before_destroy?
|
132
|
+
before_save_hook do
|
133
|
+
if reflection.returns_array?
|
134
|
+
send(reflection.remove_method, obj)
|
135
|
+
else
|
136
|
+
send(reflection.setter_method, nil)
|
137
|
+
end
|
135
138
|
end
|
136
139
|
end
|
137
140
|
after_save_hook{obj.destroy} if opts[:destroy]
|
@@ -205,9 +205,9 @@ module Sequel
|
|
205
205
|
klass.from_xml_node(node)
|
206
206
|
end
|
207
207
|
elsif cols.include?(k)
|
208
|
-
self[k.to_sym] = node.children.first.to_s
|
208
|
+
self[k.to_sym] = node[:nil] ? nil : node.children.first.to_s
|
209
209
|
elsif meths.include?("#{k}=")
|
210
|
-
send("#{k}=", node.children.first.to_s)
|
210
|
+
send("#{k}=", node[:nil] ? nil : node.children.first.to_s)
|
211
211
|
else
|
212
212
|
raise Error, "Entry in XML not an association or column and no setter method exists: #{k}"
|
213
213
|
end
|
@@ -268,7 +268,15 @@ module Sequel
|
|
268
268
|
x = model.xml_builder(opts)
|
269
269
|
x.send(name_proc[opts.fetch(:root_name, model.send(:underscore, model.name)).to_s]) do |x1|
|
270
270
|
cols.each do |c|
|
271
|
-
|
271
|
+
attrs = {}
|
272
|
+
if types
|
273
|
+
attrs[:type] = db_schema.fetch(c, {})[:type]
|
274
|
+
end
|
275
|
+
v = vals[c]
|
276
|
+
if v.nil?
|
277
|
+
attrs[:nil] = ''
|
278
|
+
end
|
279
|
+
x1.send(name_proc[c.to_s], v, attrs)
|
272
280
|
end
|
273
281
|
if inc.is_a?(Hash)
|
274
282
|
inc.each{|k, v| to_xml_include(x1, k, v)}
|
data/lib/sequel/version.rb
CHANGED
@@ -3,7 +3,7 @@ module Sequel
|
|
3
3
|
MAJOR = 3
|
4
4
|
# The minor version of Sequel. Bumped for every non-patch level
|
5
5
|
# release, generally around once a month.
|
6
|
-
MINOR =
|
6
|
+
MINOR = 17
|
7
7
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
8
8
|
# releases that fix regressions from previous versions.
|
9
9
|
TINY = 0
|
data/spec/core/database_spec.rb
CHANGED
@@ -23,6 +23,11 @@ context "A new Database" do
|
|
23
23
|
Sequel::Database.new(1 => 2, :logger => [4], :loggers => [3]).loggers.should == [4,3]
|
24
24
|
end
|
25
25
|
|
26
|
+
specify "should set the sql_log_level from opts[:sql_log_level]" do
|
27
|
+
db = Sequel::Database.new(1 => 2, :sql_log_level=>:debug).sql_log_level.should == :debug
|
28
|
+
db = Sequel::Database.new(1 => 2, :sql_log_level=>'debug').sql_log_level.should == :debug
|
29
|
+
end
|
30
|
+
|
26
31
|
specify "should create a connection pool" do
|
27
32
|
@db.pool.should be_a_kind_of(Sequel::ConnectionPool)
|
28
33
|
@db.pool.max_size.should == 4
|
@@ -262,6 +267,15 @@ context "Database#log_yield" do
|
|
262
267
|
@o.logs.first.last.should =~ /\A\(\d\.\d{6}s\) blah\z/
|
263
268
|
end
|
264
269
|
|
270
|
+
specify "should respect sql_log_level setting" do
|
271
|
+
@db.sql_log_level = :debug
|
272
|
+
@db.log_yield('blah'){}
|
273
|
+
@o.logs.length.should == 1
|
274
|
+
@o.logs.first.length.should == 2
|
275
|
+
@o.logs.first.first.should == :debug
|
276
|
+
@o.logs.first.last.should =~ /\A\(\d\.\d{6}s\) blah\z/
|
277
|
+
end
|
278
|
+
|
265
279
|
specify "should log message with duration at warn level if duration greater than log_warn_duration" do
|
266
280
|
@db.log_warn_duration = 0
|
267
281
|
@db.log_yield('blah'){}
|
@@ -954,19 +968,19 @@ context "A Database adapter with a scheme" do
|
|
954
968
|
c = Sequel.ccc('mydb')
|
955
969
|
p = proc{c.opts.delete_if{|k,v| k == :disconnection_proc || k == :single_threaded}}
|
956
970
|
c.should be_a_kind_of(CCC)
|
957
|
-
p.call.should == {:adapter=>:ccc, :database => 'mydb'}
|
971
|
+
p.call.should == {:adapter=>:ccc, :database => 'mydb', :adapter_class=>CCC}
|
958
972
|
|
959
973
|
c = Sequel.ccc('mydb', :host => 'localhost')
|
960
974
|
c.should be_a_kind_of(CCC)
|
961
|
-
p.call.should == {:adapter=>:ccc, :database => 'mydb', :host => 'localhost'}
|
975
|
+
p.call.should == {:adapter=>:ccc, :database => 'mydb', :host => 'localhost', :adapter_class=>CCC}
|
962
976
|
|
963
977
|
c = Sequel.ccc
|
964
978
|
c.should be_a_kind_of(CCC)
|
965
|
-
p.call.should == {:adapter=>:ccc}
|
979
|
+
p.call.should == {:adapter=>:ccc, :adapter_class=>CCC}
|
966
980
|
|
967
981
|
c = Sequel.ccc(:database => 'mydb', :host => 'localhost')
|
968
982
|
c.should be_a_kind_of(CCC)
|
969
|
-
p.call.should == {:adapter=>:ccc, :database => 'mydb', :host => 'localhost'}
|
983
|
+
p.call.should == {:adapter=>:ccc, :database => 'mydb', :host => 'localhost', :adapter_class=>CCC}
|
970
984
|
end
|
971
985
|
|
972
986
|
specify "should be accessible through Sequel.connect with options" do
|
@@ -1484,6 +1498,31 @@ context "Database#remove_servers" do
|
|
1484
1498
|
end
|
1485
1499
|
end
|
1486
1500
|
|
1501
|
+
context "Database#each_server with do/jdbc adapter connection string without :adapter option" do
|
1502
|
+
before do
|
1503
|
+
klass = Class.new(Sequel::Database)
|
1504
|
+
klass.should_receive(:adapter_class).once.with(:jdbc).and_return(MockDatabase)
|
1505
|
+
@db = klass.connect('jdbc:blah:', :host=>1, :database=>2, :servers=>{:server1=>{:host=>3}})
|
1506
|
+
def @db.connect(server)
|
1507
|
+
server_opts(server)
|
1508
|
+
end
|
1509
|
+
def @db.disconnect_connection(c)
|
1510
|
+
end
|
1511
|
+
end
|
1512
|
+
|
1513
|
+
specify "should yield a separate database object for each server" do
|
1514
|
+
hosts = []
|
1515
|
+
@db.each_server do |db|
|
1516
|
+
db.should be_a_kind_of(Sequel::Database)
|
1517
|
+
db.should_not == @db
|
1518
|
+
db.opts[:adapter_class].should == MockDatabase
|
1519
|
+
db.opts[:database].should == 2
|
1520
|
+
hosts << db.opts[:host]
|
1521
|
+
end
|
1522
|
+
hosts.sort.should == [1, 3]
|
1523
|
+
end
|
1524
|
+
end
|
1525
|
+
|
1487
1526
|
context "Database#each_server" do
|
1488
1527
|
before do
|
1489
1528
|
@db = Sequel.connect(:adapter=>:mock, :host=>1, :database=>2, :servers=>{:server1=>{:host=>3}, :server2=>{:host=>4}})
|
@@ -160,6 +160,20 @@ describe Sequel::Dataset, " graphing" do
|
|
160
160
|
].should(include(ds.sql))
|
161
161
|
end
|
162
162
|
|
163
|
+
it "#set_graph_aliases should allow a single array entry to specify a table, assuming the same column as the key" do
|
164
|
+
ds = @ds1.graph(:lines, :x=>:id).set_graph_aliases(:x=>[:points], :y=>[:lines])
|
165
|
+
['SELECT points.x, lines.y FROM points LEFT OUTER JOIN lines ON (lines.x = points.id)',
|
166
|
+
'SELECT lines.y, points.x FROM points LEFT OUTER JOIN lines ON (lines.x = points.id)'
|
167
|
+
].should(include(ds.sql))
|
168
|
+
end
|
169
|
+
|
170
|
+
it "#set_graph_aliases should allow hash values to be symbols specifying table, assuming the same column as the key" do
|
171
|
+
ds = @ds1.graph(:lines, :x=>:id).set_graph_aliases(:x=>:points, :y=>:lines)
|
172
|
+
['SELECT points.x, lines.y FROM points LEFT OUTER JOIN lines ON (lines.x = points.id)',
|
173
|
+
'SELECT lines.y, points.x FROM points LEFT OUTER JOIN lines ON (lines.x = points.id)'
|
174
|
+
].should(include(ds.sql))
|
175
|
+
end
|
176
|
+
|
163
177
|
it "#set_graph_aliases should only alias columns if necessary" do
|
164
178
|
ds = @ds1.set_graph_aliases(:x=>[:points, :x], :y=>[:lines, :y])
|
165
179
|
['SELECT points.x, lines.y FROM points',
|
@@ -282,6 +296,26 @@ describe Sequel::Dataset, " graphing" do
|
|
282
296
|
results.first.should == {:points=>{:z1=>2}, :lines=>{:z2=>3}}
|
283
297
|
end
|
284
298
|
|
299
|
+
it "#graph_each should correctly map values when #set_graph_aliases is used with a single argument for each entry" do
|
300
|
+
ds = @ds1.graph(:lines, :x=>:id).set_graph_aliases(:x=>[:points], :y=>[:lines])
|
301
|
+
def ds.fetch_rows(sql, &block)
|
302
|
+
yield({:x=>2,:y=>3})
|
303
|
+
end
|
304
|
+
results = ds.all
|
305
|
+
results.length.should == 1
|
306
|
+
results.first.should == {:points=>{:x=>2}, :lines=>{:y=>3}}
|
307
|
+
end
|
308
|
+
|
309
|
+
it "#graph_each should correctly map values when #set_graph_aliases is used with a symbol for each entry" do
|
310
|
+
ds = @ds1.graph(:lines, :x=>:id).set_graph_aliases(:x=>:points, :y=>:lines)
|
311
|
+
def ds.fetch_rows(sql, &block)
|
312
|
+
yield({:x=>2,:y=>3})
|
313
|
+
end
|
314
|
+
results = ds.all
|
315
|
+
results.length.should == 1
|
316
|
+
results.first.should == {:points=>{:x=>2}, :lines=>{:y=>3}}
|
317
|
+
end
|
318
|
+
|
285
319
|
it "#graph_each should run the row_proc for graphed datasets" do
|
286
320
|
@ds1.row_proc = proc{|h| h.keys.each{|k| h[k] *= 2}; h}
|
287
321
|
@ds2.row_proc = proc{|h| h.keys.each{|k| h[k] *= 3}; h}
|
@@ -1,5 +1,8 @@
|
|
1
1
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper')
|
2
2
|
|
3
|
+
if defined?(ActiveSupport::Inflector)
|
4
|
+
skip_warn "inflector extension: active_support inflector loaded"
|
5
|
+
else
|
3
6
|
describe String do
|
4
7
|
it "#camelize and #camelcase should transform the word to CamelCase" do
|
5
8
|
"egg_and_hams".camelize.should == "EggAndHams"
|
@@ -179,3 +182,4 @@ describe 'Default inflections' do
|
|
179
182
|
end
|
180
183
|
end
|
181
184
|
end
|
185
|
+
end
|
@@ -42,6 +42,25 @@ describe Sequel::Model, "many_through_many" do
|
|
42
42
|
proc{@c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], :album_tags]}.should raise_error(Sequel::Error)
|
43
43
|
end
|
44
44
|
|
45
|
+
it "should allow only two arguments with the :through option" do
|
46
|
+
@c1.many_through_many :tags, :through=>[[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
|
47
|
+
n = @c1.load(:id => 1234)
|
48
|
+
a = n.tags_dataset
|
49
|
+
a.should be_a_kind_of(Sequel::Dataset)
|
50
|
+
a.sql.should == 'SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1234))'
|
51
|
+
n.tags.should == [@c2.load(:id=>1)]
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should be clonable" do
|
55
|
+
@c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
|
56
|
+
@c1.many_through_many :other_tags, :clone=>:tags
|
57
|
+
n = @c1.load(:id => 1234)
|
58
|
+
a = n.other_tags_dataset
|
59
|
+
a.should be_a_kind_of(Sequel::Dataset)
|
60
|
+
a.sql.should == 'SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1234))'
|
61
|
+
n.tags.should == [@c2.load(:id=>1)]
|
62
|
+
end
|
63
|
+
|
45
64
|
it "should use join tables given" do
|
46
65
|
@c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
|
47
66
|
n = @c1.load(:id => 1234)
|
@@ -186,7 +186,7 @@ describe "NestedAttributes plugin" do
|
|
186
186
|
ar.set(:first_album_attributes=>{:id=>10, :_delete=>'t'})
|
187
187
|
@mods.should == []
|
188
188
|
ar.save
|
189
|
-
@mods.should == [[:u, :
|
189
|
+
@mods.should == [[:u, :artists, {:name=>"Ar"}, "(id = 20)"], [:d, :albums, "(id = 10)"]]
|
190
190
|
end
|
191
191
|
|
192
192
|
it "should support destroying one_to_many objects" do
|
@@ -196,7 +196,7 @@ describe "NestedAttributes plugin" do
|
|
196
196
|
ar.set(:albums_attributes=>[{:id=>10, :_delete=>'t'}])
|
197
197
|
@mods.should == []
|
198
198
|
ar.save
|
199
|
-
@mods.should == [[:u, :
|
199
|
+
@mods.should == [[:u, :artists, {:name=>"Ar"}, '(id = 20)'], [:d, :albums, '(id = 10)']]
|
200
200
|
end
|
201
201
|
|
202
202
|
it "should support destroying many_to_many objects" do
|
@@ -20,6 +20,14 @@ describe "optimistic_locking plugin" do
|
|
20
20
|
else
|
21
21
|
0
|
22
22
|
end
|
23
|
+
when /UPDATE people SET #{lv} = (\d+) WHERE \(\(id = (\d+)\) AND \(#{lv} = (\d+)\)\)/
|
24
|
+
m = h[$2.to_i]
|
25
|
+
if m && m[:lock_version] == $3.to_i
|
26
|
+
m.merge!(:lock_version=>$1.to_i)
|
27
|
+
1
|
28
|
+
else
|
29
|
+
0
|
30
|
+
end
|
23
31
|
else
|
24
32
|
puts update_sql(opts)
|
25
33
|
end
|
@@ -96,5 +104,22 @@ describe "optimistic_locking plugin" do
|
|
96
104
|
p2 = c[1]
|
97
105
|
p1.update(:name=>'Jim')
|
98
106
|
proc{p2.update(:name=>'Bob')}.should raise_error(Sequel::Plugins::OptimisticLocking::Error)
|
99
|
-
end
|
107
|
+
end
|
108
|
+
|
109
|
+
specify "should increment the lock column when #modified! even if no columns are changed" do
|
110
|
+
p1 = @c[1]
|
111
|
+
p1.modified!
|
112
|
+
lv = p1.lock_version
|
113
|
+
p1.save_changes
|
114
|
+
p1.lock_version.should == lv + 1
|
115
|
+
end
|
116
|
+
|
117
|
+
specify "should not increment the lock column when the update fails" do
|
118
|
+
@c.dataset.meta_def(:update) { raise Exception }
|
119
|
+
p1 = @c[1]
|
120
|
+
p1.modified!
|
121
|
+
lv = p1.lock_version
|
122
|
+
proc{p1.save_changes}.should raise_error(Exception)
|
123
|
+
p1.lock_version.should == lv
|
124
|
+
end
|
100
125
|
end
|
@@ -36,6 +36,16 @@ describe "Sequel::Plugins::XmlSerializer" do
|
|
36
36
|
Album.from_xml(@album.to_xml).should == @album
|
37
37
|
end
|
38
38
|
|
39
|
+
it "should round trip successfully with empty strings" do
|
40
|
+
artist = Artist.load(:id=>2, :name=>'')
|
41
|
+
Artist.from_xml(artist.to_xml).should == artist
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should round trip successfully with nil values" do
|
45
|
+
artist = Artist.load(:id=>2, :name=>nil)
|
46
|
+
Artist.from_xml(artist.to_xml).should == artist
|
47
|
+
end
|
48
|
+
|
39
49
|
it "should handle the :only option" do
|
40
50
|
Artist.from_xml(@artist.to_xml(:only=>:name)).should == Artist.load(:name=>@artist.name)
|
41
51
|
Album.from_xml(@album.to_xml(:only=>[:id, :name])).should == Album.load(:id=>@album.id, :name=>@album.name)
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 67
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 3
|
8
|
-
-
|
8
|
+
- 17
|
9
9
|
- 0
|
10
|
-
version: 3.
|
10
|
+
version: 3.17.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Jeremy Evans
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-11-05 00:00:00 -07:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
@@ -80,6 +80,7 @@ extra_rdoc_files:
|
|
80
80
|
- doc/release_notes/3.14.0.txt
|
81
81
|
- doc/release_notes/3.15.0.txt
|
82
82
|
- doc/release_notes/3.16.0.txt
|
83
|
+
- doc/release_notes/3.17.0.txt
|
83
84
|
files:
|
84
85
|
- COPYING
|
85
86
|
- CHANGELOG
|
@@ -127,6 +128,7 @@ files:
|
|
127
128
|
- doc/release_notes/3.14.0.txt
|
128
129
|
- doc/release_notes/3.15.0.txt
|
129
130
|
- doc/release_notes/3.16.0.txt
|
131
|
+
- doc/release_notes/3.17.0.txt
|
130
132
|
- doc/sharding.rdoc
|
131
133
|
- doc/sql.rdoc
|
132
134
|
- doc/virtual_rows.rdoc
|