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