class Aliyun::Log::Record::Relation

Public Class Methods

new(klass, opts = {}) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 7
def initialize(klass, opts = {})
  @klass = klass
  @opts = opts
  @klass.auto_load_schema
end

Public Instance Methods

api(opts) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 104
def api(opts)
  @opts.merge!(opts)
  self
end
count() click to toggle source
# File lib/aliyun/log/record/relation.rb, line 164
def count
  query = @opts.dup
  if query[:select].blank?
    @opts[:select] = 'COUNT(*) as count'
    sql = to_sql
  else
    _sql = to_sql
    sql = "SELECT COUNT(*) as count"
    sql += " FROM(#{_sql})" if _sql.present?
  end
  query[:query] = "#{query[:search] || '*'}|#{sql}"
  res = execute(query)
  res.dig(0, 'count').to_i
end
fifth() click to toggle source
# File lib/aliyun/log/record/relation.rb, line 33
def fifth
  find_offset(4)
end
find_offset(nth, line = 1, reverse = false) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 42
def find_offset(nth, line = 1, reverse = false)
  # @opts[:select] = '*'
  @opts[:line] = line
  @opts[:offset] = nth
  if @opts[:order].present? && reverse
    conds = []
    @opts[:order].split(',').each do |field|
      field.gsub!(/\s+desc|asc.*/i, '')
      conds << "#{field.strip} DESC"
    end
    @opts[:order] = conds.join(', ')
  end
  @opts[:order] ||= reverse ? '__time__ DESC' : '__time__ ASC'
  if @opts[:select].blank?
    line <= 1 ? load[0] : load
  else
    line <= 1 ? result[0] : result
  end
end
first(line = 1) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 17
def first(line = 1)
  find_offset(0, line, false)
end
fourth() click to toggle source
# File lib/aliyun/log/record/relation.rb, line 29
def fourth
  find_offset(3)
end
from(from) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 70
def from(from)
  ts = from.is_a?(Integer) ? from : from.to_time.to_i
  @opts[:from] = ts
  self
end
group(*fields) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 140
def group(*fields)
  @opts[:group] = fields.join(', ')
  self
end
inspect() click to toggle source
# File lib/aliyun/log/record/relation.rb, line 13
def inspect
  "#<#{self.class}>"
end
last(line = 1) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 37
def last(line = 1)
  data = find_offset(0, line, true)
  line > 1 ? data.reverse : data
end
limit(val)
Alias for: line
line(val) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 82
def line(val)
  @opts[:line] = val.to_i
  self
end
Also aliased as: limit
load() click to toggle source
# File lib/aliyun/log/record/relation.rb, line 200
def load
  result.map do |json_attr|
    record = @klass.new
    json_attr.each do |key, value|
      record.send("#{key}=", value) if record.respond_to?("#{key}=")
    end
    record
  end
end
offset(val) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 88
def offset(val)
  @opts[:offset] = val.to_i
  self
end
order(*fields) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 145
def order(*fields)
  if fields[0].is_a?(Hash)
    @opts[:order] = fields[0].map do |k, v|
      unless %w[asc desc].include?(v.to_s)
        raise ArgumentError, "Direction \"#{v}\" is invalid. Valid directions are: [:asc, :desc, :ASC, :DESC]" 
      end
      "#{k} #{v}"
    end.join(', ')
  else
    @opts[:order] = fields.join(', ')
  end
  self
end
page(val) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 93
def page(val)
  @opts[:page] = val - 1 if val >= 1
  singleton_class.send(:define_method, :total_count) do
    @total_count ||= count
  end
  singleton_class.send(:define_method, :total_pages) do
    (total_count.to_f / (@opts[:line] || 100)).ceil
  end
  self
end
query(opts = {}) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 159
def query(opts = {})
  @opts[:query] = opts
  self
end
result() click to toggle source
# File lib/aliyun/log/record/relation.rb, line 187
def result
  query = @opts.dup
  query[:query] = query[:search] || '*'
  sql = to_sql
  if sql.present?
    query[:query] += "|#{sql}"
    @opts[:line] = nil
    @opts[:offset] = nil
    @opts[:page] = nil
  end
  execute(query)
end
scoping() { || ... } click to toggle source
# File lib/aliyun/log/record/relation.rb, line 62
def scoping
  previous = @klass.current_scope
  @klass.current_scope = self
  yield
ensure
  @klass.current_scope = previous
end
second() click to toggle source
# File lib/aliyun/log/record/relation.rb, line 21
def second
  find_offset(1)
end
select(*fields) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 135
def select(*fields)
  @opts[:select] = fields.join(', ')
  self
end
sql(*statement) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 115
def sql(*statement)
  unless statement[0].is_a?(String)
    raise ParseStatementInvalid, 'Only support string statement'
  end
  ql = sanitize_array(*statement)
  @opts[:sql] = ql if ql.present?
  self
end
sum(field) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 179
def sum(field)
  @opts[:select] = "SUM(#{field}) as sum"
  query = @opts.dup
  query[:query] = "#{query[:search] || '*'}|#{to_sql}"
  res = execute(query)
  res.dig(0, 'sum').to_f
end
third() click to toggle source
# File lib/aliyun/log/record/relation.rb, line 25
def third
  find_offset(2)
end
to(to) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 76
def to(to)
  ts = to.is_a?(Integer) ? to : to.to_time.to_i
  @opts[:to] = ts
  self
