sequel 5.11.0 → 5.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +32 -0
- data/doc/advanced_associations.rdoc +132 -14
- data/doc/postgresql.rdoc +14 -0
- data/doc/release_notes/5.12.0.txt +141 -0
- data/lib/sequel/adapters/ado/mssql.rb +1 -1
- data/lib/sequel/adapters/oracle.rb +5 -6
- data/lib/sequel/adapters/postgres.rb +18 -5
- data/lib/sequel/adapters/shared/mysql.rb +5 -5
- data/lib/sequel/adapters/sqlite.rb +0 -5
- data/lib/sequel/adapters/tinytds.rb +0 -5
- data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +2 -5
- data/lib/sequel/core.rb +6 -1
- data/lib/sequel/dataset/graph.rb +25 -9
- data/lib/sequel/dataset/placeholder_literalizer.rb +47 -17
- data/lib/sequel/dataset/prepared_statements.rb +86 -18
- data/lib/sequel/dataset/sql.rb +5 -1
- data/lib/sequel/extensions/caller_logging.rb +79 -0
- data/lib/sequel/extensions/constraint_validations.rb +1 -1
- data/lib/sequel/extensions/pg_static_cache_updater.rb +2 -2
- data/lib/sequel/model/associations.rb +56 -23
- data/lib/sequel/model/base.rb +3 -3
- data/lib/sequel/plugins/eager_graph_eager.rb +139 -0
- data/lib/sequel/plugins/static_cache.rb +9 -8
- data/lib/sequel/plugins/tactical_eager_loading.rb +63 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/oracle_spec.rb +44 -0
- data/spec/adapters/postgres_spec.rb +39 -0
- data/spec/core/dataset_spec.rb +23 -9
- data/spec/core/object_graph_spec.rb +314 -284
- data/spec/extensions/caller_logging_spec.rb +52 -0
- data/spec/extensions/eager_graph_eager_spec.rb +100 -0
- data/spec/extensions/finder_spec.rb +1 -1
- data/spec/extensions/prepared_statements_spec.rb +7 -12
- data/spec/extensions/static_cache_spec.rb +14 -0
- data/spec/extensions/tactical_eager_loading_spec.rb +262 -1
- data/spec/integration/associations_test.rb +72 -0
- data/spec/integration/dataset_test.rb +3 -3
- data/spec/model/eager_loading_spec.rb +90 -0
- metadata +8 -2
@@ -441,7 +441,7 @@ module Sequel
|
|
441
441
|
/cannot be null/ => NotNullConstraintViolation,
|
442
442
|
/Deadlock found when trying to get lock; try restarting transaction/ => SerializationFailure,
|
443
443
|
/CONSTRAINT .+ failed for/ => CheckConstraintViolation,
|
444
|
-
/\
|
444
|
+
/\A(Statement aborted because lock\(s\) could not be acquired immediately and NOWAIT is set\.|Lock wait timeout exceeded; try restarting transaction)/ => DatabaseLockTimeout,
|
445
445
|
}.freeze
|
446
446
|
def database_error_regexps
|
447
447
|
DATABASE_ERROR_REGEXPS
|
@@ -804,9 +804,9 @@ module Sequel
|
|
804
804
|
true
|
805
805
|
end
|
806
806
|
|
807
|
-
#
|
807
|
+
# MariaDB 10.3+ supports INTERSECT or EXCEPT
|
808
808
|
def supports_intersect_except?
|
809
|
-
|
809
|
+
db.mariadb? && db.server_version >= 100300
|
810
810
|
end
|
811
811
|
|
812
812
|
# MySQL does not support limits in correlated subqueries (or any subqueries that use IN).
|
@@ -819,9 +819,9 @@ module Sequel
|
|
819
819
|
true
|
820
820
|
end
|
821
821
|
|
822
|
-
# MySQL 8+
|
822
|
+
# MySQL 8+ and MariaDB 10.3+ support NOWAIT.
|
823
823
|
def supports_nowait?
|
824
|
-
|
824
|
+
db.server_version >= (db.mariadb? ? 100300 : 80000)
|
825
825
|
end
|
826
826
|
|
827
827
|
# MySQL's DISTINCT ON emulation using GROUP BY does not respect the
|
@@ -306,11 +306,6 @@ module Sequel
|
|
306
306
|
def prepared_arg(k)
|
307
307
|
LiteralString.new("#{prepared_arg_placeholder}#{k.to_s.gsub('.', '__')}")
|
308
308
|
end
|
309
|
-
|
310
|
-
# Always assume a prepared argument.
|
311
|
-
def prepared_arg?(k)
|
312
|
-
true
|
313
|
-
end
|
314
309
|
end
|
315
310
|
|
316
311
|
BindArgumentMethods = prepared_statements_module(:bind, ArgumentMapper)
|
@@ -194,11 +194,6 @@ module Sequel
|
|
194
194
|
def prepared_arg(k)
|
195
195
|
LiteralString.new("@#{k.to_s.gsub('.', '__')}")
|
196
196
|
end
|
197
|
-
|
198
|
-
# Always assume a prepared argument.
|
199
|
-
def prepared_arg?(k)
|
200
|
-
true
|
201
|
-
end
|
202
197
|
end
|
203
198
|
|
204
199
|
PreparedStatementMethods = prepared_statements_module("sql = prepared_sql; opts = Hash[opts]; opts[:arguments] = bind_arguments", ArgumentMapper)
|
@@ -32,14 +32,11 @@ module Sequel
|
|
32
32
|
row_count = @opts[:offset_total_count] || ds.clone(:append_sql=>String.new, :placeholder_literal_null=>true).count
|
33
33
|
dsa1 = dataset_alias(1)
|
34
34
|
|
35
|
-
if o.is_a?(Symbol) && @opts[:bind_vars] &&
|
35
|
+
if o.is_a?(Symbol) && @opts[:bind_vars] && /\A\$(.*)\z/ =~ o
|
36
36
|
# Handle use of bound variable offsets. Unfortunately, prepared statement
|
37
37
|
# bound variable offsets cannot be handled, since the bound variable value
|
38
38
|
# isn't available until later.
|
39
|
-
|
40
|
-
if prepared_arg?(s)
|
41
|
-
o = prepared_arg(s)
|
42
|
-
end
|
39
|
+
o = prepared_arg($1.to_sym)
|
43
40
|
end
|
44
41
|
|
45
42
|
reverse_offset = row_count - o
|
data/lib/sequel/core.rb
CHANGED
@@ -58,6 +58,11 @@ module Sequel
|
|
58
58
|
#
|
59
59
|
# Sequel.single_threaded = true
|
60
60
|
attr_accessor :single_threaded
|
61
|
+
|
62
|
+
# Alias of original require method, as Sequel.require is does a relative
|
63
|
+
# require for backwards compatibility.
|
64
|
+
alias orig_require require
|
65
|
+
private :orig_require
|
61
66
|
end
|
62
67
|
|
63
68
|
# Returns true if the passed object could be a specifier of conditions, false otherwise.
|
@@ -142,7 +147,7 @@ module Sequel
|
|
142
147
|
# Sequel.extension(:blank)
|
143
148
|
# Sequel.extension(:core_extensions, :named_timezones)
|
144
149
|
def self.extension(*extensions)
|
145
|
-
extensions.each{|e|
|
150
|
+
extensions.each{|e| orig_require("sequel/extensions/#{e}")}
|
146
151
|
end
|
147
152
|
|
148
153
|
# The exception classed raised if there is an error parsing JSON.
|
data/lib/sequel/dataset/graph.rb
CHANGED
@@ -96,10 +96,30 @@ module Sequel
|
|
96
96
|
|
97
97
|
table_alias_qualifier = qualifier_from_alias_symbol(table_alias, table)
|
98
98
|
implicit_qualifier = options[:implicit_qualifier]
|
99
|
+
joined_dataset = joined_dataset?
|
99
100
|
ds = self
|
101
|
+
graph = opts[:graph]
|
102
|
+
|
103
|
+
if !graph && (select = @opts[:select]) && !select.empty?
|
104
|
+
select_columns = nil
|
105
|
+
|
106
|
+
unless !joined_dataset && select.length == 1 && (select[0].is_a?(SQL::ColumnAll))
|
107
|
+
force_from_self = false
|
108
|
+
select_columns = select.map do |sel|
|
109
|
+
unless col = _hash_key_symbol(sel)
|
110
|
+
force_from_self = true
|
111
|
+
break
|
112
|
+
end
|
113
|
+
|
114
|
+
[sel, col]
|
115
|
+
end
|
116
|
+
|
117
|
+
select_columns = nil if force_from_self
|
118
|
+
end
|
119
|
+
end
|
100
120
|
|
101
121
|
# Use a from_self if this is already a joined table (or from_self specifically disabled for graphs)
|
102
|
-
if (@opts[:graph_from_self] != false &&
|
122
|
+
if (@opts[:graph_from_self] != false && !graph && (joined_dataset || force_from_self))
|
103
123
|
from_selfed = true
|
104
124
|
implicit_qualifier = options[:from_self_alias] || first_source
|
105
125
|
ds = ds.from_self(:alias=>implicit_qualifier)
|
@@ -115,7 +135,7 @@ module Sequel
|
|
115
135
|
# Whether to include the table in the result set
|
116
136
|
add_table = options[:select] == false ? false : true
|
117
137
|
|
118
|
-
if graph
|
138
|
+
if graph
|
119
139
|
graph = graph.dup
|
120
140
|
select = opts[:select].dup
|
121
141
|
[:column_aliases, :table_aliases, :column_alias_num].each{|k| graph[k] = graph[k].dup}
|
@@ -137,12 +157,8 @@ module Sequel
|
|
137
157
|
# Keep track of the alias numbers used
|
138
158
|
ca_num = graph[:column_alias_num] = Hash.new(0)
|
139
159
|
|
140
|
-
|
141
|
-
|
142
|
-
# has been used.
|
143
|
-
if (select = @opts[:select]) && !select.empty? && !(select.length == 1 && (select.first.is_a?(SQL::ColumnAll)))
|
144
|
-
select = select.map do |sel|
|
145
|
-
raise Error, "can't figure out alias to use for graphing for #{sel.inspect}" unless column = _hash_key_symbol(sel)
|
160
|
+
select = if select_columns
|
161
|
+
select_columns.map do |sel, column|
|
146
162
|
column_aliases[column] = [master, column]
|
147
163
|
if from_selfed
|
148
164
|
# Initial dataset was wrapped in subselect, selected all
|
@@ -155,7 +171,7 @@ module Sequel
|
|
155
171
|
end
|
156
172
|
end
|
157
173
|
else
|
158
|
-
|
174
|
+
columns.map do |column|
|
159
175
|
column_aliases[column] = [master, column]
|
160
176
|
SQL::QualifiedIdentifier.new(qualifier, column)
|
161
177
|
end
|
@@ -77,23 +77,8 @@ module Sequel
|
|
77
77
|
# Yields the receiver and the dataset to the block, which should
|
78
78
|
# call #arg on the receiver for each placeholder argument, and
|
79
79
|
# return the dataset that you want to load.
|
80
|
-
def loader(dataset)
|
81
|
-
|
82
|
-
@args = []
|
83
|
-
ds = yield self, dataset
|
84
|
-
sql = ds.clone(:placeholder_literalizer=>self).sql
|
85
|
-
|
86
|
-
last_offset = 0
|
87
|
-
fragments = @args.map do |used_sql, offset, arg, t|
|
88
|
-
raise Error, "placeholder literalizer argument literalized into different string than dataset returned" unless used_sql.equal?(sql)
|
89
|
-
a = [sql[last_offset...offset], arg, t]
|
90
|
-
last_offset = offset
|
91
|
-
a
|
92
|
-
end
|
93
|
-
final_sql = sql[last_offset..-1]
|
94
|
-
|
95
|
-
arity = @argn+1
|
96
|
-
PlaceholderLiteralizer.new(ds.clone, fragments, final_sql, arity)
|
80
|
+
def loader(dataset, &block)
|
81
|
+
PlaceholderLiteralizer.new(*process(dataset, &block))
|
97
82
|
end
|
98
83
|
|
99
84
|
# Return an Argument with the specified position, or the next position. In
|
@@ -111,6 +96,51 @@ module Sequel
|
|
111
96
|
def use(sql, arg, transformer)
|
112
97
|
@args << [sql, sql.length, arg, transformer]
|
113
98
|
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
# Return an array with two elements, the first being an
|
103
|
+
# SQL string with interpolated prepared argument placeholders
|
104
|
+
# (suitable for inspect), the the second being an array of
|
105
|
+
# SQL fragments suitable for using for creating a
|
106
|
+
# Sequel::SQL::PlaceholderLiteralString. Designed for use with
|
107
|
+
# emulated prepared statements.
|
108
|
+
def prepared_sql_and_frags(dataset, prepared_args, &block)
|
109
|
+
_, frags, final_sql, _ = process(dataset, &block)
|
110
|
+
|
111
|
+
frags = frags.map(&:first)
|
112
|
+
prepared_sql = String.new
|
113
|
+
frags.each_with_index do |sql, i|
|
114
|
+
prepared_sql << sql
|
115
|
+
prepared_sql << "$#{prepared_args[i]}"
|
116
|
+
end
|
117
|
+
if final_sql
|
118
|
+
frags << final_sql
|
119
|
+
prepared_sql << final_sql
|
120
|
+
end
|
121
|
+
|
122
|
+
[prepared_sql, frags]
|
123
|
+
end
|
124
|
+
|
125
|
+
# Internals of #loader and #prepared_sql_and_frags.
|
126
|
+
def process(dataset)
|
127
|
+
@argn = -1
|
128
|
+
@args = []
|
129
|
+
ds = yield self, dataset
|
130
|
+
sql = ds.clone(:placeholder_literalizer=>self).sql
|
131
|
+
|
132
|
+
last_offset = 0
|
133
|
+
fragments = @args.map do |used_sql, offset, arg, t|
|
134
|
+
raise Error, "placeholder literalizer argument literalized into different string than dataset returned" unless used_sql.equal?(sql)
|
135
|
+
a = [sql[last_offset...offset], arg, t]
|
136
|
+
last_offset = offset
|
137
|
+
a
|
138
|
+
end
|
139
|
+
final_sql = sql[last_offset..-1]
|
140
|
+
|
141
|
+
arity = @argn+1
|
142
|
+
[ds, fragments, final_sql, arity]
|
143
|
+
end
|
114
144
|
end
|
115
145
|
|
116
146
|
# Create a PlaceholderLiteralizer by yielding a Recorder and dataset to the
|
@@ -67,6 +67,14 @@ module Sequel
|
|
67
67
|
end
|
68
68
|
cache_set(:_prepared_sql, super)
|
69
69
|
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# Report that prepared statements are not emulated, since
|
74
|
+
# all adapters that use this use native prepared statements.
|
75
|
+
def emulate_prepared_statements?
|
76
|
+
false
|
77
|
+
end
|
70
78
|
end
|
71
79
|
|
72
80
|
# Backbone of the prepared statement support. Grafts bind variable
|
@@ -155,13 +163,8 @@ module Sequel
|
|
155
163
|
# prepared_args is present. If so, they are considered placeholders,
|
156
164
|
# and they are substituted using prepared_arg.
|
157
165
|
def literal_symbol_append(sql, v)
|
158
|
-
if @opts[:bind_vars]
|
159
|
-
|
160
|
-
if prepared_arg?(s)
|
161
|
-
literal_append(sql, prepared_arg(s))
|
162
|
-
else
|
163
|
-
sql << v.to_s
|
164
|
-
end
|
166
|
+
if @opts[:bind_vars] && /\A\$(.*)\z/ =~ v
|
167
|
+
literal_append(sql, prepared_arg($1.to_sym))
|
165
168
|
else
|
166
169
|
super
|
167
170
|
end
|
@@ -214,11 +217,6 @@ module Sequel
|
|
214
217
|
@opts[:bind_vars][k]
|
215
218
|
end
|
216
219
|
|
217
|
-
# Whether there is a bound value for the given key.
|
218
|
-
def prepared_arg?(k)
|
219
|
-
@opts[:bind_vars].has_key?(k)
|
220
|
-
end
|
221
|
-
|
222
220
|
# The symbol cache should always be skipped, since placeholders are symbols.
|
223
221
|
def skip_symbol_cache?
|
224
222
|
true
|
@@ -228,9 +226,12 @@ module Sequel
|
|
228
226
|
# support and using the same argument hash so that you can use
|
229
227
|
# bind variables/prepared arguments in subselects.
|
230
228
|
def subselect_sql_append(sql, ds)
|
231
|
-
|
232
|
-
|
233
|
-
|
229
|
+
subselect_sql_dataset(sql, ds).prepared_sql
|
230
|
+
end
|
231
|
+
|
232
|
+
def subselect_sql_dataset(sql, ds)
|
233
|
+
super.clone(:prepared_args=>prepared_args, :bind_vars=>@opts[:bind_vars]).
|
234
|
+
send(:to_prepared_statement, :select, nil, :extend=>prepared_statement_modules)
|
234
235
|
end
|
235
236
|
end
|
236
237
|
|
@@ -258,11 +259,63 @@ module Sequel
|
|
258
259
|
prepared_args << k
|
259
260
|
prepared_arg_placeholder
|
260
261
|
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Prepared statements emulation support for adapters that don't
|
265
|
+
# support native prepared statements. Uses a placeholder
|
266
|
+
# literalizer to hold the prepared sql with the ability to
|
267
|
+
# interpolate arguments to prepare the final SQL string.
|
268
|
+
module EmulatePreparedStatementMethods
|
269
|
+
include UnnumberedArgumentMapper
|
270
|
+
|
271
|
+
def run(&block)
|
272
|
+
if @opts[:prepared_sql_frags]
|
273
|
+
sql = literal(Sequel::SQL::PlaceholderLiteralString.new(@opts[:prepared_sql_frags], @opts[:bind_arguments], false))
|
274
|
+
clone(:prepared_sql_frags=>nil, :sql=>sql, :prepared_sql=>sql).run(&block)
|
275
|
+
else
|
276
|
+
super
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
private
|
261
281
|
|
262
|
-
#
|
263
|
-
|
282
|
+
# Turn emulation of prepared statements back on, since ArgumentMapper
|
283
|
+
# turns it off.
|
284
|
+
def emulate_prepared_statements?
|
264
285
|
true
|
265
286
|
end
|
287
|
+
|
288
|
+
def emulated_prepared_statement(type, name, values)
|
289
|
+
prepared_sql, frags = Sequel::Dataset::PlaceholderLiteralizer::Recorder.new.send(:prepared_sql_and_frags, self, prepared_args) do |pl, ds|
|
290
|
+
ds = ds.clone(:recorder=>pl)
|
291
|
+
|
292
|
+
case type
|
293
|
+
when :first
|
294
|
+
ds.limit(1)
|
295
|
+
when :update, :insert, :insert_select, :delete
|
296
|
+
ds.with_sql(:"#{type}_sql", *values)
|
297
|
+
when :insert_pk
|
298
|
+
ds.with_sql(:insert_sql, *values)
|
299
|
+
else
|
300
|
+
ds
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
prepared_args.freeze
|
305
|
+
clone(:prepared_sql_frags=>frags, :prepared_sql=>prepared_sql, :sql=>prepared_sql)
|
306
|
+
end
|
307
|
+
|
308
|
+
# Associates the argument with name k with the next position in
|
309
|
+
# the output array.
|
310
|
+
def prepared_arg(k)
|
311
|
+
prepared_args << k
|
312
|
+
@opts[:recorder].arg
|
313
|
+
end
|
314
|
+
|
315
|
+
def subselect_sql_dataset(sql, ds)
|
316
|
+
super.clone(:recorder=>@opts[:recorder]).
|
317
|
+
with_extend(EmulatePreparedStatementMethods)
|
318
|
+
end
|
266
319
|
end
|
267
320
|
|
268
321
|
# Set the bind variables to use for the call. If bind variables have
|
@@ -315,7 +368,16 @@ module Sequel
|
|
315
368
|
# DB.call(:select_by_name, name: 'Blah') # Same thing
|
316
369
|
def prepare(type, name, *values)
|
317
370
|
ps = to_prepared_statement(type, values, :name=>name, :extend=>prepared_statement_modules, :no_delayed_evaluations=>true)
|
318
|
-
|
371
|
+
|
372
|
+
ps = if ps.send(:emulate_prepared_statements?)
|
373
|
+
ps = ps.with_extend(EmulatePreparedStatementMethods)
|
374
|
+
ps.send(:emulated_prepared_statement, type, name, values)
|
375
|
+
else
|
376
|
+
sql = ps.prepared_sql
|
377
|
+
ps.prepared_args.freeze
|
378
|
+
ps.clone(:prepared_sql=>sql, :sql=>sql)
|
379
|
+
end
|
380
|
+
|
319
381
|
db.set_prepared_statement(name, ps)
|
320
382
|
ps
|
321
383
|
end
|
@@ -344,6 +406,12 @@ module Sequel
|
|
344
406
|
prepared_statement_modules
|
345
407
|
end
|
346
408
|
|
409
|
+
# Whether prepared statements should be emulated. True by
|
410
|
+
# default so that adapters have to opt in.
|
411
|
+
def emulate_prepared_statements?
|
412
|
+
true
|
413
|
+
end
|
414
|
+
|
347
415
|
def prepared_statement_modules
|
348
416
|
[]
|
349
417
|
end
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -1553,7 +1553,11 @@ module Sequel
|
|
1553
1553
|
|
1554
1554
|
# Append literalization of the subselect to SQL string.
|
1555
1555
|
def subselect_sql_append(sql, ds)
|
1556
|
-
|
1556
|
+
subselect_sql_dataset(sql, ds).sql
|
1557
|
+
end
|
1558
|
+
|
1559
|
+
def subselect_sql_dataset(sql, ds)
|
1560
|
+
ds.clone(:append_sql=>sql)
|
1557
1561
|
end
|
1558
1562
|
|
1559
1563
|
# The number of decimal digits of precision to use in timestamps.
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
#
|
3
|
+
# The caller_logging extension includes caller information before
|
4
|
+
# query logging, showing which code caused the query. It skips
|
5
|
+
# internal Sequel code, showing the first non-Sequel caller line.
|
6
|
+
#
|
7
|
+
# DB.extension :caller_logging
|
8
|
+
# DB[:table].first
|
9
|
+
# # Logger:
|
10
|
+
# # (0.000041s) (source: /path/to/app/foo/t.rb:12 in `get_first`) SELECT * FROM table LIMIT 1
|
11
|
+
#
|
12
|
+
# You can further filter the caller lines by setting
|
13
|
+
# <tt>Database#caller_logging_ignore</tt> to a regexp of additional
|
14
|
+
# caller lines to ignore. This is useful if you have specific
|
15
|
+
# methods or internal extensions/plugins that you would also
|
16
|
+
# like to ignore as they obscure the code actually making the
|
17
|
+
# request.
|
18
|
+
#
|
19
|
+
# DB.caller_logging_ignore = %r{/path/to/app/lib/plugins}
|
20
|
+
#
|
21
|
+
# You can also format the caller before it is placed in the logger,
|
22
|
+
# using +caller_logging_formatter+:
|
23
|
+
#
|
24
|
+
# DB.caller_logging_formatter = lambda do |caller|
|
25
|
+
# "(#{caller.sub(/\A\/path\/to\/app\//, '')})"
|
26
|
+
# end
|
27
|
+
# DB[:table].first
|
28
|
+
# # Logger:
|
29
|
+
# # (0.000041s) (foo/t.rb:12 in `get_first`) SELECT * FROM table LIMIT 1
|
30
|
+
#
|
31
|
+
# Related module: Sequel::CallerLogging
|
32
|
+
|
33
|
+
require 'rbconfig'
|
34
|
+
|
35
|
+
#
|
36
|
+
module Sequel
|
37
|
+
module CallerLogging
|
38
|
+
SEQUEL_LIB_PATH = (File.expand_path('../../..', __FILE__) + '/').freeze
|
39
|
+
|
40
|
+
# A regexp of caller lines to ignore, in addition to internal Sequel and Ruby code.
|
41
|
+
attr_accessor :caller_logging_ignore
|
42
|
+
|
43
|
+
# A callable to format the external caller
|
44
|
+
attr_accessor :caller_logging_formatter
|
45
|
+
|
46
|
+
# Include caller information when logging query.
|
47
|
+
def log_connection_yield(sql, conn, args=nil)
|
48
|
+
if !@loggers.empty? && (external_caller = external_caller_for_log)
|
49
|
+
sql = "#{external_caller} #{sql}"
|
50
|
+
end
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# The caller to log, ignoring internal Sequel and Ruby code, and user specified
|
57
|
+
# lines to ignore.
|
58
|
+
def external_caller_for_log
|
59
|
+
ignore = caller_logging_ignore
|
60
|
+
c = caller.find do |line|
|
61
|
+
!(line.start_with?(SEQUEL_LIB_PATH) ||
|
62
|
+
line.start_with?(RbConfig::CONFIG["rubylibdir"]) ||
|
63
|
+
(ignore && line =~ ignore))
|
64
|
+
end
|
65
|
+
|
66
|
+
if c
|
67
|
+
c = if formatter = caller_logging_formatter
|
68
|
+
formatter.call(c)
|
69
|
+
else
|
70
|
+
"(source: #{c})"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
c
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
Database.register_extension(:caller_logging, CallerLogging)
|
79
|
+
end
|