sequel 5.60.1 → 5.81.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.
Files changed (166) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +280 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +27 -24
  5. data/doc/advanced_associations.rdoc +16 -14
  6. data/doc/association_basics.rdoc +53 -17
  7. data/doc/cheat_sheet.rdoc +3 -3
  8. data/doc/code_order.rdoc +5 -3
  9. data/doc/dataset_basics.rdoc +1 -1
  10. data/doc/mass_assignment.rdoc +1 -1
  11. data/doc/migration.rdoc +15 -0
  12. data/doc/model_hooks.rdoc +1 -1
  13. data/doc/object_model.rdoc +8 -8
  14. data/doc/opening_databases.rdoc +18 -7
  15. data/doc/postgresql.rdoc +8 -8
  16. data/doc/querying.rdoc +7 -2
  17. data/doc/release_notes/5.61.0.txt +43 -0
  18. data/doc/release_notes/5.62.0.txt +132 -0
  19. data/doc/release_notes/5.63.0.txt +33 -0
  20. data/doc/release_notes/5.64.0.txt +50 -0
  21. data/doc/release_notes/5.65.0.txt +21 -0
  22. data/doc/release_notes/5.66.0.txt +24 -0
  23. data/doc/release_notes/5.67.0.txt +32 -0
  24. data/doc/release_notes/5.68.0.txt +61 -0
  25. data/doc/release_notes/5.69.0.txt +26 -0
  26. data/doc/release_notes/5.70.0.txt +35 -0
  27. data/doc/release_notes/5.71.0.txt +21 -0
  28. data/doc/release_notes/5.72.0.txt +33 -0
  29. data/doc/release_notes/5.73.0.txt +66 -0
  30. data/doc/release_notes/5.74.0.txt +45 -0
  31. data/doc/release_notes/5.75.0.txt +35 -0
  32. data/doc/release_notes/5.76.0.txt +86 -0
  33. data/doc/release_notes/5.77.0.txt +63 -0
  34. data/doc/release_notes/5.78.0.txt +67 -0
  35. data/doc/release_notes/5.79.0.txt +28 -0
  36. data/doc/release_notes/5.80.0.txt +40 -0
  37. data/doc/release_notes/5.81.0.txt +31 -0
  38. data/doc/schema_modification.rdoc +3 -3
  39. data/doc/security.rdoc +9 -9
  40. data/doc/sharding.rdoc +3 -1
  41. data/doc/sql.rdoc +14 -14
  42. data/doc/testing.rdoc +16 -12
  43. data/doc/transactions.rdoc +6 -6
  44. data/doc/virtual_rows.rdoc +1 -1
  45. data/lib/sequel/adapters/ibmdb.rb +1 -1
  46. data/lib/sequel/adapters/jdbc/h2.rb +3 -0
  47. data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -0
  48. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -0
  49. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +15 -0
  50. data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -0
  51. data/lib/sequel/adapters/jdbc.rb +10 -6
  52. data/lib/sequel/adapters/mysql.rb +19 -7
  53. data/lib/sequel/adapters/mysql2.rb +2 -2
  54. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  55. data/lib/sequel/adapters/oracle.rb +1 -0
  56. data/lib/sequel/adapters/postgres.rb +10 -5
  57. data/lib/sequel/adapters/shared/access.rb +9 -1
  58. data/lib/sequel/adapters/shared/db2.rb +12 -0
  59. data/lib/sequel/adapters/shared/mssql.rb +71 -9
  60. data/lib/sequel/adapters/shared/mysql.rb +82 -3
  61. data/lib/sequel/adapters/shared/oracle.rb +17 -7
  62. data/lib/sequel/adapters/shared/postgres.rb +469 -160
  63. data/lib/sequel/adapters/shared/sqlanywhere.rb +18 -5
  64. data/lib/sequel/adapters/shared/sqlite.rb +39 -3
  65. data/lib/sequel/adapters/sqlite.rb +42 -3
  66. data/lib/sequel/adapters/trilogy.rb +117 -0
  67. data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
  68. data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
  69. data/lib/sequel/connection_pool/threaded.rb +14 -8
  70. data/lib/sequel/connection_pool/timed_queue.rb +270 -0
  71. data/lib/sequel/connection_pool.rb +57 -31
  72. data/lib/sequel/database/connecting.rb +25 -1
  73. data/lib/sequel/database/dataset.rb +16 -6
  74. data/lib/sequel/database/misc.rb +65 -14
  75. data/lib/sequel/database/query.rb +72 -1
  76. data/lib/sequel/database/schema_generator.rb +1 -1
  77. data/lib/sequel/database/schema_methods.rb +12 -5
  78. data/lib/sequel/database/transactions.rb +6 -0
  79. data/lib/sequel/dataset/actions.rb +60 -13
  80. data/lib/sequel/dataset/dataset_module.rb +1 -1
  81. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
  82. data/lib/sequel/dataset/features.rb +15 -1
  83. data/lib/sequel/dataset/graph.rb +1 -0
  84. data/lib/sequel/dataset/misc.rb +12 -2
  85. data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
  86. data/lib/sequel/dataset/query.rb +119 -45
  87. data/lib/sequel/dataset/sql.rb +58 -36
  88. data/lib/sequel/dataset.rb +4 -0
  89. data/lib/sequel/exceptions.rb +5 -0
  90. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  91. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  92. data/lib/sequel/extensions/any_not_empty.rb +2 -2
  93. data/lib/sequel/extensions/async_thread_pool.rb +21 -13
  94. data/lib/sequel/extensions/auto_cast_date_and_time.rb +94 -0
  95. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  96. data/lib/sequel/extensions/caller_logging.rb +4 -1
  97. data/lib/sequel/extensions/connection_expiration.rb +15 -9
  98. data/lib/sequel/extensions/connection_validator.rb +16 -11
  99. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  100. data/lib/sequel/extensions/date_arithmetic.rb +1 -1
  101. data/lib/sequel/extensions/duplicate_columns_handler.rb +10 -9
  102. data/lib/sequel/extensions/index_caching.rb +5 -1
  103. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  104. data/lib/sequel/extensions/migration.rb +65 -15
  105. data/lib/sequel/extensions/named_timezones.rb +22 -6
  106. data/lib/sequel/extensions/pg_array.rb +33 -4
  107. data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
  108. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  109. data/lib/sequel/extensions/pg_enum.rb +1 -2
  110. data/lib/sequel/extensions/pg_extended_date_support.rb +38 -27
  111. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  112. data/lib/sequel/extensions/pg_hstore.rb +5 -0
  113. data/lib/sequel/extensions/pg_inet.rb +10 -11
  114. data/lib/sequel/extensions/pg_interval.rb +10 -11
  115. data/lib/sequel/extensions/pg_json.rb +10 -10
  116. data/lib/sequel/extensions/pg_json_ops.rb +3 -3
  117. data/lib/sequel/extensions/pg_multirange.rb +6 -11
  118. data/lib/sequel/extensions/pg_range.rb +9 -14
  119. data/lib/sequel/extensions/pg_row.rb +20 -19
  120. data/lib/sequel/extensions/pg_timestamptz.rb +27 -3
  121. data/lib/sequel/extensions/provenance.rb +110 -0
  122. data/lib/sequel/extensions/round_timestamps.rb +1 -1
  123. data/lib/sequel/extensions/schema_caching.rb +1 -1
  124. data/lib/sequel/extensions/schema_dumper.rb +32 -9
  125. data/lib/sequel/extensions/server_block.rb +2 -1
  126. data/lib/sequel/extensions/set_literalizer.rb +58 -0
  127. data/lib/sequel/extensions/sqlite_json_ops.rb +76 -18
  128. data/lib/sequel/extensions/temporarily_release_connection.rb +178 -0
  129. data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
  130. data/lib/sequel/model/associations.rb +38 -11
  131. data/lib/sequel/model/base.rb +32 -18
  132. data/lib/sequel/model/dataset_module.rb +3 -0
  133. data/lib/sequel/model/exceptions.rb +15 -3
  134. data/lib/sequel/plugins/auto_validations.rb +53 -15
  135. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  136. data/lib/sequel/plugins/column_encryption.rb +28 -7
  137. data/lib/sequel/plugins/composition.rb +2 -2
  138. data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
  139. data/lib/sequel/plugins/constraint_validations.rb +8 -5
  140. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  141. data/lib/sequel/plugins/dirty.rb +1 -1
  142. data/lib/sequel/plugins/finder.rb +4 -2
  143. data/lib/sequel/plugins/input_transformer.rb +1 -1
  144. data/lib/sequel/plugins/list.rb +5 -2
  145. data/lib/sequel/plugins/many_through_many.rb +1 -1
  146. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  147. data/lib/sequel/plugins/nested_attributes.rb +4 -4
  148. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  149. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  150. data/lib/sequel/plugins/paged_operations.rb +181 -0
  151. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +9 -3
  152. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  153. data/lib/sequel/plugins/prepared_statements.rb +2 -1
  154. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
  155. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  156. data/lib/sequel/plugins/rcte_tree.rb +7 -4
  157. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  158. data/lib/sequel/plugins/sql_comments.rb +1 -1
  159. data/lib/sequel/plugins/static_cache.rb +38 -0
  160. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  161. data/lib/sequel/plugins/tactical_eager_loading.rb +14 -14
  162. data/lib/sequel/plugins/validate_associated.rb +22 -12
  163. data/lib/sequel/plugins/validation_helpers.rb +29 -2
  164. data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
  165. data/lib/sequel/version.rb +2 -2
  166. metadata +79 -6
