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,120 @@
|
|
1
|
+
module Martyr
|
2
|
+
# The conversion from and to interval sets is used for merging between data slices and memory slices.
|
3
|
+
class MetricSliceDefinition < BaseSliceDefinition
|
4
|
+
|
5
|
+
OPERATORS = [:gt, :lt, :gte, :lte, :eq, :not]
|
6
|
+
attr_accessor :metric, *OPERATORS
|
7
|
+
|
8
|
+
def self.from_interval_set(interval_set)
|
9
|
+
base = {metric: metric}
|
10
|
+
new base.merge!(interval_set_to_hash(interval_set))
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_hash
|
14
|
+
{metric.id => OPERATORS.inject({}) { |h, op| send(op) ? h.merge!(op => send(op)) : h }}
|
15
|
+
end
|
16
|
+
|
17
|
+
def merge(other)
|
18
|
+
self.class.from_interval_set(to_interval_set.intersect(other.to_interval_set))
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Array<Array<Hash>>]
|
22
|
+
# Every element in the top level array is an array with "OR" statements.
|
23
|
+
# The top level statements are to be combined with an "AND".
|
24
|
+
#
|
25
|
+
# Example:
|
26
|
+
# [ [1,2,3], [4], [5] ]
|
27
|
+
# # => "(1 OR 2 OR 3) AND (4) AND (5)"
|
28
|
+
#
|
29
|
+
def combined_statements
|
30
|
+
statements = []
|
31
|
+
statements << [{data_operator: gt_operator, memory_operator: gt_operator, value: gt_value}] if gt_operator
|
32
|
+
statements << [{data_operator: lt_operator, memory_operator: lt_operator, value: lt_value}] if lt_operator
|
33
|
+
statements << Array.wrap(eq).map {|value| {data_operator: '=', memory_operator: '==', value: value} } if eq
|
34
|
+
|
35
|
+
Array.wrap(self.not).each do |value|
|
36
|
+
statements << [{data_operator: '!=', memory_operator: '!=', value: value}]
|
37
|
+
end
|
38
|
+
|
39
|
+
statements
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def compile_operators
|
45
|
+
hash = interval_set_to_hash(to_interval_set)
|
46
|
+
OPERATORS.each do |operator|
|
47
|
+
instance_variable_set "@#{operator}", hash[operator]
|
48
|
+
end
|
49
|
+
set_null if hash.compact.empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.interval_set_to_hash(interval_set)
|
53
|
+
return {} if interval_set.null?
|
54
|
+
not_arr = interval_set.extract_and_fill_holes.presence
|
55
|
+
eq_arr = interval_set.extract_and_remove_points.presence
|
56
|
+
raise Internal::Error.new('Unexpected interval set format') unless interval_set.null? or interval_set.continuous?
|
57
|
+
|
58
|
+
upper_point = interval_set.upper_bound
|
59
|
+
lte = upper_point.x if upper_point.try(:closed?)
|
60
|
+
lt = upper_point.x if upper_point.try(:open?)
|
61
|
+
|
62
|
+
lower_point = interval_set.lower_bound
|
63
|
+
gte = lower_point.x if lower_point.try(:closed?)
|
64
|
+
gt = lower_point.x if lower_point.try(:open?)
|
65
|
+
|
66
|
+
{ not: not_arr, eq: eq_arr, lte: lte, lt: lt, gte: gte, gt: gt }
|
67
|
+
end
|
68
|
+
delegate :interval_set_to_hash, to: 'self.class'
|
69
|
+
|
70
|
+
# Calculate each time to avoid messing up internal state
|
71
|
+
def to_interval_set
|
72
|
+
interval_set = IntervalSet.new.add
|
73
|
+
merge_eq_interval_set(interval_set)
|
74
|
+
merge_not_interval_set(interval_set)
|
75
|
+
interval_set.intersect IntervalSet.new(to: lt) if lt
|
76
|
+
interval_set.intersect IntervalSet.new(to: [lte]) if lte
|
77
|
+
interval_set.intersect IntervalSet.new(from: gt) if gt
|
78
|
+
interval_set.intersect IntervalSet.new(from: [gte]) if gte
|
79
|
+
interval_set
|
80
|
+
end
|
81
|
+
|
82
|
+
def merge_eq_interval_set(interval_set)
|
83
|
+
return unless eq.present?
|
84
|
+
set = IntervalSet.new
|
85
|
+
Array.wrap(eq).each {|x| set.add(from: [x], to: [x]) }
|
86
|
+
interval_set.intersect(set)
|
87
|
+
end
|
88
|
+
|
89
|
+
def merge_not_interval_set(interval_set)
|
90
|
+
return unless self.not.present?
|
91
|
+
Array.wrap(self.not).
|
92
|
+
map {|x| IntervalSet.new(to: x).add(from: x) }.
|
93
|
+
inject(interval_set) {|set, hole| set.intersect(hole)}
|
94
|
+
end
|
95
|
+
|
96
|
+
def gt_value
|
97
|
+
gte || gt
|
98
|
+
end
|
99
|
+
|
100
|
+
def lt_value
|
101
|
+
lte || lt
|
102
|
+
end
|
103
|
+
|
104
|
+
def gt_operator
|
105
|
+
if gte.present?
|
106
|
+
'>='
|
107
|
+
elsif gt.present?
|
108
|
+
'>'
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def lt_operator
|
113
|
+
if lte.present?
|
114
|
+
'<='
|
115
|
+
elsif lt.present?
|
116
|
+
'<'
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Martyr
|
2
|
+
class PlainDimensionLevelSliceDefinition < BaseSliceDefinition
|
3
|
+
|
4
|
+
# @attribute level [BaseLevelDefinition]
|
5
|
+
attr_accessor :level, :with
|
6
|
+
|
7
|
+
def to_hash
|
8
|
+
{level.id => {with: with}}
|
9
|
+
end
|
10
|
+
|
11
|
+
def merge(other)
|
12
|
+
raise Internal::Error.new('Cannot merge two different levels') unless level.id == other.level.id
|
13
|
+
merged_with = with.present? && other.with.present? ? with & other.with : with + other.with
|
14
|
+
self.class.new(level: level, with: merged_with)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def compile_operators
|
20
|
+
@with = Array.wrap(@with).uniq
|
21
|
+
set_null unless @with.present?
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Martyr
|
2
|
+
module Runtime
|
3
|
+
module FactFillerStrategies
|
4
|
+
|
5
|
+
# Instead of every fact object figuring out itself how to extract fact values from the raw data,
|
6
|
+
# we calculate a memoized set of strategies in advance.
|
7
|
+
|
8
|
+
def fact_levels_filler_hash
|
9
|
+
return @fact_levels_filler_hash if @fact_levels_filler_hash
|
10
|
+
hash = {}
|
11
|
+
supported_level_definitions.each do |level_definition|
|
12
|
+
level_id = level_definition.id
|
13
|
+
if has_association_with_level?(level_id)
|
14
|
+
level_association = association_from_id(level_id)
|
15
|
+
if level_association.degenerate?
|
16
|
+
filler = DegenerateLevelAssociationFillerStrategy.new(level_association)
|
17
|
+
else
|
18
|
+
filler = QueryLevelAssociationFillerStrategy.new(level_association)
|
19
|
+
end
|
20
|
+
else
|
21
|
+
filler = UnassociatedLevelFillerStrategy.new(level_definition)
|
22
|
+
end
|
23
|
+
hash[level_id] = filler
|
24
|
+
end
|
25
|
+
@fact_levels_filler_hash = hash
|
26
|
+
end
|
27
|
+
|
28
|
+
class DegenerateLevelAssociationFillerStrategy
|
29
|
+
def initialize(level_association)
|
30
|
+
@fact_alias = level_association.fact_alias
|
31
|
+
end
|
32
|
+
|
33
|
+
def value(fact)
|
34
|
+
fact.raw.fetch(@fact_alias)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class QueryLevelAssociationFillerStrategy
|
39
|
+
def initialize(level_association)
|
40
|
+
@level_association = level_association
|
41
|
+
end
|
42
|
+
|
43
|
+
def value(fact)
|
44
|
+
fact_key_value = fact.raw.fetch(@level_association.fact_alias)
|
45
|
+
FutureFactValue.new(fact, @level_association.level_definition, key_supported: true, fact_key_value: fact_key_value)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class UnassociatedLevelFillerStrategy
|
50
|
+
def initialize(level_definition)
|
51
|
+
@level_definition = level_definition
|
52
|
+
end
|
53
|
+
|
54
|
+
def value(fact)
|
55
|
+
FutureFactValue.new(fact, @level_definition, key_supported: false)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Martyr
|
2
|
+
module Runtime
|
3
|
+
class QueryMetrics < HashWithIndifferentAccess
|
4
|
+
include Martyr::Registrable
|
5
|
+
include Martyr::Translations
|
6
|
+
|
7
|
+
attr_reader :sub_cube
|
8
|
+
|
9
|
+
delegate :cube, to: :sub_cube
|
10
|
+
|
11
|
+
include Martyr::Delegators
|
12
|
+
each_child_delegator :add_to_select, to: :values
|
13
|
+
|
14
|
+
alias_method :find_metric, :find_or_error
|
15
|
+
|
16
|
+
def initialize(sub_cube)
|
17
|
+
@sub_cube = sub_cube
|
18
|
+
end
|
19
|
+
|
20
|
+
def inspect
|
21
|
+
"#<#{self.class} #{to_a}>"
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_a
|
25
|
+
keys
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_metric(metric_id)
|
29
|
+
with_standard_id(metric_id) do |cube_name, metric_name|
|
30
|
+
register cube.find_metric(metric_name) if cube_name == sub_cube.cube_name
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def built_in_metrics
|
35
|
+
values.select{|x| x.is_a?(Schema::BuiltInMetric)}
|
36
|
+
end
|
37
|
+
|
38
|
+
def custom_metrics
|
39
|
+
values.select{|x| x.is_a?(Schema::CustomMetric)}
|
40
|
+
end
|
41
|
+
|
42
|
+
def custom_rollups
|
43
|
+
values.select{|x| x.is_a?(Schema::CustomRollup)}
|
44
|
+
end
|
45
|
+
|
46
|
+
def metric_ids
|
47
|
+
values.map(&:id)
|
48
|
+
end
|
49
|
+
|
50
|
+
def metric_objects
|
51
|
+
values
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Martyr
|
2
|
+
module Runtime
|
3
|
+
class SubCube
|
4
|
+
|
5
|
+
include Martyr::LevelComparator
|
6
|
+
include Martyr::Translations
|
7
|
+
include Martyr::Runtime::FactFillerStrategies
|
8
|
+
|
9
|
+
attr_reader :query_context, :cube, :fact_scopes, :metrics, :grain
|
10
|
+
delegate :combined_sql, :test, to: :fact_scopes
|
11
|
+
|
12
|
+
# TODO: supported_* methods are delegated to the grain, but there are equivalent methods in the cube that mean
|
13
|
+
# something else and sometimes needed. #select_supported_level_ids, for instance, is relying on those
|
14
|
+
# methods in the cube, not the grain.
|
15
|
+
# This is confusing.
|
16
|
+
delegate :cube_name, :dimension_associations, :select_supported_level_ids, :standardizer, to: :cube
|
17
|
+
delegate :supported_level_associations, :supported_level_definitions, :has_association_with_level?, to: :grain
|
18
|
+
|
19
|
+
delegate :find_metric, :metric_ids, :metric_objects, :built_in_metrics, :custom_metrics, to: :metrics
|
20
|
+
delegate :facts, to: :fact_indexer
|
21
|
+
delegate :definition_from_id, to: :query_context
|
22
|
+
|
23
|
+
alias_method :dimension_bus, :query_context
|
24
|
+
|
25
|
+
# @param cube [Martyr::Cube]
|
26
|
+
def initialize(query_context, cube)
|
27
|
+
@query_context = query_context
|
28
|
+
@cube = cube
|
29
|
+
@metrics = QueryMetrics.new(self)
|
30
|
+
@grain = SubCubeGrain.new(self)
|
31
|
+
end
|
32
|
+
|
33
|
+
def inspect
|
34
|
+
to_hash.inspect
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_hash
|
38
|
+
{cube_name => {metrics: metrics.to_a, grain: grain.to_a}}
|
39
|
+
end
|
40
|
+
|
41
|
+
def dimension_definitions
|
42
|
+
cube.supported_dimension_definitions
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [DimensionReference, LevelAssociation]
|
46
|
+
def association_from_id(id)
|
47
|
+
with_standard_id(id) do |x, y|
|
48
|
+
return dimension_associations[x].try(:levels).try(:[], y) if x and y
|
49
|
+
dimension_associations[x]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def common_denominator_level_association(level_id, prefer_query: false)
|
54
|
+
@_common_denominator_level_association ||= {}
|
55
|
+
return @_common_denominator_level_association[level_id] if @_common_denominator_level_association[level_id]
|
56
|
+
|
57
|
+
level = definition_from_id(level_id)
|
58
|
+
dimension_association = dimension_associations.find_dimension_association(level.dimension_name)
|
59
|
+
level = find_common_denominator_level(level, dimension_association.level_objects, prefer_query: prefer_query)
|
60
|
+
return nil unless level
|
61
|
+
|
62
|
+
@_common_denominator_level_association[level_id] = dimension_association.levels[level.name]
|
63
|
+
end
|
64
|
+
|
65
|
+
# = Definitions
|
66
|
+
|
67
|
+
def set_metrics(metrics_arr)
|
68
|
+
return unless metrics_arr.present?
|
69
|
+
metrics_arr.each do |metric_id|
|
70
|
+
@metrics.add_metric(metric_id)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def set_grain(grain_arr)
|
75
|
+
select_supported_level_ids(grain_arr).each do |level_id|
|
76
|
+
@grain.add_granularity(level_id)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def set_sub_facts(sub_facts_arr)
|
81
|
+
@fact_scopes = cube.build_fact_scopes(sub_facts_arr)
|
82
|
+
end
|
83
|
+
|
84
|
+
def lowest_level_ids_in_grain
|
85
|
+
grain.level_ids
|
86
|
+
end
|
87
|
+
|
88
|
+
# @param data_slice [DataSlice] that is scoped to the cube
|
89
|
+
def decorate_all_scopes(data_slice)
|
90
|
+
grain.add_to_select(fact_scopes)
|
91
|
+
metrics.add_to_select(fact_scopes)
|
92
|
+
data_slice.add_to_where(fact_scopes, dimension_bus)
|
93
|
+
grain.add_to_group_by(fact_scopes)
|
94
|
+
end
|
95
|
+
|
96
|
+
# = Running
|
97
|
+
|
98
|
+
# @param memory_slice [MemorySlice]
|
99
|
+
# @option levels [Array<String, Martyr::Level>] array of level IDs or any type of level to group facts by.
|
100
|
+
# Default is all levels in the query context.
|
101
|
+
# @option metrics [Array<String, BaseMetric>] array of metric IDs or metric objects to roll up in the elements.
|
102
|
+
def elements(memory_slice, levels: nil, metrics: nil)
|
103
|
+
element_locator_for(memory_slice, metrics: metrics).all(sanitize_levels(levels: levels).map(&:id))
|
104
|
+
end
|
105
|
+
|
106
|
+
def element_locator_for(memory_slice, metrics: nil)
|
107
|
+
ElementLocator.new memory_slice: memory_slice, metrics: sanitize_metrics(metrics: metrics),
|
108
|
+
fact_indexer: fact_indexer, helper_module: query_context.element_helper_module,
|
109
|
+
restrict_level_ids: grain.supported_level_definition_ids, standardizer: standardizer
|
110
|
+
end
|
111
|
+
|
112
|
+
def fact_indexer
|
113
|
+
@fact_indexer ||= FactIndexer.new(self, grain.null? ? [] : fact_scopes.run.map { |hash| Fact.new(self, hash) })
|
114
|
+
end
|
115
|
+
|
116
|
+
# @option metrics [Array<String, BaseMetric>] array of metric IDs or metric objects
|
117
|
+
def sanitize_metrics(metrics: nil)
|
118
|
+
metric_ids = Array.wrap(metrics).map { |x| to_id(x) }.presence || self.metrics.metric_ids
|
119
|
+
metric_ids = metric_ids & self.metrics.metric_ids
|
120
|
+
metric_ids.map {|id| query_context.metric(id) }
|
121
|
+
end
|
122
|
+
|
123
|
+
# @option levels [Array<String, Martyr::Level>] array of level IDs or any type of level to group facts by.
|
124
|
+
# Default is all levels in grain
|
125
|
+
# @return [Array<String, BaseLevelScope>]
|
126
|
+
def sanitize_levels(levels: nil)
|
127
|
+
level_ids = levels.nil? ? query_context.level_ids_in_grain : Array.wrap(levels).map { |x| to_id(x) }
|
128
|
+
level_ids = select_supported_level_ids(level_ids)
|
129
|
+
query_context.levels_and_above_for(level_ids)
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Martyr
|
2
|
+
module Runtime
|
3
|
+
class SubCubeGrain
|
4
|
+
include Martyr::LevelComparator
|
5
|
+
|
6
|
+
attr_reader :sub_cube, :grain
|
7
|
+
delegate :cube, to: :sub_cube
|
8
|
+
|
9
|
+
def initialize(sub_cube)
|
10
|
+
@sub_cube = sub_cube
|
11
|
+
@grain = cube.default_fact_grain_level_associations.index_by{|x| x.dimension_name}
|
12
|
+
@null = false
|
13
|
+
end
|
14
|
+
|
15
|
+
def inspect
|
16
|
+
"#<#{self.class} #{to_a}>"
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_a
|
20
|
+
supported_level_associations.map(&:id)
|
21
|
+
end
|
22
|
+
|
23
|
+
def level_ids
|
24
|
+
grain.values.map(&:id)
|
25
|
+
end
|
26
|
+
|
27
|
+
def null?
|
28
|
+
@null
|
29
|
+
end
|
30
|
+
|
31
|
+
# Maintains for every dimension the lowest supported level
|
32
|
+
def add_granularity(level_id)
|
33
|
+
level_to_add = sub_cube.common_denominator_level_association(level_id)
|
34
|
+
@null = true and return unless level_to_add
|
35
|
+
dimension = level_to_add.dimension_name
|
36
|
+
@grain[dimension] = more_detailed_level(@grain[dimension], level_to_add)
|
37
|
+
end
|
38
|
+
|
39
|
+
# TODO: remove
|
40
|
+
# def set_all_if_empty
|
41
|
+
# return if @grain.present?
|
42
|
+
# sub_cube.dimension_associations.each do |dimension_name, dimension_object|
|
43
|
+
# @grain[dimension_name.to_s] = dimension_object.lowest_level
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
|
47
|
+
# TODO: delete
|
48
|
+
# def nullify_scope_if_null(fact_scopes)
|
49
|
+
# fact_scopes.set_null_scope if null?
|
50
|
+
# end
|
51
|
+
|
52
|
+
# Adds all supported levels including and above the sliced level
|
53
|
+
# @param fact_scopes [Runtime::FactScopeCollection]
|
54
|
+
def add_to_select(fact_scopes)
|
55
|
+
supported_level_associations.each do |level_object|
|
56
|
+
fact_scopes.add_select_operator_for_dimension do |operator|
|
57
|
+
operator.add_select(level_object.fact_key, as: level_object.fact_alias)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# @param fact_scopes [Runtime::FactScopeCollection]
|
63
|
+
def add_to_group_by(fact_scopes)
|
64
|
+
supported_level_associations.each do |level_object|
|
65
|
+
fact_scopes.add_group_operator do |operator|
|
66
|
+
operator.add_group(level_object.fact_alias)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Assume the following levels, where (*) denotes has association to the fact
|
72
|
+
# L1
|
73
|
+
# L2 (*)
|
74
|
+
# L3 (*)
|
75
|
+
# L4 (*)
|
76
|
+
#
|
77
|
+
# Assuming the lowest level in the grain is L3:
|
78
|
+
#
|
79
|
+
# supported_level_associations
|
80
|
+
# # => [L2, L3] of LevelAssociation objects
|
81
|
+
#
|
82
|
+
# supported_level_definitions
|
83
|
+
# # => [L1, L2, L3] of BaseLevelDefinition objects
|
84
|
+
#
|
85
|
+
# [L1, L2, L3, L4].map{|x| x.has_association_with_level(x) }
|
86
|
+
# # => [false, true, true, false]
|
87
|
+
#
|
88
|
+
|
89
|
+
def supported_level_associations
|
90
|
+
return [] if null?
|
91
|
+
@supported_level_associations ||= grain.flat_map do |_dimension_name, lowest_level|
|
92
|
+
sub_cube.association_from_id(lowest_level.id).level_and_above
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def supported_level_definitions
|
97
|
+
return [] if null?
|
98
|
+
@_supported_level_definitions ||= grain.flat_map do |_dimension_name, lowest_level|
|
99
|
+
sub_cube.definition_from_id(lowest_level.id).level_and_above
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def supported_level_definition_ids
|
104
|
+
supported_level_definitions.map(&:id)
|
105
|
+
end
|
106
|
+
|
107
|
+
def supported_level_associations_lookup
|
108
|
+
@_supported_level_associations_lookup ||= supported_level_associations.index_by(&:id)
|
109
|
+
end
|
110
|
+
|
111
|
+
def has_association_with_level?(level_id)
|
112
|
+
!!supported_level_associations_lookup[level_id]
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Martyr
|
2
|
+
module Schema
|
3
|
+
class DimensionAssociationCollection < HashWithIndifferentAccess
|
4
|
+
include Martyr::Registrable
|
5
|
+
include Martyr::Translations
|
6
|
+
|
7
|
+
attr_reader :dimension_definitions
|
8
|
+
alias_method :find_dimension_association, :find_or_error
|
9
|
+
alias_method :supports_dimension?, :has_key?
|
10
|
+
|
11
|
+
# @param dimension_definitions [DimensionDefinitionCollection]
|
12
|
+
def initialize(dimension_definitions)
|
13
|
+
@dimension_definitions = dimension_definitions
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [LevelAssociation]
|
17
|
+
def has_dimension_level(dimension_name, level_name, **args)
|
18
|
+
if has_key?(dimension_name)
|
19
|
+
dimension = find_or_nil(dimension_name)
|
20
|
+
else
|
21
|
+
dimension = dimension_definitions.find_dimension(dimension_name)
|
22
|
+
dimension = Martyr::DimensionReference.new(dimension, LevelAssociationCollection)
|
23
|
+
register dimension
|
24
|
+
end
|
25
|
+
dimension.has_dimension_level(level_name, **args)
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_level_association(level_id)
|
29
|
+
with_standard_id(level_id) {|dimension, level| find_dimension_association(dimension).find_level(level)}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Martyr
|
2
|
+
module Schema
|
3
|
+
class LevelAssociation
|
4
|
+
include Martyr::Level
|
5
|
+
|
6
|
+
attr_accessor :level, :fact_key, :fact_alias, :sort
|
7
|
+
alias_method :level_definition, :level
|
8
|
+
|
9
|
+
# Important so that the to_i will take into account all levels defined for the dimension, not just the supported one
|
10
|
+
delegate :to_i, to: :level
|
11
|
+
|
12
|
+
delegate :label_key, :label_expression, to: :level
|
13
|
+
|
14
|
+
# @param collection [LevelAssociationCollection]
|
15
|
+
# @param level [BaseLevelDefinition]
|
16
|
+
def initialize(collection, level, fact_key: nil, fact_alias: nil, sort: nil)
|
17
|
+
@collection = collection
|
18
|
+
@level = level
|
19
|
+
@fact_key = fact_key || level.fact_key
|
20
|
+
@fact_alias = fact_alias || level.fact_alias
|
21
|
+
@sort = sort || level.sort
|
22
|
+
end
|
23
|
+
|
24
|
+
def supported?
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# Delegate everything to level
|
31
|
+
def method_missing(method_name, *args, &block)
|
32
|
+
level.respond_to?(method_name) ? level.send(method_name, *args, &block) : super
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Martyr
|
2
|
+
module Schema
|
3
|
+
class LevelAssociationCollection < HashWithIndifferentAccess
|
4
|
+
include Martyr::LevelCollection
|
5
|
+
|
6
|
+
# @param level [String, Symbol]
|
7
|
+
# @return [LevelAssociation]
|
8
|
+
def has_dimension_level(level, **args)
|
9
|
+
level_definition = dimension_definition.levels[level]
|
10
|
+
raise Schema::Error.new("Could not find level `#{level}` for dimension #{dimension_name}") unless level_definition
|
11
|
+
level_association = LevelAssociation.new(self, level_definition, **args)
|
12
|
+
register level_association
|
13
|
+
arr = sort_by{|_name, level| level.to_i}
|
14
|
+
clear.merge!(Hash[arr])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Martyr
|
2
|
+
module Schema
|
3
|
+
class DimensionDefinitionCollection < HashWithIndifferentAccess
|
4
|
+
include Martyr::Translations
|
5
|
+
include Martyr::Registrable
|
6
|
+
|
7
|
+
alias_method :supports_dimension?, :has_key?
|
8
|
+
alias_method :find_dimension, :find_or_error
|
9
|
+
alias_method :all, :to_hash
|
10
|
+
|
11
|
+
# @return [DimensionDefinition]
|
12
|
+
def define_dimension(*args, &block)
|
13
|
+
register PlainDimensionDefinition.new(*args, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_level_definition(level_id)
|
17
|
+
with_standard_id(level_id) { |dimension, level| find_dimension(dimension).find_level(level) }
|
18
|
+
end
|
19
|
+
|
20
|
+
# # @param name [String, Symbol]
|
21
|
+
# # @return [DimensionDefinition] object that was found by traversing up the lookup tree
|
22
|
+
# def recursive_lookup(name)
|
23
|
+
# fetch(name.to_s, parent_dimension_definitions.try(:recursive_lookup, name.to_s))
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# def find_dimension(name)
|
27
|
+
# recursive_lookup(name) || raise(Schema::Error.new("Could not find dimension `#{name}`"))
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
|
31
|
+
# # @return [Hash] { dimension_name => PlainDimensionDefinition } including dimension definitions from all superclasses
|
32
|
+
# def all
|
33
|
+
# return to_hash unless parent_dimension_definitions
|
34
|
+
# to_hash.merge(parent_dimension_definitions.all)
|
35
|
+
# end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Martyr
|
2
|
+
module Schema
|
3
|
+
class PlainDimensionDefinition
|
4
|
+
include ActiveModel::Model
|
5
|
+
include Martyr::HasLevelCollection
|
6
|
+
|
7
|
+
attr_accessor :name, :title
|
8
|
+
delegate :degenerate_level, :query_level, to: :levels
|
9
|
+
|
10
|
+
# @param name [String]
|
11
|
+
# @option title [String]
|
12
|
+
def initialize(name, **options, &block)
|
13
|
+
super name: name.to_s,
|
14
|
+
title: options[:title] || name.to_s.titleize
|
15
|
+
|
16
|
+
@levels = LevelDefinitionCollection.new(dimension: self)
|
17
|
+
instance_eval(&block) if block
|
18
|
+
end
|
19
|
+
|
20
|
+
def dimension_definition
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
# For reflection
|
25
|
+
def dimension?
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_data_slice(*args)
|
30
|
+
Runtime::PlainDimensionDataSlice.new(self, *args)
|
31
|
+
end
|
32
|
+
|
33
|
+
def build_memory_slice(*args)
|
34
|
+
Runtime::PlainDimensionMemorySlice.new(self, *args)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|