sequel 5.39.0 → 5.72.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 (219) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +408 -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 +13 -6
  11. data/doc/model_hooks.rdoc +1 -1
  12. data/doc/object_model.rdoc +8 -8
  13. data/doc/opening_databases.rdoc +26 -12
  14. data/doc/postgresql.rdoc +16 -8
  15. data/doc/querying.rdoc +5 -3
  16. data/doc/release_notes/5.40.0.txt +40 -0
  17. data/doc/release_notes/5.41.0.txt +25 -0
  18. data/doc/release_notes/5.42.0.txt +136 -0
  19. data/doc/release_notes/5.43.0.txt +98 -0
  20. data/doc/release_notes/5.44.0.txt +32 -0
  21. data/doc/release_notes/5.45.0.txt +34 -0
  22. data/doc/release_notes/5.46.0.txt +87 -0
  23. data/doc/release_notes/5.47.0.txt +59 -0
  24. data/doc/release_notes/5.48.0.txt +14 -0
  25. data/doc/release_notes/5.49.0.txt +59 -0
  26. data/doc/release_notes/5.50.0.txt +78 -0
  27. data/doc/release_notes/5.51.0.txt +47 -0
  28. data/doc/release_notes/5.52.0.txt +87 -0
  29. data/doc/release_notes/5.53.0.txt +23 -0
  30. data/doc/release_notes/5.54.0.txt +27 -0
  31. data/doc/release_notes/5.55.0.txt +21 -0
  32. data/doc/release_notes/5.56.0.txt +51 -0
  33. data/doc/release_notes/5.57.0.txt +23 -0
  34. data/doc/release_notes/5.58.0.txt +31 -0
  35. data/doc/release_notes/5.59.0.txt +73 -0
  36. data/doc/release_notes/5.60.0.txt +22 -0
  37. data/doc/release_notes/5.61.0.txt +43 -0
  38. data/doc/release_notes/5.62.0.txt +132 -0
  39. data/doc/release_notes/5.63.0.txt +33 -0
  40. data/doc/release_notes/5.64.0.txt +50 -0
  41. data/doc/release_notes/5.65.0.txt +21 -0
  42. data/doc/release_notes/5.66.0.txt +24 -0
  43. data/doc/release_notes/5.67.0.txt +32 -0
  44. data/doc/release_notes/5.68.0.txt +61 -0
  45. data/doc/release_notes/5.69.0.txt +26 -0
  46. data/doc/release_notes/5.70.0.txt +35 -0
  47. data/doc/release_notes/5.71.0.txt +21 -0
  48. data/doc/release_notes/5.72.0.txt +33 -0
  49. data/doc/schema_modification.rdoc +1 -1
  50. data/doc/security.rdoc +9 -9
  51. data/doc/sharding.rdoc +3 -1
  52. data/doc/sql.rdoc +28 -16
  53. data/doc/testing.rdoc +22 -11
  54. data/doc/transactions.rdoc +6 -6
  55. data/doc/virtual_rows.rdoc +2 -2
  56. data/lib/sequel/adapters/ado/access.rb +1 -1
  57. data/lib/sequel/adapters/ado.rb +17 -17
  58. data/lib/sequel/adapters/amalgalite.rb +3 -5
  59. data/lib/sequel/adapters/ibmdb.rb +2 -2
  60. data/lib/sequel/adapters/jdbc/derby.rb +8 -0
  61. data/lib/sequel/adapters/jdbc/h2.rb +60 -10
  62. data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +7 -4
  64. data/lib/sequel/adapters/jdbc.rb +16 -18
  65. data/lib/sequel/adapters/mysql.rb +92 -67
  66. data/lib/sequel/adapters/mysql2.rb +54 -49
  67. data/lib/sequel/adapters/odbc.rb +6 -2
  68. data/lib/sequel/adapters/oracle.rb +4 -3
  69. data/lib/sequel/adapters/postgres.rb +83 -40
  70. data/lib/sequel/adapters/shared/access.rb +11 -1
  71. data/lib/sequel/adapters/shared/db2.rb +30 -0
  72. data/lib/sequel/adapters/shared/mssql.rb +90 -9
  73. data/lib/sequel/adapters/shared/mysql.rb +47 -2
  74. data/lib/sequel/adapters/shared/oracle.rb +82 -1
  75. data/lib/sequel/adapters/shared/postgres.rb +496 -178
  76. data/lib/sequel/adapters/shared/sqlanywhere.rb +11 -1
  77. data/lib/sequel/adapters/shared/sqlite.rb +116 -11
  78. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  79. data/lib/sequel/adapters/sqlite.rb +60 -18
  80. data/lib/sequel/adapters/tinytds.rb +1 -1
  81. data/lib/sequel/adapters/trilogy.rb +117 -0
  82. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  83. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  84. data/lib/sequel/ast_transformer.rb +6 -0
  85. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  86. data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
  87. data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
  88. data/lib/sequel/connection_pool/single.rb +6 -8
  89. data/lib/sequel/connection_pool/threaded.rb +14 -8
  90. data/lib/sequel/connection_pool/timed_queue.rb +270 -0
  91. data/lib/sequel/connection_pool.rb +55 -31
  92. data/lib/sequel/core.rb +28 -18
  93. data/lib/sequel/database/connecting.rb +27 -3
  94. data/lib/sequel/database/dataset.rb +16 -6
  95. data/lib/sequel/database/misc.rb +69 -14
  96. data/lib/sequel/database/query.rb +73 -2
  97. data/lib/sequel/database/schema_generator.rb +46 -53
  98. data/lib/sequel/database/schema_methods.rb +18 -2
  99. data/lib/sequel/dataset/actions.rb +108 -14
  100. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
  101. data/lib/sequel/dataset/features.rb +20 -0
  102. data/lib/sequel/dataset/misc.rb +12 -2
  103. data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
  104. data/lib/sequel/dataset/prepared_statements.rb +2 -0
  105. data/lib/sequel/dataset/query.rb +171 -44
  106. data/lib/sequel/dataset/sql.rb +182 -47
  107. data/lib/sequel/dataset.rb +4 -0
  108. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  109. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  110. data/lib/sequel/extensions/any_not_empty.rb +1 -1
  111. data/lib/sequel/extensions/async_thread_pool.rb +439 -0
  112. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  113. data/lib/sequel/extensions/blank.rb +8 -0
  114. data/lib/sequel/extensions/connection_expiration.rb +15 -9
  115. data/lib/sequel/extensions/connection_validator.rb +16 -11
  116. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  117. data/lib/sequel/extensions/core_refinements.rb +36 -11
  118. data/lib/sequel/extensions/date_arithmetic.rb +71 -31
  119. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  120. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  121. data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
  122. data/lib/sequel/extensions/eval_inspect.rb +2 -0
  123. data/lib/sequel/extensions/index_caching.rb +5 -1
  124. data/lib/sequel/extensions/inflector.rb +9 -1
  125. data/lib/sequel/extensions/is_distinct_from.rb +141 -0
  126. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  127. data/lib/sequel/extensions/migration.rb +11 -2
  128. data/lib/sequel/extensions/named_timezones.rb +26 -6
  129. data/lib/sequel/extensions/pagination.rb +1 -1
  130. data/lib/sequel/extensions/pg_array.rb +32 -4
  131. data/lib/sequel/extensions/pg_array_ops.rb +2 -2
  132. data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
  133. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  134. data/lib/sequel/extensions/pg_enum.rb +2 -3
  135. data/lib/sequel/extensions/pg_extended_date_support.rb +38 -27
  136. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  137. data/lib/sequel/extensions/pg_hstore.rb +6 -1
  138. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  139. data/lib/sequel/extensions/pg_inet.rb +10 -11
  140. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  141. data/lib/sequel/extensions/pg_interval.rb +45 -19
  142. data/lib/sequel/extensions/pg_json.rb +13 -15
  143. data/lib/sequel/extensions/pg_json_ops.rb +73 -2
  144. data/lib/sequel/extensions/pg_loose_count.rb +3 -1
  145. data/lib/sequel/extensions/pg_multirange.rb +367 -0
  146. data/lib/sequel/extensions/pg_range.rb +11 -24
  147. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  148. data/lib/sequel/extensions/pg_row.rb +21 -19
  149. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  150. data/lib/sequel/extensions/query.rb +2 -0
  151. data/lib/sequel/extensions/s.rb +2 -1
  152. data/lib/sequel/extensions/schema_caching.rb +1 -1
  153. data/lib/sequel/extensions/schema_dumper.rb +45 -11
  154. data/lib/sequel/extensions/server_block.rb +10 -13
  155. data/lib/sequel/extensions/set_literalizer.rb +58 -0
  156. data/lib/sequel/extensions/sql_comments.rb +110 -3
  157. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  158. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  159. data/lib/sequel/extensions/string_agg.rb +1 -1
  160. data/lib/sequel/extensions/string_date_time.rb +19 -23
  161. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  162. data/lib/sequel/model/associations.rb +345 -101
  163. data/lib/sequel/model/base.rb +51 -27
  164. data/lib/sequel/model/dataset_module.rb +3 -0
  165. data/lib/sequel/model/errors.rb +10 -1
  166. data/lib/sequel/model/inflections.rb +1 -1
  167. data/lib/sequel/model/plugins.rb +5 -0
  168. data/lib/sequel/plugins/association_proxies.rb +2 -0
  169. data/lib/sequel/plugins/async_thread_pool.rb +39 -0
  170. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  171. data/lib/sequel/plugins/auto_validations.rb +87 -15
  172. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  173. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  174. data/lib/sequel/plugins/column_encryption.rb +728 -0
  175. data/lib/sequel/plugins/composition.rb +10 -4
  176. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  177. data/lib/sequel/plugins/constraint_validations.rb +10 -6
  178. data/lib/sequel/plugins/dataset_associations.rb +4 -1
  179. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  180. data/lib/sequel/plugins/dirty.rb +1 -1
  181. data/lib/sequel/plugins/enum.rb +124 -0
  182. data/lib/sequel/plugins/finder.rb +4 -2
  183. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  184. data/lib/sequel/plugins/instance_specific_default.rb +1 -1
  185. data/lib/sequel/plugins/json_serializer.rb +39 -24
  186. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  187. data/lib/sequel/plugins/list.rb +3 -1
  188. data/lib/sequel/plugins/many_through_many.rb +109 -10
  189. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  190. data/lib/sequel/plugins/nested_attributes.rb +12 -7
  191. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  192. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  193. data/lib/sequel/plugins/pg_array_associations.rb +56 -38
  194. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +11 -3
  195. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  196. data/lib/sequel/plugins/prepared_statements.rb +12 -2
  197. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
  198. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  199. data/lib/sequel/plugins/rcte_tree.rb +27 -19
  200. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  201. data/lib/sequel/plugins/serialization.rb +9 -3
  202. data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
  203. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  204. data/lib/sequel/plugins/sql_comments.rb +189 -0
  205. data/lib/sequel/plugins/static_cache.rb +39 -1
  206. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  207. data/lib/sequel/plugins/subclasses.rb +28 -11
  208. data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
  209. data/lib/sequel/plugins/timestamps.rb +1 -1
  210. data/lib/sequel/plugins/unused_associations.rb +521 -0
  211. data/lib/sequel/plugins/update_or_create.rb +1 -1
  212. data/lib/sequel/plugins/validate_associated.rb +22 -12
  213. data/lib/sequel/plugins/validation_helpers.rb +46 -12
  214. data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
  215. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  216. data/lib/sequel/sql.rb +1 -1
  217. data/lib/sequel/timezones.rb +12 -14
  218. data/lib/sequel/version.rb +1 -1
  219. metadata +132 -38
