mondrian-olap 1.2.0 → 1.3.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.
@@ -9,21 +9,8 @@ module Mondrian
9
9
  end
10
10
 
11
11
  module ScriptElements
12
- def javascript(text)
13
- script text, :language => 'JavaScript'
14
- end
15
-
16
12
  private
17
13
 
18
- def coffeescript_function(arguments_string, text)
19
- # construct function to ensure that last expression is returned
20
- coffee_text = "#{arguments_string} ->\n" << text.gsub(/^/, ' ')
21
- javascript_text = CoffeeScript.compile(coffee_text, :bare => true)
22
- # remove function definition first and last lines
23
- javascript_text = javascript_text.strip.lines.to_a[1..-2].join
24
- javascript javascript_text
25
- end
26
-
27
14
  def ruby(*options, &block)
28
15
  udf_class_name = if options.include?(:shared)
29
16
  "#{name.capitalize}Udf"
@@ -74,64 +61,13 @@ module Mondrian
74
61
 
75
62
  class UserDefinedFunction < SchemaElement
76
63
  include ScriptElements
64
+
77
65
  attributes :name, # Name with which the user-defined function will be referenced in MDX expressions.
78
- # Name of the class which implemenets this user-defined function.
79
- # Must implement the mondrian.spi.UserDefinedFunction interface.
66
+ # Name of the class which implements this user-defined function.
67
+ # Must implement the mondrian.spi.UserDefinedFunction interface.
80
68
  :class_name
81
69
  elements :script
82
70
 
83
- def coffeescript(text)
84
- coffee_text = "__udf__ = {\n" << text << "}\n"
85
- javascript_text = CoffeeScript.compile(coffee_text, :bare => true)
86
- javascript_text << <<-JS
87
-
88
- __udf__.parameters || (__udf__.parameters = []);
89
- __udf__.returns || (__udf__.returns = "Scalar");
90
- var __scalarTypes__ = {"Numeric":true,"String":true,"Boolean":true,"DateTime":true,"Decimal":true,"Scalar":true};
91
- function __getType__(type) {
92
- if (__scalarTypes__[type]) {
93
- return new mondrian.olap.type[type+"Type"];
94
- } else if (type === "Member") {
95
- return mondrian.olap.type.MemberType.Unknown;
96
- } else {
97
- return null;
98
- }
99
- }
100
- function getParameterTypes() {
101
- var parameters = __udf__.parameters || [],
102
- types = [];
103
- for (var i = 0, len = parameters.length; i < len; i++) {
104
- types.push(__getType__(parameters[i]))
105
- }
106
- return types;
107
- }
108
- function getReturnType(parameterTypes) {
109
- var returns = __udf__.returns || "Scalar";
110
- return __getType__(returns);
111
- }
112
- if (__udf__.syntax) {
113
- function getSyntax() {
114
- return mondrian.olap.Syntax[__udf__.syntax];
115
- }
116
- }
117
- function execute(evaluator, args) {
118
- var parameters = __udf__.parameters || [],
119
- values = [],
120
- value;
121
- for (var i = 0, len = parameters.length; i < len; i++) {
122
- if (__scalarTypes__[parameters[i]]) {
123
- value = args[i].evaluateScalar(evaluator);
124
- } else {
125
- value = args[i].evaluate(evaluator);
126
- }
127
- values.push(value);
128
- }
129
- return __udf__.execute.apply(__udf__, values);
130
- }
131
- JS
132
- javascript javascript_text
133
- end
134
-
135
71
  class RubyUdfBase
136
72
  include Java::mondrian.spi.UserDefinedFunction
137
73
  def self.function_name=(name); @function_name = name; end
@@ -225,7 +161,7 @@ JS
225
161
  arguments_array_class = java.lang.Class.forName "[Lmondrian.spi.UserDefinedFunction$Argument;", true, class_loader
226
162
  add_method_signature("execute", [java.lang.Object, Java::mondrian.olap.Evaluator, arguments_array_class])
227
163
 
