mainej-activewarehouse 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. data/activewarehouse/README +99 -0
  2. data/activewarehouse/Rakefile +165 -0
  3. data/activewarehouse/TODO +4 -0
  4. data/activewarehouse/db/migrations/001_create_table_reports.rb +28 -0
  5. data/activewarehouse/doc/references.txt +4 -0
  6. data/activewarehouse/generators/bridge/USAGE +1 -0
  7. data/activewarehouse/generators/bridge/bridge_generator.rb +46 -0
  8. data/activewarehouse/generators/bridge/templates/fixture.yml +5 -0
  9. data/activewarehouse/generators/bridge/templates/migration.rb +27 -0
  10. data/activewarehouse/generators/bridge/templates/model.rb +3 -0
  11. data/activewarehouse/generators/bridge/templates/unit_test.rb +8 -0
  12. data/activewarehouse/generators/cube/USAGE +1 -0
  13. data/activewarehouse/generators/cube/cube_generator.rb +28 -0
  14. data/activewarehouse/generators/cube/templates/model.rb +3 -0
  15. data/activewarehouse/generators/cube/templates/unit_test.rb +8 -0
  16. data/activewarehouse/generators/date_dimension/USAGE +1 -0
  17. data/activewarehouse/generators/date_dimension/date_dimension_generator.rb +16 -0
  18. data/activewarehouse/generators/date_dimension/templates/fixture.yml +5 -0
  19. data/activewarehouse/generators/date_dimension/templates/migration.rb +31 -0
  20. data/activewarehouse/generators/date_dimension/templates/model.rb +3 -0
  21. data/activewarehouse/generators/date_dimension/templates/unit_test.rb +8 -0
  22. data/activewarehouse/generators/dimension/USAGE +1 -0
  23. data/activewarehouse/generators/dimension/dimension_generator.rb +46 -0
  24. data/activewarehouse/generators/dimension/templates/fixture.yml +5 -0
  25. data/activewarehouse/generators/dimension/templates/migration.rb +11 -0
  26. data/activewarehouse/generators/dimension/templates/model.rb +3 -0
  27. data/activewarehouse/generators/dimension/templates/unit_test.rb +8 -0
  28. data/activewarehouse/generators/dimension_view/USAGE +1 -0
  29. data/activewarehouse/generators/dimension_view/dimension_view_generator.rb +62 -0
  30. data/activewarehouse/generators/dimension_view/templates/migration.rb +17 -0
  31. data/activewarehouse/generators/dimension_view/templates/model.rb +3 -0
  32. data/activewarehouse/generators/dimension_view/templates/unit_test.rb +10 -0
  33. data/activewarehouse/generators/fact/USAGE +1 -0
  34. data/activewarehouse/generators/fact/fact_generator.rb +46 -0
  35. data/activewarehouse/generators/fact/templates/fixture.yml +5 -0
  36. data/activewarehouse/generators/fact/templates/migration.rb +13 -0
  37. data/activewarehouse/generators/fact/templates/model.rb +3 -0
  38. data/activewarehouse/generators/fact/templates/unit_test.rb +10 -0
  39. data/activewarehouse/generators/time_dimension/USAGE +1 -0
  40. data/activewarehouse/generators/time_dimension/templates/fixture.yml +5 -0
  41. data/activewarehouse/generators/time_dimension/templates/migration.rb +12 -0
  42. data/activewarehouse/generators/time_dimension/templates/model.rb +3 -0
  43. data/activewarehouse/generators/time_dimension/templates/unit_test.rb +8 -0
  44. data/activewarehouse/generators/time_dimension/time_dimension_generator.rb +14 -0
  45. data/activewarehouse/init.rb +1 -0
  46. data/activewarehouse/install.rb +5 -0
  47. data/activewarehouse/lib/active_warehouse.rb +91 -0
  48. data/activewarehouse/lib/active_warehouse/aggregate.rb +75 -0
  49. data/activewarehouse/lib/active_warehouse/aggregate/dwarf_aggregate.rb +369 -0
  50. data/activewarehouse/lib/active_warehouse/aggregate/dwarf_common.rb +44 -0
  51. data/activewarehouse/lib/active_warehouse/aggregate/dwarf_printer.rb +34 -0
  52. data/activewarehouse/lib/active_warehouse/aggregate/no_aggregate.rb +212 -0
  53. data/activewarehouse/lib/active_warehouse/aggregate/pid_aggregate.rb +29 -0
  54. data/activewarehouse/lib/active_warehouse/aggregate_field.rb +59 -0
  55. data/activewarehouse/lib/active_warehouse/bridge.rb +19 -0
  56. data/activewarehouse/lib/active_warehouse/bridge/hierarchy_bridge.rb +46 -0
  57. data/activewarehouse/lib/active_warehouse/builder.rb +3 -0
  58. data/activewarehouse/lib/active_warehouse/builder/date_dimension_builder.rb +91 -0
  59. data/activewarehouse/lib/active_warehouse/builder/generator/generator.rb +13 -0
  60. data/activewarehouse/lib/active_warehouse/builder/generator/name_generator.rb +20 -0
  61. data/activewarehouse/lib/active_warehouse/builder/generator/paragraph_generator.rb +11 -0
  62. data/activewarehouse/lib/active_warehouse/builder/random_data_builder.rb +239 -0
  63. data/activewarehouse/lib/active_warehouse/builder/test_data_builder.rb +54 -0
  64. data/activewarehouse/lib/active_warehouse/calculated_field.rb +27 -0
  65. data/activewarehouse/lib/active_warehouse/compat/compat.rb +49 -0
  66. data/activewarehouse/lib/active_warehouse/core_ext.rb +1 -0
  67. data/activewarehouse/lib/active_warehouse/core_ext/time.rb +5 -0
  68. data/activewarehouse/lib/active_warehouse/core_ext/time/calculations.rb +40 -0
  69. data/activewarehouse/lib/active_warehouse/cube.rb +235 -0
  70. data/activewarehouse/lib/active_warehouse/cube_query_result.rb +69 -0
  71. data/activewarehouse/lib/active_warehouse/dimension.rb +329 -0
  72. data/activewarehouse/lib/active_warehouse/dimension/date_dimension.rb +15 -0
  73. data/activewarehouse/lib/active_warehouse/dimension/dimension_reflection.rb +21 -0
  74. data/activewarehouse/lib/active_warehouse/dimension/dimension_view.rb +27 -0
  75. data/activewarehouse/lib/active_warehouse/dimension/hierarchical_dimension.rb +99 -0
  76. data/activewarehouse/lib/active_warehouse/dimension/slowly_changing_dimension.rb +147 -0
  77. data/activewarehouse/lib/active_warehouse/fact.rb +239 -0
  78. data/activewarehouse/lib/active_warehouse/field.rb +74 -0
  79. data/activewarehouse/lib/active_warehouse/migrations.rb +64 -0
  80. data/activewarehouse/lib/active_warehouse/ordered_hash.rb +34 -0
  81. data/activewarehouse/lib/active_warehouse/prejoin_fact.rb +97 -0
  82. data/activewarehouse/lib/active_warehouse/report.rb +7 -0
  83. data/activewarehouse/lib/active_warehouse/report/abstract_report.rb +149 -0
  84. data/activewarehouse/lib/active_warehouse/report/chart_report.rb +9 -0
  85. data/activewarehouse/lib/active_warehouse/report/data_cell.rb +21 -0
  86. data/activewarehouse/lib/active_warehouse/report/data_column.rb +19 -0
  87. data/activewarehouse/lib/active_warehouse/report/data_row.rb +15 -0
  88. data/activewarehouse/lib/active_warehouse/report/dimension.rb +58 -0
  89. data/activewarehouse/lib/active_warehouse/report/table_report.rb +38 -0
  90. data/activewarehouse/lib/active_warehouse/version.rb +9 -0
  91. data/activewarehouse/lib/active_warehouse/view.rb +9 -0
  92. data/activewarehouse/lib/active_warehouse/view/crumb.rb +64 -0
  93. data/activewarehouse/lib/active_warehouse/view/report_helper.rb +98 -0
  94. data/activewarehouse/lib/active_warehouse/view/table_view.rb +134 -0
  95. data/activewarehouse/lib/active_warehouse/view/yui_adapter.rb +68 -0
  96. data/activewarehouse/tasks/active_warehouse_tasks.rake +122 -0
  97. metadata +237 -0