@@ -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
 
@@ -865,6 +805,7 @@ module Sequel
865
805
  # DB.values([[1, 2], [3, 4]]).order(:column2).limit(1, 1)
866
806
  # # VALUES ((1, 2), (3, 4)) ORDER BY column2 LIMIT 1 OFFSET 1
867
807
  def values(v)
808
+ raise Error, "Cannot provide an empty array for values" if v.empty?
868
809
  @default_dataset.clone(:values=>v)
869
810
  end
870
811
 
@@ -883,6 +824,220 @@ module Sequel
883
824
 
884
825
  private
885
826
 
827
+ # Dataset used to retrieve CHECK constraint information
828
+ def _check_constraints_ds
829
+ @_check_constraints_ds ||= metadata_dataset.
830
+ from{pg_constraint.as(:co)}.
831
+ left_join(Sequel[:pg_attribute].as(:att), :attrelid=>:conrelid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:conkey])).
832
+ where(:contype=>'c').
833
+ select{[co[:conname].as(:constraint), att[:attname].as(:column), pg_get_constraintdef(co[:oid]).as(:definition)]}
834
+ end
835
+
836
+ # Dataset used to retrieve foreign keys referenced by a table
837
+ def _foreign_key_list_ds
838
+ @_foreign_key_list_ds ||= __foreign_key_list_ds(false)
839
+ end
840
+
841
+ # Dataset used to retrieve foreign keys referencing a table
842
+ def _reverse_foreign_key_list_ds
843
+ @_reverse_foreign_key_list_ds ||= __foreign_key_list_ds(true)
844
+ end
845
+
846
+ # Build dataset used for foreign key list methods.
847
+ def __foreign_key_list_ds(reverse)
848
+ if reverse
849
+ ctable = Sequel[:att2]
850
+ cclass = Sequel[:cl2]
851
+ rtable = Sequel[:att]
852
+ rclass = Sequel[:cl]
853
+ else
854
+ ctable = Sequel[:att]
855
+ cclass = Sequel[:cl]
856
+ rtable = Sequel[:att2]
857
+ rclass = Sequel[:cl2]
858
+ end
859
+
860
+ if server_version >= 90500
861
+ cpos = Sequel.expr{array_position(co[:conkey], ctable[:attnum])}
862
+ rpos = Sequel.expr{array_position(co[:confkey], rtable[:attnum])}
863
+ # :nocov:
864
+ else
865
+ range = 0...32
866
+ cpos = Sequel.expr{SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:conkey], [x]), x]}, 32, ctable[:attnum])}
867
+ rpos = Sequel.expr{SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:confkey], [x]), x]}, 32, rtable[:attnum])}
868
+ # :nocov:
869
+ end
870
+
871
+ ds = metadata_dataset.
872
+ from{pg_constraint.as(:co)}.
873
+ join(Sequel[:pg_class].as(cclass), :oid=>:conrelid).
874
+ join(Sequel[:pg_attribute].as(ctable), :attrelid=>:oid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:conkey])).
875
+ join(Sequel[:pg_class].as(rclass), :oid=>Sequel[:co][:confrelid]).
876
+ join(Sequel[:pg_attribute].as(rtable), :attrelid=>:oid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:confkey])).
877
+ join(Sequel[:pg_namespace].as(:nsp), :oid=>Sequel[:cl2][:relnamespace]).
878
+ order{[co[:conname], cpos]}.
879
+ where{{
880
+ cl[:relkind]=>%w'r p',
881
+ co[:contype]=>'f',
882
+ cpos=>rpos
883
+ }}.
884
+ select{[
885
+ co[:conname].as(:name),
886
+ ctable[:attname].as(:column),
887
+ co[:confupdtype].as(:on_update),
888
+ co[:confdeltype].as(:on_delete),
889
+ cl2[:relname].as(:table),
890
+ rtable[:attname].as(:refcolumn),
891
+ SQL::BooleanExpression.new(:AND, co[:condeferrable], co[:condeferred]).as(:deferrable),
892
+ nsp[:nspname].as(:schema)
893
+ ]}
894
+
895
+ if reverse
896
+ ds = ds.order_append(Sequel[:nsp][:nspname], Sequel[:cl2][:relname])
897
+ end
898
+
899
+ ds
900
+ end
901
+
902
+ # Dataset used to retrieve index information
903
+ def _indexes_ds
904
+ @_indexes_ds ||= begin
905
+ if server_version >= 90500
906
+ order = [Sequel[:indc][:relname], Sequel.function(:array_position, Sequel[:ind][:indkey], Sequel[:att][:attnum])]
907
+ # :nocov:
908
+ else
909
+ range = 0...32
910
+ order = [Sequel[:indc][:relname], SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(Sequel[:ind][:indkey], [x]), x]}, 32, Sequel[:att][:attnum])]
911
+ # :nocov:
912
+ end
913
+
914
+ attnums = SQL::Function.new(:ANY, Sequel[:ind][:indkey])
915
+
916
+ ds = metadata_dataset.
917
+ from{pg_class.as(:tab)}.
918
+ join(Sequel[:pg_index].as(:ind), :indrelid=>:oid).
919
+ join(Sequel[:pg_class].as(:indc), :oid=>:indexrelid).
920
+ join(Sequel[:pg_attribute].as(:att), :attrelid=>Sequel[:tab][:oid], :attnum=>attnums).
921
+ left_join(Sequel[:pg_constraint].as(:con), :conname=>Sequel[:indc][:relname]).
922
+ where{{
923
+ indc[:relkind]=>%w'i I',
924
+ ind[:indisprimary]=>false,
925
+ :indexprs=>nil,
926
+ :indisvalid=>true}}.
927
+ order(*order).
928
+ select{[indc[:relname].as(:name), ind[:indisunique].as(:unique), att[:attname].as(:column), con[:condeferrable].as(:deferrable)]}
929
+
930
+ # :nocov:
931
+ ds = ds.where(:indisready=>true) if server_version >= 80300
932
+ ds = ds.where(:indislive=>true) if server_version >= 90300
933
+ # :nocov:
934
+
935
+ ds
936
+ end
937
+ end
938
+
939
+ # Dataset used to determine custom serial sequences for tables
940
+ def _select_custom_sequence_ds
941
+ @_select_custom_sequence_ds ||= metadata_dataset.
942
+ from{pg_class.as(:t)}.
943
+ join(:pg_namespace, {:oid => :relnamespace}, :table_alias=>:name).
944
+ join(:pg_attribute, {:attrelid => Sequel[:t][:oid]}, :table_alias=>:attr).
945
+ join(:pg_attrdef, {:adrelid => :attrelid, :adnum => :attnum}, :table_alias=>:def).
946
+ join(:pg_constraint, {:conrelid => :adrelid, Sequel[:cons][:conkey].sql_subscript(1) => :adnum}, :table_alias=>:cons).
947
+ where{{cons[:contype] => 'p', pg_get_expr(self.def[:adbin], attr[:attrelid]) => /nextval/i}}.
948
+ select{
949
+ expr = split_part(pg_get_expr(self.def[:adbin], attr[:attrelid]), "'", 2)
950
+ [
951
+ name[:nspname].as(:schema),
952
+ Sequel.case({{expr => /./} => substr(expr, strpos(expr, '.')+1)}, expr).as(:sequence)
953
+ ]
954
+ }
955
+ end
956
+
957
+ # Dataset used to determine normal serial sequences for tables
958
+ def _select_serial_sequence_ds
959
+ @_serial_sequence_ds ||= metadata_dataset.
960
+ from{[
961
+ pg_class.as(:seq),
962
+ pg_attribute.as(:attr),
963
+ pg_depend.as(:dep),
964
+ pg_namespace.as(:name),
965
+ pg_constraint.as(:cons),
966
+ pg_class.as(:t)
967
+ ]}.
968
+ where{[
969
+ [seq[:oid], dep[:objid]],
970
+ [seq[:relnamespace], name[:oid]],
971
+ [seq[:relkind], 'S'],
972
+ [attr[:attrelid], dep[:refobjid]],
973
+ [attr[:attnum], dep[:refobjsubid]],
974
+ [attr[:attrelid], cons[:conrelid]],
975
+ [attr[:attnum], cons[:conkey].sql_subscript(1)],
976
+ [attr[:attrelid], t[:oid]],
977
+ [cons[:contype], 'p']
978
+ ]}.
979
+ select{[
980
+ name[:nspname].as(:schema),
981
+ seq[:relname].as(:sequence)
982
+ ]}
983
+ end
984
+
985
+ # Dataset used to determine primary keys for tables
986
+ def _select_pk_ds
987
+ @_select_pk_ds ||= metadata_dataset.
988
+ from(:pg_class, :pg_attribute, :pg_index, :pg_namespace).
989
+ where{[
990
+ [pg_class[:oid], pg_attribute[:attrelid]],
991
+ [pg_class[:relnamespace], pg_namespace[:oid]],
992
+ [pg_class[:oid], pg_index[:indrelid]],
993
+ [pg_index[:indkey].sql_subscript(0), pg_attribute[:attnum]],
994
+ [pg_index[:indisprimary], 't']
995
+ ]}.
996
+ select{pg_attribute[:attname].as(:pk)}
997
+ end
998
+
999
+ # Dataset used to get schema for tables
1000
+ def _schema_ds
1001
+ @_schema_ds ||= begin
1002
+ ds = metadata_dataset.select{[
1003
+ pg_attribute[:attname].as(:name),
1004
+ SQL::Cast.new(pg_attribute[:atttypid], :integer).as(:oid),
1005
+ SQL::Cast.new(basetype[:oid], :integer).as(:base_oid),
1006
+ SQL::Function.new(:format_type, basetype[:oid], pg_type[:typtypmod]).as(:db_base_type),
1007
+ SQL::Function.new(:format_type, pg_type[:oid], pg_attribute[:atttypmod]).as(:db_type),
1008
+ SQL::Function.new(:pg_get_expr, pg_attrdef[:adbin], pg_class[:oid]).as(:default),
1009
+ SQL::BooleanExpression.new(:NOT, pg_attribute[:attnotnull]).as(:allow_null),
1010
+ SQL::Function.new(:COALESCE, SQL::BooleanExpression.from_value_pairs(pg_attribute[:attnum] => SQL::Function.new(:ANY, pg_index[:indkey])), false).as(:primary_key),
1011
+ Sequel[:pg_type][:typtype],
1012
+ (~Sequel[Sequel[:elementtype][:oid]=>nil]).as(:is_array),
1013
+ ]}.
1014
+ from(:pg_class).
1015
+ join(:pg_attribute, :attrelid=>:oid).
1016
+ join(:pg_type, :oid=>:atttypid).
1017
+ left_outer_join(Sequel[:pg_type].as(:basetype), :oid=>:typbasetype).
1018
+ left_outer_join(Sequel[:pg_type].as(:elementtype), :typarray=>Sequel[:pg_type][:oid]).
1019
+ left_outer_join(:pg_attrdef, :adrelid=>Sequel[:pg_class][:oid], :adnum=>Sequel[:pg_attribute][:attnum]).
1020
+ left_outer_join(:pg_index, :indrelid=>Sequel[:pg_class][:oid], :indisprimary=>true).
1021
+ where{{pg_attribute[:attisdropped]=>false}}.
1022
+ where{pg_attribute[:attnum] > 0}.
1023
+ order{pg_attribute[:attnum]}
1024
+
1025
+ # :nocov:
1026
+ if server_version > 100000
1027
+ # :nocov:
1028
+ ds = ds.select_append{pg_attribute[:attidentity]}
1029
+
1030
+ # :nocov:
1031
+ if server_version > 120000
1032
+ # :nocov:
1033
+ ds = ds.select_append{Sequel.~(pg_attribute[:attgenerated]=>'').as(:generated)}
1034
+ end
1035
+ end
1036
+
1037
+ ds
1038
+ end
1039
+ end
1040
+
886
1041
  def alter_table_add_column_sql(table, op)
