Upstream sync

This commit is contained in:
Senad Uka
2020-02-07 16:09:57 +01:00
parent 530951ff45
commit 37ad466439
12 changed files with 86 additions and 44 deletions

View File

@@ -23,6 +23,6 @@ group :development do
gem 'rubocop', '~> 0.54', require: false
gem 'rubocop-rspec', '~> 1.23', require: false
gem 'simplecov', '~> 0.16', require: false
gem "rexical"
gem "racc"
gem "rexical", '~> 1.0', require: false
gem "racc", '~> 1.4', require: false
end

View File

@@ -134,9 +134,9 @@ DEPENDENCIES
juwelier (~> 2.1)
pg (~> 0.15)
pry (~> 0.12)
racc
racc (~> 1.4)
rdoc (~> 3.12)
rexical
rexical (~> 1.0)
rspec (~> 3.8)
rubocop (~> 0.54)
rubocop-rspec (~> 1.23)

View File

@@ -14,7 +14,9 @@ class Query
| TERM_WITHOUT_QUOTES COLON TERM_WITH_QUOTES { result = {val[0] => val[2]} }
| expression OPERATOR_OR expression { result = {:OPERATOR_OR => [val[0], val[2]]} }
| expression OPERATOR_AND expression { result = {:OPERATOR_AND => [val[0], val[2]]} }
| OPERATOR_NOT expression { result = {:OPERATOR_NOT => val[1]} }
| L_BRACKET expression R_BRACKET { result = val[1] }
| expression expression { result = {:OPERATOR_OR => [val[0], val[1]]} }
end
---- header

View File

