mondrian-olap 0.4.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.rspec +2 -0
  2. data/Changelog.md +60 -0
  3. data/Gemfile +21 -0
  4. data/LICENSE-Mondrian.html +259 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +302 -0
  7. data/RUNNING_TESTS.rdoc +66 -0
  8. data/Rakefile +48 -0
  9. data/VERSION +1 -0
  10. data/lib/mondrian-olap.rb +1 -0
  11. data/lib/mondrian/jars/commons-collections-3.1.jar +0 -0
  12. data/lib/mondrian/jars/commons-dbcp-1.2.1.jar +0 -0
  13. data/lib/mondrian/jars/commons-logging-1.0.4.jar +0 -0
  14. data/lib/mondrian/jars/commons-math-1.0.jar +0 -0
  15. data/lib/mondrian/jars/commons-pool-1.2.jar +0 -0
  16. data/lib/mondrian/jars/commons-vfs-1.0.jar +0 -0
  17. data/lib/mondrian/jars/eigenbase-properties.jar +0 -0
  18. data/lib/mondrian/jars/eigenbase-resgen.jar +0 -0
  19. data/lib/mondrian/jars/eigenbase-xom.jar +0 -0
  20. data/lib/mondrian/jars/javacup.jar +0 -0
  21. data/lib/mondrian/jars/log4j-1.2.8.jar +0 -0
  22. data/lib/mondrian/jars/log4j.properties +5 -0
  23. data/lib/mondrian/jars/mondrian.jar +0 -0
  24. data/lib/mondrian/jars/olap4j.jar +0 -0
  25. data/lib/mondrian/olap.rb +17 -0
  26. data/lib/mondrian/olap/connection.rb +201 -0
  27. data/lib/mondrian/olap/cube.rb +297 -0
  28. data/lib/mondrian/olap/error.rb +57 -0
  29. data/lib/mondrian/olap/query.rb +342 -0
  30. data/lib/mondrian/olap/result.rb +264 -0
  31. data/lib/mondrian/olap/schema.rb +378 -0
  32. data/lib/mondrian/olap/schema_element.rb +153 -0
  33. data/lib/mondrian/olap/schema_udf.rb +282 -0
  34. data/mondrian-olap.gemspec +128 -0
  35. data/spec/connection_role_spec.rb +130 -0
  36. data/spec/connection_spec.rb +72 -0
  37. data/spec/cube_spec.rb +318 -0
  38. data/spec/fixtures/MondrianTest.xml +134 -0
  39. data/spec/fixtures/MondrianTestOracle.xml +134 -0
  40. data/spec/mondrian_spec.rb +53 -0
  41. data/spec/query_spec.rb +807 -0
  42. data/spec/rake_tasks.rb +260 -0
  43. data/spec/schema_definition_spec.rb +1249 -0
  44. data/spec/spec_helper.rb +134 -0
  45. data/spec/support/matchers/be_like.rb +24 -0
  46. metadata +278 -0
