agraph 0.1.0.beta1
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/README.rdoc +137 -0
- data/Rakefile +48 -0
- data/lib/allegro_graph.rb +5 -0
- data/lib/allegro_graph/catalog.rb +39 -0
- data/lib/allegro_graph/federation.rb +72 -0
- data/lib/allegro_graph/proxy/geo.rb +117 -0
- data/lib/allegro_graph/proxy/mapping.rb +34 -0
- data/lib/allegro_graph/proxy/query.rb +37 -0
- data/lib/allegro_graph/proxy/statements.rb +59 -0
- data/lib/allegro_graph/repository.rb +89 -0
- data/lib/allegro_graph/server.rb +68 -0
- data/lib/allegro_graph/session.rb +64 -0
- data/lib/allegro_graph/transport.rb +169 -0
- data/lib/code_smells.reek +3 -0
- data/spec/fake_transport.yml +287 -0
- data/spec/fake_transport_helper.rb +38 -0
- data/spec/integration/basic_spec.rb +364 -0
- data/spec/lib/allegro_graph/catalog_spec.rb +85 -0
- data/spec/lib/allegro_graph/federation_spec.rb +113 -0
- data/spec/lib/allegro_graph/proxy/geo_spec.rb +89 -0
- data/spec/lib/allegro_graph/proxy/mapping_spec.rb +39 -0
- data/spec/lib/allegro_graph/proxy/query_spec.rb +62 -0
- data/spec/lib/allegro_graph/proxy/statements_spec.rb +58 -0
- data/spec/lib/allegro_graph/repository_spec.rb +224 -0
- data/spec/lib/allegro_graph/server_spec.rb +75 -0
- data/spec/lib/allegro_graph/session_spec.rb +76 -0
- data/spec/lib/allegro_graph/transport_spec.rb +116 -0
- data/spec/spec_helper.rb +26 -0
- metadata +92 -0
data/README.rdoc
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
|
2
|
+
= Ruby client for the AllegroGraph RDF graph database
|
3
|
+
|
4
|
+
The agraph gem provides a client for the RESTful interface of AllegroGraph RDF graph database version 4.x. This
|
5
|
+
document should give you overview, how the client can be used. To get familiar with the database server, this
|
6
|
+
documentation[http://franz.com/agraph/support/documentation/current/agraph-introduction.html] is strongly recommended.
|
7
|
+
|
8
|
+
The features that are exposed by this client are...
|
9
|
+
* simple repository management
|
10
|
+
* add / remove statements (aka triples) to / from the store
|
11
|
+
* searching for statements by subject, predicate or object (or any combination of them)
|
12
|
+
* searching for statements by geo-spatial queries
|
13
|
+
* transactions
|
14
|
+
* performing SparQL and Prolog queries
|
15
|
+
* mapping of data type
|
16
|
+
|
17
|
+
== Repository management
|
18
|
+
|
19
|
+
A repository can be created by simply typing the following.
|
20
|
+
|
21
|
+
require 'allegro_graph'
|
22
|
+
|
23
|
+
server = AllegroGraph::Server.new :username => "user", :password => "pass"
|
24
|
+
repository = AllegroGraph::Repository.new server, "test_repository"
|
25
|
+
repository.create_if_missing!
|
26
|
+
|
27
|
+
The code will try to connect to a AllegroGraph server running at <tt>localhost:10035</tt> with the credentials
|
28
|
+
<tt>user</tt> and <tt>pass</tt>, and creates a repository named <tt>test_repository</tt> if it's not already existing.
|
29
|
+
|
30
|
+
== Adding and Removing triples
|
31
|
+
|
32
|
+
Once a repository is created, statements can be added.
|
33
|
+
|
34
|
+
repository.statements.create "<a_subject>", "<a_predicate>", "<an_object>", "<context>"
|
35
|
+
|
36
|
+
The last argument is optional and defines a context for the given triple. This context can be used to define named
|
37
|
+
graphs inside the repository.
|
38
|
+
|
39
|
+
To delete statements, matching options can be passed to the delete method.
|
40
|
+
|
41
|
+
repository.statements.delete :predicate => "<a_predicate>"
|
42
|
+
|
43
|
+
This will delete all statements with the predicate <tt><a_predicate></tt>.
|
44
|
+
|
45
|
+
== Searching for statements
|
46
|
+
|
47
|
+
The <tt>find</tt> method provides an easy way to find specified statements.
|
48
|
+
|
49
|
+
repository.statements.find :subject => "<a_subject>"
|
50
|
+
|
51
|
+
The result will be an array that holds arrays of all matching statements.
|
52
|
+
|
53
|
+
[
|
54
|
+
[ "<a_subject>", "<a_predicate>", "<a_object>" ],
|
55
|
+
[ "<a_subject>", "<another_predicate>", "<another_object>" ]
|
56
|
+
]
|
57
|
+
|
58
|
+
== Searching for statements by geo-spatial queries
|
59
|
+
|
60
|
+
=== Adding coordinates to the database
|
61
|
+
|
62
|
+
Before coordinates can be added, a fitting type has to be requested from the database. Coordinates can have the
|
63
|
+
cartesian (x and y) or spherical (latitude and longitude) type. When creating the type, also a range has to be defined.
|
64
|
+
|
65
|
+
cartesian_type = repository.geo.cartesian_type 1, 0, 0, 100, 100
|
66
|
+
|
67
|
+
The first argument specifies the strip width and the last four the top-left and bottom-right corner of the rectangle
|
68
|
+
that represent the boundaries for any coordinate.
|
69
|
+
|
70
|
+
Afterwards, the return type can be used add geometric nodes (subjects or objects) to the database.
|
71
|
+
|
72
|
+
repository.statements.create "<a_subject>", "<a_predicate>", "\"+20+20\"^^#{cartesian_type}"
|
73
|
+
|
74
|
+
=== Finding statements by geo-spatial queries
|
75
|
+
|
76
|
+
To find statements by thier assigned coordinates, the following methods are provided.
|
77
|
+
|
78
|
+
repository.geo.inside_box
|
79
|
+
repository.geo.inside_circle
|
80
|
+
repository.geo.inside_haversine
|
81
|
+
repository.geo.inside_polygon
|
82
|
+
|
83
|
+
The previously create statement will be returned by
|
84
|
+
|
85
|
+
repository.geo.inside_box cartesian_type, "<a_predicate>", 10, 10, 30, 30
|
86
|
+
|
87
|
+
== Transactions
|
88
|
+
|
89
|
+
agraph also allows you to perform transaction. All operations performed within a transaction will committed at once. If
|
90
|
+
an error occurs during the transaction, all operations will be rolled back.
|
91
|
+
|
92
|
+
repository.transaction do
|
93
|
+
statements.create "<a_subject>", "<a_predicate>", "<an_object>"
|
94
|
+
statements.create "<a_subject>", "<a_predicate>", "<another_object>"
|
95
|
+
# ... if an error is raised here, no operation will be committed
|
96
|
+
end
|
97
|
+
# ... if no error has occured, all operations will be committed
|
98
|
+
|
99
|
+
== SparQL and Prolog queries
|
100
|
+
|
101
|
+
The perform a SparQL or Prolog query, simply set the language that the query is written in and pass the query string to
|
102
|
+
the following method.
|
103
|
+
|
104
|
+
repository.query.language = :sparql
|
105
|
+
repository.query.perform "SELECT ?s WHERE { ?s ?p ?o . }"
|
106
|
+
|
107
|
+
At the moment only <tt>:sparql</tt> and <tt>:prolog</tt> queries are supported.
|
108
|
+
|
109
|
+
The result will look like this.
|
110
|
+
|
111
|
+
{ "names" => [ "s" ], "values" => [ [ "<a_subject>" ], [ "<another_subject>" ] ] }
|
112
|
+
|
113
|
+
== Mapping of data types
|
114
|
+
|
115
|
+
To add and remove mapping between types and it's encodings, the following methods can be used.
|
116
|
+
|
117
|
+
repository.mapping.create "<time>", "<http://www.w3.org/2001/XMLSchema#dateTime>"
|
118
|
+
repository.mapping.delete "<time>"
|
119
|
+
|
120
|
+
With such a type defined, it's possible to perform range queries like
|
121
|
+
|
122
|
+
repository.statements.create "<event_one>", "<occurs>", "\"2010-03-29T11:40:00\"^^<time>"
|
123
|
+
repository.statements.create "<event_two>", "<occurs>", "\"2010-03-29T17:40:00\"^^<time>"
|
124
|
+
|
125
|
+
repository.statements.find :predicate => "<occurs>", :object => [ "\"2010-03-29T11:00:00\"^^<time>", "\"2010-03-29T12:00:00\"^^<time>" ]
|
126
|
+
|
127
|
+
Only the second event would be returned.
|
128
|
+
|
129
|
+
== More Examples
|
130
|
+
|
131
|
+
More examples can be found in the integration specs (spec/integration)
|
132
|
+
|
133
|
+
== Contribution
|
134
|
+
|
135
|
+
Any contribution - especially bug reports - is welcome.
|
136
|
+
|
137
|
+
Fork or send mail to b.phifty@gmail.com
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'rspec'
|
3
|
+
require 'spec'
|
4
|
+
require "rake/rdoctask"
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
require 'spec/rake/spectask'
|
7
|
+
|
8
|
+
task :default => :spec
|
9
|
+
|
10
|
+
specification = Gem::Specification.new do |specification|
|
11
|
+
specification.name = "agraph"
|
12
|
+
specification.version = "0.1.0.beta1"
|
13
|
+
specification.date = "2010-03-29"
|
14
|
+
specification.email = "b.phifty@gmail.com"
|
15
|
+
specification.homepage = "http://github.com/phifty/agraph"
|
16
|
+
specification.summary = "Client for the AllegroGraph 4.x graph database."
|
17
|
+
specification.description = "The gem provides a client for the AllegroGraph 4.x RDF graph database. Features like searching geo-spatial data, type mapping and transactions are supported."
|
18
|
+
specification.has_rdoc = true
|
19
|
+
specification.authors = [ "Philipp Bruell" ]
|
20
|
+
specification.files = [ "README.rdoc", "Rakefile" ] + Dir["{lib,spec}/**/*"]
|
21
|
+
specification.extra_rdoc_files = [ "README.rdoc" ]
|
22
|
+
specification.require_path = "lib"
|
23
|
+
end
|
24
|
+
|
25
|
+
Rake::GemPackageTask.new(specification) do |package|
|
26
|
+
package.gem_spec = specification
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "Generate the rdoc"
|
30
|
+
Rake::RDocTask.new do |rdoc|
|
31
|
+
rdoc.rdoc_files.add [ "README.rdoc", "lib/**/*.rb" ]
|
32
|
+
rdoc.main = "README.rdoc"
|
33
|
+
rdoc.title = "Client for the AllegroGraph 4.x graph database."
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "Run all specs in spec directory"
|
37
|
+
Spec::Rake::SpecTask.new do |task|
|
38
|
+
task.spec_files = FileList["spec/lib/**/*_spec.rb"]
|
39
|
+
end
|
40
|
+
|
41
|
+
namespace :spec do
|
42
|
+
|
43
|
+
desc "Run all integration specs in spec/integration directory"
|
44
|
+
Spec::Rake::SpecTask.new(:integration) do |task|
|
45
|
+
task.spec_files = FileList["spec/integration/**/*_spec.rb"]
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,5 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "allegro_graph", "server"))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "allegro_graph", "catalog"))
|
3
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "allegro_graph", "repository"))
|
4
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "allegro_graph", "session"))
|
5
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "allegro_graph", "federation"))
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "repository")
|
2
|
+
|
3
|
+
module AllegroGraph
|
4
|
+
|
5
|
+
class Catalog
|
6
|
+
|
7
|
+
attr_reader :server
|
8
|
+
attr_accessor :name
|
9
|
+
|
10
|
+
def initialize(server, name, options = { })
|
11
|
+
@server = server
|
12
|
+
@name = name
|
13
|
+
@root = options[:root] || false
|
14
|
+
end
|
15
|
+
|
16
|
+
def ==(other)
|
17
|
+
other.is_a?(self.class) && self.server == other.server && self.root? == other.root? && self.name == other.name
|
18
|
+
end
|
19
|
+
|
20
|
+
def path
|
21
|
+
self.root? ? "" : "/catalogs/#{@name}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def root?
|
25
|
+
!!@root
|
26
|
+
end
|
27
|
+
|
28
|
+
def exists?
|
29
|
+
@server.catalogs.include? self
|
30
|
+
end
|
31
|
+
|
32
|
+
def repositories
|
33
|
+
repositories = @server.request :get, self.path + "/repositories", :expected_status_code => 200
|
34
|
+
repositories.map { |repository| Repository.new self, repository["id"] }
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "transport")
|
2
|
+
require File.join(File.dirname(__FILE__), "proxy", "statements")
|
3
|
+
require File.join(File.dirname(__FILE__), "proxy", "query")
|
4
|
+
require File.join(File.dirname(__FILE__), "proxy", "geo")
|
5
|
+
require File.join(File.dirname(__FILE__), "proxy", "mapping")
|
6
|
+
|
7
|
+
module AllegroGraph
|
8
|
+
|
9
|
+
class Federation
|
10
|
+
|
11
|
+
attr_reader :server
|
12
|
+
attr_accessor :name
|
13
|
+
attr_accessor :repository_names
|
14
|
+
attr_accessor :repository_urls
|
15
|
+
|
16
|
+
attr_reader :statements
|
17
|
+
attr_reader :query
|
18
|
+
attr_reader :geo
|
19
|
+
attr_reader :mapping
|
20
|
+
|
21
|
+
def initialize(server, name, options = { })
|
22
|
+
@server, @name = server, name
|
23
|
+
|
24
|
+
@repository_names = options[:repository_names]
|
25
|
+
@repository_urls = options[:repository_urls]
|
26
|
+
|
27
|
+
@statements = Proxy::Statements.new self
|
28
|
+
@query = Proxy::Query.new self
|
29
|
+
@geo = Proxy::Geo.new self
|
30
|
+
@mapping = Proxy::Mapping.new self
|
31
|
+
end
|
32
|
+
|
33
|
+
def ==(other)
|
34
|
+
other.is_a?(self.class) && @server == other.server && @name == other.name
|
35
|
+
end
|
36
|
+
|
37
|
+
def path
|
38
|
+
"/federated/#{@name}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def request(http_method, path, options = { })
|
42
|
+
@server.request http_method, path, options
|
43
|
+
end
|
44
|
+
|
45
|
+
def exists?
|
46
|
+
@server.federations.include? self
|
47
|
+
end
|
48
|
+
|
49
|
+
def create!
|
50
|
+
parameters = { }
|
51
|
+
parameters.merge! :repo => @repository_names if @repository_names
|
52
|
+
parameters.merge! :url => @repository_urls if @repository_urls
|
53
|
+
@server.request :put, self.path, :parameters => parameters, :expected_status_code => 204
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_if_missing!
|
58
|
+
create! unless exists?
|
59
|
+
end
|
60
|
+
|
61
|
+
def delete!
|
62
|
+
@server.request :delete, self.path, :expected_status_code => 204
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
def delete_if_exists!
|
67
|
+
delete! if exists?
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
|
2
|
+
module AllegroGraph
|
3
|
+
|
4
|
+
module Proxy
|
5
|
+
|
6
|
+
class Geo
|
7
|
+
|
8
|
+
attr_reader :resource
|
9
|
+
|
10
|
+
def initialize(resource)
|
11
|
+
@resource = resource
|
12
|
+
end
|
13
|
+
|
14
|
+
def path
|
15
|
+
"#{@resource.path}/geo"
|
16
|
+
end
|
17
|
+
|
18
|
+
def cartesian_type(strip_width, x_min, y_min, x_max, y_max)
|
19
|
+
parameters = {
|
20
|
+
:stripWidth => strip_width.to_s,
|
21
|
+
:xmin => x_min.to_s,
|
22
|
+
:ymin => y_min.to_s,
|
23
|
+
:xmax => x_max.to_s,
|
24
|
+
:ymax => y_max.to_s
|
25
|
+
}
|
26
|
+
type = @resource.request :post, self.path + "/types/cartesian", :parameters => parameters, :expected_status_code => 200
|
27
|
+
type.sub! /^.*</, "<"
|
28
|
+
type.sub! />.*$/, ">"
|
29
|
+
type
|
30
|
+
end
|
31
|
+
|
32
|
+
def spherical_type(strip_width, unit, latitude_min, longitude_min, latitude_max, longitude_max)
|
33
|
+
parameters = {
|
34
|
+
:stripWidth => strip_width.to_s,
|
35
|
+
:unit => unit.to_s,
|
36
|
+
:latmin => latitude_min.to_s,
|
37
|
+
:longmin => longitude_min.to_s,
|
38
|
+
:latmax => latitude_max.to_s,
|
39
|
+
:longmax => longitude_max.to_s
|
40
|
+
}
|
41
|
+
type = @resource.request :post, self.path + "/types/spherical", :parameters => parameters, :expected_status_code => 200
|
42
|
+
type.sub! /^.*</, "<"
|
43
|
+
type.sub! />.*$/, ">"
|
44
|
+
type
|
45
|
+
end
|
46
|
+
|
47
|
+
def create_polygon(name, type, points)
|
48
|
+
raise ArgumentError, "at least three points has to defined" unless points.is_a?(Array) && points.size >= 3
|
49
|
+
parameters = {
|
50
|
+
:resource => "\"#{name}\"",
|
51
|
+
:point => points.map{ |point| "\"%+g%+g\"^^%s" % [ point[0], point[1], type ] }
|
52
|
+
}
|
53
|
+
@resource.request :put, self.path + "/polygon", :parameters => parameters, :expected_status_code => 204
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
def inside_box(type, predicate, x_min, y_min, x_max, y_max)
|
58
|
+
parameters = {
|
59
|
+
:type => type,
|
60
|
+
:predicate => predicate,
|
61
|
+
:xmin => x_min.to_s,
|
62
|
+
:ymin => y_min.to_s,
|
63
|
+
:xmax => x_max.to_s,
|
64
|
+
:ymax => y_max.to_s
|
65
|
+
}
|
66
|
+
@resource.request :get, self.path + "/box", :parameters => parameters, :expected_status_code => 200
|
67
|
+
end
|
68
|
+
|
69
|
+
def inside_circle(type, predicate, x, y, radius)
|
70
|
+
parameters = {
|
71
|
+
:type => type,
|
72
|
+
:predicate => predicate,
|
73
|
+
:x => x.to_s,
|
74
|
+
:y => y.to_s,
|
75
|
+
:radius => radius.to_s
|
76
|
+
}
|
77
|
+
@resource.request :get, self.path + "/circle", :parameters => parameters, :expected_status_code => 200
|
78
|
+
end
|
79
|
+
|
80
|
+
def inside_haversine(type, predicate, latitude, longitude, radius, unit = :km)
|
81
|
+
parameters = {
|
82
|
+
:type => type,
|
83
|
+
:predicate => predicate,
|
84
|
+
:lat => float_to_iso_6709(latitude, 2),
|
85
|
+
:long => float_to_iso_6709(longitude, 3),
|
86
|
+
:radius => radius.to_s,
|
87
|
+
:unit => unit.to_s
|
88
|
+
}
|
89
|
+
@resource.request :get, self.path + "/haversine", :parameters => parameters, :expected_status_code => 200
|
90
|
+
end
|
91
|
+
|
92
|
+
def inside_polygon(type, predicate, name)
|
93
|
+
parameters = {
|
94
|
+
:type => type,
|
95
|
+
:predicate => predicate,
|
96
|
+
:polygon => "\"#{name}\""
|
97
|
+
}
|
98
|
+
@resource.request :get, self.path + "/polygon", :parameters => parameters, :expected_status_code => 200
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def float_to_iso_6709(value, digits)
|
104
|
+
sign = "+"
|
105
|
+
if value < 0
|
106
|
+
sign = "-"
|
107
|
+
value = -value
|
108
|
+
end
|
109
|
+
floor = value.to_i
|
110
|
+
sign + (("%%0%dd" % digits) % floor) + (".%07d" % ((value - floor) * 10000000))
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
module AllegroGraph
|
3
|
+
|
4
|
+
module Proxy
|
5
|
+
|
6
|
+
class Mapping
|
7
|
+
|
8
|
+
attr_reader :resource
|
9
|
+
|
10
|
+
def initialize(resource)
|
11
|
+
@resource = resource
|
12
|
+
end
|
13
|
+
|
14
|
+
def path
|
15
|
+
"#{@resource.path}/mapping"
|
16
|
+
end
|
17
|
+
|
18
|
+
def create(type, encoding)
|
19
|
+
parameters = { :type => type, :encoding => encoding }
|
20
|
+
@resource.request :put, self.path + "/type", :parameters => parameters, :expected_status_code => 204
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(type)
|
25
|
+
parameters = { :type => type }
|
26
|
+
@resource.request :delete, self.path + "/type", :parameters => parameters, :expected_status_code => 204
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|