lib/influxer/metrics/relation.rb in influxer-0.0.1 vs lib/influxer/metrics/relation.rb in influxer-0.1.0

- old
+ new

@@ -1,14 +1,74 @@ +require 'influxer/metrics/relation/time_query' +require 'influxer/metrics/relation/fanout_query' + module Influxer class Relation + attr_reader :values + + include Influxer::TimeQuery + include Influxer::FanoutQuery + MULTI_VALUE_METHODS = [:select, :where, :group] + + MULTI_KEY_METHODS = [:fanout] + + SINGLE_VALUE_METHODS = [:fill, :limit, :merge, :time] + + MULTI_VALUE_SIMPLE_METHODS = [:select, :group] + + SINGLE_VALUE_SIMPLE_METHODS = [:fill, :limit, :merge] + + MULTI_VALUE_METHODS.each do |name| + class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{name}_values # def select_values + @values[:#{name}] ||= [] # @values[:select] || [] + end # end + CODE + end + + MULTI_KEY_METHODS.each do |name| + class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{name}_values # def fanout_values + @values[:#{name}] ||= {} # @values[:fanout] || {} + end # end + CODE + end + + SINGLE_VALUE_METHODS.each do |name| + class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{name}_value # def limit_value + @values[:#{name}] # @values[:limit] + end # end + CODE + end + + SINGLE_VALUE_SIMPLE_METHODS.each do |name| + class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{name}(val) # def limit(val) + @values[:#{name}] = val # @value[:limit] = val + self # self + end # end + CODE + end + + MULTI_VALUE_SIMPLE_METHODS.each do |name| + class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{name}(*args) # def select(*args) + #{name}_values.concat args.map(&:to_s) # select_values.concat args.map(&:to_s) + self # self + end # end + CODE + end + # Initialize new Relation for 'klass' (Class) metrics. # # Available params: # :attributes - hash of attributes to be included to new Metrics object and where clause of Relation # def initialize(klass, params = {}) + @klass = klass @instance = klass.new params[:attributes] self.reset self.where(params[:attributes]) if params[:attributes].present? self end @@ -24,64 +84,51 @@ @instance.send("#{key}=", val) if @instance.respond_to?(key) end @instance end - # accepts strings and symbols only - def select(*args) - return self if args.empty? - @select_values.concat args - self - end - # accepts hash or strings conditions - # TODO: add sanitization and array support - def where(*args,**hargs) - @where_values.concat args.map{|str| "(#{str})"} - - unless hargs.empty? - hargs.each do |key, val| - @where_values << "(#{key}=#{quoted(val)})" - end - end + build_where(args, hargs, false) self end - def group(*args) - return self if args.empty? - @group_values.concat args + def not(*args, **hargs) + build_where(args, hargs, true) self end - def limit(val) - @limit = val - self - end - def to_sql sql = ["select"] - if @select_values.empty? + if select_values.empty? sql << "*" else - sql << @select_values.join(",") + sql << select_values.uniq.join(",") end - sql << "from #{@instance.series}" + sql << "from #{ build_series_name }" - unless @group_values.empty? - sql << "group by #{@group_values.join(",")}" + unless merge_value.nil? + sql << "merge #{ @instance.quote_series(merge_value) }" end - unless @where_values.empty? - sql << "where #{@where_values.join(" and ")}" + unless group_values.empty? and time_value.nil? + sql << "group by #{ (time_value.nil? ? [] : ['time('+@values[:time]+')']).concat(group_values).uniq.join(",") }" end - unless @limit.nil? - sql << "limit #{@limit}" + unless fill_value.nil? + sql << "fill(#{ fill_value })" end + + unless where_values.empty? + sql << "where #{ where_values.join(" and ") }" + end + + unless limit_value.nil? + sql << "limit #{ limit_value }" + end sql.join " " end def to_a return @records if loaded? @@ -99,28 +146,109 @@ def as_json to_a.as_json end def delete_all + sql = ["delete"] + sql << "from #{@instance.series}" + + unless where_values.empty? + sql << "where #{where_values.join(" and ")}" + end + + sql = sql.join " " + + @instance.client.query sql end + def scoping + previous, @klass.current_scope = @klass.current_scope, self + yield + ensure + @klass.current_scope = previous + end + + def merge!(rel) + return self if rel.nil? + MULTI_VALUE_METHODS.each do |method| + (@values[method]||=[]).concat(rel.values[method]).uniq! unless rel.values[method].nil? + end + + MULTI_KEY_METHODS.each do |method| + (@values[method]||={}).merge!(rel.values[method]) unless rel.values[method].nil? + end + + SINGLE_VALUE_METHODS.each do |method| + @values[method] = rel.values[method] unless rel.values[method].nil? + end + + self + end + protected + def build_where(args, hargs, negate) + case + when (args.present? and args[0].is_a?(String)) + where_values.concat args.map{|str| "(#{str})"} + when hargs.present? + build_hash_where(hargs, negate) + else + false + end + end + + def build_hash_where(hargs, negate = false) + hargs.each do |key, val| + if @klass.fanout?(key) + build_fanout(key,val) + else + where_values << "(#{ build_eql(key,val,negate) })" + end + end + end + + def build_eql(key,val,negate) + case val + when Regexp + "#{key}#{ negate ? '!~' : '=~'}#{val.inspect}" + when Array + build_in(key,val,negate) + when Range + build_range(key,val,negate) + else + "#{key}#{ negate ? '<>' : '='}#{quoted(val)}" + end + end + + def build_in(key, arr, negate) + buf = [] + arr.each do |val| + buf << build_eql(key,val,negate) + end + "#{ buf.join( negate ? ' and ' : ' or ') }" + end + + def build_range(key,val,negate) + unless negate + "#{key}>#{quoted(val.begin)} and #{key}<#{quoted(val.end)}" + else + "#{key}<#{quoted(val.begin)} and #{key}>#{quoted(val.end)}" + end + end + def load - @records = @instance.client.query to_sql + @records = get_points(@instance.client.cached_query(to_sql)) @loaded = true end def loaded? @loaded end def reset - @limit = nil - @select_values = [] - @group_values = [] - @where_values = [] + @values = {} @records = nil @loaded = false self end @@ -129,15 +257,25 @@ self.load self end def quoted(val) - if val.is_a?(String) + if val.is_a?(String) or val.is_a?(Symbol) "'#{val}'" elsif val.kind_of?(Time) or val.kind_of?(DateTime) "#{val.to_i}s" else val.to_s end + end + + def method_missing(method, *args, &block) + if @klass.respond_to?(method) + merge!(scoping { @klass.public_send(method, *args, &block) }) + end + end + + def get_points(hash) + hash.values.reduce([],:+) end end end \ No newline at end of file