sequel 5.45.0 → 5.77.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 (218) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +434 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +59 -27
  5. data/bin/sequel +11 -3
  6. data/doc/advanced_associations.rdoc +16 -14
  7. data/doc/association_basics.rdoc +119 -24
  8. data/doc/cheat_sheet.rdoc +11 -3
  9. data/doc/mass_assignment.rdoc +1 -1
  10. data/doc/migration.rdoc +27 -6
  11. data/doc/model_hooks.rdoc +1 -1
  12. data/doc/object_model.rdoc +8 -8
  13. data/doc/opening_databases.rdoc +28 -12
  14. data/doc/postgresql.rdoc +16 -8
  15. data/doc/querying.rdoc +5 -3
  16. data/doc/release_notes/5.46.0.txt +87 -0
  17. data/doc/release_notes/5.47.0.txt +59 -0
  18. data/doc/release_notes/5.48.0.txt +14 -0
  19. data/doc/release_notes/5.49.0.txt +59 -0
  20. data/doc/release_notes/5.50.0.txt +78 -0
  21. data/doc/release_notes/5.51.0.txt +47 -0
  22. data/doc/release_notes/5.52.0.txt +87 -0
  23. data/doc/release_notes/5.53.0.txt +23 -0
  24. data/doc/release_notes/5.54.0.txt +27 -0
  25. data/doc/release_notes/5.55.0.txt +21 -0
  26. data/doc/release_notes/5.56.0.txt +51 -0
  27. data/doc/release_notes/5.57.0.txt +23 -0
  28. data/doc/release_notes/5.58.0.txt +31 -0
  29. data/doc/release_notes/5.59.0.txt +73 -0
  30. data/doc/release_notes/5.60.0.txt +22 -0
  31. data/doc/release_notes/5.61.0.txt +43 -0
  32. data/doc/release_notes/5.62.0.txt +132 -0
  33. data/doc/release_notes/5.63.0.txt +33 -0
  34. data/doc/release_notes/5.64.0.txt +50 -0
  35. data/doc/release_notes/5.65.0.txt +21 -0
  36. data/doc/release_notes/5.66.0.txt +24 -0
  37. data/doc/release_notes/5.67.0.txt +32 -0
  38. data/doc/release_notes/5.68.0.txt +61 -0
  39. data/doc/release_notes/5.69.0.txt +26 -0
  40. data/doc/release_notes/5.70.0.txt +35 -0
  41. data/doc/release_notes/5.71.0.txt +21 -0
  42. data/doc/release_notes/5.72.0.txt +33 -0
  43. data/doc/release_notes/5.73.0.txt +66 -0
  44. data/doc/release_notes/5.74.0.txt +45 -0
  45. data/doc/release_notes/5.75.0.txt +35 -0
  46. data/doc/release_notes/5.76.0.txt +86 -0
  47. data/doc/release_notes/5.77.0.txt +63 -0
  48. data/doc/schema_modification.rdoc +1 -1
  49. data/doc/security.rdoc +9 -9
  50. data/doc/sharding.rdoc +3 -1
  51. data/doc/sql.rdoc +27 -15
  52. data/doc/testing.rdoc +23 -13
  53. data/doc/transactions.rdoc +6 -6
  54. data/doc/virtual_rows.rdoc +1 -1
  55. data/lib/sequel/adapters/ado/access.rb +1 -1
  56. data/lib/sequel/adapters/ado.rb +1 -1
  57. data/lib/sequel/adapters/amalgalite.rb +3 -5
  58. data/lib/sequel/adapters/ibmdb.rb +3 -3
  59. data/lib/sequel/adapters/jdbc/derby.rb +8 -0
  60. data/lib/sequel/adapters/jdbc/h2.rb +63 -10
  61. data/lib/sequel/adapters/jdbc/hsqldb.rb +8 -0
  62. data/lib/sequel/adapters/jdbc/postgresql.rb +7 -4
  63. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +15 -0
  64. data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -0
  65. data/lib/sequel/adapters/jdbc.rb +24 -22
  66. data/lib/sequel/adapters/mysql.rb +92 -67
  67. data/lib/sequel/adapters/mysql2.rb +56 -51
  68. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  69. data/lib/sequel/adapters/odbc.rb +1 -1
  70. data/lib/sequel/adapters/oracle.rb +4 -3
  71. data/lib/sequel/adapters/postgres.rb +89 -45
  72. data/lib/sequel/adapters/shared/access.rb +11 -1
  73. data/lib/sequel/adapters/shared/db2.rb +42 -0
  74. data/lib/sequel/adapters/shared/mssql.rb +91 -10
  75. data/lib/sequel/adapters/shared/mysql.rb +78 -3
  76. data/lib/sequel/adapters/shared/oracle.rb +86 -7
  77. data/lib/sequel/adapters/shared/postgres.rb +576 -171
  78. data/lib/sequel/adapters/shared/sqlanywhere.rb +21 -5
  79. data/lib/sequel/adapters/shared/sqlite.rb +92 -8
  80. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  81. data/lib/sequel/adapters/sqlite.rb +99 -18
  82. data/lib/sequel/adapters/tinytds.rb +1 -1
  83. data/lib/sequel/adapters/trilogy.rb +117 -0
  84. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  85. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  86. data/lib/sequel/ast_transformer.rb +6 -0
  87. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  88. data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
  89. data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
  90. data/lib/sequel/connection_pool/single.rb +6 -8
  91. data/lib/sequel/connection_pool/threaded.rb +14 -8
  92. data/lib/sequel/connection_pool/timed_queue.rb +270 -0
  93. data/lib/sequel/connection_pool.rb +57 -31
  94. data/lib/sequel/core.rb +17 -18
  95. data/lib/sequel/database/connecting.rb +27 -3
  96. data/lib/sequel/database/dataset.rb +16 -6
  97. data/lib/sequel/database/misc.rb +70 -14
  98. data/lib/sequel/database/query.rb +73 -2
  99. data/lib/sequel/database/schema_generator.rb +11 -6
  100. data/lib/sequel/database/schema_methods.rb +23 -4
  101. data/lib/sequel/database/transactions.rb +6 -0
  102. data/lib/sequel/dataset/actions.rb +111 -15
  103. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
  104. data/lib/sequel/dataset/features.rb +20 -1
  105. data/lib/sequel/dataset/misc.rb +12 -2
  106. data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
  107. data/lib/sequel/dataset/query.rb +170 -41
  108. data/lib/sequel/dataset/sql.rb +190 -71
  109. data/lib/sequel/dataset.rb +4 -0
  110. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  111. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  112. data/lib/sequel/extensions/any_not_empty.rb +2 -2
  113. data/lib/sequel/extensions/async_thread_pool.rb +14 -13
  114. data/lib/sequel/extensions/auto_cast_date_and_time.rb +94 -0
  115. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  116. data/lib/sequel/extensions/connection_expiration.rb +15 -9
  117. data/lib/sequel/extensions/connection_validator.rb +16 -11
  118. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  119. data/lib/sequel/extensions/core_refinements.rb +36 -11
  120. data/lib/sequel/extensions/date_arithmetic.rb +36 -8
  121. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  122. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  123. data/lib/sequel/extensions/duplicate_columns_handler.rb +11 -10
  124. data/lib/sequel/extensions/index_caching.rb +5 -1
  125. data/lib/sequel/extensions/inflector.rb +1 -1
  126. data/lib/sequel/extensions/is_distinct_from.rb +141 -0
  127. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  128. data/lib/sequel/extensions/migration.rb +57 -15
  129. data/lib/sequel/extensions/named_timezones.rb +22 -6
  130. data/lib/sequel/extensions/pagination.rb +1 -1
  131. data/lib/sequel/extensions/pg_array.rb +33 -4
  132. data/lib/sequel/extensions/pg_array_ops.rb +2 -2
  133. data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
  134. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  135. data/lib/sequel/extensions/pg_enum.rb +1 -2
  136. data/lib/sequel/extensions/pg_extended_date_support.rb +39 -28
  137. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  138. data/lib/sequel/extensions/pg_hstore.rb +6 -1
  139. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  140. data/lib/sequel/extensions/pg_inet.rb +10 -11
  141. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  142. data/lib/sequel/extensions/pg_interval.rb +11 -11
  143. data/lib/sequel/extensions/pg_json.rb +13 -15
  144. data/lib/sequel/extensions/pg_json_ops.rb +125 -2
  145. data/lib/sequel/extensions/pg_multirange.rb +367 -0
  146. data/lib/sequel/extensions/pg_range.rb +13 -26
  147. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  148. data/lib/sequel/extensions/pg_row.rb +20 -19
  149. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  150. data/lib/sequel/extensions/pg_timestamptz.rb +27 -3
  151. data/lib/sequel/extensions/round_timestamps.rb +1 -1
  152. data/lib/sequel/extensions/s.rb +2 -1
  153. data/lib/sequel/extensions/schema_caching.rb +1 -1
  154. data/lib/sequel/extensions/schema_dumper.rb +45 -11
  155. data/lib/sequel/extensions/server_block.rb +10 -13
  156. data/lib/sequel/extensions/set_literalizer.rb +58 -0
  157. data/lib/sequel/extensions/sql_comments.rb +110 -3
  158. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  159. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  160. data/lib/sequel/extensions/string_agg.rb +1 -1
  161. data/lib/sequel/extensions/string_date_time.rb +19 -23
  162. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  163. data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
  164. data/lib/sequel/model/associations.rb +286 -92
  165. data/lib/sequel/model/base.rb +53 -33
  166. data/lib/sequel/model/dataset_module.rb +3 -0
  167. data/lib/sequel/model/errors.rb +10 -1
  168. data/lib/sequel/model/exceptions.rb +15 -3
  169. data/lib/sequel/model/inflections.rb +1 -1
  170. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  171. data/lib/sequel/plugins/auto_validations.rb +74 -16
  172. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  173. data/lib/sequel/plugins/column_encryption.rb +29 -8
  174. data/lib/sequel/plugins/composition.rb +3 -2
  175. data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
  176. data/lib/sequel/plugins/constraint_validations.rb +8 -5
  177. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  178. data/lib/sequel/plugins/dirty.rb +1 -1
  179. data/lib/sequel/plugins/enum.rb +124 -0
  180. data/lib/sequel/plugins/finder.rb +4 -2
  181. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  182. data/lib/sequel/plugins/instance_specific_default.rb +1 -1
  183. data/lib/sequel/plugins/json_serializer.rb +2 -2
  184. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  185. data/lib/sequel/plugins/list.rb +8 -3
  186. data/lib/sequel/plugins/many_through_many.rb +109 -10
  187. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  188. data/lib/sequel/plugins/nested_attributes.rb +4 -4
  189. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  190. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  191. data/lib/sequel/plugins/paged_operations.rb +181 -0
  192. data/lib/sequel/plugins/pg_array_associations.rb +46 -34
  193. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +9 -3
  194. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  195. data/lib/sequel/plugins/prepared_statements.rb +12 -2
  196. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
  197. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  198. data/lib/sequel/plugins/rcte_tree.rb +7 -4
  199. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  200. data/lib/sequel/plugins/serialization.rb +1 -0
  201. data/lib/sequel/plugins/serialization_modification_detection.rb +1 -0
  202. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  203. data/lib/sequel/plugins/sql_comments.rb +189 -0
  204. data/lib/sequel/plugins/static_cache.rb +39 -1
  205. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  206. data/lib/sequel/plugins/subclasses.rb +28 -11
  207. data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
  208. data/lib/sequel/plugins/timestamps.rb +1 -1
  209. data/lib/sequel/plugins/unused_associations.rb +521 -0
  210. data/lib/sequel/plugins/update_or_create.rb +1 -1
  211. data/lib/sequel/plugins/validate_associated.rb +22 -12
  212. data/lib/sequel/plugins/validation_helpers.rb +41 -11
  213. data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
  214. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  215. data/lib/sequel/sql.rb +1 -1
  216. data/lib/sequel/timezones.rb +12 -14
  217. data/lib/sequel/version.rb +1 -1
  218. metadata +109 -19
