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
@@ -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])