martyr 0.1.74.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|