Initial commit

This commit is contained in:
Senad Uka
2020-01-28 13:31:56 +01:00
parent 7f7c6e95bc
commit 2749c53aac
56 changed files with 6516 additions and 1 deletions

View File

@@ -0,0 +1,115 @@
# frozen_string_literal: true
describe PgSearchable do
include_examples 'pg_search', VectorModel
include_examples 'pg_search', VectorWithCustomPrimaryKeyModel
include_examples 'pg_search', VectorWithCustomCallback
include_examples 'pg_search', SimpleVectorModel
include_examples 'pg_search', VectorWithoutWildcardModel
include_examples 'pg_search', VectorModelWithCustomSearchScope, 'fulltext'
include_examples 'pg_search', VectorModelWithTagValues
include_examples 'pg_search', DynamicModel
include_examples 'pg_search', DynamicModelWithTagValues
include_examples 'pg_search', DynamicModelWithCategory
include_examples 'pg_search', DynamicModelWithSectionsTrhough
describe 'pg_search' do
describe 'joins' do
it 'can dynamically query compound relation' do
record = DynamicModelWithCategory.create(name: 'something', value: 'amazing')
category = Category.create(name: 'searchable')
Tag.create(value: 'impressive', category: category, taggable: record)
expect(DynamicModelWithCategory.scope_search('searchable')).to include(record)
end
it 'can use has_many :through relation' do
record = DynamicModelWithSectionsTrhough.create(name: 'something', value: 'amazing')
tag = Tag.create(value: 'impressive', taggable: record)
Section.create(name: 'searchable', tag: tag)
expect(DynamicModelWithSectionsTrhough.scope_search('searchable')).to include(record)
end
end
describe 'properties' do
describe 'skip_callback' do
context 'when enabled' do
let(:record) { VectorModel.create(name: 'something', value: 'amazing') }
it 'can find the record after it updates' do
record.update(name: 'cookie')
expect(VectorModel.scope_search('cookie')).to include(record)
end
end
context 'when disabled' do
let(:record) { VectorModelWithoutCallback.create(name: 'something', value: 'amazing') }
it 'cannot find the record after it updates' do
record.update(name: 'cookie')
expect(VectorModelWithoutCallback.scope_search('cookie')).not_to include(record)
end
it 'can find the record after manually calling .update_pg_search_cache' do
record.update(name: 'cookie')
record.update_pg_search_cache
expect(VectorModelWithoutCallback.scope_search('cookie')).to include(record)
end
end
end
describe 'scope' do
it 'defaults to "scope_search"' do
expect(VectorModel).to respond_to(:scope_search)
end
it 'can use a different scope name' do
expect(VectorModelWithCustomSearchScope).to respond_to(:fulltext)
end
it 'doesnt pollutes the default method name if customized' do
expect(VectorModelWithCustomSearchScope).not_to respond_to(:scope_search)
end
end
describe 'language' do
it 'defaults to english lexemes' do
record = VectorModel.create name: 'something', value: 'amazing'
expect(VectorModel.scope_search('amaz')).to include(record)
end
it 'can be changed to simple to avoid lexeme truncation' do
record = SimpleVectorModel.create name: 'something', value: 'amazing'
expect(SimpleVectorModel.scope_search('amazings')).not_to include(record)
end
end
describe 'wildcard' do
it 'by default uses it' do
record = VectorModel.create name: '12345', value: 'amazing'
expect(VectorModel.scope_search('123')).to include(record)
end
it 'can be set to false' do
record = VectorWithoutWildcardModel.create name: '12345', value: 'amazing'
expect(VectorWithoutWildcardModel.scope_search('123')).not_to include(record)
end
end
describe 'tags' do
it 'allow indexing fields of other associations' do
record = DynamicModelWithTagValues.create name: 'something', value: 'amazing'
Tag.create(taggable: record, value: 'red')
expect(DynamicModelWithTagValues.scope_search('red')).to include(record)
end
end
describe 'external_cache_data' do
it 'can index external data using a method' do
record = VectorModelWithTagValues.create name: 'something', value: 'amazing'
Tag.create(taggable: record, value: 'red')
expect(VectorModelWithTagValues.scope_search('red')).to include(record)
end
end
end
end
end

View File

