sequel 5.39.0 → 5.63.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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +308 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +57 -25
  5. data/bin/sequel +11 -3
  6. data/doc/advanced_associations.rdoc +13 -13
  7. data/doc/association_basics.rdoc +89 -24
  8. data/doc/cheat_sheet.rdoc +11 -3
  9. data/doc/migration.rdoc +12 -6
  10. data/doc/model_hooks.rdoc +1 -1
  11. data/doc/object_model.rdoc +8 -8
  12. data/doc/opening_databases.rdoc +18 -11
  13. data/doc/postgresql.rdoc +16 -8
  14. data/doc/querying.rdoc +5 -3
  15. data/doc/release_notes/5.40.0.txt +40 -0
  16. data/doc/release_notes/5.41.0.txt +25 -0
  17. data/doc/release_notes/5.42.0.txt +136 -0
  18. data/doc/release_notes/5.43.0.txt +98 -0
  19. data/doc/release_notes/5.44.0.txt +32 -0
  20. data/doc/release_notes/5.45.0.txt +34 -0
  21. data/doc/release_notes/5.46.0.txt +87 -0
  22. data/doc/release_notes/5.47.0.txt +59 -0
  23. data/doc/release_notes/5.48.0.txt +14 -0
  24. data/doc/release_notes/5.49.0.txt +59 -0
  25. data/doc/release_notes/5.50.0.txt +78 -0
  26. data/doc/release_notes/5.51.0.txt +47 -0
  27. data/doc/release_notes/5.52.0.txt +87 -0
  28. data/doc/release_notes/5.53.0.txt +23 -0
  29. data/doc/release_notes/5.54.0.txt +27 -0
  30. data/doc/release_notes/5.55.0.txt +21 -0
  31. data/doc/release_notes/5.56.0.txt +51 -0
  32. data/doc/release_notes/5.57.0.txt +23 -0
  33. data/doc/release_notes/5.58.0.txt +31 -0
  34. data/doc/release_notes/5.59.0.txt +73 -0
  35. data/doc/release_notes/5.60.0.txt +22 -0
  36. data/doc/release_notes/5.61.0.txt +43 -0
  37. data/doc/release_notes/5.62.0.txt +132 -0
  38. data/doc/release_notes/5.63.0.txt +33 -0
  39. data/doc/schema_modification.rdoc +1 -1
  40. data/doc/security.rdoc +9 -9
  41. data/doc/sql.rdoc +27 -15
  42. data/doc/testing.rdoc +22 -11
  43. data/doc/transactions.rdoc +6 -6
  44. data/doc/virtual_rows.rdoc +2 -2
  45. data/lib/sequel/adapters/ado/access.rb +1 -1
  46. data/lib/sequel/adapters/ado.rb +17 -17
  47. data/lib/sequel/adapters/amalgalite.rb +3 -5
  48. data/lib/sequel/adapters/ibmdb.rb +2 -2
  49. data/lib/sequel/adapters/jdbc/derby.rb +8 -0
  50. data/lib/sequel/adapters/jdbc/h2.rb +60 -10
  51. data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
  52. data/lib/sequel/adapters/jdbc/postgresql.rb +4 -4
  53. data/lib/sequel/adapters/jdbc.rb +16 -18
  54. data/lib/sequel/adapters/mysql.rb +80 -67
  55. data/lib/sequel/adapters/mysql2.rb +54 -49
  56. data/lib/sequel/adapters/odbc.rb +6 -2
  57. data/lib/sequel/adapters/oracle.rb +3 -3
  58. data/lib/sequel/adapters/postgres.rb +83 -40
  59. data/lib/sequel/adapters/shared/access.rb +11 -1
  60. data/lib/sequel/adapters/shared/db2.rb +30 -0
  61. data/lib/sequel/adapters/shared/mssql.rb +58 -7
  62. data/lib/sequel/adapters/shared/mysql.rb +40 -2
  63. data/lib/sequel/adapters/shared/oracle.rb +76 -0
  64. data/lib/sequel/adapters/shared/postgres.rb +418 -174
  65. data/lib/sequel/adapters/shared/sqlanywhere.rb +10 -0
  66. data/lib/sequel/adapters/shared/sqlite.rb +102 -11
  67. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  68. data/lib/sequel/adapters/sqlite.rb +60 -18
  69. data/lib/sequel/adapters/tinytds.rb +1 -1
  70. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  71. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  72. data/lib/sequel/ast_transformer.rb +6 -0
  73. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  74. data/lib/sequel/connection_pool/sharded_threaded.rb +5 -1
  75. data/lib/sequel/connection_pool/single.rb +6 -8
  76. data/lib/sequel/connection_pool/threaded.rb +8 -8
  77. data/lib/sequel/connection_pool/timed_queue.rb +257 -0
  78. data/lib/sequel/connection_pool.rb +47 -30
  79. data/lib/sequel/core.rb +28 -18
  80. data/lib/sequel/database/connecting.rb +26 -2
  81. data/lib/sequel/database/misc.rb +69 -14
  82. data/lib/sequel/database/query.rb +38 -1
  83. data/lib/sequel/database/schema_generator.rb +45 -52
  84. data/lib/sequel/database/schema_methods.rb +17 -1
  85. data/lib/sequel/dataset/actions.rb +107 -13
  86. data/lib/sequel/dataset/features.rb +20 -0
  87. data/lib/sequel/dataset/misc.rb +1 -1
  88. data/lib/sequel/dataset/prepared_statements.rb +2 -0
  89. data/lib/sequel/dataset/query.rb +118 -16
  90. data/lib/sequel/dataset/sql.rb +177 -47
  91. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  92. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  93. data/lib/sequel/extensions/any_not_empty.rb +1 -1
  94. data/lib/sequel/extensions/async_thread_pool.rb +438 -0
  95. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  96. data/lib/sequel/extensions/blank.rb +8 -0
  97. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  98. data/lib/sequel/extensions/core_refinements.rb +36 -11
  99. data/lib/sequel/extensions/date_arithmetic.rb +71 -31
  100. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  101. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  102. data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
  103. data/lib/sequel/extensions/eval_inspect.rb +2 -0
  104. data/lib/sequel/extensions/inflector.rb +9 -1
  105. data/lib/sequel/extensions/is_distinct_from.rb +141 -0
  106. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  107. data/lib/sequel/extensions/migration.rb +7 -2
  108. data/lib/sequel/extensions/named_timezones.rb +26 -6
  109. data/lib/sequel/extensions/pagination.rb +1 -1
  110. data/lib/sequel/extensions/pg_array.rb +23 -3
  111. data/lib/sequel/extensions/pg_array_ops.rb +2 -2
  112. data/lib/sequel/extensions/pg_auto_parameterize.rb +478 -0
  113. data/lib/sequel/extensions/pg_enum.rb +1 -1
  114. data/lib/sequel/extensions/pg_extended_date_support.rb +28 -25
  115. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  116. data/lib/sequel/extensions/pg_hstore.rb +6 -1
  117. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  118. data/lib/sequel/extensions/pg_inet.rb +10 -11
  119. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  120. data/lib/sequel/extensions/pg_interval.rb +45 -19
  121. data/lib/sequel/extensions/pg_json.rb +13 -15
  122. data/lib/sequel/extensions/pg_json_ops.rb +73 -2
  123. data/lib/sequel/extensions/pg_loose_count.rb +3 -1
  124. data/lib/sequel/extensions/pg_multirange.rb +367 -0
  125. data/lib/sequel/extensions/pg_range.rb +10 -23
  126. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  127. data/lib/sequel/extensions/pg_row.rb +19 -13
  128. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  129. data/lib/sequel/extensions/query.rb +2 -0
  130. data/lib/sequel/extensions/s.rb +2 -1
  131. data/lib/sequel/extensions/schema_dumper.rb +13 -2
  132. data/lib/sequel/extensions/server_block.rb +8 -12
  133. data/lib/sequel/extensions/sql_comments.rb +110 -3
  134. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  135. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  136. data/lib/sequel/extensions/string_agg.rb +1 -1
  137. data/lib/sequel/extensions/string_date_time.rb +19 -23
  138. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  139. data/lib/sequel/model/associations.rb +325 -96
  140. data/lib/sequel/model/base.rb +51 -27
  141. data/lib/sequel/model/errors.rb +10 -1
  142. data/lib/sequel/model/inflections.rb +1 -1
  143. data/lib/sequel/model/plugins.rb +5 -0
  144. data/lib/sequel/plugins/association_proxies.rb +2 -0
  145. data/lib/sequel/plugins/async_thread_pool.rb +39 -0
  146. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  147. data/lib/sequel/plugins/auto_validations.rb +87 -15
  148. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  149. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  150. data/lib/sequel/plugins/column_encryption.rb +728 -0
  151. data/lib/sequel/plugins/composition.rb +10 -4
  152. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  153. data/lib/sequel/plugins/constraint_validations.rb +2 -1
  154. data/lib/sequel/plugins/dataset_associations.rb +4 -1
  155. data/lib/sequel/plugins/dirty.rb +1 -1
  156. data/lib/sequel/plugins/enum.rb +124 -0
  157. data/lib/sequel/plugins/finder.rb +3 -1
  158. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  159. data/lib/sequel/plugins/instance_specific_default.rb +1 -1
  160. data/lib/sequel/plugins/json_serializer.rb +39 -24
  161. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  162. data/lib/sequel/plugins/list.rb +3 -1
  163. data/lib/sequel/plugins/many_through_many.rb +108 -9
  164. data/lib/sequel/plugins/nested_attributes.rb +12 -7
  165. data/lib/sequel/plugins/pg_array_associations.rb +56 -38
  166. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +3 -1
  167. data/lib/sequel/plugins/prepared_statements.rb +10 -1
  168. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  169. data/lib/sequel/plugins/rcte_tree.rb +27 -19
  170. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  171. data/lib/sequel/plugins/serialization.rb +9 -3
  172. data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
  173. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  174. data/lib/sequel/plugins/sql_comments.rb +189 -0
  175. data/lib/sequel/plugins/static_cache.rb +1 -1
  176. data/lib/sequel/plugins/subclasses.rb +28 -11
  177. data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
  178. data/lib/sequel/plugins/timestamps.rb +1 -1
  179. data/lib/sequel/plugins/unused_associations.rb +521 -0
  180. data/lib/sequel/plugins/update_or_create.rb +1 -1
  181. data/lib/sequel/plugins/validate_associated.rb +22 -12
  182. data/lib/sequel/plugins/validation_helpers.rb +38 -11
  183. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  184. data/lib/sequel/sql.rb +1 -1
  185. data/lib/sequel/timezones.rb +12 -14
  186. data/lib/sequel/version.rb +1 -1
  187. metadata +97 -43
