sequel 5.61.0 → 5.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +46 -0
  3. data/README.rdoc +20 -19
  4. data/doc/advanced_associations.rdoc +13 -13
  5. data/doc/association_basics.rdoc +21 -15
  6. data/doc/cheat_sheet.rdoc +3 -3
  7. data/doc/model_hooks.rdoc +1 -1
  8. data/doc/object_model.rdoc +8 -8
  9. data/doc/opening_databases.rdoc +4 -4
  10. data/doc/postgresql.rdoc +8 -8
  11. data/doc/querying.rdoc +1 -1
  12. data/doc/release_notes/5.62.0.txt +132 -0
  13. data/doc/release_notes/5.63.0.txt +33 -0
  14. data/doc/schema_modification.rdoc +1 -1
  15. data/doc/security.rdoc +9 -9
  16. data/doc/sql.rdoc +13 -13
  17. data/doc/testing.rdoc +13 -11
  18. data/doc/transactions.rdoc +6 -6
  19. data/doc/virtual_rows.rdoc +1 -1
  20. data/lib/sequel/adapters/postgres.rb +4 -0
  21. data/lib/sequel/adapters/shared/access.rb +9 -1
  22. data/lib/sequel/adapters/shared/mssql.rb +9 -5
  23. data/lib/sequel/adapters/shared/mysql.rb +7 -0
  24. data/lib/sequel/adapters/shared/oracle.rb +7 -0
  25. data/lib/sequel/adapters/shared/postgres.rb +275 -152
  26. data/lib/sequel/adapters/shared/sqlanywhere.rb +7 -0
  27. data/lib/sequel/adapters/shared/sqlite.rb +5 -0
  28. data/lib/sequel/connection_pool/sharded_threaded.rb +5 -1
  29. data/lib/sequel/connection_pool/threaded.rb +8 -8
  30. data/lib/sequel/connection_pool/timed_queue.rb +257 -0
  31. data/lib/sequel/connection_pool.rb +47 -30
  32. data/lib/sequel/database/connecting.rb +24 -0
  33. data/lib/sequel/database/misc.rb +8 -2
  34. data/lib/sequel/database/query.rb +37 -0
  35. data/lib/sequel/dataset/actions.rb +56 -11
  36. data/lib/sequel/dataset/features.rb +5 -0
  37. data/lib/sequel/dataset/misc.rb +1 -1
  38. data/lib/sequel/dataset/query.rb +9 -9
  39. data/lib/sequel/dataset/sql.rb +5 -1
  40. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  41. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  42. data/lib/sequel/extensions/async_thread_pool.rb +11 -11
  43. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  44. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  45. data/lib/sequel/extensions/date_arithmetic.rb +1 -1
  46. data/lib/sequel/extensions/migration.rb +1 -1
  47. data/lib/sequel/extensions/named_timezones.rb +21 -5
  48. data/lib/sequel/extensions/pg_array.rb +22 -3
  49. data/lib/sequel/extensions/pg_auto_parameterize.rb +478 -0
  50. data/lib/sequel/extensions/pg_extended_date_support.rb +12 -0
  51. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  52. data/lib/sequel/extensions/pg_hstore.rb +5 -0
  53. data/lib/sequel/extensions/pg_inet.rb +9 -10
  54. data/lib/sequel/extensions/pg_interval.rb +9 -10
  55. data/lib/sequel/extensions/pg_json.rb +10 -10
  56. data/lib/sequel/extensions/pg_multirange.rb +5 -10
  57. data/lib/sequel/extensions/pg_range.rb +5 -10
  58. data/lib/sequel/extensions/pg_row.rb +18 -13
  59. data/lib/sequel/model/associations.rb +9 -4
  60. data/lib/sequel/model/base.rb +6 -5
  61. data/lib/sequel/plugins/auto_validations.rb +53 -15
  62. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  63. data/lib/sequel/plugins/composition.rb +2 -2
  64. data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
  65. data/lib/sequel/plugins/dirty.rb +1 -1
  66. data/lib/sequel/plugins/finder.rb +3 -1
  67. data/lib/sequel/plugins/nested_attributes.rb +4 -4
  68. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +1 -1
  69. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  70. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  71. data/lib/sequel/plugins/sql_comments.rb +1 -1
  72. data/lib/sequel/plugins/tactical_eager_loading.rb +14 -14
  73. data/lib/sequel/plugins/validate_associated.rb +22 -12
  74. data/lib/sequel/plugins/validation_helpers.rb +20 -0
  75. data/lib/sequel/version.rb +1 -1
  76. metadata +14 -6
