mondrian-olap 0.5.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|