lib/druid/post_aggregation.rb
module Druid
class PostAggregation
include ActiveModel::Model
attr_accessor :numBuckets
attr_accessor :type
validates :type, inclusion: { in: %w(arithmetic fieldAccess constant javascript hyperUniqueCardinality) }
class NameValidator < ActiveModel::EachValidator
TYPES = %w(arithmetic constant javascript)
def validate_each(record, attribute, value)
if !TYPES.include?(record.type)
record.errors.add(attribute, "is not supported by type=#{record.type}") if value
end
end
end
attr_accessor :name
validates :name, name: true
class FnValidator < ActiveModel::EachValidator
TYPES = %w(arithmetic)
def validate_each(record, attribute, value)
if TYPES.include?(record.type)
record.errors.add(attribute, 'must be a valid arithmetic operation') unless %w(+ - * /).include?(value)
else
record.errors.add(attribute, "is not supported by type=#{record.type}") if value
end
end
end
attr_accessor :fn
validates :fn, fn: true
class FieldsValidator < ActiveModel::EachValidator
TYPES = %w(arithmetic)
def validate_each(record, attribute, value)
if TYPES.include?(record.type)
value.each(&:valid?) # trigger validation
value.each do |fvalue|
fvalue.errors.messages.each do |k, v|
record.errors.add(attribute, { k => v })
end
end
else
record.errors.add(attribute, "is not supported by type=#{record.type}") if value
end
end
end
attr_accessor :fields
validates :fields, fields: true
def fields=(value)
@fields = [value].flatten.compact.map do |aggregation|
PostAggregation.new(aggregation)
end
end
class FieldnameValidator < ActiveModel::EachValidator
TYPES = %w(fieldAccess hyperUniqueCardinality)
def validate_each(record, attribute, value)
if TYPES.include?(record.type)
record.errors.add(attribute, 'may not be blank') if value.blank?
else
record.errors.add(attribute, "is not supported by type=#{record.type}") if value
end
end
end
attr_accessor :fieldName
validates :fieldName, fieldname: true
class ValueValidator < ActiveModel::EachValidator
TYPES = %w(constant)
def validate_each(record, attribute, value)
if TYPES.include?(record.type)
record.errors.add(attribute, 'must be numeric') if !value.is_a?(Numeric)
else
record.errors.add(attribute, "is not supported by type=#{record.type}") if value
end
end
end
attr_accessor :value
validates :value, value: true
class FunctionValidator < ActiveModel::EachValidator
TYPES = %w(javascript)
def validate_each(record, attribute, value)
if TYPES.include?(record.type)
record.errors.add(attribute, 'may not be blank') if value.blank?
else
record.errors.add(attribute, "is not supported by type=#{record.type}") if value
end
end
end
attr_accessor :function
validates :function, function: true
attr_reader :fieldNames
def method_missing(name, *args)
if args.empty?
PostAggregationField.new(fieldName: name)
end
end
def js(*args)
if args.empty?
PostAggregationField.new(fieldName: :js)
else
PostAggregationJavascript.new(args.first)
end
end
end
module PostAggregationOperators
def +(value)
PostAggregationOperation.new(self, :+, value)
end
def -(value)
PostAggregationOperation.new(self, :-, value)
end
def *(value)
PostAggregationOperation.new(self, :*, value)
end
def /(value)
PostAggregationOperation.new(self, :/, value)
end
end
class PostAggregationOperation < PostAggregation
include PostAggregationOperators
def initialize(left, operator, right)
super()
@type = 'arithmetic'
@fn = operator
@fields = [
left.is_a?(Numeric) ? PostAggregationConstant.new(value: left) : left,
right.is_a?(Numeric) ? PostAggregationConstant.new(value: right) : right,
]
end
def as(field)
@name = field.fieldName.to_s
self
end
def field_names
fields.map(&:field_names).flatten.compact.uniq
end
end
class PostAggregationField < PostAggregation
include PostAggregationOperators
def initialize(attributes = {})
super
@type = 'fieldAccess'
end
def field_names
[@fieldName]
end
end
class PostAggregationConstant < PostAggregation
include PostAggregationOperators
def initialize(attributes = {})
super
@type = 'constant'
end
def field_names
[]
end
end
class PostAggregationJavascript < PostAggregation
include PostAggregationOperators
def initialize(function)
super()
@type = 'javascript'
@fieldNames = extract_fields(function)
@function = function
end
def field_names
@fieldNames
end
def as(field)
@name = field.fieldName.to_s
self
end
private
def extract_fields(function)
match = function.match(/function\((.+)\)/)
raise 'Invalid Javascript function' unless match && match.captures
match.captures.first.split(',').map(&:strip)
end
end
class PostAggregationHistogramEqualBuckets < PostAggregation
attr_accessor :numBuckets
def initialize(attributes = {})
super(attributes)
@type = "equalBuckets"
@numBuckets ||= 10
end
end
class PostAggregationHistogramBuckets < PostAggregation
attr_accessor :bucketSize
attr_accessor :offset
def initialize(attributes = {})
super
@type = "buckets"
end
end
class PostAggregationHistogramCustomBuckets < PostAggregation
attr_accessor :breaks
def initialize(attributes = {})
super
@type = "customBuckets"
end
end
class PostAggregationHistogramMin < PostAggregation
def initialize(attributes = {})
super
@type = "min"
end
end
class PostAggregationHistogramMax < PostAggregation
def initialize(attributes = {})
super
@type = "max"
end
end
class PostAggregationHistogramQuantile < PostAggregation
attr_accessor :propability
def initialize(attributes = {})
super
@type = "quantile"
end
end
class PostAggregationHistogramQuantiles < PostAggregation
attr_accessor :probabilities
def initialize(attributes = {})
super
@type = "quantiles"
end
end
end