sequel 5.60.1 → 5.62.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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +44 -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.61.0.txt +43 -0
  13. data/doc/release_notes/5.62.0.txt +132 -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.rb +42 -28
  29. data/lib/sequel/database/connecting.rb +24 -0
  30. data/lib/sequel/database/misc.rb +62 -12
  31. data/lib/sequel/database/query.rb +37 -0
  32. data/lib/sequel/dataset/actions.rb +31 -11
  33. data/lib/sequel/dataset/features.rb +5 -0
  34. data/lib/sequel/dataset/misc.rb +1 -1
  35. data/lib/sequel/dataset/query.rb +9 -9
  36. data/lib/sequel/dataset/sql.rb +5 -1
  37. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  38. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  39. data/lib/sequel/extensions/async_thread_pool.rb +11 -11
  40. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  41. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  42. data/lib/sequel/extensions/date_arithmetic.rb +1 -1
  43. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  44. data/lib/sequel/extensions/migration.rb +1 -1
  45. data/lib/sequel/extensions/named_timezones.rb +17 -5
  46. data/lib/sequel/extensions/pg_array.rb +22 -3
  47. data/lib/sequel/extensions/pg_auto_parameterize.rb +478 -0
  48. data/lib/sequel/extensions/pg_extended_date_support.rb +27 -24
  49. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  50. data/lib/sequel/extensions/pg_hstore.rb +5 -0
  51. data/lib/sequel/extensions/pg_inet.rb +10 -11
  52. data/lib/sequel/extensions/pg_interval.rb +10 -11
  53. data/lib/sequel/extensions/pg_json.rb +10 -10
  54. data/lib/sequel/extensions/pg_json_ops.rb +0 -52
  55. data/lib/sequel/extensions/pg_multirange.rb +5 -10
  56. data/lib/sequel/extensions/pg_range.rb +6 -11
  57. data/lib/sequel/extensions/pg_row.rb +18 -13
  58. data/lib/sequel/model/associations.rb +7 -2
  59. data/lib/sequel/model/base.rb +6 -5
  60. data/lib/sequel/plugins/auto_validations.rb +53 -15
  61. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  62. data/lib/sequel/plugins/composition.rb +2 -2
  63. data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
  64. data/lib/sequel/plugins/dirty.rb +1 -1
  65. data/lib/sequel/plugins/finder.rb +3 -1
  66. data/lib/sequel/plugins/nested_attributes.rb +4 -4
  67. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +1 -1
  68. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  69. data/lib/sequel/plugins/sql_comments.rb +1 -1
  70. data/lib/sequel/plugins/validation_helpers.rb +20 -0
  71. data/lib/sequel/version.rb +2 -2
  72. metadata +12 -5