@@ -127,6 +127,18 @@ module Sequel
127
127
  #
128
128
  # DB[:table].delete # DELETE * FROM table
129
129
  # # => 3
130
+ #
131
+ # Some databases support using multiple tables in a DELETE query. This requires
132
+ # multiple FROM tables (JOINs can also be used). As multiple FROM tables use
133
+ # an implicit CROSS JOIN, you should make sure your WHERE condition uses the
134
+ # appropriate filters for the FROM tables:
135
+ #
136
+ # DB.from(:a, :b).join(:c, :d=>Sequel[:b][:e]).where{{a[:f]=>b[:g], a[:id]=>c[:h]}}.
137
+ # delete
138
+ # # DELETE FROM a
139
+ # # USING b
140
+ # # INNER JOIN c ON (c.d = b.e)
141
+ # # WHERE ((a.f = b.g) AND (a.id = c.h))
130
142
  def delete(&block)
131
143
  sql = delete_sql
132
144
  if uses_returning?(:delete)
@@ -162,7 +174,7 @@ module Sequel
162
174
  # # => false
163
175
  def empty?
164
176
  cached_dataset(:_empty_ds) do
165
- single_value_ds.unordered.select(EMPTY_SELECT)
177
+ (@opts[:sql] ? from_self : self).single_value_ds.unordered.select(EMPTY_SELECT)
166
178
  end.single_value!.nil?