@@ -0,0 +1,259 @@
require './lexer'
class QueryLexerTester
describe 'Testing the Lexer' do
before do
@evaluator = Query.new
end
it 'tests bracket expression' do
@result = @evaluator.tokenize('()')
expect(@result.length).to eq 2
expect(@result[0][0]).to eq :L_BRACKET
expect(@result[1][0]).to eq :R_BRACKET
end
it 'tests bracket expression with spaces' do
@result = @evaluator.tokenize(' ( ) ')
expect(@result.length).to eq 2
expect(@result[0][0]).to eq :L_BRACKET
expect(@result[1][0]).to eq :R_BRACKET
end
it 'tests expression with OR operator' do
@result = @evaluator.tokenize('() or () OR ()')
expect(@result.length).to eq 8
expect(@result[0][0]).to eq :L_BRACKET
expect(@result[1][0]).to eq :R_BRACKET
expect(@result[2][0]).to eq :OPERATOR_OR
expect(@result[3][0]).to eq :L_BRACKET
expect(@result[4][0]).to eq :R_BRACKET
expect(@result[5][0]).to eq :OPERATOR_OR
expect(@result[6][0]).to eq :L_BRACKET
expect(@result[7][0]).to eq :R_BRACKET
end
it 'tests expression with AND operator' do
@result = @evaluator.tokenize('() AND () and ()')
expect(@result.length).to eq 8
expect(@result[0][0]).to eq :L_BRACKET
expect(@result[1][0]).to eq :R_BRACKET
expect(@result[2][0]).to eq :OPERATOR_AND
expect(@result[3][0]).to eq :L_BRACKET
expect(@result[4][0]).to eq :R_BRACKET
expect(@result[5][0]).to eq :OPERATOR_AND
expect(@result[6][0]).to eq :L_BRACKET
expect(@result[7][0]).to eq :R_BRACKET
end
it 'tests expression with NOT OR and NOT AND operator' do
@result = @evaluator.tokenize('() NOT or () not AND ()')
expect(@result.length).to eq 10
expect(@result[0][0]).to eq :L_BRACKET
expect(@result[1][0]).to eq :R_BRACKET
expect(@result[2][0]).to eq :OPERATOR_NOT
expect(@result[3][0]).to eq :OPERATOR_OR
expect(@result[4][0]).to eq :L_BRACKET
expect(@result[5][0]).to eq :R_BRACKET
expect(@result[6][0]).to eq :OPERATOR_NOT
expect(@result[7][0]).to eq :OPERATOR_AND
expect(@result[8][0]).to eq :L_BRACKET
expect(@result[9][0]).to eq :R_BRACKET
end
it 'tests search term under quotes' do
@result = @evaluator.tokenize('"123-456"')
expect(@result.length).to eq 1
expect(@result[0][0]).to eq :TERM_WITH_QUOTES
expect(@result[0][1]).to eq '"123-456"'
end
it 'tests term without quotes' do
@result = @evaluator.tokenize('device_id')
expect(@result.length).to eq 1
expect(@result[0][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[0][1]).to eq 'device_id'
end
it 'tests integer term without quotes' do
@result = @evaluator.tokenize('123')
expect(@result.length).to eq 1
expect(@result[0][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[0][1]).to eq '123'
end
it 'tests multiple terms without quotes' do
@result = @evaluator.tokenize('device_id tag 123-456 name123')
expect(@result.length).to eq 4
expect(@result[0][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[0][1]).to eq 'device_id'
expect(@result[1][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[1][1]).to eq 'tag'
expect(@result[2][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[2][1]).to eq '123-456'
expect(@result[3][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[3][1]).to eq 'name123'
end
it 'tests simple query with column name and search term without quotes' do
@result = @evaluator.tokenize('name:JF')
expect(@result.length).to eq 3
expect(@result[0][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[0][1]).to eq 'name'
expect(@result[1][0]).to eq :COLON
expect(@result[2][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[2][1]).to eq 'JF'
end
it 'tests simple query with two columns with name and search terms without quotes' do
@result = @evaluator.tokenize('name:JF tag:mta')
expect(@result.length).to eq 6
expect(@result[0][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[0][1]).to eq 'name'
expect(@result[1][0]).to eq :COLON
expect(@result[2][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[2][1]).to eq 'JF'
expect(@result[3][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[3][1]).to eq 'tag'
expect(@result[4][0]).to eq :COLON
expect(@result[5][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[5][1]).to eq 'mta'
end
it 'tests simple query with column name and search term with quotes' do
@result = @evaluator.tokenize('name:"name with space"')
expect(@result.length).to eq 3
expect(@result[0][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[0][1]).to eq 'name'
expect(@result[1][0]).to eq :COLON
expect(@result[2][0]).to eq :TERM_WITH_QUOTES
expect(@result[2][1]).to eq '"name with space"'
end
it 'tests search term with quotes containing non alphanumerical characters' do
@result = @evaluator.tokenize('"|*|/\()#-!=<>&$"')
expect(@result.length).to eq 1
expect(@result[0][0]).to eq :TERM_WITH_QUOTES
expect(@result[0][1]).to eq '"|*|/\()#-!=<>&$"'
end
it 'tests simple query in brackets' do
@result = @evaluator.tokenize('(name:"name with space")')
expect(@result.length).to eq 5
expect(@result[0][0]).to eq :L_BRACKET
expect(@result[1][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[1][1]).to eq 'name'
expect(@result[2][0]).to eq :COLON
expect(@result[3][0]).to eq :TERM_WITH_QUOTES
expect(@result[3][1]).to eq '"name with space"'
expect(@result[4][0]).to eq :R_BRACKET
end
it 'tests multiple query wtih brackets' do
@result = @evaluator.tokenize('(name:"name with space") or (tag:mta)')
expect(@result.length).to eq 11
expect(@result[0][0]).to eq :L_BRACKET
expect(@result[1][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[1][1]).to eq 'name'
expect(@result[2][0]).to eq :COLON
expect(@result[3][0]).to eq :TERM_WITH_QUOTES
expect(@result[3][1]).to eq '"name with space"'
expect(@result[4][0]).to eq :R_BRACKET
expect(@result[5][0]).to eq :OPERATOR_OR
expect(@result[6][0]).to eq :L_BRACKET
expect(@result[7][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[7][1]).to eq 'tag'
expect(@result[8][0]).to eq :COLON
expect(@result[9][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[9][1]).to eq 'mta'
expect(@result[10][0]).to eq :R_BRACKET
end
it 'tests complex query' do
@result = @evaluator.tokenize('(device-id:"with space" tag: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)')
expect(@result.length).to eq 27
expect(@result[0][0]).to eq :L_BRACKET
expect(@result[1][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[1][1]).to eq 'device-id'
expect(@result[2][0]).to eq :COLON
expect(@result[3][0]).to eq :TERM_WITH_QUOTES
expect(@result[3][1]).to eq '"with space"'
expect(@result[4][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[4][1]).to eq 'tag'
expect(@result[5][0]).to eq :COLON
expect(@result[6][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[6][1]).to eq 'mta'
expect(@result[7][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[7][1]).to eq 'no-quotes-id-123'
expect(@result[8][0]).to eq :R_BRACKET
expect(@result[9][0]).to eq :OPERATOR_OR
expect(@result[10][0]).to eq :TERM_WITH_QUOTES
expect(@result[10][1]).to eq '"id with quotes-5"'
expect(@result[11][0]).to eq :OPERATOR_AND
expect(@result[12][0]).to eq :L_BRACKET
expect(@result[13][0]).to eq :L_BRACKET
expect(@result[14][0]).to eq :TERM_WITH_QUOTES
expect(@result[14][1]).to eq '"id with q 10"'
expect(@result[15][0]).to eq :OPERATOR_OR
expect(@result[16][0]).to eq :TERM_WITH_QUOTES
expect(@result[16][1]).to eq '"id with q 20"'
expect(@result[17][0]).to eq :R_BRACKET
expect(@result[18][0]).to eq :OPERATOR_AND
expect(@result[19][0]).to eq :L_BRACKET
expect(@result[20][0]).to eq :TERM_WITH_QUOTES
expect(@result[20][1]).to eq '"id with Q 30"'
expect(@result[21][0]).to eq :TERM_WITH_QUOTES
expect(@result[21][1]).to eq '"id with Q 40"'
expect(@result[22][0]).to eq :R_BRACKET
expect(@result[23][0]).to eq :OPERATOR_AND
expect(@result[24][0]).to eq :OPERATOR_NOT
expect(@result[25][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[25][1]).to eq 'id-without-Q-50'
expect(@result[26][0]).to eq :R_BRACKET
end
it 'tests query with -or-, -and- and -not- words inside quoted expression' do
@result = @evaluator.tokenize('tag:"tag with or and not inside"')
expect(@result.length).to eq 3
expect(@result[0][0]).to eq :TERM_WITHOUT_QUOTES
expect(@result[0][1]).to eq 'tag'
expect(@result[1][0]).to eq :COLON
expect(@result[2][0]).to eq :TERM_WITH_QUOTES
expect(@result[2][1]).to eq '"tag with or and not inside"'
end
end
end

View File

@@ -0,0 +1,164 @@
require './parser'
class QueryParserTester
describe 'Testing the Parser' do
before do
@evaluator = Query.new
end
it 'tests query with only one search term without quotes and without column name' do
@result = @evaluator.parse('-123')
expect(@result[:DEFAULT_COLUMN]).to eq '-123'
end
it 'tests query with only one search term with quotes and without column name' do
@result = @evaluator.parse('"OR 128"')
expect(@result[:DEFAULT_COLUMN]).to eq '"OR 128"'
end
it 'tests query with one column and search term without quotes' do
@result = @evaluator.parse('tag:mta')
expect(@result['tag']).to eq 'mta'
end
it 'tests query with one column and search term with quotes' do
@result = @evaluator.parse('tag:"tag 120"')
expect(@result['tag']).to eq '"tag 120"'
end
it 'tests query with two columns connected with OR and search terms without quotes' do
@result = @evaluator.parse('tag:mta OR tag:12')
@expected_array = [
{ 'tag' => 'mta' },
{ 'tag' => '12' }
]
expect(@result.count).to eq 1
expect(@result[:OPERATOR_OR]).to eq @expected_array
end
it 'tests query with two columns connected with OR and search terms with quotes' do
@result = @evaluator.parse('tag:mta OR tag:"tag 12"')
@expected_array = [
{ 'tag' => 'mta' },
{ 'tag' => '"tag 12"' }
]
expect(@result.count).to eq 1
expect(@result[:OPERATOR_OR]).to eq @expected_array
end
it 'tests query with two columns connected with AND and search terms without quotes' do
@result = @evaluator.parse('tag:mta AND tag:12')
@expected_array = [
{ 'tag' => 'mta' },
{ 'tag' => '12' }
]
expect(@result.count).to eq 1
expect(@result[:OPERATOR_AND]).to eq @expected_array
end
it 'tests query with two columns connected with AND and search terms with quotes' do
@result = @evaluator.parse('tag:mta and tag:"tag 12"')
@expected_array = [
{ 'tag' => 'mta' },
{ 'tag' => '"tag 12"' }
]
expect(@result.count).to eq 1
expect(@result[:OPERATOR_AND]).to eq @expected_array
end
it 'tests simple query with brackets' do
@result = @evaluator.parse('(123)')
expect(@result.count).to eq 1
expect(@result[:DEFAULT_COLUMN]).to eq '123'
end
it 'tests simple query with brackets and with a column name' do
@result = @evaluator.parse('(name:JF)')
expect(@result.count).to eq 1
expect(@result['name']).to eq 'JF'
end
it 'tests query with OR operator in brackets' do
@result = @evaluator.parse('(name:JF or tag:mta)')
@expected_array = [
{ 'name' => 'JF' },
{ 'tag' => 'mta' }
]
expect(@result.count).to eq 1
expect(@result[:OPERATOR_OR]).to eq @expected_array
end
it 'tests query with two simple brackets expressions' do
@result = @evaluator.parse('(name:JF) and (-456)')
@expected_array = [
{ 'name' => 'JF' },
{ :DEFAULT_COLUMN => '-456' }
]
expect(@result.count).to eq 1
expect(@result[:OPERATOR_AND]).to eq @expected_array
end
it 'tests query with two brackets expressions' do
@result = @evaluator.parse('(name:JF or tag:"tag 0") and (-456)')
@expected_array_part_1 = [
{ 'name' => 'JF' },
{ 'tag' => '"tag 0"' }
]
@expected_array_total = [
{:OPERATOR_OR => @expected_array_part_1},
{:DEFAULT_COLUMN => '-456'}
]
expect(@result.count).to eq 1
expect(@result[:OPERATOR_AND]).to eq @expected_array_total
end
it 'tests operator precedence' do
@result1 = @evaluator.parse('tag:mta or name:JF and 12_4')
@result2 = @evaluator.parse('tag:mta or (name:JF and 12_4)')
expect(@result1).to eq @result2
expect(@result1.length).to eq 1
@expected_array_part_2 = [
{'name' => 'JF'},
{:DEFAULT_COLUMN => '12_4'}
]
@expected_array_total = [
{'tag' => 'mta'},
{:OPERATOR_AND => @expected_array_part_2}
]
expect(@result1[:OPERATOR_OR]).to eq @expected_array_total
end
# Tests to write :
# * query with multiple column names and search terms without logical operators
# * AND NOT, OR NOT tests
end
end

View File

@@ -0,0 +1,22 @@
# frozen_string_literal: true
describe TextToRegexQuery do
include_examples 'pg_search', SimpleVectorModel
describe '.new' do
# just default
it { expect(described_class.new('some-default-value', [:"players.name"], :"players.name").where_clause(SimpleVectorModel)).to eq([' players.name like ? OR 1<>1', '%some-default-value%']) }
# default and named
it { expect(described_class.new('name:hamo id:1', [:"players.name"], :"players.name").where_clause(SimpleVectorModel)).to eq([' players.name like ? OR players.name like ? OR 1<>1', '%id:1%', '%hamo%']) }
# escape characters
it { expect(described_class.new('name:hamo id:1',[:"players.name"], :"players.name").where_clause(SimpleVectorModel)).to eq([' players.name like ? OR players.name like ? OR 1<>1', '%id:1%', '%hamo%']) }
# default and explicit with underscore
it { expect(described_class.new('device_id:-123', [:"players.name", :"players.device_id"], :"players.device_id").where_clause(SimpleVectorModel)).to eq([" players.device_id like ? OR 1<>1", "%-123%"]) }
# default and explicit with underscore with multiple columns
it { expect(described_class.new('name:bla device_id:-123', [:"players.name", :"players.device_id"], :"players.device_id").where_clause(SimpleVectorModel)).to eq([" players.name like ? OR players.device_id like ? OR 1<>1", "%bla%", "%-123%"]) }
end
end

View File

@@ -0,0 +1,80 @@
# frozen_string_literal: true
describe TextToTsquery do
describe '.new' do
# partial match
it { expect(described_class.new('A').tsquery).to eq('A:*') }
it { expect(described_class.new('A', wildcard: false).tsquery).to eq('A:') }
it { expect(described_class.new(' A ').tsquery).to eq('A:*') }
# AND operations
it { expect(described_class.new('A B').tsquery).to eq('A:*&B:*') }
it { expect(described_class.new('A B C').tsquery).to eq('A:*&B:*&C:*') }
it { expect(described_class.new('A and B').tsquery).to eq('A:*&B:*') }
it { expect(described_class.new('A AND B').tsquery).to eq('A:*&B:*') }
it { expect(described_class.new('A & B').tsquery).to eq('A:*&B:*') }
it { expect(described_class.new('A && B').tsquery).to eq('A:*&B:*') }
it { expect(described_class.new('A & B && C and D AND E F').tsquery).to eq('A:*&B:*&C:*&D:*&E:*&F:*') }
# OR operations
it { expect(described_class.new('A or B').tsquery).to eq('A:*|B:*') }
it { expect(described_class.new('A or B', wildcard: false).tsquery).to eq('A:|B:') }
it { expect(described_class.new('A OR B').tsquery).to eq('A:*|B:*') }
it { expect(described_class.new('A OR B', wildcard: false).tsquery).to eq('A:|B:') }
it { expect(described_class.new('A | B').tsquery).to eq('A:*|B:*') }
it { expect(described_class.new('A | B', wildcard: false).tsquery).to eq('A:|B:') }
it { expect(described_class.new('A || B').tsquery).to eq('A:*|B:*') }
it { expect(described_class.new('A || B', wildcard: false).tsquery).to eq('A:|B:') }
it { expect(described_class.new('A or or B').tsquery).to eq('A:*|B:*') }
it { expect(described_class.new('A or or B', wildcard: false).tsquery).to eq('A:|B:') }
it { expect(described_class.new('A | B || C or D OR E').tsquery).to eq('A:*|B:*|C:*|D:*|E:*') }
it { expect(described_class.new('A | B || C or D OR E', wildcard: false).tsquery).to eq('A:|B:|C:|D:|E:') }
# () Precedence
it { expect(described_class.new('(A)').tsquery).to eq('(A:*)') }
it { expect(described_class.new('(A)', wildcard: false).tsquery).to eq('(A:)') }
it { expect(described_class.new('(A B)').tsquery).to eq('(A:*&B:*)') }
it { expect(described_class.new('(A B)', wildcard: false).tsquery).to eq('(A:&B:)') }
it { expect(described_class.new('A (B !C)').tsquery).to eq('A:*&(B:*&!C)') }
it { expect(described_class.new('A (B !C)', wildcard: false).tsquery).to eq('A:&(B:&!C)') }
it { expect(described_class.new('(A AND B) OR C').tsquery).to eq('(A:*&B:*)|C:*') }
it { expect(described_class.new('(A AND B) OR C', wildcard: false).tsquery).to eq('(A:&B:)|C:') }
it { expect(described_class.new('A AND (B OR C)').tsquery).to eq('A:*&(B:*|C:*)') }
it { expect(described_class.new('A AND (B OR C)', wildcard: false).tsquery).to eq('A:&(B:|C:)') }
it { expect(described_class.new('(A & B) || C').tsquery).to eq('(A:*&B:*)|C:*') }
it { expect(described_class.new('(A & B) || C', wildcard: false).tsquery).to eq('(A:&B:)|C:') }
it { expect(described_class.new('A && (B | C)').tsquery).to eq('A:*&(B:*|C:*)') }
it { expect(described_class.new('A && (B | C)', wildcard: false).tsquery).to eq('A:&(B:|C:)') }
it { expect(described_class.new('A && !D (B | C | !E)').tsquery).to eq('A:*&!D&(B:*|C:*|!E)') }
it { expect(described_class.new('A && !D (B | C | !E)', wildcard: false).tsquery).to eq('A:&!D&(B:|C:|!E)') }
# Exact Matches
it { expect(described_class.new('"A"').tsquery).to eq("'A'") }
it { expect(described_class.new('"A B"').tsquery).to eq("'A B'") }
it { expect(described_class.new('"A&B"').tsquery).to eq("'A&B'") }
it { expect(described_class.new('"-A|B"').tsquery).to eq("'-A|B'") }
it { expect(described_class.new('"A-B"').tsquery).to eq("'A-B'") }
it { expect(described_class.new('"A" B').tsquery).to eq("'A'&B:*") }
it { expect(described_class.new('"A" B', wildcard: false).tsquery).to eq("'A'&B:") }
it { expect(described_class.new('"A B" C').tsquery).to eq("'A B'&C:*") }
it { expect(described_class.new('"A B" C', wildcard: false).tsquery).to eq("'A B'&C:") }
it { expect(described_class.new('("A B" or C) and D').tsquery).to eq("('A B'|C:*)&D:*") }
it { expect(described_class.new('("A B" or C) and D', wildcard: false).tsquery).to eq("('A B'|C:)&D:") }
describe 'validations' do
it { expect { described_class.new('(') }.to raise_error(ArgumentError, /parenthesis/) }
end
end
describe '.valid_search_parenthesis?' do
it { expect(described_class.valid_search_parenthesis?('')).to eq true }
it { expect(described_class.valid_search_parenthesis?('()')).to eq true }
it { expect(described_class.valid_search_parenthesis?('()()')).to eq true }
it { expect(described_class.valid_search_parenthesis?('(()())')).to eq true }
it { expect(described_class.valid_search_parenthesis?('((())())')).to eq true }
it { expect(described_class.valid_search_parenthesis?('(')).to eq false }
it { expect(described_class.valid_search_parenthesis?(')(')).to eq false }
it { expect(described_class.valid_search_parenthesis?('())')).to eq false }
it { expect(described_class.valid_search_parenthesis?('((()())')).to eq false }
end
end