sequel 3.34.1 → 3.35.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. data/CHANGELOG +52 -0
  2. data/README.rdoc +3 -1
  3. data/Rakefile +2 -10
  4. data/doc/active_record.rdoc +1 -0
  5. data/doc/migration.rdoc +18 -7
  6. data/doc/model_hooks.rdoc +6 -0
  7. data/doc/opening_databases.rdoc +3 -0
  8. data/doc/prepared_statements.rdoc +0 -1
  9. data/doc/release_notes/3.35.0.txt +144 -0
  10. data/doc/schema_modification.rdoc +16 -1
  11. data/doc/thread_safety.rdoc +17 -0
  12. data/lib/sequel/adapters/do.rb +2 -2
  13. data/lib/sequel/adapters/do/postgres.rb +1 -52
  14. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  15. data/lib/sequel/adapters/firebird.rb +1 -1
  16. data/lib/sequel/adapters/ibmdb.rb +2 -2
  17. data/lib/sequel/adapters/jdbc.rb +23 -19
  18. data/lib/sequel/adapters/jdbc/db2.rb +0 -5
  19. data/lib/sequel/adapters/jdbc/derby.rb +29 -2
  20. data/lib/sequel/adapters/jdbc/firebird.rb +0 -5
  21. data/lib/sequel/adapters/jdbc/h2.rb +1 -1
  22. data/lib/sequel/adapters/jdbc/hsqldb.rb +7 -0
  23. data/lib/sequel/adapters/jdbc/informix.rb +0 -5
  24. data/lib/sequel/adapters/jdbc/jtds.rb +0 -5
  25. data/lib/sequel/adapters/jdbc/mysql.rb +0 -5
  26. data/lib/sequel/adapters/jdbc/postgresql.rb +4 -35
  27. data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
  28. data/lib/sequel/adapters/jdbc/sqlserver.rb +0 -5
  29. data/lib/sequel/adapters/jdbc/transactions.rb +4 -4
  30. data/lib/sequel/adapters/mysql2.rb +1 -1
  31. data/lib/sequel/adapters/odbc.rb +3 -3
  32. data/lib/sequel/adapters/odbc/mssql.rb +14 -1
  33. data/lib/sequel/adapters/oracle.rb +6 -18
  34. data/lib/sequel/adapters/postgres.rb +36 -53
  35. data/lib/sequel/adapters/shared/db2.rb +16 -2
  36. data/lib/sequel/adapters/shared/mssql.rb +40 -9
  37. data/lib/sequel/adapters/shared/mysql.rb +16 -4
  38. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +2 -2
  39. data/lib/sequel/adapters/shared/oracle.rb +2 -0
  40. data/lib/sequel/adapters/shared/postgres.rb +135 -211
  41. data/lib/sequel/adapters/sqlite.rb +2 -2
  42. data/lib/sequel/adapters/swift.rb +1 -1
  43. data/lib/sequel/adapters/swift/postgres.rb +1 -71
  44. data/lib/sequel/adapters/tinytds.rb +3 -3
  45. data/lib/sequel/core.rb +27 -4
  46. data/lib/sequel/database/connecting.rb +7 -8
  47. data/lib/sequel/database/logging.rb +6 -1
  48. data/lib/sequel/database/misc.rb +20 -4
  49. data/lib/sequel/database/query.rb +38 -18
  50. data/lib/sequel/database/schema_generator.rb +5 -2
  51. data/lib/sequel/database/schema_methods.rb +34 -8
  52. data/lib/sequel/dataset/prepared_statements.rb +1 -1
  53. data/lib/sequel/dataset/sql.rb +18 -24
  54. data/lib/sequel/extensions/core_extensions.rb +0 -23
  55. data/lib/sequel/extensions/migration.rb +22 -8
  56. data/lib/sequel/extensions/pg_auto_parameterize.rb +4 -0
  57. data/lib/sequel/extensions/schema_dumper.rb +1 -1
  58. data/lib/sequel/model.rb +2 -2
  59. data/lib/sequel/model/associations.rb +95 -70
  60. data/lib/sequel/model/base.rb +16 -18
  61. data/lib/sequel/plugins/dirty.rb +214 -0
  62. data/lib/sequel/plugins/identity_map.rb +1 -1
  63. data/lib/sequel/plugins/json_serializer.rb +16 -1
  64. data/lib/sequel/plugins/many_through_many.rb +22 -32
  65. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +2 -2
  66. data/lib/sequel/plugins/prepared_statements.rb +22 -8
  67. data/lib/sequel/plugins/prepared_statements_associations.rb +2 -3
  68. data/lib/sequel/plugins/prepared_statements_with_pk.rb +1 -1
  69. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  70. data/lib/sequel/plugins/subclasses.rb +10 -2
  71. data/lib/sequel/plugins/timestamps.rb +1 -1
  72. data/lib/sequel/plugins/xml_serializer.rb +12 -1
  73. data/lib/sequel/sql.rb +1 -1
  74. data/lib/sequel/version.rb +2 -2
  75. data/spec/adapters/postgres_spec.rb +30 -79
  76. data/spec/core/database_spec.rb +46 -2
  77. data/spec/core/dataset_spec.rb +28 -22
  78. data/spec/core/schema_generator_spec.rb +1 -1
  79. data/spec/core/schema_spec.rb +51 -0
  80. data/spec/extensions/arbitrary_servers_spec.rb +0 -4
  81. data/spec/extensions/association_autoreloading_spec.rb +17 -0
  82. data/spec/extensions/association_proxies_spec.rb +4 -4
  83. data/spec/extensions/core_extensions_spec.rb +1 -24
  84. data/spec/extensions/dirty_spec.rb +155 -0
  85. data/spec/extensions/json_serializer_spec.rb +13 -0
  86. data/spec/extensions/migration_spec.rb +28 -15
  87. data/spec/extensions/named_timezones_spec.rb +6 -8
  88. data/spec/extensions/pg_auto_parameterize_spec.rb +6 -5
  89. data/spec/extensions/schema_dumper_spec.rb +3 -1
  90. data/spec/extensions/xml_serializer_spec.rb +13 -0
  91. data/spec/files/{transactionless_migrations → transaction_specified_migrations}/001_create_alt_basic.rb +1 -1
  92. data/spec/files/{transactionless_migrations → transaction_specified_migrations}/002_create_basic.rb +0 -0
  93. data/spec/files/{transaction_migrations → transaction_unspecified_migrations}/001_create_alt_basic.rb +0 -0
  94. data/spec/files/{transaction_migrations → transaction_unspecified_migrations}/002_create_basic.rb +0 -0
  95. data/spec/integration/associations_test.rb +5 -7
  96. data/spec/integration/dataset_test.rb +25 -7
  97. data/spec/integration/plugin_test.rb +1 -1
  98. data/spec/integration/schema_test.rb +16 -1
  99. data/spec/model/associations_spec.rb +2 -2
  100. metadata +14 -9
  101. data/lib/sequel/adapters/odbc/db2.rb +0 -17
