mondrian-olap 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/Changelog.md +38 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +302 -0
  5. data/VERSION +1 -1
  6. data/lib/mondrian/jars/commons-collections-3.2.jar +0 -0
  7. data/lib/mondrian/jars/commons-logging-1.1.1.jar +0 -0
  8. data/lib/mondrian/jars/commons-math-1.1.jar +0 -0
  9. data/lib/mondrian/jars/eigenbase-properties-1.1.2.jar +0 -0
  10. data/lib/mondrian/jars/eigenbase-resgen-1.3.1.jar +0 -0
  11. data/lib/mondrian/jars/eigenbase-xom-1.3.1.jar +0 -0
  12. data/lib/mondrian/jars/{javacup.jar → javacup-10k.jar} +0 -0
  13. data/lib/mondrian/jars/log4j-1.2.14.jar +0 -0
  14. data/lib/mondrian/jars/mondrian.jar +0 -0
  15. data/lib/mondrian/jars/olap4j-1.0.1.539.jar +0 -0
  16. data/lib/mondrian/olap.rb +2 -1
  17. data/lib/mondrian/olap/connection.rb +163 -32
  18. data/lib/mondrian/olap/cube.rb +163 -24
  19. data/lib/mondrian/olap/error.rb +57 -0
  20. data/lib/mondrian/olap/query.rb +52 -17
  21. data/lib/mondrian/olap/result.rb +298 -6
  22. data/lib/mondrian/olap/schema.rb +220 -29
  23. data/lib/mondrian/olap/schema_element.rb +31 -11
  24. data/lib/mondrian/olap/schema_udf.rb +331 -0
  25. data/lib/mondrian/olap/version.rb +5 -0
  26. data/spec/connection_role_spec.rb +130 -0
  27. data/spec/connection_spec.rb +36 -1
  28. data/spec/cube_spec.rb +137 -7
  29. data/spec/fixtures/MondrianTest.xml +4 -4
  30. data/spec/mondrian_spec.rb +53 -0
  31. data/spec/query_spec.rb +294 -11
  32. data/spec/rake_tasks.rb +8 -8
  33. data/spec/schema_definition_spec.rb +845 -26
  34. data/spec/spec_helper.rb +26 -17
  35. data/spec/support/matchers/be_like.rb +2 -2
  36. metadata +296 -237
  37. data/.rspec +0 -2
  38. data/Gemfile +0 -18
  39. data/README.rdoc +0 -221
  40. data/RUNNING_TESTS.rdoc +0 -66
  41. data/Rakefile +0 -46
  42. data/lib/mondrian/jars/commons-collections-3.1.jar +0 -0
  43. data/lib/mondrian/jars/commons-logging-1.0.4.jar +0 -0
  44. data/lib/mondrian/jars/commons-math-1.0.jar +0 -0
  45. data/lib/mondrian/jars/eigenbase-properties.jar +0 -0
  46. data/lib/mondrian/jars/eigenbase-resgen.jar +0 -0
  47. data/lib/mondrian/jars/eigenbase-xom.jar +0 -0
  48. data/lib/mondrian/jars/log4j-1.2.8.jar +0 -0
  49. data/lib/mondrian/jars/olap4j.jar +0 -0
  50. data/mondrian-olap.gemspec +0 -126
@@ -1,5 +1,20 @@
1
1
  module Mondrian
2
2
  module OLAP
3
+ module Annotated
4
+ private
5
+
6
+ def annotations_for(raw_element)
7
+ @annotations ||= begin
8
+ annotated = raw_element.unwrap(Java::MondrianOlap::Annotated.java_class)
9
+ annotations_hash = annotated.getAnnotationMap.to_hash
10
+ annotations_hash.each do |key, annotation|
11
+ annotations_hash[key] = annotation.getValue
12
+ end
13
+ annotations_hash
14
+ end
15
+ end
16
+ end
17
+
3
18
  class Cube
4
19
  def self.get(connection, name)
5
20
  if raw_cube = connection.raw_schema.getCubes.get(name)
@@ -12,10 +27,25 @@ module Mondrian
12
27
  @raw_cube = raw_cube
13
28
  end
14
29
 
30
+ attr_reader :raw_cube
31
+
15
32
  def name
16
33
  @name ||= @raw_cube.getName
17
34
  end
18
35
 
36
+ def description
37
+ @description ||= @raw_cube.getDescription
38
+ end
39
+
40
+ def caption
41
+ @caption ||= @raw_cube.getCaption
42
+ end
43
+
44
+ include Annotated
45
+ def annotations
46
+ annotations_for(@raw_cube)
47
+ end
48
+
19
49
  def dimensions
