mondrian-olap 0.3.0 → 0.5.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 +7 -0
- data/Changelog.md +38 -0
- data/LICENSE.txt +1 -1
- data/README.md +302 -0
- data/VERSION +1 -1
- data/lib/mondrian/jars/commons-collections-3.2.jar +0 -0
- data/lib/mondrian/jars/commons-logging-1.1.1.jar +0 -0
- data/lib/mondrian/jars/commons-math-1.1.jar +0 -0
- data/lib/mondrian/jars/eigenbase-properties-1.1.2.jar +0 -0
- data/lib/mondrian/jars/eigenbase-resgen-1.3.1.jar +0 -0
- data/lib/mondrian/jars/eigenbase-xom-1.3.1.jar +0 -0
- data/lib/mondrian/jars/{javacup.jar → javacup-10k.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.rb +2 -1
- data/lib/mondrian/olap/connection.rb +163 -32
- data/lib/mondrian/olap/cube.rb +163 -24
- data/lib/mondrian/olap/error.rb +57 -0
- data/lib/mondrian/olap/query.rb +52 -17
- data/lib/mondrian/olap/result.rb +298 -6
- data/lib/mondrian/olap/schema.rb +220 -29
- data/lib/mondrian/olap/schema_element.rb +31 -11
- data/lib/mondrian/olap/schema_udf.rb +331 -0
- data/lib/mondrian/olap/version.rb +5 -0
- data/spec/connection_role_spec.rb +130 -0
- data/spec/connection_spec.rb +36 -1
- data/spec/cube_spec.rb +137 -7
- data/spec/fixtures/MondrianTest.xml +4 -4
- data/spec/mondrian_spec.rb +53 -0
- data/spec/query_spec.rb +294 -11
- data/spec/rake_tasks.rb +8 -8
- data/spec/schema_definition_spec.rb +845 -26
- data/spec/spec_helper.rb +26 -17
- data/spec/support/matchers/be_like.rb +2 -2
- metadata +296 -237
- data/.rspec +0 -2
- data/Gemfile +0 -18
- data/README.rdoc +0 -221
- data/RUNNING_TESTS.rdoc +0 -66
- data/Rakefile +0 -46
- data/lib/mondrian/jars/commons-collections-3.1.jar +0 -0
- data/lib/mondrian/jars/commons-logging-1.0.4.jar +0 -0
- data/lib/mondrian/jars/commons-math-1.0.jar +0 -0
- data/lib/mondrian/jars/eigenbase-properties.jar +0 -0
- data/lib/mondrian/jars/eigenbase-resgen.jar +0 -0
- data/lib/mondrian/jars/eigenbase-xom.jar +0 -0
- data/lib/mondrian/jars/log4j-1.2.8.jar +0 -0
- data/lib/mondrian/jars/olap4j.jar +0 -0
- data/mondrian-olap.gemspec +0 -126
@@ -1,9 +1,7 @@
|
|
1
|
-
require 'nokogiri'
|
2
|
-
|
3
1
|
module Mondrian
|
4
2
|
module OLAP
|
5
3
|
class SchemaElement
|
6
|
-
def initialize(name = nil, attributes = {}, &block)
|
4
|
+
def initialize(name = nil, attributes = {}, parent = nil, &block)
|
7
5
|
# if just attributes hash provided
|
8
6
|
if name.is_a?(Hash) && attributes == {}
|
9
7
|
attributes = name
|
@@ -12,7 +10,14 @@ module Mondrian
|
|
12
10
|
@attributes = {}
|
13
11
|
if name
|
14
12
|
if self.class.content
|
15
|
-
|
13
|
+
if attributes.is_a?(Hash)
|
14
|
+
@content = name
|
15
|
+
else
|
16
|
+
# used for Annotation element where both name and content is given as arguments
|
17
|
+
@attributes[:name] = name
|
18
|
+
@content = attributes
|
19
|
+
attributes = {}
|
20
|
+
end
|
16
21
|
else
|
17
22
|
@attributes[:name] = name
|
18
23
|
end
|
@@ -21,8 +26,12 @@ module Mondrian
|
|
21
26
|
self.class.elements.each do |element|
|
22
27
|
instance_variable_set("@#{pluralize(element)}", [])
|
23
28
|
end
|
29
|
+
# extract annotations from options
|
30
|
+
if @attributes[:annotations] && self.class.elements.include?(:annotations)
|
31
|
+
annotations @attributes.delete(:annotations)
|
32
|
+
end
|
24
33
|
@xml_fragments = []
|
25
|
-
instance_eval
|
34
|
+
instance_eval(&block) if block
|
26
35
|
end
|
27
36
|
|
28
37
|
def self.attributes(*names)
|
@@ -54,10 +63,11 @@ module Mondrian
|
|
54
63
|
@elements.concat(names)
|
55
64
|
|
56
65
|
names.each do |name|
|
66
|
+
next if name == :xml
|
57
67
|
attr_reader pluralize(name).to_sym
|
58
68
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
59
69
|
def #{name}(name=nil, attributes = {}, &block)
|
60
|
-
@#{pluralize(name)} << Schema::#{camel_case(name)}.new(name, attributes, &block)
|
70
|
+
@#{pluralize(name)} << Schema::#{camel_case(name)}.new(name, attributes, self, &block)
|
61
71
|
end
|
62
72
|
RUBY
|
63
73
|
end
|
@@ -78,7 +88,7 @@ module Mondrian
|
|
78
88
|
|
79
89
|
def to_xml(options={})
|
80
90
|
options[:upcase_data_dictionary] = @upcase_data_dictionary unless @upcase_data_dictionary.nil?
|
81
|
-
Nokogiri::XML::Builder.new do |xml|
|
91
|
+
Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
|
82
92
|
add_to_xml(xml, options)
|
83
93
|
end.to_xml
|
84
94
|
end
|
@@ -90,16 +100,26 @@ module Mondrian
|
|
90
100
|
xml.send(tag_name(self.class.name), @content, xmlized_attributes(options))
|
91
101
|
else
|
92
102
|
xml.send(tag_name(self.class.name), xmlized_attributes(options)) do
|
103
|
+
xml_fragments_added = false
|
93
104
|
self.class.elements.each do |element|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
105
|
+
if element == :xml
|
106
|
+
add_xml_fragments(xml)
|
107
|
+
xml_fragments_added = true
|
108
|
+
else
|
109
|
+
instance_variable_get("@#{pluralize(element)}").each {|item| item.add_to_xml(xml, options)}
|
110
|
+
end
|
98
111
|
end
|
112
|
+
add_xml_fragments(xml) unless xml_fragments_added
|
99
113
|
end
|
100
114
|
end
|
101
115
|
end
|
102
116
|
|
117
|
+
def add_xml_fragments(xml)
|
118
|
+
@xml_fragments.each do |xml_fragment|
|
119
|
+
xml.send(:insert, Nokogiri::XML::DocumentFragment.parse(xml_fragment))
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
103
123
|
private
|
104
124
|
|
105
125
|
def xmlized_attributes(options)
|
@@ -0,0 +1,331 @@
|
|
1
|
+
require 'jruby/core_ext'
|
2
|
+
|
3
|
+
module Mondrian
|
4
|
+
module OLAP
|
5
|
+
class Schema < SchemaElement
|
6
|
+
|
7
|
+
def user_defined_cell_formatter(name, &block)
|
8
|
+
CellFormatter.new(name, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
module ScriptElements
|
12
|
+
def javascript(text)
|
13
|
+
script text, :language => 'JavaScript'
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def coffeescript_function(arguments_string, text)
|
19
|
+
# construct function to ensure that last expression is returned
|
20
|
+
coffee_text = "#{arguments_string} ->\n" << text.gsub(/^/,' ')
|
21
|
+
javascript_text = CoffeeScript.compile(coffee_text, :bare => true)
|
22
|
+
# remove function definition first and last lines
|
23
|
+
javascript_text = javascript_text.strip.lines.to_a[1..-2].join
|
24
|
+
javascript javascript_text
|
25
|
+
end
|
26
|
+
|
27
|
+
def ruby(*options, &block)
|
28
|
+
udf_class_name = if options.include?(:shared)
|
29
|
+
"#{name.capitalize}Udf"
|
30
|
+
end
|
31
|
+
if udf_class_name && self.class.const_defined?(udf_class_name)
|
32
|
+
udf_class = self.class.const_get(udf_class_name)
|
33
|
+
else
|
34
|
+
udf_class = Class.new(RubyUdfBase)
|
35
|
+
self.class.const_set(udf_class_name, udf_class) if udf_class_name
|
36
|
+
end
|
37
|
+
udf_class.function_name = name
|
38
|
+
udf_class.class_eval(&block)
|
39
|
+
udf_java_class = udf_class.become_java!(false)
|
40
|
+
|
41
|
+
class_name udf_java_class.getName
|
42
|
+
end
|
43
|
+
|
44
|
+
def ruby_formatter(options, interface_class, method, signature, &block)
|
45
|
+
formatter_class_name = if options.include?(:shared) && @attributes[:name]
|
46
|
+
ruby_formatter_name_to_class_name(@attributes[:name])
|
47
|
+
end
|
48
|
+
if formatter_class_name && self.class.const_defined?(formatter_class_name)
|
49
|
+
formatter_class = self.class.const_get(formatter_class_name)
|
50
|
+
else
|
51
|
+
formatter_class = Class.new
|
52
|
+
self.class.const_set(formatter_class_name, formatter_class) if formatter_class_name
|
53
|
+
end
|
54
|
+
|
55
|
+
formatter_class.class_eval do
|
56
|
+
include interface_class
|
57
|
+
define_method method, &block
|
58
|
+
add_method_signature(method, signature)
|
59
|
+
end
|
60
|
+
formatter_java_class = formatter_class.become_java!(false)
|
61
|
+
class_name formatter_java_class.getName
|
62
|
+
end
|
63
|
+
|
64
|
+
def ruby_formatter_name_to_class_name(name)
|
65
|
+
# upcase just first character
|
66
|
+
"#{name.sub(/\A./){|m| m.upcase}}Udf"
|
67
|
+
end
|
68
|
+
|
69
|
+
def ruby_formatter_java_class_name(name)
|
70
|
+
"rubyobj.#{self.class.name.gsub('::','.')}.#{ruby_formatter_name_to_class_name(name)}"
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
class UserDefinedFunction < SchemaElement
|
76
|
+
include ScriptElements
|
77
|
+
attributes :name, # Name with which the user-defined function will be referenced in MDX expressions.
|
78
|
+
# Name of the class which implemenets this user-defined function.
|
79
|
+
# Must implement the mondrian.spi.UserDefinedFunction interface.
|
80
|
+
:class_name
|
81
|
+
elements :script
|
82
|
+
|
83
|
+
def coffeescript(text)
|
84
|
+
coffee_text = "__udf__ = {\n" << text << "}\n"
|
85
|
+
javascript_text = CoffeeScript.compile(coffee_text, :bare => true)
|
86
|
+
javascript_text << <<-JS
|
87
|
+
|
88
|
+
__udf__.parameters || (__udf__.parameters = []);
|
89
|
+
__udf__.returns || (__udf__.returns = "Scalar");
|
90
|
+
var __scalarTypes__ = {"Numeric":true,"String":true,"Boolean":true,"DateTime":true,"Decimal":true,"Scalar":true};
|
91
|
+
function __getType__(type) {
|
92
|
+
if (__scalarTypes__[type]) {
|
93
|
+
return new mondrian.olap.type[type+"Type"];
|
94
|
+
} else if (type === "Member") {
|
95
|
+
return mondrian.olap.type.MemberType.Unknown;
|
96
|
+
} else {
|
97
|
+
return null;
|
98
|
+
}
|
99
|
+
}
|
100
|
+
function getParameterTypes() {
|
101
|
+
var parameters = __udf__.parameters || [],
|
102
|
+
types = [];
|
103
|
+
for (var i = 0, len = parameters.length; i < len; i++) {
|
104
|
+
types.push(__getType__(parameters[i]))
|
105
|
+
}
|
106
|
+
return types;
|
107
|
+
}
|
108
|
+
function getReturnType(parameterTypes) {
|
109
|
+
var returns = __udf__.returns || "Scalar";
|
110
|
+
return __getType__(returns);
|
111
|
+
}
|
112
|
+
if (__udf__.syntax) {
|
113
|
+
function getSyntax() {
|
114
|
+
return mondrian.olap.Syntax[__udf__.syntax];
|
115
|
+
}
|
116
|
+
}
|
117
|
+
function execute(evaluator, args) {
|
118
|
+
var parameters = __udf__.parameters || [],
|
119
|
+
values = [],
|
120
|
+
value;
|
121
|
+
for (var i = 0, len = parameters.length; i < len; i++) {
|
122
|
+
if (__scalarTypes__[parameters[i]]) {
|
123
|
+
value = args[i].evaluateScalar(evaluator);
|
124
|
+
} else {
|
125
|
+
value = args[i].evaluate(evaluator);
|
126
|
+
}
|
127
|
+
values.push(value);
|
128
|
+
}
|
129
|
+
return __udf__.execute.apply(__udf__, values);
|
130
|
+
}
|
131
|
+
JS
|
132
|
+
javascript javascript_text
|
133
|
+
end
|
134
|
+
|
135
|
+
class RubyUdfBase
|
136
|
+
include Java::mondrian.spi.UserDefinedFunction
|
137
|
+
def self.function_name=(name); @function_name = name; end
|
138
|
+
def self.function_name; @function_name; end
|
139
|
+
|
140
|
+
def getName
|
141
|
+
self.class.function_name
|
142
|
+
end
|
143
|
+
add_method_signature("getName", [java.lang.String])
|
144
|
+
|
145
|
+
def getDescription
|
146
|
+
getName
|
147
|
+
end
|
148
|
+
add_method_signature("getDescription", [java.lang.String])
|
149
|
+
|
150
|
+
def self.parameters(*types)
|
151
|
+
if types.empty?
|
152
|
+
@parameters || []
|
153
|
+
else
|
154
|
+
@parameters = types.map{|type| stringified_type(type)}
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.returns(type = nil)
|
159
|
+
if type
|
160
|
+
@returns = stringified_type(type)
|
161
|
+
else
|
162
|
+
@returns || 'Scalar'
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
VALID_SYNTAX_TYPES = %w(Function Property Method)
|
167
|
+
def self.syntax(type = nil)
|
168
|
+
if type
|
169
|
+
type = stringify(type)
|
170
|
+
raise ArgumentError, "invalid user defined function type #{type.inspect}" unless VALID_SYNTAX_TYPES.include? type
|
171
|
+
@syntax = type
|
172
|
+
else
|
173
|
+
@syntax || 'Function'
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def getSyntax
|
178
|
+
Java::mondrian.olap.Syntax.const_get self.class.syntax
|
179
|
+
end
|
180
|
+
add_method_signature("getSyntax", [Java::mondrian.olap.Syntax])
|
181
|
+
|
182
|
+
UDF_SCALAR_TYPES = {
|
183
|
+
"Numeric" => Java::mondrian.olap.type.NumericType,
|
184
|
+
"String" => Java::mondrian.olap.type.StringType,
|
185
|
+
"Boolean" => Java::mondrian.olap.type.BooleanType,
|
186
|
+
"DateTime" => Java::mondrian.olap.type.DateTimeType,
|
187
|
+
"Decimal" => Java::mondrian.olap.type.DecimalType,
|
188
|
+
"Scalar" => Java::mondrian.olap.type.ScalarType
|
189
|
+
}
|
190
|
+
UDF_OTHER_TYPES = {
|
191
|
+
"Member" => Java::mondrian.olap.type.MemberType::Unknown,
|
192
|
+
"Set" => Java::mondrian.olap.type.SetType.new(Java::mondrian.olap.type.MemberType::Unknown),
|
193
|
+
"Hierarchy" => Java::mondrian.olap.type.HierarchyType.new(nil, nil),
|
194
|
+
"Level" => Java::mondrian.olap.type.LevelType::Unknown
|
195
|
+
}
|
196
|
+
|
197
|
+
def getParameterTypes
|
198
|
+
@parameterTypes ||= self.class.parameters.map{|p| get_java_type(p)}
|
199
|
+
end
|
200
|
+
class_loader = JRuby.runtime.jruby_class_loader
|
201
|
+
type_array_class = java.lang.Class.forName "[Lmondrian.olap.type.Type;", true, class_loader
|
202
|
+
add_method_signature("getParameterTypes", [type_array_class])
|
203
|
+
|
204
|
+
def getReturnType(parameterTypes)
|
205
|
+
@returnType ||= get_java_type self.class.returns
|
206
|
+
end
|
207
|
+
add_method_signature("getReturnType", [Java::mondrian.olap.type.Type, type_array_class])
|
208
|
+
|
209
|
+
def getReservedWords
|
210
|
+
nil
|
211
|
+
end
|
212
|
+
string_array_class = java.lang.Class.forName "[Ljava.lang.String;", true, class_loader
|
213
|
+
add_method_signature("getReservedWords", [string_array_class])
|
214
|
+
|
215
|
+
def execute(evaluator, arguments)
|
216
|
+
values = []
|
217
|
+
self.class.parameters.each_with_index do |p,i|
|
218
|
+
value = UDF_SCALAR_TYPES[p] ? arguments[i].evaluateScalar(evaluator) : arguments[i].evaluate(evaluator)
|
219
|
+
values << value
|
220
|
+
end
|
221
|
+
call_with_evaluator(evaluator, *values)
|
222
|
+
end
|
223
|
+
arguments_array_class = java.lang.Class.forName "[Lmondrian.spi.UserDefinedFunction$Argument;", true, class_loader
|
224
|
+
add_method_signature("execute", [java.lang.Object, Java::mondrian.olap.Evaluator, arguments_array_class])
|
225
|
+
|
226
|
+
# Override this metho if evaluator is needed
|
227
|
+
def call_with_evaluator(evaluator, *values)
|
228
|
+
call(*values)
|
229
|
+
end
|
230
|
+
|
231
|
+
private
|
232
|
+
|
233
|
+
def get_java_type(type)
|
234
|
+
if type_class = UDF_SCALAR_TYPES[type]
|
235
|
+
type_class.new
|
236
|
+
else
|
237
|
+
UDF_OTHER_TYPES[type]
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def self.stringified_type(type)
|
242
|
+
type = stringify(type)
|
243
|
+
raise ArgumentError, "invalid user defined function type #{type.inspect}" unless UDF_SCALAR_TYPES[type] || UDF_OTHER_TYPES[type]
|
244
|
+
type
|
245
|
+
end
|
246
|
+
|
247
|
+
def self.stringify(arg)
|
248
|
+
arg = arg.to_s.split('_').map{|s| s.capitalize}.join if arg.is_a? Symbol
|
249
|
+
arg
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def ruby(*options, &block)
|
254
|
+
udf_class_name = if options.include?(:shared)
|
255
|
+
"#{name.capitalize}Udf"
|
256
|
+
end
|
257
|
+
if udf_class_name && self.class.const_defined?(udf_class_name)
|
258
|
+
udf_class = self.class.const_get(udf_class_name)
|
259
|
+
else
|
260
|
+
udf_class = Class.new(RubyUdfBase)
|
261
|
+
self.class.const_set(udf_class_name, udf_class) if udf_class_name
|
262
|
+
end
|
263
|
+
udf_class.function_name = name
|
264
|
+
udf_class.class_eval(&block)
|
265
|
+
udf_java_class = udf_class.become_java!(false)
|
266
|
+
|
267
|
+
class_name udf_java_class.getName
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
class Script < SchemaElement
|
272
|
+
attributes :language
|
273
|
+
content :text
|
274
|
+
end
|
275
|
+
|
276
|
+
class CellFormatter < SchemaElement
|
277
|
+
include ScriptElements
|
278
|
+
# Name of a formatter class for the appropriate cell being displayed.
|
279
|
+
# The class must implement the mondrian.olap.CellFormatter interface.
|
280
|
+
attributes :class_name
|
281
|
+
elements :script
|
282
|
+
|
283
|
+
def initialize(name = nil, attributes = {}, parent = nil, &block)
|
284
|
+
super
|
285
|
+
if name && !attributes[:class_name] && !block_given?
|
286
|
+
# use shared ruby implementation
|
287
|
+
@attributes[:class_name] = ruby_formatter_java_class_name(name)
|
288
|
+
@attributes.delete(:name)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def coffeescript(text)
|
293
|
+
coffeescript_function('(value)', text)
|
294
|
+
end
|
295
|
+
|
296
|
+
def ruby(*options, &block)
|
297
|
+
ruby_formatter(options, Java::mondrian.spi.CellFormatter, 'formatCell', [java.lang.String, java.lang.Object], &block)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
class MemberFormatter < SchemaElement
|
302
|
+
include ScriptElements
|
303
|
+
attributes :class_name
|
304
|
+
elements :script
|
305
|
+
|
306
|
+
def coffeescript(text)
|
307
|
+
coffeescript_function('(member)', text)
|
308
|
+
end
|
309
|
+
|
310
|
+
def ruby(*options, &block)
|
311
|
+
ruby_formatter(options, Java::mondrian.spi.MemberFormatter, 'formatMember', [java.lang.String, Java::mondrian.olap.Member], &block)
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
class PropertyFormatter < SchemaElement
|
316
|
+
include ScriptElements
|
317
|
+
attributes :class_name
|
318
|
+
elements :script
|
319
|
+
|
320
|
+
def coffeescript(text)
|
321
|
+
coffeescript_function('(member,propertyName,propertyValue)', text)
|
322
|
+
end
|
323
|
+
|
324
|
+
def ruby(*options, &block)
|
325
|
+
ruby_formatter(options, Java::mondrian.spi.PropertyFormatter, 'formatProperty', [java.lang.String, Java::mondrian.olap.Member, java.lang.String, java.lang.Object], &block)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Connection role" do
|
4
|
+
|
5
|
+
describe "create connection" do
|
6
|
+
before(:each) do
|
7
|
+
@role_name = role_name = 'California manager'
|
8
|
+
@role_name2 = role_name2 = 'Dummy, with comma'
|
9
|
+
@schema = Mondrian::OLAP::Schema.define do
|
10
|
+
cube 'Sales' do
|
11
|
+
table 'sales'
|
12
|
+
dimension 'Gender', :foreign_key => 'customer_id' do
|
13
|
+
hierarchy :has_all => true, :primary_key => 'id' do
|
14
|
+
table 'customers'
|
15
|
+
level 'Gender', :column => 'gender', :unique_members => true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
dimension 'Customers', :foreign_key => 'customer_id' do
|
19
|
+
hierarchy :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
|
20
|
+
table 'customers'
|
21
|
+
level 'Country', :column => 'country', :unique_members => true
|
22
|
+
level 'State Province', :column => 'state_province', :unique_members => true
|
23
|
+
level 'City', :column => 'city', :unique_members => false
|
24
|
+
level 'Name', :column => 'fullname', :unique_members => true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
dimension 'Time', :foreign_key => 'time_id' do
|
28
|
+
hierarchy :has_all => false, :primary_key => 'id' do
|
29
|
+
table 'time'
|
30
|
+
level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true
|
31
|
+
level 'Quarter', :column => 'quarter', :unique_members => false
|
32
|
+
level 'Month', :column => 'month_of_year', :type => 'Numeric', :unique_members => false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
measure 'Unit Sales', :column => 'unit_sales', :aggregator => 'sum'
|
36
|
+
measure 'Store Sales', :column => 'store_sales', :aggregator => 'sum'
|
37
|
+
end
|
38
|
+
role role_name do
|
39
|
+
schema_grant :access => 'none' do
|
40
|
+
cube_grant :cube => 'Sales', :access => 'all' do
|
41
|
+
dimension_grant :dimension => '[Measures]', :access => 'all'
|
42
|
+
hierarchy_grant :hierarchy => '[Customers]', :access => 'custom',
|
43
|
+
:top_level => '[Customers].[State Province]', :bottom_level => '[Customers].[City]' do
|
44
|
+
member_grant :member => '[Customers].[USA].[CA]', :access => 'all'
|
45
|
+
member_grant :member => '[Customers].[USA].[CA].[Los Angeles]', :access => 'none'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
role role_name2
|
51
|
+
|
52
|
+
# to test that Role elements are generated before UserDefinedFunction
|
53
|
+
user_defined_function 'Factorial' do
|
54
|
+
ruby do
|
55
|
+
parameters :numeric
|
56
|
+
returns :numeric
|
57
|
+
def call(n)
|
58
|
+
n <= 1 ? 1 : n * call(n - 1)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should connect" do
|
67
|
+
@olap.should be_connected
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should get available role names" do
|
71
|
+
@olap.available_role_names.should == [@role_name, @role_name2]
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should not get role name if not set" do
|
75
|
+
@olap.role_name.should be_nil
|
76
|
+
@olap.role_names.should be_empty
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should set and get role name" do
|
80
|
+
@olap.role_name = @role_name
|
81
|
+
@olap.role_name.should == @role_name
|
82
|
+
@olap.role_names.should == [@role_name]
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should raise error when invalid role name is set" do
|
86
|
+
expect {
|
87
|
+
@olap.role_name = 'invalid'
|
88
|
+
}.to raise_error {|e|
|
89
|
+
e.should be_kind_of(Mondrian::OLAP::Error)
|
90
|
+
e.message.should == "org.olap4j.OlapException: Unknown role 'invalid'"
|
91
|
+
e.root_cause_message.should == "Unknown role 'invalid'"
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should set and get several role names" do
|
96
|
+
@olap.role_names = [@role_name, @role_name2]
|
97
|
+
@olap.role_name.should == "[#{@role_name}, #{@role_name2}]"
|
98
|
+
@olap.role_names.should == [@role_name, @role_name2]
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should not get non-visible member" do
|
102
|
+
@cube = @olap.cube('Sales')
|
103
|
+
@cube.member('[Customers].[USA].[CA].[Los Angeles]').should_not be_nil
|
104
|
+
@olap.role_name = @role_name
|
105
|
+
@cube.member('[Customers].[USA].[CA].[Los Angeles]').should be_nil
|
106
|
+
end
|
107
|
+
|
108
|
+
# TODO: investigate why role name is not returned when set in connection string
|
109
|
+
# it "should set role name from connection parameters" do
|
110
|
+
# @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema,
|
111
|
+
# :role => @role_name)
|
112
|
+
# @olap.role_name.should == @role_name
|
113
|
+
# end
|
114
|
+
|
115
|
+
it "should not get non-visible member when role name set in connection parameters" do
|
116
|
+
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema,
|
117
|
+
:role => @role_name)
|
118
|
+
@cube = @olap.cube('Sales')
|
119
|
+
@cube.member('[Customers].[USA].[CA].[Los Angeles]').should be_nil
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should not get non-visible member when several role names set in connection parameters" do
|
123
|
+
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema,
|
124
|
+
:roles => [@role_name, @role_name2])
|
125
|
+
@cube = @olap.cube('Sales')
|
126
|
+
@cube.member('[Customers].[USA].[CA].[Los Angeles]').should be_nil
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|