Upstream sync
This commit is contained in:
4
Gemfile
4
Gemfile
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
@@ -1,4 +1,4 @@
|
||||
require './lexer'
|
||||
require_relative '../../lib/lexer'
|
||||
|
||||
class QueryLexerTester
|
||||
describe 'Testing the Lexer' do
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require './parser'
|
||||
require_relative '../../lib/parser'
|
||||
|
||||
class QueryParserTester
|
||||
describe 'Testing the Parser' do
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
module Version
|
||||
MAJOR = 1
|
||||
MINOR = 0
|
||||
PATCH = 21
|
||||
PATCH = 24
|
||||
|
||||
def self.to_s
|
||||
[MAJOR, MINOR, PATCH].compact.join('.')
|
||||
|
||||
Reference in New Issue
Block a user