228
- # Override this metho if evaluator is needed
164
+ # Override this method if evaluator is needed
229
165
  def call_with_evaluator(evaluator, *values)
230
166
  call(*values)
231
167
  end
@@ -294,10 +230,6 @@ JS
294
230
  end
295
231
  end
296
232
 
297
- def coffeescript(text)
298
- coffeescript_function('(value)', text)
299
- end
300
-
301
233
  def ruby(*options, &block)
302
234
  ruby_formatter(options, Java::mondrian.spi.CellFormatter, 'formatCell', [java.lang.String, java.lang.Object], &block)
303
235
  end
@@ -308,12 +240,9 @@ JS
308
240
  attributes :class_name
309
241
  elements :script
310
242
 
311
- def coffeescript(text)
312
- coffeescript_function('(member)', text)
313
- end
314
-
315
243
  def ruby(*options, &block)
316
- ruby_formatter(options, Java::mondrian.spi.MemberFormatter, 'formatMember', [java.lang.String, Java::mondrian.olap.Member], &block)
244
+ ruby_formatter(options, Java::mondrian.spi.MemberFormatter, 'formatMember',
245
+ [java.lang.String, Java::mondrian.olap.Member], &block)
317
246
  end
318
247
  end
319
248
 
@@ -322,12 +251,9 @@ JS
322
251
  attributes :class_name
323
252
  elements :script
324
253
 
325
- def coffeescript(text)
326
- coffeescript_function('(member,propertyName,propertyValue)', text)
327
- end
328
-
329
254
  def ruby(*options, &block)
330
- ruby_formatter(options, Java::mondrian.spi.PropertyFormatter, 'formatProperty', [java.lang.String, Java::mondrian.olap.Member, java.lang.String, java.lang.Object], &block)
255
+ ruby_formatter(options, Java::mondrian.spi.PropertyFormatter, 'formatProperty',
256
+ [java.lang.String, Java::mondrian.olap.Member, java.lang.String, java.lang.Object], &block)
331
257
  end
332
258
  end
333
259
 
data/lib/mondrian/olap.rb CHANGED
@@ -1,18 +1,22 @@
1
1
  require 'java'
2
2
  require 'nokogiri'
3
3
 
4
+ {
5
+ # Do not register MondrianOlap4jDriver
6
+ "mondrian.olap4j.registerDriver" => false,
7
+ # Do not register log3j2 MBean
8
+ "log4j2.disable.jmx" => true
9
+ }.each do |key, value|
10
+ if java.lang.System.getProperty(key).nil?
11
+ java.lang.System.setProperty(key, value.to_s)
12
+ end
13
+ end
14
+
4
15
  directory = File.expand_path("../jars", __FILE__)
5
16
  Dir["#{directory}/*.jar"].each do |file|
6
17
  require file
7
18
  end
8
19
 
9
- unless java.lang.System.getProperty("log4j.configuration")
10
- file_uri = java.io.File.new("#{directory}/log4j.properties").toURI.to_s
11
- java.lang.System.setProperty("log4j.configuration", file_uri)
12
- end
13
- # register Mondrian olap4j driver
14
- Java::mondrian.olap4j.MondrianOlap4jDriver
15
-
16
20
  %w(error connection query result schema schema_udf cube).each do |file|
17
21
  require "mondrian/olap/#{file}"
18
22
  end
@@ -12,7 +12,7 @@ describe "Connection" do
12
12
  end
13
13
 
14
14
  it "should be successful" do
15
- @olap.connect.should be_true
15
+ @olap.connect.should == true
16
16
  end
17
17
 
18
18
  end
@@ -25,7 +25,7 @@ describe "Connection" do
25
25
  @olap = Mondrian::OLAP::Connection.new(CONNECTION_PARAMS.merge(
26
26
  :catalog_content => @schema_xml
27
27
  ))
28
- @olap.connect.should be_true
28
+ @olap.connect.should == true
29
29
  end
30
30
 
31
31
  end
@@ -49,10 +49,11 @@ describe "Connection" do
49
49
  when 'mysql' then 'mondrian.spi.impl.MySqlDialect'
