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
@@ -19,6 +19,9 @@ module Sequel
19
19
  module Postgres
20
20
  Sequel::Database.set_shared_adapter_scheme(:postgres, self)
21
21
 
22
+ # Exception class ranged when literalizing integers outside the bigint/int8 range.
23
+ class IntegerOutsideBigintRange < InvalidValue; end
24
+
22
25
  NAN = 0.0/0.0
23
26
  PLUS_INFINITY = 1.0/0.0
24
27
  MINUS_INFINITY = -1.0/0.0
@@ -83,11 +86,22 @@ module Sequel
83
86
  def primary_key(table)
84
87
  :id
85
88
  end
89
+
90
+ private
91
+
92
+ # Handle NoMethodErrors when parsing schema due to output_identifier
93
+ # being called with nil when the Database fetch results are not set
94
+ # to what schema parsing expects.
95
+ def schema_parse_table(table, opts=OPTS)
96
+ super
97
+ rescue NoMethodError
98
+ []
99
+ end
86
100
  end
87
101
 
88
102
  def self.mock_adapter_setup(db)
89
103
  db.instance_exec do
90
- @server_version = 90500
104
+ @server_version = 150000
91
105
  initialize_postgres_adapter
92
106
  extend(MockAdapterDatabaseMethods)
93
107
  end
@@ -230,7 +244,6 @@ module Sequel
230
244
  module DatabaseMethods
231
245
  include UnmodifiedIdentifiers::DatabaseMethods
232
246
 
233
- PREPARED_ARG_PLACEHOLDER = LiteralString.new('$').freeze
234
247
  FOREIGN_KEY_LIST_ON_DELETE_MAP = {'a'=>:no_action, 'r'=>:restrict, 'c'=>:cascade, 'n'=>:set_null, 'd'=>:set_default}.freeze
235
248
  ON_COMMIT = {:drop => 'DROP', :delete_rows => 'DELETE ROWS', :preserve_rows => 'PRESERVE ROWS'}.freeze
236
249
  ON_COMMIT.each_value(&:freeze)
@@ -254,7 +267,7 @@ module Sequel
254
267
  WHERE cons.contype = 'p'
255
268
  AND pg_get_expr(def.adbin, attr.attrelid) ~* 'nextval'
256
269
  end_sql
257
- ).strip.gsub(/\s+/, ' ').freeze
270
+ ).strip.gsub(/\s+/, ' ').freeze # SEQUEL6: Remove
258
271
 
259
272
  # SQL fragment for determining primary key column for the given table. Only
260
273
  # returns the first primary key if the table has a composite primary key.
@@ -267,7 +280,7 @@ module Sequel
267
280
  AND pg_index.indkey[0] = pg_attribute.attnum
268
281
  AND pg_index.indisprimary = 't'
269
282
  end_sql
270
- ).strip.gsub(/\s+/, ' ').freeze
283
+ ).strip.gsub(/\s+/, ' ').freeze # SEQUEL6: Remove
271
284
 
272
285
  # SQL fragment for getting sequence associated with table's
273
286
  # primary key, assuming it was a serial primary key column.
@@ -285,7 +298,7 @@ module Sequel
285
298
  AND attr.attrelid = t.oid
286
299
  AND cons.contype = 'p'
287
300
  end_sql
288
- ).strip.gsub(/\s+/, ' ').freeze
301
+ ).strip.gsub(/\s+/, ' ').freeze # SEQUEL6: Remove
289
302
 
290
303
  # A hash of conversion procs, keyed by type integer (oid) and
291
304
  # having callable values for the conversion proc for that type.
@@ -319,14 +332,8 @@ module Sequel
319
332
  def check_constraints(table)
320
333
  m = output_identifier_meth
321
334
 
322
- rows = metadata_dataset.
323
- from{pg_constraint.as(:co)}.
324
- left_join(Sequel[:pg_attribute].as(:att), :attrelid=>:conrelid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:conkey])).
325
- where(:conrelid=>regclass_oid(table), :contype=>'c').
326
- select{[co[:conname].as(:constraint), att[:attname].as(:column), pg_get_constraintdef(co[:oid]).as(:definition)]}
327
-
328
335
  hash = {}
329
- rows.each do |row|
336
+ _check_constraints_ds.where_each(:conrelid=>regclass_oid(table)) do |row|
330
337
  constraint = m.call(row[:constraint])
331
338
  entry = hash[constraint] ||= {:definition=>row[:definition], :columns=>[]}
332
339
  entry[:columns] << m.call(row[:column]) if row[:column]
@@ -365,9 +372,10 @@ module Sequel
365
372
 
366
373
  table_oid = regclass_oid(table)
367
374
  im = input_identifier_meth
368
- unless column = im.call(opts[:column] || ((sch = schema(table).find{|_, sc| sc[:primary_key] && sc[:auto_increment]}) && sch[0]))
375
+ unless column = (opts[:column] || ((sch = schema(table).find{|_, sc| sc[:primary_key] && sc[:auto_increment]}) && sch[0]))
369
376
  raise Error, "could not determine column to convert from serial to identity automatically"
370
377
  end
378
+ column = im.call(column)
371
379
 
372
380
  column_num = ds.from(:pg_attribute).
373
381
  where(:attrelid=>table_oid, :attname=>column).
@@ -414,6 +422,7 @@ module Sequel
414
422
  # 2 :: argument name
415
423
  # 3 :: argument mode (e.g. in, out, inout)
416
424
  # :behavior :: Should be IMMUTABLE, STABLE, or VOLATILE. PostgreSQL assumes VOLATILE by default.
425
+ # :parallel :: The thread safety attribute of the function. Should be SAFE, UNSAFE, RESTRICTED. PostgreSQL assumes UNSAFE by default.
417
426
  # :cost :: The estimated cost of the function, used by the query planner.
418
427
  # :language :: The language the function uses. SQL is the default.
419
428
  # :link_symbol :: For a dynamically loaded see function, the function's link symbol if different from the definition argument.
@@ -479,6 +488,7 @@ module Sequel
479
488
  # :each_row :: Calls the trigger for each row instead of for each statement.
480
489
  # :events :: Can be :insert, :update, :delete, or an array of any of those. Calls the trigger whenever that type of statement is used. By default,
481
490
  # the trigger is called for insert, update, or delete.
491
+ # :replace :: Replace the trigger with the same name if it already exists (PostgreSQL 14+).
482
492
  # :when :: A filter to use for the trigger
483
493
  def create_trigger(table, name, function, opts=OPTS)
484
494
  self << create_trigger_sql(table, name, function, opts)
@@ -488,6 +498,25 @@ module Sequel
488
498
  :postgres
489
499
  end
490
500
 
501
+ # For constraints that are deferrable, defer constraints until
502
+ # transaction commit. Options:
503
+ #
504
+ # :constraints :: An identifier of the constraint, or an array of
505
+ # identifiers for constraints, to apply this
506
+ # change to specific constraints.
507
+ # :server :: The server/shard on which to run the query.
508
+ #
509
+ # Examples:
510
+ #
511
+ # DB.defer_constraints
512
+ # # SET CONSTRAINTS ALL DEFERRED
513
+ #
514
+ # DB.defer_constraints(constraints: [:c1, Sequel[:sc][:c2]])
515
+ # # SET CONSTRAINTS "c1", "sc"."s2" DEFERRED
516
+ def defer_constraints(opts=OPTS)
517
+ _set_constraints(' DEFERRED', opts)
518
+ end
519
+
491
520
  # Use PostgreSQL's DO syntax to execute an anonymous code block. The code should
492
521
  # be the literal code string to use in the underlying procedural language. Options:
493
522
  #
@@ -549,63 +578,12 @@ module Sequel
549
578
  def foreign_key_list(table, opts=OPTS)
550
579
  m = output_identifier_meth
551
580
  schema, _ = opts.fetch(:schema, schema_and_table(table))
