class SPARQL::Algebra::Operator

A SPARQL operator.

@abstract

Constants

ARITY
IsURI

The SPARQL `isIRI`/`isURI` operator.

@example

(prefix ((xsd: <http://www.w3.org/2001/XMLSchema#>)
         (: <http://example.org/things#>))
  (project (?x ?v)
    (filter (isIRI ?v)
      (bgp (triple ?x :p ?v)))))

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

URI

The SPARQL `iri` operator.

@example

(base <http://example.org/> (project (?uri ?iri)
  (extend ((?uri (uri "uri")) (?iri (iri "iri")))
    (bgp))))

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

Attributes

operands[R]

The operands to this operator.

@return [Array]

Public Class Methods

arity() click to toggle source

Returns the arity of this operator class.

@example

Operator.arity           #=> -1
Operator::Nullary.arity  #=> 0
Operator::Unary.arity    #=> 1
Operator::Binary.arity   #=> 2
Operator::Ternary.arity  #=> 3

@return [Integer] an integer in the range `(-1..3)`

# File lib/sparql/algebra/operator.rb, line 332
def self.arity
  self.const_get(:ARITY)
end
base_uri() click to toggle source

Base URI used for reading data sources with relative URIs

@return [RDF::URI]

# File lib/sparql/algebra/operator.rb, line 398
def self.base_uri
  @base_uri
end
base_uri=(uri) click to toggle source

Set Base URI associated with SPARQL document, typically done when reading SPARQL from a URI

@param [RDF::URI] uri @return [RDF::URI]

# File lib/sparql/algebra/operator.rb, line 408
def self.base_uri=(uri)
  @base_uri = RDF::URI(uri)
end
evaluate(*operands, **options) click to toggle source

@param [Array<RDF::Term>] operands @return [RDF::Term] @see Operator#evaluate

# File lib/sparql/algebra/operator.rb, line 317
def self.evaluate(*operands, **options)
  self.new(*operands, **options).evaluate(RDF::Query::Solution.new, **options)
end
for(name, arity = nil) click to toggle source

Returns an operator class for the given operator `name`.

@param [Symbol, to_s] name @param [Integer] arity @return [Class] an operator class, or `nil` if an operator was not found

# File lib/sparql/algebra/operator.rb, line 159
def self.for(name, arity = nil)
  # TODO: refactor this to dynamically introspect loaded operator classes.
  case (name.to_s.downcase.to_sym rescue nil)
    when :'!='            then NotEqual
    when :'/'             then Divide
    when :'='             then Equal
    when :*               then Multiply
    when :+               then Plus
    when :-               then arity.eql?(1) ? Negate : Subtract
    when :<               then LessThan
    when :<=              then LessThanOrEqual
    when :<=>             then Compare # non-standard
    when :>               then GreaterThan
    when :>=              then GreaterThanOrEqual
    when :abs             then Abs
    when :add             then Add
    when :alt             then Alt
    when :and, :'&&'      then And
    when :avg             then Avg
    when :bnode           then BNode
    when :bound           then Bound
    when :coalesce        then Coalesce
    when :ceil            then Ceil
    when :concat          then Concat
    when :contains        then Contains
    when :count           then Count
    when :datatype        then Datatype
    when :day             then Day
    when :encode_for_uri  then EncodeForURI
    when :divide          then Divide
    when :exists          then Exists
    when :floor           then Floor
    when :group_concat    then GroupConcat
    when :hours           then Hours
    when :if              then If
    when :in              then In
    when :iri, :uri       then IRI
    when :isblank         then IsBlank
    when :isiri           then IsIRI
    when :isliteral       then IsLiteral
    when :isnumeric       then IsNumeric
    when :isuri           then IsIRI # alias
    when :lang            then Lang
    when :langmatches     then LangMatches
    when :lcase           then LCase
    when :md5             then MD5
    when :max             then Max
    when :min             then Min
    when :minus           then Minus
    when :minutes         then Minutes
    when :month           then Month
    when :multiply        then Multiply
    when :not, :'!'       then Not
    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 :sequence        then Sequence
    when :sha1            then SHA1
    when :sha256          then SHA256
    when :sha512          then SHA512
    when :str             then Str
    when :strafter        then StrAfter
    when :strbefore       then StrBefore
    when :strdt           then StrDT
    when :strends         then StrEnds
    when :strlang         then StrLang
    when :strlen          then StrLen
    when :strstarts       then StrStarts
    when :struuid         then StrUUID
    when :substr          then SubStr
    when :subtract        then Subtract
    when :sum             then Sum
    when :timezone        then Timezone
    when :tz              then TZ
    when :ucase           then UCase
    when :uuid            then UUID
    when :year            then Year

    # Miscellaneous
    when :asc             then Asc
    when :desc            then Desc
    when :exprlist        then Exprlist

    # Datasets
    when :dataset         then Dataset

    # Query forms
    when :ask             then Ask
    when :base            then Base
    when :bgp             then BGP
    when :construct       then Construct
    when :describe        then Describe
    when :distinct        then Distinct
    when :extend          then Extend
    when :filter          then Filter
    when :graph           then Graph
    when :group           then Group
    when :join            then Join
    when :leftjoin        then LeftJoin
    when :order           then Order
    when :minus           then Minus
    when :prefix          then Prefix
    when :project         then Project
    when :reduced         then Reduced
    when :slice           then Slice
    when :table           then Table
    when :triple          then RDF::Query::Pattern
    when :union           then Union

    # Update forms
    when :add             then Add
    when :clear           then Clear
    when :copy            then Copy
    when :create          then Create
    when :delete          then Delete
    when :deletedata      then DeleteData
    when :deletewhere     then DeleteWhere
    when :drop            then Drop
    when :insert          then Insert
    when :insertdata      then InsertData
    when :load            then Load
    when :modify          then Modify
    when :move            then Move
    when :update          then Update
    when :using           then Using
    when :with            then With

    # RDF-star
    when :istriple        then IsTriple
    when :triple          then Triple
    when :subject         then Subject
    when :predicate       then Predicate
    when :object          then Object

    else                       nil # not found
  end
end
new(*operands, **options) click to toggle source

Initializes a new operator instance.

@overload initialize(*operands)

@param  [Array<RDF::Term>] operands

@overload initialize(*operands, **options)

@param  [Array<RDF::Term>] operands
@param  [Hash{Symbol => Object}] options
  any additional options
@option options [Boolean] :memoize (false)
  whether to memoize results for particular operands

@raise [TypeError] if any operand is invalid

# File lib/sparql/algebra/operator.rb, line 351
def initialize(*operands, **options)
  @options  = options.dup
  @operands = operands.map! do |operand|
    case operand
      when Array
        operand.each {|op| op.parent = self if operand.respond_to?(:parent=)}
        operand
      when Operator, Variable, RDF::Term, RDF::Query, RDF::Query::Pattern, Array, Symbol
        operand.parent = self if operand.respond_to?(:parent=)
        operand
      when TrueClass, FalseClass, Numeric, String, DateTime, Date, Time
        RDF::Literal(operand)
      when NilClass
        nil
      else raise TypeError, "invalid SPARQL::Algebra::Operator operand: #{operand.inspect}"
    end
  end
end
prefixes() click to toggle source

Prefixes useful for future serialization

@return [Hash{Symbol => RDF::URI}]

Prefix definitions
# File lib/sparql/algebra/operator.rb, line 426
def self.prefixes
  @prefixes
end
prefixes=(hash) click to toggle source

Prefixes useful for future serialization

@param [Hash{Symbol => RDF::URI}] hash

Prefix definitions

@return [Hash{Symbol => RDF::URI}]

# File lib/sparql/algebra/operator.rb, line 436
def self.prefixes=(hash)
  @prefixes = hash
end

Private Class Methods

inherited(child) click to toggle source

@private @return [void]

Calls superclass method
# File lib/sparql/algebra/operator.rb, line 743
def self.inherited(child)
  @@subclasses << child unless child.superclass.equal?(Operator) # grandchildren only
  super
end

Public Instance Methods

==(other)
Alias for: eql?
aggregate?() click to toggle source

Returns `true` if this is an aggregate

Overridden in evaluatables which are aggregates

@return [Boolean] `true` or `false`

# File lib/sparql/algebra/operator.rb, line 511
def aggregate?
  respond_to?(:aggregate)
end
base_uri() click to toggle source

Base URI used for reading data sources with relative URIs

@return [RDF::URI]

# File lib/sparql/algebra/operator.rb, line 390
def base_uri
  Operator.base_uri
end
bind(solution) click to toggle source

Binds the pattern to a solution, making it no longer variable if all variables are resolved to bound variables

@param [RDF::Query::Solution] solution @return [self]

# File lib/sparql/algebra/operator.rb, line 381
def bind(solution)
  @operands.each {|op| op.bind(solution)}
  self
end
constant?() click to toggle source

Returns `true` if none of the operands are variables, `false` otherwise.

@return [Boolean] `true` or `false` @see variable?

# File lib/sparql/algebra/operator.rb, line 501
def constant?
  !(variable?)
end
deep_dup() click to toggle source

Deep duplicate operands

# File lib/sparql/algebra/operator.rb, line 372
def deep_dup
  self.class.new(*operands.map(&:deep_dup), **@options)
end
descendants(&block)
Alias for: each_descendant
each(&block)
Alias for: each_descendant
each_descendant(&block) click to toggle source

Enumerate via depth-first recursive descent over operands, yielding each operator @yield operator @yieldparam [Object] operator @return [Enumerator]

# File lib/sparql/algebra/operator.rb, line 624
def each_descendant(&block)
  if block_given?
    operands.each do |operand|
      case operand
      when Array
        operand.each do |op|
          op.each_descendant(&block) if op.respond_to?(:each_descendant)
          block.call(op)
        end
      else
        operand.each_descendant(&block) if operand.respond_to?(:each_descendant)
      end
      block.call(operand)
    end
  end
  enum_for(:each_descendant)
end
Also aliased as: descendants, each
eql?(other) click to toggle source

@param [Statement] other @return [Boolean]

# File lib/sparql/algebra/operator.rb, line 600
def eql?(other)
  other.class == self.class && other.operands == self.operands
end
Also aliased as: ==
evaluatable?() click to toggle source

Returns `true` if this is evaluatable (i.e., returns values for a binding), `false` otherwise.

@return [Boolean] `true` or `false`

# File lib/sparql/algebra/operator.rb, line 482
def evaluatable?
  respond_to?(:evaluate)
end
executable?() click to toggle source

Returns `true` if this is executable (i.e., contains a graph patterns), `false` otherwise.

@return [Boolean] `true` or `false`

# File lib/sparql/algebra/operator.rb, line 491
def executable?
  respond_to?(:execute)
end
first_ancestor(klass) click to toggle source

First ancestor operator of type `klass`

@param [Class] klass @return [Operator]

# File lib/sparql/algebra/operator.rb, line 663
def first_ancestor(klass)
  parent.is_a?(klass) ? parent : parent.first_ancestor(klass) if parent
end
inspect() click to toggle source

Returns a developer-friendly representation of this operator.

@return [String]

# File lib/sparql/algebra/operator.rb, line 593
def inspect
  sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, operands.to_sse.gsub(/\s+/m, ' '))
end
ndvars() click to toggle source

Return the non-destinguished variables contained within this operator @return [Array<RDF::Query::Variable>]

# File lib/sparql/algebra/operator.rb, line 608
def ndvars
  vars.reject(&:distinguished?)
end
node?() click to toggle source

Returns `true` if any of the operands are nodes, `false` otherwise.

@return [Boolean]

# File lib/sparql/algebra/operator.rb, line 471
def node?
  operands.any? do |operand|
    operand.respond_to?(:node?) ? operand.node? : operand.node?
  end
end
operand(index = 0) click to toggle source

Returns the operand at the given `index`.

@param [Integer] index

an operand index in the range `(0...(operands.count))`

@return [RDF::Term]

# File lib/sparql/algebra/operator.rb, line 452
def operand(index = 0)
  operands[index]
end
optimize(**options) click to toggle source

Returns an optimized version of this expression.

For constant expressions containing no variables, returns the result of evaluating the expression with empty bindings; otherwise returns a copy of `self`.

Optimization is not possible if the expression raises an exception, such as a `TypeError` or `ZeroDivisionError`, which must be conserved at runtime.

@return [SPARQL::Algebra::Expression] @see RDF::Query#optimize

Calls superclass method SPARQL::Algebra::Expression#optimize
# File lib/sparql/algebra/operator.rb, line 528
def optimize(**options)
  if constant?
    # Note that if evaluation results in a `TypeError` or other error,
    # we must return `self` so that the error is conserved at runtime:
    evaluate(RDF::Query::Solution.new) rescue self
  else
    super # returns a copy of `self`
  end
end
optimize!(**options) click to toggle source

Optimizes this query by optimizing its constituent operands according to their cost estimates.

@return [self] @see RDF::Query#optimize!

# File lib/sparql/algebra/operator.rb, line 544
def optimize!(**options)
  @operands.map! do |op|
    op.optimize(**options) if op.respond_to?(:optimize)
  end
  self
end
parent() click to toggle source

Parent expression, if any

@return [Operator]

# File lib/sparql/algebra/operator.rb, line 648
def parent; @options[:parent]; end
parent=(operator) click to toggle source

Parent operator, if any

@return [Operator]

# File lib/sparql/algebra/operator.rb, line 654
def parent=(operator)
  @options[:parent]= operator
end
prefixes() click to toggle source

Prefixes useful for future serialization

@return [Hash{Symbol => RDF::URI}]

Prefix definitions
# File lib/sparql/algebra/operator.rb, line 417
def prefixes
  Operator.prefixes
end
rewrite(&block) click to toggle source

Rewrite operands by yielding each operand. Recursively descends through operands implementing this method.

@yield operand @yieldparam [] operand @yieldreturn [SPARQL::Algebra::Expression] the re-written operand @return [SPARQL::Algebra::Expression] `self`

# File lib/sparql/algebra/operator.rb, line 559
def rewrite(&block)
  @operands = @operands.map do |op|
    # Rewrite the operand
    unless new_op = block.call(op)
      # Not re-written, rewrite
      new_op = op.respond_to?(:rewrite) ? op.rewrite(&block) : op
    end
    new_op
  end
  self
end
to_sxp() click to toggle source

Returns an S-Expression (SXP) representation of this operator

@return [String]

# File lib/sparql/algebra/operator.rb, line 585
def to_sxp
  to_sxp_bin.to_sxp
end
to_sxp_bin() click to toggle source

Returns the SPARQL S-Expression (SSE) representation of this operator.

@return [Array] @see openjena.org/wiki/SSE

# File lib/sparql/algebra/operator.rb, line 576
def to_sxp_bin
  operator = [self.class.const_get(:NAME)].flatten.first
  [operator, *(operands || []).map(&:to_sxp_bin)]
end
validate!() click to toggle source

Validate all operands, operator specific classes should override for operator-specific validation @return [SPARQL::Algebra::Expression] `self` @raise [ArgumentError] if the value is invalid

# File lib/sparql/algebra/operator.rb, line 671
def validate!
  operands.each {|op| op.validate! if op.respond_to?(:validate!)}
  self
end
variable?() click to toggle source

Returns `true` if any of the operands are variables, `false` otherwise.

@return [Boolean] `true` or `false` @see constant?

# File lib/sparql/algebra/operator.rb, line 462
def variable?
  operands.any? {|op| op.respond_to?(:variable?) && op.variable?}
end
vars() click to toggle source

Return the variables contained within this operator @return [Array<RDF::Query::Variable>]

# File lib/sparql/algebra/operator.rb, line 615
def vars
  operands.select {|o| o.respond_to?(:vars)}.map(&:vars).flatten
end

Protected Instance Methods

boolean(literal) click to toggle source

Returns the effective boolean value (EBV) of the given `literal`.

@param [RDF::Literal] literal @return [RDF::Literal::Boolean] `true` or `false` @raise [TypeError] if the literal could not be coerced to an `RDF::Literal::Boolean` @see www.w3.org/TR/sparql11-query/#ebv

# File lib/sparql/algebra/operator.rb, line 684
def boolean(literal)
  case literal
    when FalseClass then RDF::Literal::FALSE
    when TrueClass  then RDF::Literal::TRUE
    when RDF::Literal::Boolean
      # If the argument is a typed literal with a datatype of
      # `xsd:boolean`, the EBV is the value of that argument.
      # However, the EBV of any literal whose type is `xsd:boolean` is
      # false if the lexical form is not valid for that datatype.
      RDF::Literal(literal.valid? && literal.true?)
    when RDF::Literal::Numeric
      # If the argument is a numeric type or a typed literal with a
      # datatype derived from a numeric type, the EBV is false if the
      # operand value is NaN or is numerically equal to zero; otherwise
      # the EBV is true.
      # However, the EBV of any literal whose type is numeric is
      # false if the lexical form is not valid for that datatype.
      RDF::Literal(literal.valid? && !(literal.zero?) && !(literal.respond_to?(:nan?) && literal.nan?))
    else case
      when literal.is_a?(RDF::Literal) && literal.plain?
        # If the argument is a plain literal or a typed literal with a
        # datatype of `xsd:string`, the EBV is false if the operand value
        # has zero length; otherwise the EBV is true.
        RDF::Literal(!(literal.value.empty?))
      else
        # All other arguments, including unbound arguments, produce a type error.
        raise TypeError, "could not coerce #{literal.inspect} to an RDF::Literal::Boolean"
    end
  end
end
to_binary(klass, *expressions) click to toggle source

Transform an array of expressions into a recursive set of binary operations e.g.: a || b || c => (|| a (|| b c)) @param [Class] klass Binary Operator class @param [Array<SPARQL::Algebra::Expression>] expressions @return [SPARQL::Algebra::Expression]

# File lib/sparql/algebra/operator.rb, line 722
def to_binary(klass, *expressions)
  case expressions.length
  when 0
    # Oops!
    raise "Operator#to_binary requires two or more expressions"
  when 1
    expressions.first
  when 2
    klass.new(*expressions)
  else
    klass.new(expressions.shift, to_binary(klass, *expressions))
  end
end