50
50
  when 'postgresql' then 'mondrian.spi.impl.PostgreSqlDialect'
51
51
  when 'oracle' then 'mondrian.spi.impl.OracleDialect'
52
- when 'mssql' then 'mondrian.spi.impl.MicrosoftSqlServerDialect'
53
52
  when 'sqlserver' then 'mondrian.spi.impl.MicrosoftSqlServerDialect'
54
53
  when 'vertica' then 'mondrian.spi.impl.VerticaDialect'
55
54
  when 'snowflake' then 'mondrian.spi.impl.SnowflakeDialect'
55
+ when 'clickhouse' then 'mondrian.spi.impl.ClickHouseDialect'
56
+ when 'mariadb' then 'mondrian.spi.impl.MariaDBDialect'
56
57
  end
57
58
  end
58
59
 
@@ -89,4 +90,37 @@ describe "Connection" do
89
90
 
90
91
  end
91
92
 
93
+ describe "jdbc_uri" do
94
+ before(:all) { @olap_connection = Mondrian::OLAP::Connection }
95
+
96
+ describe "SQL Server" do
97
+ it "should return a valid JDBC URI" do
98
+ @olap_connection.new(
99
+ driver: 'sqlserver',
100
+ host: 'example.com',
101
+ port: 1234,
102
+ instance: 'MSSQLSERVER',
103
+ database: 'example_db'
104
+ ).jdbc_uri.should == 'jdbc:sqlserver://example.com:1234;databaseName=example_db;instanceName=MSSQLSERVER'
105
+ end
106
+
107
+ it "should return a valid JDBC URI with instance name as property" do
108
+ @olap_connection.new(
109
+ driver: 'sqlserver',
110
+ host: 'example.com',
111
+ properties: {
112
+ instanceName: "MSSQLSERVER"
113
+ }
114
+ ).jdbc_uri.should == 'jdbc:sqlserver://example.com;instanceName=MSSQLSERVER'
115
+ end
116
+
117
+ it "should return a valid JDBC URI with enabled integratedSecurity" do
118
+ @olap_connection.new(
119
+ driver: 'sqlserver',
120
+ host: 'example.com',
121
+ integrated_security: 'true'
122
+ ).jdbc_uri.should == 'jdbc:sqlserver://example.com;integratedSecurity=true'
123
+ end
124
+ end
125
+ end
92
126
  end
@@ -73,7 +73,7 @@ describe "Cube" do
73
73
  end
74
74
 
75
75
  # Do not execute tests on analytical databases with slow individual inserts
76
- describe 'cache', unless: %w(vertica snowflake).include?(MONDRIAN_DRIVER) do
76
+ describe 'cache', unless: %w(vertica snowflake clickhouse mariadb).include?(MONDRIAN_DRIVER) do
77
77
  def qt(name)
78
78
  @connection.quote_table_name(name.to_s)
79
79
  end
@@ -90,7 +90,7 @@ describe "Cube" do
90
90
  case MONDRIAN_DRIVER
91
91
  when 'mysql', 'jdbc_mysql', 'postgresql', 'oracle'
92
92
  @connection.execute 'CREATE TABLE sales_copy AS SELECT * FROM sales'
93
- when 'mssql', 'sqlserver'
93
+ when 'sqlserver'
94
94
  # Use raw_connection.execute to avoid detecting this query as a SELECT query
95
95
  # for which executeQuery JDBC method will fail
96
96
  @connection.raw_connection.execute_update 'SELECT * INTO sales_copy FROM sales'
data/spec/cube_spec.rb CHANGED
@@ -166,6 +166,31 @@ describe "Cube" do
166
166
 
167
167
  end
168
168
 
