plunk 0.2.11 → 0.3.1

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: 7c08aca732911470f0154ec5b6c12cdfcaa3876f
4
- data.tar.gz: 9b0ffbfa317e102a4e44911d20d614cc50a3f17e
3
+ metadata.gz: bb14cb961c9f1fcfadaeefc60ca753cb061bf2eb
4
+ data.tar.gz: ee823ac07dfcb711d8d35becf70aea8a4f3cde05
5
5
  SHA512:
6
- metadata.gz: 573e8a419a39a2b0e1fbed92b7c6a1c7bbe5388b19c17e789393d8726ffe5e75ada2872c89226ce869155b3361b749e18e4d8d5718b7d725a6edbf14f79a84f3
7
- data.tar.gz: c5e897c8090906fc6d0ec9fbe9d0254eb3d95031397049fc71e7ef2537e39c5f0a5487b68589701190bb73ec03bfefa87da940f886470e2fe4f515904a8c8098
6
+ metadata.gz: d77fa8591e0c76809bbec4e24d408073836dd517bd35fad324ed389826fa02d4a989933e2fc1c71c5a61628dfea50c81416ca3e20333789b4a5e73a676c2810d
7
+ data.tar.gz: 6c76177d7f17f571f9f6ad46808ea6c99b0c2eadd4fc87256345e44d5111ebad5ca29171b5484676ad16ba3d13c1089c962c638a1d1016568616c2515afbc50f
data/AUTHORS CHANGED
@@ -1,2 +1,3 @@
1
1
  Jamil Bou Kheir <jamil@elbii.com>
2
2
  Ram Mehta <ram.mehta@gmail.com>
3
+ Roman Heinrich <roman.heinrich@gmail.com>
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- plunk (0.2.10)
4
+ plunk (0.3.1)
5
5
  activesupport
6
6
  elasticsearch
7
7
  json
data/lib/plunk.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'elasticsearch'
2
2
 
3
+ require 'plunk/helper'
3
4
  require 'plunk/utils'
4
5
  require 'plunk/parser'
5
6
  require 'plunk/transformer'
@@ -32,6 +33,10 @@ module Plunk
32
33
  end
33
34
 
34
35
  def self.search(query_string)
35
- transformer.apply(parser.parse(query_string)).eval
36
+ ResultSet.new(
37
+ transformer.apply(
38
+ parser.parse(query_string)
39
+ )
40
+ ).eval
36
41
  end
37
42
  end
@@ -0,0 +1,55 @@
1
+ require 'active_support/core_ext'
2
+
3
+ module Plunk
4
+ class Helper
5
+ def self.query_builder(query_string)
6
+ {
7
+ query: {
8
+ query_string: {
9
+ query: query_string
10
+ }
11
+ }
12
+ }
13
+ end
14
+
15
+ def self.filter_builder(filter)
16
+ {
17
+ query: {
18
+ filtered: {
19
+ filter: filter
20
+ }
21
+ }
22
+ }
23
+ end
24
+
25
+ def self.range_builder(range_min, range_max)
26
+ {
27
+ range: {
28
+ Plunk.timestamp_field => {
29
+ gte: range_min,
30
+ gte: range_max
31
+ }
32
+ }
33
+ }
34
+ end
35
+
36
+ def self.time_query_to_timestamp(int_quantity, quantifier)
37
+ case quantifier
38
+ when 's'
39
+ int_quantity.seconds.ago
40
+ when 'm'
41
+ int_quantity.minutes.ago
42
+ when 'h'
43
+ int_quantity.hours.ago
44
+ when 'd'
45
+ int_quantity.days.ago
46
+ when 'w'
47
+ int_quantity.weeks.ago
48
+ end
49
+ end
50
+
51
+ def self.timestamp_format(time)
52
+ time.utc.to_datetime.iso8601(3)
53
+ end
54
+ end
55
+ end
data/lib/plunk/parser.rb CHANGED
@@ -3,14 +3,11 @@ require 'parslet'
3
3
  module Plunk
4
4
  class Parser < Parslet::Parser
5
5
 
6
- def parenthesized(atom)
7
- lparen >> atom >> rparen
8
- end
6
+ # BUILDING BLOCKS
9
7
 
10
8
  # Single character rules
11
9
  rule(:lparen) { str('(') >> space? }
12
10
  rule(:rparen) { str(')') >> space? }
13
- rule(:comma) { str(',') >> space? }
14
11
  rule(:digit) { match('[0-9]') }
15
12
  rule(:space) { match('\s').repeat(1) }
16
13
  rule(:space?) { space.maybe }
@@ -21,6 +18,8 @@ module Plunk
21
18
  str('-').maybe >> digit.repeat(1) >> str('.') >> digit.repeat(1) >> space?
