sequel 5.61.0 → 5.62.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +32 -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/schema_modification.rdoc +1 -1
  14. data/doc/security.rdoc +9 -9
  15. data/doc/sql.rdoc +13 -13
  16. data/doc/testing.rdoc +13 -11
  17. data/doc/transactions.rdoc +6 -6
  18. data/doc/virtual_rows.rdoc +1 -1
  19. data/lib/sequel/adapters/postgres.rb +4 -0
  20. data/lib/sequel/adapters/shared/access.rb +9 -1
  21. data/lib/sequel/adapters/shared/mssql.rb +9 -5
  22. data/lib/sequel/adapters/shared/mysql.rb +7 -0
  23. data/lib/sequel/adapters/shared/oracle.rb +7 -0
  24. data/lib/sequel/adapters/shared/postgres.rb +275 -152
  25. data/lib/sequel/adapters/shared/sqlanywhere.rb +7 -0
  26. data/lib/sequel/adapters/shared/sqlite.rb +5 -0
  27. data/lib/sequel/connection_pool.rb +42 -28
  28. data/lib/sequel/database/connecting.rb +24 -0
  29. data/lib/sequel/database/misc.rb +8 -2
  30. data/lib/sequel/database/query.rb +37 -0
  31. data/lib/sequel/dataset/actions.rb +31 -11
  32. data/lib/sequel/dataset/features.rb +5 -0
  33. data/lib/sequel/dataset/misc.rb +1 -1
  34. data/lib/sequel/dataset/query.rb +9 -9
  35. data/lib/sequel/dataset/sql.rb +5 -1
  36. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  37. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  38. data/lib/sequel/extensions/async_thread_pool.rb +11 -11
  39. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  40. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  41. data/lib/sequel/extensions/date_arithmetic.rb +1 -1
  42. data/lib/sequel/extensions/migration.rb +1 -1
  43. data/lib/sequel/extensions/named_timezones.rb +17 -5
  44. data/lib/sequel/extensions/pg_array.rb +22 -3
  45. data/lib/sequel/extensions/pg_auto_parameterize.rb +478 -0
  46. data/lib/sequel/extensions/pg_extended_date_support.rb +12 -0
  47. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  48. data/lib/sequel/extensions/pg_hstore.rb +5 -0
  49. data/lib/sequel/extensions/pg_inet.rb +9 -10
  50. data/lib/sequel/extensions/pg_interval.rb +9 -10
  51. data/lib/sequel/extensions/pg_json.rb +10 -10
  52. data/lib/sequel/extensions/pg_multirange.rb +5 -10
  53. data/lib/sequel/extensions/pg_range.rb +5 -10
  54. data/lib/sequel/extensions/pg_row.rb +18 -13
  55. data/lib/sequel/model/associations.rb +7 -2
  56. data/lib/sequel/model/base.rb +6 -5
  57. data/lib/sequel/plugins/auto_validations.rb +53 -15
  58. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  59. data/lib/sequel/plugins/composition.rb +2 -2
  60. data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
  61. data/lib/sequel/plugins/dirty.rb +1 -1
  62. data/lib/sequel/plugins/finder.rb +3 -1
  63. data/lib/sequel/plugins/nested_attributes.rb +4 -4
  64. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +1 -1
  65. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  66. data/lib/sequel/plugins/sql_comments.rb +1 -1
  67. data/lib/sequel/plugins/validation_helpers.rb +20 -0
  68. data/lib/sequel/version.rb +1 -1
  69. metadata +10 -5
@@ -241,6 +241,30 @@ module Sequel
241
241
  pool.servers
242
242
  end
243
243
 
244
+ # Connect to the given server/shard. Handles database-generic post-connection
245
+ # setup not handled by #connect, using the :after_connect and :connect_sqls
246
+ # options.
247
+ def new_connection(server)
248
+ conn = connect(server)
249
+ opts = server_opts(server)
250
+
251
+ if ac = opts[:after_connect]
252
+ if ac.arity == 2
253
+ ac.call(conn, server)
254
+ else
255
+ ac.call(conn)
256
+ end
257
+ end
258
+
259
+ if cs = opts[:connect_sqls]
260
+ cs.each do |sql|
261
+ log_connection_execute(conn, sql)
262
+ end
263
+ end
264
+
265
+ conn
266
+ end
267
+
244
268
  # Returns true if the database is using a single-threaded connection pool.