@@ -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
- changed_columns << column unless changed_columns.include?(column)
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[:final_reverse_edge]
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 ? all.map{|obj| Literal.new(obj.to_json(opts))} : all
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
- # 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
-
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
- self[:final_edge] = edges.pop
123
- self[:final_reverse_edge] = reverse_edges.pop
124
- self[:edges] = edges
125
- self[:reverse_edges] = reverse_edges
126
- nil
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[:final_reverse_edge]
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[:final_reverse_edge]
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[:final_edge]
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[:final_edge][:left])), meths, obj)
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.fetch(:many_to_one_pk_lookup) do
42
- opts[:many_to_one_pk_lookup] = opts[:type] == :many_to_one &&
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
- @prepared_statements[:delete] ||= prepare_statement(filter(prepared_statement_key_array(primary_key)), :delete)
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
- @prepared_statements[:insert][prepared_columns(cols)] ||= prepare_statement(dataset, :insert, prepared_statement_key_hash(cols))
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
- @prepared_statements[:insert_select][prepared_columns(cols)] ||= prepare_statement(naked.clone(:server=>dataset.opts.fetch(:server, :default)), :insert_select, prepared_statement_key_hash(cols))
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
- @prepared_statements[:lookup] ||= prepare_statement(filter(prepared_statement_key_array(primary_key)), :first)
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
- @prepared_statements[:refresh] ||= prepare_statement(naked.clone(:server=>dataset.opts.fetch(:server, :default)).filter(prepared_statement_key_array(primary_key)), :first)
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
- @prepared_statements[:update][prepared_columns(cols)] ||= prepare_statement(filter(prepared_statement_key_array(primary_key)), :update, prepared_statement_key_hash(cols))
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