mondrian-olap 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|