sequel 5.45.0 → 5.77.0

Sign up to get free protection for your applications and to get access to all the features.
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