sequel 4.19.0 → 4.20.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.
- 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
|