agama 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.0
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,4 @@
1
+ require 'agama/graph'
2
+ require 'agama/traverser'
3
+ require 'agama/keyify'
4
+ require 'agama/config'
@@ -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
@@ -0,0 +1,7 @@
1
+ module Agama
2
+ module Config
3
+ DEFAULT_TYPE = "_"
4
+ NODE_DELIMITOR = "\0"
5
+ EDGE_DELIMITOR = "\0\0"
6
+ end
7
+ end
@@ -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
+
@@ -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: 11
4
+ hash: 9
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 5
9
- - 0
10
- version: 0.5.0
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