class ConceptQL::Scope

Scope coordinates the creation of any common table expressions that might be used when a Recall operator is present in the statement.

Any time an operator is given a label, it becomes a candidate for a Recall operator to reuse the output of that operator somewhere else in the statement.

Scope keeps track of all labeled operators and provides an API for Recall operators to fetch the results/domains from labeled operators.

Constants

ADDITIONAL_COLUMNS
COLUMN_TYPES
DEFAULT_COLUMNS

Attributes

known_operators[RW]
person_ids[RW]

Public Class Methods

new(opts = {}) click to toggle source
# File lib/conceptql/scope.rb, line 46
def initialize(opts = {})
  @known_operators = {}
  @recall_dependencies = {}
  @recall_stack = []
  @annotation = {}
  @opts = opts.dup
  @annotation[:errors] = @errors = {}
  @annotation[:warnings] = @warnings = {}
  @annotation[:counts] = @counts = {}
  @annotation[:operators] = @operators = []
  @query_columns = DEFAULT_COLUMNS.keys
end

Public Instance Methods

add_counts(key, domain, counts) click to toggle source
# File lib/conceptql/scope.rb, line 67
def add_counts(key, domain, counts)
  c = @counts[key] ||= {}
  c[domain] = counts
end
add_errors(key, errors) click to toggle source
# File lib/conceptql/scope.rb, line 59
def add_errors(key, errors)
  @errors[key] = errors
end
add_operator(operator) click to toggle source
# File lib/conceptql/scope.rb, line 123
def add_operator(operator)
  known_operators[operator.label] = operator
end
add_operators(operator) click to toggle source
# File lib/conceptql/scope.rb, line 72
def add_operators(operator)
  @operators << operator.operator_name
  @operators.compact!
  @operators.uniq!
end
add_required_columns(op) click to toggle source
# File lib/conceptql/scope.rb, line 78
def add_required_columns(op)
  @query_columns |= op.required_columns if op.required_columns
end
add_warnings(key, errors) click to toggle source
# File lib/conceptql/scope.rb, line 63
def add_warnings(key, errors)
  @warnings[key] = errors
end
ctes() click to toggle source
# File lib/conceptql/scope.rb, line 199
def ctes
  @ctes ||= sort_ctes([], known_operators, recall_dependencies)
end
domains(label, db) click to toggle source
# File lib/conceptql/scope.rb, line 153
def domains(label, db)
  fetch_operator(label).domains(db)
rescue
  [:invalid]
end
duplicate_label?(label) click to toggle source
# File lib/conceptql/scope.rb, line 127
def duplicate_label?(label)
  known_operators.keys.map(&:downcase).include?(label.downcase)
end
fetch_operator(label) click to toggle source
# File lib/conceptql/scope.rb, line 203
def fetch_operator(label)
  known_operators[label]
end
from(db, label) click to toggle source
# File lib/conceptql/scope.rb, line 135
def from(db, label)
  ds = db.from(label)

  if ENV['CONCEPTQL_CHECK_COLUMNS']
    # Work around requests for columns by operators.  These
    # would fail because the CTE would not be defined.  You
    # don't want to define the CTE normally, but to allow the
    # columns to still work, send the columns request to the
    # underlying operator.
    op = fetch_operator(label)
    (class << ds; self; end).send(:define_method, :columns) do
      (@main_op ||= op.evaluate(db)).columns
    end
  end

  ds
end
nest(op) { || ... } click to toggle source
# File lib/conceptql/scope.rb, line 82
def nest(op)
  add_required_columns(op)
  return yield unless label = op.is_a?(Operators::Recall) ? op.source : op.label

  unless label.is_a?(String)
    op.instance_eval do
      @errors = []
      add_error("invalid label")
    end
    return
  end

  recall_dependencies[label] ||= []

  if nested_recall?(label)
    op.instance_eval do
      @errors = []
      add_error("nested recall")
    end
    return
  end

  if duplicate_label?(label) && !op.is_a?(Operators::Recall)
    op.instance_eval do
      @errors = []
      add_error("duplicate label")
    end
  end

  if last = recall_stack.last
    recall_dependencies[last] << label
  end

  begin
    recall_stack.push(label)
    yield
  ensure
    recall_stack.pop if recall_stack.last == label
  end
end
nested_recall?(label) click to toggle source
# File lib/conceptql/scope.rb, line 131
def nested_recall?(label)
  recall_stack.map(&:downcase).include?(label.downcase)
end
sort_ctes(sorted, unsorted, deps) click to toggle source
# File lib/conceptql/scope.rb, line 159
def sort_ctes(sorted, unsorted, deps)
  if unsorted.empty?
    return sorted
  end

  add, unsorted = unsorted.partition do |label, _|
    deps[label].length == 0
  end

  sorted += add

  new_deps = {}
  deps.map do |label, dps|
    new_deps[label] = dps - sorted.map(&:first)
  end

  sort_ctes(sorted, unsorted, new_deps)
end
valid?() click to toggle source
# File lib/conceptql/scope.rb, line 178
def valid?
  recall_dependencies.each_value do |deps|
    unless (deps - known_operators.keys).empty?
      return false
    end
  end
  true
end
with_ctes(query, db) click to toggle source
# File lib/conceptql/scope.rb, line 187
def with_ctes(query, db)
  raise "recall operator use without matching label" unless valid?

  query = query.from_self

  ctes.each do |label, operator|
    query = query.with(label, operator.evaluate(db))
  end

  query
end