hyperion-riak 0.1.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.
- data/lib/hyperion/riak.rb +9 -0
- data/lib/hyperion/riak/datastore.rb +138 -0
- data/lib/hyperion/riak/map_reduce_js.rb +53 -0
- data/lib/hyperion/riak/optimized_filter_order.rb +111 -0
- data/lib/hyperion/riak/optimized_range_filters.rb +42 -0
- data/lib/hyperion/riak/spec_helper.rb +26 -0
- data/spec/hyperion/riak/optimized_filter_order_spec.rb +122 -0
- data/spec/hyperion/riak/optimized_range_filters_spec.rb +87 -0
- data/spec/hyperion/riak_spec.rb +12 -0
- metadata +106 -0
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'hyperion'
|
2
|
+
require 'hyperion/key'
|
3
|
+
require 'hyperion/riak/map_reduce_js'
|
4
|
+
require 'riak'
|
5
|
+
require 'hyperion/riak/optimized_filter_order'
|
6
|
+
|
7
|
+
module Hyperion
|
8
|
+
module Riak
|
9
|
+
class Datastore
|
10
|
+
def initialize(opts={})
|
11
|
+
opts ||= {}
|
12
|
+
@app = opts[:app] || ''
|
13
|
+
@client = ::Riak::Client.new(opts.reject {|k, v| k == :app})
|
14
|
+
@buckets = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def save(records)
|
18
|
+
records.map do |record|
|
19
|
+
Hyperion.new?(record) ? create(record) : update(record)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def find_by_key(key)
|
24
|
+
kind, riak_key = Hyperion::Key.decompose_key(key)
|
25
|
+
robject = bucket(kind).get(riak_key)
|
26
|
+
record_from_db(kind, robject.key, robject.data)
|
27
|
+
end
|
28
|
+
|
29
|
+
def find(query)
|
30
|
+
mr = new_mapreduce_with_returned_result(query)
|
31
|
+
mr.run.map do |record|
|
32
|
+
record_from_db(query.kind, record.delete('riak_key'), record)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete_by_key(key)
|
37
|
+
kind, riak_key = Hyperion::Key.decompose_key(key)
|
38
|
+
delete_with_riak_key(kind, riak_key)
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def delete(query)
|
43
|
+
mr = new_mapreduce_with_returned_result(query)
|
44
|
+
mr.run.each do |record|
|
45
|
+
delete_with_riak_key(query.kind, record['riak_key'])
|
46
|
+
end
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def count(query)
|
51
|
+
mr = new_mapreduce(query)
|
52
|
+
mr.reduce(MapReduceJs.count, :keep => true)
|
53
|
+
mr.run.first
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def create(record)
|
59
|
+
kind = record[:kind]
|
60
|
+
robject = bucket(kind).new
|
61
|
+
store(kind, robject, record_to_db(record))
|
62
|
+
end
|
63
|
+
|
64
|
+
def update(record)
|
65
|
+
kind, riak_key = Hyperion::Key.decompose_key(record[:key])
|
66
|
+
robject = bucket(kind).get(riak_key)
|
67
|
+
store(kind, robject, robject.data.merge(record_to_db(record)))
|
68
|
+
end
|
69
|
+
|
70
|
+
def store(kind, robject, record_data)
|
71
|
+
robject.data = record_data
|
72
|
+
robject.indexes = record_data_to_index(record_data)
|
73
|
+
robject.store
|
74
|
+
record_from_db(kind, robject.key, robject.data)
|
75
|
+
end
|
76
|
+
|
77
|
+
def record_data_to_index(data)
|
78
|
+
data.reduce({}) do |new_record, (key, value)|
|
79
|
+
new_record[index_name(key)] = value
|
80
|
+
new_record
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def delete_with_riak_key(kind, key)
|
85
|
+
bucket(kind).delete(key)
|
86
|
+
end
|
87
|
+
|
88
|
+
def new_mapreduce(query)
|
89
|
+
mr = ::Riak::MapReduce.new(@client)
|
90
|
+
add_query_filters(mr, query)
|
91
|
+
sorts = query.sorts
|
92
|
+
mr.reduce(MapReduceJs.sort(sorts)) unless sorts.empty?
|
93
|
+
mr.reduce(MapReduceJs.offset(query.offset)) if query.offset
|
94
|
+
mr.reduce(MapReduceJs.limit(query.limit)) if query.limit
|
95
|
+
mr
|
96
|
+
end
|
97
|
+
|
98
|
+
def add_query_filters(mr, query)
|
99
|
+
bucket_name = bucket_name(query.kind)
|
100
|
+
optimizer = OptimizedFilterOrder.new(query.filters, bucket_name)
|
101
|
+
field_index = index_name(optimizer.optimal_index_field)
|
102
|
+
mr.index(bucket_name, field_index, optimizer.optimal_index_value)
|
103
|
+
mr.map(MapReduceJs.filter(optimizer.filters))
|
104
|
+
end
|
105
|
+
|
106
|
+
def index_name(field_name)
|
107
|
+
if field_name != '$bucket'
|
108
|
+
"#{field_name}_bin"
|
109
|
+
else
|
110
|
+
field_name
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def new_mapreduce_with_returned_result(query)
|
115
|
+
mr = new_mapreduce(query)
|
116
|
+
mr.reduce(MapReduceJs.pass_thru, :keep => true)
|
117
|
+
end
|
118
|
+
|
119
|
+
def record_to_db(record)
|
120
|
+
record.reject {|k, v| [:kind, :key].include?(k)}
|
121
|
+
end
|
122
|
+
|
123
|
+
def record_from_db(kind, riak_key, data)
|
124
|
+
key = Hyperion::Key.compose_key(kind, riak_key)
|
125
|
+
data.merge(:kind => kind, :key => key)
|
126
|
+
end
|
127
|
+
|
128
|
+
def bucket(kind)
|
129
|
+
name = bucket_name(kind)
|
130
|
+
@buckets[name] ||= @client.bucket(name)
|
131
|
+
end
|
132
|
+
|
133
|
+
def bucket_name(kind)
|
134
|
+
@app.to_s + kind
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Hyperion
|
4
|
+
module Riak
|
5
|
+
class MapReduceJs
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def filter(filters)
|
9
|
+
template(:filter).result(binding)
|
10
|
+
end
|
11
|
+
|
12
|
+
def sort(sorts)
|
13
|
+
template(:sort).result(binding)
|
14
|
+
end
|
15
|
+
|
16
|
+
def offset(offset)
|
17
|
+
template(:offset).result(binding)
|
18
|
+
end
|
19
|
+
|
20
|
+
def offset(offset)
|
21
|
+
template(:offset).result(binding)
|
22
|
+
end
|
23
|
+
|
24
|
+
def limit(limit)
|
25
|
+
template(:limit).result(binding)
|
26
|
+
end
|
27
|
+
|
28
|
+
def count
|
29
|
+
template(:count).result
|
30
|
+
end
|
31
|
+
|
32
|
+
def pass_thru
|
33
|
+
template(:pass_thru).result
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def template(name)
|
39
|
+
@templates ||= {}
|
40
|
+
@templates[name] ||= ERB.new(file_contents(template_path(name)))
|
41
|
+
end
|
42
|
+
|
43
|
+
def template_path(name)
|
44
|
+
File.expand_path(File.join('..', 'map_reduce', "#{name}.js.erb"), __FILE__)
|
45
|
+
end
|
46
|
+
|
47
|
+
def file_contents(path)
|
48
|
+
File.open(path, 'rb') { |f| f.read }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'hyperion/riak/optimized_range_filters'
|
2
|
+
|
3
|
+
module Hyperion
|
4
|
+
module Riak
|
5
|
+
class OptimizedFilterOrder
|
6
|
+
|
7
|
+
def initialize(filters, bucket_name)
|
8
|
+
@bucket_name = bucket_name
|
9
|
+
@filters = filters
|
10
|
+
end
|
11
|
+
|
12
|
+
def optimal_strategy
|
13
|
+
@optimal_strategy ||= OPTIMAL_ORDER.map do |strategy_klass|
|
14
|
+
strategy_klass.new(@filters, @bucket_name)
|
15
|
+
end.find do |strategy|
|
16
|
+
strategy.can_optimize?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def optimal_index_field
|
21
|
+
optimal_strategy.optimal_index_field
|
22
|
+
end
|
23
|
+
|
24
|
+
def optimal_index_value
|
25
|
+
optimal_strategy.optimal_index_value
|
26
|
+
end
|
27
|
+
|
28
|
+
def filters
|
29
|
+
optimal_strategy.filters
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
end
|
34
|
+
|
35
|
+
class EqualsStrategy
|
36
|
+
def initialize(filters, bucket_name)
|
37
|
+
@filters = filters
|
38
|
+
end
|
39
|
+
|
40
|
+
def can_optimize?
|
41
|
+
!first_equals_filter.nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
def optimal_index_field
|
45
|
+
first_equals_filter.field
|
46
|
+
end
|
47
|
+
|
48
|
+
def optimal_index_value
|
49
|
+
first_equals_filter.value.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
def filters
|
53
|
+
@remaining_filters ||= (@filters - [first_equals_filter])
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def first_equals_filter
|
59
|
+
@first_equals_filter ||= @filters.find { |f| f.operator == "=" }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class RangeStrategy
|
64
|
+
def initialize(filters, bucket_name)
|
65
|
+
@filters = filters
|
66
|
+
@optimizer = OptimizedRangeFilters.new(filters)
|
67
|
+
end
|
68
|
+
|
69
|
+
def can_optimize?
|
70
|
+
@optimizer.less_than_filter && @optimizer.greater_than_filter
|
71
|
+
end
|
72
|
+
|
73
|
+
def optimal_index_field
|
74
|
+
@optimizer.less_than_filter.field
|
75
|
+
end
|
76
|
+
|
77
|
+
def optimal_index_value
|
78
|
+
@value ||= @optimizer.less_than_filter.value.to_s .. @optimizer.greater_than_filter.value.to_s
|
79
|
+
end
|
80
|
+
|
81
|
+
def filters
|
82
|
+
@optimizer.remaining_filters
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class BucketStrategy
|
87
|
+
def initialize(filters, bucket_name)
|
88
|
+
@filters = filters
|
89
|
+
@bucket_name = bucket_name
|
90
|
+
end
|
91
|
+
|
92
|
+
def can_optimize?
|
93
|
+
true
|
94
|
+
end
|
95
|
+
|
96
|
+
def optimal_index_field
|
97
|
+
'$bucket'
|
98
|
+
end
|
99
|
+
|
100
|
+
def optimal_index_value
|
101
|
+
@bucket_name
|
102
|
+
end
|
103
|
+
|
104
|
+
def filters
|
105
|
+
@filters
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
OPTIMAL_ORDER = [EqualsStrategy, RangeStrategy, BucketStrategy]
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Hyperion
|
2
|
+
module Riak
|
3
|
+
class OptimizedRangeFilters
|
4
|
+
def initialize(filters)
|
5
|
+
@filters = filters
|
6
|
+
end
|
7
|
+
|
8
|
+
def remaining_filters
|
9
|
+
@remaining_filters ||= @filters - [less_than_filter, greater_than_filter]
|
10
|
+
end
|
11
|
+
|
12
|
+
def less_than_filter
|
13
|
+
@less_than_filter ||= find_first_match(less_than_candidates, greater_than_candidates)
|
14
|
+
end
|
15
|
+
|
16
|
+
def greater_than_filter
|
17
|
+
@greater_than_filter ||= find_first_match(greater_than_candidates, less_than_candidates)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def find_first_match(filters_to_search, filters_to_match_against)
|
23
|
+
filters_to_search.find do |f1|
|
24
|
+
filters_to_match_against.any? { |f2| f1.field == f2.field }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def greater_than_candidates
|
29
|
+
@greater_than_filters ||= @filters.select do |filter|
|
30
|
+
filter.operator == '>'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def less_than_candidates
|
35
|
+
@less_than_candidates ||= @filters.select do |filter|
|
36
|
+
filter.operator == '<'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
def test_app_name
|
2
|
+
'_HTEST_'
|
3
|
+
end
|
4
|
+
|
5
|
+
BUCKETS = ['testing', 'other_testing']
|
6
|
+
|
7
|
+
def empty_buckets(ds)
|
8
|
+
client = ds.instance_variable_get(:@client)
|
9
|
+
BUCKETS.each do |bucket_name|
|
10
|
+
bucket_name = ds.send(:bucket_name, bucket_name)
|
11
|
+
bucket = client.bucket(bucket_name)
|
12
|
+
bucket.get_index('$bucket', bucket_name).each do |key|
|
13
|
+
bucket.delete(key)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def with_testable_riak_datastore
|
19
|
+
ds = Hyperion.new_datastore(:riak, :app => test_app_name, :protocol => :pbc)
|
20
|
+
around :each do |example|
|
21
|
+
Hyperion.datastore = ds
|
22
|
+
example.run
|
23
|
+
empty_buckets(ds)
|
24
|
+
Hyperion.datastore = nil
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'hyperion/filter'
|
2
|
+
require 'hyperion/riak/optimized_filter_order'
|
3
|
+
|
4
|
+
describe Hyperion::Riak::OptimizedFilterOrder do
|
5
|
+
|
6
|
+
def filter(field, operator, value)
|
7
|
+
Hyperion::Filter.new(field, operator, value)
|
8
|
+
end
|
9
|
+
|
10
|
+
context "no filters" do
|
11
|
+
it 'returns "$bucket" for optimal_index_field' do
|
12
|
+
filters = []
|
13
|
+
bucket_name = "hamburgers"
|
14
|
+
|
15
|
+
o = described_class.new(filters, bucket_name)
|
16
|
+
o.optimal_index_field.should == '$bucket'
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'returns bucket name for optimal_index_value' do
|
20
|
+
filters = []
|
21
|
+
bucket_name = "hamburgers"
|
22
|
+
|
23
|
+
o = described_class.new(filters, bucket_name)
|
24
|
+
o.optimal_index_value.should == bucket_name
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'returns an empty collection for filters' do
|
28
|
+
filters = []
|
29
|
+
bucket_name = "hamburgers"
|
30
|
+
|
31
|
+
o = described_class.new(filters, bucket_name)
|
32
|
+
o.filters.should == []
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'optimizes for an equals filter' do
|
37
|
+
it 'returns the index field' do
|
38
|
+
filters = [filter(:int, '=', 1)]
|
39
|
+
o = described_class.new(filters, '')
|
40
|
+
o.optimal_index_field.should == :int
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'returns bucket name for optimal_index_value' do
|
44
|
+
filters = [filter(:int, '=', 1)]
|
45
|
+
bucket_name = "hamburgers"
|
46
|
+
|
47
|
+
o = described_class.new(filters, bucket_name)
|
48
|
+
o.optimal_index_value.should == '1'
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'excludes the optimized filter' do
|
52
|
+
filters = [filter(:int, '=', 1)]
|
53
|
+
o = described_class.new(filters, '')
|
54
|
+
o.filters.should be_empty
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'optimizes for a range filter' do
|
59
|
+
it 'returns the index field' do
|
60
|
+
filters = [filter(:int, '<', 1), filter(:int, '>', 2), filter(:int, '?', 3)]
|
61
|
+
o = described_class.new(filters, '')
|
62
|
+
o.optimal_index_field.should == :int
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'returns bucket name for optimal_index_value' do
|
66
|
+
filters = [filter(:int, '<', 1), filter(:int, '>', 2), filter(:int, '?', 3)]
|
67
|
+
|
68
|
+
o = described_class.new(filters, '')
|
69
|
+
o.optimal_index_value.should == ('1'..'2')
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'excludes the optimized filters' do
|
73
|
+
leftover_filter = filter(:int, '?', 3)
|
74
|
+
filters = [filter(:int, '<', 1), filter(:int, '>', 2), leftover_filter]
|
75
|
+
o = described_class.new(filters, '')
|
76
|
+
o.filters.should == [leftover_filter]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'cannot be optimized' do
|
81
|
+
it 'returns "$bucket" for index_field if no optimal filter' do
|
82
|
+
filters = [filter(:int, '?', 1)]
|
83
|
+
o = described_class.new(filters, '')
|
84
|
+
o.optimal_index_field.should == '$bucket'
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'returns bucket name for optimal_index_value' do
|
88
|
+
filters = [filter(:int, '?', 1)]
|
89
|
+
o = described_class.new(filters, 'cheeseburgers')
|
90
|
+
o.optimal_index_value.should == 'cheeseburgers'
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'filters contains all filters' do
|
94
|
+
filters = [filter(:int, '?', 1)]
|
95
|
+
o = described_class.new(filters, '')
|
96
|
+
o.filters.should == filters
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'chooses equals filter over bucket' do
|
101
|
+
equal_filter = filter(:data, '=', 3)
|
102
|
+
o = described_class.new([equal_filter], '')
|
103
|
+
o.optimal_index_field.should == :data
|
104
|
+
o.optimal_index_value.should == '3'
|
105
|
+
o.filters.should == []
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'chooses equals filter over range filter' do
|
109
|
+
equal_filter = filter(:data, '=', 3)
|
110
|
+
filters = [filter(:int, '<', 1), filter(:int, '>', 2)]
|
111
|
+
o = described_class.new(filters + [equal_filter], '')
|
112
|
+
o.optimal_index_field.should == :data
|
113
|
+
o.optimal_index_value.should == '3'
|
114
|
+
o.filters.should == filters
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'chooses range filter over bucket filter' do
|
118
|
+
filters = [filter(:int, '<', 1), filter(:int, '>', 2)]
|
119
|
+
o = described_class.new(filters, '')
|
120
|
+
o.optimal_index_field.should == :int
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'hyperion/filter'
|
2
|
+
require 'hyperion/riak/optimized_range_filters'
|
3
|
+
|
4
|
+
describe Hyperion::Riak::OptimizedRangeFilters do
|
5
|
+
let(:test_less_than_filter) { filter(:test, '<', 1) }
|
6
|
+
let(:test_greater_than_filter) { filter(:test, '>', 1) }
|
7
|
+
let(:test_equals_filter) { filter(:test, '=', 1) }
|
8
|
+
let(:other_test_less_than_filter) { filter(:other_test, '<', 1) }
|
9
|
+
let(:other_test_greater_than_filter) { filter(:other_test, '>', 1) }
|
10
|
+
|
11
|
+
it 'returns the filters' do
|
12
|
+
optimizer = described_class.new([])
|
13
|
+
optimizer.remaining_filters.should == []
|
14
|
+
end
|
15
|
+
|
16
|
+
def filter(field, operator, value)
|
17
|
+
Hyperion::Filter.new(field, operator, value)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'returns the optimal filters' do
|
21
|
+
optimizer = described_class.new([test_less_than_filter, test_greater_than_filter])
|
22
|
+
optimizer.less_than_filter.should == test_less_than_filter
|
23
|
+
optimizer.greater_than_filter.should == test_greater_than_filter
|
24
|
+
optimizer.remaining_filters.should == []
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'returns the optimal filters' do
|
28
|
+
optimizer = described_class.new([test_greater_than_filter, test_less_than_filter])
|
29
|
+
optimizer.less_than_filter.should == test_less_than_filter
|
30
|
+
optimizer.greater_than_filter.should == test_greater_than_filter
|
31
|
+
optimizer.remaining_filters.should == []
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'returns non-range filter as remaining filter' do
|
35
|
+
optimizer = described_class.new([
|
36
|
+
test_greater_than_filter,
|
37
|
+
test_less_than_filter,
|
38
|
+
test_equals_filter
|
39
|
+
])
|
40
|
+
optimizer.remaining_filters.should == [test_equals_filter]
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'does not return a less than filter if there is no greater than' do
|
44
|
+
filters = [test_less_than_filter, test_equals_filter]
|
45
|
+
optimizer = described_class.new(filters)
|
46
|
+
optimizer.remaining_filters.should == filters
|
47
|
+
optimizer.less_than_filter.should be_nil
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'does not return a less than filter if there is no matching greater than' do
|
51
|
+
filters = [
|
52
|
+
filter(:test, '<', 1),
|
53
|
+
filter(:other_test, '>', 1),
|
54
|
+
test_equals_filter
|
55
|
+
]
|
56
|
+
optimizer = described_class.new(filters)
|
57
|
+
optimizer.remaining_filters.should == filters
|
58
|
+
optimizer.less_than_filter.should be_nil
|
59
|
+
optimizer.greater_than_filter.should be_nil
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'returns a matching range when there are other candidates' do
|
63
|
+
filters = [
|
64
|
+
test_less_than_filter,
|
65
|
+
other_test_less_than_filter,
|
66
|
+
other_test_greater_than_filter,
|
67
|
+
test_equals_filter
|
68
|
+
]
|
69
|
+
optimizer = described_class.new(filters)
|
70
|
+
optimizer.remaining_filters.should == [test_less_than_filter, test_equals_filter]
|
71
|
+
optimizer.less_than_filter.should == other_test_less_than_filter
|
72
|
+
optimizer.greater_than_filter.should == other_test_greater_than_filter
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'returns a matching range when there are other candidates' do
|
76
|
+
filters = [
|
77
|
+
test_greater_than_filter,
|
78
|
+
other_test_greater_than_filter,
|
79
|
+
test_less_than_filter,
|
80
|
+
test_equals_filter
|
81
|
+
]
|
82
|
+
optimizer = described_class.new(filters)
|
83
|
+
optimizer.remaining_filters.should == [other_test_greater_than_filter, test_equals_filter]
|
84
|
+
optimizer.less_than_filter.should == test_less_than_filter
|
85
|
+
optimizer.greater_than_filter.should == test_greater_than_filter
|
86
|
+
end
|
87
|
+
end
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hyperion-riak
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Myles Megyesi
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-17 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - '='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.11.0
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - '='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.11.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: hyperion-api
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - '='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.1.0
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - '='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.1.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: riak-client
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - '='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.0.4
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.0.4
|
62
|
+
description: Riak datastore for Hyperion
|
63
|
+
email:
|
64
|
+
- myles@8thlight.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- lib/hyperion/riak.rb
|
70
|
+
- lib/hyperion/riak/map_reduce_js.rb
|
71
|
+
- lib/hyperion/riak/optimized_filter_order.rb
|
72
|
+
- lib/hyperion/riak/optimized_range_filters.rb
|
73
|
+
- lib/hyperion/riak/spec_helper.rb
|
74
|
+
- lib/hyperion/riak/datastore.rb
|
75
|
+
- spec/hyperion/riak_spec.rb
|
76
|
+
- spec/hyperion/riak/optimized_filter_order_spec.rb
|
77
|
+
- spec/hyperion/riak/optimized_range_filters_spec.rb
|
78
|
+
homepage: https://github.com/mylesmegyesi/hyperion-ruby
|
79
|
+
licenses:
|
80
|
+
- Eclipse Public License
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ! '>='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 1.8.7
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 1.8.24
|
100
|
+
signing_key:
|
101
|
+
specification_version: 3
|
102
|
+
summary: Riak datastore for Hypeiron
|
103
|
+
test_files:
|
104
|
+
- spec/hyperion/riak_spec.rb
|
105
|
+
- spec/hyperion/riak/optimized_filter_order_spec.rb
|
106
|
+
- spec/hyperion/riak/optimized_range_filters_spec.rb
|