sequel 5.58.0 → 5.78.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (161) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +288 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +24 -23
  5. data/bin/sequel +11 -3
  6. data/doc/advanced_associations.rdoc +16 -14
  7. data/doc/association_basics.rdoc +53 -17
  8. data/doc/cheat_sheet.rdoc +3 -3
  9. data/doc/mass_assignment.rdoc +1 -1
  10. data/doc/migration.rdoc +15 -0
  11. data/doc/model_hooks.rdoc +1 -1
  12. data/doc/object_model.rdoc +8 -8
  13. data/doc/opening_databases.rdoc +20 -12
  14. data/doc/postgresql.rdoc +8 -8
  15. data/doc/querying.rdoc +1 -1
  16. data/doc/release_notes/5.59.0.txt +73 -0
  17. data/doc/release_notes/5.60.0.txt +22 -0
  18. data/doc/release_notes/5.61.0.txt +43 -0
  19. data/doc/release_notes/5.62.0.txt +132 -0
  20. data/doc/release_notes/5.63.0.txt +33 -0
  21. data/doc/release_notes/5.64.0.txt +50 -0
  22. data/doc/release_notes/5.65.0.txt +21 -0
  23. data/doc/release_notes/5.66.0.txt +24 -0
  24. data/doc/release_notes/5.67.0.txt +32 -0
  25. data/doc/release_notes/5.68.0.txt +61 -0
  26. data/doc/release_notes/5.69.0.txt +26 -0
  27. data/doc/release_notes/5.70.0.txt +35 -0
  28. data/doc/release_notes/5.71.0.txt +21 -0
  29. data/doc/release_notes/5.72.0.txt +33 -0
  30. data/doc/release_notes/5.73.0.txt +66 -0
  31. data/doc/release_notes/5.74.0.txt +45 -0
  32. data/doc/release_notes/5.75.0.txt +35 -0
  33. data/doc/release_notes/5.76.0.txt +86 -0
  34. data/doc/release_notes/5.77.0.txt +63 -0
  35. data/doc/release_notes/5.78.0.txt +67 -0
  36. data/doc/schema_modification.rdoc +3 -3
  37. data/doc/security.rdoc +9 -9
  38. data/doc/sharding.rdoc +3 -1
  39. data/doc/sql.rdoc +14 -14
  40. data/doc/testing.rdoc +16 -12
  41. data/doc/transactions.rdoc +6 -6
  42. data/doc/virtual_rows.rdoc +1 -1
  43. data/lib/sequel/adapters/ibmdb.rb +1 -1
  44. data/lib/sequel/adapters/jdbc/h2.rb +3 -0
  45. data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -0
  46. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -0
  47. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +15 -0
  48. data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -0
  49. data/lib/sequel/adapters/jdbc.rb +10 -6
  50. data/lib/sequel/adapters/mysql.rb +19 -7
  51. data/lib/sequel/adapters/mysql2.rb +2 -2
  52. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  53. data/lib/sequel/adapters/oracle.rb +1 -0
  54. data/lib/sequel/adapters/postgres.rb +62 -16
  55. data/lib/sequel/adapters/shared/access.rb +9 -1
  56. data/lib/sequel/adapters/shared/db2.rb +12 -0
  57. data/lib/sequel/adapters/shared/mssql.rb +71 -9
  58. data/lib/sequel/adapters/shared/mysql.rb +80 -1
  59. data/lib/sequel/adapters/shared/oracle.rb +17 -7
  60. data/lib/sequel/adapters/shared/postgres.rb +494 -164
  61. data/lib/sequel/adapters/shared/sqlanywhere.rb +18 -5
  62. data/lib/sequel/adapters/shared/sqlite.rb +40 -4
  63. data/lib/sequel/adapters/sqlite.rb +42 -3
  64. data/lib/sequel/adapters/trilogy.rb +117 -0
  65. data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
  66. data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
  67. data/lib/sequel/connection_pool/threaded.rb +14 -8
  68. data/lib/sequel/connection_pool/timed_queue.rb +270 -0
  69. data/lib/sequel/connection_pool.rb +57 -31
  70. data/lib/sequel/database/connecting.rb +25 -1
  71. data/lib/sequel/database/dataset.rb +16 -6
  72. data/lib/sequel/database/misc.rb +65 -14
  73. data/lib/sequel/database/query.rb +72 -1
  74. data/lib/sequel/database/schema_generator.rb +2 -1
  75. data/lib/sequel/database/schema_methods.rb +13 -3
  76. data/lib/sequel/database/transactions.rb +6 -0
  77. data/lib/sequel/dataset/actions.rb +60 -13
  78. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
  79. data/lib/sequel/dataset/features.rb +15 -1
  80. data/lib/sequel/dataset/misc.rb +12 -2
  81. data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
  82. data/lib/sequel/dataset/query.rb +62 -37
  83. data/lib/sequel/dataset/sql.rb +58 -36
  84. data/lib/sequel/dataset.rb +4 -0
  85. data/lib/sequel/exceptions.rb +5 -0
  86. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  87. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  88. data/lib/sequel/extensions/any_not_empty.rb +2 -2
  89. data/lib/sequel/extensions/async_thread_pool.rb +21 -13
  90. data/lib/sequel/extensions/auto_cast_date_and_time.rb +94 -0
  91. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  92. data/lib/sequel/extensions/connection_expiration.rb +15 -9
  93. data/lib/sequel/extensions/connection_validator.rb +16 -11
  94. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  95. data/lib/sequel/extensions/date_arithmetic.rb +36 -8
  96. data/lib/sequel/extensions/duplicate_columns_handler.rb +10 -9
  97. data/lib/sequel/extensions/index_caching.rb +5 -1
  98. data/lib/sequel/extensions/is_distinct_from.rb +3 -1
  99. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  100. data/lib/sequel/extensions/migration.rb +65 -15
  101. data/lib/sequel/extensions/named_timezones.rb +22 -6
  102. data/lib/sequel/extensions/pg_array.rb +33 -4
  103. data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
  104. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  105. data/lib/sequel/extensions/pg_enum.rb +1 -2
  106. data/lib/sequel/extensions/pg_extended_date_support.rb +38 -27
  107. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  108. data/lib/sequel/extensions/pg_hstore.rb +5 -0
  109. data/lib/sequel/extensions/pg_inet.rb +10 -11
  110. data/lib/sequel/extensions/pg_interval.rb +10 -11
  111. data/lib/sequel/extensions/pg_json.rb +10 -10
  112. data/lib/sequel/extensions/pg_json_ops.rb +52 -0
  113. data/lib/sequel/extensions/pg_multirange.rb +6 -11
  114. data/lib/sequel/extensions/pg_range.rb +9 -14
  115. data/lib/sequel/extensions/pg_row.rb +20 -19
  116. data/lib/sequel/extensions/pg_timestamptz.rb +27 -3
  117. data/lib/sequel/extensions/round_timestamps.rb +1 -1
  118. data/lib/sequel/extensions/schema_caching.rb +1 -1
  119. data/lib/sequel/extensions/schema_dumper.rb +32 -9
  120. data/lib/sequel/extensions/server_block.rb +2 -1
  121. data/lib/sequel/extensions/set_literalizer.rb +58 -0
  122. data/lib/sequel/extensions/sqlite_json_ops.rb +76 -18
  123. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  124. data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
  125. data/lib/sequel/model/associations.rb +50 -11
  126. data/lib/sequel/model/base.rb +45 -21
  127. data/lib/sequel/model/dataset_module.rb +3 -0
  128. data/lib/sequel/model/exceptions.rb +15 -3
  129. data/lib/sequel/plugins/auto_validations.rb +53 -15
  130. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  131. data/lib/sequel/plugins/column_encryption.rb +27 -6
  132. data/lib/sequel/plugins/composition.rb +2 -2
  133. data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
  134. data/lib/sequel/plugins/constraint_validations.rb +8 -5
  135. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  136. data/lib/sequel/plugins/dirty.rb +1 -1
  137. data/lib/sequel/plugins/finder.rb +4 -2
  138. data/lib/sequel/plugins/list.rb +8 -3
  139. data/lib/sequel/plugins/many_through_many.rb +1 -1
  140. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  141. data/lib/sequel/plugins/nested_attributes.rb +4 -4
  142. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  143. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  144. data/lib/sequel/plugins/paged_operations.rb +181 -0
  145. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +9 -3
  146. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  147. data/lib/sequel/plugins/prepared_statements.rb +2 -1
  148. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
  149. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  150. data/lib/sequel/plugins/rcte_tree.rb +7 -4
  151. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  152. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  153. data/lib/sequel/plugins/sql_comments.rb +5 -5
  154. data/lib/sequel/plugins/static_cache.rb +38 -0
  155. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  156. data/lib/sequel/plugins/tactical_eager_loading.rb +21 -14
  157. data/lib/sequel/plugins/validate_associated.rb +22 -12
  158. data/lib/sequel/plugins/validation_helpers.rb +29 -2
  159. data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
  160. data/lib/sequel/version.rb +1 -1
  161. metadata +76 -6
