@@ -68,7 +68,7 @@ module StringCteParser
68
68
# Examples that DON'T match:
69
69
# users (no backticks)
70
70
# `users (missing closing backtick)
71
- BACKTICK_QUOTED_TABLE = /`([^`]+)`/
71
+ BACKTICK_QUOTED_TABLE = /`([^`]+)`/ . freeze
72
72
73
73
# Matches table names enclosed in double quotes: "table_name"
74
74
# Examples that MATCH:
@@ -78,7 +78,7 @@ module StringCteParser
78
78
# Examples that DON'T match:
79
79
# users (no quotes)
80
80
# "users (missing closing quote)
81
- DOUBLE_QUOTED_TABLE = /"([^"]+)"/
81
+ DOUBLE_QUOTED_TABLE = /"([^"]+)"/ . freeze
82
82
83
83
# Matches unquoted table names: must start with letter/underscore, can contain letters/numbers/underscores
84
84
# Examples that MATCH:
@@ -92,23 +92,23 @@ module StringCteParser
92
92
# user-posts (contains hyphen)
93
93
# user.posts (contains dot)
94
94
# "users" (has quotes - handled by other patterns)
95
- UNQUOTED_TABLE = /([a-zA-Z_][a-zA-Z0-9_]*)/
95
+ UNQUOTED_TABLE = /([a-zA-Z_][a-zA-Z0-9_]*)/ . freeze
96
96
97
97
# Combines all table name patterns with non-capturing group
98
98
# Examples that MATCH:
99
99
# `users` → captures "users" from backtick group
100
100
# "user_posts" → captures "user_posts" from quote group
101
101
# popular_posts → captures "popular_posts" from unquoted group
102
102
# Note: Only one capture group will have a value, the others will be nil
103
- TABLE_NAME_PATTERN = /(?:#{ BACKTICK_QUOTED_TABLE } |#{ DOUBLE_QUOTED_TABLE } |#{ UNQUOTED_TABLE } )/
103
+ TABLE_NAME_PATTERN = /(?:#{ BACKTICK_QUOTED_TABLE } |#{ DOUBLE_QUOTED_TABLE } |#{ UNQUOTED_TABLE } )/ . freeze
104
104
105
105
# Matches the AS keyword (case insensitive)
106
106
# Examples that MATCH:
107
107
# AS, as, As, aS (any case combination)
108
108
# Examples that DON'T match:
109
109
# A S (space in between)
110
110
# ASS (too many letters)
111
- AS_KEYWORD = /AS/i
111
+ AS_KEYWORD = /AS/i . freeze
112
112
113
113
# Matches the SQL expression inside parentheses (greedy match for everything inside)
114
114
# Examples that MATCH:
@@ -119,7 +119,7 @@ module StringCteParser
119
119
# SELECT * FROM posts (no parentheses)
120
120
# (SELECT * FROM posts (missing closing paren)
121
121
# SELECT * FROM posts) (missing opening paren)
122
- EXPRESSION_PATTERN = /\( (.+)\) /
122
+ EXPRESSION_PATTERN = /\( (.+)\) / . freeze
123
123
124
124
# Complete CTE string pattern: optional whitespace + table_name + whitespace + AS + whitespace + (expression) + optional whitespace
125
125
# Examples that MATCH:
@@ -132,7 +132,7 @@ module StringCteParser
132
132
# "popular_posts AS SELECT * FROM posts" (missing parentheses)
133
133
# "AS (SELECT * FROM posts)" (missing table name)
134
134
# "123_table AS (SELECT * FROM posts)" (invalid table name)
135
- CTE_STRING_PATTERN = /\A \s *#{ TABLE_NAME_PATTERN } \s +#{ AS_KEYWORD } \s +#{ EXPRESSION_PATTERN } \s *\z /i
135
+ CTE_STRING_PATTERN = /\A \s *#{ TABLE_NAME_PATTERN } \s +#{ AS_KEYWORD } \s +#{ EXPRESSION_PATTERN } \s *\z /i . freeze
136
136
137
137
# ---------------------------------------------------------------------------
138
138
# Main parsing method that converts a CTE string into an Arel::Nodes::As node
@@ -146,9 +146,11 @@ def self.parse(string)
146
146
if string . strip . empty?
147
147
raise ArgumentError , "CTE string cannot be empty"
148
148
elsif !string . match ( /\s AS\s /i )
149
- raise ArgumentError , "CTE string must contain 'AS' keyword. Expected 'table_name AS (SELECT ...)' but got: #{ string } "
150
- elsif !string . include? ( '(' ) || !string . include? ( ')' )
151
- raise ArgumentError , "CTE expression must be enclosed in parentheses. Expected 'table_name AS (SELECT ...)' but got: #{ string } "
149
+ raise ArgumentError ,
150
+ "CTE string must contain 'AS' keyword. Expected 'table_name AS (SELECT ...)' but got: #{ string } "
151
+ elsif !string . include? ( "(" ) || !string . include? ( ")" )
152
+ raise ArgumentError ,
153
+ "CTE expression must be enclosed in parentheses. Expected 'table_name AS (SELECT ...)' but got: #{ string } "
152
154
else
153
155
raise ArgumentError , "Invalid CTE string format. Expected 'table_name AS (SELECT ...)' but got: #{ string } "
154
156
end
@@ -174,13 +176,9 @@ def self.parse(string)
174
176
# ---------------------------------------------------------------------------
175
177
# Validates that the extracted CTE components are not empty or whitespace-only
176
178
def self . validate_cte_components ( table_name , expression )
177
- if table_name . nil? || table_name . strip . empty?
178
- raise ArgumentError , "Empty table name in CTE string"
179
- end
179
+ raise ArgumentError , "Empty table name in CTE string" if table_name . nil? || table_name . strip . empty?
180
180
181
- if expression . nil? || expression . strip . empty?
182
- raise ArgumentError , "Empty expression in CTE string"
183
- end
181
+ raise ArgumentError , "Empty expression in CTE string" if expression . nil? || expression . strip . empty?
184
182
end
185
183
186
184
# ---------------------------------------------------------------------------
@@ -191,18 +189,16 @@ def self.validate_expression_syntax(expression)
191
189
paren_count = 0
192
190
expression . each_char do |char |
193
191
case char
194
- when '('
192
+ when "("
195
193
paren_count += 1
196
- when ')'
194
+ when ")"
197
195
paren_count -= 1
198
196
# If we have more closing than opening parens, fail immediately
199
- break if paren_count < 0
197
+ break if paren_count . negative?
200
198
end
201
199
end
202
200
203
- unless paren_count == 0
204
- raise ArgumentError , "Unbalanced parentheses in CTE expression: #{ expression } "
205
- end
201
+ raise ArgumentError , "Unbalanced parentheses in CTE expression: #{ expression } " unless paren_count . zero?
206
202
end
207
203
end
208
204
end
0 commit comments