class Query

Public Class Methods

new(criteria) click to toggle source
# File lib/redisant/query.rb, line 3
def initialize criteria
  @criteria = criteria
end

Public Instance Methods

run() click to toggle source
# File lib/redisant/query.rb, line 7
def run
  raise "Count and random cannot be combined" if @criteria.count? && @criteria.random?
  
  collect_keys
  if @criteria.count?
    return count
  elsif @criteria.random?
    random
  else
    fetch
  end

  if @criteria.criteria[:exists]
    any?
  elsif @criteria.ids?
    flatten_single_items @ids
  else
    load_objects
    flatten_single_items @objects
  end
ensure
  delete_tmp
end

Private Instance Methods

any?() click to toggle source
# File lib/redisant/query.rb, line 33
def any?
  @ids and @ids.any?
end
collect_keys() click to toggle source
# File lib/redisant/query.rb, line 37
def collect_keys
  @sub_keys = []
  # start from a has_many relation?
  @sub_keys << @criteria.ids_key if @criteria.get_relation || @criteria.get_conditions.empty?
  
  # where conditions
  name = @criteria.object_class.name.downcase
  @criteria.get_conditions.each_pair do |k,v|
    @sub_keys << "#{name}:search:#{k}:#{v}"
  end
  
  @final_key = @sub_keys.first if @sub_keys.size == 1
end
count() click to toggle source
# File lib/redisant/query.rb, line 51
def count
  if @sub_keys.size > 1
    count_intersection
  else
    count_ids
  end
end
count_ids() click to toggle source
# File lib/redisant/query.rb, line 152
def count_ids
  @result = $redis.scard @final_key
end
count_intersection() click to toggle source
# File lib/redisant/query.rb, line 116
def count_intersection
  lua = "return #redis.call('SINTER', unpack(KEYS));"
  $redis.eval lua, @sub_keys
end
count_set() click to toggle source
# File lib/redisant/query.rb, line 121
def count_set
  $redis.scard @final_key
end
delete_tmp() click to toggle source
# File lib/redisant/query.rb, line 101
def delete_tmp
  $redis.del @final_key if @del
  @del = nil
end
fetch() click to toggle source
# File lib/redisant/query.rb, line 67
def fetch
  if @sub_keys.size > 1
    if @criteria.sort? || @criteria.limit? || @criteria.single?
      store_intersection
      @ids = sort_and_limit
    else
      @ids = fetch_intersection
    end
  else
    @final_key = @sub_keys.first
    @ids = sort_and_limit
  end
end
fetch_intersection() click to toggle source
# File lib/redisant/query.rb, line 89
def fetch_intersection
  got = $redis.sinter @sub_keys
  got.map { |id| id.to_i } if got
end
flatten_single_items(a) click to toggle source
# File lib/redisant/query.rb, line 81
def flatten_single_items a
  if @criteria.single?
    a.first
  else
    a
  end
end
load_objects() click to toggle source
# File lib/redisant/query.rb, line 164
def load_objects
  @objects = @ids.map { |id| @criteria.object_class.load id }
end
random() click to toggle source
# File lib/redisant/query.rb, line 59
def random
  if @sub_keys.size > 1
    @ids = [random_intersection]
  else
    @ids = [random_ids]
  end
end
random_ids() click to toggle source
# File lib/redisant/query.rb, line 156
def random_ids
  $redis.srandmember(@final_key).to_i
end
random_intersection() click to toggle source
# File lib/redisant/query.rb, line 106
def random_intersection
  # random numbers in redis lua scripts must be seeded with an outside integer
  lua = "
  math.randomseed(tonumber(ARGV[1]))
  local ids=redis.call('SINTER', unpack(KEYS))
  return ids[ math.random(#ids) ]
  "
  $redis.eval(lua, @sub_keys, [rand(2**32)]).to_i
end
random_set() click to toggle source
# File lib/redisant/query.rb, line 160
def random_set
  $redis.srandmember(@final_key).to_i
end
sort_and_limit() click to toggle source
# File lib/redisant/query.rb, line 125
def sort_and_limit
  criteria = @criteria.criteria
  
  sort = criteria[:sort]
  # use dup because string might be frozen, and we might need to modify it later
  order = criteria[:order].to_s.dup || 'asc'
  
  if criteria[:limit]
    limit = [criteria[:offset] || 0, criteria[:limit]]
  end
  
  if sort
    index = @criteria.object_class.find_index(sort)
    type = index.type
    by = "#{@criteria.object_class.name.downcase}:*:attributes->#{sort}"
    by << ":float" if type == 'float'
    if type == 'string'
      order << ' alpha'
    end
  else
    by = 'nosort' unless criteria[:limit]==1
  end
  
  ids = $redis.sort @final_key, limit: limit, by: by, order: order
  ids.map! { |t| t.to_i } if ids
end
store_intersection() click to toggle source
# File lib/redisant/query.rb, line 94
def store_intersection
  # combine search sets to temporary set that we can sort later
  @final_key = "tmp:#{rand(36**16).to_s(36)}"
  $redis.sinterstore @final_key, @sub_keys
  @del = true
end