Skip to content

Commit 17df2c0

Browse files
fix: restrict the use of surrounding parentheses only to IN operator
2 parents 7618379 + 2e8d0cd commit 17df2c0

File tree

3 files changed

+50
-12
lines changed

3 files changed

+50
-12
lines changed

app/services/forest_liana/filters_parser.rb

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -109,16 +109,7 @@ def parse_condition_without_smart_field(condition)
109109
parsed_value = parse_value(operator, value)
110110
field_and_operator = "#{parsed_field} #{parsed_operator}"
111111

112-
# parenthesis around the parsed_value are required to make the `IN` operator work
113-
# and have no side effects on other requests
114-
if Rails::VERSION::MAJOR < 5
115-
"#{field_and_operator} (#{ActiveRecord::Base.sanitize(parsed_value)})"
116-
# NOTICE: sanitize method as been removed in Rails 5.1 and sanitize_sql introduced in Rails 5.2.
117-
elsif Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR == 1
118-
"#{field_and_operator} (#{ActiveRecord::Base.connection.quote(parsed_value)})"
119-
else
120-
ActiveRecord::Base.sanitize_sql(["#{field_and_operator} (?)", parsed_value])
121-
end
112+
sanitize_condition(field_and_operator, operator, parsed_value)
122113
end
123114

124115
def parse_aggregation_operator(aggregator_operator)
@@ -296,5 +287,26 @@ def ensure_valid_condition(condition)
296287
raise ForestLiana::Errors::HTTP422Error.new('Invalid condition format')
297288
end
298289
end
290+
291+
private
292+
293+
def prepare_value_for_operator(operator, value)
294+
# parenthesis around the parsed_value are required to make the `IN` operator work
295+
operator == 'in' ? "(#{value})" : value
296+
end
297+
298+
def sanitize_condition(field_and_operator, operator, parsed_value)
299+
if Rails::VERSION::MAJOR < 5
300+
condition_value = prepare_value_for_operator(operator, ActiveRecord::Base.sanitize(parsed_value))
301+
"#{field_and_operator} #{condition_value}"
302+
# NOTICE: sanitize method as been removed in Rails 5.1 and sanitize_sql introduced in Rails 5.2.
303+
elsif Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR == 1
304+
condition_value = prepare_value_for_operator(operator, ActiveRecord::Base.connection.quote(parsed_value))
305+
"#{field_and_operator} #{condition_value}"
306+
else
307+
condition_value = prepare_value_for_operator(operator, '?')
308+
ActiveRecord::Base.sanitize_sql(["#{field_and_operator} #{condition_value}", parsed_value])
309+
end
310+
end
299311
end
300312
end

spec/services/forest_liana/filters_parser_spec.rb

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module ForestLiana
1111
let(:date_condition_1) { { 'field' => 'created_at', 'operator' => 'before', 'value' => 2.hours.ago } }
1212
let(:date_condition_2) { { 'field' => 'created_at', 'operator' => 'today' } }
1313
let(:date_condition_3) { { 'field' => 'created_at', 'operator' => 'previous_x_days', 'value' => 2 } }
14+
let(:presence_condition) { { 'field' => 'name', 'operator' => 'present' } }
1415

1516
before {
1617
island = Island.create!(name: "L'île de la muerta")
@@ -274,14 +275,39 @@ module ForestLiana
274275
let(:filters) { { 'aggregator' => 'or', 'conditions' => [simple_condition_2, simple_condition_3] } }
275276
it { expect(resource.where(query).count).to eq 2 }
276277
end
278+
279+
context "'name ends_with \"3\"' 'or' 'name is not null'" do
280+
let(:filters) { { 'aggregator' => 'or', 'conditions' => [simple_condition_2, presence_condition] } }
281+
it { expect(resource.where(query).count).to eq 3 }
282+
end
277283
end
278284

279285
describe 'parse_condition' do
280286
let(:condition) { simple_condition_2 }
281287
let(:result) { filter_parser.parse_condition(condition) }
282288

283289
context 'on valid condition' do
284-
it { expect(result).to eq "\"trees\".\"name\" LIKE ('%3')" }
290+
context 'when the condition uses the contains operator' do
291+
it { expect(result).to eq "\"trees\".\"name\" LIKE '%3'" }
292+
end
293+
294+
context 'when the condition uses the blank operator' do
295+
let(:condition) { { 'field' => 'name', 'operator' => 'blank' } }
296+
297+
it { expect(result).to eq "\"trees\".\"name\" IS NULL" }
298+
end
299+
300+
context 'when the condition uses the presence operator' do
301+
let(:condition) { presence_condition }
302+
303+
it { expect(result).to eq "\"trees\".\"name\" IS NOT NULL" }
304+
end
305+
306+
context 'when the condition uses the in operator' do
307+
let(:condition) { { 'field' => 'name', 'operator' => 'in', 'value' => ['Tree n1', 'Tree n3'] } }
308+
309+
it { expect(result).to eq "\"trees\".\"name\" IN ('Tree n1','Tree n3')" }
310+
end
285311
end
286312

287313
context 'on belongs_to condition' do

spec/services/forest_liana/resource_updater_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ module ForestLiana
8484
subject.perform
8585

8686
expect(subject.record).to be nil
87-
expect(subject.errors[0][:detail]).to eq 'Couldn\'t find User with \'id\'=1 [WHERE (("users"."id" > (2)))]'
87+
expect(subject.errors[0][:detail]).to eq 'Couldn\'t find User with \'id\'=1 [WHERE (("users"."id" > 2))]'
8888
end
8989
end
9090

0 commit comments

Comments
 (0)