@@ -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
@@ -0,0 +1,478 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # This extension changes Sequel's postgres adapter to automatically
4
+ # parameterize queries by default. Sequel's default behavior has always
5
+ # been to literalize all arguments unless specifically using
6
+ # parameters (via :$arg placeholders and the Dataset#prepare/call methods).
7
+ # This extension makes Sequel use string, numeric, blob, date, and
8
+ # time types as parameters. Example:
9
+ #
10
+ # # Default
11
+ # DB[:test].where(:a=>1)
12
+ # # SQL: SELECT * FROM test WHERE a = 1
13
+ #
14
+ # DB.extension :pg_auto_parameterize
15
+ # DB[:test].where(:a=>1)
16
+ # # SQL: SELECT * FROM test WHERE a = $1 (args: [1])
17
+ #
18
+ # Other pg_* extensions that ship with Sequel and add support for
19
+ # PostgreSQL-specific types support automatically parameterizing those
20
+ # types when used with this extension.
21
+ #
22
+ # This extension is not generally faster than the default behavior.
23
+ # In some cases it is faster, such as when using large strings.
24
+ # However, the use of parameters avoids potential security issues,
25
+ # in case Sequel does not correctly literalize one of the arguments
26
+ # that this extension would automatically parameterize.
27
+ #
28
+ # There are some known issues with automatic parameterization:
29
+ #
30
+ # 1. In order to avoid most type errors, the extension attempts to guess
31
+ # the appropriate type and automatically casts most placeholders,
32
+ # except plain Ruby strings (which PostgreSQL treats as an unknown
33
+ # type).
34
+ #
35
+ # Unfortunately, if the type guess is incorrect, or a plain Ruby
36
+ # string is used and PostgreSQL cannot determine the data type for it,
37
+ # the query may result in a DatabaseError. To fix both issues, you can
38
+ # explicitly cast values using <tt>Sequel.cast(value, type)</tt>, and
39
+ # Sequel will cast to that type.
40
+ #
41
+ # 2. PostgreSQL supports a maximum of 65535 parameters per query.
42
+ # Attempts to use a query with more than this number of parameters
43
+ # will result in a Sequel::DatabaseError being raised. Sequel tries
44
+ # to mitigate this issue by turning <tt>column IN (int, ...)</tt>
45
+ # queries into <tt>column = ANY(CAST($ AS int8[]))</tt> using an
46
+ # array parameter, to reduce the number of parameters. It also limits
47
+ # inserting multiple rows at once to a maximum of 40 rows per query by
48
+ # default. While these mitigations handle the most common cases
49
+ # where a large number of parameters would be used, there are other
50
+ # cases.
51
+ #
52
+ # 3. Automatic parameterization will consider the same objects as
53
+ # equivalent when building SQL. However, for performance, it does
54
+ # not perform equality checks. So code such as:
55
+ #
56
+ # DB[:t].select{foo('a').as(:f)}.group{foo('a')}
57
+ # # SELECT foo('a') AS "f" FROM "t" GROUP BY foo('a')
58
+ #
59
+ # Will get auto paramterized as:
60
+ #
61
+ # # SELECT foo($1) AS "f" FROM "t" GROUP BY foo($2)
62
+ #
63
+ # Which will result in a DatabaseError, since that is not valid SQL.
64
+ #
65
+ # If you use the same expression, it will use the same parameter:
66
+ #
67
+ # foo = Sequel.function(:foo, 'a')
68
+ # DB[:t].select(foo.as(:f)).group(foo)
69
+ # # SELECT foo($1) AS "f" FROM "t" GROUP BY foo($1)
70
+ #
71
+ # Note that Dataset#select_group and similar methods that take arguments
72
+ # used in multiple places in the SQL will generally handle this
73
+ # automatically, since they will use the same objects:
74
+ #
75
+ # DB[:t].select_group{foo('a').as(:f)}
76
+ # # SELECT foo($1) AS "f" FROM "t" GROUP BY foo($1)
77
+ #
78
+ # You can work around any issues that come up by disabling automatic
79
+ # parameterization by calling the +no_auto_parameterize+ method on the
80
+ # dataset (which returns a clone of the dataset). You can avoid
81
+ # parameterization for specific values in the query by wrapping them
82
+ # with +Sequel.skip_pg_auto_param+.
83
+ #
84
+ # It is likely there are corner cases not mentioned above
85
+ # when using this extension. Users are encouraged to provide feedback
86
+ # when using this extension if they come across such corner cases.
87
+ #
88
+ # This extension is only compatible when using the pg driver, not
89
+ # when using the sequel-postgres-pr, jeremyevans-postgres-pr, or
90
+ # postgres-pr drivers, as those do not support bound variables.
91
+ #
92
+ # Related module: Sequel::Postgres::AutoParameterize
93
+
94
+ module Sequel
95
+ module Postgres
96
+ # Enable automatically parameterizing queries.
97
+ module AutoParameterize
98
+ # SQL query string that also holds an array of parameters
99
+ class QueryString < ::String
100
+ # The array of parameters used by this query.
101
+ attr_reader :args
102
+
103
+ # Add a new parameter to this query, which adds
104
+ # the parameter to the array of parameters, and an
105
+ # SQL placeholder to the query itself.
106
+ def add_arg(s)
107
+ unless defined?(@args)
108
+ @args = []
109
+ @arg_map = {}
110
+ @arg_map.compare_by_identity
111
+ end
112
+
113
+ unless pos = @arg_map[s]
114
+ @args << s
115
+ pos = @arg_map[s] = @args.length.to_s
116
+ end
117
+ self << '$' << pos
118
+ end
119
+
120
+ # Return a new QueryString with the given string appended
121
+ # to the receiver, and the same arguments.
122
+ def +(other)
123
+ v = self.class.new(super)
124
+ v.instance_variable_set(:@args, @args) if @args
125
+ v
126
+ end
127
+
128
+ # Whether this query string currently supports
129
+ # automatic parameterization. Automatic parameterization
130
+ # is disabled at certain points during query building where
131
+ # PostgreSQL does not support it.
132
+ def auto_param?
133
+ !@skip_auto_param
134
+ end
135
+
136
+ # Skip automatic parameterization inside the passed block.
137
+ # This is used during query generation to disable
138
+ # automatic parameterization for clauses not supporting it.
139
+ def skip_auto_param
140
+ skip_auto_param = @skip_auto_param
141
+ begin
142
+ @skip_auto_param = true
143
+ yield
144
+ ensure
145
+ @skip_auto_param = skip_auto_param
146
+ end
147
+ end
148
+
149
+ # Freeze the stored arguments when freezing the query string.
150
+ def freeze
151
+ @args.freeze if @args
152
+ super
153
+ end
154
+
155
+ # Show args when the query string is inspected
156
+ def inspect
157
+ @args ? "#{self}; #{@args.inspect}".inspect : super
158
+ end
159
+ end
160
+
161
+ # Wrapper class that skips auto parameterization for the wrapped object.
162
+ class SkipAutoParam < SQL::Wrapper
163
+ def to_s_append(ds, sql)
164
+ if sql.is_a?(QueryString)
165
+ sql.skip_auto_param{super}
166
+ else
167
+ super
168
+ end
169
+ end
170
+ end
171
+
172
+ module DatabaseMethods
173
+ def self.extended(db)
174
+ unless (db.adapter_scheme == :postgres && USES_PG) || (db.adapter_scheme == :mock && db.database_type == :postgres)
175
+ raise Error, "pg_auto_parameterize is only supported when using the postgres adapter with the pg driver"
176
+ end
177
+ db.extend_datasets(DatasetMethods)
178
+ end
179
+
180
+ # If the sql string has an embedded parameter array,
181
+ # extract the parameter values from that.
182
+ def execute(sql, opts={})
183
+ if sql.is_a?(QueryString) && (args = sql.args)
184
+ opts = opts.merge(:arguments=>args)
185
+ end
186
+ super
187
+ end
188
+
189
+ private
190
+
191
+ # Disable auto_parameterization during COPY TABLE.
192
+ def copy_table_sql(table, opts=OPTS)
193
+ table = _no_auto_parameterize(table)
194
+ super
195
+ end
196
+
197
+ # Disable auto_parameterization during CREATE TABLE AS.
198
+ def create_table_as(name, sql, options)
199
+ sql = _no_auto_parameterize(sql)
200
+ super
201
+ end
202
+
203
+ # Disable auto_parameterization during CREATE VIEW.
204
+ def create_view_sql(name, source, options)
205
+ source = _no_auto_parameterize(source)
206
+ super
207
+ end
208
+
209
+ # Disable automatic parameterization for the given table if supported.
210
+ def _no_auto_parameterize(table)
211
+ if table.is_a?(DatasetMethods)
212
+ table.no_auto_parameterize
213
+ else
214
+ table
215
+ end
216
+ end
217
+ end
218
+
219
+ module DatasetMethods
220
+ # Return a clone of the dataset that will not do
221
+ # automatic parameterization.
222
+ def no_auto_parameterize
223
+ cached_dataset(:_no_auto_parameterize_ds) do
224
+ @opts[:no_auto_parameterize] ? self : clone(:no_auto_parameterize=>true)
225
+ end
226
+ end
227
+
228
+ # Do not add implicit typecasts for directly typecasted values,
229
+ # since the user is presumably doing so to set the type, not convert
230
+ # from the implicitly typecasted type.
231
+ def cast_sql_append(sql, expr, type)
232
+ if auto_param?(sql) && auto_param_type(expr)
233
+ sql << 'CAST('
234
+ sql.add_arg(expr)
235
+ sql << ' AS ' << db.cast_type_literal(type).to_s << ')'
236
+ else
237
+ super
238
+ end
239
+ end
240
+
241
+ # Transform column IN (int, ...) expressions into column = ANY($)
242
+ # and column NOT IN (int, ...) expressions into column != ALL($)
243
+ # using an integer array bound variable for the ANY/ALL argument.
244
+ # This is the same optimization PostgreSQL performs internally,
245
+ # but this reduces the number of bound variables.
246
+ def complex_expression_sql_append(sql, op, args)
247
+ case op
248
+ when :IN, :"NOT IN"
249
+ l, r = args
250
+ if auto_param?(sql) && !l.is_a?(Array) && _integer_array?(r) && r.size > 1
251
+ if op == :IN
252
+ op = :"="
253
+ func = :ANY
254
+ else
255
+ op = :!=
256
+ func = :ALL
257
+ end
258
+ args = [l, Sequel.function(func, Sequel.cast(_integer_array_auto_param(r), 'int8[]'))]
259
+ end
260
+ end
261
+
262
+ super
263
+ end
264
+
265
+ # Parameterize insertion of multiple values
266
+ def multi_insert_sql(columns, values)
267
+ if @opts[:no_auto_parameterize]
268
+ super
269
+ else
270
+ [clone(:multi_insert_values=>values.map{|r| Array(r)}).insert_sql(columns, LiteralString.new('VALUES '))]
271
+ end
272
+ end
273
+
274
+ # For strings, numeric arguments, and date/time arguments, add
275
+ # them as parameters to the query instead of literalizing them
276
+ # into the SQL.
277
+ def literal_append(sql, v)
278
+ if auto_param?(sql) && (type = auto_param_type(v))
279
+ sql.add_arg(v) << type
280
+ else
281
+ super
282
+ end
283
+ end
284
+
285
+ # Placeholder literalizers are not supported supported when using automatic parameterization.
286
+ def supports_placeholder_literalizer?
287
+ @opts[:no_auto_parameterize]
288
+ end
289
+
290
+ # Disable automatic parameterization when using a cursor.
291
+ def use_cursor(*)
292
+ super.no_auto_parameterize
293
+ end
294
+
295
+ # Store receiving dataset and args when with_sql is used with a method name symbol, so sql
296
+ # can be parameterized correctly if used as a subselect.
297
+ def with_sql(*a)
298
+ ds = super
299
+ if Symbol === a[0]
300
+ ds = ds.clone(:with_sql_dataset=>self, :with_sql_args=>a.freeze)
301
+ end
302
+ ds
303
+ end
304
+
305
+ protected
306
+
307
+ # Disable automatic parameterization for prepared statements,
308
+ # since they will use manual parameterization.
309
+ def to_prepared_statement(*a)
310
+ @opts[:no_auto_parameterize] ? super : no_auto_parameterize.to_prepared_statement(*a)
311
+ end
312
+
313
+ private
314
+
315
+ # If auto parameterization is supported for the value, return a string
316
+ # for the implicit typecast to use. Return false/nil if the value should not be
317
+ # automatically parameterized.
318
+ def auto_param_type(v)
319
+ case v
320
+ when String
321
+ case v
322
+ when LiteralString
323
+ false
324
+ when Sequel::SQL::Blob
325
+ "::bytea"
326
+ else
327
+ ""
328
+ end
329
+ when Integer
330
+ ((v > 2147483647 || v < -2147483648) ? "::int8" : "::int4")
331
+ when Float
332
+ # PostgreSQL treats literal floats as numeric, not double precision
333
+ # But older versions of PostgreSQL don't handle Infinity/NaN in numeric
334
+ v.finite? ? "::numeric" : "::double precision"
335
+ when BigDecimal
336
+ "::numeric"
337
+ when Sequel::SQLTime
338
+ "::time"
339
+ when Time
340
+ "::#{@db.cast_type_literal(Time)}"
341
+ when DateTime
342
+ "::#{@db.cast_type_literal(DateTime)}"
343
+ when Date
344
+ "::date"
345
+ else
346
+ v.respond_to?(:sequel_auto_param_type) ? v.sequel_auto_param_type(self) : auto_param_type_fallback(v)
347
+ end
348
+ end
349
+
350
+ # Allow other extensions to support auto parameterization in ways that do not
351
+ # require adding the sequel_auto_param_type method.
352
+ def auto_param_type_fallback(v)
353
+ super if defined?(super)
354
+ end
355
+
356
+ # Whether the given query string currently supports automatic parameterization.
357
+ def auto_param?(sql)
358
+ sql.is_a?(QueryString) && sql.auto_param?
359
+ end
360
+
361
+ # Default the import slice to 40, since PostgreSQL supports a maximum of 1600
362
+ # columns per table, and it supports a maximum of 65k parameters. Technically,
363
+ # there can be more than one parameter per column, so this doesn't prevent going
364
+ # over the limit, though it does make it less likely.
365
+ def default_import_slice
366
+ 40
367
+ end
368
+
369
+ # Handle parameterization of multi_insert_sql
370
+ def _insert_values_sql(sql, values)
371
+ super
372
+
373
+ if values = @opts[:multi_insert_values]
374
+ expression_list_append(sql, values.map{|r| Array(r)})
375
+ end
376
+ end
377
+
378
+ # Whether the given argument is an array of integers or NULL values, recursively.
379
+ def _integer_array?(v)
380
+ Array === v && v.all?{|x| nil == x || Integer === x}
381
+ end
382
+
383
+ # Create the bound variable string that will be used for the IN (int, ...) to = ANY($)
384
+ # optimization for integer arrays.
385
+ def _integer_array_auto_param(v)
386
+ buf = String.new
387
+ buf << '{'
388
+ comma = false
389
+ v.each do |x|
390
+ if comma
391
+ buf << ","
392
+ else
393
+ comma = true
394
+ end
395
+
396
+ buf << (x ? x.to_s : 'NULL')
397
+ end
398
+ buf << '}'
399
+ end
400
+
401
+ # Skip auto parameterization in LIMIT and OFFSET clauses
402
+ def select_limit_sql(sql)
403
+ if auto_param?(sql) && (@opts[:limit] || @opts[:offset])
404
+ sql.skip_auto_param{super}
405
+ else
406
+ super
407
+ end
408
+ end
409
+
410
+ # Skip auto parameterization in ORDER clause if used with
411
+ # integer values indicating ordering by the nth column.
412
+ def select_order_sql(sql)
413
+ if auto_param?(sql) && (order = @opts[:order]) && order.any?{|o| Integer === o || (SQL::OrderedExpression === o && Integer === o.expression)}
414
+ sql.skip_auto_param{super}
415
+ else
416
+ super
417
+ end
418
+ end
419
+
420
+ # Skip auto parameterization in CTE CYCLE clause
421
+ def select_with_sql_cte_search_cycle(sql,cte)
422
+ if auto_param?(sql) && cte[:cycle]
423
+ sql.skip_auto_param{super}
424
+ else
425
+ super
426
+ end
427
+ end
428
+
429
+ # Unless auto parameterization is disabled, use a string that
430
+ # can store the parameterized arguments.
431
+ def sql_string_origin
432
+ @opts[:no_auto_parameterize] ? super : QueryString.new
433
+ end
434
+
435
+ # If subquery uses with_sql with a method name symbol, get the dataset
436
+ # with_sql was called on, and use that as the subquery, recording the
437
+ # arguments to with_sql that will be used to calculate the sql.
438
+ def subselect_sql_dataset(sql, ds)
439
+ if ws_ds = ds.opts[:with_sql_dataset]
440
+ super(sql, ws_ds).clone(:subselect_sql_args=>ds.opts[:with_sql_args])
441
+ else
442
+ super
443
+ end
444
+ end
445
+
446
+ # If subquery used with_sql with a method name symbol, use the arguments to
447
+ # with_sql to determine the sql, so that the subselect can be parameterized.
448
+ def subselect_sql_append_sql(sql, ds)
449
+ if args = ds.opts[:subselect_sql_args]
450
+ ds.send(*args)
451
+ else
452
+ super
453
+ end
454
+ end
455
+
456
+ # Use auto parameterization for datasets with static SQL using placeholders.
457
+ def static_sql(sql)
458
+ if @opts[:append_sql] || @opts[:no_auto_parameterize] || String === sql
459
+ super
460
+ else
461
+ query_string = QueryString.new
462
+ literal_append(query_string, sql)
463
+ query_string
464
+ end
465
+ end
466
+ end
467
+ end
468
+ end
469
+
470
+ module SQL::Builders
471
+ # Skip auto parameterization for the given object when building queries.
472
+ def skip_pg_auto_param(v)
473
+ Postgres::AutoParameterize::SkipAutoParam.new(v)
474
+ end
475
+ end
476
+
477
+ Database.register_extension(:pg_auto_parameterize, Postgres::AutoParameterize::DatabaseMethods)
478
+ end
@@ -29,6 +29,8 @@ module Sequel
29
29
  INFINITE_DATETIME_VALUES = ([PLUS_INFINITY, MINUS_INFINITY] + INFINITE_TIMESTAMP_STRINGS).freeze
