class RDF::Query ## # An RDF query solution. # # @example Iterating over every binding in the solution # solution.each_binding { |name, value| puts value.inspect } # solution.each_variable { |variable| puts variable.value.inspect } # # @example Iterating over every value in the solution # solution.each_value { |value| puts value.inspect } # # @example Checking whether a variable is bound or unbound # solution.bound?(:title) # solution.unbound?(:mbox) # # @example Retrieving the value of a bound variable # solution[:mbox] # solution.mbox # # @example Retrieving all bindings in the solution as a `Hash` # solution.to_h #=> {mbox: "jrhacker@example.org", ...} # class Solution # Undefine all superfluous instance methods: alias_method :__send, :send undef_method(*instance_methods. map(&:to_s). select {|m| m.match?(/^\w+$/)}. reject {|m| %w(object_id dup instance_eval inspect to_s private_methods class should should_not pretty_print).include?(m) || m[0,2] == '__'}. map(&:to_sym)) include Enumerable ## # Initializes the query solution. # # @param [Hash{Symbol => RDF::Term}] bindings # @yield [solution] def initialize(bindings = {}, &block) @bindings = bindings.to_h if block_given? case block.arity when 1 then block.call(self) else instance_eval(&block) end end end # @private attr_reader :bindings ## # Enumerates over every variable binding in this solution. # # @yield [name, value] # @yieldparam [Symbol] name # @yieldparam [RDF::Term] value # @return [Enumerator] def each_binding(&block) @bindings.each(&block) if block_given? enum_binding end alias_method :each, :each_binding ## # Returns an enumerator for {#each_binding}. # # @return [Enumerator] # @see #each_subject def enum_binding enum_for(:each_binding) end ## # Enumerates over every variable name in this solution. # # @yield [name] # @yieldparam [Symbol] name # @return [Enumerator] def each_name(&block) @bindings.each_key(&block) if block_given? enum_name end alias_method :each_key, :each_name ## # Returns an enumerator for {#each_name}. # # @return [Enumerator] # @see #each_subject def enum_name enum_for(:each_name) end ## # Enumerates over every variable value in this solution. # # @yield [value] # @yieldparam [RDF::Term] value # @return [Enumerator] def each_value(&block) @bindings.each_value(&block) if block_given? enum_value end ## # Returns an enumerator for {#each_value}. # # @return [Enumerator] # @see #each_subject def enum_value enum_for(:each_value) end ## # Returns `true` if this solution contains bindings for any of the given # `variables`. # # @param [Array] variables # an array of variables to check # @return [Boolean] `true` or `false` # @since 0.3.0 def has_variables?(variables) variables.any? { |variable| bound?(variable) } end ## # Enumerates over every variable in this solution. # # @yield [variable] # @yieldparam [Variable] # @return [Enumerator] def each_variable if block_given? @bindings.each do |name, value| yield Variable.new(name, value) end end enum_variable end ## # Returns an enumerator for {#each_variable}. # # @return [Enumerator] # @see #each_subject def enum_variable enum_for(:each_variable) end ## # Returns `true` if the variable `name` is bound in this solution. # # @param [Symbol, #to_sym] name # the variable name # @return [Boolean] `true` or `false` def bound?(name) !unbound?(name) end ## # Returns `true` if the variable `name` is unbound in this solution. # # @param [Symbol, #to_sym] name # the variable name # @return [Boolean] `true` or `false` def unbound?(name) @bindings[name.to_sym].nil? end ## # Returns the value of the variable `name`. # # @param [Symbol, #to_sym] name # the variable name # @return [RDF::Term] def [](name) @bindings[name.to_sym] end ## # Binds or rebinds the variable `name` to the given `value`. # # @param [Symbol, #to_sym] name # the variable name # @param [RDF::Term] value # @return [RDF::Term] # @since 0.3.0 def []=(name, value) @bindings[name.to_sym] = value.is_a?(RDF::Term) ? value : RDF::Literal(value) end ## # Merges the bindings from the given `other` query solution into this # one, overwriting any existing ones having the same name. # # @param [RDF::Query::Solution, #to_h] other # another query solution or hash bindings # @return [void] self # @since 0.3.0 def merge!(other) @bindings.merge!(other.to_h) self end ## # Merges the bindings from the given `other` query solution with a copy # of this one. # # @param [RDF::Query::Solution, #to_h] other # another query solution or hash bindings # @return [RDF::Query::Solution] # @since 0.3.0 def merge(other) self.class.new(@bindings.dup).merge!(other) end ## # Duplicate solution, preserving patterns # @return [RDF::Statement] def dup merge({}) end ## # Compatible Mappings # # Two solution mappings u1 and u2 are compatible if, for every variable v in dom(u1) and in dom(u2), u1(v) = u2(v). # # @param [RDF::Query::Solution, #to_h] other # another query solution or hash bindings # @return [Boolean] # @see http://www.w3.org/TR/2013/REC-sparql11-query-20130321/#defn_algCompatibleMapping def compatible?(other) @bindings.all? do |k, v| !other.to_h.has_key?(k) || other[k].eql?(v) end end ## # Disjoint mapping # # A solution is disjoint with another solution if it shares no common variables in their domains. # # @param [RDF::Query::Solution] other # @return [Boolean] # @see http://www.w3.org/TR/2013/REC-sparql11-query-20130321/#defn_algMinus def disjoint?(other) @bindings.none? do |k, v| v && other.to_h.has_key?(k) && other[k].eql?(v) end end ## # Isomorphic Mappings # Two solution mappings u1 and u2 are isomorphic if, # for every variable v in dom(u1) and in dom(u2), u1(v) = u2(v). # # @param [RDF::Query::Solution, #to_h] other # another query solution or hash bindings # @return [Boolean] def isomorphic_with?(other) @bindings.all? do |k, v| !other.to_h.has_key?(k) || other[k].eql?(v) end end ## # @return [Array} def to_a @bindings.to_a end ## # @return [Hash{Symbol => RDF::Term}} def to_h @bindings.dup end ## # Integer hash of this solution # @return [Integer] def hash @bindings.hash end ## # Equivalence of solution def eql?(other) other.is_a?(Solution) && @bindings.eql?(other.bindings) end alias_method :==, :eql? ## # Equals of solution def ==(other) other.is_a?(Solution) && @bindings == other.bindings end ## # @return [String] def inspect sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, @bindings.inspect) end protected ## # @overload binding(name) # Return the binding for this name # # @param [Symbol] name # @return [RDF::Term] def method_missing(name, *args, &block) if args.empty? && @bindings.has_key?(name.to_sym) @bindings[name.to_sym] else super # raises NoMethodError end end ## # @return [Boolean] def respond_to_missing?(name, include_private = false) @bindings.has_key?(name.to_sym) || super end ## # @private # @param [Symbol, #to_sym] method # @return [Enumerator] # @see Object#enum_for def enum_for(method = :each) # Ensure that enumerators are, themselves, queryable this = self Enumerator.new do |yielder| this.__send(method) {|*y| yielder << (y.length > 1 ? y : y.first)} end end alias_method :to_enum, :enum_for end # Solution end # RDF::Query