22
19
  }
23
20
  rule(:number) { integer | float }
21
+
22
+ # Dates
24
23
  rule(:datetime) {
25
24
  # 1979-05-27T07:32:00Z
26
25
  digit.repeat(4) >> str("-") >>
@@ -30,10 +29,11 @@ module Plunk
30
29
  digit.repeat(2) >> str(":") >>
31
30
  digit.repeat(2) >> str("Z")
32
31
  }
32
+
33
+ # Strings
33
34
  rule(:escaped_special) {
34
35
  str("\\") >> match['0tnr"\\\\']
35
36
  }
36
-
37
37
  rule(:string_special) {
38
38
  match['\0\t\n\r"\\\\']
39
39
  }
@@ -43,79 +43,101 @@ module Plunk
43
43
  str('"')
44
44
  }
45
45
 
46
- # Field / value
47
- rule(:identifier) { match('[^=\s)(|]').repeat(1) >> match('[^=\s]').repeat }
48
- # possible right-hand side values
49
- rule(:wildcard) { match('[^=\s)(|]').repeat(1) }
50
- rule(:searchop) { match('[=]').as(:op) }
46
+ # Booleans
47
+ rule(:and_operator) { (str('and') | str('AND') | str('&')) >> space? }
48
+ rule(:or_operator) { (str('or') | str('OR') | str('|')) >> space? }
49
+ rule(:not_operator) { (str('not') | str('NOT') | str('~')) >> space? }
51
50
 
52
- rule(:query_value) { string | wildcard | datetime | number }
53
51
 
54
- # boolean operators search
55
- rule(:concatop) { (str('OR') | str('AND')) >> space? }
56
- rule(:negateop) { str('NOT') >> space? }
57
- rule(:operator) { match('[|]').as(:op) >> space? }
58
- rule(:timerange) {
59
- integer.as(:quantity) >> match('s|m|h|d|w').as(:quantifier)
60
- }
52
+ # COMMANDS
61
53
 
