class Mongery::Query

Constants

OPERATOR_MAP

Attributes

custom_operators[R]
mapped_properties[R]
schema[R]
table[R]

Public Class Methods

new(table, schema, mapped_properties, custom_operators) click to toggle source
# File lib/mongery.rb, line 53
def initialize(table, schema, mapped_properties, custom_operators)
  @table = table
  @schema = schema
  @mapped_properties = mapped_properties
  @custom_operators = custom_operators
  @condition = nil
end

Public Instance Methods

arel() click to toggle source
# File lib/mongery.rb, line 66
def arel
  @arel ||= build_arel
end
count() click to toggle source
# File lib/mongery.rb, line 123
def count
  table.project('COUNT(*)').tap do |t|
    t.where(condition) if condition
  end
end
delete() click to toggle source
# File lib/mongery.rb, line 144
def delete
  Arel::DeleteManager.new(table.engine).tap do |manager|
    manager.from(table)
    manager.where(condition) if condition
  end
end
index(col) click to toggle source
# File lib/mongery.rb, line 109
def index(col)
  Arel.sql(%Q[CREATE INDEX "#{table.name}_#{col}_idx" ON "#{table.name}" ((#{sql_json_exp(col)}))])
end
insert(args) click to toggle source
# File lib/mongery.rb, line 129
def insert(args)
  Arel::InsertManager.new(table.engine).tap do |manager|
    manager.into(table)
    manager.insert([[table[:id], args['_id']], [table[:data], args.to_json], *mapped_values(args)])
  end
end
limit(number) click to toggle source
# File lib/mongery.rb, line 78
def limit(number)
  arel.take(number)
  self
end
skip(number) click to toggle source
# File lib/mongery.rb, line 83
def skip(number)
  arel.skip(number)
  self
end
sort(params) click to toggle source
# File lib/mongery.rb, line 88
def sort(params)
  params.each do |col, val|
    order = val > 0 ? :asc : :desc
    case col.to_s
    when "_id"
      arel.order(table[:id].send(order))
    else
      arel.order(sql_json_path(col).send(order))
    end
  end
  self
end
sql_json_exp(col) click to toggle source
# File lib/mongery.rb, line 101
def sql_json_exp(col)
  if schema && numeric?(col)
    sql_json_path(col) + "::numeric"
  else
    sql_json_path(col)
  end
end
to_arel() click to toggle source
# File lib/mongery.rb, line 70
def to_arel
  arel
end
to_sql() click to toggle source
# File lib/mongery.rb, line 74
def to_sql
  to_arel.to_sql
end
update(args) click to toggle source
# File lib/mongery.rb, line 136
def update(args)
  Arel::UpdateManager.new(table.engine).tap do |manager|
    manager.table(table)
    manager.set([[table[:data], args.to_json], *mapped_values(args)])
    manager.where(condition) if condition
  end
end
where(args) click to toggle source
# File lib/mongery.rb, line 61
def where(args)
  @where = args
  self
end

Private Instance Methods

build_arel() click to toggle source
# File lib/mongery.rb, line 161
def build_arel
  table.project(table[:data]).tap do |t|
    t.where(condition) if condition
  end
end
chain(op, conditions) click to toggle source
# File lib/mongery.rb, line 347
def chain(op, conditions)
  result = nil
  conditions.each do |cond|
    result = result ? result.send(op, cond) : cond
  end
  result
end
compare(col, val, op) click to toggle source
# File lib/mongery.rb, line 286
def compare(col, val, op)
  operator(wrap(col, val), op, val)
end
compare_schema(col, val, type, op) click to toggle source
# File lib/mongery.rb, line 308
def compare_schema(col, val, type, op)
  case type
  when "string"
    operator(Arel.sql("(#{col})"), op, val)
  when "number", "integer"
    operator(Arel.sql("(#{col})::numeric"), op, val)
  else
    case val
    when Numeric
      operator(Arel.sql("(#{col})"), op, val.to_s)
    else
      operator(Arel.sql("(#{col})"), op, val)
    end
  end
end
condition() click to toggle source
# File lib/mongery.rb, line 157
def condition
  @condition ||= translate(@where)
end
has_operator?(value) click to toggle source
# File lib/mongery.rb, line 282
def has_operator?(value)
  value.keys.any? {|key| key =~ /^\$/ }
end
json_pathize(paths) click to toggle source
# File lib/mongery.rb, line 338
def json_pathize(paths)
  quote("{#{paths.join(',')}}")
end
mapped_keys() click to toggle source
# File lib/mongery.rb, line 153
def mapped_keys
  mapped_properties.keys
end
mapped_values(args) click to toggle source
# File lib/mongery.rb, line 113
def mapped_values(args)
  pairs = []
  mapped_properties.each do |key, column|
    pairs.push([table[column], args[key]]) if args.key?(key)
  end
  pairs
end
numeric?(col) click to toggle source
# File lib/mongery.rb, line 303
def numeric?(col)
  type = schema.column_type(col.to_s)
  ["number", "integer"].include?(type)
end
operator(col, op, val) click to toggle source
# File lib/mongery.rb, line 324
def operator(col, op, val)
  case op
  when Symbol
    col.send(op, val)
  else
    op.call(col, val)
  end
end
quote(str) click to toggle source
# File lib/mongery.rb, line 342
def quote(str)
  # FIXME there should be a better way to do this
  table.engine.connection.quote(str)
end
sql_json_path(col) click to toggle source
# File lib/mongery.rb, line 333
def sql_json_path(col)
  paths = col.to_s.split('.')
  Arel.sql("data#>>#{json_pathize(paths)}")
end
translate(query) click to toggle source
# File lib/mongery.rb, line 167
def translate(query)
  chain(:and, query.map {|col, value| translate_cv(col, value) })
end
translate_cv(col, value) click to toggle source
# File lib/mongery.rb, line 171
def translate_cv(col, value)
  case col.to_s
  when "$or"
    chain(:or, value.map {|q| translate(q) })
  when "$and"
    chain(:and, value.map {|q| translate(q) })
  when /^\$/
    raise UnsupportedQuery, "Unsupported operator #{col}"
  when "_id"
    translate_value(table[:id], value)
  when *mapped_keys
    translate_value(table[mapped_properties[col.to_s]], value)
  else
    if schema
      translate_value_schema(col, sql_json_path(col), value)
    else
      translate_value_dynamic(sql_json_path(col), value)
    end
  end
end
translate_value(col, value) click to toggle source
# File lib/mongery.rb, line 197
def translate_value(col, value)
  case value
  when Hash
    if has_operator?(value)
      chain(:and, value.map {|op, val|
              if custom_operators[op]
                operator(col, custom_operators[op], val)
              elsif OPERATOR_MAP.key?(op)
                col.send(OPERATOR_MAP[op], val)
              else
                raise UnsupportedQuery, "Unknown operator #{op}"
              end
            })
    else
      col.eq(value.to_json)
    end
  else
    col.eq(value)
  end
end
translate_value_dynamic(col, value) click to toggle source
# File lib/mongery.rb, line 250
def translate_value_dynamic(col, value)
  case value
  when String, TrueClass, FalseClass
    col.eq(value.to_s)
  when Numeric, NilClass
    compare(col, value, :eq)
  when Hash
    if has_operator?(value)
      chain(:and, value.map {|op, val|
              case op
              when "$in"
                if val.all? {|v| v.is_a? Numeric }
                  wrap(col, val.first).in(val)
                else
                  col.in(val.map(&:to_s))
                end
              when *(custom_operators.keys)
                compare(col, val, custom_operators[op])
              when "$eq", "$ne", "$gt", "$gte", "$lt", "$lte"
                compare(col, val, OPERATOR_MAP[op])
              else
                raise UnsupportedQuery, "Unknown operator #{op}"
              end
            })
    else
      col.eq(value.to_json)
    end
  else
    col.eq(value.to_json)
  end
end
translate_value_schema(column, col, value) click to toggle source
# File lib/mongery.rb, line 218
def translate_value_schema(column, col, value)
  type = schema.column_type(column.to_s)
  case value
  when Hash
    if has_operator?(value)
      chain(:and, value.map {|op, val|
              case op
              when "$in"
                case type
                when "array"
                  chain(:or, val.map { |v| col.matches(%Q[%"#{v}"%]) })
                else
                  compare_schema(col, val, type, :in)
                end
              when *(custom_operators.keys)
                compare_schema(col, val, type, custom_operators[op])
              when "$eq", "$ne", "$gt", "$gte", "$lt", "$lte"
                compare_schema(col, val, type, OPERATOR_MAP[op])
              else
                raise UnsupportedQuery, "Unknown operator #{op}"
              end
            })
    else
      col.eq(value.to_json)
    end
  when String, Numeric, NilClass
    compare_schema(col, value, type, :eq)
  else
    compare_schema(col, value.to_json, type, :eq)
  end
end
wrap(col, val) click to toggle source
# File lib/mongery.rb, line 290
def wrap(col, val)
  case val
  when NilClass
    # data#>>'{foo}' IS NULL    is invalid
    # (data#>>'{foo}') IS NULL  is valid
    Arel.sql("(#{col})")
  when Numeric
    Arel.sql("(#{col})::numeric")
  else
    col
  end
end