sequel 3.21.0 → 3.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +413 -3
- data/README.rdoc +20 -6
- data/Rakefile +23 -16
- data/bin/sequel +1 -5
- data/doc/association_basics.rdoc +210 -43
- data/doc/dataset_basics.rdoc +4 -4
- data/doc/mass_assignment.rdoc +54 -0
- data/doc/migration.rdoc +15 -538
- data/doc/model_hooks.rdoc +72 -27
- data/doc/opening_databases.rdoc +86 -50
- data/doc/prepared_statements.rdoc +40 -13
- data/doc/reflection.rdoc +8 -2
- data/doc/release_notes/3.22.0.txt +39 -0
- data/doc/release_notes/3.23.0.txt +172 -0
- data/doc/release_notes/3.24.0.txt +420 -0
- data/doc/release_notes/3.25.0.txt +88 -0
- data/doc/release_notes/3.26.0.txt +88 -0
- data/doc/release_notes/3.27.0.txt +82 -0
- data/doc/release_notes/3.28.0.txt +304 -0
- data/doc/release_notes/3.29.0.txt +459 -0
- data/doc/schema_modification.rdoc +547 -0
- data/doc/sharding.rdoc +7 -1
- data/doc/testing.rdoc +115 -0
- data/doc/transactions.rdoc +137 -0
- data/doc/virtual_rows.rdoc +2 -2
- data/lib/sequel/adapters/ado/mssql.rb +18 -6
- data/lib/sequel/adapters/ado.rb +40 -18
- data/lib/sequel/adapters/amalgalite.rb +15 -7
- data/lib/sequel/adapters/db2.rb +175 -86
- data/lib/sequel/adapters/dbi.rb +15 -15
- data/lib/sequel/adapters/do/mysql.rb +0 -5
- data/lib/sequel/adapters/do/postgres.rb +0 -5
- data/lib/sequel/adapters/do/sqlite.rb +0 -5
- data/lib/sequel/adapters/do.rb +17 -36
- data/lib/sequel/adapters/firebird.rb +27 -208
- data/lib/sequel/adapters/ibmdb.rb +448 -0
- data/lib/sequel/adapters/informix.rb +6 -23
- data/lib/sequel/adapters/jdbc/as400.rb +5 -31
- data/lib/sequel/adapters/jdbc/db2.rb +44 -0
- data/lib/sequel/adapters/jdbc/derby.rb +217 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
- data/lib/sequel/adapters/jdbc/h2.rb +26 -17
- data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
- data/lib/sequel/adapters/jdbc/informix.rb +26 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
- data/lib/sequel/adapters/jdbc/mysql.rb +9 -10
- data/lib/sequel/adapters/jdbc/oracle.rb +67 -25
- data/lib/sequel/adapters/jdbc/postgresql.rb +6 -11
- data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
- data/lib/sequel/adapters/jdbc/sqlserver.rb +41 -0
- data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
- data/lib/sequel/adapters/jdbc.rb +107 -38
- data/lib/sequel/adapters/mock.rb +315 -0
- data/lib/sequel/adapters/mysql.rb +78 -182
- data/lib/sequel/adapters/mysql2.rb +24 -23
- data/lib/sequel/adapters/odbc/db2.rb +17 -0
- data/lib/sequel/adapters/odbc/mssql.rb +0 -5
- data/lib/sequel/adapters/odbc.rb +28 -9
- data/lib/sequel/adapters/openbase.rb +2 -4
- data/lib/sequel/adapters/oracle.rb +349 -51
- data/lib/sequel/adapters/postgres.rb +169 -21
- data/lib/sequel/adapters/shared/access.rb +21 -6
- data/lib/sequel/adapters/shared/db2.rb +288 -0
- data/lib/sequel/adapters/shared/firebird.rb +214 -0
- data/lib/sequel/adapters/shared/informix.rb +45 -0
- data/lib/sequel/adapters/shared/mssql.rb +108 -65
- data/lib/sequel/adapters/shared/mysql.rb +56 -13
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
- data/lib/sequel/adapters/shared/oracle.rb +185 -34
- data/lib/sequel/adapters/shared/postgres.rb +107 -58
- data/lib/sequel/adapters/shared/progress.rb +0 -6
- data/lib/sequel/adapters/shared/sqlite.rb +168 -41
- data/lib/sequel/adapters/sqlite.rb +27 -18
- data/lib/sequel/adapters/swift/mysql.rb +9 -5
- data/lib/sequel/adapters/swift/postgres.rb +0 -5
- data/lib/sequel/adapters/swift/sqlite.rb +6 -4
- data/lib/sequel/adapters/swift.rb +5 -5
- data/lib/sequel/adapters/tinytds.rb +152 -17
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +63 -0
- data/lib/sequel/ast_transformer.rb +190 -0
- data/lib/sequel/connection_pool/threaded.rb +3 -2
- data/lib/sequel/connection_pool.rb +1 -1
- data/lib/sequel/core.rb +46 -5
- data/lib/sequel/database/connecting.rb +5 -6
- data/lib/sequel/database/dataset.rb +4 -4
- data/lib/sequel/database/dataset_defaults.rb +59 -1
- data/lib/sequel/database/logging.rb +1 -1
- data/lib/sequel/database/misc.rb +100 -19
- data/lib/sequel/database/query.rb +153 -58
- data/lib/sequel/database/schema_generator.rb +8 -5
- data/lib/sequel/database/schema_methods.rb +59 -29
- data/lib/sequel/dataset/actions.rb +194 -57
- data/lib/sequel/dataset/features.rb +81 -8
- data/lib/sequel/dataset/graph.rb +8 -7
- data/lib/sequel/dataset/misc.rb +50 -23
- data/lib/sequel/dataset/mutation.rb +5 -6
- data/lib/sequel/dataset/prepared_statements.rb +32 -15
- data/lib/sequel/dataset/query.rb +223 -42
- data/lib/sequel/dataset/sql.rb +58 -61
- data/lib/sequel/dataset.rb +8 -0
- data/lib/sequel/exceptions.rb +4 -0
- data/lib/sequel/extensions/columns_introspection.rb +61 -0
- data/lib/sequel/extensions/migration.rb +6 -4
- data/lib/sequel/extensions/named_timezones.rb +5 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
- data/lib/sequel/extensions/to_dot.rb +95 -83
- data/lib/sequel/model/associations.rb +997 -331
- data/lib/sequel/model/base.rb +386 -129
- data/lib/sequel/model/errors.rb +1 -1
- data/lib/sequel/model/exceptions.rb +5 -1
- data/lib/sequel/model.rb +15 -8
- data/lib/sequel/plugins/association_pks.rb +22 -4
- data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
- data/lib/sequel/plugins/dataset_associations.rb +100 -0
- data/lib/sequel/plugins/defaults_setter.rb +58 -0
- data/lib/sequel/plugins/force_encoding.rb +6 -6
- data/lib/sequel/plugins/identity_map.rb +114 -7
- data/lib/sequel/plugins/many_through_many.rb +65 -7
- data/lib/sequel/plugins/prepared_statements.rb +151 -0
- data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
- data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
- data/lib/sequel/plugins/rcte_tree.rb +29 -15
- data/lib/sequel/plugins/serialization.rb +6 -1
- data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
- data/lib/sequel/plugins/sharding.rb +12 -25
- data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
- data/lib/sequel/plugins/typecast_on_load.rb +9 -12
- data/lib/sequel/plugins/update_primary_key.rb +2 -2
- data/lib/sequel/plugins/xml_serializer.rb +3 -3
- data/lib/sequel/sql.rb +107 -51
- data/lib/sequel/timezones.rb +44 -35
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/db2_spec.rb +146 -0
- data/spec/adapters/mssql_spec.rb +65 -29
- data/spec/adapters/mysql_spec.rb +122 -123
- data/spec/adapters/oracle_spec.rb +48 -76
- data/spec/adapters/postgres_spec.rb +213 -61
- data/spec/adapters/spec_helper.rb +6 -5
- data/spec/adapters/sqlite_spec.rb +35 -21
- data/spec/core/connection_pool_spec.rb +71 -92
- data/spec/core/core_sql_spec.rb +20 -31
- data/spec/core/database_spec.rb +729 -508
- data/spec/core/dataset_spec.rb +980 -1044
- data/spec/core/expression_filters_spec.rb +159 -42
- data/spec/core/mock_adapter_spec.rb +378 -0
- data/spec/core/object_graph_spec.rb +48 -114
- data/spec/core/schema_generator_spec.rb +3 -3
- data/spec/core/schema_spec.rb +298 -38
- data/spec/core/spec_helper.rb +6 -48
- data/spec/extensions/association_pks_spec.rb +38 -0
- data/spec/extensions/class_table_inheritance_spec.rb +1 -1
- data/spec/extensions/columns_introspection_spec.rb +91 -0
- data/spec/extensions/dataset_associations_spec.rb +199 -0
- data/spec/extensions/defaults_setter_spec.rb +64 -0
- data/spec/extensions/identity_map_spec.rb +162 -0
- data/spec/extensions/instance_hooks_spec.rb +71 -0
- data/spec/extensions/many_through_many_spec.rb +195 -20
- data/spec/extensions/migration_spec.rb +17 -17
- data/spec/extensions/named_timezones_spec.rb +22 -2
- data/spec/extensions/nested_attributes_spec.rb +4 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
- data/spec/extensions/prepared_statements_spec.rb +72 -0
- data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
- data/spec/extensions/schema_dumper_spec.rb +2 -2
- data/spec/extensions/schema_spec.rb +13 -21
- data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
- data/spec/extensions/serialization_spec.rb +5 -8
- data/spec/extensions/single_table_inheritance_spec.rb +11 -0
- data/spec/extensions/spec_helper.rb +7 -1
- data/spec/extensions/thread_local_timezones_spec.rb +22 -2
- data/spec/extensions/to_dot_spec.rb +3 -5
- data/spec/extensions/typecast_on_load_spec.rb +1 -6
- data/spec/extensions/xml_serializer_spec.rb +16 -4
- data/spec/integration/associations_test.rb +522 -21
- data/spec/integration/database_test.rb +4 -2
- data/spec/integration/dataset_test.rb +375 -62
- data/spec/integration/eager_loader_test.rb +19 -21
- data/spec/integration/model_test.rb +80 -1
- data/spec/integration/plugin_test.rb +304 -116
- data/spec/integration/prepared_statement_test.rb +200 -120
- data/spec/integration/schema_test.rb +161 -30
- data/spec/integration/spec_helper.rb +39 -30
- data/spec/integration/timezone_test.rb +38 -12
- data/spec/integration/transaction_test.rb +172 -5
- data/spec/integration/type_test.rb +17 -3
- data/spec/model/association_reflection_spec.rb +174 -7
- data/spec/model/associations_spec.rb +849 -661
- data/spec/model/base_spec.rb +255 -95
- data/spec/model/dataset_methods_spec.rb +7 -27
- data/spec/model/eager_loading_spec.rb +640 -676
- data/spec/model/hooks_spec.rb +309 -67
- data/spec/model/model_spec.rb +207 -167
- data/spec/model/plugins_spec.rb +24 -13
- data/spec/model/record_spec.rb +321 -218
- data/spec/model/spec_helper.rb +13 -71
- data/spec/model/validations_spec.rb +11 -0
- metadata +95 -38
data/lib/sequel/model/errors.rb
CHANGED
|
@@ -40,7 +40,7 @@ module Sequel
|
|
|
40
40
|
def full_messages
|
|
41
41
|
inject([]) do |m, kv|
|
|
42
42
|
att, errors = *kv
|
|
43
|
-
errors.each {|e| m << "#{Array(att).join(ATTRIBUTE_JOINER)} #{e}"}
|
|
43
|
+
errors.each {|e| m << (e.is_a?(LiteralString) ? e : "#{Array(att).join(ATTRIBUTE_JOINER)} #{e}")}
|
|
44
44
|
m
|
|
45
45
|
end
|
|
46
46
|
end
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
module Sequel
|
|
2
2
|
# Exception class raised when +raise_on_save_failure+ is set and a before hook returns false
|
|
3
|
-
|
|
3
|
+
# or an around hook doesn't call super or yield.
|
|
4
|
+
class HookFailed < Error; end
|
|
5
|
+
|
|
6
|
+
# Deprecated alias for HookFailed, kept for backwards compatibility
|
|
7
|
+
BeforeHookFailed = HookFailed
|
|
4
8
|
|
|
5
9
|
# Exception class raised when +require_modification+ is set and an UPDATE or DELETE statement to modify the dataset doesn't
|
|
6
10
|
# modify a single row.
|
data/lib/sequel/model.rb
CHANGED
|
@@ -11,9 +11,9 @@ module Sequel
|
|
|
11
11
|
# with the +Database+ in +source+ to create the
|
|
12
12
|
# dataset to use)
|
|
13
13
|
# Dataset :: Sets the dataset for this model to +source+.
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
14
|
+
# other :: Sets the table name for this model to +source+. The
|
|
15
|
+
# class will use the default database for model
|
|
16
|
+
# classes in order to create the dataset.
|
|
17
17
|
#
|
|
18
18
|
# The purpose of this method is to set the dataset/database automatically
|
|
19
19
|
# for a model class, if the table name doesn't match the implicit
|
|
@@ -49,10 +49,11 @@ module Sequel
|
|
|
49
49
|
# called directly on the class. Model datasets return rows as model instances,
|
|
50
50
|
# which have fairly standard ORM instance behavior.
|
|
51
51
|
#
|
|
52
|
-
# <tt>Sequel::Model</tt> is built completely out of plugins
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
52
|
+
# <tt>Sequel::Model</tt> is built completely out of plugins. Plugins can override any class,
|
|
53
|
+
# instance, or dataset method defined by a previous plugin and call super to get the default
|
|
54
|
+
# behavior. By default, <tt>Sequel::Model</tt> loads two plugins, <tt>Sequel::Model</tt>
|
|
55
|
+
# (which is itself a plugin) for the base support, and <tt>Sequel::Model::Associations</tt>
|
|
56
|
+
# for the associations support.
|
|
56
57
|
#
|
|
57
58
|
# You can set the +SEQUEL_NO_ASSOCIATIONS+ constant or environment variable to
|
|
58
59
|
# make Sequel not load the associations plugin by default.
|
|
@@ -78,7 +79,13 @@ module Sequel
|
|
|
78
79
|
|
|
79
80
|
# Hooks that are called after an action. When overriding these, it is recommended to call
|
|
80
81
|
# +super+ on the first line of your method, so later hooks are called after earlier hooks.
|
|
81
|
-
AFTER_HOOKS = [:after_initialize, :after_create, :after_update, :after_save, :after_destroy,
|
|
82
|
+
AFTER_HOOKS = [:after_initialize, :after_create, :after_update, :after_save, :after_destroy,
|
|
83
|
+
:after_validation, :after_commit, :after_rollback, :after_destroy_commit, :after_destroy_rollback]
|
|
84
|
+
|
|
85
|
+
# Hooks that are called around an action. If overridden, these methods must call super
|
|
86
|
+
# exactly once if the behavior they wrap is desired. The can be used to rescue exceptions
|
|
87
|
+
# raised by the code they wrap or ensure that some behavior is executed no matter what.
|
|
88
|
+
AROUND_HOOKS = [:around_create, :around_update, :around_save, :around_destroy, :around_validation]
|
|
82
89
|
|
|
83
90
|
# Empty instance methods to create that the user can override to get hook/callback behavior.
|
|
84
91
|
# Just like any other method defined by Sequel, if you override one of these, you should
|
|
@@ -14,7 +14,7 @@ module Sequel
|
|
|
14
14
|
#
|
|
15
15
|
# Note that it uses the singular form of the association name. Also note
|
|
16
16
|
# that the setter both associates to new primary keys not in the assocation
|
|
17
|
-
# and
|
|
17
|
+
# and disassociates from primary keys not provided to the method.
|
|
18
18
|
#
|
|
19
19
|
# This plugin makes modifications directly to the underlying tables,
|
|
20
20
|
# it does not create or return any model objects, and therefore does
|
|
@@ -56,6 +56,7 @@ module Sequel
|
|
|
56
56
|
_join_table_dataset(opts).filter(opts[:left_key]=>send(opts[:left_primary_key])).select_map(opts[:right_key])
|
|
57
57
|
end
|
|
58
58
|
def_association_pks_setter(opts) do |pks|
|
|
59
|
+
pks = convert_pk_array(opts, pks)
|
|
59
60
|
checked_transaction do
|
|
60
61
|
ds = _join_table_dataset(opts).filter(opts[:left_key]=>send(opts[:left_primary_key]))
|
|
61
62
|
ds.exclude(opts[:right_key]=>pks).delete
|
|
@@ -71,17 +72,34 @@ module Sequel
|
|
|
71
72
|
super
|
|
72
73
|
return if opts[:type] == :one_to_one
|
|
73
74
|
def_association_pks_getter(opts) do
|
|
74
|
-
send(opts.dataset_method).select_map(opts
|
|
75
|
+
send(opts.dataset_method).select_map(opts.associated_class.primary_key)
|
|
75
76
|
end
|
|
76
77
|
def_association_pks_setter(opts) do |pks|
|
|
78
|
+
pks = convert_pk_array(opts, pks)
|
|
77
79
|
checked_transaction do
|
|
78
80
|
ds = send(opts.dataset_method)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
primary_key = opts.associated_class.primary_key
|
|
82
|
+
key = opts[:key]
|
|
83
|
+
ds.unfiltered.filter(primary_key=>pks).update(key=>pk)
|
|
84
|
+
ds.exclude(primary_key=>pks).update(key=>nil)
|
|
81
85
|
end
|
|
82
86
|
end
|
|
83
87
|
end
|
|
84
88
|
end
|
|
89
|
+
|
|
90
|
+
module InstanceMethods
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
# If the associated class's primary key column type is integer,
|
|
94
|
+
# typecast all provided values to integer before using them.
|
|
95
|
+
def convert_pk_array(opts, pks)
|
|
96
|
+
if klass = opts.associated_class and sch = klass.db_schema and col = sch[klass.primary_key] and col[:type] == :integer
|
|
97
|
+
pks.map{|pk| model.db.typecast_value(:integer, pk)}
|
|
98
|
+
else
|
|
99
|
+
pks
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
85
103
|
end
|
|
86
104
|
end
|
|
87
105
|
end
|
|
@@ -90,9 +90,9 @@ module Sequel
|
|
|
90
90
|
@cti_columns = {table_name=>columns}
|
|
91
91
|
@cti_table_map = opts[:table_map] || {}
|
|
92
92
|
dataset.row_proc = if key
|
|
93
|
-
lambda{|r| (m.call(r[key]) rescue model).
|
|
93
|
+
lambda{|r| (m.call(r[key]) rescue model).call(r)}
|
|
94
94
|
else
|
|
95
|
-
|
|
95
|
+
model
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
98
|
end
|
|
@@ -152,9 +152,9 @@ module Sequel
|
|
|
152
152
|
subclass.instance_eval do
|
|
153
153
|
m = method(:constantize)
|
|
154
154
|
dataset.row_proc = if cti_key
|
|
155
|
-
lambda{|r| (m.call(r[ck]) rescue subclass).
|
|
155
|
+
lambda{|r| (m.call(r[ck]) rescue subclass).call(r)}
|
|
156
156
|
else
|
|
157
|
-
|
|
157
|
+
subclass
|
|
158
158
|
end
|
|
159
159
|
(columns - [cbm.primary_key]).each{|a| define_lazy_attribute_getter(a)}
|
|
160
160
|
cti_tables.reverse.each do |table|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
# DatasetAssociations allows you to easily use your model associations
|
|
4
|
+
# via datasets. For each association you define, it creates a dataset
|
|
5
|
+
# method for that association that returns a dataset of all objects
|
|
6
|
+
# that are associated to objects in the current dataset. Here's a simple
|
|
7
|
+
# example:
|
|
8
|
+
#
|
|
9
|
+
# class Artist < Sequel::Model
|
|
10
|
+
# plugin :dataset_associations
|
|
11
|
+
# one_to_many :albums
|
|
12
|
+
# end
|
|
13
|
+
# Artist.filter(id=>1..100).albums
|
|
14
|
+
# # SELECT * FROM albums
|
|
15
|
+
# # WHERE (albums.artist_id IN (
|
|
16
|
+
# # SELECT id FROM artists
|
|
17
|
+
# # WHERE ((id >= 1) AND (id <= 100))))
|
|
18
|
+
#
|
|
19
|
+
# This works for all of the association types that ship with Sequel,
|
|
20
|
+
# including the many_through_many type. Most association options that
|
|
21
|
+
# are supported when eager loading are supported when using a
|
|
22
|
+
# dataset association. However, associations that use :limit or
|
|
23
|
+
# one_to_one associations that are really one_to_many relationships
|
|
24
|
+
# in the database will not work correctly, returning all associated
|
|
25
|
+
# objects.
|
|
26
|
+
#
|
|
27
|
+
# As the dataset methods return datasets, you can easily chain the
|
|
28
|
+
# methods to get associated datasets of associated datasets:
|
|
29
|
+
#
|
|
30
|
+
# Artist.filter(id=>1..100).albums.filter{name < 'M'}.tags
|
|
31
|
+
# # SELECT tags.* FROM tags
|
|
32
|
+
# # WHERE (tags.id IN (
|
|
33
|
+
# # SELECT albums_tags.tag_id FROM albums
|
|
34
|
+
# # INNER JOIN albums_tags
|
|
35
|
+
# # ON (albums_tags.album_id = albums.id)
|
|
36
|
+
# # WHERE
|
|
37
|
+
# # ((albums.artist_id IN (
|
|
38
|
+
# # SELECT id FROM artists
|
|
39
|
+
# # WHERE ((id >= 1) AND (id <= 100)))
|
|
40
|
+
# # AND
|
|
41
|
+
# # (name < 'M')))))
|
|
42
|
+
#
|
|
43
|
+
# Usage:
|
|
44
|
+
#
|
|
45
|
+
# # Make all model subclasses create association methods for datasets
|
|
46
|
+
# Sequel::Model.plugin :dataset_associations
|
|
47
|
+
#
|
|
48
|
+
# # Make the Album class create association methods for datasets
|
|
49
|
+
# Album.plugin :dataset_associations
|
|
50
|
+
module DatasetAssociations
|
|
51
|
+
module ClassMethods
|
|
52
|
+
# Set up a dataset method for each association to return an associated dataset
|
|
53
|
+
def associate(type, name, *)
|
|
54
|
+
ret = super
|
|
55
|
+
r = association_reflection(name)
|
|
56
|
+
meth = r.returns_array? ? name : pluralize(name).to_sym
|
|
57
|
+
def_dataset_method(meth){associated(name)}
|
|
58
|
+
ret
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
module DatasetMethods
|
|
63
|
+
# For the association given by +name+, return a dataset of associated objects
|
|
64
|
+
# such that it would return the union of calling the association method on
|
|
65
|
+
# all objects returned by the current dataset.
|
|
66
|
+
#
|
|
67
|
+
# This supports most options that are supported when eager loading. It doesn't
|
|
68
|
+
# support limits on the associations, or one_to_one associations that are really
|
|
69
|
+
# one_to_many and use an order to select the first matching object. In both of
|
|
70
|
+
# those cases, this will return an array of all matching objects.
|
|
71
|
+
def associated(name)
|
|
72
|
+
raise Error, "unrecognized association name: #{name.inspect}" unless r = model.association_reflection(name)
|
|
73
|
+
ds = r.associated_class.dataset
|
|
74
|
+
sds = opts[:limit] ? self : unordered
|
|
75
|
+
ds = case r[:type]
|
|
76
|
+
when :many_to_one
|
|
77
|
+
ds.filter(r.qualified_primary_key=>sds.select(*Array(r[:qualified_key])))
|
|
78
|
+
when :one_to_one, :one_to_many
|
|
79
|
+
ds.filter(r.qualified_key=>sds.select(*Array(r.qualified_primary_key)))
|
|
80
|
+
when :many_to_many
|
|
81
|
+
ds.filter(r.qualified_right_primary_key=>sds.select(*Array(r.qualified_right_key)).
|
|
82
|
+
join(r[:join_table], r[:left_keys].zip(r[:left_primary_keys]), :implicit_qualifier=>model.table_name))
|
|
83
|
+
when :many_through_many
|
|
84
|
+
fre = r.reverse_edges.first
|
|
85
|
+
fe, *edges = r.edges
|
|
86
|
+
sds = sds.select(*Array(r.qualify(fre[:table], fre[:left]))).
|
|
87
|
+
join(fe[:table], Array(fe[:right]).zip(Array(fe[:left])), :implicit_qualifier=>model.table_name)
|
|
88
|
+
edges.each{|e| sds = sds.join(e[:table], Array(e[:right]).zip(Array(e[:left])))}
|
|
89
|
+
ds.filter(r.qualified_right_primary_key=>sds)
|
|
90
|
+
else
|
|
91
|
+
raise Error, "unrecognized association type for association #{name.inspect}: #{r[:type].inspect}"
|
|
92
|
+
end
|
|
93
|
+
ds = model.apply_association_dataset_opts(r, ds)
|
|
94
|
+
r[:extend].each{|m| ds.extend(m)}
|
|
95
|
+
ds
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
# DefaultsSetter is a simple plugin that sets non-nil/NULL default values upon
|
|
4
|
+
# initialize:
|
|
5
|
+
#
|
|
6
|
+
# # column a default NULL
|
|
7
|
+
# # column b default 2
|
|
8
|
+
# album = Album.new.values # {:b => 2}
|
|
9
|
+
# album = Album.new(:a=>1, :b=>3).values # {:a => 1, :b => 3}
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
#
|
|
13
|
+
# # Make all model subclass instances set defaults (called before loading subclasses)
|
|
14
|
+
# Sequel::Model.plugin :defaults_setter
|
|
15
|
+
#
|
|
16
|
+
# # Make the Album class set defaults
|
|
17
|
+
# Album.plugin :defaults_setter
|
|
18
|
+
module DefaultsSetter
|
|
19
|
+
# Set the default values based on the model schema
|
|
20
|
+
def self.configure(model)
|
|
21
|
+
model.send(:set_default_values)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module ClassMethods
|
|
25
|
+
# The default values to set in initialize for this model. A hash with column symbol
|
|
26
|
+
# keys and default values. If the default values respond to +call+, it will be called
|
|
27
|
+
# to get the value, otherwise the value will be used directly. You can manually modify
|
|
28
|
+
# this hash to set specific default values, by default the ones will be parsed from the database.
|
|
29
|
+
attr_reader :default_values
|
|
30
|
+
|
|
31
|
+
# Set the default values when loading the dataset.
|
|
32
|
+
def set_dataset(*)
|
|
33
|
+
x = super
|
|
34
|
+
set_default_values
|
|
35
|
+
x
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def set_default_values
|
|
41
|
+
h = {}
|
|
42
|
+
@db_schema.each{|k, v| h[k] = v[:ruby_default] if v[:ruby_default]} if @db_schema
|
|
43
|
+
@default_values = h
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
module InstanceMethods
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# Set default values if they are not already set by the hash provided to initialize.
|
|
51
|
+
def initialize_set(h)
|
|
52
|
+
super
|
|
53
|
+
model.default_values.each{|k,v| self[k] = (v.respond_to?(:call) ? v.call : v) unless values.has_key?(k)}
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -8,7 +8,7 @@ module Sequel
|
|
|
8
8
|
# attribute, the resulting value is forced to a given encoding if the
|
|
9
9
|
# value is a string. There are two ways to specify the encoding. You
|
|
10
10
|
# can either do so in the plugin call itself, or via the
|
|
11
|
-
# forced_encoding class accessor
|
|
11
|
+
# forced_encoding class accessor.
|
|
12
12
|
#
|
|
13
13
|
# Usage:
|
|
14
14
|
#
|
|
@@ -46,6 +46,11 @@ module Sequel
|
|
|
46
46
|
super(force_hash_encoding(row))
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
# Force the encoding of all string values when setting the instance's values.
|
|
50
|
+
def set_values(row)
|
|
51
|
+
super(force_hash_encoding(row))
|
|
52
|
+
end
|
|
53
|
+
|
|
49
54
|
private
|
|
50
55
|
|
|
51
56
|
# Force the encoding for all string values in the given row hash.
|
|
@@ -55,11 +60,6 @@ module Sequel
|
|
|
55
60
|
row
|
|
56
61
|
end
|
|
57
62
|
|
|
58
|
-
# Force the encoding of all string values when setting the instance's values.
|
|
59
|
-
def set_values(row)
|
|
60
|
-
super(force_hash_encoding(row))
|
|
61
|
-
end
|
|
62
|
-
|
|
63
63
|
# Force the encoding of all returned strings to the model's forced_encoding.
|
|
64
64
|
def typecast_value(column, value)
|
|
65
65
|
s = super
|
|
@@ -22,10 +22,7 @@ module Sequel
|
|
|
22
22
|
# Identity maps are thread-local and only persist for the duration of the block,
|
|
23
23
|
# so they should only be considered as a possible performance enhancer.
|
|
24
24
|
#
|
|
25
|
-
# The identity_map plugin is not compatible with the
|
|
26
|
-
# many_to_many and many_through_many associations. If you want to use the identity_map plugin,
|
|
27
|
-
# you should use +eager_graph+ instead of +eager+ for those associations. It is also
|
|
28
|
-
# not compatible with the eager loading in the +rcte_tree+ plugin.
|
|
25
|
+
# The identity_map plugin is not compatible with the eager loading in the +rcte_tree+ plugin.
|
|
29
26
|
#
|
|
30
27
|
# Usage:
|
|
31
28
|
#
|
|
@@ -37,6 +34,116 @@ module Sequel
|
|
|
37
34
|
# # would need to do Album.with_identity_map{} to use the identity map
|
|
38
35
|
module IdentityMap
|
|
39
36
|
module ClassMethods
|
|
37
|
+
# Override the default :eager_loader option for many_*_many associations to work
|
|
38
|
+
# with an identity_map. If the :eager_graph association option is used, you'll probably have to use
|
|
39
|
+
# :uniq=>true on the current association amd :cartesian_product_number=>2 on the association
|
|
40
|
+
# mentioned by :eager_graph, otherwise you'll end up with duplicates because the row proc will be
|
|
41
|
+
# getting called multiple times for the same object. If you do have duplicates and you use :eager_graph,
|
|
42
|
+
# they'll probably be lost. Making that work correctly would require changing a lot of the core
|
|
43
|
+
# architecture, such as how graphing and eager graphing work.
|
|
44
|
+
def associate(type, name, opts = {}, &block)
|
|
45
|
+
if opts[:eager_loader]
|
|
46
|
+
super
|
|
47
|
+
elsif type == :many_to_many
|
|
48
|
+
opts = super
|
|
49
|
+
el = opts[:eager_loader]
|
|
50
|
+
model = self
|
|
51
|
+
left_pk = opts[:left_primary_key]
|
|
52
|
+
uses_lcks = opts[:uses_left_composite_keys]
|
|
53
|
+
uses_rcks = opts[:uses_right_composite_keys]
|
|
54
|
+
right = opts[:right_key]
|
|
55
|
+
join_table = opts[:join_table]
|
|
56
|
+
left = opts[:left_key]
|
|
57
|
+
lcks = opts[:left_keys]
|
|
58
|
+
left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
|
|
59
|
+
opts[:eager_loader] = lambda do |eo|
|
|
60
|
+
return el.call(eo) unless model.identity_map
|
|
61
|
+
h = eo[:key_hash][left_pk]
|
|
62
|
+
eo[:rows].each{|object| object.associations[name] = []}
|
|
63
|
+
r = uses_rcks ? rcks.zip(opts.right_primary_keys) : [[right, opts.right_primary_key]]
|
|
64
|
+
l = uses_lcks ? [[lcks.map{|k| SQL::QualifiedIdentifier.new(join_table, k)}, h.keys]] : [[left, h.keys]]
|
|
65
|
+
|
|
66
|
+
# Replace the row proc to remove the left key alias before calling the previous row proc.
|
|
67
|
+
# Associate the value of the left key alias with the associated object (through its object_id).
|
|
68
|
+
# When loading the associated objects, lookup the left key alias value and associate the
|
|
69
|
+
# associated objects to the main objects if the left key alias value matches the left primary key
|
|
70
|
+
# value of the main object.
|
|
71
|
+
#
|
|
72
|
+
# The deleting of the left key alias from the hash before calling the previous row proc
|
|
73
|
+
# is necessary when an identity map is used, otherwise if the same associated object is returned more than
|
|
74
|
+
# once for the association, it won't know which of current objects to associate it to.
|
|
75
|
+
ds = opts.associated_class.inner_join(join_table, r + l)
|
|
76
|
+
pr = ds.row_proc
|
|
77
|
+
h2 = {}
|
|
78
|
+
ds.row_proc = proc do |hash|
|
|
79
|
+
hash_key = if uses_lcks
|
|
80
|
+
left_key_alias.map{|k| hash.delete(k)}
|
|
81
|
+
else
|
|
82
|
+
hash.delete(left_key_alias)
|
|
83
|
+
end
|
|
84
|
+
obj = pr.call(hash)
|
|
85
|
+
(h2[obj.object_id] ||= []) << hash_key
|
|
86
|
+
obj
|
|
87
|
+
end
|
|
88
|
+
model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo) .all do |assoc_record|
|
|
89
|
+
if hash_keys = h2.delete(assoc_record.object_id)
|
|
90
|
+
hash_keys.each do |hash_key|
|
|
91
|
+
if objects = h[hash_key]
|
|
92
|
+
objects.each{|object| object.associations[name].push(assoc_record)}
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
opts
|
|
99
|
+
elsif type == :many_through_many
|
|
100
|
+
opts = super
|
|
101
|
+
el = opts[:eager_loader]
|
|
102
|
+
model = self
|
|
103
|
+
left_pk = opts[:left_primary_key]
|
|
104
|
+
left_key = opts[:left_key]
|
|
105
|
+
uses_lcks = opts[:uses_left_composite_keys]
|
|
106
|
+
left_keys = Array(left_key)
|
|
107
|
+
left_key_alias = opts[:left_key_alias]
|
|
108
|
+
opts[:eager_loader] = lambda do |eo|
|
|
109
|
+
return el.call(eo) unless model.identity_map
|
|
110
|
+
h = eo[:key_hash][left_pk]
|
|
111
|
+
eo[:rows].each{|object| object.associations[name] = []}
|
|
112
|
+
ds = opts.associated_class
|
|
113
|
+
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
|
114
|
+
ft = opts[:final_reverse_edge]
|
|
115
|
+
conds = uses_lcks ? [[left_keys.map{|k| SQL::QualifiedIdentifier.new(ft[:table], k)}, h.keys]] : [[left_key, h.keys]]
|
|
116
|
+
|
|
117
|
+
# See above comment in many_to_many eager_loader
|
|
118
|
+
ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + conds, :table_alias=>ft[:alias])
|
|
119
|
+
pr = ds.row_proc
|
|
120
|
+
h2 = {}
|
|
121
|
+
ds.row_proc = proc do |hash|
|
|
122
|
+
hash_key = if uses_lcks
|
|
123
|
+
left_key_alias.map{|k| hash.delete(k)}
|
|
124
|
+
else
|
|
125
|
+
hash.delete(left_key_alias)
|
|
126
|
+
end
|
|
127
|
+
obj = pr.call(hash)
|
|
128
|
+
(h2[obj.object_id] ||= []) << hash_key
|
|
129
|
+
obj
|
|
130
|
+
end
|
|
131
|
+
model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo).all do |assoc_record|
|
|
132
|
+
if hash_keys = h2.delete(assoc_record.object_id)
|
|
133
|
+
hash_keys.each do |hash_key|
|
|
134
|
+
if objects = h[hash_key]
|
|
135
|
+
objects.each{|object| object.associations[name].push(assoc_record)}
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
opts
|
|
142
|
+
else
|
|
143
|
+
super
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
40
147
|
# Returns the current thread-local identity map. Should be a hash if
|
|
41
148
|
# there is an active identity map, and nil otherwise.
|
|
42
149
|
def identity_map
|
|
@@ -56,7 +163,7 @@ module Sequel
|
|
|
56
163
|
# certain fields in an initial query, make modifications to some of those
|
|
57
164
|
# fields and request other, potentially overlapping fields in a new query,
|
|
58
165
|
# and not have the second query override fields you modified.
|
|
59
|
-
def
|
|
166
|
+
def call(row)
|
|
60
167
|
return super unless idm = identity_map
|
|
61
168
|
if o = idm[identity_map_key(Array(primary_key).map{|x| row[x]})]
|
|
62
169
|
o.merge_db_update(row)
|
|
@@ -121,9 +228,9 @@ module Sequel
|
|
|
121
228
|
# key option has a value and the association uses the primary key of
|
|
122
229
|
# the associated class as the :primary_key option, check the identity
|
|
123
230
|
# map for the associated object and return it if present.
|
|
124
|
-
def _load_associated_objects(opts)
|
|
231
|
+
def _load_associated_objects(opts, dynamic_opts={})
|
|
125
232
|
klass = opts.associated_class
|
|
126
|
-
if klass.respond_to?(:identity_map) && idm = klass.identity_map and opts[:type] == :many_to_one and opts
|
|
233
|
+
if !dynamic_opts[:callback] && klass.respond_to?(:identity_map) && idm = klass.identity_map and opts[:type] == :many_to_one and opts.primary_key == klass.primary_key and
|
|
127
234
|
opts[:key] and pk = _associated_object_pk(opts[:key]) and o = idm[klass.identity_map_key(pk)]
|
|
128
235
|
o
|
|
129
236
|
else
|
|
@@ -58,6 +58,16 @@ module Sequel
|
|
|
58
58
|
self[:uses_left_composite_keys] ? (0...self[:through].first[:left].length).map{|i| :"x_foreign_key_#{i}_x"} : :x_foreign_key_x
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
# The hash key to use for the eager loading predicate (left side of IN (1, 2, 3))
|
|
62
|
+
def eager_loading_predicate_key
|
|
63
|
+
self[:eager_loading_predicate_key] ||= begin
|
|
64
|
+
calculate_edges
|
|
65
|
+
e = self[:edges].first
|
|
66
|
+
f = self[:final_reverse_edge]
|
|
67
|
+
qualify(f[:alias], e[:right])
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
61
71
|
# The list of joins to use when eager graphing
|
|
62
72
|
def edges
|
|
63
73
|
self[:edges] || calculate_edges || self[:edges]
|
|
@@ -116,6 +126,7 @@ module Sequel
|
|
|
116
126
|
nil
|
|
117
127
|
end
|
|
118
128
|
end
|
|
129
|
+
|
|
119
130
|
module ClassMethods
|
|
120
131
|
# Create a many_through_many association. Arguments:
|
|
121
132
|
# * name - Same as associate, the name of the association.
|
|
@@ -181,13 +192,27 @@ module Sequel
|
|
|
181
192
|
left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
|
|
182
193
|
opts[:eager_loader] ||= lambda do |eo|
|
|
183
194
|
h = eo[:key_hash][left_pk]
|
|
184
|
-
|
|
195
|
+
rows = eo[:rows]
|
|
196
|
+
rows.each{|object| object.associations[name] = []}
|
|
185
197
|
ds = opts.associated_class
|
|
186
198
|
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
|
187
199
|
ft = opts[:final_reverse_edge]
|
|
188
|
-
|
|
189
|
-
ds =
|
|
190
|
-
|
|
200
|
+
ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + [[opts.eager_loading_predicate_key, h.keys]], :table_alias=>ft[:alias])
|
|
201
|
+
ds = model.eager_loading_dataset(opts, ds, nil, eo[:associations], eo)
|
|
202
|
+
case opts.eager_limit_strategy
|
|
203
|
+
when :window_function
|
|
204
|
+
delete_rn = true
|
|
205
|
+
rn = ds.row_number_column
|
|
206
|
+
ds = apply_window_function_eager_limit_strategy(ds, opts)
|
|
207
|
+
when :correlated_subquery
|
|
208
|
+
ds = apply_correlated_subquery_eager_limit_strategy(ds, opts) do |xds|
|
|
209
|
+
dsa = ds.send(:dataset_alias, 2)
|
|
210
|
+
opts.reverse_edges.each{|t| xds = xds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
|
211
|
+
xds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + left_keys.map{|k| [k, SQL::QualifiedIdentifier.new(ft[:table], k)]}, :table_alias=>dsa)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
ds.all do |assoc_record|
|
|
215
|
+
assoc_record.values.delete(rn) if delete_rn
|
|
191
216
|
hash_key = if uses_lcks
|
|
192
217
|
left_key_alias.map{|k| assoc_record.values.delete(k)}
|
|
193
218
|
else
|
|
@@ -196,6 +221,10 @@ module Sequel
|
|
|
196
221
|
next unless objects = h[hash_key]
|
|
197
222
|
objects.each{|object| object.associations[name].push(assoc_record)}
|
|
198
223
|
end
|
|
224
|
+
if opts.eager_limit_strategy == :ruby
|
|
225
|
+
limit, offset = opts.limit_and_offset
|
|
226
|
+
rows.each{|o| o.associations[name] = o.associations[name].slice(offset||0, limit) || []}
|
|
227
|
+
end
|
|
199
228
|
end
|
|
200
229
|
|
|
201
230
|
join_type = opts[:graph_join_type]
|
|
@@ -204,19 +233,48 @@ module Sequel
|
|
|
204
233
|
only_conditions = opts[:graph_only_conditions]
|
|
205
234
|
use_only_conditions = opts.include?(:graph_only_conditions)
|
|
206
235
|
conditions = opts[:graph_conditions]
|
|
207
|
-
opts[:eager_grapher] ||= proc do |
|
|
208
|
-
|
|
236
|
+
opts[:eager_grapher] ||= proc do |eo|
|
|
237
|
+
ds = eo[:self]
|
|
238
|
+
iq = eo[:implicit_qualifier]
|
|
209
239
|
opts.edges.each do |t|
|
|
210
240
|
ds = ds.graph(t[:table], t.fetch(:only_conditions, (Array(t[:right]).zip(Array(t[:left])) + t[:conditions])), :select=>false, :table_alias=>ds.unused_table_alias(t[:table]), :join_type=>t[:join_type], :implicit_qualifier=>iq, &t[:block])
|
|
211
241
|
iq = nil
|
|
212
242
|
end
|
|
213
243
|
fe = opts[:final_edge]
|
|
214
|
-
ds.graph(opts.associated_class, use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>
|
|
244
|
+
ds.graph(opts.associated_class, use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>eo[:table_alias], :join_type=>join_type, &graph_block)
|
|
215
245
|
end
|
|
216
246
|
|
|
217
247
|
def_association_dataset_methods(opts)
|
|
218
248
|
end
|
|
219
249
|
end
|
|
250
|
+
|
|
251
|
+
module DatasetMethods
|
|
252
|
+
private
|
|
253
|
+
|
|
254
|
+
# Use a subquery to filter rows to those related to the given associated object
|
|
255
|
+
def many_through_many_association_filter_expression(op, ref, obj)
|
|
256
|
+
lpks = ref[:left_primary_keys]
|
|
257
|
+
lpks = lpks.first if lpks.length == 1
|
|
258
|
+
edges = ref.edges
|
|
259
|
+
first, rest = edges.first, edges[1..-1]
|
|
260
|
+
last = edges.last
|
|
261
|
+
ds = model.db[first[:table]].select(*Array(ref.qualify(first[:table], first[:right])))
|
|
262
|
+
rest.each{|e| ds = ds.join(e[:table], e.fetch(:only_conditions, (Array(e[:right]).zip(Array(e[:left])) + e[:conditions])), :table_alias=>ds.unused_table_alias(e[:table]), &e[:block])}
|
|
263
|
+
last_alias = if rest.empty?
|
|
264
|
+
first[:table]
|
|
265
|
+
else
|
|
266
|
+
last_join = ds.opts[:join].last
|
|
267
|
+
last_join.table_alias || last_join.table
|
|
268
|
+
end
|
|
269
|
+
exp = association_filter_key_expression(ref.qualify(last_alias, Array(ref[:final_edge][:left])), ref.right_primary_keys, obj)
|
|
270
|
+
if exp == SQL::Constants::FALSE
|
|
271
|
+
association_filter_handle_inversion(op, exp, Array(lpks))
|
|
272
|
+
else
|
|
273
|
+
ds = ds.where(exp).exclude(SQL::BooleanExpression.from_value_pairs(ds.opts[:select].zip([]), :OR))
|
|
274
|
+
association_filter_handle_inversion(op, SQL::BooleanExpression.from_value_pairs(lpks=>ds), Array(lpks))
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
220
278
|
end
|
|
221
279
|
end
|
|
222
280
|
end
|