hari 0.0.1 → 0.0.3
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.
- 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
|