887
1042
  "ADD COLUMN#{' IF NOT EXISTS' if op[:if_not_exists]} #{column_definition_sql(op)}"
888
1043
  end
@@ -1116,6 +1271,7 @@ module Sequel
1116
1271
  #{opts[:behavior].to_s.upcase if opts[:behavior]}
1117
1272
  #{'STRICT' if opts[:strict]}
1118
1273
  #{'SECURITY DEFINER' if opts[:security_definer]}
1274
+ #{"PARALLEL #{opts[:parallel].to_s.upcase}" if opts[:parallel]}
1119
1275
  #{"COST #{opts[:cost]}" if opts[:cost]}
1120
1276
  #{"ROWS #{opts[:rows]}" if opts[:rows]}
1121
1277
  #{opts[:set].map{|k,v| " SET #{k} = #{v}"}.join("\n") if opts[:set]}
@@ -1149,7 +1305,7 @@ module Sequel
1149
1305
  when :hash
1150
1306
  mod, remainder = generator.hash_values
1151
1307
  sql << " FOR VALUES WITH (MODULUS #{literal(mod)}, REMAINDER #{literal(remainder)})"
1152
- when :default
1308
+ else # when :default
1153
1309
  sql << " DEFAULT"
1154
1310
  end
1155
1311
 
