agama 0.5.0 → 0.5.1
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.
- data/VERSION +1 -1
- data/agama.gemspec +65 -0
- data/lib/agama.rb +4 -0
- data/lib/agama/adapters/tokyocabinet.rb +160 -0
- data/lib/agama/config.rb +7 -0
- data/lib/agama/graph.rb +267 -0
- data/lib/agama/keyify.rb +62 -0
- data/lib/agama/traverser.rb +78 -0
- metadata +9 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.5.
|
1
|
+
0.5.1
|
data/agama.gemspec
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{agama}
|
8
|
+
s.version = "0.5.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Aditya Rachakonda"]
|
12
|
+
s.date = %q{2011-05-10}
|
13
|
+
s.description = %q{A simple Graph DB on a key-value store}
|
14
|
+
s.email = %q{aditya.rachakonda@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
"Gemfile",
|
22
|
+
"Gemfile.lock",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"README.rdoc",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"agama.gemspec",
|
28
|
+
"lib/agama.rb",
|
29
|
+
"lib/agama/adapters/tokyocabinet.rb",
|
30
|
+
"lib/agama/config.rb",
|
31
|
+
"lib/agama/graph.rb",
|
32
|
+
"lib/agama/keyify.rb",
|
33
|
+
"lib/agama/traverser.rb",
|
34
|
+
"test/helper.rb",
|
35
|
+
"test/test_agama.rb"
|
36
|
+
]
|
37
|
+
s.homepage = %q{http://github.com/arrac/agama}
|
38
|
+
s.licenses = ["MIT"]
|
39
|
+
s.require_paths = ["lib"]
|
40
|
+
s.rubygems_version = %q{1.3.7}
|
41
|
+
s.summary = %q{A simple Graph DB on a key-value store}
|
42
|
+
|
43
|
+
if s.respond_to? :specification_version then
|
44
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
45
|
+
s.specification_version = 3
|
46
|
+
|
47
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
48
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
49
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
50
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.6.0"])
|
51
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
52
|
+
else
|
53
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
54
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
55
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
|
56
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
57
|
+
end
|
58
|
+
else
|
59
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
60
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
61
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
|
62
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
data/lib/agama.rb
CHANGED
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'tokyocabinet'
|
2
|
+
include TokyoCabinet
|
3
|
+
|
4
|
+
module Agama
|
5
|
+
module Adapters
|
6
|
+
class TC
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
def open (path)
|
11
|
+
@meta = HDB::new
|
12
|
+
# open the meta database
|
13
|
+
if !@meta.open(path + "/meta.tch", HDB::OWRITER | HDB::OCREAT)
|
14
|
+
ecode = @meta.ecode
|
15
|
+
raise "Error opening meta_db: #{@meta.errmsg(ecode)}"
|
16
|
+
end
|
17
|
+
|
18
|
+
@nodes = HDB::new
|
19
|
+
# open the nodes database
|
20
|
+
if !@nodes.open(path + "/nodes.tch", HDB::OWRITER | HDB::OCREAT)
|
21
|
+
ecode = @nodes.ecode
|
22
|
+
raise "Error opening nodes_db: #{@nodes.errmsg(ecode)}"
|
23
|
+
end
|
24
|
+
|
25
|
+
@edges = BDB::new
|
26
|
+
# open the edges database
|
27
|
+
if !@edges.open(path + "/edges.tcb", BDB::OWRITER | BDB::OCREAT)
|
28
|
+
ecode = @edges.ecode
|
29
|
+
raise "Error opening edges_db: #{@edges.errmsg(ecode)}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
def close
|
36
|
+
if !@meta.close
|
37
|
+
ecode = @meta.ecode
|
38
|
+
raise "Error closing meta_db: #{@meta.errmsg(ecode)}"
|
39
|
+
end
|
40
|
+
if !@nodes.close
|
41
|
+
ecode = @nodes.ecode
|
42
|
+
raise "Error closing nodes_db: #{@nodes.errmsg(ecode)}"
|
43
|
+
end
|
44
|
+
if !@edges.close
|
45
|
+
ecode = @edges.ecode
|
46
|
+
raise "Error closing edges_db: #{@edges.errmsg(ecode)}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
def m_put (key, value)
|
54
|
+
unless @meta.put(key, value)
|
55
|
+
ecode = @meta.ecode
|
56
|
+
raise "Error inserting meta_db: #{@meta.errmsg(ecode)}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def m_get (key)
|
61
|
+
return @meta.get(key)
|
62
|
+
end
|
63
|
+
|
64
|
+
def m_del (key)
|
65
|
+
unless @meta.out(key)
|
66
|
+
ecode = @meta.ecode
|
67
|
+
raise "Error deleting from meta_db: #{@meta.errmsg(ecode)}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
def n_put (key, value)
|
75
|
+
unless @nodes.put(key, value)
|
76
|
+
ecode = @nodes.ecode
|
77
|
+
raise "Error inserting into nodes_db: #{@nodes.errmsg(ecode)}"
|
78
|
+
end
|
79
|
+
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
def n_get (key)
|
84
|
+
return @nodes.get(key)
|
85
|
+
end
|
86
|
+
|
87
|
+
def n_del (key)
|
88
|
+
unless @nodes.out(key)
|
89
|
+
ecode = @nodes.ecode
|
90
|
+
raise "Error deleting from nodes_db: #{@nodes.errmsg(ecode)}"
|
91
|
+
end
|
92
|
+
|
93
|
+
true
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
def e_put (key, value)
|
98
|
+
unless @edges.put(key, value)
|
99
|
+
ecode = @edges.ecode
|
100
|
+
raise "Error inserting into edges_db: #{@edges.errmsg(ecode)}"
|
101
|
+
end
|
102
|
+
|
103
|
+
true
|
104
|
+
end
|
105
|
+
|
106
|
+
def e_get (key)
|
107
|
+
return @edges.get(key)
|
108
|
+
end
|
109
|
+
|
110
|
+
def e_del (key)
|
111
|
+
unless @edges.out(key, value)
|
112
|
+
ecode = @edges.ecode
|
113
|
+
raise "Error deleting from edges_db: #{@edges.errmsg(ecode)}"
|
114
|
+
end
|
115
|
+
|
116
|
+
true
|
117
|
+
end
|
118
|
+
|
119
|
+
def e_cursor
|
120
|
+
return TCCursor.new(@edges, "edge")
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
class TCCursor
|
126
|
+
def initialize(bptree, type)
|
127
|
+
@c = BDBCUR::new(bptree)
|
128
|
+
@t = type
|
129
|
+
end
|
130
|
+
|
131
|
+
def first
|
132
|
+
@c.first
|
133
|
+
end
|
134
|
+
|
135
|
+
def last
|
136
|
+
@c.last
|
137
|
+
end
|
138
|
+
|
139
|
+
def jump(key)
|
140
|
+
@c.jump(key)
|
141
|
+
end
|
142
|
+
|
143
|
+
def prev
|
144
|
+
@c.prev
|
145
|
+
end
|
146
|
+
|
147
|
+
def next
|
148
|
+
@c.next
|
149
|
+
end
|
150
|
+
|
151
|
+
def key
|
152
|
+
@c.key
|
153
|
+
end
|
154
|
+
|
155
|
+
def value
|
156
|
+
@c.val
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
data/lib/agama/config.rb
ADDED
data/lib/agama/graph.rb
ADDED
@@ -0,0 +1,267 @@
|
|
1
|
+
module Agama
|
2
|
+
class Graph
|
3
|
+
|
4
|
+
#Initialises variables and sets the path
|
5
|
+
|
6
|
+
def initialize(params)
|
7
|
+
@db_path = params[:path] || "./"
|
8
|
+
@db = params[:db]
|
9
|
+
end
|
10
|
+
|
11
|
+
#Opens the database for access
|
12
|
+
|
13
|
+
def open
|
14
|
+
@db.open(@db_path)
|
15
|
+
|
16
|
+
#Create meta variables if they are absent
|
17
|
+
unless @db.m_get("n")
|
18
|
+
@db.m_put("n", Marshal.dump(0)) #Total node count
|
19
|
+
@db.m_put("m", Marshal.dump(0)) #Total edge count
|
20
|
+
@db.m_put("node#{Config::DEFAULT_TYPE}",
|
21
|
+
Marshal.dump(0)) #Node count for default type
|
22
|
+
@db.m_put("edge#{Config::DEFAULT_TYPE}",
|
23
|
+
Marshal.dump({:directed => false, :count => 0})) #Edge count for default type
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
#Closes the database connection
|
29
|
+
def close
|
30
|
+
@db.close
|
31
|
+
end
|
32
|
+
|
33
|
+
#Creates/Updates a node
|
34
|
+
|
35
|
+
def set_node(node)
|
36
|
+
return nil unless node[:name]
|
37
|
+
|
38
|
+
#Get the type of the node
|
39
|
+
type = node[:type] || Config::DEFAULT_TYPE
|
40
|
+
node[:type] = type
|
41
|
+
|
42
|
+
#Convert the node into Key and Value strings for storage
|
43
|
+
key = Keyify.node(node)
|
44
|
+
value = Marshal.dump(self.clean_node(node)) #remove key items from value
|
45
|
+
|
46
|
+
#Check if the node type exists, and if so get its count
|
47
|
+
count = Marshal.load(@db.m_get("node#{type}")) if @db.m_get("node#{type}")
|
48
|
+
|
49
|
+
#Check whether the operation is an insert (not an update), if so increment count
|
50
|
+
unless @db.n_get(key)
|
51
|
+
#Increment type-specific count
|
52
|
+
if count
|
53
|
+
count += 1
|
54
|
+
@db.m_put("node#{type}", Marshal.dump(count))
|
55
|
+
else
|
56
|
+
@db.m_put("node#{type}", Marshal.dump(1))
|
57
|
+
end
|
58
|
+
|
59
|
+
#Increment global count
|
60
|
+
@db.m_put("n", Marshal.dump(Marshal.load(@db.m_get("n")) + 1))
|
61
|
+
end
|
62
|
+
|
63
|
+
#Store the node
|
64
|
+
if @db.n_put(key, value)
|
65
|
+
node
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
#Fetches the node requested
|
70
|
+
|
71
|
+
def get_node(node)
|
72
|
+
return nil unless node[:name]
|
73
|
+
|
74
|
+
#Convert the node into a Key string and fetch the corresponding data
|
75
|
+
key = Keyify.node(node)
|
76
|
+
value = @db.n_get(key)
|
77
|
+
|
78
|
+
if value
|
79
|
+
new_node = Marshal.load(value)
|
80
|
+
new_node[:name] = node[:name]
|
81
|
+
new_node[:type] = node[:type] || Config::DEFAULT_TYPE
|
82
|
+
new_node
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
#Creates/Updates an edge
|
87
|
+
|
88
|
+
def set_edge(edge)
|
89
|
+
return false unless edge
|
90
|
+
return false unless edge[:from][:name]
|
91
|
+
return false unless edge[:to][:name]
|
92
|
+
|
93
|
+
#Get the type of the edge
|
94
|
+
type = edge[:type] || Config::DEFAULT_TYPE
|
95
|
+
edge[:type] = type
|
96
|
+
|
97
|
+
#Check if the edge type exists
|
98
|
+
etype = Marshal.load(@db.m_get("edge#{type}")) if @db.m_get("edge#{type}")
|
99
|
+
|
100
|
+
#Integrity check: Check whether the edge direction is not contradictory
|
101
|
+
if edge[:directed]
|
102
|
+
if etype
|
103
|
+
if etype[:directed] != edge[:directed]
|
104
|
+
raise "Edge creation error: edge direction contradicting existing edges"
|
105
|
+
return
|
106
|
+
end
|
107
|
+
end
|
108
|
+
else
|
109
|
+
if etype
|
110
|
+
edge[:directed] = etype[:directed]
|
111
|
+
else
|
112
|
+
edge[:directed] = false
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
#Convert the edge into Key, Reversed Key and Value strings for storage
|
117
|
+
key, reverse_key = Keyify.edge(edge)
|
118
|
+
value = Marshal.dump(clean_edge(edge))
|
119
|
+
|
120
|
+
#Integrity check: Check if the incident nodes are defined
|
121
|
+
unless (self.get_node(edge[:from]) and self.get_node(edge[:to]))
|
122
|
+
raise "Edge creation error: node(s) not defined"
|
123
|
+
return
|
124
|
+
end
|
125
|
+
|
126
|
+
#Check whether the operation is an insert (not an update), if so increment count
|
127
|
+
unless @db.e_get(key)
|
128
|
+
if etype
|
129
|
+
etype[:count] += 1
|
130
|
+
@db.m_put("edge#{type}", Marshal.dump(etype))
|
131
|
+
else
|
132
|
+
@db.m_put("edge#{type}",
|
133
|
+
Marshal.dump({:directed => edge[:directed], :count => 1}))
|
134
|
+
end
|
135
|
+
|
136
|
+
#Increment global count
|
137
|
+
@db.m_put("m", Marshal.dump(Marshal.load(@db.m_get("m")) + 1))
|
138
|
+
end
|
139
|
+
|
140
|
+
#Add the edge and the reversed edge
|
141
|
+
if @db.e_put(key, value) and @db.e_put(reverse_key, value)
|
142
|
+
return edge
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
#Fetches the value assigned to the edge from the _from_ node to the _to_ node
|
147
|
+
|
148
|
+
def get_edge(edge)
|
149
|
+
return nil unless edge[:from][:name]
|
150
|
+
return nil unless edge[:to][:name]
|
151
|
+
|
152
|
+
#Get the type of the edge
|
153
|
+
type = edge[:type] || Config::DEFAULT_TYPE
|
154
|
+
edge[:type] = type
|
155
|
+
|
156
|
+
#Check if the edge type exists
|
157
|
+
etype = Marshal.load(@db.m_get("edge#{type}")) if @db.m_get("edge#{type}")
|
158
|
+
|
159
|
+
#Integrity check: Check whether the edge direction is not contradictory
|
160
|
+
if edge[:directed]
|
161
|
+
if etype
|
162
|
+
if etype[:directed] != edge[:directed]
|
163
|
+
return false
|
164
|
+
end
|
165
|
+
else
|
166
|
+
#If there is no edge of that type then pre-empt the result
|
167
|
+
return false
|
168
|
+
end
|
169
|
+
else
|
170
|
+
if etype
|
171
|
+
edge[:directed] = etype[:directed]
|
172
|
+
else
|
173
|
+
#If there is no edge of that type then pre-empt the result
|
174
|
+
return false
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
#Convert the edge into a Key string and fetch the corresponding data
|
179
|
+
key, reverse_key = Keyify.edge(edge)
|
180
|
+
value = @db.e_get(key)
|
181
|
+
|
182
|
+
if value
|
183
|
+
new_edge = Marshal.load(value)
|
184
|
+
new_edge[:from] = self.get_node(edge[:from])
|
185
|
+
new_edge[:to] = self.get_node(edge[:to])
|
186
|
+
new_edge[:type] = edge[:type]
|
187
|
+
new_edge[:directed] = etype[:directed] #Pick direction alone from the meta_db for consistency
|
188
|
+
new_edge
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def neighbours(node)
|
193
|
+
traverser = Traverser.new(@db, self)
|
194
|
+
traverser.set(:from => node)
|
195
|
+
end
|
196
|
+
|
197
|
+
#def filter(params)
|
198
|
+
#end
|
199
|
+
|
200
|
+
#def delete_node(node)
|
201
|
+
#return nil unless node[:name]
|
202
|
+
|
203
|
+
#key = Key.node(node)
|
204
|
+
|
205
|
+
#end
|
206
|
+
|
207
|
+
#def delete_edge(edge)
|
208
|
+
|
209
|
+
#end
|
210
|
+
|
211
|
+
# def put(store, key, value)
|
212
|
+
# unless store.put(key, value)
|
213
|
+
# STDERR.printf("put error: %s\n", store.errmsg(store.ecode))
|
214
|
+
# end
|
215
|
+
# end
|
216
|
+
|
217
|
+
#Accessors for meta values
|
218
|
+
|
219
|
+
def node_count(type = nil)
|
220
|
+
if type
|
221
|
+
value = @db.m_get("node#{type}")
|
222
|
+
if value
|
223
|
+
Marshal.load(value)
|
224
|
+
end
|
225
|
+
else
|
226
|
+
Marshal.load(@db.m_get("n"))
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def edge_count(type = nil)
|
231
|
+
if type
|
232
|
+
value = @db.m_get("edge#{type}")
|
233
|
+
if value
|
234
|
+
etype = Marshal.load(value)
|
235
|
+
etype[:count]
|
236
|
+
end
|
237
|
+
else
|
238
|
+
Marshal.load(@db.m_get("m"))
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
#Methods to seperate key from the value
|
243
|
+
|
244
|
+
def clean_node(node)
|
245
|
+
new_node = {}
|
246
|
+
node.each do |key, value|
|
247
|
+
next if (key == :type or key == :name)
|
248
|
+
new_node[key] = value
|
249
|
+
end
|
250
|
+
|
251
|
+
return new_node
|
252
|
+
end
|
253
|
+
|
254
|
+
def clean_edge(edge)
|
255
|
+
new_edge = {}
|
256
|
+
edge.each do |key, value|
|
257
|
+
next if (key == :type or key == :from or key == :to or key == :directed)
|
258
|
+
new_edge[key] = value
|
259
|
+
end
|
260
|
+
|
261
|
+
return new_edge
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|
267
|
+
|
data/lib/agama/keyify.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
module Agama
|
2
|
+
|
3
|
+
module Keyify
|
4
|
+
|
5
|
+
def self.node(node)
|
6
|
+
type = node[:type] || Config::DEFAULT_TYPE
|
7
|
+
[type, node[:name]].join(Config::NODE_DELIMITOR)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.edge(edge)
|
11
|
+
from = self.node(edge[:from])
|
12
|
+
type = edge[:type] || Config::DEFAULT_TYPE
|
13
|
+
outgoing, incoming = if edge[:directed] then
|
14
|
+
["O", "I"]
|
15
|
+
else
|
16
|
+
["N", "N"]
|
17
|
+
end
|
18
|
+
|
19
|
+
to = self.node(edge[:to])
|
20
|
+
|
21
|
+
key = [from, type, outgoing, to].join(Config::EDGE_DELIMITOR)
|
22
|
+
reverse_key = [to, type, incoming, from].join(Config::EDGE_DELIMITOR)
|
23
|
+
|
24
|
+
[key, reverse_key]
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.range(params)
|
28
|
+
from = self.node(params[:from]) if params[:from]
|
29
|
+
|
30
|
+
type = params[:edge_type]
|
31
|
+
|
32
|
+
if type
|
33
|
+
dir = params[:direction]
|
34
|
+
if dir
|
35
|
+
to_type = params[:to_type]
|
36
|
+
if to_type
|
37
|
+
[from, type, dir, to_type].join(Config::EDGE_DELIMITOR)
|
38
|
+
else
|
39
|
+
[from, type, dir, nil].join(Config::EDGE_DELIMITOR)
|
40
|
+
end
|
41
|
+
else
|
42
|
+
[from, type, nil].join(Config::EDGE_DELIMITOR)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
[from, nil].join(Config::EDGE_DELIMITOR)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.parse(key)
|
50
|
+
from_key, edge_type, direction, to_key = key.split(Config::EDGE_DELIMITOR)
|
51
|
+
type, name = to_key.split(Config::NODE_DELIMITOR)
|
52
|
+
directed = true
|
53
|
+
directed = false if (direction == "N")
|
54
|
+
[{:type => type, :name => name}, edge_type, direction, directed]
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.subkey?(subkey, key)
|
58
|
+
key [0, subkey.length] == subkey
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Agama
|
2
|
+
class Traverser
|
3
|
+
|
4
|
+
attr_accessor :params
|
5
|
+
|
6
|
+
def initialize(db, graph)
|
7
|
+
@cursor = db.e_cursor
|
8
|
+
@search_key = ""
|
9
|
+
@unset = true
|
10
|
+
@graph = graph
|
11
|
+
@from = nil
|
12
|
+
@params = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def set(params)
|
16
|
+
@params.merge!(params)
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def along(edge_type)
|
21
|
+
self.set(:edge_type => edge_type)
|
22
|
+
end
|
23
|
+
|
24
|
+
def outgoing
|
25
|
+
self.set(:edge_type => Config::DEFAULT_TYPE) unless @params[:edge_type]
|
26
|
+
self.set(:direction => "O")
|
27
|
+
end
|
28
|
+
|
29
|
+
def incoming
|
30
|
+
self.set(:edge_type => Config::DEFAULT_TYPE) unless @params[:edge_type]
|
31
|
+
self.set(:direction => "I")
|
32
|
+
end
|
33
|
+
|
34
|
+
def undirected
|
35
|
+
self.set(:edge_type => Config::DEFAULT_TYPE) unless @params[:edge_type]
|
36
|
+
self.set(:direction => "N")
|
37
|
+
end
|
38
|
+
|
39
|
+
def of_type(node_type)
|
40
|
+
self.set(:edge_type => Config::DEFAULT_TYPE) unless @params[:edge_type]
|
41
|
+
self.set(:direction => "N") unless @params[:direction]
|
42
|
+
self.set(:to_type => node_type)
|
43
|
+
end
|
44
|
+
|
45
|
+
def each
|
46
|
+
search_key = ""
|
47
|
+
from = nil
|
48
|
+
if @unset
|
49
|
+
search_key = Keyify.range(@params)
|
50
|
+
from = @graph.get_node(@params[:from])
|
51
|
+
|
52
|
+
@cursor.jump(search_key)
|
53
|
+
@unset = false
|
54
|
+
end
|
55
|
+
|
56
|
+
while @cursor.key and Keyify.subkey?(search_key, @cursor.key)
|
57
|
+
to, type, direction, directed = Keyify.parse(@cursor.key)
|
58
|
+
|
59
|
+
if @cursor.value
|
60
|
+
new_edge = Marshal.load(@cursor.value)
|
61
|
+
new_edge[:type] = type
|
62
|
+
new_edge[:directed] = directed
|
63
|
+
if direction == "I"
|
64
|
+
new_edge[:to] = from
|
65
|
+
new_edge[:from] = @graph.get_node(to)
|
66
|
+
else
|
67
|
+
new_edge[:from] = from
|
68
|
+
new_edge[:to] = @graph.get_node(to)
|
69
|
+
end
|
70
|
+
yield new_edge
|
71
|
+
end
|
72
|
+
|
73
|
+
@cursor.next
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: agama
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 9
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 5
|
9
|
-
-
|
10
|
-
version: 0.5.
|
9
|
+
- 1
|
10
|
+
version: 0.5.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Aditya Rachakonda
|
@@ -95,7 +95,13 @@ files:
|
|
95
95
|
- README.rdoc
|
96
96
|
- Rakefile
|
97
97
|
- VERSION
|
98
|
+
- agama.gemspec
|
98
99
|
- lib/agama.rb
|
100
|
+
- lib/agama/adapters/tokyocabinet.rb
|
101
|
+
- lib/agama/config.rb
|
102
|
+
- lib/agama/graph.rb
|
103
|
+
- lib/agama/keyify.rb
|
104
|
+
- lib/agama/traverser.rb
|
99
105
|
- test/helper.rb
|
100
106
|
- test/test_agama.rb
|
101
107
|
has_rdoc: true
|