@@ -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 = 140000
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).
@@ -490,6 +498,25 @@ module Sequel
490
498
  :postgres
491
499
  end
492
500
 
501
+ # For constraints that are deferrable, defer constraints until
502
+ # transaction commit. Options:
503
+ #
504
+ # :constraints :: An identifier of the constraint, or an array of
505
+ # identifiers for constraints, to apply this
506
+ # change to specific constraints.
507
+ # :server :: The server/shard on which to run the query.
508
+ #
509
+ # Examples:
510
+ #
511
+ # DB.defer_constraints
512
+ # # SET CONSTRAINTS ALL DEFERRED
513
+ #
514
+ # DB.defer_constraints(constraints: [:c1, Sequel[:sc][:c2]])
515
+ # # SET CONSTRAINTS "c1", "sc"."s2" DEFERRED
516
+ def defer_constraints(opts=OPTS)
517
+ _set_constraints(' DEFERRED', opts)
518
+ end
519
+
493
520
  # Use PostgreSQL's DO syntax to execute an anonymous code block. The code should
494
521
  # be the literal code string to use in the underlying procedural language. Options:
495
522
  #
@@ -551,63 +578,12 @@ module Sequel
551
578
  def foreign_key_list(table, opts=OPTS)
552
579
  m = output_identifier_meth
553
580
  schema, _ = opts.fetch(:schema, schema_and_table(table))
554
- oid = regclass_oid(table)
555
- reverse = opts[:reverse]
556
-
557
- if reverse
558
- ctable = Sequel[:att2]
559
- cclass = Sequel[:cl2]
560
- rtable = Sequel[:att]
561
- rclass = Sequel[:cl]
562
- else
563
- ctable = Sequel[:att]
564
- cclass = Sequel[:cl]
565
- rtable = Sequel[:att2]
566
- rclass = Sequel[:cl2]
567
- end
568
-
569
- if server_version >= 90500
570
- cpos = Sequel.expr{array_position(co[:conkey], ctable[:attnum])}
571
- rpos = Sequel.expr{array_position(co[:confkey], rtable[:attnum])}
572
- else
573
- range = 0...32
574
- cpos = Sequel.expr{SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:conkey], [x]), x]}, 32, ctable[:attnum])}
575
- rpos = Sequel.expr{SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:confkey], [x]), x]}, 32, rtable[:attnum])}
576
- end
577
-
578
- ds = metadata_dataset.
579
- from{pg_constraint.as(:co)}.
580
- join(Sequel[:pg_class].as(cclass), :oid=>:conrelid).
581
- join(Sequel[:pg_attribute].as(ctable), :attrelid=>:oid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:conkey])).
582
- join(Sequel[:pg_class].as(rclass), :oid=>Sequel[:co][:confrelid]).
583
- join(Sequel[:pg_attribute].as(rtable), :attrelid=>:oid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:confkey])).
584
- join(Sequel[:pg_namespace].as(:nsp), :oid=>Sequel[:cl2][:relnamespace]).
585
- order{[co[:conname], cpos]}.
586
- where{{
587
- cl[:relkind]=>'r',
588
- co[:contype]=>'f',
589
- cl[:oid]=>oid,
590
- cpos=>rpos
591
- }}.
592
- select{[
593
- co[:conname].as(:name),
594
- ctable[:attname].as(:column),
595
- co[:confupdtype].as(:on_update),
596
- co[:confdeltype].as(:on_delete),
597
- cl2[:relname].as(:table),
598
- rtable[:attname].as(:refcolumn),
599
- SQL::BooleanExpression.new(:AND, co[:condeferrable], co[:condeferred]).as(:deferrable),
600
- nsp[:nspname].as(:schema)
601
- ]}
602
-
603
- if reverse
604
- ds = ds.order_append(Sequel[:nsp][:nspname], Sequel[:cl2][:relname])
605
- end
606
581
 
607
582
  h = {}
608
583
  fklod_map = FOREIGN_KEY_LIST_ON_DELETE_MAP
584
+ reverse = opts[:reverse]
609
585
 
610
- ds.each do |row|
586
+ (reverse ? _reverse_foreign_key_list_ds : _foreign_key_list_ds).where_each(Sequel[:cl][:oid]=>regclass_oid(table)) do |row|
611
587
  if reverse
612
588
  key = [row[:schema], row[:table], row[:name]]
613
589
  else
@@ -642,45 +618,44 @@ module Sequel
642
618
  def freeze
643
619
  server_version
644
620
  supports_prepared_transactions?
621
+ _schema_ds
622
+ _select_serial_sequence_ds
623
+ _select_custom_sequence_ds
624
+ _select_pk_ds
625
+ _indexes_ds
626
+ _check_constraints_ds
627
+ _foreign_key_list_ds
628
+ _reverse_foreign_key_list_ds
645
629
  @conversion_procs.freeze