552
- oid = regclass_oid(table)
553
- reverse = opts[:reverse]
554
-
555
- if reverse
556
- ctable = Sequel[:att2]
557
- cclass = Sequel[:cl2]
558
- rtable = Sequel[:att]
559
- rclass = Sequel[:cl]
560
- else
561
- ctable = Sequel[:att]
562
- cclass = Sequel[:cl]
563
- rtable = Sequel[:att2]
564
- rclass = Sequel[:cl2]
565
- end
566
-
567
- if server_version >= 90500
568
- cpos = Sequel.expr{array_position(co[:conkey], ctable[:attnum])}
569
- rpos = Sequel.expr{array_position(co[:confkey], rtable[:attnum])}
570
- else
571
- range = 0...32
572
- cpos = Sequel.expr{SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:conkey], [x]), x]}, 32, ctable[:attnum])}
573
- rpos = Sequel.expr{SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:confkey], [x]), x]}, 32, rtable[:attnum])}
574
- end
575
-
576
- ds = metadata_dataset.
577
- from{pg_constraint.as(:co)}.
578
- join(Sequel[:pg_class].as(cclass), :oid=>:conrelid).
579
- join(Sequel[:pg_attribute].as(ctable), :attrelid=>:oid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:conkey])).
580
- join(Sequel[:pg_class].as(rclass), :oid=>Sequel[:co][:confrelid]).
581
- join(Sequel[:pg_attribute].as(rtable), :attrelid=>:oid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:confkey])).
582
- join(Sequel[:pg_namespace].as(:nsp), :oid=>Sequel[:cl2][:relnamespace]).
583
- order{[co[:conname], cpos]}.
584
- where{{
585
- cl[:relkind]=>'r',
586
- co[:contype]=>'f',
587
- cl[:oid]=>oid,
588
- cpos=>rpos
589
- }}.
590
- select{[
591
- co[:conname].as(:name),
592
- ctable[:attname].as(:column),
593
- co[:confupdtype].as(:on_update),
594
- co[:confdeltype].as(:on_delete),
595
- cl2[:relname].as(:table),
596
- rtable[:attname].as(:refcolumn),
597
- SQL::BooleanExpression.new(:AND, co[:condeferrable], co[:condeferred]).as(:deferrable),
598
- nsp[:nspname].as(:schema)
599
- ]}
600
-
601
- if reverse
602
- ds = ds.order_append(Sequel[:nsp][:nspname], Sequel[:cl2][:relname])
603
- end
604
581
 
605
582
  h = {}
606
583
  fklod_map = FOREIGN_KEY_LIST_ON_DELETE_MAP
584
+ reverse = opts[:reverse]
607
585
 
608
- ds.each do |row|
586
+ (reverse ? _reverse_foreign_key_list_ds : _foreign_key_list_ds).where_each(Sequel[:cl][:oid]=>regclass_oid(table)) do |row|
609
587
  if reverse
610
588
  key = [row[:schema], row[:table], row[:name]]
611
589
  else
@@ -640,45 +618,44 @@ module Sequel
640
618
  def freeze
641
619
  server_version
642
620
  supports_prepared_transactions?
621
+ _schema_ds
622
+ _select_serial_sequence_ds
623
+ _select_custom_sequence_ds
624
+ _select_pk_ds
625
+ _indexes_ds
626
+ _check_constraints_ds
627
+ _foreign_key_list_ds
628
+ _reverse_foreign_key_list_ds
643
629
  @conversion_procs.freeze
644
630
  super
645
631
  end
646
632
 
633
+ # Immediately apply deferrable constraints.
634
+ #
635
+ # :constraints :: An identifier of the constraint, or an array of
636
+ # identifiers for constraints, to apply this
637
+ # change to specific constraints.
638
+ # :server :: The server/shard on which to run the query.
639
+ #
640
+ # Examples:
641
+ #
642
+ # DB.immediate_constraints
643
+ # # SET CONSTRAINTS ALL IMMEDIATE
644
+ #
645
+ # DB.immediate_constraints(constraints: [:c1, Sequel[:sc][:c2]])
646
+ # # SET CONSTRAINTS "c1", "sc"."s2" IMMEDIATE
647
+ def immediate_constraints(opts=OPTS)
648
+ _set_constraints(' IMMEDIATE', opts)
649
+ end
650
+
647
651
  # Use the pg_* system tables to determine indexes on a table
648
652
  def indexes(table, opts=OPTS)
649
653
  m = output_identifier_meth
650
- oid = regclass_oid(table, opts)
651
-
652
- if server_version >= 90500
653
- order = [Sequel[:indc][:relname], Sequel.function(:array_position, Sequel[:ind][:indkey], Sequel[:att][:attnum])]
654
- else
655
- range = 0...32
656
- order = [Sequel[:indc][:relname], SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(Sequel[:ind][:indkey], [x]), x]}, 32, Sequel[:att][:attnum])]
657
- end
658
-
659
- attnums = SQL::Function.new(:ANY, Sequel[:ind][:indkey])
660
-
661
- ds = metadata_dataset.
662
- from{pg_class.as(:tab)}.
663
- join(Sequel[:pg_index].as(:ind), :indrelid=>:oid).
664
- join(Sequel[:pg_class].as(:indc), :oid=>:indexrelid).
665
- join(Sequel[:pg_attribute].as(:att), :attrelid=>Sequel[:tab][:oid], :attnum=>attnums).
666
- left_join(Sequel[:pg_constraint].as(:con), :conname=>Sequel[:indc][:relname]).
667
- where{{
668
- indc[:relkind]=>'i',
669
- ind[:indisprimary]=>false,
670
- :indexprs=>nil,
671
- :indisvalid=>true,
672
- tab[:oid]=>oid}}.
673
- order(*order).
674
- select{[indc[:relname].as(:name), ind[:indisunique].as(:unique), att[:attname].as(:column), con[:condeferrable].as(:deferrable)]}
675
-
676
- ds = ds.where(:indpred=>nil) unless opts[:include_partial]
677
- ds = ds.where(:indisready=>true) if server_version >= 80300
678
- ds = ds.where(:indislive=>true) if server_version >= 90300
654
+ cond = {Sequel[:tab][:oid]=>regclass_oid(table, opts)}
655
+ cond[:indpred] = nil unless opts[:include_partial]
679
656
 
680
657
  indexes = {}
681
- ds.each do |r|
658
+ _indexes_ds.where_each(cond) do |r|
682
659
  i = indexes[m.call(r[:name])] ||= {:columns=>[], :unique=>r[:unique], :deferrable=>r[:deferrable]}
683
660
  i[:columns] << m.call(r[:column])
684
661
  end
@@ -711,8 +688,7 @@ module Sequel
711
688
  def primary_key(table, opts=OPTS)
712
689
  quoted_table = quote_schema_table(table)
713
690
  Sequel.synchronize{return @primary_keys[quoted_table] if @primary_keys.has_key?(quoted_table)}
714
- sql = "#{SELECT_PK_SQL} AND pg_class.oid = #{literal(regclass_oid(table, opts))}"
715
- value = fetch(sql).single_value
691
+ value = _select_pk_ds.where_single_value(Sequel[:pg_class][:oid] => regclass_oid(table, opts))
716
692
  Sequel.synchronize{@primary_keys[quoted_table] = value}
717
693
  end
718
694
 
@@ -720,24 +696,21 @@ module Sequel
720
696
  def primary_key_sequence(table, opts=OPTS)
721
697
  quoted_table = quote_schema_table(table)
722
698
  Sequel.synchronize{return @primary_key_sequences[quoted_table] if @primary_key_sequences.has_key?(quoted_table)}
