Class: ConceptQL::Scope

Inherits:
Object
  • Object
show all
Defined in:
lib/conceptql/scope.rb

Overview

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.

Constant Summary collapse

DEFAULT_COLUMNS =
{
  person_id: :Bigint,
  criterion_id: :Bigint,
  criterion_domain: :String,
  start_date: :Date,
  end_date: :Date,
  source_value: :String
}.freeze
ADDITIONAL_COLUMNS =
{
  value_as_number: :Float,
  value_as_string: :String,
  value_as_concept_id: :Bigint,
  unit_source_value: :String,
  visit_occurrence_id: :Bigint,
  provenance_type: :Bigint,
  provider_id: :Bigint,
  place_of_service_concept_id: :Bigint,
  range_low: :Float,
  range_high: :Float,
  drug_name: :String,
  drug_amount: :Float,
  drug_amount_units: :String,
  days_supply: :Float,
  quantity: :Bigint
}.freeze
COLUMN_TYPES =
(DEFAULT_COLUMNS.merge(ADDITIONAL_COLUMNS)).freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Scope

Returns a new instance of Scope.



46
47
48
49
50
51
52
53
54
55
56
57
# 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

Instance Attribute Details

#annotationObject (readonly)

Returns the value of attribute annotation.



44
45
46
# File 'lib/conceptql/scope.rb', line 44

def annotation
  @annotation
end

#known_operatorsObject (readonly)

Returns the value of attribute known_operators.



44
45
46
# File 'lib/conceptql/scope.rb', line 44

def known_operators
  @known_operators
end

#optsObject (readonly)

Returns the value of attribute opts.



44
45
46
# File 'lib/conceptql/scope.rb', line 44

def opts
  @opts
end

#person_idsObject

Returns the value of attribute person_ids.



42
43
44
# File 'lib/conceptql/scope.rb', line 42

def person_ids
  @person_ids
end

#query_columnsObject (readonly)

Returns the value of attribute query_columns.



44
45
46
# File 'lib/conceptql/scope.rb', line 44

def query_columns
  @query_columns
end

#recall_dependenciesObject (readonly)

Returns the value of attribute recall_dependencies.



44
45
46
# File 'lib/conceptql/scope.rb', line 44

def recall_dependencies
  @recall_dependencies
end

#recall_stackObject (readonly)

Returns the value of attribute recall_stack.



44
45
46
# File 'lib/conceptql/scope.rb', line 44

def recall_stack
  @recall_stack
end

Instance Method Details

#add_counts(key, domain, counts) ⇒ Object



67
68
69
70
# File 'lib/conceptql/scope.rb', line 67

def add_counts(key, domain, counts)
  c = @counts[key] ||= {}
  c[domain] = counts
end

#add_errors(key, errors) ⇒ Object



59
60
61
# File 'lib/conceptql/scope.rb', line 59

def add_errors(key, errors)
  @errors[key] = errors
end

#add_operator(operator) ⇒ Object



123
124
125
# File 'lib/conceptql/scope.rb', line 123

def add_operator(operator)
  known_operators[operator.label] = operator
end

#add_operators(operator) ⇒ Object



72
73
74
75
76
# File 'lib/conceptql/scope.rb', line 72

def add_operators(operator)
  @operators << operator.operator_name
  @operators.compact!
  @operators.uniq!
end

#add_required_columns(op) ⇒ Object



78
79
80
# 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) ⇒ Object



63
64
65
# File 'lib/conceptql/scope.rb', line 63

def add_warnings(key, errors)
  @warnings[key] = errors
end

#ctesObject



199
200
201
# File 'lib/conceptql/scope.rb', line 199

def ctes
  @ctes ||= sort_ctes([], known_operators, recall_dependencies)
end

#domains(label, db) ⇒ Object



153
154
155
156
157
# File 'lib/conceptql/scope.rb', line 153

def domains(label, db)
  fetch_operator(label).domains(db)
rescue
  [:invalid]
end

#duplicate_label?(label) ⇒ Boolean

Returns:

  • (Boolean)


127
128
129
# File 'lib/conceptql/scope.rb', line 127

def duplicate_label?(label)
  known_operators.keys.map(&:downcase).include?(label.downcase)
end

#fetch_operator(label) ⇒ Object



203
204
205
# File 'lib/conceptql/scope.rb', line 203

def fetch_operator(label)
  known_operators[label]
end

#from(db, label) ⇒ Object



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# 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) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# 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) ⇒ Boolean

Returns:

  • (Boolean)


131
132
133
# File 'lib/conceptql/scope.rb', line 131

def nested_recall?(label)
  recall_stack.map(&:downcase).include?(label.downcase)
end

#sort_ctes(sorted, unsorted, deps) ⇒ Object



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# 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?Boolean

Returns:

  • (Boolean)


178
179
180
181
182
183
184
185
# 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) ⇒ Object



187
188
189
190
191
192
193
194
195
196
197
# 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