@@ -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.
@@ -125,6 +130,11 @@ module Sequel
125
130
  false
126
131
  end
127
132
 
133
+ # Whether the MERGE statement is supported, false by default.
134
+ def supports_merge?
135
+ false
136
+ end
137
+
128
138
  # Whether modifying joined datasets is supported, false by default.
129
139
  def supports_modifying_joins?
130
140
  false
@@ -147,6 +157,11 @@ module Sequel
147
157
  supports_distinct_on?
148
158
  end
149
159
 
160
+ # Whether placeholder literalizers are supported, true by default.
161
+ def supports_placeholder_literalizer?
162
+ true
163
+ end
164
+
150
165
  # Whether the dataset supports pattern matching by regular expressions, false by default.
151
166
  def supports_regexp?
152
167
  false
@@ -173,10 +188,14 @@ module Sequel
173
188
  true
174
189
  end
175
190
 
191
+ # :nocov:
192
+
176
193
  # Whether the dataset supports timezones in literal timestamps, false by default.
177
194
  def supports_timestamp_timezones?
195
+ # SEQUEL6: Remove
178
196
  false
179
197
  end
198
+ # :nocov:
180
199
 
181
200
  # Whether the dataset supports fractional seconds in literal timestamps, true by default.
182
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:
@@ -508,6 +518,7 @@ module Sequel
508
518
  # argument.