646
630
  super
647
631
  end
648
632
 
633
+ # Immediately apply deferrable constraints.
634
+ #
635
+ # :constraints :: An identifier of the constraint, or an array of
636
+ # identifiers for constraints, to apply this
637
+ # change to specific constraints.
638
+ # :server :: The server/shard on which to run the query.
639
+ #
640
+ # Examples:
641
+ #
642
+ # DB.immediate_constraints
643
+ # # SET CONSTRAINTS ALL IMMEDIATE
644
+ #
645
+ # DB.immediate_constraints(constraints: [:c1, Sequel[:sc][:c2]])
646
+ # # SET CONSTRAINTS "c1", "sc"."s2" IMMEDIATE
647
+ def immediate_constraints(opts=OPTS)
648
+ _set_constraints(' IMMEDIATE', opts)
649
+ end
650
+
649
651
  # Use the pg_* system tables to determine indexes on a table
650
652
  def indexes(table, opts=OPTS)
651
653
  m = output_identifier_meth
652
- oid = regclass_oid(table, opts)
653
-
654
- if server_version >= 90500
655
- order = [Sequel[:indc][:relname], Sequel.function(:array_position, Sequel[:ind][:indkey], Sequel[:att][:attnum])]
656
- else
657
- range = 0...32
658
- order = [Sequel[:indc][:relname], SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(Sequel[:ind][:indkey], [x]), x]}, 32, Sequel[:att][:attnum])]
659
- end
660
-
661
- attnums = SQL::Function.new(:ANY, Sequel[:ind][:indkey])
662
-
663
- ds = metadata_dataset.
664
- from{pg_class.as(:tab)}.
665
- join(Sequel[:pg_index].as(:ind), :indrelid=>:oid).
666
- join(Sequel[:pg_class].as(:indc), :oid=>:indexrelid).
667
- join(Sequel[:pg_attribute].as(:att), :attrelid=>Sequel[:tab][:oid], :attnum=>attnums).
668
- left_join(Sequel[:pg_constraint].as(:con), :conname=>Sequel[:indc][:relname]).
669
- where{{
670
- indc[:relkind]=>'i',
671
- ind[:indisprimary]=>false,
672
- :indexprs=>nil,
673
- :indisvalid=>true,
674
- tab[:oid]=>oid}}.
675
- order(*order).
676
- select{[indc[:relname].as(:name), ind[:indisunique].as(:unique), att[:attname].as(:column), con[:condeferrable].as(:deferrable)]}
677
-
678
- ds = ds.where(:indpred=>nil) unless opts[:include_partial]
679
- ds = ds.where(:indisready=>true) if server_version >= 80300
680
- ds = ds.where(:indislive=>true) if server_version >= 90300
654
+ cond = {Sequel[:tab][:oid]=>regclass_oid(table, opts)}
655
+ cond[:indpred] = nil unless opts[:include_partial]
681
656
 
682
657
  indexes = {}
683
- ds.each do |r|
658
+ _indexes_ds.where_each(cond) do |r|
684
659
  i = indexes[m.call(r[:name])] ||= {:columns=>[], :unique=>r[:unique], :deferrable=>r[:deferrable]}
685
660
  i[:columns] << m.call(r[:column])
686
661
  end
@@ -713,8 +688,7 @@ module Sequel
713
688
  def primary_key(table, opts=OPTS)
714
689
  quoted_table = quote_schema_table(table)
715
690
  Sequel.synchronize{return @primary_keys[quoted_table] if @primary_keys.has_key?(quoted_table)}
716
- sql = "#{SELECT_PK_SQL} AND pg_class.oid = #{literal(regclass_oid(table, opts))}"
717
- value = fetch(sql).single_value
691
+ value = _select_pk_ds.where_single_value(Sequel[:pg_class][:oid] => regclass_oid(table, opts))
718
692
  Sequel.synchronize{@primary_keys[quoted_table] = value}
719
693
  end
720
694
 
@@ -722,24 +696,21 @@ module Sequel
722
696
  def primary_key_sequence(table, opts=OPTS)
723
697
  quoted_table = quote_schema_table(table)
724
698
  Sequel.synchronize{return @primary_key_sequences[quoted_table] if @primary_key_sequences.has_key?(quoted_table)}
725
- sql = "#{SELECT_SERIAL_SEQUENCE_SQL} AND t.oid = #{literal(regclass_oid(table, opts))}"
726
- if pks = fetch(sql).single_record
727
- value = literal(SQL::QualifiedIdentifier.new(pks[:schema], pks[:sequence]))
728
- Sequel.synchronize{@primary_key_sequences[quoted_table] = value}
729
- else
730
- sql = "#{SELECT_CUSTOM_SEQUENCE_SQL} AND t.oid = #{literal(regclass_oid(table, opts))}"
731
- if pks = fetch(sql).single_record
732
- value = literal(SQL::QualifiedIdentifier.new(pks[:schema], LiteralString.new(pks[:sequence])))
733
- Sequel.synchronize{@primary_key_sequences[quoted_table] = value}
734
- end
699
+ cond = {Sequel[:t][:oid] => regclass_oid(table, opts)}
700
+ value = if pks = _select_serial_sequence_ds.first(cond)
701
+ literal(SQL::QualifiedIdentifier.new(pks[:schema], pks[:sequence]))
702
+ elsif pks = _select_custom_sequence_ds.first(cond)
703
+ literal(SQL::QualifiedIdentifier.new(pks[:schema], LiteralString.new(pks[:sequence])))
735
704
  end
705
+
706
+ Sequel.synchronize{@primary_key_sequences[quoted_table] = value} if value
736
707
  end
737
708
 
738
709
  # Refresh the materialized view with the given name.
739
710
  #
740
711
  # DB.refresh_view(:items_view)
741
712
  # # REFRESH MATERIALIZED VIEW items_view
742
- # DB.refresh_view(:items_view, :concurrently=>true)
713
+ # DB.refresh_view(:items_view, concurrently: true)
743
714
  # # REFRESH MATERIALIZED VIEW CONCURRENTLY items_view
744
715
  def refresh_view(name, opts=OPTS)
745
716
  run "REFRESH MATERIALIZED VIEW#{' CONCURRENTLY' if opts[:concurrently]} #{quote_schema_table(name)}"
@@ -758,10 +729,12 @@ module Sequel
758
729
  seq_ds = metadata_dataset.from(:pg_sequence).where(:seqrelid=>regclass_oid(LiteralString.new(seq)))
759
730
  increment_by = :seqincrement
760
731
  min_value = :seqmin
732
+ # :nocov:
761
733
  else
762
734
  seq_ds = metadata_dataset.from(LiteralString.new(seq))
763
735
  increment_by = :increment_by
