Skip to content

Commit

Permalink
Progress on operators and parsing property paths.
Browse files Browse the repository at this point in the history
[ci skip]
  • Loading branch information
gkellogg committed May 6, 2015
1 parent 089709e commit 3f6df7f
Show file tree
Hide file tree
Showing 16 changed files with 600 additions and 29 deletions.
1 change: 0 additions & 1 deletion etc/sparql11.bnf
Expand Up @@ -111,7 +111,6 @@
[91] PathElt ::= PathPrimary PathMod?
[92] PathEltOrInverse ::= PathElt | '^' PathElt
[93] PathMod ::= '*' | '?' | '+'
| '{' ( Integer ( ',' ( '}' | Integer '}' ) | '}' ) | ',' Integer '}' )
[94] PathPrimary ::= iri | 'a' | '!' PathNegatedPropertySet | '(' Path ')'
[95] PathNegatedPropertySet ::= PathOneInPropertySet
| '(' ( PathOneInPropertySet ( '|' PathOneInPropertySet )* )? ')'
Expand Down
18 changes: 17 additions & 1 deletion lib/sparql/algebra/operator.rb
Expand Up @@ -85,6 +85,15 @@ class Operator
autoload :Subtract, 'sparql/algebra/operator/subtract'
autoload :UCase, 'sparql/algebra/operator/ucase'

# Property Paths
autoload :NotOneOf, 'sparql/algebra/operator/notoneof'
autoload :PathOpt, 'sparql/algebra/operator/path_opt'
autoload :PathPlus, 'sparql/algebra/operator/path_plus'
autoload :PathStar, 'sparql/algebra/operator/path_star'
autoload :Path, 'sparql/algebra/operator/path'
autoload :Reverse, 'sparql/algebra/operator/reverse'
autoload :Seq, 'sparql/algebra/operator/seq'

# Miscellaneous
autoload :Asc, 'sparql/algebra/operator/asc'
autoload :Coalesce, 'sparql/algebra/operator/coalesce'
Expand Down Expand Up @@ -195,18 +204,25 @@ def self.for(name, arity = nil)
when :month then Month
when :multiply then Multiply
when :not, :'!' then Not
when :notexists then NotExists
when :notexists then NotExists
when :notin then NotIn
when :notoneof then NotOneOf
when :now then Now
when :or, :'||' then Or
when :path then Path
when :path? then PathOpt
when :"path*" then PathStar
when :"path+" then PathPlus
when :plus then Plus
when :rand then Rand
when :regex then Regex
when :replace then Replace
when :reverse then Reverse
when :round then Round
when :sameterm then SameTerm
when :sample then Sample
when :seconds then Seconds
when :seq then Seq
when :sha1 then SHA1
when :sha256 then SHA256
when :sha512 then SHA512
Expand Down
36 changes: 36 additions & 0 deletions lib/sparql/algebra/operator/alt.rb
@@ -0,0 +1,36 @@
module SPARQL; module Algebra
class Operator
##
# The SPARQL Property Path `alt` (Alternative Property Path) operator.
#
# @example
# (alt a b)
#
# @see http://www.w3.org/TR/sparql11-query/#defn_evalPP_alternative
class Alt < Operator::Binary
include Query

NAME = :alt

##
# XXX
#
#
# @param [RDF::Queryable] queryable
# the graph or repository to query
# @param [Hash{Symbol => Object}] options
# any additional keyword options
# @param [RDF::Term, RDF::Variable] :subject
# @param [RDF::Term, RDF::Variable] :object
# @yield [solution]
# each matching solution
# @yieldparam [RDF::Query::Solution] solution
# @yieldreturn [void] ignored
# @see http://www.w3.org/TR/rdf-sparql-query/#sparqlAlgebra
def execute(queryable, options = {}, &block)
debug(options) {"Alt #{operands.to_sse}"}
subject, object = options[:subject], options[:object]
end
end # Alt
end # Operator
end; end # SPARQL::Algebra
36 changes: 36 additions & 0 deletions lib/sparql/algebra/operator/notoneof.rb
@@ -0,0 +1,36 @@
module SPARQL; module Algebra
class Operator
##
# The SPARQL Property Path `notoneof` (NegatedPropertySet) operator.
#
# @example
# (notoneof ex:p1 ex:p2)
#
# @see http://www.w3.org/TR/sparql11-query/#eval_negatedPropertySet
class NotOneOf < Operator
include Query

NAME = :notoneof