@@ -65,7 +65,7 @@ module Sequel
65
65
  Sequel.synchronize{EXTENSIONS[ext] = block}
66
66
  end
67
67
 
68
- # On Ruby 2.4+, use clone(:freeze=>false) to create clones, because
68
+ # On Ruby 2.4+, use clone(freeze: false) to create clones, because
69
69
  # we use true freezing in that case, and we need to modify the opts
70
70
  # in the frozen copy.
71
71
  #
@@ -116,7 +116,7 @@ module Sequel
116
116
  # DB[:items].order(:id).distinct(:id) # SQL: SELECT DISTINCT ON (id) * FROM items ORDER BY id
117
117
  # DB[:items].order(:id).distinct{func(:id)} # SQL: SELECT DISTINCT ON (func(id)) * FROM items ORDER BY id
118
118
  #
119
- # There is support for emualting the DISTINCT ON support in MySQL, but it
119
+ # There is support for emulating the DISTINCT ON support in MySQL, but it
120
120
  # does not support the ORDER of the dataset, and also doesn't work in many
121
121
  # cases if the ONLY_FULL_GROUP_BY sql_mode is used, which is the default on
122
122
  # MySQL 5.7.5+.
@@ -787,7 +787,7 @@ module Sequel
787
787
  # DB[:items].order(Sequel.lit('a + b')) # SELECT * FROM items ORDER BY a + b
788
788
  # DB[:items].order(Sequel[:a] + :b) # SELECT * FROM items ORDER BY (a + b)
789
789
  # DB[:items].order(Sequel.desc(:name)) # SELECT * FROM items ORDER BY name DESC
790
- # DB[:items].order(Sequel.asc(:name, :nulls=>:last)) # SELECT * FROM items ORDER BY name ASC NULLS LAST
790
+ # DB[:items].order(Sequel.asc(:name, nulls: :last)) # SELECT * FROM items ORDER BY name ASC NULLS LAST
791
791
  # DB[:items].order{sum(name).desc} # SELECT * FROM items ORDER BY sum(name) DESC
792
792
  # DB[:items].order(nil) # SELECT * FROM items
793
793
  def order(*columns, &block)
@@ -857,13 +857,13 @@ module Sequel
857
857
  # DB[:items].returning(nil) # RETURNING NULL
858
858
  # DB[:items].returning(:id, :name) # RETURNING id, name
859
859
  #
860
- # DB[:items].returning.insert(:a=>1) do |hash|
860
+ # DB[:items].returning.insert(a: 1) do |hash|
861
861
  # # hash for each row inserted, with values for all columns
862
862
  # end
863
- # DB[:items].returning.update(:a=>1) do |hash|
863
+ # DB[:items].returning.update(a: 1) do |hash|
864
864
  # # hash for each row updated, with values for all columns
865
865
  # end
866
- # DB[:items].returning.delete(:a=>1) do |hash|
866
+ # DB[:items].returning.delete(a: 1) do |hash|
867
867
  # # hash for each row deleted, with values for all columns
868
868
  # end
869
869
  def returning(*values)
@@ -1102,7 +1102,7 @@ module Sequel
1102
1102
  # referenced in window functions. See Sequel::SQL::Window for a list of
1103
1103
  # options that can be passed in. Example:
1104
1104
  #
1105
- # DB[:items].window(:w, :partition=>:c1, :order=>:c2)
1105
+ # DB[:items].window(:w, partition: :c1, order: :c2)
1106
1106
  # # SELECT * FROM items WINDOW w AS (PARTITION BY c1 ORDER BY c2)
1107
1107
  def window(name, opts)
1108
1108
  clone(:window=>((@opts[:window]||EMPTY_ARRAY) + [[name, SQL::Window.new(opts)].freeze]).freeze)
@@ -1163,7 +1163,7 @@ module Sequel
1163
1163
  # DB[:t].with_recursive(:t,
1164
1164
  # DB[:i1].select(:id, :parent_id).where(parent_id: nil),
1165
1165
  # DB[:i1].join(:t, id: :parent_id).select(Sequel[:i1][:id], Sequel[:i1][:parent_id]),
1166
- # :args=>[:id, :parent_id])
1166
+ # args: [:id, :parent_id])
1167
1167
  #
1168
1168
  # # WITH RECURSIVE t(id, parent_id) AS (
