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