245
269
  def single_threaded?
246
270
  @single_threaded
@@ -100,10 +100,14 @@ module Sequel
100
100
  # options hash.
101
101
  #
102
102
  # Accepts the following options:
103
+ # :after_connect :: A callable object called after each new connection is made, with the
104
+ # connection object (and server argument if the callable accepts 2 arguments),
105
+ # useful for customizations that you want to apply to all connections.
103
106
  # :before_preconnect :: Callable that runs after extensions from :preconnect_extensions are loaded,
104
107
  # but before any connections are created.
105
108
  # :cache_schema :: Whether schema should be cached for this Database instance
106
109
  # :check_string_typecast_bytesize :: Whether to check the bytesize of strings before typecasting.
110
+ # :connect_sqls :: An array of sql strings to execute on each new connection, after :after_connect runs.
107
111
  # :default_string_column_size :: The default size of string columns, 255 by default.
108
112
  # :extensions :: Extensions to load into this Database instance. Can be a symbol, array of symbols,
109
113
  # or string with extensions separated by columns. These extensions are loaded after
@@ -126,9 +130,11 @@ module Sequel
126
130
  # :single_threaded :: Whether to use a single-threaded connection pool.
127
131
  # :sql_log_level :: Method to use to log SQL to a logger, :info by default.
128
132
  #
133
+ # For sharded connection pools, :after_connect and :connect_sqls can be specified per-shard.
134
+ #
129
135
  # All options given are also passed to the connection pool. Additional options respected by
130
- # the connection pool are :after_connect, :connect_sqls, :max_connections, :pool_timeout,
131
- # :servers, and :servers_hash. See the connection pool documentation for details.
136
+ # the connection pool are :max_connections, :pool_timeout, :servers, and :servers_hash. See the
137
+ # connection pool documentation for details.
132
138
  def initialize(opts = OPTS)
133
139
  @opts ||= opts
134
140
  @opts = connection_pool_default_options.merge(@opts)
@@ -175,6 +175,9 @@ module Sequel
175
175
  if !c[:max_length] && c[:type] == :string && (max_length = column_schema_max_length(c[:db_type]))
176
176
  c[:max_length] = max_length
177
177
  end
178
+ if !c[:max_value] && !c[:min_value] && c[:type] == :integer && (min_max = column_schema_integer_min_max_values(c[:db_type]))
179
+ c[:min_value], c[:max_value] = min_max
180
+ end
178
181
  end
179
182
  schema_post_process(cols)
180
183
 
@@ -272,6 +275,40 @@ module Sequel
272
275
  column_schema_default_to_ruby_value(default, type) rescue nil
273
276
  end
274
277
 
278
+ INTEGER1_MIN_MAX = [-128, 127].freeze
279
+ INTEGER2_MIN_MAX = [-32768, 32767].freeze
280
+ INTEGER3_MIN_MAX = [-8388608, 8388607].freeze
281
+ INTEGER4_MIN_MAX = [-2147483648, 2147483647].freeze
282
+ INTEGER8_MIN_MAX = [-9223372036854775808, 9223372036854775807].freeze
283
+ UNSIGNED_INTEGER1_MIN_MAX = [0, 255].freeze
284
+ UNSIGNED_INTEGER2_MIN_MAX = [0, 65535].freeze
285
+ UNSIGNED_INTEGER3_MIN_MAX = [0, 16777215].freeze
286
+ UNSIGNED_INTEGER4_MIN_MAX = [0, 4294967295].freeze
287
+ UNSIGNED_INTEGER8_MIN_MAX = [0, 18446744073709551615].freeze
288
+
289
+ # Look at the db_type and guess the minimum and maximum integer values for
290
+ # the column.
291
+ def column_schema_integer_min_max_values(db_type)
292
+ unsigned = /unsigned/i =~ db_type
293
+ case db_type
294
+ when /big|int8/i
295
+ unsigned ? UNSIGNED_INTEGER8_MIN_MAX : INTEGER8_MIN_MAX
296
+ when /medium/i
297
+ unsigned ? UNSIGNED_INTEGER3_MIN_MAX : INTEGER3_MIN_MAX
298
+ when /small|int2/i
299
+ unsigned ? UNSIGNED_INTEGER2_MIN_MAX : INTEGER2_MIN_MAX
300
+ when /tiny/i
301
+ (unsigned || column_schema_tinyint_type_is_unsigned?) ? UNSIGNED_INTEGER1_MIN_MAX : INTEGER1_MIN_MAX
302
+ else
303
+ unsigned ? UNSIGNED_INTEGER4_MIN_MAX : INTEGER4_MIN_MAX
304
+ end
305
+ end
306
+
307
+ # Whether the tinyint type (if supported by the database) is unsigned by default.
308
+ def column_schema_tinyint_type_is_unsigned?
309
+ false
310
+ end
311
+
275
312
  # Look at the db_type and guess the maximum length of the column.
