Skip to content

Commit 0016280

Browse files
committed
Don't require an active connection for table and column quoting
Extracted from: rails#50793 Right now quoting table or column names requires a leased Adapter instance, even though none of the implementations actually requires an active connection. The idea here is to move these methods to the class so that the quoting can be done without leasing a connection or even needing the connection to ever have been established. I also checked `activerecord-sqlserver-adapter` and `oracle-enhanced` gems, and neither need an active connection.
1 parent 0a9ca01 commit 0016280

File tree

18 files changed

+307
-225
lines changed

18 files changed

+307
-225
lines changed

activerecord/lib/active_record/attribute_methods/primary_key.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def composite_primary_key? # :nodoc:
9292
# Returns a quoted version of the primary key name, used to construct
9393
# SQL statements.
9494
def quoted_primary_key
95-
@quoted_primary_key ||= connection.quote_column_name(primary_key)
95+
@quoted_primary_key ||= adapter_class.quote_column_name(primary_key)
9696
end
9797

9898
def reset_primary_key # :nodoc:

activerecord/lib/active_record/connection_adapters/abstract/quoting.rb

Lines changed: 65 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,67 @@ module ActiveRecord
77
module ConnectionAdapters # :nodoc:
88
# = Active Record Connection Adapters \Quoting
99
module Quoting
10+
extend ActiveSupport::Concern
11+
12+
module ClassMethods # :nodoc:
13+
# Regexp for column names (with or without a table name prefix).
14+
# Matches the following:
15+
#
16+
# "#{table_name}.#{column_name}"
17+
# "#{column_name}"
18+
def column_name_matcher
19+
/
20+
\A
21+
(
22+
(?:
23+
# table_name.column_name | function(one or no argument)
24+
((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
25+
)
26+
(?:(?:\s+AS)?\s+\w+)?
27+
)
28+
(?:\s*,\s*\g<1>)*
29+
\z
30+
/ix
31+
end
32+
33+
# Regexp for column names with order (with or without a table name prefix,
34+
# with or without various order modifiers). Matches the following:
35+
#
36+
# "#{table_name}.#{column_name}"
37+
# "#{table_name}.#{column_name} #{direction}"
38+
# "#{table_name}.#{column_name} #{direction} NULLS FIRST"
39+
# "#{table_name}.#{column_name} NULLS LAST"
40+
# "#{column_name}"
41+
# "#{column_name} #{direction}"
42+
# "#{column_name} #{direction} NULLS FIRST"
43+
# "#{column_name} NULLS LAST"
44+
def column_name_with_order_matcher
45+
/
46+
\A
47+
(
48+
(?:
49+
# table_name.column_name | function(one or no argument)
50+
((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
51+
)
52+
(?:\s+ASC|\s+DESC)?
53+
(?:\s+NULLS\s+(?:FIRST|LAST))?
54+
)
55+
(?:\s*,\s*\g<1>)*
56+
\z
57+
/ix
58+
end
59+
60+
# Quotes the column name. Must be implemented by subclasses
61+
def quote_column_name(column_name)
62+
raise NotImplementedError
63+
end
64+
65+
# Quotes the table name. Defaults to column name quoting.
66+
def quote_table_name(table_name)
67+
quote_column_name(table_name)
68+
end
69+
end
70+
1071
# Quotes the column value to help prevent
1172
# {SQL injection attacks}[https://en.wikipedia.org/wiki/SQL_injection].
1273
def quote(value)
@@ -71,14 +132,14 @@ def quote_string(s)
71132
s.gsub("\\", '\&\&').gsub("'", "''") # ' (for ruby-mode)
72133
end
73134

74-
# Quotes the column name. Defaults to no quoting.
135+
# Quotes the column name.
75136
def quote_column_name(column_name)
76-
column_name.to_s
137+
self.class.quote_column_name(column_name)
77138
end
78139

79-
# Quotes the table name. Defaults to column name quoting.
140+
# Quotes the table name.
80141
def quote_table_name(table_name)
81-
quote_column_name(table_name)
142+
self.class.quote_table_name(table_name)
82143
end
83144

84145
# Override to return the quoted table name for assignment. Defaults to
@@ -159,59 +220,6 @@ def sanitize_as_sql_comment(value) # :nodoc:
159220
comment
160221
end
161222

162-
def column_name_matcher # :nodoc:
163-
COLUMN_NAME
164-
end
165-
166-
def column_name_with_order_matcher # :nodoc:
167-
COLUMN_NAME_WITH_ORDER
168-
end
169-
170-
# Regexp for column names (with or without a table name prefix).
171-
# Matches the following:
172-
#
173-
# "#{table_name}.#{column_name}"
174-
# "#{column_name}"
175-
COLUMN_NAME = /
176-
\A
177-
(
178-
(?:
179-
# table_name.column_name | function(one or no argument)
180-
((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
181-
)
182-
(?:(?:\s+AS)?\s+\w+)?
183-
)
184-
(?:\s*,\s*\g<1>)*
185-
\z
186-
/ix
187-
188-
# Regexp for column names with order (with or without a table name prefix,
189-
# with or without various order modifiers). Matches the following:
190-
#
191-
# "#{table_name}.#{column_name}"
192-
# "#{table_name}.#{column_name} #{direction}"
193-
# "#{table_name}.#{column_name} #{direction} NULLS FIRST"
194-
# "#{table_name}.#{column_name} NULLS LAST"
195-
# "#{column_name}"
196-
# "#{column_name} #{direction}"
197-
# "#{column_name} #{direction} NULLS FIRST"
198-
# "#{column_name} NULLS LAST"
199-
COLUMN_NAME_WITH_ORDER = /
200-
\A
201-
(
202-
(?:
203-
# table_name.column_name | function(one or no argument)
204-
((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
205-
)
206-
(?:\s+ASC|\s+DESC)?
207-
(?:\s+NULLS\s+(?:FIRST|LAST))?
208-
)
209-
(?:\s*,\s*\g<1>)*
210-
\z
211-
/ix
212-
213-
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
214-
215223
private
216224
def type_casted_binds(binds)
217225
binds.map do |value|

activerecord/lib/active_record/connection_adapters/mysql/quoting.rb

Lines changed: 43 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,52 @@ module ActiveRecord
66
module ConnectionAdapters
77
module MySQL
88
module Quoting # :nodoc:
9+
extend ActiveSupport::Concern
10+
911
QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
1012
QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
1113

14+
module ClassMethods # :nodoc:
15+
def column_name_matcher
16+
/
17+
\A
18+
(
19+
(?:
20+
# `table_name`.`column_name` | function(one or no argument)
21+
((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
22+
)
23+
(?:(?:\s+AS)?\s+(?:\w+|`\w+`))?
24+
)
25+
(?:\s*,\s*\g<1>)*
26+
\z
27+
/ix
28+
end
29+
30+
def column_name_with_order_matcher
31+
/
32+
\A
33+
(
34+
(?:
35+
# `table_name`.`column_name` | function(one or no argument)
36+
((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
37+
)
38+
(?:\s+COLLATE\s+(?:\w+|"\w+"))?
39+
(?:\s+ASC|\s+DESC)?
40+
)
41+
(?:\s*,\s*\g<1>)*
42+
\z
43+
/ix
44+
end
45+
46+
def quote_column_name(name)
47+
QUOTED_COLUMN_NAMES[name] ||= "`#{name.to_s.gsub('`', '``')}`".freeze
48+
end
49+
50+
def quote_table_name(name)
51+
QUOTED_TABLE_NAMES[name] ||= "`#{name.to_s.gsub('`', '``').gsub(".", "`.`")}`".freeze
52+
end
53+
end
54+
1255
def cast_bound_value(value)
1356
case value
1457
when Rational
@@ -26,14 +69,6 @@ def cast_bound_value(value)
2669
end
2770
end
2871

29-
def quote_column_name(name)
30-
QUOTED_COLUMN_NAMES[name] ||= "`#{super.gsub('`', '``')}`".freeze
31-
end
32-
33-
def quote_table_name(name)
34-
QUOTED_TABLE_NAMES[name] ||= -super.gsub(".", "`.`").freeze
35-
end
36-
3772
def unquoted_true
3873
1
3974
end
@@ -81,43 +116,6 @@ def type_cast(value) # :nodoc:
81116
super
82117
end
83118
end
84-
85-
def column_name_matcher
86-
COLUMN_NAME
87-
end
88-
89-
def column_name_with_order_matcher
90-
COLUMN_NAME_WITH_ORDER
91-
end
92-
93-
COLUMN_NAME = /
94-
\A
95-
(
96-
(?:
97-
# `table_name`.`column_name` | function(one or no argument)
98-
((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
99-
)
100-
(?:(?:\s+AS)?\s+(?:\w+|`\w+`))?
101-
)
102-
(?:\s*,\s*\g<1>)*
103-
\z
104-
/ix
105-
106-
COLUMN_NAME_WITH_ORDER = /
107-
\A
108-
(
109-
(?:
110-
# `table_name`.`column_name` | function(one or no argument)
111-
((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
112-
)
113-
(?:\s+COLLATE\s+(?:\w+|"\w+"))?
114-
(?:\s+ASC|\s+DESC)?
115-
)
116-
(?:\s*,\s*\g<1>)*
117-
\z
118-
/ix
119-
120-
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
121119
end
122120
end
123121
end

0 commit comments

Comments
 (0)