764
736
  min_value = :min_value
737
+ # :nocov:
765
738
  end
766
739
 
767
740
  get{setval(seq, db[table].select(coalesce(max(pk)+seq_ds.select(increment_by), seq_ds.select(min_value))), false)}
@@ -774,7 +747,9 @@ module Sequel
774
747
  # PostgreSQL uses SERIAL psuedo-type instead of AUTOINCREMENT for
775
748
  # managing incrementing primary keys.
776
749
  def serial_primary_key_options
750
+ # :nocov:
777
751
  auto_increment_key = server_version >= 100002 ? :identity : :serial
752
+ # :nocov:
778
753
  {:primary_key => true, auto_increment_key => true, :type=>Integer}
779
754
  end
780
755
 
@@ -867,6 +842,7 @@ module Sequel
867
842
  # DB.values([[1, 2], [3, 4]]).order(:column2).limit(1, 1)
868
843
  # # VALUES ((1, 2), (3, 4)) ORDER BY column2 LIMIT 1 OFFSET 1
869
844
  def values(v)
845
+ raise Error, "Cannot provide an empty array for values" if v.empty?
870
846
  @default_dataset.clone(:values=>v)
871
847
  end
872
848
 
@@ -883,8 +859,282 @@ module Sequel
883
859
  pg_class_relname(relkind, opts)
884
860
  end
885
861
 
862
+ # Attempt to acquire an exclusive advisory lock with the given lock_id (which should be
863
+ # a 64-bit integer). If successful, yield to the block, then release the advisory lock
864
+ # when the block exits. If unsuccessful, raise a Sequel::AdvisoryLockError.
865
+ #
866
+ # DB.with_advisory_lock(1347){DB.get(1)}
867
+ # # SELECT pg_try_advisory_lock(1357) LIMIT 1
868
+ # # SELECT 1 AS v LIMIT 1
869
+ # # SELECT pg_advisory_unlock(1357) LIMIT 1
870
+ #
871
+ # Options:
872
+ # :wait :: Do not raise an error, instead, wait until the advisory lock can be acquired.
873
+ def with_advisory_lock(lock_id, opts=OPTS)
874
+ ds = dataset
875
+ if server = opts[:server]
876
+ ds = ds.server(server)
877
+ end
878
+
879
+ synchronize(server) do |c|
880
+ begin
881
+ if opts[:wait]
882
+ ds.get{pg_advisory_lock(lock_id)}
883
+ locked = true
884
+ else
885
+ unless locked = ds.get{pg_try_advisory_lock(lock_id)}
886
+ raise AdvisoryLockError, "unable to acquire advisory lock #{lock_id.inspect}"
887
+ end
888
+ end
889
+
890
+ yield
891
+ ensure
892
+ ds.get{pg_advisory_unlock(lock_id)} if locked
893
+ end
894
+ end
895
+ end
896
+
886
897
  private
887
898
 