169
+ describe "cube hierarchies" do
170
+ before(:all) do
171
+ @cube = @olap.cube('Sales')
172
+ @hierarchy_names = %w(Measures Gender Customers Time Time.Weekly)
173
+ end
174
+
175
+ it "should get hierarchy names" do
176
+ @cube.hierarchy_names.should == @hierarchy_names
177
+ end
178
+
179
+ it "should get hierarchy by name" do
180
+ @cube.hierarchy('Gender').name.should == 'Gender'
181
+ end
182
+
183
+ it "should get hierarchy dimension name" do
184
+ hierarchy = @cube.hierarchy('Time.Weekly')
185
+ hierarchy.dimension.name.should == 'Time'
186
+ hierarchy.dimension_name.should == 'Time'
187
+ end
188
+
189
+ it "should return nil when getting dimension with invalid name" do
190
+ @cube.hierarchy('invalid').should be_nil
191
+ end
192
+ end
193
+
169
194
  describe "dimension hierarchies" do
170
195
  before(:all) do
171
196
  @cube = @olap.cube('Sales')
@@ -218,6 +243,15 @@ describe "Cube" do
218
243
  @cube.dimension('Gender').hierarchy.levels.map(&:members_count).should == [1, 2]
219
244
  end
220
245
 
246
+ it "should set and get hierarchy level cardinality" do
247
+ level = @cube.dimension('Gender').hierarchy.levels.last
248
+ level.cardinality.should == Java::JavaLang::Integer::MIN_VALUE
249
+ level.cardinality = 2
250
+ @olap.cube('Sales').dimension('Gender').hierarchy.levels.last.cardinality.should == 2
251
+ level.cardinality = nil
252
+ @olap.cube('Sales').dimension('Gender').hierarchy.levels.last.cardinality.should == Java::JavaLang::Integer::MIN_VALUE
253
+ end
254
+
221
255
  it "should get hierarchy annotations" do
222
256
  @cube.dimension('Customers').hierarchy.annotations.should == {'foo' => 'bar'}
223
257
  end
@@ -238,12 +272,12 @@ describe "Cube" do
238
272
  end
239
273
 
240
274
  it "should get hierarchy all member" do
241
- @cube.dimension('Gender').hierarchy.has_all?.should be_true
275
+ @cube.dimension('Gender').hierarchy.has_all?.should == true
242
276
  @cube.dimension('Gender').hierarchy.all_member_name.should == 'All Genders'
243
277
  end
244
278
 
245
279
  it "should not get all member for hierarchy without all member" do
246
- @cube.dimension('Time').hierarchy.has_all?.should be_false
280
+ @cube.dimension('Time').hierarchy.has_all?.should == false
247
281
  @cube.dimension('Time').hierarchy.all_member_name.should be_nil
248
282
  end
249
283
 
@@ -126,4 +126,44 @@ fullname
126
126
  </CalculatedMember>
127
127
  </Cube>
128
128
 
129
+ <Cube name="Warehouse">
130
+ <Table name="warehouse"/>
131
+ <DimensionUsage name="Time" source="Time" foreignKey="time_id"/>
132
+ <DimensionUsage name="Product" source="Product" foreignKey="product_id"/>
133
+ <Measure aggregator="sum" column="units_shipped" formatString="#,##0" name="Units Shipped"/>
134
+ <Measure aggregator="sum" column="store_invoice" formatString="#,##0.00" name="Store Invoice"/>
135
+ <Measure name="Products with units shipped" aggregator="distinct-count" formatString="#,###">
136
+ <MeasureExpression>
137
+ <SQL dialect="generic">
138
+ CASE WHEN units_shipped IS NOT NULL THEN product_id END
139
+ </SQL>
140
+ </MeasureExpression>
141
+ </Measure>
142
+ </Cube>
143
+
144
+ <VirtualCube name="Sales and Warehouse">
145
+ <VirtualCubeDimension name="Customers" cubeName="Sales"/>
146
+ <VirtualCubeDimension name="Gender" cubeName="Sales"/>
147
+ <VirtualCubeDimension name="Product"/>
148
+ <VirtualCubeDimension name="Time"/>
149
+ <VirtualCubeMeasure cubeName="Sales" name="[Measures].[Unit Sales]"/>
150
+ <VirtualCubeMeasure cubeName="Sales" name="[Measures].[Store Cost]"/>
151
+ <VirtualCubeMeasure cubeName="Sales" name="[Measures].[Store Sales]"/>
152
+ <VirtualCubeMeasure cubeName="Sales" name="[Measures].[Sales Count]"/>
153
+ <VirtualCubeMeasure cubeName="Sales" name="[Measures].[Customer Count]"/>
154
+ <VirtualCubeMeasure cubeName="Warehouse" name="[Measures].[Units Shipped]"/>
155
+ <VirtualCubeMeasure cubeName="Warehouse" name="[Measures].[Store Invoice]"/>
156
+ <VirtualCubeMeasure cubeName="Warehouse" name="[Measures].[Products with units shipped]"/>
157
+ </VirtualCube>
158
+
159
+ <Role name="Canada manager">
160
+ <SchemaGrant access="none">
161
+ <CubeGrant access="all" cube="Sales">
162
+ <HierarchyGrant access="custom" hierarchy="[Customers]">
163
+ <MemberGrant access="all" member="[Customers].[Canada]"/>
164
+ </HierarchyGrant>
165
+ </CubeGrant>
166
+ </SchemaGrant>
167
+ </Role>
168
+
129
169
  </Schema>
