hari 0.0.1 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.travis.yml +15 -0
- data/LICENSE +15 -19
- data/README.md +22 -0
- data/hari.gemspec +2 -1
- data/lib/hari.rb +56 -1
- data/lib/hari/entity.rb +8 -11
- data/lib/hari/entity/property.rb +2 -2
- data/lib/hari/entity/property/builder.rb +9 -1
- data/lib/hari/entity/repository.rb +3 -2
- data/lib/hari/entity/serialization/float.rb +2 -0
- data/lib/hari/entity/serialization/integer.rb +3 -10
- data/lib/hari/node.rb +20 -3
- data/lib/hari/node/queries.rb +33 -0
- data/lib/hari/node/queries/list.rb +147 -0
- data/lib/hari/node/queries/relation.rb +100 -0
- data/lib/hari/node/queries/relation/backend/list.rb +35 -0
- data/lib/hari/node/queries/relation/backend/sorted_set.rb +95 -0
- data/lib/hari/node/queries/relation/runnable.rb +24 -0
- data/lib/hari/node/queries/relation/start.rb +19 -0
- data/lib/hari/node/queries/relation/step.rb +17 -0
- data/lib/hari/node/queries/set.rb +116 -0
- data/lib/hari/node/queries/sorted_set.rb +130 -0
- data/lib/hari/node/repository.rb +24 -0
- data/lib/hari/node/serialization.rb +24 -0
- data/lib/hari/relation.rb +72 -0
- data/lib/hari/relation/linked_list.rb +16 -0
- data/lib/hari/relation/sorted_set.rb +16 -0
- data/lib/hari/version.rb +1 -1
- data/spec/hari/configuration_spec.rb +5 -0
- data/spec/hari/entity/repository_spec.rb +4 -4
- data/spec/hari/entity/serialization_spec.rb +2 -2
- data/spec/hari/node/lists_spec.rb +126 -0
- data/spec/hari/node/sets_spec.rb +85 -0
- data/spec/hari/node/sorted_sets_spec.rb +89 -0
- data/spec/hari/node_spec.rb +79 -0
- data/spec/hari_spec.rb +37 -0
- data/spec/spec_helper.rb +11 -1
- metadata +48 -32
- data/lib/hari/relationship.rb +0 -72
- data/lib/hari/relationship/linked_list.rb +0 -22
- data/lib/hari/relationship/sorted_set.rb +0 -22
- data/lib/hari/scripts.rb +0 -26
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'hari/node/queries/relation/step'
|
2
|
+
require 'hari/node/queries/relation/start'
|
3
|
+
require 'hari/node/queries/relation/runnable'
|
4
|
+
require 'hari/node/queries/relation/backend/sorted_set'
|
5
|
+
|
6
|
+
module Hari
|
7
|
+
class Node < Entity
|
8
|
+
module Queries
|
9
|
+
class Relation
|
10
|
+
include Relation::Step
|
11
|
+
include Relation::Runnable
|
12
|
+
|
13
|
+
attr_reader :parent, :direction, :relation, :level, :options
|
14
|
+
|
15
|
+
def initialize(parent, direction, relation, *args)
|
16
|
+
@parent, @direction, @relation = parent, direction, relation
|
17
|
+
@level = parent.level + 1
|
18
|
+
@options = {}
|
19
|
+
args.extract_options!.each { |k, v| send k, v }
|
20
|
+
|
21
|
+
@options[:backend] = args.first.presence || Backend::SortedSet
|
22
|
+
end
|
23
|
+
|
24
|
+
def backend
|
25
|
+
options[:backend]
|
26
|
+
end
|
27
|
+
|
28
|
+
def calculate_limit
|
29
|
+
options[:limit] ? (options[:limit].to_i - 1) : -1
|
30
|
+
end
|
31
|
+
|
32
|
+
%w(limit from step).each do |method|
|
33
|
+
define_method method do |value|
|
34
|
+
options[method.to_sym] = value
|
35
|
+
self
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# TODO for later, filter by node type
|
40
|
+
def types(*types)
|
41
|
+
options[:types] = types
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
alias :type :types
|
46
|
+
|
47
|
+
%w(nodes_ids relations_ids nodes).each do |result_type|
|
48
|
+
define_method result_type do
|
49
|
+
options[:result_type] = result_type.to_sym
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
define_method "#{result_type}!" do
|
54
|
+
send result_type
|
55
|
+
result
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
alias :nids :nodes_ids
|
60
|
+
alias :rel_ids :relations_ids
|
61
|
+
alias :rids :relations_ids
|
62
|
+
|
63
|
+
def count
|
64
|
+
options[:result_type] = :count
|
65
|
+
result
|
66
|
+
end
|
67
|
+
|
68
|
+
def start_node
|
69
|
+
level == 1 ? parent.node : parent.start_node
|
70
|
+
end
|
71
|
+
|
72
|
+
def call(final = true)
|
73
|
+
if level == 1
|
74
|
+
backend.fetch parent.node, call_args(final)
|
75
|
+
else
|
76
|
+
backend.step start_node, parent.call(false), call_args(final)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def call_args(final = true)
|
81
|
+
{
|
82
|
+
relation: relation,
|
83
|
+
direction: direction,
|
84
|
+
limit: calculate_limit,
|
85
|
+
from: options[:from],
|
86
|
+
step: options[:step],
|
87
|
+
result: result_type(final)
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
def result_type(final = false)
|
92
|
+
return :nodes_ids unless final
|
93
|
+
|
94
|
+
options.fetch :result_type, :nodes
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Hari::Node::Queries::Relation
|
2
|
+
module Backend
|
3
|
+
module List
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def fetch(node, options = {})
|
7
|
+
list = node.list list_name(options)
|
8
|
+
send "fetch_#{options[:result]}", list, options
|
9
|
+
end
|
10
|
+
|
11
|
+
def fetch_relations_ids(list, options = {})
|
12
|
+
start = options.fetch(:from, 0)
|
13
|
+
stop = options.limit(:limit, -1)
|
14
|
+
|
15
|
+
list.range start, stop
|
16
|
+
end
|
17
|
+
|
18
|
+
def fetch_nodes_ids(list, options)
|
19
|
+
index = list.name =~ /in$/ ? 1 : 2
|
20
|
+
fetch_relations_ids(list, options).map { |r| r.split(':')[index] }
|
21
|
+
end
|
22
|
+
|
23
|
+
def fetch_count(list, options)
|
24
|
+
list.count
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def list_name(options)
|
30
|
+
"#{options[:relation]}:#{options[:direction]}"
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
class Hari::Node::Queries::Relation
|
2
|
+
module Backend
|
3
|
+
module SortedSet
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def fetch(node, options = {})
|
7
|
+
set = node.sorted_set set_name(options)
|
8
|
+
send "fetch_#{options[:result]}", set, options
|
9
|
+
end
|
10
|
+
|
11
|
+
def fetch_relations_ids(set, options)
|
12
|
+
from, limit = options.values_at(:from, :limit)
|
13
|
+
|
14
|
+
if from.present?
|
15
|
+
set.range_by_score from, '+inf', desc: true, limit: [0, limit]
|
16
|
+
else
|
17
|
+
set.range from, limit, desc: true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch_nodes_ids(set, options)
|
22
|
+
index = set.name =~ /in$/ ? 0 : 2
|
23
|
+
fetch_relations_ids(set, options).map { |r| r.split(':')[index] }
|
24
|
+
end
|
25
|
+
|
26
|
+
def fetch_nodes(set, options)
|
27
|
+
nodes_ids = fetch_nodes_ids(set, options)
|
28
|
+
nodes_ids.empty? ? [] : Hari.redis.mget(nodes_ids)
|
29
|
+
end
|
30
|
+
|
31
|
+
def fetch_count(set, options)
|
32
|
+
set.count
|
33
|
+
end
|
34
|
+
|
35
|
+
def step(start_node, nodes_ids, options = {})
|
36
|
+
stream = start_node.sorted_set("stream:#{SecureRandom.hex(6)}")
|
37
|
+
direction = options[:direction] == :in ? 0 : 2
|
38
|
+
limit = options.fetch(:limit, -1)
|
39
|
+
|
40
|
+
nodes_ids.each_with_index do |node_id, index|
|
41
|
+
prune, stop = true, options.fetch(:step, 5)
|
42
|
+
|
43
|
+
if limit == -1 || stream.count < limit
|
44
|
+
prune, stop = false, limit
|
45
|
+
end
|
46
|
+
|
47
|
+
start, digg = 0, true
|
48
|
+
|
49
|
+
while digg
|
50
|
+
set = Hari(node_id).sorted_set set_name(options)
|
51
|
+
|
52
|
+
if from = options[:from].presence
|
53
|
+
scored_relations_ids = set.range_by_score(from, '+inf', desc: true, with_scores: true, limit: [start, stop])
|
54
|
+
else
|
55
|
+
scored_relations_ids = set.range(start, stop, desc: true, with_scores: true)
|
56
|
+
end
|
57
|
+
|
58
|
+
break if scored_relations_ids.empty?
|
59
|
+
|
60
|
+
scored_nodes_ids = scored_relations_ids.map { |(r, s)| [s, r.split(':')[direction]] }.flatten
|
61
|
+
stream.add *scored_nodes_ids
|
62
|
+
|
63
|
+
last_node_id = scored_nodes_ids.last
|
64
|
+
|
65
|
+
if prune
|
66
|
+
stream.trim_by_rank 0, stop
|
67
|
+
|
68
|
+
unless stream.include? last_node_id
|
69
|
+
digg = false
|
70
|
+
start += stop + 1
|
71
|
+
end
|
72
|
+
else
|
73
|
+
digg = false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
nodes_ids = stream.revrange
|
79
|
+
|
80
|
+
if nodes_ids.any? && options[:result] == :nodes
|
81
|
+
Hari.redis.mget nodes_ids
|
82
|
+
else
|
83
|
+
nodes_ids
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def set_name(options)
|
90
|
+
"#{options[:relation]}:#{options[:direction]}"
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Hari
|
2
|
+
class Node < Entity
|
3
|
+
module Queries
|
4
|
+
class Relation
|
5
|
+
module Runnable
|
6
|
+
|
7
|
+
def result
|
8
|
+
result = call(true)
|
9
|
+
|
10
|
+
case result_type(true)
|
11
|
+
when :nodes
|
12
|
+
result.map &Hari::Node.method(:from_source)
|
13
|
+
else
|
14
|
+
result
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
alias :to_a :result
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Hari
|
2
|
+
class Node < Entity
|
3
|
+
module Queries
|
4
|
+
class Set
|
5
|
+
|
6
|
+
attr_reader :node, :name
|
7
|
+
|
8
|
+
def initialize(node = nil)
|
9
|
+
@node = node
|
10
|
+
end
|
11
|
+
|
12
|
+
def key
|
13
|
+
@key ||= begin
|
14
|
+
prefix = node ? "#{Hari.node_key(node)}:" : ''
|
15
|
+
prefix + name.to_s
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def set(name)
|
20
|
+
@name = name
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def set!(name)
|
25
|
+
@name = name
|
26
|
+
members
|
27
|
+
end
|
28
|
+
|
29
|
+
def members
|
30
|
+
Hari.redis.smembers key
|
31
|
+
end
|
32
|
+
|
33
|
+
def rand(count = 1)
|
34
|
+
Hari.redis.srandmember key, count
|
35
|
+
end
|
36
|
+
|
37
|
+
def count
|
38
|
+
Hari.redis.scard key
|
39
|
+
end
|
40
|
+
|
41
|
+
alias :size :count
|
42
|
+
alias :length :count
|
43
|
+
|
44
|
+
def empty?
|
45
|
+
count == 0
|
46
|
+
end
|
47
|
+
|
48
|
+
def one?
|
49
|
+
count == 1
|
50
|
+
end
|
51
|
+
|
52
|
+
def many?
|
53
|
+
count > 1
|
54
|
+
end
|
55
|
+
|
56
|
+
def include?(member)
|
57
|
+
Hari.redis.sismember key, member
|
58
|
+
end
|
59
|
+
|
60
|
+
alias :member? :include?
|
61
|
+
|
62
|
+
def add(*members)
|
63
|
+
Hari.redis.sadd key, members
|
64
|
+
end
|
65
|
+
|
66
|
+
def <<(member)
|
67
|
+
add member
|
68
|
+
end
|
69
|
+
|
70
|
+
def delete(*members)
|
71
|
+
Hari.redis.srem key, members
|
72
|
+
end
|
73
|
+
|
74
|
+
def pop
|
75
|
+
Hari.redis.spop key
|
76
|
+
end
|
77
|
+
|
78
|
+
def intersect(*set_queries)
|
79
|
+
Hari.redis.sinter key, set_query_keys(set_queries)
|
80
|
+
end
|
81
|
+
|
82
|
+
def &(other_set_query)
|
83
|
+
intersect other_set_query
|
84
|
+
end
|
85
|
+
|
86
|
+
def diff(*set_queries)
|
87
|
+
Hari.redis.sdiff key, set_query_keys(set_queries)
|
88
|
+
end
|
89
|
+
|
90
|
+
def -(other_set_query)
|
91
|
+
diff other_set_query
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def set_query_keys(set_queries)
|
97
|
+
keys = set_queries.map do |query|
|
98
|
+
ensure_set_query! query
|
99
|
+
query.key
|
100
|
+
end
|
101
|
+
|
102
|
+
fail 'no query keys' if keys.empty?
|
103
|
+
|
104
|
+
keys
|
105
|
+
end
|
106
|
+
|
107
|
+
def ensure_set_query!(query)
|
108
|
+
unless query.kind_of?(Hari::Node::Queries::Set)
|
109
|
+
fail 'not a set query'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Hari
|
2
|
+
class Node < Entity
|
3
|
+
module Queries
|
4
|
+
class SortedSet
|
5
|
+
|
6
|
+
attr_reader :node, :name
|
7
|
+
|
8
|
+
def initialize(node = nil)
|
9
|
+
@node = node
|
10
|
+
end
|
11
|
+
|
12
|
+
def key
|
13
|
+
@key ||= begin
|
14
|
+
prefix = node ? "#{Hari.node_key(node)}:" : ''
|
15
|
+
prefix + name.to_s
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def sorted_set(name)
|
20
|
+
@name = name
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def sorted_set!(name)
|
25
|
+
@name = name
|
26
|
+
members
|
27
|
+
end
|
28
|
+
|
29
|
+
def range(start = 0, stop = -1, options = {})
|
30
|
+
return revrange(start, stop, options) if options[:desc]
|
31
|
+
|
32
|
+
Hari.redis.zrange key, start, stop, options.slice(:with_scores)
|
33
|
+
end
|
34
|
+
|
35
|
+
alias :members :range
|
36
|
+
|
37
|
+
def range_with_scores
|
38
|
+
range 0, -1, with_scores: true
|
39
|
+
end
|
40
|
+
|
41
|
+
def revrange(start = 0, stop = -1, options = {})
|
42
|
+
Hari.redis.zrevrange key, start, stop, options.slice(:with_scores)
|
43
|
+
end
|
44
|
+
|
45
|
+
alias :reverse_range :revrange
|
46
|
+
alias :desc_range :revrange
|
47
|
+
|
48
|
+
def revrange_with_scores
|
49
|
+
revrange 0, -1, with_scores: true
|
50
|
+
end
|
51
|
+
|
52
|
+
def range_by_score(min, max, options = {})
|
53
|
+
return revrange_by_score(min, max, options) if options[:desc]
|
54
|
+
|
55
|
+
Hari.redis.zrangebyscore key, min, max, options.slice(:with_scores, :limit)
|
56
|
+
end
|
57
|
+
|
58
|
+
def revrange_by_score(min, max, options = {})
|
59
|
+
Hari.redis.zrevrangebyscore key, max, min, options.slice(:with_scores, :limit)
|
60
|
+
end
|
61
|
+
|
62
|
+
def rank(member, options = {})
|
63
|
+
return revrank(member, options) if options[:desc]
|
64
|
+
|
65
|
+
Hari.redis.zrank key, member
|
66
|
+
end
|
67
|
+
|
68
|
+
alias :ranking :rank
|
69
|
+
alias :position :rank
|
70
|
+
|
71
|
+
def revrank(member)
|
72
|
+
Hari.redis.zrevrank key, member
|
73
|
+
end
|
74
|
+
|
75
|
+
alias :reverse_ranking :revrank
|
76
|
+
alias :reverse_position :revrank
|
77
|
+
|
78
|
+
def count
|
79
|
+
Hari.redis.zcard key
|
80
|
+
end
|
81
|
+
|
82
|
+
alias :size :count
|
83
|
+
alias :length :count
|
84
|
+
|
85
|
+
def empty?
|
86
|
+
count == 0
|
87
|
+
end
|
88
|
+
|
89
|
+
def one?
|
90
|
+
count == 1
|
91
|
+
end
|
92
|
+
|
93
|
+
def many?
|
94
|
+
count > 1
|
95
|
+
end
|
96
|
+
|
97
|
+
def include?(member)
|
98
|
+
score(member).present?
|
99
|
+
end
|
100
|
+
|
101
|
+
alias :member? :include?
|
102
|
+
|
103
|
+
def score(member)
|
104
|
+
Hari.redis.zscore key, member
|
105
|
+
end
|
106
|
+
|
107
|
+
def add(*score_members)
|
108
|
+
Hari.redis.zadd key, score_members.to_a.flatten
|
109
|
+
end
|
110
|
+
|
111
|
+
def <<(*score_members)
|
112
|
+
add score_members
|
113
|
+
end
|
114
|
+
|
115
|
+
def delete(*members)
|
116
|
+
Hari.redis.zrem key, members
|
117
|
+
end
|
118
|
+
|
119
|
+
def trim_by_rank(start, stop)
|
120
|
+
Hari.redis.zremrangebyrank key, start, stop
|
121
|
+
end
|
122
|
+
|
123
|
+
def trim_by_score(min, max)
|
124
|
+
Hari.redis.zremrangebyscore key, min, max
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|