276
313
  # This assumes types such as varchar(255).
277
314
  def column_schema_max_length(db_type)
@@ -313,14 +313,18 @@ module Sequel
313
313
 
314
314
  # Inserts multiple records into the associated table. This method can be
315
315
  # used to efficiently insert a large number of records into a table in a
316
- # single query if the database supports it. Inserts
317
- # are automatically wrapped in a transaction.
316
+ # single query if the database supports it. Inserts are automatically
317
+ # wrapped in a transaction if necessary.
318
318
  #
319
319
  # This method is called with a columns array and an array of value arrays:
320
320
  #
321
321
  # DB[:table].import([:x, :y], [[1, 2], [3, 4]])
322
322
  # # INSERT INTO table (x, y) VALUES (1, 2)
323
- # # INSERT INTO table (x, y) VALUES (3, 4)
323
+ # # INSERT INTO table (x, y) VALUES (3, 4)
324
+ #
325
+ # or, if the database supports it:
326
+ #
327
+ # # INSERT INTO table (x, y) VALUES (1, 2), (3, 4)
324
328
  #
325
329
  # This method also accepts a dataset instead of an array of value arrays:
326
330
  #
@@ -328,9 +332,13 @@ module Sequel
328
332
  # # INSERT INTO table (x, y) SELECT a, b FROM table2
329
333
  #
330
334
  # Options:
331
- # :commit_every :: Open a new transaction for every given number of records.
332
- # For example, if you provide a value of 50, will commit
333
- # after every 50 records.
335
+ # :commit_every :: Open a new transaction for every given number of
336
+ # records. For example, if you provide a value of 50,
337
+ # will commit after every 50 records. When a
338
+ # transaction is not required, this option controls
339
+ # the maximum number of values to insert with a single
340
+ # statement; it does not force the use of a
341
+ # transaction.
334
342
  # :return :: When this is set to :primary_key, returns an array of
335
343
  # autoincremented primary key values for the rows inserted.
336
344
  # This does not have an effect if +values+ is a Dataset.
@@ -576,7 +584,7 @@ module Sequel
576
584
  # # SELECT * FROM table ORDER BY id LIMIT 1000 OFFSET 1000
577
585
  # # ...
578
586
  #
579
- # DB[:table].order(:id).paged_each(:rows_per_fetch=>100){|row| }
587
+ # DB[:table].order(:id).paged_each(rows_per_fetch: 100){|row| }
580
588
  # # SELECT * FROM table ORDER BY id LIMIT 100
581
589
  # # SELECT * FROM table ORDER BY id LIMIT 100 OFFSET 100
582
590
  # # ...
@@ -1023,18 +1031,19 @@ module Sequel
1023
1031
 
1024
1032
  # Internals of #import. If primary key values are requested, use
1025
1033
  # separate insert commands for each row. Otherwise, call #multi_insert_sql
1026
- # and execute each statement it gives separately.
1034
+ # and execute each statement it gives separately. A transaction is only used
1035
+ # if there are multiple statements to execute.
1027
1036
  def _import(columns, values, opts)
1028
1037
  trans_opts = Hash[opts]
1029
1038
  trans_opts[:server] = @opts[:server]
1030
1039
  if opts[:return] == :primary_key
1031
- @db.transaction(trans_opts){values.map{|v| insert(columns, v)}}
1040
+ _import_transaction(values, trans_opts){values.map{|v| insert(columns, v)}}
1032
1041
  else
