mainej-activewarehouse 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/activewarehouse/README +99 -0
- data/activewarehouse/Rakefile +165 -0
- data/activewarehouse/TODO +4 -0
- data/activewarehouse/db/migrations/001_create_table_reports.rb +28 -0
- data/activewarehouse/doc/references.txt +4 -0
- data/activewarehouse/generators/bridge/USAGE +1 -0
- data/activewarehouse/generators/bridge/bridge_generator.rb +46 -0
- data/activewarehouse/generators/bridge/templates/fixture.yml +5 -0
- data/activewarehouse/generators/bridge/templates/migration.rb +27 -0
- data/activewarehouse/generators/bridge/templates/model.rb +3 -0
- data/activewarehouse/generators/bridge/templates/unit_test.rb +8 -0
- data/activewarehouse/generators/cube/USAGE +1 -0
- data/activewarehouse/generators/cube/cube_generator.rb +28 -0
- data/activewarehouse/generators/cube/templates/model.rb +3 -0
- data/activewarehouse/generators/cube/templates/unit_test.rb +8 -0
- data/activewarehouse/generators/date_dimension/USAGE +1 -0
- data/activewarehouse/generators/date_dimension/date_dimension_generator.rb +16 -0
- data/activewarehouse/generators/date_dimension/templates/fixture.yml +5 -0
- data/activewarehouse/generators/date_dimension/templates/migration.rb +31 -0
- data/activewarehouse/generators/date_dimension/templates/model.rb +3 -0
- data/activewarehouse/generators/date_dimension/templates/unit_test.rb +8 -0
- data/activewarehouse/generators/dimension/USAGE +1 -0
- data/activewarehouse/generators/dimension/dimension_generator.rb +46 -0
- data/activewarehouse/generators/dimension/templates/fixture.yml +5 -0
- data/activewarehouse/generators/dimension/templates/migration.rb +11 -0
- data/activewarehouse/generators/dimension/templates/model.rb +3 -0
- data/activewarehouse/generators/dimension/templates/unit_test.rb +8 -0
- data/activewarehouse/generators/dimension_view/USAGE +1 -0
- data/activewarehouse/generators/dimension_view/dimension_view_generator.rb +62 -0
- data/activewarehouse/generators/dimension_view/templates/migration.rb +17 -0
- data/activewarehouse/generators/dimension_view/templates/model.rb +3 -0
- data/activewarehouse/generators/dimension_view/templates/unit_test.rb +10 -0
- data/activewarehouse/generators/fact/USAGE +1 -0
- data/activewarehouse/generators/fact/fact_generator.rb +46 -0
- data/activewarehouse/generators/fact/templates/fixture.yml +5 -0
- data/activewarehouse/generators/fact/templates/migration.rb +13 -0
- data/activewarehouse/generators/fact/templates/model.rb +3 -0
- data/activewarehouse/generators/fact/templates/unit_test.rb +10 -0
- data/activewarehouse/generators/time_dimension/USAGE +1 -0
- data/activewarehouse/generators/time_dimension/templates/fixture.yml +5 -0
- data/activewarehouse/generators/time_dimension/templates/migration.rb +12 -0
- data/activewarehouse/generators/time_dimension/templates/model.rb +3 -0
- data/activewarehouse/generators/time_dimension/templates/unit_test.rb +8 -0
- data/activewarehouse/generators/time_dimension/time_dimension_generator.rb +14 -0
- data/activewarehouse/init.rb +1 -0
- data/activewarehouse/install.rb +5 -0
- data/activewarehouse/lib/active_warehouse.rb +91 -0
- data/activewarehouse/lib/active_warehouse/aggregate.rb +75 -0
- data/activewarehouse/lib/active_warehouse/aggregate/dwarf_aggregate.rb +369 -0
- data/activewarehouse/lib/active_warehouse/aggregate/dwarf_common.rb +44 -0
- data/activewarehouse/lib/active_warehouse/aggregate/dwarf_printer.rb +34 -0
- data/activewarehouse/lib/active_warehouse/aggregate/no_aggregate.rb +212 -0
- data/activewarehouse/lib/active_warehouse/aggregate/pid_aggregate.rb +29 -0
- data/activewarehouse/lib/active_warehouse/aggregate_field.rb +59 -0
- data/activewarehouse/lib/active_warehouse/bridge.rb +19 -0
- data/activewarehouse/lib/active_warehouse/bridge/hierarchy_bridge.rb +46 -0
- data/activewarehouse/lib/active_warehouse/builder.rb +3 -0
- data/activewarehouse/lib/active_warehouse/builder/date_dimension_builder.rb +91 -0
- data/activewarehouse/lib/active_warehouse/builder/generator/generator.rb +13 -0
- data/activewarehouse/lib/active_warehouse/builder/generator/name_generator.rb +20 -0
- data/activewarehouse/lib/active_warehouse/builder/generator/paragraph_generator.rb +11 -0
- data/activewarehouse/lib/active_warehouse/builder/random_data_builder.rb +239 -0
- data/activewarehouse/lib/active_warehouse/builder/test_data_builder.rb +54 -0
- data/activewarehouse/lib/active_warehouse/calculated_field.rb +27 -0
- data/activewarehouse/lib/active_warehouse/compat/compat.rb +49 -0
- data/activewarehouse/lib/active_warehouse/core_ext.rb +1 -0
- data/activewarehouse/lib/active_warehouse/core_ext/time.rb +5 -0
- data/activewarehouse/lib/active_warehouse/core_ext/time/calculations.rb +40 -0
- data/activewarehouse/lib/active_warehouse/cube.rb +235 -0
- data/activewarehouse/lib/active_warehouse/cube_query_result.rb +69 -0
- data/activewarehouse/lib/active_warehouse/dimension.rb +329 -0
- data/activewarehouse/lib/active_warehouse/dimension/date_dimension.rb +15 -0
- data/activewarehouse/lib/active_warehouse/dimension/dimension_reflection.rb +21 -0
- data/activewarehouse/lib/active_warehouse/dimension/dimension_view.rb +27 -0
- data/activewarehouse/lib/active_warehouse/dimension/hierarchical_dimension.rb +99 -0
- data/activewarehouse/lib/active_warehouse/dimension/slowly_changing_dimension.rb +147 -0
- data/activewarehouse/lib/active_warehouse/fact.rb +239 -0
- data/activewarehouse/lib/active_warehouse/field.rb +74 -0
- data/activewarehouse/lib/active_warehouse/migrations.rb +64 -0
- data/activewarehouse/lib/active_warehouse/ordered_hash.rb +34 -0
- data/activewarehouse/lib/active_warehouse/prejoin_fact.rb +97 -0
- data/activewarehouse/lib/active_warehouse/report.rb +7 -0
- data/activewarehouse/lib/active_warehouse/report/abstract_report.rb +149 -0
- data/activewarehouse/lib/active_warehouse/report/chart_report.rb +9 -0
- data/activewarehouse/lib/active_warehouse/report/data_cell.rb +21 -0
- data/activewarehouse/lib/active_warehouse/report/data_column.rb +19 -0
- data/activewarehouse/lib/active_warehouse/report/data_row.rb +15 -0
- data/activewarehouse/lib/active_warehouse/report/dimension.rb +58 -0
- data/activewarehouse/lib/active_warehouse/report/table_report.rb +38 -0
- data/activewarehouse/lib/active_warehouse/version.rb +9 -0
- data/activewarehouse/lib/active_warehouse/view.rb +9 -0
- data/activewarehouse/lib/active_warehouse/view/crumb.rb +64 -0
- data/activewarehouse/lib/active_warehouse/view/report_helper.rb +98 -0
- data/activewarehouse/lib/active_warehouse/view/table_view.rb +134 -0
- data/activewarehouse/lib/active_warehouse/view/yui_adapter.rb +68 -0
- data/activewarehouse/tasks/active_warehouse_tasks.rake +122 -0
- metadata +237 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
module ActiveWarehouse #:nodoc:
|
2
|
+
module Aggregate #:nodoc:
|
3
|
+
# Dwarf support class that prints a representation of the Dwarf
|
4
|
+
class DwarfPrinter
|
5
|
+
# Print the specified node at the given depth.
|
6
|
+
def self.print_node(node, depth=0, recurse=true)
|
7
|
+
#puts "printing node #{node.index}"
|
8
|
+
cells = node.cells.collect { |c| cell_to_string(c)}.join('|')
|
9
|
+
|
10
|
+
parent_node = node.parent ? "#{cell_to_string(node.parent)}:" : ''
|
11
|
+
puts "#{node.index}=#{' '*depth}#{parent_node}[#{cells}|#{all_cell_to_string(node.all_cell)}]"
|
12
|
+
if !node.leaf?
|
13
|
+
print_node(node.all_cell.child, depth + 1, false) if node.all_cell
|
14
|
+
end
|
15
|
+
if recurse
|
16
|
+
node.children.each { |child| print_node(child, depth+1) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.cell_to_string(cell)
|
21
|
+
# a new String object must be created here, otherwise to_s returns a reference
|
22
|
+
# to the same String object each time and thus the value will be appended each time
|
23
|
+
# which is not what I want
|
24
|
+
s = String.new(cell.key.to_s)
|
25
|
+
s << " #{cell.value.join(',')}" if cell.node.leaf?
|
26
|
+
s
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.all_cell_to_string(cell)
|
30
|
+
cell ? (cell.value ? cell.value.inspect : '') : ''
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module ActiveWarehouse #:nodoc:
|
4
|
+
module Aggregate #:nodoc:
|
5
|
+
# An aggregate which goes directly to the fact and dimensions to answer questions
|
6
|
+
class NoAggregate < Aggregate
|
7
|
+
# Populate the aggregate (in this case it is a no-op implementation)
|
8
|
+
def populate
|
9
|
+
# do nothing
|
10
|
+
end
|
11
|
+
|
12
|
+
# Query the aggregate
|
13
|
+
# def query(column_dimension_name, column_hierarchy_name,
|
14
|
+
# row_dimension_name, row_hierarchy_name, conditions=nil,
|
15
|
+
# cstage=0, rstage=0, filters={})
|
16
|
+
|
17
|
+
# Query the aggregate
|
18
|
+
def query(*args)
|
19
|
+
options = parse_query_args(*args)
|
20
|
+
|
21
|
+
column_dimension_name = options[:column_dimension_name]
|
22
|
+
column_hierarchy_name = options[:column_hierarchy_name]
|
23
|
+
row_dimension_name = options[:row_dimension_name]
|
24
|
+
row_hierarchy_name = options[:row_hierarchy_name]
|
25
|
+
conditions = options[:conditions]
|
26
|
+
cstage = options[:cstage] || 0
|
27
|
+
rstage = options[:rstage] || 0
|
28
|
+
filters = options[:filters] || {}
|
29
|
+
|
30
|
+
fact_class = cube_class.fact_class
|
31
|
+
column_dimension = fact_class.dimension_class(column_dimension_name)
|
32
|
+
column_hierarchy = column_dimension.hierarchy(column_hierarchy_name)
|
33
|
+
row_dimension = fact_class.dimension_class(row_dimension_name)
|
34
|
+
row_hierarchy = row_dimension.hierarchy(row_hierarchy_name)
|
35
|
+
|
36
|
+
used_dimensions = Set.new
|
37
|
+
used_dimensions.merge([column_dimension_name, row_dimension_name])
|
38
|
+
row_dim_reflection = fact_class.dimension_relationships[row_dimension_name].dependent_dimension_reflections
|
39
|
+
used_dimensions.merge(row_dim_reflection.collect{|d| d.name})
|
40
|
+
col_dim_reflection = fact_class.dimension_relationships[column_dimension_name].dependent_dimension_reflections
|
41
|
+
used_dimensions.merge(col_dim_reflection.collect{|d| d.name})
|
42
|
+
filters.each do |k,v|
|
43
|
+
used_dimensions << k.split('.')[0]
|
44
|
+
end
|
45
|
+
if conditions
|
46
|
+
cube_class.dimensions.each do |dimension|
|
47
|
+
if conditions =~ /#{dimension}\./i
|
48
|
+
used_dimensions << dimension
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# This method assumes at most one dimension is hierarchical dimension
|
54
|
+
# in the query params. TODO: need to handle when both row and column
|
55
|
+
# are hierarchical dimensions.
|
56
|
+
hierarchical_dimension = nil
|
57
|
+
hierarchical_dimension_name = nil
|
58
|
+
hierarchical_stage = nil
|
59
|
+
|
60
|
+
if !column_dimension.hierarchical_dimension?
|
61
|
+
current_column_name = column_hierarchy[cstage]
|
62
|
+
else
|
63
|
+
hierarchical_dimension = column_dimension
|
64
|
+
hierarchical_dimension_name = column_dimension_name
|
65
|
+
hierarchical_stage = cstage
|
66
|
+
current_column_name = column_hierarchy[0]
|
67
|
+
end
|
68
|
+
|
69
|
+
if !row_dimension.hierarchical_dimension?
|
70
|
+
current_row_name = row_hierarchy[rstage]
|
71
|
+
else
|
72
|
+
hierarchical_dimension = row_dimension
|
73
|
+
hierarchical_dimension_name = row_dimension_name
|
74
|
+
hierarchical_stage = rstage
|
75
|
+
current_row_name = row_hierarchy[0]
|
76
|
+
end
|
77
|
+
|
78
|
+
fact_columns = cube_class.aggregate_fields(used_dimensions).collect { |c|
|
79
|
+
agg_sql = ''
|
80
|
+
quoted_label = cube_class.connection.quote_column_name(c.label)
|
81
|
+
if hierarchical_dimension and !c.levels_from_parent.empty?
|
82
|
+
bridge = hierarchical_dimension.bridge_class
|
83
|
+
bridge_table_name = bridge.table_name
|
84
|
+
levels_from_parent = bridge.levels_from_parent
|
85
|
+
get_all = false
|
86
|
+
c.levels_from_parent.each do |level|
|
87
|
+
case level
|
88
|
+
when :all
|
89
|
+
agg_sql += " #{c.strategy_name}(#{c.from_table_name}.#{c.name}) AS #{quoted_label})"
|
90
|
+
get_all = true
|
91
|
+
when :self
|
92
|
+
agg_sql += " #{c.strategy_name}(CASE " if agg_sql.length == 0
|
93
|
+
agg_sql += " WHEN #{bridge_table_name}.#{levels_from_parent} = 0 THEN #{c.from_table_name}.#{c.name} \n"
|
94
|
+
when Integer
|
95
|
+
agg_sql += " #{c.strategy_name}(CASE " if agg_sql.length == 0
|
96
|
+
agg_sql += " WHEN #{bridge_table_name}.#{levels_from_parent} = #{level} then #{c.from_table_name}.#{c.name} \n"
|
97
|
+
else
|
98
|
+
raise ArgumentError, "Each element to :levels_from_parent option must be :all, :self, or Integer"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
agg_sql += " ELSE 0 END) AS #{quoted_label}" unless get_all
|
102
|
+
else
|
103
|
+
if c.is_distinct?
|
104
|
+
agg_sql = " #{c.strategy_name}(distinct #{c.from_table_name}.#{c.name}) AS #{quoted_label}"
|
105
|
+
else
|
106
|
+
agg_sql = " #{c.strategy_name}(#{c.from_table_name}.#{c.name}) AS #{quoted_label}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
agg_sql
|
110
|
+
}.join(",\n")
|
111
|
+
|
112
|
+
sql = ''
|
113
|
+
sql += "SELECT\n"
|
114
|
+
sql += " #{column_dimension_name}.#{current_column_name} as #{column_dimension_name}_1_#{current_column_name},\n"
|
115
|
+
sql += " #{row_dimension_name}.#{current_row_name} as #{row_dimension_name}_2_#{current_row_name},\n"
|
116
|
+
sql += fact_columns
|
117
|
+
sql += "\nFROM\n"
|
118
|
+
|
119
|
+
sql += " #{fact_class.table_name}"
|
120
|
+
cube_class.dimensions_hierarchies.each do |dimension_name, hierarchy_names|
|
121
|
+
next if !used_dimensions.include?(dimension_name)
|
122
|
+
dimension = fact_class.dimension_class(dimension_name)
|
123
|
+
if !dimension.hierarchical_dimension?
|
124
|
+
if fact_class.belongs_to_relationship?(dimension_name)
|
125
|
+
sql += "\nJOIN #{dimension.table_name} as #{dimension_name}"
|
126
|
+
sql += "\n ON #{fact_class.table_name}.#{fact_class.foreign_key_for(dimension_name)} = "
|
127
|
+
sql += "#{dimension_name}.#{dimension.primary_key}"
|
128
|
+
elsif fact_class.has_and_belongs_to_many_relationship?(dimension_name)
|
129
|
+
relationship = fact_class.dimension_relationship(dimension_name)
|
130
|
+
sql += "\nJOIN #{relationship.options[:join_table]} as #{dimension_name}_bridge"
|
131
|
+
sql += "\n ON #{fact_class.table_name}.#{fact_class.primary_key} = "
|
132
|
+
sql += "#{dimension_name}_bridge.#{relationship.options[:foreign_key]}"
|
133
|
+
sql += "\nJOIN #{dimension.table_name} as #{dimension_name}"
|
134
|
+
sql += "\n ON #{dimension_name}_bridge.#{relationship.options[:association_foreign_key]} = "
|
135
|
+
sql += "#{dimension_name}.#{dimension.primary_key}"
|
136
|
+
end
|
137
|
+
else
|
138
|
+
dimension_bridge = dimension.bridge_class
|
139
|
+
sql += "\nJOIN #{dimension_bridge.table_name}"
|
140
|
+
sql += "\n ON #{fact_class.table_name}.#{fact_class.foreign_key_for(dimension_name)} = "
|
141
|
+
sql += "#{dimension_bridge.table_name}.#{dimension.parent_foreign_key}"
|
142
|
+
if dimension.slowly_changing_dimension?
|
143
|
+
sql += " and (#{dimension_bridge.table_name}.#{dimension_bridge.effective_date} <= "
|
144
|
+
sql += "#{fact_class.slowly_changes_over_name(dimension_name)}."
|
145
|
+
sql += "#{fact_class.slowly_changes_over_class(dimension_name).sql_date_stamp} "
|
146
|
+
sql += "and #{dimension_bridge.table_name}.#{dimension_bridge.expiration_date} >= "
|
147
|
+
sql += "#{fact_class.slowly_changes_over_name(dimension_name)}."
|
148
|
+
sql += "#{fact_class.slowly_changes_over_class(dimension_name).sql_date_stamp}) "
|
149
|
+
end
|
150
|
+
sql += "\nJOIN #{dimension.table_name} as #{dimension_name}"
|
151
|
+
sql += "\n ON #{dimension_bridge.table_name}.#{dimension.child_foreign_key} = "
|
152
|
+
sql += "#{dimension_name}.#{dimension.primary_key}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# build the where clause
|
157
|
+
# first add conditions
|
158
|
+
where_clause = Array(conditions)
|
159
|
+
|
160
|
+
# apply filters
|
161
|
+
filters.each do |key, value|
|
162
|
+
dimension_name, column = key.split('.')
|
163
|
+
where_clause << "#{dimension_name}.#{column} = #{cube_class.connection.quote(value)}"
|
164
|
+
end
|
165
|
+
sql += %Q(\nWHERE\n #{where_clause.join(" AND\n ")} ) if where_clause.length > 0
|
166
|
+
|
167
|
+
# for hierarchical dimension we need to add where clause in for drill downs
|
168
|
+
if !hierarchical_dimension.nil?
|
169
|
+
if where_clause.length == 0
|
170
|
+
sql += "\n WHERE "
|
171
|
+
else
|
172
|
+
sql += " \n AND "
|
173
|
+
end
|
174
|
+
sql += "\n #{hierarchical_dimension_name}.#{hierarchical_dimension.primary_key} IN ( "
|
175
|
+
sql += "\n SELECT #{hierarchical_dimension.parent_foreign_key} FROM #{hierarchical_dimension.bridge_class.table_name} "
|
176
|
+
if hierarchical_stage == 0
|
177
|
+
sql += "\n WHERE #{hierarchical_dimension.bridge_class.top_flag} = #{connection.send(:quote, hierarchical_dimension.bridge_class.top_flag_value)})"
|
178
|
+
else
|
179
|
+
sql += "\n WHERE #{hierarchical_dimension.child_foreign_key} = #{hierarchical_stage} AND #{hierarchical_dimension.levels_from_parent} = 1)"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
sql += "\nGROUP BY\n"
|
184
|
+
sql += " #{column_dimension_name}.#{current_column_name},\n"
|
185
|
+
sql += " #{row_dimension_name}.#{current_row_name}"
|
186
|
+
|
187
|
+
if options[:order]
|
188
|
+
order_by = options[:order]
|
189
|
+
order_by = [order_by] if order_by.is_a?(String)
|
190
|
+
order_by.collect!{ |v| cube_class.connection.quote_column_name(order_by) }
|
191
|
+
sql += %Q(\nORDER BY\n #{order_by.join(",\n")})
|
192
|
+
end
|
193
|
+
|
194
|
+
if options[:return] == :sql
|
195
|
+
sql
|
196
|
+
else
|
197
|
+
result = ActiveWarehouse::CubeQueryResult.new(
|
198
|
+
cube_class.aggregate_fields(used_dimensions)
|
199
|
+
)
|
200
|
+
|
201
|
+
cube_class.connection.select_all(sql).each do |row|
|
202
|
+
result.add_data(row.delete("#{row_dimension_name}_2_#{current_row_name}"),
|
203
|
+
row.delete("#{column_dimension_name}_1_#{current_column_name}"),
|
204
|
+
row) # the rest of the members of row are the fact columns
|
205
|
+
end
|
206
|
+
|
207
|
+
result
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ActiveWarehouse #:nodoc:
|
2
|
+
module Aggregate #:nodoc:
|
3
|
+
# Implementation of a Partitioning and Inserting Dwarf algorithm as defined
|
4
|
+
# in http://www.zju.edu.cn/jzus/2005/A0506/A050608.pdf
|
5
|
+
class PidAggregate < Aggregate
|
6
|
+
include DwarfCommon
|
7
|
+
|
8
|
+
# Initialize the aggregate
|
9
|
+
def initialize(cube_class)
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
# Populate the aggregate
|
14
|
+
def populate
|
15
|
+
create_dwarf_cube(sorted_facts)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Query the aggregate
|
19
|
+
def query(*args)
|
20
|
+
options = parse_query_args(*args)
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_dwarf_cube(sorted_facts)
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module ActiveWarehouse
|
2
|
+
# Encapsulates a fact column in a fact table. These fields
|
3
|
+
# represent columns that should be aggregated.
|
4
|
+
class AggregateField < Field
|
5
|
+
|
6
|
+
attr_reader :strategy_name
|
7
|
+
|
8
|
+
# +fact_class+ is the class of the fact table this field is found in.
|
9
|
+
# +column_definition+ is the ActiveRecord ColumnDefinition instance for this
|
10
|
+
# column.
|
11
|
+
# +strategy_name+ is the name of th aggregation strategy to be used, defaults to :sum
|
12
|
+
# +field_options+ is a hash of raw options from the original aggregate definition.
|
13
|
+
def initialize(fact_class, column_definition, strategy_name = :sum, field_options = {})
|
14
|
+
super(fact_class, column_definition.name, column_definition.type, field_options)
|
15
|
+
@column_definition = column_definition
|
16
|
+
@limit = column_definition.limit
|
17
|
+
@scale = column_definition.scale
|
18
|
+
@precision = column_definition.precision
|
19
|
+
@strategy_name = strategy_name
|
20
|
+
end
|
21
|
+
|
22
|
+
# delegates to owning_class, returns the Fact that has this field
|
23
|
+
def fact_class
|
24
|
+
owning_class
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns true if the field is semi-additive
|
28
|
+
def is_semiadditive?
|
29
|
+
!field_options[:semiadditive].nil?
|
30
|
+
end
|
31
|
+
|
32
|
+
def is_distinct?
|
33
|
+
field_options[:distinct] and field_options[:distinct] == true
|
34
|
+
end
|
35
|
+
|
36
|
+
def is_count_distinct?
|
37
|
+
@strategy_name == :count and is_distinct?
|
38
|
+
end
|
39
|
+
|
40
|
+
# returns the Dimension that this semiadditive fact is over
|
41
|
+
def semiadditive_over
|
42
|
+
Dimension.to_dimension(field_options[:semiadditive])
|
43
|
+
end
|
44
|
+
|
45
|
+
# overrides Field.label, prepending the aggregation strategy name to label
|
46
|
+
def label
|
47
|
+
@label ? @label : "#{super}_#{strategy_name}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def levels_from_parent
|
51
|
+
field_options[:levels_from_parent].nil? ? [] : field_options[:levels_from_parent]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Typecast the specified value using the column definition
|
55
|
+
def type_cast(value)
|
56
|
+
@column_definition.type_cast(value)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ActiveWarehouse #:nodoc
|
2
|
+
# Implements a bridge table.
|
3
|
+
class Bridge < ActiveRecord::Base
|
4
|
+
class << self
|
5
|
+
# Get the table name. By default the table name will be the name of the
|
6
|
+
# bridge in singular form.
|
7
|
+
#
|
8
|
+
# Example: DepartmentHierarchyBridge will have a table called
|
9
|
+
# department_hierarchy_bridge
|
10
|
+
def table_name
|
11
|
+
name = self.name.demodulize.underscore
|
12
|
+
set_table_name(name)
|
13
|
+
name
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'active_warehouse/bridge/hierarchy_bridge'
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ActiveWarehouse #:nodoc:
|
2
|
+
# Bridge class that models ragged hierarchies.
|
3
|
+
class HierarchyBridge < Bridge
|
4
|
+
class << self
|
5
|
+
def set_levels_from_parent(name)
|
6
|
+
@levels_from_parent = name
|
7
|
+
end
|
8
|
+
|
9
|
+
def levels_from_parent
|
10
|
+
@levels_from_parent ||= "levels_from_parent"
|
11
|
+
end
|
12
|
+
|
13
|
+
def set_effective_date(name)
|
14
|
+
@effective_date = name
|
15
|
+
end
|
16
|
+
|
17
|
+
def effective_date
|
18
|
+
@effective_date ||= "effective_date"
|
19
|
+
end
|
20
|
+
|
21
|
+
def set_expiration_date(name)
|
22
|
+
@expiration_date = name
|
23
|
+
end
|
24
|
+
|
25
|
+
def expiration_date
|
26
|
+
@expiration_date ||= "expiration_date"
|
27
|
+
end
|
28
|
+
|
29
|
+
def set_top_flag(name)
|
30
|
+
@top_flag = name
|
31
|
+
end
|
32
|
+
|
33
|
+
def top_flag
|
34
|
+
@top_flag ||= "top_flag"
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_top_flag_value(value)
|
38
|
+
@top_flag_value = value
|
39
|
+
end
|
40
|
+
|
41
|
+
def top_flag_value
|
42
|
+
@top_flag_value ||= 'Y'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module ActiveWarehouse #:nodoc:
|
2
|
+
module Builder #:nodoc:
|
3
|
+
# A builder which will build a data structure which can be used to populate a date dimension using
|
4
|
+
# commonly used date dimension columns.
|
5
|
+
class DateDimensionBuilder
|
6
|
+
# Specify the start date for the first record
|
7
|
+
attr_accessor :start_date
|
8
|
+
|
9
|
+
# Specify the end date for the last record
|
10
|
+
attr_accessor :end_date
|
11
|
+
|
12
|
+
# Define any holiday indicators
|
13
|
+
attr_accessor :holiday_indicators
|
14
|
+
|
15
|
+
# Define the weekday indicators. The default array begins on Sunday and goes to Saturday.
|
16
|
+
cattr_accessor :weekday_indicators
|
17
|
+
@@weekday_indicators = ['Weekend','Weekday','Weekday','Weekday','Weekday','Weekday','Weekend']
|
18
|
+
|
19
|
+
# Initialize the builder.
|
20
|
+
#
|
21
|
+
# * <tt>start_date</tt>: The start date. Defaults to 5 years ago from today.
|
22
|
+
# * <tt>end_date</tt>: The end date. Defaults to now.
|
23
|
+
def initialize(start_date=Time.now.years_ago(5), end_date=Time.now)
|
24
|
+
@start_date = start_date.to_date
|
25
|
+
@end_date = end_date.to_date
|
26
|
+
@holiday_indicators = []
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns an array of hashes representing records in the dimension.
|
30
|
+
def build(options={})
|
31
|
+
(start_date..end_date).map { |date| record_from_date(date) }
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Returns a hash representing a record in the dimension. The values for each record are
|
37
|
+
# accessed by name.
|
38
|
+
def record_from_date(date)
|
39
|
+
time = date.to_time # need methods only available in Time
|
40
|
+
record = {}
|
41
|
+
record[:date] = time.strftime("%m/%d/%Y")
|
42
|
+
record[:full_date_description] = time.strftime("%B %d,%Y")
|
43
|
+
record[:day_of_week] = time.strftime("%A")
|
44
|
+
#record[:day_number_in_epoch] = time.to_i / 24
|
45
|
+
#record[:week_number_in_epoch] = time.to_i / (24 * 7)
|
46
|
+
#record[:month_number_in_epoch] = time.to_i / (24 * 7 * 30)
|
47
|
+
record[:day_number_in_calendar_month] = time.day
|
48
|
+
record[:day_number_in_calendar_year] = time.yday
|
49
|
+
record[:day_number_in_fiscal_month] = time.day # should this be different from CY?
|
50
|
+
record[:day_number_in_fiscal_year] = time.fiscal_year_yday
|
51
|
+
#record[:last_day_in_week_indicator] =
|
52
|
+
#record[:last_day_in_month_indicator] =
|
53
|
+
#record[:calendar_week_ending_date] =
|
54
|
+
record[:calendar_week] = "Week #{time.week}"
|
55
|
+
record[:calendar_week_number] = time.week
|
56
|
+
record[:calendar_week_number_in_year] = time.week
|
57
|
+
record[:calendar_month_name] = time.strftime("%B")
|
58
|
+
record[:calendar_month_number] = time.month
|
59
|
+
record[:calendar_month_number_in_year] = time.month
|
60
|
+
record[:calendar_year_month] = time.strftime("%Y-%m")
|
61
|
+
record[:calendar_quarter] = "Q#{time.quarter}"
|
62
|
+
record[:calendar_quarter_number] = time.quarter
|
63
|
+
record[:calendar_quarter_number_in_year] = time.quarter
|
64
|
+
record[:calendar_year_quarter] = "#{time.strftime('%Y')}-#{record[:calendar_quarter]}"
|
65
|
+
#record[:calendar_half_year] =
|
66
|
+
record[:calendar_year] = "#{time.year}"
|
67
|
+
record[:fiscal_week] = "FY Week #{time.fiscal_year_week}"
|
68
|
+
record[:fiscal_week_number] = time.fiscal_year_week
|
69
|
+
record[:fiscal_week_number_in_year] = time.fiscal_year_week
|
70
|
+
record[:fiscal_month] = time.fiscal_year_month
|
71
|
+
record[:fiscal_month_number] = time.fiscal_year_month
|
72
|
+
record[:fiscal_month_number_in_year] = time.fiscal_year_month
|
73
|
+
record[:fiscal_year_month] = "FY#{time.fiscal_year}-" + time.fiscal_year_month.to_s.rjust(2, '0')
|
74
|
+
record[:fiscal_quarter] = "FY Q#{time.fiscal_year_quarter}"
|
75
|
+
record[:fiscal_year_quarter] = "FY#{time.fiscal_year}-Q#{time.fiscal_year_quarter}"
|
76
|
+
record[:fiscal_quarter_number] = time.fiscal_year_quarter
|
77
|
+
record[:fiscal_year_quarter_number] = time.fiscal_year_quarter
|
78
|
+
#record[:fiscal_half_year] =
|
79
|
+
record[:fiscal_year] = "FY#{time.fiscal_year}"
|
80
|
+
record[:fiscal_year_number] = time.fiscal_year
|
81
|
+
record[:holiday_indicator] = holiday_indicators.include?(date) ? 'Holiday' : 'Nonholiday'
|
82
|
+
record[:weekday_indicator] = weekday_indicators[time.wday]
|
83
|
+
record[:selling_season] = 'None'
|
84
|
+
record[:major_event] = 'None'
|
85
|
+
record[:sql_date_stamp] = date
|
86
|
+
|
87
|
+
record
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|