@@ -1237,13 +1393,17 @@ module Sequel
1237
1393
  raise Error, "Trigger conditions are not supported for this database" unless supports_trigger_conditions?
1238
1394
  filter = " WHEN #{filter_expr(filter)}"
1239
1395
  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(', ')})"
1396
+ "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
1397
  end
1242
1398
 
1243
1399
  # DDL fragment for initial part of CREATE VIEW statement
1244
1400
  def create_view_prefix_sql(name, options)
1245
1401
  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
1402
 
1403
+ if options[:security_invoker]
1404
+ sql += " WITH (security_invoker)"
1405
+ end
1406
+
1247
1407
  if tablespace = options[:tablespace]
1248
1408
  sql += " TABLESPACE #{quote_identifier(tablespace)}"
1249
1409
  end
@@ -1301,16 +1461,20 @@ module Sequel
1301
1461
  def index_definition_sql(table_name, index)
1302
1462
  cols = index[:columns]
1303
1463
  index_name = index[:name] || default_index_name(table_name, cols)
1464
+
1304
1465
  expr = if o = index[:opclass]
1305
1466
  "(#{Array(cols).map{|c| "#{literal(c)} #{o}"}.join(', ')})"
1306
1467
  else
1307
1468
  literal(Array(cols))
1308
1469
  end
