sequel 5.58.0 → 5.78.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 (161) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +288 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +24 -23
  5. data/bin/sequel +11 -3
  6. data/doc/advanced_associations.rdoc +16 -14
  7. data/doc/association_basics.rdoc +53 -17
  8. data/doc/cheat_sheet.rdoc +3 -3
  9. data/doc/mass_assignment.rdoc +1 -1
  10. data/doc/migration.rdoc +15 -0
  11. data/doc/model_hooks.rdoc +1 -1
  12. data/doc/object_model.rdoc +8 -8
  13. data/doc/opening_databases.rdoc +20 -12
  14. data/doc/postgresql.rdoc +8 -8
  15. data/doc/querying.rdoc +1 -1
  16. data/doc/release_notes/5.59.0.txt +73 -0
  17. data/doc/release_notes/5.60.0.txt +22 -0
  18. data/doc/release_notes/5.61.0.txt +43 -0
  19. data/doc/release_notes/5.62.0.txt +132 -0
  20. data/doc/release_notes/5.63.0.txt +33 -0
  21. data/doc/release_notes/5.64.0.txt +50 -0
  22. data/doc/release_notes/5.65.0.txt +21 -0
  23. data/doc/release_notes/5.66.0.txt +24 -0
  24. data/doc/release_notes/5.67.0.txt +32 -0
  25. data/doc/release_notes/5.68.0.txt +61 -0
  26. data/doc/release_notes/5.69.0.txt +26 -0
  27. data/doc/release_notes/5.70.0.txt +35 -0
  28. data/doc/release_notes/5.71.0.txt +21 -0
  29. data/doc/release_notes/5.72.0.txt +33 -0
  30. data/doc/release_notes/5.73.0.txt +66 -0
  31. data/doc/release_notes/5.74.0.txt +45 -0
  32. data/doc/release_notes/5.75.0.txt +35 -0
  33. data/doc/release_notes/5.76.0.txt +86 -0
  34. data/doc/release_notes/5.77.0.txt +63 -0
  35. data/doc/release_notes/5.78.0.txt +67 -0
  36. data/doc/schema_modification.rdoc +3 -3
  37. data/doc/security.rdoc +9 -9
  38. data/doc/sharding.rdoc +3 -1
  39. data/doc/sql.rdoc +14 -14
  40. data/doc/testing.rdoc +16 -12
  41. data/doc/transactions.rdoc +6 -6
  42. data/doc/virtual_rows.rdoc +1 -1
  43. data/lib/sequel/adapters/ibmdb.rb +1 -1
  44. data/lib/sequel/adapters/jdbc/h2.rb +3 -0
  45. data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -0
  46. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -0
  47. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +15 -0
  48. data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -0
  49. data/lib/sequel/adapters/jdbc.rb +10 -6
  50. data/lib/sequel/adapters/mysql.rb +19 -7
  51. data/lib/sequel/adapters/mysql2.rb +2 -2
  52. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  53. data/lib/sequel/adapters/oracle.rb +1 -0
  54. data/lib/sequel/adapters/postgres.rb +62 -16
  55. data/lib/sequel/adapters/shared/access.rb +9 -1
  56. data/lib/sequel/adapters/shared/db2.rb +12 -0
  57. data/lib/sequel/adapters/shared/mssql.rb +71 -9
  58. data/lib/sequel/adapters/shared/mysql.rb +80 -1
  59. data/lib/sequel/adapters/shared/oracle.rb +17 -7
  60. data/lib/sequel/adapters/shared/postgres.rb +494 -164
  61. data/lib/sequel/adapters/shared/sqlanywhere.rb +18 -5
  62. data/lib/sequel/adapters/shared/sqlite.rb +40 -4
  63. data/lib/sequel/adapters/sqlite.rb +42 -3
  64. data/lib/sequel/adapters/trilogy.rb +117 -0
  65. data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
  66. data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
  67. data/lib/sequel/connection_pool/threaded.rb +14 -8
  68. data/lib/sequel/connection_pool/timed_queue.rb +270 -0
  69. data/lib/sequel/connection_pool.rb +57 -31
  70. data/lib/sequel/database/connecting.rb +25 -1
  71. data/lib/sequel/database/dataset.rb +16 -6
  72. data/lib/sequel/database/misc.rb +65 -14
  73. data/lib/sequel/database/query.rb +72 -1
  74. data/lib/sequel/database/schema_generator.rb +2 -1
  75. data/lib/sequel/database/schema_methods.rb +13 -3
  76. data/lib/sequel/database/transactions.rb +6 -0
  77. data/lib/sequel/dataset/actions.rb +60 -13
  78. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
  79. data/lib/sequel/dataset/features.rb +15 -1
  80. data/lib/sequel/dataset/misc.rb +12 -2
  81. data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
  82. data/lib/sequel/dataset/query.rb +62 -37
  83. data/lib/sequel/dataset/sql.rb +58 -36
  84. data/lib/sequel/dataset.rb +4 -0
  85. data/lib/sequel/exceptions.rb +5 -0
  86. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  87. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  88. data/lib/sequel/extensions/any_not_empty.rb +2 -2
  89. data/lib/sequel/extensions/async_thread_pool.rb +21 -13
  90. data/lib/sequel/extensions/auto_cast_date_and_time.rb +94 -0
  91. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  92. data/lib/sequel/extensions/connection_expiration.rb +15 -9
  93. data/lib/sequel/extensions/connection_validator.rb +16 -11
  94. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  95. data/lib/sequel/extensions/date_arithmetic.rb +36 -8
  96. data/lib/sequel/extensions/duplicate_columns_handler.rb +10 -9
  97. data/lib/sequel/extensions/index_caching.rb +5 -1
  98. data/lib/sequel/extensions/is_distinct_from.rb +3 -1
  99. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  100. data/lib/sequel/extensions/migration.rb +65 -15
  101. data/lib/sequel/extensions/named_timezones.rb +22 -6
  102. data/lib/sequel/extensions/pg_array.rb +33 -4
  103. data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
  104. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  105. data/lib/sequel/extensions/pg_enum.rb +1 -2
  106. data/lib/sequel/extensions/pg_extended_date_support.rb +38 -27
  107. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  108. data/lib/sequel/extensions/pg_hstore.rb +5 -0
  109. data/lib/sequel/extensions/pg_inet.rb +10 -11
  110. data/lib/sequel/extensions/pg_interval.rb +10 -11
  111. data/lib/sequel/extensions/pg_json.rb +10 -10
  112. data/lib/sequel/extensions/pg_json_ops.rb +52 -0
  113. data/lib/sequel/extensions/pg_multirange.rb +6 -11
  114. data/lib/sequel/extensions/pg_range.rb +9 -14
  115. data/lib/sequel/extensions/pg_row.rb +20 -19
  116. data/lib/sequel/extensions/pg_timestamptz.rb +27 -3
  117. data/lib/sequel/extensions/round_timestamps.rb +1 -1
  118. data/lib/sequel/extensions/schema_caching.rb +1 -1
  119. data/lib/sequel/extensions/schema_dumper.rb +32 -9
  120. data/lib/sequel/extensions/server_block.rb +2 -1
  121. data/lib/sequel/extensions/set_literalizer.rb +58 -0
  122. data/lib/sequel/extensions/sqlite_json_ops.rb +76 -18
  123. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  124. data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
  125. data/lib/sequel/model/associations.rb +50 -11
  126. data/lib/sequel/model/base.rb +45 -21
  127. data/lib/sequel/model/dataset_module.rb +3 -0
  128. data/lib/sequel/model/exceptions.rb +15 -3
  129. data/lib/sequel/plugins/auto_validations.rb +53 -15
  130. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  131. data/lib/sequel/plugins/column_encryption.rb +27 -6
  132. data/lib/sequel/plugins/composition.rb +2 -2
  133. data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
  134. data/lib/sequel/plugins/constraint_validations.rb +8 -5
  135. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  136. data/lib/sequel/plugins/dirty.rb +1 -1
  137. data/lib/sequel/plugins/finder.rb +4 -2
  138. data/lib/sequel/plugins/list.rb +8 -3
  139. data/lib/sequel/plugins/many_through_many.rb +1 -1
  140. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  141. data/lib/sequel/plugins/nested_attributes.rb +4 -4
  142. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  143. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  144. data/lib/sequel/plugins/paged_operations.rb +181 -0
  145. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +9 -3
  146. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  147. data/lib/sequel/plugins/prepared_statements.rb +2 -1
  148. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
  149. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  150. data/lib/sequel/plugins/rcte_tree.rb +7 -4
  151. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  152. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  153. data/lib/sequel/plugins/sql_comments.rb +5 -5
  154. data/lib/sequel/plugins/static_cache.rb +38 -0
  155. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  156. data/lib/sequel/plugins/tactical_eager_loading.rb +21 -14
  157. data/lib/sequel/plugins/validate_associated.rb +22 -12
  158. data/lib/sequel/plugins/validation_helpers.rb +29 -2
  159. data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
  160. data/lib/sequel/version.rb +1 -1
  161. metadata +76 -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])).
