class Flounder::Query::Base
Attributes
Bound values in this query.
Domain
that this query was issued from.
Database engine that links Arel to Postgres.
Entity
this query operates on.
Arel *Manager that accumulates this query.
Public Class Methods
# 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
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
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
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
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
Kickers
# File lib/flounder/query/base.rb, line 49 def to_sql prepare_kick manager.to_sql.tap { |sql| domain.log_sql(sql) } end
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
# 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
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
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
# 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
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
Resolves an entity through the domain.
# File lib/flounder/query/base.rb, line 157 def resolve_entity sym domain[sym] end
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
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
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