conceptql 0.0.1

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.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/CHANGELOG.md +17 -0
  4. data/Gemfile +4 -0
  5. data/Guardfile +28 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +108 -0
  8. data/Rakefile +1 -0
  9. data/bin/conceptql +5 -0
  10. data/conceptql.gemspec +30 -0
  11. data/doc/ConceptQL Specification (alpha).pdf +0 -0
  12. data/doc/diagram_0.png +0 -0
  13. data/doc/spec.md +1208 -0
  14. data/lib/conceptql/behaviors/dottable.rb +71 -0
  15. data/lib/conceptql/cli.rb +135 -0
  16. data/lib/conceptql/date_adjuster.rb +45 -0
  17. data/lib/conceptql/graph.rb +49 -0
  18. data/lib/conceptql/graph_nodifier.rb +123 -0
  19. data/lib/conceptql/logger.rb +10 -0
  20. data/lib/conceptql/nodes/after.rb +12 -0
  21. data/lib/conceptql/nodes/before.rb +11 -0
  22. data/lib/conceptql/nodes/binary_operator_node.rb +41 -0
  23. data/lib/conceptql/nodes/casting_node.rb +75 -0
  24. data/lib/conceptql/nodes/complement.rb +16 -0
  25. data/lib/conceptql/nodes/concept.rb +38 -0
  26. data/lib/conceptql/nodes/condition_type.rb +63 -0
  27. data/lib/conceptql/nodes/cpt.rb +20 -0
  28. data/lib/conceptql/nodes/date_range.rb +39 -0
  29. data/lib/conceptql/nodes/death.rb +19 -0
  30. data/lib/conceptql/nodes/during.rb +16 -0
  31. data/lib/conceptql/nodes/except.rb +11 -0
  32. data/lib/conceptql/nodes/first.rb +24 -0
  33. data/lib/conceptql/nodes/from.rb +15 -0
  34. data/lib/conceptql/nodes/gender.rb +27 -0
  35. data/lib/conceptql/nodes/hcpcs.rb +20 -0
  36. data/lib/conceptql/nodes/icd10.rb +23 -0
  37. data/lib/conceptql/nodes/icd9.rb +23 -0
  38. data/lib/conceptql/nodes/icd9_procedure.rb +20 -0
  39. data/lib/conceptql/nodes/intersect.rb +29 -0
  40. data/lib/conceptql/nodes/last.rb +24 -0
  41. data/lib/conceptql/nodes/loinc.rb +20 -0
  42. data/lib/conceptql/nodes/node.rb +71 -0
  43. data/lib/conceptql/nodes/occurrence.rb +47 -0
  44. data/lib/conceptql/nodes/pass_thru.rb +11 -0
  45. data/lib/conceptql/nodes/person.rb +25 -0
  46. data/lib/conceptql/nodes/person_filter.rb +12 -0
  47. data/lib/conceptql/nodes/place_of_service_code.rb +23 -0
  48. data/lib/conceptql/nodes/procedure_occurrence.rb +21 -0
  49. data/lib/conceptql/nodes/race.rb +23 -0
  50. data/lib/conceptql/nodes/rxnorm.rb +20 -0
  51. data/lib/conceptql/nodes/snomed.rb +19 -0
  52. data/lib/conceptql/nodes/source_vocabulary_node.rb +54 -0
  53. data/lib/conceptql/nodes/standard_vocabulary_node.rb +43 -0
  54. data/lib/conceptql/nodes/started_by.rb +16 -0
  55. data/lib/conceptql/nodes/temporal_node.rb +25 -0
  56. data/lib/conceptql/nodes/time_window.rb +54 -0
  57. data/lib/conceptql/nodes/union.rb +15 -0
  58. data/lib/conceptql/nodes/visit.rb +11 -0
  59. data/lib/conceptql/nodes/visit_occurrence.rb +26 -0
  60. data/lib/conceptql/nodifier.rb +9 -0
  61. data/lib/conceptql/query.rb +39 -0
  62. data/lib/conceptql/tree.rb +36 -0
  63. data/lib/conceptql/version.rb +3 -0
  64. data/lib/conceptql/view_maker.rb +56 -0
  65. data/lib/conceptql.rb +7 -0
  66. data/spec/conceptql/behaviors/dottable_spec.rb +111 -0
  67. data/spec/conceptql/date_adjuster_spec.rb +68 -0
  68. data/spec/conceptql/nodes/after_spec.rb +18 -0
  69. data/spec/conceptql/nodes/before_spec.rb +18 -0
  70. data/spec/conceptql/nodes/casting_node_spec.rb +73 -0
  71. data/spec/conceptql/nodes/complement_spec.rb +15 -0
  72. data/spec/conceptql/nodes/concept_spec.rb +34 -0
  73. data/spec/conceptql/nodes/condition_type_spec.rb +113 -0
  74. data/spec/conceptql/nodes/cpt_spec.rb +31 -0
  75. data/spec/conceptql/nodes/date_range_spec.rb +35 -0
  76. data/spec/conceptql/nodes/death_spec.rb +12 -0
  77. data/spec/conceptql/nodes/during_spec.rb +32 -0
  78. data/spec/conceptql/nodes/except_spec.rb +18 -0
  79. data/spec/conceptql/nodes/first_spec.rb +37 -0
  80. data/spec/conceptql/nodes/from_spec.rb +15 -0
  81. data/spec/conceptql/nodes/gender_spec.rb +29 -0
  82. data/spec/conceptql/nodes/hcpcs_spec.rb +31 -0
  83. data/spec/conceptql/nodes/icd10_spec.rb +36 -0
  84. data/spec/conceptql/nodes/icd9_procedure_spec.rb +31 -0
  85. data/spec/conceptql/nodes/icd9_spec.rb +36 -0
  86. data/spec/conceptql/nodes/intersect_spec.rb +33 -0
  87. data/spec/conceptql/nodes/last_spec.rb +38 -0
  88. data/spec/conceptql/nodes/loinc_spec.rb +31 -0
  89. data/spec/conceptql/nodes/occurrence_spec.rb +89 -0
  90. data/spec/conceptql/nodes/person_filter_spec.rb +18 -0
  91. data/spec/conceptql/nodes/person_spec.rb +12 -0
  92. data/spec/conceptql/nodes/place_of_service_code_spec.rb +26 -0
  93. data/spec/conceptql/nodes/procedure_occurrence_spec.rb +12 -0
  94. data/spec/conceptql/nodes/query_double.rb +19 -0
  95. data/spec/conceptql/nodes/race_spec.rb +23 -0
  96. data/spec/conceptql/nodes/rxnorm_spec.rb +31 -0
  97. data/spec/conceptql/nodes/snomed_spec.rb +31 -0
  98. data/spec/conceptql/nodes/source_vocabulary_node_spec.rb +37 -0
  99. data/spec/conceptql/nodes/standard_vocabulary_node_spec.rb +40 -0
  100. data/spec/conceptql/nodes/started_by_spec.rb +25 -0
  101. data/spec/conceptql/nodes/temporal_node_spec.rb +57 -0
  102. data/spec/conceptql/nodes/time_window_spec.rb +66 -0
  103. data/spec/conceptql/nodes/union_spec.rb +25 -0
  104. data/spec/conceptql/nodes/visit_occurrence_spec.rb +12 -0
  105. data/spec/conceptql/query_spec.rb +20 -0
  106. data/spec/conceptql/tree_spec.rb +54 -0
  107. data/spec/doubles/stream_for_casting_double.rb +9 -0
  108. data/spec/doubles/stream_for_occurrence_double.rb +21 -0
  109. data/spec/doubles/stream_for_temporal_double.rb +6 -0
  110. data/spec/spec_helper.rb +74 -0
  111. metadata +327 -0
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+ require 'conceptql/nodes/occurrence'
3
+ require_double('stream_for_occurrence')
4
+
5
+ describe ConceptQL::Nodes::Occurrence do
6
+ it 'behaves itself' do
7
+ ConceptQL::Nodes::Occurrence.new.must_behave_like(:evaluator)
8
+ end
9
+
10
+ describe 'occurrence set to 1' do
11
+ subject do
12
+ ConceptQL::Nodes::Occurrence.new(1, StreamForOccurrenceDouble.new).query(Sequel.mock).sql
13
+ end
14
+
15
+ it 'should order by ascending start_date' do
16
+ subject.must_match 'ORDER BY start_date ASC'
17
+ end
18
+
19
+ it 'should partition by person_id' do
20
+ subject.must_match 'PARTITION BY person_id'
21
+ end
22
+
23
+ it 'should assign a row number' do
24
+ subject.must_match 'row_number()'
25
+ end
26
+
27
+ it 'should find the all rows with rn = 1' do
28
+ subject.must_match 'rn = 1'
29
+ end
30
+ end
31
+
32
+ describe 'occurrence set to 2' do
33
+ subject do
34
+ ConceptQL::Nodes::Occurrence.new(2, StreamForOccurrenceDouble.new).query(Sequel.mock).sql
35
+ end
36
+
37
+ it 'should order by ascending start_date' do
38
+ subject.must_match 'ORDER BY start_date ASC'
39
+ end
40
+
41
+ it 'should find the all rows with rn = 2' do
42
+ subject.must_match 'rn = 2'
43
+ end
44
+ end
45
+
46
+ describe 'occurrence set to -1' do
47
+ subject do
48
+ ConceptQL::Nodes::Occurrence.new(-1, StreamForOccurrenceDouble.new).query(Sequel.mock).sql
49
+ end
50
+
51
+ it 'should order by ascending start_date' do
52
+ subject.must_match 'ORDER BY start_date DESC'
53
+ end
54
+
55
+ it 'should find the all rows with rn = 1' do
56
+ subject.must_match 'rn = 1'
57
+ end
58
+ end
59
+
60
+ describe 'occurrence set to -2' do
61
+ subject do
62
+ ConceptQL::Nodes::Occurrence.new(-2, StreamForOccurrenceDouble.new).query(Sequel.mock).sql
63
+ end
64
+
65
+ it 'should order by ascending start_date' do
66
+ subject.must_match 'ORDER BY start_date DESC'
67
+ end
68
+
69
+ it 'should find the all rows with rn = 2' do
70
+ subject.must_match 'rn = 2'
71
+ end
72
+ end
73
+
74
+ describe 'occurrence respects types' do
75
+ subject do
76
+ dub = StreamForOccurrenceDouble.new
77
+ def dub.types
78
+ [:condition_occurrence]
79
+ end
80
+ ConceptQL::Nodes::Occurrence.new(-2, dub).query(Sequel.mock).sql
81
+ end
82
+
83
+ it 'should order by ascending start_date' do
84
+ subject.must_match ', condition_occurrence_id ASC'
85
+ end
86
+ end
87
+ end
88
+
89
+
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+ require 'conceptql/nodes/person_filter'
3
+ require_relative 'query_double'
4
+
5
+ describe ConceptQL::Nodes::PersonFilter do
6
+ it 'behaves itself' do
7
+ ConceptQL::Nodes::PersonFilter.new.must_behave_like(:evaluator)
8
+ end
9
+
10
+ describe '#query' do
11
+ it 'uses right stream as argument to PERSON_FILTER against left stream' do
12
+ double1 = QueryDouble.new(1)
13
+ double2 = QueryDouble.new(2)
14
+ double1.must_behave_like(:evaluator)
15
+ ConceptQL::Nodes::PersonFilter.new(left: double1, right: double2).query(Sequel.mock).sql.must_equal "SELECT * FROM (SELECT * FROM table1) AS t1 WHERE (person_id IN (SELECT person_id FROM table2 GROUP BY person_id))"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+ require 'conceptql/nodes/person'
3
+ require_double('stream_for_casting')
4
+
5
+ describe ConceptQL::Nodes::CastingNode do
6
+ it 'behaves itself' do
7
+ ConceptQL::Nodes::Person.new.must_behave_like(:casting_node)
8
+ end
9
+ end
10
+
11
+
12
+
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+ require 'conceptql/nodes/place_of_service_code'
3
+
4
+ describe ConceptQL::Nodes::PlaceOfServiceCode do
5
+ it 'behaves itself' do
6
+ ConceptQL::Nodes::PlaceOfServiceCode.new.must_behave_like(:evaluator)
7
+ end
8
+
9
+ describe '#query' do
10
+ it 'works for 23' do
11
+ correct_query = "SELECT * FROM visit_occurrence_with_dates AS v INNER JOIN vocabulary.concept AS vc ON (vc.concept_id = v.place_of_service_concept_id) WHERE (vc.concept_code IN ('23'))"
12
+ ConceptQL::Nodes::PlaceOfServiceCode.new('23').query(Sequel.mock).sql.must_equal correct_query
13
+ end
14
+
15
+ it 'works for 23 as number' do
16
+ correct_query = "SELECT * FROM visit_occurrence_with_dates AS v INNER JOIN vocabulary.concept AS vc ON (vc.concept_id = v.place_of_service_concept_id) WHERE (vc.concept_code IN ('23'))"
17
+ ConceptQL::Nodes::PlaceOfServiceCode.new(23).query(Sequel.mock).sql.must_equal correct_query
18
+ end
19
+
20
+ it 'works for multiple values' do
21
+ correct_query = "SELECT * FROM visit_occurrence_with_dates AS v INNER JOIN vocabulary.concept AS vc ON (vc.concept_id = v.place_of_service_concept_id) WHERE (vc.concept_code IN ('23', '22'))"
22
+ ConceptQL::Nodes::PlaceOfServiceCode.new('23', '22').query(Sequel.mock).sql.must_equal correct_query
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+ require 'conceptql/nodes/procedure_occurrence'
3
+ require_double('stream_for_casting')
4
+
5
+ describe ConceptQL::Nodes::CastingNode do
6
+ it 'behaves itself' do
7
+ ConceptQL::Nodes::ProcedureOccurrence.new.must_behave_like(:casting_node)
8
+ end
9
+ end
10
+
11
+
12
+
@@ -0,0 +1,19 @@
1
+ require 'conceptql/nodes/node'
2
+ class QueryDouble < ConceptQL::Nodes::Node
3
+ def initialize(num, type = :visit_occurrence)
4
+ @num = num
5
+ @type = type
6
+ end
7
+
8
+ def types
9
+ [@type]
10
+ end
11
+
12
+ def evaluate(db)
13
+ query(db)
14
+ end
15
+
16
+ def query(db)
17
+ db["table#{@num}".to_sym]
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+ require 'conceptql/nodes/race'
3
+
4
+ describe ConceptQL::Nodes::Race do
5
+ it 'behaves itself' do
6
+ ConceptQL::Nodes::Race.new.must_behave_like(:evaluator)
7
+ end
8
+
9
+ describe '#query' do
10
+ it 'works for white' do
11
+ correct_query = "SELECT * FROM person_with_dates AS p INNER JOIN vocabulary.concept AS vc ON (vc.concept_id = p.race_concept_id) WHERE (lower(vc.concept_name) IN ('white'))"
12
+ ConceptQL::Nodes::Race.new('White').query(Sequel.mock).sql.must_equal correct_query
13
+ ConceptQL::Nodes::Race.new('white').query(Sequel.mock).sql.must_equal correct_query
14
+ end
15
+
16
+ it 'works for multiple values' do
17
+ correct_query = "SELECT * FROM person_with_dates AS p INNER JOIN vocabulary.concept AS vc ON (vc.concept_id = p.race_concept_id) WHERE (lower(vc.concept_name) IN ('white', 'other'))"
18
+ ConceptQL::Nodes::Race.new('White', 'Other').query(Sequel.mock).sql.must_equal correct_query
19
+ ConceptQL::Nodes::Race.new('white', 'other').query(Sequel.mock).sql.must_equal correct_query
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+ require 'conceptql/nodes/rxnorm'
3
+
4
+ describe ConceptQL::Nodes::Rxnorm do
5
+ it 'behaves itself' do
6
+ ConceptQL::Nodes::Rxnorm.new.must_behave_like(:standard_vocabulary_node)
7
+ end
8
+
9
+ subject do
10
+ ConceptQL::Nodes::Rxnorm.new
11
+ end
12
+
13
+ describe '#table' do
14
+ it 'should be drug_exposure' do
15
+ subject.table.must_equal :drug_exposure
16
+ end
17
+ end
18
+
19
+ describe '#concept_column' do
20
+ it 'should be drug_concept_id' do
21
+ subject.concept_column.must_equal :drug_concept_id
22
+ end
23
+ end
24
+
25
+ describe '#vocabulary_id' do
26
+ it 'should be 8' do
27
+ subject.vocabulary_id.must_equal 8
28
+ end
29
+ end
30
+ end
31
+
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+ require 'conceptql/nodes/snomed'
3
+
4
+ describe ConceptQL::Nodes::Snomed do
5
+ it 'behaves itself' do
6
+ ConceptQL::Nodes::Snomed.new.must_behave_like(:standard_vocabulary_node)
7
+ end
8
+
9
+ subject do
10
+ ConceptQL::Nodes::Snomed.new
11
+ end
12
+
13
+ describe '#table' do
14
+ it 'should be condition_occurrence' do
15
+ subject.table.must_equal :condition_occurrence
16
+ end
17
+ end
18
+
19
+ describe '#concept_column' do
20
+ it 'should be condition_concept_id' do
21
+ subject.concept_column.must_equal :condition_concept_id
22
+ end
23
+ end
24
+
25
+ describe '#vocabulary_id' do
26
+ it 'should be 1' do
27
+ subject.vocabulary_id.must_equal 1
28
+ end
29
+ end
30
+ end
31
+
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+ require 'conceptql/nodes/source_vocabulary_node'
3
+
4
+ describe ConceptQL::Nodes::SourceVocabularyNode do
5
+ it 'behaves itself' do
6
+ ConceptQL::Nodes::SourceVocabularyNode.new.must_behave_like(:evaluator)
7
+ end
8
+
9
+ class SourceVocabularyDouble < ConceptQL::Nodes::SourceVocabularyNode
10
+ def table
11
+ :table
12
+ end
13
+
14
+ def source_column
15
+ :source_column
16
+ end
17
+
18
+ def concept_column
19
+ :concept_column
20
+ end
21
+
22
+ def vocabulary_id
23
+ 1
24
+ end
25
+ end
26
+
27
+ describe '#query' do
28
+ it 'works for single values' do
29
+ SourceVocabularyDouble.new('value').query(Sequel.mock).sql.must_equal "SELECT * FROM table_with_dates AS tab INNER JOIN vocabulary.source_to_concept_map AS scm ON (scm.target_concept_id = tab.concept_column) WHERE ((scm.source_code IN ('value')) AND (scm.source_vocabulary_id = 1) AND (scm.source_code = tab.source_column))"
30
+ end
31
+
32
+ it 'works for multiple values' do
33
+ SourceVocabularyDouble.new('value1', 'value2').query(Sequel.mock).sql.must_equal "SELECT * FROM table_with_dates AS tab INNER JOIN vocabulary.source_to_concept_map AS scm ON (scm.target_concept_id = tab.concept_column) WHERE ((scm.source_code IN ('value1', 'value2')) AND (scm.source_vocabulary_id = 1) AND (scm.source_code = tab.source_column))"
34
+ end
35
+ end
36
+ end
37
+
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'conceptql/nodes/standard_vocabulary_node'
3
+
4
+ describe ConceptQL::Nodes::StandardVocabularyNode do
5
+ it 'behaves itself' do
6
+ ConceptQL::Nodes::StandardVocabularyNode.new.must_behave_like(:evaluator)
7
+ end
8
+
9
+ class StandardVocabularyDouble < ConceptQL::Nodes::StandardVocabularyNode
10
+ def table
11
+ :table
12
+ end
13
+
14
+ def concept_column
15
+ :concept_column
16
+ end
17
+
18
+ def vocabulary_id
19
+ 1
20
+ end
21
+ end
22
+
23
+ describe StandardVocabularyDouble do
24
+ it 'behaves itself' do
25
+ StandardVocabularyDouble.new.must_behave_like(:standard_vocabulary_node)
26
+ end
27
+ end
28
+
29
+ describe '#query' do
30
+ it 'works for single values' do
31
+ StandardVocabularyDouble.new('value').query(Sequel.mock).sql.must_equal "SELECT * FROM table_with_dates AS tab INNER JOIN vocabulary.concept AS c ON (c.concept_id = tab.concept_column) WHERE ((c.concept_code IN ('value')) AND (c.vocabulary_id = 1))"
32
+ end
33
+
34
+ it 'works for multiple diagnoses' do
35
+ StandardVocabularyDouble.new('value1', 'value2').query(Sequel.mock).sql.must_equal "SELECT * FROM table_with_dates AS tab INNER JOIN vocabulary.concept AS c ON (c.concept_id = tab.concept_column) WHERE ((c.concept_code IN ('value1', 'value2')) AND (c.vocabulary_id = 1))"
36
+ end
37
+ end
38
+ end
39
+
40
+
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+ require 'conceptql/nodes/started_by'
3
+ require_double('stream_for_temporal')
4
+
5
+ describe ConceptQL::Nodes::StartedBy do
6
+ it 'behaves itself' do
7
+ ConceptQL::Nodes::StartedBy.new.must_behave_like(:temporal_node)
8
+ end
9
+
10
+ subject do
11
+ ConceptQL::Nodes::StartedBy.new(left: StreamForTemporalDouble.new, right: StreamForTemporalDouble.new)
12
+ end
13
+
14
+ it 'should use proper where clause' do
15
+ subject.query(Sequel.mock).sql.must_match 'l.start_date = r.start_date'
16
+ subject.query(Sequel.mock).sql.must_match 'l.end_date > r.end_date'
17
+ end
18
+
19
+ it 'should use proper where clause when inclusive' do
20
+ sub = ConceptQL::Nodes::StartedBy.new(left: StreamForTemporalDouble.new, right: StreamForTemporalDouble.new, inclusive: true)
21
+ sub.query(Sequel.mock).sql.must_match 'l.start_date = r.start_date'
22
+ sub.query(Sequel.mock).sql.must_match 'l.end_date >= r.end_date'
23
+ end
24
+ end
25
+
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+ require 'conceptql/nodes/temporal_node'
3
+ require_double('stream_for_temporal')
4
+
5
+ describe ConceptQL::Nodes::TemporalNode do
6
+ it 'behaves itself' do
7
+ ConceptQL::Nodes::TemporalNode.new.must_behave_like(:evaluator)
8
+ end
9
+
10
+ class TemporalDouble < ConceptQL::Nodes::TemporalNode
11
+ def where_clause
12
+ Proc.new do
13
+ l__end_date < r__start_date
14
+ end
15
+ end
16
+ end
17
+
18
+ describe TemporalDouble do
19
+ it 'behaves itself' do
20
+ TemporalDouble.new.must_behave_like(:temporal_node)
21
+ end
22
+ end
23
+
24
+ describe StreamForTemporalDouble do
25
+ it 'behaves itself' do
26
+ StreamForTemporalDouble.new.must_behave_like(:evaluator)
27
+ end
28
+ end
29
+
30
+ describe '#inclusive?' do
31
+ it 'defaults to false' do
32
+ refute(TemporalDouble.new.inclusive?)
33
+ end
34
+
35
+ it 'can be set to true' do
36
+ assert(TemporalDouble.new(inclusive: true).inclusive?)
37
+ end
38
+ end
39
+
40
+ describe '#query' do
41
+ it 'uses logic from where_clause' do
42
+ sql = TemporalDouble.new(left: StreamForTemporalDouble.new, right: StreamForTemporalDouble.new).query(Sequel.mock).sql
43
+ sql.must_match('l.end_date < r.start_date')
44
+ end
45
+
46
+ it 'pulls from the right tables' do
47
+ sql = TemporalDouble.new(left: StreamForTemporalDouble.new, right: StreamForTemporalDouble.new).query(Sequel.mock).sql
48
+ sql.must_match('FROM table')
49
+ end
50
+
51
+ it 'is ok with symbols' do
52
+ sql = TemporalDouble.new(left: StreamForTemporalDouble.new, right: StreamForTemporalDouble.new).query(Sequel.mock).sql
53
+ sql.must_match('FROM table')
54
+ end
55
+ end
56
+ end
57
+
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+ require 'conceptql/nodes/time_window'
3
+ require 'conceptql/nodes/node'
4
+
5
+ describe ConceptQL::Nodes::TimeWindow do
6
+ class Stream4TimeWindowDouble < ConceptQL::Nodes::Node
7
+ def types
8
+ [:visit_occurrence]
9
+ end
10
+
11
+ def query(db)
12
+ db
13
+ end
14
+ end
15
+
16
+ describe '#evaluate' do
17
+ it 'adjusts start by 1 day' do
18
+ sql = ConceptQL::Nodes::TimeWindow.new(Stream4TimeWindowDouble.new, { start: 'd', end: '' }).evaluate(Sequel.mock).sql
19
+ sql.must_match(%q{date((date(start_date) + interval '1 day')) AS start_date})
20
+ sql.must_match(%q{date(end_date) AS end_date})
21
+ end
22
+
23
+ it 'adjusts start by 1 day' do
24
+ sql = ConceptQL::Nodes::TimeWindow.new(Stream4TimeWindowDouble.new, { start: '', end: 'd' }).evaluate(Sequel.mock).sql
25
+ sql.must_match(%q{date(start_date) AS start_date})
26
+ sql.must_match(%q{date((date(end_date) + interval '1 day')) AS end_date})
27
+ end
28
+
29
+ it 'adjusts both values by 1 day' do
30
+ sql = ConceptQL::Nodes::TimeWindow.new(Stream4TimeWindowDouble.new, { start: 'd', end: 'd' }).evaluate(Sequel.mock).sql
31
+ sql.must_match(%q{date((date(start_date) + interval '1 day')) AS start_date, date((date(end_date) + interval '1 day')) AS end_date})
32
+ end
33
+
34
+ it 'makes multiple adjustments to both values' do
35
+ sql = ConceptQL::Nodes::TimeWindow.new(Stream4TimeWindowDouble.new, { start: 'dmy', end: '-d-m-y' }).evaluate(Sequel.mock).sql
36
+ sql.must_match(%q{date((date((date((date(start_date) + interval '1 day')) + interval '1 month')) + interval '1 year'))})
37
+ sql.must_match(%q{date((date((date((date(end_date) + interval '-1 day')) + interval '-1 month')) + interval '-1 year'))})
38
+ end
39
+
40
+ it 'can set start_date to be end_date' do
41
+ sql = ConceptQL::Nodes::TimeWindow.new(Stream4TimeWindowDouble.new, { start: 'end', end: '' }).evaluate(Sequel.mock).sql
42
+ sql.must_match(%q{end_date AS start_date})
43
+ sql.must_match(%q{date(end_date) AS end_date})
44
+ end
45
+
46
+ it 'can set end_date to be start_date' do
47
+ sql = ConceptQL::Nodes::TimeWindow.new(Stream4TimeWindowDouble.new, { start: '', end: 'start' }).evaluate(Sequel.mock).sql
48
+ sql.must_match(%q{start_date AS end_date})
49
+ sql.must_match(%q{date(start_date) AS start_date})
50
+ end
51
+
52
+ it 'will swap start and end dates, though this is a bad idea but you should probably know about this' do
53
+ sql = ConceptQL::Nodes::TimeWindow.new(Stream4TimeWindowDouble.new, { start: 'end', end: 'start' }).evaluate(Sequel.mock).sql
54
+ sql.must_match(%q{start_date AS end_date})
55
+ sql.must_match(%q{end_date AS start_date})
56
+ end
57
+
58
+ it 'handles nil arguments to both start and end' do
59
+ sql = ConceptQL::Nodes::TimeWindow.new(Stream4TimeWindowDouble.new, { start: nil, end: nil }).evaluate(Sequel.mock).sql
60
+ sql.must_match(%q{date(start_date) AS start_date})
61
+ sql.must_match(%q{date(end_date) AS end_date})
62
+ end
63
+ end
64
+ end
65
+
66
+
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+ require 'conceptql/nodes/union'
3
+ require_relative 'query_double'
4
+
5
+ describe ConceptQL::Nodes::Union do
6
+ it 'behaves itself' do
7
+ ConceptQL::Nodes::Union.new.must_behave_like(:evaluator)
8
+ end
9
+
10
+ describe '#query' do
11
+ it 'works for multiple criteria' do
12
+ double1 = QueryDouble.new(1)
13
+ double2 = QueryDouble.new(2)
14
+ double3 = QueryDouble.new(3)
15
+ double1.must_behave_like(:evaluator)
16
+ ConceptQL::Nodes::Union.new(double1, double2, double3).query(Sequel.mock).sql.must_equal "SELECT * FROM (SELECT * FROM (SELECT * FROM table1 UNION ALL SELECT * FROM table2) AS t1 UNION ALL SELECT * FROM table3) AS t1"
17
+ end
18
+
19
+ it 'works for single criteria' do
20
+ double1 = QueryDouble.new(1)
21
+ double1.must_behave_like(:evaluator)
22
+ ConceptQL::Nodes::Union.new(double1).query(Sequel.mock).sql.must_equal "SELECT * FROM table1"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+ require 'conceptql/nodes/visit_occurrence'
3
+ require_double('stream_for_casting')
4
+
5
+ describe ConceptQL::Nodes::CastingNode do
6
+ it 'behaves itself' do
7
+ ConceptQL::Nodes::VisitOccurrence.new.must_behave_like(:casting_node)
8
+ end
9
+ end
10
+
11
+
12
+
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+ require 'conceptql'
3
+
4
+ describe ConceptQL::Query do
5
+ describe '#query' do
6
+ it 'passes request on to tree' do
7
+ yaml = Psych.dump({ icd9: '799.22' })
8
+ mock_tree = Minitest::Mock.new
9
+ mock_node = Minitest::Mock.new
10
+
11
+ query = ConceptQL::Query.new(:mock_db, yaml, mock_tree)
12
+ mock_tree.expect :root, mock_node, [query]
13
+ mock_node.expect :evaluate, nil, [:mock_db]
14
+ query.query
15
+
16
+ mock_node.verify
17
+ mock_tree.verify
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+ require 'conceptql/tree'
3
+
4
+ describe ConceptQL::Tree do
5
+ describe '#root' do
6
+ before do
7
+ @mock_query_obj = Minitest::Mock.new
8
+ @mock_nodifier = Minitest::Mock.new
9
+ @tree = ConceptQL::Tree.new(nodifier: @mock_nodifier)
10
+ end
11
+
12
+ after do
13
+ @mock_query_obj.verify
14
+ @mock_nodifier.verify
15
+ end
16
+
17
+ it 'should walk single node criteria tree and convert to node' do
18
+ @mock_nodifier.expect :create, :success_indicator, [:icd9, '799.22']
19
+ @mock_query_obj.expect :statement, { icd9: '799.22' }
20
+
21
+ @tree.root(@mock_query_obj).must_equal :success_indicator
22
+ end
23
+
24
+ it 'should extend all nodes created with behavior passed in' do
25
+ mock_icd9_obj = Minitest::Mock.new
26
+ mock_icd9_obj.expect :extend, nil, [:mock_behavior]
27
+
28
+ @mock_nodifier.expect :create, mock_icd9_obj, [:icd9, '799.22']
29
+ @mock_query_obj.expect :statement, { icd9: '799.22' }
30
+
31
+ tree = ConceptQL::Tree.new(nodifier: @mock_nodifier, behavior: :mock_behavior)
32
+ tree.root(@mock_query_obj)
33
+ end
34
+
35
+ it 'should walk multi-criteria node' do
36
+ @mock_nodifier.expect :create, :mock_icd9, [:icd9, '799.22']
37
+ @mock_nodifier.expect :create, :success_indicator, [:nth, { occurrence: 1, expression: :mock_icd9 }]
38
+
39
+ @mock_query_obj.expect :statement, { nth: { occurrence: 1, expression: { icd9: '799.22' } } }
40
+
41
+ @tree.root(@mock_query_obj).must_equal :success_indicator
42
+ end
43
+
44
+ it 'should walk multi-node criteria tree and convert to nodes' do
45
+ @mock_nodifier.expect :create, :mock_icd9, [:icd9, '799.22']
46
+ @mock_nodifier.expect :create, :success_indicator, [:any, [:mock_icd9]]
47
+
48
+ @mock_query_obj.expect :statement, { any: [{ icd9: '799.22' }] }
49
+
50
+ @tree.root(@mock_query_obj).must_equal :success_indicator
51
+ end
52
+ end
53
+ end
54
+
@@ -0,0 +1,9 @@
1
+ class StreamForCastingDouble < ConceptQL::Nodes::Node
2
+ def query(db)
3
+ db.from(:table)
4
+ end
5
+
6
+ def types=(types)
7
+ @types = types
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ class StreamForOccurrenceDouble < ConceptQL::Nodes::Node
2
+ def query(db)
3
+ ds = db.from(:table)
4
+ # Occurrence needs window functions to work
5
+ meta_def(ds, :supports_window_functions?){true}
6
+ ds
7
+ end
8
+
9
+ # Stole this from:
10
+ # https://github.com/jeremyevans/sequel/blob/63397b787335d06de97dc89ddf49b7a3a93ffdc9/spec/core/expression_filters_spec.rb#L400
11
+ #
12
+ # By default, the Sequel.mock datasets don't allow window functions, but I need them
13
+ # enabled for testing
14
+ #
15
+ # I saw that Sequel tests had this little nugget in them to temporarily enable
16
+ # window functions and sure enough, it works
17
+ def meta_def(obj, name, &block)
18
+ (class << obj; self end).send(:define_method, name, &block)
19
+ end
20
+ end
21
+
@@ -0,0 +1,6 @@
1
+ class StreamForTemporalDouble < ConceptQL::Nodes::Node
2
+ def query(db)
3
+ db.from(:table)
4
+ end
5
+ end
6
+