martyr 0.1.74.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.tags +868 -0
  7. data/.travis.yml +3 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +265 -0
  11. data/Rakefile +1 -0
  12. data/TODO.txt +54 -0
  13. data/bin/console +62 -0
  14. data/bin/setup +7 -0
  15. data/lib/martyr/base_cube.rb +73 -0
  16. data/lib/martyr/cube.rb +134 -0
  17. data/lib/martyr/dimension_reference.rb +26 -0
  18. data/lib/martyr/errors.rb +20 -0
  19. data/lib/martyr/helpers/delegators.rb +17 -0
  20. data/lib/martyr/helpers/intervals.rb +222 -0
  21. data/lib/martyr/helpers/metric_id_standardizer.rb +47 -0
  22. data/lib/martyr/helpers/registrable.rb +15 -0
  23. data/lib/martyr/helpers/sorter.rb +79 -0
  24. data/lib/martyr/helpers/translations.rb +34 -0
  25. data/lib/martyr/level_concern/has_level_collection.rb +11 -0
  26. data/lib/martyr/level_concern/level.rb +45 -0
  27. data/lib/martyr/level_concern/level_collection.rb +60 -0
  28. data/lib/martyr/level_concern/level_comparator.rb +45 -0
  29. data/lib/martyr/level_concern/level_definitions_by_dimension.rb +24 -0
  30. data/lib/martyr/runtime/data_set/coordinates.rb +108 -0
  31. data/lib/martyr/runtime/data_set/element.rb +66 -0
  32. data/lib/martyr/runtime/data_set/element_common.rb +51 -0
  33. data/lib/martyr/runtime/data_set/element_locator.rb +143 -0
  34. data/lib/martyr/runtime/data_set/fact.rb +83 -0
  35. data/lib/martyr/runtime/data_set/fact_indexer.rb +72 -0
  36. data/lib/martyr/runtime/data_set/future_fact_value.rb +58 -0
  37. data/lib/martyr/runtime/data_set/future_metric.rb +40 -0
  38. data/lib/martyr/runtime/data_set/virtual_element.rb +131 -0
  39. data/lib/martyr/runtime/data_set/virtual_elements_builder.rb +202 -0
  40. data/lib/martyr/runtime/dimension_scopes/base_level_scope.rb +20 -0
  41. data/lib/martyr/runtime/dimension_scopes/degenerate_level_scope.rb +76 -0
  42. data/lib/martyr/runtime/dimension_scopes/dimension_scope_collection.rb +78 -0
  43. data/lib/martyr/runtime/dimension_scopes/level_scope_collection.rb +20 -0
  44. data/lib/martyr/runtime/dimension_scopes/query_level_scope.rb +223 -0
  45. data/lib/martyr/runtime/fact_scopes/base_fact_scope.rb +62 -0
  46. data/lib/martyr/runtime/fact_scopes/fact_scope_collection.rb +127 -0
  47. data/lib/martyr/runtime/fact_scopes/main_fact_scope.rb +7 -0
  48. data/lib/martyr/runtime/fact_scopes/null_scope.rb +7 -0
  49. data/lib/martyr/runtime/fact_scopes/sub_fact_scope.rb +16 -0
  50. data/lib/martyr/runtime/fact_scopes/wrapped_fact_scope.rb +11 -0
  51. data/lib/martyr/runtime/pivot/pivot_axis.rb +67 -0
  52. data/lib/martyr/runtime/pivot/pivot_cell.rb +54 -0
  53. data/lib/martyr/runtime/pivot/pivot_grain_element.rb +22 -0
  54. data/lib/martyr/runtime/pivot/pivot_row.rb +49 -0
  55. data/lib/martyr/runtime/pivot/pivot_table.rb +109 -0
  56. data/lib/martyr/runtime/pivot/pivot_table_builder.rb +125 -0
  57. data/lib/martyr/runtime/query/metric_dependency_resolver.rb +149 -0
  58. data/lib/martyr/runtime/query/query_context.rb +246 -0
  59. data/lib/martyr/runtime/query/query_context_builder.rb +215 -0
  60. data/lib/martyr/runtime/scope_operators/base_operator.rb +113 -0
  61. data/lib/martyr/runtime/scope_operators/group_operator.rb +18 -0
  62. data/lib/martyr/runtime/scope_operators/select_operator_for_dimension.rb +24 -0
  63. data/lib/martyr/runtime/scope_operators/select_operator_for_metric.rb +35 -0
  64. data/lib/martyr/runtime/scope_operators/where_operator_for_dimension.rb +43 -0
  65. data/lib/martyr/runtime/scope_operators/where_operator_for_metric.rb +25 -0
  66. data/lib/martyr/runtime/slices/data_slices/data_slice.rb +70 -0
  67. data/lib/martyr/runtime/slices/data_slices/metric_data_slice.rb +54 -0
  68. data/lib/martyr/runtime/slices/data_slices/plain_dimension_data_slice.rb +109 -0
  69. data/lib/martyr/runtime/slices/data_slices/time_dimension_data_slice.rb +9 -0
  70. data/lib/martyr/runtime/slices/has_scoped_levels.rb +29 -0
  71. data/lib/martyr/runtime/slices/memory_slices/TO_DELETE.md +188 -0
  72. data/lib/martyr/runtime/slices/memory_slices/memory_slice.rb +84 -0
  73. data/lib/martyr/runtime/slices/memory_slices/metric_memory_slice.rb +59 -0
  74. data/lib/martyr/runtime/slices/memory_slices/plain_dimension_memory_slice.rb +48 -0
  75. data/lib/martyr/runtime/slices/scopeable_slice_data.rb +73 -0
  76. data/lib/martyr/runtime/slices/slice_definitions/base_slice_definition.rb +30 -0
  77. data/lib/martyr/runtime/slices/slice_definitions/metric_slice_definition.rb +120 -0
  78. data/lib/martyr/runtime/slices/slice_definitions/plain_dimension_level_slice_definition.rb +26 -0
  79. data/lib/martyr/runtime/sub_cubes/fact_filler_strategies.rb +61 -0
  80. data/lib/martyr/runtime/sub_cubes/query_metrics.rb +56 -0
  81. data/lib/martyr/runtime/sub_cubes/sub_cube.rb +134 -0
  82. data/lib/martyr/runtime/sub_cubes/sub_cube_grain.rb +117 -0
  83. data/lib/martyr/schema/dimension_associations/dimension_association_collection.rb +33 -0
  84. data/lib/martyr/schema/dimension_associations/level_association.rb +37 -0
  85. data/lib/martyr/schema/dimension_associations/level_association_collection.rb +18 -0
  86. data/lib/martyr/schema/dimensions/dimension_definition_collection.rb +39 -0
  87. data/lib/martyr/schema/dimensions/plain_dimension_definition.rb +39 -0
  88. data/lib/martyr/schema/dimensions/time_dimension_definition.rb +24 -0
  89. data/lib/martyr/schema/facts/base_fact_definition.rb +22 -0
  90. data/lib/martyr/schema/facts/fact_definition_collection.rb +44 -0
  91. data/lib/martyr/schema/facts/main_fact_definition.rb +45 -0
  92. data/lib/martyr/schema/facts/sub_fact_definition.rb +44 -0
  93. data/lib/martyr/schema/metrics/base_metric.rb +77 -0
  94. data/lib/martyr/schema/metrics/built_in_metric.rb +38 -0
  95. data/lib/martyr/schema/metrics/count_distinct_metric.rb +172 -0
  96. data/lib/martyr/schema/metrics/custom_metric.rb +26 -0
  97. data/lib/martyr/schema/metrics/custom_rollup.rb +31 -0
  98. data/lib/martyr/schema/metrics/dependency_inferrer.rb +150 -0
  99. data/lib/martyr/schema/metrics/metric_definition_collection.rb +94 -0
  100. data/lib/martyr/schema/named_scopes/named_scope.rb +19 -0
  101. data/lib/martyr/schema/named_scopes/named_scope_collection.rb +42 -0
  102. data/lib/martyr/schema/plain_dimension_levels/base_level_definition.rb +39 -0
  103. data/lib/martyr/schema/plain_dimension_levels/degenerate_level_definition.rb +75 -0
  104. data/lib/martyr/schema/plain_dimension_levels/level_definition_collection.rb +15 -0
  105. data/lib/martyr/schema/plain_dimension_levels/query_level_definition.rb +99 -0
  106. data/lib/martyr/version.rb +3 -0
  107. data/lib/martyr/virtual_cube.rb +74 -0
  108. data/lib/martyr.rb +55 -0
  109. data/martyr.gemspec +41 -0
  110. metadata +296 -0
