mondrian-olap 0.5.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/Changelog.md +86 -0
- data/LICENSE-Mondrian.txt +87 -0
- data/LICENSE.txt +1 -1
- data/README.md +43 -40
- data/VERSION +1 -1
- data/lib/mondrian/jars/commons-collections-3.2.2.jar +0 -0
- data/lib/mondrian/jars/commons-dbcp-1.4.jar +0 -0
- data/lib/mondrian/jars/commons-io-2.2.jar +0 -0
- data/lib/mondrian/jars/commons-lang-2.6.jar +0 -0
- data/lib/mondrian/jars/commons-logging-1.2.jar +0 -0
- data/lib/mondrian/jars/commons-pool-1.5.7.jar +0 -0
- data/lib/mondrian/jars/commons-vfs2-2.2.jar +0 -0
- data/lib/mondrian/jars/eigenbase-xom-1.3.5.jar +0 -0
- data/lib/mondrian/jars/guava-17.0.jar +0 -0
- data/lib/mondrian/jars/log4j-1.2.17.jar +0 -0
- data/lib/mondrian/jars/log4j.properties +2 -4
- data/lib/mondrian/jars/mondrian-9.1.0.0.jar +0 -0
- data/lib/mondrian/jars/olap4j-1.2.0.jar +0 -0
- data/lib/mondrian/olap/connection.rb +252 -67
- data/lib/mondrian/olap/cube.rb +63 -2
- data/lib/mondrian/olap/error.rb +37 -8
- data/lib/mondrian/olap/query.rb +41 -21
- data/lib/mondrian/olap/result.rb +163 -44
- data/lib/mondrian/olap/schema.rb +42 -3
- data/lib/mondrian/olap/schema_element.rb +25 -6
- data/lib/mondrian/olap/schema_udf.rb +21 -16
- data/spec/connection_role_spec.rb +69 -13
- data/spec/connection_spec.rb +3 -2
- data/spec/cube_cache_control_spec.rb +261 -0
- data/spec/cube_spec.rb +32 -4
- data/spec/fixtures/MondrianTest.xml +1 -6
- data/spec/fixtures/MondrianTestOracle.xml +1 -6
- data/spec/mondrian_spec.rb +71 -1
- data/spec/query_spec.rb +323 -25
- data/spec/rake_tasks.rb +253 -159
- data/spec/schema_definition_spec.rb +314 -61
- data/spec/spec_helper.rb +115 -45
- data/spec/support/data/customers.csv +10902 -0
- data/spec/support/data/product_classes.csv +101 -0
- data/spec/support/data/products.csv +101 -0
- data/spec/support/data/sales.csv +101 -0
- data/spec/support/data/time.csv +731 -0
- metadata +126 -124
- data/LICENSE-Mondrian.html +0 -259
- data/lib/mondrian/jars/commons-collections-3.2.jar +0 -0
- data/lib/mondrian/jars/commons-dbcp-1.2.1.jar +0 -0
- data/lib/mondrian/jars/commons-logging-1.1.1.jar +0 -0
- data/lib/mondrian/jars/commons-pool-1.2.jar +0 -0
- data/lib/mondrian/jars/commons-vfs-1.0.jar +0 -0
- data/lib/mondrian/jars/eigenbase-xom-1.3.1.jar +0 -0
- data/lib/mondrian/jars/log4j-1.2.14.jar +0 -0
- data/lib/mondrian/jars/mondrian.jar +0 -0
- data/lib/mondrian/jars/olap4j-1.0.1.539.jar +0 -0
data/lib/mondrian/olap/cube.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
1
3
|
module Mondrian
|
2
4
|
module OLAP
|
3
5
|
module Annotated
|
@@ -16,6 +18,8 @@ module Mondrian
|
|
16
18
|
end
|
17
19
|
|
18
20
|
class Cube
|
21
|
+
extend Forwardable
|
22
|
+
|
19
23
|
def self.get(connection, name)
|
20
24
|
if raw_cube = connection.raw_schema.getCubes.get(name)
|
21
25
|
Cube.new(connection, raw_cube)
|
@@ -25,6 +29,7 @@ module Mondrian
|
|
25
29
|
def initialize(connection, raw_cube)
|
26
30
|
@connection = connection
|
27
31
|
@raw_cube = raw_cube
|
32
|
+
@cache_control = CacheControl.new(@connection, self)
|
28
33
|
end
|
29
34
|
|
30
35
|
attr_reader :raw_cube
|
@@ -46,6 +51,10 @@ module Mondrian
|
|
46
51
|
annotations_for(@raw_cube)
|
47
52
|
end
|
48
53
|
|
54
|
+
def visible?
|
55
|
+
@raw_cube.isVisible
|
56
|
+
end
|
57
|
+
|
49
58
|
def dimensions
|
50
59
|
@dimenstions ||= @raw_cube.getDimensions.map{|d| Dimension.new(self, d)}
|
51
60
|
end
|
@@ -73,6 +82,9 @@ module Mondrian
|
|
73
82
|
raw_member = @raw_cube.lookupMember(segment_list)
|
74
83
|
raw_member && Member.new(raw_member)
|
75
84
|
end
|
85
|
+
|
86
|
+
def_delegators :@cache_control, :flush_region_cache_with_segments, :flush_region_cache_with_segments
|
87
|
+
def_delegators :@cache_control, :flush_region_cache_with_full_names, :flush_region_cache_with_full_names
|
76
88
|
end
|
77
89
|
|
78
90
|
class Dimension
|
@@ -132,6 +144,10 @@ module Mondrian
|
|
132
144
|
annotations_for(@raw_dimension)
|
133
145
|
end
|
134
146
|
|
147
|
+
def visible?
|
148
|
+
@raw_dimension.isVisible
|
149
|
+
end
|
150
|
+
|
135
151
|
end
|
136
152
|
|
137
153
|
class Hierarchy
|
@@ -207,6 +223,10 @@ module Mondrian
|
|
207
223
|
annotations_for(@raw_hierarchy)
|
208
224
|
end
|
209
225
|
|
226
|
+
def visible?
|
227
|
+
@raw_hierarchy.isVisible
|
228
|
+
end
|
229
|
+
|
210
230
|
end
|
211
231
|
|
212
232
|
class Level
|
@@ -260,6 +280,10 @@ module Mondrian
|
|
260
280
|
annotations_for(@raw_level)
|
261
281
|
end
|
262
282
|
|
283
|
+
def visible?
|
284
|
+
@raw_level.isVisible
|
285
|
+
end
|
286
|
+
|
263
287
|
end
|
264
288
|
|
265
289
|
class Member
|
@@ -358,6 +382,10 @@ module Mondrian
|
|
358
382
|
end
|
359
383
|
end
|
360
384
|
|
385
|
+
def mondrian_member
|
386
|
+
@raw_member.unwrap(Java::MondrianOlap::Member.java_class)
|
387
|
+
end
|
388
|
+
|
361
389
|
include Annotated
|
362
390
|
def annotations
|
363
391
|
annotations_for(@raw_member)
|
@@ -374,17 +402,50 @@ module Mondrian
|
|
374
402
|
end
|
375
403
|
|
376
404
|
def cell_formatter_name
|
405
|
+
if cf = cell_formatter
|
406
|
+
cf.class.name.split('::').last.gsub(/Udf\z/, '')
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
def cell_formatter
|
377
411
|
if dimension_type == :measures
|
378
412
|
cube_measure = raw_member.unwrap(Java::MondrianOlap::Member.java_class)
|
379
413
|
if value_formatter = cube_measure.getFormatter
|
380
414
|
f = value_formatter.java_class.declared_field('cf')
|
381
415
|
f.accessible = true
|
382
|
-
|
383
|
-
cf.class.name.split('::').last.gsub(/Udf\z/, '')
|
416
|
+
f.value(value_formatter)
|
384
417
|
end
|
385
418
|
end
|
386
419
|
end
|
420
|
+
end
|
421
|
+
|
422
|
+
class CacheControl
|
423
|
+
def initialize(connection, cube)
|
424
|
+
@connection = connection
|
425
|
+
@cube = cube
|
426
|
+
@mondrian_cube = @cube.raw_cube.unwrap(Java::MondrianOlap::Cube.java_class)
|
427
|
+
@cache_control = @connection.raw_cache_control
|
428
|
+
end
|
387
429
|
|
430
|
+
def flush_region_cache_with_segments(*segment_names)
|
431
|
+
members = segment_names.map { |name| @cube.member_by_segments(*name).mondrian_member }
|
432
|
+
flush(members)
|
433
|
+
end
|
434
|
+
|
435
|
+
def flush_region_cache_with_full_names(*full_names)
|
436
|
+
members = full_names.map { |name| @cube.member(*name).mondrian_member }
|
437
|
+
flush(members)
|
438
|
+
end
|
439
|
+
|
440
|
+
private
|
441
|
+
|
442
|
+
def flush(members)
|
443
|
+
regions = members.map do |member|
|
444
|
+
@cache_control.create_member_region(member, true)
|
445
|
+
end
|
446
|
+
regions << @cache_control.create_measures_region(@mondrian_cube)
|
447
|
+
@cache_control.flush(@cache_control.create_crossjoin_region(*regions))
|
448
|
+
end
|
388
449
|
end
|
389
450
|
end
|
390
451
|
end
|
data/lib/mondrian/olap/error.rb
CHANGED
@@ -6,25 +6,43 @@ module Mondrian
|
|
6
6
|
class Error < StandardError
|
7
7
|
# root_cause will be nil if there is no cause for wrapped native error
|
8
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
|
9
|
+
attr_reader :native_error, :root_cause_message, :root_cause, :profiling_handler
|
10
10
|
|
11
|
-
def initialize(native_error)
|
11
|
+
def initialize(native_error, options = {})
|
12
12
|
@native_error = native_error
|
13
13
|
get_root_cause
|
14
|
-
super(native_error.
|
14
|
+
super(native_error.toString)
|
15
15
|
add_root_cause_to_backtrace
|
16
|
+
get_profiling(options)
|
16
17
|
end
|
17
18
|
|
18
|
-
def self.wrap_native_exception
|
19
|
+
def self.wrap_native_exception(options = {})
|
19
20
|
yield
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
# TokenMgrError for some unknown reason extends java.lang.Error which normally should not be rescued
|
22
|
+
rescue Java::JavaLang::Exception, Java::MondrianParser::TokenMgrError => e
|
23
|
+
if e.toString =~ NATIVE_ERROR_REGEXP
|
24
|
+
raise Mondrian::OLAP::Error.new(e, options)
|
23
25
|
else
|
24
26
|
raise
|
25
27
|
end
|
26
28
|
end
|
27
29
|
|
30
|
+
def profiling_plan
|
31
|
+
if profiling_handler && (plan = profiling_handler.plan)
|
32
|
+
plan.gsub("\r\n", "\n")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def profiling_timing
|
37
|
+
profiling_handler.timing if profiling_handler
|
38
|
+
end
|
39
|
+
|
40
|
+
def profiling_timing_string
|
41
|
+
if profiling_timing && (timing_string = profiling_timing.toString)
|
42
|
+
timing_string.gsub("\r\n", "\n")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
28
46
|
private
|
29
47
|
|
30
48
|
def get_root_cause
|
@@ -44,7 +62,7 @@ module Mondrian
|
|
44
62
|
bt = @native_error.backtrace
|
45
63
|
if @root_cause
|
46
64
|
root_cause_bt = Array(@root_cause.backtrace)
|
47
|
-
root_cause_bt[0,
|
65
|
+
root_cause_bt[0, 10].reverse.each do |bt_line|
|
48
66
|
bt.unshift "root cause: #{bt_line}"
|
49
67
|
end
|
50
68
|
bt.unshift "root cause: #{@root_cause.java_class.name}: #{@root_cause.message.chomp}"
|
@@ -52,6 +70,17 @@ module Mondrian
|
|
52
70
|
set_backtrace bt
|
53
71
|
end
|
54
72
|
|
73
|
+
def get_profiling(options)
|
74
|
+
if statement = options[:profiling_statement]
|
75
|
+
f = Java::mondrian.olap4j.MondrianOlap4jStatement.java_class.declared_field("openCellSet")
|
76
|
+
f.accessible = true
|
77
|
+
if cell_set = f.value(statement)
|
78
|
+
cell_set.close
|
79
|
+
@profiling_handler = statement.getProfileHandler
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
55
84
|
end
|
56
85
|
end
|
57
86
|
end
|
data/lib/mondrian/olap/query.rb
CHANGED
@@ -34,7 +34,7 @@ module Mondrian
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
AXIS_ALIASES = %w(columns rows pages sections
|
37
|
+
AXIS_ALIASES = %w(columns rows pages chapters sections)
|
38
38
|
AXIS_ALIASES.each_with_index do |axis, i|
|
39
39
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
40
40
|
def #{axis}(*axis_members)
|
@@ -47,7 +47,7 @@ module Mondrian
|
|
47
47
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
48
48
|
def #{method}(*axis_members)
|
49
49
|
raise ArgumentError, "cannot use #{method} method before axis or with_set method" unless @current_set
|
50
|
-
raise ArgumentError, "specify
|
50
|
+
raise ArgumentError, "specify set of members for #{method} method" if axis_members.empty?
|
51
51
|
members = axis_members.length == 1 && axis_members[0].is_a?(Array) ? axis_members[0] : axis_members
|
52
52
|
@current_set.replace [:#{method}, @current_set.clone, members]
|
53
53
|
self
|
@@ -57,7 +57,7 @@ module Mondrian
|
|
57
57
|
|
58
58
|
def except(*axis_members)
|
59
59
|
raise ArgumentError, "cannot use except method before axis or with_set method" unless @current_set
|
60
|
-
raise ArgumentError, "specify
|
60
|
+
raise ArgumentError, "specify set of members for except method" if axis_members.empty?
|
61
61
|
members = axis_members.length == 1 && axis_members[0].is_a?(Array) ? axis_members[0] : axis_members
|
62
62
|
if [:crossjoin, :nonempty_crossjoin].include? @current_set[0]
|
63
63
|
@current_set[2] = [:except, @current_set[2], members]
|
@@ -73,7 +73,13 @@ module Mondrian
|
|
73
73
|
self
|
74
74
|
end
|
75
75
|
|
76
|
-
def
|
76
|
+
def distinct
|
77
|
+
raise ArgumentError, "cannot use distinct method before axis method" unless @current_set
|
78
|
+
@current_set.replace [:distinct, @current_set.clone]
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
def filter(condition, options = {})
|
77
83
|
raise ArgumentError, "cannot use filter method before axis or with_set method" unless @current_set
|
78
84
|
@current_set.replace [:filter, @current_set.clone, condition]
|
79
85
|
@current_set << options[:as] if options[:as]
|
@@ -87,6 +93,19 @@ module Mondrian
|
|
87
93
|
self
|
88
94
|
end
|
89
95
|
|
96
|
+
def generate(*axis_members)
|
97
|
+
raise ArgumentError, "cannot use generate method before axis or with_set method" unless @current_set
|
98
|
+
all = if axis_members.last == :all
|
99
|
+
axis_members.pop
|
100
|
+
'ALL'
|
101
|
+
end
|
102
|
+
raise ArgumentError, "specify set of members for generate method" if axis_members.empty?
|
103
|
+
members = axis_members.length == 1 && axis_members[0].is_a?(Array) ? axis_members[0] : axis_members
|
104
|
+
@current_set.replace [:generate, @current_set.clone, members]
|
105
|
+
@current_set << all if all
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
90
109
|
VALID_ORDERS = ['ASC', 'BASC', 'DESC', 'BDESC']
|
91
110
|
|
92
111
|
def order(expression, direction)
|
@@ -119,7 +138,7 @@ module Mondrian
|
|
119
138
|
end
|
120
139
|
end
|
121
140
|
|
122
|
-
def hierarchize(order=nil, all=nil)
|
141
|
+
def hierarchize(order = nil, all = nil)
|
123
142
|
raise ArgumentError, "cannot use hierarchize method before axis or with_set method" unless @current_set
|
124
143
|
order = order && order.to_s.upcase
|
125
144
|
raise ArgumentError, "invalid hierarchize order #{order.inspect}" unless order.nil? || order == 'POST'
|
@@ -133,7 +152,7 @@ module Mondrian
|
|
133
152
|
self
|
134
153
|
end
|
135
154
|
|
136
|
-
def hierarchize_all(order=nil)
|
155
|
+
def hierarchize_all(order = nil)
|
137
156
|
hierarchize(order, :all)
|
138
157
|
end
|
139
158
|
|
@@ -216,20 +235,16 @@ module Mondrian
|
|
216
235
|
mdx
|
217
236
|
end
|
218
237
|
|
219
|
-
def execute
|
220
|
-
|
221
|
-
@connection.execute to_mdx
|
222
|
-
end
|
238
|
+
def execute(parameters = {})
|
239
|
+
@connection.execute to_mdx, parameters
|
223
240
|
end
|
224
241
|
|
225
242
|
def execute_drill_through(options = {})
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
@connection.execute_drill_through drill_through_mdx
|
232
|
-
end
|
243
|
+
drill_through_mdx = "DRILLTHROUGH "
|
244
|
+
drill_through_mdx << "MAXROWS #{options[:max_rows]} " if options[:max_rows]
|
245
|
+
drill_through_mdx << to_mdx
|
246
|
+
drill_through_mdx << " RETURN #{Array(options[:return]).join(',')}" if options[:return]
|
247
|
+
@connection.execute_drill_through drill_through_mdx
|
233
248
|
end
|
234
249
|
|
235
250
|
private
|
@@ -281,10 +296,10 @@ module Mondrian
|
|
281
296
|
}
|
282
297
|
|
283
298
|
def members_to_mdx(members)
|
284
|
-
# if only one member which does not end with ]
|
299
|
+
# if only one member which does not end with ] or .Item(...)
|
285
300
|
# then assume it is expression which returns set
|
286
301
|
# TODO: maybe always include also single expressions in {...} to avoid some edge cases?
|
287
|
-
if members.length == 1 && members[0]
|
302
|
+
if members.length == 1 && members[0] !~ /(\]|\.Item\(\d+\))\z/i
|
288
303
|
members[0]
|
289
304
|
elsif members[0].is_a?(Symbol)
|
290
305
|
case members[0]
|
@@ -296,9 +311,13 @@ module Mondrian
|
|
296
311
|
"EXCEPT(#{members_to_mdx(members[1])}, #{members_to_mdx(members[2])})"
|
297
312
|
when :nonempty
|
298
313
|
"NON EMPTY #{members_to_mdx(members[1])}"
|
314
|
+
when :distinct
|
315
|
+
"DISTINCT(#{members_to_mdx(members[1])})"
|
299
316
|
when :filter
|
300
317
|
as_alias = members[3] ? " AS #{members[3]}" : nil
|
301
318
|
"FILTER(#{members_to_mdx(members[1])}#{as_alias}, #{members[2]})"
|
319
|
+
when :generate
|
320
|
+
"GENERATE(#{members_to_mdx(members[1])}, #{members_to_mdx(members[2])}#{members[3] && ", #{members[3]}"})"
|
302
321
|
when :order
|
303
322
|
"ORDER(#{members_to_mdx(members[1])}, #{expression_to_mdx(members[2])}, #{members[3]})"
|
304
323
|
when :top_count, :bottom_count
|
@@ -357,8 +376,9 @@ module Mondrian
|
|
357
376
|
end
|
358
377
|
|
359
378
|
def extract_dimension_name(full_name)
|
360
|
-
|
361
|
-
|
379
|
+
# "[Foo [Bar]]].[Baz]" => "Foo [Bar]"
|
380
|
+
if full_name
|
381
|
+
full_name.gsub(/\A\[|\]\z/, '').split('].[').first.try(:gsub, ']]', ']')
|
362
382
|
end
|
363
383
|
end
|
364
384
|
end
|
data/lib/mondrian/olap/result.rb
CHANGED
@@ -3,11 +3,15 @@ require 'bigdecimal'
|
|
3
3
|
module Mondrian
|
4
4
|
module OLAP
|
5
5
|
class Result
|
6
|
-
def initialize(connection, raw_cell_set)
|
6
|
+
def initialize(connection, raw_cell_set, options = {})
|
7
7
|
@connection = connection
|
8
8
|
@raw_cell_set = raw_cell_set
|
9
|
+
@profiling_handler = options[:profiling_handler]
|
10
|
+
@total_duration = options[:total_duration]
|
9
11
|
end
|
10
12
|
|
13
|
+
attr_reader :raw_cell_set, :profiling_handler, :total_duration
|
14
|
+
|
11
15
|
def axes_count
|
12
16
|
axes.length
|
13
17
|
end
|
@@ -103,6 +107,32 @@ module Mondrian
|
|
103
107
|
end
|
104
108
|
end
|
105
109
|
|
110
|
+
def profiling_plan
|
111
|
+
if profiling_handler
|
112
|
+
@raw_cell_set.close
|
113
|
+
if plan = profiling_handler.plan
|
114
|
+
plan.gsub("\r\n", "\n")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def profiling_timing
|
120
|
+
if profiling_handler
|
121
|
+
@raw_cell_set.close
|
122
|
+
profiling_handler.timing
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def profiling_mark_full(name, duration)
|
127
|
+
profiling_timing && profiling_timing.markFull(name, duration)
|
128
|
+
end
|
129
|
+
|
130
|
+
def profiling_timing_string
|
131
|
+
if profiling_timing && (timing_string = profiling_timing.toString)
|
132
|
+
timing_string.gsub("\r\n", "\n")
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
106
136
|
# Specify drill through cell position, for example, as
|
107
137
|
# :row => 0, :cell => 1
|
108
138
|
# Specify max returned rows with :max_rows parameter
|
@@ -175,7 +205,7 @@ module Mondrian
|
|
175
205
|
if @raw_result_set.next
|
176
206
|
row_values = []
|
177
207
|
column_types.each_with_index do |column_type, i|
|
178
|
-
row_values << Result.java_to_ruby_value(@raw_result_set.getObject(i+1), column_type)
|
208
|
+
row_values << Result.java_to_ruby_value(@raw_result_set.getObject(i + 1), column_type)
|
179
209
|
end
|
180
210
|
row_values
|
181
211
|
else
|
@@ -240,25 +270,34 @@ module Mondrian
|
|
240
270
|
end
|
241
271
|
|
242
272
|
def self.generate_drill_through_sql(rolap_cell, result, params)
|
243
|
-
|
273
|
+
nonempty_columns, return_fields = parse_return_fields(result, params)
|
274
|
+
return_expressions = return_fields.map{|field| field[:member]}
|
244
275
|
|
245
276
|
sql_non_extended = rolap_cell.getDrillThroughSQL(return_expressions, false)
|
246
277
|
sql_extended = rolap_cell.getDrillThroughSQL(return_expressions, true)
|
247
278
|
|
248
|
-
if sql_non_extended =~ /\Aselect (.*) from (.*) where (.*) order by (.*)\Z/
|
279
|
+
if sql_non_extended =~ /\Aselect (.*) from (.*) where (.*) order by (.*)\Z/m
|
280
|
+
non_extended_from = $2
|
281
|
+
non_extended_where = $3
|
282
|
+
# the latest Mondrian version sometimes returns sql_non_extended without order by
|
283
|
+
elsif sql_non_extended =~ /\Aselect (.*) from (.*) where (.*)\Z/m
|
249
284
|
non_extended_from = $2
|
250
285
|
non_extended_where = $3
|
286
|
+
# if drill through total measure with just all members selection
|
287
|
+
elsif sql_non_extended =~ /\Aselect (.*) from (.*)\Z/m
|
288
|
+
non_extended_from = $2
|
289
|
+
non_extended_where = "1 = 1" # dummy true condition
|
251
290
|
else
|
252
291
|
raise ArgumentError, "cannot parse drill through SQL: #{sql_non_extended}"
|
253
292
|
end
|
254
293
|
|
255
|
-
if sql_extended =~ /\Aselect (.*) from (.*) where (.*) order by (.*)\Z/
|
294
|
+
if sql_extended =~ /\Aselect (.*) from (.*) where (.*) order by (.*)\Z/m
|
256
295
|
extended_select = $1
|
257
296
|
extended_from = $2
|
258
297
|
extended_where = $3
|
259
298
|
extended_order_by = $4
|
260
299
|
# if only measures are selected then there will be no order by
|
261
|
-
elsif sql_extended =~ /\Aselect (.*) from (.*) where (.*)\Z/
|
300
|
+
elsif sql_extended =~ /\Aselect (.*) from (.*) where (.*)\Z/m
|
262
301
|
extended_select = $1
|
263
302
|
extended_from = $2
|
264
303
|
extended_where = $3
|
@@ -267,25 +306,31 @@ module Mondrian
|
|
267
306
|
raise ArgumentError, "cannot parse drill through SQL: #{sql_extended}"
|
268
307
|
end
|
269
308
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
309
|
+
if return_fields.present?
|
310
|
+
new_select_columns = []
|
311
|
+
new_order_by_columns = []
|
312
|
+
new_group_by_columns = []
|
313
|
+
group_by = params[:group_by]
|
314
|
+
|
315
|
+
return_fields.size.times do |i|
|
316
|
+
column_alias = return_fields[i][:column_alias]
|
317
|
+
new_select_columns <<
|
318
|
+
if column_expression = return_fields[i][:column_expression]
|
319
|
+
new_order_by_columns << column_expression
|
320
|
+
new_group_by_columns << column_expression if group_by && return_fields[i][:type] != :measure
|
321
|
+
"#{column_expression} AS #{column_alias}"
|
322
|
+
else
|
323
|
+
"'' AS #{column_alias}"
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
new_select = new_select_columns.join(', ')
|
328
|
+
new_order_by = new_order_by_columns.join(', ')
|
329
|
+
new_group_by = new_group_by_columns.join(', ')
|
286
330
|
else
|
287
331
|
new_select = extended_select
|
288
332
|
new_order_by = extended_order_by
|
333
|
+
new_group_by = ''
|
289
334
|
end
|
290
335
|
|
291
336
|
new_from_parts = non_extended_from.split(/,\s*/)
|
@@ -321,40 +366,96 @@ module Mondrian
|
|
321
366
|
end
|
322
367
|
|
323
368
|
sql = "select #{new_select} from #{new_from} where #{new_where}"
|
369
|
+
sql << " group by #{new_group_by}" unless new_group_by.empty?
|
324
370
|
sql << " order by #{new_order_by}" unless new_order_by.empty?
|
325
371
|
sql
|
326
372
|
end
|
327
373
|
|
328
374
|
def self.parse_return_fields(result, params)
|
329
|
-
return_field_names = []
|
330
|
-
return_expressions = nil
|
331
375
|
nonempty_columns = []
|
376
|
+
return_fields = []
|
332
377
|
|
333
378
|
if params[:return] || params[:nonempty]
|
334
379
|
rolap_cube = result.getCube
|
335
380
|
schema_reader = rolap_cube.getSchemaReader
|
381
|
+
dialect = result.getCube.getSchema.getDialect
|
382
|
+
sql_query = Java::mondrian.rolap.sql.SqlQuery.new(dialect)
|
383
|
+
|
384
|
+
if fields = params[:return]
|
385
|
+
fields = fields.split(/,\s*/) if fields.is_a? String
|
386
|
+
fields.each do |field|
|
387
|
+
return_fields << case field
|
388
|
+
when /\AName\((.*)\)\z/i then
|
389
|
+
{ member_full_name: $1, type: :name }
|
390
|
+
when /\AProperty\((.*)\s*,\s*'(.*)'\)\z/i then
|
391
|
+
{ member_full_name: $1, type: :property, name: $2 }
|
392
|
+
else
|
393
|
+
{ member_full_name: field }
|
394
|
+
end
|
395
|
+
end
|
336
396
|
|
337
|
-
|
338
|
-
|
339
|
-
|
397
|
+
# Old versions of Oracle had a limit of 30 character identifiers.
|
398
|
+
# Do not limit it for other databases (as e.g. in MySQL aliases can be longer than column names)
|
399
|
+
max_alias_length = dialect.getMaxColumnNameLength
|
400
|
+
max_alias_length = nil if max_alias_length && max_alias_length > 30
|
401
|
+
|
402
|
+
return_fields.size.times do |i|
|
403
|
+
member_full_name = return_fields[i][:member_full_name]
|
340
404
|
begin
|
341
|
-
segment_list = Java::MondrianOlap::Util.parseIdentifier(
|
342
|
-
return_field_names << segment_list.to_a.last.name
|
405
|
+
segment_list = Java::MondrianOlap::Util.parseIdentifier(member_full_name)
|
343
406
|
rescue Java::JavaLang::IllegalArgumentException
|
344
|
-
raise ArgumentError, "invalid return field #{
|
407
|
+
raise ArgumentError, "invalid return field #{member_full_name}"
|
345
408
|
end
|
346
409
|
|
410
|
+
# if this is property field then the name is initialized already
|
411
|
+
return_fields[i][:name] ||= segment_list.to_a.last.name
|
347
412
|
level_or_member = schema_reader.lookupCompound rolap_cube, segment_list, false, 0
|
413
|
+
return_fields[i][:member] = level_or_member
|
414
|
+
|
415
|
+
if level_or_member.is_a? Java::MondrianOlap::Member
|
416
|
+
raise ArgumentError, "cannot use calculated member #{member_full_name} as return field" if level_or_member.isCalculated
|
417
|
+
elsif !level_or_member.is_a? Java::MondrianOlap::Level
|
418
|
+
raise ArgumentError, "return field #{member_full_name} should be level or measure"
|
419
|
+
end
|
420
|
+
|
421
|
+
return_fields[i][:column_expression] = case return_fields[i][:type]
|
422
|
+
when :name
|
423
|
+
if level_or_member.respond_to? :getNameExp
|
424
|
+
level_or_member.getNameExp.getExpression sql_query
|
425
|
+
end
|
426
|
+
when :property
|
427
|
+
if property = level_or_member.getProperties.to_a.detect{|p| p.getName == return_fields[i][:name]}
|
428
|
+
# property.getExp is a protected method therefore
|
429
|
+
# use a workaround to get the value from the field
|
430
|
+
f = property.java_class.declared_field("exp")
|
431
|
+
f.accessible = true
|
432
|
+
if column = f.value(property)
|
433
|
+
column.getExpression sql_query
|
434
|
+
end
|
435
|
+
end
|
436
|
+
else
|
437
|
+
if level_or_member.respond_to? :getKeyExp
|
438
|
+
return_fields[i][:type] = :key
|
439
|
+
level_or_member.getKeyExp.getExpression sql_query
|
440
|
+
else
|
441
|
+
return_fields[i][:type] = :measure
|
442
|
+
column_expression = level_or_member.getMondrianDefExpression.getExpression sql_query
|
443
|
+
if params[:group_by]
|
444
|
+
level_or_member.getAggregator.getExpression column_expression
|
445
|
+
else
|
446
|
+
column_expression
|
447
|
+
end
|
448
|
+
end
|
449
|
+
end
|
348
450
|
|
349
|
-
|
350
|
-
|
351
|
-
Java::MondrianMdx::LevelExpr.new level_or_member
|
352
|
-
when Java::MondrianOlap::Member
|
353
|
-
raise ArgumentError, "cannot use calculated member #{return_field} as return field" if level_or_member.isCalculated
|
354
|
-
Java::mondrian.mdx.MemberExpr.new level_or_member
|
451
|
+
column_alias = if return_fields[i][:type] == :key
|
452
|
+
"#{return_fields[i][:name]} (Key)"
|
355
453
|
else
|
356
|
-
|
454
|
+
return_fields[i][:name]
|
357
455
|
end
|
456
|
+
return_fields[i][:column_alias] = dialect.quoteIdentifier(
|
457
|
+
max_alias_length ? column_alias[0, max_alias_length] : column_alias
|
458
|
+
)
|
358
459
|
end
|
359
460
|
end
|
360
461
|
|
@@ -364,23 +465,22 @@ module Mondrian
|
|
364
465
|
begin
|
365
466
|
segment_list = Java::MondrianOlap::Util.parseIdentifier(nonempty_field)
|
366
467
|
rescue Java::JavaLang::IllegalArgumentException
|
367
|
-
raise ArgumentError, "invalid return field #{
|
468
|
+
raise ArgumentError, "invalid return field #{nonempty_field}"
|
368
469
|
end
|
369
470
|
member = schema_reader.lookupCompound rolap_cube, segment_list, false, 0
|
370
471
|
if member.is_a? Java::MondrianOlap::Member
|
371
|
-
raise ArgumentError, "cannot use calculated member #{
|
472
|
+
raise ArgumentError, "cannot use calculated member #{nonempty_field} as nonempty field" if member.isCalculated
|
372
473
|
sql_query = member.getStarMeasure.getSqlQuery
|
373
474
|
member.getStarMeasure.generateExprString(sql_query)
|
374
475
|
else
|
375
|
-
raise ArgumentError, "nonempty field #{
|
476
|
+
raise ArgumentError, "nonempty field #{nonempty_field} should be measure"
|
376
477
|
end
|
377
478
|
end
|
378
479
|
end
|
379
480
|
end
|
380
481
|
|
381
|
-
[
|
482
|
+
[nonempty_columns, return_fields]
|
382
483
|
end
|
383
|
-
|
384
484
|
end
|
385
485
|
|
386
486
|
def self.java_to_ruby_value(value, column_type = nil)
|
@@ -389,6 +489,8 @@ module Mondrian
|
|
389
489
|
value
|
390
490
|
when Java::JavaMath::BigDecimal
|
391
491
|
BigDecimal(value.to_s)
|
492
|
+
when Java::JavaSql::Clob
|
493
|
+
clob_to_string(value)
|
392
494
|
else
|
393
495
|
value
|
394
496
|
end
|
@@ -396,11 +498,28 @@ module Mondrian
|
|
396
498
|
|
397
499
|
private
|
398
500
|
|
501
|
+
def self.clob_to_string(value)
|
502
|
+
if reader = value.getCharacterStream
|
503
|
+
buffered_reader = Java::JavaIo::BufferedReader.new(reader)
|
504
|
+
result = []
|
505
|
+
while str = buffered_reader.readLine
|
506
|
+
result << str
|
507
|
+
end
|
508
|
+
result.join("\n")
|
509
|
+
end
|
510
|
+
ensure
|
511
|
+
if buffered_reader
|
512
|
+
buffered_reader.close
|
513
|
+
elsif reader
|
514
|
+
reader.close
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
399
518
|
def axes
|
400
519
|
@axes ||= @raw_cell_set.getAxes
|
401
520
|
end
|
402
521
|
|
403
|
-
def axis_positions(map_method, join_with=false)
|
522
|
+
def axis_positions(map_method, join_with = false)
|
404
523
|
axes.map do |axis|
|
405
524
|
axis.getPositions.map do |position|
|
406
525
|
names = position.getMembers.map do |member|
|
@@ -429,7 +548,7 @@ module Mondrian
|
|
429
548
|
:chapters => 4
|
430
549
|
}.freeze
|
431
550
|
|
432
|
-
def recursive_values(value_method, axes_sequence, current_index, cell_params=[])
|
551
|
+
def recursive_values(value_method, axes_sequence, current_index, cell_params = [])
|
433
552
|
if axis_number = axes_sequence[current_index]
|
434
553
|
axis_number = AXIS_SYMBOL_TO_NUMBER[axis_number] if axis_number.is_a?(Symbol)
|
435
554
|
positions_size = axes[axis_number].getPositions.size
|