20
50
  @dimenstions ||= @raw_cube.getDimensions.map{|d| Dimension.new(self, d)}
21
51
  end
@@ -51,12 +81,20 @@ module Mondrian
51
81
  @raw_dimension = raw_dimension
52
82
  end
53
83
 
54
- attr_reader :cube
84
+ attr_reader :cube, :raw_dimension
55
85
 
56
86
  def name
57
87
  @name ||= @raw_dimension.getName
58
88
  end
59
89
 
90
+ def description
91
+ @description ||= @raw_dimension.getDescription
92
+ end
93
+
94
+ def caption
95
+ @caption ||= @raw_dimension.getCaption
96
+ end
97
+
60
98
  def full_name
61
99
  @full_name ||= @raw_dimension.getUniqueName
62
100
  end
@@ -88,6 +126,12 @@ module Mondrian
88
126
  :standard
89
127
  end
90
128
  end
129
+
130
+ include Annotated
131
+ def annotations
132
+ annotations_for(@raw_dimension)
133
+ end
134
+
91
135
  end
92
136
 
93
137
  class Hierarchy
@@ -96,10 +140,20 @@ module Mondrian
96
140
  @raw_hierarchy = raw_hierarchy
97
141
  end
98
142
 
143
+ attr_reader :raw_hierarchy
144
+
99
145
  def name
100
146
  @name ||= @raw_hierarchy.getName
101
147
  end
102
148
 
149
+ def description
150
+ @description ||= @raw_hierarchy.getDescription
151
+ end
152
+
153
+ def caption
154
+ @caption ||= @raw_hierarchy.getCaption
155
+ end
156
+
103
157
  def levels
104
158
  @levels = @raw_hierarchy.getLevels.map{|l| Level.new(self, l)}
105
159
  end
@@ -137,14 +191,22 @@ module Mondrian
137
191
  end
138
192
 
139
193
  def child_names(*parent_member_segment_names)
140
- parent_member = if parent_member_segment_names.empty?
141
- return root_member_names unless has_all?
142
- all_member
143
- else
144
- @dimension.cube.member_by_segments(*parent_member_segment_names)
194
+ Error.wrap_native_exception do
195
+ parent_member = if parent_member_segment_names.empty?
196
+ return root_member_names unless has_all?
197
+ all_member
198
+ else
199
+ @dimension.cube.member_by_segments(*parent_member_segment_names)
200
+ end
201
+ parent_member && parent_member.children.map{|m| m.name}
145
202
  end
146
- parent_member && parent_member.children.map{|m| m.name}
147
203
  end
204
+
205
+ include Annotated
206
+ def annotations
207
+ annotations_for(@raw_hierarchy)
208
+ end
209
+
148
210
  end
149
211
 
150
212
  class Level
@@ -153,10 +215,20 @@ module Mondrian
153
215
  @raw_level = raw_level
154
216
  end
155
217
 
218
+ attr_reader :raw_level
219
+
156
220
  def name
157
221
  @name ||= @raw_level.getName
158
222
  end
159
223
 
224
+ def description
225
+ @description ||= @raw_level.getDescription
226
+ end
227
+
228
+ def caption
229
+ @caption ||= @raw_level.getCaption
230
+ end
231
+
160
232
  def depth
161
233
  @raw_level.getDepth
162
234
  end
@@ -170,14 +242,24 @@ module Mondrian
170
242
  if cardinality >= 0
171
243
  cardinality
172
244
  else
173
- @raw_level.getMembers.size
245
+ Error.wrap_native_exception do
246
+ @raw_level.getMembers.size
247
+ end
174
248
  end
175
249
  end
176
250
  end
177
251
 
178
252
  def members
179
- @raw_level.getMembers.map{|m| Member.new(m)}
253
+ Error.wrap_native_exception do
254
+ @raw_level.getMembers.map{|m| Member.new(m)}
255
+ end
180
256
  end
257
+
258
+ include Annotated
259
+ def annotations
260
+ annotations_for(@raw_level)
261
+ end
262
+
181
263
  end
182
264
 
183
265
  class Member
@@ -185,18 +267,32 @@ module Mondrian
185
267
  @raw_member = raw_member
186
268
  end
187
269
 
270
+ attr_reader :raw_member
271
+
188
272
  def name
189
- @raw_member.getName
273
+ @name ||= @raw_member.getName
190
274
  end
191
275
 
192
276
  def full_name
193
- @raw_member.getUniqueName
277
+ @full_name ||= @raw_member.getUniqueName
278
+ end
279
+
280
+ def caption
281
+ @caption ||= @raw_member.getCaption
194
282
  end
195
283
 
196
284
  def calculated?
197
285
  @raw_member.isCalculated
198
286
  end
199
287
 
