pacer 1.4.2-java → 1.5.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +20 -1
  3. data/README.md +29 -15
  4. data/Rakefile +0 -1
  5. data/lib/pacer/core/graph/edges_route.rb +5 -5
  6. data/lib/pacer/core/graph/vertices_route.rb +7 -7
  7. data/lib/pacer/core/route.rb +39 -23
  8. data/lib/pacer/filter/collection_filter.rb +6 -5
  9. data/lib/pacer/filter/empty_filter.rb +1 -0
  10. data/lib/pacer/filter/loop_filter.rb +13 -2
  11. data/lib/pacer/filter/property_filter.rb +1 -1
  12. data/lib/pacer/filter/property_filter/filters.rb +1 -1
  13. data/lib/pacer/filter/uniq_section.rb +56 -0
  14. data/lib/pacer/filter/where_filter/node_visitor.rb +13 -12
  15. data/lib/pacer/graph/graph_ml.rb +4 -2
  16. data/lib/pacer/graph/graph_transactions_mixin.rb +26 -0
  17. data/lib/pacer/graph/pacer_graph.rb +1 -1
  18. data/lib/pacer/loader.rb +2 -1
  19. data/lib/pacer/pipe/collection_filter_pipe.rb +2 -0
  20. data/lib/pacer/pipe/id_collection_filter_pipe.rb +12 -2
  21. data/lib/pacer/pipe/loop_pipe.rb +1 -1
  22. data/lib/pacer/pipe/property_comparison_pipe.rb +6 -6
  23. data/lib/pacer/pipes.rb +5 -4
  24. data/lib/pacer/route/mixin/bulk_operations.rb +22 -21
  25. data/lib/pacer/support/enumerable.rb +0 -4
  26. data/lib/pacer/transform/identity.rb +10 -0
  27. data/lib/pacer/transform/lookup_ids.rb +2 -2
  28. data/lib/pacer/version.rb +3 -4
  29. data/lib/pacer/wrappers/edge_wrapper.rb +3 -1
  30. data/lib/pacer/wrappers/element_wrapper.rb +5 -3
  31. data/lib/pacer/wrappers/vertex_wrapper.rb +8 -2
  32. data/pom.xml +3 -30
  33. data/spec/pacer/blueprints/neo4j2_spec.rb +62 -0
  34. data/spec/pacer/blueprints/neo4j_spec.rb +6 -4
  35. data/spec/pacer/core/graph/graph_route_spec.rb +11 -3
  36. data/spec/pacer/core/route_spec.rb +1 -1
  37. data/spec/pacer/filter/empty_filter_spec.rb +1 -1
  38. data/spec/pacer/route/mixin/bulk_operations_spec.rb +11 -3
  39. data/spec/pacer/wrapper/element_wrapper_spec.rb +15 -15
  40. data/spec/pacer/wrapper/vertex_wrapper_spec.rb +4 -4
  41. data/spec/spec_helper.rb +5 -4
  42. data/spec/support/graph_runner.rb +11 -0
  43. data/spec/support/use_transactions.rb +4 -0
  44. metadata +7 -4
  45. data/lib/pacer/support/array.rb +0 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9e85b52180f5709fd005a51803e16d98bceacaaa
4
- data.tar.gz: 84ded6cf4d5ec23d85688034aabf7e23822f073f
3
+ metadata.gz: 73b5792b368e7fd7337c32ccceeb591ed47ab3a8
4
+ data.tar.gz: cb32f1e5e53c44accced47e77ee7fe6177eafeb4
5
5
  SHA512:
6
- metadata.gz: 87cde3ee51453d0b6a4d171a489828cc7fd3d0a53eed417b07102c2481c60b9df7bc7bf6ba16ef037a362fe566304b907794f099d0c1b58905d3f49992859945
7
- data.tar.gz: 1679920a788cf8cee52152835c893a08162e7d09e8036f5d3e597cbd0e604e4a13b9bb8ea031d5c8593d122c77289820b02129cb511a59abf914f0314b0c1ef0
6
+ metadata.gz: 3eb2c7a98df23b0cce4a79343e601363970bb3a96deb4c4d50982daab3f683ce4d97cd7a957475b8df6ecd6fde41756383bf3fa9c5fef70ad1efd08a44944c01
7
+ data.tar.gz: aff44e7a64e456465a5e15fe093ed04410d7139f63213bdecddf1ae265cf6cd4fdab1fbcbba25d26b67d20453f3b60e79d2c31479845e795c24cf86895a0e16d
data/Gemfile CHANGED
@@ -12,12 +12,31 @@ group :development do
12
12
  # pacer-* gems are required for testing pacer.
