mondrian-olap 0.8.0 → 1.1.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 +36 -0
- data/LICENSE.txt +1 -1
- data/README.md +5 -2
- data/VERSION +1 -1
- data/lib/mondrian/jars/commons-collections-3.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-vfs2-2.2.jar +0 -0
- data/lib/mondrian/jars/eigenbase-xom-1.3.5.jar +0 -0
- data/lib/mondrian/jars/log4j.properties +2 -4
- data/lib/mondrian/jars/mondrian-8.3.0.5.jar +0 -0
- data/lib/mondrian/olap/connection.rb +112 -16
- data/lib/mondrian/olap/cube.rb +9 -2
- data/lib/mondrian/olap/error.rb +37 -8
- data/lib/mondrian/olap/query.rb +14 -17
- data/lib/mondrian/olap/result.rb +73 -40
- data/lib/mondrian/olap/schema.rb +1 -0
- data/lib/mondrian/olap/schema_element.rb +20 -4
- data/lib/mondrian/olap/schema_udf.rb +21 -16
- data/spec/connection_role_spec.rb +65 -12
- data/spec/connection_spec.rb +2 -0
- data/spec/cube_cache_control_spec.rb +16 -12
- data/spec/query_spec.rb +157 -21
- data/spec/schema_definition_spec.rb +151 -55
- data/spec/spec_helper.rb +75 -0
- metadata +65 -64
- data/lib/mondrian/jars/commons-collections-3.2.1.jar +0 -0
- data/lib/mondrian/jars/commons-logging-1.1.1.jar +0 -0
- data/lib/mondrian/jars/commons-vfs2-2.1-20150824.jar +0 -0
- data/lib/mondrian/jars/eigenbase-xom-1.3.1.jar +0 -0
- data/lib/mondrian/jars/mondrian-3.12.0.6-237.jar +0 -0
data/spec/connection_spec.rb
CHANGED
@@ -52,6 +52,8 @@ describe "Connection" do
|
|
52
52
|
when 'luciddb' then 'mondrian.spi.impl.LucidDbDialect'
|
53
53
|
when 'mssql' then 'mondrian.spi.impl.MicrosoftSqlServerDialect'
|
54
54
|
when 'sqlserver' then 'mondrian.spi.impl.MicrosoftSqlServerDialect'
|
55
|
+
when 'vertica' then 'mondrian.spi.impl.VerticaDialect'
|
56
|
+
when 'snowflake' then 'mondrian.spi.impl.JdbcDialectImpl'
|
55
57
|
end
|
56
58
|
end
|
57
59
|
|
@@ -73,6 +73,10 @@ describe "Cube" do
|
|
73
73
|
end
|
74
74
|
|
75
75
|
describe 'cache', unless: MONDRIAN_DRIVER == 'luciddb' do
|
76
|
+
def qt(name)
|
77
|
+
@connection.quote_table_name(name.to_s)
|
78
|
+
end
|
79
|
+
|
76
80
|
before(:all) do
|
77
81
|
@connection = ActiveRecord::Base.connection
|
78
82
|
@cube = @olap.cube('Sales')
|
@@ -83,18 +87,18 @@ describe "Cube" do
|
|
83
87
|
SQL
|
84
88
|
|
85
89
|
case MONDRIAN_DRIVER
|
86
|
-
when 'mysql', 'jdbc_mysql', 'postgresql', 'oracle'
|
90
|
+
when 'mysql', 'jdbc_mysql', 'postgresql', 'oracle', 'vertica', 'snowflake'
|
87
91
|
@connection.execute 'CREATE TABLE sales_copy AS SELECT * FROM sales'
|
88
92
|
when 'mssql', 'sqlserver'
|
89
93
|
# Use raw_connection.execute to avoid detecting this query as a SELECT query
|
90
94
|
# for which executeQuery JDBC method will fail
|
91
|
-
@connection.raw_connection.
|
95
|
+
@connection.raw_connection.execute_update 'SELECT * INTO sales_copy FROM sales'
|
92
96
|
end
|
93
97
|
end
|
94
98
|
|
95
99
|
after(:each) do
|
96
100
|
case MONDRIAN_DRIVER
|
97
|
-
when 'mysql', 'jdbc_mysql', 'postgresql', 'oracle'
|
101
|
+
when 'mysql', 'jdbc_mysql', 'postgresql', 'oracle', 'vertica', 'snowflake'
|
98
102
|
@connection.execute 'TRUNCATE TABLE sales'
|
99
103
|
@connection.execute 'INSERT INTO sales SELECT * FROM sales_copy'
|
100
104
|
when 'mssql', 'sqlserver'
|
@@ -109,7 +113,7 @@ describe "Cube" do
|
|
109
113
|
|
110
114
|
after(:all) do
|
111
115
|
case MONDRIAN_DRIVER
|
112
|
-
when 'mysql', 'jdbc_mysql', 'postgresql', 'oracle'
|
116
|
+
when 'mysql', 'jdbc_mysql', 'postgresql', 'oracle', 'vertica', 'snowflake'
|
113
117
|
@connection.execute 'DROP TABLE sales_copy'
|
114
118
|
when 'mssql', 'sqlserver'
|
115
119
|
@connection.execute 'DROP TABLE sales_copy'
|
@@ -121,7 +125,7 @@ describe "Cube" do
|
|
121
125
|
@connection.execute <<-SQL
|
122
126
|
DELETE FROM sales
|
123
127
|
WHERE time_id IN (SELECT id
|
124
|
-
FROM
|
128
|
+
FROM #{qt :time}
|
125
129
|
WHERE the_year = 2010
|
126
130
|
AND quarter = 'Q1')
|
127
131
|
AND customer_id IN (SELECT id
|
@@ -139,7 +143,7 @@ describe "Cube" do
|
|
139
143
|
@connection.execute <<-SQL
|
140
144
|
DELETE FROM sales
|
141
145
|
WHERE time_id IN (SELECT id
|
142
|
-
FROM
|
146
|
+
FROM #{qt :time}
|
143
147
|
WHERE the_year = 2010
|
144
148
|
AND quarter = 'Q1')
|
145
149
|
AND customer_id IN (SELECT id
|
@@ -158,7 +162,7 @@ describe "Cube" do
|
|
158
162
|
store_sales = store_sales + 1,
|
159
163
|
store_cost = store_cost + 1
|
160
164
|
WHERE time_id IN (SELECT id
|
161
|
-
FROM
|
165
|
+
FROM #{qt :time}
|
162
166
|
WHERE the_year = 2010
|
163
167
|
AND quarter = 'Q1')
|
164
168
|
AND customer_id IN (SELECT id
|
@@ -178,7 +182,7 @@ describe "Cube" do
|
|
178
182
|
store_sales = store_sales + 1,
|
179
183
|
store_cost = store_cost + 1
|
180
184
|
WHERE time_id IN (SELECT id
|
181
|
-
FROM
|
185
|
+
FROM #{qt :time}
|
182
186
|
WHERE the_year = 2010
|
183
187
|
AND quarter = 'Q1')
|
184
188
|
AND customer_id IN (SELECT id
|
@@ -195,7 +199,7 @@ describe "Cube" do
|
|
195
199
|
@connection.execute <<-SQL
|
196
200
|
DELETE FROM sales
|
197
201
|
WHERE time_id IN (SELECT id
|
198
|
-
FROM
|
202
|
+
FROM #{qt :time}
|
199
203
|
WHERE the_year = 2010
|
200
204
|
AND quarter = 'Q1')
|
201
205
|
AND customer_id IN (SELECT id
|
@@ -213,7 +217,7 @@ describe "Cube" do
|
|
213
217
|
@connection.execute <<-SQL
|
214
218
|
DELETE FROM sales
|
215
219
|
WHERE time_id IN (SELECT id
|
216
|
-
FROM
|
220
|
+
FROM #{qt :time}
|
217
221
|
WHERE the_year = 2010
|
218
222
|
AND quarter = 'Q1')
|
219
223
|
AND customer_id IN (SELECT id
|
@@ -232,7 +236,7 @@ describe "Cube" do
|
|
232
236
|
store_sales = store_sales + 1,
|
233
237
|
store_cost = store_cost + 1
|
234
238
|
WHERE time_id IN (SELECT id
|
235
|
-
FROM
|
239
|
+
FROM #{qt :time}
|
236
240
|
WHERE the_year = 2010
|
237
241
|
AND quarter = 'Q1')
|
238
242
|
AND customer_id IN (SELECT id
|
@@ -252,7 +256,7 @@ describe "Cube" do
|
|
252
256
|
store_sales = store_sales + 1,
|
253
257
|
store_cost = store_cost + 1
|
254
258
|
WHERE time_id IN (SELECT id
|
255
|
-
FROM
|
259
|
+
FROM #{qt :time}
|
256
260
|
WHERE the_year = 2010
|
257
261
|
AND quarter = 'Q1')
|
258
262
|
AND customer_id IN (SELECT id
|
data/spec/query_spec.rb
CHANGED
@@ -671,20 +671,25 @@ describe "Query" do
|
|
671
671
|
end
|
672
672
|
|
673
673
|
it "should return query including WITH SET clause" do
|
674
|
-
@query.with_set('
|
674
|
+
@query.with_set('CrossJoinSet').
|
675
675
|
as('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]').
|
676
|
+
with_set('MemberSet').as('[Product].[All Products]').
|
677
|
+
with_set('FunctionSet').as('[Product].AllMembers').
|
678
|
+
with_set('ItemSet').as('[Product].AllMembers.Item(0)').
|
676
679
|
with_member('[Measures].[Profit]').
|
677
680
|
as('[Measures].[Store Sales] - [Measures].[Store Cost]').
|
678
681
|
columns('[Measures].[Profit]').
|
679
|
-
rows('
|
682
|
+
rows('CrossJoinSet').
|
680
683
|
to_mdx.should be_like <<-SQL
|
681
684
|
WITH
|
682
|
-
SET
|
683
|
-
'
|
685
|
+
SET CrossJoinSet AS 'CROSSJOIN([Product].children, {[Customers].[Canada], [Customers].[USA]})'
|
686
|
+
SET MemberSet AS '{[Product].[All Products]}'
|
687
|
+
SET FunctionSet AS '[Product].AllMembers'
|
688
|
+
SET ItemSet AS '{[Product].AllMembers.Item(0)}'
|
684
689
|
MEMBER [Measures].[Profit] AS
|
685
690
|
'[Measures].[Store Sales] - [Measures].[Store Cost]'
|
686
691
|
SELECT {[Measures].[Profit]} ON COLUMNS,
|
687
|
-
|
692
|
+
CrossJoinSet ON ROWS
|
688
693
|
FROM [Sales]
|
689
694
|
SQL
|
690
695
|
end
|
@@ -756,6 +761,17 @@ describe "Query" do
|
|
756
761
|
}
|
757
762
|
end
|
758
763
|
|
764
|
+
it "should raise error when TokenMgrError is raised" do
|
765
|
+
expect {
|
766
|
+
@query.with_member('[Measures].[Dummy]').as('[Measures].[Store Sales]]').
|
767
|
+
columns('[Measures].[Dummy]').execute
|
768
|
+
}.to raise_error {|e|
|
769
|
+
e.should be_kind_of(Mondrian::OLAP::Error)
|
770
|
+
e.message.should =~ /mondrian\.parser\.TokenMgrError/
|
771
|
+
e.root_cause_message.should =~ /Lexical error/
|
772
|
+
}
|
773
|
+
end
|
774
|
+
|
759
775
|
end
|
760
776
|
|
761
777
|
describe "drill through cell" do
|
@@ -828,18 +844,18 @@ describe "Query" do
|
|
828
844
|
]
|
829
845
|
when 'mssql'
|
830
846
|
[
|
831
|
-
|
847
|
+
Integer, String, Integer, Integer, Integer,
|
832
848
|
String, String, String, String, String, String,
|
833
|
-
# last one can be BigDecimal or
|
849
|
+
# last one can be BigDecimal or Integer, probably depends on MS SQL version
|
834
850
|
String, String, String, Numeric,
|
835
851
|
String,
|
836
852
|
BigDecimal
|
837
853
|
]
|
838
854
|
else
|
839
855
|
[
|
840
|
-
|
856
|
+
Integer, String, Integer, Integer, Integer,
|
841
857
|
String, String, String, String, String, String,
|
842
|
-
String, String, String,
|
858
|
+
String, String, String, Integer,
|
843
859
|
String,
|
844
860
|
BigDecimal
|
845
861
|
]
|
@@ -1018,13 +1034,25 @@ describe "Query" do
|
|
1018
1034
|
@sql.select_value("SELECT unit_sales FROM sales WHERE #{@condition}").to_i.should == @first_unit_sales
|
1019
1035
|
end
|
1020
1036
|
|
1021
|
-
|
1037
|
+
before do
|
1038
|
+
create_olap_connection
|
1039
|
+
@unit_sales = query_unit_sales_value
|
1040
|
+
|
1041
|
+
update_first_unit_sales(@first_unit_sales + 1)
|
1042
|
+
|
1043
|
+
# should still use previous value from cache
|
1044
|
+
create_olap_connection
|
1045
|
+
query_unit_sales_value.should == @unit_sales
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
after do
|
1022
1049
|
update_first_unit_sales(@first_unit_sales)
|
1050
|
+
Mondrian::OLAP::Connection.flush_schema_cache
|
1023
1051
|
end
|
1024
1052
|
|
1025
|
-
def create_olap_connection
|
1053
|
+
def create_olap_connection(options = {})
|
1026
1054
|
@olap2.close if @olap2
|
1027
|
-
@olap2 = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS_WITH_CATALOG)
|
1055
|
+
@olap2 = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS_WITH_CATALOG.merge(options))
|
1028
1056
|
end
|
1029
1057
|
|
1030
1058
|
def update_first_unit_sales(value)
|
@@ -1036,21 +1064,129 @@ describe "Query" do
|
|
1036
1064
|
end
|
1037
1065
|
|
1038
1066
|
it "should flush schema cache" do
|
1067
|
+
@olap2.flush_schema
|
1039
1068
|
create_olap_connection
|
1040
|
-
unit_sales
|
1041
|
-
|
1042
|
-
update_first_unit_sales(@first_unit_sales + 1)
|
1069
|
+
query_unit_sales_value.should == @unit_sales + 1
|
1070
|
+
end
|
1043
1071
|
|
1044
|
-
|
1072
|
+
it "should remove schema by key" do
|
1073
|
+
Mondrian::OLAP::Connection.flush_schema(@olap2.schema_key)
|
1045
1074
|
create_olap_connection
|
1046
|
-
query_unit_sales_value.should == unit_sales
|
1075
|
+
query_unit_sales_value.should == @unit_sales + 1
|
1076
|
+
end
|
1047
1077
|
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1078
|
+
end
|
1079
|
+
|
1080
|
+
describe "profiling" do
|
1081
|
+
before(:all) do
|
1082
|
+
if @olap
|
1083
|
+
@olap.flush_schema
|
1084
|
+
@olap.close
|
1085
|
+
end
|
1086
|
+
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS_WITH_CATALOG)
|
1087
|
+
@result = @olap.execute "SELECT [Measures].[Unit Sales] ON COLUMNS, [Product].Children ON ROWS FROM [Sales]", profiling: true
|
1088
|
+
@result.profiling_mark_full("MDX query time", 100)
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
it "should return query plan" do
|
1092
|
+
@result.profiling_plan.strip.should == <<-EOS.strip
|
1093
|
+
Axis (COLUMNS):
|
1094
|
+
SetListCalc(name=SetListCalc, class=class mondrian.olap.fun.SetFunDef$SetListCalc, type=SetType<MemberType<member=[Measures].[Unit Sales]>>, resultStyle=MUTABLE_LIST)
|
1095
|
+
2(name=2, class=class mondrian.olap.fun.SetFunDef$SetListCalc$2, type=MemberType<member=[Measures].[Unit Sales]>, resultStyle=VALUE)
|
1096
|
+
Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=MemberType<member=[Measures].[Unit Sales]>, resultStyle=VALUE_NOT_NULL, value=[Measures].[Unit Sales])
|
1097
|
+
|
1098
|
+
Axis (ROWS):
|
1099
|
+
Children(name=Children, class=class mondrian.olap.fun.BuiltinFunTable$22$1, type=SetType<MemberType<hierarchy=[Product]>>, resultStyle=LIST)
|
1100
|
+
CurrentMemberFixed(hierarchy=[Product], name=CurrentMemberFixed, class=class mondrian.olap.fun.HierarchyCurrentMemberFunDef$FixedCalcImpl, type=MemberType<hierarchy=[Product]>, resultStyle=VALUE)
|
1101
|
+
EOS
|
1052
1102
|
end
|
1053
1103
|
|
1104
|
+
it "should return SQL timing string" do
|
1105
|
+
@result.profiling_timing_string.strip.should =~
|
1106
|
+
%r{^SqlStatement-Segment.load invoked 1 times for total of \d+ms. \(Avg. \d+ms/invocation\)$}
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
it "should return custom profiling string" do
|
1110
|
+
@result.profiling_timing_string.strip.should =~
|
1111
|
+
%r{^MDX query time invoked 1 times for total of 100ms. \(Avg. 100ms/invocation\)$}
|
1112
|
+
end
|
1113
|
+
|
1114
|
+
it "should return total duration" do
|
1115
|
+
@result.total_duration.should > 0
|
1116
|
+
end
|
1117
|
+
end
|
1118
|
+
|
1119
|
+
describe "error with profiling" do
|
1120
|
+
before(:all) do
|
1121
|
+
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS_WITH_CATALOG)
|
1122
|
+
begin
|
1123
|
+
@olap.execute <<-MDX, profiling: true
|
1124
|
+
SELECT [Measures].[Unit Sales] ON COLUMNS,
|
1125
|
+
FILTER([Customers].Children, ([Customers].DefaultMember, [Measures].[Unit Sales]) > 'dummy') ON ROWS
|
1126
|
+
FROM [Sales]
|
1127
|
+
MDX
|
1128
|
+
rescue => e
|
1129
|
+
@error = e
|
1130
|
+
end
|
1131
|
+
end
|
1132
|
+
|
1133
|
+
it "should return query plan" do
|
1134
|
+
@error.profiling_plan.should =~ /^Axis \(COLUMNS\):/
|
1135
|
+
end
|
1136
|
+
|
1137
|
+
it "should return timing string" do
|
1138
|
+
@error.profiling_timing_string.should =~
|
1139
|
+
%r{^FilterFunDef invoked 1 times for total of \d+ms. \(Avg. \d+ms/invocation\)$}
|
1140
|
+
end
|
1141
|
+
end
|
1142
|
+
|
1143
|
+
describe "timeout" do
|
1144
|
+
before(:all) do
|
1145
|
+
@schema = Mondrian::OLAP::Schema.new
|
1146
|
+
@schema.define do
|
1147
|
+
cube 'Sales' do
|
1148
|
+
table 'sales'
|
1149
|
+
dimension 'Customers', foreign_key: 'customer_id' do
|
1150
|
+
hierarchy all_member_name: 'All Customers', primary_key: 'id' do
|
1151
|
+
table 'customers'
|
1152
|
+
level 'Name', column: 'fullname'
|
1153
|
+
end
|
1154
|
+
end
|
1155
|
+
calculated_member 'Sleep 5' do
|
1156
|
+
dimension 'Measures'
|
1157
|
+
formula 'Sleep(5)'
|
1158
|
+
end
|
1159
|
+
calculated_member 'Sleep 0' do
|
1160
|
+
dimension 'Measures'
|
1161
|
+
formula 'Sleep(0)'
|
1162
|
+
end
|
1163
|
+
end
|
1164
|
+
user_defined_function 'Sleep' do
|
1165
|
+
ruby do
|
1166
|
+
parameters :numeric
|
1167
|
+
returns :numeric
|
1168
|
+
def call(n)
|
1169
|
+
sleep n
|
1170
|
+
n
|
1171
|
+
end
|
1172
|
+
end
|
1173
|
+
end
|
1174
|
+
end
|
1175
|
+
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge schema: @schema)
|
1176
|
+
end
|
1177
|
+
|
1178
|
+
it "should raise timeout error for long queries" do
|
1179
|
+
expect do
|
1180
|
+
@olap.from('Sales').columns('[Measures].[Sleep 5]').execute(timeout: 0.1)
|
1181
|
+
end.to raise_error do |e|
|
1182
|
+
e.should be_kind_of(Mondrian::OLAP::Error)
|
1183
|
+
e.message.should == 'org.olap4j.OlapException: Mondrian Error:Query timeout of 0 seconds reached'
|
1184
|
+
end
|
1185
|
+
end
|
1186
|
+
|
1187
|
+
it "should not raise timeout error for short queries" do
|
1188
|
+
@olap.from('Sales').columns('[Measures].[Sleep 0]').execute(timeout: 1).values.should == [0]
|
1189
|
+
end
|
1054
1190
|
end
|
1055
1191
|
|
1056
1192
|
end
|
@@ -786,61 +786,80 @@ describe "Schema definition" do
|
|
786
786
|
end
|
787
787
|
|
788
788
|
describe "Element annotations" do
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
annotation 'key1', 'value1'
|
794
|
-
annotation 'key2', 'value2'
|
795
|
-
end
|
796
|
-
measure 'Unit Sales', :column => 'unit_sales' do
|
789
|
+
describe "block of elements" do
|
790
|
+
before do
|
791
|
+
@schema.define do
|
792
|
+
cube 'Sales' do
|
797
793
|
annotations do
|
798
|
-
annotation '
|
794
|
+
annotation 'key1', 'value1'
|
795
|
+
annotation 'key2', 'value2'
|
796
|
+
end
|
797
|
+
measure 'Unit Sales', :column => 'unit_sales' do
|
798
|
+
annotations do
|
799
|
+
annotation 'key3', 'value3'
|
800
|
+
end
|
799
801
|
end
|
800
802
|
end
|
801
803
|
end
|
802
804
|
end
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
<Annotation name="key2">value2</Annotation>
|
810
|
-
</Annotations>
|
811
|
-
<Measure aggregator="sum" column="unit_sales" name="Unit Sales">
|
805
|
+
|
806
|
+
it "should render XML" do
|
807
|
+
@schema.to_xml.should be_like <<-XML
|
808
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
809
|
+
<Schema name="default">
|
810
|
+
<Cube name="Sales">
|
812
811
|
<Annotations>
|
813
|
-
<Annotation name="
|
812
|
+
<Annotation name="key1">value1</Annotation>
|
813
|
+
<Annotation name="key2">value2</Annotation>
|
814
814
|
</Annotations>
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
815
|
+
<Measure aggregator="sum" column="unit_sales" name="Unit Sales">
|
816
|
+
<Annotations>
|
817
|
+
<Annotation name="key3">value3</Annotation>
|
818
|
+
</Annotations>
|
819
|
+
</Measure>
|
820
|
+
</Cube>
|
821
|
+
</Schema>
|
822
|
+
XML
|
823
|
+
end
|
824
|
+
|
825
|
+
it "should access annotations from schema definition" do
|
826
|
+
@schema.cubes.first.annotations_hash.should == {'key1' => 'value1', 'key2' => 'value2'}
|
827
|
+
end
|
819
828
|
end
|
820
829
|
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
830
|
+
describe "from hash options" do
|
831
|
+
before do
|
832
|
+
@schema.define do
|
833
|
+
cube 'Sales' do
|
834
|
+
annotations :key1 => 'value1', :key2 => 'value2'
|
835
|
+
measure 'Unit Sales', :column => 'unit_sales', :annotations => {:key3 => 'value3'}
|
836
|
+
end
|
826
837
|
end
|
827
838
|
end
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
<Annotation name="key2">value2</Annotation>
|
835
|
-
</Annotations>
|
836
|
-
<Measure aggregator="sum" column="unit_sales" name="Unit Sales">
|
839
|
+
|
840
|
+
it "should render XML " do
|
841
|
+
@schema.to_xml.should be_like <<-XML
|
842
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
843
|
+
<Schema name="default">
|
844
|
+
<Cube name="Sales">
|
837
845
|
<Annotations>
|
838
|
-
<Annotation name="
|
846
|
+
<Annotation name="key1">value1</Annotation>
|
847
|
+
<Annotation name="key2">value2</Annotation>
|
839
848
|
</Annotations>
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
849
|
+
<Measure aggregator="sum" column="unit_sales" name="Unit Sales">
|
850
|
+
<Annotations>
|
851
|
+
<Annotation name="key3">value3</Annotation>
|
852
|
+
</Annotations>
|
853
|
+
</Measure>
|
854
|
+
</Cube>
|
855
|
+
</Schema>
|
856
|
+
XML
|
857
|
+
end
|
858
|
+
|
859
|
+
it "should access annotations from schema definition" do
|
860
|
+
@schema.cubes.first.annotations_hash.should == {'key1' => 'value1', 'key2' => 'value2'}
|
861
|
+
@schema.cubes.first.measures.first.annotations_hash.should == {'key3' => 'value3'}
|
862
|
+
end
|
844
863
|
end
|
845
864
|
end
|
846
865
|
|
@@ -1001,11 +1020,11 @@ describe "Schema definition" do
|
|
1001
1020
|
dimension 'Measures'
|
1002
1021
|
formula 'Factorial(6)'
|
1003
1022
|
cell_formatter do
|
1004
|
-
coffeescript <<-
|
1023
|
+
coffeescript <<-CS
|
1005
1024
|
s = value.toString()
|
1006
1025
|
s = "0" + s while s.length < 20
|
1007
1026
|
s
|
1008
|
-
|
1027
|
+
CS
|
1009
1028
|
end
|
1010
1029
|
end
|
1011
1030
|
calculated_member 'City' do
|
@@ -1014,30 +1033,30 @@ describe "Schema definition" do
|
|
1014
1033
|
end
|
1015
1034
|
end
|
1016
1035
|
user_defined_function 'Factorial' do
|
1017
|
-
coffeescript <<-
|
1036
|
+
coffeescript <<-CS
|
1018
1037
|
parameters: ["Numeric"]
|
1019
1038
|
returns: "Numeric"
|
1020
1039
|
execute: (n) ->
|
1021
1040
|
if n <= 1 then 1 else n * @execute(n - 1)
|
1022
|
-
|
1041
|
+
CS
|
1023
1042
|
end
|
1024
1043
|
user_defined_function 'UpperName' do
|
1025
|
-
coffeescript <<-
|
1044
|
+
coffeescript <<-CS
|
1026
1045
|
parameters: ["Member"]
|
1027
1046
|
returns: "String"
|
1028
1047
|
syntax: "Property"
|
1029
1048
|
execute: (member) ->
|
1030
1049
|
member.getName().toUpperCase()
|
1031
|
-
|
1050
|
+
CS
|
1032
1051
|
end
|
1033
1052
|
user_defined_function 'toUpperName' do
|
1034
|
-
coffeescript <<-
|
1053
|
+
coffeescript <<-CS
|
1035
1054
|
parameters: ["Member", "String"]
|
1036
1055
|
returns: "String"
|
1037
1056
|
syntax: "Method"
|
1038
1057
|
execute: (member, dummy) ->
|
1039
1058
|
member.getName().toUpperCase()
|
1040
|
-
|
1059
|
+
CS
|
1041
1060
|
end
|
1042
1061
|
end
|
1043
1062
|
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
|
@@ -1084,12 +1103,21 @@ describe "Schema definition" do
|
|
1084
1103
|
@schema.define do
|
1085
1104
|
cube 'Sales' do
|
1086
1105
|
table 'sales'
|
1087
|
-
dimension '
|
1088
|
-
hierarchy :
|
1106
|
+
dimension 'Time', foreign_key: 'time_id' do
|
1107
|
+
hierarchy all_member_name: 'All Time', primary_key: 'time_id' do
|
1108
|
+
table 'time_by_day'
|
1109
|
+
level 'Year', column: 'the_year', type: 'Numeric'
|
1110
|
+
level 'Quarter', column: 'quarter'
|
1111
|
+
level 'Month', column: 'month_of_year', type: 'Numeric'
|
1112
|
+
level 'Day', column: 'day_of_month', type: 'Numeric'
|
1113
|
+
end
|
1114
|
+
end
|
1115
|
+
dimension 'Customers', foreign_key: 'customer_id' do
|
1116
|
+
hierarchy all_member_name: 'All Customers', primary_key: 'id' do
|
1089
1117
|
table 'customers'
|
1090
|
-
level 'Name', :
|
1118
|
+
level 'Name', column: 'fullname' do
|
1091
1119
|
member_formatter { ruby {|member| member.getName().upcase } }
|
1092
|
-
property 'City', :
|
1120
|
+
property 'City', column: 'city' do
|
1093
1121
|
property_formatter { ruby {|member, property_name, property_value| property_value.upcase} }
|
1094
1122
|
end
|
1095
1123
|
end
|
@@ -1174,8 +1202,38 @@ describe "Schema definition" do
|
|
1174
1202
|
end
|
1175
1203
|
end
|
1176
1204
|
end
|
1205
|
+
user_defined_function 'ChildrenSet' do
|
1206
|
+
ruby do
|
1207
|
+
parameters :member
|
1208
|
+
returns :member_set
|
1209
|
+
syntax :function
|
1210
|
+
def call_with_evaluator(evaluator, member)
|
1211
|
+
evaluator.getSchemaReader.getMemberChildren(member)
|
1212
|
+
end
|
1213
|
+
end
|
1214
|
+
end
|
1215
|
+
user_defined_function 'SetFirstTuple' do
|
1216
|
+
ruby do
|
1217
|
+
parameters :set
|
1218
|
+
returns :tuple
|
1219
|
+
syntax :function
|
1220
|
+
def call_with_evaluator(evaluator, set)
|
1221
|
+
set.first
|
1222
|
+
end
|
1223
|
+
end
|
1224
|
+
end
|
1225
|
+
user_defined_function 'SetFirstTuples' do
|
1226
|
+
ruby do
|
1227
|
+
parameters :tuple_set, :numeric
|
1228
|
+
returns :tuple_set
|
1229
|
+
syntax :function
|
1230
|
+
def call_with_evaluator(evaluator, set, count)
|
1231
|
+
set.to_a[0, count]
|
1232
|
+
end
|
1233
|
+
end
|
1234
|
+
end
|
1177
1235
|
end
|
1178
|
-
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :
|
1236
|
+
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge schema: @schema)
|
1179
1237
|
end
|
1180
1238
|
|
1181
1239
|
it "should execute user defined function" do
|
@@ -1250,6 +1308,35 @@ describe "Schema definition" do
|
|
1250
1308
|
result.values[i].should == [first_member.name.upcase]
|
1251
1309
|
end
|
1252
1310
|
end
|
1311
|
+
|
1312
|
+
it "should execute user defined functions with a member set result" do
|
1313
|
+
result = @olap.from('Sales').
|
1314
|
+
with_member('[Measures].[Children Set]').as("SetToStr(ChildrenSet([Customers].CurrentMember))").
|
1315
|
+
columns('[Measures].[Children Set]').rows('[Customers].DefaultMember').execute
|
1316
|
+
result.row_members.each_with_index do |member, i|
|
1317
|
+
result.values[i].first.should == "{#{member.children.map(&:full_name).join(', ')}}"
|
1318
|
+
end
|
1319
|
+
end
|
1320
|
+
|
1321
|
+
it "should execute user defined functions with a tuple result" do
|
1322
|
+
result = @olap.from('Sales').
|
1323
|
+
with_member('[Measures].[First Tuple]').
|
1324
|
+
as("TupleToStr(SetFirstTuple([Customers].CurrentMember.Children * [Time].DefaultMember))").
|
1325
|
+
columns('[Measures].[First Tuple]').rows('[Customers].DefaultMember').execute
|
1326
|
+
result.row_members.each_with_index do |member, i|
|
1327
|
+
result.values[i].first.should == "(#{member.children.first.full_name}, [Time].[All Time])"
|
1328
|
+
end
|
1329
|
+
end
|
1330
|
+
|
1331
|
+
it "should execute user defined functions with a tuple result" do
|
1332
|
+
result = @olap.from('Sales').
|
1333
|
+
with_member('[Measures].[First Tuple]').
|
1334
|
+
as("SetToStr(SetFirstTuples([Customers].CurrentMember.Children * [Time].DefaultMember, 1))").
|
1335
|
+
columns('[Measures].[First Tuple]').rows('[Customers].DefaultMember').execute
|
1336
|
+
result.row_members.each_with_index do |member, i|
|
1337
|
+
result.values[i].first.should == "{(#{member.children.first.full_name}, [Time].[All Time])}"
|
1338
|
+
end
|
1339
|
+
end
|
1253
1340
|
end
|
1254
1341
|
|
1255
1342
|
describe "Shared user defined functions in Ruby" do
|
@@ -1353,6 +1440,15 @@ describe "Schema definition" do
|
|
1353
1440
|
@olap.cube('Sales').member('[Measures].[Unit Sales]').cell_formatter_name.should be_nil
|
1354
1441
|
end
|
1355
1442
|
|
1443
|
+
it "should get measure cell formatter" do
|
1444
|
+
@olap.cube('Sales').member('[Measures].[Factorial]').cell_formatter.class.name.should ==
|
1445
|
+
'Mondrian::OLAP::Schema::CellFormatter::Integer20DigitsUdf'
|
1446
|
+
end
|
1447
|
+
|
1448
|
+
it "should not get measure cell formatter if not specified" do
|
1449
|
+
@olap.cube('Sales').member('[Measures].[Unit Sales]').cell_formatter.should be_nil
|
1450
|
+
end
|
1451
|
+
|
1356
1452
|
it "should get measure format string" do
|
1357
1453
|
@olap.cube('Sales').member('[Measures].[Unit Sales]').format_string.should == '#,##0'
|
1358
1454
|
end
|