sequel 5.58.0 → 5.78.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 (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 "