1470
+
1309
1471
  if_not_exists = " IF NOT EXISTS" if index[:if_not_exists]
1310
1472
  unique = "UNIQUE " if index[:unique]
1311
1473
  index_type = index[:type]
1312
1474
  filter = index[:where] || index[:filter]
1313
1475
  filter = " WHERE #{filter_expr(filter)}" if filter
1476
+ nulls_distinct = " NULLS#{' NOT' if index[:nulls_distinct] == false} DISTINCT" unless index[:nulls_distinct].nil?
1477
+
1314
1478
  case index_type
1315
1479
  when :full_text
1316
1480
  expr = "(to_tsvector(#{literal(index[:language] || 'simple')}::regconfig, #{literal(dataset.send(:full_text_string_join, cols))}))"
@@ -1318,7 +1482,8 @@ module Sequel
1318
1482
  when :spatial
1319
1483
  index_type = :gist
1320
1484
  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}"
1485
+
1486
+ "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
1487
  end
1323
1488
 
1324
1489
  # Setup datastructures shared by all postgres adapters.
@@ -1335,7 +1500,7 @@ module Sequel
1335
1500
  ds = metadata_dataset.from(:pg_class).where(:relkind=>type).select(:relname).server(opts[:server]).join(:pg_namespace, :oid=>:relnamespace)
1336
1501
  ds = filter_schema(ds, opts)
1337
1502
  m = output_identifier_meth
1338
- if block_given?
1503
+ if defined?(yield)
1339
1504
  yield(ds)
1340
1505
  elsif opts[:qualify]
1341
1506
  ds.select_append{pg_namespace[:nspname]}.map{|r| Sequel.qualify(m.call(r[:nspname]).to_s, m.call(r[:relname]).to_s)}
@@ -1344,11 +1509,6 @@ module Sequel
1344
1509
  end
1345
1510
  end
1346
1511
 
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
1512
  # Return an expression the oid for the table expr. Used by the metadata parsing
1353
1513
  # code to disambiguate unqualified tables.
1354
1514
  def regclass_oid(expr, opts=OPTS)
@@ -1382,11 +1542,12 @@ module Sequel
1382
1542
  end
1383
1543
 
1384
1544
  # 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.
1545
+ # a rename table operation, so specifying a new schema in new_name will not have an effect.
1386
1546
  def rename_table_sql(name, new_name)
1387
1547
  "ALTER TABLE #{quote_schema_table(name)} RENAME TO #{quote_identifier(schema_and_table(new_name).last)}"
1388
1548
  end
1389
1549
 
1550
+ # Handle interval and citext types.
1390
1551
  def schema_column_type(db_type)
1391
1552
  case db_type
1392
1553
  when /\Ainterval\z/io
@@ -1398,39 +1559,48 @@ module Sequel
1398
1559
  end
1399
1560
  end
1400
1561
 
