masamune 0.11.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.
Files changed (185) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +54 -0
  4. data/Rakefile +15 -0
  5. data/bin/masamune-elastic-mapreduce +4 -0
  6. data/bin/masamune-hive +4 -0
  7. data/bin/masamune-psql +4 -0
  8. data/bin/masamune-shell +4 -0
  9. data/lib/masamune.rb +56 -0
  10. data/lib/masamune/accumulate.rb +60 -0
  11. data/lib/masamune/actions.rb +38 -0
  12. data/lib/masamune/actions/data_flow.rb +131 -0
  13. data/lib/masamune/actions/date_parse.rb +75 -0
  14. data/lib/masamune/actions/elastic_mapreduce.rb +68 -0
  15. data/lib/masamune/actions/execute.rb +52 -0
  16. data/lib/masamune/actions/filesystem.rb +37 -0
  17. data/lib/masamune/actions/hadoop_filesystem.rb +40 -0
  18. data/lib/masamune/actions/hadoop_streaming.rb +41 -0
  19. data/lib/masamune/actions/hive.rb +74 -0
  20. data/lib/masamune/actions/postgres.rb +76 -0
  21. data/lib/masamune/actions/postgres_admin.rb +34 -0
  22. data/lib/masamune/actions/s3cmd.rb +44 -0
  23. data/lib/masamune/actions/transform.rb +89 -0
  24. data/lib/masamune/after_initialize_callbacks.rb +55 -0
  25. data/lib/masamune/cached_filesystem.rb +110 -0
  26. data/lib/masamune/commands.rb +37 -0
  27. data/lib/masamune/commands/elastic_mapreduce.rb +119 -0
  28. data/lib/masamune/commands/hadoop_filesystem.rb +57 -0
  29. data/lib/masamune/commands/hadoop_streaming.rb +116 -0
  30. data/lib/masamune/commands/hive.rb +178 -0
  31. data/lib/masamune/commands/interactive.rb +37 -0
  32. data/lib/masamune/commands/postgres.rb +128 -0
  33. data/lib/masamune/commands/postgres_admin.rb +72 -0
  34. data/lib/masamune/commands/postgres_common.rb +33 -0
  35. data/lib/masamune/commands/retry_with_backoff.rb +60 -0
  36. data/lib/masamune/commands/s3cmd.rb +70 -0
  37. data/lib/masamune/commands/shell.rb +202 -0
  38. data/lib/masamune/configuration.rb +195 -0
  39. data/lib/masamune/data_plan.rb +31 -0
  40. data/lib/masamune/data_plan/builder.rb +66 -0
  41. data/lib/masamune/data_plan/elem.rb +190 -0
  42. data/lib/masamune/data_plan/engine.rb +162 -0
  43. data/lib/masamune/data_plan/rule.rb +292 -0
  44. data/lib/masamune/data_plan/set.rb +176 -0
  45. data/lib/masamune/environment.rb +164 -0
  46. data/lib/masamune/filesystem.rb +567 -0
  47. data/lib/masamune/has_environment.rb +40 -0
  48. data/lib/masamune/helpers.rb +27 -0
  49. data/lib/masamune/helpers/postgres.rb +84 -0
  50. data/lib/masamune/io.rb +33 -0
  51. data/lib/masamune/last_element.rb +53 -0
  52. data/lib/masamune/method_logger.rb +41 -0
  53. data/lib/masamune/multi_io.rb +39 -0
  54. data/lib/masamune/schema.rb +36 -0
  55. data/lib/masamune/schema/catalog.rb +233 -0
  56. data/lib/masamune/schema/column.rb +527 -0
  57. data/lib/masamune/schema/dimension.rb +133 -0
  58. data/lib/masamune/schema/event.rb +121 -0
  59. data/lib/masamune/schema/fact.rb +133 -0
  60. data/lib/masamune/schema/map.rb +265 -0
  61. data/lib/masamune/schema/row.rb +133 -0
  62. data/lib/masamune/schema/store.rb +115 -0
  63. data/lib/masamune/schema/table.rb +308 -0
  64. data/lib/masamune/schema/table_reference.rb +76 -0
  65. data/lib/masamune/spec_helper.rb +23 -0
  66. data/lib/masamune/string_format.rb +34 -0
  67. data/lib/masamune/tasks/elastic_mapreduce_thor.rb +60 -0
  68. data/lib/masamune/tasks/hive_thor.rb +55 -0
  69. data/lib/masamune/tasks/postgres_thor.rb +47 -0
  70. data/lib/masamune/tasks/shell_thor.rb +63 -0
  71. data/lib/masamune/template.rb +77 -0
  72. data/lib/masamune/thor.rb +186 -0
  73. data/lib/masamune/thor_loader.rb +38 -0
  74. data/lib/masamune/topological_hash.rb +34 -0
  75. data/lib/masamune/transform.rb +47 -0
  76. data/lib/masamune/transform/bulk_upsert.psql.erb +64 -0
  77. data/lib/masamune/transform/bulk_upsert.rb +52 -0
  78. data/lib/masamune/transform/consolidate_dimension.rb +54 -0
  79. data/lib/masamune/transform/deduplicate_dimension.psql.erb +52 -0
  80. data/lib/masamune/transform/deduplicate_dimension.rb +53 -0
  81. data/lib/masamune/transform/define_event_view.hql.erb +51 -0
  82. data/lib/masamune/transform/define_event_view.rb +60 -0
  83. data/lib/masamune/transform/define_index.psql.erb +34 -0
  84. data/lib/masamune/transform/define_schema.hql.erb +23 -0
  85. data/lib/masamune/transform/define_schema.psql.erb +79 -0
  86. data/lib/masamune/transform/define_schema.rb +56 -0
  87. data/lib/masamune/transform/define_table.hql.erb +34 -0
  88. data/lib/masamune/transform/define_table.psql.erb +95 -0
  89. data/lib/masamune/transform/define_table.rb +40 -0
  90. data/lib/masamune/transform/define_unique.psql.erb +30 -0
  91. data/lib/masamune/transform/insert_reference_values.psql.erb +43 -0
  92. data/lib/masamune/transform/insert_reference_values.rb +64 -0
  93. data/lib/masamune/transform/load_dimension.rb +47 -0
  94. data/lib/masamune/transform/load_fact.rb +45 -0
  95. data/lib/masamune/transform/operator.rb +96 -0
  96. data/lib/masamune/transform/relabel_dimension.psql.erb +76 -0
  97. data/lib/masamune/transform/relabel_dimension.rb +39 -0
  98. data/lib/masamune/transform/rollup_fact.psql.erb +79 -0
  99. data/lib/masamune/transform/rollup_fact.rb +149 -0
  100. data/lib/masamune/transform/snapshot_dimension.psql.erb +75 -0
  101. data/lib/masamune/transform/snapshot_dimension.rb +74 -0
  102. data/lib/masamune/transform/stage_dimension.psql.erb +39 -0
  103. data/lib/masamune/transform/stage_dimension.rb +83 -0
  104. data/lib/masamune/transform/stage_fact.psql.erb +80 -0
  105. data/lib/masamune/transform/stage_fact.rb +111 -0
  106. data/lib/masamune/version.rb +25 -0
  107. data/spec/fixtures/aggregate.sql.erb +25 -0
  108. data/spec/fixtures/comment.sql.erb +27 -0
  109. data/spec/fixtures/invalid.sql.erb +23 -0
  110. data/spec/fixtures/relative.sql.erb +23 -0
  111. data/spec/fixtures/simple.sql.erb +28 -0
  112. data/spec/fixtures/whitespace.sql.erb +30 -0
  113. data/spec/masamune/actions/elastic_mapreduce_spec.rb +108 -0
  114. data/spec/masamune/actions/execute_spec.rb +50 -0
  115. data/spec/masamune/actions/hadoop_filesystem_spec.rb +44 -0
  116. data/spec/masamune/actions/hadoop_streaming_spec.rb +74 -0
  117. data/spec/masamune/actions/hive_spec.rb +117 -0
  118. data/spec/masamune/actions/postgres_admin_spec.rb +58 -0
  119. data/spec/masamune/actions/postgres_spec.rb +134 -0
  120. data/spec/masamune/actions/s3cmd_spec.rb +44 -0
  121. data/spec/masamune/actions/transform_spec.rb +144 -0
  122. data/spec/masamune/after_initialization_callbacks_spec.rb +61 -0
  123. data/spec/masamune/cached_filesystem_spec.rb +167 -0
  124. data/spec/masamune/commands/hadoop_filesystem_spec.rb +50 -0
  125. data/spec/masamune/commands/hadoop_streaming_spec.rb +106 -0
  126. data/spec/masamune/commands/hive_spec.rb +117 -0
  127. data/spec/masamune/commands/postgres_admin_spec.rb +69 -0
  128. data/spec/masamune/commands/postgres_spec.rb +100 -0
  129. data/spec/masamune/commands/retry_with_backoff_spec.rb +116 -0
  130. data/spec/masamune/commands/s3cmd_spec.rb +50 -0
  131. data/spec/masamune/commands/shell_spec.rb +101 -0
  132. data/spec/masamune/configuration_spec.rb +102 -0
  133. data/spec/masamune/data_plan/builder_spec.rb +91 -0
  134. data/spec/masamune/data_plan/elem_spec.rb +102 -0
  135. data/spec/masamune/data_plan/engine_spec.rb +356 -0
  136. data/spec/masamune/data_plan/rule_spec.rb +407 -0
  137. data/spec/masamune/data_plan/set_spec.rb +517 -0
  138. data/spec/masamune/environment_spec.rb +65 -0
  139. data/spec/masamune/filesystem_spec.rb +1421 -0
  140. data/spec/masamune/helpers/postgres_spec.rb +95 -0
  141. data/spec/masamune/schema/catalog_spec.rb +613 -0
  142. data/spec/masamune/schema/column_spec.rb +696 -0
  143. data/spec/masamune/schema/dimension_spec.rb +137 -0
  144. data/spec/masamune/schema/event_spec.rb +75 -0
  145. data/spec/masamune/schema/fact_spec.rb +117 -0
  146. data/spec/masamune/schema/map_spec.rb +593 -0
  147. data/spec/masamune/schema/row_spec.rb +28 -0
  148. data/spec/masamune/schema/store_spec.rb +49 -0
  149. data/spec/masamune/schema/table_spec.rb +395 -0
  150. data/spec/masamune/string_format_spec.rb +60 -0
  151. data/spec/masamune/tasks/elastic_mapreduce_thor_spec.rb +57 -0
  152. data/spec/masamune/tasks/hive_thor_spec.rb +75 -0
  153. data/spec/masamune/tasks/postgres_thor_spec.rb +42 -0
  154. data/spec/masamune/tasks/shell_thor_spec.rb +51 -0
  155. data/spec/masamune/template_spec.rb +77 -0
  156. data/spec/masamune/thor_spec.rb +238 -0
  157. data/spec/masamune/transform/bulk_upsert.dimension_spec.rb +200 -0
  158. data/spec/masamune/transform/consolidate_dimension_spec.rb +62 -0
  159. data/spec/masamune/transform/deduplicate_dimension_spec.rb +84 -0
  160. data/spec/masamune/transform/define_event_view_spec.rb +84 -0
  161. data/spec/masamune/transform/define_schema_spec.rb +83 -0
  162. data/spec/masamune/transform/define_table.dimension_spec.rb +306 -0
  163. data/spec/masamune/transform/define_table.fact_spec.rb +291 -0
  164. data/spec/masamune/transform/define_table.table_spec.rb +525 -0
  165. data/spec/masamune/transform/insert_reference_values.dimension_spec.rb +111 -0
  166. data/spec/masamune/transform/insert_reference_values.fact_spec.rb +149 -0
  167. data/spec/masamune/transform/load_dimension_spec.rb +76 -0
  168. data/spec/masamune/transform/load_fact_spec.rb +89 -0
  169. data/spec/masamune/transform/relabel_dimension_spec.rb +102 -0
  170. data/spec/masamune/transform/rollup_fact_spec.rb +333 -0
  171. data/spec/masamune/transform/snapshot_dimension_spec.rb +103 -0
  172. data/spec/masamune/transform/stage_dimension_spec.rb +115 -0
  173. data/spec/masamune/transform/stage_fact_spec.rb +204 -0
  174. data/spec/masamune_spec.rb +32 -0
  175. data/spec/spec_helper.rb +41 -0
  176. data/spec/support/masamune/example_group.rb +36 -0
  177. data/spec/support/masamune/mock_command.rb +99 -0
  178. data/spec/support/masamune/mock_delegate.rb +51 -0
  179. data/spec/support/masamune/mock_filesystem.rb +96 -0
  180. data/spec/support/masamune/thor_mute.rb +35 -0
  181. data/spec/support/rspec/example/action_example_group.rb +34 -0
  182. data/spec/support/rspec/example/task_example_group.rb +80 -0
  183. data/spec/support/rspec/example/transform_example_group.rb +36 -0
  184. data/spec/support/shared_examples/postgres_common_examples.rb +53 -0
  185. metadata +462 -0
