# encoding: utf-8 module Axiom module Adapter module Arango # A relation backed by an adapter class Gateway < Relation include Concord.new(:adapter, :relation) DECORATED_CLASS = superclass # remove methods so they can be proxied undef_method *DECORATED_CLASS.public_instance_methods(false).map(&:to_s) - %w[ materialize ] undef_method :project, :remove, :extend, :rename, :restrict, :sort_by, :reverse, :drop, :take # Iterate over each row in the results # # @example # gateway = Gateway.new(adapter, relation) # gateway.each { |tuple| ... } # # @yield [tuple] # # @yieldparam [Tuple] tuple # each tuple in the results # # @return [self] # # @api public def each return to_enum unless block_given? tuples.each { |tuple| yield tuple } self end # Return a relation that is the join of two relations # # @example natural join # join = relation.join(other) # # @example theta-join using a block # join = relation.join(other) { |r| r.a.gte(r.b) } # # @param [Relation] other # the other relation to join # # @yield [relation] # optional block to restrict the tuples with # # @yieldparam [Relation] relation # the context to evaluate the restriction with # # @yieldreturn [Function, #call] # predicate to restrict the tuples with # # @return [Gateway] # return a gateway if the adapters are equal # @return [Algebra::Join] # return a normal join when the adapters are not equal # @return [Algebra::Restriction] # return a normal restriction when the adapters are not equal # for a theta-join # # @api public def join(other) if block_given? super else binary_operation(__method__, other, Algebra::Join) end end # Return a relation that is the Cartesian product of two relations # # @example # product = gateway.product(other) # # @param [Relation] other # the other relation to find the product with # # @return [Gateway] # return a gateway if the adapters are equal # @return [Algebra::Product] # return a normal product when the adapters are not equal # # @api public def product(other) binary_operation(__method__, other, Algebra::Product) end # Return the union between relations # # @example # union = gateway.union(other) # # @param [Relation] other # the other relation to find the union with # # @return [Gateway] # return a gateway if the adapters are equal # @return [Algebra::Union] # return a normal union when the adapters are not equal # # @api public def union(other) binary_operation(__method__, other, Algebra::Union) end # Return the intersection between relations # # @example # intersect = gateway.intersect(other) # # @param [Relation] other # the other relation to find the intersect with # # @return [Gateway] # return a gateway if the adapters are equal # @return [Algebra::Intersection] # return a normal intersection when the adapters are not equal # # @api public def intersect(other) binary_operation(__method__, other, Algebra::Intersection) end # Return the difference between relations # # @example # difference = gateway.difference(other) # # @param [Relation] other # the other relation to find the difference with # # @return [Gateway] # return a gateway if the adapters are equal # @return [Algebra::Difference] # return a normal dfference when the adapters are not equal # # @api public def difference(other) binary_operation(__method__, other, Algebra::Difference) end # Return a summarized relation # # @example with no arguments # summarization = gateway.summarize do |context| # context.add(:count, context[:id].count) # end # # @example with a relation # summarization = gateway.summarize(relation) do |context| # context.add(:count, context[:id].count) # end # # @example with a header # summarization = gateway.summarize([ :name ]) do |context| # context.add(:count, context[:id].count) # end # # @example with another gateway # summarization = gateway.summarize(other_gateway) do |context| # context.add(:count, context[:id].count) # end # # @param [Gateway, Relation, Header, #to_ary] summarize_with # # @yield [function] # Evaluate a summarization function # # @yieldparam [Evaluator::Context] context # the context to evaluate the function within # # @return [Gateway] # return a gateway if the adapters are equal, or there is no adapter # @return [Algebra::Summarization] # return a normal summarization when the adapters are not equal # # @api public def summarize(summarize_with = TABLE_DEE, &block) if summarize_merge?(summarize_with) summarize_merge(summarize_with, &block) else summarize_split(summarize_with, &block) end end # Test if the method is supported on this object # # @param [Symbol] method # # @return [Boolean] # # @api private def respond_to?(method, *) super || forwardable?(method) end private # Proxy the message to the relation # # @param [Symbol] method # # @param [Array] args # # @return [self] # return self for all command methods # @return [Object] # return response from all query methods # # @api private def method_missing(method, *args, &block) forwardable?(method) ? forward(method, *args, &block) : super end # Test if the method can be forwarded to the relation # # @param [Symbol] method # # @return [Boolean] # # @api private def forwardable?(method) relation.respond_to?(method) end # Forward the message to the relation # # @param [Array] args # # @return [self] # return self for all command methods # @return [Object] # return response from all query methods # # @api private def forward(*args, &block) inner_relation = relation response = inner_relation.public_send(*args, &block) if response.equal?(inner_relation) self elsif response.kind_of?(DECORATED_CLASS) self.class.new(adapter, response) else response end end # Return a list of tuples to iterate over # # @return [#each] # # @api private def tuples inner_relation = relation if materialized? inner_relation else read_tuples end end # Return a tuple reader # # @return [Enumerable] # # @api private # def read_tuples adapter.reader(relation) end # Return a binary relation # # @param [Relation] other # # @return [Gateway] # return a gateway if the adapters are equal # @return [Relation] # return a binary relation when the adapters are not equal # # @api private def binary_operation(method, other, factory) if same_adapter?(other) forward(method, other.relation) else factory.new(self, other) end end # Test if the other object is a Gateway # # @param [Gateway, Relation] other # # @return [Boolean] # # @api private def gateway?(other) other.kind_of?(Gateway) end # Test if the other object uses the same adapter # # @param [Gateway, Relation] other # # @return [Boolean] # # @api private def same_adapter?(other) gateway?(other) && adapter.eql?(other.adapter) end # Test if the summarize_with object can be merged into the summarization # # @param [Gateway, Relation, Header] summarize_with # # @return [Boolean] # # @api private def summarize_merge?(summarize_with) !summarize_with.respond_to?(:header) || summarize_with.equal?(TABLE_DEE) || same_adapter?(summarize_with) end # Merge the summarize_with into the summarization # # @param [Gateway, Relation, Header] summarize_with # # @return [Gateway] # # @api private def summarize_merge(summarize_with, &block) summarize_with = summarize_with.relation if gateway?(summarize_with) forward(:summarize, summarize_with, &block) end # Split the summarize_with into a separate relation, wrapped in a summarization # # @param [Gateway, Relation, Header] summarize_with # # @return [Algebra::Summarization] # # @api private def summarize_split(summarize_with, &block) # evaluate the gateway, then summarize with the provided relation context = Evaluator::Context.new(header - summarize_with.header, &block) Algebra::Summarization.new(self, summarize_with, context.functions) end end end end end