module SPARQL; module Algebra class Operator ## # The SPARQL `bnode` operator. # # The BNODE function constructs a blank node that is distinct from all blank nodes in the dataset being queried and distinct from all blank nodes created by calls to this constructor for other query solutions. If the no argument form is used, every call results in a distinct blank node. If the form with a simple literal is used, every call results in distinct blank nodes for different simple literals, and the same blank node for calls with the same simple literal within expressions for one solution mapping. # # @example # (prefix ((: ) # (xsd: )) # (project (?s1 ?s2 ?b1 ?b2) # (extend ((?b1 (bnode ?s1)) (?b2 (bnode ?s2))) # (filter (exprlist (|| (= ?a :s1) (= ?a :s3)) (|| (= ?b :s1) (= ?b :s3))) # (bgp # (triple ?a :str ?s1) # (triple ?b :str ?s2) # ))))) # # @see https://www.w3.org/TR/sparql11-query/#func-bnode class BNode < Operator::Unary include Evaluatable NAME = :bnode ## # Initializes a new operator instance. # # @param [RDF::Literal] literal (false) # @param [Hash{Symbol => Object}] options # any additional options (see {Operator#initialize}) # @raise [TypeError] if any operand is invalid def initialize(literal = false, **options) super end ## # Evaluates this operator using the given variable `bindings`. # # @param [RDF::Query::Solution] bindings # a query solution containing zero or more variable bindings # @param [Hash{Symbol => Object}] options ({}) # options passed from query # @return [RDF::Term] def evaluate(bindings, **options) args = operands.map { |operand| operand.evaluate(bindings, depth: options[:depth].to_i + 1, **options) } apply(args.first, bindings) end ## # The BNODE function constructs a blank node that is distinct from all blank nodes in the dataset being queried and distinct from all blank nodes created by calls to this constructor for other query solutions. If the no argument form is used, every call results in a distinct blank node. If the form with a simple literal is used, every call results in distinct blank nodes for different simple literals, and the same blank node for calls with the same simple literal within expressions for one solution mapping. # # This functionality is compatible with the treatment of blank nodes in SPARQL CONSTRUCT templates. # # @param [RDF::Literal] literal (nil) # @param [RDF::Query::Solution, #[]] bindings # a query solution containing zero or more variable bindings # @return [RDF::Node] # @raise [TypeError] if the operand is not a simple literal or nil def apply(literal, bindings) @@bnode_base ||= "b0" @@bindings ||= bindings @@bnodes ||= {} if literal == RDF::Literal::FALSE l, @@bnode_base = @@bnode_base, @@bnode_base.succ RDF::Node.new(l) else raise TypeError, "expected an simple literal, but got #{literal.inspect}" unless literal.literal? && literal.simple? # Return the same BNode if used with the same binding @@bnodes, @@bindings = {}, bindings unless @@bindings == bindings @@bnodes[literal.to_s.to_sym] ||= begin l, @@bnode_base = @@bnode_base, @@bnode_base.succ RDF::Node.new(l) end end end ## # Returns the SPARQL S-Expression (SSE) representation of this expression. # # Remove the optional argument. # # @return [Array] `self` # @see https://openjena.org/wiki/SSE def to_sxp_bin [NAME] + operands.reject {|o| o == false} end end # BNode end # Operator end; end # SPARQL::Algebra