lib/conceptql/operators/temporal_operator.rb in conceptql-0.2.0 vs lib/conceptql/operators/temporal_operator.rb in conceptql-0.3.0
- old
+ new
@@ -1,34 +1,100 @@
require_relative 'binary_operator_operator'
+require_relative '../date_adjuster'
module ConceptQL
module Operators
# Base class for all temporal operators
#
# Subclasses must implement the where_clause method which should probably return
- # a proc that can be executed as a Sequel "virtual row" e.g.
- # Proc.new { l.end_date < r.start_date }
+ # a Sequel expression to use for filtering.
class TemporalOperator < BinaryOperatorOperator
reset_categories
- category %w(Temporal Relative)
+ category "Filter by Comparing"
+ default_query_columns
+ option :within, type: :string, instructions: 'Enter a numeric value and specify "d", "m", or "y" for "days", "months", or "years". Negative numbers change dates prior to the existing date. Example: -30d = 30 days before the existing date.'
+ option :at_least, type: :string, instructions: 'Enter a numeric value and specify "d", "m", or "y" for "days", "months", or "years". Negative numbers change dates prior to the existing date. Example: -30d = 30 days before the existing date.'
+ option :occurrences, type: :integer, desc: "Number of occurrences that must precede the event of interest, e.g. if you'd like the 4th event in a set of events, set occurrences to 3"
+
+ validate_option DateAdjuster::VALID_INPUT, :within, :at_least
+ validate_option /\A\d+\z/, :occurrences
+
+ def self.within_skip(type)
+ define_method(:"within_check_#{type}?"){false}
+ end
+
def query(db)
- db.from(db.from(left_stream(db))
- .join(right_stream(db), l__person_id: :r__person_id)
- .where(where_clause)
- .select_all(:l))
+ ds = db.from(left_stream(db))
+ .join(right_stream(db), l__person_id: :r__person_id)
+ .where(where_clause)
+ .select_all(:l)
+
+ ds = add_option_conditions(ds)
+ ds.from_self
end
+ def add_option_conditions(ds)
+ if within = options[:within]
+ ds = add_within_condition(ds, within)
+ end
+
+ if at_least = options[:at_least]
+ ds = add_within_condition(ds, at_least, :exclude)
+ end
+
+ if occurrences = options[:occurrences]
+ ds = add_occurrences_condition(ds, occurrences)
+ end
+
+ ds
+ end
+
+ def add_within_condition(ds, within, meth=:where)
+ within = DateAdjuster.new(within)
+ after = within.adjust(:r__start_date, true)
+ before = within.adjust(:r__end_date)
+ within_col = Sequel.expr(within_column)
+ ds = ds.send(meth){within_col >= after} if within_check_after?
+ ds = ds.send(meth){within_col <= before} if within_check_before?
+ ds.distinct
+ end
+
+ def add_occurrences_condition(ds, occurrences)
+ occurrences_col = occurrences_column
+ ds.distinct.from_self
+ .select_append{row_number{}.over(:partition => :person_id, :order => occurrences_col).as(:occurrence)}
+ .from_self
+ .select(*query_columns(ds))
+ .where{occurrence > occurrences.to_i}
+ end
+
+ def within_column
+ :l__start_date
+ end
+
+ def occurrences_column
+ :start_date
+ end
+
+ def within_check_after?
+ true
+ end
+
+ def within_check_before?
+ true
+ end
+
def inclusive?
options[:inclusive]
end
def left_stream(db)
- Sequel.expr(left.evaluate(db)).as(:l)
+ Sequel.expr(left.evaluate(db).from_self).as(:l)
end
def right_stream(db)
- Sequel.expr(right.evaluate(db)).as(:r)
+ Sequel.expr(right.evaluate(db).from_self).as(:r)
end
end
end
end