509
519
  # :implicit_qualifier :: The name to use for qualifying implicit conditions. By default,
510
520
  # the last joined or primary table is used.
521
+ # :join_using :: Force the using of JOIN USING, even if +expr+ is not an array of symbols.
511
522
  # :reset_implicit_qualifier :: Can set to false to ignore this join when future joins determine qualifier
512
523
  # for implicit conditions.
513
524
  # :qualify :: Can be set to false to not do any implicit qualification. Can be set
@@ -541,7 +552,7 @@ module Sequel
541
552
  return s.join_table(type, ds, expr, options, &block)
542
553
  end
543
554
 
544
- using_join = expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)}
555
+ using_join = options[:join_using] || (expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)})
545
556
  if using_join && !supports_join_using?
546
557
  h = {}
547
558
  expr.each{|e| h[e] = e}
@@ -616,7 +627,7 @@ module Sequel
616
627
  UNCONDITIONED_JOIN_TYPES.each do |jtype|
617
628
  class_eval(<<-END, __FILE__, __LINE__+1)
618
629
  def #{jtype}_join(table, opts=Sequel::OPTS)
619
- raise(Sequel::Error, '#{jtype}_join does not accept join table blocks') if block_given?
630
+ raise(Sequel::Error, '#{jtype}_join does not accept join table blocks') if defined?(yield)
620
631
  raise(Sequel::Error, '#{jtype}_join 2nd argument should be an options hash, not conditions') unless opts.is_a?(Hash)
