Skip to content

Commit 2fb72f5

Browse files
authored
Merge pull request rails#45081 from iheanyi/iheanyi/custom-query-log-tags-separators
Add ability to set the `tags_format` for `QueryLogs`
2 parents 08ef43e + 6df894f commit 2fb72f5

File tree

8 files changed

+132
-4
lines changed

8 files changed

+132
-4
lines changed

activerecord/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
* Add configurable formatter on query log tags to support sqlcommenter. See #45139
2+
3+
It is now possible to opt into sqlcommenter-formatted query log tags with `config.active_record.query_log_tags_format = :sqlcommenter`.
4+
5+
*Modulitos and Iheanyi*
6+
17
* Allow any ERB in the database.yml when creating rake tasks.
28

39
Any ERB can be used in `database.yml` even if it accesses environment

activerecord/lib/active_record/query_logs.rb

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
require "active_support/core_ext/module/attribute_accessors_per_thread"
4+
require "active_record/query_logs_formatter"
45

56
module ActiveRecord
67
# = Active Record Query Logs
@@ -73,6 +74,7 @@ module QueryLogs
7374
mattr_accessor :tags, instance_accessor: false, default: [ :application ]
7475
mattr_accessor :prepend_comment, instance_accessor: false, default: false
7576
mattr_accessor :cache_query_log_tags, instance_accessor: false, default: false
77+
mattr_accessor :tags_formatter, instance_accessor: false
7678
thread_mattr_accessor :cached_comment, instance_accessor: false
7779

7880
class << self
@@ -88,6 +90,11 @@ def clear_cache # :nodoc:
8890
self.cached_comment = nil
8991
end
9092

93+
# Updates the formatter to be what the passed in format is.
94+
def update_formatter(format = :legacy)
95+
self.tags_formatter = QueryLogs::FormatterFactory.from_symbol(format)
96+
end
97+
9198
ActiveSupport::ExecutionContext.after_change { ActiveRecord::QueryLogs.clear_cache }
9299

93100
private
@@ -101,6 +108,10 @@ def comment
101108
end
102109
end
103110

111+
def formatter
112+
self.tags_formatter ||= QueryLogs::FormatterFactory.from_symbol(:legacy)
113+
end
114+
104115
def uncached_comment
105116
content = tag_content
106117
if content.present?
@@ -130,7 +141,7 @@ def tag_content
130141
else
131142
handler
132143
end
133-
"#{key}:#{val}" unless val.nil?
144+
"#{key}#{self.formatter.key_value_separator}#{self.formatter.format_value(val)}" unless val.nil?
134145
end.join(",")
135146
end
136147
end
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# frozen_string_literal: true
2+
3+
module ActiveRecord
4+
module QueryLogs
5+
class Formatter # :nodoc:
6+
attr_reader :key_value_separator
7+
8+
# @param [String] key_value_separator: indicates the string used for
9+
# separating keys and values.
10+
#
11+
# @param [Symbol] quote_values: indicates how values will be formatted (eg:
12+
# in single quotes, not quoted at all, etc)
13+
def initialize(key_value_separator:)
14+
@key_value_separator = key_value_separator
15+
end
16+
17+
# @param [String-coercible] value
18+
# @return [String] The formatted value that will be used in our key-value
19+
# pairs.
20+
def format_value(value)
21+
value
22+
end
23+
end
24+
25+
class QuotingFormatter < Formatter # :nodoc:
26+
def format_value(value)
27+
"'#{value.to_s.gsub("'", "\\\\'")}'"
28+
end
29+
end
30+
31+
class FormatterFactory # :nodoc:
32+
# @param [Symbol] formatter: the kind of formatter we're building.
33+
# @return [Formatter]
34+
def self.from_symbol(formatter)
35+
case formatter
36+
when :legacy
37+
Formatter.new(key_value_separator: ":")
38+
when :sqlcommenter
39+
QuotingFormatter.new(key_value_separator: "=")
40+
else
41+
raise ArgumentError, "Formatter is unsupported: #{formatter}"
42+
end
43+
end
44+
end
45+
end
46+
end

activerecord/lib/active_record/railtie.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class Railtie < Rails::Railtie # :nodoc:
3434
config.active_record.sqlite3_production_warning = true
3535
config.active_record.query_log_tags_enabled = false
3636
config.active_record.query_log_tags = [ :application ]
37+
config.active_record.query_log_tags_format = :legacy
3738
config.active_record.cache_query_log_tags = false
3839