@@ -126,4 +126,44 @@ FULLNAME
126
126
  </CalculatedMember>
127
127
  </Cube>
128
128
 
129
+ <Cube name="Warehouse">
130
+ <Table name="WAREHOUSE"/>
131
+ <DimensionUsage name="Time" source="Time" foreignKey="TIME_ID"/>
132
+ <DimensionUsage name="Product" source="Product" foreignKey="PRODUCT_ID"/>
133
+ <Measure aggregator="sum" column="UNITS_SHIPPED" formatString="#,##0" name="Units Shipped"/>
134
+ <Measure aggregator="sum" column="STORE_INVOICE" formatString="#,##0.00" name="Store Invoice"/>
135
+ <Measure name="Products with units shipped" aggregator="distinct-count" formatString="#,###">
136
+ <MeasureExpression>
137
+ <SQL dialect="generic">
138
+ CASE WHEN UNITS_SHIPPED IS NOT NULL THEN PRODUCT_ID END
139
+ </SQL>
140
+ </MeasureExpression>
141
+ </Measure>
142
+ </Cube>
143
+
144
+ <VirtualCube name="Sales and Warehouse">
145
+ <VirtualCubeDimension name="Customers" cubeName="Sales"/>
146
+ <VirtualCubeDimension name="Gender" cubeName="Sales"/>
147
+ <VirtualCubeDimension name="Product"/>
148
+ <VirtualCubeDimension name="Time"/>
149
+ <VirtualCubeMeasure cubeName="Sales" name="[Measures].[Unit Sales]"/>
150
+ <VirtualCubeMeasure cubeName="Sales" name="[Measures].[Store Cost]"/>
151
+ <VirtualCubeMeasure cubeName="Sales" name="[Measures].[Store Sales]"/>
152
+ <VirtualCubeMeasure cubeName="Sales" name="[Measures].[Sales Count]"/>
153
+ <VirtualCubeMeasure cubeName="Sales" name="[Measures].[Customer Count]"/>
154
+ <VirtualCubeMeasure cubeName="Warehouse" name="[Measures].[Units Shipped]"/>
155
+ <VirtualCubeMeasure cubeName="Warehouse" name="[Measures].[Store Invoice]"/>
156
+ <VirtualCubeMeasure cubeName="Warehouse" name="[Measures].[Products with units shipped]"/>
157
+ </VirtualCube>
158
+
159
+ <Role name="Canada manager">
160
+ <SchemaGrant access="none">
161
+ <CubeGrant access="all" cube="Sales">
162
+ <HierarchyGrant access="custom" hierarchy="[Customers]">
163
+ <MemberGrant access="all" member="[Customers].[Canada]"/>
164
+ </HierarchyGrant>
165
+ </CubeGrant>
166
+ </SchemaGrant>
167
+ </Role>
168
+
129
169
  </Schema>
