require 'json' ## # Extensions for Ruby's `NilClass` class. class NilClass def evaluate(bindings, **options) self end end ## # Extensions for Ruby's `Object` class. class Object ## # Returns the SXP binary representation of this object, defaults to `self`. # # @return [String] def to_sxp_bin self end ## # Make sure the object is in SXP form and transform it to a string form # @return String def to_sse SXP::Generator.string(self.to_sxp_bin) end ## # A duplicate of this object. # # @return [Object] a copy of `self` # @see SPARQL::Algebra::Expression#optimize def optimize(**options) self.deep_dup end ## # Default for deep_dup is shallow dup # @return [Object] def deep_dup dup end end ## # Extensions for Ruby's `Array` class. class Array ## # Returns the SXP representation of this object, defaults to `self`. # # @return [String] def to_sxp_bin map {|x| x.to_sxp_bin} end ## # Evaluates the array using the given variable `bindings`. # # In this case, the Array has two elements, the first of which is # an XSD datatype, and the second is the expression to be evaluated. # The result is cast as a literal of the appropriate type # # @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] # @see SPARQL::Algebra::Expression.evaluate def evaluate(bindings, **options) SPARQL::Algebra::Expression.extension(*self.map {|o| o.evaluate(bindings, **options)}) end ## # If `#execute` is invoked, it implies that a non-implemented Algebra operator # is being invoked # # @param [RDF::Queryable] queryable # the graph or repository to query # @param [Hash{Symbol => Object}] options # @raise [NotImplementedError] # If an attempt is made to perform an unsupported operation # @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra def execute(queryable, **options) raise NotImplementedError, "SPARQL::Algebra '#{first}' operator not implemented" end ## # Return an optimized version of this array. # # @return [Array] a copy of `self` # @see SPARQL::Algebra::Expression#optimize def optimize(**options) self.map do |op| op.optimize(**options) if op.respond_to?(:optimize) end end ## # 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] def bind(solution) map! do |op| op.respond_to?(:bind) ? op.bind(solution) : op end self end ## # Returns `true` if any of the operands are variables, `false` # otherwise. # # @return [Boolean] `true` or `false` # @see #constant? def variable? any? {|op| op.respond_to?(:variable?) && op.variable?} end def constant?; !(variable?); end ## # Does this contain any nodes? # # @return [Boolean] def node? any?(&:node?) end def evaluatable?; true; end def executable?; false; end def aggregate?; false; end ## # Replace operators which are variables with the result of the block # descending into operators which are also evaluatable # # @yield var # @yieldparam [RDF::Query::Variable] var # @yieldreturn [RDF::Query::Variable, SPARQL::Algebra::Evaluatable] # @return [SPARQL::Algebra::Evaluatable] self def replace_vars!(&block) map! do |op| case when op.respond_to?(:variable?) && op.variable? yield op when op.respond_to?(:replace_vars!) op.replace_vars!(&block) else op end end self end ## # Recursively re-map operators to replace aggregates with temporary variables returned from the block # # @yield agg # @yieldparam [SPARQL::Algebra::Aggregate] agg # @yieldreturn [RDF::Query::Variable] # @return [SPARQL::Algebra::Evaluatable, RDF::Query::Variable] self def replace_aggregate!(&block) map! do |op| case when op.respond_to?(:aggregate?) && op.aggregate? yield op when op.respond_to?(:replace_aggregate!) op.replace_aggregate!(&block) else op end end self end ## # Return the non-destinguished variables contained within this Array # @return [Array] def ndvars vars.reject(&:distinguished?) end ## # Return the variables contained within this Array # @return [Array] def vars select {|o| o.respond_to?(:vars)}.map(&:vars).flatten.compact end ## # Is this value composed only of valid components? # # @return [Boolean] `true` or `false` def valid? all? {|e| e.respond_to?(:valid?) ? e.valid? : true} end ## # Validate all components. # @return [Array] `self` # @raise [ArgumentError] if the value is invalid def validate! each {|e| e.validate! if e.respond_to?(:validate!)} self end ## # Deep duplicate def deep_dup map(&:deep_dup) end end ## # Extensions for Ruby's `Hash` class. class Hash ## # Returns the SXP representation of this object, defaults to `self`. # # @return [String] def to_sxp_bin to_a.to_sxp_bin end def to_sxp; to_sxp_bin; end ## # A duplicate of this hash. # # @return [Hash] a copy of `self` # @see SPARQL::Algebra::Expression#optimize def optimize(**options) self.deep_dup end ## # Deep duplicate def deep_dup inject({}) {|memo, (k, v)| memo.merge(k => v.deep_dup)} end end ## # Extensions for `RDF::Term`. module RDF::Term include SPARQL::Algebra::Expression # @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) self end def aggregate?; false; end ## # Return the non-destinguished variables contained within this operator # @return [Array] def ndvars vars.reject(&:distinguished?) end ## # Return the variables contained within this operator # @return [Array] def vars variable? ? [self] : [] end ## # A duplicate of this term. # # @return [RDF::Term] a copy of `self` # @see SPARQL::Algebra::Expression#optimize def optimize(**options) optimized = self.deep_dup optimized.lexical = nil if optimized.respond_to?(:lexical=) optimized end end # RDF::Term # Override RDF::Queryable to execute against SPARQL::Algebra::Query elements as well as RDF::Query and RDF::Pattern module RDF::Queryable alias_method :query_without_sparql, :query ## # Queries `self` for RDF statements matching the given `pattern`. # # Monkey patch to RDF::Queryable#query to execute a {SPARQL::Algebra::Operator} # in addition to an {RDF::Query} object. # # @example # queryable.query([nil, RDF::DOAP.developer, nil]) # queryable.query(predicate: RDF::DOAP.developer) # # op = SPARQL::Algebra::Expression.parse(%q((bgp (triple ?a doap:developer ?b)))) # queryable.query(op) # # @param [RDF::Query, RDF::Statement, Array(RDF::Term), Hash, SPARQL::Operator] pattern # @yield [statement] # each matching statement # @yieldparam [RDF::Statement] statement # @yieldreturn [void] ignored # @return [Enumerator] # @see RDF::Queryable#query_pattern def query(pattern, options = {}, &block) raise TypeError, "#{self} is not queryable" if respond_to?(:queryable?) && !queryable? if pattern.is_a?(SPARQL::Algebra::Operator) && pattern.respond_to?(:execute) before_query(pattern) if respond_to?(:before_query) solutions = if method(:query_execute).arity == 1 query_execute(pattern, &block) else query_execute(pattern, **options, &block) end after_query(pattern) if respond_to?(:after_query) if !pattern.respond_to?(:query_yeilds_solutions?) || pattern.query_yields_solutions? # Just return solutions solutions else # Return an enumerator enum_for(:query, pattern, **options) end else query_without_sparql(pattern, **options, &block) end end end class RDF::Statement # Transform Statement Pattern into an SXP # @return [Array] def to_sxp_bin if has_graph? [:quad, subject, predicate, object, graph_name] else [:triple, subject, predicate, object] end end ## # A duplicate of this Statement. # # @return [RDF::Statement] a copy of `self` # @see SPARQL::Algebra::Expression#optimize def optimize(**options) self.dup end end class RDF::Query # Equivalence for Queries: # Same Patterns # Same Context # @return [Boolean] def ==(other) # FIXME: this should be graph_name == other.graph_name other.is_a?(RDF::Query) && patterns == other.patterns && graph_name == graph_name end ## # Don't do any more rewriting # @return [SPARQL::Algebra::Expression] `self` def rewrite(&block) self end # Transform Query into an Array form of an SSE # # If Query has the `as_container` option set, serialize as Quads # Otherwise, If Query is named, serialize as a GroupGraphPattern. # Otherise, serialize as a BGP # # @return [Array] def to_sxp_bin if options[:as_container] [:graph, graph_name] + [patterns.map(&:to_sxp_bin)] else res = [:bgp] + patterns.map(&:to_sxp_bin) (graph_name ? [:graph, graph_name, res] : res) end end ## # 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] def bind(solution) patterns.each {|p| p.bind(solution)} self end # Query results in a boolean result (e.g., ASK) # @return [Boolean] def query_yields_boolean? false end # Query results statements (e.g., CONSTRUCT, DESCRIBE, CREATE) # @return [Boolean] def query_yields_statements? false end # Query results solutions (e.g., SELECT) # @return [Boolean] def query_yields_solutions? true end ## # Return the non-destinguished variables contained within patterns and graph name # @return [Array] def ndvars vars.reject(&:distinguished?) end ## # Return the variables contained within patterns and graph name # @return [Array] def vars variables.values end alias_method :optimize_without_expression!, :optimize! ## # Optimize the query, removing lexical shortcuts in URIs # # @return [self] # @see SPARQL::Algebra::Expression#optimize! def optimize!(**options) @patterns = @patterns.map do |pattern| components = pattern.to_a.map do |term| if term.respond_to?(:lexical=) term.dup.instance_eval {@lexical = nil; self} else term end end RDF::Query::Pattern.from(components, **pattern.options) end self.optimize_without_expression!(**options) end ## # Returns `true` if this is executable (i.e., contains a graph patterns), `false` # otherwise. # # @return [Boolean] `true` or `false` def executable?; true; end end class RDF::Query::Pattern # Transform Query Pattern into an SXP # @return [Array] def to_sxp_bin if has_graph? [:quad, subject, predicate, object, graph_name] else [:triple, subject, predicate, object] end end ## # Return the non-destinguished variables contained within this pattern # @return [Array] def ndvars vars.reject(&:distinguished?) end ## # Return the variables contained within this pattern # @return [Array] def vars variables.values end end ## # Extensions for `RDF::Query::Variable`. class RDF::Query::Variable include SPARQL::Algebra::Expression ## # Returns the value of this variable in the given `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] the value of this variable # @raise [TypeError] if the variable is not bound def evaluate(bindings, **options) raise TypeError if bindings.respond_to?(:bound?) && !bindings.bound?(self) bindings[name.to_sym] end ## # Return self # # @return [RDF::Query::Variable] a copy of `self` # @see SPARQL::Algebra::Expression#optimize def optimize(**options) self end end # RDF::Query::Variable ## # Extensions for `RDF::Query::Solutions`. class RDF::Query::Solutions alias_method :filter_without_expression, :filter ## # Filters this solution sequence by the given `criteria`. # # @param [SPARQL::Algebra::Expression] expression # @yield [solution] # each solution # @yieldparam [RDF::Query::Solution] solution # @yieldreturn [Boolean] # @return [void] `self` def filter(expression = {}, &block) case expression when SPARQL::Algebra::Expression filter_without_expression do |solution| expression.evaluate(solution).true? end filter_without_expression(&block) if block_given? self else filter_without_expression(expression, &block) end end alias_method :filter!, :filter end # RDF::Query::Solutions ## # Extensions for `RDF::Query::Solution`. class RDF::Query::Solution ## # Returns the SXP representation of this object, defaults to `self`. # # @return [String] def to_sxp_bin to_a.to_sxp_bin end def to_sxp; to_sxp_bin; end end # RDF::Query::Solution