30
30
  PLUS_DATE_INFINITY = Date::Infinity.new
31
31
  MINUS_DATE_INFINITY = -PLUS_DATE_INFINITY
32
+ RATIONAL_60 = Rational(60)
33
+ TIME_CAN_PARSE_BC = RUBY_VERSION >= '2.5'
32
34
 
33
35
  # Add dataset methods and update the conversion proces for dates and timestamps.
34
36
  def self.extended(db)
@@ -38,6 +40,18 @@ module Sequel
38
40
  procs[1184] = procs[1114] = db.method(:to_application_timestamp)
39
41
  end
40
42
 
43
+ # Handle BC dates and times in bound variables. This is necessary for Date values
44
+ # when using both the postgres and jdbc adapters, but also necessary for Time values
45
+ # on jdbc.
46
+ def bound_variable_arg(arg, conn)
47
+ case arg
48
+ when Date, Time
49
+ literal(arg)
50
+ else
51
+ super
52
+ end
53
+ end
54
+
41
55
  # Whether infinite timestamps/dates should be converted on retrieval. By default, no
42
56
  # conversion is done, so an error is raised if you attempt to retrieve an infinite
43
57
  # timestamp/date. You can set this to :nil to convert to nil, :string to leave
@@ -86,27 +100,18 @@ module Sequel
86
100
  if value.is_a?(String) && (m = /((?:[-+]\d\d:\d\d)(:\d\d)?)?( BC)?\z/.match(value)) && (m[2] || m[3])