@@ -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)
@@ -549,63 +559,12 @@ module Sequel
549
559
  def foreign_key_list(table, opts=OPTS)
550
560
  m = output_identifier_meth
551
561
  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
562
 
605
563
  h = {}
606
564
  fklod_map = FOREIGN_KEY_LIST_ON_DELETE_MAP
565
+ reverse = opts[:reverse]
607
566
 
608
- ds.each do |row|
567
+ (reverse ? _reverse_foreign_key_list_ds : _foreign_key_list_ds).where_each(Sequel[:cl][:oid]=>regclass_oid(table)) do |row|
609
568
  if reverse
610
569
  key = [row[:schema], row[:table], row[:name]]
611
570
  else
@@ -640,6 +599,14 @@ module Sequel
640
599
  def freeze
641
600
  server_version
642
601
  supports_prepared_transactions?
602
+ _schema_ds
603
+ _select_serial_sequence_ds
604
+ _select_custom_sequence_ds
605
+ _select_pk_ds
606
+ _indexes_ds
607
+ _check_constraints_ds
608
+ _foreign_key_list_ds
609
+ _reverse_foreign_key_list_ds
643
610
  @conversion_procs.freeze
644
611
  super
645
612
  end
@@ -647,38 +614,11 @@ module Sequel
647
614
  # Use the pg_* system tables to determine indexes on a table