13
13
  # If you have the gem repos cloned locally, we'll use them.
14
14
  #
15
- [ 'pacer-neo4j', 'pacer-orient', 'pacer-dex'].each do |lib|
15
+ [ 'pacer-orient', 'pacer-dex' ].each do |lib|
16
16
  if File.directory? "../#{lib}"
17
17
  gem lib, :path => "../#{lib}"
18
18
  end
19
19
  end
20
20
 
21
+ # Neo4j versions are mutually incompatible
22
+ # To test Pacer against Neo4j 1.x when the neo2 gem is present, use:
23
+ #
24
+ # neo=1 bundle
25
+ # rspec
26
+ #
27
+ # To switch back, just use:
28
+ #
29
+ # bundle
30
+ # rspec
31
+ #
32
+ if File.directory? "../pacer-neo4j" and (ENV['neo'] == '1' or not File.directory? "../pacer-neo4j2")
33
+ gem 'pacer-neo4j', :path => "../pacer-neo4j"
34
+ end
35
+
36
+ if File.directory? "../pacer-neo4j2" and ENV['neo'] != '1'
37
+ gem 'pacer-neo4j2', :path => "../pacer-neo4j2"
38
+ end
39
+
21
40
  if File.directory? "../mcfly"
22
41
  gem 'pacer-mcfly', :path => "../mcfly"
23
42
  end
data/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  Pacer is a JRuby library that enables very expressive graph traversals.
4
4
 