87
101
  if m[3]
88
102
  value = value.sub(' BC', '').sub(' ', ' BC ')
89
- conv = defined?(JRUBY_VERSION) && JRUBY_VERSION == '9.2.0.0'
90
103
  end
91
- if m[2] || conv
92
- dt = DateTime.parse(value)
93
- if conv
94
- # :nocov:
95
- if Sequel.datetime_class == DateTime
96
- dt >>= 12
97
- else
98
- dt >>= 24
99
- end
100
- # :nocov:
101
- end
102
- unless Sequel.datetime_class == DateTime
103
- dt = dt.to_time
104
- if conv && (timezone == nil || timezone == :local) && !m[1]
105
- # :nocov:
106
- dt = Sequel.send(:convert_input_timestamp, dt.strftime("%F %T.%6N"), :local)
107
- # :nocov:
108
- end
104
+ if m[2]
105
+ dt = if Sequel.datetime_class == DateTime
106
+ DateTime.parse(value)
107
+ elsif TIME_CAN_PARSE_BC
108
+ Time.parse(value)
109
+ # :nocov:
110
+ else
111
+ DateTime.parse(value).to_time
112
+ # :nocov:
109
113
  end
114
+
110
115
  Sequel.convert_output_timestamp(dt, Sequel.application_timezone)
111
116
  else
112
117
  super(value)
@@ -223,10 +228,7 @@ module Sequel
223
228
  # Work around JRuby bug #4822 in Time#to_datetime for times before date of calendar reform
224
229
  def literal_time(time)
225
230
  if time < TIME_YEAR_1
226
- dt = DateTime.parse(super)
227
- # Work around JRuby bug #5191
228
- dt >>= 12 if JRUBY_VERSION == '9.2.0.0'
229
- literal_datetime(dt)
231
+ literal_datetime(DateTime.parse(super))
230
232
  else
231
233
  super
232
234
  end
@@ -236,7 +238,8 @@ module Sequel
236
238
  # Handle BC Time objects.
237
239
  def literal_time(time)
238
240
  if time < TIME_YEAR_1
239
- literal_datetime(time.to_datetime)
241
+ time = db.from_application_timestamp(time)
242
+ time.strftime("'#{sprintf('%04i', time.year.abs+1)}-%m-%d %H:%M:%S.%N#{format_timestamp_offset(*(time.utc_offset/RATIONAL_60).divmod(60))} BC'")
240
243
  else
241
244
  super
242
245
  end