167
179
  end
168
180
 
@@ -313,14 +325,18 @@ module Sequel
313
325
 
314
326
  # Inserts multiple records into the associated table. This method can be
315
327
  # used to efficiently insert a large number of records into a table in a
316
- # single query if the database supports it. Inserts
317
- # are automatically wrapped in a transaction.
328
+ # single query if the database supports it. Inserts are automatically
329
+ # wrapped in a transaction if necessary.
318
330
  #
319
331
  # This method is called with a columns array and an array of value arrays:
320
332
  #
321
333
  # DB[:table].import([:x, :y], [[1, 2], [3, 4]])
322
334
  # # INSERT INTO table (x, y) VALUES (1, 2)
323
- # # INSERT INTO table (x, y) VALUES (3, 4)
335
+ # # INSERT INTO table (x, y) VALUES (3, 4)
336
+ #
337
+ # or, if the database supports it:
338
+ #
339
+ # # INSERT INTO table (x, y) VALUES (1, 2), (3, 4)
324
340
  #
325
341
  # This method also accepts a dataset instead of an array of value arrays:
326
342
  #
@@ -328,17 +344,23 @@ module Sequel
328
344
  # # INSERT INTO table (x, y) SELECT a, b FROM table2
329
345
  #
330
346
  # Options:
331
- # :commit_every :: Open a new transaction for every given number of records.
332
- # For example, if you provide a value of 50, will commit
333
- # after every 50 records.
347
+ # :commit_every :: Open a new transaction for every given number of
348
+ # records. For example, if you provide a value of 50,
349
+ # will commit after every 50 records. When a
350
+ # transaction is not required, this option controls
351
+ # the maximum number of values to insert with a single
352
+ # statement; it does not force the use of a
353
+ # transaction.
334
354
  # :return :: When this is set to :primary_key, returns an array of
