sparql 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +3 -0
- data/CREDITS +0 -0
- data/README.markdown +103 -53
- data/UNLICENSE +24 -0
- data/VERSION +1 -0
- data/bin/sparql +87 -0
- data/lib/sparql.rb +105 -22
- data/lib/sparql/algebra.rb +369 -0
- data/lib/sparql/algebra/evaluatable.rb +37 -0
- data/lib/sparql/algebra/expression.rb +284 -0
- data/lib/sparql/algebra/extensions.rb +159 -0
- data/lib/sparql/algebra/operator.rb +492 -0
- data/lib/sparql/algebra/operator/add.rb +34 -0
- data/lib/sparql/algebra/operator/and.rb +65 -0
- data/lib/sparql/algebra/operator/asc.rb +29 -0
- data/lib/sparql/algebra/operator/ask.rb +46 -0
- data/lib/sparql/algebra/operator/base.rb +46 -0
- data/lib/sparql/algebra/operator/bgp.rb +26 -0
- data/lib/sparql/algebra/operator/bound.rb +48 -0
- data/lib/sparql/algebra/operator/compare.rb +84 -0
- data/lib/sparql/algebra/operator/construct.rb +85 -0
- data/lib/sparql/algebra/operator/dataset.rb +77 -0
- data/lib/sparql/algebra/operator/datatype.rb +42 -0
- data/lib/sparql/algebra/operator/desc.rb +17 -0
- data/lib/sparql/algebra/operator/describe.rb +71 -0
- data/lib/sparql/algebra/operator/distinct.rb +50 -0
- data/lib/sparql/algebra/operator/divide.rb +43 -0
- data/lib/sparql/algebra/operator/equal.rb +32 -0
- data/lib/sparql/algebra/operator/exprlist.rb +52 -0
- data/lib/sparql/algebra/operator/filter.rb +71 -0
- data/lib/sparql/algebra/operator/graph.rb +28 -0
- data/lib/sparql/algebra/operator/greater_than.rb +32 -0
- data/lib/sparql/algebra/operator/greater_than_or_equal.rb +33 -0
- data/lib/sparql/algebra/operator/is_blank.rb +35 -0
- data/lib/sparql/algebra/operator/is_iri.rb +37 -0
- data/lib/sparql/algebra/operator/is_literal.rb +36 -0
- data/lib/sparql/algebra/operator/join.rb +67 -0
- data/lib/sparql/algebra/operator/lang.rb +29 -0
- data/lib/sparql/algebra/operator/lang_matches.rb +53 -0
- data/lib/sparql/algebra/operator/left_join.rb +95 -0
- data/lib/sparql/algebra/operator/less_than.rb +32 -0
- data/lib/sparql/algebra/operator/less_than_or_equal.rb +32 -0
- data/lib/sparql/algebra/operator/minus.rb +31 -0
- data/lib/sparql/algebra/operator/multiply.rb +34 -0
- data/lib/sparql/algebra/operator/not.rb +35 -0
- data/lib/sparql/algebra/operator/not_equal.rb +26 -0
- data/lib/sparql/algebra/operator/or.rb +65 -0
- data/lib/sparql/algebra/operator/order.rb +69 -0
- data/lib/sparql/algebra/operator/plus.rb +31 -0
- data/lib/sparql/algebra/operator/prefix.rb +45 -0
- data/lib/sparql/algebra/operator/project.rb +46 -0
- data/lib/sparql/algebra/operator/reduced.rb +47 -0
- data/lib/sparql/algebra/operator/regex.rb +70 -0
- data/lib/sparql/algebra/operator/same_term.rb +46 -0
- data/lib/sparql/algebra/operator/slice.rb +60 -0
- data/lib/sparql/algebra/operator/str.rb +35 -0
- data/lib/sparql/algebra/operator/subtract.rb +32 -0
- data/lib/sparql/algebra/operator/union.rb +55 -0
- data/lib/sparql/algebra/query.rb +99 -0
- data/lib/sparql/algebra/sxp_extensions.rb +35 -0
- data/lib/sparql/algebra/version.rb +20 -0
- data/lib/sparql/extensions.rb +102 -0
- data/lib/sparql/grammar.rb +298 -0
- data/lib/sparql/grammar/lexer.rb +609 -0
- data/lib/sparql/grammar/parser.rb +1383 -0
- data/lib/sparql/grammar/parser/meta.rb +1801 -0
- data/lib/sparql/results.rb +220 -0
- data/lib/sparql/version.rb +20 -0
- metadata +232 -62
- data/Rakefile +0 -22
- data/coverage/index.html +0 -252
- data/coverage/lib-sparql-execute_sparql_rb.html +0 -621
- data/coverage/lib-sparql_rb.html +0 -622
- data/lib/sparql/execute_sparql.rb +0 -27
- data/lib/sparql/sparql.treetop +0 -159
- data/sparql.gemspec +0 -16
- data/spec/spec.opts +0 -2
- data/spec/spec_helper.rb +0 -24
- data/spec/unit/graph_parsing_spec.rb +0 -76
- data/spec/unit/iri_parsing_spec.rb +0 -46
- data/spec/unit/prefixed_names_parsing_spec.rb +0 -40
- data/spec/unit/primitives_parsing_spec.rb +0 -26
- data/spec/unit/sparql_parsing_spec.rb +0 -72
- data/spec/unit/variables_parsing_spec.rb +0 -36
@@ -0,0 +1,369 @@
|
|
1
|
+
require 'rdf' # @see http://rubygems.org/gems/rdf
|
2
|
+
require 'rdf/xsd'
|
3
|
+
|
4
|
+
module SPARQL
|
5
|
+
##
|
6
|
+
# A SPARQL algebra for RDF.rb.
|
7
|
+
#
|
8
|
+
# Parses Sparql S-Expressions (SSE) into SPARQL Algebra operators.
|
9
|
+
#
|
10
|
+
# Operators implementing {SPARQL::Algebra::Query#execute} may directly
|
11
|
+
# execute an object implementing {RDF::Queryable}, and so may be treated
|
12
|
+
# equivalently to {RDF::Query}.
|
13
|
+
#
|
14
|
+
# Operators implementing {SPARQL::Algebra::Expression#evaluate} may be
|
15
|
+
# evaluated with RDF::Query::Solution bindings to yield an appropriate result.
|
16
|
+
#
|
17
|
+
# An entire SSE expression is parsed into a recursive set of {SPARQL::Algebra::Operator}
|
18
|
+
# instances, with each operand representing an additional operator.
|
19
|
+
#
|
20
|
+
# {RDF::Query} and {RDF::Query::Pattern} are used as primitives for `bgp` and `triple` expressions.
|
21
|
+
#
|
22
|
+
# # Queries
|
23
|
+
#
|
24
|
+
# require 'sparql/algebra'
|
25
|
+
#
|
26
|
+
# include SPARQL::Algebra
|
27
|
+
#
|
28
|
+
# ## Basic Query
|
29
|
+
# BASE <http://example.org/x/>
|
30
|
+
# PREFIX : <>
|
31
|
+
#
|
32
|
+
# SELECT * WHERE { :x ?p ?v }
|
33
|
+
#
|
34
|
+
# is equivalent to
|
35
|
+
#
|
36
|
+
# (base <http://example.org/x/>
|
37
|
+
# (prefix ((: <>))
|
38
|
+
# (bgp (triple :x ?p ?v))))
|
39
|
+
#
|
40
|
+
# ## Prefixes
|
41
|
+
#
|
42
|
+
# PREFIX ns: <http://example.org/ns#>
|
43
|
+
# PREFIX x: <http://example.org/x/>
|
44
|
+
#
|
45
|
+
# SELECT * WHERE { x:x ns:p ?v }
|
46
|
+
#
|
47
|
+
# is equivalent to
|
48
|
+
#
|
49
|
+
# (prefix ((ns: <http://example.org/ns#>)
|
50
|
+
# (x: <http://example.org/x/>))
|
51
|
+
# (bgp (triple x:x ns:p ?v)))
|
52
|
+
#
|
53
|
+
# ## Ask
|
54
|
+
#
|
55
|
+
# PREFIX : <http://example/>
|
56
|
+
#
|
57
|
+
# ASK WHERE { :x :p ?x }
|
58
|
+
#
|
59
|
+
# is equivalent to
|
60
|
+
#
|
61
|
+
# (prefix ((: <http://example/>))
|
62
|
+
# (ask
|
63
|
+
# (bgp (triple :x :p ?x))))
|
64
|
+
#
|
65
|
+
# ## Datasets
|
66
|
+
#
|
67
|
+
# PREFIX : <http://example/>
|
68
|
+
#
|
69
|
+
# SELECT *
|
70
|
+
# FROM <data-g1.ttl>
|
71
|
+
# FROM NAMED <data-g2.ttl>
|
72
|
+
# { ?s ?p ?o }
|
73
|
+
#
|
74
|
+
# is equivalent to
|
75
|
+
#
|
76
|
+
# (prefix ((: <http://example/>))
|
77
|
+
# (dataset (<data-g1.ttl> (named <data-g2.ttl>))
|
78
|
+
# (bgp (triple ?s ?p ?o))))
|
79
|
+
#
|
80
|
+
# ## Join
|
81
|
+
#
|
82
|
+
# PREFIX : <http://example/>
|
83
|
+
#
|
84
|
+
# SELECT *
|
85
|
+
# {
|
86
|
+
# ?s ?p ?o
|
87
|
+
# GRAPH ?g { ?s ?q ?v }
|
88
|
+
# }
|
89
|
+
#
|
90
|
+
# is equivalent to
|
91
|
+
#
|
92
|
+
# (prefix ((: <http://example/>))
|
93
|
+
# (join
|
94
|
+
# (bgp (triple ?s ?p ?o))
|
95
|
+
# (graph ?g
|
96
|
+
# (bgp (triple ?s ?q ?v)))))
|
97
|
+
#
|
98
|
+
# ## Union
|
99
|
+
#
|
100
|
+
# PREFIX : <http://example/>
|
101
|
+
#
|
102
|
+
# SELECT *
|
103
|
+
# {
|
104
|
+
# { ?s ?p ?o }
|
105
|
+
# UNION
|
106
|
+
# { GRAPH ?g { ?s ?p ?o } }
|
107
|
+
# }
|
108
|
+
#
|
109
|
+
# is equivalent to
|
110
|
+
#
|
111
|
+
# (prefix ((: <http://example/>))
|
112
|
+
# (union
|
113
|
+
# (bgp (triple ?s ?p ?o))
|
114
|
+
# (graph ?g
|
115
|
+
# (bgp (triple ?s ?p ?o)))))
|
116
|
+
#
|
117
|
+
# ## LeftJoin
|
118
|
+
#
|
119
|
+
# PREFIX : <http://example/>
|
120
|
+
#
|
121
|
+
# SELECT *
|
122
|
+
# {
|
123
|
+
# ?x :p ?v .
|
124
|
+
# OPTIONAL
|
125
|
+
# {
|
126
|
+
# ?y :q ?w .
|
127
|
+
# FILTER(?v=2)
|
128
|
+
# }
|
129
|
+
# }
|
130
|
+
#
|
131
|
+
# is equivalent to
|
132
|
+
#
|
133
|
+
# (prefix ((: <http://example/>))
|
134
|
+
# (leftjoin
|
135
|
+
# (bgp (triple ?x :p ?v))
|
136
|
+
# (bgp (triple ?y :q ?w))
|
137
|
+
# (= ?v 2)))
|
138
|
+
#
|
139
|
+
# # Expressions
|
140
|
+
#
|
141
|
+
# ## Constructing operator expressions manually
|
142
|
+
#
|
143
|
+
# Operator(:isBlank).new(RDF::Node(:foobar)).to_sxp #=> "(isBlank _:foobar)"
|
144
|
+
# Operator(:isIRI).new(RDF::URI('http://rdf.rubyforge.org/')).to_sxp #=> "(isIRI <http://rdf.rubyforge.org/>)"
|
145
|
+
# Operator(:isLiteral).new(RDF::Literal(3.1415)).to_sxp #=> "(isLiteral 3.1415)"
|
146
|
+
# Operator(:str).new(Operator(:datatype).new(RDF::Literal(3.1415))).to_sxp #=> "(str (datatype 3.1415))"
|
147
|
+
#
|
148
|
+
# ## Constructing operator expressions using SSE forms
|
149
|
+
#
|
150
|
+
# Expression[:isBlank, RDF::Node(:foobar)].to_sxp #=> "(isBlank _:foobar)"
|
151
|
+
# Expression[:isIRI, RDF::URI('http://rdf.rubyforge.org/')].to_sxp #=> "(isIRI <http://rdf.rubyforge.org/>)"
|
152
|
+
# Expression[:isLiteral, RDF::Literal(3.1415)].to_sxp #=> "(isLiteral 3.1415)"
|
153
|
+
# Expression[:str, [:datatype, RDF::Literal(3.1415)]].to_sxp #=> "(str (datatype 3.1415))"
|
154
|
+
#
|
155
|
+
# ## Constructing operator expressions using SSE strings
|
156
|
+
#
|
157
|
+
# Expression.parse('(isBlank _:foobar)')
|
158
|
+
# Expression.parse('(isIRI <http://rdf.rubyforge.org/>)')
|
159
|
+
# Expression.parse('(isLiteral 3.1415)')
|
160
|
+
# Expression.parse('(str (datatype 3.1415))')
|
161
|
+
#
|
162
|
+
# ## Evaluating operators standalone
|
163
|
+
#
|
164
|
+
# Operator(:isBlank).evaluate(RDF::Node(:foobar)) #=> RDF::Literal::TRUE
|
165
|
+
# Operator(:isIRI).evaluate(RDF::DC.title) #=> RDF::Literal::TRUE
|
166
|
+
# Operator(:isLiteral).evaluate(RDF::Literal(3.1415)) #=> RDF::Literal::TRUE
|
167
|
+
#
|
168
|
+
# ## Optimizing expressions containing constant subexpressions
|
169
|
+
#
|
170
|
+
# Expression.parse('(sameTerm ?var ?var)').optimize #=> RDF::Literal::TRUE
|
171
|
+
# Expression.parse('(* -2 (- (* (+ 1 2) (+ 3 4))))').optimize #=> RDF::Literal(42)
|
172
|
+
#
|
173
|
+
# ## Evaluating expressions on a solution sequence
|
174
|
+
#
|
175
|
+
# # Find all people and their names & e-mail addresses:
|
176
|
+
# solutions = RDF::Query.execute(RDF::Graph.load('etc/doap.ttl')) do |query|
|
177
|
+
# query.pattern [:person, RDF.type, RDF::FOAF.Person]
|
178
|
+
# query.pattern [:person, RDF::FOAF.name, :name]
|
179
|
+
# query.pattern [:person, RDF::FOAF.mbox, :email], :optional => true
|
180
|
+
# end
|
181
|
+
#
|
182
|
+
# # Find people who have a name but don't have a known e-mail address:
|
183
|
+
# expression = Expression[:not, [:bound, Variable(:email)]] # ...or just...
|
184
|
+
# expression = Expression.parse('(not (bound ?email))')
|
185
|
+
# solutions.filter!(expression)
|
186
|
+
#
|
187
|
+
# @example Optimizations
|
188
|
+
#
|
189
|
+
# Some very simple optimizations are currently implemented for `FILTER`
|
190
|
+
# expressions. Use the following to obtain optimized SSE forms:
|
191
|
+
#
|
192
|
+
# Expression.parse(sse).optimize.to_sse
|
193
|
+
#
|
194
|
+
# ## Constant comparison folding
|
195
|
+
#
|
196
|
+
# (sameTerm ?x ?x) #=> true
|
197
|
+
#
|
198
|
+
# ## Constant arithmetic folding
|
199
|
+
#
|
200
|
+
# (!= ?x (+ 123)) #=> (!= ?x 123)
|
201
|
+
# (!= ?x (- -1.0)) #=> (!= ?x 1.0)
|
202
|
+
# (!= ?x (+ 1 2)) #=> (!= ?x 3)
|
203
|
+
# (!= ?x (- 4 5)) #=> (!= ?x -1)
|
204
|
+
# (!= ?x (* 6 7)) #=> (!= ?x 42)
|
205
|
+
# (!= ?x (/ 0 0.0)) #=> (!= ?x NaN)
|
206
|
+
#
|
207
|
+
# ## Memoization
|
208
|
+
#
|
209
|
+
# Expressions can optionally be [memoized][memoization], which can speed up
|
210
|
+
# repeatedly executing the expression on a solution sequence:
|
211
|
+
#
|
212
|
+
# Expression.parse(sse, :memoize => true)
|
213
|
+
# Operator.new(*operands, :memoize => true)
|
214
|
+
#
|
215
|
+
# Memoization is implemented using RDF.rb's [RDF::Util::Cache][] utility
|
216
|
+
# library, a weak-reference cache that allows values contained in the cache to
|
217
|
+
# be garbage collected. This allows the cache to dynamically adjust to
|
218
|
+
# changing memory conditions, caching more objects when memory is plentiful,
|
219
|
+
# but evicting most objects if memory pressure increases to the point of
|
220
|
+
# scarcity.
|
221
|
+
#
|
222
|
+
# [memoization]: http://en.wikipedia.org/wiki/Memoization
|
223
|
+
# [RDF::Util::Cache]: http://rdf.rubyforge.org/RDF/Util/Cache.html
|
224
|
+
#
|
225
|
+
# ## Documentation
|
226
|
+
#
|
227
|
+
# * {SPARQL::Algebra}
|
228
|
+
# * {SPARQL::Algebra::Expression}
|
229
|
+
# * {SPARQL::Algebra::Query}
|
230
|
+
# * {SPARQL::Algebra::Operator}
|
231
|
+
# * {SPARQL::Algebra::Operator::Add}
|
232
|
+
# * {SPARQL::Algebra::Operator::And}
|
233
|
+
# * {SPARQL::Algebra::Operator::Asc}
|
234
|
+
# * {SPARQL::Algebra::Operator::Ask}
|
235
|
+
# * {SPARQL::Algebra::Operator::Base}
|
236
|
+
# * {SPARQL::Algebra::Operator::Bound}
|
237
|
+
# * {SPARQL::Algebra::Operator::Compare}
|
238
|
+
# * {SPARQL::Algebra::Operator::Construct}
|
239
|
+
# * {SPARQL::Algebra::Operator::Dataset}
|
240
|
+
# * {SPARQL::Algebra::Operator::Datatype}
|
241
|
+
# * {SPARQL::Algebra::Operator::Desc}
|
242
|
+
# * {SPARQL::Algebra::Operator::Describe}
|
243
|
+
# * {SPARQL::Algebra::Operator::Distinct}
|
244
|
+
# * {SPARQL::Algebra::Operator::Divide}
|
245
|
+
# * {SPARQL::Algebra::Operator::Equal}
|
246
|
+
# * {SPARQL::Algebra::Operator::Exprlist}
|
247
|
+
# * {SPARQL::Algebra::Operator::Filter}
|
248
|
+
# * {SPARQL::Algebra::Operator::Graph}
|
249
|
+
# * {SPARQL::Algebra::Operator::GreaterThan}
|
250
|
+
# * {SPARQL::Algebra::Operator::GreaterThanOrEqual}
|
251
|
+
# * {SPARQL::Algebra::Operator::IsBlank}
|
252
|
+
# * {SPARQL::Algebra::Operator::IsIRI}
|
253
|
+
# * {SPARQL::Algebra::Operator::IsLiteral}
|
254
|
+
# * {SPARQL::Algebra::Operator::Join}
|
255
|
+
# * {SPARQL::Algebra::Operator::Lang}
|
256
|
+
# * {SPARQL::Algebra::Operator::LangMatches}
|
257
|
+
# * {SPARQL::Algebra::Operator::LeftJoin}
|
258
|
+
# * {SPARQL::Algebra::Operator::LessThan}
|
259
|
+
# * {SPARQL::Algebra::Operator::LessThanOrEqual}
|
260
|
+
# * {SPARQL::Algebra::Operator::Minus}
|
261
|
+
# * {SPARQL::Algebra::Operator::Multiply}
|
262
|
+
# * {SPARQL::Algebra::Operator::Not}
|
263
|
+
# * {SPARQL::Algebra::Operator::NotEqual}
|
264
|
+
# * {SPARQL::Algebra::Operator::Or}
|
265
|
+
# * {SPARQL::Algebra::Operator::Order}
|
266
|
+
# * {SPARQL::Algebra::Operator::Plus}
|
267
|
+
# * {SPARQL::Algebra::Operator::Prefix}
|
268
|
+
# * {SPARQL::Algebra::Operator::Project}
|
269
|
+
# * {SPARQL::Algebra::Operator::Reduced}
|
270
|
+
# * {SPARQL::Algebra::Operator::Regex}
|
271
|
+
# * {SPARQL::Algebra::Operator::SameTerm}
|
272
|
+
# * {SPARQL::Algebra::Operator::Slice}
|
273
|
+
# * {SPARQL::Algebra::Operator::Str}
|
274
|
+
# * {SPARQL::Algebra::Operator::Subtract}
|
275
|
+
# * {SPARQL::Algebra::Operator::Union}
|
276
|
+
#
|
277
|
+
# TODO
|
278
|
+
# ====
|
279
|
+
# * Need to come up with appropriate SXP for SPARQL 1.1
|
280
|
+
# * Operator#optimize needs to be completed and tested.
|
281
|
+
#
|
282
|
+
# @see http://www.w3.org/TR/rdf-sparql-query/#sparqlAlgebra
|
283
|
+
module Algebra
|
284
|
+
include RDF
|
285
|
+
|
286
|
+
autoload :Evaluatable, 'sparql/algebra/evaluatable'
|
287
|
+
autoload :Expression, 'sparql/algebra/expression'
|
288
|
+
autoload :Operator, 'sparql/algebra/operator'
|
289
|
+
autoload :Query, 'sparql/algebra/query'
|
290
|
+
|
291
|
+
##
|
292
|
+
# @example
|
293
|
+
# sse = (prefix ((foaf: <http://xmlns.com/foaf/0.1/>))
|
294
|
+
# (project (?name ?mbox)
|
295
|
+
# (join
|
296
|
+
# (bgp (triple ?x foaf:name ?name))
|
297
|
+
# (bgp (triple ?x foaf:mbox ?mbox)))))
|
298
|
+
# }
|
299
|
+
# @param [String] sse
|
300
|
+
# a SPARQL S-Expression (SSE) string
|
301
|
+
# @param [Hash{Symbol => Object}] options
|
302
|
+
# any additional options (see {Operator#initialize})
|
303
|
+
# @return [SPARQL::Algebra::Operator]
|
304
|
+
def parse(sse, options = {})
|
305
|
+
Expression.parse(sse, options)
|
306
|
+
end
|
307
|
+
module_function :parse
|
308
|
+
|
309
|
+
##
|
310
|
+
# Parses input from the given file name or URL.
|
311
|
+
#
|
312
|
+
# @param [String, #to_s] filename
|
313
|
+
# @param [Hash{Symbol => Object}] options
|
314
|
+
# any additional options (see {Operator#initialize})
|
315
|
+
# @option options [RDF::URI, #to_s] :base_uri
|
316
|
+
# Base URI used for loading relative URIs.
|
317
|
+
#
|
318
|
+
# @yield [expression]
|
319
|
+
# @yieldparam [SPARQL::Algebra::Expression] expression
|
320
|
+
# @yieldreturn [void] ignored
|
321
|
+
# @return [Expression]
|
322
|
+
def open(sse, options = {})
|
323
|
+
Expression.open(sse, options)
|
324
|
+
end
|
325
|
+
module_function :open
|
326
|
+
|
327
|
+
##
|
328
|
+
# @example
|
329
|
+
# Expression(:isLiteral, RDF::Literal(3.1415))
|
330
|
+
#
|
331
|
+
# @param [Array] sse
|
332
|
+
# a SPARQL S-Expression (SSE) form
|
333
|
+
# @return [SPARQL::Algebra::Expression]
|
334
|
+
def Expression(*sse)
|
335
|
+
Expression.for(*sse)
|
336
|
+
end
|
337
|
+
alias_method :Expr, :Expression
|
338
|
+
module_function :Expr, :Expression
|
339
|
+
|
340
|
+
##
|
341
|
+
# @example
|
342
|
+
# Operator(:isLiteral)
|
343
|
+
#
|
344
|
+
# @param [Symbol, #to_sym] name
|
345
|
+
# @return [Class]
|
346
|
+
def Operator(name, arity = nil)
|
347
|
+
Operator.for(name, arity)
|
348
|
+
end
|
349
|
+
alias_method :Op, :Operator
|
350
|
+
module_function :Op, :Operator
|
351
|
+
|
352
|
+
##
|
353
|
+
# @example
|
354
|
+
# Variable(:foobar)
|
355
|
+
#
|
356
|
+
# @param [Symbol, #to_sym] name
|
357
|
+
# @return [Variable]
|
358
|
+
# @see http://rdf.rubyforge.org/RDF/Query/Variable.html
|
359
|
+
def Variable(name)
|
360
|
+
Variable.new(name)
|
361
|
+
end
|
362
|
+
alias_method :Var, :Variable
|
363
|
+
module_function :Var, :Variable
|
364
|
+
|
365
|
+
Variable = RDF::Query::Variable
|
366
|
+
end # Algebra
|
367
|
+
end # SPARQL
|
368
|
+
|
369
|
+
require 'sparql/algebra/extensions'
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module SPARQL; module Algebra
|
2
|
+
##
|
3
|
+
# Mixin for Algebra::Operator sub-classes that evaluate bindings to return a result
|
4
|
+
#
|
5
|
+
# @abstract
|
6
|
+
module Evaluatable
|
7
|
+
##
|
8
|
+
# Evaluates this operator using the given variable `bindings`.
|
9
|
+
#
|
10
|
+
# @param [RDF::Query::Solution, #[]] bindings
|
11
|
+
# a query solution containing zero or more variable bindings
|
12
|
+
# @return [RDF::Term]
|
13
|
+
# @abstract
|
14
|
+
def evaluate(bindings = {})
|
15
|
+
args = operands.map { |operand| operand.evaluate(bindings) }
|
16
|
+
options[:memoize] ? memoize(*args) : apply(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# @param [Array<RDF::Term>] operands
|
21
|
+
# evaluated operands
|
22
|
+
# @return [RDF::Term] the memoized result
|
23
|
+
def memoize(*operands)
|
24
|
+
@cache ||= RDF::Util::Cache.new(options[:memoize].is_a?(Integer) ? options[:memoize] : -1)
|
25
|
+
@cache[operands] ||= apply(*operands)
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# @param [Array<RDF::Term>] operands
|
30
|
+
# evaluated operands
|
31
|
+
# @return [RDF::Term]
|
32
|
+
# @abstract
|
33
|
+
def apply(*operands)
|
34
|
+
raise NotImplementedError, "#{self.class}#apply(#{operands.map(&:class).join(', ')})"
|
35
|
+
end
|
36
|
+
end # Query
|
37
|
+
end; end # SPARQL::Algebra
|
@@ -0,0 +1,284 @@
|
|
1
|
+
module SPARQL; module Algebra
|
2
|
+
##
|
3
|
+
# A SPARQL algebra expression.
|
4
|
+
#
|
5
|
+
# @abstract
|
6
|
+
module Expression
|
7
|
+
##
|
8
|
+
# @example
|
9
|
+
# Expression.parse('(isLiteral 3.1415)')
|
10
|
+
#
|
11
|
+
# @param [IO, String, #read, #to_s] sse
|
12
|
+
# a SPARQL S-Expression (SSE) string or IO object responding to #read
|
13
|
+
# @param [Hash{Symbol => Object}] options
|
14
|
+
# any additional options (see {Operator#initialize})
|
15
|
+
# @option options [RDF::URI, #to_s] :base_uri
|
16
|
+
# Base URI used for loading relative URIs.
|
17
|
+
#
|
18
|
+
# @yield [expression]
|
19
|
+
# @yieldparam [SPARQL::Algebra::Expression] expression
|
20
|
+
# @yieldreturn [void] ignored
|
21
|
+
# @return [Expression]
|
22
|
+
def self.parse(sse, options = {}, &block)
|
23
|
+
begin
|
24
|
+
require 'sxp' # @see http://rubygems.org/gems/sxp
|
25
|
+
rescue LoadError
|
26
|
+
abort "SPARQL::Algebra::Expression.parse requires the SXP gem (hint: `gem install sxp')."
|
27
|
+
end
|
28
|
+
require 'sparql/algebra/sxp_extensions'
|
29
|
+
|
30
|
+
if sse.respond_to?(:force_encoding) # Ruby 1.9+
|
31
|
+
sse = sse.dup.force_encoding(Encoding::UTF_8)
|
32
|
+
end
|
33
|
+
sxp = SXP::Reader::SPARQL.new(sse) do |reader|
|
34
|
+
# Set base_uri if we have one
|
35
|
+
reader.base_uri = options[:base_uri] if options[:base_uri]
|
36
|
+
end
|
37
|
+
sxp_result = sxp.read
|
38
|
+
|
39
|
+
debug(options) {"base_uri: #{options[:base_uri]}"}
|
40
|
+
Operator.base_uri = options.delete(:base_uri) if options.has_key?(:base_uri)
|
41
|
+
Operator.prefixes = sxp.prefixes || {}
|
42
|
+
|
43
|
+
expression = self.new(sxp_result, options)
|
44
|
+
|
45
|
+
yield(expression) if block_given?
|
46
|
+
expression
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Parses input from the given file name or URL.
|
51
|
+
#
|
52
|
+
# @param [String, #to_s] filename
|
53
|
+
# @param [Hash{Symbol => Object}] options
|
54
|
+
# any additional options (see {Operator#initialize})
|
55
|
+
# @option options [RDF::URI, #to_s] :base_uri
|
56
|
+
# Base URI used for loading relative URIs.
|
57
|
+
#
|
58
|
+
# @yield [expression]
|
59
|
+
# @yieldparam [SPARQL::Algebra::Expression] expression
|
60
|
+
# @yieldreturn [void] ignored
|
61
|
+
# @return [Expression]
|
62
|
+
def self.open(filename, options = {}, &block)
|
63
|
+
RDF::Util::File.open_file(filename, options) do |file|
|
64
|
+
options[:base_uri] ||= filename
|
65
|
+
Expression.parse(file, options, &block)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# @example
|
71
|
+
# Expression.for(:isLiteral, RDF::Literal(3.1415))
|
72
|
+
# Expression[:isLiteral, RDF::Literal(3.1415)]
|
73
|
+
#
|
74
|
+
# @param [Array] sse
|
75
|
+
# a SPARQL S-Expression (SSE) form
|
76
|
+
# @return [Expression]
|
77
|
+
def self.for(*sse)
|
78
|
+
self.new(sse)
|
79
|
+
end
|
80
|
+
class << self; alias_method :[], :for; end
|
81
|
+
|
82
|
+
##
|
83
|
+
# @example
|
84
|
+
# Expression.new([:isLiteral, RDF::Literal(3.1415)], :version => 1.0)
|
85
|
+
#
|
86
|
+
# @param [Array] sse
|
87
|
+
# a SPARQL S-Expression (SSE) form
|
88
|
+
# @param [Hash{Symbol => Object}] options
|
89
|
+
# any additional options (see {Operator#initialize})
|
90
|
+
# @return [Expression]
|
91
|
+
# @raise [TypeError] if any of the operands is invalid
|
92
|
+
def self.new(sse, options = {})
|
93
|
+
raise ArgumentError, "invalid SPARQL::Algebra::Expression form: #{sse.inspect}" unless sse.is_a?(Array)
|
94
|
+
|
95
|
+
operator = Operator.for(sse.first, sse.length - 1)
|
96
|
+
unless operator
|
97
|
+
return case sse.first
|
98
|
+
when Array
|
99
|
+
debug(options) {"Map array elements #{sse}"}
|
100
|
+
sse.map {|s| self.new(s, options.merge(:depth => options[:depth].to_i + 1))}
|
101
|
+
else
|
102
|
+
debug(options) {"No operator found for #{sse.first}"}
|
103
|
+
sse.map do |s|
|
104
|
+
s.is_a?(Array) ?
|
105
|
+
self.new(s, options.merge(:depth => options[:depth].to_i + 1)) :
|
106
|
+
s
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
operands = sse[1..-1].map do |operand|
|
112
|
+
debug(options) {"Operator=#{operator.inspect}, Operand=#{operand.inspect}"}
|
113
|
+
case operand
|
114
|
+
when Array
|
115
|
+
self.new(operand, options.merge(:depth => options[:depth].to_i + 1))
|
116
|
+
when Operator, Variable, RDF::Term, RDF::Query, Symbol
|
117
|
+
operand
|
118
|
+
when TrueClass, FalseClass, Numeric, String, DateTime, Date, Time
|
119
|
+
RDF::Literal(operand)
|
120
|
+
else raise TypeError, "invalid SPARQL::Algebra::Expression operand: #{operand.inspect}"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
debug(options) {"#{operator.inspect}(#{operands.map(&:inspect).join(',')})"}
|
125
|
+
options.delete_if {|k, v| [:debug, :depth, :prefixes, :base_uri].include?(k) }
|
126
|
+
operands << options unless options.empty?
|
127
|
+
operator.new(*operands)
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
# Casts operand as the specified datatype
|
132
|
+
#
|
133
|
+
# @param [RDF::URI] datatype
|
134
|
+
# Datatype to evaluate, one of:
|
135
|
+
# xsd:integer, xsd:decimal xsd:float, xsd:double, xsd:string, xsd:boolean, or xsd:dateTime
|
136
|
+
# @param [RDF::Term] value
|
137
|
+
# Value, which should be a typed literal, where the type must be that specified
|
138
|
+
# @raise [TypeError] if datatype is not a URI or value cannot be cast to datatype
|
139
|
+
# @return [Boolean]
|
140
|
+
# @see http://www.w3.org/TR/rdf-sparql-query/#FunctionMapping
|
141
|
+
def self.cast(datatype, value)
|
142
|
+
case datatype
|
143
|
+
when RDF::XSD.dateTime
|
144
|
+
case value
|
145
|
+
when RDF::Literal::DateTime, RDF::Literal::Date, RDF::Literal::Time
|
146
|
+
RDF::Literal.new(value, :datatype => datatype)
|
147
|
+
when RDF::Literal::Numeric, RDF::Literal::Boolean, RDF::URI, RDF::Node
|
148
|
+
raise TypeError, "Value #{value.inspect} cannot be cast as #{datatype}"
|
149
|
+
else
|
150
|
+
RDF::Literal.new(value.value, :datatype => datatype, :validate => true)
|
151
|
+
end
|
152
|
+
when RDF::XSD.float, RDF::XSD.double
|
153
|
+
case value
|
154
|
+
when RDF::Literal::Numeric, RDF::Literal::Boolean
|
155
|
+
RDF::Literal.new(value, :datatype => datatype)
|
156
|
+
when RDF::Literal::DateTime, RDF::Literal::Date, RDF::Literal::Time, RDF::URI, RDF::Node
|
157
|
+
raise TypeError, "Value #{value.inspect} cannot be cast as #{datatype}"
|
158
|
+
else
|
159
|
+
RDF::Literal.new(value.value, :datatype => datatype, :validate => true)
|
160
|
+
end
|
161
|
+
when RDF::XSD.boolean
|
162
|
+
case value
|
163
|
+
when RDF::Literal::Boolean
|
164
|
+
value
|
165
|
+
when RDF::Literal::Numeric
|
166
|
+
RDF::Literal::Boolean.new(value.value != 0)
|
167
|
+
when RDF::Literal::DateTime, RDF::Literal::Date, RDF::Literal::Time, RDF::URI, RDF::Node
|
168
|
+
raise TypeError, "Value #{value.inspect} cannot be cast as #{datatype}"
|
169
|
+
else
|
170
|
+
RDF::Literal.new(!value.to_s.empty?, :datatype => datatype, :validate => true)
|
171
|
+
end
|
172
|
+
when RDF::XSD.decimal, RDF::XSD.integer
|
173
|
+
case value
|
174
|
+
when RDF::Literal::Integer, RDF::Literal::Decimal, RDF::Literal::Boolean
|
175
|
+
RDF::Literal.new(value, :datatype => datatype)
|
176
|
+
when RDF::Literal::DateTime, RDF::Literal::Date, RDF::Literal::Time, RDF::URI, RDF::Node
|
177
|
+
raise TypeError, "Value #{value.inspect} cannot be cast as #{datatype}"
|
178
|
+
else
|
179
|
+
RDF::Literal.new(value.value, :datatype => datatype, :validate => true)
|
180
|
+
end
|
181
|
+
when RDF::XSD.string
|
182
|
+
RDF::Literal.new(value, :datatype => datatype)
|
183
|
+
else
|
184
|
+
raise TypeError, "Expected datatype (#{datatype}) to be an XSD type"
|
185
|
+
end
|
186
|
+
rescue
|
187
|
+
raise TypeError, $!.message
|
188
|
+
end
|
189
|
+
|
190
|
+
##
|
191
|
+
# Returns `false`.
|
192
|
+
#
|
193
|
+
# @return [Boolean] `true` or `false`
|
194
|
+
# @see #variable?
|
195
|
+
def variable?
|
196
|
+
false
|
197
|
+
end
|
198
|
+
|
199
|
+
##
|
200
|
+
# Returns `true`.
|
201
|
+
#
|
202
|
+
# @return [Boolean] `true` or `false`
|
203
|
+
# @see #variable?
|
204
|
+
def constant?
|
205
|
+
!(variable?)
|
206
|
+
end
|
207
|
+
|
208
|
+
##
|
209
|
+
# Returns an optimized version of this expression.
|
210
|
+
#
|
211
|
+
# This is the default implementation, which simply returns `self`.
|
212
|
+
# Subclasses can override this method in order to implement something
|
213
|
+
# more useful.
|
214
|
+
#
|
215
|
+
# @return [Expression] `self`
|
216
|
+
def optimize
|
217
|
+
self
|
218
|
+
end
|
219
|
+
|
220
|
+
##
|
221
|
+
# Evaluates this expression using the given variable `bindings`.
|
222
|
+
#
|
223
|
+
# This is the default implementation, which simply returns `self`.
|
224
|
+
# Subclasses can override this method in order to implement something
|
225
|
+
# more useful.
|
226
|
+
#
|
227
|
+
# @param [RDF::Query::Solution, #[]] bindings
|
228
|
+
# @return [Expression] `self`
|
229
|
+
def evaluate(bindings = {})
|
230
|
+
self
|
231
|
+
end
|
232
|
+
|
233
|
+
##
|
234
|
+
# Returns the SPARQL S-Expression (SSE) representation of this expression.
|
235
|
+
#
|
236
|
+
# This is the default implementation, which simply returns `self`.
|
237
|
+
# Subclasses can override this method in order to implement something
|
238
|
+
# more useful.
|
239
|
+
#
|
240
|
+
# @return [Array] `self`
|
241
|
+
# @see http://openjena.org/wiki/SSE
|
242
|
+
def to_sse
|
243
|
+
self
|
244
|
+
end
|
245
|
+
|
246
|
+
private
|
247
|
+
# @overload: May be called with node, message and an option hash
|
248
|
+
# @param [String] node processing node
|
249
|
+
# @param [String] message
|
250
|
+
# @param [Hash{Symbol => Object}] options
|
251
|
+
# @option options [Boolean] :debug output debug messages to $stderr
|
252
|
+
# @option options [Integer] :depth (@productions.length)
|
253
|
+
# Processing depth for indenting message output.
|
254
|
+
# @yieldreturn [String] appended to message, to allow for lazy-evaulation of message
|
255
|
+
#
|
256
|
+
# @overload: May be called with node and an option hash
|
257
|
+
# @param [String] node processing node
|
258
|
+
# @param [Hash{Symbol => Object}] options
|
259
|
+
# @option options [Boolean] :debug output debug messages to $stderr
|
260
|
+
# @option options [Integer] :depth (@productions.length)
|
261
|
+
# Processing depth for indenting message output.
|
262
|
+
# @yieldreturn [String] appended to message, to allow for lazy-evaulation of message
|
263
|
+
#
|
264
|
+
# @overload: May be called with only options, in which case the block is used to return the output message
|
265
|
+
# @param [String] node processing node
|
266
|
+
# @param [Hash{Symbol => Object}] options
|
267
|
+
# @option options [Boolean] :debug output debug messages to $stderr
|
268
|
+
# @option options [Integer] :depth (@productions.length)
|
269
|
+
# Processing depth for indenting message output.
|
270
|
+
# @yieldreturn [String] appended to message, to allow for lazy-evaulation of message
|
271
|
+
def self.debug(*args)
|
272
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
273
|
+
return unless options[:debug]
|
274
|
+
message = args.join(": ")
|
275
|
+
message = message + yield if block_given?
|
276
|
+
depth = options[:depth] || 0
|
277
|
+
$stderr.puts("#{' ' * depth}#{message}") if options[:debug]
|
278
|
+
end
|
279
|
+
|
280
|
+
def debug(*args, &block)
|
281
|
+
Expression.debug(*args, &block)
|
282
|
+
end
|
283
|
+
end # Expression
|
284
|
+
end; end # SPARQL::Algebra
|