@@ -10,7 +10,7 @@ require 'racc/parser.rb'
class Query < Racc::Parser
module_eval(<<'...end grammar.y/module_eval...', 'grammar.y', 24)
module_eval(<<'...end grammar.y/module_eval...', 'grammar.y', 26)
def parse(input)
scan_str(input)
end
@@ -18,34 +18,44 @@ module_eval(<<'...end grammar.y/module_eval...', 'grammar.y', 24)
##### State transition tables begin ###
racc_action_table = [
8, 7, 3, 4, 11, 5, 16, 3, 4, 9,
5, 3, 4, 6, 5, 3, 4, 8, 5, 8,
7, 14, 15 ]
5, 10, 9, 3, 4, 5, 6, 19, 3, 4,
7, 6, 5, 10, 9, 3, 4, 5, 6, 11,
3, 4, 5, 6, 14, 3, 4, nil, 6, 5,
10, 9, 3, 4, 5, 6, nil, 3, 4, 5,
6, nil, 3, 4, nil, 6, 5, 10, nil, 3,
4, 5, 6, nil, 3, 4, nil, 6, 3, 4,
nil, 6, 17, 18 ]
racc_action_check = [
10, 10, 8, 8, 6, 8, 10, 0, 0, 3,
0, 5, 5, 1, 5, 7, 7, 12, 7, 2,
2, 9, 9 ]
13, 13, 13, 13, 13, 0, 13, 13, 0, 0,
1, 0, 2, 2, 2, 2, 2, 5, 2, 3,
5, 5, 6, 5, 7, 6, 6, nil, 6, 8,
8, 8, 8, 8, 9, 8, nil, 9, 9, 10,
9, nil, 10, 10, nil, 10, 15, 15, nil, 15,
15, 16, 15, nil, 16, 16, nil, 16, 12, 12,
nil, 12, 11, 11 ]
racc_action_pointer = [
2, 13, 16, 2, nil, 6, 4, 10, -3, 16,
-3, nil, 14, nil, nil, nil, nil ]
3, 10, 10, 12, nil, 15, 20, 24, 27, 32,
37, 57, 53, -2, nil, 44, 49, nil, nil, nil ]
racc_action_default = [
-2, -10, -1, -3, -4, -10, -10, -10, -10, -10,
-10, 17, -7, -8, -5, -6, -9 ]
-2, -12, -1, -3, -4, -12, -12, -12, -11, -12,
-12, -12, -9, -12, 20, -7, -8, -5, -6, -10 ]
racc_goto_table = [
2, 1, nil, nil, nil, 10, nil, 12, 13 ]
2, 1, nil, nil, nil, 12, 13, nil, nil, 15,
16 ]
racc_goto_check = [
2, 1, nil, nil, nil, 2, nil, 2, 2 ]
2, 1, nil, nil, nil, 2, 2, nil, nil, 2,
2 ]
racc_goto_pointer = [
nil, 1, 0 ]
racc_goto_default = [
nil, nil, nil ]
nil, nil, 8 ]
racc_reduce_table = [
0, 0, :racc_error,
@@ -57,11 +67,13 @@ racc_reduce_table = [
3, 12, :_reduce_6,
3, 12, :_reduce_7,
3, 12, :_reduce_8,
3, 12, :_reduce_9 ]
2, 12, :_reduce_9,
3, 12, :_reduce_10,
2, 12, :_reduce_11 ]
racc_reduce_n = 10
racc_reduce_n = 12
racc_shift_n = 17
racc_shift_n = 20
racc_token_table = {
false => 0,
@@ -169,11 +181,25 @@ module_eval(<<'.,.,', 'grammar.y', 15)
module_eval(<<'.,.,', 'grammar.y', 16)
def _reduce_9(val, _values, result)
result = {:OPERATOR_NOT => val[1]}
result
end
.,.,
module_eval(<<'.,.,', 'grammar.y', 17)
def _reduce_10(val, _values, result)
result = val[1]
result
end
.,.,
module_eval(<<'.,.,', 'grammar.y', 18)
def _reduce_11(val, _values, result)
result = {:OPERATOR_OR => [val[0], val[1]]}
result
end
.,.,
def _reduce_none(val, _values, result)
val[0]
end

View File

@@ -42,6 +42,7 @@ class TextToSqlQuery
end
not_expression = not_array.shift
not_params = not_array
["NOT #{not_expression}"] + not_params

Binary file not shown.

View File

@@ -1,4 +1,4 @@
require './lexer'
require_relative '../../lib/lexer'
class QueryLexerTester
describe 'Testing the Lexer' do

View File

@@ -1,4 +1,4 @@
require './parser'
require_relative '../../lib/parser'
class QueryParserTester
describe 'Testing the Parser' do

View File

@@ -1,61 +1,61 @@
# frozen_string_literal: true
require_relative '../../lib/text_to_sql_query'
describe TextToRegexQuery do
include_examples 'pg_search', SimpleVectorModel
describe TextToSqlQuery do
describe '.new' do
# tests simple search term without column name and without quotes
it { expect(described_class.new('some-default-value', [:"players.name"], :"players.name").where_clause(SimpleVectorModel)).to eq(['players.name ILIKE ?', '%some-default-value%']) }
it { expect(described_class.new('some-default-value', [:"players.name"], :"players.name").where_clause).to eq(['players.name ILIKE ?', '%some-default-value%']) }
# tests simple search term with column name and without quotes
it { expect(described_class.new('title:ab', [:"players.title"], :"players.title").where_clause(SimpleVectorModel)).to eq(['players.title ILIKE ?', '%ab%']) }
it { expect(described_class.new('name:ab', [:"players.name"], :"players.name").where_clause).to eq(['players.name ILIKE ?', '%ab%']) }
# tests simple search term with unknown column name and without quotes
it { expect(described_class.new('unknown:ab', [:"players.name"], :"players.name").where_clause(SimpleVectorModel)).to raise_error(RuntimeError, "Unknown field 'unknown'") }
it { expect{described_class.new('unknown:ab', [:"players.name"], :"players.name").where_clause}.to raise_error(RuntimeError, "Unknown field 'unknown'") }
# tests simple search term without column name and with quotes
it { expect(described_class.new('"ab"', [:"players.name", :"players.device_id"], :"players.device_id").where_clause(SimpleVectorModel)).to eq(["players.device_id ILIKE ?", "%ab"]) }
it { expect(described_class.new('"ab"', [:"players.name", :"players.device_id"], :"players.device_id").where_clause).to eq(["players.device_id ILIKE ?", "%ab%"]) }
# tests simple search term with column name and with quotes
it { expect(described_class.new('tag:"ab"', [:"players.name", :"players.tag"], :"players.device_id").where_clause(SimpleVectorModel)).to eq(["players.tag ILIKE ?", "%ab%"]) }
it { expect(described_class.new('tags:"ab"', [:"players.name", :"players.tags"], :"players.device_id").where_clause).to eq(["players.tags ILIKE ?", "%ab%"]) }
# tests search without operators
it { expect(described_class.new('123 456', [:"players.name", :"players.device_id"], :"players.device_id").where_clause(SimpleVectorModel)).to eq(["(players.device_id ILIKE ? OR players.device_id ILIKE ?)", "%123%", "%456%"]) }
it { expect(described_class.new('123 456', [:"players.name", :"players.device_id"], :"players.device_id").where_clause).to eq(["(players.device_id ILIKE ? OR players.device_id ILIKE ?)", "%123%", "%456%"]) }
# tests search with OR operator
it { expect(described_class.new('123 or 456', [:"players.name", :"players.device_id"], :"players.device_id").where_clause(SimpleVectorModel)).to eq(["(players.device_id ILIKE ? OR players.device_id ILIKE ?)", "%123%", "%456%"]) }
it { expect(described_class.new('123 or 456', [:"players.name", :"players.device_id"], :"players.device_id").where_clause).to eq(["(players.device_id ILIKE ? OR players.device_id ILIKE ?)", "%123%", "%456%"]) }
# tests search with AND operator
it { expect(described_class.new('123 and 456', [:"players.name", :"players.device_id"], :"players.device_id").where_clause(SimpleVectorModel)).to eq(["(players.device_id ILIKE ? AND players.device_id ILIKE ?)", "%123%", "%456%"]) }
it { expect(described_class.new('123 and 456', [:"players.name", :"players.device_id"], :"players.device_id").where_clause).to eq(["(players.device_id ILIKE ? AND players.device_id ILIKE ?)", "%123%", "%456%"]) }
# tests search with NOT operator on default column
it { expect(described_class.new('not 23', [:"players.name", :"players.device_id"], :"players.device_id").where_clause(SimpleVectorModel)).to eq(["NOT players.device_id ILIKE ?", "%23%"]) }
it { expect(described_class.new('not 23', [:"players.name", :"players.device_id"], :"players.device_id").where_clause).to eq(["NOT players.device_id ILIKE ?", "%23%"]) }
# tests search with NOT operator on non-default column
it { expect(described_class.new('not tag:23', [:"players.name", :"players.tag"], :"players.device_id").where_clause(SimpleVectorModel)).to eq(["NOT players.tag ILIKE ?", "%23%"]) }
it { expect(described_class.new('not value:23', [:"players.name", :"players.value"], :"players.device_id").where_clause).to eq(["NOT players.value ILIKE ?", "%23%"]) }
# tests search with mixed logical operators
it { expect(described_class.new('title:ab and not tag:hf-1', [:"players.title", :"players.tag"], :"players.device_id").where_clause(SimpleVectorModel)).to eq(['(players.title ILIKE ? AND NOT players.tag ILIKE ?)', '%ab%', '%hf-1%']) }
it { expect(described_class.new('name:ab and not value:hf-1', [:"players.name", :"players.value"], :"players.device_id").where_clause).to eq(['(players.name ILIKE ? AND NOT players.value ILIKE ?)', '%ab%', '%hf-1%']) }
# tests search with mixed logical operators without NOT'
it { expect(described_class.new('title:a and title:b or title:c', [:"players.title", :"players.tag"], :"players.device_id").where_clause(SimpleVectorModel)).to eq(['((players.title ILIKE ? AND players.title ILIKE ?) OR players.title ILIKE ?)', '%a%', '%b%', '%c%']) }
it { expect(described_class.new('name:a and name:b or name:c', [:"players.name", :"players.value"], :"players.device_id").where_clause).to eq(['((players.name ILIKE ? AND players.name ILIKE ?) OR players.name ILIKE ?)', '%a%', '%b%', '%c%']) }
# tests search with brackets in expression
it { expect(described_class.new('title:a and (title:b or title:c)', [:"players.title", :"players.tag"], :"players.device_id").where_clause(SimpleVectorModel)).to eq(['(players.title ILIKE ? AND (players.title ILIKE ? OR players.title ILIKE ?))', '%a%', '%b%', '%c%']) }
it { expect(described_class.new('name:a and (name:b or name:c)', [:"players.name", :"players.value"], :"players.device_id").where_clause).to eq(['(players.name ILIKE ? AND (players.name ILIKE ? OR players.name ILIKE ?))', '%a%', '%b%', '%c%']) }
# tests search with brackets in expression and with NOT operator
it { expect(described_class.new('title:a and not (title:b or title:c)', [:"players.title", :"players.tag"], :"players.device_id").where_clause(SimpleVectorModel)).to eq(['(players.title ILIKE ? AND NOT (players.title ILIKE ? OR players.title ILIKE ?))', '%a%', '%b%', '%c%']) }
it { expect(described_class.new('name:a and not (name:b or name:c)', [:"players.name", :"players.value"], :"players.device_id").where_clause).to eq(['(players.name ILIKE ? AND NOT (players.name ILIKE ? OR players.name ILIKE ?))', '%a%', '%b%', '%c%']) }
# tests search with special characters in search term
it { expect(described_class.new('title:"%a_\"', [:"players.title", :"players.tag"], :"players.device_id").where_clause(SimpleVectorModel)).to eq(['players.title ILIKE ?', '%\%a\_\\%']) }
it { expect(described_class.new('name:"%a_\"', [:"players.name", :"players.value"], :"players.device_id").where_clause).to eq(['players.name ILIKE ?', '%\%a\_\\%']) }
# tests search with field mappings
it { expect(described_class.new('tags:h1-r', [:'players.title', :'players.name', :'players.device_id'], :"players.device_id", { tags: "tags.name" }).where_clause(SimpleVectorModel)).to eq(['tags.name ILIKE ?', '%h1-r%']) }
it { expect(described_class.new('tags:h1-r', [:'players.name', :'players.value', :'players.device_id'], :"players.device_id", { tags: "tags.value" }).where_clause).to eq(['tags.value ILIKE ?', '%h1-r%']) }
# tests search with field mappings when fields array has same mapping
it { expect(described_class.new('tags:hs1-r', [:'players.title', :'players.tags', :'players.device_id'], :"players.device_id", { tags: "tags.name" }).where_clause(SimpleVectorModel)).to eq(["players.tag ILIKE ?", "%ab%"]) }
it { expect(described_class.new('tags:h1-r', [:'players.name', :'players.tags', :'players.device_id'], :"players.device_id", { tags: "tags.value" }).where_clause).to eq(["tags.value ILIKE ?", "%h1-r%"]) }
# tests complex query
it { expect(described_class.new('(device_id:"with space" tags:mta no-quotes-id-123) or "id with quotes-5" and ( ("id with q 10" or "id with q 20") and ("id with Q 30" "id with Q 40") and not id-without-Q-50)', [:'players.title', :'players.name', :'players.device_id'], :"players.device_id", { tags: 'tags.name' }).where_clause(SimpleVectorModel)).to eq(['((players.device_id ILIKE ? OR (tags.name ILIKE ? OR players.device_id ILIKE ?)) OR (players.device_id ILIKE ? AND (((players.device_id ILIKE ? OR players.device_id ILIKE ?) AND (players.device_id ILIKE ? OR players.device_id ILIKE ?)) AND NOT players.device_id ILIKE ?)))', '%with space%', '%mta%', '%no-quotes-id-123%', '%id with quotes-5%', '%id with q 10%', '%id with q 20%', '%id with Q 30%', '%id with Q 40%', '%id-without-Q-50%']) }
it { expect(described_class.new('(device_id:"with space" tags:mta no-quotes-id-123) or "id with quotes-5" and ( ("id with q 10" or "id with q 20") and ("id with Q 30" "id with Q 40") and not id-without-Q-50)', [:'players.name', :'players.value', :'players.device_id'], :"players.device_id", { tags: 'tags.name' }).where_clause).to eq(['((players.device_id ILIKE ? OR (tags.name ILIKE ? OR players.device_id ILIKE ?)) OR (players.device_id ILIKE ? AND (((players.device_id ILIKE ? OR players.device_id ILIKE ?) AND (players.device_id ILIKE ? OR players.device_id ILIKE ?)) AND NOT players.device_id ILIKE ?)))', '%with space%', '%mta%', '%no-quotes-id-123%', '%id with quotes-5%', '%id with q 10%', '%id with q 20%', '%id with Q 30%', '%id with Q 40%', '%id-without-Q-50%']) }
end
end

View File

@@ -42,4 +42,11 @@ ActiveRecord::Schema.define do
t.string :name
t.timestamps null: false
end
create_table :players, force: true do |t|
t.references :tag
t.string :name
t.string :value
t.string :device_id
end
end

View File

@@ -35,6 +35,12 @@ class SimpleVectorModel < ActiveRecord::Base
pg_search fields: %i[id name value], cache: :search_cache, language: :simple
end
class SimplePlayerModel < ActiveRecord::Base
self.table_name = :players
include PgSearchable
pg_search fields: %i[device_id name value tags], cache: :search_cache, language: :simple
end
class VectorWithoutWildcardModel < ActiveRecord::Base
self.table_name = :vector_models
include PgSearchable

View File

@@ -3,7 +3,7 @@
module Version
MAJOR = 1
MINOR = 0
PATCH = 21
PATCH = 24
def self.to_s
[MAJOR, MINOR, PATCH].compact.join('.')