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.
Files changed (259) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/lib/sequel/adapters/ado/access.rb +2 -2
  4. data/lib/sequel/adapters/ado.rb +2 -2
  5. data/lib/sequel/adapters/amalgalite.rb +16 -1
  6. data/lib/sequel/adapters/ibmdb.rb +2 -1
  7. data/lib/sequel/adapters/jdbc/db2.rb +2 -2
  8. data/lib/sequel/adapters/jdbc/derby.rb +4 -2
  9. data/lib/sequel/adapters/jdbc/h2.rb +4 -4
  10. data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -2
  11. data/lib/sequel/adapters/jdbc/jtds.rb +2 -2
  12. data/lib/sequel/adapters/jdbc/mysql.rb +3 -3
  13. data/lib/sequel/adapters/jdbc/oracle.rb +6 -6
  14. data/lib/sequel/adapters/jdbc/postgresql.rb +5 -5
  15. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +6 -6
  16. data/lib/sequel/adapters/jdbc/sqlite.rb +3 -3
  17. data/lib/sequel/adapters/jdbc/sqlserver.rb +3 -3
  18. data/lib/sequel/adapters/jdbc.rb +29 -15
  19. data/lib/sequel/adapters/mysql.rb +1 -1
  20. data/lib/sequel/adapters/mysql2.rb +10 -3
  21. data/lib/sequel/adapters/odbc.rb +1 -1
  22. data/lib/sequel/adapters/oracle.rb +16 -0
  23. data/lib/sequel/adapters/postgres.rb +2 -1
  24. data/lib/sequel/adapters/shared/access.rb +1 -0
  25. data/lib/sequel/adapters/shared/db2.rb +8 -3
  26. data/lib/sequel/adapters/shared/mssql.rb +9 -8
  27. data/lib/sequel/adapters/shared/mysql.rb +16 -11
  28. data/lib/sequel/adapters/shared/oracle.rb +1 -0
  29. data/lib/sequel/adapters/shared/postgres.rb +438 -47
  30. data/lib/sequel/adapters/shared/sqlite.rb +9 -7
  31. data/lib/sequel/adapters/sqlite.rb +20 -2
  32. data/lib/sequel/adapters/tinytds.rb +2 -2
  33. data/lib/sequel/adapters/trilogy.rb +2 -3
  34. data/lib/sequel/ast_transformer.rb +2 -0
  35. data/lib/sequel/connection_pool/sharded_timed_queue.rb +42 -11
  36. data/lib/sequel/connection_pool/timed_queue.rb +33 -10
  37. data/lib/sequel/connection_pool.rb +8 -2
  38. data/lib/sequel/core.rb +31 -2
  39. data/lib/sequel/database/connecting.rb +1 -1
  40. data/lib/sequel/database/dataset_defaults.rb +3 -3
  41. data/lib/sequel/database/misc.rb +29 -11
  42. data/lib/sequel/database/query.rb +12 -12
  43. data/lib/sequel/database/schema_generator.rb +117 -16
  44. data/lib/sequel/database/schema_methods.rb +85 -30
  45. data/lib/sequel/dataset/actions.rb +83 -11
  46. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +1 -1
  47. data/lib/sequel/dataset/placeholder_literalizer.rb +3 -0
  48. data/lib/sequel/dataset/prepared_statements.rb +76 -28
  49. data/lib/sequel/dataset/query.rb +26 -11
  50. data/lib/sequel/dataset/sql.rb +40 -17
  51. data/lib/sequel/extensions/async_thread_pool.rb +1 -1
  52. data/lib/sequel/extensions/caller_logging.rb +1 -3
  53. data/lib/sequel/extensions/connection_checkout_event_callback.rb +151 -0
  54. data/lib/sequel/extensions/connection_expiration.rb +44 -5
  55. data/lib/sequel/extensions/connection_validator.rb +15 -10
  56. data/lib/sequel/extensions/constraint_validations.rb +3 -3
  57. data/lib/sequel/extensions/dataset_run.rb +41 -0
  58. data/lib/sequel/extensions/date_arithmetic.rb +6 -6
  59. data/lib/sequel/extensions/empty_array_consider_nulls.rb +7 -0
  60. data/lib/sequel/extensions/eval_inspect.rb +3 -1
  61. data/lib/sequel/extensions/inflector.rb +2 -2
  62. data/lib/sequel/extensions/lit_require_frozen.rb +131 -0
  63. data/lib/sequel/extensions/looser_typecasting.rb +5 -12
  64. data/lib/sequel/extensions/migration.rb +38 -21
  65. data/lib/sequel/extensions/null_dataset.rb +2 -2
  66. data/lib/sequel/extensions/pg_array_ops.rb +41 -0
  67. data/lib/sequel/extensions/pg_auto_parameterize.rb +10 -3
  68. data/lib/sequel/extensions/pg_auto_parameterize_duplicate_query_detection.rb +191 -0
  69. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +94 -10
  70. data/lib/sequel/extensions/pg_enum.rb +4 -4
  71. data/lib/sequel/extensions/pg_hstore.rb +1 -1
  72. data/lib/sequel/extensions/pg_hstore_ops.rb +10 -5
  73. data/lib/sequel/extensions/pg_json_ops.rb +674 -18
  74. data/lib/sequel/extensions/pg_multirange.rb +1 -1
  75. data/lib/sequel/extensions/pg_range.rb +14 -2
  76. data/lib/sequel/extensions/pg_row.rb +5 -3
  77. data/lib/sequel/extensions/pg_schema_caching.rb +90 -0
  78. data/lib/sequel/extensions/provenance.rb +1 -3
  79. data/lib/sequel/extensions/query_blocker.rb +172 -0
  80. data/lib/sequel/extensions/schema_caching.rb +24 -9
  81. data/lib/sequel/extensions/schema_dumper.rb +17 -5
  82. data/lib/sequel/extensions/set_literalizer.rb +20 -39
  83. data/lib/sequel/extensions/split_array_nil.rb +12 -2
  84. data/lib/sequel/extensions/sqlite_json_ops.rb +1 -1
  85. data/lib/sequel/extensions/string_agg.rb +2 -2
  86. data/lib/sequel/extensions/to_dot.rb +5 -0
  87. data/lib/sequel/extensions/virtual_row_method_block.rb +1 -0
  88. data/lib/sequel/model/associations.rb +215 -15
  89. data/lib/sequel/model/base.rb +198 -35
  90. data/lib/sequel/model/plugins.rb +12 -1
  91. data/lib/sequel/model.rb +1 -0
  92. data/lib/sequel/plugins/accessed_columns.rb +4 -0
  93. data/lib/sequel/plugins/active_model.rb +5 -6
  94. data/lib/sequel/plugins/association_pks.rb +2 -0
  95. data/lib/sequel/plugins/auto_validations.rb +2 -0
  96. data/lib/sequel/plugins/class_table_inheritance_constraint_validations.rb +82 -0
  97. data/lib/sequel/plugins/columns_updated.rb +4 -0
  98. data/lib/sequel/plugins/composition.rb +3 -1
  99. data/lib/sequel/plugins/constraint_validations.rb +15 -10
  100. data/lib/sequel/plugins/dataset_associations.rb +20 -1
  101. data/lib/sequel/plugins/deprecated_associations.rb +151 -0
  102. data/lib/sequel/plugins/detect_unnecessary_association_options.rb +164 -0
  103. data/lib/sequel/plugins/dirty.rb +9 -2
  104. data/lib/sequel/plugins/enum.rb +1 -1
  105. data/lib/sequel/plugins/forbid_lazy_load.rb +15 -1
  106. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  107. data/lib/sequel/plugins/insert_returning_select.rb +10 -1
  108. data/lib/sequel/plugins/inspect_pk.rb +44 -0
  109. data/lib/sequel/plugins/instance_filters.rb +8 -1
  110. data/lib/sequel/plugins/instance_hooks.rb +4 -0
  111. data/lib/sequel/plugins/inverted_subsets.rb +1 -0
  112. data/lib/sequel/plugins/json_serializer.rb +6 -10
  113. data/lib/sequel/plugins/lazy_attributes.rb +1 -1
  114. data/lib/sequel/plugins/many_through_many.rb +21 -0
  115. data/lib/sequel/plugins/modification_detection.rb +2 -0
  116. data/lib/sequel/plugins/mssql_optimistic_locking.rb +1 -1
  117. data/lib/sequel/plugins/nested_attributes.rb +10 -5
  118. data/lib/sequel/plugins/paged_operations.rb +5 -2
  119. data/lib/sequel/plugins/pg_array_associations.rb +2 -2
  120. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +6 -1
  121. data/lib/sequel/plugins/pg_auto_validate_enums.rb +88 -0
  122. data/lib/sequel/plugins/pg_eager_any_typed_array.rb +95 -0
  123. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +1 -1
  124. data/lib/sequel/plugins/rcte_tree.rb +6 -6
  125. data/lib/sequel/plugins/serialization.rb +23 -6
  126. data/lib/sequel/plugins/serialization_modification_detection.rb +4 -0
  127. data/lib/sequel/plugins/single_statement_dataset_destroy.rb +49 -0
  128. data/lib/sequel/plugins/split_values.rb +12 -0
  129. data/lib/sequel/plugins/sql_comments.rb +7 -2
  130. data/lib/sequel/plugins/static_cache.rb +13 -0
  131. data/lib/sequel/plugins/static_cache_cache.rb +50 -13
  132. data/lib/sequel/plugins/subset_conditions.rb +85 -5
  133. data/lib/sequel/plugins/subset_static_cache.rb +278 -0
  134. data/lib/sequel/plugins/table_select.rb +7 -0
  135. data/lib/sequel/plugins/tactical_eager_loading.rb +4 -0
  136. data/lib/sequel/plugins/unused_associations.rb +11 -3
  137. data/lib/sequel/plugins/update_primary_key.rb +4 -0
  138. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  139. data/lib/sequel/plugins/validation_contexts.rb +4 -0
  140. data/lib/sequel/sql.rb +27 -12
  141. data/lib/sequel/version.rb +1 -1
  142. metadata +17 -240
  143. data/CHANGELOG +0 -1393
  144. data/README.rdoc +0 -936
  145. data/doc/advanced_associations.rdoc +0 -884
  146. data/doc/association_basics.rdoc +0 -1859
  147. data/doc/bin_sequel.rdoc +0 -146
  148. data/doc/cheat_sheet.rdoc +0 -255
  149. data/doc/code_order.rdoc +0 -104
  150. data/doc/core_extensions.rdoc +0 -405
  151. data/doc/dataset_basics.rdoc +0 -96
  152. data/doc/dataset_filtering.rdoc +0 -222
  153. data/doc/extensions.rdoc +0 -77
  154. data/doc/fork_safety.rdoc +0 -84
  155. data/doc/mass_assignment.rdoc +0 -98
  156. data/doc/migration.rdoc +0 -660
  157. data/doc/model_dataset_method_design.rdoc +0 -129
  158. data/doc/model_hooks.rdoc +0 -254
  159. data/doc/model_plugins.rdoc +0 -270
  160. data/doc/mssql_stored_procedures.rdoc +0 -43
  161. data/doc/object_model.rdoc +0 -563
  162. data/doc/opening_databases.rdoc +0 -439
  163. data/doc/postgresql.rdoc +0 -611
  164. data/doc/prepared_statements.rdoc +0 -144
  165. data/doc/querying.rdoc +0 -1070
  166. data/doc/reflection.rdoc +0 -120
  167. data/doc/release_notes/5.0.0.txt +0 -159
  168. data/doc/release_notes/5.1.0.txt +0 -31
  169. data/doc/release_notes/5.10.0.txt +0 -84
  170. data/doc/release_notes/5.11.0.txt +0 -83
  171. data/doc/release_notes/5.12.0.txt +0 -141
  172. data/doc/release_notes/5.13.0.txt +0 -27
  173. data/doc/release_notes/5.14.0.txt +0 -63
  174. data/doc/release_notes/5.15.0.txt +0 -39
  175. data/doc/release_notes/5.16.0.txt +0 -110
  176. data/doc/release_notes/5.17.0.txt +0 -31
  177. data/doc/release_notes/5.18.0.txt +0 -69
  178. data/doc/release_notes/5.19.0.txt +0 -28
  179. data/doc/release_notes/5.2.0.txt +0 -33
  180. data/doc/release_notes/5.20.0.txt +0 -89
  181. data/doc/release_notes/5.21.0.txt +0 -87
  182. data/doc/release_notes/5.22.0.txt +0 -48
  183. data/doc/release_notes/5.23.0.txt +0 -56
  184. data/doc/release_notes/5.24.0.txt +0 -56
  185. data/doc/release_notes/5.25.0.txt +0 -32
  186. data/doc/release_notes/5.26.0.txt +0 -35
  187. data/doc/release_notes/5.27.0.txt +0 -21
  188. data/doc/release_notes/5.28.0.txt +0 -16
  189. data/doc/release_notes/5.29.0.txt +0 -22
  190. data/doc/release_notes/5.3.0.txt +0 -121
  191. data/doc/release_notes/5.30.0.txt +0 -20
  192. data/doc/release_notes/5.31.0.txt +0 -148
  193. data/doc/release_notes/5.32.0.txt +0 -46
  194. data/doc/release_notes/5.33.0.txt +0 -24
  195. data/doc/release_notes/5.34.0.txt +0 -40
  196. data/doc/release_notes/5.35.0.txt +0 -56
  197. data/doc/release_notes/5.36.0.txt +0 -60
  198. data/doc/release_notes/5.37.0.txt +0 -30
  199. data/doc/release_notes/5.38.0.txt +0 -28
  200. data/doc/release_notes/5.39.0.txt +0 -19
  201. data/doc/release_notes/5.4.0.txt +0 -80
  202. data/doc/release_notes/5.40.0.txt +0 -40
  203. data/doc/release_notes/5.41.0.txt +0 -25
  204. data/doc/release_notes/5.42.0.txt +0 -136
  205. data/doc/release_notes/5.43.0.txt +0 -98
  206. data/doc/release_notes/5.44.0.txt +0 -32
  207. data/doc/release_notes/5.45.0.txt +0 -34
  208. data/doc/release_notes/5.46.0.txt +0 -87
  209. data/doc/release_notes/5.47.0.txt +0 -59
  210. data/doc/release_notes/5.48.0.txt +0 -14
  211. data/doc/release_notes/5.49.0.txt +0 -59
  212. data/doc/release_notes/5.5.0.txt +0 -61
  213. data/doc/release_notes/5.50.0.txt +0 -78
  214. data/doc/release_notes/5.51.0.txt +0 -47
  215. data/doc/release_notes/5.52.0.txt +0 -87
  216. data/doc/release_notes/5.53.0.txt +0 -23
  217. data/doc/release_notes/5.54.0.txt +0 -27
  218. data/doc/release_notes/5.55.0.txt +0 -21
  219. data/doc/release_notes/5.56.0.txt +0 -51
  220. data/doc/release_notes/5.57.0.txt +0 -23
  221. data/doc/release_notes/5.58.0.txt +0 -31
  222. data/doc/release_notes/5.59.0.txt +0 -73
  223. data/doc/release_notes/5.6.0.txt +0 -31
  224. data/doc/release_notes/5.60.0.txt +0 -22
  225. data/doc/release_notes/5.61.0.txt +0 -43
  226. data/doc/release_notes/5.62.0.txt +0 -132
  227. data/doc/release_notes/5.63.0.txt +0 -33
  228. data/doc/release_notes/5.64.0.txt +0 -50
  229. data/doc/release_notes/5.65.0.txt +0 -21
  230. data/doc/release_notes/5.66.0.txt +0 -24
  231. data/doc/release_notes/5.67.0.txt +0 -32
  232. data/doc/release_notes/5.68.0.txt +0 -61
  233. data/doc/release_notes/5.69.0.txt +0 -26
  234. data/doc/release_notes/5.7.0.txt +0 -108
  235. data/doc/release_notes/5.70.0.txt +0 -35
  236. data/doc/release_notes/5.71.0.txt +0 -21
  237. data/doc/release_notes/5.72.0.txt +0 -33
  238. data/doc/release_notes/5.73.0.txt +0 -66
  239. data/doc/release_notes/5.74.0.txt +0 -45
  240. data/doc/release_notes/5.75.0.txt +0 -35
  241. data/doc/release_notes/5.76.0.txt +0 -86
  242. data/doc/release_notes/5.77.0.txt +0 -63
  243. data/doc/release_notes/5.78.0.txt +0 -67
  244. data/doc/release_notes/5.79.0.txt +0 -28
  245. data/doc/release_notes/5.8.0.txt +0 -170
  246. data/doc/release_notes/5.80.0.txt +0 -40
  247. data/doc/release_notes/5.81.0.txt +0 -31
  248. data/doc/release_notes/5.82.0.txt +0 -61
  249. data/doc/release_notes/5.83.0.txt +0 -56
  250. data/doc/release_notes/5.9.0.txt +0 -99
  251. data/doc/schema_modification.rdoc +0 -679
  252. data/doc/security.rdoc +0 -443
  253. data/doc/sharding.rdoc +0 -286
  254. data/doc/sql.rdoc +0 -648
  255. data/doc/testing.rdoc +0 -204
  256. data/doc/thread_safety.rdoc +0 -15
  257. data/doc/transactions.rdoc +0 -250
  258. data/doc/validations.rdoc +0 -558
  259. 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
- # 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+.
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 more
31
- # than two elements, where all elements are of the same class (other than
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, Sequel.pg_array(r, type))]
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 > 1
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
- "text" if db.typecast_value(:boolean, db.opts[:treat_string_list_as_text_array])
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, &HStore.method(:parse))
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 obj.is_a?(Array) && Sequel.respond_to?(:pg_array)
300
- Sequel.pg_array(obj)
301
- else
302
- obj
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