1169
1169
  # # SELECT id, parent_id FROM i1 WHERE (parent_id IS NULL)
@@ -1241,7 +1241,7 @@ module Sequel
1241
1241
  #
1242
1242
  # You can also provide a method name and arguments to call to get the SQL:
1243
1243
  #
1244
- # DB[:items].with_sql(:insert_sql, :b=>1) # INSERT INTO items (b) VALUES (1)
1244
+ # DB[:items].with_sql(:insert_sql, b: 1) # INSERT INTO items (b) VALUES (1)
1245
1245
  #
1246
1246
  # Note that datasets that specify custom SQL using this method will generally
1247
1247
  # ignore future dataset methods that modify the SQL used, as specifying custom SQL
@@ -1725,7 +1725,7 @@ module Sequel
1725
1725
  # Append literalization of the subselect to SQL string.
1726
1726
  def subselect_sql_append(sql, ds)
1727
1727
  sds = subselect_sql_dataset(sql, ds)
1728
- sds.sql
1728
+ subselect_sql_append_sql(sql, sds)
1729
1729
  unless sds.send(:cache_sql?)
1730
1730
  # If subquery dataset does not allow caching SQL,
1731
1731
  # then this dataset should not allow caching SQL.
@@ -1737,6 +1737,10 @@ module Sequel
1737
1737
  ds.clone(:append_sql=>sql)
1738
1738
  end
1739
1739
 
1740
+ def subselect_sql_append_sql(sql, ds)
1741
+ ds.sql
1742
+ end
1743
+
1740
1744
  # The number of decimal digits of precision to use in timestamps.
1741
1745
  def timestamp_precision
1742
1746
  supports_timestamp_usecs? ? 6 : 0
@@ -23,18 +23,6 @@ module Sequel
23
23
  super
24
24
  end
25
25
  end
26
-
27
- private
28
-
29
- # Handle Sequel::Model instances in bound variable arrays.
30
- def bound_variable_array(arg)
31
- case arg
32
- when Sequel::Model
33
- "\"(#{arg.values.values_at(*arg.columns).map{|v| bound_variable_array(v)}.join(',').gsub(/("|\\)/, '\\\\\1')})\""
34
- else
35
- super
36
- end
37
- end
38
26
  end
39
27
  end
40
28
  end
@@ -39,7 +39,7 @@ module Sequel
39
39
 
40
40
  # Hash of the maximum size of the value for each column
41
41
  def self.column_sizes(records, columns) # :nodoc:
42
- sizes = Hash.new {0}
42
+ sizes = Hash.new(0)
43
43
  columns.each do |c|
44
44
  sizes[c] = c.to_s.size
45
45
  end
@@ -5,9 +5,9 @@
5
5
  # code
6
6
  #
7
7
  # DB.extension :async_thread_pool
8
- # foos = DB[:foos].async.where{:name=>'A'..'M'}.all
8
+ # foos = DB[:foos].async.where(name: 'A'..'M').all
9
9
  # bar_names = DB[:bar].async.select_order_map(:name)
10
- # baz_1 = DB[:bazes].async.first(:id=>1)
10
+ # baz_1 = DB[:bazes].async.first(id: 1)
11
11
  #
12
12
  # All 3 queries will be run in separate threads. +foos+, +bar_names+
13
13
  # and +baz_1+ will be proxy objects. Calling a method on the proxy
@@ -15,9 +15,9 @@
15
15
  # of calling that method on the result of the query method. For example,
16
16
  # if you run:
17
17
  #
18
- # foos = DB[:foos].async.where{:name=>'A'..'M'}.all
18
+ # foos = DB[:foos].async.where(name: 'A'..'M').all
19
19
  # bar_names = DB[:bars].async.select_order_map(:name)
20
- # baz_1 = DB[:bazes].async.first(:id=>1)
20
+ # baz_1 = DB[:bazes].async.first(id: 1)
21
21
  # sleep(1)
22
22
  # foos.size
23
23
  # bar_names.first
@@ -26,9 +26,9 @@
26
26
  # These three queries will generally be run concurrently in separate
27
27
  # threads. If you instead run:
28
28
  #
29
- # DB[:foos].async.where{:name=>'A'..'M'}.all.size
29
+ # DB[:foos].async.where(name: 'A'..'M').all.size
30
30
  # DB[:bars].async.select_order_map(:name).first
