Skip to content

Commit af85f74

Browse files
authored
Merge pull request rails#51174 from Shopify/connection-less-quoting
Don't require an active connection for table and column quoting
2 parents 0a9ca01 + 0016280 commit af85f74

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)