class Flounder::Query::Base

Attributes

bind_values[R]

Bound values in this query.

domain[R]

Domain that this query was issued from.

engine[R]

Database engine that links Arel to Postgres.

entity[R]

Entity this query operates on.

manager[R]

Arel *Manager that accumulates this query.

Public Class Methods

new(domain, manager_klass, entity) click to toggle source
# File lib/flounder/query/base.rb, line 10
def initialize domain, manager_klass, entity
  @domain = domain
  @engine = Flounder::Engine.new(domain.connection_pool)
  @manager = manager_klass.new(engine)
  @entity = entity

  @bind_values = []
end

Public Instance Methods

column_name_to_entity(name) click to toggle source

Implement this if your column names in the query allow inferring the entity and the column name. Return them as a tuple <entity, name>.

# File lib/flounder/query/base.rb, line 81
def column_name_to_entity name
end
kick(connection=nil) click to toggle source

Returns all rows of the query result as an array. Individual rows are mapped to objects using the row mapper.

# File lib/flounder/query/base.rb, line 59
def kick connection=nil
  all = nil
  connection ||= engine

  measure do
    result = connection.exec(self.to_sql, bind_values) 

    descriptor = ::Flounder::Result::Descriptor.new(
      connection, entity, result, &method(:column_name_to_entity))

    all = Array.new(result.ntuples, nil)
    result.ntuples.times do |row_idx|
      all[row_idx] = descriptor.row(row_idx)
    end
  end

  all
end
measure() { || ... } click to toggle source

Measures the block given to it and logs time spent in the block to the domain.

# File lib/flounder/query/base.rb, line 87
def measure
  measure = Benchmark.measure {
    yield        
  }
  domain.log_bm measure
end
parse_conditions(*conditions, &apply) click to toggle source

Parses a conditions array like it is found with where and having and calls the block for each condition bit. Returns self.

Example:

parse_conditions(conditions) { |bit| manager.where(bit) }
# File lib/flounder/query/base.rb, line 100
def parse_conditions *conditions, &apply
  conditions = conditions.dup

  resolve_entity = entity

  # is the first argument an entity? if yes, interpret field names relative
  # to that entity.
  if conditions.size > 1 && entity_like?(conditions.first)
    resolve_entity = convert_to_entity(conditions.shift)
  end

  # is this a hash? extract the first element
  if conditions.size == 1 && conditions.first.kind_of?(Hash)
    conditions = conditions.first 

    conditions.each do |k, v|
      apply.call(
        transform_tuple(resolve_entity, k, v))
    end
    return self
  end

  # or maybe this is a Flounder::Expression::Expr?
  if conditions.size == 1 && conditions.first.kind_of?(Flounder::Expression::Expr)
    condition = conditions.first
    apply.call(
      Arel::Nodes::SqlLiteral.new(condition.to_sql))
    
    return self
  end

  # maybe conditions is of the second form?
  conditions.each do |cond_str, *values|
    apply.call(
      Arel::Nodes::SqlLiteral.new(
        rewrite_bind_variables(cond_str, bind_values.size, values.size)))
    bind_values.concat values
  end
  return self
end
to_sql() click to toggle source

Kickers

# File lib/flounder/query/base.rb, line 49
def to_sql
  prepare_kick
  
  manager.to_sql.tap { |sql| 
    domain.log_sql(sql) }
end
where(*conditions) click to toggle source

Restricts the result returned to only those records that match the conditions.

Example:

query.where(id: 1)            # ... WHERE id = 1 ...
query.where(:id => :user_id)  # ... WHERE id = user_id
query.where(:id.noteq => 1)   # ... WHERE id != 1
# File lib/flounder/query/base.rb, line 39
def where *conditions
  parse_conditions(*conditions) { |bit| manager.where(bit) }
end
with(name, query) click to toggle source
# File lib/flounder/query/base.rb, line 43
def with name, query
  # Nodes::TableAlias.new(relation, name)
  manager.with(query.manager)
end

Private Instance Methods

add_binding(value) click to toggle source

Adds a binding