648
615
  def indexes(table, opts=OPTS)
649
616
  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
617
+ cond = {Sequel[:tab][:oid]=>regclass_oid(table, opts)}
618
+ cond[:indpred] = nil unless opts[:include_partial]
679
619
 
680
620
  indexes = {}
681
- ds.each do |r|
621
+ _indexes_ds.where_each(cond) do |r|
682
622
  i = indexes[m.call(r[:name])] ||= {:columns=>[], :unique=>r[:unique], :deferrable=>r[:deferrable]}
683
623
  i[:columns] << m.call(r[:column])
684
624
  end
@@ -711,8 +651,7 @@ module Sequel
711
651
  def primary_key(table, opts=OPTS)
712
652
  quoted_table = quote_schema_table(table)
713
653
  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
654
+ value = _select_pk_ds.where_single_value(Sequel[:pg_class][:oid] => regclass_oid(table, opts))
716
655
  Sequel.synchronize{@primary_keys[quoted_table] = value}
717
656
  end
718
657
 
@@ -720,24 +659,21 @@ module Sequel
720
659
  def primary_key_sequence(table, opts=OPTS)
721
660
  quoted_table = quote_schema_table(table)
722
661
  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
662
+ cond = {Sequel[:t][:oid] => regclass_oid(table, opts)}
663
+ value = if pks = _select_serial_sequence_ds.first(cond)
664
+ literal(SQL::QualifiedIdentifier.new(pks[:schema], pks[:sequence]))
665
+ elsif pks = _select_custom_sequence_ds.first(cond)
666
+ literal(SQL::QualifiedIdentifier.new(pks[:schema], LiteralString.new(pks[:sequence])))
733
667
  end
668
+
669
+ Sequel.synchronize{@primary_key_sequences[quoted_table] = value} if value
734
670
  end
735
671
 
736
672
  # Refresh the materialized view with the given name.
737
673
  #
738
674
  # DB.refresh_view(:items_view)
739
675
  # # REFRESH MATERIALIZED VIEW items_view
740
- # DB.refresh_view(:items_view, :concurrently=>true)
676
+ # DB.refresh_view(:items_view, concurrently: true)
741
677
  # # REFRESH MATERIALIZED VIEW CONCURRENTLY items_view
742
678
  def refresh_view(name, opts=OPTS)
743
679
  run "REFRESH MATERIALIZED VIEW#{' CONCURRENTLY' if opts[:concurrently]} #{quote_schema_table(name)}"
@@ -756,10 +692,12 @@ module Sequel
756
692
  seq_ds = metadata_dataset.from(:pg_sequence).where(:seqrelid=>regclass_oid(LiteralString.new(seq)))
757
693
  increment_by = :seqincrement
758
694
  min_value = :seqmin
695
+ # :nocov:
759
696
  else
760
697
  seq_ds = metadata_dataset.from(LiteralString.new(seq))
761
698
  increment_by = :increment_by
762
699
  min_value = :min_value
700
+ # :nocov:
763
701
  end
764
702
 
765
703
  get{setval(seq, db[table].select(coalesce(max(pk)+seq_ds.select(increment_by), seq_ds.select(min_value))), false)}
@@ -772,7 +710,9 @@ module Sequel
772
710
  # PostgreSQL uses SERIAL psuedo-type instead of AUTOINCREMENT for
773
711
  # managing incrementing primary keys.
774
712
  def serial_primary_key_options
713
+ # :nocov:
775
714
  auto_increment_key = server_version >= 100002 ? :identity : :serial
715
+ # :nocov:
776
716
  {:primary_key => true, auto_increment_key => true, :type=>Integer}
777
717
  end
778
718
 
@@ -883,6 +823,216 @@ module Sequel
883
823
 
884
824
  private
885
825
 