1562
+ # The schema :type entry to use for array types.
1563
+ def schema_array_type(db_type)
1564
+ :array
1565
+ end
1566
+
1567
+ # The schema :type entry to use for row/composite types.
1568
+ def schema_composite_type(db_type)
1569
+ :composite
1570
+ end
1571
+
1572
+ # The schema :type entry to use for enum types.
1573
+ def schema_enum_type(db_type)
1574
+ :enum
1575
+ end
1576
+
1577
+ # The schema :type entry to use for range types.
1578
+ def schema_range_type(db_type)
1579
+ :range
1580
+ end
1581
+
1582
+ # The schema :type entry to use for multirange types.
1583
+ def schema_multirange_type(db_type)
1584
+ :multirange
1585
+ end
1586
+
1587
+ MIN_DATE = Date.new(-4713, 11, 24)
1588
+ MAX_DATE = Date.new(5874897, 12, 31)
1589
+ MIN_TIMESTAMP = Time.utc(-4713, 11, 24).freeze
1590
+ MAX_TIMESTAMP = (Time.utc(294277) - Rational(1, 1000000)).freeze
1591
+ TYPTYPE_METHOD_MAP = {
1592
+ 'c' => :schema_composite_type,
1593
+ 'e' => :schema_enum_type,
1594
+ 'r' => :schema_range_type,
1595
+ 'm' => :schema_multirange_type,
1596
+ }
1597
+ TYPTYPE_METHOD_MAP.default = :schema_column_type
1598
+ TYPTYPE_METHOD_MAP.freeze
1401
1599
  # The dataset used for parsing table schemas, using the pg_* system catalogs.
1402
1600
  def schema_parse_table(table_name, opts)
1403
1601
  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
1602
 
1433
- ds.map do |row|
1603
+ _schema_ds.where_all(Sequel[:pg_class][:oid]=>regclass_oid(table_name, opts)).map do |row|
1434
1604
  row[:default] = nil if blank_object?(row[:default])
1435
1605
  if row[:base_oid]
1436
1606
  row[:domain_oid] = row[:oid]
@@ -1441,11 +1611,33 @@ module Sequel
1441
1611
  row.delete(:base_oid)
1442
1612
  row.delete(:db_base_type)
1443
1613
  end
1444
- row[:type] = schema_column_type(row[:db_type])
1614
+
1615
+ db_type = row[:db_type]
1616
+ row[:type] = if row.delete(:is_array)
1617
+ schema_array_type(db_type)
1618
+ else
1619
+ send(TYPTYPE_METHOD_MAP[row.delete(:typtype)], db_type)
1620
+ end
1445
1621
  identity = row.delete(:attidentity)
1446
1622
  if row[:primary_key]
1447
1623
  row[:auto_increment] = !!(row[:default] =~ /\A(?:nextval)/i) || identity == 'a' || identity == 'd'
1448
1624
  end
1625
+
1626
+ # :nocov:
1627
+ if server_version >= 90600
1628
+ # :nocov:
1629
+ case row[:oid]
1630
+ when 1082
1631
+ row[:min_value] = MIN_DATE
1632
+ row[:max_value] = MAX_DATE
1633
+ when 1184, 1114
1634
+ if Sequel.datetime_class == Time
1635
+ row[:min_value] = MIN_TIMESTAMP
1636
+ row[:max_value] = MAX_TIMESTAMP
1637
+ end
1638
+ end
1639
+ end
1640
+
1449
1641
  [m.call(row.delete(:name)), row]
1450
1642
  end
1451
1643
  end
@@ -1500,10 +1692,12 @@ module Sequel
1500
1692
  # disallowed or there is a size specified, use the varchar type.
1501
1693
  # Otherwise use the text type.
1502
1694
  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})"
1695
+ if column[:text]
1696
+ :text
1697
+ elsif column[:fixed]
1698
+ "char(#{column[:size]||default_string_column_size})"
1699
+ elsif column[:text] == false || column[:size]
1700
+ "varchar(#{column[:size]||default_string_column_size})"
1507
1701
  else
1508
1702
  :text
1509
1703
  end
@@ -1511,7 +1705,9 @@ module Sequel
1511
1705
 
1512
1706
  # PostgreSQL 9.4+ supports views with check option.
1513
1707
  def view_with_check_option_support
1708
+ # :nocov:
1514
1709
  :local if server_version >= 90400
1710
+ # :nocov:
1515
1711
  end
1516
1712
  end
1517
1713
 
@@ -1522,7 +1718,7 @@ module Sequel
1522
1718
  LOCK_MODES = ['ACCESS SHARE', 'ROW SHARE', 'ROW EXCLUSIVE', 'SHARE UPDATE EXCLUSIVE', 'SHARE', 'SHARE ROW EXCLUSIVE', 'EXCLUSIVE', 'ACCESS EXCLUSIVE'].each(&:freeze).freeze
1523
1719
 
1524
1720
  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']])
1721
+ 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
1722
  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
1723
  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
1724
 
@@ -1549,8 +1745,6 @@ module Sequel
1549
1745
  literal_append(sql, args[0])
1550
1746
  sql << ' ' << op.to_s << ' '
1551
1747
  literal_append(sql, args[1])
1552
- sql << " ESCAPE "
1553
- literal_append(sql, "\\")
1554
1748
  sql << ')'
1555
1749
  else
1556
1750
  super
@@ -1575,6 +1769,12 @@ module Sequel
1575
1769
  clone(:disable_insert_returning=>true)
1576
1770
  end
1577
1771
 
1772
+ # Always return false when using VALUES
1773
+ def empty?
1774
+ return false if @opts[:values]
1775
+ super
1776
+ end
1777
+
1578
1778
  # Return the results of an EXPLAIN query as a string
1579
1779
  def explain(opts=OPTS)
1580
1780
  with_sql((opts[:analyze] ? 'EXPLAIN ANALYZE ' : 'EXPLAIN ') + select_sql).map(:'QUERY PLAN').join("\r\n")