##
# XXX
#
#
# @param [RDF::Queryable] queryable
# the graph or repository to query
# @param [Hash{Symbol => Object}] options
# any additional keyword options
# @param [RDF::Term, RDF::Variable] :subject
# @param [RDF::Term, RDF::Variable] :object
# @yield [solution]
# each matching solution
# @yieldparam [RDF::Query::Solution] solution
# @yieldreturn [void] ignored
# @see http://www.w3.org/TR/rdf-sparql-query/#sparqlAlgebra
def execute(queryable, options = {}, &block)
debug(options) {"NotOneOf #{operands.to_sse}"}
subject, object = options[:subject], options[:object]
end
end # NotOneOf
end # Operator
end; end # SPARQL::Algebra
44 changes: 44 additions & 0 deletions lib/sparql/algebra/operator/path.rb
@@ -0,0 +1,44 @@
module SPARQL; module Algebra
class Operator
##
# The SPARQL Property Path `path` operator.
#
# @example
# (path :a (path+ :p) ?z)
#
# @see http://www.w3.org/TR/sparql11-query/#sparqlTranslatePathExpressions
class Path < Operator::Ternary
include Query

NAME = :path

##
# Finds solutions from `queryable` matching the path.
#
# @param [RDF::Queryable] queryable
# the graph or repository to query
# @param [Hash{Symbol => Object}] options
# any additional keyword options
# @yield [solution]
# each matching solution
# @yieldparam [RDF::Query::Solution] solution
# @yieldreturn [void] ignored
# @return [RDF::Query::Solutions]
# the resulting solution sequence
# @see http://www.w3.org/TR/rdf-sparql-query/#sparqlAlgebra
def execute(queryable, options = {}, &block)
debug(options) {"Path #{operands.to_sse}"}
subject, path_op, object = operands

@solutions = path_op.execute(queryable, options.merge(
subject: subject,
object: object,
depth: options[:depth.to_i + 1])
)
debug(options) {"=> #{@solutions.inspect}"}
@solutions.each(&block) if block_given?
@solutions
end
end # Path
end # Operator
end; end # SPARQL::Algebra
76 changes: 76 additions & 0 deletions lib/sparql/algebra/operator/path_opt.rb
@@ -0,0 +1,76 @@
module SPARQL; module Algebra
class Operator
##
# The SPARQL Property Path `path?` (ZeroOrOnePath) operator.
#
# @example
# (path? :p)
#
# @see http://www.w3.org/TR/sparql11-query/#defn_evalPP_ZeroOrOnePath
class PathOpt < Operator::Unary
include Query

NAME = :path?

##
# Equivalent to:
#
# `(path x (path? :p) y)`
# => `(union (bgp ((x :p y))) (filter (x = x) (solution x y)))`
#
#
# @param [RDF::Queryable] queryable
# the graph or repository to query
# @param [Hash{Symbol => Object}] options
# any additional keyword options
# @param [RDF::Term, RDF::Variable] :subject
# @param [RDF::Term, RDF::Variable] :object
# @yield [solution]
# each matching solution
# @yieldparam [RDF::Query::Solution] solution
# @yieldreturn [void] ignored
# @see http://www.w3.org/TR/rdf-sparql-query/#sparqlAlgebra
def execute(queryable, options = {}, &block)
debug(options) {"Path? #{operands.to_sse}"}
subject, object = options[:subject], options[:object]

# Solutions where predicate exists
pattern = RDF::Query::Pattern.new(subject: subject, predicate: operand, object: object, context: options.fetch(:context, false))
queryable.execute(pattern, &block)

# Solutions where subject == object
nodes = []
case
when subject.variable? && object.variable?
# Nodes is the set of all subjects and objects in queryable
# FIXME: should this be Queryable#enum_nodes?
queryable.query(subject: subject, context: options.fetch(:context, false)).each do |solution|
nodes << solution.merge!(object => solution[subject])
end
queryable.query(object: subject, context: options.fetch(:context, false)).each do |solution|
nodes << solution.merge!(object => solution[subject])
end
when subject.variable?
queryable.query(subject: object, context: options.fetch(:context, false)).each do |solution|
nodes << solution.merge!(subject => object)
end
queryable.query(object: object, context: options.fetch(:context, false)).each do |solution|
nodes << solution.merge!(subject => object)
end
when object.variable?
queryable.query(subject: subject, context: options.fetch(:context, false)).each do |solution|
nodes << solution.merge!(object => subject)
end
queryable.query(object: subject, context: options.fetch(:context, false)).each do |solution|
nodes << solution.merge!(object => subject)
end
else
# Otherwise, if subject == object, an empty solution
nodes << RDF::Query::Solution.new if subject == object
end
# Yield each solution only once
nodes.uniq.each(&block)
end
end # PathOpt
end # Operator
end; end # SPARQL::Algebra
36 changes: 36 additions & 0 deletions lib/sparql/algebra/operator/path_plus.rb
@@ -0,0 +1,36 @@
module SPARQL; module Algebra
class Operator
##
# The SPARQL Property Path `path+` (OneOrMorePath) operator.
#
# @example
# (path+ :p)
#
# @see http://www.w3.org/TR/sparql11-query/#defn_evalPP_OneOrMorePath
class PathPlus < Operator::Unary
include Query