723
- sql = "#{SELECT_SERIAL_SEQUENCE_SQL} AND t.oid = #{literal(regclass_oid(table, opts))}"
724
- if pks = fetch(sql).single_record
725
- value = literal(SQL::QualifiedIdentifier.new(pks[:schema], pks[:sequence]))
726
- Sequel.synchronize{@primary_key_sequences[quoted_table] = value}
727
- else
728
- sql = "#{SELECT_CUSTOM_SEQUENCE_SQL} AND t.oid = #{literal(regclass_oid(table, opts))}"
729
- if pks = fetch(sql).single_record
730
- value = literal(SQL::QualifiedIdentifier.new(pks[:schema], LiteralString.new(pks[:sequence])))
731
- Sequel.synchronize{@primary_key_sequences[quoted_table] = value}
732
- end
699
+ cond = {Sequel[:t][:oid] => regclass_oid(table, opts)}
700
+ value = if pks = _select_serial_sequence_ds.first(cond)
701
+ literal(SQL::QualifiedIdentifier.new(pks[:schema], pks[:sequence]))
702
+ elsif pks = _select_custom_sequence_ds.first(cond)
703
+ literal(SQL::QualifiedIdentifier.new(pks[:schema], LiteralString.new(pks[:sequence])))
733
704
  end
705
+
706
+ Sequel.synchronize{@primary_key_sequences[quoted_table] = value} if value
734
707
  end
735
708
 
736
709
  # Refresh the materialized view with the given name.
737
710
  #
738
711
  # DB.refresh_view(:items_view)
739
712
  # # REFRESH MATERIALIZED VIEW items_view
740
- # DB.refresh_view(:items_view, :concurrently=>true)
713
+ # DB.refresh_view(:items_view, concurrently: true)
741
714
  # # REFRESH MATERIALIZED VIEW CONCURRENTLY items_view
742
715
  def refresh_view(name, opts=OPTS)
743
716
  run "REFRESH MATERIALIZED VIEW#{' CONCURRENTLY' if opts[:concurrently]} #{quote_schema_table(name)}"
@@ -756,10 +729,12 @@ module Sequel
756
729
  seq_ds = metadata_dataset.from(:pg_sequence).where(:seqrelid=>regclass_oid(LiteralString.new(seq)))
757
730
  increment_by = :seqincrement
758
731
  min_value = :seqmin
732
+ # :nocov:
759
733
  else
760
734
  seq_ds = metadata_dataset.from(LiteralString.new(seq))
761
735
  increment_by = :increment_by
762
736
  min_value = :min_value
737
+ # :nocov:
763
738
  end
764
739
 
765
740
  get{setval(seq, db[table].select(coalesce(max(pk)+seq_ds.select(increment_by), seq_ds.select(min_value))), false)}
@@ -772,7 +747,9 @@ module Sequel
772
747
  # PostgreSQL uses SERIAL psuedo-type instead of AUTOINCREMENT for
773
748
  # managing incrementing primary keys.
774
749
  def serial_primary_key_options
750
+ # :nocov:
775
751
  auto_increment_key = server_version >= 100002 ? :identity : :serial
752
+ # :nocov:
776
753
  {:primary_key => true, auto_increment_key => true, :type=>Integer}
777
754
  end
778
755
 
@@ -865,6 +842,7 @@ module Sequel
865
842
  # DB.values([[1, 2], [3, 4]]).order(:column2).limit(1, 1)
866
843
  # # VALUES ((1, 2), (3, 4)) ORDER BY column2 LIMIT 1 OFFSET 1
867
844
  def values(v)
845
+ raise Error, "Cannot provide an empty array for values" if v.empty?
868
846
  @default_dataset.clone(:values=>v)
869
847
  end
870
848
 
@@ -883,6 +861,245 @@ module Sequel
883
861
 
884
862
  private
885
863
 
