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 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