899
+ # Dataset used to retrieve CHECK constraint information
900
+ def _check_constraints_ds
901
+ @_check_constraints_ds ||= metadata_dataset.
902
+ from{pg_constraint.as(:co)}.
903
+ left_join(Sequel[:pg_attribute].as(:att), :attrelid=>:conrelid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:conkey])).
904
+ where(:contype=>'c').
905
+ select{[co[:conname].as(:constraint), att[:attname].as(:column), pg_get_constraintdef(co[:oid]).as(:definition)]}
906
+ end
907
+
908
+ # Dataset used to retrieve foreign keys referenced by a table
909
+ def _foreign_key_list_ds
910
+ @_foreign_key_list_ds ||= __foreign_key_list_ds(false)
911
+ end
912
+
913
+ # Dataset used to retrieve foreign keys referencing a table
914
+ def _reverse_foreign_key_list_ds
915
+ @_reverse_foreign_key_list_ds ||= __foreign_key_list_ds(true)
916
+ end
917
+
918
+ # Build dataset used for foreign key list methods.
919
+ def __foreign_key_list_ds(reverse)
920
+ if reverse
921
+ ctable = Sequel[:att2]
922
+ cclass = Sequel[:cl2]
923
+ rtable = Sequel[:att]
924
+ rclass = Sequel[:cl]
925
+ else
926
+ ctable = Sequel[:att]
927
+ cclass = Sequel[:cl]
928
+ rtable = Sequel[:att2]
929
+ rclass = Sequel[:cl2]
930
+ end
931
+
932
+ if server_version >= 90500
933
+ cpos = Sequel.expr{array_position(co[:conkey], ctable[:attnum])}
934
+ rpos = Sequel.expr{array_position(co[:confkey], rtable[:attnum])}
935
+ # :nocov:
936
+ else
937
+ range = 0...32
938
+ cpos = Sequel.expr{SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:conkey], [x]), x]}, 32, ctable[:attnum])}
939
+ rpos = Sequel.expr{SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:confkey], [x]), x]}, 32, rtable[:attnum])}
940
+ # :nocov:
941
+ end
942
+
943
+ ds = metadata_dataset.
944
+ from{pg_constraint.as(:co)}.
945
+ join(Sequel[:pg_class].as(cclass), :oid=>:conrelid).
946
+ join(Sequel[:pg_attribute].as(ctable), :attrelid=>:oid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:conkey])).
947
+ join(Sequel[:pg_class].as(rclass), :oid=>Sequel[:co][:confrelid]).
948
+ join(Sequel[:pg_attribute].as(rtable), :attrelid=>:oid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:confkey])).
949
+ join(Sequel[:pg_namespace].as(:nsp), :oid=>Sequel[:cl2][:relnamespace]).
950
+ order{[co[:conname], cpos]}.
951
+ where{{
952
+ cl[:relkind]=>%w'r p',
953
+ co[:contype]=>'f',
954
+ cpos=>rpos
955
+ }}.
956
+ select{[
957
+ co[:conname].as(:name),
958
+ ctable[:attname].as(:column),
959
+ co[:confupdtype].as(:on_update),
960
+ co[:confdeltype].as(:on_delete),
961
+ cl2[:relname].as(:table),
962
+ rtable[:attname].as(:refcolumn),
963
+ SQL::BooleanExpression.new(:AND, co[:condeferrable], co[:condeferred]).as(:deferrable),
964
+ nsp[:nspname].as(:schema)
965
+ ]}
966
+
967
+ if reverse
968
+ ds = ds.order_append(Sequel[:nsp][:nspname], Sequel[:cl2][:relname])
969
+ end
970
+
971
+ ds
972
+ end
973
+
974
+ # Dataset used to retrieve index information
975
+ def _indexes_ds
976
+ @_indexes_ds ||= begin
977
+ if server_version >= 90500
978
+ order = [Sequel[:indc][:relname], Sequel.function(:array_position, Sequel[:ind][:indkey], Sequel[:att][:attnum])]
979
+ # :nocov:
980
+ else
981
+ range = 0...32
982
+ order = [Sequel[:indc][:relname], SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(Sequel[:ind][:indkey], [x]), x]}, 32, Sequel[:att][:attnum])]
983
+ # :nocov:
984
+ end
985
+
986
+ attnums = SQL::Function.new(:ANY, Sequel[:ind][:indkey])
987
+
988
+ ds = metadata_dataset.
989
+ from{pg_class.as(:tab)}.
990
+ join(Sequel[:pg_index].as(:ind), :indrelid=>:oid).
991
+ join(Sequel[:pg_class].as(:indc), :oid=>:indexrelid).
992
+ join(Sequel[:pg_attribute].as(:att), :attrelid=>Sequel[:tab][:oid], :attnum=>attnums).
993
+ left_join(Sequel[:pg_constraint].as(:con), :conname=>Sequel[:indc][:relname]).
994
+ where{{
995
+ indc[:relkind]=>%w'i I',
996
+ ind[:indisprimary]=>false,
997
+ :indexprs=>nil,
998
+ :indisvalid=>true}}.
999
+ order(*order).
1000
+ select{[indc[:relname].as(:name), ind[:indisunique].as(:unique), att[:attname].as(:column), con[:condeferrable].as(:deferrable)]}
1001
+
1002
+ # :nocov:
1003
+ ds = ds.where(:indisready=>true) if server_version >= 80300
1004
+ ds = ds.where(:indislive=>true) if server_version >= 90300
1005
+ # :nocov:
1006
+
1007
+ ds
1008
+ end
1009
+ end
1010
+
1011
+ # Dataset used to determine custom serial sequences for tables
1012
+ def _select_custom_sequence_ds
1013
+ @_select_custom_sequence_ds ||= metadata_dataset.
1014
+ from{pg_class.as(:t)}.
1015
+ join(:pg_namespace, {:oid => :relnamespace}, :table_alias=>:name).
1016
+ join(:pg_attribute, {:attrelid => Sequel[:t][:oid]}, :table_alias=>:attr).
1017
+ join(:pg_attrdef, {:adrelid => :attrelid, :adnum => :attnum}, :table_alias=>:def).
1018
+ join(:pg_constraint, {:conrelid => :adrelid, Sequel[:cons][:conkey].sql_subscript(1) => :adnum}, :table_alias=>:cons).
1019
+ where{{cons[:contype] => 'p', pg_get_expr(self.def[:adbin], attr[:attrelid]) => /nextval/i}}.
1020
+ select{
1021
+ expr = split_part(pg_get_expr(self.def[:adbin], attr[:attrelid]), "'", 2)
1022
+ [
1023
+ name[:nspname].as(:schema),
1024
+ Sequel.case({{expr => /./} => substr(expr, strpos(expr, '.')+1)}, expr).as(:sequence)
1025
+ ]
1026
+ }
1027
+ end
1028
+
1029
+ # Dataset used to determine normal serial sequences for tables
1030
+ def _select_serial_sequence_ds
1031
+ @_serial_sequence_ds ||= metadata_dataset.
1032
+ from{[
1033
+ pg_class.as(:seq),
1034
+ pg_attribute.as(:attr),
1035
+ pg_depend.as(:dep),
1036
+ pg_namespace.as(:name),
1037
+ pg_constraint.as(:cons),
1038
+ pg_class.as(:t)
1039
+ ]}.
1040
+ where{[
1041
+ [seq[:oid], dep[:objid]],
1042
+ [seq[:relnamespace], name[:oid]],
1043
+ [seq[:relkind], 'S'],
1044
+ [attr[:attrelid], dep[:refobjid]],
1045
+ [attr[:attnum], dep[:refobjsubid]],
1046
+ [attr[:attrelid], cons[:conrelid]],
1047
+ [attr[:attnum], cons[:conkey].sql_subscript(1)],
1048
+ [attr[:attrelid], t[:oid]],
1049
+ [cons[:contype], 'p']
1050
+ ]}.
1051
+ select{[
1052
+ name[:nspname].as(:schema),
1053
+ seq[:relname].as(:sequence)
1054
+ ]}
1055
+ end
1056
+
1057
+ # Dataset used to determine primary keys for tables
1058
+ def _select_pk_ds
1059
+ @_select_pk_ds ||= metadata_dataset.
1060
+ from(:pg_class, :pg_attribute, :pg_index, :pg_namespace).
1061
+ where{[
1062
+ [pg_class[:oid], pg_attribute[:attrelid]],
1063
+ [pg_class[:relnamespace], pg_namespace[:oid]],
1064
+ [pg_class[:oid], pg_index[:indrelid]],
1065
+ [pg_index[:indkey].sql_subscript(0), pg_attribute[:attnum]],
1066
+ [pg_index[:indisprimary], 't']
1067
+ ]}.
1068
+ select{pg_attribute[:attname].as(:pk)}
1069
+ end
1070
+
1071
+ # Dataset used to get schema for tables
1072
+ def _schema_ds
1073
+ @_schema_ds ||= begin
1074
+ ds = metadata_dataset.select{[
1075
+ pg_attribute[:attname].as(:name),
1076
+ SQL::Cast.new(pg_attribute[:atttypid], :integer).as(:oid),
1077
+ SQL::Cast.new(basetype[:oid], :integer).as(:base_oid),
1078
+ SQL::Function.new(:format_type, basetype[:oid], pg_type[:typtypmod]).as(:db_base_type),
1079
+ SQL::Function.new(:format_type, pg_type[:oid], pg_attribute[:atttypmod]).as(:db_type),
1080
+ SQL::Function.new(:pg_get_expr, pg_attrdef[:adbin], pg_class[:oid]).as(:default),
1081
+ SQL::BooleanExpression.new(:NOT, pg_attribute[:attnotnull]).as(:allow_null),
1082
+ SQL::Function.new(:COALESCE, SQL::BooleanExpression.from_value_pairs(pg_attribute[:attnum] => SQL::Function.new(:ANY, pg_index[:indkey])), false).as(:primary_key),
1083
+ Sequel[:pg_type][:typtype],
1084
+ (~Sequel[Sequel[:elementtype][:oid]=>nil]).as(:is_array),
1085
+ ]}.
1086
+ from(:pg_class).
1087
+ join(:pg_attribute, :attrelid=>:oid).
1088
+ join(:pg_type, :oid=>:atttypid).
1089
+ left_outer_join(Sequel[:pg_type].as(:basetype), :oid=>:typbasetype).
1090
+ left_outer_join(Sequel[:pg_type].as(:elementtype), :typarray=>Sequel[:pg_type][:oid]).
1091
+ left_outer_join(:pg_attrdef, :adrelid=>Sequel[:pg_class][:oid], :adnum=>Sequel[:pg_attribute][:attnum]).
1092
+ left_outer_join(:pg_index, :indrelid=>Sequel[:pg_class][:oid], :indisprimary=>true).
1093
+ where{{pg_attribute[:attisdropped]=>false}}.
1094
+ where{pg_attribute[:attnum] > 0}.
1095
+ order{pg_attribute[:attnum]}
1096
+
1097
+ # :nocov:
1098
+ if server_version > 100000
1099
+ # :nocov:
1100
+ ds = ds.select_append{pg_attribute[:attidentity]}
1101
+
1102
+ # :nocov:
1103
+ if server_version > 120000
1104
+ # :nocov:
1105
+ ds = ds.select_append{Sequel.~(pg_attribute[:attgenerated]=>'').as(:generated)}
1106
+ end
1107
+ end
1108
+
1109
+ ds
1110
+ end
1111
+ end
1112
+
1113
+ # Internals of defer_constraints/immediate_constraints
1114
+ def _set_constraints(type, opts)
1115
+ execute_ddl(_set_constraints_sql(type, opts), opts)
1116
+ end
1117
+
1118
+ # SQL to use for SET CONSTRAINTS
1119
+ def _set_constraints_sql(type, opts)
1120
+ sql = String.new
1121
+ sql << "SET CONSTRAINTS "
1122
+ if constraints = opts[:constraints]
1123
+ dataset.send(:source_list_append, sql, Array(constraints))
1124
+ else
1125
+ sql << "ALL"
1126
+ end
1127
+ sql << type
1128
+ end
1129
+
1130
+ # Consider lock or statement timeout errors as evidence that the table exists
1131
+ # but is locked.
1132
+ def _table_exists?(ds)
1133
+ super
1134
+ rescue DatabaseError => e
1135
+ raise e unless /canceling statement due to (?:statement|lock) timeout/ =~ e.message
1136
+ end
1137
+
888
1138
  def alter_table_add_column_sql(table, op)