end
to_sql() click to toggle source
# File lib/aliyun/log/record/relation.rb, line 210
def to_sql
  return @opts[:sql] if @opts[:sql].present?
  opts = @opts.dup
  sql_query = []
  sql_query << "WHERE #{opts[:where]}" if opts[:where].present?
  sql_query << "GROUP BY #{opts[:group]}" if opts[:group].present?
  sql_query << "ORDER BY #{opts[:order]}" if opts[:order].present?
  if sql_query.present? || opts[:select].present?
    sql_query.insert(0, "SELECT #{opts[:select] || '*'}")
    sql_query.insert(1, 'FROM log') unless opts[:select]&.match?(/from\s+log/i)
    if opts[:line] || opts[:page] || opts[:offset]
      parse_page
      sql_query << "LIMIT #{@opts[:offset]},#{@opts[:line]}"
    end
  end
  "#{opts[:query]}#{sql_query.join(' ')}"
end
where(*statement) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 124
def where(*statement)
  if statement[0].is_a?(String)
    ql = sanitize_array(*statement)
    @opts[:where] = ql
  else
    ql = statement_ql(*statement)
    @opts[:search] = ql
  end
  self
end

Private Instance Methods

_quote(type, value) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 328
def _quote(type, value)
  v = TypeCasting.cast_field(value, cast_type: type || :string)
  case type
  when :string, nil then "'#{v.to_s}'"
  when :bigdecimal  then v.to_s("F")
  when :integer     then v.to_s.to_i
  when :datetime, :date then "'#{v.iso8601}'"
  else
    value.to_s
  end
end
_quote_type_value(value) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 340
def _quote_type_value(value)
  case value.class.name
  when 'String'                   then "'#{value.to_s}'"
  when 'BigDecimal', 'Float'      then value.to_s("F")
  when 'Date', 'DateTime', 'Time' then "'#{value.iso8601}'"
  else
    value
  end
end
execute(query) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 230
def execute(query)
  puts query.slice(:from, :to, :search, :query).to_json
  res = Log.record_connection.get_logs(@klass.project_name, @klass.logstore_name, query)
  JSON.parse(res)
end
method_missing(method, *args, &block) click to toggle source
Calls superclass method
# File lib/aliyun/log/record/relation.rb, line 356
def method_missing(method, *args, &block)
  if @klass.respond_to?(method)
    scoping { @klass.public_send(method, *args, &block) }
  else
    super
  end
end
parse_page() click to toggle source
# File lib/aliyun/log/record/relation.rb, line 236
def parse_page
  @opts[:line] ||= 100
  @opts[:page] ||= 0
  @opts[:offset] = @opts[:page] * @opts[:line]
end
raise_if_hash_quote(value) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 350
def raise_if_hash_quote(value)
  if value.is_a?(Hash) || value.is_a?(ActiveSupport::HashWithIndifferentAccess)
    raise ParseStatementInvalid, "can't quote Hash"
  end
end
replace_bind_variables(statement, values) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 308
def replace_bind_variables(statement, values)
  expected = statement.count('?')
  provided = values.size
  if expected != provided
    raise ParseStatementInvalid, "wrong number of bind variables (#{provided} " \
                                 "for #{expected}) in: #{statement}"
  end
  bound = values.dup
  statement.gsub(/\?/) do
    value = bound.shift
    raise_if_hash_quote(value)
    if value.is_a?(Array) || value.is_a?(Range)
      values = value.map { |v| _quote_type_value(v) }
      values.join(', ')
    else
      _quote_type_value(value)
    end
  end
end
replace_named_bind_variables(statement, bind_vars) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 291
def replace_named_bind_variables(statement, bind_vars)
  statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match|
    if bind_vars.include?(match = Regexp.last_match(2).to_sym)
      match_value = bind_vars[match]
      raise_if_hash_quote(match_value)
      if match_value.is_a?(Array) || match_value.is_a?(Range)
        values = match_value.map { |v| _quote_type_value(v) }
        values.join(', ')
      else
        _quote_type_value(match_value)
      end
    else
      raise ParseStatementInvalid, "missing value for :#{match} in #{statement}"
    end
  end
end
sanitize_array(*ary) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 278
def sanitize_array(*ary)
  statement, *values = ary
  if values.first.is_a?(Hash) && /:\w+/.match?(statement)
    replace_named_bind_variables(statement, values.first)
  elsif statement.include?('?')
    replace_bind_variables(statement, values)
  elsif statement.blank? || values.blank?
    statement
  else
    statement % values.collect(&:to_s)
  end
end
sanitize_hash(search_content) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 250
def sanitize_hash(search_content)
  return search_content unless search_content.is_a?(Hash)

  search_content.reject { |_, v| v.nil? }.map do |key, value|
    options = @klass.attributes[:"#{key}"]
    unless options
      raise UnknownAttributeError, "unknown field '#{key}' for #{@klass.name}."
    end

    raise_if_hash_quote(value)

    cast_type = options[:cast_type]

    is_tag = @klass.tag_attributes[:"#{key}"]
    key = :"__tag__:#{key}" if is_tag
    if value.is_a?(Array)
      values = value.uniq.map { |v| is_tag ? v : _quote(cast_type, v) }
      str_values = values.map { |v| "#{key}: #{v}" }.join(' OR ')
      values.size > 1 ? "(#{str_values})" : str_values
    elsif value.is_a?(Range)
      "#{key} in [#{value.begin} #{value.end}]"
    else
      quote_value = is_tag ? value : _quote(cast_type, value)
      "#{key}: #{quote_value}"
    end
  end.join(' AND ')
end
statement_ql(*statement) click to toggle source
# File lib/aliyun/log/record/relation.rb, line 242
def statement_ql(*statement)
  if statement.size == 1
    sanitize_hash(statement.first)
  elsif statement.size > 1
    sanitize_array(*statement)
  end
end