864
+ # Dataset used to retrieve CHECK constraint information
865
+ def _check_constraints_ds
866
+ @_check_constraints_ds ||= metadata_dataset.
867
+ from{pg_constraint.as(:co)}.
868
+ left_join(Sequel[:pg_attribute].as(:att), :attrelid=>:conrelid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:conkey])).
869
+ where(:contype=>'c').
870
+ select{[co[:conname].as(:constraint), att[:attname].as(:column), pg_get_constraintdef(co[:oid]).as(:definition)]}
871
+ end
872
+
873
+ # Dataset used to retrieve foreign keys referenced by a table
874
+ def _foreign_key_list_ds
875
+ @_foreign_key_list_ds ||= __foreign_key_list_ds(false)
876
+ end
877
+
878
+ # Dataset used to retrieve foreign keys referencing a table
879
+ def _reverse_foreign_key_list_ds
880
+ @_reverse_foreign_key_list_ds ||= __foreign_key_list_ds(true)
881
+ end
882
+
883
+ # Build dataset used for foreign key list methods.
884
+ def __foreign_key_list_ds(reverse)
885
+ if reverse
886
+ ctable = Sequel[:att2]
887
+ cclass = Sequel[:cl2]
888
+ rtable = Sequel[:att]
889
+ rclass = Sequel[:cl]
890
+ else
891
+ ctable = Sequel[:att]
892
+ cclass = Sequel[:cl]
893
+ rtable = Sequel[:att2]
894
+ rclass = Sequel[:cl2]
895
+ end
896
+
897
+ if server_version >= 90500
898
+ cpos = Sequel.expr{array_position(co[:conkey], ctable[:attnum])}
899
+ rpos = Sequel.expr{array_position(co[:confkey], rtable[:attnum])}
900
+ # :nocov:
901
+ else
902
+ range = 0...32
903
+ cpos = Sequel.expr{SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:conkey], [x]), x]}, 32, ctable[:attnum])}
904
+ rpos = Sequel.expr{SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:confkey], [x]), x]}, 32, rtable[:attnum])}
905
+ # :nocov:
906
+ end
907
+
908
+ ds = metadata_dataset.
909
+ from{pg_constraint.as(:co)}.
910
+ join(Sequel[:pg_class].as(cclass), :oid=>:conrelid).
911
+ join(Sequel[:pg_attribute].as(ctable), :attrelid=>:oid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:conkey])).
912
+ join(Sequel[:pg_class].as(rclass), :oid=>Sequel[:co][:confrelid]).
913
+ join(Sequel[:pg_attribute].as(rtable), :attrelid=>:oid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:confkey])).
914
+ join(Sequel[:pg_namespace].as(:nsp), :oid=>Sequel[:cl2][:relnamespace]).
915
+ order{[co[:conname], cpos]}.
916
+ where{{
917
+ cl[:relkind]=>%w'r p',
918
+ co[:contype]=>'f',
919
+ cpos=>rpos
920
+ }}.
921
+ select{[
922
+ co[:conname].as(:name),
923
+ ctable[:attname].as(:column),
924
+ co[:confupdtype].as(:on_update),
925
+ co[:confdeltype].as(:on_delete),
926
+ cl2[:relname].as(:table),
927
+ rtable[:attname].as(:refcolumn),
928
+ SQL::BooleanExpression.new(:AND, co[:condeferrable], co[:condeferred]).as(:deferrable),
929
+ nsp[:nspname].as(:schema)
930
+ ]}
931
+
932
+ if reverse
933
+ ds = ds.order_append(Sequel[:nsp][:nspname], Sequel[:cl2][:relname])
934
+ end
935
+
936
+ ds
937
+ end
938
+
939
+ # Dataset used to retrieve index information
940
+ def _indexes_ds
941
+ @_indexes_ds ||= begin
942
+ if server_version >= 90500
943
+ order = [Sequel[:indc][:relname], Sequel.function(:array_position, Sequel[:ind][:indkey], Sequel[:att][:attnum])]
944
+ # :nocov:
945
+ else
946
+ range = 0...32
947
+ order = [Sequel[:indc][:relname], SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(Sequel[:ind][:indkey], [x]), x]}, 32, Sequel[:att][:attnum])]
948
+ # :nocov:
949
+ end
950
+
951
+ attnums = SQL::Function.new(:ANY, Sequel[:ind][:indkey])
952
+
953
+ ds = metadata_dataset.
954
+ from{pg_class.as(:tab)}.
955
+ join(Sequel[:pg_index].as(:ind), :indrelid=>:oid).
956
+ join(Sequel[:pg_class].as(:indc), :oid=>:indexrelid).
957
+ join(Sequel[:pg_attribute].as(:att), :attrelid=>Sequel[:tab][:oid], :attnum=>attnums).
958
+ left_join(Sequel[:pg_constraint].as(:con), :conname=>Sequel[:indc][:relname]).
959
+ where{{
960
+ indc[:relkind]=>%w'i I',
961
+ ind[:indisprimary]=>false,
962
+ :indexprs=>nil,
963
+ :indisvalid=>true}}.
964
+ order(*order).
965
+ select{[indc[:relname].as(:name), ind[:indisunique].as(:unique), att[:attname].as(:column), con[:condeferrable].as(:deferrable)]}
966
+
967
+ # :nocov:
968
+ ds = ds.where(:indisready=>true) if server_version >= 80300
969
+ ds = ds.where(:indislive=>true) if server_version >= 90300
970
+ # :nocov:
971
+
972
+ ds
973
+ end
974
+ end
975
+
976
+ # Dataset used to determine custom serial sequences for tables
977
+ def _select_custom_sequence_ds
978
+ @_select_custom_sequence_ds ||= metadata_dataset.
979
+ from{pg_class.as(:t)}.
980
+ join(:pg_namespace, {:oid => :relnamespace}, :table_alias=>:name).
981
+ join(:pg_attribute, {:attrelid => Sequel[:t][:oid]}, :table_alias=>:attr).
982
+ join(:pg_attrdef, {:adrelid => :attrelid, :adnum => :attnum}, :table_alias=>:def).
983
+ join(:pg_constraint, {:conrelid => :adrelid, Sequel[:cons][:conkey].sql_subscript(1) => :adnum}, :table_alias=>:cons).
984
+ where{{cons[:contype] => 'p', pg_get_expr(self.def[:adbin], attr[:attrelid]) => /nextval/i}}.
985
+ select{
986
+ expr = split_part(pg_get_expr(self.def[:adbin], attr[:attrelid]), "'", 2)
987
+ [
988
+ name[:nspname].as(:schema),
989
+ Sequel.case({{expr => /./} => substr(expr, strpos(expr, '.')+1)}, expr).as(:sequence)
990
+ ]
991
+ }
992
+ end
993
+
994
+ # Dataset used to determine normal serial sequences for tables
995
+ def _select_serial_sequence_ds
996
+ @_serial_sequence_ds ||= metadata_dataset.
997
+ from{[
998
+ pg_class.as(:seq),
999
+ pg_attribute.as(:attr),
1000
+ pg_depend.as(:dep),
1001
+ pg_namespace.as(:name),
1002
+ pg_constraint.as(:cons),
1003
+ pg_class.as(:t)
1004
+ ]}.
1005
+ where{[
1006
+ [seq[:oid], dep[:objid]],
1007
+ [seq[:relnamespace], name[:oid]],
1008
+ [seq[:relkind], 'S'],
1009
+ [attr[:attrelid], dep[:refobjid]],
1010
+ [attr[:attnum], dep[:refobjsubid]],
1011
+ [attr[:attrelid], cons[:conrelid]],
1012
+ [attr[:attnum], cons[:conkey].sql_subscript(1)],
1013
+ [attr[:attrelid], t[:oid]],
1014
+ [cons[:contype], 'p']
1015
+ ]}.
1016
+ select{[
1017
+ name[:nspname].as(:schema),
1018
+ seq[:relname].as(:sequence)
1019
+ ]}
1020
+ end
1021
+
1022
+ # Dataset used to determine primary keys for tables
1023
+ def _select_pk_ds
1024
+ @_select_pk_ds ||= metadata_dataset.
1025
+ from(:pg_class, :pg_attribute, :pg_index, :pg_namespace).
1026
+ where{[
1027
+ [pg_class[:oid], pg_attribute[:attrelid]],
1028
+ [pg_class[:relnamespace], pg_namespace[:oid]],
1029
+ [pg_class[:oid], pg_index[:indrelid]],
1030
+ [pg_index[:indkey].sql_subscript(0), pg_attribute[:attnum]],
1031
+ [pg_index[:indisprimary], 't']
1032
+ ]}.
1033
+ select{pg_attribute[:attname].as(:pk)}
1034
+ end
1035
+
1036
+ # Dataset used to get schema for tables
1037
+ def _schema_ds
1038
+ @_schema_ds ||= begin
1039
+ ds = metadata_dataset.select{[
1040
+ pg_attribute[:attname].as(:name),
1041
+ SQL::Cast.new(pg_attribute[:atttypid], :integer).as(:oid),
1042
+ SQL::Cast.new(basetype[:oid], :integer).as(:base_oid),
1043
+ SQL::Function.new(:format_type, basetype[:oid], pg_type[:typtypmod]).as(:db_base_type),
1044
+ SQL::Function.new(:format_type, pg_type[:oid], pg_attribute[:atttypmod]).as(:db_type),
1045
+ SQL::Function.new(:pg_get_expr, pg_attrdef[:adbin], pg_class[:oid]).as(:default),
1046
+ SQL::BooleanExpression.new(:NOT, pg_attribute[:attnotnull]).as(:allow_null),
1047
+ SQL::Function.new(:COALESCE, SQL::BooleanExpression.from_value_pairs(pg_attribute[:attnum] => SQL::Function.new(:ANY, pg_index[:indkey])), false).as(:primary_key),
1048
+ Sequel[:pg_type][:typtype],
1049
+ (~Sequel[Sequel[:elementtype][:oid]=>nil]).as(:is_array),
1050
+ ]}.
1051
+ from(:pg_class).
1052
+ join(:pg_attribute, :attrelid=>:oid).
1053
+ join(:pg_type, :oid=>:atttypid).
1054
+ left_outer_join(Sequel[:pg_type].as(:basetype), :oid=>:typbasetype).
1055
+ left_outer_join(Sequel[:pg_type].as(:elementtype), :typarray=>Sequel[:pg_type][:oid]).
1056
+ left_outer_join(:pg_attrdef, :adrelid=>Sequel[:pg_class][:oid], :adnum=>Sequel[:pg_attribute][:attnum]).
1057
+ left_outer_join(:pg_index, :indrelid=>Sequel[:pg_class][:oid], :indisprimary=>true).
1058
+ where{{pg_attribute[:attisdropped]=>false}}.
1059
+ where{pg_attribute[:attnum] > 0}.
1060
+ order{pg_attribute[:attnum]}
1061
+
1062
+ # :nocov:
1063
+ if server_version > 100000
1064
+ # :nocov:
1065
+ ds = ds.select_append{pg_attribute[:attidentity]}
1066
+
1067
+ # :nocov:
1068
+ if server_version > 120000
1069
+ # :nocov:
1070
+ ds = ds.select_append{Sequel.~(pg_attribute[:attgenerated]=>'').as(:generated)}
1071
+ end
1072
+ end
1073
+
1074
+ ds
1075
+ end
1076
+ end
1077
+
1078
+ # Internals of defer_constraints/immediate_constraints
1079
+ def _set_constraints(type, opts)
1080
+ execute_ddl(_set_constraints_sql(type, opts), opts)
1081
+ end
1082
+
1083
+ # SQL to use for SET CONSTRAINTS
1084
+ def _set_constraints_sql(type, opts)
1085
+ sql = String.new
1086
+ sql << "SET CONSTRAINTS "
1087
+ if constraints = opts[:constraints]
1088
+ dataset.send(:source_list_append, sql, Array(constraints))
1089
+ else
1090
+ sql << "ALL"
1091
+ end
1092
+ sql << type
1093
+ end
1094
+
1095
+ # Consider lock or statement timeout errors as evidence that the table exists
1096
+ # but is locked.
1097
+ def _table_exists?(ds)
1098
+ super
1099
+ rescue DatabaseError => e
1100
+ raise e unless /canceling statement due to (?:statement|lock) timeout/ =~ e.message
1101
+ end
1102
+
886
1103
  def alter_table_add_column_sql(table, op)
