sequel 3.34.1 → 3.35.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 +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
|