889
1139
  "ADD COLUMN#{' IF NOT EXISTS' if op[:if_not_exists]} #{column_definition_sql(op)}"
890
1140
  end
@@ -1152,7 +1402,7 @@ module Sequel
1152
1402
  when :hash
1153
1403
  mod, remainder = generator.hash_values
1154
1404
  sql << " FOR VALUES WITH (MODULUS #{literal(mod)}, REMAINDER #{literal(remainder)})"
1155
- when :default
1405
+ else # when :default
1156
1406
  sql << " DEFAULT"
1157
1407
  end
1158
1408
 
@@ -1247,6 +1497,10 @@ module Sequel
1247
1497
  def create_view_prefix_sql(name, options)
1248
1498
  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])
1249
1499
 
1500
+ if options[:security_invoker]
1501
+ sql += " WITH (security_invoker)"
1502
+ end
1503
+
1250
1504
  if tablespace = options[:tablespace]
1251
1505
  sql += " TABLESPACE #{quote_identifier(tablespace)}"
1252
1506
  end
@@ -1294,7 +1548,11 @@ module Sequel
1294
1548
  # currently visible schemas.
1295
1549
  def filter_schema(ds, opts)
1296
1550
  expr = if schema = opts[:schema]
1297
- schema.to_s
1551
+ if schema.is_a?(SQL::Identifier)
1552
+ schema.value.to_s
1553
+ else
1554
+ schema.to_s
1555
+ end
1298
1556
  else
1299
1557
  Sequel.function(:any, Sequel.function(:current_schemas, false))
1300
1558
  end
@@ -1304,16 +1562,20 @@ module Sequel
1304
1562
  def index_definition_sql(table_name, index)
1305
1563
  cols = index[:columns]
1306
1564
  index_name = index[:name] || default_index_name(table_name, cols)
1565
+
1307
1566
  expr = if o = index[:opclass]
1308
1567
  "(#{Array(cols).map{|c| "#{literal(c)} #{o}"}.join(', ')})"
1309
1568
  else
1310
1569
  literal(Array(cols))
1311
1570
  end
1571
+
1312
1572
  if_not_exists = " IF NOT EXISTS" if index[:if_not_exists]
1313
1573
  unique = "UNIQUE " if index[:unique]
1314
1574
  index_type = index[:type]
1315
1575
  filter = index[:where] || index[:filter]
1316
1576
  filter = " WHERE #{filter_expr(filter)}" if filter
1577
+ nulls_distinct = " NULLS#{' NOT' if index[:nulls_distinct] == false} DISTINCT" unless index[:nulls_distinct].nil?
1578
+
1317
1579
  case index_type
1318
1580
  when :full_text
1319
1581
  expr = "(to_tsvector(#{literal(index[:language] || 'simple')}::regconfig, #{literal(dataset.send(:full_text_string_join, cols))}))"
@@ -1321,7 +1583,8 @@ module Sequel
1321
1583
  when :spatial
1322
1584
  index_type = :gist
1323
1585
  end
1324
- "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}"
1586
+
1587
+ "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}"
1325
1588
  end
1326
1589
 
1327
1590
  # Setup datastructures shared by all postgres adapters.
@@ -1347,11 +1610,6 @@ module Sequel
1347
1610
  end
1348
1611
  end
1349
1612
 
1350
- # Use a dollar sign instead of question mark for the argument placeholder.
1351
- def prepared_arg_placeholder
1352
- PREPARED_ARG_PLACEHOLDER
1353
- end
1354
-
1355
1613
  # Return an expression the oid for the table expr. Used by the metadata parsing
1356
1614
  # code to disambiguate unqualified tables.
1357
1615
  def regclass_oid(expr, opts=OPTS)
@@ -1385,11 +1643,12 @@ module Sequel
1385
1643
  end
1386
1644
 
1387
1645
  # SQL DDL statement for renaming a table. PostgreSQL doesn't allow you to change a table's schema in
1388
- # a rename table operation, so speciying a new schema in new_name will not have an effect.
1646
+ # a rename table operation, so specifying a new schema in new_name will not have an effect.
1389
1647
  def rename_table_sql(name, new_name)
1390
1648
  "ALTER TABLE #{quote_schema_table(name)} RENAME TO #{quote_identifier(schema_and_table(new_name).last)}"
1391
1649
  end
1392
1650
 
1651
+ # Handle interval and citext types.
1393
1652
  def schema_column_type(db_type)
1394
1653
  case db_type
1395
1654
  when /\Ainterval\z/io
@@ -1401,39 +1660,48 @@ module Sequel
1401
1660
  end
1402
1661
  end
1403
1662
 