@@ -0,0 +1,57 @@
1
+ module Mondrian
2
+ module OLAP
3
+
4
+ NATIVE_ERROR_REGEXP = /^(org\.olap4j\.|mondrian\.|java\.lang\.reflect\.UndeclaredThrowableException\: Mondrian Error\:)/
5
+
6
+ class Error < StandardError
7
+ # root_cause will be nil if there is no cause for wrapped native error
8
+ # root_cause_message will have either root_cause message or wrapped native error message
9
+ attr_reader :native_error, :root_cause_message, :root_cause
10
+
11
+ def initialize(native_error)
12
+ @native_error = native_error
13
+ get_root_cause
14
+ super(native_error.message)
15
+ add_root_cause_to_backtrace
16
+ end
17
+
18
+ def self.wrap_native_exception
19
+ yield
20
+ rescue NativeException => e
21
+ if e.message =~ NATIVE_ERROR_REGEXP
22
+ raise Mondrian::OLAP::Error.new(e)
23
+ else
24
+ raise
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def get_root_cause
31
+ @root_cause = nil
32
+ e = @native_error
33
+ while e.respond_to?(:cause) && (cause = e.cause)
34
+ @root_cause = e = cause
35
+ end
36
+ message = e.message
37
+ if message =~ /\AMondrian Error:(.*)\Z/m
38
+ message = $1
39
+ end
40
+ @root_cause_message = message
41
+ end
42
+
43
+ def add_root_cause_to_backtrace
44
+ bt = @native_error.backtrace
45
+ if @root_cause
46
+ root_cause_bt = Array(@root_cause.backtrace)
47
+ root_cause_bt[0,5].reverse.each do |bt_line|
48
+ bt.unshift "root cause: #{bt_line}"
49
+ end
50
+ bt.unshift "root cause: #{@root_cause.java_class.name}: #{@root_cause.message.chomp}"
51
+ end
52
+ set_backtrace bt
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,342 @@
1
+ module Mondrian
2
+ module OLAP
3
+ class Query
4
+ def self.from(connection, cube_name)
5
+ query = self.new(connection)
6
+ query.cube_name = cube_name
7
+ query
8
+ end
9
+
10
+ attr_accessor :cube_name
11
+
12
+ def initialize(connection)
13
+ @connection = connection
14
+ @cube = nil
15
+ @axes = []
16
+ @where = []
17
+ @with = []
18
+ end
19
+
20
+ # Add new axis(i) to query
21
+ # or return array of axis(i) members if no arguments specified
22
+ def axis(i, *axis_members)
23
+ if axis_members.empty?
24
+ @axes[i]
25
+ else
26
+ @axes[i] ||= []
27
+ @current_set = @axes[i]
28
+ if axis_members.length == 1 && axis_members[0].is_a?(Array)
29
+ @current_set.concat(axis_members[0])
30
+ else
31
+ @current_set.concat(axis_members)
32
+ end
33
+ self
34
+ end
35
+ end
36
+
37
+ AXIS_ALIASES = %w(columns rows pages sections chapters)
38
+ AXIS_ALIASES.each_with_index do |axis, i|
39
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
40
+ def #{axis}(*axis_members)
41
+ axis(#{i}, *axis_members)
42
+ end
43
+ RUBY
44
+ end
45
+
46
+ %w(crossjoin nonempty_crossjoin).each do |method|
47
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
48
+ def #{method}(*axis_members)
49
+ raise ArgumentError, "cannot use #{method} method before axis or with_set method" unless @current_set
50
+ raise ArgumentError, "specify list of members for #{method} method" if axis_members.empty?
51
+ members = axis_members.length == 1 && axis_members[0].is_a?(Array) ? axis_members[0] : axis_members
52
+ @current_set.replace [:#{method}, @current_set.clone, members]
53
+ self
54
+ end
55
+ RUBY
56
+ end
57
+
58
+ def except(*axis_members)
59
+ raise ArgumentError, "cannot use except method before axis or with_set method" unless @current_set
60
+ raise ArgumentError, "specify list of members for except method" if axis_members.empty?
61
+ members = axis_members.length == 1 && axis_members[0].is_a?(Array) ? axis_members[0] : axis_members
62
+ if [:crossjoin, :nonempty_crossjoin].include? @current_set[0]
63
+ @current_set[2] = [:except, @current_set[2], members]
64
+ else
65
+ @current_set.replace [:except, @current_set.clone, members]
66
+ end
67
+ self
68
+ end
69
+
70
+ def nonempty
71
+ raise ArgumentError, "cannot use nonempty method before axis method" unless @current_set
72
+ @current_set.replace [:nonempty, @current_set.clone]
73
+ self
74
+ end
75
+
76
+ def filter(condition, options={})
77
+ raise ArgumentError, "cannot use filter method before axis or with_set method" unless @current_set
78
+ @current_set.replace [:filter, @current_set.clone, condition]
79
+ @current_set << options[:as] if options[:as]
80
+ self
81
+ end
82
+
83
+ def filter_nonempty
84
+ raise ArgumentError, "cannot use filter_nonempty method before axis or with_set method" unless @current_set
85
+ condition = "NOT ISEMPTY(S.CURRENT)"
86
+ @current_set.replace [:filter, @current_set.clone, condition, 'S']
87
+ self
88
+ end
89
+
90
+ VALID_ORDERS = ['ASC', 'BASC', 'DESC', 'BDESC']
91
+
92
+ def order(expression, direction)
93
+ raise ArgumentError, "cannot use order method before axis or with_set method" unless @current_set
94
+ direction = direction.to_s.upcase
95
+ raise ArgumentError, "invalid order direction #{direction.inspect}," <<
96
+ " should be one of #{VALID_ORDERS.inspect[1..-2]}" unless VALID_ORDERS.include?(direction)
97
+ @current_set.replace [:order, @current_set.clone, expression, direction]
98
+ self
99
+ end
100
+
101
+ %w(top bottom).each do |extreme|
102
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
103
+ def #{extreme}_count(count, expression=nil)
104
+ raise ArgumentError, "cannot use #{extreme}_count method before axis or with_set method" unless @current_set
105
+ @current_set.replace [:#{extreme}_count, @current_set.clone, count]
106
+ @current_set << expression if expression
107
+ self
108
+ end
109
+ RUBY
110
+
111
+ %w(percent sum).each do |extreme_name|
112
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
113
+ def #{extreme}_#{extreme_name}(value, expression)
114
+ raise ArgumentError, "cannot use #{extreme}_#{extreme_name} method before axis or with_set method" unless @current_set
115
+ @current_set.replace [:#{extreme}_#{extreme_name}, @current_set.clone, value, expression]
116
+ self
117
+ end
118
+ RUBY
119
+ end
120
+ end
121
+
122
+ def hierarchize(order=nil, all=nil)
123
+ raise ArgumentError, "cannot use hierarchize method before axis or with_set method" unless @current_set
124
+ order = order && order.to_s.upcase
125
+ raise ArgumentError, "invalid hierarchize order #{order.inspect}" unless order.nil? || order == 'POST'
126
+ if all.nil? && [:crossjoin, :nonempty_crossjoin].include?(@current_set[0])
127
+ @current_set[2] = [:hierarchize, @current_set[2]]
128
+ @current_set[2] << order if order
129
+ else
130
+ @current_set.replace [:hierarchize, @current_set.clone]
131
+ @current_set << order if order
132
+ end
133
+ self
134
+ end
135
+
136
+ def hierarchize_all(order=nil)
137
+ hierarchize(order, :all)
138
+ end
139
+
140
+ # Add new WHERE condition to query
141
+ # or return array of existing conditions if no arguments specified
142
+ def where(*members)
143
+ if members.empty?
144
+ @where
145
+ else
146
+ @current_set = @where
147
+ if members.length == 1 && members[0].is_a?(Array)
148
+ @where.concat(members[0])
149
+ else
150
+ @where.concat(members)
151
+ end
152
+ self
153
+ end
154
+ end
155
+
156
+ # Add definition of calculated member
157
+ def with_member(member_name)
158
+ @with << [:member, member_name]
159
+ @current_set = nil
160
+ self
161
+ end
162
+
163
+ # Add definition of named_set
164
+ def with_set(set_name)
165
+ @current_set = []
166
+ @with << [:set, set_name, @current_set]
167
+ self
168
+ end
169
+
170
+ # return array of member and set definitions
171
+ def with
172
+ @with
173
+ end
174
+
175
+ # Add definition to calculated member or to named set
176
+ def as(*params)
177
+ # definition of named set
178
+ if @current_set
179
+ if params.empty?
180
+ raise ArgumentError, "named set cannot be empty"
181
+ else
182
+ raise ArgumentError, "cannot use 'as' method before with_set method" unless @current_set.empty?
183
+ if params.length == 1 && params[0].is_a?(Array)
184
+ @current_set.concat(params[0])
185
+ else
186
+ @current_set.concat(params)
187
+ end
188
+ end
189
+ # definition of calculated member
190
+ else
191
+ member_definition = @with.last
192
+ options = params.last.is_a?(Hash) ? params.pop : nil
193
+ raise ArgumentError, "cannot use 'as' method before with_member method" unless member_definition &&
194
+ member_definition[0] == :member && member_definition.length == 2
195
+ raise ArgumentError, "calculated member definition should be single expression" unless params.length == 1
196
+ member_definition << params[0]
197
+ member_definition << options if options
198
+ end
199
+ self
200
+ end
201
+
202
+ def to_mdx
203
+ mdx = ""
204
+ mdx << "WITH #{with_to_mdx}\n" unless @with.empty?
205
+ mdx << "SELECT #{axis_to_mdx}\n"
206
+ mdx << "FROM #{from_to_mdx}"
207
+ mdx << "\nWHERE #{where_to_mdx}" unless @where.empty?
208
+ mdx
209
+ end
210
+
211
+ def execute
212
+ Error.wrap_native_exception do
213
+ @connection.execute to_mdx
214
+ end
215
+ end
216
+
217
+ private
218
+
219
+ # FIXME: keep original order of WITH MEMBER and WITH SET defitions
220
+ def with_to_mdx
221
+ @with.map do |definition|
222
+ case definition[0]
223
+ when :member
224
+ member_name = definition[1]
225
+ expression = definition[2]
226
+ options = definition[3]
227
+ options_string = ''
228
+ options && options.each do |option, value|
229
+ options_string << ", #{option.to_s.upcase} = #{quote_value(value)}"
230
+ end
231
+ "MEMBER #{member_name} AS #{quote_value(expression)}#{options_string}"
232
+ when :set
233
+ set_name = definition[1]
234
+ set_members = definition[2]
235
+ "SET #{set_name} AS #{quote_value(members_to_mdx(set_members))}"
236
+ end
237
+ end.join("\n")
238
+ end
239
+
240
+ def axis_to_mdx
241
+ mdx = ""
242
+ @axes.each_with_index do |axis_members, i|
243
+ axis_name = AXIS_ALIASES[i] ? AXIS_ALIASES[i].upcase : "AXIS(#{i})"
244
+ mdx << ",\n" if i > 0
245
+ mdx << members_to_mdx(axis_members) << " ON " << axis_name
246
+ end
247
+ mdx
248
+ end
249
+
250
+ MDX_FUNCTIONS = {
251
+ :top_count => 'TOPCOUNT',
252
+ :top_percent => 'TOPPERCENT',
253
+ :top_sum => 'TOPSUM',
254
+ :bottom_count => 'BOTTOMCOUNT',
255
+ :bottom_percent => 'BOTTOMPERCENT',
256
+ :bottom_sum => 'BOTTOMSUM'
257
+ }
258
+
259
+ def members_to_mdx(members)
260
+ # if only one member which does not end with ]
261
+ # then assume it is expression which returns set
262
+ # TODO: maybe always include also single expressions in {...} to avoid some edge cases?
263
+ if members.length == 1 && members[0][-1,1] != ']'
264
+ members[0]
265
+ elsif members[0].is_a?(Symbol)
266
+ case members[0]
267
+ when :crossjoin
268
+ "CROSSJOIN(#{members_to_mdx(members[1])}, #{members_to_mdx(members[2])})"
269
+ when :nonempty_crossjoin
270
+ "NONEMPTYCROSSJOIN(#{members_to_mdx(members[1])}, #{members_to_mdx(members[2])})"
271
+ when :except
272
+ "EXCEPT(#{members_to_mdx(members[1])}, #{members_to_mdx(members[2])})"
273
+ when :nonempty
274
+ "NON EMPTY #{members_to_mdx(members[1])}"
275
+ when :filter
276
+ as_alias = members[3] ? " AS #{members[3]}" : nil
277
+ "FILTER(#{members_to_mdx(members[1])}#{as_alias}, #{members[2]})"
278
+ when :order
279
+ "ORDER(#{members_to_mdx(members[1])}, #{expression_to_mdx(members[2])}, #{members[3]})"
280
+ when :top_count, :bottom_count
281
+ mdx = "#{MDX_FUNCTIONS[members[0]]}(#{members_to_mdx(members[1])}, #{members[2]}"
282
+ mdx << (members[3] ? ", #{expression_to_mdx(members[3])})" : ")")
283
+ when :top_percent, :top_sum, :bottom_percent, :bottom_sum
284
+ "#{MDX_FUNCTIONS[members[0]]}(#{members_to_mdx(members[1])}, #{members[2]}, #{expression_to_mdx(members[3])})"
285
+ when :hierarchize
286
+ "HIERARCHIZE(#{members_to_mdx(members[1])}#{members[2] && ", #{members[2]}"})"
287
+ else
288
+ raise ArgumentError, "Cannot generate MDX for invalid set operation #{members[0].inspect}"
289
+ end
290
+ else
291
+ "{#{members.join(', ')}}"
292
+ end
293
+ end
294
+
295
+ def expression_to_mdx(expression)
296
+ expression.is_a?(Array) ? "(#{expression.join(', ')})" : expression
297
+ end
298
+
299
+ def from_to_mdx
300
+ "[#{@cube_name}]"
301
+ end
302
+
303
+ def where_to_mdx
304
+ # generate set MDX expression
305
+ if @where[0].is_a?(Symbol) ||
306
+ @where.length > 1 && @where.map{|full_name| extract_dimension_name(full_name)}.uniq.length == 1
307
+ members_to_mdx(@where)
308
+ # generate tuple MDX expression
309
+ else
310
+ where_to_mdx_tuple
311
+ end
312
+ end
313
+
314
+ def where_to_mdx_tuple
315
+ mdx = '('
316
+ mdx << @where.map do |condition|
317
+ condition
318
+ end.join(', ')
319
+ mdx << ')'
320
+ end
321
+
322
+ def quote_value(value)
323
+ case value
324
+ when String
325
+ "'#{value.gsub("'", "''")}'"
326
+ when TrueClass, FalseClass
327
+ value ? 'TRUE' : 'FALSE'
328
+ when NilClass
329
+ 'NULL'
330
+ else
331
+ "#{value}"
332
+ end
333
+ end
334
+
335
+ def extract_dimension_name(full_name)
336
+ if full_name =~ /\A[^\[]*\[([^\]]+)\]/
337
+ $1
338
+ end
339
+ end
340
+ end
341
+ end
342
+ end
@@ -0,0 +1,264 @@
1
+ require 'nokogiri'
2
+ require 'bigdecimal'
3
+
4
+ module Mondrian
5
+ module OLAP
6
+ class Result
7
+ def initialize(connection, raw_cell_set)
8
+ @connection = connection
9
+ @raw_cell_set = raw_cell_set
10
+ end
11
+
12
+ def axes_count
13
+ axes.length
14
+ end
15
+
16
+ def axis_names
17
+ @axis_names ||= axis_positions(:getName)
18
+ end
19
+
20
+ def axis_full_names
21
+ @axis_full_names ||= axis_positions(:getUniqueName)
22
+ end
23
+
24
+ def axis_members
25
+ @axis_members ||= axis_positions(:to_member)
26
+ end
27
+
28
+ AXIS_SYMBOLS = [:column, :row, :page, :section, :chapter]
29
+ AXIS_SYMBOLS.each_with_index do |axis, i|
30
+ define_method :"#{axis}_names" do
31
+ axis_names[i]
32
+ end
33
+
34
+ define_method :"#{axis}_full_names" do
35
+ axis_full_names[i]
36
+ end
37
+
38
+ define_method :"#{axis}_members" do
39
+ axis_members[i]
40
+ end
41
+ end
42
+
43
+ def values(*axes_sequence)
44
+ values_using(:getValue, axes_sequence)
45
+ end
46
+
47
+ def formatted_values(*axes_sequence)
48
+ values_using(:getFormattedValue, axes_sequence)
49
+ end
50
+
51
+ def values_using(values_method, axes_sequence = [])
52
+ if axes_sequence.empty?
53
+ axes_sequence = (0...axes_count).to_a.reverse
54
+ elsif axes_sequence.size != axes_count
55
+ raise ArgumentError, "axes sequence size is not equal to result axes count"
56
+ end
57
+ recursive_values(values_method, axes_sequence, 0)
58
+ end
59
+
60
+ # format results in simple HTML table
61
+ def to_html(options = {})
62
+ case axes_count
63
+ when 1
64
+ builder = Nokogiri::XML::Builder.new do |doc|
65
+ doc.table do
66
+ doc.tr do
67
+ column_full_names.each do |column_full_name|
68
+ column_full_name = column_full_name.join(',') if column_full_name.is_a?(Array)
69
+ doc.th column_full_name, :align => 'right'
70
+ end
71
+ end
72
+ doc.tr do
73
+ (options[:formatted] ? formatted_values : values).each do |value|
74
+ doc.td value, :align => 'right'
75
+ end
76
+ end
77
+ end
78
+ end
79
+ builder.doc.to_html
80
+ when 2
81
+ builder = Nokogiri::XML::Builder.new do |doc|
82
+ doc.table do
83
+ doc.tr do
84
+ doc.th
85
+ column_full_names.each do |column_full_name|
86
+ column_full_name = column_full_name.join(',') if column_full_name.is_a?(Array)
87
+ doc.th column_full_name, :align => 'right'
88
+ end
89
+ end
90
+ (options[:formatted] ? formatted_values : values).each_with_index do |row, i|
91
+ doc.tr do
92
+ row_full_name = row_full_names[i].is_a?(Array) ? row_full_names[i].join(',') : row_full_names[i]
93
+ doc.th row_full_name, :align => 'left'
94
+ row.each do |cell|
95
+ doc.td cell, :align => 'right'
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ builder.doc.to_html
102
+ else
103
+ raise ArgumentError, "just columns and rows axes are supported"
104
+ end
105
+ end
106
+
107
+ # specify drill through cell position, for example, as
108
+ # :row => 0, :cell => 1
109
+ # specify max returned rows with :max_rows parameter
110
+ def drill_through(position_params = {})
111
+ Error.wrap_native_exception do
112
+ cell_params = []
113
+ axes_count.times do |i|
114
+ axis_symbol = AXIS_SYMBOLS[i]
115
+ raise ArgumentError, "missing position #{axis_symbol.inspect}" unless axis_position = position_params[axis_symbol]
116
+ cell_params << Java::JavaLang::Integer.new(axis_position)
117
+ end
118
+ raw_cell = @raw_cell_set.getCell(cell_params)
119
+ DrillThrough.from_raw_cell(raw_cell, position_params)
120
+ end
121
+ end
122
+
123
+ class DrillThrough
124
+ def self.from_raw_cell(raw_cell, params = {})
125
+ max_rows = params[:max_rows] || -1
126
+ # workaround to avoid calling raw_cell.drillThroughInternal private method
127
+ # which fails when running inside TorqueBox
128
+ cell_field = raw_cell.java_class.declared_field('cell')
129
+ cell_field.accessible = true
130
+ rolap_cell = cell_field.value(raw_cell)
131
+ if rolap_cell.canDrillThrough
132
+ sql_statement = rolap_cell.drillThroughInternal(max_rows, -1, nil, true, nil)
133
+ raw_result_set = sql_statement.getWrappedResultSet
134
+ new(raw_result_set)
135
+ end
136
+ end
137
+
138
+ def initialize(raw_result_set)
139
+ @raw_result_set = raw_result_set
140
+ end
141
+
142
+ def column_types
143
+ @column_types ||= (1..metadata.getColumnCount).map{|i| metadata.getColumnTypeName(i).to_sym}
144
+ end
145
+
146
+ def column_names
147
+ @column_names ||= begin
148
+ # if PostgreSQL then use getBaseColumnName as getColumnName returns empty string
149
+ if metadata.respond_to?(:getBaseColumnName)
150
+ (1..metadata.getColumnCount).map{|i| metadata.getBaseColumnName(i)}
151
+ else
152
+ (1..metadata.getColumnCount).map{|i| metadata.getColumnName(i)}
153
+ end
154
+ end
155
+ end
156
+
157
+ def table_names
158
+ @table_names ||= begin
159
+ # if PostgreSQL then use getBaseTableName as getTableName returns empty string
160
+ if metadata.respond_to?(:getBaseTableName)
161
+ (1..metadata.getColumnCount).map{|i| metadata.getBaseTableName(i)}
162
+ else
163
+ (1..metadata.getColumnCount).map{|i| metadata.getTableName(i)}
164
+ end
165
+ end
166
+ end
167
+
168
+ def column_labels
169
+ @column_labels ||= (1..metadata.getColumnCount).map{|i| metadata.getColumnLabel(i)}
170
+ end
171
+
172
+ def fetch
173
+ if @raw_result_set.next
174
+ row_values = []
175
+ column_types.each_with_index do |column_type, i|
176
+ row_values << Result.java_to_ruby_value(@raw_result_set.getObject(i+1), column_type)
177
+ end
178
+ row_values
179
+ else
180
+ @raw_result_set.close
181
+ nil
182
+ end
183
+ end
184
+
185
+ def rows
186
+ @rows ||= begin
187
+ rows_values = []
188
+ while row_values = fetch
189
+ rows_values << row_values
190
+ end
191
+ rows_values
192
+ end
193
+ end
194
+
195
+ private
196
+
197
+ def metadata
198
+ @metadata ||= @raw_result_set.getMetaData
199
+ end
200
+
201
+ end
202
+
203
+ def self.java_to_ruby_value(value, column_type = nil)
204
+ case value
205
+ when Numeric, String
206
+ value
207
+ when Java::JavaMath::BigDecimal
208
+ BigDecimal(value.to_s)
209
+ else
210
+ value
211
+ end
212
+ end
213
+
214
+ private
215
+
216
+ def axes
217
+ @axes ||= @raw_cell_set.getAxes
218
+ end
219
+
220
+ def axis_positions(map_method, join_with=false)
221
+ axes.map do |axis|
222
+ axis.getPositions.map do |position|
223
+ names = position.getMembers.map do |member|
224
+ if map_method == :to_member
225
+ Member.new(member)
226
+ else
227
+ member.send(map_method)
228
+ end
229
+ end
230
+ if names.size == 1
231
+ names[0]
232
+ elsif join_with
233
+ names.join(join_with)
234
+ else
235
+ names
236
+ end
237
+ end
238
+ end
239
+ end
240
+
241
+ AXIS_SYMBOL_TO_NUMBER = {
242
+ :columns => 0,
243
+ :rows => 1,
244
+ :pages => 2,
245
+ :sections => 3,
246
+ :chapters => 4
247
+ }.freeze
248
+
249
+ def recursive_values(value_method, axes_sequence, current_index, cell_params=[])
250
+ if axis_number = axes_sequence[current_index]
251
+ axis_number = AXIS_SYMBOL_TO_NUMBER[axis_number] if axis_number.is_a?(Symbol)
252
+ positions_size = axes[axis_number].getPositions.size
253
+ (0...positions_size).map do |i|
254
+ cell_params[axis_number] = Java::JavaLang::Integer.new(i)
255
+ recursive_values(value_method, axes_sequence, current_index + 1, cell_params)
256
+ end
257
+ else
258
+ self.class.java_to_ruby_value(@raw_cell_set.getCell(cell_params).send(value_method))
259
+ end
260
+ end
261
+
262
+ end
263
+ end
264
+ end