887
1104
  "ADD COLUMN#{' IF NOT EXISTS' if op[:if_not_exists]} #{column_definition_sql(op)}"
888
1105
  end
@@ -1116,6 +1333,7 @@ module Sequel
1116
1333
  #{opts[:behavior].to_s.upcase if opts[:behavior]}
1117
1334
  #{'STRICT' if opts[:strict]}
1118
1335
  #{'SECURITY DEFINER' if opts[:security_definer]}
1336
+ #{"PARALLEL #{opts[:parallel].to_s.upcase}" if opts[:parallel]}
1119
1337
  #{"COST #{opts[:cost]}" if opts[:cost]}
1120
1338
  #{"ROWS #{opts[:rows]}" if opts[:rows]}
1121
1339
  #{opts[:set].map{|k,v| " SET #{k} = #{v}"}.join("\n") if opts[:set]}
@@ -1149,7 +1367,7 @@ module Sequel
1149
1367
  when :hash
1150
1368
  mod, remainder = generator.hash_values
1151
1369
  sql << " FOR VALUES WITH (MODULUS #{literal(mod)}, REMAINDER #{literal(remainder)})"
1152
- when :default
1370
+ else # when :default
1153
1371
  sql << " DEFAULT"
1154
1372
  end
1155
1373
 
@@ -1237,13 +1455,17 @@ module Sequel
1237
1455
  raise Error, "Trigger conditions are not supported for this database" unless supports_trigger_conditions?
1238
1456
  filter = " WHEN #{filter_expr(filter)}"
1239
1457
  end
1240
- "CREATE TRIGGER #{name} #{whence} #{events.map{|e| e.to_s.upcase}.join(' OR ')} ON #{quote_schema_table(table)}#{' FOR EACH ROW' if opts[:each_row]}#{filter} EXECUTE PROCEDURE #{function}(#{Array(opts[:args]).map{|a| literal(a)}.join(', ')})"
1458
+ "CREATE #{'OR REPLACE ' if opts[:replace]}TRIGGER #{name} #{whence} #{events.map{|e| e.to_s.upcase}.join(' OR ')} ON #{quote_schema_table(table)}#{' FOR EACH ROW' if opts[:each_row]}#{filter} EXECUTE PROCEDURE #{function}(#{Array(opts[:args]).map{|a| literal(a)}.join(', ')})"
1241
1459
  end
1242
1460
 
1243
1461
  # DDL fragment for initial part of CREATE VIEW statement
1244
1462
  def create_view_prefix_sql(name, options)
1245
1463
  sql = create_view_sql_append_columns("CREATE #{'OR REPLACE 'if options[:replace]}#{'TEMPORARY 'if options[:temp]}#{'RECURSIVE ' if options[:recursive]}#{'MATERIALIZED ' if options[:materialized]}VIEW #{quote_schema_table(name)}", options[:columns] || options[:recursive])
1246
1464
 
1465
+ if options[:security_invoker]
1466
+ sql += " WITH (security_invoker)"
1467
+ end
1468
+
1247
1469
  if tablespace = options[:tablespace]
1248
1470
  sql += " TABLESPACE #{quote_identifier(tablespace)}"
1249
1471
  end
@@ -1291,7 +1513,11 @@ module Sequel
1291
1513
  # currently visible schemas.
1292
1514
  def filter_schema(ds, opts)
1293
1515
  expr = if schema = opts[:schema]
1294
- schema.to_s
1516
+ if schema.is_a?(SQL::Identifier)
1517
+ schema.value.to_s
1518
+ else
1519
+ schema.to_s
1520
+ end
1295
1521
  else
1296
1522
  Sequel.function(:any, Sequel.function(:current_schemas, false))
1297
1523
  end
@@ -1301,16 +1527,20 @@ module Sequel
1301
1527
  def index_definition_sql(table_name, index)
1302
1528
  cols = index[:columns]
1303
1529
  index_name = index[:name] || default_index_name(table_name, cols)
1530
+
1304
1531
  expr = if o = index[:opclass]
1305
1532
  "(#{Array(cols).map{|c| "#{literal(c)} #{o}"}.join(', ')})"
1306
1533
  else
1307
1534
  literal(Array(cols))
1308
1535
  end
1536
+
1309
1537
  if_not_exists = " IF NOT EXISTS" if index[:if_not_exists]
1310
1538
  unique = "UNIQUE " if index[:unique]
1311
1539
  index_type = index[:type]
1312
1540
  filter = index[:where] || index[:filter]
1313
1541
  filter = " WHERE #{filter_expr(filter)}" if filter
1542
+ nulls_distinct = " NULLS#{' NOT' if index[:nulls_distinct] == false} DISTINCT" unless index[:nulls_distinct].nil?
1543
+
1314
1544
  case index_type
1315
1545
  when :full_text
1316
1546
  expr = "(to_tsvector(#{literal(index[:language] || 'simple')}::regconfig, #{literal(dataset.send(:full_text_string_join, cols))}))"
@@ -1318,7 +1548,8 @@ module Sequel
1318
1548
  when :spatial
1319
1549
  index_type = :gist
1320
1550
  end
1321
- "CREATE #{unique}INDEX#{' CONCURRENTLY' if index[:concurrently]}#{if_not_exists} #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{"USING #{index_type} " if index_type}#{expr}#{" INCLUDE #{literal(Array(index[:include]))}" if index[:include]}#{" TABLESPACE #{quote_identifier(index[:tablespace])}" if index[:tablespace]}#{filter}"
1551
+
1552
+ "CREATE #{unique}INDEX#{' CONCURRENTLY' if index[:concurrently]}#{if_not_exists} #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{"USING #{index_type} " if index_type}#{expr}#{" INCLUDE #{literal(Array(index[:include]))}" if index[:include]}#{nulls_distinct}#{" TABLESPACE #{quote_identifier(index[:tablespace])}" if index[:tablespace]}#{filter}"
1322
1553
  end
1323
1554
 
1324
1555
  # Setup datastructures shared by all postgres adapters.
@@ -1335,7 +1566,7 @@ module Sequel
1335
1566
  ds = metadata_dataset.from(:pg_class).where(:relkind=>type).select(:relname).server(opts[:server]).join(:pg_namespace, :oid=>:relnamespace)
1336
1567
  ds = filter_schema(ds, opts)
1337
1568
  m = output_identifier_meth
1338
- if block_given?
1569
+ if defined?(yield)
1339
1570
  yield(ds)
1340
1571
  elsif opts[:qualify]
1341
1572
  ds.select_append{pg_namespace[:nspname]}.map{|r| Sequel.qualify(m.call(r[:nspname]).to_s, m.call(r[:relname]).to_s)}
@@ -1344,11 +1575,6 @@ module Sequel
1344
1575
  end
1345
1576
  end
1346
1577
 
1347
- # Use a dollar sign instead of question mark for the argument placeholder.
1348
- def prepared_arg_placeholder
1349
- PREPARED_ARG_PLACEHOLDER
1350
- end
1351
-
1352
1578
  # Return an expression the oid for the table expr. Used by the metadata parsing
1353
1579
  # code to disambiguate unqualified tables.
1354
1580
  def regclass_oid(expr, opts=OPTS)
@@ -1382,11 +1608,12 @@ module Sequel
1382
1608
  end
1383
1609
 
1384
1610
  # SQL DDL statement for renaming a table. PostgreSQL doesn't allow you to change a table's schema in
1385
- # a rename table operation, so speciying a new schema in new_name will not have an effect.
1611
+ # a rename table operation, so specifying a new schema in new_name will not have an effect.
1386
1612
  def rename_table_sql(name, new_name)
1387
1613
  "ALTER TABLE #{quote_schema_table(name)} RENAME TO #{quote_identifier(schema_and_table(new_name).last)}"