@@ -0,0 +1,215 @@
1
+ module Martyr
2
+ module Runtime
3
+ # This is a SubCube builder. The builder method is #execute.
4
+ # It takes the `select`, `slice`, and `granulate` clauses, makes sure they are well defined, and create a sub cube
5
+ # as a result of their application.
6
+ class QueryContextBuilder
7
+
8
+ include Martyr::LevelComparator
9
+ include Martyr::Translations
10
+
11
+ attr_reader :cube, :scope_helper_module
12
+ delegate :elements, :facts, :pivot, :total, :totals, to: :build
13
+
14
+ def initialize(cube, scope_helper_module)
15
+ @cube = cube
16
+ @metric_dependency_resolver = MetricDependencyResolver.new(cube)
17
+ @data_slice = {}
18
+ @granulate_args = []
19
+ @decorations = {}
20
+ @scope_helper_module = scope_helper_module
21
+ extend_scope_helper
22
+ end
23
+
24
+ # select(:a, :b, :c)
25
+ # select(:all)
26
+ def select!(*arr)
27
+ @all_metrics = true and return self if arr.length == 1 and arr.first.to_s == 'all'
28
+ standardize(arr).each do |metric_id|
29
+ @metric_dependency_resolver.add_metric(metric_id)
30
+ end
31
+ self
32
+ end
33
+
34
+ # Variant 1 - full slice as one hash:
35
+ # slice('artist.name' => {with: 'AC/DC'}, 'genre.name' => {with: 'Rock'}, quantity: {gt: 0})
36
+ #
37
+ # Variant 2 - on one dimension or metric
38
+ # slice('artist.name', with: 'AC/DC')
39
+ #
40
+ def slice!(*several_variants)
41
+ if several_variants.length == 1 and several_variants.first.is_a?(Hash)
42
+ several_variants.first.stringify_keys.except(PivotCell::METRIC_COORD_KEY).each do |slice_on, slice_definition|
43
+ @data_slice.merge! standardize(slice_on) => slice_definition
44
+ end
45
+ elsif several_variants.length == 2
46
+ slice_on, slice_definition = several_variants
47
+ @data_slice.merge! standardize(slice_on) => slice_definition
48
+ else
49
+ ArgumentError.new("wrong number of arguments (#{several_variants.length} for 1..2)")
50
+ end
51
+ self
52
+ end
53
+
54
+ # granulate('artist.name', 'genre.name')
55
+ def granulate!(*arr)
56
+ @granulate_args += arr
57
+ self
58
+ end
59
+
60
+ # @param level_id [String]
61
+ # @param *args [Array] will be sent to includes
62
+ def decorate!(level_id, lambda = nil, &block)
63
+ @decorations[level_id] = lambda || block
64
+ self
65
+ end
66
+
67
+ def select(*args)
68
+ data_dup.select!(*args)
69
+ end
70
+
71
+ def slice(*args)
72
+ data_dup.slice!(*args)
73
+ end
74
+
75
+ def granulate(*args)
76
+ data_dup.granulate!(*args)
77
+ end
78
+
79
+ def decorate(*args, &block)
80
+ data_dup.decorate!(*args, &block)
81
+ end
82
+
83
+ def build
84
+ return @context if @context
85
+ context = QueryContext.new
86
+ add_metric_slices_to_metric_dependencies
87
+ add_all_metrics_if_none_selected
88
+ setup_context_grain_and_metrics(context)
89
+ setup_context_dimension_scopes(context)
90
+ setup_context_sub_cubes_metrics_and_grain_and_sub_facts(context)
91
+ setup_context_data_slice(context)
92
+ setup_virtual_cube(context)
93
+ decorate_all_scopes(context)
94
+ @context = context
95
+ end
96
+
97
+ private
98
+
99
+ # = Building steps
100
+
101
+ def add_metric_slices_to_metric_dependencies
102
+ @data_slice.keys.select{ |slice_on| cube.metric?(slice_on) }.each do |metric_id|
103
+ @metric_dependency_resolver.add_metric(metric_id, explicit: false)
104
+ end
105
+ end
106
+
107
+ def add_all_metrics_if_none_selected
108
+ return unless @all_metrics
109
+ return if @metric_dependency_resolver.metric_ids.present?
110
+ cube.metrics.values.each do |metric|
111
+ @metric_dependency_resolver.add_metric(metric.id)
112
+ end
113
+ end
114
+
115
+ # Step 1
116
+ # Add all levels to the query grain
117
+ def setup_context_grain_and_metrics(context)
118
+ context.level_ids_in_grain = Array.wrap(@granulate_args)
119
+ context.metrics = @metric_dependency_resolver.metrics
120
+ end
121
+
122
+ # Step 2 (relies on Steps 1)
123
+ # Build the dimension scope objects supported by the grain AND slices.
124
+ # DegeneratesAndBottomLevels.granulate('genres.name', 'media_types.name').slice('customers.country', with: 'USA')
125
+ def setup_context_dimension_scopes(context)
126
+ relevant_dimensions = (default_grains + context.level_ids_in_grain + @data_slice.keys).map { |x| first_element_from_id(x) }
127
+ context.dimension_scopes = cube.build_dimension_scopes(relevant_dimensions.uniq)
128
+
129
+ @decorations.each do |level_id, proc|
130
+ context.dimension_scopes.find_level(level_id).decorate_scope(&proc)
131
+ end
132
+ end
133
+
134
+ # Step 3
135
+ # Setup the sub cubes, metrics, and grain for all cubes. Note that each cube only takes the levels of dimensions
136
+ # it supports. That said, if dimension has levels L1, L2, L3 and a cube supports only L1 and L2, if L3 is
137
+ # requested it will yield an empty cube.
138
+ # If no metrics were given, select all.
139
+ # If no grain was given, select all levels - separately for each cube.
140
+ def setup_context_sub_cubes_metrics_and_grain_and_sub_facts(context)
141
+ cube.contained_cube_classes.index_by(&:cube_name).each do |cube_name, cube_class|
142
+ metric_ids = @metric_dependency_resolver.metric_ids_for(cube_name, all: true)
143
+ grain = (@granulate_args + @metric_dependency_resolver.inferred_fact_grain_for(cube_name) +
144
+ cube_class.default_fact_grain).uniq
145
+ sub_facts = @metric_dependency_resolver.sub_facts_for(cube_name)
146
+
147
+ sub_cube = Runtime::SubCube.new(context, cube_class)
148
+ context.sub_cubes_hash[cube_name] = sub_cube
149
+ sub_cube.set_metrics(metric_ids)
150
+ sub_cube.set_grain(grain)
151
+ sub_cube.set_sub_facts(sub_facts)
152
+ end
153
+ end
154
+
155
+ # Step 4
156
+ # Sets the data slice. Note that no slicing actually occurs - only setup.
157
+ def setup_context_data_slice(context)
158
+ @data_slice.each do |slice_on, slice_definition|
159
+ context.data_slice.slice(slice_on, slice_definition)
160
+ end
161
+ end
162
+
163
+ # Step 5
164
+ # Sets the virtual cube
165
+ def setup_virtual_cube(context)
166
+ return unless cube.virtual?
167
+ context.virtual_cube = cube
168
+ context.virtual_cube_metric_ids = @metric_dependency_resolver.metric_ids_for(cube.cube_name, all: true)
169
+ end
170
+
171
+ # Step 6 (depends on steps 3 and 4)
172
+ # All scopes are altered to represent the necessary queries - fact scopes (select, where, and group by) and
173
+ # dimension scopes (where)
174
+ def decorate_all_scopes(context)
175
+ context.data_slice.add_to_dimension_scope(context)
176
+ context.sub_cubes_hash.each do |_cube_name, sub_cube|
177
+ sub_cube.decorate_all_scopes context.data_slice.for_cube(sub_cube)
178
+ end
179
+ end
180
+
181
+ protected
182
+
183
+ def extend_scope_helper
184
+ return self unless scope_helper_module
185
+ extend(scope_helper_module)
186
+ end
187
+
188
+ private
189
+
190
+ def data_dup
191
+ dup.instance_eval do
192
+ @decorations = @decorations.dup
193
+ @data_slice = @data_slice.dup
194
+ @granulate_args = @granulate_args.dup
195
+ @metric_dependency_resolver = @metric_dependency_resolver.data_dup
196
+ extend_scope_helper
197
+ self
198
+ end
199
+ end
200
+
201
+ def standardize(object)
202
+ @standardizer ||= Martyr::MetricIdStandardizer.new(cube.contained_cube_classes.first.cube_name,
203
+ raise_if_not_ok: cube.contained_cube_classes.length > 1)
204
+
205
+ @standardizer.standardize(object)
206
+ end
207
+
208
+ def default_grains
209
+ (@metric_dependency_resolver.inferred_fact_grain +
210
+ cube.contained_cube_classes.flat_map(&:default_fact_grain)).uniq
211
+ end
212
+
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,113 @@
1
+ module Martyr
2
+ module Runtime
3
+
4
+ class BaseOperator
5
+ attr_reader :setup_block, :operation_args
6
+ attr_accessor :fact_scope
7
+
8
+ # Defines the method #add_<operator_name> to a class.
9
+ #
10
+ # @param klass [#operators] any class that has an instance method called operators
11
+ def self.register_to(klass)
12
+ current_class = self
13
+ klass.class_eval do
14
+ define_method "add_#{current_class.name.split('::').last.underscore}" do |*args, &block|
15
+ operators << current_class.new(*args, &block)
16
+ end
17
+ end
18
+ end
19
+
20
+ def initialize(&setup_block)
21
+ @setup_block = setup_block
22
+ end
23
+
24
+ def inspect
25
+ to_hash.inspect
26
+ end
27
+
28
+ def to_hash
29
+ {
30
+ self.class.name.split('::').last.underscore =>
31
+ (instance_variables - [:@setup_block, :@main_setup, :@fact_scope]).map{|x| {x => instance_variable_get(x)}}
32
+ }
33
+ end
34
+
35
+ # = Running
36
+
37
+ # There are 3 types of fact scopes that are handled:
38
+ # Inner main
39
+ # The main query fact scope.
40
+ # Inner sub
41
+ # A sub query fact scope.
42
+ # Outer wrapper
43
+ # The wrapper block on the query.
44
+ #
45
+ # Step 1
46
+ # Run the setup block if it is determined that the block can be supported for the current scope.
47
+ # The setup block is expected to run the appropriate operator "verb" on the provided operator instance, e.g.
48
+ # #add_select, #add_where, etc.
49
+ # The verb saves information that is to be applied on a fact scope - main, sub, or outer.
50
+ # The execution of the setup block is yielded self with the @fact_scope ivar initialized, so that
51
+ # support methods on self are available to the setup block.
52
+ #
53
+ # Step 2
54
+ # If the fact_scope is the main fact scope, a dup of the object is stored in @main_setup ivar, so that the
55
+ # result of running the setup block can be reused when reapplying on the outer wrapper.
56
+ #
57
+ # Step 3
58
+ # The concrete handle_inner is called for the current fact scope, which is assumed to be the main.
59
+ #
60
+
61
+ def apply_on_inner(fact_scope)
62
+ return if fact_scope.is_a?(Runtime::SubFactScope) and !run_for_sub_fact?
63
+ return unless supported_for?(fact_scope)
64
+ with_fact_scope_ivar(fact_scope) { setup_block.call(self) }
65
+ @main_setup = dup unless fact_scope.is_a?(Runtime::SubFactScope)
66
+ dup.send(:handle_inner, fact_scope)
67
+ end
68
+
69
+ def reapply_on_outer_wrapper(wrapper)
70
+ raise Internal::Error.new('@main_setup is not initialized. #apply_on_inner must be run on the ' +
71
+ 'main fact scope before calling #reapply_on_outer_wrapper') unless @main_setup.present?
72
+
73
+ @main_setup.send(:handle_outer, wrapper)
74
+ end
75
+
76
+ # Concrete classes can optionally alias their particular verb (add_where, add_group, etc.) to this method
77
+ def operate(*args)
78
+ @operation_args = args
79
+ end
80
+
81
+ private
82
+
83
+ # = Concrete class methods to override
84
+
85
+ # @return [Boolean] whether the operator should be run for sub facts
86
+ def run_for_sub_fact?
87
+ false
88
+ end
89
+
90
+ # @return [Boolean] whether the particular fact_scope is supported by this operator
91
+ def supported_for?(fact_scope)
92
+ true
93
+ end
94
+
95
+ def handle_inner(fact_scope)
96
+ # no-op
97
+ end
98
+
99
+ def handle_outer(wrapper)
100
+ # no-op
101
+ end
102
+
103
+ # = Helpers
104
+
105
+ def with_fact_scope_ivar(fact_scope)
106
+ @fact_scope = fact_scope
107
+ yield
108
+ ensure
109
+ @fact_scope = nil
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,18 @@
1
+ module Martyr
2
+ module Runtime
3
+ class GroupOperator < BaseOperator
4
+
5
+ attr_reader :field_alias
6
+
7
+ def add_group(field_alias)
8
+ @field_alias = field_alias
9
+ end
10
+
11
+ private
12
+
13
+ def handle_outer(wrapper)
14
+ wrapper.add_to_group_by(field_alias)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ module Martyr
2
+ module Runtime
3
+ class SelectOperatorForDimension < BaseOperator
4
+
5
+ attr_reader :what, :as
6
+
7
+ def add_select(what, as:)
8
+ @what = what
9
+ @as = as
10
+ end
11
+
12
+ private
13
+
14
+ def handle_inner(fact_scope)
15
+ fact_scope.decorate_scope {|scope| scope.select("#{what} AS #{as}") }
16
+ end
17
+
18
+ def handle_outer(wrapper)
19
+ wrapper.add_to_select(as)
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,35 @@
1
+ module Martyr
2
+ module Runtime
3
+ class SelectOperatorForMetric < BaseOperator
4
+
5
+ attr_reader :metric_name
6
+ attr_reader :what, :as, :data_rollup_sql
7
+
8
+ def initialize(metric_name, &block)
9
+ super(&block)
10
+ @metric_name = metric_name
11
+ end
12
+
13
+ def add_select(what, as:, data_rollup_sql: nil)
14
+ @what = what
15
+ @as = as
16
+ @data_rollup_sql = data_rollup_sql
17
+ end
18
+
19
+ private
20
+
21
+ def handle_inner(fact_scope)
22
+ fact_scope.decorate_scope {|scope| scope.select("#{what} AS #{as}") }
23
+ end
24
+
25
+ def handle_outer(wrapper)
26
+ wrapper.add_to_select(data_rollup_sql) if data_rollup_sql.present?
27
+ end
28
+
29
+ def supported_for?(fact_scope)
30
+ fact_scope.supports_metric?(metric_name)
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,43 @@
1
+ module Martyr
2
+ module Runtime
3
+ class WhereOperatorForDimension < BaseOperator
4
+ include Martyr::LevelComparator
5
+
6
+ attr_reader :dimension_name, :level_name, :block
7
+ delegate :level_key_for_where, to: :fact_scope
8
+
9
+ alias_method :add_where, :operate
10
+
11
+ def initialize(dimension_name, level_name, &block)
12
+ super(&block)
13
+ @dimension_name = dimension_name
14
+ @level_name = level_name
15
+ end
16
+
17
+ # = Support methods (@fact_scope is initialized)
18
+
19
+ def common_denominator_level(level_definition)
20
+ common_level = find_common_denominator_level(level_definition, fact_scope.dimensions[dimension_name].level_objects)
21
+
22
+ # This should never be raised, since the QueryGrain should have already nullified the cube
23
+ raise Internal::Error.new("Internal error: Dimension `#{dimension_name}` slice on level `#{level_name}` has no common denominator.") unless common_level
24
+ common_level
25
+ end
26
+
27
+ private
28
+
29
+ def handle_inner(fact_scope)
30
+ fact_scope.decorate_scope {|scope| scope.where(*operation_args) }
31
+ end
32
+
33
+ def run_for_sub_fact?
34
+ true
35
+ end
36
+
37
+ def supported_for?(fact_scope)
38
+ fact_scope.supports_dimension_level?(dimension_name, level_name)
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,25 @@
1
+ module Martyr
2
+ module Runtime
3
+ class WhereOperatorForMetric < BaseOperator
4
+
5
+ attr_reader :metric_name
6
+ alias_method :add_where, :operate
7
+
8
+ def initialize(metric_name, &block)
9
+ super(&block)
10
+ @metric_name = metric_name
11
+ end
12
+
13
+ private
14
+
15
+ def handle_outer(wrapper)
16
+ wrapper.add_to_where(*operation_args)
17
+ end
18
+
19
+ def supported_for?(fact_scope)
20
+ fact_scope.supports_metric?(metric_name)
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,70 @@
1
+ module Martyr
2
+ module Runtime
3
+ class DataSlice
4
+
5
+ attr_accessor :slices
6
+ attr_reader :definition_resolver
7
+
8
+ include Martyr::Delegators
9
+ each_child_delegator :add_to_grain, :add_to_where, :add_to_dimension_scope, to: :slice_objects
10
+
11
+ include Martyr::Translations
12
+
13
+ # = Slice definitions
14
+
15
+ # @param definition_resolver [#definition_from_id]
16
+ def initialize(definition_resolver)
17
+ @definition_resolver = definition_resolver
18
+ @slices = ScopeableSliceData.new
19
+ end
20
+
21
+ def inspect
22
+ to_hash.inspect
23
+ end
24
+
25
+ def to_hash
26
+ slice_objects.inject({}) {|h,slice| h.merge! slice.to_hash}
27
+ end
28
+
29
+ # @return [Array<String>] with either metric ID or level ID (contrast with dimension name)
30
+ def keys
31
+ slice_objects.flat_map(&:keys)
32
+ end
33
+
34
+ def definition_object_for(slice_on)
35
+ definition_resolver.definition_from_id(slice_on) || raise(Query::Error.new("Could not find `#{slice_on}` to apply slice on"))
36
+ end
37
+
38
+ # @param slice_on [String] e.g. 'customers.last_name' or 'amount_sold'
39
+ # @param slice_definition [Hash]
40
+ def slice(slice_on, slice_definition)
41
+ slice_on_object = definition_object_for(slice_on)
42
+ slices[slice_on_object.slice_id] ||= slice_on_object.build_data_slice
43
+ slices[slice_on_object.slice_id].set_slice(slice_on_object, **slice_definition.symbolize_keys)
44
+ end
45
+
46
+ def slice_objects
47
+ slices.values
48
+ end
49
+
50
+ # @return [Array<String>]
51
+ def dimension_names
52
+ slices.reject{|_slice_id, slice_object| slice_object.is_a?(MetricDataSlice)}.keys
53
+ end
54
+
55
+ # @param sub_cube [SubCube]
56
+ # @return [DataSlice] new object with a new ScopeableDataSliceData object set to be scoped to the sub cube
57
+ def for_cube(sub_cube)
58
+ dup.for_cube!(sub_cube)
59
+ end
60
+
61
+ # @param sub_cube [SubCube]
62
+ # @return [DataSlice] same object with a new ScopeableDataSliceData object set to be scoped to the sub cube
63
+ def for_cube!(sub_cube)
64
+ self.slices = slices.scope(sub_cube)
65
+ self
66
+ end
67
+
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,54 @@
1
+ module Martyr
2
+ module Runtime
3
+ class MetricDataSlice
4
+ attr_reader :metric, :slice_definition
5
+ delegate :to_hash, to: :slice_definition
6
+ delegate :cube_name, to: :metric
7
+ delegate :id, to: :metric, prefix: true
8
+
9
+ def initialize(metric)
10
+ @metric = metric
11
+ end
12
+
13
+ def keys
14
+ [metric_id]
15
+ end
16
+
17
+ def set_slice(metric_definition, **options)
18
+ raise Martyr::Error.new('Internal error. Inconsistent metric received') unless metric_definition.id == metric_id
19
+ @slice_definition = MetricSliceDefinition.new(metric: metric, **options)
20
+ end
21
+
22
+ def get_slice(metric_id_for_get)
23
+ raise Martyr::Error.new('Internal error. Inconsistent metric received') unless metric_id_for_get == metric_id
24
+ @slice_definition
25
+ end
26
+
27
+ def add_to_dimension_scope(*)
28
+ # no-op
29
+ end
30
+
31
+ def add_to_grain(*)
32
+ # no-op
33
+ end
34
+
35
+ # @param fact_scopes [Runtime::FactScopeCollection]
36
+ def add_to_where(fact_scopes, *)
37
+ slice_definition.combined_statements.each do |or_statements|
38
+ fact_scopes.add_where_operator_for_metric(metric_id) do |operator|
39
+ operator.add_where compile_or_statement_group(or_statements),
40
+ *or_statements.map{|slice_statement| slice_statement[:value]}
41
+ end
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def compile_or_statement_group(or_statement_group)
48
+ or_statement_group.map do |slice_statement|
49
+ "#{metric.fact_alias} #{slice_statement[:data_operator]} ?"
50
+ end.join(' OR ')
51
+ end
52
+ end
53
+ end
54
+ end