NAME = :"path+"

##
# XXX
#
#
# @param [RDF::Queryable] queryable
# the graph or repository to query
# @param [Hash{Symbol => Object}] options
# any additional keyword options
# @param [RDF::Term, RDF::Variable] :subject
# @param [RDF::Term, RDF::Variable] :object
# @yield [solution]
# each matching solution
# @yieldparam [RDF::Query::Solution] solution
# @yieldreturn [void] ignored
# @see http://www.w3.org/TR/rdf-sparql-query/#sparqlAlgebra
def execute(queryable, options = {}, &block)
debug(options) {"Path? #{operands.to_sse}"}
subject, object = options[:subject], options[:object]
end
end # PathPlus
end # Operator
end; end # SPARQL::Algebra
71 changes: 71 additions & 0 deletions lib/sparql/algebra/operator/path_star.rb
@@ -0,0 +1,71 @@
module SPARQL; module Algebra
class Operator
##
# The SPARQL Property Path `path*` (ZeroOrMorePath) operator.
#
# @example
# (path* :p)
#
# @see http://www.w3.org/TR/sparql11-query/#defn_evalPP_ZeroOrMorePath
class PathStar < Operator::Unary
include Query

NAME = :"path*"

##
# Solutions are the unique subjects and objects in `queryable` as with `path?` plus the results of `path+`
#
# @param [RDF::Queryable] queryable
# the graph or repository to query
# @param [Hash{Symbol => Object}] options
# any additional keyword options
# @param [RDF::Term, RDF::Variable] :subject
# @param [RDF::Term, RDF::Variable] :object
# @yield [solution]
# each matching solution
# @yieldparam [RDF::Query::Solution] solution
# @yieldreturn [void] ignored
# @see http://www.w3.org/TR/rdf-sparql-query/#sparqlAlgebra
def execute(queryable, options = {}, &block)
debug(options) {"Path* #{operands.to_sse}"}
subject, object = options[:subject], options[:object]

plus = PathPlus.new(*operands)
plus.execute(queryable, options.merge(depth: options[:depth].to_i + 1), &block)

# Solutions where subject == object
nodes = []
case
when subject.variable? && object.variable?
# Nodes is the set of all subjects and objects in queryable
# FIXME: should this be Queryable#enum_nodes?
queryable.query(subject: subject, context: options.fetch(:context, false)).each do |solution|
nodes << solution.merge!(object => solution[subject])
end
queryable.query(object: subject, context: options.fetch(:context, false)).each do |solution|
nodes << solution.merge!(object => solution[subject])
end
when subject.variable?
queryable.query(subject: object, context: options.fetch(:context, false)).each do |solution|
nodes << solution.merge!(subject => object)
end
queryable.query(object: object, context: options.fetch(:context, false)).each do |solution|
nodes << solution.merge!(subject => object)
end
when object.variable?
queryable.query(subject: subject, context: options.fetch(:context, false)).each do |solution|
nodes << solution.merge!(object => subject)
end
queryable.query(object: subject, context: options.fetch(:context, false)).each do |solution|
nodes << solution.merge!(object => subject)
end
else
# Otherwise, if subject == object, an empty solution
nodes << RDF::Query::Solution.new if subject == object
end
# Yield each solution only once
nodes.uniq.each(&block)
end
end # PathStar
end # Operator
end; end # SPARQL::Algebra
36 changes: 36 additions & 0 deletions lib/sparql/algebra/operator/reverse.rb
@@ -0,0 +1,36 @@
module SPARQL; module Algebra
class Operator
##
# The SPARQL Property Path `reverse` (NegatedPropertySet) operator.
#
# @example
# (reverse :p)
#
# @see http://www.w3.org/TR/sparql11-query/#defn_evalPP_inverse
class Reverse < Operator::Unary
include Query

NAME = :reverse

##
# XXX
#
#
# @param [RDF::Queryable] queryable
# the graph or repository to query
# @param [Hash{Symbol => Object}] options
# any additional keyword options
# @param [RDF::Term, RDF::Variable] :subject
# @param [RDF::Term, RDF::Variable] :object
# @yield [solution]
# each matching solution
# @yieldparam [RDF::Query::Solution] solution
# @yieldreturn [void] ignored
# @see http://www.w3.org/TR/rdf-sparql-query/#sparqlAlgebra
def execute(queryable, options = {}, &block)
debug(options) {"Reverse #{operands.to_sse}"}
subject, object = options[:subject], options[:object]
end
end # Reverse
end # Operator
end; end # SPARQL::Algebra

0 comments on commit 3f6df7f

Please sign in to comment.