1388
1614
  end
1389
1615
 
1616
+ # Handle interval and citext types.
1390
1617
  def schema_column_type(db_type)
1391
1618
  case db_type
1392
1619
  when /\Ainterval\z/io
@@ -1398,39 +1625,48 @@ module Sequel
1398
1625
  end
1399
1626
  end
1400
1627
 
1628
+ # The schema :type entry to use for array types.
1629
+ def schema_array_type(db_type)
1630
+ :array
1631
+ end
1632
+
1633
+ # The schema :type entry to use for row/composite types.
1634
+ def schema_composite_type(db_type)
1635
+ :composite
1636
+ end
1637
+
1638
+ # The schema :type entry to use for enum types.
1639
+ def schema_enum_type(db_type)
1640
+ :enum
1641
+ end
1642
+
1643
+ # The schema :type entry to use for range types.
1644
+ def schema_range_type(db_type)
1645
+ :range
1646
+ end
1647
+
1648
+ # The schema :type entry to use for multirange types.
1649
+ def schema_multirange_type(db_type)
1650
+ :multirange
1651
+ end
1652
+
1653
+ MIN_DATE = Date.new(-4713, 11, 24)
1654
+ MAX_DATE = Date.new(5874897, 12, 31)
1655
+ MIN_TIMESTAMP = Time.utc(-4713, 11, 24).freeze
1656
+ MAX_TIMESTAMP = (Time.utc(294277) - Rational(1, 1000000)).freeze
1657
+ TYPTYPE_METHOD_MAP = {
1658
+ 'c' => :schema_composite_type,
1659
+ 'e' => :schema_enum_type,
1660
+ 'r' => :schema_range_type,
1661
+ 'm' => :schema_multirange_type,
1662
+ }
1663
+ TYPTYPE_METHOD_MAP.default = :schema_column_type
1664
+ TYPTYPE_METHOD_MAP.freeze
1401
1665
  # The dataset used for parsing table schemas, using the pg_* system catalogs.
1402
1666
  def schema_parse_table(table_name, opts)
1403
1667
  m = output_identifier_meth(opts[:dataset])
1404
- oid = regclass_oid(table_name, opts)
1405
- ds = metadata_dataset.select{[
1406
- pg_attribute[:attname].as(:name),
1407
- SQL::Cast.new(pg_attribute[:atttypid], :integer).as(:oid),
1408
- SQL::Cast.new(basetype[:oid], :integer).as(:base_oid),
1409
- SQL::Function.new(:format_type, basetype[:oid], pg_type[:typtypmod]).as(:db_base_type),
1410
- SQL::Function.new(:format_type, pg_type[:oid], pg_attribute[:atttypmod]).as(:db_type),
1411
- SQL::Function.new(:pg_get_expr, pg_attrdef[:adbin], pg_class[:oid]).as(:default),
1412
- SQL::BooleanExpression.new(:NOT, pg_attribute[:attnotnull]).as(:allow_null),
1413
- SQL::Function.new(:COALESCE, SQL::BooleanExpression.from_value_pairs(pg_attribute[:attnum] => SQL::Function.new(:ANY, pg_index[:indkey])), false).as(:primary_key)]}.
1414
- from(:pg_class).
1415
- join(:pg_attribute, :attrelid=>:oid).
1416
- join(:pg_type, :oid=>:atttypid).
1417
- left_outer_join(Sequel[:pg_type].as(:basetype), :oid=>:typbasetype).
1418
- left_outer_join(:pg_attrdef, :adrelid=>Sequel[:pg_class][:oid], :adnum=>Sequel[:pg_attribute][:attnum]).
1419
- left_outer_join(:pg_index, :indrelid=>Sequel[:pg_class][:oid], :indisprimary=>true).
1420
- where{{pg_attribute[:attisdropped]=>false}}.
1421
- where{pg_attribute[:attnum] > 0}.
1422
- where{{pg_class[:oid]=>oid}}.
1423
- order{pg_attribute[:attnum]}
1424
-
1425
- if server_version > 100000
1426
- ds = ds.select_append{pg_attribute[:attidentity]}
1427
-
1428
- if server_version > 120000
1429
- ds = ds.select_append{Sequel.~(pg_attribute[:attgenerated]=>'').as(:generated)}
1430
- end
1431
- end
1432
1668
 
1433
- ds.map do |row|
1669
+ _schema_ds.where_all(Sequel[:pg_class][:oid]=>regclass_oid(table_name, opts)).map do |row|
1434
1670
  row[:default] = nil if blank_object?(row[:default])
1435
1671
  if row[:base_oid]
1436
1672
  row[:domain_oid] = row[:oid]
@@ -1441,11 +1677,33 @@ module Sequel
1441
1677
  row.delete(:base_oid)
1442
1678
  row.delete(:db_base_type)
1443
1679
  end
1444
- row[:type] = schema_column_type(row[:db_type])
1680
+
1681
+ db_type = row[:db_type]
1682
+ row[:type] = if row.delete(:is_array)
1683
+ schema_array_type(db_type)
1684
+ else
1685
+ send(TYPTYPE_METHOD_MAP[row.delete(:typtype)], db_type)
1686
+ end
1445
1687
  identity = row.delete(:attidentity)
1446
1688
  if row[:primary_key]
1447
1689
  row[:auto_increment] = !!(row[:default] =~ /\A(?:nextval)/i) || identity == 'a' || identity == 'd'
1448
1690
  end
1691
+
1692
+ # :nocov:
1693
+ if server_version >= 90600
1694
+ # :nocov:
1695
+ case row[:oid]
1696
+ when 1082
1697
+ row[:min_value] = MIN_DATE
1698
+ row[:max_value] = MAX_DATE
1699
+ when 1184, 1114
1700
+ if Sequel.datetime_class == Time
1701
+ row[:min_value] = MIN_TIMESTAMP
1702
+ row[:max_value] = MAX_TIMESTAMP
1703
+ end
1704
+ end
1705
+ end
1706
+
1449
1707
  [m.call(row.delete(:name)), row]
1450
1708
  end
1451
1709
  end
@@ -1503,9 +1761,9 @@ module Sequel
1503
1761
  if column[:text]
1504
1762
  :text
1505
1763
  elsif column[:fixed]
1506
- "char(#{column[:size]||255})"
1764
+ "char(#{column[:size]||default_string_column_size})"
1507
1765
  elsif column[:text] == false || column[:size]
1508
- "varchar(#{column[:size]||255})"
1766
+ "varchar(#{column[:size]||default_string_column_size})"
1509
1767
  else
1510
1768
  :text
1511
1769
  end
@@ -1513,7 +1771,9 @@ module Sequel
1513
1771
 
1514
1772
  # PostgreSQL 9.4+ supports views with check option.
1515
1773
  def view_with_check_option_support
1774
+ # :nocov:
1516
1775
  :local if server_version >= 90400
1776
+ # :nocov:
1517
1777
  end
1518
1778
  end
1519
1779
 
@@ -1524,7 +1784,7 @@ module Sequel
1524
1784
  LOCK_MODES = ['ACCESS SHARE', 'ROW SHARE', 'ROW EXCLUSIVE', 'SHARE UPDATE EXCLUSIVE', 'SHARE', 'SHARE ROW EXCLUSIVE', 'EXCLUSIVE', 'ACCESS EXCLUSIVE'].each(&:freeze).freeze
1525
1785
 
1526
1786
  Dataset.def_sql_method(self, :delete, [['if server_version >= 90100', %w'with delete from using where returning'], ['else', %w'delete from using where returning']])
1527
- Dataset.def_sql_method(self, :insert, [['if server_version >= 90500', %w'with insert into columns values conflict returning'], ['elsif server_version >= 90100', %w'with insert into columns values returning'], ['else', %w'insert into columns values returning']])
1787
+ Dataset.def_sql_method(self, :insert, [['if server_version >= 90500', %w'with insert into columns override values conflict returning'], ['elsif server_version >= 90100', %w'with insert into columns values returning'], ['else', %w'insert into columns values returning']])
1528
1788
  Dataset.def_sql_method(self, :select, [['if opts[:values]', %w'values order limit'], ['elsif server_version >= 80400', %w'with select distinct columns from join where group having window compounds order limit lock'], ['else', %w'select distinct columns from join where group having compounds order limit lock']])
