qiita-elasticsearch 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c85be5babc5ed5cefb8af6487087d0612347eaee
4
- data.tar.gz: 73a5134cf3385b9e1616faebb4cace348b46313a
3
+ metadata.gz: 3fc76e99680f3ae5cf6f574778cf5ca6478784bc
4
+ data.tar.gz: ab714fac5fbc1dda9b07ff2ee94244941f6525ba
5
5
  SHA512:
6
- metadata.gz: 12222f5696ef38b2afd9b27a0959b965da581deba85bc6b3272cf47d344daf9afa6c2b0fa20b4f9b8b9e82691838a06f4cc8a0805668650a8b8ee16e32bfb922
7
- data.tar.gz: 45d3786363ac701876d52f74679611e028e7a1ad1276f74fa0aca2a94226ff11976cb113627bf602c6854b7f2f2d4fbdbee120958fae1ed6fcc802a33d204abb
6
+ metadata.gz: 0f0419cf79eb3a511d2fbae7ce22cae22dd919abf45c15da5a11a9c4d64c2d5ccd4a219c78647d5512e9d26131d0946af80f87a508fcf35fd89ce97e4b371fb3
7
+ data.tar.gz: 525def987114b9e7567d0403d44b52a7164782e34ecc318ba2534818ef9a558f0679e400842e50180235eba773d98f189c7546481948cab0ab366cb03f255ce3
data/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ ## 0.2.0
2
+ - Use filtered query for negative or filter tokens
3
+
1
4
  ## 0.1.4
2
5
  - Use downcased term for term and prefix on filtered query
3
6
 
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
  Elasticsearch client helper for Qiita.
3
3
 
4
4
  ## Usage
5
+ `Qiita::Elasticsearch::QueryBuilder` builds Elasticsearch query from query string.
6
+
5
7
  ```rb
6
8
  query_builder = Qiita::Elasticsearch::QueryBuilder.new
7
9
  #=> #<Qiita::Elasticsearch::QueryBuilder:0x007ff81e67c4d8 @filterable_fields=nil, @matchable_fields=nil>
@@ -20,10 +22,11 @@ query_builder.build('"a b"')
20
22
 
21
23
  query_builder.build("a OR b")
22
24
  #=> {"bool"=>{"should"=>[{"match"=>{"_all"=>"a"}}, {"match"=>{"_all"=>"b"}}]}}
23
-
24
25
  ```
25
26
 
26
27
  ### matchable_fields
28
+ Pass `:matchable_fields` option to tell matchable field names (default: `_all`).
29
+
27
30
  ```rb
28
31
  query_builder = Qiita::Elasticsearch::QueryBuilder.new(matchable_fields: ["body", "title"])
29
32
  #=> #<Qiita::Elasticsearch::QueryBuilder:0x007ff81fa59168 @filterable_fields=nil, @matchable_fields=["body", "title"]>
@@ -33,16 +36,30 @@ query_builder.build("a")
33
36
  ```
34
37
 
35
38
  ### filterable_fields
39
+ Pass `:filterable_fields` option to enable filtered queries like `tag:Ruby`.
40
+
36
41
  ```rb
37
42
  query_builder = Qiita::Elasticsearch::QueryBuilder.new(filterable_fields: ["tag", "title"])
38
43
  #=> #<Qiita::Elasticsearch::QueryBuilder:0x007ff81e2de1f0 @filterable_fields=["tag", "title"], @matchable_fields=nil>
39
44
 
40
45
  query_builder.build("tag:a")
41
- #=> {"filtered"=>{"filter"=>{"term"=>{"tag"=>"a"}}, "query"=>{"match_all"=>{}}}}
46
+ #=> {"filtered"=>{"filter"=>{"term"=>{"tag"=>"a"}}}}
42
47
 
43
48
  query_builder.build("tag:a b")
44
- #=> {"bool"=>{"must"=>[{"filtered"=>{"filter"=>{"term"=>{"tag"=>"a"}}, "query"=>{"match_all"=>{}}}}], "should"=>[{"match"=>{"_all"=>"b"}}]}}
49
+ #=> {"bool"=>{"must"=>[{"filtered"=>{"filter"=>{"term"=>{"tag"=>"a"}}}}], "should"=>[{"match"=>{"_all"=>"b"}}]}}
45
50
 
46
51
  query_builder.build("user:a b")
47
52
  #=> {"bool"=>{"should"=>[{"match"=>{"_all"=>"user:a"}}, {"match"=>{"_all"=>"b"}}]}}
48
53
  ```
54
+
55
+ ### hierarchal_fields
56
+ Pass `:hierarchal_fields` options with `:matchable_fields` to enable prefixed filtered queries.
57
+ With this option, `tag:foo` will hit documents tagged with `foo`, or `foo/...`.
58
+
59
+ ```rb
60
+ query_builder = Qiita::Elasticsearch::QueryBuilder.new(filterable_fields: ["tag"], hierarchal_fields: ["tag"])
61
+ => #<Qiita::Elasticsearch::QueryBuilder:0x007fe96d6d5ed0 @filterable_fields=["tag"], @hierarchal_fields=["tag"], @matchable_fields=nil>
62
+
63
+ query_builder.build("tag:ruby")
64
+ => {"filtered"=>{"filter"=>{"bool"=>{"should"=>[{"prefix"=>{"tag"=>"ruby/"}}, {"term"=>{"tag"=>"ruby"}}]}}}}
65
+ ```
@@ -0,0 +1,73 @@
1
+ require "qiita/elasticsearch/nodes/match_node"
2
+ require "qiita/elasticsearch/nodes/term_node"
3
+
4
+ module Qiita
5
+ module Elasticsearch
6
+ module Nodes
7
+ class FilterNode
8
+ # @param [Array<Qiita::Elasticsearch::Tokens>] tokens
9
+ # @param [Array<String>, nil] hierarchal_fields
10
+ # @param [Array<String>, nil] matchable_fields
11
+ def initialize(tokens, hierarchal_fields: nil, matchable_fields: nil)
12
+ @hierarchal_fields = hierarchal_fields
13
+ @matchable_fields = matchable_fields
14
+ @tokens = tokens
15
+ end
16
+
17
+ def to_hash
18
+ if must_not_tokens.empty? && must_tokens.length == 1
19
+ TermNode.new(
20
+ must_tokens.first,
21
+ hierarchal_fields: @hierarchal_fields,
22
+ ).to_hash
23
+ else
24
+ {
25
+ "_cache" => true,
26
+ "bool" => {
27
+ "must" => must_queries,
28
+ "must_not" => must_not_queries,
29
+ }.reject do |key, value|
30
+ value.empty?
31
+ end,
32
+ }
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def must_not_queries
39
+ must_not_tokens.map do |token|
40
+ if token.field_name.nil?
41
+ MatchNode.new(
42
+ token,
43
+ matchable_fields: @matchable_fields,
44
+ )
45
+ else
46
+ TermNode.new(
47
+ token,
48
+ hierarchal_fields: @hierarchal_fields,
49
+ )
50
+ end.to_hash
51
+ end
52
+ end
53
+
54
+ def must_not_tokens
55
+ @must_not_tokens ||= @tokens.select(&:must_not?)
56
+ end
57
+
58
+ def must_queries
59
+ must_tokens.map do |token|
60
+ TermNode.new(
61
+ token,
62
+ hierarchal_fields: @hierarchal_fields,
63
+ ).to_hash
64
+ end
65
+ end
66
+
67
+ def must_tokens
68
+ @must_tokens ||= @tokens.select(&:must?)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,54 @@
1
+ require "qiita/elasticsearch/nodes/filter_node"
2
+ require "qiita/elasticsearch/nodes/query_node"
3
+
4
+ module Qiita
5
+ module Elasticsearch
6
+ module Nodes
7
+ class FilterableNode
8
+ # @param [Array<Qiita::Elasticsearch::Tokens>] tokens
9
+ # @param [Array<String>, nil] hierarchal_fields
10
+ # @param [Array<String>, nil] matchable_fields
11
+ def initialize(tokens, hierarchal_fields: nil, matchable_fields: nil)
12
+ @hierarchal_fields = hierarchal_fields
13
+ @matchable_fields = matchable_fields
14
+ @tokens = tokens
15
+ end
16
+
17
+ def to_hash
18
+ if filter_tokens.empty?
19
+ QueryNode.new(
20
+ not_filter_tokens,
21
+ matchable_fields: @matchable_fields,
22
+ ).to_hash
23
+ else
24
+ {
25
+ "filtered" => {
26
+ "filter" => FilterNode.new(
27
+ filter_tokens,
28
+ hierarchal_fields: @hierarchal_fields,
29
+ matchable_fields: @matchable_fields,
30
+ ).to_hash,
31
+ "query" => QueryNode.new(
32
+ not_filter_tokens,
33
+ matchable_fields: @matchable_fields,
34
+ ).to_hash,
35
+ }.reject do |key, value|
36
+ value.empty?
37
+ end,
38
+ }
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def filter_tokens
45
+ @filter_tokens ||= @tokens.select(&:filter?)
46
+ end
47
+
48
+ def not_filter_tokens
49
+ @not_filter_tokens ||= @tokens.reject(&:filter?)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -1,7 +1,7 @@
1
1
  module Qiita
2
2
  module Elasticsearch
3
3
  module Nodes
4
- class MatchQueryNode
4
+ class MatchNode
5
5
  # @param [Qiita::Elasticsearch::Token] token
6
6
  # @param [Array<String>, nil] matchable_fields
7
7
  def initialize(token, matchable_fields: nil)
@@ -9,7 +9,6 @@ module Qiita
9
9
  @token = token
10
10
  end
11
11
 
12
- # @return [Hash] match query or multi_match query
13
12
  def to_hash
14
13
  if @matchable_fields.nil?
15
14
  {
@@ -0,0 +1,36 @@
1
+ require "qiita/elasticsearch/nodes/match_node"
2
+
3
+ module Qiita
4
+ module Elasticsearch
5
+ module Nodes
6
+ class MultiShouldNode
7
+ # @param [Array<Qiita::Elasticsearch::Tokens>] tokens
8
+ # @param [Array<String>, nil] matchable_fields
9
+ def initialize(tokens, matchable_fields: nil)
10
+ @matchable_fields = matchable_fields
11
+ @tokens = tokens
12
+ end
13
+
14
+ def to_hash
15
+ {
16
+ "bool" => {
17
+ "should" => should_queries,
18
+ },
19
+ }
20
+ end
21
+
22
+ private
23
+
24
+ # @return [Array<Hash>] Queries to be used as a value of `should` property of bool query.
25
+ def should_queries
26
+ @tokens.map do |token|
27
+ MatchNode.new(
28
+ token,
29
+ matchable_fields: @matchable_fields,
30
+ ).to_hash
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,4 +1,4 @@
1
- require "qiita/elasticsearch/nodes/bool_query_node"
1
+ require "qiita/elasticsearch/nodes/filterable_node"
2
2
  require "qiita/elasticsearch/nodes/null_node"
3
3
 
4
4
  module Qiita
@@ -19,7 +19,7 @@ module Qiita
19
19
  when 0
20
20
  Nodes::NullNode.new.to_hash
21
21
  when 1
22
- Nodes::BoolQueryNode.new(
22
+ Nodes::FilterableNode.new(
23
23
  tokens_grouped_by_or_token.first,
24
24
  hierarchal_fields: @hierarchal_fields,
25
25
  matchable_fields: @matchable_fields,
@@ -28,7 +28,7 @@ module Qiita
28
28
  {
29
29
  "bool" => {
30
30
  "should" => tokens_grouped_by_or_token.map do |tokens|
31
- Nodes::BoolQueryNode.new(
31
+ Nodes::FilterableNode.new(
32
32
  tokens,
33
33
  hierarchal_fields: @hierarchal_fields,
34
34
  matchable_fields: @matchable_fields,
@@ -0,0 +1,35 @@
1
+ require "qiita/elasticsearch/nodes/match_node"
2
+ require "qiita/elasticsearch/nodes/multi_should_node"
3
+
4
+ module Qiita
5
+ module Elasticsearch
6
+ module Nodes
7
+ class QueryNode
8
+ # @param [Array<Qiita::Elasticsearch::Tokens>] tokens
9
+ # @param [Array<String>, nil] matchable_fields
10
+ def initialize(tokens, hierarchal_fields: nil, matchable_fields: nil)
11
+ @hierarchal_fields = hierarchal_fields
12
+ @matchable_fields = matchable_fields
13
+ @tokens = tokens
14
+ end
15
+
16
+ def to_hash
17
+ case @tokens.length
18
+ when 0
19
+ {}
20
+ when 1
21
+ MatchNode.new(
22
+ @tokens.first,
23
+ matchable_fields: @matchable_fields,
24
+ ).to_hash
25
+ else
26
+ MultiShouldNode.new(
27
+ @tokens,
28
+ matchable_fields: @matchable_fields,
29
+ ).to_hash
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,54 @@
1
+ module Qiita
2
+ module Elasticsearch
3
+ module Nodes
4
+ class TermNode
5
+ DEFAULT_HIERARCHAL_FIELDS = []
6
+
7
+ # @param [Qiita::Elasticsearch::Token] token
8
+ # @param [Array<String>, nil] hierarchal_fields
9
+ def initialize(token, hierarchal_fields: nil)
10
+ @hierarchal_fields = hierarchal_fields
11
+ @token = token
12
+ end
13
+
14
+ # @return [Hash]
15
+ def to_hash
16
+ if has_hierarchal_token?
17
+ {
18
+ "bool" => {
19
+ "should" => [
20
+ {
21
+ "prefix" => {
22
+ @token.field_name => @token.downcased_term + "/",
23
+ },
24
+ },
25
+ {
26
+ "term" => {
27
+ @token.field_name => @token.downcased_term,
28
+ },
29
+ },
30
+ ],
31
+ },
32
+ }
33
+ else
34
+ {
35
+ "term" => {
36
+ @token.field_name => @token.downcased_term,
37
+ },
38
+ }
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def has_hierarchal_token?
45
+ hierarchal_fields.include?(@token.field_name)
46
+ end
47
+
48
+ def hierarchal_fields
49
+ @hierarchal_fields || DEFAULT_HIERARCHAL_FIELDS
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -15,8 +15,12 @@ module Qiita
15
15
  @downcased_term ||= term.downcase
16
16
  end
17
17
 
18
+ def filter?
19
+ !field_name.nil? || negative?
20
+ end
21
+
18
22
  def must?
19
- !field_name.nil? && !negative?
23
+ !field_name.nil? && positive?
20
24
  end
21
25
 
22
26
  def must_not?
@@ -51,10 +55,6 @@ module Qiita
51
55
  def quoted?
52
56
  !!@quoted
53
57
  end
54
-
55
- def should?
56
- !must? && !must_not?
57
- end
58
58
  end
59
59
  end
60
60
  end
@@ -1,5 +1,5 @@
1
1
  module Qiita
2
2
  module Elasticsearch
3
- VERSION = "0.1.4"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qiita-elasticsearch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryo Nakamura
@@ -97,12 +97,14 @@ files:
97
97
  - README.md
98
98
  - Rakefile
99
99
  - lib/qiita/elasticsearch.rb
100
- - lib/qiita/elasticsearch/nodes/bool_query_node.rb
101
- - lib/qiita/elasticsearch/nodes/filter_query_node.rb
102
- - lib/qiita/elasticsearch/nodes/match_query_node.rb
100
+ - lib/qiita/elasticsearch/nodes/filter_node.rb
101
+ - lib/qiita/elasticsearch/nodes/filterable_node.rb
102
+ - lib/qiita/elasticsearch/nodes/match_node.rb
103
+ - lib/qiita/elasticsearch/nodes/multi_should_node.rb
103
104
  - lib/qiita/elasticsearch/nodes/null_node.rb
104
105
  - lib/qiita/elasticsearch/nodes/or_separatable_node.rb
105
- - lib/qiita/elasticsearch/nodes/token_node.rb
106
+ - lib/qiita/elasticsearch/nodes/query_node.rb
107
+ - lib/qiita/elasticsearch/nodes/term_node.rb
106
108
  - lib/qiita/elasticsearch/parser.rb
107
109
  - lib/qiita/elasticsearch/query_builder.rb
108
110
  - lib/qiita/elasticsearch/token.rb
@@ -1,103 +0,0 @@
1
- require "qiita/elasticsearch/nodes/token_node"
2
-
3
- module Qiita
4
- module Elasticsearch
5
- module Nodes
6
- class BoolQueryNode
7
- # @param [Array<Qiita::Elasticsearch::Tokens>] tokens
8
- # @param [Array<String>, nil] hierarchal_fields
9
- # @param [Array<String>, nil] matchable_fields
10
- def initialize(tokens, hierarchal_fields: nil, matchable_fields: nil)
11
- @hierarchal_fields = hierarchal_fields
12
- @matchable_fields = matchable_fields
13
- @tokens = tokens
14
- end
15
-
16
- def to_hash
17
- case
18
- when has_only_one_should_token?
19
- should_query
20
- when has_only_one_must_token?
21
- must_query
22
- else
23
- {
24
- "bool" => {
25
- "must" => must_queries,
26
- "must_not" => must_not_queries,
27
- "should" => should_queries,
28
- }.reject do |key, value|
29
- value.empty?
30
- end,
31
- }
32
- end
33
- end
34
-
35
- private
36
-
37
- def has_only_one_must_token?
38
- must_not_tokens.empty? && should_tokens.empty? && must_tokens.size == 1
39
- end
40
-
41
- def has_only_one_should_token?
42
- must_not_tokens.empty? && must_tokens.empty? && should_tokens.size == 1
43
- end
44
-
45
- def must_not_queries
46
- must_not_tokens.map do |token|
47
- Nodes::TokenNode.new(
48
- token,
49
- hierarchal_fields: @hierarchal_fields,
50
- matchable_fields: @matchable_fields,
51
- ).to_hash
52
- end
53
- end
54
-
55
- def must_not_tokens
56
- @must_not_tokens ||= @tokens.select(&:must_not?)
57
- end
58
-
59
- def must_query
60
- Nodes::TokenNode.new(
61
- must_tokens.first,
62
- hierarchal_fields: @hierarchal_fields,
63
- matchable_fields: @matchable_fields,
64
- ).to_hash
65
- end
66
-
67
- def must_queries
68
- must_tokens.map do |token|
69
- Nodes::TokenNode.new(
70
- token,
71
- hierarchal_fields: @hierarchal_fields,
72
- matchable_fields: @matchable_fields,
73
- ).to_hash
74
- end
75
- end
76
-
77
- def must_tokens
78
- @must_tokens ||= @tokens.select(&:must?)
79
- end
80
-
81
- def should_query
82
- Nodes::TokenNode.new(
83
- should_tokens.first,
84
- matchable_fields: @matchable_fields,
85
- ).to_hash
86
- end
87
-
88
- def should_queries
89
- should_tokens.map do |token|
90
- Nodes::TokenNode.new(
91
- token,
92
- matchable_fields: @matchable_fields,
93
- ).to_hash
94
- end
95
- end
96
-
97
- def should_tokens
98
- @should_tokens ||= @tokens.select(&:should?)
99
- end
100
- end
101
- end
102
- end
103
- end
@@ -1,67 +0,0 @@
1
- module Qiita
2
- module Elasticsearch
3
- module Nodes
4
- class FilterQueryNode
5
- DEFAULT_HIERARCHAL_FIELDS = []
6
-
7
- # @param [Qiita::Elasticsearch::Token] token
8
- def initialize(token, hierarchal_fields: nil)
9
- @hierarchal_fields = hierarchal_fields
10
- @token = token
11
- end
12
-
13
- # @return [Hash]
14
- def to_hash
15
- if has_hierarchal_token?
16
- {
17
- "filtered" => {
18
- "filter" => {
19
- "bool" => {
20
- "should" => [
21
- {
22
- "prefix" => {
23
- @token.field_name => @token.downcased_term + "/",
24
- },
25
- },
26
- {
27
- "term" => {
28
- @token.field_name => @token.downcased_term,
29
- },
30
- },
31
- ],
32
- },
33
- },
34
- "query" => {
35
- "match_all" => {},
36
- },
37
- },
38
- }
39
- else
40
- {
41
- "filtered" => {
42
- "filter" => {
43
- "term" => {
44
- @token.field_name => @token.downcased_term,
45
- },
46
- },
47
- "query" => {
48
- "match_all" => {},
49
- },
50
- },
51
- }
52
- end
53
- end
54
-
55
- private
56
-
57
- def has_hierarchal_token?
58
- hierarchal_fields.include?(@token.field_name)
59
- end
60
-
61
- def hierarchal_fields
62
- @hierarchal_fields || DEFAULT_HIERARCHAL_FIELDS
63
- end
64
- end
65
- end
66
- end
67
- end
@@ -1,27 +0,0 @@
1
- require "qiita/elasticsearch/nodes/filter_query_node"
2
- require "qiita/elasticsearch/nodes/match_query_node"
3
-
4
- module Qiita
5
- module Elasticsearch
6
- module Nodes
7
- class TokenNode
8
- # @param [Qiita::Elasticsearch::Token] token
9
- # @param [Array<String>, nil] hierarchal_fields
10
- # @param [Array<String>, nil] matchable_fields
11
- def initialize(token, hierarchal_fields: nil, matchable_fields: nil)
12
- @hierarchal_fields = hierarchal_fields
13
- @matchable_fields = matchable_fields
14
- @token = token
15
- end
16
-
17
- def to_hash
18
- if @token.field_name
19
- FilterQueryNode.new(@token, hierarchal_fields: @hierarchal_fields).to_hash
20
- else
21
- MatchQueryNode.new(@token, matchable_fields: @matchable_fields).to_hash
22
- end
23
- end
24
- end
25
- end
26
- end
27
- end