@@ -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?
@@ -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
@@ -12,6 +12,10 @@ module Sequel
12
12
  # in the extension).
13
13
  EXTENSIONS = {}
14
14
 
15
+ # Hash of extension name symbols to modules to load to implement the extension.
16
+ EXTENSION_MODULES = {}
17
+ private_constant :EXTENSION_MODULES
18
+
15
19
  EMPTY_ARRAY = [].freeze
16
20
 
17
21
  # The dataset options that require the removal of cached columns if changed.
@@ -45,12 +49,8 @@ module Sequel
45
49
  METHS
46
50
 
47
51
  # Register an extension callback for Dataset objects. ext should be the
48
- # extension name symbol, and mod should either be a Module that the
49
- # dataset is extended with, or a callable object called with the database
50
- # object. If mod is not provided, a block can be provided and is treated
51
- # as the mod object.
52
- #
53
- # If mod is a module, this also registers a Database extension that will
52
+ # extension name symbol, and mod should be a Module that will be
53
+ # included in the dataset's class. This also registers a Database extension that will
54
54
  # extend all of the database's datasets.
55
55
  def self.register_extension(ext, mod=nil, &block)
56
56
  if mod
@@ -58,14 +58,20 @@ module Sequel
58
58
  if mod.is_a?(Module)