335
355
  # autoincremented primary key values for the rows inserted.
336
356
  # This does not have an effect if +values+ is a Dataset.
337
357
  # :server :: Set the server/shard to use for the transaction and insert
338
358
  # queries.
359
+ # :skip_transaction :: Do not use a transaction even when using multiple
360
+ # INSERT queries.
339
361
  # :slice :: Same as :commit_every, :commit_every takes precedence.
340
362
  def import(columns, values, opts=OPTS)
341
- return @db.transaction{insert(columns, values)} if values.is_a?(Dataset)
363
+ return insert(columns, values) if values.is_a?(Dataset)
342
364
 
343
365
  return if values.empty?
344
366
  raise(Error, 'Using Sequel::Dataset#import with an empty column array is not allowed') if columns.empty?
@@ -568,6 +590,8 @@ module Sequel
568
590
  # if your ORDER BY expressions are not simple columns, if they contain
569
591
  # qualified identifiers that would be ambiguous unqualified, if they contain
570
592
  # any identifiers that are aliased in SELECT, and potentially other cases.
593
+ # :skip_transaction :: Do not use a transaction. This can be useful if you want to prevent
594
+ # a lock on the database table, at the expense of consistency.
571
595
  #
572
596
  # Examples:
573
597
  #
@@ -576,7 +600,7 @@ module Sequel
576
600
  # # SELECT * FROM table ORDER BY id LIMIT 1000 OFFSET 1000
577
601
  # # ...
578
602
  #
579
- # DB[:table].order(:id).paged_each(:rows_per_fetch=>100){|row| }
603
+ # DB[:table].order(:id).paged_each(rows_per_fetch: 100){|row| }
580
604
  # # SELECT * FROM table ORDER BY id LIMIT 100
581
605
  # # SELECT * FROM table ORDER BY id LIMIT 100 OFFSET 100
582
606
  # # ...
@@ -918,6 +942,19 @@ module Sequel
918
942
  #
919
943
  # DB[:table].update(x: Sequel[:x]+1, y: 0) # UPDATE table SET x = (x + 1), y = 0
920
944
  # # => 10
945
+ #
946
+ # Some databases support using multiple tables in an UPDATE query. This requires
947
+ # multiple FROM tables (JOINs can also be used). As multiple FROM tables use
948
+ # an implicit CROSS JOIN, you should make sure your WHERE condition uses the
949
+ # appropriate filters for the FROM tables:
950
+ #
951
+ # DB.from(:a, :b).join(:c, :d=>Sequel[:b][:e]).where{{a[:f]=>b[:g], a[:id]=>10}}.
952
+ # update(:f=>Sequel[:c][:h])
953
+ # # UPDATE a
954
+ # # SET f = c.h
955
+ # # FROM b
956
+ # # INNER JOIN c ON (c.d = b.e)
957
+ # # WHERE ((a.f = b.g) AND (a.id = 10))
921
958
  def update(values=OPTS, &block)
922
959
  sql = update_sql(values)
923
960
  if uses_returning?(:update)
@@ -1023,18 +1060,19 @@ module Sequel
1023
1060
 
1024
1061
  # Internals of #import. If primary key values are requested, use
1025
1062
  # separate insert commands for each row. Otherwise, call #multi_insert_sql
1026
- # and execute each statement it gives separately.
1063
+ # and execute each statement it gives separately. A transaction is only used
1064
+ # if there are multiple statements to execute.
1027
1065
  def _import(columns, values, opts)
1028
1066
  trans_opts = Hash[opts]
1029
1067
  trans_opts[:server] = @opts[:server]
1030
1068
  if opts[:return] == :primary_key
1031
- @db.transaction(trans_opts){values.map{|v| insert(columns, v)}}
1069
+ _import_transaction(values, trans_opts){values.map{|v| insert(columns, v)}}
1032
1070
  else
1033
1071
  stmts = multi_insert_sql(columns, values)
1034
- @db.transaction(trans_opts){stmts.each{|st| execute_dui(st)}}
1072
+ _import_transaction(stmts, trans_opts){stmts.each{|st| execute_dui(st)}}
1035
1073
  end
1036
1074
  end
