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