59
59
  block = proc{|ds| ds.extend(mod)}
60
60
  Sequel::Database.register_extension(ext){|db| db.extend_datasets(mod)}
61
+ Sequel.synchronize{EXTENSION_MODULES[ext] = mod}
61
62
  else
62
63
  block = mod
63
64
  end
64
65
  end
66
+
67
+ unless mod.is_a?(Module)
68
+ Sequel::Deprecation.deprecate("Providing a block or non-module to Sequel::Dataset.register_extension is deprecated and support for it will be removed in Sequel 6.")
69
+ end
70
+
65
71
  Sequel.synchronize{EXTENSIONS[ext] = block}
66
72
  end
67
73
 
68
- # On Ruby 2.4+, use clone(:freeze=>false) to create clones, because
74
+ # On Ruby 2.4+, use clone(freeze: false) to create clones, because
69
75
  # we use true freezing in that case, and we need to modify the opts
70
76
  # in the frozen copy.
71
77
  #
@@ -116,7 +122,7 @@ module Sequel
116
122
  # DB[:items].order(:id).distinct(:id) # SQL: SELECT DISTINCT ON (id) * FROM items ORDER BY id
117
123
  # DB[:items].order(:id).distinct{func(:id)} # SQL: SELECT DISTINCT ON (func(id)) * FROM items ORDER BY id
118
124
  #
119
- # There is support for emualting the DISTINCT ON support in MySQL, but it
125
+ # There is support for emulating the DISTINCT ON support in MySQL, but it
120
126
  # does not support the ORDER of the dataset, and also doesn't work in many