1037
-
1075
+
1038
1076
  # Return an array of arrays of values given by the symbols in ret_cols.
1039
1077
  def _select_map_multiple(ret_cols)
1040
1078
  map{|r| r.values_at(*ret_cols)}
@@ -1073,6 +1111,15 @@ module Sequel
1073
1111
  end
1074
1112
  end
1075
1113
 
1114
+ # Use a transaction when yielding to the block if multiple values/statements
1115
+ # are provided. When only a single value or statement is provided, then yield
1116
+ # without using a transaction.
1117
+ def _import_transaction(values, trans_opts, &block)
1118
+ # OK to mutate trans_opts as it is generated by _import
1119
+ trans_opts[:skip_transaction] = true if values.length <= 1
1120
+ @db.transaction(trans_opts, &block)
1121
+ end
1122
+
1076
1123
  # Internals of +select_hash+ and +select_hash_groups+
1077
1124
  def _select_hash(meth, key_column, value_column, opts=OPTS)
1078
1125
  select(*(key_column.is_a?(Array) ? key_column : [key_column]) + (value_column.is_a?(Array) ? value_column : [value_column])).
@@ -21,7 +21,7 @@ module Sequel
21
21
  where exclude exclude_having having
22
22
  distinct grep group group_and_count group_append
23
23
  limit offset order order_append order_prepend reverse
24
- select select_all select_append select_group server
24
+ select select_all select_append select_group select_prepend server
25
25
  METHS
26
26
 
27
27
  # Define a method in the module
@@ -0,0 +1,42 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ class Dataset
5
+ # This module implements methods to support deprecated use of extensions registered
6
+ # not using a module. In such cases, for backwards compatibility, Sequel has to use
7
+ # a singleton class for the dataset.
8
+ module DeprecatedSingletonClassMethods
9
+ # Load the extension into a clone of the receiver.
10
+ def extension(*a)
11
+ c = _clone(:freeze=>false)
12
+ c.send(:_extension!, a)
13
+ c.freeze
14
+ end
15
+
16
+ # Extend the cloned of the receiver with the given modules, instead of the default
17
+ # approach of creating a subclass of the receiver's class and including the modules
18
+ # into that.
19
+ def with_extend(*mods, &block)
20
+ c = _clone(:freeze=>false)
21
+ c.extend(*mods) unless mods.empty?
22
+ c.extend(DatasetModule.new(&block)) if block
23
+ c.freeze
24
+ end
25
+
26
+ private
27
+
28
+ # Load the extensions into the receiver.
29
+ def _extension!(exts)
30
+ Sequel.extension(*exts)
31
+ exts.each do |ext|
32
+ if pr = Sequel.synchronize{EXTENSIONS[ext]}
33
+ pr.call(self)
34
+ else
35
+ raise(Error, "Extension #{ext} does not have specific support handling individual datasets (try: Sequel.extension #{ext.inspect})")
36
+ end
37
+ end
38
+ self
39
+ end
40
+ end
41
+ end
42
+ end
@@ -25,11 +25,16 @@ module Sequel
25
25
  false
26
26
  end
27
27
 
28
+ # :nocov:
29
+
28
30
  # Whether the dataset requires SQL standard datetimes. False by default,
29
- # as most allow strings with ISO 8601 format.
31
+ # as most allow strings with ISO 8601 format. Only for backwards compatibility,
32
+ # no longer used internally, do not use in new code.
30
33
  def requires_sql_standard_datetimes?
34
+ # SEQUEL6: Remove
31
35
  false
32
36
  end
37
+ # :nocov:
33
38
 
34
39
  # Whether type specifiers are required for prepared statement/bound
35
40
  # variable argument placeholders (i.e. :bv__integer), false by default.
@@ -152,6 +157,11 @@ module Sequel
152
157
  supports_distinct_on?
153
158
  end
154
159
 
160
+ # Whether placeholder literalizers are supported, true by default.
161
+ def supports_placeholder_literalizer?
162
+ true
163
+ end
164
+
155
165
  # Whether the dataset supports pattern matching by regular expressions, false by default.
156
166
  def supports_regexp?
157
167
  false
@@ -178,10 +188,14 @@ module Sequel
178
188
  true
179
189
  end
180
190
 
