hari 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +6 -14
- data/LICENSE +17 -15
- data/README.md +131 -11
- data/lib/hari/entity/property.rb +1 -2
- data/lib/hari/entity/serialization.rb +8 -8
- data/lib/hari/entity.rb +5 -0
- data/lib/hari/keys/key.rb +48 -0
- data/lib/hari/keys/list.rb +132 -0
- data/lib/hari/keys/set.rb +101 -0
- data/lib/hari/keys/sorted_set.rb +115 -0
- data/lib/hari/keys/string.rb +81 -0
- data/lib/hari/keys.rb +9 -0
- data/lib/hari/node/queries/relation/backend/sorted_set.rb +14 -4
- data/lib/hari/node/queries/relation.rb +13 -7
- data/lib/hari/node/queries/type.rb +62 -0
- data/lib/hari/node/queries.rb +10 -8
- data/lib/hari/relation/sorted_set.rb +33 -4
- data/lib/hari/relation.rb +0 -3
- data/lib/hari/version.rb +1 -1
- data/lib/hari.rb +10 -3
- data/spec/hari/keys/key_spec.rb +41 -0
- data/spec/hari/{node → keys}/lists_spec.rb +1 -1
- data/spec/hari/{node → keys}/sets_spec.rb +1 -1
- data/spec/hari/{node → keys}/sorted_sets_spec.rb +1 -1
- data/spec/hari/keys/string_spec.rb +57 -0
- data/spec/hari/node_spec.rb +45 -0
- data/spec/hari_spec.rb +13 -0
- metadata +28 -24
- data/lib/hari/node/queries/list.rb +0 -147
- data/lib/hari/node/queries/relation/backend/list.rb +0 -35
- data/lib/hari/node/queries/set.rb +0 -116
- data/lib/hari/node/queries/sorted_set.rb +0 -130
- data/lib/hari/relation/linked_list.rb +0 -16
@@ -0,0 +1,81 @@
|
|
1
|
+
module Hari
|
2
|
+
module Keys
|
3
|
+
class String < Key
|
4
|
+
|
5
|
+
def string(name)
|
6
|
+
@name = name
|
7
|
+
self
|
8
|
+
end
|
9
|
+
|
10
|
+
def string!(name)
|
11
|
+
@name = name
|
12
|
+
to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
Hari.redis.get key
|
17
|
+
end
|
18
|
+
|
19
|
+
def set(value)
|
20
|
+
Hari.redis.set key, value
|
21
|
+
end
|
22
|
+
|
23
|
+
def length
|
24
|
+
Hari.redis.strlen key
|
25
|
+
end
|
26
|
+
|
27
|
+
alias :size :length
|
28
|
+
|
29
|
+
def range(start = nil, stop = nil)
|
30
|
+
start ||= 0
|
31
|
+
stop ||= -1
|
32
|
+
Hari.redis.getrange key, start, stop
|
33
|
+
end
|
34
|
+
|
35
|
+
def at(index)
|
36
|
+
Hari.redis.getrange key, index, index
|
37
|
+
end
|
38
|
+
|
39
|
+
def [](*args)
|
40
|
+
arg = args.first
|
41
|
+
|
42
|
+
if args.size == 2
|
43
|
+
range *args
|
44
|
+
elsif arg.kind_of? Integer
|
45
|
+
at arg
|
46
|
+
elsif arg.kind_of? Range
|
47
|
+
range arg.first, arg.last
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def <<(value)
|
52
|
+
Hari.redis.append key, value
|
53
|
+
end
|
54
|
+
|
55
|
+
def +(i)
|
56
|
+
Hari.redis.incrby key, i
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def -(i)
|
61
|
+
Hari.redis.decrby key, i
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def bitcount(start = nil, stop = nil)
|
66
|
+
start ||= 0
|
67
|
+
stop ||= -1
|
68
|
+
Hari.redis.bitcount key, start, stop
|
69
|
+
end
|
70
|
+
|
71
|
+
def getbit(offset)
|
72
|
+
Hari.redis.getbit key, offset
|
73
|
+
end
|
74
|
+
|
75
|
+
def setbit(offset, value)
|
76
|
+
Hari.redis.setbit key, offset, value
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/hari/keys.rb
ADDED
@@ -10,10 +10,14 @@ class Hari::Node::Queries::Relation
|
|
10
10
|
|
11
11
|
def fetch_relations_ids(set, options)
|
12
12
|
from, limit = options.values_at(:from, :limit)
|
13
|
+
limit = limit.try(:to_i)
|
13
14
|
|
14
|
-
if from.present?
|
15
|
-
set.range_by_score from, '+inf', desc: true, limit: [0, limit]
|
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]
|
16
19
|
else
|
20
|
+
limit -= 1 unless limit <= 0
|
17
21
|
set.range from, limit, desc: true
|
18
22
|
end
|
19
23
|
end
|
@@ -35,7 +39,7 @@ class Hari::Node::Queries::Relation
|
|
35
39
|
def step(start_node, nodes_ids, options = {})
|
36
40
|
stream = start_node.sorted_set("stream:#{SecureRandom.hex(6)}")
|
37
41
|
direction = options[:direction] == :in ? 0 : 2
|
38
|
-
limit = options.fetch(:limit, -1)
|
42
|
+
limit = options.fetch(:limit, -1).try :to_i
|
39
43
|
|
40
44
|
nodes_ids.each_with_index do |node_id, index|
|
41
45
|
prune, stop = true, options.fetch(:step, 5)
|
@@ -50,7 +54,13 @@ class Hari::Node::Queries::Relation
|
|
50
54
|
set = Hari(node_id).sorted_set set_name(options)
|
51
55
|
|
52
56
|
if from = options[:from].presence
|
53
|
-
|
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
|
54
64
|
else
|
55
65
|
scored_relations_ids = set.range(start, stop, desc: true, with_scores: true)
|
56
66
|
end
|
@@ -12,6 +12,8 @@ module Hari
|
|
12
12
|
|
13
13
|
attr_reader :parent, :direction, :relation, :level, :options
|
14
14
|
|
15
|
+
alias :name :relation
|
16
|
+
|
15
17
|
def initialize(parent, direction, relation, *args)
|
16
18
|
@parent, @direction, @relation = parent, direction, relation
|
17
19
|
@level = parent.level + 1
|
@@ -26,24 +28,22 @@ module Hari
|
|
26
28
|
end
|
27
29
|
|
28
30
|
def calculate_limit
|
29
|
-
options[:limit]
|
31
|
+
options[:limit] || -1
|
30
32
|
end
|
31
33
|
|
32
|
-
%w(limit
|
34
|
+
%w(limit step).each do |method|
|
33
35
|
define_method method do |value|
|
34
36
|
options[method.to_sym] = value
|
35
37
|
self
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
-
options[:
|
41
|
+
def from(score, direction = nil)
|
42
|
+
direction ||= :up
|
43
|
+
options[:from] = { score: score, direction: direction.to_s }
|
42
44
|
self
|
43
45
|
end
|
44
46
|
|
45
|
-
alias :type :types
|
46
|
-
|
47
47
|
%w(nodes_ids relations_ids nodes).each do |result_type|
|
48
48
|
define_method result_type do
|
49
49
|
options[:result_type] = result_type.to_sym
|
@@ -60,6 +60,12 @@ module Hari
|
|
60
60
|
alias :rel_ids :relations_ids
|
61
61
|
alias :rids :relations_ids
|
62
62
|
|
63
|
+
def type(name)
|
64
|
+
fail 'type not supported for chained queries' if level > 1
|
65
|
+
|
66
|
+
Type.new self, name
|
67
|
+
end
|
68
|
+
|
63
69
|
def count
|
64
70
|
options[:result_type] = :count
|
65
71
|
result
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Hari
|
2
|
+
class Node < Entity
|
3
|
+
module Queries
|
4
|
+
class Type
|
5
|
+
|
6
|
+
attr_reader :relation, :name
|
7
|
+
|
8
|
+
def initialize(relation, name)
|
9
|
+
@relation, @name = relation, name
|
10
|
+
end
|
11
|
+
|
12
|
+
def intersect_count(type)
|
13
|
+
intersect_key = interstore(type)
|
14
|
+
|
15
|
+
Hari.redis.zcard(intersect_key).tap do
|
16
|
+
Hari.redis.del intersect_key
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def intersect(type, start = nil, stop = nil)
|
21
|
+
start ||= 0
|
22
|
+
stop ||= -1
|
23
|
+
|
24
|
+
intersect_key = interstore(type)
|
25
|
+
|
26
|
+
Hari.redis.zrevrange(intersect_key, start, stop).tap do
|
27
|
+
Hari.redis.del intersect_key
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def sort_by(type, offset = nil, count = nil, order = nil)
|
32
|
+
offset ||= 0
|
33
|
+
count ||= Hari.redis.zcard(key)
|
34
|
+
order = 'desc' unless order.to_s == 'asc'
|
35
|
+
|
36
|
+
Hari.redis.sort key, by: type.sort_key, order: "#{order} alpha", limit: [offset, count]
|
37
|
+
end
|
38
|
+
|
39
|
+
def interstore(type)
|
40
|
+
intersect_key(type).tap do |destination|
|
41
|
+
Hari.redis.zinterstore destination, [key, type.key]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def key
|
46
|
+
start_key = Hari.node_key(relation.parent.node)
|
47
|
+
"#{start_key}:#{relation.name}:#{relation.direction}:#{name}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def sort_key
|
51
|
+
start_key = Hari.node_key(relation.parent.node)
|
52
|
+
"#{start_key}:#{relation.name}:#{name}#*"
|
53
|
+
end
|
54
|
+
|
55
|
+
def intersect_key(type)
|
56
|
+
"inter:#{key}:#{type.key}"
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/hari/node/queries.rb
CHANGED
@@ -1,30 +1,32 @@
|
|
1
|
-
require 'hari/node/queries/list'
|
2
|
-
require 'hari/node/queries/set'
|
3
|
-
require 'hari/node/queries/sorted_set'
|
4
|
-
require 'hari/node/queries/relation'
|
5
|
-
|
6
1
|
module Hari
|
7
2
|
class Node < Entity
|
8
3
|
module Queries
|
4
|
+
autoload :Relation, 'hari/node/queries/relation'
|
5
|
+
autoload :Type, 'hari/node/queries/type'
|
9
6
|
|
10
7
|
delegate :in, :out, to: :relation_query
|
11
8
|
|
12
9
|
delegate :set, :set!, to: :set_query
|
13
10
|
delegate :sorted_set, :sorted_set!, to: :sorted_set_query
|
14
11
|
delegate :list, :list!, to: :list_query
|
12
|
+
delegate :string, :string!, to: :string_query
|
15
13
|
|
16
14
|
private
|
17
15
|
|
18
16
|
def set_query
|
19
|
-
|
17
|
+
Keys::Set.new query_node
|
20
18
|
end
|
21
19
|
|
22
20
|
def sorted_set_query
|
23
|
-
|
21
|
+
Keys::SortedSet.new query_node
|
24
22
|
end
|
25
23
|
|
26
24
|
def list_query
|
27
|
-
|
25
|
+
Keys::List.new query_node
|
26
|
+
end
|
27
|
+
|
28
|
+
def string_query
|
29
|
+
Keys::String.new query_node
|
28
30
|
end
|
29
31
|
|
30
32
|
def relation_query
|
@@ -3,12 +3,41 @@ module Hari
|
|
3
3
|
module SortedSet
|
4
4
|
extend self
|
5
5
|
|
6
|
-
def create(
|
7
|
-
Relation::DIRECTIONS.each
|
6
|
+
def create(relation)
|
7
|
+
Relation::DIRECTIONS.each do |direction|
|
8
|
+
key = relation.key(direction)
|
9
|
+
weight = relation.weight(direction)
|
10
|
+
|
11
|
+
Hari.redis.zadd key, weight, relation.id
|
12
|
+
Hari.redis.zadd type_key(relation, direction), weight, type_id(relation, direction)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def delete(relation)
|
17
|
+
Relation::DIRECTIONS.each do |direction|
|
18
|
+
key = relation.key(direction)
|
19
|
+
|
20
|
+
Hari.redis.zrem key, relation.id
|
21
|
+
Hari.redis.zrem type_key(relation, direction), type_id(relation, direction)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def type_key(relation, direction)
|
28
|
+
case direction.to_s
|
29
|
+
when 'out'
|
30
|
+
"#{relation.start_node_id}:#{relation.label}:out:#{Hari.node_type(relation.end_node_id)}"
|
31
|
+
when 'in'
|
32
|
+
"#{relation.end_node_id}:#{relation.label}:in:#{Hari.node_type(relation.start_node_id)}"
|
33
|
+
end
|
8
34
|
end
|
9
35
|
|
10
|
-
def
|
11
|
-
|
36
|
+
def type_id(relation, direction)
|
37
|
+
case direction.to_s
|
38
|
+
when 'in' then Hari.node_id(relation.start_node_id)
|
39
|
+
when 'out' then Hari.node_id(relation.end_node_id)
|
40
|
+
end
|
12
41
|
end
|
13
42
|
|
14
43
|
end
|
data/lib/hari/relation.rb
CHANGED
data/lib/hari/version.rb
CHANGED
data/lib/hari.rb
CHANGED
@@ -12,12 +12,15 @@ require 'ostruct'
|
|
12
12
|
require 'hari/version'
|
13
13
|
require 'hari/configuration'
|
14
14
|
require 'hari/errors'
|
15
|
-
require 'hari/entity'
|
16
|
-
require 'hari/node'
|
17
|
-
require 'hari/relation'
|
18
15
|
|
19
16
|
module Hari
|
20
17
|
extend self
|
18
|
+
|
19
|
+
autoload :Entity, 'hari/entity'
|
20
|
+
autoload :Keys, 'hari/keys'
|
21
|
+
autoload :Node, 'hari/node'
|
22
|
+
autoload :Relation, 'hari/relation'
|
23
|
+
|
21
24
|
extend Configuration
|
22
25
|
extend Hari::Node::Queries
|
23
26
|
|
@@ -68,6 +71,10 @@ module Hari
|
|
68
71
|
Relation.create type, from, target
|
69
72
|
end
|
70
73
|
|
74
|
+
def remove_relation!(type, from, target)
|
75
|
+
relation!(type, from, target).delete
|
76
|
+
end
|
77
|
+
|
71
78
|
end
|
72
79
|
|
73
80
|
def Hari(arg)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hari::Keys::Key do
|
4
|
+
|
5
|
+
let(:node) { Hari.node user: 10 }
|
6
|
+
subject { node.list :friends }
|
7
|
+
|
8
|
+
specify { subject.instance_of? Hari::Keys::Key }
|
9
|
+
|
10
|
+
before { subject.add 1 }
|
11
|
+
|
12
|
+
specify '#exists? + #delete!' do
|
13
|
+
subject.exists?.should be_true
|
14
|
+
|
15
|
+
subject.delete!
|
16
|
+
|
17
|
+
subject.exists?.should be_false
|
18
|
+
end
|
19
|
+
|
20
|
+
specify '#type' do
|
21
|
+
subject.type.should eq('list')
|
22
|
+
end
|
23
|
+
|
24
|
+
specify '#expire + #persist + #ttl' do
|
25
|
+
subject.expire 80 # ms
|
26
|
+
(subject.ttl.to_i < 80).should be_true
|
27
|
+
|
28
|
+
sleep 0.1
|
29
|
+
|
30
|
+
subject.exists?.should be_false
|
31
|
+
|
32
|
+
subject.add 12
|
33
|
+
subject.expire 100
|
34
|
+
subject.persist
|
35
|
+
|
36
|
+
sleep 0.2
|
37
|
+
|
38
|
+
subject.exists?.should be_true
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hari::Keys::String do
|
4
|
+
|
5
|
+
let(:node) { Hari.node user: 10 }
|
6
|
+
subject { node.string :bio }
|
7
|
+
|
8
|
+
specify '#string! + #to_s + #set' do
|
9
|
+
subject.set 'lol'
|
10
|
+
node.string!(:bio).should eq('lol')
|
11
|
+
|
12
|
+
subject.to_s.should eq('lol')
|
13
|
+
end
|
14
|
+
|
15
|
+
specify '#length' do
|
16
|
+
subject.set 'lol'
|
17
|
+
subject.length.should eq(3)
|
18
|
+
subject.size.should eq(3)
|
19
|
+
end
|
20
|
+
|
21
|
+
specify '#at + #range + #[]' do
|
22
|
+
subject.set 'de his omnibus non cogitavi'
|
23
|
+
|
24
|
+
subject.at(7).should eq('o')
|
25
|
+
subject[7].should eq('o')
|
26
|
+
|
27
|
+
subject.range(7, 13).should eq('omnibus')
|
28
|
+
subject[7, 13].should eq('omnibus')
|
29
|
+
subject[7..13].should eq('omnibus')
|
30
|
+
end
|
31
|
+
|
32
|
+
specify '#<<' do
|
33
|
+
subject.set 'lol'
|
34
|
+
subject << 'omg'
|
35
|
+
subject << 'bbq'
|
36
|
+
|
37
|
+
subject.to_s.should eq('lolomgbbq')
|
38
|
+
end
|
39
|
+
|
40
|
+
specify '#+ + #-' do
|
41
|
+
subject.set 1
|
42
|
+
subject + 4
|
43
|
+
subject.to_s.should eq('5')
|
44
|
+
|
45
|
+
subject - 7
|
46
|
+
subject.to_s.should eq('-2')
|
47
|
+
end
|
48
|
+
|
49
|
+
specify '#bitcount + #getbit + #setbit' do
|
50
|
+
subject.set 'lol'
|
51
|
+
subject.bitcount.should eq(14)
|
52
|
+
subject.getbit(3).should eq(0)
|
53
|
+
subject.setbit(4, 1)
|
54
|
+
subject.getbit(4).should eq(1)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
data/spec/hari/node_spec.rb
CHANGED
@@ -65,6 +65,12 @@ describe Hari::Node do
|
|
65
65
|
it 'paginates queries' do
|
66
66
|
following = joao.out(:follow).from(15.minutes.ago.to_f).to_a
|
67
67
|
following.map(&:id).sort.should eq [teresa, raimundo].map(&:id).sort
|
68
|
+
|
69
|
+
following = joao.out(:follow).from(15.minutes.ago.to_f, 'down').to_a
|
70
|
+
following.map(&:id).sort.should eq [maria, joaquim, lili].map(&:id).sort
|
71
|
+
|
72
|
+
following = joao.out(:follow).limit(1).from(15.minutes.ago.to_f, 'down').to_a
|
73
|
+
following.map(&:id).sort.should eq [maria.id]
|
68
74
|
end
|
69
75
|
|
70
76
|
it 'paginates chained queries' do
|
@@ -73,6 +79,45 @@ describe Hari::Node do
|
|
73
79
|
following = teresa.out(:follow).out(:follow).from(15.minutes.ago.to_f)
|
74
80
|
following.to_a.map(&:id).sort.should eq [teresa, raimundo].map(&:id).sort
|
75
81
|
following.nodes!.map(&:id).sort.should eq [teresa, raimundo].map(&:id).sort
|
82
|
+
|
83
|
+
following = teresa.out(:follow).out(:follow).from(15.minutes.ago.to_f, 'down')
|
84
|
+
following.to_a.map(&:id).sort.should eq [maria, joaquim, lili].map(&:id).sort
|
85
|
+
|
86
|
+
following = teresa.out(:follow).out(:follow).limit(1).from(15.minutes.ago.to_f, 'down')
|
87
|
+
following.to_a.map(&:id).sort.should eq [maria.id]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe 'queries by type' do
|
92
|
+
before do
|
93
|
+
Hari.relation! :follow, 'user#1', 'celeb#x'
|
94
|
+
Hari.relation! :follow, 'user#2', 'user#1'
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'can intersect type queries' do
|
98
|
+
friends = Hari('user#2').out(:follow).type(:user)
|
99
|
+
fans = Hari('celeb#x').in(:follow).type(:user)
|
100
|
+
|
101
|
+
fans.intersect_count(friends).should eq(1)
|
102
|
+
friends.intersect_count(fans).should eq(1)
|
103
|
+
|
104
|
+
fans.intersect(friends).should eq %w(1)
|
105
|
+
|
106
|
+
Hari.relation! :follow, 'user#3', 'celeb#x'
|
107
|
+
Hari.relation! :follow, 'user#4', 'celeb#x'
|
108
|
+
Hari.relation! :follow, 'user#5', 'celeb#x'
|
109
|
+
Hari.relation! :follow, 'user#6', 'celeb#x'
|
110
|
+
Hari.relation! :follow, 'user#7', 'celeb#x'
|
111
|
+
Hari.relation! :follow, 'user#8', 'celeb#x'
|
112
|
+
|
113
|
+
Hari.relation! :follow, 'user#2', 'user#4'
|
114
|
+
Hari.relation! :follow, 'user#2', 'user#5'
|
115
|
+
Hari.relation! :follow, 'user#2', 'user#6'
|
116
|
+
Hari.relation! :follow, 'user#2', 'user#7'
|
117
|
+
|
118
|
+
fans.intersect(friends, 1, 3).should eq %w(6 5 4)
|
119
|
+
|
120
|
+
fans.sort_by(friends).take(5).should eq %w(7 6 5 4 1)
|
76
121
|
end
|
77
122
|
end
|
78
123
|
|
data/spec/hari_spec.rb
CHANGED
@@ -34,4 +34,17 @@ describe Hari do
|
|
34
34
|
node.node_type.should eq('user')
|
35
35
|
end
|
36
36
|
|
37
|
+
specify '.relation!' do
|
38
|
+
Hari.relation! :follow, 'user#1', 'user#2'
|
39
|
+
|
40
|
+
Hari(user: 1).out(:follow).nodes_ids!.should eq %w(user#2)
|
41
|
+
end
|
42
|
+
|
43
|
+
specify '.remove_relation!' do
|
44
|
+
Hari.relation! :follow, 'user#6', 'user#9'
|
45
|
+
Hari.remove_relation! :follow, 'user#6', 'user#9'
|
46
|
+
|
47
|
+
Hari(user: 1).out(:follow).nodes_ids!.should be_empty
|
48
|
+
end
|
49
|
+
|
37
50
|
end
|