121
127
  # cases if the ONLY_FULL_GROUP_BY sql_mode is used, which is the default on
122
128
  # MySQL 5.7.5+.
@@ -195,11 +201,15 @@ module Sequel
195
201
  if TRUE_FREEZE
196
202
  # Return a clone of the dataset loaded with the given dataset extensions.
197
203
  # If no related extension file exists or the extension does not have
198
- # specific support for Dataset objects, an Error will be raised.
199
- def extension(*a)
200
- c = _clone(:freeze=>false)
201
- c.send(:_extension!, a)
202
- c.freeze
204
+ # specific support for Dataset objects, an error will be raised.
205
+ def extension(*exts)
206
+ Sequel.extension(*exts)
207
+ mods = exts.map{|ext| Sequel.synchronize{EXTENSION_MODULES[ext]}}
208
+ if mods.all?
209
+ with_extend(*mods)
210
+ else
211
+ with_extend(DeprecatedSingletonClassMethods).extension(*exts)
212
+ end
203
213
  end
204
214
  else
205
215
  # :nocov:
@@ -787,7 +797,7 @@ module Sequel
787
797
  # DB[:items].order(Sequel.lit('a + b')) # SELECT * FROM items ORDER BY a + b
788
798
  # DB[:items].order(Sequel[:a] + :b) # SELECT * FROM items ORDER BY (a + b)
789
799
  # DB[:items].order(Sequel.desc(:name)) # SELECT * FROM items ORDER BY name DESC
790
- # DB[:items].order(Sequel.asc(:name, :nulls=>:last)) # SELECT * FROM items ORDER BY name ASC NULLS LAST
800
+ # DB[:items].order(Sequel.asc(:name, nulls: :last)) # SELECT * FROM items ORDER BY name ASC NULLS LAST
791
801
  # DB[:items].order{sum(name).desc} # SELECT * FROM items ORDER BY sum(name) DESC
792
802
  # DB[:items].order(nil) # SELECT * FROM items
793
803
  def order(*columns, &block)
@@ -857,13 +867,13 @@ module Sequel
857
867
  # DB[:items].returning(nil) # RETURNING NULL
858
868
  # DB[:items].returning(:id, :name) # RETURNING id, name
859
869
  #
860
- # DB[:items].returning.insert(:a=>1) do |hash|
870
+ # DB[:items].returning.insert(a: 1) do |hash|
861
871
  # # hash for each row inserted, with values for all columns
862
872
  # end
863
- # DB[:items].returning.update(:a=>1) do |hash|
873
+ # DB[:items].returning.update(a: 1) do |hash|
864
874
  # # hash for each row updated, with values for all columns
865
875
  # end
866
- # DB[:items].returning.delete(:a=>1) do |hash|
876
+ # DB[:items].returning.delete(a: 1) do |hash|
867
877
  # # hash for each row deleted, with values for all columns
868
878
  # end
869
879
  def returning(*values)
@@ -1102,7 +1112,7 @@ module Sequel
1102
1112
  # referenced in window functions. See Sequel::SQL::Window for a list of
1103
1113
  # options that can be passed in. Example:
1104
1114
  #
1105
- # DB[:items].window(:w, :partition=>:c1, :order=>:c2)
1115
+ # DB[:items].window(:w, partition: :c1, order: :c2)
1106
1116
  # # SELECT * FROM items WINDOW w AS (PARTITION BY c1 ORDER BY c2)
1107
1117
  def window(name, opts)
1108
1118
  clone(:window=>((@opts[:window]||EMPTY_ARRAY) + [[name, SQL::Window.new(opts)].freeze]).freeze)
@@ -1163,7 +1173,7 @@ module Sequel
1163
1173
  # DB[:t].with_recursive(:t,
1164
1174
  # DB[:i1].select(:id, :parent_id).where(parent_id: nil),
1165
1175
  # DB[:i1].join(:t, id: :parent_id).select(Sequel[:i1][:id], Sequel[:i1][:parent_id]),
1166
- # :args=>[:id, :parent_id])
1176
+ # args: [:id, :parent_id])
1167
1177
  #
