hari 0.0.4 → 0.0.5
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 +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
|