1033
1042
  stmts = multi_insert_sql(columns, values)
1034
- @db.transaction(trans_opts){stmts.each{|st| execute_dui(st)}}
1043
+ _import_transaction(stmts, trans_opts){stmts.each{|st| execute_dui(st)}}
1035
1044
  end
1036
1045
  end
1037
-
1046
+
1038
1047
  # Return an array of arrays of values given by the symbols in ret_cols.
1039
1048
  def _select_map_multiple(ret_cols)
1040
1049
  map{|r| r.values_at(*ret_cols)}
@@ -1073,6 +1082,17 @@ module Sequel
1073
1082
  end
1074
1083
  end
1075
1084
 
1085
+ # Use a transaction when yielding to the block if multiple values/statements
1086
+ # are provided. When only a single value or statement is provided, then yield
1087
+ # without using a transaction.
1088
+ def _import_transaction(values, trans_opts, &block)
1089
+ if values.length > 1
1090
+ @db.transaction(trans_opts, &block)
1091
+ else
1092
+ yield
1093
+ end
1094
+ end
1095
+
1076
1096
  # Internals of +select_hash+ and +select_hash_groups+
1077
1097
  def _select_hash(meth, key_column, value_column, opts=OPTS)
1078
1098
  select(*(key_column.is_a?(Array) ? key_column : [key_column]) + (value_column.is_a?(Array) ? value_column : [value_column])).
@@ -152,6 +152,11 @@ module Sequel
152
152
  supports_distinct_on?
153
153
  end
154
154
 
155
+ # Whether placeholder literalizers are supported, true by default.
156
+ def supports_placeholder_literalizer?
157
+ true
158
+ end
159
+
155
160
  # Whether the dataset supports pattern matching by regular expressions, false by default.
156
161
  def supports_regexp?
157
162
  false
@@ -302,7 +302,7 @@ module Sequel
302
302
  cache_set(key, loader + 1)
303
303
  loader = nil
304
304
  end
305
- elsif cache_sql?
305
+ elsif cache_sql? && supports_placeholder_literalizer?
306
306
  cache_set(key, 1)
307
307
  end
308
308
 
@@ -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
@@ -76,33 +76,45 @@ module Sequel
76
76
  raise unless disamb = tzinfo_disambiguator_for(v)
77
77
  period = input_timezone.period_for_local(v, &disamb)
78
78
  offset = period.utc_total_offset
79
- Time.at(v.to_i - offset, :in => input_timezone)
79
+ # :nocov:
80
+ if defined?(JRUBY_VERSION)
81
+ Time.at(v.to_i - offset, :in => input_timezone) + v.nsec/1000000000.0
82
+ # :nocov:
83
+ else
84
+ Time.at(v.to_i - offset, v.nsec, :nsec, :in => input_timezone)
85
+ end
80
86
  end
81
87
 
82
88
  # Convert the given input Time to the given output timezone,
83
89
  # which should be a TZInfo::Timezone instance.
84
90
  def convert_output_time_other(v, output_timezone)
85
- Time.at(v.to_i, :in => output_timezone)
91
+ # :nocov:
92
+ if defined?(JRUBY_VERSION)
93
+ Time.at(v.to_i, :in => output_timezone) + v.nsec/1000000000.0
94
+ # :nocov:
95
+ else
96
+ Time.at(v.to_i, v.nsec, :nsec, :in => output_timezone)
97
+ end
86
98
  end
87
99
  # :nodoc:
88
100
  # :nocov:
89
101
  else
90
102
  def convert_input_time_other(v, input_timezone)
91
103
  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
104
+ Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i + v.nsec/1000000000.0
93
105
  end
94
106
 
95
107
  if defined?(TZInfo::VERSION) && TZInfo::VERSION > '2'
96
108
  def convert_output_time_other(v, output_timezone)
97
109
  v = output_timezone.utc_to_local(v.getutc)
98
110
  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
111
+ Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i + v.nsec/1000000000.0 + local_offset
100
112
  end
101
113
  else
102
114
  def convert_output_time_other(v, output_timezone)
103
115
  v = output_timezone.utc_to_local(v.getutc)
104
116
  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
117
+ Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i + v.nsec/1000000000.0
106
118
  end
107
119
  end
108
120
  # :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