sequel 5.39.0 → 5.72.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +408 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +59 -27
- data/bin/sequel +11 -3
- data/doc/advanced_associations.rdoc +16 -14
- data/doc/association_basics.rdoc +119 -24
- data/doc/cheat_sheet.rdoc +11 -3
- data/doc/mass_assignment.rdoc +1 -1
- data/doc/migration.rdoc +13 -6
- data/doc/model_hooks.rdoc +1 -1
- data/doc/object_model.rdoc +8 -8
- data/doc/opening_databases.rdoc +26 -12
- data/doc/postgresql.rdoc +16 -8
- data/doc/querying.rdoc +5 -3
- data/doc/release_notes/5.40.0.txt +40 -0
- data/doc/release_notes/5.41.0.txt +25 -0
- data/doc/release_notes/5.42.0.txt +136 -0
- data/doc/release_notes/5.43.0.txt +98 -0
- data/doc/release_notes/5.44.0.txt +32 -0
- data/doc/release_notes/5.45.0.txt +34 -0
- data/doc/release_notes/5.46.0.txt +87 -0
- data/doc/release_notes/5.47.0.txt +59 -0
- data/doc/release_notes/5.48.0.txt +14 -0
- data/doc/release_notes/5.49.0.txt +59 -0
- data/doc/release_notes/5.50.0.txt +78 -0
- data/doc/release_notes/5.51.0.txt +47 -0
- data/doc/release_notes/5.52.0.txt +87 -0
- data/doc/release_notes/5.53.0.txt +23 -0
- data/doc/release_notes/5.54.0.txt +27 -0
- data/doc/release_notes/5.55.0.txt +21 -0
- data/doc/release_notes/5.56.0.txt +51 -0
- data/doc/release_notes/5.57.0.txt +23 -0
- data/doc/release_notes/5.58.0.txt +31 -0
- data/doc/release_notes/5.59.0.txt +73 -0
- data/doc/release_notes/5.60.0.txt +22 -0
- data/doc/release_notes/5.61.0.txt +43 -0
- data/doc/release_notes/5.62.0.txt +132 -0
- data/doc/release_notes/5.63.0.txt +33 -0
- data/doc/release_notes/5.64.0.txt +50 -0
- data/doc/release_notes/5.65.0.txt +21 -0
- data/doc/release_notes/5.66.0.txt +24 -0
- data/doc/release_notes/5.67.0.txt +32 -0
- data/doc/release_notes/5.68.0.txt +61 -0
- data/doc/release_notes/5.69.0.txt +26 -0
- data/doc/release_notes/5.70.0.txt +35 -0
- data/doc/release_notes/5.71.0.txt +21 -0
- data/doc/release_notes/5.72.0.txt +33 -0
- data/doc/schema_modification.rdoc +1 -1
- data/doc/security.rdoc +9 -9
- data/doc/sharding.rdoc +3 -1
- data/doc/sql.rdoc +28 -16
- data/doc/testing.rdoc +22 -11
- data/doc/transactions.rdoc +6 -6
- data/doc/virtual_rows.rdoc +2 -2
- data/lib/sequel/adapters/ado/access.rb +1 -1
- data/lib/sequel/adapters/ado.rb +17 -17
- data/lib/sequel/adapters/amalgalite.rb +3 -5
- data/lib/sequel/adapters/ibmdb.rb +2 -2
- data/lib/sequel/adapters/jdbc/derby.rb +8 -0
- data/lib/sequel/adapters/jdbc/h2.rb +60 -10
- data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +7 -4
- data/lib/sequel/adapters/jdbc.rb +16 -18
- data/lib/sequel/adapters/mysql.rb +92 -67
- data/lib/sequel/adapters/mysql2.rb +54 -49
- data/lib/sequel/adapters/odbc.rb +6 -2
- data/lib/sequel/adapters/oracle.rb +4 -3
- data/lib/sequel/adapters/postgres.rb +83 -40
- data/lib/sequel/adapters/shared/access.rb +11 -1
- data/lib/sequel/adapters/shared/db2.rb +30 -0
- data/lib/sequel/adapters/shared/mssql.rb +90 -9
- data/lib/sequel/adapters/shared/mysql.rb +47 -2
- data/lib/sequel/adapters/shared/oracle.rb +82 -1
- data/lib/sequel/adapters/shared/postgres.rb +496 -178
- data/lib/sequel/adapters/shared/sqlanywhere.rb +11 -1
- data/lib/sequel/adapters/shared/sqlite.rb +116 -11
- data/lib/sequel/adapters/sqlanywhere.rb +1 -1
- data/lib/sequel/adapters/sqlite.rb +60 -18
- data/lib/sequel/adapters/tinytds.rb +1 -1
- data/lib/sequel/adapters/trilogy.rb +117 -0
- data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
- data/lib/sequel/ast_transformer.rb +6 -0
- data/lib/sequel/connection_pool/sharded_single.rb +5 -7
- data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
- data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
- data/lib/sequel/connection_pool/single.rb +6 -8
- data/lib/sequel/connection_pool/threaded.rb +14 -8
- data/lib/sequel/connection_pool/timed_queue.rb +270 -0
- data/lib/sequel/connection_pool.rb +55 -31
- data/lib/sequel/core.rb +28 -18
- data/lib/sequel/database/connecting.rb +27 -3
- data/lib/sequel/database/dataset.rb +16 -6
- data/lib/sequel/database/misc.rb +69 -14
- data/lib/sequel/database/query.rb +73 -2
- data/lib/sequel/database/schema_generator.rb +46 -53
- data/lib/sequel/database/schema_methods.rb +18 -2
- data/lib/sequel/dataset/actions.rb +108 -14
- data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
- data/lib/sequel/dataset/features.rb +20 -0
- data/lib/sequel/dataset/misc.rb +12 -2
- data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
- data/lib/sequel/dataset/prepared_statements.rb +2 -0
- data/lib/sequel/dataset/query.rb +171 -44
- data/lib/sequel/dataset/sql.rb +182 -47
- data/lib/sequel/dataset.rb +4 -0
- data/lib/sequel/extensions/_model_pg_row.rb +0 -12
- data/lib/sequel/extensions/_pretty_table.rb +1 -1
- data/lib/sequel/extensions/any_not_empty.rb +1 -1
- data/lib/sequel/extensions/async_thread_pool.rb +439 -0
- data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
- data/lib/sequel/extensions/blank.rb +8 -0
- data/lib/sequel/extensions/connection_expiration.rb +15 -9
- data/lib/sequel/extensions/connection_validator.rb +16 -11
- data/lib/sequel/extensions/constraint_validations.rb +1 -1
- data/lib/sequel/extensions/core_refinements.rb +36 -11
- data/lib/sequel/extensions/date_arithmetic.rb +71 -31
- data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
- data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
- data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
- data/lib/sequel/extensions/eval_inspect.rb +2 -0
- data/lib/sequel/extensions/index_caching.rb +5 -1
- data/lib/sequel/extensions/inflector.rb +9 -1
- data/lib/sequel/extensions/is_distinct_from.rb +141 -0
- data/lib/sequel/extensions/looser_typecasting.rb +3 -0
- data/lib/sequel/extensions/migration.rb +11 -2
- data/lib/sequel/extensions/named_timezones.rb +26 -6
- data/lib/sequel/extensions/pagination.rb +1 -1
- data/lib/sequel/extensions/pg_array.rb +32 -4
- data/lib/sequel/extensions/pg_array_ops.rb +2 -2
- data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
- data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
- data/lib/sequel/extensions/pg_enum.rb +2 -3
- data/lib/sequel/extensions/pg_extended_date_support.rb +38 -27
- data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
- data/lib/sequel/extensions/pg_hstore.rb +6 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
- data/lib/sequel/extensions/pg_inet.rb +10 -11
- data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
- data/lib/sequel/extensions/pg_interval.rb +45 -19
- data/lib/sequel/extensions/pg_json.rb +13 -15
- data/lib/sequel/extensions/pg_json_ops.rb +73 -2
- data/lib/sequel/extensions/pg_loose_count.rb +3 -1
- data/lib/sequel/extensions/pg_multirange.rb +367 -0
- data/lib/sequel/extensions/pg_range.rb +11 -24
- data/lib/sequel/extensions/pg_range_ops.rb +37 -9
- data/lib/sequel/extensions/pg_row.rb +21 -19
- data/lib/sequel/extensions/pg_row_ops.rb +1 -1
- data/lib/sequel/extensions/query.rb +2 -0
- data/lib/sequel/extensions/s.rb +2 -1
- data/lib/sequel/extensions/schema_caching.rb +1 -1
- data/lib/sequel/extensions/schema_dumper.rb +45 -11
- data/lib/sequel/extensions/server_block.rb +10 -13
- data/lib/sequel/extensions/set_literalizer.rb +58 -0
- data/lib/sequel/extensions/sql_comments.rb +110 -3
- data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
- data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
- data/lib/sequel/extensions/string_agg.rb +1 -1
- data/lib/sequel/extensions/string_date_time.rb +19 -23
- data/lib/sequel/extensions/symbol_aref.rb +2 -0
- data/lib/sequel/model/associations.rb +345 -101
- data/lib/sequel/model/base.rb +51 -27
- data/lib/sequel/model/dataset_module.rb +3 -0
- data/lib/sequel/model/errors.rb +10 -1
- data/lib/sequel/model/inflections.rb +1 -1
- data/lib/sequel/model/plugins.rb +5 -0
- data/lib/sequel/plugins/association_proxies.rb +2 -0
- data/lib/sequel/plugins/async_thread_pool.rb +39 -0
- data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
- data/lib/sequel/plugins/auto_validations.rb +87 -15
- data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
- data/lib/sequel/plugins/column_encryption.rb +728 -0
- data/lib/sequel/plugins/composition.rb +10 -4
- data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
- data/lib/sequel/plugins/constraint_validations.rb +10 -6
- data/lib/sequel/plugins/dataset_associations.rb +4 -1
- data/lib/sequel/plugins/defaults_setter.rb +16 -0
- data/lib/sequel/plugins/dirty.rb +1 -1
- data/lib/sequel/plugins/enum.rb +124 -0
- data/lib/sequel/plugins/finder.rb +4 -2
- data/lib/sequel/plugins/insert_conflict.rb +4 -0
- data/lib/sequel/plugins/instance_specific_default.rb +1 -1
- data/lib/sequel/plugins/json_serializer.rb +39 -24
- data/lib/sequel/plugins/lazy_attributes.rb +3 -0
- data/lib/sequel/plugins/list.rb +3 -1
- data/lib/sequel/plugins/many_through_many.rb +109 -10
- data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
- data/lib/sequel/plugins/nested_attributes.rb +12 -7
- data/lib/sequel/plugins/optimistic_locking.rb +9 -42
- data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
- data/lib/sequel/plugins/pg_array_associations.rb +56 -38
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +11 -3
- data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
- data/lib/sequel/plugins/prepared_statements.rb +12 -2
- data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
- data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
- data/lib/sequel/plugins/rcte_tree.rb +27 -19
- data/lib/sequel/plugins/require_valid_schema.rb +67 -0
- data/lib/sequel/plugins/serialization.rb +9 -3
- data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
- data/lib/sequel/plugins/sql_comments.rb +189 -0
- data/lib/sequel/plugins/static_cache.rb +39 -1
- data/lib/sequel/plugins/static_cache_cache.rb +5 -1
- data/lib/sequel/plugins/subclasses.rb +28 -11
- data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/unused_associations.rb +521 -0
- data/lib/sequel/plugins/update_or_create.rb +1 -1
- data/lib/sequel/plugins/validate_associated.rb +22 -12
- data/lib/sequel/plugins/validation_helpers.rb +46 -12
- data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
- data/lib/sequel/plugins/xml_serializer.rb +1 -1
- data/lib/sequel/sql.rb +1 -1
- data/lib/sequel/timezones.rb +12 -14
- data/lib/sequel/version.rb +1 -1
- metadata +132 -38
@@ -0,0 +1,509 @@
|
|
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
|
+
if @args
|
152
|
+
@args.freeze
|
153
|
+
@arg_map.freeze
|
154
|
+
end
|
155
|
+
super
|
156
|
+
end
|
157
|
+
|
158
|
+
# Show args when the query string is inspected
|
159
|
+
def inspect
|
160
|
+
@args ? "#{self}; #{@args.inspect}".inspect : super
|
161
|
+
end
|
162
|
+
|
163
|
+
def initialize_copy(other)
|
164
|
+
super
|
165
|
+
if args = other.instance_variable_get(:@args)
|
166
|
+
@args = args.dup
|
167
|
+
@arg_map = other.instance_variable_get(:@arg_map).dup
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Wrapper class that skips auto parameterization for the wrapped object.
|
173
|
+
class SkipAutoParam < SQL::Wrapper
|
174
|
+
def to_s_append(ds, sql)
|
175
|
+
if sql.is_a?(QueryString)
|
176
|
+
sql.skip_auto_param{super}
|
177
|
+
else
|
178
|
+
super
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# PlacholderLiteralizer subclass with support for stored auto parameters.
|
184
|
+
class PlaceholderLiteralizer < ::Sequel::Dataset::PlaceholderLiteralizer
|
185
|
+
def initialize(dataset, fragments, final_sql, arity)
|
186
|
+
s = dataset.sql.dup
|
187
|
+
s.clear
|
188
|
+
@sql_origin = s.freeze
|
189
|
+
super
|
190
|
+
end
|
191
|
+
|
192
|
+
private
|
193
|
+
|
194
|
+
def sql_origin
|
195
|
+
@sql_origin.dup
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
module DatabaseMethods
|
200
|
+
def self.extended(db)
|
201
|
+
unless (db.adapter_scheme == :postgres && USES_PG) || (db.adapter_scheme == :mock && db.database_type == :postgres)
|
202
|
+
raise Error, "pg_auto_parameterize is only supported when using the postgres adapter with the pg driver"
|
203
|
+
end
|
204
|
+
db.extend_datasets(DatasetMethods)
|
205
|
+
end
|
206
|
+
|
207
|
+
# If the sql string has an embedded parameter array,
|
208
|
+
# extract the parameter values from that.
|
209
|
+
def execute(sql, opts={})
|
210
|
+
if sql.is_a?(QueryString) && (args = sql.args)
|
211
|
+
opts = opts.merge(:arguments=>args)
|
212
|
+
end
|
213
|
+
super
|
214
|
+
end
|
215
|
+
|
216
|
+
private
|
217
|
+
|
218
|
+
# Disable auto_parameterization during COPY TABLE.
|
219
|
+
def copy_table_sql(table, opts=OPTS)
|
220
|
+
table = _no_auto_parameterize(table)
|
221
|
+
super
|
222
|
+
end
|
223
|
+
|
224
|
+
# Disable auto_parameterization during CREATE TABLE AS.
|
225
|
+
def create_table_as(name, sql, options)
|
226
|
+
sql = _no_auto_parameterize(sql)
|
227
|
+
super
|
228
|
+
end
|
229
|
+
|
230
|
+
# Disable auto_parameterization during CREATE VIEW.
|
231
|
+
def create_view_sql(name, source, options)
|
232
|
+
source = _no_auto_parameterize(source)
|
233
|
+
super
|
234
|
+
end
|
235
|
+
|
236
|
+
# Disable automatic parameterization for the given table if supported.
|
237
|
+
def _no_auto_parameterize(table)
|
238
|
+
if table.is_a?(DatasetMethods)
|
239
|
+
table.no_auto_parameterize
|
240
|
+
else
|
241
|
+
table
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
module DatasetMethods
|
247
|
+
# Return a clone of the dataset that will not do
|
248
|
+
# automatic parameterization.
|
249
|
+
def no_auto_parameterize
|
250
|
+
cached_dataset(:_no_auto_parameterize_ds) do
|
251
|
+
@opts[:no_auto_parameterize] ? self : clone(:no_auto_parameterize=>true)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# Do not add implicit typecasts for directly typecasted values,
|
256
|
+
# since the user is presumably doing so to set the type, not convert
|
257
|
+
# from the implicitly typecasted type.
|
258
|
+
def cast_sql_append(sql, expr, type)
|
259
|
+
if auto_param?(sql) && auto_param_type(expr)
|
260
|
+
sql << 'CAST('
|
261
|
+
sql.add_arg(expr)
|
262
|
+
sql << ' AS ' << db.cast_type_literal(type).to_s << ')'
|
263
|
+
else
|
264
|
+
super
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# Transform column IN (int, ...) expressions into column = ANY($)
|
269
|
+
# and column NOT IN (int, ...) expressions into column != ALL($)
|
270
|
+
# using an integer array bound variable for the ANY/ALL argument.
|
271
|
+
# This is the same optimization PostgreSQL performs internally,
|
272
|
+
# but this reduces the number of bound variables.
|
273
|
+
def complex_expression_sql_append(sql, op, args)
|
274
|
+
case op
|
275
|
+
when :IN, :"NOT IN"
|
276
|
+
l, r = args
|
277
|
+
if auto_param?(sql) && !l.is_a?(Array) && _integer_array?(r) && r.size > 1
|
278
|
+
if op == :IN
|
279
|
+
op = :"="
|
280
|
+
func = :ANY
|
281
|
+
else
|
282
|
+
op = :!=
|
283
|
+
func = :ALL
|
284
|
+
end
|
285
|
+
args = [l, Sequel.function(func, Sequel.cast(_integer_array_auto_param(r), 'int8[]'))]
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
super
|
290
|
+
end
|
291
|
+
|
292
|
+
# Parameterize insertion of multiple values
|
293
|
+
def multi_insert_sql(columns, values)
|
294
|
+
if @opts[:no_auto_parameterize]
|
295
|
+
super
|
296
|
+
else
|
297
|
+
[clone(:multi_insert_values=>values.map{|r| Array(r)}).insert_sql(columns, LiteralString.new('VALUES '))]
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
# For strings, numeric arguments, and date/time arguments, add
|
302
|
+
# them as parameters to the query instead of literalizing them
|
303
|
+
# into the SQL.
|
304
|
+
def literal_append(sql, v)
|
305
|
+
if auto_param?(sql) && (type = auto_param_type(v))
|
306
|
+
sql.add_arg(v) << type
|
307
|
+
else
|
308
|
+
super
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
# The class to use for placeholder literalizers.
|
313
|
+
def placeholder_literalizer_class
|
314
|
+
if @opts[:no_auto_parameterize]
|
315
|
+
super
|
316
|
+
else
|
317
|
+
PlaceholderLiteralizer
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# Disable automatic parameterization when using a cursor.
|
322
|
+
def use_cursor(*)
|
323
|
+
super.no_auto_parameterize
|
324
|
+
end
|
325
|
+
|
326
|
+
# Store receiving dataset and args when with_sql is used with a method name symbol, so sql
|
327
|
+
# can be parameterized correctly if used as a subselect.
|
328
|
+
def with_sql(*a)
|
329
|
+
ds = super
|
330
|
+
if Symbol === a[0]
|
331
|
+
ds = ds.clone(:with_sql_dataset=>self, :with_sql_args=>a.freeze)
|
332
|
+
end
|
333
|
+
ds
|
334
|
+
end
|
335
|
+
|
336
|
+
protected
|
337
|
+
|
338
|
+
# Disable automatic parameterization for prepared statements,
|
339
|
+
# since they will use manual parameterization.
|
340
|
+
def to_prepared_statement(*a)
|
341
|
+
@opts[:no_auto_parameterize] ? super : no_auto_parameterize.to_prepared_statement(*a)
|
342
|
+
end
|
343
|
+
|
344
|
+
private
|
345
|
+
|
346
|
+
# If auto parameterization is supported for the value, return a string
|
347
|
+
# for the implicit typecast to use. Return false/nil if the value should not be
|
348
|
+
# automatically parameterized.
|
349
|
+
def auto_param_type(v)
|
350
|
+
case v
|
351
|
+
when String
|
352
|
+
case v
|
353
|
+
when LiteralString
|
354
|
+
false
|
355
|
+
when Sequel::SQL::Blob
|
356
|
+
"::bytea"
|
357
|
+
else
|
358
|
+
""
|
359
|
+
end
|
360
|
+
when Integer
|
361
|
+
((v > 2147483647 || v < -2147483648) ? "::int8" : "::int4")
|
362
|
+
when Float
|
363
|
+
# PostgreSQL treats literal floats as numeric, not double precision
|
364
|
+
# But older versions of PostgreSQL don't handle Infinity/NaN in numeric
|
365
|
+
v.finite? ? "::numeric" : "::double precision"
|
366
|
+
when BigDecimal
|
367
|
+
"::numeric"
|
368
|
+
when Sequel::SQLTime
|
369
|
+
"::time"
|
370
|
+
when Time
|
371
|
+
"::#{@db.cast_type_literal(Time)}"
|
372
|
+
when DateTime
|
373
|
+
"::#{@db.cast_type_literal(DateTime)}"
|
374
|
+
when Date
|
375
|
+
"::date"
|
376
|
+
else
|
377
|
+
v.respond_to?(:sequel_auto_param_type) ? v.sequel_auto_param_type(self) : auto_param_type_fallback(v)
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
# Allow other extensions to support auto parameterization in ways that do not
|
382
|
+
# require adding the sequel_auto_param_type method.
|
383
|
+
def auto_param_type_fallback(v)
|
384
|
+
super if defined?(super)
|
385
|
+
end
|
386
|
+
|
387
|
+
# Whether the given query string currently supports automatic parameterization.
|
388
|
+
def auto_param?(sql)
|
389
|
+
sql.is_a?(QueryString) && sql.auto_param?
|
390
|
+
end
|
391
|
+
|
392
|
+
# Default the import slice to 40, since PostgreSQL supports a maximum of 1600
|
393
|
+
# columns per table, and it supports a maximum of 65k parameters. Technically,
|
394
|
+
# there can be more than one parameter per column, so this doesn't prevent going
|
395
|
+
# over the limit, though it does make it less likely.
|
396
|
+
def default_import_slice
|
397
|
+
40
|
398
|
+
end
|
399
|
+
|
400
|
+
# Handle parameterization of multi_insert_sql
|
401
|
+
def _insert_values_sql(sql, values)
|
402
|
+
super
|
403
|
+
|
404
|
+
if values = @opts[:multi_insert_values]
|
405
|
+
expression_list_append(sql, values.map{|r| Array(r)})
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
# Whether the given argument is an array of integers or NULL values, recursively.
|
410
|
+
def _integer_array?(v)
|
411
|
+
Array === v && v.all?{|x| nil == x || Integer === x}
|
412
|
+
end
|
413
|
+
|
414
|
+
# Create the bound variable string that will be used for the IN (int, ...) to = ANY($)
|
415
|
+
# optimization for integer arrays.
|
416
|
+
def _integer_array_auto_param(v)
|
417
|
+
buf = String.new
|
418
|
+
buf << '{'
|
419
|
+
comma = false
|
420
|
+
v.each do |x|
|
421
|
+
if comma
|
422
|
+
buf << ","
|
423
|
+
else
|
424
|
+
comma = true
|
425
|
+
end
|
426
|
+
|
427
|
+
buf << (x ? x.to_s : 'NULL')
|
428
|
+
end
|
429
|
+
buf << '}'
|
430
|
+
end
|
431
|
+
|
432
|
+
# Skip auto parameterization in LIMIT and OFFSET clauses
|
433
|
+
def select_limit_sql(sql)
|
434
|
+
if auto_param?(sql) && (@opts[:limit] || @opts[:offset])
|
435
|
+
sql.skip_auto_param{super}
|
436
|
+
else
|
437
|
+
super
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
# Skip auto parameterization in ORDER clause if used with
|
442
|
+
# integer values indicating ordering by the nth column.
|
443
|
+
def select_order_sql(sql)
|
444
|
+
if auto_param?(sql) && (order = @opts[:order]) && order.any?{|o| Integer === o || (SQL::OrderedExpression === o && Integer === o.expression)}
|
445
|
+
sql.skip_auto_param{super}
|
446
|
+
else
|
447
|
+
super
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
# Skip auto parameterization in CTE CYCLE clause
|
452
|
+
def select_with_sql_cte_search_cycle(sql,cte)
|
453
|
+
if auto_param?(sql) && cte[:cycle]
|
454
|
+
sql.skip_auto_param{super}
|
455
|
+
else
|
456
|
+
super
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
# Unless auto parameterization is disabled, use a string that
|
461
|
+
# can store the parameterized arguments.
|
462
|
+
def sql_string_origin
|
463
|
+
@opts[:no_auto_parameterize] ? super : QueryString.new
|
464
|
+
end
|
465
|
+
|
466
|
+
# If subquery uses with_sql with a method name symbol, get the dataset
|
467
|
+
# with_sql was called on, and use that as the subquery, recording the
|
468
|
+
# arguments to with_sql that will be used to calculate the sql.
|
469
|
+
def subselect_sql_dataset(sql, ds)
|
470
|
+
if ws_ds = ds.opts[:with_sql_dataset]
|
471
|
+
super(sql, ws_ds).clone(:subselect_sql_args=>ds.opts[:with_sql_args])
|
472
|
+
else
|
473
|
+
super
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
# If subquery used with_sql with a method name symbol, use the arguments to
|
478
|
+
# with_sql to determine the sql, so that the subselect can be parameterized.
|
479
|
+
def subselect_sql_append_sql(sql, ds)
|
480
|
+
if args = ds.opts[:subselect_sql_args]
|
481
|
+
ds.send(*args)
|
482
|
+
else
|
483
|
+
super
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
# Use auto parameterization for datasets with static SQL using placeholders.
|
488
|
+
def static_sql(sql)
|
489
|
+
if @opts[:append_sql] || @opts[:no_auto_parameterize] || String === sql
|
490
|
+
super
|
491
|
+
else
|
492
|
+
query_string = QueryString.new
|
493
|
+
literal_append(query_string, sql)
|
494
|
+
query_string
|
495
|
+
end
|
496
|
+
end
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
module SQL::Builders
|
502
|
+
# Skip auto parameterization for the given object when building queries.
|
503
|
+
def skip_pg_auto_param(v)
|
504
|
+
Postgres::AutoParameterize::SkipAutoParam.new(v)
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
Database.register_extension(:pg_auto_parameterize, Postgres::AutoParameterize::DatabaseMethods)
|
509
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
#
|
3
|
+
# The pg_auto_parameterize_in_array extension builds on the pg_auto_parameterize
|
4
|
+
# extension, adding support for handling additional types when converting from
|
5
|
+
# IN to = ANY and NOT IN to != ALL:
|
6
|
+
#
|
7
|
+
# DB[:table].where(column: [1.0, 2.0, ...])
|
8
|
+
# # Without extension: column IN ($1::numeric, $2:numeric, ...) # bound variables: 1.0, 2.0, ...
|
9
|
+
# # With extension: column = ANY($1::numeric[]) # bound variables: [1.0, 2.0, ...]
|
10
|
+
#
|
11
|
+
# This prevents the use of an unbounded number of bound variables based on the
|
12
|
+
# size of the array, as well as using different SQL for different array sizes.
|
13
|
+
#
|
14
|
+
# The following types are supported when doing the conversions, with the database
|
15
|
+
# type used:
|
16
|
+
#
|
17
|
+
# Float :: if any are infinite or NaN, double precision, otherwise numeric
|
18
|
+
# BigDecimal :: numeric
|
19
|
+
# Date :: date
|
20
|
+
# Time :: timestamp (or timestamptz if pg_timestamptz extension is used)
|
21
|
+
# DateTime :: timestamp (or timestamptz if pg_timestamptz extension is used)
|
22
|
+
# Sequel::SQLTime :: time
|
23
|
+
# Sequel::SQL::Blob :: bytea
|
24
|
+
#
|
25
|
+
# String values are also supported using the +text+ type, but only if the
|
26
|
+
# +:treat_string_list_as_text_array+ Database option is used. This is because
|
27
|
+
# treating strings as text can break programs, since the type for
|
28
|
+
# literal strings in PostgreSQL is +unknown+, not +text+.
|
29
|
+
#
|
30
|
+
# The conversion is only done for single dimensional arrays that have more
|
31
|
+
# than two elements, where all elements are of the same class (other than
|
32
|
+
# nil values).
|
33
|
+
#
|
34
|
+
# Related module: Sequel::Postgres::AutoParameterizeInArray
|
35
|
+
|
36
|
+
module Sequel
|
37
|
+
module Postgres
|
38
|
+
# Enable automatically parameterizing queries.
|
39
|
+
module AutoParameterizeInArray
|
40
|
+
# Transform column IN (...) expressions into column = ANY($)
|
41
|
+
# and column NOT IN (...) expressions into column != ALL($)
|
42
|
+
# using an array bound variable for the ANY/ALL argument,
|
43
|
+
# if all values inside the predicate are of the same type and
|
44
|
+
# the type is handled by the extension.
|
45
|
+
# This is the same optimization PostgreSQL performs internally,
|
46
|
+
# but this reduces the number of bound variables.
|
47
|
+
def complex_expression_sql_append(sql, op, args)
|
48
|
+
case op
|
49
|
+
when :IN, :"NOT IN"
|
50
|
+
l, r = args
|
51
|
+
if auto_param?(sql) && (type = _bound_variable_type_for_array(r))
|
52
|
+
if op == :IN
|
53
|
+
op = :"="
|
54
|
+
func = :ANY
|
55
|
+
else
|
56
|
+
op = :!=
|
57
|
+
func = :ALL
|
58
|
+
end
|
59
|
+
args = [l, Sequel.function(func, Sequel.pg_array(r, type))]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
super
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# The bound variable type string to use for the bound variable array.
|
69
|
+
# Returns nil if a bound variable should not be used for the array.
|
70
|
+
def _bound_variable_type_for_array(r)
|
71
|
+
return unless Array === r && r.size > 1
|
72
|
+
classes = r.map(&:class)
|
73
|
+
classes.uniq!
|
74
|
+
classes.delete(NilClass)
|
75
|
+
return unless classes.size == 1
|
76
|
+
|
77
|
+
klass = classes[0]
|
78
|
+
if klass == Integer
|
79
|
+
# This branch is not taken on Ruby <2.4, because of the Fixnum/Bignum split.
|
80
|
+
# However, that causes no problems as pg_auto_parameterize handles integer
|
81
|
+
# arrays natively (though the SQL used is different)
|
82
|
+
"int8"
|
83
|
+
elsif klass == String
|
84
|
+
"text" if db.typecast_value(:boolean, db.opts[:treat_string_list_as_text_array])
|
85
|
+
elsif klass == BigDecimal
|
86
|
+
"numeric"
|
87
|
+
elsif klass == Date
|
88
|
+
"date"
|
89
|
+
elsif klass == Time
|
90
|
+
@db.cast_type_literal(Time)
|
91
|
+
elsif klass == Float
|
92
|
+
# PostgreSQL treats literal floats as numeric, not double precision
|
93
|
+
# But older versions of PostgreSQL don't handle Infinity/NaN in numeric
|
94
|
+
r.all?{|v| v.nil? || v.finite?} ? "numeric" : "double precision"
|
95
|
+
elsif klass == Sequel::SQLTime
|
96
|
+
"time"
|
97
|
+
elsif klass == DateTime
|
98
|
+
@db.cast_type_literal(DateTime)
|
99
|
+
elsif klass == Sequel::SQL::Blob
|
100
|
+
"bytea"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
Database.register_extension(:pg_auto_parameterize_in_array) do |db|
|
107
|
+
db.extension(:pg_array, :pg_auto_parameterize)
|
108
|
+
db.extend_datasets(Postgres::AutoParameterizeInArray)
|
109
|
+
end
|
110
|
+
end
|
@@ -42,7 +42,7 @@
|
|
42
42
|
#
|
43
43
|
# This extension integrates with the pg_array extension. If you plan
|
44
44
|
# to use arrays of enum types, load the pg_array extension before the
|
45
|
-
#
|
45
|
+
# pg_enum extension:
|
46
46
|
#
|
47
47
|
# DB.extension :pg_array, :pg_enum
|
48
48
|
#
|
@@ -166,8 +166,7 @@ module Sequel
|
|
166
166
|
def schema_post_process(_)
|
167
167
|
super.each do |_, s|
|
168
168
|
oid = s[:oid]
|
169
|
-
if values = Sequel.synchronize{@enum_labels[oid]}
|
170
|
-
s[:type] = :enum
|
169
|
+
if s[:type] == :enum && (values = Sequel.synchronize{@enum_labels[oid]})
|
171
170
|
s[:enum_values] = values
|
172
171
|
end
|
173
172
|
end
|