3940
config.active_record.queues = ActiveSupport::InheritableOptions.new
@@ -244,6 +245,7 @@ class Railtie < Rails::Railtie # :nodoc:
244245
:shard_resolver,
245246
:query_log_tags_enabled,
246247
:query_log_tags,
248+
:query_log_tags_format,
247249
:cache_query_log_tags,
248250
:sqlite3_production_warning,
249251
:sqlite3_adapter_strict_strings_by_default,
@@ -384,7 +386,7 @@ class Railtie < Rails::Railtie # :nodoc:
384386
ActiveRecord.query_transformers << ActiveRecord::QueryLogs
385387
ActiveRecord::QueryLogs.taggings.merge!(
386388
application: Rails.application.class.name.split("::").first,
387-
pid: -> { Process.pid },
389+
pid: -> { Process.pid.to_s },
388390
socket: -> { ActiveRecord::Base.connection_db_config.socket },
389391
db_host: -> { ActiveRecord::Base.connection_db_config.host },
390392
database: -> { ActiveRecord::Base.connection_db_config.database }
@@ -394,6 +396,10 @@ class Railtie < Rails::Railtie # :nodoc:
394396
ActiveRecord::QueryLogs.tags = app.config.active_record.query_log_tags
395397
end
396398

399+
if app.config.active_record.query_log_tags_format.present?
400+
ActiveRecord::QueryLogs.update_formatter(app.config.active_record.query_log_tags_format)
401+
end
402+
397403
if app.config.active_record.cache_query_log_tags
398404
ActiveRecord::QueryLogs.cache_query_log_tags = true
399405
end
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# frozen_string_literal: true
2+
3+
require "cases/helper"
4+
5+
class QueryLogsFormatter < ActiveRecord::TestCase
6+
def test_factory_invalid_formatter
7+
assert_raises(ArgumentError) do
8+
ActiveRecord::QueryLogs::FormatterFactory.from_symbol(:non_existing_formatter)
9+
end
10+
end
11+
12+
def test_sqlcommenter_key_value_separator
13+
formatter = ActiveRecord::QueryLogs::FormatterFactory.from_symbol(:sqlcommenter)
14+
assert_equal("=", formatter.key_value_separator)
15+
end
16+
17+
def test_sqlcommenter_format_value
18+
formatter = ActiveRecord::QueryLogs::FormatterFactory.from_symbol(:sqlcommenter)
19+
assert_equal("'Joe\\'s Crab Shack'", formatter.format_value("Joe's Crab Shack"))
20+
end
21+
22+
def test_sqlcommenter_format_value_string_coercible
23+
formatter = ActiveRecord::QueryLogs::FormatterFactory.from_symbol(:sqlcommenter)
24+
assert_equal("'1234'", formatter.format_value(1234))
25+
end
26+
end

activerecord/test/cases/query_logs_test.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ def teardown
3232
ActiveRecord::QueryLogs.prepend_comment = false
3333
ActiveRecord::QueryLogs.cache_query_log_tags = false
3434
ActiveRecord::QueryLogs.cached_comment = nil
35+
ActiveRecord::QueryLogs.update_formatter
36+
3537
# ActiveSupport::ExecutionContext context is automatically reset in Rails app via an executor hooks set in railtie
3638
# But not in Active Record's own test suite.
3739
ActiveSupport::ExecutionContext.clear
@@ -41,6 +43,11 @@ def test_escaping_good_comment
4143
assert_equal "app:foo", ActiveRecord::QueryLogs.send(:escape_sql_comment, "app:foo")
4244
end
4345

46+
def test_escaping_good_comment_with_custom_separator
47+
ActiveRecord::QueryLogs.update_formatter(:sqlcommenter)
48+
assert_equal "app='foo'", ActiveRecord::QueryLogs.send(:escape_sql_comment, "app='foo'")
49+
end
50+
4451
def test_escaping_bad_comments
4552
assert_equal "; DROP TABLE USERS;", ActiveRecord::QueryLogs.send(:escape_sql_comment, "*/; DROP TABLE USERS;/*")
4653
assert_equal "; DROP TABLE USERS;", ActiveRecord::QueryLogs.send(:escape_sql_comment, "**//; DROP TABLE USERS;/*")
@@ -148,6 +155,13 @@ def test_empty_comments_are_not_added
148155
end
149156
end
150157

158+
def test_sql_commenter_format
159+
ActiveRecord::QueryLogs.update_formatter(:sqlcommenter)
160+
assert_sql(%r{/\*application='active_record'\*/}) do
161+
Dashboard.first
162+
end
163+
end
164+
151165
def test_custom_basic_tags
152166
ActiveRecord::QueryLogs.tags = [ :application, { custom_string: "test content" } ]
153167

guides/source/configuring.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,6 +1125,10 @@ Define an `Array` specifying the key/value tags to be inserted in an SQL
11251125
comment. Defaults to `[ :application ]`, a predefined tag returning the
11261126
application name.
11271127
1128+
#### `config.active_record.query_log_tags_format`
1129+
1130+
A `Symbol` specifying the formatter to use for tags. Valid values are `:sqlcommenter` and `:legacy`. Defaults to `:legacy`.
1131+
11281132
#### `config.active_record.cache_query_log_tags`
11291133
11301134
Specifies whether or not to enable caching of query log tags. For applications

railties/test/application/query_logs_test.rb

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def index
2222
end
2323
2424
def dynamic_content
25-
Time.now.to_f
25+
Time.now.to_f.to_s
2626
end
2727
end
2828
RUBY
@@ -34,7 +34,7 @@ def perform
3434
end
3535
3636
def dynamic_content
37-
Time.now.to_f
37+
Time.now.to_f.to_s
3838
end
3939
end
4040
RUBY
@@ -87,6 +87,21 @@ def app
8787
assert_includes comment, "controller:users"
8888
end
8989

90+
test "sqlcommenter formatting works when specified" do
91+
add_to_config "config.active_record.query_log_tags_enabled = true"
92+
add_to_config "config.active_record.query_log_tags_format = :sqlcommenter"
93+
94+
add_to_config "config.active_record.query_log_tags = [ :pid ]"
95+
96+
boot_app
97+
98+
get "/"
99+
comment = last_response.body.strip
100+
101+
assert_match(/pid='\d+'/, comment)
102+
assert_includes comment, "controller='users'"
103+
end
104+
90105
test "controller actions tagging filters can be disabled" do
91106
add_to_config "config.active_record.query_log_tags_enabled = true"
92107
add_to_config "config.action_controller.log_query_tags_around_actions = false"

0 commit comments

Comments
 (0)