1529
1789
  Dataset.def_sql_method(self, :update, [['if server_version >= 90100', %w'with update table set from where returning'], ['else', %w'update table set from where returning']])
1530
1790
 
@@ -1551,8 +1811,6 @@ module Sequel
1551
1811
  literal_append(sql, args[0])
1552
1812
  sql << ' ' << op.to_s << ' '
1553
1813
  literal_append(sql, args[1])
1554
- sql << " ESCAPE "
1555
- literal_append(sql, "\\")
1556
1814
  sql << ')'
1557
1815
  else
1558
1816
  super
@@ -1577,6 +1835,12 @@ module Sequel
1577
1835
  clone(:disable_insert_returning=>true)
1578
1836
  end
1579
1837
 
1838
+ # Always return false when using VALUES
1839
+ def empty?
1840
+ return false if @opts[:values]
1841
+ super
1842
+ end
1843
+
1580
1844
  # Return the results of an EXPLAIN query as a string
1581
1845
  def explain(opts=OPTS)
1582
1846
  with_sql((opts[:analyze] ? 'EXPLAIN ANALYZE ' : 'EXPLAIN ') + select_sql).map(:'QUERY PLAN').join("\r\n")
@@ -1600,7 +1864,7 @@ module Sequel
1600
1864
  # :phrase :: Similar to :plain, but also adding an ILIKE filter to ensure that
1601
1865
  # returned rows also include the exact phrase used.
1602
1866
  # :rank :: Set to true to order by the rank, so that closer matches are returned first.
1603
- # :to_tsquery :: Can be set to :plain or :phrase to specify the function to use to
1867
+ # :to_tsquery :: Can be set to :plain, :phrase, or :websearch to specify the function to use to
1604
1868
  # convert the terms to a ts_query.
1605
1869
  # :tsquery :: Specifies the terms argument is already a valid SQL expression returning a
1606
1870
  # tsquery, and can be used directly in the query.
@@ -1620,6 +1884,8 @@ module Sequel
1620
1884
  query_func = case to_tsquery = opts[:to_tsquery]
1621
1885
  when :phrase, :plain
1622
1886
  :"#{to_tsquery}to_tsquery"
1887
+ when :websearch
1888
+ :"websearch_to_tsquery"
1623
1889
  else
1624
1890
  (opts[:phrase] || opts[:plain]) ? :plainto_tsquery : :to_tsquery
1625
1891
  end
@@ -1727,13 +1993,22 @@ module Sequel
1727
1993
  ds.insert_sql(*values)
1728
1994
  end
1729
1995
 
1996
+ # Support SQL::AliasedExpression as expr to setup a USING join with a table alias for the
1997
+ # USING columns.
1998
+ def join_table(type, table, expr=nil, options=OPTS, &block)
1999
+ if expr.is_a?(SQL::AliasedExpression) && expr.expression.is_a?(Array) && !expr.expression.empty? && expr.expression.all?
2000
+ options = options.merge(:join_using=>true)
2001
+ end
2002
+ super
2003
+ end
2004
+
1730
2005
  # Locks all tables in the dataset's FROM clause (but not in JOINs) with
1731
2006
  # the specified mode (e.g. 'EXCLUSIVE'). If a block is given, starts
1732
2007
  # a new transaction, locks the table, and yields. If a block is not given,
1733
2008
  # just locks the tables. Note that PostgreSQL will probably raise an error
1734
2009
  # if you lock the table outside of an existing transaction. Returns nil.
1735
2010
  def lock(mode, opts=OPTS)
1736
- if block_given? # perform locking inside a transaction and yield to block
2011
+ if defined?(yield) # perform locking inside a transaction and yield to block
1737
2012
  @db.transaction(opts){lock(mode, opts); yield}
1738
2013
  else
1739
2014
  sql = 'LOCK TABLE '.dup
@@ -1748,6 +2023,41 @@ module Sequel
1748
2023
  nil
1749
2024
  end
1750
2025
 
2026
+ # Return a dataset with a WHEN MATCHED THEN DO NOTHING clause added to the
2027
+ # MERGE statement. If a block is passed, treat it as a virtual row and
2028
+ # use it as additional conditions for the match.
2029
+ #
2030
+ # merge_do_nothing_when_matched
2031
+ # # WHEN MATCHED THEN DO NOTHING
2032
+ #
2033
+ # merge_do_nothing_when_matched{a > 30}
2034
+ # # WHEN MATCHED AND (a > 30) THEN DO NOTHING
2035
+ def merge_do_nothing_when_matched(&block)
2036
+ _merge_when(:type=>:matched, &block)
2037
+ end
2038
+
2039
+ # Return a dataset with a WHEN NOT MATCHED THEN DO NOTHING clause added to the
2040
+ # MERGE statement. If a block is passed, treat it as a virtual row and
2041
+ # use it as additional conditions for the match.
2042
+ #
2043
+ # merge_do_nothing_when_not_matched
2044
+ # # WHEN NOT MATCHED THEN DO NOTHING
2045
+ #
2046
+ # merge_do_nothing_when_not_matched{a > 30}
2047
+ # # WHEN NOT MATCHED AND (a > 30) THEN DO NOTHING
2048
+ def merge_do_nothing_when_not_matched(&block)
2049
+ _merge_when(:type=>:not_matched, &block)
2050
+ end
2051
+
2052
+ # Support OVERRIDING USER|SYSTEM VALUE for MERGE INSERT.
2053
+ def merge_insert(*values, &block)
2054
+ h = {:type=>:insert, :values=>values}
2055
+ if override = @opts[:override]
2056
+ h[:override] = insert_override_sql(String.new)
2057
+ end
2058
+ _merge_when(h, &block)
2059
+ end
2060
+
1751
2061
  # Use OVERRIDING USER VALUE for INSERT statements, so that identity columns
1752
2062
  # always use the user supplied value, and an error is not raised for identity
1753
2063
  # columns that are GENERATED ALWAYS.
@@ -1815,6 +2125,11 @@ module Sequel
1815
2125
  true
1816
2126
  end
1817
2127
 
2128
+ # PostgreSQL 15+ supports MERGE.
2129
+ def supports_merge?
2130
+ server_version >= 150000
2131
+ end
2132
+
1818
2133
  # PostgreSQL supports NOWAIT.
1819
2134
  def supports_nowait?
1820
2135
  true
@@ -1835,10 +2150,14 @@ module Sequel
1835
2150
  server_version >= 90500
1836
2151
  end
1837
2152
 
2153
+ # :nocov:
2154
+
1838
2155
  # PostgreSQL supports timezones in literal timestamps
1839
2156
  def supports_timestamp_timezones?
2157
+ # SEQUEL6: Remove
1840
2158
  true
1841
2159
  end
2160
+ # :nocov:
1842
2161
 
1843
2162
  # PostgreSQL 8.4+ supports WINDOW clause.
1844
2163
  def supports_window_clause?
@@ -1860,6 +2179,8 @@ module Sequel
1860
2179
  server_version >= 90000
1861
2180
  when :groups, :exclude
1862
2181
  server_version >= 110000
2182
+ else
2183
+ false
1863
2184
  end
1864
2185
  end
1865
2186
 
@@ -1902,12 +2223,10 @@ module Sequel
1902
2223
  # Otherwise, return an array of hashes.
1903
2224
  def _import(columns, values, opts=OPTS)
1904
2225
  if @opts[:returning]