191
+ # :nocov:
192
+
181
193
  # Whether the dataset supports timezones in literal timestamps, false by default.
182
194
  def supports_timestamp_timezones?
195
+ # SEQUEL6: Remove
183
196
  false
184
197
  end
198
+ # :nocov:
185
199
 
186
200
  # Whether the dataset supports fractional seconds in literal timestamps, true by default.
187
201
  def supports_timestamp_usecs?
@@ -253,6 +253,7 @@ module Sequel
253
253
  # Remove the splitting of results into subhashes, and all metadata
254
254
  # related to the current graph (if any).
255
255
  def ungraphed
256
+ return self unless opts[:graph]
256
257
  clone(:graph=>nil)
257
258
  end
258
259
 
@@ -158,6 +158,16 @@ module Sequel
158
158
  !!((opts[:from].is_a?(Array) && opts[:from].size > 1) || opts[:join])
159
159
  end
160
160
 
161
+ # The class to use for placeholder literalizers for the current dataset.
162
+ def placeholder_literalizer_class
163
+ ::Sequel::Dataset::PlaceholderLiteralizer
164
+ end
165
+
166
+ # A placeholder literalizer loader for the current dataset.
167
+ def placeholder_literalizer_loader(&block)
168
+ placeholder_literalizer_class.loader(self, &block)
169
+ end
170
+
161
171
  # The alias to use for the row_number column, used when emulating OFFSET
162
172
  # support and for eager limit strategies
163
173
  def row_number_column
@@ -296,13 +306,13 @@ module Sequel
296
306
  loader += 1
297
307
 
298
308
  if loader >= 3
299
- loader = Sequel::Dataset::PlaceholderLiteralizer.loader(self){|pl, _| yield pl}
309
+ loader = placeholder_literalizer_loader{|pl, _| yield pl}
300
310
  cache_set(key, loader)
301
311
  else
302
312
  cache_set(key, loader + 1)
303
313
  loader = nil
304
314
  end
305
- elsif cache_sql?
315
+ elsif cache_sql? && supports_placeholder_literalizer?
306
316
  cache_set(key, 1)
307
317
  end
308
318
 
@@ -77,8 +77,8 @@ module Sequel
77
77
  # Yields the receiver and the dataset to the block, which should
78
78
  # call #arg on the receiver for each placeholder argument, and
79
79
  # return the dataset that you want to load.
80
- def loader(dataset, &block)
81
- PlaceholderLiteralizer.new(*process(dataset, &block))
80
+ def loader(pl, dataset, &block)
81
+ pl.new(*process(dataset, &block))
82
82
  end
83
83
 
84
84
  # Return an Argument with the specified position, or the next position. In
@@ -145,7 +145,7 @@ module Sequel
145
145
  # given block, recording the offsets at which the recorders arguments
146
146
  # are used in the query.
147
147
  def self.loader(dataset, &block)
148
- Recorder.new.loader(dataset, &block)
148
+ Recorder.new.loader(self, dataset, &block)
149
149
  end
150
150
 
151
151
  # Save the dataset, array of SQL fragments, and ending SQL string.
@@ -199,20 +199,31 @@ module Sequel
199
199
  # Return the SQL query to use for the given arguments.
200
200
  def sql(*args)
201
201
  raise Error, "wrong number of arguments (#{args.length} for #{@arity})" unless args.length == @arity
202
- s = String.new
202
+ s = sql_origin
203
+ append_sql(s, *args)
204
+ end
205
+
206
+ # Append the SQL query to use for the given arguments to the given SQL string.
207
+ def append_sql(sql, *args)
203
208
  ds = @dataset
204
- @fragments.each do |sql, i, transformer|
205
- s << sql
209
+ @fragments.each do |s, i, transformer|
210
+ sql << s
206
211
  if i.is_a?(Integer)
207
212
  v = args.fetch(i)
208
213
  v = transformer.call(v) if transformer
209
214
  else
210
215
  v = i.call
211
216
  end
212
- ds.literal_append(s, v)
217
+ ds.literal_append(sql, v)
213
218
  end
214
- s << @final_sql
215
- s
219
+ sql << @final_sql
220
+ sql
221
+ end
222
+
223
+ private
224
+
225
+ def sql_origin
226
+ String.new
216
227
  end
217
228
  end
218
229
  end