621
632
  join_table(:#{jtype}, table, nil, opts)
622
633
  end
@@ -677,6 +688,56 @@ module Sequel
677
688
  clone(:lock => style)
678
689
  end
679
690
 
691
+ # Return a dataset with a WHEN MATCHED THEN DELETE clause added to the
692
+ # MERGE statement. If a block is passed, treat it as a virtual row and
693
+ # use it as additional conditions for the match.
694
+ #
695
+ # merge_delete
696
+ # # WHEN MATCHED THEN DELETE
697
+ #
698
+ # merge_delete{a > 30}
699
+ # # WHEN MATCHED AND (a > 30) THEN DELETE
700
+ def merge_delete(&block)
701
+ _merge_when(:type=>:delete, &block)
702
+ end
703
+
704
+ # Return a dataset with a WHEN NOT MATCHED THEN INSERT clause added to the
705
+ # MERGE statement. If a block is passed, treat it as a virtual row and
706
+ # use it as additional conditions for the match.
707
+ #
708
+ # The arguments provided can be any arguments that would be accepted by
709
+ # #insert.
710
+ #
711
+ # merge_insert(i1: :i2, a: Sequel[:b]+11)
712
+ # # WHEN NOT MATCHED THEN INSERT (i1, a) VALUES (i2, (b + 11))
713
+ #
714
+ # merge_insert(:i2, Sequel[:b]+11){a > 30}
715
+ # # WHEN NOT MATCHED AND (a > 30) THEN INSERT VALUES (i2, (b + 11))
716
+ def merge_insert(*values, &block)
717
+ _merge_when(:type=>:insert, :values=>values, &block)
718
+ end
719
+
720
+ # Return a dataset with a WHEN MATCHED THEN UPDATE clause added to the
721
+ # MERGE statement. If a block is passed, treat it as a virtual row and
722
+ # use it as additional conditions for the match.
723
+ #
724
+ # merge_update(i1: Sequel[:i1]+:i2+10, a: Sequel[:a]+:b+20)
725
+ # # WHEN MATCHED THEN UPDATE SET i1 = (i1 + i2 + 10), a = (a + b + 20)
726
+ #
727
+ # merge_update(i1: :i2){a > 30}
728
+ # # WHEN MATCHED AND (a > 30) THEN UPDATE SET i1 = i2
729
+ def merge_update(values, &block)
730
+ _merge_when(:type=>:update, :values=>values, &block)
731
+ end
732
+
733
+ # Return a dataset with the source and join condition to use for the MERGE statement.
734
+ #
735
+ # merge_using(:m2, i1: :i2)
736
+ # # USING m2 ON (i1 = i2)
737
+ def merge_using(source, join_condition)
738
+ clone(:merge_using => [source, join_condition].freeze)
739
+ end
740
+
680
741
  # Returns a cloned dataset without a row_proc.
681
742
  #
682
743
  # ds = DB[:items].with_row_proc(:invert.to_proc)
@@ -699,7 +760,7 @@ module Sequel
699
760
  end
700
761
 
701
762
  # Returns a copy of the dataset with a specified order. Can be safely combined with limit.
702
- # If you call limit with an offset, it will override override the offset if you've called
763
+ # If you call limit with an offset, it will override the offset if you've called
703
764
  # offset first.
704
765
  #
705
766
  # DB[:items].offset(10) # SELECT * FROM items OFFSET 10
@@ -736,7 +797,7 @@ module Sequel
736
797
  # DB[:items].order(Sequel.lit('a + b')) # SELECT * FROM items ORDER BY a + b
737
798
  # DB[:items].order(Sequel[:a] + :b) # SELECT * FROM items ORDER BY (a + b)
738
799
  # DB[:items].order(Sequel.desc(:name)) # SELECT * FROM items ORDER BY name DESC
739
- # 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
740
801
  # DB[:items].order{sum(name).desc} # SELECT * FROM items ORDER BY sum(name) DESC
741
802
  # DB[:items].order(nil) # SELECT * FROM items
742
803
  def order(*columns, &block)
@@ -806,13 +867,13 @@ module Sequel
806
867
  # DB[:items].returning(nil) # RETURNING NULL
807
868
  # DB[:items].returning(:id, :name) # RETURNING id, name
808
869
  #
809
- # DB[:items].returning.insert(:a=>1) do |hash|
870
+ # DB[:items].returning.insert(a: 1) do |hash|
810
871
  # # hash for each row inserted, with values for all columns
811
872
  # end
812
- # DB[:items].returning.update(:a=>1) do |hash|
873
+ # DB[:items].returning.update(a: 1) do |hash|
813
874
  # # hash for each row updated, with values for all columns
814
875
  # end
815
- # DB[:items].returning.delete(:a=>1) do |hash|
876
+ # DB[:items].returning.delete(a: 1) do |hash|
816
877
  # # hash for each row deleted, with values for all columns
817
878
  # end
818
879
  def returning(*values)
@@ -1051,7 +1112,7 @@ module Sequel
1051
1112
  # referenced in window functions. See Sequel::SQL::Window for a list of
1052
1113
  # options that can be passed in. Example:
1053
1114
  #
1054
- # DB[:items].window(:w, :partition=>:c1, :order=>:c2)
1115
+ # DB[:items].window(:w, partition: :c1, order: :c2)
1055
1116
  # # SELECT * FROM items WINDOW w AS (PARTITION BY c1 ORDER BY c2)
1056
1117
  def window(name, opts)
1057
1118
  clone(:window=>((@opts[:window]||EMPTY_ARRAY) + [[name, SQL::Window.new(opts)].freeze]).freeze)
@@ -1059,6 +1120,7 @@ module Sequel
1059
1120
 
1060
1121
  # Add a common table expression (CTE) with the given name and a dataset that defines the CTE.
1061
1122
  # A common table expression acts as an inline view for the query.
1123
+ #
1062
1124
  # Options:
1063
1125
  # :args :: Specify the arguments/columns for the CTE, should be an array of symbols.
1064
1126
  # :recursive :: Specify that this is a recursive CTE
@@ -1079,20 +1141,60 @@ module Sequel
1079
1141
 
1080
1142
  # Add a recursive common table expression (CTE) with the given name, a dataset that
1081
1143
  # defines the nonrecursive part of the CTE, and a dataset that defines the recursive part
1082
- # of the CTE. Options:
1144
+ # of the CTE.
1145
+ #
1146
+ # Options:
1083
1147
  # :args :: Specify the arguments/columns for the CTE, should be an array of symbols.
1084
1148
  # :union_all :: Set to false to use UNION instead of UNION ALL combining the nonrecursive and recursive parts.
1085
1149
  #
1150
+ # PostgreSQL 14+ Options:
1151
+ # :cycle :: Stop recursive searching when a cycle is detected. Includes two columns in the
1152
+ # result of the CTE, a cycle column indicating whether a cycle was detected for
1153
+ # the current row, and a path column for the path traversed to get to the current
1154
+ # row. If given, must be a hash with the following keys:
1155
+ # :columns :: (required) The column or array of columns to use to detect a cycle.
1156
+ # If the value of these columns match columns already traversed, then
1157
+ # a cycle is detected, and recursive searching will not traverse beyond
1158
+ # the cycle (the CTE will include the row where the cycle was detected).
1159
+ # :cycle_column :: The name of the cycle column in the output, defaults to :is_cycle.
1160
+ # :cycle_value :: The value of the cycle column in the output if the current row was
1161
+ # detected as a cycle, defaults to true.
1162
+ # :noncycle_value :: The value of the cycle column in the output if the current row
1163
+ # was not detected as a cycle, defaults to false. Only respected
1164
+ # if :cycle_value is given.
1165
+ # :path_column :: The name of the path column in the output, defaults to :path.
1166
+ # :search :: Include an order column in the result of the CTE that allows for breadth or
1167
+ # depth first searching. If given, must be a hash with the following keys:
1168
+ # :by :: (required) The column or array of columns to search by.
1169
+ # :order_column :: The name of the order column in the output, defaults to :ordercol.
1170
+ # :type :: Set to :breadth to use breadth-first searching (depth-first searching
1171
+ # is the default).
1172
+ #
1086
1173
  # DB[:t].with_recursive(:t,
1087
1174
  # DB[:i1].select(:id, :parent_id).where(parent_id: nil),
1088
1175
  # DB[:i1].join(:t, id: :parent_id).select(Sequel[:i1][:id], Sequel[:i1][:parent_id]),
1089
- # :args=>[:id, :parent_id])
1176
+ # args: [:id, :parent_id])
1090
1177
  #
1091
1178
  # # WITH RECURSIVE t(id, parent_id) AS (
1092
1179
  # # SELECT id, parent_id FROM i1 WHERE (parent_id IS NULL)
1093
1180
  # # UNION ALL
1094
1181
  # # SELECT i1.id, i1.parent_id FROM i1 INNER JOIN t ON (t.id = i1.parent_id)
1095
1182
  # # ) SELECT * FROM t
1183
+ #
1184
+ # DB[:t].with_recursive(:t,
1185
+ # DB[:i1].where(parent_id: nil),
1186
+ # DB[:i1].join(:t, id: :parent_id).select_all(:i1),
1187
+ # search: {by: :id, type: :breadth},
1188
+ # cycle: {columns: :id, cycle_value: 1, noncycle_value: 2})
1189
+ #
1190
+ # # WITH RECURSIVE t AS (
1191
+ # # SELECT * FROM i1 WHERE (parent_id IS NULL)
1192
+ # # UNION ALL
1193
+ # # (SELECT i1.* FROM i1 INNER JOIN t ON (t.id = i1.parent_id))
1194
+ # # )
1195
+ # # SEARCH BREADTH FIRST BY id SET ordercol
1196
+ # # CYCLE id SET is_cycle TO 1 DEFAULT 2 USING path
1197
+ # # SELECT * FROM t
1096
1198
  def with_recursive(name, nonrecursive, recursive, opts=OPTS)
1097
1199
  raise(Error, 'This dataset does not support common table expressions') unless supports_cte?
1098
1200
  if hoist_cte?(nonrecursive)
@@ -1107,16 +1209,27 @@ module Sequel
1107
1209
  end
1108
1210
 
1109
1211
  if TRUE_FREEZE
1110
- # 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
+ #
1111
1219
  # Note that like Object#extend, when multiple modules are provided
1112
- # as arguments the cloned dataset is extended with the modules in reverse
1113
- # order. If a block is provided, a DatasetModule is created using the block and
1114
- # the clone is extended with that module after any modules given as arguments.
1220
+ # as arguments the subclass includes the modules in reverse order.
1115
1221
  def with_extend(*mods, &block)
1116
- c = _clone(:freeze=>false)
1117
- c.extend(*mods) unless mods.empty?
1118
- c.extend(DatasetModule.new(&block)) if block
1119
- 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
1120
1233
  end
1121
1234
  else
1122
1235
  # :nocov:
@@ -1149,7 +1262,7 @@ module Sequel
1149
1262
  #
1150
1263
  # You can also provide a method name and arguments to call to get the SQL:
1151
1264
  #
1152
- # 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)
1153
1266
  #
1154
1267
  # Note that datasets that specify custom SQL using this method will generally
1155
1268
  # ignore future dataset methods that modify the SQL used, as specifying custom SQL
@@ -1223,18 +1336,22 @@ module Sequel
1223
1336
 
1224
1337
  private
1225
1338
 
1226
- # Load the extensions into the receiver, without checking if the receiver is frozen.
1227
- def _extension!(exts)
1228
- Sequel.extension(*exts)
1229
- exts.each do |ext|
1230
- if pr = Sequel.synchronize{EXTENSIONS[ext]}
1231
- pr.call(self)
1232
- else
1233
- 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
1234
1350
  end
1351
+ self
1235
1352
  end
1236
- self
1237
1353
  end
1354
+ # :nocov:
1238
1355
 
1239
1356
  # If invert is true, invert the condition.
1240
1357
  def _invert_filter(cond, invert)
@@ -1245,6 +1362,18 @@ module Sequel
1245
1362
  end
1246
1363
  end
1247
1364
 
1365
+ # Append to the current MERGE WHEN clauses.
1366
+ # Mutates the hash to add the conditions, if a virtual row block is passed.
1367
+ def _merge_when(hash, &block)
1368
+ hash[:conditions] = Sequel.virtual_row(&block) if block
1369
+
1370
+ if merge_when = @opts[:merge_when]
1371
+ clone(:merge_when => (merge_when.dup << hash.freeze).freeze)
1372
+ else
1373
+ clone(:merge_when => [hash.freeze].freeze)
1374
+ end
1375
+ end
1376
+
1248
1377
  # Add the given filter condition. Arguments:
1249
1378
  # clause :: Symbol or which SQL clause to effect, should be :where or :having
1250
1379
  # cond :: The filter condition to add