826
+ # Dataset used to retrieve CHECK constraint information
827
+ def _check_constraints_ds
828
+ @_check_constraints_ds ||= metadata_dataset.
829
+ from{pg_constraint.as(:co)}.
830
+ left_join(Sequel[:pg_attribute].as(:att), :attrelid=>:conrelid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:conkey])).
831
+ where(:contype=>'c').
832
+ select{[co[:conname].as(:constraint), att[:attname].as(:column), pg_get_constraintdef(co[:oid]).as(:definition)]}
833
+ end
834
+
835
+ # Dataset used to retrieve foreign keys referenced by a table
836
+ def _foreign_key_list_ds
837
+ @_foreign_key_list_ds ||= __foreign_key_list_ds(false)
838
+ end
839
+
840
+ # Dataset used to retrieve foreign keys referencing a table
841
+ def _reverse_foreign_key_list_ds
842
+ @_reverse_foreign_key_list_ds ||= __foreign_key_list_ds(true)
843
+ end
844
+
845
+ # Build dataset used for foreign key list methods.
846
+ def __foreign_key_list_ds(reverse)
847
+ if reverse
848
+ ctable = Sequel[:att2]
849
+ cclass = Sequel[:cl2]
850
+ rtable = Sequel[:att]
851
+ rclass = Sequel[:cl]
852
+ else
853
+ ctable = Sequel[:att]
854
+ cclass = Sequel[:cl]
855
+ rtable = Sequel[:att2]
856
+ rclass = Sequel[:cl2]
857
+ end
858
+
859
+ if server_version >= 90500
860
+ cpos = Sequel.expr{array_position(co[:conkey], ctable[:attnum])}
861
+ rpos = Sequel.expr{array_position(co[:confkey], rtable[:attnum])}
862
+ # :nocov:
863
+ else
864
+ range = 0...32
865
+ cpos = Sequel.expr{SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:conkey], [x]), x]}, 32, ctable[:attnum])}
866
+ rpos = Sequel.expr{SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:confkey], [x]), x]}, 32, rtable[:attnum])}
867
+ # :nocov:
868
+ end
869
+
870
+ ds = metadata_dataset.
871
+ from{pg_constraint.as(:co)}.
872
+ join(Sequel[:pg_class].as(cclass), :oid=>:conrelid).
873
+ join(Sequel[:pg_attribute].as(ctable), :attrelid=>:oid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:conkey])).
874
+ join(Sequel[:pg_class].as(rclass), :oid=>Sequel[:co][:confrelid]).
875
+ join(Sequel[:pg_attribute].as(rtable), :attrelid=>:oid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:confkey])).
876
+ join(Sequel[:pg_namespace].as(:nsp), :oid=>Sequel[:cl2][:relnamespace]).
877
+ order{[co[:conname], cpos]}.
878
+ where{{
879
+ cl[:relkind]=>%w'r p',
880
+ co[:contype]=>'f',
881
+ cpos=>rpos
882
+ }}.
883
+ select{[
884
+ co[:conname].as(:name),
885
+ ctable[:attname].as(:column),
886
+ co[:confupdtype].as(:on_update),
887
+ co[:confdeltype].as(:on_delete),
888
+ cl2[:relname].as(:table),
889
+ rtable[:attname].as(:refcolumn),
890
+ SQL::BooleanExpression.new(:AND, co[:condeferrable], co[:condeferred]).as(:deferrable),
891
+ nsp[:nspname].as(:schema)
892
+ ]}
893
+
894
+ if reverse
895
+ ds = ds.order_append(Sequel[:nsp][:nspname], Sequel[:cl2][:relname])
896
+ end
897
+
898
+ ds
899
+ end
900
+
901
+ # Dataset used to retrieve index information
902
+ def _indexes_ds
903
+ @_indexes_ds ||= begin
904
+ if server_version >= 90500
905
+ order = [Sequel[:indc][:relname], Sequel.function(:array_position, Sequel[:ind][:indkey], Sequel[:att][:attnum])]
906
+ # :nocov:
907
+ else
908
+ range = 0...32
909
+ order = [Sequel[:indc][:relname], SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(Sequel[:ind][:indkey], [x]), x]}, 32, Sequel[:att][:attnum])]
910
+ # :nocov:
911
+ end
912
+
913
+ attnums = SQL::Function.new(:ANY, Sequel[:ind][:indkey])
914
+
915
+ ds = metadata_dataset.
916
+ from{pg_class.as(:tab)}.
917
+ join(Sequel[:pg_index].as(:ind), :indrelid=>:oid).
918
+ join(Sequel[:pg_class].as(:indc), :oid=>:indexrelid).
919
+ join(Sequel[:pg_attribute].as(:att), :attrelid=>Sequel[:tab][:oid], :attnum=>attnums).
920
+ left_join(Sequel[:pg_constraint].as(:con), :conname=>Sequel[:indc][:relname]).
921
+ where{{
922
+ indc[:relkind]=>'i',
923
+ ind[:indisprimary]=>false,
924
+ :indexprs=>nil,
925
+ :indisvalid=>true}}.
926
+ order(*order).
927
+ select{[indc[:relname].as(:name), ind[:indisunique].as(:unique), att[:attname].as(:column), con[:condeferrable].as(:deferrable)]}
928
+
929
+ # :nocov:
930
+ ds = ds.where(:indisready=>true) if server_version >= 80300
931
+ ds = ds.where(:indislive=>true) if server_version >= 90300
932
+ # :nocov:
933
+
934
+ ds
935
+ end
936
+ end
937
+
938
+ # Dataset used to determine custom serial sequences for tables
939
+ def _select_custom_sequence_ds
940
+ @_select_custom_sequence_ds ||= metadata_dataset.
941
+ from{pg_class.as(:t)}.
942
+ join(:pg_namespace, {:oid => :relnamespace}, :table_alias=>:name).
943
+ join(:pg_attribute, {:attrelid => Sequel[:t][:oid]}, :table_alias=>:attr).
944
+ join(:pg_attrdef, {:adrelid => :attrelid, :adnum => :attnum}, :table_alias=>:def).
945
+ join(:pg_constraint, {:conrelid => :adrelid, Sequel[:cons][:conkey].sql_subscript(1) => :adnum}, :table_alias=>:cons).
946
+ where{{cons[:contype] => 'p', pg_get_expr(self.def[:adbin], attr[:attrelid]) => /nextval/i}}.
947
+ select{
948
+ expr = split_part(pg_get_expr(self.def[:adbin], attr[:attrelid]), "'", 2)
949
+ [
950
+ name[:nspname].as(:schema),
951
+ Sequel.case({{expr => /./} => substr(expr, strpos(expr, '.')+1)}, expr).as(:sequence)
952
+ ]
953
+ }
954
+ end
955
+
956
+ # Dataset used to determine normal serial sequences for tables
957
+ def _select_serial_sequence_ds
958
+ @_serial_sequence_ds ||= metadata_dataset.
959
+ from{[
960
+ pg_class.as(:seq),
961
+ pg_attribute.as(:attr),
962
+ pg_depend.as(:dep),
963
+ pg_namespace.as(:name),
964
+ pg_constraint.as(:cons),
965
+ pg_class.as(:t)
966
+ ]}.
967
+ where{[
968
+ [seq[:oid], dep[:objid]],
969
+ [seq[:relnamespace], name[:oid]],
970
+ [seq[:relkind], 'S'],
971
+ [attr[:attrelid], dep[:refobjid]],
972
+ [attr[:attnum], dep[:refobjsubid]],
973
+ [attr[:attrelid], cons[:conrelid]],
974
+ [attr[:attnum], cons[:conkey].sql_subscript(1)],
975
+ [attr[:attrelid], t[:oid]],
976
+ [cons[:contype], 'p']
977
+ ]}.
978
+ select{[
979
+ name[:nspname].as(:schema),
980
+ seq[:relname].as(:sequence)
981
+ ]}
982
+ end
983
+
984
+ # Dataset used to determine primary keys for tables
985
+ def _select_pk_ds
986
+ @_select_pk_ds ||= metadata_dataset.
987
+ from(:pg_class, :pg_attribute, :pg_index, :pg_namespace).
988
+ where{[
989
+ [pg_class[:oid], pg_attribute[:attrelid]],
990
+ [pg_class[:relnamespace], pg_namespace[:oid]],
991
+ [pg_class[:oid], pg_index[:indrelid]],
992
+ [pg_index[:indkey].sql_subscript(0), pg_attribute[:attnum]],
993
+ [pg_index[:indisprimary], 't']
994
+ ]}.
995
+ select{pg_attribute[:attname].as(:pk)}
996
+ end
997
+
998
+ # Dataset used to get schema for tables
999
+ def _schema_ds
1000
+ @_schema_ds ||= begin
1001
+ ds = metadata_dataset.select{[
1002
+ pg_attribute[:attname].as(:name),
1003
+ SQL::Cast.new(pg_attribute[:atttypid], :integer).as(:oid),
1004
+ SQL::Cast.new(basetype[:oid], :integer).as(:base_oid),
1005
+ SQL::Function.new(:format_type, basetype[:oid], pg_type[:typtypmod]).as(:db_base_type),
1006
+ SQL::Function.new(:format_type, pg_type[:oid], pg_attribute[:atttypmod]).as(:db_type),
1007
+ SQL::Function.new(:pg_get_expr, pg_attrdef[:adbin], pg_class[:oid]).as(:default),
1008
+ SQL::BooleanExpression.new(:NOT, pg_attribute[:attnotnull]).as(:allow_null),
1009
+ SQL::Function.new(:COALESCE, SQL::BooleanExpression.from_value_pairs(pg_attribute[:attnum] => SQL::Function.new(:ANY, pg_index[:indkey])), false).as(:primary_key)]}.
1010
+ from(:pg_class).
1011
+ join(:pg_attribute, :attrelid=>:oid).
1012
+ join(:pg_type, :oid=>:atttypid).
1013
+ left_outer_join(Sequel[:pg_type].as(:basetype), :oid=>:typbasetype).
1014
+ left_outer_join(:pg_attrdef, :adrelid=>Sequel[:pg_class][:oid], :adnum=>Sequel[:pg_attribute][:attnum]).
1015
+ left_outer_join(:pg_index, :indrelid=>Sequel[:pg_class][:oid], :indisprimary=>true).
1016
+ where{{pg_attribute[:attisdropped]=>false}}.
1017
+ where{pg_attribute[:attnum] > 0}.
1018
+ order{pg_attribute[:attnum]}
1019
+
1020
+ # :nocov:
1021
+ if server_version > 100000
1022
+ # :nocov:
1023
+ ds = ds.select_append{pg_attribute[:attidentity]}
1024
+
1025
+ # :nocov:
1026
+ if server_version > 120000
1027
+ # :nocov:
1028
+ ds = ds.select_append{Sequel.~(pg_attribute[:attgenerated]=>'').as(:generated)}
1029
+ end
1030
+ end
1031
+
1032
+ ds
1033
+ end
1034
+ end
1035
+
886
1036
  def alter_table_add_column_sql(table, op)
