sequel 5.61.0 → 5.63.0

Sign up to get free protection for your applications and to get access to all the features.
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