62
- # Grammar parts
63
- rule(:rhs) {
64
- regexp | subsearch | booleanop
54
+ # Command parts
55
+ rule(:identifier) { match('[^=\s)(|]').repeat(1) >> match('[^=\s]').repeat }
56
+ rule(:wildcard) {
57
+ (lparen >> wildcard >> rparen) |
58
+ match('[^=\s|)(]').repeat(1)
65
59
  }
66
-
67
- rule(:boolean_value) {
68
- booleanparen | (negateop.maybe >> query_value)
60
+ rule(:query_value) { string | wildcard | datetime | number }
61
+ rule(:searchop) { match['='] }
62
+ rule(:rhs) {
63
+ regexp | query_value
69
64
  }
70
65
 
71
- # AND, OR
72
- rule(:boolean_logic) {
73
- space >> concatop >> boolean_value
66
+ # Field = Value
67
+ rule(:field_value) {
68
+ identifier.as(:field) >> space? >>
69
+ searchop >> space? >>
70
+ (rhs.as(:value) | subsearch.as(:subsearch))
74
71
  }
75
72
 
76
- # handles recursion for parentheses and values
77
- rule(:booleanop) {
78
- boolean_value >> boolean_logic.repeat
79
- }
80
- rule(:booleanparen) {
81
- lparen >> space? >> booleanop >> space? >> rparen
73
+ # Value-only
74
+ rule(:value_only) {
75
+ rhs.as(:value)
82
76
  }
83
77
 
78
+ # Regexp
84
79
  rule(:regexp) {
85
80
  str('/') >> (str('\/') | match('[^/]')).repeat >> str('/')
86
81
  }
87
82
 
83
+ # Last
88
84
  rule(:last) {
89
- str("last") >> space >> timerange.as(:timerange) >> (space >>
90
- search.as(:search)).maybe
91
- }
92
-
93
- rule(:search) {
94
- identifier.as(:field) >> space? >> searchop >> space? >>
95
- rhs.as(:value) | rhs.as(:match)
85
+ str('last') >>
86
+ space >>
87
+ integer.as(:quantity) >>
88
+ match('s|m|h|d|w').as(:quantifier)
96
89
  }
97
90
 
91
+ # Subsearch
98
92
  rule(:subsearch) {
99
93
  str('`') >> space? >> nested_search >> str('`')
100
94
  }
101
-
102
95
  rule(:nested_search) {
103
- job.as(:initial_query) >> space? >> str('|') >> space? >>
96
+ plunk_query.as(:initial_query) >> space? >> str('|') >> space? >>
104
97
  match('[^`]').repeat.as(:extractors)
105
98
  }
106
99
 
107
- rule(:paren) {
108
- lparen >> space? >> job >> space? >> rparen
100
+ # Reference your custom commands here to make them eligible for parsing
101
+ # NOTE: order matters!
102
+ rule(:command) {
103
+ (
104
+ last |
105
+ field_value |
106
+ value_only
107
+ ).as(:command) >> space?
109
108
  }
110
109
 
111
- rule(:job) {
112
- last | search | paren
110
+
111
+ # QUERY JOINING
112
+
113
+ rule(:negated_command) {
114
+ (not_operator >> command.as(:not)) |
115
+ command
116
+ }
117
+ rule(:primary) { lparen >> or_operation >> rparen | negated_command }
118
+
119
+ rule(:negated_and) {
120
+ (not_operator >> and_operation.as(:not)) |
121
+ and_operation
122
+ }
123
+ rule(:and_operation) {
124
+ (primary.as(:left) >> and_operator >>
125
+ negated_and.as(:right)).as(:and) |
126
+ primary }
127
+
128
+ rule(:negated_or) {
129
+ (not_operator >> or_operation.as(:not)) |
130
+ or_operation
113
131
  }
132
+ rule(:or_operation) {
133
+ (and_operation.as(:left) >> or_operator >>
134
+ negated_or.as(:right)).as(:or) |
135
+ and_operation }
114
136
 
115
137
  rule(:plunk_query) {
116
- space? >> job >> (space >> job).repeat >> space?
138
+ space? >> or_operation >> space?
117
139
  }
118
140
 
119
- root :plunk_query
141
+ root(:plunk_query)
120
142
  end
121
143
  end
@@ -2,60 +2,15 @@ module Plunk
2
2
  class ResultSet
3
3
  attr_accessor :query, :query_string
4
4
 
5
- def initialize(opts={})
6
- @query = { query: { filtered: {}}}
7
-
8
- if opts.size >= 3 # use "and" filter to AND filters
9
- @query_string = opts[:query_string]
10
- @query[:query][:filtered][:query] = {
11
- query_string: {
12
- query: opts[:query_string] }}
13
- @query[:query][:filtered][:filter] = {
14
- and: [
15
- range: {
16
- Plunk.timestamp_field => {
17
- gte: opts[:start_time],
18
- lte: opts[:end_time] }}]}
19
- else
20
- if @query_string = opts[:query_string]
21
- @query[:query][:filtered][:query] = {
22
- query_string: {
23
- query: opts[:query_string] }}
24
- elsif opts[:start_time] and opts[:end_time]
25
- @query[:query][:filtered][:query] = {
26
- range: {
27
- Plunk.timestamp_field => {
28
- gte: opts[:start_time],
29
- lte: opts[:end_time] }}}
30
- end
31
- end
5
+ def initialize(filter)
6
+ @query = { query: { filtered: { filter: filter }}}
32
7
  end
33
8
 
34
9
  def eval
35
10
  Plunk.elasticsearch_client.search(
36
- body: @query.to_json,
11
+ body: @query,
37
12
  size: Plunk.max_number_of_hits || 10
38
- ).to_json if @query
39
- end
40
-
41
- # merges multiple queries with implicit AND
42
- def self.merge(result_sets)
43
- first = result_sets.delete_at 0
44
-
45
- first.query[:query][:filtered][:filter] ||= {}
46
- first.query[:query][:filtered][:filter][:and] ||= []
47
-
48
- result_sets.each do |result_set|
49
- first.query[:query][:filtered][:filter][:and] <<
50
- result_set.query[:query][:filtered]
51
-
52
- if result_set.query[:query][:filtered][:filter]
53
- first.query[:query][:filtered][:filter][:and] +=
54
- result_set.query[:query][:filtered][:filter][:and]
55
- end
56
- end
57
-
58
- first
13
+ ) if @query
59
14
  end
60
15
  end
61
16
  end
@@ -1,135 +1,54 @@
1
1
  require 'parslet'
2
- require 'active_support/core_ext'
3
2
 
4
3
  module Plunk
5
-
6
- class Helper
7
- def self.time_query_to_timestamp(int_quantity, quantifier)
8
- case quantifier
9
- when 's'
10
- int_quantity.seconds.ago
11
- when 'm'
12
- int_quantity.minutes.ago
13
- when 'h'
14
- int_quantity.hours.ago
15
- when 'd'
16
- int_quantity.days.ago
17
- when 'w'
18
- int_quantity.weeks.ago
19
- end
20
- end
21
-
22
- def self.timestamp_format(time)
23
- time.utc.to_datetime.iso8601(3)
24
- end
25
-
26
- def self.time_range_hash(start_time, end_time)
27
- {
28
- start_time: Plunk::Helper.timestamp_format(start_time),
29
- end_time: Plunk::Helper.timestamp_format(end_time)
30
- }
31
- end
32
- end
33
-
34
4
  class Transformer < Parslet::Transform
35
5
 
36
- # last 24h foo=bar
37
- rule(
6
+ # Field = Value
7
+ rule(command: {
38
8
  field: simple(:field),
39
- value: {
40
- initial_query: subtree(:initial_query),
41
- extractors: simple(:extractors)
42
- },
43
- op: '=',
44
- timerange: {
45
- quantity: simple(:quantity),
46
- quantifier: simple(:quantifier)
47
- }) do
48
-
49
- int_quantity = quantity.to_s.to_i
50
- start_time = Plunk::Helper.time_query_to_timestamp(int_quantity, quantifier)
51
- end_time = Time.now
52
-
53
- # recursively apply nested query
54
- result_set = Plunk::Transformer.new.apply(initial_query)
55
-
56
- json = JSON.parse result_set.eval
57
- values = Plunk::Utils.extract_values json, extractors.to_s.split(',')
58
-
59
- result_set_params = Plunk::Helper.time_range_hash(start_time, end_time)
60
- if values.empty?
61
- result_set_params.merge!(query_string: "#{field}:(#{values.uniq.join(' OR ')})",)
62
- end
63
- Plunk::ResultSet.new(result_set_params)
9
+ value: simple(:value)
10
+ }) do
11
+ Helper.query_builder(
12
+ String(field) + ":" + String(value)
13
+ )
64
14
  end
65
15
 
66
- rule(match: simple(:value)) do
67
- ResultSet.new(query_string: "#{value}")
16
+ # Value-only
17
+ rule(command: { value: simple(:value) }) do
18
+ Helper.query_builder(String(value))
68
19
  end
69
20
 
70
- # foo=`bar=baz|field1,field2,field3`
71
- rule(
72
- field: simple(:field),
73
- value: {
74
- initial_query: subtree(:initial_query),
75
- extractors: simple(:extractors)
76
- },
77
- op: '=') do
78
-
79
- # recursively apply nested query
80
- result_set = Transformer.new.apply(initial_query)
81
-
82
- json = JSON.parse result_set.eval
83
- values = Utils.extract_values json, extractors.to_s.split(',')
21
+ rule(command: {
22
+ quantity: simple(:quantity),
23
+ quantifier: simple(:quantifier)
24
+ }) do
25
+ start_timestamp = Helper.time_query_to_timestamp(
26
+ Integer(quantity),
27
+ String(quantifier)
28
+ )
84
29
 
85
- if values.empty?
86
- ResultSet.new
87
- else
88
- ResultSet.new(query_string: "#{field}:(#{values.uniq.join(' OR ')})")
89
- end
90
- end
30
+ start_time = Helper.timestamp_format start_timestamp
31
+ end_time = Helper.timestamp_format(Time.now)
91
32
 
92
- # foo=bar
93
- rule(field: simple(:field), value: simple(:value), op: '=') do
94
- ResultSet.new(query_string: "#{field}:#{value}")
33
+ Helper.range_builder(start_time, end_time)
95
34
  end
96
35
 
97
- rule(
98
- timerange: {
99
- quantity: simple(:quantity),
100
- quantifier: simple(:quantifier)
101
- }) do
102
-
103
- int_quantity = quantity.to_s.to_i
104
- start_time = Plunk::Helper.time_query_to_timestamp(int_quantity, quantifier)
105
- end_time = Time.now
106
-
107
- result_set_params = Plunk::Helper.time_range_hash(start_time, end_time)
108
- Plunk::ResultSet.new(result_set_params)
36
+ rule(:negate => subtree(:not)) do
37
+ { not: negate }
109
38
  end
110
39
 
111
- # last 24h
112
- rule(
113
- search: simple(:result_set),
114
- timerange: {
115
- quantity: simple(:quantity),
116
- quantifier: simple(:quantifier)
117
- }) do
118
-
119
- int_quantity = quantity.to_s.to_i
120
- start_time = Plunk::Helper.time_query_to_timestamp(int_quantity, quantifier)
121
- end_time = Time.now
122
-
123
- result_set_params = Plunk::Helper.time_range_hash(start_time, end_time)
124
- result_set_params.merge!(query_string: result_set.query_string)
125
- Plunk::ResultSet.new(result_set_params)
40
+ rule(:or => {
41
+ left: subtree(:left),
42
+ right: subtree(:right)
43
+ }) do
44
+ { or: [left, right] }
126
45
  end
127
46
 
128
- # last 24h foo=bar baz=fez
129
- rule(
130
- sequence(:set)
131
- ) do
132
- Plunk::ResultSet.merge(set)
47
+ rule(:and => {
48
+ left: subtree(:left),
49
+ right: subtree(:right)
50
+ }) do
51
+ { and: [left, right] }
133
52
  end
134
53
  end
135
54
  end