conceptql 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +226 -117
- data/LICENSE +21 -0
- data/README.md +35 -83
- data/bin/conceptql +2 -1
- data/bin/conceptql_utils +6 -0
- data/config/multiple_vocabularies.csv +4 -0
- data/config/provenance.yml +489 -0
- data/config/race.yml +50 -0
- data/config/vocabularies.csv +87 -0
- data/lib/conceptql/behaviors/code_lister.rb +23 -0
- data/lib/conceptql/behaviors/drugish.rb +17 -0
- data/lib/conceptql/behaviors/labish.rb +19 -0
- data/lib/conceptql/behaviors/metadatable.rb +226 -73
- data/lib/conceptql/behaviors/provenanceable.rb +26 -0
- data/lib/conceptql/behaviors/single_event.rb +11 -0
- data/lib/conceptql/behaviors/timeless.rb +11 -0
- data/lib/conceptql/behaviors/utilizable.rb +206 -0
- data/lib/conceptql/behaviors/windowable.rb +27 -0
- data/lib/conceptql/cli.rb +117 -108
- data/lib/conceptql/columnizer.rb +75 -0
- data/lib/conceptql/data_model/base.rb +462 -0
- data/lib/conceptql/data_model/gdm.rb +155 -0
- data/lib/conceptql/data_model/gdm_wide.rb +42 -0
- data/lib/conceptql/data_model/omopv4_plus.rb +12 -0
- data/lib/conceptql/data_model.rb +30 -0
- data/lib/conceptql/database.rb +214 -0
- data/lib/conceptql/date_adjuster.rb +66 -18
- data/lib/conceptql/diagram.rb +243 -0
- data/lib/conceptql/lexicon/lexicon_gdm.rb +47 -0
- data/lib/conceptql/lexicon/lexicon_no_db.rb +49 -0
- data/lib/conceptql/lexicon/lexicon_ohdsi.rb +63 -0
- data/lib/conceptql/lexicon/lexicon_strategy.rb +116 -0
- data/lib/conceptql/lexicon.rb +111 -0
- data/lib/conceptql/nodifier.rb +65 -24
- data/lib/conceptql/operators/after.rb +34 -14
- data/lib/conceptql/operators/any_overlap.rb +8 -6
- data/lib/conceptql/operators/before.rb +34 -10
- data/lib/conceptql/operators/binary_operator_operator.rb +66 -23
- data/lib/conceptql/operators/casting_operator.rb +51 -36
- data/lib/conceptql/operators/co_reported.rb +111 -0
- data/lib/conceptql/operators/concurrent_within.rb +69 -0
- data/lib/conceptql/operators/contains.rb +12 -8
- data/lib/conceptql/operators/count.rb +17 -9
- data/lib/conceptql/operators/date_range.rb +48 -14
- data/lib/conceptql/operators/death.rb +14 -4
- data/lib/conceptql/operators/dedup.rb +31 -0
- data/lib/conceptql/operators/during.rb +7 -12
- data/lib/conceptql/operators/episode.rb +146 -0
- data/lib/conceptql/operators/equal.rb +9 -3
- data/lib/conceptql/operators/ethnicity.rb +49 -0
- data/lib/conceptql/operators/except.rb +19 -12
- data/lib/conceptql/operators/filter.rb +23 -8
- data/lib/conceptql/operators/first.rb +16 -3
- data/lib/conceptql/operators/from.rb +52 -4
- data/lib/conceptql/operators/gender.rb +34 -11
- data/lib/conceptql/operators/hospice.rb +19 -0
- data/lib/conceptql/operators/hospitalization.rb +19 -0
- data/lib/conceptql/operators/information_periods.rb +36 -0
- data/lib/conceptql/operators/invalid.rb +35 -0
- data/lib/conceptql/operators/last.rb +16 -3
- data/lib/conceptql/operators/match.rb +91 -0
- data/lib/conceptql/operators/multiple_vocabularies.rb +172 -0
- data/lib/conceptql/operators/numeric.rb +65 -23
- data/lib/conceptql/operators/numeric_filter.rb +68 -0
- data/lib/conceptql/operators/occurrence.rb +153 -32
- data/lib/conceptql/operators/one_in_two_out.rb +237 -38
- data/lib/conceptql/operators/operator.rb +624 -178
- data/lib/conceptql/operators/pass_thru.rb +17 -3
- data/lib/conceptql/operators/person.rb +18 -6
- data/lib/conceptql/operators/person_filter.rb +9 -3
- data/lib/conceptql/operators/place_of_service_filter.rb +43 -0
- data/lib/conceptql/operators/provenance.rb +55 -0
- data/lib/conceptql/operators/provider_filter.rb +62 -0
- data/lib/conceptql/operators/race.rb +55 -7
- data/lib/conceptql/operators/read.rb +168 -0
- data/lib/conceptql/operators/recall.rb +49 -14
- data/lib/conceptql/operators/snf.rb +19 -0
- data/lib/conceptql/operators/sum.rb +29 -12
- data/lib/conceptql/operators/temporal_operator.rb +108 -16
- data/lib/conceptql/operators/time_window.rb +20 -32
- data/lib/conceptql/operators/trim_date.rb +71 -0
- data/lib/conceptql/operators/trim_date_end.rb +28 -40
- data/lib/conceptql/operators/trim_date_start.rb +28 -41
- data/lib/conceptql/operators/union.rb +67 -6
- data/lib/conceptql/operators/vocabulary.rb +151 -0
- data/lib/conceptql/paths.rb +41 -0
- data/lib/conceptql/query.rb +191 -26
- data/lib/conceptql/query_modifiers/gdm/admission_date_query_modifier.rb +46 -0
- data/lib/conceptql/query_modifiers/gdm/drug_query_modifier.rb +54 -0
- data/lib/conceptql/query_modifiers/gdm/lab_query_modifier.rb +57 -0
- data/lib/conceptql/query_modifiers/gdm/pos_query_modifier.rb +46 -0
- data/lib/conceptql/query_modifiers/gdm/provenance_query_modifier.rb +45 -0
- data/lib/conceptql/query_modifiers/gdm/provider_query_modifier.rb +53 -0
- data/lib/conceptql/query_modifiers/gdm_wide/admission_date_query_modifier.rb +42 -0
- data/lib/conceptql/query_modifiers/gdm_wide/drug_query_modifier.rb +52 -0
- data/lib/conceptql/query_modifiers/gdm_wide/lab_query_modifier.rb +55 -0
- data/lib/conceptql/query_modifiers/gdm_wide/pos_query_modifier.rb +37 -0
- data/lib/conceptql/query_modifiers/gdm_wide/provenance_query_modifier.rb +38 -0
- data/lib/conceptql/query_modifiers/omopv4_plus/admission_date_query_modifier.rb +45 -0
- data/lib/conceptql/query_modifiers/omopv4_plus/drug_query_modifier.rb +80 -0
- data/lib/conceptql/query_modifiers/omopv4_plus/pos_query_modifier.rb +47 -0
- data/lib/conceptql/query_modifiers/omopv4_plus/provenance_query_modifier.rb +45 -0
- data/lib/conceptql/query_modifiers/omopv4_plus/provider_query_modifier.rb +26 -0
- data/lib/conceptql/query_modifiers/query_modifier.rb +20 -0
- data/lib/conceptql/rdbms/duckdb.rb +25 -0
- data/lib/conceptql/rdbms/generic.rb +120 -0
- data/lib/conceptql/rdbms/postgres.rb +51 -0
- data/lib/conceptql/rdbms/spark.rb +22 -0
- data/lib/conceptql/rdbms.rb +22 -0
- data/lib/conceptql/scope.rb +481 -31
- data/lib/conceptql/sql_formatters/formatter.rb +25 -0
- data/lib/conceptql/sql_formatters.rb +22 -0
- data/lib/conceptql/utils.rb +113 -0
- data/lib/conceptql/utils_cli.rb +17 -0
- data/lib/conceptql/version.rb +3 -1
- data/lib/conceptql/visitors/metadata_extractor.rb +54 -0
- data/lib/conceptql/vocabularies/behaviors/costish.rb +24 -0
- data/lib/conceptql/vocabularies/behaviors/gdmish.rb +19 -0
- data/lib/conceptql/vocabularies/behaviors/omopish.rb +32 -0
- data/lib/conceptql/vocabularies/dynamic_vocabularies.rb +63 -0
- data/lib/conceptql/vocabularies/entry.rb +138 -0
- data/lib/conceptql/vocabularies/vocabulary_class_factory.rb +85 -0
- data/lib/conceptql/window/base.rb +26 -0
- data/lib/conceptql/window/date_range.rb +25 -0
- data/lib/conceptql/window/table.rb +116 -0
- data/lib/conceptql/window.rb +72 -0
- data/lib/conceptql.rb +65 -2
- data/lib/sequel/extensions/pg_ctas_explain.rb +22 -0
- data/lib/sequel/extensions/pg_vacuum_table.rb +23 -0
- data/schemas/gdm.yml +882 -0
- data/schemas/gdm_wide.yml +757 -0
- data/schemas/ohdsi_vocabs.yml +143 -0
- data/schemas/omopv4_plus.yml +729 -0
- data/schemas/omopv4_plus_more_ignorables.tsv +13 -0
- metadata +267 -207
- data/.gitignore +0 -23
- data/Gemfile +0 -4
- data/Guardfile +0 -56
- data/LICENSE.txt +0 -22
- data/Rakefile +0 -1
- data/conceptql.gemspec +0 -32
- data/doc/ConceptQL Specification (alpha).pdf +0 -0
- data/doc/diagram_0.png +0 -0
- data/doc/implementation_notes.md +0 -39
- data/doc/spec.md +0 -1344
- data/lib/conceptql/behaviors/debuggable.rb +0 -70
- data/lib/conceptql/behaviors/dottable.rb +0 -103
- data/lib/conceptql/behaviors/preppable.rb +0 -20
- data/lib/conceptql/converter.rb +0 -65
- data/lib/conceptql/debugger.rb +0 -48
- data/lib/conceptql/fake_grapher.rb +0 -20
- data/lib/conceptql/graph.rb +0 -52
- data/lib/conceptql/graph_nodifier.rb +0 -203
- data/lib/conceptql/logger.rb +0 -10
- data/lib/conceptql/operators/complement.rb +0 -38
- data/lib/conceptql/operators/concept.rb +0 -70
- data/lib/conceptql/operators/condition_type.rb +0 -71
- data/lib/conceptql/operators/cpt.rb +0 -25
- data/lib/conceptql/operators/drug_type_concept.rb +0 -21
- data/lib/conceptql/operators/from_seer_visits.rb +0 -23
- data/lib/conceptql/operators/hcpcs.rb +0 -25
- data/lib/conceptql/operators/icd10.rb +0 -28
- data/lib/conceptql/operators/icd9.rb +0 -28
- data/lib/conceptql/operators/icd9_procedure.rb +0 -25
- data/lib/conceptql/operators/intersect.rb +0 -34
- data/lib/conceptql/operators/loinc.rb +0 -25
- data/lib/conceptql/operators/medcode.rb +0 -28
- data/lib/conceptql/operators/medcode_procedure.rb +0 -27
- data/lib/conceptql/operators/ndc.rb +0 -29
- data/lib/conceptql/operators/observation_by_enttype.rb +0 -29
- data/lib/conceptql/operators/observation_period.rb +0 -30
- data/lib/conceptql/operators/overlapped_by.rb +0 -23
- data/lib/conceptql/operators/overlaps.rb +0 -19
- data/lib/conceptql/operators/place_of_service_code.rb +0 -24
- data/lib/conceptql/operators/procedure_occurrence.rb +0 -25
- data/lib/conceptql/operators/prodcode.rb +0 -29
- data/lib/conceptql/operators/rxnorm.rb +0 -24
- data/lib/conceptql/operators/snomed.rb +0 -24
- data/lib/conceptql/operators/snomed_condition.rb +0 -26
- data/lib/conceptql/operators/source_vocabulary_operator.rb +0 -58
- data/lib/conceptql/operators/standard_vocabulary_operator.rb +0 -45
- data/lib/conceptql/operators/started_by.rb +0 -22
- data/lib/conceptql/operators/to_seer_visits.rb +0 -24
- data/lib/conceptql/operators/visit.rb +0 -15
- data/lib/conceptql/operators/visit_occurrence.rb +0 -30
- data/lib/conceptql/tree.rb +0 -53
- data/lib/conceptql/utils/temp_table.rb +0 -72
- data/spec/conceptql/behaviors/dottable_spec.rb +0 -99
- data/spec/conceptql/converter_spec.rb +0 -51
- data/spec/conceptql/date_adjuster_spec.rb +0 -68
- data/spec/conceptql/operators/after_spec.rb +0 -16
- data/spec/conceptql/operators/before_spec.rb +0 -16
- data/spec/conceptql/operators/casting_operator_spec.rb +0 -68
- data/spec/conceptql/operators/complement_spec.rb +0 -15
- data/spec/conceptql/operators/concept_spec.rb +0 -40
- data/spec/conceptql/operators/condition_type_spec.rb +0 -128
- data/spec/conceptql/operators/contains_spec.rb +0 -19
- data/spec/conceptql/operators/cpt_spec.rb +0 -29
- data/spec/conceptql/operators/date_range_spec.rb +0 -33
- data/spec/conceptql/operators/death_spec.rb +0 -10
- data/spec/conceptql/operators/during_spec.rb +0 -30
- data/spec/conceptql/operators/except_spec.rb +0 -15
- data/spec/conceptql/operators/first_spec.rb +0 -35
- data/spec/conceptql/operators/from_spec.rb +0 -13
- data/spec/conceptql/operators/gender_spec.rb +0 -27
- data/spec/conceptql/operators/hcpcs_spec.rb +0 -29
- data/spec/conceptql/operators/icd10_spec.rb +0 -34
- data/spec/conceptql/operators/icd9_procedure_spec.rb +0 -29
- data/spec/conceptql/operators/icd9_spec.rb +0 -34
- data/spec/conceptql/operators/intersect_spec.rb +0 -28
- data/spec/conceptql/operators/last_spec.rb +0 -36
- data/spec/conceptql/operators/loinc_spec.rb +0 -29
- data/spec/conceptql/operators/medcode_procedure_spec.rb +0 -34
- data/spec/conceptql/operators/medcode_spec.rb +0 -34
- data/spec/conceptql/operators/observation_period_spec.rb +0 -10
- data/spec/conceptql/operators/occurrence_spec.rb +0 -87
- data/spec/conceptql/operators/overlapped_by_spec.rb +0 -32
- data/spec/conceptql/operators/overlaps_spec.rb +0 -21
- data/spec/conceptql/operators/person_filter_spec.rb +0 -15
- data/spec/conceptql/operators/person_spec.rb +0 -10
- data/spec/conceptql/operators/place_of_service_code_spec.rb +0 -24
- data/spec/conceptql/operators/procedure_occurrence_spec.rb +0 -10
- data/spec/conceptql/operators/prodcode_spec.rb +0 -35
- data/spec/conceptql/operators/query_double.rb +0 -20
- data/spec/conceptql/operators/query_double_spec.rb +0 -7
- data/spec/conceptql/operators/race_spec.rb +0 -21
- data/spec/conceptql/operators/rxnorm_spec.rb +0 -29
- data/spec/conceptql/operators/snomed_spec.rb +0 -29
- data/spec/conceptql/operators/source_vocabulary_operator_spec.rb +0 -35
- data/spec/conceptql/operators/standard_vocabulary_operator_spec.rb +0 -35
- data/spec/conceptql/operators/started_by_spec.rb +0 -22
- data/spec/conceptql/operators/temporal_operator_spec.rb +0 -51
- data/spec/conceptql/operators/time_window_spec.rb +0 -77
- data/spec/conceptql/operators/union_spec.rb +0 -21
- data/spec/conceptql/operators/visit_occurrence_spec.rb +0 -10
- data/spec/conceptql/query_spec.rb +0 -22
- data/spec/conceptql/tree_spec.rb +0 -50
- data/spec/doubles/stream_for_casting_double.rb +0 -9
- data/spec/doubles/stream_for_occurrence_double.rb +0 -25
- data/spec/doubles/stream_for_temporal_double.rb +0 -6
- data/spec/spec_helper.rb +0 -102
|
@@ -1,235 +1,681 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require
|
|
4
|
-
|
|
5
|
-
require
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "zlib"
|
|
4
|
+
require_relative "../behaviors/metadatable"
|
|
5
|
+
require "forwardable"
|
|
6
6
|
|
|
7
7
|
module ConceptQL
|
|
8
8
|
module Operators
|
|
9
|
+
# Built-in operators only: the classes that call Operator.register when
|
|
10
|
+
# their file loads. Vocabulary operators are not in here; each
|
|
11
|
+
# ConceptQL::Database builds its own from its lexicon (Database#operators).
|
|
12
|
+
STATIC_OPERATORS = { omopv4_plus: {}, gdm: {}, gdm_wide: {} }.freeze
|
|
13
|
+
|
|
14
|
+
SELECTED_COLUMNS = %i[person_id
|
|
15
|
+
criterion_id
|
|
16
|
+
criterion_table
|
|
17
|
+
criterion_domain
|
|
18
|
+
start_date
|
|
19
|
+
end_date
|
|
20
|
+
value_as_number
|
|
21
|
+
value_as_string
|
|
22
|
+
value_as_concept_id
|
|
23
|
+
units_source_value
|
|
24
|
+
source_value].freeze
|
|
25
|
+
|
|
26
|
+
def self.static_operators
|
|
27
|
+
STATIC_OPERATORS
|
|
28
|
+
end
|
|
29
|
+
|
|
9
30
|
class Operator
|
|
10
31
|
extend Forwardable
|
|
11
|
-
extend Metadatable
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
:criterion_id,
|
|
15
|
-
:criterion_type,
|
|
16
|
-
:start_date,
|
|
17
|
-
:end_date,
|
|
18
|
-
:value_as_number,
|
|
19
|
-
:value_as_string,
|
|
20
|
-
:value_as_concept_id,
|
|
21
|
-
:units_source_value,
|
|
22
|
-
:source_value
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
attr :values, :options, :arguments, :upstreams
|
|
32
|
+
extend ConceptQL::Metadatable
|
|
33
|
+
|
|
34
|
+
attr_reader :nodifier, :values, :options, :arguments, :upstreams, :op_name, :errors, :warnings
|
|
26
35
|
|
|
27
36
|
option :label, type: :string
|
|
28
37
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
@validations = []
|
|
39
|
+
|
|
40
|
+
class << self
|
|
41
|
+
attr_reader :validations, :required_columns
|
|
42
|
+
|
|
43
|
+
def register(file, *data_models)
|
|
44
|
+
data_models = STATIC_OPERATORS.keys if data_models.empty?
|
|
45
|
+
data_models.each do |dm|
|
|
46
|
+
op_name = File.basename(file).sub(/\.rb\z/, "").downcase
|
|
47
|
+
Operators.static_operators[dm][op_name] = self
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def query_columns(*tables)
|
|
52
|
+
define_method(:query_cols) do
|
|
53
|
+
dm.table_columns(*tables)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def default_query_columns
|
|
58
|
+
define_method(:query_cols) do
|
|
59
|
+
dynamic_columns
|
|
60
|
+
end
|
|
61
|
+
end
|
|
32
62
|
|
|
33
|
-
|
|
34
|
-
|
|
63
|
+
def require_column(column)
|
|
64
|
+
@required_columns ||= []
|
|
65
|
+
@required_columns << column
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
validation_meths = <<-END.split.map(&:to_sym)
|
|
69
|
+
no_upstreams
|
|
70
|
+
one_upstream
|
|
71
|
+
at_least_one_upstream
|
|
72
|
+
at_most_one_upstream
|
|
73
|
+
no_arguments
|
|
74
|
+
one_argument
|
|
75
|
+
at_least_one_argument
|
|
76
|
+
at_most_one_argument
|
|
77
|
+
option
|
|
78
|
+
required_options
|
|
79
|
+
codes_match
|
|
80
|
+
END
|
|
81
|
+
|
|
82
|
+
validation_meths.each do |type|
|
|
83
|
+
meth = :"validate_#{type}"
|
|
84
|
+
define_method(meth) do |*args|
|
|
85
|
+
validations << [ meth, *args ]
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Declares inherited options that this operator does not support.
|
|
90
|
+
# They leave the operator's metadata, and a statement that sets one
|
|
91
|
+
# fails validation.
|
|
92
|
+
def unsupported_options(*names)
|
|
93
|
+
names.each { |name| remove_option(name) }
|
|
94
|
+
validations << [ :validate_unsupported_options, *names ]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def inherited(subclass)
|
|
98
|
+
super
|
|
99
|
+
subclass.instance_variable_set(:@validations, validations.dup)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def new(*)
|
|
103
|
+
operator = super
|
|
104
|
+
|
|
105
|
+
# If operator has a label, replace it with a recall so all references
|
|
106
|
+
# to it use the same code.
|
|
107
|
+
if operator.label && !operator.errors
|
|
108
|
+
operator.scope.add_operator(operator)
|
|
109
|
+
operator = Operators::Recall.new(operator.nodifier, "recall", operator.label, replaced: true)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
operator
|
|
113
|
+
end
|
|
35
114
|
end
|
|
36
115
|
|
|
37
|
-
def
|
|
38
|
-
@
|
|
39
|
-
|
|
116
|
+
def initialize(nodifier, op_name, *args)
|
|
117
|
+
@nodifier = nodifier
|
|
118
|
+
|
|
119
|
+
# Under what name was this operator instantiated?
|
|
120
|
+
# For operators like "vocabulary", this tells the instance which
|
|
121
|
+
# vocabulary operator to impersonate
|
|
122
|
+
@op_name = op_name
|
|
123
|
+
@options = {}
|
|
124
|
+
@options = @options.merge(ConceptQL::Utils.rekey(args.pop)) while args.last.is_a?(Hash)
|
|
125
|
+
args.reject! { |arg| arg.nil? || arg == "" }
|
|
126
|
+
@upstreams, @arguments = args.partition { |arg| arg.is_a?(Array) || arg.is_a?(Operator) }
|
|
40
127
|
@values = args
|
|
128
|
+
|
|
129
|
+
scope.nest(self) do
|
|
130
|
+
create_upstreams
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def create_upstreams
|
|
135
|
+
@upstreams.map! { |stmt| to_op(stmt) }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def to_op(stmt)
|
|
139
|
+
stmt.is_a?(Operator) ? stmt : nodifier.create(*stmt)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def operator_name
|
|
143
|
+
self.class.just_class_name.underscore
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def required_columns
|
|
147
|
+
cols = self.class.required_columns || []
|
|
148
|
+
cols |= [ :uuid ] if options[:uuid]
|
|
149
|
+
cols
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def dynamic_columns
|
|
153
|
+
scope.query_columns
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def cte_name(name)
|
|
157
|
+
scope.cte_name(name)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def annotate(db, opts = {})
|
|
161
|
+
return @annotate if defined?(@annotate)
|
|
162
|
+
|
|
163
|
+
scope_key = options[:id] || op_name
|
|
164
|
+
annotation = {}
|
|
165
|
+
counts = (annotation[:counts] ||= {})
|
|
166
|
+
metadata = { annotation: annotation }
|
|
167
|
+
|
|
168
|
+
if (name = preferred_name)
|
|
169
|
+
metadata[:name] = name
|
|
170
|
+
end
|
|
171
|
+
res = [ op_name, *annotate_values(db, opts) ]
|
|
172
|
+
|
|
173
|
+
if upstreams_valid?(db, opts) && scope.valid? && include_counts?(db, opts)
|
|
174
|
+
scope.with_ctes(self, db)
|
|
175
|
+
.from_self
|
|
176
|
+
.select_group(:criterion_domain)
|
|
177
|
+
.select_append { Sequel.function(:count, 1).as(:rows) }
|
|
178
|
+
.select_append { count(:person_id).distinct.as(:n) }
|
|
179
|
+
.each do |h|
|
|
180
|
+
counts[h.delete(:criterion_domain).to_sym] = h
|
|
181
|
+
end
|
|
182
|
+
elsif !errors.empty?
|
|
183
|
+
annotation[:errors] = errors
|
|
184
|
+
scope.add_errors(scope_key, errors)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
domains(db).each do |domain|
|
|
188
|
+
cur_counts = counts[domain] ||= { rows: 0, n: 0 }
|
|
189
|
+
scope.add_counts(scope_key, domain, cur_counts)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
if defined?(@warnings) && !warnings.empty?
|
|
193
|
+
annotation[:warnings] = warnings
|
|
194
|
+
scope.add_warnings(scope_key, warnings.dup)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
if res.last.is_a?(Hash)
|
|
198
|
+
res.last.merge!(metadata)
|
|
199
|
+
else
|
|
200
|
+
res << metadata
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
@annotate = res
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def code_list(db)
|
|
207
|
+
upstreams.flat_map { |upstream_op| upstream_op.code_list(db) }
|
|
41
208
|
end
|
|
42
209
|
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
scope.add_operator(self)
|
|
210
|
+
def inspect
|
|
211
|
+
"<##{self.class} upstreams=[#{upstreams.map(&:inspect).join(', ')}] arguments=[#{arguments.map(&:inspect).join(', ')}]>"
|
|
46
212
|
end
|
|
47
213
|
|
|
48
214
|
def evaluate(db)
|
|
49
|
-
select_it(query(db))
|
|
215
|
+
q = select_it(query(db))
|
|
216
|
+
name = cte_name(op_name)
|
|
217
|
+
opts = {}
|
|
218
|
+
opts[:materialized] = true if rdbms.supports_materialized?
|
|
219
|
+
db[name].with(name, q, opts)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def pretty_print(pp)
|
|
223
|
+
pp.object_group self do
|
|
224
|
+
unless pp_upstreams.empty?
|
|
225
|
+
pp.breakable
|
|
226
|
+
pp.text "@upstreams="
|
|
227
|
+
pp.pp pp_upstreams
|
|
228
|
+
pp.comma_breakable unless arguments.empty?
|
|
229
|
+
end
|
|
230
|
+
unless arguments.empty?
|
|
231
|
+
pp.breakable if pp_upstreams.empty?
|
|
232
|
+
pp.text "@arguments="
|
|
233
|
+
pp.pp arguments
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def pp_upstreams
|
|
239
|
+
upstreams
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def all_upstreams
|
|
243
|
+
upstreams
|
|
50
244
|
end
|
|
51
245
|
|
|
52
246
|
def sql(db)
|
|
53
247
|
evaluate(db).sql
|
|
54
248
|
end
|
|
55
249
|
|
|
56
|
-
def select_it(query,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
250
|
+
def select_it(query, opts = {})
|
|
251
|
+
specific_table = opts[:specific_table]
|
|
252
|
+
specific_table ||= dm.determine_table(:source_table)
|
|
253
|
+
specific_table ||= dm.determine_table(:table)
|
|
254
|
+
specific_table ||= dm.determine_table(:domain)
|
|
255
|
+
|
|
256
|
+
dom = begin
|
|
257
|
+
opts[:domain] || domain
|
|
258
|
+
rescue StandardError
|
|
259
|
+
nil
|
|
61
260
|
end
|
|
62
|
-
|
|
261
|
+
|
|
262
|
+
opts = {
|
|
263
|
+
table: specific_table,
|
|
264
|
+
criterion_domain: dom,
|
|
265
|
+
query_columns: override_columns,
|
|
266
|
+
uuid: opts[:uuid] || options[:uuid]
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
cf = column_family || (specific_table ? ConceptQL::Scope::DEFAULT_COLUMN_FAMILY : nil)
|
|
270
|
+
opts[:replace] ||= {}
|
|
271
|
+
opts[:replace][:label] = label if label
|
|
272
|
+
opts[:replace][:column_family] = cf if cf
|
|
273
|
+
|
|
274
|
+
query = query.comment(comment) if comments?
|
|
275
|
+
|
|
276
|
+
dm.selectify(query, opts)
|
|
63
277
|
end
|
|
64
278
|
|
|
65
|
-
def
|
|
66
|
-
|
|
279
|
+
def comments?
|
|
280
|
+
ENV["CONCEPTQL_ENABLE_COMMENTS"] == "true"
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def comment
|
|
284
|
+
PP.pp(self, "".dup, 10)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def override_columns
|
|
288
|
+
nil
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def domains(db)
|
|
292
|
+
@domains ||= determine_domains(db)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def tables
|
|
296
|
+
@tables ||= determine_tables
|
|
67
297
|
end
|
|
68
298
|
|
|
69
299
|
def stream
|
|
70
300
|
@stream ||= upstreams.first
|
|
71
301
|
end
|
|
72
302
|
|
|
73
|
-
def columns(
|
|
74
|
-
|
|
75
|
-
if local_type
|
|
76
|
-
criterion_type = Sequel.cast_string(local_type.to_s).as(:criterion_type)
|
|
77
|
-
end
|
|
78
|
-
columns = [:person_id,
|
|
79
|
-
type_id(local_type),
|
|
80
|
-
criterion_type]
|
|
81
|
-
columns += date_columns(query, local_type)
|
|
82
|
-
columns += value_columns(query, local_type)
|
|
303
|
+
def columns(table = nil)
|
|
304
|
+
dm.columns(table: table)
|
|
83
305
|
end
|
|
84
306
|
|
|
85
307
|
def label
|
|
86
|
-
|
|
308
|
+
@label ||= begin
|
|
309
|
+
options.delete(:label) if options[:label] && options[:label].to_s.strip.empty?
|
|
310
|
+
options[:label].respond_to?(:strip) ? options[:label].strip : options[:label]
|
|
311
|
+
end
|
|
87
312
|
end
|
|
88
313
|
|
|
89
|
-
|
|
90
|
-
|
|
314
|
+
def column_family; end
|
|
315
|
+
|
|
316
|
+
def valid?(db, opts = {})
|
|
317
|
+
validate(db, opts)
|
|
318
|
+
errors.empty?
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def upstreams_valid?(db, opts = {})
|
|
322
|
+
valid?(db, opts) && upstreams.all? { |u| u.upstreams_valid?(db, opts) }
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def scope
|
|
326
|
+
nodifier.scope
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def data_model
|
|
330
|
+
nodifier.data_model
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def database_type
|
|
334
|
+
nodifier.database_type
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def cast_column(column, value = nil)
|
|
338
|
+
type = Scope::COLUMN_TYPES.fetch(column)
|
|
339
|
+
case type
|
|
340
|
+
when String, :String
|
|
341
|
+
Sequel.cast_string(value).as(column)
|
|
342
|
+
when Date, :Date
|
|
343
|
+
Sequel.cast(value, type).as(column)
|
|
344
|
+
when Float, :Bigint, :Float
|
|
345
|
+
Sequel.cast_numeric(value, type).as(column)
|
|
346
|
+
else
|
|
347
|
+
raise "Unexpected type: '#{type.inspect}' for column: '#{column}'"
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def omopv4_plus?
|
|
352
|
+
data_model == :omopv4_plus
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def gdm?
|
|
356
|
+
%i[gdm gdm_wide].include?(data_model)
|
|
357
|
+
end
|
|
91
358
|
|
|
92
|
-
def
|
|
93
|
-
|
|
359
|
+
def wide?
|
|
360
|
+
%i[gdm_wide].include?(data_model)
|
|
94
361
|
end
|
|
95
362
|
|
|
96
|
-
def
|
|
97
|
-
|
|
98
|
-
type = :person if type == :death
|
|
99
|
-
Sequel.expr(make_type_id(type)).as(:criterion_id)
|
|
363
|
+
def dm
|
|
364
|
+
@dm ||= DataModel.for(self, nodifier)
|
|
100
365
|
end
|
|
101
366
|
|
|
102
|
-
def
|
|
103
|
-
|
|
367
|
+
def rdbms
|
|
368
|
+
dm.rdbms
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def query_cols
|
|
372
|
+
raise NotImplementedError, self
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def events_per_patient
|
|
376
|
+
if upstreams.first
|
|
377
|
+
upstreams.first.events_per_patient
|
|
378
|
+
else
|
|
379
|
+
:multiple
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def vocabularies
|
|
384
|
+
if respond_to?(:vocab_entry)
|
|
385
|
+
[ vocab_entry.omopv5_vocabulary_id ]
|
|
386
|
+
elsif upstreams.any?
|
|
387
|
+
upstreams.flat_map(&:vocabularies).uniq
|
|
388
|
+
else
|
|
389
|
+
[]
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def multiple_vocabularies
|
|
394
|
+
if upstreams.any?
|
|
395
|
+
upstreams.first.multiple_vocabularies
|
|
396
|
+
else
|
|
397
|
+
false
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
def accept(visitor, opts = {})
|
|
402
|
+
visitor.visit(self)
|
|
403
|
+
upstreams.each { |u| u.accept(visitor, opts) }
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
private
|
|
407
|
+
|
|
408
|
+
def annotate_values(db, opts)
|
|
409
|
+
(upstreams.map { |op| op.annotate(db, opts) } + arguments).push(options)
|
|
104
410
|
end
|
|
105
411
|
|
|
106
412
|
def make_table_name(table)
|
|
107
|
-
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def source_value(query,
|
|
141
|
-
return :source_value if query.
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
return
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
payer_plan_period: :payer_plan_period_end_date,
|
|
178
|
-
person: person_date_of_birth(query),
|
|
179
|
-
procedure_occurrence: :procedure_date,
|
|
180
|
-
procedure_cost: nil,
|
|
181
|
-
observation: :observation_date,
|
|
182
|
-
observation_period: :observation_period_end_date,
|
|
183
|
-
visit_occurrence: :visit_end_date
|
|
184
|
-
}[type]
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
def source_value_column(query, type)
|
|
188
|
-
{
|
|
189
|
-
condition_occurrence: :condition_source_value,
|
|
190
|
-
death: :cause_of_death_source_value,
|
|
191
|
-
drug_exposure: :drug_source_value,
|
|
192
|
-
drug_cost: nil,
|
|
193
|
-
payer_plan_period: :payer_plan_period_source_value,
|
|
194
|
-
person: :person_source_value,
|
|
195
|
-
procedure_occurrence: :procedure_source_value,
|
|
196
|
-
procedure_cost: nil,
|
|
197
|
-
observation: :observation_source_value,
|
|
198
|
-
observation_period: nil,
|
|
199
|
-
visit_occurrence: :place_of_service_source_value
|
|
200
|
-
}[type]
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
def person_date_of_birth(query)
|
|
204
|
-
assemble_date(query, :year_of_birth, :month_of_birth, :day_of_birth)
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
def assemble_date(query, *symbols)
|
|
208
|
-
strings = symbols.map do |symbol|
|
|
209
|
-
Sequel.cast_string(Sequel.function(:coalesce, Sequel.cast_string(symbol), Sequel.cast_string('01')))
|
|
210
|
-
end
|
|
211
|
-
strings = strings.zip(['-'] * (symbols.length - 1)).flatten.compact
|
|
212
|
-
concatted_strings = Sequel.join(strings)
|
|
213
|
-
case query.db.database_type
|
|
214
|
-
when :oracle
|
|
215
|
-
Sequel.function(:to_date, concatted_strings, 'YYYY-MM-DD')
|
|
216
|
-
when :mssql
|
|
217
|
-
Sequel.lit('CONVERT(DATETIME, ?)', concatted_strings)
|
|
413
|
+
Sequel.as(table, :tab)
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def query_columns(query)
|
|
417
|
+
dm.query_columns(query)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def additional_columns(query, table)
|
|
421
|
+
special_columns = {
|
|
422
|
+
code_provenance_type: proc { code_provenance_type(query, table) },
|
|
423
|
+
file_provenance_type: proc { file_provenance_type(query, table) },
|
|
424
|
+
provider_id: proc { provider_id(query, table) },
|
|
425
|
+
visit_source_concept_id: proc { dm.place_of_service_concept_id(query, table) }
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
additional_cols = special_columns.each_with_object([]) do |(column, proc_obj), columns|
|
|
429
|
+
columns << proc_obj.call if dynamic_columns.include?(column)
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
standard_columns = dynamic_columns - Scope::DEFAULT_COLUMNS.keys
|
|
433
|
+
standard_columns -= special_columns.keys
|
|
434
|
+
|
|
435
|
+
standard_columns.each do |column|
|
|
436
|
+
additional_cols << if query_columns(query).include?(column)
|
|
437
|
+
column
|
|
438
|
+
else
|
|
439
|
+
cast_column(column)
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
additional_cols
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def source_value(query, table)
|
|
447
|
+
return :source_value if query_columns(query).include?(:source_value)
|
|
448
|
+
|
|
449
|
+
cast_column(:source_value, dm.source_value_column(query, table))
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def source_vocabulary_id(query, table)
|
|
453
|
+
return :source_vocabulary_id if query_columns(query).include?(:source_vocabulary_id)
|
|
454
|
+
|
|
455
|
+
cast_column(:source_vocabulary_id, dm.source_vocabulary_id(query, table))
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
def code_provenance_type(query, table)
|
|
459
|
+
return :code_provenance_type if query_columns(query).include?(:code_provenance_type)
|
|
460
|
+
|
|
461
|
+
cast_column(:code_provenance_type, dm.provenance_type_column(query, table))
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def file_provenance_type(query, table)
|
|
465
|
+
return :file_provenance_type if query_columns(query).include?(:file_provenance_type)
|
|
466
|
+
|
|
467
|
+
cast_column(:file_provenance_type, dm.provenance_type_column(query, table))
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def provider_id(query, table)
|
|
471
|
+
return :provider_id if query_columns(query).include?(:provider_id)
|
|
472
|
+
|
|
473
|
+
cast_column(:provider_id, dm.provider_id_column(query, table))
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def determine_tables
|
|
477
|
+
if upstreams.empty?
|
|
478
|
+
if respond_to?(:table)
|
|
479
|
+
[ table ]
|
|
480
|
+
else
|
|
481
|
+
[ :invalid ]
|
|
482
|
+
end
|
|
218
483
|
else
|
|
219
|
-
|
|
484
|
+
tables = upstreams.compact.map(&:tables).flatten.uniq
|
|
485
|
+
tables.empty? ? [ :invalid ] : tables
|
|
220
486
|
end
|
|
221
487
|
end
|
|
222
488
|
|
|
223
|
-
def
|
|
489
|
+
def determine_domains(db)
|
|
224
490
|
if upstreams.empty?
|
|
225
|
-
if respond_to?(:
|
|
226
|
-
[
|
|
491
|
+
if respond_to?(:domain)
|
|
492
|
+
[ domain ]
|
|
227
493
|
else
|
|
228
|
-
|
|
494
|
+
[ :invalid ]
|
|
229
495
|
end
|
|
230
496
|
else
|
|
231
|
-
upstreams.
|
|
497
|
+
doms = upstreams.compact.flat_map { |up| up.domains(db) }.uniq
|
|
498
|
+
doms.empty? ? [ :invalid ] : doms
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
# Validation Related
|
|
503
|
+
|
|
504
|
+
def upstream_operator_names
|
|
505
|
+
@upstreams.map(&:op_name)
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
def validate(db, opts = {})
|
|
509
|
+
return if @_validated
|
|
510
|
+
|
|
511
|
+
@errors = [] unless defined?(@errors)
|
|
512
|
+
@warnings = [] unless defined?(@warnings)
|
|
513
|
+
|
|
514
|
+
add_error("invalid label") if label && !label.is_a?(String)
|
|
515
|
+
self.class.validations.each do |args|
|
|
516
|
+
send(*args)
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
additional_validation(db, opts)
|
|
520
|
+
|
|
521
|
+
@_validated = true
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
def validate_no_upstreams
|
|
525
|
+
add_error("has upstreams", upstream_operator_names) unless @upstreams.empty?
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def validate_one_upstream
|
|
529
|
+
validate_at_least_one_upstream
|
|
530
|
+
validate_at_most_one_upstream
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
def validate_at_most_one_upstream
|
|
534
|
+
add_error("has multiple upstreams", upstream_operator_names) if @upstreams.length > 1
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
def validate_at_least_one_upstream
|
|
538
|
+
add_error("has no upstream") if @upstreams.empty?
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
def validate_no_arguments
|
|
542
|
+
add_error("has arguments", @arguments) unless @arguments.empty?
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
def validate_one_argument
|
|
546
|
+
validate_at_least_one_argument
|
|
547
|
+
validate_at_most_one_argument
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
def validate_at_most_one_argument
|
|
551
|
+
add_error("has multiple arguments", @arguments) if @arguments.length > 1
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
def validate_at_least_one_argument
|
|
555
|
+
add_error("has no arguments") if @arguments.empty?
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
def validate_option(format, *opts)
|
|
559
|
+
opts.each do |opt|
|
|
560
|
+
next unless options.key?(opt)
|
|
561
|
+
|
|
562
|
+
add_error("wrong option format", opt.to_s, options[opt]) unless format === options[opt]
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def validate_required_options(*opts)
|
|
567
|
+
opts.each do |opt|
|
|
568
|
+
add_error("required option not present", opt.to_s) unless options.key?(opt)
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
# A blank value counts as not set, as in Occurrence#at_least_option
|
|
573
|
+
def validate_unsupported_options(*opts)
|
|
574
|
+
opts.each do |opt|
|
|
575
|
+
next if options[opt].to_s.strip.empty?
|
|
576
|
+
|
|
577
|
+
add_error("unsupported option", opt.to_s, options[opt])
|
|
578
|
+
end
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
def bad_arguments
|
|
582
|
+
return [] unless code_regexp
|
|
583
|
+
# Vocabulary#select_all? is private, so include private methods.
|
|
584
|
+
return [] if respond_to?(:select_all?, true) && select_all?
|
|
585
|
+
|
|
586
|
+
@bad_arguments ||= arguments.reject do |arg|
|
|
587
|
+
code_regexp === arg
|
|
588
|
+
end
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
# Only Vocabulary operators carry a code format (Vocabulary#code_regexp)
|
|
592
|
+
def code_regexp
|
|
593
|
+
nil
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
def validate_codes_match
|
|
597
|
+
return if bad_arguments.empty?
|
|
598
|
+
|
|
599
|
+
add_warning("improperly formatted code", *bad_arguments)
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
def additional_validation(_db, _opts = {})
|
|
603
|
+
# Do nothing by default
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
def add_warnings?(db, opts = {})
|
|
607
|
+
@errors.empty? && !no_db?(db, opts)
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
def add_error(*args)
|
|
611
|
+
errors << args
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
def add_warning(*args)
|
|
615
|
+
warnings << args
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
def include_counts?(db, opts)
|
|
619
|
+
!(no_db?(db, opts) || opts[:skip_counts])
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
def skip_db?(opts)
|
|
623
|
+
opts[:skip_db]
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
def no_db?(db, opts = {})
|
|
627
|
+
no_db = db.nil? || db.adapter_scheme == :mock || skip_db?(opts)
|
|
628
|
+
no_db ||= table_is_missing?(db)
|
|
629
|
+
no_db
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
def table_is_missing?(_db)
|
|
633
|
+
false
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
def preferred_name
|
|
637
|
+
self.class.pref_name
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
def semi_or_inner_join(ds, table, *exprs)
|
|
641
|
+
ds = Sequel[ds] if ds.is_a?(Symbol)
|
|
642
|
+
table = Sequel[table] if table.is_a?(Symbol)
|
|
643
|
+
expr = exprs.inject(&:&)
|
|
644
|
+
if use_inner_join?
|
|
645
|
+
ds.join(table.as(:r), expr)
|
|
646
|
+
.select(*query_cols.map { |c| Sequel[:l][c] })
|
|
647
|
+
else
|
|
648
|
+
rdbms.semi_join(ds, table, *exprs)
|
|
649
|
+
end
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
def matching_columns
|
|
653
|
+
if scope.opts.dig(:window_opts, :window_table)
|
|
654
|
+
%i[person_id window_id]
|
|
655
|
+
else
|
|
656
|
+
[ :person_id ]
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
def use_inner_join?
|
|
661
|
+
options[:inner_join]
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
# The names in an option that lists columns (join_columns, only_columns
|
|
665
|
+
# and so on), as symbols. A JSON statement gives them as strings, and
|
|
666
|
+
# Sequel renders a String as a string literal, not as a column. Call it
|
|
667
|
+
# only for options that name columns: other options are free-form.
|
|
668
|
+
#
|
|
669
|
+
# The value is an Array or a comma-separated String, such as
|
|
670
|
+
# "uuid, start_date". A blank String is an unset option, so it gives
|
|
671
|
+
# nil.
|
|
672
|
+
def column_names_option(name)
|
|
673
|
+
value = options[name]
|
|
674
|
+
if value.is_a?(String)
|
|
675
|
+
value = value.split(",").map(&:strip).reject(&:empty?)
|
|
676
|
+
return nil if value.empty?
|
|
232
677
|
end
|
|
678
|
+
value&.map(&:to_sym)
|
|
233
679
|
end
|
|
234
680
|
end
|
|
235
681
|
end
|