1663
+ # The schema :type entry to use for array types.
1664
+ def schema_array_type(db_type)
1665
+ :array
1666
+ end
1667
+
1668
+ # The schema :type entry to use for row/composite types.
1669
+ def schema_composite_type(db_type)
1670
+ :composite
1671
+ end
1672
+
1673
+ # The schema :type entry to use for enum types.
1674
+ def schema_enum_type(db_type)
1675
+ :enum
1676
+ end
1677
+
1678
+ # The schema :type entry to use for range types.
1679
+ def schema_range_type(db_type)
1680
+ :range
1681
+ end
1682
+
1683
+ # The schema :type entry to use for multirange types.
1684
+ def schema_multirange_type(db_type)
1685
+ :multirange
1686
+ end
1687
+
1688
+ MIN_DATE = Date.new(-4713, 11, 24)
1689
+ MAX_DATE = Date.new(5874897, 12, 31)
1690
+ MIN_TIMESTAMP = Time.utc(-4713, 11, 24).freeze
1691
+ MAX_TIMESTAMP = (Time.utc(294277) - Rational(1, 1000000)).freeze
1692
+ TYPTYPE_METHOD_MAP = {
1693
+ 'c' => :schema_composite_type,
1694
+ 'e' => :schema_enum_type,
1695
+ 'r' => :schema_range_type,
1696
+ 'm' => :schema_multirange_type,
1697
+ }
1698
+ TYPTYPE_METHOD_MAP.default = :schema_column_type
1699
+ TYPTYPE_METHOD_MAP.freeze
1404
1700
  # The dataset used for parsing table schemas, using the pg_* system catalogs.
1405
1701
  def schema_parse_table(table_name, opts)
1406
1702
  m = output_identifier_meth(opts[:dataset])
1407
- oid = regclass_oid(table_name, opts)
1408
- ds = metadata_dataset.select{[
1409
- pg_attribute[:attname].as(:name),
1410
- SQL::Cast.new(pg_attribute[:atttypid], :integer).as(:oid),
1411
- SQL::Cast.new(basetype[:oid], :integer).as(:base_oid),
1412
- SQL::Function.new(:format_type, basetype[:oid], pg_type[:typtypmod]).as(:db_base_type),
1413
- SQL::Function.new(:format_type, pg_type[:oid], pg_attribute[:atttypmod]).as(:db_type),
1414
- SQL::Function.new(:pg_get_expr, pg_attrdef[:adbin], pg_class[:oid]).as(:default),
1415
- SQL::BooleanExpression.new(:NOT, pg_attribute[:attnotnull]).as(:allow_null),
1416
- SQL::Function.new(:COALESCE, SQL::BooleanExpression.from_value_pairs(pg_attribute[:attnum] => SQL::Function.new(:ANY, pg_index[:indkey])), false).as(:primary_key)]}.
1417
- from(:pg_class).
1418
- join(:pg_attribute, :attrelid=>:oid).
1419
- join(:pg_type, :oid=>:atttypid).
1420
- left_outer_join(Sequel[:pg_type].as(:basetype), :oid=>:typbasetype).
1421
- left_outer_join(:pg_attrdef, :adrelid=>Sequel[:pg_class][:oid], :adnum=>Sequel[:pg_attribute][:attnum]).
1422
- left_outer_join(:pg_index, :indrelid=>Sequel[:pg_class][:oid], :indisprimary=>true).
1423
- where{{pg_attribute[:attisdropped]=>false}}.
1424
- where{pg_attribute[:attnum] > 0}.
1425
- where{{pg_class[:oid]=>oid}}.
1426
- order{pg_attribute[:attnum]}
1427
-
1428
- if server_version > 100000
1429
- ds = ds.select_append{pg_attribute[:attidentity]}
1430
-
1431
- if server_version > 120000
1432
- ds = ds.select_append{Sequel.~(pg_attribute[:attgenerated]=>'').as(:generated)}
1433
- end
1434
- end
1435
1703
 
1436
- ds.map do |row|
1704
+ _schema_ds.where_all(Sequel[:pg_class][:oid]=>regclass_oid(table_name, opts)).map do |row|
1437
1705
  row[:default] = nil if blank_object?(row[:default])
1438
1706
  if row[:base_oid]
1439
1707
  row[:domain_oid] = row[:oid]
@@ -1444,11 +1712,33 @@ module Sequel
1444
1712
  row.delete(:base_oid)
1445
1713
  row.delete(:db_base_type)
1446
1714
  end
1447
- row[:type] = schema_column_type(row[:db_type])
1715
+
1716
+ db_type = row[:db_type]
1717
+ row[:type] = if row.delete(:is_array)
1718
+ schema_array_type(db_type)
1719
+ else
1720
+ send(TYPTYPE_METHOD_MAP[row.delete(:typtype)], db_type)
1721
+ end
1448
1722
  identity = row.delete(:attidentity)
1449
1723
  if row[:primary_key]
1450
1724
  row[:auto_increment] = !!(row[:default] =~ /\A(?:nextval)/i) || identity == 'a' || identity == 'd'
1451
1725
  end
1726
+
1727
+ # :nocov:
1728
+ if server_version >= 90600
1729
+ # :nocov:
1730
+ case row[:oid]
1731
+ when 1082
1732
+ row[:min_value] = MIN_DATE
1733
+ row[:max_value] = MAX_DATE
1734
+ when 1184, 1114
1735
+ if Sequel.datetime_class == Time
1736
+ row[:min_value] = MIN_TIMESTAMP
1737
+ row[:max_value] = MAX_TIMESTAMP
1738
+ end
1739
+ end
1740
+ end
1741
+
1452
1742
  [m.call(row.delete(:name)), row]
1453
1743
  end
1454
1744
  end
@@ -1516,7 +1806,9 @@ module Sequel
1516
1806
 
1517
1807
  # PostgreSQL 9.4+ supports views with check option.
1518
1808
  def view_with_check_option_support
1809
+ # :nocov:
1519
1810
  :local if server_version >= 90400
1811
+ # :nocov:
1520
1812
  end
1521
1813
  end
1522
1814
 
@@ -1528,7 +1820,7 @@ module Sequel
1528
1820
 
1529
1821
  Dataset.def_sql_method(self, :delete, [['if server_version >= 90100', %w'with delete from using where returning'], ['else', %w'delete from using where returning']])
1530
1822
  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']])
1531
- 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']])
1823
+ Dataset.def_sql_method(self, :select, [['if opts[:values]', %w'values compounds 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']])
1532
1824
  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']])
1533
1825
 
1534
1826
  # Return the results of an EXPLAIN ANALYZE query as a string
@@ -1554,8 +1846,6 @@ module Sequel
1554
1846
  literal_append(sql, args[0])
1555
1847
  sql << ' ' << op.to_s << ' '
1556
1848
  literal_append(sql, args[1])
1557
- sql << " ESCAPE "
1558
- literal_append(sql, "\\")
1559
1849
  sql << ')'
1560
1850
  else
1561
1851
  super
@@ -1580,6 +1870,12 @@ module Sequel
1580
1870
  clone(:disable_insert_returning=>true)
1581
1871
  end
1582
1872
 
1873
+ # Always return false when using VALUES
1874
+ def empty?
1875
+ return false if @opts[:values]
1876
+ super
1877
+ end
1878
+
1583
1879
  # Return the results of an EXPLAIN query as a string
1584
1880
  def explain(opts=OPTS)