288
+ def calculated_in_query?
289
+ @raw_member.isCalculatedInQuery
290
+ end
291
+
292
+ def visible?
293
+ @raw_member.isVisible
294
+ end
295
+
200
296
  def all_member?
201
297
  @raw_member.isAll
202
298
  end
@@ -226,26 +322,69 @@ module Mondrian
226
322
  end
227
323
 
228
324
  def children
229
- @raw_member.getChildMembers.map{|m| Member.new(m)}
325
+ Error.wrap_native_exception do
326
+ @raw_member.getChildMembers.map{|m| Member.new(m)}
327
+ end
230
328
  end
231
329
 
232
330
  def descendants_at_level(level)
233
- raw_level = @raw_member.getLevel
234
- raw_levels = raw_level.getHierarchy.getLevels
235
- current_level_index = raw_levels.indexOf(raw_level)
236
- descendants_level_index = raw_levels.indexOfName(level)
331
+ Error.wrap_native_exception do
332
+ raw_level = @raw_member.getLevel
333
+ raw_levels = raw_level.getHierarchy.getLevels
334
+ current_level_index = raw_levels.indexOf(raw_level)
335
+ descendants_level_index = raw_levels.indexOfName(level)
336
+
337
+ return nil unless descendants_level_index > current_level_index
338
+
339
+ members = [self]
340
+ (descendants_level_index - current_level_index).times do
341
+ members = members.map do |member|
342
+ member.children
343
+ end.flatten
344
+ end
345
+ members
346
+ end
347
+ end
348
+
349
+ def property_value(name)
350
+ if property = @raw_member.getProperties.get(name)
351
+ @raw_member.getPropertyValue(property)
352
+ end
353
+ end
354
+
355
+ def property_formatted_value(name)
356
+ if property = @raw_member.getProperties.get(name)
357
+ @raw_member.getPropertyFormattedValue(property)
358
+ end
359
+ end
360
+
361
+ include Annotated
362
+ def annotations
363
+ annotations_for(@raw_member)
364
+ end
237
365
 
238
- return nil unless descendants_level_index > current_level_index
366
+ def format_string
367
+ format_exp = property_value('FORMAT_EXP')
368
+ if format_exp && format_exp =~ /\A"(.*)"\z/
369
+ format_exp = $1
370
+ end
371
+ if format_exp && !format_exp.empty?
372
+ format_exp
373
+ end
374
+ end
239
375
 
240
- members = [self]
241
- (descendants_level_index - current_level_index).times do
242
- members = members.map do |member|
243
- member.children
244
- end.flatten
376
+ def cell_formatter_name
377
+ if dimension_type == :measures
378
+ cube_measure = raw_member.unwrap(Java::MondrianOlap::Member.java_class)
379
+ if value_formatter = cube_measure.getFormatter
380
+ f = value_formatter.java_class.declared_field('cf')
381
+ f.accessible = true
382
+ cf = f.value(value_formatter)
383
+ cf.class.name.split('::').last.gsub(/Udf\z/, '')
384
+ end
245
385
  end
246
- members
247
386
  end
248
387
 
249
388
  end
250
389
  end
251
- end
390
+ end
@@ -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
@@ -43,19 +43,23 @@ module Mondrian
43
43
  RUBY
44
44
  end
45
45
 
46
- def crossjoin(*axis_members)
47
- raise ArgumentError, "cannot use crossjoin method before axis or with_set method" unless @current_set
48
- raise ArgumentError, "specify list of members for crossjoin method" if axis_members.empty?
49
- members = axis_members.length == 1 && axis_members[0].is_a?(Array) ? axis_members[0] : axis_members
50
- @current_set.replace [:crossjoin, @current_set.clone, members]
51
- self
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
52
56
  end
53
57
 
54
58
  def except(*axis_members)
55
59
  raise ArgumentError, "cannot use except method before axis or with_set method" unless @current_set
56
60
  raise ArgumentError, "specify list of members for except method" if axis_members.empty?
57
61
  members = axis_members.length == 1 && axis_members[0].is_a?(Array) ? axis_members[0] : axis_members
58
- if @current_set[0] == :crossjoin
62
+ if [:crossjoin, :nonempty_crossjoin].include? @current_set[0]
59
63
  @current_set[2] = [:except, @current_set[2], members]
60
64
  else
61
65
  @current_set.replace [:except, @current_set.clone, members]
@@ -119,7 +123,7 @@ module Mondrian
119
123
  raise ArgumentError, "cannot use hierarchize method before axis or with_set method" unless @current_set
120
124
  order = order && order.to_s.upcase
121
125
  raise ArgumentError, "invalid hierarchize order #{order.inspect}" unless order.nil? || order == 'POST'