887
1037
  "ADD COLUMN#{' IF NOT EXISTS' if op[:if_not_exists]} #{column_definition_sql(op)}"
888
1038
  end
@@ -1116,6 +1266,7 @@ module Sequel
1116
1266
  #{opts[:behavior].to_s.upcase if opts[:behavior]}
1117
1267
  #{'STRICT' if opts[:strict]}
1118
1268
  #{'SECURITY DEFINER' if opts[:security_definer]}
1269
+ #{"PARALLEL #{opts[:parallel].to_s.upcase}" if opts[:parallel]}
1119
1270
  #{"COST #{opts[:cost]}" if opts[:cost]}
1120
1271
  #{"ROWS #{opts[:rows]}" if opts[:rows]}
1121
1272
  #{opts[:set].map{|k,v| " SET #{k} = #{v}"}.join("\n") if opts[:set]}
@@ -1149,7 +1300,7 @@ module Sequel
1149
1300
  when :hash
1150
1301
  mod, remainder = generator.hash_values
1151
1302
  sql << " FOR VALUES WITH (MODULUS #{literal(mod)}, REMAINDER #{literal(remainder)})"
1152
- when :default
1303
+ else # when :default
1153
1304
  sql << " DEFAULT"
1154
1305
  end
1155
1306
 
@@ -1237,13 +1388,17 @@ module Sequel
1237
1388
  raise Error, "Trigger conditions are not supported for this database" unless supports_trigger_conditions?
1238
1389
  filter = " WHEN #{filter_expr(filter)}"
1239
1390
  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(', ')})"
