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.
@@ -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