1585
1881
  with_sql((opts[:analyze] ? 'EXPLAIN ANALYZE ' : 'EXPLAIN ') + select_sql).map(:'QUERY PLAN').join("\r\n")
@@ -1603,7 +1899,7 @@ module Sequel
1603
1899
  # :phrase :: Similar to :plain, but also adding an ILIKE filter to ensure that
1604
1900
  # returned rows also include the exact phrase used.
1605
1901
  # :rank :: Set to true to order by the rank, so that closer matches are returned first.
1606
- # :to_tsquery :: Can be set to :plain or :phrase to specify the function to use to
1902
+ # :to_tsquery :: Can be set to :plain, :phrase, or :websearch to specify the function to use to
1607
1903
  # convert the terms to a ts_query.
1608
1904
  # :tsquery :: Specifies the terms argument is already a valid SQL expression returning a
1609
1905
  # tsquery, and can be used directly in the query.
@@ -1623,6 +1919,8 @@ module Sequel
1623
1919
  query_func = case to_tsquery = opts[:to_tsquery]
1624
1920
  when :phrase, :plain
1625
1921
  :"#{to_tsquery}to_tsquery"
1922
+ when :websearch
1923
+ :"websearch_to_tsquery"
1626
1924
  else
1627
1925
  (opts[:phrase] || opts[:plain]) ? :plainto_tsquery : :to_tsquery
1628
1926
  end
@@ -1887,10 +2185,14 @@ module Sequel
1887
2185
  server_version >= 90500
1888
2186
  end
1889
2187
 
2188
+ # :nocov:
2189
+
1890
2190
  # PostgreSQL supports timezones in literal timestamps
1891
2191
  def supports_timestamp_timezones?
2192
+ # SEQUEL6: Remove
1892
2193
  true
1893
2194
  end
2195
+ # :nocov:
1894
2196
 
1895
2197
  # PostgreSQL 8.4+ supports WINDOW clause.
1896
2198
  def supports_window_clause?
@@ -1912,6 +2214,8 @@ module Sequel
1912
2214
  server_version >= 90000
1913
2215
  when :groups, :exclude
1914
2216
  server_version >= 110000
2217
+ else
2218
+ false
1915
2219
  end
1916
2220
  end
1917
2221
 
@@ -1954,12 +2258,10 @@ module Sequel
1954
2258
  # Otherwise, return an array of hashes.
1955
2259
  def _import(columns, values, opts=OPTS)
1956
2260
  if @opts[:returning]
1957
- statements = multi_insert_sql(columns, values)
1958
- trans_opts = Hash[opts]
1959
- trans_opts[:server] = @opts[:server]
1960
- @db.transaction(trans_opts) do
1961
- statements.map{|st| returning_fetch_rows(st)}
1962
- end.first.map{|v| v.length == 1 ? v.values.first : v}
2261
+ # no transaction: our multi_insert_sql_strategy should guarantee
2262
+ # that there's only ever a single statement.
2263
+ sql = multi_insert_sql(columns, values)[0]
2264
+ returning_fetch_rows(sql).map{|v| v.length == 1 ? v.values.first : v}
1963
2265
  elsif opts[:return] == :primary_key
1964
2266
  returning(insert_pk)._import(columns, values, opts)
1965
2267
  else
@@ -1999,12 +2301,22 @@ module Sequel
1999
2301
  "TRUNCATE TABLE#{' ONLY' if to[:only]} #{table}#{' RESTART IDENTITY' if to[:restart]}#{' CASCADE' if to[:cascade]}"
2000
2302
  end
2001
2303
 
2304
+ # Use from_self for aggregate dataset using VALUES.
2305
+ def aggreate_dataset_use_from_self?
2306
+ super || @opts[:values]
2307
+ end
2308
+
2002
2309
  # Allow truncation of multiple source tables.
2003
2310
  def check_truncation_allowed!
2004
2311
  raise(InvalidOperation, "Grouped datasets cannot be truncated") if opts[:group]
2005
2312
  raise(InvalidOperation, "Joined datasets cannot be truncated") if opts[:join]
2006
2313
  end
2007
2314
 
2315
+ # The strftime format to use when literalizing the time.
2316
+ def default_timestamp_format
2317
+ "'%Y-%m-%d %H:%M:%S.%6N%z'"
2318
+ end
2319
+
2008
2320
  # Only include the primary table in the main delete clause
2009
2321
  def delete_from_sql(sql)
2010
2322
  sql << ' FROM '
@@ -2058,12 +2370,11 @@ module Sequel
2058
2370
 
2059
2371
  # Return the primary key to use for RETURNING in an INSERT statement
2060
2372
  def insert_pk
2061
- if (f = opts[:from]) && !f.empty?
2062
- case t = f.first
2063
- when Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier
2064
- if pk = db.primary_key(t)
2065
- Sequel::SQL::Identifier.new(pk)
2066
- end
2373
+ (f = opts[:from]) && !f.empty? && (t = f.first)
2374
+ case t
2375
+ when Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier
2376
+ if pk = db.primary_key(t)
2377
+ Sequel::SQL::Identifier.new(pk)
2067
2378
  end
2068
2379
  end
2069
2380
  end
@@ -2124,6 +2435,22 @@ module Sequel
2124
2435
  end
2125
2436
  end
2126
2437
 
2438
+ # Handle Ruby integers outside PostgreSQL bigint range specially.
2439
+ def literal_integer(v)
2440
+ if v > 9223372036854775807 || v < -9223372036854775808
2441
+ literal_integer_outside_bigint_range(v)
2442
+ else
2443
+ v.to_s
2444
+ end
2445
+ end
2446
+
2447
+ # Raise IntegerOutsideBigintRange when attempting to literalize Ruby integer
2448
+ # outside PostgreSQL bigint range, so PostgreSQL doesn't treat
2449
+ # the value as numeric.
2450
+ def literal_integer_outside_bigint_range(v)
2451
+ raise IntegerOutsideBigintRange, "attempt to literalize Ruby integer outside PostgreSQL bigint range: #{v}"
2452
+ end
2453
+
2127
2454
  # Assume that SQL standard quoting is on, per Sequel's defaults
2128
2455
  def literal_string_append(sql, v)
2129
2456
  sql << "'" << v.gsub("'", "''") << "'"
@@ -2222,7 +2549,10 @@ module Sequel
2222
2549
  # Support PostgreSQL 14+ CTE SEARCH/CYCLE clauses
2223
2550
  def select_with_sql_cte(sql, cte)
2224
2551
  super
2552
+ select_with_sql_cte_search_cycle(sql, cte)
2553
+ end
2225
2554
 
2555
+ def select_with_sql_cte_search_cycle(sql, cte)
2226
2556
  if search_opts = cte[:search]
2227
2557
  sql << if search_opts[:type] == :breadth
2228
2558
  " SEARCH BREADTH FIRST BY "