5
- It currently supports 2 major graph database: [Neo4j](http://neo4j.org)
6
- and [Dex](http://www.sparsity-technologies.com/dex) using the
5
+ It currently supports all of the major graph databases including [Neo4j](http://neo4j.org)
6
+ and [Dex](http://www.sparsity-technologies.com/dex) thanks to the
7
7
  [Tinkerpop](http://tinkerpop.com) graphdb stack. Plus there's a very
8
8
  convenient in-memory graph called TinkerGraph which is part of
9
9
  [Blueprints](http://blueprints.tinkerpop.com).
@@ -17,19 +17,21 @@ it's very fast!
17
17
 
18
18
  ## Mailing List
19
19
 
20
- With the release of 0.8.1, I just set up a brand new [pacer google
21
- group](http://groups.google.com/group/pacer-users?lnk=gcimv). Join and
22
- let's get the conversation going!
20
+ Pacer has a [google group](http://groups.google.com/group/pacer-users?lnk=gcimv)! Join and feel free to ask questions and discuss about Pacer or graphs in general.
23
21
 
24
22
  ## Documentation
25
23
 
26
- Pacer is documented with a comprehensive RSpec test suite and with a
24
+ Check out the [wiki](https://github.com/pangloss/pacer/wiki) for detailed explanations of many of Pacer's features. Please contribute to it or open issues against it if there is anything missing that you want to see.
25
+
26
+ Pacer is also documented with a comprehensive RSpec test suite and with a
27
27
  thorough YARD documentation. [Dig in!](http://rubydoc.info/github/pangloss/pacer/develop/frames)
28
28
 
29
29
  If you like, you can also use the documentation locally via
30
30
 
31
+ ```
31
32
  gem install yard
32
33
  yard server
34
+ ```
33
35
 
34
36
  ## JRuby 1.7 Required
35
37
 
@@ -57,20 +59,31 @@ compatible with Pacer, but I have not yet implemented the simple
57
59
  adapters Pacer needs to use them yet. Here is the list of what's
58
60
  supported so far:
59
61
 
60
- * TinkerGraph - In-memory graph db, built in and ready to use without
61
- additional dependencies.
62
- * [Neo4J](http://neo4j.org) - The industry-leading graph db. `gem
63
- install pacer-neo4j`
64
- [pangloss/pacer-neo4j](https://github.com/pangloss/pacer-neo4j)
65
- * [Dex](http://sparsity-technologies.com) - A very fast, relatively new graph db. `gem
66
- install pacer-dex`
67
- [pangloss/pacer-dex](https://github.com/pangloss/pacer-dex)
62
+ | Graph | Info | Gem Required | Gem address |
63
+ |------------------------------------------------|------------------------------------------|---------------------------|-----------------------------------------------------------------|
64
+ | TinkerGraph | In-memory graph db. Included with Pacer. | | |
65
+ | [Neo4J](http://neo4j.org) | The industry-leading graph db. | `gem install pacer-neo4j` | [pangloss/pacer-neo4j](https://github.com/pangloss/pacer-neo4j) |
66
+ | [Dex](http://sparsity-technologies.com) | A very fast, relatively new graph db. | `gem install pacer-dex` | [pangloss/pacer-dex](https://github.com/pangloss/pacer-dex) |
67
+ | [Titan](http://thinkaurelius.github.io/titan/) | Built on top of a pluggable nosql backend store | `gem install pacer-titan` | [pacer-titan](https://github.com/mrbotch/pacer-titan) |
68
+
69
+
68
70
 
69
71
  You can run any or all of the above graph databases. Pacer supports
70
72
  running them simultaneuosly and even supports having many of any given
71
73
  type open at once.
72
74
 
73
- ### Interoperation with the neo4j gem
75
+ ### Using Pacer with Neo4j
76
+
77
+ All you need is the [pacer-neo4j](https://github.com/pangloss/pacer-neo4j) gem.
78
+
79
+ ```ruby
80
+ require 'pacer-neo4j'
81
+ graph = Pacer.neo4j("path/to/graph")
82
+ ```
83
+
84
+ See the gem repository for more details on Neo4j-specific features and functionality.
85
+
86
+ #### (optional) Interoperation with the neo4j gem
74
87
 
75
88
  Pacer can work together with other Ruby GraphDB libraries, too. The
76
89
  first functioning example is with theo neo4j gem. Hopefully more will
@@ -82,6 +95,7 @@ as follows:
82
95
  ```ruby
83
96
  require 'neo4j'
84
97
  require 'pacer-neo4j'
98
+ # start neo4j via the external gem rather than using pacer-neo4j
85
99
  Neo4j.db.start
86
100
  graph = Pacer.neo4j(Neo4j.db.graph)
87
101
  ```
data/Rakefile CHANGED
@@ -20,7 +20,6 @@ file 'pom.xml' => 'lib/pacer/version.rb' do
20
20
  line.sub!(%r{<gem.version>.*</gem.version>}, "<gem.version>#{ Pacer::VERSION }</gem.version>")
21
21
  line.sub!(%r{<blueprints.version>.*</blueprints.version>}, "<blueprints.version>#{ Pacer::BLUEPRINTS_VERSION }</blueprints.version>")
22
22
  line.sub!(%r{<pipes.version>.*</pipes.version>}, "<pipes.version>#{ Pacer::PIPES_VERSION }</pipes.version>")
23
- line.sub!(%r{<gremlin.version>.*</gremlin.version>}, "<gremlin.version>#{ Pacer::GREMLIN_VERSION }</gremlin.version>")
24
23
  f << line
25
24
  end
26
25
  end
@@ -2,9 +2,9 @@ module Pacer::Core::Graph
2
2
 
3
3
  # Basic methods for routes that contain only edges.
4
4
  module EdgesRoute
5
- import com.tinkerpop.gremlin.pipes.transform.OutVertexPipe
6
- import com.tinkerpop.gremlin.pipes.transform.InVertexPipe
7
- import com.tinkerpop.gremlin.pipes.transform.BothVerticesPipe
5
+ import com.tinkerpop.pipes.transform.OutVertexPipe
6
+ import com.tinkerpop.pipes.transform.InVertexPipe
7
+ import com.tinkerpop.pipes.transform.BothVerticesPipe
8
8
 
9
9
  # Extends the route with out vertices from this route's matching edges.
10
10
  #
@@ -56,7 +56,7 @@ module Pacer::Core::Graph
56
56
  #
57
57
  # @return [Core::Route]
58
58
  def labels
59
- chain_route(:pipe_class => com.tinkerpop.gremlin.pipes.transform.LabelPipe,
59
+ chain_route(:pipe_class => com.tinkerpop.pipes.transform.LabelPipe,
60
60
  :route_name => 'labels',
61
61
  :element_type => :object)
62
62
  end
@@ -84,7 +84,7 @@ module Pacer::Core::Graph
84
84
  protected
85
85
 
86
86
  def id_pipe_class
87
- com.tinkerpop.gremlin.pipes.transform.IdEdgePipe
87
+ com.tinkerpop.pipes.transform.IdEdgePipe
88
88
  end
89
89
  end
90
90
  end
@@ -2,12 +2,12 @@ module Pacer::Core::Graph
2
2
 
3
3
  # Basic methods for routes that contain only vertices.
4
4
  module VerticesRoute
5
- import com.tinkerpop.gremlin.pipes.transform.OutEdgesPipe
6
- import com.tinkerpop.gremlin.pipes.transform.OutPipe
7
- import com.tinkerpop.gremlin.pipes.transform.InEdgesPipe
8
- import com.tinkerpop.gremlin.pipes.transform.InPipe
9
- import com.tinkerpop.gremlin.pipes.transform.BothEdgesPipe
10
- import com.tinkerpop.gremlin.pipes.transform.BothPipe
5
+ import com.tinkerpop.pipes.transform.OutEdgesPipe
6
+ import com.tinkerpop.pipes.transform.OutPipe
7
+ import com.tinkerpop.pipes.transform.InEdgesPipe
8
+ import com.tinkerpop.pipes.transform.InPipe
9
+ import com.tinkerpop.pipes.transform.BothEdgesPipe
10
+ import com.tinkerpop.pipes.transform.BothPipe
11
11
 
12
12
  # Extends the route with out edges from this route's matching vertices.
13
13
  #
@@ -219,7 +219,7 @@ module Pacer::Core::Graph
219
219
 
220
220
  # TODO: move id_pipe_class into the element_type object
221
221
  def id_pipe_class
222
- com.tinkerpop.gremlin.pipes.transform.IdVertexPipe
222
+ com.tinkerpop.pipes.transform.IdVertexPipe
223
223
  end
224
224
  end
225
225
  end
@@ -308,32 +308,37 @@ HELP
308
308
  if Pacer.hide_route_elements or hide_elements or source_iterator.nil?
309
309
  description
310
310
  else
311
- Pacer.hide_route_elements do
312
- count = 0
313
- limit ||= Pacer.inspect_limit
314
- results = collect do |v|
315
- count += 1
316
- return route.inspect if count > limit
317
- v.inspect
318
- end
319
- if count > 0
320
- lens = results.collect { |r| r.length }
321
- max = lens.max
322
- cols = (Pacer.columns / (max + 1).to_f).floor
323
- cols = 1 if cols < 1
324
- if cols == 1
325
- template_part = ['%s']
326
- else
327
- template_part = ["%-#{max}s"]
311
+ graph_read_transaction do
312
+ Pacer.hide_route_elements do
313
+ count = 0
314
+ limit ||= Pacer.inspect_limit
315
+ results = collect do |v|
316
+ count += 1
317
+ if count > limit
318
+ puts "Total: > #{ limit } (Pacer.inspect_limit)"
319
+ return route.inspect
320
+ end
321
+ v.inspect
328
322
  end
329
- template = (template_part * cols).join(' ')
330
- results.each_slice(cols) do |row|
331
- template = (template_part * row.count).join(' ') if row.count < cols
332
- puts template % row
323
+ if count > 0
324
+ lens = results.collect { |r| r.length }
325
+ max = lens.max
326
+ cols = (Pacer.columns / (max + 1).to_f).floor
327
+ cols = 1 if cols < 1
328
+ if cols == 1
329
+ template_part = ['%s']
330
+ else
331
+ template_part = ["%-#{max}s"]
332
+ end
333
+ template = (template_part * cols).join(' ')
334
+ results.each_slice(cols) do |row|
335
+ template = (template_part * row.count).join(' ') if row.count < cols
336
+ puts template % row
337
+ end
333
338
  end
339
+ puts "Total: #{ count }"
340
+ description
334
341
  end
335
- puts "Total: #{ count }"
336
- description
337
342
  end
338
343
  end
339
344
  end
@@ -425,6 +430,8 @@ HELP
425
430
  self
426
431
  elsif @back
427
432
  @back.get_section_route(name)
433
+ elsif @empty_back
434
+ @empty_back.get_section_route(name)
428
435
  else
429
436
  raise ArgumentError, "Section #{ name } not found"
430
437
  end
@@ -601,6 +608,15 @@ HELP
601
608
  s = "#{s} #{ info }" if info
602
609
  s
603
610
  end
611
+
612
+ def graph_read_transaction(&block)
613
+ if graph
614
+ graph.read_transaction &block
615
+ else
616
+ yield
617
+ end
618
+ end
619
+
604
620
  end
605
621
  end
606
622
  end
@@ -26,6 +26,7 @@ module Pacer
26
26
  module Filter
27
27
  module CollectionFilter
28
28
  import java.util.HashSet
29
+ import com.tinkerpop.blueprints.Contains
29
30
 
30
31
  include Pacer::Visitors::VisitsSection
31
32
 
@@ -33,24 +34,24 @@ module Pacer
33
34
 
34
35
  def except=(collection)
35
36
  self.collection = collection
36
- @comparison = Pacer::Pipes::NOT_EQUAL
37
+ @comparison = Contains::NOT_IN
37
38
  end
38
39
 
39
40
  def except_var=(var)
40
41
  @var = var
41
42
  self.section = var
42
- @comparison = Pacer::Pipes::NOT_EQUAL
43
+ @comparison = Contains::NOT_IN
43
44
  end
44
45
 
45
46
  def only=(collection)
46
47
  self.collection = collection
47
- @comparison = Pacer::Pipes::EQUAL
48
+ @comparison = Contains::IN
48
49
  end
49
50
 
50
51
  def only_var=(var)
51
52
  @var = var
52
53
  self.section = var
53
- @comparison = Pacer::Pipes::EQUAL
54
+ @comparison = Contains::IN
54
55
  end
55
56
 
56
57
  protected
@@ -96,7 +97,7 @@ module Pacer
96
97
  end
97
98
 
98
99
  def inspect_class_name
99
- if @comparison == Pacer::Pipes::NOT_EQUAL
100
+ if @comparison == Contains::NOT_IN
100
101
  'Except'
101
102
  else
102
103
  'Only'
@@ -52,6 +52,7 @@ module Pacer
52
52
  protected
53
53
 
54
54
  def after_initialize
55
+ @empty_back = @back
55
56
  @back = @source = nil
56
57
  super
57
58
  end
@@ -25,9 +25,10 @@ module Pacer
25
25
  # a pattern may be nested to varying depths.
26
26
  def repeat(arg, &block)
27
27
  case arg
28
+ when 0
29
+ identity
28
30
  when Fixnum
29
- range = arg..arg
30
- arg.to_enum(:times).inject(self) do |route_end, count|
31
+ (0...arg).inject(self) do |route_end, count|
31
32
  yield route_end
32
33
  end
33
34
  when Range
@@ -53,6 +54,16 @@ module Pacer
53
54
  fail ArgumentError, "Invalid repeat range"
54
55
  end
55
56
  end
57
+
58
+ def breadth_first(opts = {}, &block)
59
+ min_depth = opts.fetch :min_depth, 0
60
+ max_depth = opts.fetch :max_depth, 10
61
+ (min_depth..max_depth).reduce(self) do |route, depth|
62
+ route.branch do |b|
63
+ b.repeat depth, &block
64
+ end
65
+ end.merge_exhaustive
66
+ end
56
67
  end
57
68
  end
58
69
 
@@ -69,7 +69,7 @@ module Pacer
69
69
  module Filter
70
70
  module PropertyFilter
71
71
  #import com.tinkerpop.pipes.filter.LabelCollectionFilterPipe
72
- import com.tinkerpop.gremlin.pipes.filter.PropertyFilterPipe
72
+ import com.tinkerpop.pipes.filter.PropertyFilterPipe
73
73
 
74
74
  def filters=(f)
75
75
  if f.is_a? Filters
@@ -125,7 +125,7 @@ module Pacer
125
125
  when NodeVisitor::Value
126
126
  # no op
127
127
  else
128
- new_pipe = PropertyFilterPipe.new(key, value, Pacer::Pipes::EQUAL)
128
+ new_pipe = PropertyFilterPipe.new(key, Pacer::Pipes::EQUAL, value)
129
129
  end
130
130
  if new_pipe
131
131
  new_pipe.set_starts pipe if pipe
@@ -0,0 +1,56 @@
1
+ module Pacer
2
+ module Routes
3
+ module RouteOperations
4
+ def uniq_in_section(section = nil)
5
+ chain_route filter: Pacer::Filter::UniqueSectionFilter, section: section
6
+ end
7
+ end
8
+ end
9
+
10
+ module Filter
11
+ module UniqueSectionFilter
12
+ # VisitsSection module provides:
13
+ # section=
14
+ # section_visitor
15
+ include Pacer::Visitors::VisitsSection
16
+
17
+ def attach_pipe(end_pipe)
18
+ pipe = UniqueSectionPipe.new(self, section_visitor)
19
+ pipe.setStarts end_pipe if end_pipe
20
+ pipe
21
+ end
22
+
23
+ class UniqueSectionPipe < Pacer::Pipes::RubyPipe
24
+ attr_reader :seen, :section, :route
25
+
26
+
27
+ def initialize(route, section)
28
+ super()
29
+ @seen = Set[]
30
+ @section = section
31
+ @route = route
32
+ section.visitor = self if section
33
+ end
34
+
35
+ def processNextStart
36
+ while true
37
+ element = starts.next
38
+ unless seen.include? element
39
+ seen << element
40
+ return element
41
+ end
42
+ end
43
+ end
44
+
45
+ def after_element
46
+ seen.clear
47
+ end
48
+
49
+ def reset
50
+ seen.clear
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+
@@ -9,7 +9,7 @@ module Pacer
9
9
  import com.tinkerpop.pipes.filter.AndFilterPipe
10
10
  import com.tinkerpop.pipes.filter.OrFilterPipe
11
11
  import com.tinkerpop.pipes.filter.ObjectFilterPipe
12
- import com.tinkerpop.gremlin.pipes.transform.PropertyPipe
12
+ import com.tinkerpop.pipes.transform.PropertyPipe
13
13
  import com.tinkerpop.pipes.transform.HasCountPipe
14
14
  NeverPipe = Pacer::Pipes::NeverPipe
15
15
  IdentityPipe = Pacer::Pipes::IdentityPipe
@@ -19,21 +19,22 @@ module Pacer
19
19
  UnaryTransformPipe = Pacer::Pipes::UnaryTransformPipe
20
20
  BlockFilterPipe = Pacer::Pipes::BlockFilterPipe
21
21
 
22
+ import com.tinkerpop.blueprints.Compare
22
23
  Filters = {
23
- '==' => FilterPipe::Filter::EQUAL,
24
- '=' => FilterPipe::Filter::EQUAL,
25
- '!=' => FilterPipe::Filter::NOT_EQUAL,
26
- '>' => FilterPipe::Filter::GREATER_THAN,
27
- '<' => FilterPipe::Filter::LESS_THAN,
28
- '>=' => FilterPipe::Filter::GREATER_THAN_EQUAL,
29
- '<=' => FilterPipe::Filter::LESS_THAN_EQUAL
24
+ '==' => Compare::EQUAL,
25
+ '=' => Compare::EQUAL,
26
+ '!=' => Compare::NOT_EQUAL,
27
+ '>' => Compare::GREATER_THAN,
28
+ '<' => Compare::LESS_THAN,
29
+ '>=' => Compare::GREATER_THAN_EQUAL,
30
+ '<=' => Compare::LESS_THAN_EQUAL
30
31
  }
31
32
 
32
33
  ReverseFilters = Filters.merge(
33
- '<' => FilterPipe::Filter::GREATER_THAN,
34
- '>' => FilterPipe::Filter::LESS_THAN,
35
- '<=' => FilterPipe::Filter::GREATER_THAN_EQUAL,
36
- '>=' => FilterPipe::Filter::LESS_THAN_EQUAL
34
+ '<' => Compare::GREATER_THAN,
35
+ '>' => Compare::LESS_THAN,
36
+ '<=' => Compare::GREATER_THAN_EQUAL,
37
+ '>=' => Compare::LESS_THAN_EQUAL
37
38
  )
38
39
 
39
40
  COMPARATORS = %w[ == != > < >= <= ]