sparql 0.0.1 → 0.0.2
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/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
|