1905
- statements = multi_insert_sql(columns, values)
1906
- trans_opts = Hash[opts]
1907
- trans_opts[:server] = @opts[:server]
1908
- @db.transaction(trans_opts) do
1909
- statements.map{|st| returning_fetch_rows(st)}
1910
- end.first.map{|v| v.length == 1 ? v.values.first : v}
2226
+ # no transaction: our multi_insert_sql_strategy should guarantee
2227
+ # that there's only ever a single statement.
2228
+ sql = multi_insert_sql(columns, values)[0]
2229
+ returning_fetch_rows(sql).map{|v| v.length == 1 ? v.values.first : v}
1911
2230
  elsif opts[:return] == :primary_key
1912
2231
  returning(insert_pk)._import(columns, values, opts)
1913
2232
  else
@@ -1925,18 +2244,44 @@ module Sequel
1925
2244
 
1926
2245
  private
1927
2246
 
2247
+ # Append the INSERT sql used in a MERGE
2248
+ def _merge_insert_sql(sql, data)
2249
+ sql << " THEN INSERT "
2250
+ columns, values = _parse_insert_sql_args(data[:values])
2251
+ _insert_columns_sql(sql, columns)
2252
+ if override = data[:override]
2253
+ sql << override
2254
+ end
2255
+ _insert_values_sql(sql, values)
2256
+ end
2257
+
2258
+ def _merge_matched_sql(sql, data)
2259
+ sql << " THEN DO NOTHING"
2260
+ end
2261
+ alias _merge_not_matched_sql _merge_matched_sql
2262
+
1928
2263
  # Format TRUNCATE statement with PostgreSQL specific options.
1929
2264
  def _truncate_sql(table)
1930
2265
  to = @opts[:truncate_opts] || OPTS
1931
2266
  "TRUNCATE TABLE#{' ONLY' if to[:only]} #{table}#{' RESTART IDENTITY' if to[:restart]}#{' CASCADE' if to[:cascade]}"
1932
2267
  end
1933
2268
 
2269
+ # Use from_self for aggregate dataset using VALUES.
2270
+ def aggreate_dataset_use_from_self?
2271
+ super || @opts[:values]
2272
+ end
2273
+
1934
2274
  # Allow truncation of multiple source tables.
1935
2275
  def check_truncation_allowed!
1936
2276
  raise(InvalidOperation, "Grouped datasets cannot be truncated") if opts[:group]
1937
2277
  raise(InvalidOperation, "Joined datasets cannot be truncated") if opts[:join]
1938
2278
  end
1939
2279
 
2280
+ # The strftime format to use when literalizing the time.
2281
+ def default_timestamp_format
2282
+ "'%Y-%m-%d %H:%M:%S.%6N%z'"
2283
+ end
2284
+
1940
2285
  # Only include the primary table in the main delete clause
1941
2286
  def delete_from_sql(sql)
1942
2287
  sql << ' FROM '
@@ -1990,25 +2335,23 @@ module Sequel
1990
2335
 
1991
2336
  # Return the primary key to use for RETURNING in an INSERT statement
1992
2337
  def insert_pk
1993
- if (f = opts[:from]) && !f.empty?
1994
- case t = f.first
1995
- when Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier
1996
- if pk = db.primary_key(t)
1997
- Sequel::SQL::Identifier.new(pk)
1998
- end
2338
+ (f = opts[:from]) && !f.empty? && (t = f.first)
2339
+ case t
2340
+ when Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier
2341
+ if pk = db.primary_key(t)
2342
+ Sequel::SQL::Identifier.new(pk)
1999
2343
  end
2000
2344
  end
2001
2345
  end
2002
2346
 
2003
2347
  # Support OVERRIDING SYSTEM|USER VALUE in insert statements
2004
- def insert_values_sql(sql)
2348
+ def insert_override_sql(sql)
2005
2349
  case opts[:override]
2006
2350
  when :system
2007
2351
  sql << " OVERRIDING SYSTEM VALUE"
2008
2352
  when :user
2009
2353
  sql << " OVERRIDING USER VALUE"
2010
2354
  end
2011
- super
2012
2355
  end
2013
2356
 
2014
2357
  # For multiple table support, PostgreSQL requires at least
@@ -2023,6 +2366,17 @@ module Sequel
2023
2366
  end
2024
2367
  end
2025
2368
 
2369
+ # Support table aliases for USING columns
2370
+ def join_using_clause_using_sql_append(sql, using_columns)
2371
+ if using_columns.is_a?(SQL::AliasedExpression)
2372
+ super(sql, using_columns.expression)
2373
+ sql << ' AS '
2374
+ identifier_append(sql, using_columns.alias)
2375
+ else
2376
+ super
2377
+ end
2378
+ end
2379
+
2026
2380
  # Use a generic blob quoting method, hopefully overridden in one of the subadapter methods
2027
2381
  def literal_blob_append(sql, v)
2028
2382
  sql << "'" << v.gsub(/[\000-\037\047\134\177-\377]/n){|b| "\\#{("%o" % b[0..1].unpack("C")[0]).rjust(3, '0')}"} << "'"
@@ -2046,6 +2400,22 @@ module Sequel
2046
2400
  end
2047
2401
  end
2048
2402
 
2403
+ # Handle Ruby integers outside PostgreSQL bigint range specially.
2404
+ def literal_integer(v)
2405
+ if v > 9223372036854775807 || v < -9223372036854775808
2406
+ literal_integer_outside_bigint_range(v)
2407
+ else
2408
+ v.to_s
2409
+ end
2410
+ end
2411
+
2412
+ # Raise IntegerOutsideBigintRange when attempting to literalize Ruby integer
2413
+ # outside PostgreSQL bigint range, so PostgreSQL doesn't treat
2414
+ # the value as numeric.
2415
+ def literal_integer_outside_bigint_range(v)
2416
+ raise IntegerOutsideBigintRange, "attempt to literalize Ruby integer outside PostgreSQL bigint range: #{v}"
2417
+ end
2418
+
2049
2419
  # Assume that SQL standard quoting is on, per Sequel's defaults
2050
2420
  def literal_string_append(sql, v)
2051
2421
  sql << "'" << v.gsub("'", "''") << "'"
@@ -2141,6 +2511,41 @@ module Sequel
2141
2511
  opts[:with].any?{|w| w[:recursive]} ? "WITH RECURSIVE " : super
2142
2512
  end
2143
2513
 
2514
+ # Support PostgreSQL 14+ CTE SEARCH/CYCLE clauses
2515
+ def select_with_sql_cte(sql, cte)
2516
+ super
2517
+ select_with_sql_cte_search_cycle(sql, cte)
2518
+ end
2519
+
2520
+ def select_with_sql_cte_search_cycle(sql, cte)
2521
+ if search_opts = cte[:search]
2522
+ sql << if search_opts[:type] == :breadth
2523
+ " SEARCH BREADTH FIRST BY "
2524
+ else
2525
+ " SEARCH DEPTH FIRST BY "
2526
+ end
2527
+
2528
+ identifier_list_append(sql, Array(search_opts[:by]))
2529
+ sql << " SET "
2530
+ identifier_append(sql, search_opts[:set] || :ordercol)
2531
+ end
2532
+
2533
+ if cycle_opts = cte[:cycle]
2534
+ sql << " CYCLE "
2535
+ identifier_list_append(sql, Array(cycle_opts[:columns]))
2536
+ sql << " SET "
2537
+ identifier_append(sql, cycle_opts[:cycle_column] || :is_cycle)
2538
+ if cycle_opts.has_key?(:cycle_value)
2539
+ sql << " TO "
2540
+ literal_append(sql, cycle_opts[:cycle_value])
2541
+ sql << " DEFAULT "
2542
+ literal_append(sql, cycle_opts.fetch(:noncycle_value, false))
2543
+ end
2544
+ sql << " USING "
2545
+ identifier_append(sql, cycle_opts[:path_column] || :path)
2546
+ end
2547
+ end
2548
+
2144
2549
  # The version of the database server
2145
2550
  def server_version
2146
2551
  db.server_version(@opts[:server])