Commit 21d9f59f authored by Lin Jen-Shin's avatar Lin Jen-Shin

Introduce ::Gitlab::SQL::Glob.to_like

Feedback:
https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2112#note_33111037
parent 2040a0bd
...@@ -196,10 +196,7 @@ module EE ...@@ -196,10 +196,7 @@ module EE
where = <<~SQL where = <<~SQL
environment_scope IN (:wildcard, :environment_name) OR environment_scope IN (:wildcard, :environment_name) OR
:environment_name LIKE :environment_name LIKE
REPLACE(REPLACE(REPLACE(environment_scope, #{::Gitlab::SQL::Glob.to_like('environment_scope')}
:underscore, :escaped_underscore),
:percent, :escaped_percent),
:wildcard, :percent)
SQL SQL
order = <<~SQL order = <<~SQL
...@@ -213,15 +210,28 @@ module EE ...@@ -213,15 +210,28 @@ module EE
values = { values = {
wildcard: '*', wildcard: '*',
environment_name: environment.name, environment_name: environment.name,
percent: '%',
escaped_percent: '\\%',
underscore: '_',
escaped_underscore: '\\_'
} }
quoted_values = quoted_values =
values.transform_values(&self.class.connection.method(:quote)) values.transform_values(&self.class.connection.method(:quote))
# The query is trying to find variables with scopes matching the
# current environment name. Suppose the environment name is
# 'review/app', and we have variables with environment scopes like:
# * variable A: review
# * variable B: review/app
# * variable C: review/*
# * variable D: *
# And the query should find variable B, C, and D, because it would
# try to convert the scope into a LIKE pattern for each variable:
# * A: review
# * B: review/app
# * C: review/%
# * D: %
# Note that we'll match % and _ literally therefore we'll escape them.
# In this case, B, C, and D would match. We also want to prioritize
# the exact matched name, and put * last, and everything else in the
# middle. So the order should be: D < C < B
query query
.where(where, values) .where(where, values)
.order(order % quoted_values) # `order` cannot escape for us! .order(order % quoted_values) # `order` cannot escape for us!
......
module Gitlab
module SQL
module Glob
extend self
# Convert a simple glob pattern with wildcard (*) to SQL LIKE pattern
# with SQL expression
def to_like(pattern)
<<~SQL
REPLACE(REPLACE(REPLACE(#{pattern},
#{q('%')}, #{q('\\%')}),
#{q('_')}, #{q('\\_')}),
#{q('*')}, #{q('%')})
SQL
end
def q(string)
ActiveRecord::Base.connection.quote(string)
end
end
end
end
require 'spec_helper'
describe Gitlab::SQL::Glob, lib: true do
describe '.to_like' do
it 'matches * as %' do
expect(glob('apple', '*')).to be(true)
expect(glob('apple', 'app*')).to be(true)
expect(glob('apple', 'apple*')).to be(true)
expect(glob('apple', '*pple')).to be(true)
expect(glob('apple', 'ap*le')).to be(true)
expect(glob('apple', '*a')).to be(false)
expect(glob('apple', 'app*a')).to be(false)
expect(glob('apple', 'ap*l')).to be(false)
end
it 'matches % literally' do
expect(glob('100%', '100%')).to be(true)
expect(glob('100%', '%')).to be(false)
end
it 'matches _ literally' do
expect(glob('^_^', '^_^')).to be(true)
expect(glob('^A^', '^_^')).to be(false)
end
end
def glob(string, pattern)
match(string, subject.to_like(quote(pattern)))
end
def match(string, pattern)
query("SELECT #{quote(string)} LIKE #{pattern} AS match")
.first['match']
end
def query(sql)
result = ActiveRecord::Base.connection.exec_query(sql)
result.map do |row|
row.each_with_object({}) do |(column, value), hash|
hash[column] =
result.column_types[column].type_cast_from_database(value)
end
end
end
def quote(string)
ActiveRecord::Base.connection.quote(string)
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment