qiita-elasticsearch 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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