hari 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/hari.gemspec +10 -2
- data/lib/hari.rb +8 -4
- data/lib/hari/configuration/redis.rb +6 -2
- data/lib/hari/entity.rb +10 -20
- data/lib/hari/entity/property.rb +19 -4
- data/lib/hari/entity/property/builder.rb +18 -3
- data/lib/hari/entity/repository.rb +11 -3
- data/lib/hari/entity/serialization.rb +53 -9
- data/lib/hari/entity/serialization/array.rb +21 -0
- data/lib/hari/entity/serialization/hash.rb +31 -0
- data/lib/hari/keys.rb +5 -0
- data/lib/hari/keys/hash.rb +67 -0
- data/lib/hari/keys/key.rb +24 -3
- data/lib/hari/keys/list.rb +10 -8
- data/lib/hari/keys/set.rb +8 -8
- data/lib/hari/keys/sorted_set.rb +20 -8
- data/lib/hari/node.rb +19 -2
- data/lib/hari/node/index.rb +152 -0
- data/lib/hari/node/queries.rb +32 -17
- data/lib/hari/node/queries/relation.rb +14 -1
- data/lib/hari/node/queries/relation/backend/sorted_set.rb +41 -90
- data/lib/hari/node/queries/relation/backend/sorted_set/count_step.rb +16 -0
- data/lib/hari/node/queries/relation/backend/sorted_set/node_step.rb +91 -0
- data/lib/hari/node/queries/type.rb +69 -4
- data/lib/hari/node/repository.rb +36 -0
- data/lib/hari/node/serialization.rb +11 -10
- data/lib/hari/object.rb +6 -0
- data/lib/hari/serialization.rb +3 -0
- data/lib/hari/version.rb +1 -1
- data/spec/hari/entity/repository_spec.rb +17 -0
- data/spec/hari/entity/serialization/hash_spec.rb +16 -0
- data/spec/hari/entity/serialization_spec.rb +14 -4
- data/spec/hari/keys/hash_spec.rb +55 -0
- data/spec/hari/keys/lists_spec.rb +27 -0
- data/spec/hari/node/index_spec.rb +199 -0
- data/spec/hari/node_spec.rb +84 -0
- data/spec/hari/serialization_spec.rb +41 -0
- data/spec/spec_helper.rb +6 -2
- metadata +27 -4
@@ -28,7 +28,7 @@ module Hari
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def calculate_limit
|
31
|
-
options[:limit] || -1
|
31
|
+
(options[:limit].presence || -1).to_i
|
32
32
|
end
|
33
33
|
|
34
34
|
%w(limit step).each do |method|
|
@@ -66,6 +66,10 @@ module Hari
|
|
66
66
|
Type.new self, name
|
67
67
|
end
|
68
68
|
|
69
|
+
def first
|
70
|
+
limit(1).to_a.first
|
71
|
+
end
|
72
|
+
|
69
73
|
def count
|
70
74
|
options[:result_type] = :count
|
71
75
|
result
|
@@ -75,6 +79,14 @@ module Hari
|
|
75
79
|
level == 1 ? parent.node : parent.start_node
|
76
80
|
end
|
77
81
|
|
82
|
+
def <<(nodes)
|
83
|
+
fail 'cannot create relation for chained queries' if level > 1
|
84
|
+
|
85
|
+
Array(nodes).each do |node|
|
86
|
+
Hari.relation! relation, parent.node, Hari(node)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
78
90
|
def call(final = true)
|
79
91
|
if level == 1
|
80
92
|
backend.fetch parent.node, call_args(final)
|
@@ -87,6 +99,7 @@ module Hari
|
|
87
99
|
{
|
88
100
|
relation: relation,
|
89
101
|
direction: direction,
|
102
|
+
position: direction == :in ? 0 : 2,
|
90
103
|
limit: calculate_limit,
|
91
104
|
from: options[:from],
|
92
105
|
step: options[:step],
|
@@ -1,105 +1,56 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
if from.present? && from[:direction] == 'up'
|
16
|
-
set.range_by_score from[:score], '+inf', desc: true, limit: [0, limit]
|
17
|
-
elsif from.present? && from[:direction] == 'down'
|
18
|
-
set.range_by_score '-inf', from[:score], desc: true, limit: [0, limit]
|
19
|
-
else
|
20
|
-
limit -= 1 unless limit <= 0
|
21
|
-
set.range from, limit, desc: true
|
1
|
+
require 'hari/node/queries/relation/backend/sorted_set/node_step'
|
2
|
+
require 'hari/node/queries/relation/backend/sorted_set/count_step'
|
3
|
+
|
4
|
+
module Hari::Node::Queries
|
5
|
+
class Relation
|
6
|
+
module Backend
|
7
|
+
module SortedSet
|
8
|
+
extend self
|
9
|
+
extend SortedSet::NodeStep
|
10
|
+
extend SortedSet::CountStep
|
11
|
+
|
12
|
+
def fetch(node, options = {})
|
13
|
+
set = node.sorted_set set_name(options)
|
14
|
+
send "fetch_#{options[:result]}", set, options
|
22
15
|
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def fetch_nodes_ids(set, options)
|
26
|
-
index = set.name =~ /in$/ ? 0 : 2
|
27
|
-
fetch_relations_ids(set, options).map { |r| r.split(':')[index] }
|
28
|
-
end
|
29
16
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
direction = options[:direction] == :in ? 0 : 2
|
42
|
-
limit = options.fetch(:limit, -1).try :to_i
|
43
|
-
|
44
|
-
nodes_ids.each_with_index do |node_id, index|
|
45
|
-
prune, stop = true, options.fetch(:step, 5)
|
46
|
-
|
47
|
-
if limit == -1 || stream.count < limit
|
48
|
-
prune, stop = false, limit
|
17
|
+
def fetch_relations_ids(set, options)
|
18
|
+
from, limit = options.values_at(:from, :limit)
|
19
|
+
limit = limit.try(:to_i)
|
20
|
+
|
21
|
+
if from.present? && from[:direction] == 'up'
|
22
|
+
set.range_by_score from[:score], '+inf', desc: true, limit: [0, limit]
|
23
|
+
elsif from.present? && from[:direction] == 'down'
|
24
|
+
set.range_by_score '-inf', from[:score], desc: true, limit: [0, limit]
|
25
|
+
else
|
26
|
+
limit -= 1 unless limit <= 0
|
27
|
+
set.range from, limit, desc: true
|
49
28
|
end
|
29
|
+
end
|
50
30
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
if from = options[:from].presence
|
57
|
-
args = { desc: true, with_scores: true, limit: [start, stop] }
|
58
|
-
|
59
|
-
if from[:direction] == 'up'
|
60
|
-
scored_relations_ids = set.range_by_score(from[:score], '+inf', args)
|
61
|
-
elsif from[:direction] == 'down'
|
62
|
-
scored_relations_ids = set.range_by_score('-inf', from[:score], args)
|
63
|
-
end
|
64
|
-
else
|
65
|
-
scored_relations_ids = set.range(start, stop, desc: true, with_scores: true)
|
66
|
-
end
|
67
|
-
|
68
|
-
break if scored_relations_ids.empty?
|
69
|
-
|
70
|
-
scored_nodes_ids = scored_relations_ids.map { |(r, s)| [s, r.split(':')[direction]] }.flatten
|
71
|
-
stream.add *scored_nodes_ids
|
72
|
-
|
73
|
-
last_node_id = scored_nodes_ids.last
|
74
|
-
|
75
|
-
if prune
|
76
|
-
stream.trim_by_rank 0, stop
|
31
|
+
def fetch_nodes_ids(set, options)
|
32
|
+
index = set.name =~ /in$/ ? 0 : 2
|
33
|
+
fetch_relations_ids(set, options).map { |r| r.split(':')[index] }
|
34
|
+
end
|
77
35
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
end
|
82
|
-
else
|
83
|
-
digg = false
|
84
|
-
end
|
85
|
-
end
|
36
|
+
def fetch_nodes(set, options)
|
37
|
+
nodes_ids = fetch_nodes_ids(set, options)
|
38
|
+
nodes_ids.empty? ? [] : Hari.redis.mget(nodes_ids)
|
86
39
|
end
|
87
40
|
|
88
|
-
|
41
|
+
def fetch_count(set, options)
|
42
|
+
set.count
|
43
|
+
end
|
89
44
|
|
90
|
-
|
91
|
-
|
92
|
-
else
|
93
|
-
nodes_ids
|
45
|
+
def step(start_node, nodes_ids, options)
|
46
|
+
send "step_#{options[:result]}", start_node, nodes_ids, options
|
94
47
|
end
|
95
|
-
end
|
96
48
|
|
97
|
-
|
49
|
+
def set_name(options)
|
50
|
+
"#{options[:relation]}:#{options[:direction]}"
|
51
|
+
end
|
98
52
|
|
99
|
-
def set_name(options)
|
100
|
-
"#{options[:relation]}:#{options[:direction]}"
|
101
53
|
end
|
102
|
-
|
103
54
|
end
|
104
55
|
end
|
105
56
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Hari::Node::Queries
|
2
|
+
class Relation
|
3
|
+
module Backend
|
4
|
+
module SortedSet
|
5
|
+
module CountStep
|
6
|
+
|
7
|
+
def step_count(start_node, nodes_ids, options)
|
8
|
+
# CONSIDER LIMIT AND FROM
|
9
|
+
nodes_ids.inject(0) { |b, n| b + Hari(n).sorted_set(set_name(options)).count }
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Hari::Node::Queries
|
2
|
+
class Relation
|
3
|
+
module Backend
|
4
|
+
module SortedSet
|
5
|
+
module NodeStep
|
6
|
+
|
7
|
+
def step_nodes(start_node, nodes_ids, options)
|
8
|
+
stream = start_node.sorted_set("stream:#{SecureRandom.hex(6)}")
|
9
|
+
|
10
|
+
nodes_ids.each { |node_id| step_node node_id, stream, options }
|
11
|
+
|
12
|
+
ids = stream.revrange
|
13
|
+
|
14
|
+
stream.delete!
|
15
|
+
|
16
|
+
case options[:result]
|
17
|
+
when :nodes_ids
|
18
|
+
ids
|
19
|
+
when :nodes
|
20
|
+
ids.any? ? Hari.redis.mget(ids) : []
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
alias :step_nodes_ids :step_nodes
|
25
|
+
|
26
|
+
def step_node(node_id, stream, options)
|
27
|
+
set = Hari(node_id).sorted_set set_name(options)
|
28
|
+
|
29
|
+
start = 0
|
30
|
+
stop = calculate_stop(stream, options)
|
31
|
+
prune = stream.count > options[:limit] && options[:limit] != -1
|
32
|
+
|
33
|
+
digg = true
|
34
|
+
|
35
|
+
while digg
|
36
|
+
if from = options[:from].presence
|
37
|
+
args = { desc: true, with_scores: true, limit: [start, stop] }
|
38
|
+
|
39
|
+
if from[:direction] == 'up'
|
40
|
+
scored_relations_ids = set.range_by_score(from[:score], '+inf', args)
|
41
|
+
elsif from[:direction] == 'down'
|
42
|
+
scored_relations_ids = set.range_by_score('-inf', from[:score], args)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
scored_relations_ids = set.range(start, stop, desc: true, with_scores: true)
|
46
|
+
end
|
47
|
+
|
48
|
+
break if scored_relations_ids.empty?
|
49
|
+
|
50
|
+
scored_nodes_ids = scored_relations_ids.map { |(r, s)| [s, r.split(':')[options[:position]]] }.flatten
|
51
|
+
stream.add *scored_nodes_ids
|
52
|
+
|
53
|
+
last_node_id = scored_nodes_ids.last
|
54
|
+
|
55
|
+
if prune
|
56
|
+
stream.trim_by_rank 0, stop
|
57
|
+
|
58
|
+
unless stream.include? last_node_id
|
59
|
+
digg = false
|
60
|
+
start += stop + 1
|
61
|
+
end
|
62
|
+
else
|
63
|
+
digg = false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
stream_count = stream.count
|
68
|
+
|
69
|
+
if options[:limit] != -1 && stream_count > options[:limit]
|
70
|
+
trim_stop = stream_count - options[:limit] - 1
|
71
|
+
stream.trim_by_rank 0, trim_stop
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def calculate_stop(stream, options)
|
76
|
+
return options[:step].to_i if options[:step].present?
|
77
|
+
return options[:limit] if stream.count < options[:limit]
|
78
|
+
|
79
|
+
case options[:limit]
|
80
|
+
when -1, 1...5
|
81
|
+
options[:limit]
|
82
|
+
else
|
83
|
+
5
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -3,10 +3,11 @@ module Hari
|
|
3
3
|
module Queries
|
4
4
|
class Type
|
5
5
|
|
6
|
-
attr_reader :relation, :name
|
6
|
+
attr_reader :relation, :name, :options
|
7
7
|
|
8
8
|
def initialize(relation, name)
|
9
9
|
@relation, @name = relation, name
|
10
|
+
@options = {}
|
10
11
|
end
|
11
12
|
|
12
13
|
def intersect_count(type)
|
@@ -42,20 +43,84 @@ module Hari
|
|
42
43
|
end
|
43
44
|
end
|
44
45
|
|
46
|
+
def count
|
47
|
+
Hari.redis.zcard key
|
48
|
+
end
|
49
|
+
|
50
|
+
def ids
|
51
|
+
start = options[:start] || 0
|
52
|
+
stop = options[:stop] || -1
|
53
|
+
|
54
|
+
Hari.redis.zrevrange key, start, stop
|
55
|
+
end
|
56
|
+
|
57
|
+
def nodes_ids
|
58
|
+
ids.map { |id| "#{name}##{id}" }
|
59
|
+
end
|
60
|
+
|
61
|
+
alias :nodes_ids! :nodes_ids
|
62
|
+
alias :n_ids :nodes_ids
|
63
|
+
alias :nids :nodes_ids
|
64
|
+
|
65
|
+
def nodes
|
66
|
+
if ids = nodes_ids.presence
|
67
|
+
Hari.redis.mget(ids).map &Hari::Node.method(:from_source)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
alias :nodes! :nodes
|
72
|
+
alias :to_a :nodes
|
73
|
+
|
74
|
+
def relations_ids
|
75
|
+
ids.map &method(:relation_key)
|
76
|
+
end
|
77
|
+
|
78
|
+
alias :relations_ids! :relations_ids
|
79
|
+
alias :rids :relations_ids
|
80
|
+
alias :rel_ids :relations_ids
|
81
|
+
|
82
|
+
def limit(start, stop)
|
83
|
+
options.merge! start: start, stop: stop
|
84
|
+
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
def <<(ids)
|
89
|
+
Array(ids).each do |id|
|
90
|
+
Hari.relation! relation.name, relation.parent.node, Hari(name => id)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def where(conditions = {})
|
95
|
+
type_class.where(conditions).tap do |index|
|
96
|
+
index.indexes << self
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
45
100
|
def key
|
46
|
-
start_key = Hari.node_key(relation.parent.node)
|
47
101
|
"#{start_key}:#{relation.name}:#{relation.direction}:#{name}"
|
48
102
|
end
|
49
103
|
|
50
104
|
def sort_key
|
51
|
-
|
52
|
-
|
105
|
+
relation_key '*'
|
106
|
+
end
|
107
|
+
|
108
|
+
def relation_key(id)
|
109
|
+
"#{start_key}:#{relation.name}:#{name}##{id}"
|
53
110
|
end
|
54
111
|
|
55
112
|
def intersect_key(type)
|
56
113
|
"inter:#{key}:#{type.key}"
|
57
114
|
end
|
58
115
|
|
116
|
+
def start_key
|
117
|
+
Hari.node_key relation.parent.node
|
118
|
+
end
|
119
|
+
|
120
|
+
def type_class
|
121
|
+
name.to_s.camelize.constantize
|
122
|
+
end
|
123
|
+
|
59
124
|
end
|
60
125
|
end
|
61
126
|
end
|
data/lib/hari/node/repository.rb
CHANGED
@@ -3,6 +3,27 @@ module Hari
|
|
3
3
|
module Repository
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
|
+
def reindex(options = {})
|
7
|
+
self.class.indexed_properties.each do |property|
|
8
|
+
if change = previous_changes[property.name]
|
9
|
+
previous, current = change
|
10
|
+
|
11
|
+
Index.new(property, previous).delete self
|
12
|
+
Index.new(property, current).add self
|
13
|
+
elsif options[:force_index]
|
14
|
+
value = send(property.name)
|
15
|
+
Index.new(property, value).add self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def remove_from_indexes
|
21
|
+
self.class.indexed_properties.each do |property|
|
22
|
+
value = send(property.name)
|
23
|
+
Index.new(property, value).delete self
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
6
27
|
module ClassMethods
|
7
28
|
|
8
29
|
def find_one(id, options = {})
|
@@ -18,6 +39,21 @@ module Hari
|
|
18
39
|
super ids, options
|
19
40
|
end
|
20
41
|
|
42
|
+
def find_by(name, value)
|
43
|
+
if property = indexed_properties.find { |p| p.name.to_s == name.to_s }
|
44
|
+
Index.new property, value
|
45
|
+
else
|
46
|
+
fail "missing index for key #{name}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def where(conditions = {})
|
51
|
+
conditions.inject(nil) do |index, (key, value)|
|
52
|
+
query = find_by(key, value)
|
53
|
+
index ? index.append(query) : query
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
21
57
|
end
|
22
58
|
end
|
23
59
|
end
|
@@ -5,18 +5,19 @@ module Hari
|
|
5
5
|
def from_source(source)
|
6
6
|
return if source.blank?
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
|
-
buffer
|
8
|
+
case source
|
9
|
+
when ::String
|
10
|
+
hash = Yajl::Parser.parse(source)
|
11
|
+
source_class(hash).from_hash hash
|
12
|
+
when ::Hash
|
13
|
+
source_class(source).from_hash source
|
17
14
|
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
18
|
|
19
|
-
|
19
|
+
def source_class(source)
|
20
|
+
source['id'].split('#').first.camelize.constantize
|
20
21
|
end
|
21
22
|
|
22
23
|
end
|