@@ -39,6 +39,7 @@ describe "Mondrian features" do
39
39
  level 'City', :column => 'city', :unique_members => false
40
40
  level 'Name', :column => 'fullname', :unique_members => true do
41
41
  property 'Related name', :column => 'related_fullname', :type => "String"
42
+ property 'Birthdate', :column => 'birthdate', :type => "String"
42
43
  end
43
44
  end
44
45
  hierarchy 'ID', :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
@@ -54,6 +55,9 @@ describe "Mondrian features" do
54
55
  level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true, :level_type => 'TimeYears'
55
56
  level 'Quarter', :column => 'quarter', :unique_members => false, :level_type => 'TimeQuarters'
56
57
  level 'Month', :column => 'month_of_year', :type => 'Numeric', :unique_members => false, :level_type => 'TimeMonths'
58
+ level 'Day', :column => 'day_of_month', :type => 'Numeric', :unique_members => false, :level_type => 'TimeDays' do
59
+ property 'Date', :column => 'the_date', :type => "String"
60
+ end
57
61
  end
58
62
  hierarchy 'Weekly', :has_all => false, :primary_key => 'id' do
59
63
  table 'time'
@@ -64,6 +68,17 @@ describe "Mondrian features" do
64
68
  measure 'Unit Sales', :column => 'unit_sales', :aggregator => 'sum'
65
69
  measure 'Store Sales', :column => 'store_sales', :aggregator => 'sum'
66
70
  end
71
+
72
+ user_defined_function 'IsDirty' do
73
+ ruby do
74
+ returns :scalar
75
+ syntax :function
76
+ def call_with_evaluator(evaluator)
77
+ evaluator.isDirty
78
+ end
79
+ end
80
+ end
81
+
67
82
  end
68
83
  @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
69
84
  end
@@ -120,4 +135,121 @@ describe "Mondrian features" do
120
135
  expect { @olap.execute mdx }.not_to raise_error
121
136
  end
122
137
 
