sequel 5.61.0 → 5.62.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +32 -0
- data/README.rdoc +20 -19
- data/doc/advanced_associations.rdoc +13 -13
- data/doc/association_basics.rdoc +21 -15
- data/doc/cheat_sheet.rdoc +3 -3
- data/doc/model_hooks.rdoc +1 -1
- data/doc/object_model.rdoc +8 -8
- data/doc/opening_databases.rdoc +4 -4
- data/doc/postgresql.rdoc +8 -8
- data/doc/querying.rdoc +1 -1
- data/doc/release_notes/5.62.0.txt +132 -0
- data/doc/schema_modification.rdoc +1 -1
- data/doc/security.rdoc +9 -9
- data/doc/sql.rdoc +13 -13
- data/doc/testing.rdoc +13 -11
- data/doc/transactions.rdoc +6 -6
- data/doc/virtual_rows.rdoc +1 -1
- data/lib/sequel/adapters/postgres.rb +4 -0
- data/lib/sequel/adapters/shared/access.rb +9 -1
- data/lib/sequel/adapters/shared/mssql.rb +9 -5
- data/lib/sequel/adapters/shared/mysql.rb +7 -0
- data/lib/sequel/adapters/shared/oracle.rb +7 -0
- data/lib/sequel/adapters/shared/postgres.rb +275 -152
- data/lib/sequel/adapters/shared/sqlanywhere.rb +7 -0
- data/lib/sequel/adapters/shared/sqlite.rb +5 -0
- data/lib/sequel/connection_pool.rb +42 -28
- data/lib/sequel/database/connecting.rb +24 -0
- data/lib/sequel/database/misc.rb +8 -2
- data/lib/sequel/database/query.rb +37 -0
- data/lib/sequel/dataset/actions.rb +31 -11
- data/lib/sequel/dataset/features.rb +5 -0
- data/lib/sequel/dataset/misc.rb +1 -1
- data/lib/sequel/dataset/query.rb +9 -9
- data/lib/sequel/dataset/sql.rb +5 -1
- data/lib/sequel/extensions/_model_pg_row.rb +0 -12
- data/lib/sequel/extensions/_pretty_table.rb +1 -1
- data/lib/sequel/extensions/async_thread_pool.rb +11 -11
- data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
- data/lib/sequel/extensions/constraint_validations.rb +1 -1
- data/lib/sequel/extensions/date_arithmetic.rb +1 -1
- data/lib/sequel/extensions/migration.rb +1 -1
- data/lib/sequel/extensions/named_timezones.rb +17 -5
- data/lib/sequel/extensions/pg_array.rb +22 -3
- data/lib/sequel/extensions/pg_auto_parameterize.rb +478 -0
- data/lib/sequel/extensions/pg_extended_date_support.rb +12 -0
- data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
- data/lib/sequel/extensions/pg_hstore.rb +5 -0
- data/lib/sequel/extensions/pg_inet.rb +9 -10
- data/lib/sequel/extensions/pg_interval.rb +9 -10
- data/lib/sequel/extensions/pg_json.rb +10 -10
- data/lib/sequel/extensions/pg_multirange.rb +5 -10
- data/lib/sequel/extensions/pg_range.rb +5 -10
- data/lib/sequel/extensions/pg_row.rb +18 -13
- data/lib/sequel/model/associations.rb +7 -2
- data/lib/sequel/model/base.rb +6 -5
- data/lib/sequel/plugins/auto_validations.rb +53 -15
- data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
- data/lib/sequel/plugins/composition.rb +2 -2
- data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
- data/lib/sequel/plugins/dirty.rb +1 -1
- data/lib/sequel/plugins/finder.rb +3 -1
- data/lib/sequel/plugins/nested_attributes.rb +4 -4
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +1 -1
- data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
- data/lib/sequel/plugins/sql_comments.rb +1 -1
- data/lib/sequel/plugins/validation_helpers.rb +20 -0
- data/lib/sequel/version.rb +1 -1
- metadata +10 -5
@@ -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
|
@@ -40,6 +40,18 @@ module Sequel
|
|
40
40
|
procs[1184] = procs[1114] = db.method(:to_application_timestamp)
|
41
41
|
end
|
42
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
|
+
|
43
55
|
# Whether infinite timestamps/dates should be converted on retrieval. By default, no
|
44
56
|
# conversion is done, so an error is raised if you attempt to retrieve an infinite
|
45
57
|
# timestamp/date. You can set this to :nil to convert to nil, :string to leave
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
#
|
3
|
+
# The pg_extended_integer_support extension supports literalizing
|
4
|
+
# Ruby integers outside of PostgreSQL bigint range on PostgreSQL.
|
5
|
+
# Sequel by default will raise exceptions when
|
6
|
+
# literalizing such integers, as PostgreSQL would treat them
|
7
|
+
# as numeric type values instead of integer/bigint type values
|
8
|
+
# if unquoted, which can result in unexpected negative performance
|
9
|
+
# (e.g. forcing sequential scans when index scans would be used for
|
10
|
+
# an integer/bigint type).
|
11
|
+
#
|
12
|
+
# To load the extension into a Dataset (this returns a new Dataset):
|
13
|
+
#
|
14
|
+
# dataset = dataset.extension(:pg_extended_integer_support)
|
15
|
+
#
|
16
|
+
# To load the extension into a Database, so it affects all of the
|
17
|
+
# Database's datasets:
|
18
|
+
#
|
19
|
+
# DB.extension :pg_extended_integer_support
|
20
|
+
#
|
21
|
+
# By default, the extension will quote integers outside
|
22
|
+
# bigint range:
|
23
|
+
#
|
24
|
+
# DB.literal(2**63) # => "'9223372036854775808'"
|
25
|
+
#
|
26
|
+
# Quoting the value treats the type as unknown:
|
27
|
+
#
|
28
|
+
# DB.get{pg_typeof(2**63)} # => 'unknown'
|
29
|
+
#
|
30
|
+
# PostgreSQL will implicitly cast the unknown type to the appropriate
|
31
|
+
# database type, raising an error if it cannot be casted. Be aware this
|
32
|
+
# can result in the integer value being implicitly casted to text or
|
33
|
+
# any other PostgreSQL type:
|
34
|
+
#
|
35
|
+
# # Returns a string, not an integer:
|
36
|
+
# DB.get{2**63}
|
37
|
+
# # => "9223372036854775808"
|
38
|
+
#
|
39
|
+
# You can use the Dataset#integer_outside_bigint_range_strategy method
|
40
|
+
# with the value +:raw+ to change the strategy to not quote the variable:
|
41
|
+
#
|
42
|
+
# DB.dataset.
|
43
|
+
# integer_outside_bigint_range_strategy(:raw).
|
44
|
+
# literal(2**63)
|
45
|
+
# # => "9223372036854775808"
|
46
|
+
#
|
47
|
+
# Note that not quoting the value will result in PostgreSQL treating
|
48
|
+
# the type as numeric instead of integer:
|
49
|
+
#
|
50
|
+
# DB.dataset.
|
51
|
+
# integer_outside_bigint_range_strategy(:raw).
|
52
|
+
# get{pg_typeof(2**63)}
|
53
|
+
# # => "numeric"
|
54
|
+
#
|
55
|
+
# The +:raw+ behavior was Sequel's historical behavior, but unless
|
56
|
+
# you fully understand the reprecussions of PostgreSQL using a
|
57
|
+
# numeric type for integer values, you should not use it.
|
58
|
+
#
|
59
|
+
# To get the current default behavior of raising an exception for
|
60
|
+
# integers outside of PostgreSQL bigint range, you can use a strategy
|
61
|
+
# of +:raise+.
|
62
|
+
#
|
63
|
+
# To specify a default strategy for handling integers outside
|
64
|
+
# bigint range that applies to all of a Database's datasets, you can
|
65
|
+
# use the +:integer_outside_bigint_range_strategy+ Database option with
|
66
|
+
# a value of +:raise+ or +:raw+:
|
67
|
+
#
|
68
|
+
# DB.opts[:integer_outside_bigint_range_strategy] = :raw
|
69
|
+
#
|
70
|
+
# The Database option will be used as a fallback if you did not call
|
71
|
+
# the Dataset#integer_outside_bigint_range_strategy method to specify
|
72
|
+
# a strategy for the dataset.
|
73
|
+
#
|
74
|
+
# Related module: Sequel::Postgres::ExtendedIntegerSupport
|
75
|
+
|
76
|
+
#
|
77
|
+
module Sequel
|
78
|
+
module Postgres
|
79
|
+
module ExtendedIntegerSupport
|
80
|
+
# Set the strategy for handling integers outside PostgreSQL
|
81
|
+
# bigint range. Supported values:
|
82
|
+
#
|
83
|
+
# :quote :: Quote the integer value. PostgreSQL will treat
|
84
|
+
# the integer as a unknown type, implicitly casting
|
85
|
+
# to any other type as needed. This is the default
|
86
|
+
# value when using the pg_extended_integer_support
|
87
|
+
# extension.
|
88
|
+
# :raise :: Raise error when attempting to literalize the integer
|
89
|
+
# (the default behavior of Sequel on PostgreSQL when
|
90
|
+
# not using the pg_extended_integer_support extension).
|
91
|
+
# :raw :: Use raw integer value without quoting. PostgreSQL
|
92
|
+
# will treat the integer as a numeric. This was Sequel's
|
93
|
+
# historical behavior, but it is unlikely to be desired.
|
94
|
+
def integer_outside_bigint_range_strategy(strategy)
|
95
|
+
clone(:integer_outside_bigint_range_strategy=>strategy)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# Handle integers outside the bigint range by using
|
101
|
+
# the configured strategy.
|
102
|
+
def literal_integer_outside_bigint_range(v)
|
103
|
+
case @opts[:integer_outside_bigint_range_strategy] || @db.opts[:integer_outside_bigint_range_strategy]
|
104
|
+
when :raise
|
105
|
+
super
|
106
|
+
when :raw
|
107
|
+
v.to_s
|
108
|
+
else # when :quote
|
109
|
+
"'#{v}'"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
Dataset.register_extension(:pg_extended_integer_support, Postgres::ExtendedIntegerSupport)
|
116
|
+
end
|
@@ -75,16 +75,6 @@ module Sequel
|
|
75
75
|
|
76
76
|
private
|
77
77
|
|
78
|
-
# Handle inet[]/cidr[] types in bound variables.
|
79
|
-
def bound_variable_array(a)
|
80
|
-
case a
|
81
|
-
when IPAddr
|
82
|
-
"\"#{a.to_s}/#{a.instance_variable_get(:@mask_addr).to_s(2).count('1')}\""
|
83
|
-
else
|
84
|
-
super
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
78
|
# Make the column type detection recognize the inet and cidr types.
|
89
79
|
def schema_column_type(db_type)
|
90
80
|
case db_type
|
@@ -121,6 +111,15 @@ module Sequel
|
|
121
111
|
module InetDatasetMethods
|
122
112
|
private
|
123
113
|
|
114
|
+
# Allow auto parameterization of IPAddr instances.
|
115
|
+
def auto_param_type_fallback(v)
|
116
|
+
if defined?(super) && (type = super)
|
117
|
+
type
|
118
|
+
elsif IPAddr === v
|
119
|
+
"::inet"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
124
123
|
# Convert IPAddr value to a string and append a literal version
|
125
124
|
# of the string to the sql.
|
126
125
|
def literal_other_append(sql, value)
|
@@ -163,16 +163,6 @@ module Sequel
|
|
163
163
|
|
164
164
|
private
|
165
165
|
|
166
|
-
# Handle arrays of interval types in bound variables.
|
167
|
-
def bound_variable_array(a)
|
168
|
-
case a
|
169
|
-
when ActiveSupport::Duration
|
170
|
-
"\"#{IntervalDatabaseMethods.literal_duration(a)}\""
|
171
|
-
else
|
172
|
-
super
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
166
|
# Set the :ruby_default value if the default value is recognized as an interval.
|
177
167
|
def schema_post_process(_)
|
178
168
|
super.each do |a|
|
@@ -207,6 +197,15 @@ module Sequel
|
|
207
197
|
module IntervalDatasetMethods
|
208
198
|
private
|
209
199
|
|
200
|
+
# Allow auto parameterization of ActiveSupport::Duration instances.
|
201
|
+
def auto_param_type_fallback(v)
|
202
|
+
if defined?(super) && (type = super)
|
203
|
+
type
|
204
|
+
elsif ActiveSupport::Duration === v
|
205
|
+
"::interval"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
210
209
|
# Handle literalization of ActiveSupport::Duration objects, treating them as
|
211
210
|
# PostgreSQL intervals.
|
212
211
|
def literal_other_append(sql, v)
|