sequel 3.34.1 → 3.35.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +52 -0
- data/README.rdoc +3 -1
- data/Rakefile +2 -10
- data/doc/active_record.rdoc +1 -0
- data/doc/migration.rdoc +18 -7
- data/doc/model_hooks.rdoc +6 -0
- data/doc/opening_databases.rdoc +3 -0
- data/doc/prepared_statements.rdoc +0 -1
- data/doc/release_notes/3.35.0.txt +144 -0
- data/doc/schema_modification.rdoc +16 -1
- data/doc/thread_safety.rdoc +17 -0
- data/lib/sequel/adapters/do.rb +2 -2
- data/lib/sequel/adapters/do/postgres.rb +1 -52
- data/lib/sequel/adapters/do/sqlite.rb +0 -5
- data/lib/sequel/adapters/firebird.rb +1 -1
- data/lib/sequel/adapters/ibmdb.rb +2 -2
- data/lib/sequel/adapters/jdbc.rb +23 -19
- data/lib/sequel/adapters/jdbc/db2.rb +0 -5
- data/lib/sequel/adapters/jdbc/derby.rb +29 -2
- data/lib/sequel/adapters/jdbc/firebird.rb +0 -5
- data/lib/sequel/adapters/jdbc/h2.rb +1 -1
- data/lib/sequel/adapters/jdbc/hsqldb.rb +7 -0
- data/lib/sequel/adapters/jdbc/informix.rb +0 -5
- data/lib/sequel/adapters/jdbc/jtds.rb +0 -5
- data/lib/sequel/adapters/jdbc/mysql.rb +0 -5
- data/lib/sequel/adapters/jdbc/postgresql.rb +4 -35
- data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
- data/lib/sequel/adapters/jdbc/sqlserver.rb +0 -5
- data/lib/sequel/adapters/jdbc/transactions.rb +4 -4
- data/lib/sequel/adapters/mysql2.rb +1 -1
- data/lib/sequel/adapters/odbc.rb +3 -3
- data/lib/sequel/adapters/odbc/mssql.rb +14 -1
- data/lib/sequel/adapters/oracle.rb +6 -18
- data/lib/sequel/adapters/postgres.rb +36 -53
- data/lib/sequel/adapters/shared/db2.rb +16 -2
- data/lib/sequel/adapters/shared/mssql.rb +40 -9
- data/lib/sequel/adapters/shared/mysql.rb +16 -4
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +2 -2
- data/lib/sequel/adapters/shared/oracle.rb +2 -0
- data/lib/sequel/adapters/shared/postgres.rb +135 -211
- data/lib/sequel/adapters/sqlite.rb +2 -2
- data/lib/sequel/adapters/swift.rb +1 -1
- data/lib/sequel/adapters/swift/postgres.rb +1 -71
- data/lib/sequel/adapters/tinytds.rb +3 -3
- data/lib/sequel/core.rb +27 -4
- data/lib/sequel/database/connecting.rb +7 -8
- data/lib/sequel/database/logging.rb +6 -1
- data/lib/sequel/database/misc.rb +20 -4
- data/lib/sequel/database/query.rb +38 -18
- data/lib/sequel/database/schema_generator.rb +5 -2
- data/lib/sequel/database/schema_methods.rb +34 -8
- data/lib/sequel/dataset/prepared_statements.rb +1 -1
- data/lib/sequel/dataset/sql.rb +18 -24
- data/lib/sequel/extensions/core_extensions.rb +0 -23
- data/lib/sequel/extensions/migration.rb +22 -8
- data/lib/sequel/extensions/pg_auto_parameterize.rb +4 -0
- data/lib/sequel/extensions/schema_dumper.rb +1 -1
- data/lib/sequel/model.rb +2 -2
- data/lib/sequel/model/associations.rb +95 -70
- data/lib/sequel/model/base.rb +16 -18
- data/lib/sequel/plugins/dirty.rb +214 -0
- data/lib/sequel/plugins/identity_map.rb +1 -1
- data/lib/sequel/plugins/json_serializer.rb +16 -1
- data/lib/sequel/plugins/many_through_many.rb +22 -32
- data/lib/sequel/plugins/many_to_one_pk_lookup.rb +2 -2
- data/lib/sequel/plugins/prepared_statements.rb +22 -8
- data/lib/sequel/plugins/prepared_statements_associations.rb +2 -3
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +1 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
- data/lib/sequel/plugins/subclasses.rb +10 -2
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/xml_serializer.rb +12 -1
- data/lib/sequel/sql.rb +1 -1
- data/lib/sequel/version.rb +2 -2
- data/spec/adapters/postgres_spec.rb +30 -79
- data/spec/core/database_spec.rb +46 -2
- data/spec/core/dataset_spec.rb +28 -22
- data/spec/core/schema_generator_spec.rb +1 -1
- data/spec/core/schema_spec.rb +51 -0
- data/spec/extensions/arbitrary_servers_spec.rb +0 -4
- data/spec/extensions/association_autoreloading_spec.rb +17 -0
- data/spec/extensions/association_proxies_spec.rb +4 -4
- data/spec/extensions/core_extensions_spec.rb +1 -24
- data/spec/extensions/dirty_spec.rb +155 -0
- data/spec/extensions/json_serializer_spec.rb +13 -0
- data/spec/extensions/migration_spec.rb +28 -15
- data/spec/extensions/named_timezones_spec.rb +6 -8
- data/spec/extensions/pg_auto_parameterize_spec.rb +6 -5
- data/spec/extensions/schema_dumper_spec.rb +3 -1
- data/spec/extensions/xml_serializer_spec.rb +13 -0
- data/spec/files/{transactionless_migrations → transaction_specified_migrations}/001_create_alt_basic.rb +1 -1
- data/spec/files/{transactionless_migrations → transaction_specified_migrations}/002_create_basic.rb +0 -0
- data/spec/files/{transaction_migrations → transaction_unspecified_migrations}/001_create_alt_basic.rb +0 -0
- data/spec/files/{transaction_migrations → transaction_unspecified_migrations}/002_create_basic.rb +0 -0
- data/spec/integration/associations_test.rb +5 -7
- data/spec/integration/dataset_test.rb +25 -7
- data/spec/integration/plugin_test.rb +1 -1
- data/spec/integration/schema_test.rb +16 -1
- data/spec/model/associations_spec.rb +2 -2
- metadata +14 -9
- data/lib/sequel/adapters/odbc/db2.rb +0 -17
data/lib/sequel/model/base.rb
CHANGED
@@ -220,7 +220,7 @@ module Sequel
|
|
220
220
|
# end # COMMIT
|
221
221
|
def db
|
222
222
|
return @db if @db
|
223
|
-
@db = self == Model ? DATABASES.first : superclass.db
|
223
|
+
@db = self == Model ? Sequel.synchronize{DATABASES.first} : superclass.db
|
224
224
|
raise(Error, "No database associated with #{self}: have you called Sequel.connect or #{self}.db= ?") unless @db
|
225
225
|
@db
|
226
226
|
end
|
@@ -295,21 +295,6 @@ module Sequel
|
|
295
295
|
end
|
296
296
|
end
|
297
297
|
|
298
|
-
module_eval(if RUBY_VERSION < '1.8.7'
|
299
|
-
<<-END
|
300
|
-
def def_model_dataset_method_block(arg)
|
301
|
-
meta_def(arg){|*args| dataset.send(arg, *args)}
|
302
|
-
end
|
303
|
-
END
|
304
|
-
else
|
305
|
-
<<-END
|
306
|
-
def def_model_dataset_method_block(arg)
|
307
|
-
meta_def(arg){|*args, &block| dataset.send(arg, *args, &block)}
|
308
|
-
end
|
309
|
-
END
|
310
|
-
end, __FILE__, __LINE__ - 4)
|
311
|
-
private :def_model_dataset_method_block
|
312
|
-
|
313
298
|
# Finds a single record according to the supplied filter.
|
314
299
|
# You are encouraged to use Model.[] or Model.first instead of this method.
|
315
300
|
#
|
@@ -685,6 +670,13 @@ module Sequel
|
|
685
670
|
end
|
686
671
|
end
|
687
672
|
|
673
|
+
# Define a model method that calls the dataset method with the same name,
|
674
|
+
# only used for methods with names that can't be presented directly in
|
675
|
+
# ruby code.
|
676
|
+
def def_model_dataset_method_block(arg)
|
677
|
+
meta_def(arg){|*args, &block| dataset.send(arg, *args, &block)}
|
678
|
+
end
|
679
|
+
|
688
680
|
# Get the schema from the database, fall back on checking the columns
|
689
681
|
# via the database if that will return inaccurate results or if
|
690
682
|
# it raises an error.
|
@@ -935,8 +927,7 @@ module Sequel
|
|
935
927
|
v = typecast_value(column, value)
|
936
928
|
vals = @values
|
937
929
|
if new? || !vals.include?(column) || v != (c = vals[column]) || v.class != c.class
|
938
|
-
|
939
|
-
vals[column] = v
|
930
|
+
change_column_value(column, v)
|
940
931
|
end
|
941
932
|
end
|
942
933
|
|
@@ -1710,6 +1701,13 @@ module Sequel
|
|
1710
1701
|
use_transaction?(opts) ? db.transaction({:server=>this_server}.merge(opts)){yield} : yield
|
1711
1702
|
end
|
1712
1703
|
|
1704
|
+
# Change the value of the column to given value, recording the change.
|
1705
|
+
def change_column_value(column, value)
|
1706
|
+
cc = changed_columns
|
1707
|
+
cc << column unless cc.include?(column)
|
1708
|
+
@values[column] = value
|
1709
|
+
end
|
1710
|
+
|
1713
1711
|
# Set the columns with the given hash. By default, the same as +set+, but
|
1714
1712
|
# exists so it can be overridden. This is called only for new records, before
|
1715
1713
|
# changed_columns is cleared.
|
@@ -0,0 +1,214 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# The dirty plugin makes Sequel save the initial value of
|
4
|
+
# a column when setting a new value for the column. This
|
5
|
+
# makes it easier to see what changes were made to the object:
|
6
|
+
#
|
7
|
+
# artist.name # => 'Foo'
|
8
|
+
# artist.name = 'Bar'
|
9
|
+
# artist.initial_value(:name) # 'Foo'
|
10
|
+
# artist.column_change(:name) # ['Foo', 'Bar']
|
11
|
+
# artist.column_changes # {:name => ['Foo', 'Bar']}
|
12
|
+
# artist.column_changed?(:name) # true
|
13
|
+
# artist.reset_column(:name)
|
14
|
+
# artist.name # => 'Foo'
|
15
|
+
# artist.column_changed?(:name) # false
|
16
|
+
#
|
17
|
+
# It allows makes changed_columns more accurate in that it
|
18
|
+
# can detect when a the column value is changed and then
|
19
|
+
# changed back:
|
20
|
+
#
|
21
|
+
# artist.name # => 'Foo'
|
22
|
+
# artist.name = 'Bar'
|
23
|
+
# artist.changed_columns # => [:name]
|
24
|
+
# artist.name = 'Foo'
|
25
|
+
# artist.changed_columns # => []
|
26
|
+
#
|
27
|
+
# It can handle situations where a column value is
|
28
|
+
# modified in place:
|
29
|
+
#
|
30
|
+
# artist.will_change_column(:name)
|
31
|
+
# artist.name.gsub!(/o/, 'u')
|
32
|
+
# artist.changed_columns # => [:name]
|
33
|
+
# artist.initial_value(:name) # => 'Foo'
|
34
|
+
# artist.column_change(:name) # => ['Foo', 'Fuu']
|
35
|
+
#
|
36
|
+
# It also saves the previously changed values after an update:
|
37
|
+
#
|
38
|
+
# artist.update(:name=>'Bar')
|
39
|
+
# artist.column_changes # => {}
|
40
|
+
# artist.previous_changes # => {:name=>['Foo', 'Bar']}
|
41
|
+
#
|
42
|
+
# Usage:
|
43
|
+
#
|
44
|
+
# # Make all model subclass instances record previous values (called before loading subclasses)
|
45
|
+
# Sequel::Model.plugin :dirty
|
46
|
+
#
|
47
|
+
# # Make the Album class record previous values
|
48
|
+
# Album.plugin :dirty
|
49
|
+
module Dirty
|
50
|
+
module InstanceMethods
|
51
|
+
# A hash of previous changes before the object was
|
52
|
+
# saved, in the same format as #column_changes.
|
53
|
+
# Note that this is not necessarily the same as the columns
|
54
|
+
# that were used in the update statement.
|
55
|
+
attr_reader :previous_changes
|
56
|
+
|
57
|
+
# An array with the initial value and the current value
|
58
|
+
# of the column, if the column has been changed. If the
|
59
|
+
# column has not been changed, returns nil.
|
60
|
+
#
|
61
|
+
# column_change(:name) # => ['Initial', 'Current']
|
62
|
+
def column_change(column)
|
63
|
+
[initial_value(column), send(column)] if column_changed?(column)
|
64
|
+
end
|
65
|
+
|
66
|
+
# A hash with column symbol keys and pairs of initial and
|
67
|
+
# current values for all changed columns.
|
68
|
+
#
|
69
|
+
# column_changes # => {:name => ['Initial', 'Current']}
|
70
|
+
def column_changes
|
71
|
+
h = {}
|
72
|
+
initial_values.each do |column, value|
|
73
|
+
h[column] = [value, send(column)]
|
74
|
+
end
|
75
|
+
h
|
76
|
+
end
|
77
|
+
|
78
|
+
# Either true or false depending on whether the column has
|
79
|
+
# changed. Note that this is not exactly the same as checking if
|
80
|
+
# the column is in changed_columns, if the column was not set
|
81
|
+
# initially.
|
82
|
+
#
|
83
|
+
# column_changed?(:name) # => true
|
84
|
+
def column_changed?(column)
|
85
|
+
initial_values.has_key?(column)
|
86
|
+
end
|
87
|
+
|
88
|
+
# The initial value of the given column. If the column value has
|
89
|
+
# not changed, this will be the same as the current value of the
|
90
|
+
# column.
|
91
|
+
#
|
92
|
+
# initial_value(:name) # => 'Initial'
|
93
|
+
def initial_value(column)
|
94
|
+
initial_values.fetch(column){send(column)}
|
95
|
+
end
|
96
|
+
|
97
|
+
# A hash with column symbol keys and initial values.
|
98
|
+
#
|
99
|
+
# initial_values # {:name => 'Initial'}
|
100
|
+
def initial_values
|
101
|
+
@initial_values ||= {}
|
102
|
+
end
|
103
|
+
|
104
|
+
# Reset the column to its initial value. If the column was not set
|
105
|
+
# initial, removes it from the values.
|
106
|
+
#
|
107
|
+
# reset_column(:name)
|
108
|
+
# name # => 'Initial'
|
109
|
+
def reset_column(column)
|
110
|
+
if initial_values.has_key?(column)
|
111
|
+
send(:"#{column}=", initial_values[column])
|
112
|
+
end
|
113
|
+
if missing_initial_values.include?(column)
|
114
|
+
values.delete(column)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Manually specify that a column will change. This should only be used
|
119
|
+
# if you plan to modify a column value in place, which is not recommended.
|
120
|
+
#
|
121
|
+
# will_change_column(:name)
|
122
|
+
# name.gsub(/i/i, 'o')
|
123
|
+
# column_change(:name) # => ['Initial', 'onotoal']
|
124
|
+
def will_change_column(column)
|
125
|
+
changed_columns << column unless changed_columns.include?(column)
|
126
|
+
check_missing_initial_value(column)
|
127
|
+
|
128
|
+
value = if initial_values.has_key?(column)
|
129
|
+
initial_values[column]
|
130
|
+
else
|
131
|
+
send(column)
|
132
|
+
end
|
133
|
+
|
134
|
+
initial_values[column] = if value && value != true && value.respond_to?(:clone)
|
135
|
+
begin
|
136
|
+
value.clone
|
137
|
+
rescue TypeError
|
138
|
+
value
|
139
|
+
end
|
140
|
+
else
|
141
|
+
value
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
# Reset the initial values after saving.
|
148
|
+
def after_save
|
149
|
+
super
|
150
|
+
reset_initial_values
|
151
|
+
end
|
152
|
+
|
153
|
+
# Save the current changes so they are available after updating. This happens
|
154
|
+
# before after_save resets them.
|
155
|
+
def after_update
|
156
|
+
super
|
157
|
+
@previous_changes = column_changes
|
158
|
+
end
|
159
|
+
|
160
|
+
# Reset the initial values when refreshing.
|
161
|
+
def _refresh(dataset)
|
162
|
+
super
|
163
|
+
reset_initial_values
|
164
|
+
end
|
165
|
+
|
166
|
+
# When changing the column value, save the initial column value. If the column
|
167
|
+
# value is changed back to the initial value, update changed columns to remove
|
168
|
+
# the column.
|
169
|
+
def change_column_value(column, value)
|
170
|
+
if (iv = initial_values).has_key?(column)
|
171
|
+
initial = iv[column]
|
172
|
+
super
|
173
|
+
if value == initial
|
174
|
+
changed_columns.delete(column) unless missing_initial_values.include?(column)
|
175
|
+
iv.delete(column)
|
176
|
+
end
|
177
|
+
else
|
178
|
+
check_missing_initial_value(column)
|
179
|
+
iv[column] = send(column)
|
180
|
+
super
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# If the values hash does not contain the column, make sure missing_initial_values
|
185
|
+
# does so that it doesn't get deleted from changed_columns if changed back,
|
186
|
+
# and so that reseting the column value can be handled correctly.
|
187
|
+
def check_missing_initial_value(column)
|
188
|
+
unless values.has_key?(column) || (miv = missing_initial_values).include?(column)
|
189
|
+
miv << column
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Reset the initial values when initializing.
|
194
|
+
def initialize_set(h)
|
195
|
+
super
|
196
|
+
reset_initial_values
|
197
|
+
end
|
198
|
+
|
199
|
+
# Array holding column symbols that were not present initially. This is necessary
|
200
|
+
# to differentiate between values that were not present and values that were
|
201
|
+
# present but equal to nil.
|
202
|
+
def missing_initial_values
|
203
|
+
@missing_initial_values ||= []
|
204
|
+
end
|
205
|
+
|
206
|
+
# Clear the data structures that store the initial values.
|
207
|
+
def reset_initial_values
|
208
|
+
@initial_values.clear if @initial_values
|
209
|
+
@missing_initial_values.clear if @missing_initial_values
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
@@ -111,7 +111,7 @@ module Sequel
|
|
111
111
|
eo[:rows].each{|object| object.associations[name] = []}
|
112
112
|
ds = opts.associated_class
|
113
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
|
114
|
+
ft = opts.final_reverse_edge
|
115
115
|
conds = uses_lcks ? [[left_keys.map{|k| SQL::QualifiedIdentifier.new(ft[:table], k)}, h.keys]] : [[left_key, h.keys]]
|
116
116
|
|
117
117
|
# See above comment in many_to_many eager_loader
|
@@ -56,6 +56,11 @@ module Sequel
|
|
56
56
|
# Album.to_json
|
57
57
|
# Album.filter(:artist_id=>1).to_json(:include=>:tags)
|
58
58
|
#
|
59
|
+
# If you have an existing array of model instances you want to convert to
|
60
|
+
# JSON, you can call the class to_json method with the :array option:
|
61
|
+
#
|
62
|
+
# Album.to_json(:array=>[Album[1], Album[2]])
|
63
|
+
#
|
59
64
|
# Usage:
|
60
65
|
#
|
61
66
|
# # Add JSON output capability to all model subclass instances (called before loading subclasses)
|
@@ -203,7 +208,17 @@ module Sequel
|
|
203
208
|
else
|
204
209
|
opts = model.json_serializer_opts
|
205
210
|
end
|
206
|
-
res = row_proc
|
211
|
+
res = if row_proc
|
212
|
+
array = if opts[:array]
|
213
|
+
opts = opts.dup
|
214
|
+
opts.delete(:array)
|
215
|
+
else
|
216
|
+
all
|
217
|
+
end
|
218
|
+
array.map{|obj| Literal.new(obj.to_json(opts))}
|
219
|
+
else
|
220
|
+
all
|
221
|
+
end
|
207
222
|
opts[:root] ? {model.send(:pluralize, model.send(:underscore, model.to_s)) => res}.to_json(*a) : res.to_json(*a)
|
208
223
|
end
|
209
224
|
end
|
@@ -47,30 +47,18 @@ module Sequel
|
|
47
47
|
class ManyThroughManyAssociationReflection < Sequel::Model::Associations::ManyToManyAssociationReflection
|
48
48
|
Sequel::Model::Associations::ASSOCIATION_TYPES[:many_through_many] = self
|
49
49
|
|
50
|
-
# The table containing the column to use for the associated key when eagerly loading
|
51
|
-
def associated_key_table
|
52
|
-
self[:associated_key_table] = self[:final_reverse_edge][:alias]
|
53
|
-
end
|
54
|
-
|
55
50
|
# The default associated key alias(es) to use when eager loading
|
56
51
|
# associations via eager.
|
57
52
|
def default_associated_key_alias
|
58
53
|
self[:uses_left_composite_keys] ? (0...self[:through].first[:left].length).map{|i| :"x_foreign_key_#{i}_x"} : :x_foreign_key_x
|
59
54
|
end
|
60
55
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
qualify(f[:alias], e[:right])
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
# The list of joins to use when eager graphing
|
72
|
-
def edges
|
73
|
-
self[:edges] || calculate_edges || self[:edges]
|
56
|
+
%w'associated_key_table eager_loading_predicate_key edges final_edge final_reverse_edge reverse_edges'.each do |meth|
|
57
|
+
class_eval(<<-END, __FILE__, __LINE__+1)
|
58
|
+
def #{meth}
|
59
|
+
cached_fetch(:#{meth}){calculate_edges[:#{meth}]}
|
60
|
+
end
|
61
|
+
END
|
74
62
|
end
|
75
63
|
|
76
64
|
# Many through many associations don't have a reciprocal
|
@@ -78,11 +66,6 @@ module Sequel
|
|
78
66
|
nil
|
79
67
|
end
|
80
68
|
|
81
|
-
# The list of joins to use when lazy loading or eager loading
|
82
|
-
def reverse_edges
|
83
|
-
self[:reverse_edges] || calculate_edges || self[:reverse_edges]
|
84
|
-
end
|
85
|
-
|
86
69
|
private
|
87
70
|
|
88
71
|
# Make sure to use unique table aliases when lazy loading or eager loading
|
@@ -119,11 +102,18 @@ module Sequel
|
|
119
102
|
reverse_edges = es.reverse.map{|e| {:table=>e[:left_table], :left=>e[:left_key], :right=>e[:right_key]}}
|
120
103
|
reverse_edges.pop
|
121
104
|
calculate_reverse_edge_aliases(reverse_edges)
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
105
|
+
final_reverse_edge = reverse_edges.pop
|
106
|
+
final_reverse_alias = final_reverse_edge[:alias]
|
107
|
+
|
108
|
+
h = {:final_edge=>edges.pop,
|
109
|
+
:final_reverse_edge=>final_reverse_edge,
|
110
|
+
:edges=>edges,
|
111
|
+
:reverse_edges=>reverse_edges,
|
112
|
+
:eager_loading_predicate_key=>qualify(final_reverse_alias, edges.first[:right]),
|
113
|
+
:associated_key_table=>final_reverse_edge[:alias],
|
114
|
+
}
|
115
|
+
h.each{|k, v| cached_set(k, v)}
|
116
|
+
h
|
127
117
|
end
|
128
118
|
end
|
129
119
|
|
@@ -185,7 +175,7 @@ module Sequel
|
|
185
175
|
opts[:dataset] ||= lambda do
|
186
176
|
ds = opts.associated_class
|
187
177
|
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
188
|
-
ft = opts
|
178
|
+
ft = opts.final_reverse_edge
|
189
179
|
ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + left_keys.zip(left_pks.map{|k| send(k)}), :table_alias=>ft[:alias])
|
190
180
|
end
|
191
181
|
|
@@ -196,7 +186,7 @@ module Sequel
|
|
196
186
|
rows.each{|object| object.associations[name] = []}
|
197
187
|
ds = opts.associated_class
|
198
188
|
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
199
|
-
ft = opts
|
189
|
+
ft = opts.final_reverse_edge
|
200
190
|
ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + [[opts.eager_loading_predicate_key, h.keys]], :table_alias=>ft[:alias])
|
201
191
|
ds = model.eager_loading_dataset(opts, ds, nil, eo[:associations], eo)
|
202
192
|
case opts.eager_limit_strategy
|
@@ -240,7 +230,7 @@ module Sequel
|
|
240
230
|
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])
|
241
231
|
iq = nil
|
242
232
|
end
|
243
|
-
fe = opts
|
233
|
+
fe = opts.final_edge
|
244
234
|
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)
|
245
235
|
end
|
246
236
|
|
@@ -269,7 +259,7 @@ module Sequel
|
|
269
259
|
end
|
270
260
|
meths = ref.right_primary_keys
|
271
261
|
meths = ref.qualify(obj.model.table_name, meths) if obj.is_a?(Sequel::Dataset)
|
272
|
-
exp = association_filter_key_expression(ref.qualify(last_alias, Array(ref
|
262
|
+
exp = association_filter_key_expression(ref.qualify(last_alias, Array(ref.final_edge[:left])), meths, obj)
|
273
263
|
if exp == SQL::Constants::FALSE
|
274
264
|
association_filter_handle_inversion(op, exp, Array(lpks))
|
275
265
|
else
|
@@ -38,8 +38,8 @@ module Sequel
|
|
38
38
|
# caching if the associated model is using caching.
|
39
39
|
def _load_associated_object(opts, dynamic_opts)
|
40
40
|
klass = opts.associated_class
|
41
|
-
cache_lookup = opts.
|
42
|
-
opts[:
|
41
|
+
cache_lookup = opts.send(:cached_fetch, :many_to_one_pk_lookup) do
|
42
|
+
opts[:type] == :many_to_one &&
|
43
43
|
opts[:key] &&
|
44
44
|
opts.primary_key == klass.primary_key
|
45
45
|
end
|
@@ -31,14 +31,14 @@ module Sequel
|
|
31
31
|
|
32
32
|
# Setup the datastructure used to hold the prepared statements in the model.
|
33
33
|
def self.apply(model)
|
34
|
-
model.instance_variable_set(:@prepared_statements, :insert=>{}, :insert_select=>{}, :update=>{}, :lookup_sql=>{})
|
34
|
+
model.instance_variable_set(:@prepared_statements, :insert=>{}, :insert_select=>{}, :update=>{}, :lookup_sql=>{}, :fixed=>{})
|
35
35
|
end
|
36
36
|
|
37
37
|
module ClassMethods
|
38
38
|
# Setup the datastructure used to hold the prepared statements in the subclass.
|
39
39
|
def inherited(subclass)
|
40
40
|
super
|
41
|
-
subclass.instance_variable_set(:@prepared_statements, :insert=>{}, :insert_select=>{}, :update=>{}, :lookup_sql=>{})
|
41
|
+
subclass.instance_variable_set(:@prepared_statements, :insert=>{}, :insert_select=>{}, :update=>{}, :lookup_sql=>{}, :fixed=>{})
|
42
42
|
end
|
43
43
|
|
44
44
|
private
|
@@ -58,30 +58,30 @@ module Sequel
|
|
58
58
|
|
59
59
|
# Return a prepared statement that can be used to delete a row from this model's dataset.
|
60
60
|
def prepared_delete
|
61
|
-
|
61
|
+
cached_prepared_statement(:fixed, :delete){prepare_statement(filter(prepared_statement_key_array(primary_key)), :delete)}
|
62
62
|
end
|
63
63
|
|
64
64
|
# Return a prepared statement that can be used to insert a row using the given columns.
|
65
65
|
def prepared_insert(cols)
|
66
|
-
|
66
|
+
cached_prepared_statement(:insert, prepared_columns(cols)){prepare_statement(dataset, :insert, prepared_statement_key_hash(cols))}
|
67
67
|
end
|
68
68
|
|
69
69
|
# Return a prepared statement that can be used to insert a row using the given columns
|
70
70
|
# and return that column values for the row created.
|
71
71
|
def prepared_insert_select(cols)
|
72
72
|
if dataset.supports_insert_select?
|
73
|
-
|
73
|
+
cached_prepared_statement(:insert_select, prepared_columns(cols)){prepare_statement(naked.clone(:server=>dataset.opts.fetch(:server, :default)), :insert_select, prepared_statement_key_hash(cols))}
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
77
77
|
# Return a prepared statement that can be used to lookup a row solely based on the primary key.
|
78
78
|
def prepared_lookup
|
79
|
-
|
79
|
+
cached_prepared_statement(:fixed, :lookup){prepare_statement(filter(prepared_statement_key_array(primary_key)), :first)}
|
80
80
|
end
|
81
81
|
|
82
82
|
# Return a prepared statement that can be used to refresh a row to get new column values after insertion.
|
83
83
|
def prepared_refresh
|
84
|
-
|
84
|
+
cached_prepared_statement(:fixed, :refresh){prepare_statement(naked.clone(:server=>dataset.opts.fetch(:server, :default)).filter(prepared_statement_key_array(primary_key)), :first)}
|
85
85
|
end
|
86
86
|
|
87
87
|
# Return an array of two element arrays with the column symbol as the first entry and the
|
@@ -108,13 +108,27 @@ module Sequel
|
|
108
108
|
|
109
109
|
# Return a prepared statement that can be used to update row using the given columns.
|
110
110
|
def prepared_update(cols)
|
111
|
-
|
111
|
+
cached_prepared_statement(:update, prepared_columns(cols)){prepare_statement(filter(prepared_statement_key_array(primary_key)), :update, prepared_statement_key_hash(cols))}
|
112
112
|
end
|
113
113
|
|
114
114
|
# Use a prepared statement to query the database for the row matching the given primary key.
|
115
115
|
def primary_key_lookup(pk)
|
116
116
|
prepared_lookup.call(primary_key_hash(pk))
|
117
117
|
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
# If a prepared statement has already been cached for the given type and subtype,
|
122
|
+
# return it. Otherwise, yield to the block to get the prepared statement, and cache it.
|
123
|
+
def cached_prepared_statement(type, subtype)
|
124
|
+
h = @prepared_statements[type]
|
125
|
+
Sequel.synchronize do
|
126
|
+
if v = h[subtype]
|
127
|
+
return v end
|
128
|
+
end
|
129
|
+
ps = yield
|
130
|
+
Sequel.synchronize{h[subtype] = ps}
|
131
|
+
end
|
118
132
|
end
|
119
133
|
|
120
134
|
module InstanceMethods
|