122
- if all.nil? && @current_set[0] == :crossjoin
126
+ if all.nil? && [:crossjoin, :nonempty_crossjoin].include?(@current_set[0])
123
127
  @current_set[2] = [:hierarchize, @current_set[2]]
124
128
  @current_set[2] << order if order
125
129
  else
@@ -185,7 +189,15 @@ module Mondrian
185
189
  # definition of calculated member
186
190
  else
187
191
  member_definition = @with.last
188
- options = params.last.is_a?(Hash) ? params.pop : nil
192
+ if params.last.is_a?(Hash)
193
+ options = params.pop
194
+ # if formatter does not include . then it should be ruby formatter name
195
+ if (formatter = options[:cell_formatter]) && !formatter.include?('.')
196
+ options = options.merge(:cell_formatter => Mondrian::OLAP::Schema::CellFormatter.new(formatter).class_name)
197
+ end
198
+ else
199
+ options = nil
200
+ end
189
201
  raise ArgumentError, "cannot use 'as' method before with_member method" unless member_definition &&
190
202
  member_definition[0] == :member && member_definition.length == 2
191
203
  raise ArgumentError, "calculated member definition should be single expression" unless params.length == 1
@@ -205,7 +217,19 @@ module Mondrian
205
217
  end
206
218
 
207
219
  def execute
208
- @connection.execute to_mdx
220
+ Error.wrap_native_exception do
221
+ @connection.execute to_mdx
222
+ end
223
+ end
224
+
225
+ def execute_drill_through(options = {})
226
+ Error.wrap_native_exception do
227
+ drill_through_mdx = "DRILLTHROUGH "
228
+ drill_through_mdx << "MAXROWS #{options[:max_rows]} " if options[:max_rows]
229
+ drill_through_mdx << to_mdx
230
+ drill_through_mdx << " RETURN #{Array(options[:return]).join(',')}" if options[:return]
231
+ @connection.execute_drill_through drill_through_mdx
232
+ end
209
233
  end
210
234
 
211
235
  private
@@ -220,7 +244,13 @@ module Mondrian
220
244
  options = definition[3]
221
245
  options_string = ''
222
246
  options && options.each do |option, value|
223
- options_string << ", #{option.to_s.upcase} = #{quote_value(value)}"
247
+ option_name = case option
248
+ when :caption
249
+ '$caption'
250
+ else
251
+ option.to_s.upcase
252
+ end
253
+ options_string << ", #{option_name} = #{quote_value(value)}"
224
254
  end
225
255
  "MEMBER #{member_name} AS #{quote_value(expression)}#{options_string}"
226
256
  when :set
@@ -251,12 +281,17 @@ module Mondrian
251
281
  }
252
282
 
253
283
  def members_to_mdx(members)
254
- if members.length == 1
284
+ # if only one member which does not end with ]
285
+ # then assume it is expression which returns set
286
+ # TODO: maybe always include also single expressions in {...} to avoid some edge cases?
287
+ if members.length == 1 && members[0][-1,1] != ']'
255
288
  members[0]
256
289
  elsif members[0].is_a?(Symbol)
257
290
  case members[0]
258
291
  when :crossjoin
259
292
  "CROSSJOIN(#{members_to_mdx(members[1])}, #{members_to_mdx(members[2])})"
293
+ when :nonempty_crossjoin
294
+ "NONEMPTYCROSSJOIN(#{members_to_mdx(members[1])}, #{members_to_mdx(members[2])})"
260
295
  when :except
261
296
  "EXCEPT(#{members_to_mdx(members[1])}, #{members_to_mdx(members[2])})"
262
297
  when :nonempty
@@ -294,13 +329,13 @@ module Mondrian
294
329
  if @where[0].is_a?(Symbol) ||
295
330
  @where.length > 1 && @where.map{|full_name| extract_dimension_name(full_name)}.uniq.length == 1
296
331
  members_to_mdx(@where)
297
- # generate tupple MDX expression
332
+ # generate tuple MDX expression
298
333
  else
299
- where_to_mdx_tupple
334
+ where_to_mdx_tuple
300
335
  end
301
336
  end
302
337
 
303
- def where_to_mdx_tupple
338
+ def where_to_mdx_tuple
304
339
  mdx = '('
305
340
  mdx << @where.map do |condition|
306
341
  condition
@@ -322,10 +357,10 @@ module Mondrian
322
357
  end
323
358
 
324
359
  def extract_dimension_name(full_name)
325
- if full_name =~ /\A\[([^\]]+)\]/
360
+ if full_name =~ /\A[^\[]*\[([^\]]+)\]/
326
361
  $1
327
362
  end
328
363
  end
329
364
  end
330
365
  end
331
- end
366
+ end