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.
@@ -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.execute 'SELECT * INTO sales_copy FROM sales'
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 TIME
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 TIME
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 TIME
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 TIME
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 TIME
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 TIME
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 TIME
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 TIME
259
+ FROM #{qt :time}
256
260
  WHERE the_year = 2010
257
261
  AND quarter = 'Q1')
258
262
  AND customer_id IN (SELECT id
@@ -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('SelectedRows').
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('SelectedRows').
682
+ rows('CrossJoinSet').
680
683
  to_mdx.should be_like <<-SQL
681
684
  WITH
682
- SET SelectedRows AS
683
- 'CROSSJOIN([Product].children, {[Customers].[Canada], [Customers].[USA]})'
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
- SelectedRows ON ROWS
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
- Fixnum, String, Fixnum, Fixnum, Fixnum,
847
+ Integer, String, Integer, Integer, Integer,
832
848
  String, String, String, String, String, String,
833
- # last one can be BigDecimal or Fixnum, probably depends on MS SQL version
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
- Fixnum, String, Fixnum, Fixnum, Fixnum,
856
+ Integer, String, Integer, Integer, Integer,
841
857
  String, String, String, String, String, String,
842
- String, String, String, Fixnum,
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
- after(:all) do
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 = query_unit_sales_value
1041
-
1042
- update_first_unit_sales(@first_unit_sales + 1)
1069
+ query_unit_sales_value.should == @unit_sales + 1
1070
+ end
1043
1071
 
1044
- # should still use previous value from cache
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
- # should query new value from the database after flush schema cache
1049
- @olap2.flush_schema_cache
1050
- create_olap_connection
1051
- query_unit_sales_value.should == unit_sales + 1
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
- it "should render XML from block of elements" do
790
- @schema.define do
791
- cube 'Sales' do
792
- annotations do
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 'key3', 'value3'
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
- @schema.to_xml.should be_like <<-XML
804
- <?xml version="1.0" encoding="UTF-8"?>
805
- <Schema name="default">
806
- <Cube name="Sales">
807
- <Annotations>
808
- <Annotation name="key1">value1</Annotation>
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="key3">value3</Annotation>
812
+ <Annotation name="key1">value1</Annotation>
813
+ <Annotation name="key2">value2</Annotation>
814
814
  </Annotations>
815
- </Measure>
816
- </Cube>
817
- </Schema>
818
- XML
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
- it "should render XML from hash options" do
822
- @schema.define do
823
- cube 'Sales' do
824
- annotations :key1 => 'value1', :key2 => 'value2'
825
- measure 'Unit Sales', :column => 'unit_sales', :annotations => {:key3 => 'value3'}
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
- @schema.to_xml.should be_like <<-XML
829
- <?xml version="1.0" encoding="UTF-8"?>
830
- <Schema name="default">
831
- <Cube name="Sales">
832
- <Annotations>
833
- <Annotation name="key1">value1</Annotation>
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="key3">value3</Annotation>
846
+ <Annotation name="key1">value1</Annotation>
847
+ <Annotation name="key2">value2</Annotation>
839
848
  </Annotations>
840
- </Measure>
841
- </Cube>
842
- </Schema>
843
- XML
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 <<-JS
1023
+ coffeescript <<-CS
1005
1024
  s = value.toString()
1006
1025
  s = "0" + s while s.length < 20
1007
1026
  s
1008
- JS
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 <<-JS
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
- JS
1041
+ CS
1023
1042
  end
1024
1043
  user_defined_function 'UpperName' do
1025
- coffeescript <<-JS
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
- JS
1050
+ CS
1032
1051
  end
1033
1052
  user_defined_function 'toUpperName' do
1034
- coffeescript <<-JS
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
- JS
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 'Customers', :foreign_key => 'customer_id' do
1088
- hierarchy :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
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', :column => 'fullname' do
1118
+ level 'Name', column: 'fullname' do
1091
1119
  member_formatter { ruby {|member| member.getName().upcase } }
1092
- property 'City', :column => 'city' do
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 :schema => @schema)
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