module SPARQL::Algebra

A SPARQL algebra for RDF.rb.

Parses Sparql S-Expressions (SSE) into SPARQL Algebra operators.

Operators implementing {SPARQL::Algebra::Query#execute} may directly execute an object implementing {RDF::Queryable}, and so may be treated equivalently to {RDF::Query}.

Operators implementing {SPARQL::Algebra::Expression#evaluate} may be evaluated with RDF::Query::Solution bindings to yield an appropriate result.

An entire SSE expression is parsed into a recursive set of {SPARQL::Algebra::Operator} instances, with each operand representing an additional operator.

{RDF::Query} and {RDF::Query::Pattern} are used as primitives for `bgp` and `triple` expressions.

# Queries

require 'sparql/algebra'

include SPARQL::Algebra

## Basic Query

BASE <http://example.org/x/> 
PREFIX : <>

SELECT * WHERE { :x ?p ?v }

is equivalent to

(base <http://example.org/x/>
  (prefix ((: <>))
    (bgp (triple :x ?p ?v))))

## Prefixes

PREFIX ns: <http://example.org/ns#>
PREFIX x:  <http://example.org/x/>

SELECT * WHERE { x:x ns:p ?v }

is equivalent to

(prefix ((ns: <http://example.org/ns#>)
         (x: <http://example.org/x/>))
  (bgp (triple x:x ns:p ?v)))

## Ask

PREFIX :  <http://example/>

ASK WHERE { :x :p ?x }

is equivalent to

(prefix ((: <http://example/>))
  (ask
    (bgp (triple :x :p ?x))))

## Datasets

PREFIX : <http://example/> 

SELECT * 
FROM <data-g1.ttl>
FROM NAMED <data-g2.ttl>
{ ?s ?p ?o }

is equivalent to

(prefix ((: <http://example/>))
  (dataset (<data-g1.ttl> (named <data-g2.ttl>))
    (bgp (triple ?s ?p ?o))))

## Join

PREFIX : <http://example/> 

SELECT * 
{ 
   ?s ?p ?o
   GRAPH ?g { ?s ?q ?v }
}

is equivalent to

(prefix ((: <http://example/>))
  (join
    (bgp (triple ?s ?p ?o))
    (graph ?g
      (bgp (triple ?s ?q ?v)))))

## Union

PREFIX : <http://example/> 

SELECT * 
{ 
   { ?s ?p ?o }
  UNION
   { GRAPH ?g { ?s ?p ?o } }
}

is equivalent to

(prefix ((: <http://example/>))
  (union
    (bgp (triple ?s ?p ?o))
    (graph ?g
      (bgp (triple ?s ?p ?o)))))

## LeftJoin

PREFIX :    <http://example/>

SELECT *
{ 
  ?x :p ?v .
  OPTIONAL
  { 
    ?y :q ?w .
    FILTER(?v=2)
  }
}

is equivalent to

(prefix ((: <http://example/>))
  (leftjoin
    (bgp (triple ?x :p ?v))
    (bgp (triple ?y :q ?w))
    (= ?v 2)))

# Expressions

## Constructing operator expressions manually

Operator(:isBlank).new(RDF::Node(:foobar)).to_sxp                        #=> "(isBlank _:foobar)"
Operator(:isIRI).new(RDF::URI('https://rubygems.org/gems/rdf/')).to_sxp       #=> "(isIRI <https://rubygems.org/gems/rdf/>)"
Operator(:isLiteral).new(RDF::Literal(3.1415)).to_sxp                    #=> "(isLiteral 3.1415e0)"
Operator(:str).new(Operator(:datatype).new(RDF::Literal(3.1415))).to_sxp #=> "(str (datatype 3.1415e0))"

## Constructing operator expressions using SSE forms

SPARQL::Algebra::Expression[:isBlank, RDF::Node(:foobar)].to_sxp                          #=> "(isBlank _:foobar)"
SPARQL::Algebra::Expression[:isIRI, RDF::URI('https://rubygems.org/gems/rdf/')].to_sxp         #=> "(isIRI <https://rubygems.org/gems/rdf/>)"
SPARQL::Algebra::Expression[:isLiteral, RDF::Literal(3.1415)].to_sxp                      #=> "(isLiteral 3.1415e0)"
SPARQL::Algebra::Expression[:str, [:datatype, RDF::Literal(3.1415)]].to_sxp               #=> "(str (datatype 3.1415e0))"

## Constructing operator expressions using SSE strings

SPARQL::Algebra::Expression.parse('(isBlank _:foobar)')
SPARQL::Algebra::Expression.parse('(isIRI <https://rubygems.org/gems/rdf/>)')
SPARQL::Algebra::Expression.parse('(isLiteral 3.1415)')
SPARQL::Algebra::Expression.parse('(str (datatype 3.1415))')

## Evaluating operators standalone

Operator(:isBlank).evaluate(RDF::Node(:foobar))                          #=> RDF::Literal::TRUE
Operator(:isIRI).evaluate(RDF::Vocab::DC.title)                                 #=> RDF::Literal::TRUE
Operator(:isLiteral).evaluate(RDF::Literal(3.1415))                      #=> RDF::Literal::TRUE

## Evaluating expressions on a solution sequence

# Find all people and their names & e-mail addresses:
solutions = RDF::Query.execute(RDF::Graph.load('etc/doap.ttl')) do |query|
  query.pattern [:person, RDF.type,  RDF::Vocab::FOAF.Person]
  query.pattern [:person, RDF::Vocab::FOAF.name, :name]
  query.pattern [:person, RDF::Vocab::FOAF.mbox, :email], optional: true
end

# Find people who have a name but don't have a known e-mail address:
expression = SPARQL::Algebra::Expression[:not, [:bound, Variable(:email)]]    # ...or just...
expression = SPARQL::Algebra::Expression.parse('(not (bound ?email))')
solutions.filter!(expression)

@example Optimizations

Some very simple optimizations are currently implemented for `FILTER` expressions. Use the following to obtain optimized SSE forms:

SPARQL::Algebra::Expression.parse(sse).optimize.to_sxp_bin

## Constant comparison folding

(sameTerm ?x ?x)   #=> true

## Constant arithmetic folding

(!= ?x (+ 123))    #=> (!= ?x 123)
(!= ?x (- -1.0))   #=> (!= ?x 1.0)
(!= ?x (+ 1 2))    #=> (!= ?x 3)
(!= ?x (- 4 5))    #=> (!= ?x -1)
(!= ?x (* 6 7))    #=> (!= ?x 42)
(!= ?x (/ 0 0.0))  #=> (!= ?x NaN)

## Memoization

Expressions can optionally be [memoized], which can speed up repeatedly executing the expression on a solution sequence:

SPARQL::Algebra::Expression.parse(sse, memoize: true)
Operator.new(*operands, memoize: true)

Memoization is implemented using RDF.rb's [RDF::Util::Cache][] utility library, a weak-reference cache that allows values contained in the cache to be garbage collected. This allows the cache to dynamically adjust to changing memory conditions, caching more objects when memory is plentiful, but evicting most objects if memory pressure increases to the point of scarcity.

[memoization]: en.wikipedia.org/wiki/Memoization [RDF::Util::Cache]: www.rubydoc.info/github/ruby-rdf/rdf/RDF/Util/Cache

## Documentation

TODO

@see www.w3.org/TR/sparql11-query/#sparqlAlgebra

Constants

Variable

Public Class Methods

Expression(*sse) click to toggle source

@example

Expression(:isLiteral, RDF::Literal(3.1415))

@param [Array] sse

a SPARQL S-Expression (SSE) form

@return [SPARQL::Algebra::Expression]

# File lib/sparql/algebra.rb, line 400
def Expression(*sse)
  Expression.for(*sse)
end
Operator(name, arity = nil) click to toggle source

@example

Operator(:isLiteral)

@param [Symbol, to_sym] name @return [Class]

# File lib/sparql/algebra.rb, line 411
def Operator(name, arity = nil)
  Operator.for(name, arity)
end
Variable(name) click to toggle source

@example

Variable(:foobar)

@param [Symbol, to_sym] name @return [Variable] @see www.rubydoc.info/github/ruby-rdf/rdf/RDF/Query/Variable

# File lib/sparql/algebra.rb, line 423
def Variable(name)
  Variable.new(name)
end
open(sse, **options) click to toggle source

Parses input from the given file name or URL.

@param [String, to_s] sse @param [Hash{Symbol => Object}] options

any additional options (see {Operator#initialize})

@option options [RDF::URI, to_s] :base_uri

Base URI used for loading relative URIs.

@yield [expression] @yieldparam [SPARQL::Algebra::Expression] expression @yieldreturn [void] ignored @return [Expression]

# File lib/sparql/algebra.rb, line 388
def open(sse, **options)
  Expression.open(sse, **options)
end
parse(sse, **options) click to toggle source

@example

sse = (prefix ((foaf: <http://xmlns.com/foaf/0.1/>))
        (project (?name ?mbox)
          (join
            (bgp (triple ?x foaf:name ?name))
            (bgp (triple ?x foaf:mbox ?mbox)))))
}

@param [String] sse

a SPARQL S-Expression (SSE) string

@param [Hash{Symbol => Object}] options

any additional options (see {Operator#initialize})

@return [SPARQL::Algebra::Operator]

# File lib/sparql/algebra.rb, line 370
def parse(sse, **options)
  Expression.parse(sse, **options)
end

Private Instance Methods

Expression(*sse) click to toggle source

@example

Expression(:isLiteral, RDF::Literal(3.1415))

@param [Array] sse

a SPARQL S-Expression (SSE) form

@return [SPARQL::Algebra::Expression]

# File lib/sparql/algebra.rb, line 400
def Expression(*sse)
  Expression.for(*sse)
end
Operator(name, arity = nil) click to toggle source

@example

Operator(:isLiteral)

@param [Symbol, to_sym] name @return [Class]

# File lib/sparql/algebra.rb, line 411
def Operator(name, arity = nil)
  Operator.for(name, arity)
end
Variable(name) click to toggle source

@example

Variable(:foobar)

@param [Symbol, to_sym] name @return [Variable] @see www.rubydoc.info/github/ruby-rdf/rdf/RDF/Query/Variable

# File lib/sparql/algebra.rb, line 423
def Variable(name)
  Variable.new(name)
end
open(sse, **options) click to toggle source

Parses input from the given file name or URL.

@param [String, to_s] sse @param [Hash{Symbol => Object}] options

any additional options (see {Operator#initialize})

@option options [RDF::URI, to_s] :base_uri

Base URI used for loading relative URIs.

@yield [expression] @yieldparam [SPARQL::Algebra::Expression] expression @yieldreturn [void] ignored @return [Expression]

# File lib/sparql/algebra.rb, line 388
def open(sse, **options)
  Expression.open(sse, **options)
end
parse(sse, **options) click to toggle source

@example

sse = (prefix ((foaf: <http://xmlns.com/foaf/0.1/>))
        (project (?name ?mbox)
          (join
            (bgp (triple ?x foaf:name ?name))
            (bgp (triple ?x foaf:mbox ?mbox)))))
}

@param [String] sse

a SPARQL S-Expression (SSE) string

@param [Hash{Symbol => Object}] options

any additional options (see {Operator#initialize})

@return [SPARQL::Algebra::Operator]

# File lib/sparql/algebra.rb, line 370
def parse(sse, **options)
  Expression.parse(sse, **options)
end