1168
1178
  # # WITH RECURSIVE t(id, parent_id) AS (
1169
1179
  # # SELECT id, parent_id FROM i1 WHERE (parent_id IS NULL)
@@ -1199,16 +1209,27 @@ module Sequel
1199
1209
  end
1200
1210
 
1201
1211
  if TRUE_FREEZE
1202
- # Return a clone of the dataset extended with the given modules.
1212
+ # Create a subclass of the receiver's class, and include the given modules
1213
+ # into it. If a block is provided, a DatasetModule is created using the block and
1214
+ # is included into the subclass. Create an instance of the subclass using the
1215
+ # same db and opts, so that the returned dataset operates similarly to a clone
1216
+ # extended with the given modules. This approach is used to avoid singleton
1217
+ # classes, which significantly improves performance.
1218
+ #
1203
1219
  # Note that like Object#extend, when multiple modules are provided
1204
- # as arguments the cloned dataset is extended with the modules in reverse
1205
- # order. If a block is provided, a DatasetModule is created using the block and
1206
- # the clone is extended with that module after any modules given as arguments.
1220
+ # as arguments the subclass includes the modules in reverse order.
1207
1221
  def with_extend(*mods, &block)
1208
- c = _clone(:freeze=>false)
1209
- c.extend(*mods) unless mods.empty?
1210
- c.extend(DatasetModule.new(&block)) if block
1211
- c.freeze
1222
+ c = Class.new(self.class)
1223
+ c.include(*mods) unless mods.empty?
1224
+ c.include(DatasetModule.new(&block)) if block
1225
+ o = c.freeze.allocate
1226
+ o.instance_variable_set(:@db, @db)
1227
+ o.instance_variable_set(:@opts, @opts)
1228
+ o.instance_variable_set(:@cache, {})
1229
+ if cols = cache_get(:_columns)
1230
+ o.send(:columns=, cols)
1231
+ end
1232
+ o.freeze
1212
1233
  end
1213
1234
  else
1214
1235
  # :nocov:
@@ -1241,7 +1262,7 @@ module Sequel
1241
1262
  #
1242
1263
  # You can also provide a method name and arguments to call to get the SQL:
1243
1264
  #
1244
- # DB[:items].with_sql(:insert_sql, :b=>1) # INSERT INTO items (b) VALUES (1)
1265
+ # DB[:items].with_sql(:insert_sql, b: 1) # INSERT INTO items (b) VALUES (1)
1245
1266
  #
1246
1267
  # Note that datasets that specify custom SQL using this method will generally
1247
1268
  # ignore future dataset methods that modify the SQL used, as specifying custom SQL
@@ -1315,18 +1336,22 @@ module Sequel
1315
1336
 
1316
1337
  private
1317
1338
 
1318
- # Load the extensions into the receiver, without checking if the receiver is frozen.
1319
- def _extension!(exts)
1320
- Sequel.extension(*exts)
1321
- exts.each do |ext|
1322
- if pr = Sequel.synchronize{EXTENSIONS[ext]}
1323
- pr.call(self)
1324
- else
1325
- raise(Error, "Extension #{ext} does not have specific support handling individual datasets (try: Sequel.extension #{ext.inspect})")
1339
+ # :nocov:
1340
+ unless TRUE_FREEZE
1341
+ # Load the extensions into the receiver, without checking if the receiver is frozen.
1342
+ def _extension!(exts)
1343
+ Sequel.extension(*exts)
1344
+ exts.each do |ext|
1345
+ if pr = Sequel.synchronize{EXTENSIONS[ext]}
1346
+ pr.call(self)
1347
+ else
1348
+ raise(Error, "Extension #{ext} does not have specific support handling individual datasets (try: Sequel.extension #{ext.inspect})")
1349
+ end
1326
1350
  end
1351
+ self
1327
1352
  end
1328
- self
1329
1353
  end
1354
+ # :nocov:
1330
1355
 
1331
1356
  # If invert is true, invert the condition.
1332
1357
  def _invert_filter(cond, invert)