1391
+ "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
1392
  end
1242
1393
 
1243
1394
  # DDL fragment for initial part of CREATE VIEW statement
1244
1395
  def create_view_prefix_sql(name, options)
1245
1396
  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
1397
 
1398
+ if options[:security_invoker]
1399
+ sql += " WITH (security_invoker)"
1400
+ end
1401
+
1247
1402
  if tablespace = options[:tablespace]
1248
1403
  sql += " TABLESPACE #{quote_identifier(tablespace)}"
1249
1404
  end
@@ -1301,16 +1456,20 @@ module Sequel
1301
1456
  def index_definition_sql(table_name, index)
1302
1457
  cols = index[:columns]
1303
1458
  index_name = index[:name] || default_index_name(table_name, cols)
1459
+
1304
1460
  expr = if o = index[:opclass]
1305
1461
  "(#{Array(cols).map{|c| "#{literal(c)} #{o}"}.join(', ')})"
1306
1462
  else
1307
1463
  literal(Array(cols))
1308
1464
  end
1465
+
1309
1466
  if_not_exists = " IF NOT EXISTS" if index[:if_not_exists]
1310
1467
  unique = "UNIQUE " if index[:unique]
1311
1468
  index_type = index[:type]
1312
1469
  filter = index[:where] || index[:filter]
1313
1470
  filter = " WHERE #{filter_expr(filter)}" if filter
1471
+ nulls_distinct = " NULLS#{' NOT' if index[:nulls_distinct] == false} DISTINCT" unless index[:nulls_distinct].nil?
1472
+
1314
1473
  case index_type
1315
1474
  when :full_text
1316
1475
  expr = "(to_tsvector(#{literal(index[:language] || 'simple')}::regconfig, #{literal(dataset.send(:full_text_string_join, cols))}))"
@@ -1318,7 +1477,8 @@ module Sequel
1318
1477
  when :spatial
1319
1478
  index_type = :gist
1320
1479
  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}"
1480
+
1481
+ "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
1482
  end
1323
1483
 
1324
1484
  # Setup datastructures shared by all postgres adapters.
@@ -1335,7 +1495,7 @@ module Sequel
1335
1495
  ds = metadata_dataset.from(:pg_class).where(:relkind=>type).select(:relname).server(opts[:server]).join(:pg_namespace, :oid=>:relnamespace)
1336
1496
  ds = filter_schema(ds, opts)
1337
1497
  m = output_identifier_meth
1338
- if block_given?
1498
+ if defined?(yield)
1339
1499
  yield(ds)
1340
1500
  elsif opts[:qualify]
1341
1501
  ds.select_append{pg_namespace[:nspname]}.map{|r| Sequel.qualify(m.call(r[:nspname]).to_s, m.call(r[:relname]).to_s)}
@@ -1344,11 +1504,6 @@ module Sequel
1344
1504
  end
1345
1505
  end
1346
1506
 
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
1507
  # Return an expression the oid for the table expr. Used by the metadata parsing
1353
1508
  # code to disambiguate unqualified tables.
1354
1509
  def regclass_oid(expr, opts=OPTS)
@@ -1401,36 +1556,8 @@ module Sequel
1401
1556
  # The dataset used for parsing table schemas, using the pg_* system catalogs.
1402
1557
  def schema_parse_table(table_name, opts)
1403
1558
  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
1559
 
1433
- ds.map do |row|
1560
+ _schema_ds.where_all(Sequel[:pg_class][:oid]=>regclass_oid(table_name, opts)).map do |row|
1434
1561
  row[:default] = nil if blank_object?(row[:default])
1435
1562
  if row[:base_oid]
1436
1563
  row[:domain_oid] = row[:oid]
@@ -1500,10 +1627,12 @@ module Sequel
1500
1627
  # disallowed or there is a size specified, use the varchar type.
1501
1628
  # Otherwise use the text type.
1502
1629
  def type_literal_generic_string(column)
1503
- if column[:fixed]
1504
- "char(#{column[:size]||255})"
1505
- elsif column[:text] == false or column[:size]
1506
- "varchar(#{column[:size]||255})"
1630
+ if column[:text]
1631
+ :text
1632
+ elsif column[:fixed]
1633
+ "char(#{column[:size]||default_string_column_size})"
1634
+ elsif column[:text] == false || column[:size]
1635
+ "varchar(#{column[:size]||default_string_column_size})"
1507
1636
  else
1508
1637
  :text
1509
1638
  end
@@ -1511,7 +1640,9 @@ module Sequel
1511
1640
 
1512
1641
  # PostgreSQL 9.4+ supports views with check option.
1513
1642
  def view_with_check_option_support
1643
+ # :nocov:
1514
1644
  :local if server_version >= 90400
1645
+ # :nocov:
1515
1646
  end
1516
1647
  end
1517
1648
 
@@ -1522,7 +1653,7 @@ module Sequel
1522
1653
  LOCK_MODES = ['ACCESS SHARE', 'ROW SHARE', 'ROW EXCLUSIVE', 'SHARE UPDATE EXCLUSIVE', 'SHARE', 'SHARE ROW EXCLUSIVE', 'EXCLUSIVE', 'ACCESS EXCLUSIVE'].each(&:freeze).freeze
1523
1654
 
1524
1655
  Dataset.def_sql_method(self, :delete, [['if server_version >= 90100', %w'with delete from using where returning'], ['else', %w'delete from using where returning']])
1525
- 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']])
1656
+ 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']])
1526
1657
  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']])
1527
1658
  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']])
1528
1659
 
@@ -1725,13 +1856,22 @@ module Sequel
1725
1856
  ds.insert_sql(*values)
1726
1857
  end
1727
1858
 