@@ -1725,13 +1925,22 @@ module Sequel
1725
1925
  ds.insert_sql(*values)
1726
1926
  end
1727
1927
 
1928
+ # Support SQL::AliasedExpression as expr to setup a USING join with a table alias for the
1929
+ # USING columns.
1930
+ def join_table(type, table, expr=nil, options=OPTS, &block)
1931
+ if expr.is_a?(SQL::AliasedExpression) && expr.expression.is_a?(Array) && !expr.expression.empty? && expr.expression.all?
1932
+ options = options.merge(:join_using=>true)
1933
+ end
1934
+ super
1935
+ end
1936
+
1728
1937
  # Locks all tables in the dataset's FROM clause (but not in JOINs) with
1729
1938
  # the specified mode (e.g. 'EXCLUSIVE'). If a block is given, starts
1730
1939
  # a new transaction, locks the table, and yields. If a block is not given,
1731
1940
  # just locks the tables. Note that PostgreSQL will probably raise an error
1732
1941
  # if you lock the table outside of an existing transaction. Returns nil.
1733
1942
  def lock(mode, opts=OPTS)
1734
- if block_given? # perform locking inside a transaction and yield to block
1943
+ if defined?(yield) # perform locking inside a transaction and yield to block
1735
1944
  @db.transaction(opts){lock(mode, opts); yield}
1736
1945
  else
1737
1946
  sql = 'LOCK TABLE '.dup
@@ -1746,6 +1955,41 @@ module Sequel
1746
1955
  nil
1747
1956
  end
1748
1957
 
1958
+ # Return a dataset with a WHEN MATCHED THEN DO NOTHING clause added to the
1959
+ # MERGE statement. If a block is passed, treat it as a virtual row and
1960
+ # use it as additional conditions for the match.
1961
+ #
1962
+ # merge_do_nothing_when_matched
1963
+ # # WHEN MATCHED THEN DO NOTHING
1964
+ #
1965
+ # merge_do_nothing_when_matched{a > 30}
1966
+ # # WHEN MATCHED AND (a > 30) THEN DO NOTHING
1967
+ def merge_do_nothing_when_matched(&block)
1968
+ _merge_when(:type=>:matched, &block)
1969
+ end
1970
+
1971
+ # Return a dataset with a WHEN NOT MATCHED THEN DO NOTHING clause added to the
1972
+ # MERGE statement. If a block is passed, treat it as a virtual row and
1973
+ # use it as additional conditions for the match.
1974
+ #
1975
+ # merge_do_nothing_when_not_matched
1976
+ # # WHEN NOT MATCHED THEN DO NOTHING
1977
+ #
1978
+ # merge_do_nothing_when_not_matched{a > 30}
1979
+ # # WHEN NOT MATCHED AND (a > 30) THEN DO NOTHING
1980
+ def merge_do_nothing_when_not_matched(&block)
1981
+ _merge_when(:type=>:not_matched, &block)
1982
+ end
1983
+
1984
+ # Support OVERRIDING USER|SYSTEM VALUE for MERGE INSERT.
1985
+ def merge_insert(*values, &block)
1986
+ h = {:type=>:insert, :values=>values}
1987
+ if override = @opts[:override]
1988
+ h[:override] = insert_override_sql(String.new)
1989
+ end
1990
+ _merge_when(h, &block)
1991
+ end
1992
+
1749
1993
  # Use OVERRIDING USER VALUE for INSERT statements, so that identity columns
1750
1994
  # always use the user supplied value, and an error is not raised for identity
1751
1995
  # columns that are GENERATED ALWAYS.
@@ -1813,6 +2057,11 @@ module Sequel
1813
2057
  true
1814
2058
  end
1815
2059
 
2060
+ # PostgreSQL 15+ supports MERGE.
2061
+ def supports_merge?
2062
+ server_version >= 150000
2063
+ end
2064
+
1816
2065
  # PostgreSQL supports NOWAIT.
1817
2066
  def supports_nowait?
1818
2067
  true
@@ -1858,6 +2107,8 @@ module Sequel
1858
2107
  server_version >= 90000
1859
2108
  when :groups, :exclude
1860
2109
  server_version >= 110000
2110
+ else
2111
+ false
1861
2112
  end
1862
2113
  end
1863
2114
 
@@ -1900,12 +2151,10 @@ module Sequel
1900
2151
  # Otherwise, return an array of hashes.
1901
2152
  def _import(columns, values, opts=OPTS)
1902
2153
  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}
2154
+ # no transaction: our multi_insert_sql_strategy should guarantee
2155
+ # that there's only ever a single statement.
2156
+ sql = multi_insert_sql(columns, values)[0]
2157
+ returning_fetch_rows(sql).map{|v| v.length == 1 ? v.values.first : v}
1909
2158
  elsif opts[:return] == :primary_key
1910
2159
  returning(insert_pk)._import(columns, values, opts)
1911
2160
  else
@@ -1923,12 +2172,33 @@ module Sequel
1923
2172
 
1924
2173
  private
1925
2174
 
2175
+ # Append the INSERT sql used in a MERGE
2176
+ def _merge_insert_sql(sql, data)
2177
+ sql << " THEN INSERT "
2178
+ columns, values = _parse_insert_sql_args(data[:values])
2179
+ _insert_columns_sql(sql, columns)
2180
+ if override = data[:override]
2181
+ sql << override
2182
+ end
2183
+ _insert_values_sql(sql, values)
2184
+ end
2185
+
2186
+ def _merge_matched_sql(sql, data)
2187
+ sql << " THEN DO NOTHING"
2188
+ end
2189
+ alias _merge_not_matched_sql _merge_matched_sql
2190
+
1926
2191
  # Format TRUNCATE statement with PostgreSQL specific options.
1927
2192
  def _truncate_sql(table)
1928
2193
  to = @opts[:truncate_opts] || OPTS