31
- # DB[:bazes].async.first(:id=>1).name
31
+ # DB[:bazes].async.first(id: 1).name
32
32
  #
33
33
  # Then will run each query sequentially, since you need the result of
34
34
  # one query before running the next query. The queries will still be
@@ -37,11 +37,11 @@
37
37
  # What is run in the separate thread is the entire method call that
38
38
  # returns results. So with the original example:
39
39
  #
40
- # foos = DB[:foos].async.where{:name=>'A'..'M'}.all
40
+ # foos = DB[:foos].async.where(name: 'A'..'M').all
41
41
  # bar_names = DB[:bars].async.select_order_map(:name)
42
- # baz_1 = DB[:bazes].async.first(:id=>1)
42
+ # baz_1 = DB[:bazes].async.first(id: 1)
43
43
  #
44
- # The +all+, <tt>select_order_map(:name)</tt>, and <tt>first(:id=>1)</tt>
44
+ # The +all+, <tt>select_order_map(:name)</tt>, and <tt>first(id: 1)</tt>
45
45
  # calls are run in separate threads. If a block is passed to a method
46
46
  # such as +all+ or +each+, the block is also run in that thread. If you
47
47
  # have code such as:
@@ -156,10 +156,10 @@
156
156
  # so that the query will run in the current thread instead of waiting
157
157
  # for an async thread to become available. With the following code:
158
158
  #
159
- # foos = DB[:foos].async.where{:name=>'A'..'M'}.all
159
+ # foos = DB[:foos].async.where(name: 'A'..'M').all
160
160
  # bar_names = DB[:bar].async.select_order_map(:name)
161
161
  # if foos.length > 4
162
- # baz_1 = DB[:bazes].async.first(:id=>1)
162
+ # baz_1 = DB[:bazes].async.first(id: 1)
163
163
  # end
164
164
  #
165
165
  # Whether you need the +baz_1+ variable depends on the value of foos.
@@ -22,7 +22,7 @@
22
22
  #
23
23
  # Named placeholders can also be used with a hash:
24
24
  #
25
- # ds.where("name > :a", :a=>"A")
25
+ # ds.where("name > :a", a: "A")
26
26
  # # SELECT * FROM table WHERE (name > 'A')
27
27
  #
28
28
  # This extension also allows the use of a plain string passed to Dataset#update:
@@ -126,7 +126,7 @@
126
126
  # be emulated by dropping the table and recreating it with the constraints.
127
127
  # If you want to use this plugin on SQLite with an alter_table block,
128
128
  # you should drop all constraint validation metadata using
129
- # <tt>drop_constraint_validations_for(:table=>'table')</tt>, and then
129
+ # <tt>drop_constraint_validations_for(table: 'table')</tt>, and then
130
130
  # readd all constraints you want to use inside the alter table block,
131
131
  # making no other changes inside the alter_table block.
132
132
  #
@@ -25,7 +25,7 @@
25
25
  # By default, values are casted to the generic timestamp type for the
26
26
  # database. You can override the cast type using the :cast option:
27
27
  #
28
- # add = Sequel.date_add(:date_column, {years: 1, months: 2, days: 3}, :cast=>:timestamptz)
28
+ # add = Sequel.date_add(:date_column, {years: 1, months: 2, days: 3}, cast: :timestamptz)
29
29
  #
30
30
  # These expressions can be used in your datasets, or anywhere else that
31
31
  # Sequel expressions are allowed:
@@ -377,7 +377,7 @@ module Sequel
377
377
  # Raise a NotCurrentError unless the migrator is current, takes the same
378
378
  # arguments as #run.
379
379
  def self.check_current(*args)
380
- raise(NotCurrentError, 'migrator is not current') unless is_current?(*args)
380
+ raise(NotCurrentError, 'current migration version does not match latest available version') unless is_current?(*args)
381
381
  end
382
382
 
383
383
  # Return whether the migrator is current (i.e. it does not need to make
@@ -68,6 +68,10 @@ module Sequel
68
68
  private
69
69
 
70
70
  if RUBY_VERSION >= '2.6'
71
+ # Whether Time.at with :nsec and :in is broken. True on JRuby < 9.3.9.0.
72
+ BROKEN_TIME_AT_WITH_NSEC = defined?(JRUBY_VERSION) && (JRUBY_VERSION < '9.3' || (JRUBY_VERSION < '9.4' && JRUBY_VERSION.split('.')[2].to_i < 9))
73
+ private_constant :BROKEN_TIME_AT_WITH_NSEC
74
+
71
75
  # Convert the given input Time (which must be in UTC) to the given input timezone,