1859
+ # Support SQL::AliasedExpression as expr to setup a USING join with a table alias for the
1860
+ # USING columns.
1861
+ def join_table(type, table, expr=nil, options=OPTS, &block)
1862
+ if expr.is_a?(SQL::AliasedExpression) && expr.expression.is_a?(Array) && !expr.expression.empty? && expr.expression.all?
1863
+ options = options.merge(:join_using=>true)
1864
+ end
1865
+ super
1866
+ end
1867
+
1728
1868
  # Locks all tables in the dataset's FROM clause (but not in JOINs) with
1729
1869
  # the specified mode (e.g. 'EXCLUSIVE'). If a block is given, starts
1730
1870
  # a new transaction, locks the table, and yields. If a block is not given,
1731
1871
  # just locks the tables. Note that PostgreSQL will probably raise an error
1732
1872
  # if you lock the table outside of an existing transaction. Returns nil.
1733
1873
  def lock(mode, opts=OPTS)
1734
- if block_given? # perform locking inside a transaction and yield to block
1874
+ if defined?(yield) # perform locking inside a transaction and yield to block
1735
1875
  @db.transaction(opts){lock(mode, opts); yield}
1736
1876
  else
1737
1877
  sql = 'LOCK TABLE '.dup
@@ -1746,6 +1886,41 @@ module Sequel
1746
1886
  nil
1747
1887
  end
1748
1888
 
1889
+ # Return a dataset with a WHEN MATCHED THEN DO NOTHING clause added to the
1890
+ # MERGE statement. If a block is passed, treat it as a virtual row and
1891
+ # use it as additional conditions for the match.
1892
+ #
1893
+ # merge_do_nothing_when_matched
1894
+ # # WHEN MATCHED THEN DO NOTHING
1895
+ #
1896
+ # merge_do_nothing_when_matched{a > 30}
1897
+ # # WHEN MATCHED AND (a > 30) THEN DO NOTHING
1898
+ def merge_do_nothing_when_matched(&block)
1899
+ _merge_when(:type=>:matched, &block)
1900
+ end
1901
+
1902
+ # Return a dataset with a WHEN NOT MATCHED THEN DO NOTHING clause added to the
1903
+ # MERGE statement. If a block is passed, treat it as a virtual row and
1904
+ # use it as additional conditions for the match.
1905
+ #
1906
+ # merge_do_nothing_when_not_matched
1907
+ # # WHEN NOT MATCHED THEN DO NOTHING
1908
+ #
1909
+ # merge_do_nothing_when_not_matched{a > 30}
1910
+ # # WHEN NOT MATCHED AND (a > 30) THEN DO NOTHING
1911
+ def merge_do_nothing_when_not_matched(&block)
1912
+ _merge_when(:type=>:not_matched, &block)
1913
+ end
1914
+
1915
+ # Support OVERRIDING USER|SYSTEM VALUE for MERGE INSERT.
1916
+ def merge_insert(*values, &block)
1917
+ h = {:type=>:insert, :values=>values}
1918
+ if override = @opts[:override]
1919
+ h[:override] = insert_override_sql(String.new)
1920
+ end
1921
+ _merge_when(h, &block)
1922
+ end
1923
+
1749
1924
  # Use OVERRIDING USER VALUE for INSERT statements, so that identity columns
1750
1925
  # always use the user supplied value, and an error is not raised for identity
1751
1926
  # columns that are GENERATED ALWAYS.
@@ -1813,6 +1988,11 @@ module Sequel
1813
1988
  true
1814
1989
  end
1815
1990
 
1991
+ # PostgreSQL 15+ supports MERGE.
1992
+ def supports_merge?
1993
+ server_version >= 150000
1994
+ end
1995
+
1816
1996
  # PostgreSQL supports NOWAIT.
1817
1997
  def supports_nowait?
1818
1998
  true
@@ -1858,6 +2038,8 @@ module Sequel
1858
2038
  server_version >= 90000
1859
2039
  when :groups, :exclude
1860
2040
  server_version >= 110000
2041
+ else
2042
+ false
1861
2043
  end
1862
2044
  end
1863
2045
 
@@ -1900,12 +2082,10 @@ module Sequel
1900
2082
  # Otherwise, return an array of hashes.
1901
2083
  def _import(columns, values, opts=OPTS)
1902
2084
  if @opts[:returning]
1903
- statements = multi_insert_sql(columns, values)
1904
- trans_opts = Hash[opts]
1905
- trans_opts[:server] = @opts[:server]
1906
- @db.transaction(trans_opts) do
1907
- statements.map{|st| returning_fetch_rows(st)}
1908
- end.first.map{|v| v.length == 1 ? v.values.first : v}
2085
+ # no transaction: our multi_insert_sql_strategy should guarantee
2086
+ # that there's only ever a single statement.
2087
+ sql = multi_insert_sql(columns, values)[0]
2088
+ returning_fetch_rows(sql).map{|v| v.length == 1 ? v.values.first : v}
1909
2089
  elsif opts[:return] == :primary_key
1910
2090
  returning(insert_pk)._import(columns, values, opts)
1911
2091
  else
@@ -1923,6 +2103,22 @@ module Sequel
1923
2103
 
1924
2104
  private
1925
2105
 
2106
+ # Append the INSERT sql used in a MERGE
2107
+ def _merge_insert_sql(sql, data)
2108
+ sql << " THEN INSERT "
2109
+ columns, values = _parse_insert_sql_args(data[:values])
2110
+ _insert_columns_sql(sql, columns)
2111
+ if override = data[:override]
2112
+ sql << override
2113
+ end
2114
+ _insert_values_sql(sql, values)
2115
+ end
2116
+
2117
+ def _merge_matched_sql(sql, data)
2118
+ sql << " THEN DO NOTHING"
2119
+ end
2120
+ alias _merge_not_matched_sql _merge_matched_sql
2121
+
1926
2122
  # Format TRUNCATE statement with PostgreSQL specific options.