@@ -0,0 +1,95 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2014-2015, VMware, Inc. All Rights Reserved.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'spec_helper'
24
+
25
+ describe Masamune::Helpers::Postgres do
26
+ let(:environment) { double }
27
+ let(:instance) { described_class.new(environment) }
28
+
29
+ describe '#table_exists' do
30
+ before do
31
+ expect(instance).to receive(:database_exists?).and_return(true)
32
+ expect(instance).to receive(:postgres).with(hash_including(:exec, :tuple_output)).and_yield(' foo').and_yield(' bar').and_yield(' baz')
33
+ end
34
+
35
+ subject { instance.table_exists?(table) }
36
+
37
+ context 'when table exists' do
38
+ let(:table) { 'foo' }
39
+ it { is_expected.to eq(true) }
40
+ end
41
+
42
+ context 'when other table exists' do
43
+ let(:table) { 'baz' }
44
+ it { is_expected.to eq(true) }
45
+ end
46
+
47
+ context 'when table does not exist' do
48
+ let(:table) { 'zombo' }
49
+ it { is_expected.to eq(false) }
50
+ end
51
+ end
52
+
53
+ describe '#table_last_modified_at' do
54
+ subject { instance.table_last_modified_at('foo', options) }
55
+
56
+ context 'with last_modified_at option' do
57
+ before do
58
+ expect(instance).to receive(:table_exists?).and_return(true)
59
+ expect(instance).to receive(:postgres).with(hash_including(:exec, :tuple_output)).and_yield(output)
60
+ end
61
+
62
+ let(:options) { { last_modified_at: 'last_modified_at' } }
63
+
64
+ context 'with expected output' do
65
+ let(:output) { ' 2014-06-04 10:20:19.539656-07' }
66
+
67
+ it { is_expected.to be_a(Time) }
68
+ it { is_expected.to eq(Time.parse('2014-06-04 17:20:00 +0000')) }
69
+ end
70
+
71
+ context 'with blank output' do
72
+ let(:output) { ' ' }
73
+
74
+ it { is_expected.to be_nil }
75
+ end
76
+
77
+ context 'with invalid output' do
78
+ let(:output) { ' 2XXX' }
79
+
80
+ it { is_expected.to be_nil }
81
+ end
82
+ end
83
+
84
+ context 'without last_modified_at option' do
85
+ let(:options) { {} }
86
+
87
+ before do
88
+ expect(instance).to receive(:table_exists?).never
89
+ expect(instance).to receive(:postgres).never
90
+ end
91
+
92
+ it { is_expected.to be_nil }
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,613 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2014-2015, VMware, Inc. All Rights Reserved.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'spec_helper'
24
+
25
+ describe Masamune::Schema::Catalog do
26
+ let(:environment) { double }
27
+ let(:instance) { described_class.new(environment) }
28
+ let(:postgres) { instance.postgres }
29
+ let(:hive) { instance.hive }
30
+ let(:files) { instance.files }
31
+
32
+ describe '#method_missing' do
33
+ before do
34
+ instance.schema :postgres do
35
+ dimension 'foo', type: :two
36
+ end
37
+ end
38
+
39
+ it { expect(postgres.foo_dimension.id).to eq(:foo) }
40
+ it { expect(postgres.bar_dimension).to be_nil }
41
+ it { expect { postgres.foo_baz }.to raise_error ArgumentError, "unknown attribute type 'baz'" }
42
+ end
43
+
44
+ describe '#[]' do
45
+ context 'with :postgres' do
46
+ subject { instance[:postgres] }
47
+ it { is_expected.to eq(postgres) }
48
+ end
49
+ context 'with :hive' do
50
+ subject { instance[:hive] }
51
+ it { is_expected.to eq(hive) }
52
+ end
53
+ context 'with :mysql' do
54
+ subject { instance[:mysql] }
55
+ it { expect { subject }.to raise_error ArgumentError, "unknown type: 'mysql'" }
56
+ end
57
+ end
58
+
59
+ context '#load' do
60
+ let(:postgres_extra) { %w(/tmp/schema.psql /tmp/00_schema.psql /tmp/20_schema.psql) }
61
+ let(:hive_extra) { %w(/tmp/schema.hql /tmp/00_schema.hql /tmp/20_schema.hql) }
62
+ let(:extra) { postgres_extra + hive_extra }
63
+
64
+ before do
65
+ extra.each do |e|
66
+ instance.load(e)
67
+ end
68
+ end
69
+
70
+ it 'should load postgres extra in order' do
71
+ expect(instance.postgres.extra).to eq(postgres_extra)
72
+ expect(instance.postgres.extra(:pre).size).to eq(2)
73
+ expect(instance.postgres.extra(:post).size).to eq(1)
74
+ end
75
+
76
+ it 'should load hive extra in order' do
77
+ expect(instance.hive.extra).to eq(hive_extra)
78
+ expect(instance.hive.extra(:pre).size).to eq(2)
79
+ expect(instance.hive.extra(:post).size).to eq(1)
80
+ end
81
+ end
82
+
83
+ describe '#schema' do
84
+ context 'when schema does not define store' do
85
+ subject(:schema) do
86
+ instance.schema do
87
+ dimension 'foo', type: :two
88
+ dimension 'bar', type: :two
89
+ end
90
+ end
91
+
92
+ it { expect { schema }.to raise_error ArgumentError, "schema store arguments required" }
93
+ end
94
+
95
+ context 'when schema defines unknown store' do
96
+ subject(:schema) do
97
+ instance.schema :mysql do
98
+ dimension 'foo', type: :two
99
+ dimension 'bar', type: :two
100
+ end
101
+ end
102
+
103
+ it { expect { schema }.to raise_error ArgumentError, "unknown type: 'mysql'" }
104
+ end
105
+
106
+ context 'when schema contains dimensions' do
107
+ before do
108
+ instance.schema :postgres do
109
+ dimension 'foo', type: :two
110
+ dimension 'bar', type: :two
111
+ end
112
+ end
113
+
114
+ it { expect(postgres.dimensions).to include :foo }
115
+ it { expect(postgres.dimensions).to include :bar }
116
+ it { expect(postgres.foo_dimension.id).to eq(:foo) }
117
+ it { expect(postgres.bar_dimension.id).to eq(:bar) }
118
+ end
119
+
120
+ context 'when schema contains columns' do
121
+ before do
122
+ instance.schema :postgres do
123
+ dimension 'table_one', type: :two do
124
+ column 'column_one'
125
+ column 'column_two'
126
+ end
127
+
128
+ dimension 'table_two', type: :two do
129
+ column 'column_three'
130
+ column 'column_four'
131
+ end
132
+ end
133
+ end
134
+
135
+ let(:table_one_columns) { postgres.table_one_dimension.columns }
136
+ let(:table_two_columns) { postgres.table_two_dimension.columns }
137
+
138
+ it { expect(table_one_columns).to include :column_one }
139
+ it { expect(table_one_columns).to include :column_two }
140
+ it { expect(table_one_columns).to_not include :column_three }
141
+ it { expect(table_one_columns).to_not include :column_four }
142
+ it { expect(table_two_columns).to_not include :column_one }
143
+ it { expect(table_two_columns).to_not include :column_two }
144
+ it { expect(table_two_columns).to include :column_three }
145
+ it { expect(table_two_columns).to include :column_four }
146
+ end
147
+
148
+ context 'when schema contains columns and rows' do
149
+ before do
150
+ instance.schema :postgres do
151
+ dimension 'table_one', type: :two do
152
+ column 'column_one', type: :integer
153
+ column 'column_two', type: :string
154
+ row column_one: 1, column_two: 'a'
155
+ row column_one: 2, column_two: 'b'
156
+ end
157
+ end
158
+ end
159
+
160
+ let(:table_one_rows) { postgres.table_one_dimension.rows }
161
+
162
+ it { expect(table_one_rows[0].values).to include(column_one: 1, column_two: 'a') }
163
+ it { expect(table_one_rows[1].values).to include(column_one: 2, column_two: 'b') }
164
+ end
165
+
166
+ context 'when schema contains references' do
167
+ before do
168
+ instance.schema :postgres do
169
+ dimension 'foo', type: :one
170
+ dimension 'bar', type: :one
171
+ dimension 'baz', type: :two do
172
+ references :foo
173
+ references :bar, label: :quux
174
+ end
175
+ end
176
+ end
177
+
178
+ subject(:references) { postgres.baz_dimension.references }
179
+
180
+ it { is_expected.to include :foo }
181
+ it { is_expected.to include :quux_bar }
182
+ it { expect(references[:foo].label).to be_nil }
183
+ it { expect(references[:quux_bar].label).to eq(:quux) }
184
+ end
185
+
186
+ context 'when schema contains overrides' do
187
+ before do
188
+ instance.schema :postgres do
189
+ dimension 'cluster', type: :mini do
190
+ column 'uuid', type: :uuid, surrogate_key: true
191
+ column 'name', type: :string, unique: true
192
+ column 'description', type: :string
193
+
194
+ row name: 'current_database()', attributes: {default: true}
195
+ end
196
+ end
197
+ end
198
+
199
+ subject { postgres.cluster_dimension.columns }
200
+
201
+ it { is_expected.to include :uuid }
202
+ it { is_expected.to_not include :id }
203
+ end
204
+
205
+ context 'when schema contains facts' do
206
+ before do
207
+ instance.schema :postgres do
208
+ dimension 'dimension_one', type: :two do
209
+ column 'column_one'
210
+ column 'column_two'
211
+ end
212
+
213
+ fact 'fact_one' do
214
+ references :dimension_one
215
+ measure 'measure_one', aggregate: :sum
216
+ end
217
+
218
+ fact 'fact_two' do
219
+ references :dimension_one
220
+ measure 'measure_two', aggregate: :average
221
+ end
222
+ end
223
+ end
224
+
225
+ let(:fact_one) { postgres.fact_one_fact }
226
+ let(:fact_two) { postgres.fact_two_fact }
227
+
228
+ it { expect(fact_one.references).to include :dimension_one}
229
+ it { expect(fact_one.measures).to include :measure_one }
230
+ it { expect(fact_one.measures[:measure_one].aggregate).to eq(:sum) }
231
+ it { expect(fact_two.references).to include :dimension_one}
232
+ it { expect(fact_two.measures).to include :measure_two }
233
+ it { expect(fact_two.measures[:measure_two].aggregate).to eq(:average) }
234
+ end
235
+
236
+ context 'when schema contains fact with partition table' do
237
+ before do
238
+ instance.schema :hive do
239
+ fact 'visits', partition: 'y%Ym%m' do
240
+ measure 'count', aggregate: :sum
241
+ end
242
+ end
243
+ end
244
+
245
+ it { expect(hive.visits_fact.partition).to eq('y%Ym%m') }
246
+ it { expect(hive.visits_fact.measures).to include :count }
247
+ it { expect(hive.visits_fact.measures[:count].aggregate).to eq(:sum) }
248
+ end
249
+
250
+ context 'when schema contains fact with partition columns' do
251
+ before do
252
+ instance.schema :hive do
253
+ fact 'visits' do
254
+ partition 'y', type: :integer
255
+ partition 'm', type: :integer
256
+ partition 'd', type: :integer
257
+ measure 'count', aggregate: :sum
258
+ end
259
+ end
260
+ end
261
+
262
+ it { expect(hive.visits_fact.partitions).to include :y }
263
+ it { expect(hive.visits_fact.partitions).to include :m }
264
+ it { expect(hive.visits_fact.partitions).to include :d }
265
+ it { expect(hive.visits_fact.measures).to include :count }
266
+ it { expect(hive.visits_fact.measures[:count].aggregate).to eq(:sum) }
267
+ end
268
+
269
+ context 'when schema contains fact with degenerate dimension references' do
270
+ before do
271
+ instance.schema :hive do
272
+ fact 'visits' do
273
+ references :message_kind, degenerate: true
274
+ measure 'count', aggregate: :sum
275
+ end
276
+ end
277
+ end
278
+
279
+ it { expect(hive.visits_fact.references).to include :message_kind }
280
+ it { expect(hive.visits_fact.columns).to include :message_kind_type_id }
281
+ it { expect(hive.visits_fact.measures).to include :count }
282
+ it { expect(hive.visits_fact.measures[:count].aggregate).to eq(:sum) }
283
+ end
284
+
285
+ context 'when schema contains fact with a single grain' do
286
+ before do
287
+ instance.schema :postgres do
288
+ dimension 'user', type: :two do
289
+ column 'user_id'
290
+ end
291
+
292
+ fact 'visits', grain: 'hourly' do
293
+ references :user
294
+ measure 'count'
295
+ end
296
+ end
297
+ end
298
+
299
+ let(:visits_hourly) { postgres.visits_hourly_fact }
300
+
301
+ it { expect(visits_hourly.name).to eq('visits_hourly_fact') }
302
+ it { expect(visits_hourly.references).to include :user }
303
+ it { expect(visits_hourly.measures).to include :count }
304
+ end
305
+
306
+ context 'when schema contains fact with multiple grain' do
307
+ before do
308
+ instance.schema :postgres do
309
+ dimension 'user', type: :two do
310
+ column 'user_id'
311
+ end
312
+
313
+ fact 'visits', grain: %w(hourly daily monthly) do
314
+ references :user
315
+ measure 'count'
316
+ end
317
+ end
318
+ end
319
+
320
+ let(:visits_hourly) { postgres.visits_hourly_fact }
321
+ let(:visits_daily) { postgres.visits_daily_fact }
322
+ let(:visits_monthly) { postgres.visits_monthly_fact }
323
+
324
+ it { expect(visits_hourly.name).to eq('visits_hourly_fact') }
325
+ it { expect(visits_hourly.references).to include :user }
326
+ it { expect(visits_hourly.measures).to include :count }
327
+ it { expect(visits_daily.name).to eq('visits_daily_fact') }
328
+ it { expect(visits_daily.references).to include :user }
329
+ it { expect(visits_daily.measures).to include :count }
330
+ it { expect(visits_monthly.name).to eq('visits_monthly_fact') }
331
+ it { expect(visits_monthly.references).to include :user }
332
+ it { expect(visits_monthly.measures).to include :count }
333
+ end
334
+
335
+ context 'when schema contains events' do
336
+ before do
337
+ instance.schema :hive do
338
+ event 'event_one' do
339
+ attribute 'attribute_one'
340
+ attribute 'attribute_two'
341
+ end
342
+
343
+ event 'event_two' do
344
+ attribute 'attribute_three'
345
+ attribute 'attribute_four'
346
+ end
347
+ end
348
+ end
349
+
350
+ let(:event_one) { hive.event_one_event }
351
+ let(:event_two) { hive.event_two_event }
352
+
353
+ it { expect(event_one.attributes).to include :attribute_one }
354
+ it { expect(event_one.attributes).to include :attribute_two }
355
+ it { expect(event_two.attributes).to include :attribute_three }
356
+ it { expect(event_two.attributes).to include :attribute_four }
357
+ end
358
+
359
+ context 'when schema contains file' do
360
+ before do
361
+ instance.schema :postgres do
362
+ dimension 'user_account', type: :mini do
363
+ column 'name', type: :string
364
+ end
365
+ end
366
+
367
+ instance.schema :files do
368
+ file 'users' do
369
+ column 'postgres.user_account.name', type: :string
370
+ column 'admin', type: :boolean
371
+ end
372
+ end
373
+ end
374
+
375
+ subject(:file) { files.users }
376
+
377
+ it 'should expect dot notation column names to reference dimensions' do
378
+ expect(file.columns).to include :user_account_type_name
379
+ expect(file.columns).to include :admin
380
+ expect(file.columns[:user_account_type_name].reference).to eq(postgres.dimensions[:user_account])
381
+ expect(file.columns[:admin].reference).to be_nil
382
+ end
383
+ end
384
+
385
+ context 'when schema contains file with headers & format override' do
386
+ before do
387
+ instance.schema :postgres do
388
+ file 'override', headers: false, format: :tsv do; end
389
+ file 'default' do; end
390
+ end
391
+ end
392
+
393
+ it 'should override store format' do
394
+ expect(postgres.headers).to eq(true)
395
+ expect(postgres.format).to eq(:csv)
396
+ expect(postgres.override_file.store.headers).to eq(false)
397
+ expect(postgres.override_file.store.format).to eq(:tsv)
398
+ expect(postgres.default_file.store.headers).to eq(true)
399
+ expect(postgres.default_file.store.format).to eq(:csv)
400
+ end
401
+ end
402
+
403
+ context 'when schema contains file with invalid reference' do
404
+ subject(:schema) do
405
+ instance.schema :postgres do
406
+ file 'users' do
407
+ column 'user_account.name', type: :string
408
+ column 'admin', type: :boolean
409
+ end
410
+ end
411
+ end
412
+
413
+ it 'should raise an exception' do
414
+ expect { schema }.to raise_error /dimension user_account not defined/
415
+ end
416
+ end
417
+
418
+ context 'when schema contains map from: file' do
419
+ before do
420
+ instance.schema :postgres do
421
+ dimension 'user_account_state', type: :mini do
422
+ column 'name', type: :string
423
+ end
424
+
425
+ dimension 'user', type: :two do
426
+ references :user_account_state
427
+ column 'tenant_id', type: :integer, natural_key: true
428
+ column 'user_id', type: :integer, natural_key: true
429
+ end
430
+ end
431
+
432
+ instance.schema :files do
433
+ file 'users' do
434
+ column 'id', type: :integer
435
+ column 'tenant_id', type: :integer
436
+ column 'updated_at', type: :timestamp
437
+ column 'deleted_at', type: :timestamp
438
+ end
439
+
440
+ map from: files.users, to: postgres.user_dimension do |row|
441
+ {
442
+ 'tenant_id' => row[:tenant_id],
443
+ 'user_id' => row[:id],
444
+ 'user_account_state.name' => row[:deleted_at] ? 'deleted' : 'active',
445
+ 'start_at' => row[:updated_at],
446
+ 'delta' => 0
447
+ }
448
+ end
449
+ end
450
+ end
451
+
452
+ subject(:map) { files.users.map(to: postgres.user_dimension) }
453
+
454
+ it 'constructs map' do
455
+ expect(map.function).to_not be_nil
456
+ end
457
+ end
458
+
459
+ context 'when schema contains map from: event' do
460
+ before do
461
+ instance.schema :postgres do
462
+ dimension 'user', type: :mini do
463
+ column 'user_id', type: :integer, natural_key: true
464
+ column 'name', type: :string
465
+ end
466
+
467
+ event 'users' do
468
+ attribute 'id', type: :integer, immutable: true
469
+ attribute 'name', type: :string
470
+ end
471
+
472
+ map from: postgres.users_event, to: postgres.user_dimension do |row|
473
+ {
474
+ 'user_id' => row[:id],
475
+ 'name' => row[:name_now]
476
+ }
477
+ end
478
+ end
479
+ end
480
+
481
+ subject(:map) { postgres.users_event.map(to: postgres.user_dimension) }
482
+
483
+ it 'constructs map' do
484
+ expect(map.function).to_not be_nil
485
+ end
486
+ end
487
+
488
+ context 'when schema contains map missing the from: field' do
489
+ subject(:schema) do
490
+ instance.schema :postgres do
491
+ map do |row|
492
+ {
493
+ id: row[:id]
494
+ }
495
+ end
496
+ end
497
+ end
498
+
499
+ it 'should raise an exception' do
500
+ expect { schema }.to raise_error /invalid map, from: is missing/
501
+ end
502
+ end
503
+
504
+ context 'when schema contains map with invalid options' do
505
+ subject(:schema) do
506
+ instance.schema :postgres do
507
+ map :x do |row|
508
+ {
509
+ id: row[:id]
510
+ }
511
+ end
512
+ end
513
+ end
514
+
515
+ it 'should raise an exception' do
516
+ expect { schema }.to raise_error /invalid map, from: is missing/
517
+ end
518
+ end
519
+
520
+ context 'when schema contains map missing the to: field' do
521
+ subject(:schema) do
522
+ instance.schema :postgres do
523
+ file 'users' do; end
524
+
525
+ map from: postgres.users_file do
526
+ field 'tenant_id'
527
+ end
528
+ end
529
+ end
530
+
531
+ it 'should raise an exception' do
532
+ expect { schema }.to raise_error /invalid map from: 'users', to: is missing/
533
+ end
534
+ end
535
+
536
+ context 'when schema addressed with symbols' do
537
+ before do
538
+ instance.schema :postgres do
539
+ dimension 'user', type: :one do; end
540
+ file 'users' do; end
541
+
542
+ map from: postgres.files[:users], to: postgres.dimensions[:user] do
543
+ field 'tenant_id'
544
+ end
545
+ end
546
+ end
547
+
548
+ subject(:map) { postgres.files[:users].map(to: postgres.dimensions[:user]) }
549
+
550
+ it 'should construct map' do
551
+ is_expected.to_not be_nil
552
+ end
553
+ end
554
+
555
+ context 'when schema addressed with strings' do
556
+ before do
557
+ instance.schema :postgres do
558
+ dimension 'user', type: :one do; end
559
+ file 'users' do; end
560
+
561
+ map from: postgres.files['users'], to: postgres.dimensions['user'] do
562
+ field 'tenant_id'
563
+ end
564
+ end
565
+ end
566
+
567
+ subject(:map) { postgres.files['users'].map(to: postgres.dimensions['user']) }
568
+
569
+ it 'should construct map' do
570
+ is_expected.to_not be_nil
571
+ end
572
+ end
573
+ end
574
+
575
+ describe '.dereference_column' do
576
+ before do
577
+ instance.schema :postgres do
578
+ dimension 'table_one', type: :two do
579
+ column 'column_one'
580
+ end
581
+
582
+ dimension 'table_two', type: :two do
583
+ references :table_one
584
+ references :table_one, label: :label_one
585
+
586
+ column 'column_two'
587
+ end
588
+ end
589
+ end
590
+
591
+ subject(:result) { postgres.dereference_column(input) }
592
+
593
+ context 'with a column name' do
594
+ let(:input) { 'column_two' }
595
+ it { expect(result.name).to eq(:column_two) }
596
+ end
597
+
598
+ context 'with a table.column name' do
599
+ let(:input) { 'table_one.column_one' }
600
+ it { expect(result.name).to eq(:table_one_dimension_column_one) }
601
+ end
602
+
603
+ context 'with a labeled table.column name' do
604
+ let(:input) { 'label_one_table_one.column_one' }
605
+ it { expect(result.name).to eq(:label_one_table_one_dimension_column_one) }
606
+ end
607
+
608
+ context 'with a undefined table.column name' do
609
+ let(:input) { 'undef.column_one' }
610
+ it { expect { result }.to raise_error ArgumentError, /dimension undef not defined/ }
611
+ end
612
+ end
613
+ end