diff --git a/coverage/.last_run.json b/coverage/.last_run.json
index ec12727..8afec4c 100644
--- a/coverage/.last_run.json
+++ b/coverage/.last_run.json
@@ -1,5 +1,5 @@
{
"result": {
- "covered_percent": 66.92
+ "covered_percent": 78.1
}
}
diff --git a/coverage/.resultset.json b/coverage/.resultset.json
index ef5ab35..cb0bf64 100644
--- a/coverage/.resultset.json
+++ b/coverage/.resultset.json
@@ -9,69 +9,73 @@
1,
1,
null,
- 0,
- 0,
+ 1,
+ 1,
null,
- 0,
+ 1,
+ 13,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 1,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 13,
+ 13,
+ 13,
+ 13,
+ 13,
+ 13,
+ 13,
+ 13,
+ 13,
+ 13,
+ null,
+ null,
+ 1,
+ 13,
+ 13,
+ 24,
+ 23,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 24,
+ null,
+ 24,
+ null,
+ null,
+ 1,
0,
null,
null,
+ 1,
+ 16,
null,
null,
+ 1,
+ 13,
null,
- 0,
+ null,
+ 1,
0,
null,
null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- null,
- null,
- 0,
- 0,
- 0,
- null,
- null,
- null,
- 0,
- 0,
- 0,
- null,
- null,
- 0,
- 0,
- null,
- null,
- 0,
- 0,
- null,
- null,
- 0,
- 0,
- null,
- null,
- 0,
- 0,
- null,
- null,
- 0,
+ 1,
0,
0,
0,
@@ -181,57 +185,121 @@
null
],
"/home/hamo/projects/toptal/outfrontmedia/pg_searchable/lib/text_to_sql_query.rb": [
+ 1,
null,
+ 1,
+ 1,
+ 44,
+ 44,
+ 44,
+ 44,
+ 114,
+ 114,
+ 114,
null,
+ 44,
+ 13,
null,
null,
null,
+ 1,
+ 44,
+ 44,
+ 44,
null,
null,
+ 1,
null,
+ 1,
+ 128,
+ 128,
+ 128,
null,
+ 23,
+ 23,
null,
+ 22,
null,
+ 15,
null,
+ 10,
null,
+ 10,
+ 0,
null,
null,
+ 10,
+ 10,
null,
+ 10,
null,
null,
null,
+ 58,
+ 58,
+ 58,
+ 2,
null,
+ 56,
null,
null,
null,
null,
+ 1,
+ 37,
+ 0,
null,
null,
+ 37,
+ 37,
null,
+ 37,
+ 0,
null,
null,
+ 37,
+ 0,
null,
null,
+ 37,
+ 37,
null,
+ 37,
+ 37,
null,
+ 37,
null,
null,
+ 1,
+ 81,
+ 81,
+ 81,
+ 81,
+ 81,
null,
+ null
+ ],
+ "/home/hamo/projects/toptal/outfrontmedia/pg_searchable/lib/parser.rb": [
null,
null,
null,
null,
null,
null,
+ 1,
null,
+ 1,
null,
+ 1,
null,
+ 1,
null,
null,
null,
null,
null,
null,
+ 1,
null,
null,
null,
@@ -240,6 +308,7 @@
null,
null,
null,
+ 1,
null,
null,
null,
@@ -248,22 +317,29 @@
null,
null,
null,
+ 1,
null,
null,
null,
+ 1,
null,
null,
null,
+ 1,
null,
null,
null,
+ 1,
null,
null,
null,
+ 1,
null,
null,
+ 1,
null,
null,
+ 1,
null,
null,
null,
@@ -273,9 +349,255 @@
null,
null,
null,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ null,
+ 1,
+ null,
+ 1,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ null,
+ 1,
+ null,
+ 1,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0,
+ null,
+ null,
+ null
+ ],
+ "/home/hamo/projects/toptal/outfrontmedia/pg_searchable/lib/lexer.rb": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 1,
+ 1,
+ null,
+ 1,
+ null,
+ 1,
+ 1,
+ 1,
+ null,
+ 1,
+ 132,
+ 132,
+ 132,
+ null,
+ null,
+ 1,
+ 702,
+ null,
+ null,
+ 1,
+ 94,
+ 94,
+ null,
+ 1,
+ null,
+ 1,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ 1,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ 1,
+ 834,
+ null,
+ null,
+ 703,
+ 703,
+ null,
+ null,
+ 1,
+ 939,
+ 939,
+ 939,
+ null,
+ null,
+ 939,
+ null,
+ null,
+ 702,
+ 106,
+ null,
+ 649,
+ 106,
+ null,
+ 596,
+ 84,
+ null,
+ 554,
+ 84,
+ null,
+ 512,
+ 46,
+ null,
+ 489,
+ 132,
+ null,
+ 423,
+ 610,
+ null,
+ 118,
+ 236,
+ null,
+ null,
+ null,
+ 0,
+ 0,
+ 939,
+ null,
+ null,
+ 0,
+ null,
+ 237,
+ null,
+ null,
+ 1,
+ 38,
+ 38,
+ 38,
+ 174,
+ null,
+ 38,
+ null,
null
]
},
- "timestamp": 1580827025
+ "timestamp": 1583928139
}
}
diff --git a/coverage/index.html b/coverage/index.html
index 55faeff..5828793 100644
--- a/coverage/index.html
+++ b/coverage/index.html
@@ -1,7 +1,7 @@
- Code coverage for Gem
+ Code coverage for Pg searchable
@@ -14,27 +14,27 @@
-
Generated
2020-02-11T11:17:04+01:00
+
Generated
2020-03-11T13:02:19+01:00
All Files
- (74.27%
+ (78.1%
covered at
- 15.07
+ 69.62
hits/line)
5 files in total.
- 241 relevant lines.
- 179 lines covered and
- 62 lines missed
+ 242 relevant lines.
+ 189 lines covered and
+ 53 lines missed
@@ -51,17 +51,17 @@
- | lib/lexer.rb |
- 78.33 % |
+ lib/lexer.rb |
+ 86.67 % |
107 |
60 |
- 47 |
- 13 |
- 38.4 |
+ 52 |
+ 8 |
+ 237.5 |
- | lib/parser.rb |
+ lib/parser.rb |
96.97 % |
207 |
33 |
@@ -71,32 +71,32 @@
- | lib/pg_searchable_regex.rb |
- 78.95 % |
- 77 |
- 38 |
- 30 |
- 8 |
- 5.2 |
+ lib/pg_searchable_regex.rb |
+ 87.5 % |
+ 80 |
+ 40 |
+ 35 |
+ 5 |
+ 7.7 |
- | lib/text_to_sql_query.rb |
+ lib/text_to_sql_query.rb |
92.86 % |
93 |
56 |
52 |
4 |
- 19.3 |
+ 40.0 |
- | lib/text_to_tsquery.rb |
- 33.33 % |
+ lib/text_to_tsquery.rb |
+ 33.96 % |
99 |
- 54 |
+ 53 |
18 |
- 36 |
+ 35 |
0.3 |
@@ -116,14 +116,14 @@
-
+
@@ -232,20 +232,20 @@
def scan_setup(str)
-
- 20
+
+ 132
@ss = StringScanner.new(str)
-
- 20
+
+ 132
@lineno = 1
-
- 20
+
+ 132
@state = nil
@@ -268,8 +268,8 @@
def action
-
- 113
+
+ 702
yield
@@ -292,14 +292,14 @@
def scan_str(str)
-
- 20
+
+ 94
scan_setup(str)
-
- 20
+
+ 94
do_parse
@@ -406,8 +406,8 @@
def next_token
-
- 133
+
+ 834
return if @ss.eos?
@@ -424,14 +424,14 @@
# skips empty actions
-
- 113
+
+ 703
until token = _next_token or @ss.eos?; end
-
- 113
+
+ 703
token
@@ -454,20 +454,20 @@
def _next_token
-
- 155
+
+ 939
text = @ss.peek(1)
-
- 155
+
+ 939
@lineno += 1 if text == "\n"
-
- 155
+
+ 939
token = case @state
@@ -484,8 +484,8 @@
case
-
- 155
+
+ 939
when (text = @ss.scan(/ +/))
@@ -502,14 +502,14 @@
-
- 113
+
+ 702
when (text = @ss.scan(/\(/))
-
- 12
+
+ 106
action { return [:L_BRACKET, text] }
@@ -520,14 +520,14 @@
-
- 107
+
+ 649
when (text = @ss.scan(/\)/))
-
- 12
+
+ 106
action { return [:R_BRACKET, text] }
@@ -538,14 +538,14 @@
-
- 101
+
+ 596
when (text = @ss.scan(/(?i)\bor\b/))
-
- 14
+
+ 84
action { return [:OPERATOR_OR, text] }
@@ -556,14 +556,14 @@
-
- 94
+
+ 554
when (text = @ss.scan(/(?i)\band\b/))
-
- 16
+
+ 84
action { return [:OPERATOR_AND, text] }
@@ -574,14 +574,14 @@
-
- 86
+
+ 512
when (text = @ss.scan(/(?i)\bnot\b/))
-
- 10
+
+ 46
action { return [:OPERATOR_NOT, text] }
@@ -592,14 +592,14 @@
-
- 81
+
+ 489
when (text = @ss.scan(/"([^"]*)"/))
-
- 18
+
+ 132
action { return [:TERM_WITH_QUOTES, text] }
@@ -610,14 +610,14 @@
-
- 72
+
+ 423
when (text = @ss.scan(/[a-zA-Z0-9\-_]+/))
-
- 104
+
+ 610
action { return [:TERM_WITHOUT_QUOTES, text] }
@@ -628,14 +628,14 @@
-
- 20
+
+ 118
when (text = @ss.scan(/\:/))
-
- 40
+
+ 236
action { return [:COLON, text] }
@@ -670,8 +670,8 @@
raise ScanError, "can not match: '" + text + "'"
-
- 155
+
+ 939
end # if
@@ -700,8 +700,8 @@
end # case state
-
- 42
+
+ 237
token
@@ -724,26 +724,26 @@
def tokenize(code)
-
-
+
+ 38
scan_setup(code)
-
-
+
+ 38
tokens = []
-
-
+
+ 38
while token = next_token
-
-
+
+ 174
tokens << token
@@ -754,8 +754,8 @@
end
-
-
+
+ 38
tokens
@@ -777,7 +777,7 @@
-
+
-
+
@@ -2319,196 +2319,214 @@
13
- scope ts_scope_method, ->(value) { ts_search(value) }
+ scope ts_scope_method, ->(value) do
-
+
+ 24
-
- end
+ resulting_ids = ts_search(value).map(&:id)
-
+
+ 23
-
- end
+ where(id: resulting_ids)
+ end
+
+
+
+
+
+ end
+
+
+
+
+
+ end
+
+
+
+
+
-
+
1
def ts_search(value)
-
-
+
+ 24
return if @ts_search_fields.blank? || value.blank?
-
-
-
- includes(@ts_joins).references(:all).where(
-
-
-
-
-
- TextToSqlQuery.new(value, @ts_search_fields, @default_field, @ts_search_fields_mappings).where_clause)
-
-
-
-
-
- end
-
-
-
+ includes(@ts_joins).references(:all).where(
-
- 1
+
+ 24
- def should_update_cache_field?
+ TextToSqlQuery.new(value, @ts_search_fields, @default_field, @ts_search_fields_mappings).where_clause).distinct
-
+
- !@ts_skip_cache_update && @ts_cache_field.present?
+ end
- end
-
-
-
-
-
-
+
1
- def ts_cache_field
+ def should_update_cache_field?
-
+
- @ts_cache_field
+ !@ts_skip_cache_update && @ts_cache_field.present?
+
+
+
+
+
+ end
- end
-
-
-
-
-
-
+
1
- def ts_scope_method
+ def ts_cache_field
-
- 13
+
+ 16
- @ts_scope_method
+ @ts_cache_field
+
+
+
+
+
+ end
- end
-
-
-
-
-
-
+
1
- def ts_cache_method
+ def ts_scope_method
-
+
+ 13
+
+ @ts_scope_method
+
+
+
- @ts_cache_method
+ end
+
+
+
+
+ 1
+
+ def ts_cache_method
+
+
+
+
+
+ @ts_cache_method
+
+
+
+
+
end
-
+
-
+
1
def ts_fields_to_vector(extra_data = [])
-
+
field_to_vector = ->(field) { "to_tsvector('#{@ts_language}', coalesce(#{field}::text, ''))" }
-
+
data_to_vector = ->(data) { "to_tsvector('#{@ts_language}', '#{data}')" }
-
+
(@ts_search_fields.map(&field_to_vector) + extra_data.map(&data_to_vector)).join(' || ')
-
+
end
-
+
end
-
+
end
@@ -2519,7 +2537,7 @@
-
+
-
+
@@ -3356,7 +3374,7 @@
acc + 1
-
+
elsif char == ')'
diff --git a/lib/pg_searchable_regex.rb b/lib/pg_searchable_regex.rb
index dc1b18d..8c7374b 100644
--- a/lib/pg_searchable_regex.rb
+++ b/lib/pg_searchable_regex.rb
@@ -42,14 +42,17 @@ module PgSearchable
def ts_add_scope
class_eval do
- scope ts_scope_method, ->(value) { ts_search(value) }
+ scope ts_scope_method, ->(value) do
+ resulting_ids = ts_search(value).map(&:id)
+ where(id: resulting_ids)
+ end
end
end
def ts_search(value)
return if @ts_search_fields.blank? || value.blank?
includes(@ts_joins).references(:all).where(
- TextToSqlQuery.new(value, @ts_search_fields, @default_field, @ts_search_fields_mappings).where_clause)
+ TextToSqlQuery.new(value, @ts_search_fields, @default_field, @ts_search_fields_mappings).where_clause).distinct
end
def should_update_cache_field?
diff --git a/lib/text_to_regex_query.rb b/lib/text_to_regex_query.rb
deleted file mode 100644
index 7b7c569..0000000
--- a/lib/text_to_regex_query.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-# frozen_string_literal: true
-
-# transforms "english like" text queries into a where clause with regex
-# https://www.postgresql.org/docs/9.5/textsearch-controls.html#TEXTSEARCH-PARSING-QUERIES
-
-class TextToRegexQuery
- def initialize(text, fields, default_field, fields_mappings = {})
- @text = text.to_s.strip
- @fields = fields.map(&:to_sym)
- @default_field = default_field.to_sym
- @fields_mappings = fields_mappings.merge(@fields.reduce({}) do |mappings, field|
- table_name, field_name = field.to_s.split(".")
- mappings[field_name.to_sym] = field
- mappings
- end)
- end
-
- def where_clause(query)
- @cleared_text = @text.dup
- @column_chunks = []
- remove_duplicated_spaces
- extract_columns
- escape_special_characters
- generate_where_clause(query)
- end
-
-private
-
- def remove_duplicated_spaces
- @cleared_text.gsub!(/\s+/, ' ')
- end
-
- def escape_special_characters
- @cleared_text.gsub!(/\_/, '\_')
- @cleared_text.tr!('\\', '\\')
- @cleared_text.gsub!(/%/, '\%')
- end
-
- def extract_columns
- column_search_term_pairs = @cleared_text.scan(/([A-Za-z0-9_]+:[\w\_-]+)/)
-
- @column_chunks = (column_search_term_pairs.flatten.map do |pair|
- column, term = pair.split(':')
- next unless @fields_mappings.include?(column.to_sym)
- @cleared_text.gsub!(pair, '')
- { @fields_mappings[column.to_sym] => term }
- end).compact
- unless @cleared_text.strip.empty?
- @column_chunks = [{ @default_field.to_s => @cleared_text.strip }] + @column_chunks
- end
- @column_chunks
- end
-
- def generate_where_clause(query)
- where_clause = ''
- columns = @column_chunks.map { |c| c.keys.first }
- values = @column_chunks.map { |c| c.values.first }
-
- columns.each do |column|
- quoted_column = '"' + column.to_s.gsub(".",'"."') + '"'
- where_clause += "#{quoted_column} ILIKE ? OR "
- end
- where_clause += " 1<>1 "
- regexed_values = values.map { |v| "%#{v}%" }
- query.where([where_clause] + regexed_values)
- end
-end
diff --git a/spec/lib/pg_searchable_new_spec.rb b/spec/lib/pg_searchable_new_spec.rb
index 1df925a..2bf7fd6 100644
--- a/spec/lib/pg_searchable_new_spec.rb
+++ b/spec/lib/pg_searchable_new_spec.rb
@@ -136,6 +136,16 @@ describe PgSearchable do
expect(DynamicModelWithTagValues.scope_search('tag:red value:"not"')).to contain_exactly(record1, record2)
end
+ it 'can find models without tags' do
+ record1 = DynamicModelWithTagValues.create name: 'something', value: 'amazing'
+ record2 = DynamicModelWithTagValues.create name: 'new record', value: 'not so amazing'
+ Tag.create(taggable: record1, value: 'green')
+
+ expect(DynamicModelWithTagValues.scope_search('tag:green or value:"not"')).to contain_exactly(record1, record2)
+ end
+
+
+
it 'can search in referenced column and in model columns with multiple search terms connected with logical operators' do
record1 = DynamicModelWithTagValues.create name: 'something', value: 'amazing'
record2 = DynamicModelWithTagValues.create name: 'new record', value: 'not so amazing'
diff --git a/spec/lib/pg_searchable_spec.rb b/spec/lib/pg_searchable_spec.rb
deleted file mode 100644
index 08c67ff..0000000
--- a/spec/lib/pg_searchable_spec.rb
+++ /dev/null
@@ -1,116 +0,0 @@
-# frozen_string_literal: true
-require_relative '../../lib/pg_searchable_regex'
-
-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
diff --git a/spec/lib/text_to_tsquery_spec.rb b/spec/lib/text_to_tsquery_spec.rb
deleted file mode 100644
index 99e396a..0000000
--- a/spec/lib/text_to_tsquery_spec.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-# frozen_string_literal: true
-require_relative '../../lib/text_to_tsquery'
-
-
-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
diff --git a/version.rb b/version.rb
index f0966b2..7a7c437 100644
--- a/version.rb
+++ b/version.rb
@@ -3,7 +3,7 @@
module Version
MAJOR = 1
MINOR = 0
- PATCH = 24
+ PATCH = 27
def self.to_s
[MAJOR, MINOR, PATCH].compact.join('.')