# File lib/flounder/query/base.rb, line 179
def add_binding value
  bind_values << value
  # is 1-based - hence the postop
  Arel::Nodes::SqlLiteral.new("$#{bind_values.size}")    
end
has_entity?(sym) click to toggle source

Checks if a given symbol can be an entity after resolution.

# File lib/flounder/query/base.rb, line 151
def has_entity? sym
  domain.has_entity?(sym)
end
join_and_condition_part(entity, arel_field, value, kind=:eq) click to toggle source
# File lib/flounder/query/base.rb, line 224
def join_and_condition_part entity, arel_field, value, kind=:eq
  case value
    # covers subselects
    when Flounder::Query::Base
      arel_field.send(kind, value.manager)
    # covers :field_a => :field_b
    when Symbol
      value_field = entity[value].arel_field
      arel_field.send(kind, value_field)
    # covers: :field => (1..100)
    when Range
      arel_field.in(value)
    when Set
      arel_field.in(value.to_a)
  else
    arel_field.send(kind, value)
  end
end
prepare_kick() click to toggle source

Prepares a kick - meaning an execution on the database or a transform to an sql string. Ready Set…

# File lib/flounder/query/base.rb, line 145
def prepare_kick
  # should be overridden
end
resolve_entity(sym) click to toggle source

Resolves an entity through the domain.

# File lib/flounder/query/base.rb, line 157
def resolve_entity sym
  domain[sym]
end
rewrite_bind_variables(str, offset, limit) click to toggle source

Rewrites a statement that contains bind placeholders like ‘$1’ to contain placeholders starting at offset+1. Also checks that no placeholder exceeds the limit.

# File lib/flounder/query/base.rb, line 165
def rewrite_bind_variables str, offset, limit
  str.gsub(%r(\$(?<idx>\d+))) do |match|
    idx = Integer($~[:idx])

    raise Flounder::BindIndexOutOfBounds, 
      "Binding to $#{idx} in #{str.inspect}, but only #{limit} variables provided" \
      if idx-1 >= limit

    "$#{idx + offset}" 
  end
end
transform_tuple(entity, field, value) click to toggle source

Called on each key/value pair of a

* condition
* join

clause, this returns a field that can be passed to Arel

* #where
* #on
# File lib/flounder/query/base.rb, line 192
def transform_tuple entity, field, value
  if value.kind_of? Flounder::Field
    value = value.arel_field
  end

  if defined?(DataMapper) && 
    defined?(DataMapper::Query::Operator) &&
    field.kind_of?(DataMapper::Query::Operator)

    # interop: datamapper hijacks some of our operators, let's reverse this
    field = Flounder::SymbolExtensions::Modifier.new(
      field.target, field.operator)
  end

  case field
    # covers: :field_a => ...
    when Symbol
      join_and_condition_part(entity, entity[field].arel_field, value)
    # covers: entity[:field] => ...
    when Flounder::Field
      join_and_condition_part(entity, field.arel_field, value)
    # covers: :field_a.noteq => ...
    when Flounder::SymbolExtensions::Modifier
      join_and_condition_part(
        entity, 
        field.to_arel_field(entity), 
        value, 
        field.kind)
  else
    fail "Could not transform condition part. (#{field.inspect}, #{value.inspect})"
  end
end
transform_tuple_for_set(field, value) click to toggle source

Called on each key/value pair of an update clause, this returns a hash that can be passed to Arel update.

# File lib/flounder/query/base.rb, line 246
def transform_tuple_for_set field, value
  if value.kind_of? Hash
    value = PgHstore.dump(value, true)
  end

  # If this is update by subselect, don't create a binding.
  placeholder = if value.kind_of?(Flounder::Query::Base)
    Flounder.literal("(#{value.manager.to_sql})")
  else
    add_binding(value)
  end

  case field
    when Symbol, String
      [entity[field.to_sym].arel_field, placeholder]
    when Flounder::Field
      [field.arel_field, placeholder]
  else
    fail "Could not transform condition part. (#{field.inspect}, #{value.inspect})"
  end
end