mondrian-olap 1.2.0 → 1.3.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 +4 -4
- data/Changelog.md +21 -1
- data/README.md +4 -4
- data/VERSION +1 -1
- data/lib/mondrian/jars/log4j-api-2.17.1.jar +0 -0
- data/lib/mondrian/jars/log4j-core-2.17.1.jar +0 -0
- data/lib/mondrian/jars/log4j2-config.jar +0 -0
- data/lib/mondrian/jars/mondrian-9.3.0.0.jar +0 -0
- data/lib/mondrian/olap/connection.rb +33 -20
- data/lib/mondrian/olap/cube.rb +46 -4
- data/lib/mondrian/olap/error.rb +10 -2
- data/lib/mondrian/olap/query.rb +1 -0
- data/lib/mondrian/olap/result.rb +128 -59
- data/lib/mondrian/olap/schema.rb +9 -3
- data/lib/mondrian/olap/schema_udf.rb +8 -82
- data/lib/mondrian/olap.rb +11 -7
- data/spec/connection_spec.rb +37 -3
- data/spec/cube_cache_control_spec.rb +2 -2
- data/spec/cube_spec.rb +36 -2
- data/spec/fixtures/MondrianTest.xml +40 -0
- data/spec/fixtures/MondrianTestOracle.xml +40 -0
- data/spec/mondrian_spec.rb +132 -0
- data/spec/query_spec.rb +86 -21
- data/spec/rake_tasks.rb +85 -16
- data/spec/schema_definition_spec.rb +0 -235
- data/spec/spec_helper.rb +317 -75
- data/spec/support/data/customers.csv +111 -111
- data/spec/support/data/promotions.csv +11 -0
- data/spec/support/data/sales.csv +101 -101
- data/spec/support/data/warehouse.csv +101 -0
- data/spec/support/matchers/be_like.rb +1 -0
- metadata +36 -73
- data/lib/mondrian/jars/log4j-1.2.17.jar +0 -0
- data/lib/mondrian/jars/log4j.properties +0 -3
- data/lib/mondrian/jars/mondrian-9.1.0.0.jar +0 -0
@@ -9,21 +9,8 @@ module Mondrian
|
|
9
9
|
end
|
10
10
|
|
11
11
|
module ScriptElements
|
12
|
-
def javascript(text)
|
13
|
-
script text, :language => 'JavaScript'
|
14
|
-
end
|
15
|
-
|
16
12
|
private
|
17
13
|
|
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
14
|
def ruby(*options, &block)
|
28
15
|
udf_class_name = if options.include?(:shared)
|
29
16
|
"#{name.capitalize}Udf"
|
@@ -74,64 +61,13 @@ module Mondrian
|
|
74
61
|
|
75
62
|
class UserDefinedFunction < SchemaElement
|
76
63
|
include ScriptElements
|
64
|
+
|
77
65
|
attributes :name, # Name with which the user-defined function will be referenced in MDX expressions.
|
78
|
-
# Name of the class which
|
79
|
-
# Must implement the mondrian.spi.UserDefinedFunction
|
66
|
+
# Name of the class which implements this user-defined function.
|
67
|
+
# Must implement the mondrian.spi.UserDefinedFunction interface.
|
80
68
|
:class_name
|
81
69
|
elements :script
|
82
70
|
|
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
71
|
class RubyUdfBase
|
136
72
|
include Java::mondrian.spi.UserDefinedFunction
|
137
73
|
def self.function_name=(name); @function_name = name; end
|
@@ -225,7 +161,7 @@ JS
|
|
225
161
|
arguments_array_class = java.lang.Class.forName "[Lmondrian.spi.UserDefinedFunction$Argument;", true, class_loader
|
226
162
|
add_method_signature("execute", [java.lang.Object, Java::mondrian.olap.Evaluator, arguments_array_class])
|
227
163
|
|
228
|
-
# Override this
|
164
|
+
# Override this method if evaluator is needed
|
229
165
|
def call_with_evaluator(evaluator, *values)
|
230
166
|
call(*values)
|
231
167
|
end
|
@@ -294,10 +230,6 @@ JS
|
|
294
230
|
end
|
295
231
|
end
|
296
232
|
|
297
|
-
def coffeescript(text)
|
298
|
-
coffeescript_function('(value)', text)
|
299
|
-
end
|
300
|
-
|
301
233
|
def ruby(*options, &block)
|
302
234
|
ruby_formatter(options, Java::mondrian.spi.CellFormatter, 'formatCell', [java.lang.String, java.lang.Object], &block)
|
303
235
|
end
|
@@ -308,12 +240,9 @@ JS
|
|
308
240
|
attributes :class_name
|
309
241
|
elements :script
|
310
242
|
|
311
|
-
def coffeescript(text)
|
312
|
-
coffeescript_function('(member)', text)
|
313
|
-
end
|
314
|
-
|
315
243
|
def ruby(*options, &block)
|
316
|
-
ruby_formatter(options, Java::mondrian.spi.MemberFormatter, 'formatMember',
|
244
|
+
ruby_formatter(options, Java::mondrian.spi.MemberFormatter, 'formatMember',
|
245
|
+
[java.lang.String, Java::mondrian.olap.Member], &block)
|
317
246
|
end
|
318
247
|
end
|
319
248
|
|
@@ -322,12 +251,9 @@ JS
|
|
322
251
|
attributes :class_name
|
323
252
|
elements :script
|
324
253
|
|
325
|
-
def coffeescript(text)
|
326
|
-
coffeescript_function('(member,propertyName,propertyValue)', text)
|
327
|
-
end
|
328
|
-
|
329
254
|
def ruby(*options, &block)
|
330
|
-
ruby_formatter(options, Java::mondrian.spi.PropertyFormatter, 'formatProperty',
|
255
|
+
ruby_formatter(options, Java::mondrian.spi.PropertyFormatter, 'formatProperty',
|
256
|
+
[java.lang.String, Java::mondrian.olap.Member, java.lang.String, java.lang.Object], &block)
|
331
257
|
end
|
332
258
|
end
|
333
259
|
|
data/lib/mondrian/olap.rb
CHANGED
@@ -1,18 +1,22 @@
|
|
1
1
|
require 'java'
|
2
2
|
require 'nokogiri'
|
3
3
|
|
4
|
+
{
|
5
|
+
# Do not register MondrianOlap4jDriver
|
6
|
+
"mondrian.olap4j.registerDriver" => false,
|
7
|
+
# Do not register log3j2 MBean
|
8
|
+
"log4j2.disable.jmx" => true
|
9
|
+
}.each do |key, value|
|
10
|
+
if java.lang.System.getProperty(key).nil?
|
11
|
+
java.lang.System.setProperty(key, value.to_s)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
4
15
|
directory = File.expand_path("../jars", __FILE__)
|
5
16
|
Dir["#{directory}/*.jar"].each do |file|
|
6
17
|
require file
|
7
18
|
end
|
8
19
|
|
9
|
-
unless java.lang.System.getProperty("log4j.configuration")
|
10
|
-
file_uri = java.io.File.new("#{directory}/log4j.properties").toURI.to_s
|
11
|
-
java.lang.System.setProperty("log4j.configuration", file_uri)
|
12
|
-
end
|
13
|
-
# register Mondrian olap4j driver
|
14
|
-
Java::mondrian.olap4j.MondrianOlap4jDriver
|
15
|
-
|
16
20
|
%w(error connection query result schema schema_udf cube).each do |file|
|
17
21
|
require "mondrian/olap/#{file}"
|
18
22
|
end
|
data/spec/connection_spec.rb
CHANGED
@@ -12,7 +12,7 @@ describe "Connection" do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
it "should be successful" do
|
15
|
-
@olap.connect.should
|
15
|
+
@olap.connect.should == true
|
16
16
|
end
|
17
17
|
|
18
18
|
end
|
@@ -25,7 +25,7 @@ describe "Connection" do
|
|
25
25
|
@olap = Mondrian::OLAP::Connection.new(CONNECTION_PARAMS.merge(
|
26
26
|
:catalog_content => @schema_xml
|
27
27
|
))
|
28
|
-
@olap.connect.should
|
28
|
+
@olap.connect.should == true
|
29
29
|
end
|
30
30
|
|
31
31
|
end
|
@@ -49,10 +49,11 @@ describe "Connection" do
|
|
49
49
|
when 'mysql' then 'mondrian.spi.impl.MySqlDialect'
|
50
50
|
when 'postgresql' then 'mondrian.spi.impl.PostgreSqlDialect'
|
51
51
|
when 'oracle' then 'mondrian.spi.impl.OracleDialect'
|
52
|
-
when 'mssql' then 'mondrian.spi.impl.MicrosoftSqlServerDialect'
|
53
52
|
when 'sqlserver' then 'mondrian.spi.impl.MicrosoftSqlServerDialect'
|
54
53
|
when 'vertica' then 'mondrian.spi.impl.VerticaDialect'
|
55
54
|
when 'snowflake' then 'mondrian.spi.impl.SnowflakeDialect'
|
55
|
+
when 'clickhouse' then 'mondrian.spi.impl.ClickHouseDialect'
|
56
|
+
when 'mariadb' then 'mondrian.spi.impl.MariaDBDialect'
|
56
57
|
end
|
57
58
|
end
|
58
59
|
|
@@ -89,4 +90,37 @@ describe "Connection" do
|
|
89
90
|
|
90
91
|
end
|
91
92
|
|
93
|
+
describe "jdbc_uri" do
|
94
|
+
before(:all) { @olap_connection = Mondrian::OLAP::Connection }
|
95
|
+
|
96
|
+
describe "SQL Server" do
|
97
|
+
it "should return a valid JDBC URI" do
|
98
|
+
@olap_connection.new(
|
99
|
+
driver: 'sqlserver',
|
100
|
+
host: 'example.com',
|
101
|
+
port: 1234,
|
102
|
+
instance: 'MSSQLSERVER',
|
103
|
+
database: 'example_db'
|
104
|
+
).jdbc_uri.should == 'jdbc:sqlserver://example.com:1234;databaseName=example_db;instanceName=MSSQLSERVER'
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should return a valid JDBC URI with instance name as property" do
|
108
|
+
@olap_connection.new(
|
109
|
+
driver: 'sqlserver',
|
110
|
+
host: 'example.com',
|
111
|
+
properties: {
|
112
|
+
instanceName: "MSSQLSERVER"
|
113
|
+
}
|
114
|
+
).jdbc_uri.should == 'jdbc:sqlserver://example.com;instanceName=MSSQLSERVER'
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should return a valid JDBC URI with enabled integratedSecurity" do
|
118
|
+
@olap_connection.new(
|
119
|
+
driver: 'sqlserver',
|
120
|
+
host: 'example.com',
|
121
|
+
integrated_security: 'true'
|
122
|
+
).jdbc_uri.should == 'jdbc:sqlserver://example.com;integratedSecurity=true'
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
92
126
|
end
|
@@ -73,7 +73,7 @@ describe "Cube" do
|
|
73
73
|
end
|
74
74
|
|
75
75
|
# Do not execute tests on analytical databases with slow individual inserts
|
76
|
-
describe 'cache', unless: %w(vertica snowflake).include?(MONDRIAN_DRIVER) do
|
76
|
+
describe 'cache', unless: %w(vertica snowflake clickhouse mariadb).include?(MONDRIAN_DRIVER) do
|
77
77
|
def qt(name)
|
78
78
|
@connection.quote_table_name(name.to_s)
|
79
79
|
end
|
@@ -90,7 +90,7 @@ describe "Cube" do
|
|
90
90
|
case MONDRIAN_DRIVER
|
91
91
|
when 'mysql', 'jdbc_mysql', 'postgresql', 'oracle'
|
92
92
|
@connection.execute 'CREATE TABLE sales_copy AS SELECT * FROM sales'
|
93
|
-
when '
|
93
|
+
when 'sqlserver'
|
94
94
|
# Use raw_connection.execute to avoid detecting this query as a SELECT query
|
95
95
|
# for which executeQuery JDBC method will fail
|
96
96
|
@connection.raw_connection.execute_update 'SELECT * INTO sales_copy FROM sales'
|
data/spec/cube_spec.rb
CHANGED
@@ -166,6 +166,31 @@ describe "Cube" do
|
|
166
166
|
|
167
167
|
end
|
168
168
|
|
169
|
+
describe "cube hierarchies" do
|
170
|
+
before(:all) do
|
171
|
+
@cube = @olap.cube('Sales')
|
172
|
+
@hierarchy_names = %w(Measures Gender Customers Time Time.Weekly)
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should get hierarchy names" do
|
176
|
+
@cube.hierarchy_names.should == @hierarchy_names
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should get hierarchy by name" do
|
180
|
+
@cube.hierarchy('Gender').name.should == 'Gender'
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should get hierarchy dimension name" do
|
184
|
+
hierarchy = @cube.hierarchy('Time.Weekly')
|
185
|
+
hierarchy.dimension.name.should == 'Time'
|
186
|
+
hierarchy.dimension_name.should == 'Time'
|
187
|
+
end
|
188
|
+
|
189
|
+
it "should return nil when getting dimension with invalid name" do
|
190
|
+
@cube.hierarchy('invalid').should be_nil
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
169
194
|
describe "dimension hierarchies" do
|
170
195
|
before(:all) do
|
171
196
|
@cube = @olap.cube('Sales')
|
@@ -218,6 +243,15 @@ describe "Cube" do
|
|
218
243
|
@cube.dimension('Gender').hierarchy.levels.map(&:members_count).should == [1, 2]
|
219
244
|
end
|
220
245
|
|
246
|
+
it "should set and get hierarchy level cardinality" do
|
247
|
+
level = @cube.dimension('Gender').hierarchy.levels.last
|
248
|
+
level.cardinality.should == Java::JavaLang::Integer::MIN_VALUE
|
249
|
+
level.cardinality = 2
|
250
|
+
@olap.cube('Sales').dimension('Gender').hierarchy.levels.last.cardinality.should == 2
|
251
|
+
level.cardinality = nil
|
252
|
+
@olap.cube('Sales').dimension('Gender').hierarchy.levels.last.cardinality.should == Java::JavaLang::Integer::MIN_VALUE
|
253
|
+
end
|
254
|
+
|
221
255
|
it "should get hierarchy annotations" do
|
222
256
|
@cube.dimension('Customers').hierarchy.annotations.should == {'foo' => 'bar'}
|
223
257
|
end
|
@@ -238,12 +272,12 @@ describe "Cube" do
|
|
238
272
|
end
|
239
273
|
|
240
274
|
it "should get hierarchy all member" do
|
241
|
-
@cube.dimension('Gender').hierarchy.has_all?.should
|
275
|
+
@cube.dimension('Gender').hierarchy.has_all?.should == true
|
242
276
|
@cube.dimension('Gender').hierarchy.all_member_name.should == 'All Genders'
|
243
277
|
end
|
244
278
|
|
245
279
|
it "should not get all member for hierarchy without all member" do
|
246
|
-
@cube.dimension('Time').hierarchy.has_all?.should
|
280
|
+
@cube.dimension('Time').hierarchy.has_all?.should == false
|
247
281
|
@cube.dimension('Time').hierarchy.all_member_name.should be_nil
|
248
282
|
end
|
249
283
|
|
@@ -126,4 +126,44 @@ fullname
|
|
126
126
|
</CalculatedMember>
|
127
127
|
</Cube>
|
128
128
|
|
129
|
+
<Cube name="Warehouse">
|
130
|
+
<Table name="warehouse"/>
|
131
|
+
<DimensionUsage name="Time" source="Time" foreignKey="time_id"/>
|
132
|
+
<DimensionUsage name="Product" source="Product" foreignKey="product_id"/>
|
133
|
+
<Measure aggregator="sum" column="units_shipped" formatString="#,##0" name="Units Shipped"/>
|
134
|
+
<Measure aggregator="sum" column="store_invoice" formatString="#,##0.00" name="Store Invoice"/>
|
135
|
+
<Measure name="Products with units shipped" aggregator="distinct-count" formatString="#,###">
|
136
|
+
<MeasureExpression>
|
137
|
+
<SQL dialect="generic">
|
138
|
+
CASE WHEN units_shipped IS NOT NULL THEN product_id END
|
139
|
+
</SQL>
|
140
|
+
</MeasureExpression>
|
141
|
+
</Measure>
|
142
|
+
</Cube>
|
143
|
+
|
144
|
+
<VirtualCube name="Sales and Warehouse">
|
145
|
+
<VirtualCubeDimension name="Customers" cubeName="Sales"/>
|
146
|
+
<VirtualCubeDimension name="Gender" cubeName="Sales"/>
|
147
|
+
<VirtualCubeDimension name="Product"/>
|
148
|
+
<VirtualCubeDimension name="Time"/>
|
149
|
+
<VirtualCubeMeasure cubeName="Sales" name="[Measures].[Unit Sales]"/>
|
150
|
+
<VirtualCubeMeasure cubeName="Sales" name="[Measures].[Store Cost]"/>
|
151
|
+
<VirtualCubeMeasure cubeName="Sales" name="[Measures].[Store Sales]"/>
|
152
|
+
<VirtualCubeMeasure cubeName="Sales" name="[Measures].[Sales Count]"/>
|
153
|
+
<VirtualCubeMeasure cubeName="Sales" name="[Measures].[Customer Count]"/>
|
154
|
+
<VirtualCubeMeasure cubeName="Warehouse" name="[Measures].[Units Shipped]"/>
|
155
|
+
<VirtualCubeMeasure cubeName="Warehouse" name="[Measures].[Store Invoice]"/>
|
156
|
+
<VirtualCubeMeasure cubeName="Warehouse" name="[Measures].[Products with units shipped]"/>
|
157
|
+
</VirtualCube>
|
158
|
+
|
159
|
+
<Role name="Canada manager">
|
160
|
+
<SchemaGrant access="none">
|
161
|
+
<CubeGrant access="all" cube="Sales">
|
162
|
+
<HierarchyGrant access="custom" hierarchy="[Customers]">
|
163
|
+
<MemberGrant access="all" member="[Customers].[Canada]"/>
|
164
|
+
</HierarchyGrant>
|
165
|
+
</CubeGrant>
|
166
|
+
</SchemaGrant>
|
167
|
+
</Role>
|
168
|
+
|
129
169
|
</Schema>
|
@@ -126,4 +126,44 @@ FULLNAME
|
|
126
126
|
</CalculatedMember>
|
127
127
|
</Cube>
|
128
128
|
|
129
|
+
<Cube name="Warehouse">
|
130
|
+
<Table name="WAREHOUSE"/>
|
131
|
+
<DimensionUsage name="Time" source="Time" foreignKey="TIME_ID"/>
|
132
|
+
<DimensionUsage name="Product" source="Product" foreignKey="PRODUCT_ID"/>
|
133
|
+
<Measure aggregator="sum" column="UNITS_SHIPPED" formatString="#,##0" name="Units Shipped"/>
|
134
|
+
<Measure aggregator="sum" column="STORE_INVOICE" formatString="#,##0.00" name="Store Invoice"/>
|
135
|
+
<Measure name="Products with units shipped" aggregator="distinct-count" formatString="#,###">
|
136
|
+
<MeasureExpression>
|
137
|
+
<SQL dialect="generic">
|
138
|
+
CASE WHEN UNITS_SHIPPED IS NOT NULL THEN PRODUCT_ID END
|
139
|
+
</SQL>
|
140
|
+
</MeasureExpression>
|
141
|
+
</Measure>
|
142
|
+
</Cube>
|
143
|
+
|
144
|
+
<VirtualCube name="Sales and Warehouse">
|
145
|
+
<VirtualCubeDimension name="Customers" cubeName="Sales"/>
|
146
|
+
<VirtualCubeDimension name="Gender" cubeName="Sales"/>
|
147
|
+
<VirtualCubeDimension name="Product"/>
|
148
|
+
<VirtualCubeDimension name="Time"/>
|
149
|
+
<VirtualCubeMeasure cubeName="Sales" name="[Measures].[Unit Sales]"/>
|
150
|
+
<VirtualCubeMeasure cubeName="Sales" name="[Measures].[Store Cost]"/>
|
151
|
+
<VirtualCubeMeasure cubeName="Sales" name="[Measures].[Store Sales]"/>
|
152
|
+
<VirtualCubeMeasure cubeName="Sales" name="[Measures].[Sales Count]"/>
|
153
|
+
<VirtualCubeMeasure cubeName="Sales" name="[Measures].[Customer Count]"/>
|
154
|
+
<VirtualCubeMeasure cubeName="Warehouse" name="[Measures].[Units Shipped]"/>
|
155
|
+
<VirtualCubeMeasure cubeName="Warehouse" name="[Measures].[Store Invoice]"/>
|
156
|
+
<VirtualCubeMeasure cubeName="Warehouse" name="[Measures].[Products with units shipped]"/>
|
157
|
+
</VirtualCube>
|
158
|
+
|
159
|
+
<Role name="Canada manager">
|
160
|
+
<SchemaGrant access="none">
|
161
|
+
<CubeGrant access="all" cube="Sales">
|
162
|
+
<HierarchyGrant access="custom" hierarchy="[Customers]">
|
163
|
+
<MemberGrant access="all" member="[Customers].[Canada]"/>
|
164
|
+
</HierarchyGrant>
|
165
|
+
</CubeGrant>
|
166
|
+
</SchemaGrant>
|
167
|
+
</Role>
|
168
|
+
|
129
169
|
</Schema>
|
data/spec/mondrian_spec.rb
CHANGED
@@ -39,6 +39,7 @@ describe "Mondrian features" do
|
|
39
39
|
level 'City', :column => 'city', :unique_members => false
|
40
40
|
level 'Name', :column => 'fullname', :unique_members => true do
|
41
41
|
property 'Related name', :column => 'related_fullname', :type => "String"
|
42
|
+
property 'Birthdate', :column => 'birthdate', :type => "String"
|
42
43
|
end
|
43
44
|
end
|
44
45
|
hierarchy 'ID', :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
|
@@ -54,6 +55,9 @@ describe "Mondrian features" do
|
|
54
55
|
level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true, :level_type => 'TimeYears'
|
55
56
|
level 'Quarter', :column => 'quarter', :unique_members => false, :level_type => 'TimeQuarters'
|
56
57
|
level 'Month', :column => 'month_of_year', :type => 'Numeric', :unique_members => false, :level_type => 'TimeMonths'
|
58
|
+
level 'Day', :column => 'day_of_month', :type => 'Numeric', :unique_members => false, :level_type => 'TimeDays' do
|
59
|
+
property 'Date', :column => 'the_date', :type => "String"
|
60
|
+
end
|
57
61
|
end
|
58
62
|
hierarchy 'Weekly', :has_all => false, :primary_key => 'id' do
|
59
63
|
table 'time'
|
@@ -64,6 +68,17 @@ describe "Mondrian features" do
|
|
64
68
|
measure 'Unit Sales', :column => 'unit_sales', :aggregator => 'sum'
|
65
69
|
measure 'Store Sales', :column => 'store_sales', :aggregator => 'sum'
|
66
70
|
end
|
71
|
+
|
72
|
+
user_defined_function 'IsDirty' do
|
73
|
+
ruby do
|
74
|
+
returns :scalar
|
75
|
+
syntax :function
|
76
|
+
def call_with_evaluator(evaluator)
|
77
|
+
evaluator.isDirty
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
67
82
|
end
|
68
83
|
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
|
69
84
|
end
|
@@ -120,4 +135,121 @@ describe "Mondrian features" do
|
|
120
135
|
expect { @olap.execute mdx }.not_to raise_error
|
121
136
|
end
|
122
137
|
|
138
|
+
# Test for https://jira.pentaho.com/browse/MONDRIAN-2714
|
139
|
+
it "should return datetime property as java.sql.Timestamp" do
|
140
|
+
full_name = '[2010].[Q1].[1].[1]'
|
141
|
+
member = @olap.cube('Sales').member(full_name)
|
142
|
+
member.property_value('Date').should be_a(java.sql.Timestamp)
|
143
|
+
|
144
|
+
result = @olap.from('Sales').
|
145
|
+
with_member('[Measures].[date]').as("#{full_name}.Properties('Date')", format_string: 'dd.mm.yyyy').
|
146
|
+
columns('[Measures].[date]').execute
|
147
|
+
result.values.first.should be_a(java.sql.Timestamp)
|
148
|
+
result.formatted_values.first.should == '01.01.2010'
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should return date property as java.sql.Date" do
|
152
|
+
expected_date_class =
|
153
|
+
case MONDRIAN_DRIVER
|
154
|
+
when 'oracle'
|
155
|
+
java.sql.Timestamp
|
156
|
+
else
|
157
|
+
java.sql.Date
|
158
|
+
end
|
159
|
+
|
160
|
+
member = @olap.cube('Sales').hierarchy('Customers').level('Name').members.first
|
161
|
+
date_value = member.property_value('Birthdate')
|
162
|
+
date_value.should be_a(expected_date_class)
|
163
|
+
|
164
|
+
result = @olap.from('Sales').
|
165
|
+
with_member('[Measures].[date]').as("#{member.full_name}.Properties('Birthdate')", format_string: 'dd.mm.yyyy').
|
166
|
+
columns('[Measures].[date]').execute
|
167
|
+
result.values.first.should be_a(expected_date_class)
|
168
|
+
result.formatted_values.first.should == Date.parse(date_value.to_s).strftime("%d.%m.%Y")
|
169
|
+
end
|
170
|
+
|
171
|
+
describe "optimized Aggregate" do
|
172
|
+
def expected_value(crossjoin_members = nil)
|
173
|
+
query = @olap.from('Sales').columns('[Measures].[Unit Sales]')
|
174
|
+
query = query.crossjoin(crossjoin_members) if crossjoin_members
|
175
|
+
query.rows('[Customers].[USA].[CA]', '[Customers].[USA].[OR]').
|
176
|
+
execute.values.map(&:first).inject(&:+)
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should aggregate stored members" do
|
180
|
+
result = @olap.from('Sales').
|
181
|
+
with_member('[Customers].[CA and OR]').as("Aggregate({[Customers].[USA].[CA], [Customers].[USA].[OR]})").
|
182
|
+
columns('[Measures].[Unit Sales]').
|
183
|
+
rows('[Customers].[CA and OR]').execute
|
184
|
+
result.values[0][0].should == expected_value
|
185
|
+
end
|
186
|
+
|
187
|
+
it "should aggregate stored members from several dimensions" do
|
188
|
+
result = @olap.from('Sales').
|
189
|
+
with_member('[Customers].[CA and OR]').
|
190
|
+
as("Aggregate({[Gender].[F]} * {[Customers].[USA].[CA], [Customers].[USA].[OR]})").
|
191
|
+
columns('[Measures].[Unit Sales]').
|
192
|
+
rows('[Customers].[CA and OR]').execute
|
193
|
+
result.values[0][0].should == expected_value('[Gender].[F]')
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should aggregate stored members and a measure" do
|
197
|
+
result = @olap.from('Sales').
|
198
|
+
with_member('[Measures].[CA and OR]').
|
199
|
+
as("Aggregate({[Customers].[USA].[CA], [Customers].[USA].[OR]} * {[Measures].[Unit Sales]})").
|
200
|
+
columns('[Measures].[CA and OR]').execute
|
201
|
+
result.values[0].should == expected_value
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should aggregate stored members with expression" do
|
205
|
+
result = @olap.from('Sales').
|
206
|
+
with_member('[Measures].[CA and OR twice]').
|
207
|
+
as("Aggregate({[Customers].[USA].[CA], [Customers].[USA].[OR]}, [Measures].[Unit Sales] * 2)").
|
208
|
+
columns('[Measures].[CA and OR twice]').execute
|
209
|
+
result.values[0].should == expected_value * 2
|
210
|
+
end
|
211
|
+
|
212
|
+
it "should aggregate calculated aggregate members" do
|
213
|
+
result = @olap.from('Sales').
|
214
|
+
with_member('[Customers].[CA calculated]').as("Aggregate({[Customers].[USA].[CA]})").
|
215
|
+
with_member('[Customers].[OR calculated]').as("Aggregate({[Customers].[USA].[OR]})").
|
216
|
+
with_member('[Customers].[CA and OR]').as("Aggregate({[Customers].[CA calculated], [Customers].[OR calculated]})").
|
217
|
+
columns('[Measures].[Unit Sales]').
|
218
|
+
rows('[Customers].[CA and OR]').execute
|
219
|
+
result.values[0][0].should == expected_value
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
it "should call evaluator isDirty method" do
|
224
|
+
result = @olap.from('Sales').
|
225
|
+
with_member('[Measures].[is dirty]').as('IsDirty()').
|
226
|
+
columns('[Measures].[is dirty]').execute
|
227
|
+
result.values[0].should == false
|
228
|
+
end
|
229
|
+
|
230
|
+
it "should support multiple values IN expression" do
|
231
|
+
lambda do
|
232
|
+
@olap.from('Sales').
|
233
|
+
columns('[Measures].[Unit Sales]').
|
234
|
+
where('[Time].[2011].[Q1]', '[Time].[2011].[Q2]').
|
235
|
+
execute
|
236
|
+
end.should_not raise_error
|
237
|
+
end
|
238
|
+
|
239
|
+
describe "functions with double argument" do
|
240
|
+
it "should get Abs with decimal measure" do
|
241
|
+
result = @olap.from('Sales').
|
242
|
+
with_member('[Measures].[Abs Store Sales]').as('Abs([Measures].[Store Sales])').
|
243
|
+
columns('[Measures].[Store Sales]', '[Measures].[Abs Store Sales]').execute
|
244
|
+
result.values[0].should == result.values[1]
|
245
|
+
end
|
246
|
+
|
247
|
+
it "should get Round with decimal measure" do
|
248
|
+
result = @olap.from('Sales').
|
249
|
+
with_member('[Measures].[Round Store Sales]').as('Round([Measures].[Store Sales])').
|
250
|
+
columns('[Measures].[Store Sales]', '[Measures].[Round Store Sales]').
|
251
|
+
where('[Customers].[USA].[CA]').execute
|
252
|
+
result.values[0].round.should == result.values[1]
|
253
|
+
end
|
254
|
+
end
|
123
255
|
end
|