1927
2123
  def _truncate_sql(table)
1928
2124
  to = @opts[:truncate_opts] || OPTS
@@ -1988,25 +2184,23 @@ module Sequel
1988
2184
 
1989
2185
  # Return the primary key to use for RETURNING in an INSERT statement
1990
2186
  def insert_pk
1991
- if (f = opts[:from]) && !f.empty?
1992
- case t = f.first
1993
- when Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier
1994
- if pk = db.primary_key(t)
1995
- Sequel::SQL::Identifier.new(pk)
1996
- end
2187
+ (f = opts[:from]) && !f.empty? && (t = f.first)
2188
+ case t
2189
+ when Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier
2190
+ if pk = db.primary_key(t)
2191
+ Sequel::SQL::Identifier.new(pk)
1997
2192
  end
1998
2193
  end
1999
2194
  end
2000
2195
 
2001
2196
  # Support OVERRIDING SYSTEM|USER VALUE in insert statements
2002
- def insert_values_sql(sql)
2197
+ def insert_override_sql(sql)
2003
2198
  case opts[:override]
2004
2199
  when :system
2005
2200
  sql << " OVERRIDING SYSTEM VALUE"
2006
2201
  when :user
2007
2202
  sql << " OVERRIDING USER VALUE"
2008
2203
  end
2009
- super
2010
2204
  end
2011
2205
 
2012
2206
  # For multiple table support, PostgreSQL requires at least
@@ -2021,6 +2215,17 @@ module Sequel
2021
2215
  end
2022
2216
  end
2023
2217
 
2218
+ # Support table aliases for USING columns
2219
+ def join_using_clause_using_sql_append(sql, using_columns)
2220
+ if using_columns.is_a?(SQL::AliasedExpression)
2221
+ super(sql, using_columns.expression)
2222
+ sql << ' AS '
2223
+ identifier_append(sql, using_columns.alias)
2224
+ else
2225
+ super
2226
+ end
2227
+ end
2228
+
2024
2229
  # Use a generic blob quoting method, hopefully overridden in one of the subadapter methods
2025
2230
  def literal_blob_append(sql, v)
2026
2231
  sql << "'" << v.gsub(/[\000-\037\047\134\177-\377]/n){|b| "\\#{("%o" % b[0..1].unpack("C")[0]).rjust(3, '0')}"} << "'"
@@ -2044,6 +2249,22 @@ module Sequel
2044
2249
  end
2045
2250
  end
2046
2251
 
2252
+ # Handle Ruby integers outside PostgreSQL bigint range specially.
2253
+ def literal_integer(v)
2254
+ if v > 9223372036854775807 || v < -9223372036854775808
2255
+ literal_integer_outside_bigint_range(v)
2256
+ else
2257
+ v.to_s
2258
+ end
2259
+ end
2260
+
2261
+ # Raise IntegerOutsideBigintRange when attempting to literalize Ruby integer
2262
+ # outside PostgreSQL bigint range, so PostgreSQL doesn't treat
2263
+ # the value as numeric.
2264
+ def literal_integer_outside_bigint_range(v)
2265
+ raise IntegerOutsideBigintRange, "attempt to literalize Ruby integer outside PostgreSQL bigint range: #{v}"
2266
+ end
2267
+
2047
2268
  # Assume that SQL standard quoting is on, per Sequel's defaults
2048
2269
  def literal_string_append(sql, v)
2049
2270
  sql << "'" << v.gsub("'", "''") << "'"
@@ -2139,15 +2360,38 @@ module Sequel
2139
2360
  opts[:with].any?{|w| w[:recursive]} ? "WITH RECURSIVE " : super
2140
2361
  end
2141
2362
 
2142
- # Support WITH AS [NOT] MATERIALIZED if :materialized option is used.
2143
- def select_with_sql_prefix(sql, w)
2363
+ # Support PostgreSQL 14+ CTE SEARCH/CYCLE clauses
2364
+ def select_with_sql_cte(sql, cte)
2144
2365
  super
2366
+ select_with_sql_cte_search_cycle(sql, cte)
2367
+ end
2368
+
2369
+ def select_with_sql_cte_search_cycle(sql, cte)
2370
+ if search_opts = cte[:search]
2371
+ sql << if search_opts[:type] == :breadth
2372
+ " SEARCH BREADTH FIRST BY "
2373
+ else
2374
+ " SEARCH DEPTH FIRST BY "
2375
+ end
2145
2376
 
2146
- case w[:materialized]
2147
- when true
2148
- sql << "MATERIALIZED "
2149
- when false
2150
- sql << "NOT MATERIALIZED "
2377
+ identifier_list_append(sql, Array(search_opts[:by]))
2378
+ sql << " SET "
2379
+ identifier_append(sql, search_opts[:set] || :ordercol)
2380
+ end
2381
+
2382
+ if cycle_opts = cte[:cycle]
2383
+ sql << " CYCLE "
2384
+ identifier_list_append(sql, Array(cycle_opts[:columns]))
2385
+ sql << " SET "
2386
+ identifier_append(sql, cycle_opts[:cycle_column] || :is_cycle)
2387
+ if cycle_opts.has_key?(:cycle_value)
2388
+ sql << " TO "
2389
+ literal_append(sql, cycle_opts[:cycle_value])
2390
+ sql << " DEFAULT "
2391
+ literal_append(sql, cycle_opts.fetch(:noncycle_value, false))
2392
+ end
2393
+ sql << " USING "
2394
+ identifier_append(sql, cycle_opts[:path_column] || :path)
2151
2395
  end
2152
2396
  end
2153
2397