1929
2194
  "TRUNCATE TABLE#{' ONLY' if to[:only]} #{table}#{' RESTART IDENTITY' if to[:restart]}#{' CASCADE' if to[:cascade]}"
1930
2195
  end
1931
2196
 
2197
+ # Use from_self for aggregate dataset using VALUES.
2198
+ def aggreate_dataset_use_from_self?
2199
+ super || @opts[:values]
2200
+ end
2201
+
1932
2202
  # Allow truncation of multiple source tables.
1933
2203
  def check_truncation_allowed!
1934
2204
  raise(InvalidOperation, "Grouped datasets cannot be truncated") if opts[:group]
@@ -1988,25 +2258,23 @@ module Sequel
1988
2258
 
1989
2259
  # Return the primary key to use for RETURNING in an INSERT statement
1990
2260
  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
2261
+ (f = opts[:from]) && !f.empty? && (t = f.first)
2262
+ case t
2263
+ when Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier
2264
+ if pk = db.primary_key(t)
2265
+ Sequel::SQL::Identifier.new(pk)
1997
2266
  end
1998
2267
  end
1999
2268
  end
2000
2269
 
2001
2270
  # Support OVERRIDING SYSTEM|USER VALUE in insert statements
2002
- def insert_values_sql(sql)
2271
+ def insert_override_sql(sql)
2003
2272
  case opts[:override]
2004
2273
  when :system
2005
2274
  sql << " OVERRIDING SYSTEM VALUE"
2006
2275
  when :user
2007
2276
  sql << " OVERRIDING USER VALUE"
2008
2277
  end
2009
- super
2010
2278
  end
2011
2279
 
2012
2280
  # For multiple table support, PostgreSQL requires at least
@@ -2021,6 +2289,17 @@ module Sequel
2021
2289
  end
2022
2290
  end
2023
2291
 
2292
+ # Support table aliases for USING columns
2293
+ def join_using_clause_using_sql_append(sql, using_columns)
2294
+ if using_columns.is_a?(SQL::AliasedExpression)
2295
+ super(sql, using_columns.expression)
2296
+ sql << ' AS '
2297
+ identifier_append(sql, using_columns.alias)
2298
+ else
2299
+ super
2300
+ end
2301
+ end
2302
+
2024
2303
  # Use a generic blob quoting method, hopefully overridden in one of the subadapter methods
2025
2304
  def literal_blob_append(sql, v)
2026
2305
  sql << "'" << v.gsub(/[\000-\037\047\134\177-\377]/n){|b| "\\#{("%o" % b[0..1].unpack("C")[0]).rjust(3, '0')}"} << "'"
@@ -2044,6 +2323,22 @@ module Sequel
2044
2323
  end
2045
2324
  end
2046
2325
 
2326
+ # Handle Ruby integers outside PostgreSQL bigint range specially.
2327
+ def literal_integer(v)
2328
+ if v > 9223372036854775807 || v < -9223372036854775808
2329
+ literal_integer_outside_bigint_range(v)
2330
+ else
2331
+ v.to_s
2332
+ end
2333
+ end
2334
+
2335
+ # Raise IntegerOutsideBigintRange when attempting to literalize Ruby integer
2336
+ # outside PostgreSQL bigint range, so PostgreSQL doesn't treat
2337
+ # the value as numeric.
2338
+ def literal_integer_outside_bigint_range(v)
2339
+ raise IntegerOutsideBigintRange, "attempt to literalize Ruby integer outside PostgreSQL bigint range: #{v}"
2340
+ end
2341
+
2047
2342
  # Assume that SQL standard quoting is on, per Sequel's defaults
2048
2343
  def literal_string_append(sql, v)
2049
2344
  sql << "'" << v.gsub("'", "''") << "'"
@@ -2139,15 +2434,38 @@ module Sequel
2139
2434
  opts[:with].any?{|w| w[:recursive]} ? "WITH RECURSIVE " : super
2140
2435
  end
2141
2436
 
2142
- # Support WITH AS [NOT] MATERIALIZED if :materialized option is used.
2143
- def select_with_sql_prefix(sql, w)
2437
+ # Support PostgreSQL 14+ CTE SEARCH/CYCLE clauses
2438
+ def select_with_sql_cte(sql, cte)
2144
2439
  super
2440
+ select_with_sql_cte_search_cycle(sql, cte)
2441
+ end
2442
+
2443
+ def select_with_sql_cte_search_cycle(sql, cte)
2444
+ if search_opts = cte[:search]
2445
+ sql << if search_opts[:type] == :breadth
2446
+ " SEARCH BREADTH FIRST BY "
2447
+ else
2448
+ " SEARCH DEPTH FIRST BY "
2449
+ end
2145
2450
 
2146
- case w[:materialized]
2147
- when true
2148
- sql << "MATERIALIZED "
2149
- when false
2150
- sql << "NOT MATERIALIZED "
2451
+ identifier_list_append(sql, Array(search_opts[:by]))
2452
+ sql << " SET "
2453
+ identifier_append(sql, search_opts[:set] || :ordercol)
2454
+ end
2455
+
2456
+ if cycle_opts = cte[:cycle]
2457
+ sql << " CYCLE "
2458
+ identifier_list_append(sql, Array(cycle_opts[:columns]))
2459
+ sql << " SET "
2460
+ identifier_append(sql, cycle_opts[:cycle_column] || :is_cycle)
2461
+ if cycle_opts.has_key?(:cycle_value)
2462
+ sql << " TO "
2463
+ literal_append(sql, cycle_opts[:cycle_value])
2464
+ sql << " DEFAULT "
2465
+ literal_append(sql, cycle_opts.fetch(:noncycle_value, false))
2466
+ end
2467
+ sql << " USING "
2468
+ identifier_append(sql, cycle_opts[:path_column] || :path)
2151
2469
  end
2152
2470
  end
2153
2471