72
76
  # which should be a TZInfo::Timezone instance.
73
77
  def convert_input_time_other(v, input_timezone)
@@ -76,33 +80,45 @@ module Sequel
76
80
  raise unless disamb = tzinfo_disambiguator_for(v)
77
81
  period = input_timezone.period_for_local(v, &disamb)
78
82
  offset = period.utc_total_offset
79
- Time.at(v.to_i - offset, :in => input_timezone)
83
+ # :nocov:
84
+ if BROKEN_TIME_AT_WITH_NSEC
85
+ Time.at(v.to_i - offset, :in => input_timezone) + v.nsec/1000000000.0
86
+ # :nocov:
87
+ else
88
+ Time.at(v.to_i - offset, v.nsec, :nsec, :in => input_timezone)
89
+ end
80
90
  end
81
91
 
82
92
  # Convert the given input Time to the given output timezone,
83
93
  # which should be a TZInfo::Timezone instance.
84
94
  def convert_output_time_other(v, output_timezone)
85
- Time.at(v.to_i, :in => output_timezone)
95
+ # :nocov:
96
+ if BROKEN_TIME_AT_WITH_NSEC
97
+ Time.at(v.to_i, :in => output_timezone) + v.nsec/1000000000.0
98
+ # :nocov:
99
+ else
100
+ Time.at(v.to_i, v.nsec, :nsec, :in => output_timezone)
101
+ end
86
102
  end
87
103
  # :nodoc:
88
104
  # :nocov:
89
105
  else
90
106
  def convert_input_time_other(v, input_timezone)
91
107
  local_offset = input_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset
92
- Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i
108
+ Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i + v.nsec/1000000000.0
93
109
  end
94
110
 
95
111
  if defined?(TZInfo::VERSION) && TZInfo::VERSION > '2'
96
112
  def convert_output_time_other(v, output_timezone)
97
113
  v = output_timezone.utc_to_local(v.getutc)
98
114
  local_offset = output_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset
99
- Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i + local_offset
115
+ Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i + v.nsec/1000000000.0 + local_offset
100
116
  end
101
117
  else
102
118
  def convert_output_time_other(v, output_timezone)
103
119
  v = output_timezone.utc_to_local(v.getutc)
104
120
  local_offset = output_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset
105
- Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i
121
+ Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i + v.nsec/1000000000.0
106
122
  end
107
123
  end
108
124
  # :nodoc:
@@ -228,16 +228,27 @@ module Sequel
228
228
  when Array
229
229
  "{#{a.map{|i| bound_variable_array(i)}.join(',')}}"
230
230
  when Sequel::SQL::Blob
231
- "\"#{literal(a)[BLOB_RANGE].gsub("''", "'").gsub(/("|\\)/, '\\\\\1')}\""
231
+ bound_variable_array_string(literal(a)[BLOB_RANGE].gsub("''", "'"))
232
232
  when Sequel::LiteralString
233
233
  a
234
234
  when String
235
- "\"#{a.gsub(/("|\\)/, '\\\\\1')}\""
235
+ bound_variable_array_string(a)
236
236
  else
237
- literal(a)
237
+ if (s = bound_variable_arg(a, nil)).is_a?(String)
238
+ bound_variable_array_string(s)
239
+ else
240
+ literal(a)
241
+ end
238
242
  end
239
243
  end
240
244
 
245
+ # Escape strings used as array members in bound variables. Most complex
246
+ # will create a regular string with bound_variable_arg, and then use this
247
+ # escaping to format it as an array member.
248
+ def bound_variable_array_string(s)
249
+ "\"#{s.gsub(/("|\\)/, '\\\\\1')}\""
250
+ end
251
+
241
252
  # Look into both the current database's array schema types and the global
242
253
  # array schema types to get the type symbol for the given database type
243
254
  # string.
@@ -457,6 +468,14 @@ module Sequel
457
468
  end
458
469
  end
459
470
 
471
+ # Allow automatic parameterization of the receiver if all elements can be
472
+ # can be automatically parameterized.
473
+ def sequel_auto_param_type(ds)
474
+ if array_type && all?{|x| nil == x || ds.send(:auto_param_type, x)}
475
+ "::#{array_type}[]"
476
+ end
477
+ end
478
+
460
479
  private
461
480
 
462
481
  # Recursive method that handles multi-dimensional