@@ -0,0 +1,75 @@
1
+ # Source file which defines the ActiveWarehouse::Aggregate module and imports
2
+ # the aggregate implementations.
3
+
4
+ module ActiveWarehouse #:nodoc:
5
+ # This module contains classes which handle aggregation of cube data using
6
+ # various algorithms
7
+ module Aggregate
8
+ # Base class for aggregate implementations
9
+ class Aggregate
10
+
11
+ # Reader for the cube class
12
+ attr_reader :cube_class
13
+
14
+ # Initialize the aggregate for the given cube class
15
+ def initialize(cube_class)
16
+ @cube_class = cube_class
17
+ end
18
+
19
+ # Helper that accepts two ActiveWarehouse::Report::Dimension
20
+ # objects, deconstructs them, and passes them on to +query+
21
+ # (implemented in inherited classes). This provides a
22
+ # cleaner interface to +query+, but it might be better to
23
+ # re-work +query+ itself.
24
+ def query_row_and_column(row, column, other_options = {})
25
+ query(other_options.reverse_merge(
26
+ :column_dimension_name => column.name,
27
+ :column_hierarchy_name => column.hierarchy_name,
28
+ :row_dimension_name => row.name,
29
+ :row_hierarchy_name => row.hierarchy_name,
30
+ :cstage => column.stage,
31
+ :rstage => row.stage,
32
+ :filters => column.query_filters.merge(row.query_filters))
33
+ )
34
+ end
35
+
36
+ protected
37
+ # Get the connection to use for SQL execution
38
+ def connection
39
+ cube_class.connection
40
+ end
41
+
42
+ # Convenience accessor to get the cube's fact class. Delegates to the
43
+ # cube class.
44
+ def fact_class
45
+ cube_class.fact_class
46
+ end
47
+
48
+ # Parse the query args and return an options hash.
49
+ def parse_query_args(*args)
50
+ options = {}
51
+ if args.length == 1
52
+ options = args[0]
53
+ elsif args.length >= 4
54
+ options[:column_dimension_name] = args[0]
55
+ options[:column_hierarchy_name] = args[1]
56
+ options[:row_dimension_name] = args[2]
57
+ options[:row_hierarchy_name] = args[3]
58
+ options[:conditions] = args[4] if args.length >= 5
59
+ options[:cstage] = args[5] if args.length >= 6
60
+ options[:rstage] = args[6] if args.length >= 7
61
+ options[:filters] = args[7] if args.length >= 8
62
+ options.merge!(args[8]) if args.length >= 9
63
+ else
64
+ raise ArgumentError, "The query method accepts either 1 Hash (new style) or 4 to 8 arguments (old style)"
65
+ end
66
+ options
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ require 'active_warehouse/aggregate/no_aggregate'
73
+ require 'active_warehouse/aggregate/dwarf_common'
74
+ require 'active_warehouse/aggregate/dwarf_aggregate'
75
+ require 'active_warehouse/aggregate/pid_aggregate'
@@ -0,0 +1,369 @@
1
+ module ActiveWarehouse #:nodoc:
2
+ module Aggregate #:nodoc:
3
+ # Implementation of the Dwarf algorithm described in
4
+ class DwarfAggregate < Aggregate
5
+ include DwarfCommon
6
+
7
+ # Initialize the aggregate
8
+ def initialize(cube_class)
9
+ super
10
+ end
11
+
12
+ # Populate the aggregate
13
+ def populate
14
+ create_dwarf_cube(sorted_facts)
15
+ end
16
+
17
+ # query
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]
27
+ rstage = options[:rstage]
28
+ filters = options[:filters]
29
+
30
+ column_dimension = Dimension.class_for_name(column_dimension_name)
31
+ row_dimension = Dimension.class_for_name(row_dimension_name)
32
+ column_hierarchy = column_dimension.hierarchy(column_hierarchy_name)
33
+ row_hierarchy = row_dimension.hierarchy(row_hierarchy_name)
34
+ dimension_ids = {}
35
+
36
+ dimension_order.each do |d|
37
+ where_clause = []
38
+ sql = "SELECT id FROM #{d.table_name}"
39
+ filters.each do |key, value|
40
+ dimension, column = key.split('.')
41
+ if d.table_name == dimension
42
+ where_clause << "#{dimension}.#{column} = '#{value}'" # TODO: protect from SQL injection
43
+ end
44
+ end
45
+ sql += %Q(\nWHERE\n #{where_clause.join(" AND\n ")}) if where_clause.length > 0
46
+ dimension_ids[d] = cube_class.connection.select_values(sql)
47
+ end
48
+ #puts "dimension ids: #{dimension_ids.inspect}"
49
+
50
+ values = Array.new(cube_class.fact_class.aggregate_fields.length, 0)
51
+
52
+ home_nodes = []
53
+ filter_nodes(@root_node, dimension_ids, 0, home_nodes)
54
+ #puts "filtered nodes: #{home_nodes.collect(&:id)}"
55
+
56
+ values
57
+ end
58
+
59
+ def filter_nodes(node, dimension_ids, depth, filtered_nodes)
60
+ #puts "filtering node #{print_node(node, depth, false)}"
61
+ dimension = dimension_order[depth]
62
+ #puts "dimension at #{depth} is #{dimension}"
63
+ node.cells.each do |c|
64
+ if dimension_ids[dimension].include?(c.key)
65
+ if depth == dimension_order.length - 1
66
+ filtered_nodes << node
67
+ else
68
+ filter_nodes(c.child, dimension_ids, depth+1, filtered_nodes) unless c.child.nil?
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ # Aggregate the node by summing all of the values in the cells
75
+ # TODO: support aggregations other than sum
76
+ def calculate_aggregate(cells)
77
+ value = Array.new(cells.first.value.length, 0)
78
+ cells.each do |c|
79
+ c.value.each_with_index do |v, index|
80
+ value[index] += v
81
+ end
82
+ end
83
+ value
84
+ end
85
+
86
+ # Create the dwarf cube with the sorted_facts
87
+ def create_dwarf_cube(sorted_facts)
88
+ last_tuple = nil
89
+ @last_nodes = nil
90
+ sorted_facts.each do |row|
91
+ tuple = row.is_a?(Hash) ? create_tuple(row) : row
92
+
93
+ prefix = calculate_prefix(tuple, last_tuple)
94
+
95
+ close_nodes(prefix).each do |n|
96
+ if n.leaf?
97
+ n.all_cell = Cell.new('*', calculate_aggregate(n.cells))
98
+ else
99
+ n.all_cell = Cell.new('*')
100
+ n.all_cell.child = suffix_coalesce(n.children)
101
+ end
102
+ n.processed = true
103
+ end
104
+
105
+ nodes = create_nodes(tuple, prefix)
106
+
107
+ write_nodes(nodes)
108
+ last_tuple = tuple
109
+ if @last_nodes.nil? then @root_node = nodes.first end
110
+ @last_nodes = nodes
111
+ end
112
+
113
+ # Alg 1, Line 13
114
+ last_leaf_node = @last_nodes.last
115
+ last_leaf_node.all_cell = Cell.new('*', calculate_aggregate(last_leaf_node.cells))
116
+
117
+ # Alg 1, Line 14
118
+ @last_nodes[0..@last_nodes.length - 2].reverse.each do |n|
119
+ n.all_cell = Cell.new('*')
120
+ n.all_cell.child = suffix_coalesce(n.children)
121
+ end
122
+
123
+ require File.dirname(__FILE__) + '/dwarf_printer'
124
+ puts DwarfPrinter.print_node(@root_node)
125
+ end
126
+
127
+ # Coalesce the nodes and return a single node
128
+ def suffix_coalesce(nodes)
129
+ if nodes.length == 1
130
+ return nodes[0]
131
+ else
132
+ sub_dwarf = Node.new
133
+ sub_dwarf.leaf = nodes.first.leaf
134
+
135
+ keys = sorted_keys(nodes)
136
+ keys.each do |k|
137
+ to_merge = []
138
+ nodes.each do |n|
139
+ n.cells.each do |c|
140
+ to_merge << c if c.key == k
141
+ end
142
+ end
143
+
144
+ if sub_dwarf.leaf?
145
+ cur_aggr = calculate_aggregate(to_merge) # Alg 2, Line 8
146
+ sub_dwarf.add_cell(Cell.new(k, cur_aggr)) # Alg 2, Line 9
147
+ else
148
+ # Alg 2, Line 11
149
+ cell = Cell.new(k)
150
+ cell.child = suffix_coalesce(to_merge.collect{|c| c.child})
151
+ sub_dwarf.add_cell(cell)
152
+ end
153
+ end
154
+
155
+ if sub_dwarf.leaf?
156
+ sub_dwarf.all_cell = Cell.new("*", calculate_aggregate(sub_dwarf.cells))
157
+ else
158
+ cell = Cell.new("*")
159
+ cell.child = suffix_coalesce(sub_dwarf.children)
160
+ sub_dwarf.all_cell = cell
161
+ end
162
+ end
163
+
164
+ sub_dwarf
165
+ end
166
+
167
+ # Get a list of sorted keys for the cells in the specified nodes
168
+ def sorted_keys(nodes)
169
+ keys = []
170
+ nodes.each do |n|
171
+ n.cells.each do |c|
172
+ keys << c.key
173
+ end
174
+ end
175
+ keys.uniq.sort { |a, b| a <=> b }
176
+ end
177
+
178
+ # Accessor for the number of dimensions in the cube.
179
+ attr_accessor :number_of_dimensions
180
+ def number_of_dimensions
181
+ @number_of_dimensions ||= cube_class.dimension_classes.length
182
+ end
183
+
184
+ # Calculates a common prefix between the two tuples
185
+ def calculate_prefix(current_tuple, last_tuple)
186
+ return [] if last_tuple.nil?
187
+ prefix = []
188
+ last_matched_index = nil
189
+ 0.upto(number_of_dimensions) do |i|
190
+ if current_tuple[i] == last_tuple[i]
191
+ prefix << current_tuple[i]
192
+ else
193
+ break
194
+ end
195
+ end
196
+ prefix
197
+ end
198
+
199
+ # Close all of the last nodes that match the specified prefix and return
200
+ # the list of newly closed nodes
201
+ def close_nodes(prefix)
202
+ new_closed = []
203
+ if @last_nodes
204
+ @last_nodes[prefix.length + 1, @last_nodes.length].each do |n|
205
+ n.closed = true
206
+ new_closed << n
207
+ end
208
+ end
209
+ new_closed
210
+ end
211
+
212
+ # Create the nodes for the current tuple
213
+ def create_nodes(current_tuple, prefix)
214
+ nodes = []
215
+ new_nodes_needed_for = []
216
+ if @last_nodes.nil?
217
+ 0.upto(number_of_dimensions - 1) do |i|
218
+ k = current_tuple[i]
219
+ parent_cell = (nodes.last.nil?) ? nil : nodes.last.cells.last
220
+ nodes << Node.new(k, parent_cell)
221
+ end
222
+ else
223
+ if prefix.length > 0
224
+ 0.upto(prefix.length - 1) do |i|
225
+ nodes << @last_nodes[i]
226
+ end
227
+ end
228
+ k = current_tuple[prefix.length]
229
+ n = @last_nodes[prefix.length]
230
+ n.add_cell(Cell.new(k))
231
+ nodes << n
232
+
233
+ (prefix.length + 1).upto(number_of_dimensions - 1) do |i|
234
+ k = current_tuple[i]
235
+ parent_cell = (nodes.last.nil?) ? nil : nodes.last.cells.last
236
+ nodes << Node.new(k, parent_cell)
237
+ end
238
+ end
239
+
240
+ nodes.last.leaf = true
241
+ cell = nodes.last.cells.last
242
+ unless cell.value
243
+ cell.value = current_tuple[number_of_dimensions..current_tuple.length-1]
244
+ end
245
+
246
+ nodes
247
+ end
248
+
249
+ # Write nodes to the filesystem.
250
+ def write_nodes(nodes)
251
+ # open(File.new(cube_class.name + '.dat'), 'w') do |f|
252
+ #
253
+ # end
254
+ end
255
+
256
+ class Cell
257
+ # The cell key, which will always be a dimension id
258
+ attr_accessor :key
259
+ # The child of the cell which will always be a node
260
+ attr_accessor :child
261
+ # The value of the cell which will only be non-nil in the cells that appear in nodes in the last dimension
262
+ attr_accessor :value
263
+ # The node that this cell is a member of
264
+ attr_accessor :node
265
+
266
+ def initialize(key, value=nil)
267
+ @key = key
268
+ @value = value
269
+ end
270
+
271
+ def child=(node)
272
+ node.parent = self
273
+ @child = node
274
+ end
275
+
276
+ def to_s
277
+ key
278
+ end
279
+ end
280
+
281
+ class Node
282
+ # A special cell which will hold either a reference to a sub node or the aggregate values for all
283
+ # of the values in the node's cells
284
+ attr_accessor :all_cell
285
+
286
+ # The parent cell or nil
287
+ attr_accessor :parent
288
+
289
+ # Set the true if the node is closed
290
+ attr_accessor :closed
291
+
292
+ # Set to true if the node has been processed
293
+ attr_accessor :processed
294
+
295
+ # Set to true if this node is a leaf node
296
+ attr_accessor :leaf
297
+
298
+ # Reader accessor for the node index, a sequential number identifying order of creation
299
+ attr_reader :index
300
+
301
+ @@sequence = 0
302
+
303
+ # Initialize the node with a cell that has the given key
304
+ def initialize(key=nil, parent_cell=nil)
305
+ @closed = false
306
+ @processed = false
307
+ @parent = parent_cell
308
+ @parent.child = self if @parent
309
+ @index = @@sequence += 1
310
+ #puts "creating node #{@index} with parent: #{@parent}"
311
+ add_cell(Cell.new(key)) if key
312
+ end
313
+
314
+ # Return an array of cells for the node
315
+ def cells
316
+ @cells ||= []
317
+ end
318
+
319
+ def keys
320
+ cells.collect { |cell| cell.key }
321
+ end
322
+
323
+ def has_cell_with_key?(key)
324
+ cells.each do |cell|
325
+ return true if cell.key == key
326
+ end
327
+ return false
328
+ end
329
+
330
+ def child(key)
331
+ cells.each do |cell|
332
+ return cell.child if cell.key == key
333
+ end
334
+ return nil
335
+ end
336
+
337
+ def children
338
+ cells.collect { |cell| cell.child }.compact
339
+ end
340
+
341
+ def closed?
342
+ closed
343
+ end
344
+
345
+ def processed?
346
+ processed
347
+ end
348
+
349
+ def leaf?
350
+ leaf
351
+ end
352
+
353
+ def add_cell(cell)
354
+ cell.node = self
355
+ cells << cell
356
+ end
357
+
358
+ def all_cell=(cell)
359
+ @all_cell = cell
360
+ @all_cell.node = self
361
+ end
362
+
363
+ def to_s
364
+ index.to_s
365
+ end
366
+ end
367
+ end
368
+ end
369
+ end
@@ -0,0 +1,44 @@
1
+ module ActiveWarehouse #:nodoc:
2
+ module Aggregate #:nodoc:
3
+ # Common methods for use inside dwarf implementations
4
+ module DwarfCommon
5
+ # Get the dimension order, defaults to sorting from highest cardinality to lowest
6
+ def dimension_order
7
+ @dimension_order ||= cube_class.dimension_classes.sort { |a, b| a.count <=> b.count }.reverse
8
+ end
9
+
10
+ # Set the dimension order
11
+ def dimension_order=(dimensions)
12
+ @dimension_order = dimensions
13
+ end
14
+
15
+ # Get the sorted fact rows for this cube, sorted by dimensions returned from dimension_order.
16
+ def sorted_facts
17
+ #puts "dimension order: #{dimension_order.inspect}"
18
+ # Determine the dimension to order by (high cardinality)
19
+ order_by = dimension_order.collect { |d| cube_class.fact_class.foreign_key_for(d) }.join(",")
20
+
21
+ # Get the sorted fact table
22
+ # TODO: determine if querying with select_all will bring the entire result set into memory
23
+ sql = "SELECT * FROM #{cube_class.fact_class.table_name} ORDER BY #{order_by}"
24
+ cube_class.connection.select_all(sql)
25
+ end
26
+
27
+ # Create a tuple from a row
28
+ def create_tuple(row)
29
+ fact_class = cube_class.fact_class
30
+ tuple = []
31
+ dimension_order.each do |d|
32
+ column_name = fact_class.foreign_key_for(d)
33
+ tuple << fact_class.columns_hash[column_name].type_cast(row[column_name])
34
+ end
35
+ fact_class.aggregate_fields.each do |f|
36
+ tuple << fact_class.columns_hash[f.to_s].type_cast(row[f.to_s])
37
+ end
38
+ #puts "tuple: #{tuple.inspect}"
39
+ tuple
40
+ end
41
+
42
+ end
43
+ end
44
+ end