sequel 5.83.0 → 5.106.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.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/lib/sequel/adapters/ado/access.rb +2 -2
- data/lib/sequel/adapters/ado.rb +2 -2
- data/lib/sequel/adapters/amalgalite.rb +16 -1
- data/lib/sequel/adapters/ibmdb.rb +2 -1
- data/lib/sequel/adapters/jdbc/db2.rb +2 -2
- data/lib/sequel/adapters/jdbc/derby.rb +4 -2
- data/lib/sequel/adapters/jdbc/h2.rb +4 -4
- data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -2
- data/lib/sequel/adapters/jdbc/jtds.rb +2 -2
- data/lib/sequel/adapters/jdbc/mysql.rb +3 -3
- data/lib/sequel/adapters/jdbc/oracle.rb +6 -6
- data/lib/sequel/adapters/jdbc/postgresql.rb +5 -5
- data/lib/sequel/adapters/jdbc/sqlanywhere.rb +6 -6
- data/lib/sequel/adapters/jdbc/sqlite.rb +3 -3
- data/lib/sequel/adapters/jdbc/sqlserver.rb +3 -3
- data/lib/sequel/adapters/jdbc.rb +29 -15
- data/lib/sequel/adapters/mysql.rb +1 -1
- data/lib/sequel/adapters/mysql2.rb +10 -3
- data/lib/sequel/adapters/odbc.rb +1 -1
- data/lib/sequel/adapters/oracle.rb +16 -0
- data/lib/sequel/adapters/postgres.rb +2 -1
- data/lib/sequel/adapters/shared/access.rb +1 -0
- data/lib/sequel/adapters/shared/db2.rb +8 -3
- data/lib/sequel/adapters/shared/mssql.rb +9 -8
- data/lib/sequel/adapters/shared/mysql.rb +16 -11
- data/lib/sequel/adapters/shared/oracle.rb +1 -0
- data/lib/sequel/adapters/shared/postgres.rb +438 -47
- data/lib/sequel/adapters/shared/sqlite.rb +9 -7
- data/lib/sequel/adapters/sqlite.rb +20 -2
- data/lib/sequel/adapters/tinytds.rb +2 -2
- data/lib/sequel/adapters/trilogy.rb +2 -3
- data/lib/sequel/ast_transformer.rb +2 -0
- data/lib/sequel/connection_pool/sharded_timed_queue.rb +42 -11
- data/lib/sequel/connection_pool/timed_queue.rb +33 -10
- data/lib/sequel/connection_pool.rb +8 -2
- data/lib/sequel/core.rb +31 -2
- data/lib/sequel/database/connecting.rb +1 -1
- data/lib/sequel/database/dataset_defaults.rb +3 -3
- data/lib/sequel/database/misc.rb +29 -11
- data/lib/sequel/database/query.rb +12 -12
- data/lib/sequel/database/schema_generator.rb +117 -16
- data/lib/sequel/database/schema_methods.rb +85 -30
- data/lib/sequel/dataset/actions.rb +83 -11
- data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +1 -1
- data/lib/sequel/dataset/placeholder_literalizer.rb +3 -0
- data/lib/sequel/dataset/prepared_statements.rb +76 -28
- data/lib/sequel/dataset/query.rb +26 -11
- data/lib/sequel/dataset/sql.rb +40 -17
- data/lib/sequel/extensions/async_thread_pool.rb +1 -1
- data/lib/sequel/extensions/caller_logging.rb +1 -3
- data/lib/sequel/extensions/connection_checkout_event_callback.rb +151 -0
- data/lib/sequel/extensions/connection_expiration.rb +44 -5
- data/lib/sequel/extensions/connection_validator.rb +15 -10
- data/lib/sequel/extensions/constraint_validations.rb +3 -3
- data/lib/sequel/extensions/dataset_run.rb +41 -0
- data/lib/sequel/extensions/date_arithmetic.rb +6 -6
- data/lib/sequel/extensions/empty_array_consider_nulls.rb +7 -0
- data/lib/sequel/extensions/eval_inspect.rb +3 -1
- data/lib/sequel/extensions/inflector.rb +2 -2
- data/lib/sequel/extensions/lit_require_frozen.rb +131 -0
- data/lib/sequel/extensions/looser_typecasting.rb +5 -12
- data/lib/sequel/extensions/migration.rb +38 -21
- data/lib/sequel/extensions/null_dataset.rb +2 -2
- data/lib/sequel/extensions/pg_array_ops.rb +41 -0
- data/lib/sequel/extensions/pg_auto_parameterize.rb +10 -3
- data/lib/sequel/extensions/pg_auto_parameterize_duplicate_query_detection.rb +191 -0
- data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +94 -10
- data/lib/sequel/extensions/pg_enum.rb +4 -4
- data/lib/sequel/extensions/pg_hstore.rb +1 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +10 -5
- data/lib/sequel/extensions/pg_json_ops.rb +674 -18
- data/lib/sequel/extensions/pg_multirange.rb +1 -1
- data/lib/sequel/extensions/pg_range.rb +14 -2
- data/lib/sequel/extensions/pg_row.rb +5 -3
- data/lib/sequel/extensions/pg_schema_caching.rb +90 -0
- data/lib/sequel/extensions/provenance.rb +1 -3
- data/lib/sequel/extensions/query_blocker.rb +172 -0
- data/lib/sequel/extensions/schema_caching.rb +24 -9
- data/lib/sequel/extensions/schema_dumper.rb +17 -5
- data/lib/sequel/extensions/set_literalizer.rb +20 -39
- data/lib/sequel/extensions/split_array_nil.rb +12 -2
- data/lib/sequel/extensions/sqlite_json_ops.rb +1 -1
- data/lib/sequel/extensions/string_agg.rb +2 -2
- data/lib/sequel/extensions/to_dot.rb +5 -0
- data/lib/sequel/extensions/virtual_row_method_block.rb +1 -0
- data/lib/sequel/model/associations.rb +215 -15
- data/lib/sequel/model/base.rb +198 -35
- data/lib/sequel/model/plugins.rb +12 -1
- data/lib/sequel/model.rb +1 -0
- data/lib/sequel/plugins/accessed_columns.rb +4 -0
- data/lib/sequel/plugins/active_model.rb +5 -6
- data/lib/sequel/plugins/association_pks.rb +2 -0
- data/lib/sequel/plugins/auto_validations.rb +2 -0
- data/lib/sequel/plugins/class_table_inheritance_constraint_validations.rb +82 -0
- data/lib/sequel/plugins/columns_updated.rb +4 -0
- data/lib/sequel/plugins/composition.rb +3 -1
- data/lib/sequel/plugins/constraint_validations.rb +15 -10
- data/lib/sequel/plugins/dataset_associations.rb +20 -1
- data/lib/sequel/plugins/deprecated_associations.rb +151 -0
- data/lib/sequel/plugins/detect_unnecessary_association_options.rb +164 -0
- data/lib/sequel/plugins/dirty.rb +9 -2
- data/lib/sequel/plugins/enum.rb +1 -1
- data/lib/sequel/plugins/forbid_lazy_load.rb +15 -1
- data/lib/sequel/plugins/insert_conflict.rb +4 -0
- data/lib/sequel/plugins/insert_returning_select.rb +10 -1
- data/lib/sequel/plugins/inspect_pk.rb +44 -0
- data/lib/sequel/plugins/instance_filters.rb +8 -1
- data/lib/sequel/plugins/instance_hooks.rb +4 -0
- data/lib/sequel/plugins/inverted_subsets.rb +1 -0
- data/lib/sequel/plugins/json_serializer.rb +6 -10
- data/lib/sequel/plugins/lazy_attributes.rb +1 -1
- data/lib/sequel/plugins/many_through_many.rb +21 -0
- data/lib/sequel/plugins/modification_detection.rb +2 -0
- data/lib/sequel/plugins/mssql_optimistic_locking.rb +1 -1
- data/lib/sequel/plugins/nested_attributes.rb +10 -5
- data/lib/sequel/plugins/paged_operations.rb +5 -2
- data/lib/sequel/plugins/pg_array_associations.rb +2 -2
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +6 -1
- data/lib/sequel/plugins/pg_auto_validate_enums.rb +88 -0
- data/lib/sequel/plugins/pg_eager_any_typed_array.rb +95 -0
- data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +1 -1
- data/lib/sequel/plugins/rcte_tree.rb +6 -6
- data/lib/sequel/plugins/serialization.rb +23 -6
- data/lib/sequel/plugins/serialization_modification_detection.rb +4 -0
- data/lib/sequel/plugins/single_statement_dataset_destroy.rb +49 -0
- data/lib/sequel/plugins/split_values.rb +12 -0
- data/lib/sequel/plugins/sql_comments.rb +7 -2
- data/lib/sequel/plugins/static_cache.rb +13 -0
- data/lib/sequel/plugins/static_cache_cache.rb +50 -13
- data/lib/sequel/plugins/subset_conditions.rb +85 -5
- data/lib/sequel/plugins/subset_static_cache.rb +278 -0
- data/lib/sequel/plugins/table_select.rb +7 -0
- data/lib/sequel/plugins/tactical_eager_loading.rb +4 -0
- data/lib/sequel/plugins/unused_associations.rb +11 -3
- data/lib/sequel/plugins/update_primary_key.rb +4 -0
- data/lib/sequel/plugins/validation_class_methods.rb +1 -1
- data/lib/sequel/plugins/validation_contexts.rb +4 -0
- data/lib/sequel/sql.rb +27 -12
- data/lib/sequel/version.rb +1 -1
- metadata +17 -240
- data/CHANGELOG +0 -1393
- data/README.rdoc +0 -936
- data/doc/advanced_associations.rdoc +0 -884
- data/doc/association_basics.rdoc +0 -1859
- data/doc/bin_sequel.rdoc +0 -146
- data/doc/cheat_sheet.rdoc +0 -255
- data/doc/code_order.rdoc +0 -104
- data/doc/core_extensions.rdoc +0 -405
- data/doc/dataset_basics.rdoc +0 -96
- data/doc/dataset_filtering.rdoc +0 -222
- data/doc/extensions.rdoc +0 -77
- data/doc/fork_safety.rdoc +0 -84
- data/doc/mass_assignment.rdoc +0 -98
- data/doc/migration.rdoc +0 -660
- data/doc/model_dataset_method_design.rdoc +0 -129
- data/doc/model_hooks.rdoc +0 -254
- data/doc/model_plugins.rdoc +0 -270
- data/doc/mssql_stored_procedures.rdoc +0 -43
- data/doc/object_model.rdoc +0 -563
- data/doc/opening_databases.rdoc +0 -439
- data/doc/postgresql.rdoc +0 -611
- data/doc/prepared_statements.rdoc +0 -144
- data/doc/querying.rdoc +0 -1070
- data/doc/reflection.rdoc +0 -120
- data/doc/release_notes/5.0.0.txt +0 -159
- data/doc/release_notes/5.1.0.txt +0 -31
- data/doc/release_notes/5.10.0.txt +0 -84
- data/doc/release_notes/5.11.0.txt +0 -83
- data/doc/release_notes/5.12.0.txt +0 -141
- data/doc/release_notes/5.13.0.txt +0 -27
- data/doc/release_notes/5.14.0.txt +0 -63
- data/doc/release_notes/5.15.0.txt +0 -39
- data/doc/release_notes/5.16.0.txt +0 -110
- data/doc/release_notes/5.17.0.txt +0 -31
- data/doc/release_notes/5.18.0.txt +0 -69
- data/doc/release_notes/5.19.0.txt +0 -28
- data/doc/release_notes/5.2.0.txt +0 -33
- data/doc/release_notes/5.20.0.txt +0 -89
- data/doc/release_notes/5.21.0.txt +0 -87
- data/doc/release_notes/5.22.0.txt +0 -48
- data/doc/release_notes/5.23.0.txt +0 -56
- data/doc/release_notes/5.24.0.txt +0 -56
- data/doc/release_notes/5.25.0.txt +0 -32
- data/doc/release_notes/5.26.0.txt +0 -35
- data/doc/release_notes/5.27.0.txt +0 -21
- data/doc/release_notes/5.28.0.txt +0 -16
- data/doc/release_notes/5.29.0.txt +0 -22
- data/doc/release_notes/5.3.0.txt +0 -121
- data/doc/release_notes/5.30.0.txt +0 -20
- data/doc/release_notes/5.31.0.txt +0 -148
- data/doc/release_notes/5.32.0.txt +0 -46
- data/doc/release_notes/5.33.0.txt +0 -24
- data/doc/release_notes/5.34.0.txt +0 -40
- data/doc/release_notes/5.35.0.txt +0 -56
- data/doc/release_notes/5.36.0.txt +0 -60
- data/doc/release_notes/5.37.0.txt +0 -30
- data/doc/release_notes/5.38.0.txt +0 -28
- data/doc/release_notes/5.39.0.txt +0 -19
- data/doc/release_notes/5.4.0.txt +0 -80
- data/doc/release_notes/5.40.0.txt +0 -40
- data/doc/release_notes/5.41.0.txt +0 -25
- data/doc/release_notes/5.42.0.txt +0 -136
- data/doc/release_notes/5.43.0.txt +0 -98
- data/doc/release_notes/5.44.0.txt +0 -32
- data/doc/release_notes/5.45.0.txt +0 -34
- data/doc/release_notes/5.46.0.txt +0 -87
- data/doc/release_notes/5.47.0.txt +0 -59
- data/doc/release_notes/5.48.0.txt +0 -14
- data/doc/release_notes/5.49.0.txt +0 -59
- data/doc/release_notes/5.5.0.txt +0 -61
- data/doc/release_notes/5.50.0.txt +0 -78
- data/doc/release_notes/5.51.0.txt +0 -47
- data/doc/release_notes/5.52.0.txt +0 -87
- data/doc/release_notes/5.53.0.txt +0 -23
- data/doc/release_notes/5.54.0.txt +0 -27
- data/doc/release_notes/5.55.0.txt +0 -21
- data/doc/release_notes/5.56.0.txt +0 -51
- data/doc/release_notes/5.57.0.txt +0 -23
- data/doc/release_notes/5.58.0.txt +0 -31
- data/doc/release_notes/5.59.0.txt +0 -73
- data/doc/release_notes/5.6.0.txt +0 -31
- data/doc/release_notes/5.60.0.txt +0 -22
- data/doc/release_notes/5.61.0.txt +0 -43
- data/doc/release_notes/5.62.0.txt +0 -132
- data/doc/release_notes/5.63.0.txt +0 -33
- data/doc/release_notes/5.64.0.txt +0 -50
- data/doc/release_notes/5.65.0.txt +0 -21
- data/doc/release_notes/5.66.0.txt +0 -24
- data/doc/release_notes/5.67.0.txt +0 -32
- data/doc/release_notes/5.68.0.txt +0 -61
- data/doc/release_notes/5.69.0.txt +0 -26
- data/doc/release_notes/5.7.0.txt +0 -108
- data/doc/release_notes/5.70.0.txt +0 -35
- data/doc/release_notes/5.71.0.txt +0 -21
- data/doc/release_notes/5.72.0.txt +0 -33
- data/doc/release_notes/5.73.0.txt +0 -66
- data/doc/release_notes/5.74.0.txt +0 -45
- data/doc/release_notes/5.75.0.txt +0 -35
- data/doc/release_notes/5.76.0.txt +0 -86
- data/doc/release_notes/5.77.0.txt +0 -63
- data/doc/release_notes/5.78.0.txt +0 -67
- data/doc/release_notes/5.79.0.txt +0 -28
- data/doc/release_notes/5.8.0.txt +0 -170
- data/doc/release_notes/5.80.0.txt +0 -40
- data/doc/release_notes/5.81.0.txt +0 -31
- data/doc/release_notes/5.82.0.txt +0 -61
- data/doc/release_notes/5.83.0.txt +0 -56
- data/doc/release_notes/5.9.0.txt +0 -99
- data/doc/schema_modification.rdoc +0 -679
- data/doc/security.rdoc +0 -443
- data/doc/sharding.rdoc +0 -286
- data/doc/sql.rdoc +0 -648
- data/doc/testing.rdoc +0 -204
- data/doc/thread_safety.rdoc +0 -15
- data/doc/transactions.rdoc +0 -250
- data/doc/validations.rdoc +0 -558
- data/doc/virtual_rows.rdoc +0 -265
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
#
|
|
3
|
+
# The pg_auto_parameterize_duplicate_query_detection extension builds on the
|
|
4
|
+
# pg_auto_parameterize extension, adding support for detecting duplicate
|
|
5
|
+
# queries inside a block that occur at the same location. This is designed
|
|
6
|
+
# mostly to catch duplicate query issues (e.g. N+1 queries) during testing.
|
|
7
|
+
#
|
|
8
|
+
# To detect duplicate queries inside a block of code, wrap the code with
|
|
9
|
+
# +detect_duplicate_queries+:
|
|
10
|
+
#
|
|
11
|
+
# DB.detect_duplicate_queries{your_code}
|
|
12
|
+
#
|
|
13
|
+
# With this approach, if the test runs code where the same query is executed
|
|
14
|
+
# more than once with the same call stack, a
|
|
15
|
+
# Sequel::Postgres::AutoParameterizeDuplicateQueryDetection::DuplicateQueries
|
|
16
|
+
# exception will be raised.
|
|
17
|
+
#
|
|
18
|
+
# You can pass the +:warn+ option to +detect_duplicate_queries+ to warn
|
|
19
|
+
# instead of raising. Note that if the block passed to +detect_duplicate_queries+
|
|
20
|
+
# raises, this extension will warn, and raise the original exception.
|
|
21
|
+
#
|
|
22
|
+
# For more control, you can pass the +:handler+ option to
|
|
23
|
+
# +detect_duplicate_queries+. If the +:handler+ option is provided, this
|
|
24
|
+
# extension will call the +:handler+ option with the hash of duplicate
|
|
25
|
+
# query information, and will not raise or warn. This can be useful in
|
|
26
|
+
# production environments, to record duplicate queries for later analysis.
|
|
27
|
+
#
|
|
28
|
+
# For accuracy, the entire call stack is always used as part of the hash key
|
|
29
|
+
# to determine whether a query is duplicate. However, you can filter the
|
|
30
|
+
# displayed backtrace by using the +:backtrace_filter+ option.
|
|
31
|
+
#
|
|
32
|
+
# +detect_duplicate_queries+ is concurrency aware, it uses the same approach
|
|
33
|
+
# that Sequel's default connection pools use. So if you are running multiple
|
|
34
|
+
# threads, +detect_duplicate_queries+ will only report duplicate queries for
|
|
35
|
+
# the current thread (or fiber if the fiber_concurrency extension is used).
|
|
36
|
+
#
|
|
37
|
+
# When testing applications, it's probably best to use this to wrap the
|
|
38
|
+
# application being tested. For example, testing with rack-test, if your app
|
|
39
|
+
# is +App+, you would want to wrap it:
|
|
40
|
+
#
|
|
41
|
+
# include Rack::Test::Methods
|
|
42
|
+
#
|
|
43
|
+
# WrappedApp = lambda do |env|
|
|
44
|
+
# DB.detect_duplicate_queries{App.call(env)}
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# def app
|
|
48
|
+
# WrappedApp
|
|
49
|
+
# end
|
|
50
|
+
#
|
|
51
|
+
# It is possible to use this to wrap each separate spec using an around hook,
|
|
52
|
+
# but that can result in false positives when using libraries that have
|
|
53
|
+
# implicit retrying (such as Capybara), as you can have the same call stack
|
|
54
|
+
# for multiple requests.
|
|
55
|
+
#
|
|
56
|
+
# Related module: Sequel::Postgres::AutoParameterizeDuplicateQueryDetection
|
|
57
|
+
|
|
58
|
+
module Sequel
|
|
59
|
+
module Postgres
|
|
60
|
+
# Enable detecting duplicate queries inside a block
|
|
61
|
+
module AutoParameterizeDuplicateQueryDetection
|
|
62
|
+
def self.extended(db)
|
|
63
|
+
db.instance_exec do
|
|
64
|
+
@duplicate_query_detection_contexts = {}
|
|
65
|
+
@duplicate_query_detection_mutex = Mutex.new
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Exception class raised when duplicate queries are detected.
|
|
70
|
+
class DuplicateQueries < Sequel::Error
|
|
71
|
+
# A hash of queries that were duplicate. Keys are arrays
|
|
72
|
+
# with 2 entries, the first being the query SQL, and the
|
|
73
|
+
# second being the related call stack (backtrace).
|
|
74
|
+
# The values are the number of query executions.
|
|
75
|
+
attr_reader :queries
|
|
76
|
+
|
|
77
|
+
def initialize(message, queries)
|
|
78
|
+
@queries = queries
|
|
79
|
+
super(message)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Record each query executed so duplicates can be detected,
|
|
84
|
+
# if queries are being recorded.
|
|
85
|
+
def execute(sql, opts=OPTS, &block)
|
|
86
|
+
record, queries = duplicate_query_recorder_state
|
|
87
|
+
|
|
88
|
+
if record
|
|
89
|
+
queries[[sql.is_a?(Symbol) ? sql : sql.to_s, caller].freeze] += 1
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
super
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Ignore (do not record) queries inside given block. This can
|
|
96
|
+
# be useful in situations where you want to run your entire test suite
|
|
97
|
+
# with duplicate query detection, but you have duplicate queries in
|
|
98
|
+
# some parts of your application where it is not trivial to use a
|
|
99
|
+
# different approach. You can mark those specific sections with
|
|
100
|
+
# +ignore_duplicate_queries+, and still get duplicate query detection
|
|
101
|
+
# for the rest of the application.
|
|
102
|
+
def ignore_duplicate_queries(&block)
|
|
103
|
+
if state = duplicate_query_recorder_state
|
|
104
|
+
change_duplicate_query_recorder_state(state, false, &block)
|
|
105
|
+
else
|
|
106
|
+
# If we are not inside a detect_duplicate_queries block, there is
|
|
107
|
+
# no need to do anything, since we are not recording queries.
|
|
108
|
+
yield
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Run the duplicate query detector during the block.
|
|
113
|
+
# Options:
|
|
114
|
+
#
|
|
115
|
+
# :backtrace_filter :: Regexp used to filter the displayed backtrace.
|
|
116
|
+
# :handler :: If present, called with hash of duplicate query information,
|
|
117
|
+
# instead of raising or warning.
|
|
118
|
+
# :warn :: Always warn instead of raising for duplicate queries.
|
|
119
|
+
#
|
|
120
|
+
# Note that if you nest calls to this method, only the top
|
|
121
|
+
# level call will respect the passed options.
|
|
122
|
+
def detect_duplicate_queries(opts=OPTS, &block)
|
|
123
|
+
current = Sequel.current
|
|
124
|
+
if state = duplicate_query_recorder_state(current)
|
|
125
|
+
return change_duplicate_query_recorder_state(state, true, &block)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
@duplicate_query_detection_mutex.synchronize do
|
|
129
|
+
@duplicate_query_detection_contexts[current] = [true, Hash.new(0)]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
begin
|
|
133
|
+
yield
|
|
134
|
+
rescue Exception => e
|
|
135
|
+
raise
|
|
136
|
+
ensure
|
|
137
|
+
_, queries = @duplicate_query_detection_mutex.synchronize do
|
|
138
|
+
@duplicate_query_detection_contexts.delete(current)
|
|
139
|
+
end
|
|
140
|
+
queries.delete_if{|_,v| v < 2}
|
|
141
|
+
|
|
142
|
+
unless queries.empty?
|
|
143
|
+
if handler = opts[:handler]
|
|
144
|
+
handler.call(queries)
|
|
145
|
+
else
|
|
146
|
+
backtrace_filter = opts[:backtrace_filter]
|
|
147
|
+
backtrace_filter_note = backtrace_filter ? " (filtered)" : ""
|
|
148
|
+
query_info = queries.map do |k,v|
|
|
149
|
+
backtrace = k[1]
|
|
150
|
+
backtrace = backtrace.grep(backtrace_filter) if backtrace_filter
|
|
151
|
+
"times:#{v}\nsql:#{k[0]}\nbacktrace#{backtrace_filter_note}:\n#{backtrace.join("\n")}\n"
|
|
152
|
+
end
|
|
153
|
+
message = "duplicate queries detected:\n\n#{query_info.join("\n")}"
|
|
154
|
+
|
|
155
|
+
if e || opts[:warn]
|
|
156
|
+
warn(message)
|
|
157
|
+
else
|
|
158
|
+
raise DuplicateQueries.new(message, queries)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
private
|
|
166
|
+
|
|
167
|
+
# Get the query record state for the given context.
|
|
168
|
+
def duplicate_query_recorder_state(current=Sequel.current)
|
|
169
|
+
@duplicate_query_detection_mutex.synchronize{@duplicate_query_detection_contexts[current]}
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Temporarily change whether to record queries for the block, resetting the
|
|
173
|
+
# previous setting after the block exits.
|
|
174
|
+
def change_duplicate_query_recorder_state(state, setting)
|
|
175
|
+
prev = state[0]
|
|
176
|
+
state[0] = setting
|
|
177
|
+
|
|
178
|
+
begin
|
|
179
|
+
yield
|
|
180
|
+
ensure
|
|
181
|
+
state[0] = prev
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
Database.register_extension(:pg_auto_parameterize_duplicate_query_detection) do |db|
|
|
188
|
+
db.extension(:pg_auto_parameterize)
|
|
189
|
+
db.extend(Postgres::AutoParameterizeDuplicateQueryDetection)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
@@ -21,15 +21,26 @@
|
|
|
21
21
|
# DateTime :: timestamp (or timestamptz if pg_timestamptz extension is used)
|
|
22
22
|
# Sequel::SQLTime :: time
|
|
23
23
|
# Sequel::SQL::Blob :: bytea
|
|
24
|
+
#
|
|
25
|
+
# Arrays of string values are not automatically converted by default, because the Ruby
|
|
26
|
+
# String class can represent a number of different database types. To convert
|
|
27
|
+
# arrays of Ruby strings to an untyped array (a query parameter with no explicit
|
|
28
|
+
# type cast), set the +:treat_string_list_as_untyped_array+ Database option
|
|
29
|
+
# before loading the extension.
|
|
24
30
|
#
|
|
25
|
-
#
|
|
26
|
-
# +:treat_string_list_as_text_array+ Database option is used. This
|
|
27
|
-
#
|
|
28
|
-
#
|
|
31
|
+
# If you will only be using arrays of Ruby strings that represent the +text+ type,
|
|
32
|
+
# you can use the +:treat_string_list_as_text_array+ Database option is used. This
|
|
33
|
+
# can break programs, since the type for literal strings in PostgreSQL is +unknown+,
|
|
34
|
+
# not +text+.
|
|
29
35
|
#
|
|
30
|
-
# The conversion is only done for single dimensional arrays that have
|
|
31
|
-
#
|
|
32
|
-
# nil values).
|
|
36
|
+
# The conversion is only done for single dimensional arrays that have two or
|
|
37
|
+
# more elements, where all elements are of the same class (other than
|
|
38
|
+
# +nil+ values). You can also do the conversion for arrays of 1 element by setting
|
|
39
|
+
# <tt>pg_auto_parameterize_min_array_size: 1</tt> Database option. This makes
|
|
40
|
+
# finding cases that need special handling easier, but it doesn't match
|
|
41
|
+
# how PostgreSQL internally converts the expression (PostgreSQL converts
|
|
42
|
+
# <tt>IN (single_value)</tt> to <tt>= single_value</tt>, not
|
|
43
|
+
# <tt>= ANY(ARRAY[single_value])</tt>).
|
|
33
44
|
#
|
|
34
45
|
# Related module: Sequel::Postgres::AutoParameterizeInArray
|
|
35
46
|
|
|
@@ -37,6 +48,47 @@ module Sequel
|
|
|
37
48
|
module Postgres
|
|
38
49
|
# Enable automatically parameterizing queries.
|
|
39
50
|
module AutoParameterizeInArray
|
|
51
|
+
module TreatStringListAsUntypedArray
|
|
52
|
+
# Sentinal value to use as an auto param type to use auto parameterization
|
|
53
|
+
# of a string array without an explicit type cast.
|
|
54
|
+
NO_EXPLICIT_CAST = Object.new.freeze
|
|
55
|
+
|
|
56
|
+
# Wrapper for untyped PGArray values that will be parameterized directly
|
|
57
|
+
# into the query. This should only be used in cases where you know the
|
|
58
|
+
# value should be added as a query parameter.
|
|
59
|
+
class ParameterizedUntypedPGArray < SQL::Wrapper
|
|
60
|
+
def to_s_append(ds, sql)
|
|
61
|
+
sql.add_arg(@value)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
# Recognize NO_EXPLICIT_CAST sentinal value and use wrapped
|
|
68
|
+
# PGArray that will be parameterized into the query.
|
|
69
|
+
def _convert_array_to_pg_array_with_type(r, type)
|
|
70
|
+
if NO_EXPLICIT_CAST.equal?(type)
|
|
71
|
+
ParameterizedUntypedPGArray.new(Sequel.pg_array(r))
|
|
72
|
+
else
|
|
73
|
+
super
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Use a query parameter with no type cast for string arrays.
|
|
78
|
+
def _bound_variable_type_for_string_array(r)
|
|
79
|
+
NO_EXPLICIT_CAST
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
module TreatStringListAsTextArray
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
# Assume all string arrays used on RHS of IN/NOT IN are for type text[]
|
|
87
|
+
def _bound_variable_type_for_string_array(r)
|
|
88
|
+
"text"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
40
92
|
# Transform column IN (...) expressions into column = ANY($)
|
|
41
93
|
# and column NOT IN (...) expressions into column != ALL($)
|
|
42
94
|
# using an array bound variable for the ANY/ALL argument,
|
|
@@ -56,7 +108,7 @@ module Sequel
|
|
|
56
108
|
op = :!=
|
|
57
109
|
func = :ALL
|
|
58
110
|
end
|
|
59
|
-
args = [l, Sequel.function(func,
|
|
111
|
+
args = [l, Sequel.function(func, _convert_array_to_pg_array_with_type(r, type))]
|
|
60
112
|
end
|
|
61
113
|
end
|
|
62
114
|
|
|
@@ -68,7 +120,7 @@ module Sequel
|
|
|
68
120
|
# The bound variable type string to use for the bound variable array.
|
|
69
121
|
# Returns nil if a bound variable should not be used for the array.
|
|
70
122
|
def _bound_variable_type_for_array(r)
|
|
71
|
-
return unless Array === r && r.size
|
|
123
|
+
return unless (Array === r || Set === r) && r.size >= pg_auto_parameterize_min_array_size
|
|
72
124
|
classes = r.map(&:class)
|
|
73
125
|
classes.uniq!
|
|
74
126
|
classes.delete(NilClass)
|
|
@@ -81,7 +133,7 @@ module Sequel
|
|
|
81
133
|
# arrays natively (though the SQL used is different)
|
|
82
134
|
"int8"
|
|
83
135
|
elsif klass == String
|
|
84
|
-
|
|
136
|
+
_bound_variable_type_for_string_array(r)
|
|
85
137
|
elsif klass == BigDecimal
|
|
86
138
|
"numeric"
|
|
87
139
|
elsif klass == Date
|
|
@@ -100,11 +152,43 @@ module Sequel
|
|
|
100
152
|
"bytea"
|
|
101
153
|
end
|
|
102
154
|
end
|
|
155
|
+
|
|
156
|
+
# Do not auto parameterize string arrays by default.
|
|
157
|
+
def _bound_variable_type_for_string_array(r)
|
|
158
|
+
nil
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# The minimium size of array to auto parameterize.
|
|
162
|
+
def pg_auto_parameterize_min_array_size
|
|
163
|
+
2
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Convert RHS of IN/NOT IN operator to PGArray with given type.
|
|
167
|
+
def _convert_array_to_pg_array_with_type(r, type)
|
|
168
|
+
r = r.to_a if Set === r
|
|
169
|
+
Sequel.pg_array(r, type)
|
|
170
|
+
end
|
|
103
171
|
end
|
|
104
172
|
end
|
|
105
173
|
|
|
106
174
|
Database.register_extension(:pg_auto_parameterize_in_array) do |db|
|
|
107
175
|
db.extension(:pg_array, :pg_auto_parameterize)
|
|
108
176
|
db.extend_datasets(Postgres::AutoParameterizeInArray)
|
|
177
|
+
|
|
178
|
+
if db.typecast_value(:boolean, db.opts[:treat_string_list_as_text_array])
|
|
179
|
+
db.extend_datasets(Postgres::AutoParameterizeInArray::TreatStringListAsTextArray)
|
|
180
|
+
elsif db.typecast_value(:boolean, db.opts[:treat_string_list_as_untyped_array])
|
|
181
|
+
db.extend_datasets(Postgres::AutoParameterizeInArray::TreatStringListAsUntypedArray)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
if min_array_size = db.opts[:pg_auto_parameterize_min_array_size]
|
|
185
|
+
min_array_size = db.typecast_value(:integer, min_array_size)
|
|
186
|
+
mod = Module.new do
|
|
187
|
+
define_method(:pg_auto_parameterize_min_array_size){min_array_size}
|
|
188
|
+
private :pg_auto_parameterize_min_array_size
|
|
189
|
+
end
|
|
190
|
+
Sequel.set_temp_name(mod){"Sequel::Postgres::AutoParameterizeInArray::_MinArraySize#{min_array_size}"}
|
|
191
|
+
db.extend_datasets(mod)
|
|
192
|
+
end
|
|
109
193
|
end
|
|
110
194
|
end
|
|
@@ -127,7 +127,7 @@ module Sequel
|
|
|
127
127
|
|
|
128
128
|
# Run the SQL on the database, reparsing the enum labels after it is run.
|
|
129
129
|
def _process_enum_change_sql(sql)
|
|
130
|
-
run(sql)
|
|
130
|
+
run(sql.freeze)
|
|
131
131
|
parse_enum_labels
|
|
132
132
|
nil
|
|
133
133
|
end
|
|
@@ -149,12 +149,12 @@ module Sequel
|
|
|
149
149
|
from(:pg_type).
|
|
150
150
|
where(:oid=>enum_labels.keys).
|
|
151
151
|
exclude(:typarray=>0).
|
|
152
|
-
select_map([:typname, Sequel.cast(:typarray, Integer).as(:v)])
|
|
152
|
+
select_map([:typname, Sequel.cast(:typarray, Integer).as(:v), Sequel.cast(:oid, Integer).as(:sv)])
|
|
153
153
|
|
|
154
154
|
existing_oids = conversion_procs.keys
|
|
155
|
-
array_types.each do |name, oid|
|
|
155
|
+
array_types.each do |name, oid, scalar_oid|
|
|
156
156
|
next if existing_oids.include?(oid)
|
|
157
|
-
register_array_type(name, :oid=>oid)
|
|
157
|
+
register_array_type(name, :oid=>oid, :scalar_oid=>scalar_oid)
|
|
158
158
|
end
|
|
159
159
|
end
|
|
160
160
|
|
|
@@ -136,7 +136,7 @@ module Sequel
|
|
|
136
136
|
module DatabaseMethods
|
|
137
137
|
def self.extended(db)
|
|
138
138
|
db.instance_exec do
|
|
139
|
-
add_named_conversion_proc(:hstore
|
|
139
|
+
add_named_conversion_proc(:hstore){|v| HStore.parse(v)}
|
|
140
140
|
@schema_type_classes[:hstore] = HStore
|
|
141
141
|
end
|
|
142
142
|
end
|
|
@@ -294,13 +294,18 @@ module Sequel
|
|
|
294
294
|
SQL::Function.new(name, self, *args)
|
|
295
295
|
end
|
|
296
296
|
|
|
297
|
-
# Wrap argument in a PGArray if it is an array
|
|
297
|
+
# Wrap argument in a PGArray if it is an array or a set
|
|
298
298
|
def wrap_input_array(obj)
|
|
299
|
-
if
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
299
|
+
if Sequel.respond_to?(:pg_array)
|
|
300
|
+
case obj
|
|
301
|
+
when Array
|
|
302
|
+
return Sequel.pg_array(obj)
|
|
303
|
+
when Set
|
|
304
|
+
return Sequel.pg_array(obj.to_a)
|
|
305
|
+
end
|
|
303
306
|
end
|
|
307
|
+
|
|
308
|
+
obj
|
|
304
309
|
end
|
|
305
310
|
|
|
306
311
|
# Wrap argument in an Hstore if it is a hash
|