138
+ # Test for https://jira.pentaho.com/browse/MONDRIAN-2714
139
+ it "should return datetime property as java.sql.Timestamp" do
140
+ full_name = '[2010].[Q1].[1].[1]'
141
+ member = @olap.cube('Sales').member(full_name)
142
+ member.property_value('Date').should be_a(java.sql.Timestamp)
143
+
144
+ result = @olap.from('Sales').
145
+ with_member('[Measures].[date]').as("#{full_name}.Properties('Date')", format_string: 'dd.mm.yyyy').
146
+ columns('[Measures].[date]').execute
147
+ result.values.first.should be_a(java.sql.Timestamp)
148
+ result.formatted_values.first.should == '01.01.2010'
149
+ end
150
+
151
+ it "should return date property as java.sql.Date" do
152
+ expected_date_class =
153
+ case MONDRIAN_DRIVER
154
+ when 'oracle'
155
+ java.sql.Timestamp
156
+ else
157
+ java.sql.Date
158
+ end
159
+
160
+ member = @olap.cube('Sales').hierarchy('Customers').level('Name').members.first
161
+ date_value = member.property_value('Birthdate')
162
+ date_value.should be_a(expected_date_class)
163
+
164
+ result = @olap.from('Sales').
165
+ with_member('[Measures].[date]').as("#{member.full_name}.Properties('Birthdate')", format_string: 'dd.mm.yyyy').
166
+ columns('[Measures].[date]').execute
167
+ result.values.first.should be_a(expected_date_class)
168
+ result.formatted_values.first.should == Date.parse(date_value.to_s).strftime("%d.%m.%Y")
169
+ end
170
+
171
+ describe "optimized Aggregate" do
172
+ def expected_value(crossjoin_members = nil)
173
+ query = @olap.from('Sales').columns('[Measures].[Unit Sales]')
174
+ query = query.crossjoin(crossjoin_members) if crossjoin_members
175
+ query.rows('[Customers].[USA].[CA]', '[Customers].[USA].[OR]').
176
+ execute.values.map(&:first).inject(&:+)
177
+ end
178
+
179
+ it "should aggregate stored members" do
180
+ result = @olap.from('Sales').
181
+ with_member('[Customers].[CA and OR]').as("Aggregate({[Customers].[USA].[CA], [Customers].[USA].[OR]})").
182
+ columns('[Measures].[Unit Sales]').
183
+ rows('[Customers].[CA and OR]').execute
184
+ result.values[0][0].should == expected_value
185
+ end
186
+
187
+ it "should aggregate stored members from several dimensions" do
188
+ result = @olap.from('Sales').
189
+ with_member('[Customers].[CA and OR]').
190
+ as("Aggregate({[Gender].[F]} * {[Customers].[USA].[CA], [Customers].[USA].[OR]})").
191
+ columns('[Measures].[Unit Sales]').
192
+ rows('[Customers].[CA and OR]').execute
193
+ result.values[0][0].should == expected_value('[Gender].[F]')
194
+ end
195
+
196
+ it "should aggregate stored members and a measure" do
197
+ result = @olap.from('Sales').
198
+ with_member('[Measures].[CA and OR]').
199
+ as("Aggregate({[Customers].[USA].[CA], [Customers].[USA].[OR]} * {[Measures].[Unit Sales]})").
200
+ columns('[Measures].[CA and OR]').execute
201
+ result.values[0].should == expected_value
202
+ end
203
+
204
+ it "should aggregate stored members with expression" do
205
+ result = @olap.from('Sales').
206
+ with_member('[Measures].[CA and OR twice]').
207
+ as("Aggregate({[Customers].[USA].[CA], [Customers].[USA].[OR]}, [Measures].[Unit Sales] * 2)").
208
+ columns('[Measures].[CA and OR twice]').execute
209
+ result.values[0].should == expected_value * 2
210
+ end
211
+
212
+ it "should aggregate calculated aggregate members" do
213
+ result = @olap.from('Sales').
214
+ with_member('[Customers].[CA calculated]').as("Aggregate({[Customers].[USA].[CA]})").
215
+ with_member('[Customers].[OR calculated]').as("Aggregate({[Customers].[USA].[OR]})").
216
+ with_member('[Customers].[CA and OR]').as("Aggregate({[Customers].[CA calculated], [Customers].[OR calculated]})").
217
+ columns('[Measures].[Unit Sales]').
218
+ rows('[Customers].[CA and OR]').execute
219
+ result.values[0][0].should == expected_value
220
+ end
221
+ end
222
+
223
+ it "should call evaluator isDirty method" do
224
+ result = @olap.from('Sales').
225
+ with_member('[Measures].[is dirty]').as('IsDirty()').
226
+ columns('[Measures].[is dirty]').execute
227
+ result.values[0].should == false
228
+ end
229
+
230
+ it "should support multiple values IN expression" do
231
+ lambda do
232
+ @olap.from('Sales').
233
+ columns('[Measures].[Unit Sales]').
234
+ where('[Time].[2011].[Q1]', '[Time].[2011].[Q2]').
235
+ execute
236
+ end.should_not raise_error
237
+ end
238
+
239
+ describe "functions with double argument" do
240
+ it "should get Abs with decimal measure" do
241
+ result = @olap.from('Sales').
242
+ with_member('[Measures].[Abs Store Sales]').as('Abs([Measures].[Store Sales])').
243
+ columns('[Measures].[Store Sales]', '[Measures].[Abs Store Sales]').execute
244
+ result.values[0].should == result.values[1]
245
+ end
246
+
247
+ it "should get Round with decimal measure" do
248
+ result = @olap.from('Sales').
249
+ with_member('[Measures].[Round Store Sales]').as('Round([Measures].[Store Sales])').
250
+ columns('[Measures].[Store Sales]', '[Measures].[Round Store Sales]').
251
+ where('[Customers].[USA].[CA]').execute
252
+ result.values[0].round.should == result.values[1]
253
+ end
254
+ end
123
255
  end