sequel 4.19.0 → 4.20.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +36 -0
- data/doc/association_basics.rdoc +27 -2
- data/doc/release_notes/4.20.0.txt +79 -0
- data/doc/schema_modification.rdoc +1 -1
- data/doc/sharding.rdoc +10 -0
- data/lib/sequel/adapters/mysql2.rb +2 -1
- data/lib/sequel/adapters/shared/mysql.rb +3 -1
- data/lib/sequel/adapters/shared/sqlite.rb +3 -9
- data/lib/sequel/adapters/swift.rb +9 -1
- data/lib/sequel/database/transactions.rb +13 -5
- data/lib/sequel/dataset/actions.rb +4 -1
- data/lib/sequel/dataset/query.rb +1 -1
- data/lib/sequel/extensions/constraint_validations.rb +1 -1
- data/lib/sequel/extensions/pg_enum.rb +19 -0
- data/lib/sequel/extensions/schema_dumper.rb +1 -1
- data/lib/sequel/model/associations.rb +26 -13
- data/lib/sequel/model/base.rb +4 -3
- data/lib/sequel/plugins/json_serializer.rb +23 -17
- data/lib/sequel/plugins/prepared_statements_associations.rb +1 -0
- data/lib/sequel/plugins/rcte_tree.rb +11 -2
- data/lib/sequel/plugins/static_cache.rb +8 -2
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mysql_spec.rb +7 -0
- data/spec/core/database_spec.rb +13 -0
- data/spec/core/dataset_spec.rb +5 -0
- data/spec/extensions/json_serializer_spec.rb +11 -0
- data/spec/extensions/pg_enum_spec.rb +29 -1
- data/spec/extensions/prepared_statements_associations_spec.rb +8 -0
- data/spec/extensions/rcte_tree_spec.rb +6 -1
- data/spec/extensions/schema_dumper_spec.rb +2 -1
- data/spec/extensions/static_cache_spec.rb +7 -1
- data/spec/integration/timezone_test.rb +3 -3
- data/spec/model/associations_spec.rb +17 -0
- data/spec/model/eager_loading_spec.rb +39 -0
- data/spec/model/record_spec.rb +9 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6daa0c1ef4f76e1d9bfdeedbf9208bf32646ab20
|
4
|
+
data.tar.gz: f2cf0e139a84f465fe1146de98b767fad029fca9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28f1f4b25bd2c5db83ce6630ebd9310524dafb59a60ce9e72e3c8e6d12f0a9ea9eff9e1e2ec492fa3f7f655b76ee51b6f5a8e233a36b80370d51922d3024f504
|
7
|
+
data.tar.gz: 7309df3435d53878e8aa1945fc728fedad302727d503af24c399285fbf511b70571ca7cbef4f9c1b5c615aac689d9971f8072b0c9e2fb77f89491410384be665
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,39 @@
|
|
1
|
+
=== 4.20.0 (2015-03-03)
|
2
|
+
|
3
|
+
* Restore the use of AUTOINCREMENT on SQLite (jeremyevans) (#965)
|
4
|
+
|
5
|
+
* Duplicate the associations hash when duplicating a model object (jeremyevans)
|
6
|
+
|
7
|
+
* Correctly apply association limit when eager loading with an eager block using default limit strategy on some databases (jeremyevans)
|
8
|
+
|
9
|
+
* Fix eager loading when using the :window_function limit strategy with an eager block and cascaded associations (jeremyevans)
|
10
|
+
|
11
|
+
* Add support for set_column_type :auto_increment=>true to add AUTO_INCREMENT to existing column on MySQL (jeremyevans) (#959)
|
12
|
+
|
13
|
+
* Add support for overridding the :instance_specific association option (jeremyevans)
|
14
|
+
|
15
|
+
* Recognize MSSQL bit type as boolean in the schema_dumper (jeremyevans)
|
16
|
+
|
17
|
+
* Skip eager loading queries if there are no matching keys (jeremyevans) (#952)
|
18
|
+
|
19
|
+
* Dataset#paged_each now returns an enumerator if not passed a block (jeremyevans)
|
20
|
+
|
21
|
+
* Use to_json :root option with string value as the JSON object key in the json_serializer plugin (jeremyevans)
|
22
|
+
|
23
|
+
* Allow create_enum in the pg_enum extension be reversible in migrations (celsworth) (#951)
|
24
|
+
|
25
|
+
* Have swift adapter respect database and application timezone settings (asppsa, jeremyevans) (#946)
|
26
|
+
|
27
|
+
* Don't have the static cache plugin attempt to validate objects (jeremyevans)
|
28
|
+
|
29
|
+
* Make freeze not validate objects if their errors are already frozen (jeremyevans)
|
30
|
+
|
31
|
+
* Only use prepared statements for associations if caching association metadata (jeremyevans)
|
32
|
+
|
33
|
+
* Set parent association when loading descendants in the rcte_tree plugin (jeremyevans)
|
34
|
+
|
35
|
+
* Add Database#transaction :before_retry option, specifying a proc to call before retrying (uhoh-itsmaciek) (#941)
|
36
|
+
|
1
37
|
=== 4.19.0 (2015-02-01)
|
2
38
|
|
3
39
|
* Make jdbc/sqlanywhere correctly set :auto_increment entry in schema hashes (jeremyevans)
|
data/doc/association_basics.rdoc
CHANGED
@@ -268,9 +268,9 @@ an example of a directed graph:
|
|
268
268
|
# :name \----- :predecessor_id
|
269
269
|
|
270
270
|
class Node
|
271
|
-
many_to_many :
|
271
|
+
many_to_many :direct_predecessors, :left_key=>:successor_id,
|
272
272
|
:right_key=>:predecessor_id, :join_table=>:edges, :class=>self
|
273
|
-
many_to_many :
|
273
|
+
many_to_many :direct_successors, :right_key=>:successor_id,
|
274
274
|
:left_key=>:predecessor_id, :join_table=>:edges, :class=>self
|
275
275
|
end
|
276
276
|
|
@@ -886,6 +886,22 @@ that returns all albums of an artist that went gold (sold at least
|
|
886
886
|
ds.where{copies_sold > 500000}
|
887
887
|
end
|
888
888
|
|
889
|
+
The result of the block is cached as an optimization. One of the side
|
890
|
+
effects of that is that if your block depends on external state, it won't
|
891
|
+
work correctly unless you setup a delayed evaluation. For example:
|
892
|
+
|
893
|
+
Artist.one_to_many :gold_albums, :class=>:Album do |ds|
|
894
|
+
ds.where{copies_sold > $gold_limit}
|
895
|
+
end
|
896
|
+
|
897
|
+
In this case if you change <tt>$gold_limit</tt> later, the changes won't
|
898
|
+
effect the association. If you want to pick up changes to <tt>$gold_limit</tt>,
|
899
|
+
you need to setup a delayed evaluation:
|
900
|
+
|
901
|
+
Artist.one_to_many :gold_albums, :class=>:Album do |ds|
|
902
|
+
ds.where{copies_sold > Sequel.delay{$gold_limit}}
|
903
|
+
end
|
904
|
+
|
889
905
|
==== :class
|
890
906
|
|
891
907
|
This is the class of the associated objects that will be used. It's
|
@@ -1719,6 +1735,15 @@ This is usually used if the association dataset depends on specific values in
|
|
1719
1735
|
model instance that would not be valid when eager loading for multiple
|
1720
1736
|
instances.
|
1721
1737
|
|
1738
|
+
==== :instance_specific
|
1739
|
+
|
1740
|
+
This allows you to override the setting of whether the dataset contains instance
|
1741
|
+
specific code. For example, if you are passing a block to the association,
|
1742
|
+
Sequel sets this to true by default, which disables some optimizations that
|
1743
|
+
would be invalid if the association is instance specific. If you know that the
|
1744
|
+
block does not contain instance specific code, you can set this to false to
|
1745
|
+
reenable the optimizations.
|
1746
|
+
|
1722
1747
|
==== :cartesian_product_number
|
1723
1748
|
|
1724
1749
|
The number of joins completed by this association that could cause more
|
@@ -0,0 +1,79 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A :before_retry option has been added to Database#transaction, which
|
4
|
+
specifies a proc to call when retrying if the :retry_on option
|
5
|
+
is used. This can be used to implement additional logging, sleeping
|
6
|
+
between retries, or other things.
|
7
|
+
|
8
|
+
* The to_json method :root option in the json_serializer plugin can now
|
9
|
+
be a string value to specify the name for the object key, instead of
|
10
|
+
using the underscored model name.
|
11
|
+
|
12
|
+
* Dataset#paged_each now returns an enumerator if not passed a block.
|
13
|
+
|
14
|
+
* You can now set the :instance_specific association option to false.
|
15
|
+
Previously, it was automatically set to true in some cases. If you
|
16
|
+
know the association does not depend on anything instance-specific
|
17
|
+
other than the foreign/primary key, setting this option can allow
|
18
|
+
Sequel to perform some additional optimizations.
|
19
|
+
|
20
|
+
= Other Improvements
|
21
|
+
|
22
|
+
* Eager loading queries are now skipped if there are no matching keys.
|
23
|
+
There was code to check this previously, but it was accidently
|
24
|
+
removed in an earlier refactoring.
|
25
|
+
|
26
|
+
* Eager loading an association with a limit and an eager block and
|
27
|
+
cascaded associations now works correctly when the window_function
|
28
|
+
limit strategy is used (the default on databases that support
|
29
|
+
window functions).
|
30
|
+
|
31
|
+
* Eager loading an association with a limit with an eager block now
|
32
|
+
works correctly on databases do not support window functions but do
|
33
|
+
support correlated subqueries.
|
34
|
+
|
35
|
+
* The parent association is now set on associated objects when loading
|
36
|
+
descendants in the rcte_tree plugin. This allows the parent method
|
37
|
+
on any of the descendants to work without issuing a database query.
|
38
|
+
|
39
|
+
* The prepared_statements_associations plugin now only uses prepared
|
40
|
+
statements if association metadata is being cached. Previously, it
|
41
|
+
would use prepared statements even if association metadata was not
|
42
|
+
cached, which could leak the prepared statements.
|
43
|
+
|
44
|
+
* Model#dup now duplicates the associations hash for the object.
|
45
|
+
|
46
|
+
* Model#freeze no longer validates an object if the the errors for the
|
47
|
+
object are already frozen. The static_cache plugin now freezes the
|
48
|
+
errors before freezing the object, so that it doesn't validate the
|
49
|
+
object. This can skip many database queries when the
|
50
|
+
auto_validations plugin is used and there is a unique constraint or
|
51
|
+
index on the related table.
|
52
|
+
|
53
|
+
* AUTOINCREMENT is now used again on SQLite by default for primary
|
54
|
+
keys. It was removed when :auto_increment was added to the schema
|
55
|
+
hashes, but the removal changed SQLite's behavior. This restores
|
56
|
+
the previous behavior.
|
57
|
+
|
58
|
+
* Microsoft SQL Server's bit type is now recognized as a boolean type
|
59
|
+
by the schema dumper.
|
60
|
+
|
61
|
+
* The pg_enum extension's create_enum method can now be used in
|
62
|
+
reversible migrations.
|
63
|
+
|
64
|
+
* set_column_type with the :auto_increment=>true option once again
|
65
|
+
works on MySQL. It had been broken since Sequel started adding
|
66
|
+
:auto_increment to the schema hashes.
|
67
|
+
|
68
|
+
* The mysql2 adapter now recognizes the :charset option as a synonym
|
69
|
+
for :encoding.
|
70
|
+
|
71
|
+
* The swift adapter now respects database and application timezone
|
72
|
+
settings.
|
73
|
+
|
74
|
+
= Backwards Compatibility
|
75
|
+
|
76
|
+
* AssociationReflection#apply_ruby_eager_limit_strategy no longer
|
77
|
+
checks that the strategy is :ruby, callers are now expected to
|
78
|
+
check the value themselves. This should only matter if you are
|
79
|
+
using custom association types.
|
@@ -142,7 +142,7 @@ as it's third argument. A simple example is:
|
|
142
142
|
|
143
143
|
+foreign_key+ accepts the same options as +column+. For example, to have a unique foreign key with varchar(16) type:
|
144
144
|
|
145
|
-
foreign_key :column_name, :unique=>true, :type=>'varchar(16)'
|
145
|
+
foreign_key :column_name, :table, :unique=>true, :type=>'varchar(16)'
|
146
146
|
|
147
147
|
+foreign_key+ also accepts some specific options:
|
148
148
|
|
data/doc/sharding.rdoc
CHANGED
@@ -41,6 +41,16 @@ is the simplest configuration:
|
|
41
41
|
This will use the slave_server for SELECT queries and master_server for
|
42
42
|
other queries.
|
43
43
|
|
44
|
+
If you want to ensure your queries are going to a specific database, you
|
45
|
+
can force this for a given query by using the .server method and passing
|
46
|
+
the symbol name defined in the connect options. For example:
|
47
|
+
|
48
|
+
# Force the SELECT to run on the master
|
49
|
+
DB[:users].server(:default).all
|
50
|
+
|
51
|
+
# Force the SELECT to run on the read-only slave
|
52
|
+
DB[:users].server(:read_only).all
|
53
|
+
|
44
54
|
=== Multiple Read-Only Slaves, Single Master
|
45
55
|
|
46
56
|
Let's say you have 4 slave database servers with names slave_server0,
|
@@ -32,6 +32,7 @@ module Sequel
|
|
32
32
|
opts[:username] ||= opts.delete(:user)
|
33
33
|
opts[:flags] ||= 0
|
34
34
|
opts[:flags] |= ::Mysql2::Client::FOUND_ROWS if ::Mysql2::Client.const_defined?(:FOUND_ROWS)
|
35
|
+
opts[:encoding] ||= opts[:charset]
|
35
36
|
conn = ::Mysql2::Client.new(opts)
|
36
37
|
conn.query_options.merge!(:symbolize_keys=>true, :cache_rows=>false)
|
37
38
|
|
@@ -41,7 +42,7 @@ module Sequel
|
|
41
42
|
# in case the READ_DEFAULT_GROUP overrode the provided encoding.
|
42
43
|
# Doesn't work across implicit reconnects, but Sequel doesn't turn on
|
43
44
|
# that feature.
|
44
|
-
if encoding = opts[:encoding]
|
45
|
+
if encoding = opts[:encoding]
|
45
46
|
sqls.unshift("SET NAMES #{conn.escape(encoding.to_s)}")
|
46
47
|
end
|
47
48
|
|
@@ -215,7 +215,9 @@ module Sequel
|
|
215
215
|
raise Error, "cannot determine database type to use for CHANGE COLUMN operation"
|
216
216
|
end
|
217
217
|
opts = op.merge(opts)
|
218
|
-
|
218
|
+
if op.has_key?(:auto_increment)
|
219
|
+
opts[:auto_increment] = op[:auto_increment]
|
220
|
+
end
|
219
221
|
"CHANGE COLUMN #{quote_identifier(op[:name])} #{column_definition_sql(opts)}"
|
220
222
|
when :drop_constraint
|
221
223
|
case op[:type]
|
@@ -160,10 +160,8 @@ module Sequel
|
|
160
160
|
end
|
161
161
|
|
162
162
|
# Override the default setting for whether to use timezones in timestamps.
|
163
|
-
#
|
164
|
-
#
|
165
|
-
# using this method. It's possible that the default will change in a future version,
|
166
|
-
# so anyone relying on timezones in timestamps should set this to +true+.
|
163
|
+
# It is set to +false+ by default, as SQLite's date/time methods do not
|
164
|
+
# support timezones in timestamps.
|
167
165
|
attr_writer :use_timestamp_timezones
|
168
166
|
|
169
167
|
# SQLite supports timezones in timestamps, since it just stores them as strings,
|
@@ -282,11 +280,6 @@ module Sequel
|
|
282
280
|
end
|
283
281
|
end
|
284
282
|
|
285
|
-
# SQLite does not need to specify AUTOINCREMENT, integer primary keys are automatically auto incrementing.
|
286
|
-
def auto_increment_sql
|
287
|
-
''
|
288
|
-
end
|
289
|
-
|
290
283
|
def begin_new_transaction(conn, opts)
|
291
284
|
mode = opts[:mode] || @transaction_mode
|
292
285
|
sql = TRANSACTION_MODE[mode] or raise Error, "transaction :mode must be one of: :deferred, :immediate, :exclusive, nil"
|
@@ -353,6 +346,7 @@ module Sequel
|
|
353
346
|
cols.each do |c|
|
354
347
|
c[:default] = LiteralString.new(c[:default]) if c[:default]
|
355
348
|
c[:type] = c[:db_type]
|
349
|
+
c.delete(:auto_increment)
|
356
350
|
end
|
357
351
|
cols
|
358
352
|
end
|
@@ -135,10 +135,18 @@ module Sequel
|
|
135
135
|
@columns = res.fields.map do |c|
|
136
136
|
col_map[c] = output_identifier(c)
|
137
137
|
end
|
138
|
+
tz = db.timezone if Sequel.application_timezone
|
138
139
|
res.each do |r|
|
139
140
|
h = {}
|
140
141
|
r.each do |k, v|
|
141
|
-
h[col_map[k]] =
|
142
|
+
h[col_map[k]] = case v
|
143
|
+
when StringIO
|
144
|
+
SQL::Blob.new(v.read)
|
145
|
+
when DateTime
|
146
|
+
tz ? Sequel.database_to_application_timestamp(Sequel.send(:convert_input_datetime_no_offset, v, tz)) : v
|
147
|
+
else
|
148
|
+
v
|
149
|
+
end
|
142
150
|
end
|
143
151
|
yield h
|
144
152
|
end
|
@@ -45,6 +45,9 @@ module Sequel
|
|
45
45
|
# :num_retries :: The number of times to retry if the :retry_on option is used.
|
46
46
|
# The default is 5 times. Can be set to nil to retry indefinitely,
|
47
47
|
# but that is not recommended.
|
48
|
+
# :before_retry :: Proc to execute before rertrying if the :retry_on option is used.
|
49
|
+
# Called with two arguments: the number of retry attempts (counting
|
50
|
+
# the current one) and the error the last attempt failed with.
|
48
51
|
# :prepare :: A string to use as the transaction identifier for a
|
49
52
|
# prepared transaction (two-phase commit), if the database/adapter
|
50
53
|
# supports prepared transactions.
|
@@ -57,7 +60,8 @@ module Sequel
|
|
57
60
|
# :rollback :: Can the set to :reraise to reraise any Sequel::Rollback exceptions
|
58
61
|
# raised, or :always to always rollback even if no exceptions occur
|
59
62
|
# (useful for testing).
|
60
|
-
# :server :: The server to use for the transaction.
|
63
|
+
# :server :: The server to use for the transaction. Set to :default, :read_only, or
|
64
|
+
# whatever symbol you used in the connect string when naming your servers.
|
61
65
|
# :savepoint :: Whether to create a new savepoint for this transaction,
|
62
66
|
# only respected if the database/adapter supports savepoints. By
|
63
67
|
# default Sequel will reuse an existing transaction, so if you want to
|
@@ -73,13 +77,17 @@ module Sequel
|
|
73
77
|
# and :remote_write (9.2+).
|
74
78
|
def transaction(opts=OPTS, &block)
|
75
79
|
if retry_on = opts[:retry_on]
|
76
|
-
|
80
|
+
tot_retries = opts.fetch(:num_retries, 5)
|
81
|
+
num_retries = 0 unless tot_retries.nil?
|
77
82
|
begin
|
78
83
|
transaction(opts.merge(:retry_on=>nil, :retrying=>true), &block)
|
79
|
-
rescue *retry_on
|
84
|
+
rescue *retry_on => e
|
80
85
|
if num_retries
|
81
|
-
num_retries
|
82
|
-
|
86
|
+
num_retries += 1
|
87
|
+
if num_retries <= tot_retries
|
88
|
+
opts[:before_retry].call(num_retries, e) if opts[:before_retry]
|
89
|
+
retry
|
90
|
+
end
|
83
91
|
else
|
84
92
|
retry
|
85
93
|
end
|
@@ -449,7 +449,7 @@ module Sequel
|
|
449
449
|
# for using an approach with a limit and offset for every page. This can
|
450
450
|
# be set to :filter, which uses a limit and a filter that excludes
|
451
451
|
# rows from previous pages. In order for this strategy to work, you must be
|
452
|
-
# selecting the columns you are ordering by, and
|
452
|
+
# selecting the columns you are ordering by, and none of the columns can contain
|
453
453
|
# NULLs. Note that some Sequel adapters have optimized implementations that will
|
454
454
|
# use cursors or streaming regardless of the :strategy option used.
|
455
455
|
# :filter_values :: If the :strategy=>:filter option is used, this option should be a proc
|
@@ -486,6 +486,9 @@ module Sequel
|
|
486
486
|
unless @opts[:order]
|
487
487
|
raise Sequel::Error, "Dataset#paged_each requires the dataset be ordered"
|
488
488
|
end
|
489
|
+
unless block_given?
|
490
|
+
return enum_for(:paged_each, opts)
|
491
|
+
end
|
489
492
|
|
490
493
|
total_limit = @opts[:limit]
|
491
494
|
offset = @opts[:offset]
|
data/lib/sequel/dataset/query.rb
CHANGED
@@ -16,7 +16,7 @@ module Sequel
|
|
16
16
|
|
17
17
|
# Which options don't affect the SQL generation. Used by simple_select_all?
|
18
18
|
# to determine if this is a simple SELECT * FROM table.
|
19
|
-
NON_SQL_OPTIONS = [:server, :defaults, :overrides, :graph, :eager_graph, :graph_aliases]
|
19
|
+
NON_SQL_OPTIONS = [:server, :defaults, :overrides, :graph, :eager, :eager_graph, :graph_aliases]
|
20
20
|
|
21
21
|
# These symbols have _join methods created (e.g. inner_join) that
|
22
22
|
# call join_table with the symbol, passing along the arguments and
|
@@ -37,6 +37,15 @@
|
|
37
37
|
# DB[:table_name].get(:column_name)
|
38
38
|
# # ['value1', 'value2']
|
39
39
|
#
|
40
|
+
# If the migration extension is loaded before this one (the order is important),
|
41
|
+
# you can use create_enum in a reversible migration:
|
42
|
+
#
|
43
|
+
# Sequel.migration do
|
44
|
+
# change do
|
45
|
+
# create_enum(:type_name, %w'value1 value2 value3')
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
#
|
40
49
|
# Finally, typecasting for enums is setup to cast to strings, which
|
41
50
|
# allows you to use symbols in your model code. Similar, you can provide
|
42
51
|
# the enum values as symbols when creating enums using create_enum or
|
@@ -132,5 +141,15 @@ module Sequel
|
|
132
141
|
end
|
133
142
|
end
|
134
143
|
|
144
|
+
# support reversible create_enum statements if the migration extension is loaded
|
145
|
+
if defined?(MigrationReverser)
|
146
|
+
class MigrationReverser
|
147
|
+
private
|
148
|
+
def create_enum(name, _)
|
149
|
+
@actions << [:drop_enum, name]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
135
154
|
Database.register_extension(:pg_enum, Postgres::EnumDatabaseMethods)
|
136
155
|
end
|
@@ -31,7 +31,7 @@ module Sequel
|
|
31
31
|
{:type=>Bignum}
|
32
32
|
when /\A(?:real|float|double(?: precision)?|double\(\d+,\d+\)(?: unsigned)?)\z/o
|
33
33
|
{:type=>Float}
|
34
|
-
when 'boolean'
|
34
|
+
when 'boolean', 'bit'
|
35
35
|
{:type=>TrueClass}
|
36
36
|
when /\A(?:(?:tiny|medium|long|n)?text|clob)\z/o
|
37
37
|
{:type=>String, :text=>true}
|
@@ -144,15 +144,13 @@ module Sequel
|
|
144
144
|
# If the ruby eager limit strategy is being used, slice the array using the slice
|
145
145
|
# range to return the object(s) at the correct offset/limit.
|
146
146
|
def apply_ruby_eager_limit_strategy(rows)
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
rows.each{|o| o.associations[name] = o.associations[name][offset]}
|
155
|
-
end
|
147
|
+
name = self[:name]
|
148
|
+
if returns_array?
|
149
|
+
range = slice_range
|
150
|
+
rows.each{|o| o.associations[name] = o.associations[name][range] || []}
|
151
|
+
elsif slice_range
|
152
|
+
offset = slice_range.begin
|
153
|
+
rows.each{|o| o.associations[name] = o.associations[name][offset]}
|
156
154
|
end
|
157
155
|
end
|
158
156
|
|
@@ -241,18 +239,24 @@ module Sequel
|
|
241
239
|
def eager_load_results(eo, &block)
|
242
240
|
rows = eo[:rows]
|
243
241
|
initialize_association_cache(rows) unless eo[:initialize_rows] == false
|
242
|
+
if eo[:id_map]
|
243
|
+
ids = eo[:id_map].keys
|
244
|
+
return ids if ids.empty?
|
245
|
+
end
|
244
246
|
strategy = eager_limit_strategy
|
245
247
|
cascade = eo[:associations]
|
246
248
|
|
247
249
|
if eo[:eager_block] || eo[:loader] == false
|
248
250
|
strategy = true_eager_graph_limit_strategy if strategy == :union
|
251
|
+
# Correlated subqueries are not supported for regular eager loading
|
252
|
+
strategy = :ruby if strategy == :correlated_subquery
|
249
253
|
objects = apply_eager_limit_strategy(eager_loading_dataset(eo), strategy).all
|
250
254
|
elsif strategy == :union
|
251
255
|
objects = []
|
252
256
|
ds = associated_dataset
|
253
257
|
loader = union_eager_loader
|
254
258
|
joiner = " UNION ALL "
|
255
|
-
|
259
|
+
ids.each_slice(subqueries_per_union).each do |slice|
|
256
260
|
objects.concat(ds.with_sql(slice.map{|k| loader.sql(*k)}.join(joiner)).to_a)
|
257
261
|
end
|
258
262
|
ds = ds.eager(cascade) if cascade
|
@@ -260,11 +264,13 @@ module Sequel
|
|
260
264
|
else
|
261
265
|
loader = placeholder_eager_loader
|
262
266
|
loader = loader.with_dataset{|dataset| dataset.eager(cascade)} if cascade
|
263
|
-
objects = loader.all(
|
267
|
+
objects = loader.all(ids)
|
264
268
|
end
|
265
269
|
|
266
270
|
objects.each(&block)
|
267
|
-
|
271
|
+
if strategy == :ruby
|
272
|
+
apply_ruby_eager_limit_strategy(rows)
|
273
|
+
end
|
268
274
|
end
|
269
275
|
|
270
276
|
# The key to use for the key hash when eager loading
|
@@ -1651,7 +1657,7 @@ module Sequel
|
|
1651
1657
|
|
1652
1658
|
opts = orig_opts.merge(:type => type, :name => name, :cache=>({} if cache_associations), :model => self)
|
1653
1659
|
opts[:block] = block if block
|
1654
|
-
if block || orig_opts[:block] || orig_opts[:dataset]
|
1660
|
+
if !opts.has_key?(:instance_specific) && (block || orig_opts[:block] || orig_opts[:dataset])
|
1655
1661
|
# It's possible the association is instance specific, in that it depends on
|
1656
1662
|
# values other than the foreign key value. This needs to be checked for
|
1657
1663
|
# in certain places to disable optimizations.
|
@@ -2210,6 +2216,13 @@ module Sequel
|
|
2210
2216
|
end
|
2211
2217
|
end
|
2212
2218
|
|
2219
|
+
# Duplicate the associations hash when duplicating the object.
|
2220
|
+
def initialize_copy(other)
|
2221
|
+
super
|
2222
|
+
@associations = @associations.dup if @associations
|
2223
|
+
self
|
2224
|
+
end
|
2225
|
+
|
2213
2226
|
# Load the associated objects using the dataset, handling callbacks, reciprocals, and caching.
|
2214
2227
|
def load_associated_objects(opts, dynamic_opts=nil)
|
2215
2228
|
if dynamic_opts == true or dynamic_opts == false or dynamic_opts == nil
|
data/lib/sequel/model/base.rb
CHANGED
@@ -1329,9 +1329,10 @@ module Sequel
|
|
1329
1329
|
def freeze
|
1330
1330
|
values.freeze
|
1331
1331
|
changed_columns.freeze
|
1332
|
-
errors
|
1333
|
-
|
1334
|
-
|
1332
|
+
unless errors.frozen?
|
1333
|
+
validate
|
1334
|
+
errors.freeze
|
1335
|
+
end
|
1335
1336
|
this.freeze if !new? && model.primary_key
|
1336
1337
|
super
|
1337
1338
|
end
|
@@ -253,7 +253,9 @@ module Sequel
|
|
253
253
|
# :only :: Symbol or Array of Symbols of columns to only
|
254
254
|
# include in the JSON output, ignoring all other
|
255
255
|
# columns.
|
256
|
-
# :root :: Qualify the JSON with the name of the object.
|
256
|
+
# :root :: Qualify the JSON with the name of the object. If a
|
257
|
+
# string is given, use the string as the key, otherwise
|
258
|
+
# use an underscored version of the model's name.
|
257
259
|
def to_json(*a)
|
258
260
|
if opts = a.first.is_a?(Hash)
|
259
261
|
opts = model.json_serializer_opts.merge(a.first)
|
@@ -286,7 +288,12 @@ module Sequel
|
|
286
288
|
Array(inc).each{|c| h[c.to_s] = send(c)}
|
287
289
|
end
|
288
290
|
end
|
289
|
-
|
291
|
+
|
292
|
+
if root = opts[:root]
|
293
|
+
root = model.send(:underscore, model.to_s) unless root.is_a?(String)
|
294
|
+
h = {root => h}
|
295
|
+
end
|
296
|
+
|
290
297
|
Sequel.object_to_json(h, *a)
|
291
298
|
end
|
292
299
|
end
|
@@ -299,16 +306,13 @@ module Sequel
|
|
299
306
|
#
|
300
307
|
# :array :: An array of instances. If this is not provided,
|
301
308
|
# calls #all on the receiver to get the array.
|
302
|
-
# :root :: If set to :collection,
|
303
|
-
# in a root object
|
309
|
+
# :root :: If set to :collection, wraps the collection
|
310
|
+
# in a root object using the pluralized, underscored model
|
311
|
+
# name as the key. If set to :instance, only wraps
|
304
312
|
# the instances in a root object. If set to :both,
|
305
313
|
# wraps both the collection and instances in a root
|
306
|
-
# object.
|
307
|
-
#
|
308
|
-
# symbols, it defaults to both. That may change in a
|
309
|
-
# future version, so for forwards compatibility, you
|
310
|
-
# should pick a specific symbol for your desired
|
311
|
-
# behavior.
|
314
|
+
# object. If set to a string, wraps the collection in
|
315
|
+
# a root object using the string as the key.
|
312
316
|
def to_json(*a)
|
313
317
|
if opts = a.first.is_a?(Hash)
|
314
318
|
opts = model.json_serializer_opts.merge(a.first)
|
@@ -317,15 +321,17 @@ module Sequel
|
|
317
321
|
opts = model.json_serializer_opts
|
318
322
|
end
|
319
323
|
|
320
|
-
collection_root =
|
324
|
+
case collection_root = opts[:root]
|
321
325
|
when nil, false, :instance
|
322
|
-
false
|
323
|
-
when :both
|
324
|
-
true
|
326
|
+
collection_root = false
|
325
327
|
else
|
326
328
|
opts = opts.dup
|
327
|
-
|
328
|
-
|
329
|
+
unless collection_root == :both
|
330
|
+
opts.delete(:root)
|
331
|
+
end
|
332
|
+
unless collection_root.is_a?(String)
|
333
|
+
collection_root = model.send(:pluralize, model.send(:underscore, model.to_s))
|
334
|
+
end
|
329
335
|
end
|
330
336
|
|
331
337
|
res = if row_proc
|
@@ -341,7 +347,7 @@ module Sequel
|
|
341
347
|
end
|
342
348
|
|
343
349
|
if collection_root
|
344
|
-
Sequel.object_to_json({
|
350
|
+
Sequel.object_to_json({collection_root => res}, *a)
|
345
351
|
else
|
346
352
|
Sequel.object_to_json(res, *a)
|
347
353
|
end
|
@@ -50,6 +50,7 @@ module Sequel
|
|
50
50
|
# that, given appropriate bound variables, the prepared statement will work correctly for any
|
51
51
|
# instance. Return false if such a prepared statement cannot be created.
|
52
52
|
def association_prepared_statement(opts, assoc_bv)
|
53
|
+
return unless model.cache_associations
|
53
54
|
opts.send(:cached_fetch, :prepared_statement) do
|
54
55
|
unless opts[:instance_specific]
|
55
56
|
ds, bv = _associated_dataset(opts, {}).unbind
|
@@ -272,7 +272,11 @@ module Sequel
|
|
272
272
|
end
|
273
273
|
end
|
274
274
|
children_map.each do |parent_id, objs|
|
275
|
-
parent_map[parent_id]
|
275
|
+
parent_obj = parent_map[parent_id]
|
276
|
+
parent_obj.associations[childrena] = objs
|
277
|
+
objs.each do |obj|
|
278
|
+
obj.associations[parent] = parent_obj
|
279
|
+
end
|
276
280
|
end
|
277
281
|
end
|
278
282
|
end
|
@@ -333,7 +337,12 @@ module Sequel
|
|
333
337
|
(children_map[key_conv[obj]] ||= []) << obj
|
334
338
|
end
|
335
339
|
children_map.each do |parent_id, objs|
|
336
|
-
|
340
|
+
objs = objs.uniq
|
341
|
+
parent_obj = parent_map[parent_id]
|
342
|
+
parent_obj.associations[childrena] = objs
|
343
|
+
objs.each do |obj|
|
344
|
+
obj.associations[parent] = parent_obj
|
345
|
+
end
|
337
346
|
end
|
338
347
|
end
|
339
348
|
model.one_to_many descendants, d
|
@@ -12,7 +12,10 @@ module Sequel
|
|
12
12
|
# instances. This is slower as it requires creating new objects, but it allows
|
13
13
|
# you to make changes to the object and save them. If you set the option to false,
|
14
14
|
# you are responsible for updating the cache manually (the pg_static_cache_updater
|
15
|
-
# extension can handle this automatically).
|
15
|
+
# extension can handle this automatically). Note that it is not safe to use the
|
16
|
+
# :frozen=>false option if you are mutating column values directly. If you are
|
17
|
+
# mutating column values, you should also override Model.call to dup each mutable
|
18
|
+
# column value to ensure it is not shared by other instances.
|
16
19
|
#
|
17
20
|
# The caches this plugin creates are used for the following things:
|
18
21
|
#
|
@@ -180,7 +183,10 @@ module Sequel
|
|
180
183
|
def load_cache
|
181
184
|
a = dataset.all
|
182
185
|
h = {}
|
183
|
-
a.each
|
186
|
+
a.each do |o|
|
187
|
+
o.errors.freeze
|
188
|
+
h[o.pk.freeze] = o.freeze
|
189
|
+
end
|
184
190
|
@all = a.freeze
|
185
191
|
@cache = h.freeze
|
186
192
|
end
|
data/lib/sequel/version.rb
CHANGED
@@ -3,7 +3,7 @@ module Sequel
|
|
3
3
|
MAJOR = 4
|
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 = 20
|
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/adapters/mysql_spec.rb
CHANGED
@@ -90,6 +90,13 @@ describe "MySQL", '#create_table' do
|
|
90
90
|
@db.create_table!(:dolls){column :t, "enum('a', 'b', 'c', 'd')", :default=>'b'}
|
91
91
|
@db.schema(:dolls).first.last[:ruby_default].should == 'b'
|
92
92
|
end
|
93
|
+
|
94
|
+
specify "should allow setting auto_increment for existing column" do
|
95
|
+
@db.create_table(:dolls){Integer :a, :primary_key=>true}
|
96
|
+
@db.schema(:dolls).first.last[:auto_increment].should == false
|
97
|
+
@db.set_column_type :dolls, :a, Integer, :auto_increment=>true
|
98
|
+
@db.schema(:dolls).first.last[:auto_increment].should == true
|
99
|
+
end
|
93
100
|
end
|
94
101
|
|
95
102
|
if [:mysql, :mysql2].include?(DB.adapter_scheme)
|
data/spec/core/database_spec.rb
CHANGED
@@ -682,6 +682,19 @@ shared_examples_for "Database#transaction" do
|
|
682
682
|
a.should == [1] * 100
|
683
683
|
end
|
684
684
|
|
685
|
+
specify "should support :before_retry option for invoking callback before retrying" do
|
686
|
+
a, errs, calls = [], [], []
|
687
|
+
retryer = proc{|n, err| calls << n; errs << err }
|
688
|
+
@db.transaction(:retry_on=>Sequel::DatabaseDisconnectError, :before_retry => retryer) do
|
689
|
+
a << 1; raise Sequel::DatabaseDisconnectError if a.length < 3
|
690
|
+
end
|
691
|
+
@db.sqls.should == ['BEGIN', 'ROLLBACK', 'BEGIN', 'ROLLBACK', 'BEGIN', 'COMMIT']
|
692
|
+
a.should == [1, 1, 1]
|
693
|
+
errs.count.should == 2
|
694
|
+
errs.each { |e| e.class.should == Sequel::DatabaseDisconnectError }
|
695
|
+
calls.should == [1, 2]
|
696
|
+
end
|
697
|
+
|
685
698
|
specify "should raise an error if attempting to use :retry_on inside another transaction" do
|
686
699
|
proc{@db.transaction{@db.transaction(:retry_on=>Sequel::ConstraintViolation){}}}.should raise_error(Sequel::Error)
|
687
700
|
@db.sqls.should == ['BEGIN', 'ROLLBACK']
|
data/spec/core/dataset_spec.rb
CHANGED
@@ -4641,6 +4641,11 @@ describe "Dataset#paged_each" do
|
|
4641
4641
|
@rows.should == @db
|
4642
4642
|
end
|
4643
4643
|
|
4644
|
+
it "should return enumerator when called without block" do
|
4645
|
+
@ds.paged_each.each(&@proc)
|
4646
|
+
@rows.should == @db
|
4647
|
+
end
|
4648
|
+
|
4644
4649
|
it "should respect the row_proc" do
|
4645
4650
|
@ds.row_proc = lambda{|row| {:x=>row[:x]*2}}
|
4646
4651
|
@ds.paged_each(&@proc)
|
@@ -207,6 +207,11 @@ describe "Sequel::Plugins::JsonSerializer" do
|
|
207
207
|
@album.to_json(:root=>true, :only => :name).to_s.should == '{"album":{"name":"RF"}}'
|
208
208
|
end
|
209
209
|
|
210
|
+
it "should handle the :root option with a string to qualify single records using the string as the key" do
|
211
|
+
@album.to_json(:root=>"foo", :except => [:name, :artist_id]).to_s.should == '{"foo":{"id":1}}'
|
212
|
+
@album.to_json(:root=>"bar", :only => :name).to_s.should == '{"bar":{"name":"RF"}}'
|
213
|
+
end
|
214
|
+
|
210
215
|
it "should handle the :root=>:both option to qualify a dataset of records" do
|
211
216
|
Album.dataset._fetch = [{:id=>1, :name=>'RF'}, {:id=>1, :name=>'RF'}]
|
212
217
|
Album.dataset.to_json(:root=>:both, :only => :id).to_s.should == '{"albums":[{"album":{"id":1}},{"album":{"id":1}}]}'
|
@@ -223,6 +228,12 @@ describe "Sequel::Plugins::JsonSerializer" do
|
|
223
228
|
Album.dataset.to_json(:root=>:instance, :only => :id).to_s.should == '[{"album":{"id":1}},{"album":{"id":1}}]'
|
224
229
|
end
|
225
230
|
|
231
|
+
it "should handle the :root=>string option to qualify just the collection using the string as the key" do
|
232
|
+
Album.dataset._fetch = [{:id=>1, :name=>'RF'}, {:id=>1, :name=>'RF'}]
|
233
|
+
Album.dataset.to_json(:root=>"foos", :only => :id).to_s.should == '{"foos":[{"id":1},{"id":1}]}'
|
234
|
+
Album.dataset.to_json(:root=>"bars", :only => :id).to_s.should == '{"bars":[{"id":1},{"id":1}]}'
|
235
|
+
end
|
236
|
+
|
226
237
|
it "should store the default options in json_serializer_opts" do
|
227
238
|
Album.json_serializer_opts.should == {:naked=>true}
|
228
239
|
c = Class.new(Album)
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
2
2
|
|
3
|
-
|
3
|
+
Sequel.extension :migration
|
4
|
+
|
5
|
+
describe "pg_enum extension" do
|
4
6
|
before do
|
5
7
|
@db = Sequel.connect('mock://postgres', :quote_identifiers=>false)
|
6
8
|
@db.extend(Module.new do
|
@@ -61,4 +63,30 @@ describe "pg_inet extension" do
|
|
61
63
|
@db.add_enum_value(:foo, :a, :if_not_exists=>true)
|
62
64
|
@db.sqls.first.should == "ALTER TYPE foo ADD VALUE IF NOT EXISTS 'a'"
|
63
65
|
end
|
66
|
+
|
67
|
+
it "should reverse a create_enum directive in a migration" do
|
68
|
+
c = Class.new do
|
69
|
+
attr_reader :actions
|
70
|
+
def initialize(&block)
|
71
|
+
@actions = []
|
72
|
+
end
|
73
|
+
def method_missing(*args)
|
74
|
+
@actions << args
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
db = c.new
|
79
|
+
|
80
|
+
p = Proc.new do
|
81
|
+
create_enum(:type_name, %w'value1 value2 value3')
|
82
|
+
end
|
83
|
+
|
84
|
+
Sequel.migration{change(&p)}.apply(db, :up)
|
85
|
+
Sequel.migration{change(&p)}.apply(db, :down)
|
86
|
+
|
87
|
+
db.actions.should == [
|
88
|
+
[:create_enum, :type_name, ["value1", "value2", "value3"] ],
|
89
|
+
[:drop_enum, :type_name]
|
90
|
+
]
|
91
|
+
end
|
64
92
|
end
|
@@ -90,6 +90,14 @@ describe "Sequel::Plugins::PreparedStatementsAssociations" do
|
|
90
90
|
@db.sqls.should == []
|
91
91
|
end
|
92
92
|
|
93
|
+
specify "should run a regular query if not caching association metadata" do
|
94
|
+
@Artist.cache_associations = false
|
95
|
+
@Artist.load(:id=>1).albums
|
96
|
+
@db.sqls.should == ["SELECT * FROM albums WHERE (albums.artist_id = 1)"]
|
97
|
+
@Artist.load(:id=>1).album
|
98
|
+
@db.sqls.should == ["SELECT * FROM albums WHERE (albums.artist_id = 1) LIMIT 1"]
|
99
|
+
end
|
100
|
+
|
93
101
|
specify "should run a regular query if there is a callback" do
|
94
102
|
@Artist.load(:id=>1).albums(proc{|ds| ds})
|
95
103
|
@db.sqls.should == ["SELECT * FROM albums WHERE (albums.artist_id = 1)"]
|
@@ -91,14 +91,17 @@ describe Sequel::Model, "rcte_tree" do
|
|
91
91
|
@o.associations[:p].associations[:p].associations[:p].associations.fetch(:p, 1).should == nil
|
92
92
|
end
|
93
93
|
|
94
|
-
it "should add all children associations when lazily loading descendants" do
|
94
|
+
it "should add all parent and children associations when lazily loading descendants" do
|
95
95
|
@c.plugin :rcte_tree
|
96
96
|
@ds._fetch = [[{:id=>3, :name=>'??', :parent_id=>1}, {:id=>1, :name=>'A', :parent_id=>2}, {:id=>4, :name=>'B', :parent_id=>2}, {:id=>5, :name=>'?', :parent_id=>3}]]
|
97
97
|
@o.descendants.should == [@c.load(:id=>3, :name=>'??', :parent_id=>1), @c.load(:id=>1, :name=>'A', :parent_id=>2), @c.load(:id=>4, :name=>'B', :parent_id=>2), @c.load(:id=>5, :name=>'?', :parent_id=>3)]
|
98
98
|
@o.associations[:children].should == [@c.load(:id=>1, :name=>'A', :parent_id=>2), @c.load(:id=>4, :name=>'B', :parent_id=>2)]
|
99
99
|
@o.associations[:children].map{|c1| c1.associations[:children]}.should == [[@c.load(:id=>3, :name=>'??', :parent_id=>1)], []]
|
100
|
+
@o.associations[:children].map{|c1| c1.associations[:parent]}.should == [@o, @o]
|
100
101
|
@o.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[[@c.load(:id=>5, :name=>'?', :parent_id=>3)]], []]
|
102
|
+
@o.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:parent]}}.should == [[@o.children.first], []]
|
101
103
|
@o.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children].map{|c3| c3.associations[:children]}}}.should == [[[[]]], []]
|
104
|
+
@o.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children].map{|c3| c3.associations[:parent]}}}.should == [[[@o.children.first.children.first]], []]
|
102
105
|
end
|
103
106
|
|
104
107
|
it "should add all children associations when lazily loading descendants and giving options" do
|
@@ -182,7 +185,9 @@ describe Sequel::Model, "rcte_tree" do
|
|
182
185
|
[@c.load(:id=>4, :name=>'?', :parent_id=>7), @c.load(:id=>5, :name=>'?', :parent_id=>4)]]
|
183
186
|
os.map{|o| o.children}.should == [[@c.load(:id=>6, :parent_id=>2, :name=>'C'), @c.load(:id=>9, :parent_id=>2, :name=>'E')], [@c.load(:id=>3, :name=>'00', :parent_id=>6)], [@c.load(:id=>4, :name=>'?', :parent_id=>7)]]
|
184
187
|
os.map{|o1| o1.children.map{|o2| o2.children}}.should == [[[@c.load(:id=>3, :name=>'00', :parent_id=>6)], []], [[]], [[@c.load(:id=>5, :name=>'?', :parent_id=>4)]]]
|
188
|
+
os.map{|o1| o1.children.map{|o2| o2.parent}}.should == [[os[0], os[0]], [os[1]], [os[2]]]
|
185
189
|
os.map{|o1| o1.children.map{|o2| o2.children.map{|o3| o3.children}}}.should == [[[[]], []], [[]], [[[]]]]
|
190
|
+
os.map{|o1| o1.children.map{|o2| o2.children.map{|o3| o3.parent}}}.should == [[[os[0].children[0]], []], [[]], [[os[2].children[0]]]]
|
186
191
|
@db.sqls.should == []
|
187
192
|
end
|
188
193
|
|
@@ -647,7 +647,7 @@ END_MIG
|
|
647
647
|
%w"nvarchar ntext smalldatetime smallmoney binary varbinary nchar" +
|
648
648
|
["timestamp(6) without time zone", "timestamp(6) with time zone", 'mediumint(10) unsigned', 'int(9) unsigned',
|
649
649
|
'int(10) unsigned', "int(12) unsigned", 'bigint unsigned', 'tinyint(3) unsigned', 'identity', 'int identity'] +
|
650
|
-
%w"integer(10)"
|
650
|
+
%w"integer(10) bit"
|
651
651
|
@d.meta_def(:schema) do |t, *o|
|
652
652
|
i = 0
|
653
653
|
types.map{|x| [:"c#{i+=1}", {:db_type=>x, :allow_null=>true}]}
|
@@ -726,6 +726,7 @@ create_table(:x) do
|
|
726
726
|
Integer :c70
|
727
727
|
Integer :c71
|
728
728
|
Integer :c72
|
729
|
+
TrueClass :c73
|
729
730
|
|
730
731
|
check Sequel::SQL::BooleanExpression.new(:>=, Sequel::SQL::Identifier.new(:c64), 0)
|
731
732
|
check Sequel::SQL::BooleanExpression.new(:>=, Sequel::SQL::Identifier.new(:c65), 0)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
2
2
|
|
3
|
-
describe "Sequel::Plugins::StaticCache
|
3
|
+
describe "Sequel::Plugins::StaticCache" do
|
4
4
|
before do
|
5
5
|
@db = Sequel.mock
|
6
6
|
@db.fetch = [{:id=>1}, {:id=>2}]
|
@@ -9,6 +9,12 @@ describe "Sequel::Plugins::StaticCache with :frozen=>false option" do
|
|
9
9
|
@c.columns :id, :name
|
10
10
|
end
|
11
11
|
|
12
|
+
it "should not attempt to validate objects" do
|
13
|
+
@c.send(:define_method, :validate){errors.add(:name, 'bad')}
|
14
|
+
proc{@c.plugin(:static_cache)}.should_not raise_error
|
15
|
+
@c.map{|o| o.valid?}.should == [true, true]
|
16
|
+
end
|
17
|
+
|
12
18
|
shared_examples_for "Sequel::Plugins::StaticCache" do
|
13
19
|
it "should use a ruby hash as a cache of all model instances" do
|
14
20
|
@c.cache.should == {1=>@c.load(:id=>1), 2=>@c.load(:id=>2)}
|
@@ -39,7 +39,7 @@ describe "Sequel timezone support" do
|
|
39
39
|
@db.drop_table(:t)
|
40
40
|
end
|
41
41
|
|
42
|
-
cspecify "should support using UTC for database storage and local time for the application", [:
|
42
|
+
cspecify "should support using UTC for database storage and local time for the application", [:tinytds], [:do, :mysql], [:do, :postgres], [:oracle] do
|
43
43
|
Sequel.database_timezone = :utc
|
44
44
|
Sequel.application_timezone = :local
|
45
45
|
test_timezone
|
@@ -48,7 +48,7 @@ describe "Sequel timezone support" do
|
|
48
48
|
test_timezone
|
49
49
|
end
|
50
50
|
|
51
|
-
cspecify "should support using local time for database storage and UTC for the application", [:
|
51
|
+
cspecify "should support using local time for database storage and UTC for the application", [:tinytds], [:do, :mysql], [:do, :postgres], [:oracle] do
|
52
52
|
Sequel.database_timezone = :local
|
53
53
|
Sequel.application_timezone = :utc
|
54
54
|
test_timezone
|
@@ -65,7 +65,7 @@ describe "Sequel timezone support" do
|
|
65
65
|
test_timezone
|
66
66
|
end
|
67
67
|
|
68
|
-
cspecify "should support using local time for both database storage and for application", [:do, :postgres], [:oracle]
|
68
|
+
cspecify "should support using local time for both database storage and for application", [:do, :postgres], [:oracle] do
|
69
69
|
Sequel.default_timezone = :local
|
70
70
|
test_timezone
|
71
71
|
Sequel.database_timezone = :utc
|
@@ -136,6 +136,14 @@ describe Sequel::Model, "associate" do
|
|
136
136
|
proc{c.one_to_many :cs, :clone=>:c}.should raise_error(Sequel::Error)
|
137
137
|
end
|
138
138
|
|
139
|
+
it "should allow overriding the :instance_specific option" do
|
140
|
+
c = Class.new(Sequel::Model(:c))
|
141
|
+
c.many_to_one :c, :instance_specific=>true
|
142
|
+
c.association_reflection(:c)[:instance_specific].should == true
|
143
|
+
c.many_to_one :c, :instance_specific=>false do |ds| ds end
|
144
|
+
c.association_reflection(:c)[:instance_specific].should == false
|
145
|
+
end
|
146
|
+
|
139
147
|
it "should allow cloning of one_to_many to one_to_one associations and vice-versa" do
|
140
148
|
c = Class.new(Sequel::Model(:c))
|
141
149
|
c.one_to_one :c
|
@@ -4207,12 +4215,21 @@ describe "Model#freeze" do
|
|
4207
4215
|
Album::B.dataset._fetch = {:album_id=>1, :id=>2}
|
4208
4216
|
@o.b.should == Album::B.load(:id=>2, :album_id=>1)
|
4209
4217
|
@o.associations[:b].should be_nil
|
4218
|
+
|
4219
|
+
@o = @o.dup
|
4220
|
+
@o.b.should == Album::B.load(:id=>2, :album_id=>1)
|
4221
|
+
@o.associations[:b].should == Album::B.load(:id=>2, :album_id=>1)
|
4210
4222
|
end
|
4211
4223
|
|
4212
4224
|
it "should not break reciprocal associations" do
|
4213
4225
|
b = Album::B.load(:id=>2, :album_id=>nil)
|
4214
4226
|
b.album = @o
|
4215
4227
|
@o.associations[:b].should be_nil
|
4228
|
+
|
4229
|
+
@o = @o.dup
|
4230
|
+
b = Album::B.load(:id=>2, :album_id=>nil)
|
4231
|
+
b.album = @o
|
4232
|
+
@o.associations[:b].should == Album::B.load(:id=>2, :album_id=>1)
|
4216
4233
|
end
|
4217
4234
|
end
|
4218
4235
|
|
@@ -131,6 +131,16 @@ describe Sequel::Model, "#eager" do
|
|
131
131
|
DB.sqls.should == []
|
132
132
|
end
|
133
133
|
|
134
|
+
it "should skip eager loading for a many_to_one association with no matching keys" do
|
135
|
+
EagerAlbum.dataset._fetch = [{:id=>1, :band_id=>nil}]
|
136
|
+
a = EagerAlbum.eager(:band).all
|
137
|
+
DB.sqls.should == ['SELECT * FROM albums']
|
138
|
+
a.should == [EagerAlbum.load(:id => 1, :band_id => nil)]
|
139
|
+
a.first.associations.fetch(:band).should == nil
|
140
|
+
a.first.band.should == nil
|
141
|
+
DB.sqls.should == []
|
142
|
+
end
|
143
|
+
|
134
144
|
it "should eagerly load a single many_to_one association with the same name as the column" do
|
135
145
|
EagerAlbum.def_column_alias(:band_id_id, :band_id)
|
136
146
|
EagerAlbum.many_to_one :band_id, :key_column=>:band_id, :class=>EagerBand
|
@@ -1055,6 +1065,35 @@ describe Sequel::Model, "#eager" do
|
|
1055
1065
|
DB.sqls.should == []
|
1056
1066
|
end
|
1057
1067
|
|
1068
|
+
it "should allow cascading of eager loading with custom callback with symbol value when association has a limit" do
|
1069
|
+
EagerAlbum.dataset._fetch = (1..11).map{|i| {:band_id=>2, :id=>i}}
|
1070
|
+
EagerTrack.dataset._fetch = [{:id=>3, :album_id=>1}]
|
1071
|
+
a = EagerBand.eager(:top_10_albums=>{proc{|ds| ds.select(:id, :name)}=>:tracks}).all
|
1072
|
+
a.should == [EagerBand.load(:id => 2)]
|
1073
|
+
sqls = DB.sqls
|
1074
|
+
sqls.pop.should =~ /SELECT \* FROM tracks WHERE \(tracks.album_id IN \((\d+, ){10}\d+\)\)/
|
1075
|
+
sqls.should == ['SELECT * FROM bands', 'SELECT id, name FROM albums WHERE (albums.band_id IN (2))']
|
1076
|
+
a = a.first
|
1077
|
+
a.top_10_albums.should == (1..10).map{|i| EagerAlbum.load(:band_id=>2, :id=>i)}
|
1078
|
+
a.top_10_albums.map{|x| x.tracks}.should == [[EagerTrack.load(:id => 3, :album_id=>1)]] + ([[]] * 9)
|
1079
|
+
DB.sqls.should == []
|
1080
|
+
end
|
1081
|
+
|
1082
|
+
it "should allow cascading of eager loading with custom callback with symbol value when association has a limit when using window function eager limit strategy" do
|
1083
|
+
def (EagerAlbum.dataset).supports_window_functions?() true end
|
1084
|
+
EagerAlbum.dataset._fetch = {:band_id=>2, :id=>1}
|
1085
|
+
EagerTrack.dataset._fetch = [{:id=>3, :album_id=>1}]
|
1086
|
+
a = EagerBand.eager(:top_10_albums=>{proc{|ds| ds.select(:id, :name)}=>:tracks}).all
|
1087
|
+
a.should == [EagerBand.load(:id => 2)]
|
1088
|
+
DB.sqls.should == ['SELECT * FROM bands',
|
1089
|
+
'SELECT * FROM (SELECT id, name, row_number() OVER (PARTITION BY albums.band_id) AS x_sequel_row_number_x FROM albums WHERE (albums.band_id IN (2))) AS t1 WHERE (x_sequel_row_number_x <= 10)',
|
1090
|
+
'SELECT * FROM tracks WHERE (tracks.album_id IN (1))']
|
1091
|
+
a = a.first
|
1092
|
+
a.top_10_albums.should == [EagerAlbum.load(:band_id=>2, :id=>1)]
|
1093
|
+
a.top_10_albums.first.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
|
1094
|
+
DB.sqls.should == []
|
1095
|
+
end
|
1096
|
+
|
1058
1097
|
it "should allow cascading of eager loading with custom callback with array value" do
|
1059
1098
|
a = EagerTrack.eager(:album=>{proc{|ds| ds.select(:id, :band_id)}=>[:band, :band_name]}).all
|
1060
1099
|
a.should == [EagerTrack.load(:id => 3, :album_id => 1)]
|
data/spec/model/record_spec.rb
CHANGED
@@ -446,6 +446,15 @@ describe "Model#freeze" do
|
|
446
446
|
o.valid?.should == false
|
447
447
|
end
|
448
448
|
|
449
|
+
it "should not call validate if errors is already frozen" do
|
450
|
+
@o.valid?.should == true
|
451
|
+
o = Album.new
|
452
|
+
o.errors.freeze
|
453
|
+
def o.validate() errors.add(:foo, '') end
|
454
|
+
proc{o.freeze}.should_not raise_error
|
455
|
+
o.valid?.should == true
|
456
|
+
end
|
457
|
+
|
449
458
|
it "should raise an Error if trying to save/destroy/delete/refresh" do
|
450
459
|
proc{@o.save}.should raise_error(Sequel::Error)
|
451
460
|
proc{@o.destroy}.should raise_error(Sequel::Error)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.20.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-03-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -192,6 +192,7 @@ extra_rdoc_files:
|
|
192
192
|
- doc/release_notes/4.17.0.txt
|
193
193
|
- doc/release_notes/4.18.0.txt
|
194
194
|
- doc/release_notes/4.19.0.txt
|
195
|
+
- doc/release_notes/4.20.0.txt
|
195
196
|
files:
|
196
197
|
- CHANGELOG
|
197
198
|
- MIT-LICENSE
|
@@ -299,6 +300,7 @@ files:
|
|
299
300
|
- doc/release_notes/4.18.0.txt
|
300
301
|
- doc/release_notes/4.19.0.txt
|
301
302
|
- doc/release_notes/4.2.0.txt
|
303
|
+
- doc/release_notes/4.20.0.txt
|
302
304
|
- doc/release_notes/4.3.0.txt
|
303
305
|
- doc/release_notes/4.4.0.txt
|
304
306
|
- doc/release_notes/4.5.0.txt
|