martyr 0.1.74.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.tags +868 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +265 -0
- data/Rakefile +1 -0
- data/TODO.txt +54 -0
- data/bin/console +62 -0
- data/bin/setup +7 -0
- data/lib/martyr/base_cube.rb +73 -0
- data/lib/martyr/cube.rb +134 -0
- data/lib/martyr/dimension_reference.rb +26 -0
- data/lib/martyr/errors.rb +20 -0
- data/lib/martyr/helpers/delegators.rb +17 -0
- data/lib/martyr/helpers/intervals.rb +222 -0
- data/lib/martyr/helpers/metric_id_standardizer.rb +47 -0
- data/lib/martyr/helpers/registrable.rb +15 -0
- data/lib/martyr/helpers/sorter.rb +79 -0
- data/lib/martyr/helpers/translations.rb +34 -0
- data/lib/martyr/level_concern/has_level_collection.rb +11 -0
- data/lib/martyr/level_concern/level.rb +45 -0
- data/lib/martyr/level_concern/level_collection.rb +60 -0
- data/lib/martyr/level_concern/level_comparator.rb +45 -0
- data/lib/martyr/level_concern/level_definitions_by_dimension.rb +24 -0
- data/lib/martyr/runtime/data_set/coordinates.rb +108 -0
- data/lib/martyr/runtime/data_set/element.rb +66 -0
- data/lib/martyr/runtime/data_set/element_common.rb +51 -0
- data/lib/martyr/runtime/data_set/element_locator.rb +143 -0
- data/lib/martyr/runtime/data_set/fact.rb +83 -0
- data/lib/martyr/runtime/data_set/fact_indexer.rb +72 -0
- data/lib/martyr/runtime/data_set/future_fact_value.rb +58 -0
- data/lib/martyr/runtime/data_set/future_metric.rb +40 -0
- data/lib/martyr/runtime/data_set/virtual_element.rb +131 -0
- data/lib/martyr/runtime/data_set/virtual_elements_builder.rb +202 -0
- data/lib/martyr/runtime/dimension_scopes/base_level_scope.rb +20 -0
- data/lib/martyr/runtime/dimension_scopes/degenerate_level_scope.rb +76 -0
- data/lib/martyr/runtime/dimension_scopes/dimension_scope_collection.rb +78 -0
- data/lib/martyr/runtime/dimension_scopes/level_scope_collection.rb +20 -0
- data/lib/martyr/runtime/dimension_scopes/query_level_scope.rb +223 -0
- data/lib/martyr/runtime/fact_scopes/base_fact_scope.rb +62 -0
- data/lib/martyr/runtime/fact_scopes/fact_scope_collection.rb +127 -0
- data/lib/martyr/runtime/fact_scopes/main_fact_scope.rb +7 -0
- data/lib/martyr/runtime/fact_scopes/null_scope.rb +7 -0
- data/lib/martyr/runtime/fact_scopes/sub_fact_scope.rb +16 -0
- data/lib/martyr/runtime/fact_scopes/wrapped_fact_scope.rb +11 -0
- data/lib/martyr/runtime/pivot/pivot_axis.rb +67 -0
- data/lib/martyr/runtime/pivot/pivot_cell.rb +54 -0
- data/lib/martyr/runtime/pivot/pivot_grain_element.rb +22 -0
- data/lib/martyr/runtime/pivot/pivot_row.rb +49 -0
- data/lib/martyr/runtime/pivot/pivot_table.rb +109 -0
- data/lib/martyr/runtime/pivot/pivot_table_builder.rb +125 -0
- data/lib/martyr/runtime/query/metric_dependency_resolver.rb +149 -0
- data/lib/martyr/runtime/query/query_context.rb +246 -0
- data/lib/martyr/runtime/query/query_context_builder.rb +215 -0
- data/lib/martyr/runtime/scope_operators/base_operator.rb +113 -0
- data/lib/martyr/runtime/scope_operators/group_operator.rb +18 -0
- data/lib/martyr/runtime/scope_operators/select_operator_for_dimension.rb +24 -0
- data/lib/martyr/runtime/scope_operators/select_operator_for_metric.rb +35 -0
- data/lib/martyr/runtime/scope_operators/where_operator_for_dimension.rb +43 -0
- data/lib/martyr/runtime/scope_operators/where_operator_for_metric.rb +25 -0
- data/lib/martyr/runtime/slices/data_slices/data_slice.rb +70 -0
- data/lib/martyr/runtime/slices/data_slices/metric_data_slice.rb +54 -0
- data/lib/martyr/runtime/slices/data_slices/plain_dimension_data_slice.rb +109 -0
- data/lib/martyr/runtime/slices/data_slices/time_dimension_data_slice.rb +9 -0
- data/lib/martyr/runtime/slices/has_scoped_levels.rb +29 -0
- data/lib/martyr/runtime/slices/memory_slices/TO_DELETE.md +188 -0
- data/lib/martyr/runtime/slices/memory_slices/memory_slice.rb +84 -0
- data/lib/martyr/runtime/slices/memory_slices/metric_memory_slice.rb +59 -0
- data/lib/martyr/runtime/slices/memory_slices/plain_dimension_memory_slice.rb +48 -0
- data/lib/martyr/runtime/slices/scopeable_slice_data.rb +73 -0
- data/lib/martyr/runtime/slices/slice_definitions/base_slice_definition.rb +30 -0
- data/lib/martyr/runtime/slices/slice_definitions/metric_slice_definition.rb +120 -0
- data/lib/martyr/runtime/slices/slice_definitions/plain_dimension_level_slice_definition.rb +26 -0
- data/lib/martyr/runtime/sub_cubes/fact_filler_strategies.rb +61 -0
- data/lib/martyr/runtime/sub_cubes/query_metrics.rb +56 -0
- data/lib/martyr/runtime/sub_cubes/sub_cube.rb +134 -0
- data/lib/martyr/runtime/sub_cubes/sub_cube_grain.rb +117 -0
- data/lib/martyr/schema/dimension_associations/dimension_association_collection.rb +33 -0
- data/lib/martyr/schema/dimension_associations/level_association.rb +37 -0
- data/lib/martyr/schema/dimension_associations/level_association_collection.rb +18 -0
- data/lib/martyr/schema/dimensions/dimension_definition_collection.rb +39 -0
- data/lib/martyr/schema/dimensions/plain_dimension_definition.rb +39 -0
- data/lib/martyr/schema/dimensions/time_dimension_definition.rb +24 -0
- data/lib/martyr/schema/facts/base_fact_definition.rb +22 -0
- data/lib/martyr/schema/facts/fact_definition_collection.rb +44 -0
- data/lib/martyr/schema/facts/main_fact_definition.rb +45 -0
- data/lib/martyr/schema/facts/sub_fact_definition.rb +44 -0
- data/lib/martyr/schema/metrics/base_metric.rb +77 -0
- data/lib/martyr/schema/metrics/built_in_metric.rb +38 -0
- data/lib/martyr/schema/metrics/count_distinct_metric.rb +172 -0
- data/lib/martyr/schema/metrics/custom_metric.rb +26 -0
- data/lib/martyr/schema/metrics/custom_rollup.rb +31 -0
- data/lib/martyr/schema/metrics/dependency_inferrer.rb +150 -0
- data/lib/martyr/schema/metrics/metric_definition_collection.rb +94 -0
- data/lib/martyr/schema/named_scopes/named_scope.rb +19 -0
- data/lib/martyr/schema/named_scopes/named_scope_collection.rb +42 -0
- data/lib/martyr/schema/plain_dimension_levels/base_level_definition.rb +39 -0
- data/lib/martyr/schema/plain_dimension_levels/degenerate_level_definition.rb +75 -0
- data/lib/martyr/schema/plain_dimension_levels/level_definition_collection.rb +15 -0
- data/lib/martyr/schema/plain_dimension_levels/query_level_definition.rb +99 -0
- data/lib/martyr/version.rb +3 -0
- data/lib/martyr/virtual_cube.rb +74 -0
- data/lib/martyr.rb +55 -0
- data/martyr.gemspec +41 -0
- 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
|