Skip to content

Commit bfc09d6

Browse files
committed
Fix whitespace normalization to preserve SQL quoted strings in ERB templates
The previous implementation of prepare_query used a simple regex to normalize whitespace, which incorrectly modified content inside SQL quoted strings when preparing queries for logs. This caused issues with multiline ERB templates where string literals could be corrupted. Changes: - Refactored prepare_query to render ERB first, then normalize whitespace - Added regex pattern to match quoted strings and preserve their content - Only whitespace outside of quotes is normalized to single spaces - Simplified test database configuration to use consistent postgres setup - Added test coverage for multiline ERB templates with quoted strings This fix ensures that SQL string literals remain intact while still providing clean, single-line output for logging purposes.
1 parent 40b4da4 commit bfc09d6

File tree

4 files changed

+42
-19
lines changed

4 files changed

+42
-19
lines changed

lib/sql_query.rb

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,16 @@ def initialize
7878

7979
def prepare_query(for_logs)
8080
query_template = File.read(file_path)
81-
query_template = query_template.gsub(/(\n|\s)+/, ' ') if for_logs
82-
ERB.new(query_template).result(binding)
81+
rendered_sql = ERB.new(query_template).result(binding)
82+
83+
return rendered_sql unless for_logs
84+
85+
# Normalize whitespace while preserving SQL quoted strings
86+
# Matches: 'single-quoted' OR "double-quoted" OR (whitespace)
87+
# Only captures whitespace when NOT inside quotes
88+
rendered_sql.gsub(/'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|(\s+)/) do |match|
89+
$1 ? ' ' : match # Replace whitespace with space, preserve quoted strings
90+
end
8391
end
8492

8593
def split_to_path_and_name(file)

spec/spec_helper.rb

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,13 @@ def BigDecimal.new(...)
3333
mocks.verify_partial_doubles = true
3434
end
3535

36-
connection_config = if ENV['CI']
37-
{
38-
adapter: 'postgresql',
39-
host: 'localhost',
40-
username: 'postgres',
41-
password: 'postgres',
42-
database: 'sqlquery_test'
43-
}
44-
else
45-
{
46-
adapter: 'postgresql',
47-
host: 'localhost',
48-
username: 'sqlquery',
49-
password: 'sqlquery',
50-
database: 'sqlquery'
51-
}
52-
end
36+
connection_config = {
37+
adapter: 'postgresql',
38+
host: 'localhost',
39+
username: 'postgres',
40+
password: 'postgres',
41+
database: 'sqlquery_test'
42+
}
5343

5444
ActiveRecord::Base.establish_connection(connection_config)
5545

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<%
2+
field1 = @field1 || 'default1'
3+
field2 = @field2 || 'default2'
4+
%>
5+
SELECT
6+
<%= quote field1 %> as field1,
7+
<%= quote field2 %> as field2
8+
FROM players
9+
WHERE email = <%= quote @email %>

spec/sql_query_spec.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,22 @@ class Model < ActiveRecord::Base
211211
.to eq("SELECT * FROM players WHERE email = ' e@mail.dev ' ")
212212
end
213213
end
214+
215+
context 'when template has multiline ERB blocks' do
216+
let(:file_name) { :multiline_erb }
217+
let(:options) { { email: 'test@dev.com', field1: 'val1', field2: 'val2' } }
218+
let(:query) { described_class.new(file_name, options) }
219+
220+
it 'processes multiline ERB correctly without syntax errors' do
221+
expect { query.prepared_for_logs }.not_to raise_error
222+
end
223+
224+
it 'includes rendered values in prepared_for_logs output' do
225+
result = query.prepared_for_logs
226+
expect(result).to include("'val1' as field1")
227+
expect(result).to include("'val2' as field2")
228+
end
229+
end
214230
end
215231

216232
describe '.config' do

0 commit comments

Comments
 (0)