# encoding: utf-8
require "spec_helper"
describe "Schema definition" do
describe "elements" do
before(:each) do
@schema = Mondrian::OLAP::Schema.new
end
describe "root element" do
it "should render to XML" do
@schema.to_xml.should be_like <<-XML
XML
end
it "should render to XML with attributes" do
@schema.define('FoodMart') do
description 'Demo "FoodMart" schema āčē'
end
@schema.to_xml.should be_like <<-XML
XML
end
it "should render to XML using class method" do
schema = Mondrian::OLAP::Schema.define('FoodMart')
schema.to_xml.should be_like <<-XML
XML
end
end
describe "Cube" do
it "should render to XML" do
@schema.define do
cube 'Sales' do
default_measure 'Unit Sales'
description 'Sales cube'
cache false
enabled true
end
end
@schema.to_xml.should be_like <<-XML
XML
end
it "should render to XML using options hash" do
@schema.define do
cube 'Sales', :default_measure => 'Unit Sales',
:description => 'Sales cube', :cache => false, :enabled => true
end
@schema.to_xml.should be_like <<-XML
XML
end
end
describe "Table" do
it "should render to XML" do
@schema.define do
cube 'Sales' do
table 'sales_fact', :alias => 'sales'
end
end
@schema.to_xml.should be_like <<-XML
XML
end
it "should render table name in uppercase when using Oracle or LucidDB driver" do
@schema.define do
cube 'Sales' do
table 'sales_fact', :alias => 'sales', :schema => 'facts'
end
end
%w(oracle luciddb).each do |driver|
@schema.to_xml(:driver => driver).should be_like <<-XML
XML
end
end
it "should render table name in uppercase when :upcase_data_dictionary option is set to true" do
@schema.define :upcase_data_dictionary => true do
cube 'Sales' do
table 'sales_fact', :alias => 'sales', :schema => 'facts'
end
end
@schema.to_xml.should be_like <<-XML
XML
end
it "should render table name in lowercase when using Oracle or LucidDB driver but with :upcase_data_dictionary set to false" do
@schema.define :upcase_data_dictionary => false do
cube 'Sales' do
table 'sales_fact', :alias => 'sales', :schema => 'facts'
end
end
%w(oracle luciddb).each do |driver|
@schema.to_xml(:driver => driver).should be_like <<-XML
XML
end
end
it "should render table with where condition" do
@schema.define do
cube 'Sales' do
table 'sales_fact', :alias => 'sales' do
sql 'customer_id IS NOT NULL'
end
end
end
@schema.to_xml.should be_like <<-XML
XML
end
end
describe "View" do
it "should render to XML" do
@schema.define do
cube 'Sales' do
view :alias => 'sales' do
sql 'select * from sales_fact'
end
end
end
@schema.to_xml.should be_like <<-XML
select * from sales_fact
XML
end
end
describe "Dimension" do
it "should render to XML" do
@schema.define do
cube 'Sales' do
dimension 'Gender' do
foreign_key 'customer_id'
hierarchy do
has_all true
all_member_name 'All Genders'
primary_key 'customer_id'
table 'customer'
level 'Gender', :column => 'gender', :unique_members => true
end
end
end
end
@schema.to_xml.should be_like <<-XML
XML
end
it "should render time dimension" do
@schema.define do
cube 'Sales' do
dimension 'Time' do
foreign_key 'time_id'
hierarchy do
has_all false
primary_key 'time_id'
table 'time_by_day'
level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true
level 'Quarter', :column => 'quarter', :unique_members => false
level 'Month', :column => 'month_of_year', :type => 'Numeric', :unique_members => false
end
end
end
end
@schema.to_xml.should be_like <<-XML
XML
end
it "should render dimension with hierarchy and level defaults" do
@schema.define do
cube 'Sales' do
dimension 'Time' do
foreign_key 'time_id'
hierarchy do
all_member_name 'All Times' # should add :has_all => true
primary_key 'time_id'
table 'time_by_day'
level 'Year', :column => 'the_year', :type => 'Numeric' # first level should have default :unique_members => true
level 'Quarter', :column => 'quarter' # next levels should have default :unique_members => false
level 'Month', :column => 'month_of_year', :type => 'Numeric'
end
end
end
end
@schema.to_xml.should be_like <<-XML
XML
end
it "should render dimension hierarchy with join" do
@schema.define do
cube 'Sales' do
dimension 'Products', :foreign_key => 'product_id' do
hierarchy :has_all => true, :all_member_name => 'All Products',
:primary_key => 'product_id', :primary_key_table => 'product' do
join :left_key => 'product_class_id', :right_key => 'product_class_id' do
table 'product'
table 'product_class'
end
level 'Product Family', :table => 'product_class', :column => 'product_family', :unique_members => true
level 'Brand Name', :table => 'product', :column => 'brand_name', :unique_members => false
level 'Product Name', :table => 'product', :column => 'product_name', :unique_members => true
end
end
end
end
@schema.to_xml.should be_like <<-XML
XML
end
it "should render table and column names in uppercase when using Oracle driver" do
@schema.define do
cube 'Sales' do
dimension 'Products', :foreign_key => 'product_id' do
hierarchy :has_all => true, :all_member_name => 'All Products',
:primary_key => 'product_id', :primary_key_table => 'product' do
join :left_key => 'product_class_id', :right_key => 'product_class_id' do
table 'product'
table 'product_class'
end
level 'Product Family', :table => 'product_class', :column => 'product_family', :unique_members => true
level 'Brand Name', :table => 'product', :column => 'brand_name', :unique_members => false
level 'Product Name', :table => 'product', :column => 'product_name', :unique_members => true
end
end
end
end
@schema.to_xml(:driver => 'oracle').should be_like <<-XML
XML
end
it "should render dimension hierarchy with nested joins" do
@schema.define do
cube 'Sales' do
dimension 'Products', :foreign_key => 'product_id' do
hierarchy :has_all => true, :all_member_name => 'All Products',
:primary_key => 'product_id', :primary_key_table => 'product' do
join :left_key => 'product_class_id', :right_alias => 'product_class', :right_key => 'product_class_id' do
table 'product'
join :left_key => 'product_type_id', :right_key => 'product_type_id' do
table 'product_class'
table 'product_type'
end
end
level 'Product Family', :table => 'product_type', :column => 'product_family', :unique_members => true
level 'Product Category', :table => 'product_class', :column => 'product_category', :unique_members => false
level 'Brand Name', :table => 'product', :column => 'brand_name', :unique_members => false
level 'Product Name', :table => 'product', :column => 'product_name', :unique_members => true
end
end
end
end
@schema.to_xml.should be_like <<-XML
XML
end
end
describe "Shared dimension" do
it "should render to XML" do
@schema.define do
dimension 'Gender' do
hierarchy do
has_all true
all_member_name 'All Genders'
primary_key 'customer_id'
table 'customer'
level 'Gender', :column => 'gender', :unique_members => true
end
end
cube 'Sales' do
dimension_usage 'Gender', :foreign_key => 'customer_id' # by default :source => 'Gender' will be added
end
end
@schema.to_xml.should be_like <<-XML
XML
end
end
describe "Virtual cube" do
it "should render to XML" do
@schema.define do
virtual_cube 'Warehouse and Sales', :default_measure => 'Store Sales' do
virtual_cube_dimension 'Customers', :cube_name => 'Sales'
virtual_cube_dimension 'Product'
virtual_cube_measure '[Measures].[Store Sales]', :cube_name => 'Sales'
calculated_member 'Profit' do
dimension 'Measures'
formula '[Measures].[Store Sales] - [Measures].[Store Cost]'
end
end
end
@schema.to_xml.should be_like <<-XML
[Measures].[Store Sales] - [Measures].[Store Cost]
XML
end
end
describe "Measure" do
it "should render XML" do
@schema.define do
cube 'Sales' do
measure 'Unit Sales' do
column 'unit_sales'
aggregator 'sum'
end
measure 'Store Sales', :column => 'store_sales' # by default should use sum aggregator
end
end
@schema.to_xml.should be_like <<-XML
XML
end
it "should render column name in uppercase when using Oracle driver" do
@schema.define do
cube 'Sales' do
measure 'Unit Sales' do
column 'unit_sales'
aggregator 'sum'
end
end
end
@schema.to_xml(:driver => 'oracle').should be_like <<-XML
XML
end
it "should render with measure expression" do
@schema.define do
cube 'Sales' do
measure 'Double Unit Sales', :aggregator => 'sum' do
measure_expression do
sql 'unit_sales * 2'
end
end
end
end
@schema.to_xml.should be_like <<-XML
unit_sales * 2
XML
end
end
describe "Calculated Member" do
it "should render XML" do
@schema.define do
cube 'Sales' do
calculated_member 'Profit' do
dimension 'Measures'
formula '[Measures].[Store Sales] - [Measures].[Store Cost]'
format_string '#,##0.00'
end
end
end
@schema.to_xml.should be_like <<-XML
[Measures].[Store Sales] - [Measures].[Store Cost]
XML
end
it "should render XML with dimension and hierarchy" do
@schema.define do
cube 'Sales' do
calculated_member 'Current week' do
hierarchy '[Time.Weekly]'
formula '[Time.Weekly].[Week].CurrentDateMember'
end
end
end
@schema.to_xml.should be_like <<-XML
[Time.Weekly].[Week].CurrentDateMember
XML
end
it "should render embedded cube XML defintion before additional calculated member to XML" do
@schema.define do
cube 'Sales' do
xml <<-XML
XML
calculated_member 'Profit' do
dimension 'Measures'
formula '[Measures].[Store Sales] - [Measures].[Store Cost]'
format_string '#,##0.00'
end
end
end
@schema.to_xml.should be_like <<-XML
[Measures].[Store Sales] - [Measures].[Store Cost]
XML
end
end
describe "Aggregates" do
it "should render named aggregate to XML" do
@schema.define do
cube 'Sales' do
table 'sales_fact_1997' do
agg_name 'agg_c_special_sales_fact_1997' do
agg_fact_count :column => 'fact_count'
agg_measure '[Measures].[Store Cost]', :column => 'store_cost_sum'
agg_measure '[Measures].[Store Sales]', :column => 'store_sales_sum'
agg_level '[Product].[Product Family]', :column => 'product_family'
agg_level '[Time].[Quarter]', :column => 'time_quarter'
agg_level '[Time].[Year]', :column => 'time_year'
agg_level '[Time].[Quarter]', :column => 'time_quarter'
agg_level '[Time].[Month]', :column => 'time_month'
end
end
end
end
@schema.to_xml.should be_like <<-XML
XML
end
it "should render aggregate pattern to XML" do
@schema.define do
cube 'Sales' do
table 'sales_fact_1997' do
agg_pattern :pattern => 'agg_.*_sales_fact_1997' do
agg_fact_count :column => 'fact_count'
agg_measure '[Measures].[Store Cost]', :column => 'store_cost_sum'
agg_measure '[Measures].[Store Sales]', :column => 'store_sales_sum'
agg_level '[Product].[Product Family]', :column => 'product_family'
agg_level '[Time].[Quarter]', :column => 'time_quarter'
agg_level '[Time].[Year]', :column => 'time_year'
agg_level '[Time].[Quarter]', :column => 'time_quarter'
agg_level '[Time].[Month]', :column => 'time_month'
agg_exclude 'agg_c_14_sales_fact_1997'
agg_exclude 'agg_lc_100_sales_fact_1997'
end
end
end
end
@schema.to_xml.should be_like <<-XML
XML
end
it "should render embedded aggregate XML defintion to XML" do
@schema.define do
cube 'Sales' do
table 'sales_fact_1997' do
xml <<-XML
XML
end
end
end
@schema.to_xml.should be_like <<-XML
XML
end
end
describe "Member properties" do
it "should render XML" do
@schema.define do
cube 'Sales' do
dimension 'Employees', :foreign_key => 'employee_id' do
hierarchy :has_all => true, :all_member_name => 'All Employees', :primary_key => 'employee_id' do
table 'employee'
level 'Employee Id', :unique_members => true, :type => 'Numeric', :column => 'employee_id', :name_column => 'full_name',
:parent_column => 'supervisor_id', :null_parent_value => 0 do
property 'Marital Status', :column => 'marital_status'
property 'Position Title', :column => 'position_title'
property 'Gender', :column => 'gender'
property 'Salary', :column => 'salary'
property 'Education Level', :column => 'education_level'
end
end
end
end
end
@schema.to_xml.should be_like <<-XML
XML
end
end
describe "Element annotations" do
describe "block of elements" do
before do
@schema.define do
cube 'Sales' do
annotations do
annotation 'key1', 'value1'
annotation 'key2', 'value2'
end
measure 'Unit Sales', :column => 'unit_sales' do
annotations do
annotation 'key3', 'value3'
end
end
end
end
end
it "should render XML" do
@schema.to_xml.should be_like <<-XML
value1
value2
value3
XML
end
it "should access annotations from schema definition" do
@schema.cubes.first.annotations_hash.should == {'key1' => 'value1', 'key2' => 'value2'}
end
end
describe "from hash options" do
before do
@schema.define do
cube 'Sales' do
annotations :key1 => 'value1', :key2 => 'value2'
measure 'Unit Sales', :column => 'unit_sales', :annotations => {:key3 => 'value3'}
end
end
end
it "should render XML " do
@schema.to_xml.should be_like <<-XML
value1
value2
value3
XML
end
it "should access annotations from schema definition" do
@schema.cubes.first.annotations_hash.should == {'key1' => 'value1', 'key2' => 'value2'}
@schema.cubes.first.measures.first.annotations_hash.should == {'key3' => 'value3'}
end
end
end
describe "User defined functions and formatters in JavaScript" do
next pending "not supported by Mondrian in Java 8" if ENV_JAVA["java.version"] >= "1.8"
before(:each) do
@schema.define do
cube 'Sales' do
table 'sales'
dimension 'Customers', :foreign_key => 'customer_id' do
hierarchy :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
table 'customers'
level 'Name', :column => 'fullname' do
member_formatter { javascript "return member.getName().toUpperCase();" }
property 'City', :column => 'city' do
property_formatter { javascript "return propertyValue.toUpperCase();" }
end
end
end
end
calculated_member 'Factorial' do
dimension 'Measures'
formula 'Factorial(6)'
cell_formatter do
javascript <<-JS
var s = value.toString();
while (s.length < 20) {
s = "0" + s;
}
return s;
JS
end
end
calculated_member 'City' do
dimension 'Measures'
formula "[Customers].CurrentMember.Properties('City')"
end
end
user_defined_function 'Factorial' do
javascript <<-JS
function getParameterTypes() {
return new Array(
new mondrian.olap.type.NumericType());
}
function getReturnType(parameterTypes) {
return new mondrian.olap.type.NumericType();
}
function execute(evaluator, arguments) {
var n = arguments[0].evaluateScalar(evaluator);
return factorial(n);
}
function factorial(n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
JS
end
end
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
end
it "should render XML" do
@schema.to_xml.should be_like <<-XML
Factorial(6)
[Customers].CurrentMember.Properties('City')
XML
end
it "should execute user defined function" do
result = @olap.from('Sales').columns('[Measures].[Factorial]').execute
value = 1*2*3*4*5*6
result.values.should == [value]
result.formatted_values.should == ["%020d" % value]
end
it "should format members and properties" do
result = @olap.from('Sales').columns('[Measures].[City]').rows('[Customers].[All Customers].Children').execute
result.row_members.each_with_index do |member, i|
member.caption.should == member.name.upcase
city = member.property_value('City')
result.formatted_values[i].first.should == city
member.property_formatted_value('City').should == city.upcase
end
end
end
describe "User defined functions and formatters in CoffeeScript" do
next pending "not supported by Mondrian in Java 8" if ENV_JAVA["java.version"] >= "1.8"
before(:each) do
@schema.define do
cube 'Sales' do
table 'sales'
dimension 'Customers', :foreign_key => 'customer_id' do
hierarchy :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
table 'customers'
level 'Name', :column => 'fullname' do
member_formatter { coffeescript "member.getName().toUpperCase()" }
property 'City', :column => 'city' do
property_formatter { coffeescript "propertyValue.toUpperCase()" }
end
end
end
end
calculated_member 'Factorial' do
dimension 'Measures'
formula 'Factorial(6)'
cell_formatter do
coffeescript <<-CS
s = value.toString()
s = "0" + s while s.length < 20
s
CS
end
end
calculated_member 'City' do
dimension 'Measures'
formula "[Customers].CurrentMember.Properties('City')"
end
end
user_defined_function 'Factorial' do
coffeescript <<-CS
parameters: ["Numeric"]
returns: "Numeric"
execute: (n) ->
if n <= 1 then 1 else n * @execute(n - 1)
CS
end
user_defined_function 'UpperName' do
coffeescript <<-CS
parameters: ["Member"]
returns: "String"
syntax: "Property"
execute: (member) ->
member.getName().toUpperCase()
CS
end
user_defined_function 'toUpperName' do
coffeescript <<-CS
parameters: ["Member", "String"]
returns: "String"
syntax: "Method"
execute: (member, dummy) ->
member.getName().toUpperCase()
CS
end
end
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
end
it "should execute user defined function" do
result = @olap.from('Sales').columns('[Measures].[Factorial]').execute
value = 1*2*3*4*5*6
result.values.should == [value]
result.formatted_values.should == ["%020d" % value]
end
it "should format members and properties" do
result = @olap.from('Sales').columns('[Measures].[City]').rows('[Customers].[All Customers].Children').execute
result.row_members.each_with_index do |member, i|
member.caption.should == member.name.upcase
city = member.property_value('City')
result.formatted_values[i].first.should == city
member.property_formatted_value('City').should == city.upcase
end
end
it "should execute user defined property on member" do
result = @olap.from('Sales').
with_member('[Measures].[Upper Name]').as('[Customers].CurrentMember.UpperName').
columns('[Measures].[Upper Name]').rows('[Customers].Children').execute
result.row_members.each_with_index do |member, i|
result.values[i].should == [member.name.upcase]
end
end
it "should execute user defined method on member" do
result = @olap.from('Sales').
with_member('[Measures].[Upper Name]').as("[Customers].CurrentMember.toUpperName('dummy')").
columns('[Measures].[Upper Name]').rows('[Customers].Children').execute
result.row_members.each_with_index do |member, i|
result.values[i].should == [member.name.upcase]
end
end
end
describe "User defined functions and formatters in Ruby" do
before(:each) do
@schema.define do
cube 'Sales' do
table 'sales'
dimension 'Time', foreign_key: 'time_id' do
hierarchy all_member_name: 'All Time', primary_key: 'time_id' do
table 'time_by_day'
level 'Year', column: 'the_year', type: 'Numeric'
level 'Quarter', column: 'quarter'
level 'Month', column: 'month_of_year', type: 'Numeric'
level 'Day', column: 'day_of_month', type: 'Numeric'
end
end
dimension 'Customers', foreign_key: 'customer_id' do
hierarchy all_member_name: 'All Customers', primary_key: 'id' do
table 'customers'
level 'Name', column: 'fullname' do
member_formatter { ruby {|member| member.getName().upcase } }
property 'City', column: 'city' do
property_formatter { ruby {|member, property_name, property_value| property_value.upcase} }
end
end
end
end
calculated_member 'Factorial' do
dimension 'Measures'
formula 'Factorial(6)'
cell_formatter { ruby {|value| "%020d" % value} }
end
calculated_member 'City' do
dimension 'Measures'
formula "[Customers].CurrentMember.Properties('City')"
end
end
user_defined_function 'Factorial' do
ruby do
parameters :numeric
returns :numeric
def call(n)
n <= 1 ? 1 : n * call(n - 1)
end
end
end
user_defined_function 'UpperName' do
ruby do
parameters :member
returns :string
syntax :property
def call(member)
member.getName.upcase
end
end
end
user_defined_function 'toUpperName' do
ruby do
parameters :member, :string
returns :string
syntax :method
def call(member, dummy)
member.getName.upcase
end
end
end
user_defined_function 'firstUpperName' do
ruby do
parameters :set
returns :string
syntax :property
def call(set)
set.first.getName.upcase
end
end
end
user_defined_function 'firstToUpperName' do
ruby do
parameters :set, :string
returns :string
syntax :method
def call(set, dummy)
set.first.getName.upcase
end
end
end
user_defined_function 'firstChildUpperName' do
ruby do
parameters :hierarchy
returns :string
syntax :property
def call_with_evaluator(evaluator, hierarchy)
evaluator.getSchemaReader.getMemberChildren(hierarchy.getDefaultMember).first.getName.upcase
end
end
end
user_defined_function 'firstLevelChildUpperName' do
ruby do
parameters :level
returns :string
syntax :property
def call_with_evaluator(evaluator, level)
evaluator.getSchemaReader.getLevelMembers(level, false).first.getName.upcase
end
end
end
user_defined_function 'ChildrenSet' do
ruby do
parameters :member
returns :member_set
syntax :function
def call_with_evaluator(evaluator, member)
evaluator.getSchemaReader.getMemberChildren(member)
end
end
end
user_defined_function 'SetFirstTuple' do
ruby do
parameters :set
returns :tuple
syntax :function
def call_with_evaluator(evaluator, set)
set.first
end
end
end
user_defined_function 'SetFirstTuples' do
ruby do
parameters :tuple_set, :numeric
returns :tuple_set
syntax :function
def call_with_evaluator(evaluator, set, count)
set.to_a[0, count]
end
end
end
end
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge schema: @schema)
end
it "should execute user defined function" do
result = @olap.from('Sales').columns('[Measures].[Factorial]').execute
value = 1*2*3*4*5*6
result.values.should == [value]
result.formatted_values.should == ["%020d" % value]
end
it "should format members and properties" do
result = @olap.from('Sales').columns('[Measures].[City]').rows('[Customers].[All Customers].Children').execute
result.row_members.each_with_index do |member, i|
member.caption.should == member.name.upcase
city = member.property_value('City')
result.formatted_values[i].first.should == city
member.property_formatted_value('City').should == city.upcase
end
end
it "should execute user defined property on member" do
result = @olap.from('Sales').
with_member('[Measures].[Upper Name]').as('[Customers].CurrentMember.UpperName').
columns('[Measures].[Upper Name]').rows('[Customers].Children').execute
result.row_members.each_with_index do |member, i|
result.values[i].should == [member.name.upcase]
end
end
it "should execute user defined method on member" do
result = @olap.from('Sales').
with_member('[Measures].[Upper Name]').as("[Customers].CurrentMember.toUpperName('dummy')").
columns('[Measures].[Upper Name]').rows('[Customers].Children').execute
result.row_members.each_with_index do |member, i|
result.values[i].should == [member.name.upcase]
end
end
it "should execute user defined property on set" do
result = @olap.from('Sales').
with_member('[Measures].[Upper Name]').as("{[Customers].CurrentMember}.firstUpperName").
columns('[Measures].[Upper Name]').rows('[Customers].Children').execute
result.row_members.each_with_index do |member, i|
result.values[i].should == [member.name.upcase]
end
end
it "should execute user defined method on set" do
result = @olap.from('Sales').
with_member('[Measures].[Upper Name]').as("{[Customers].CurrentMember}.firstToUpperName('dummy')").
columns('[Measures].[Upper Name]').rows('[Customers].Children').execute
result.row_members.each_with_index do |member, i|
result.values[i].should == [member.name.upcase]
end
end
it "should execute user defined property on hierarchy" do
result = @olap.from('Sales').
with_member('[Measures].[Upper Name]').as("[Customers].firstChildUpperName").
columns('[Measures].[Upper Name]').rows('[Customers].Children').execute
first_member = result.row_members.first
result.row_members.each_with_index do |member, i|
result.values[i].should == [first_member.name.upcase]
end
end
it "should execute user defined property on level" do
result = @olap.from('Sales').
with_member('[Measures].[Upper Name]').as("[Customers].[Name].firstLevelChildUpperName").
columns('[Measures].[Upper Name]').rows('[Customers].Children').execute
first_member = result.row_members.first
result.row_members.each_with_index do |member, i|
result.values[i].should == [first_member.name.upcase]
end
end
it "should execute user defined functions with a member set result" do
result = @olap.from('Sales').
with_member('[Measures].[Children Set]').as("SetToStr(ChildrenSet([Customers].CurrentMember))").
columns('[Measures].[Children Set]').rows('[Customers].DefaultMember').execute
result.row_members.each_with_index do |member, i|
result.values[i].first.should == "{#{member.children.map(&:full_name).join(', ')}}"
end
end
it "should execute user defined functions with a tuple result" do
result = @olap.from('Sales').
with_member('[Measures].[First Tuple]').
as("TupleToStr(SetFirstTuple([Customers].CurrentMember.Children * [Time].DefaultMember))").
columns('[Measures].[First Tuple]').rows('[Customers].DefaultMember').execute
result.row_members.each_with_index do |member, i|
result.values[i].first.should == "(#{member.children.first.full_name}, [Time].[All Time])"
end
end
it "should execute user defined functions with a tuple result" do
result = @olap.from('Sales').
with_member('[Measures].[First Tuple]').
as("SetToStr(SetFirstTuples([Customers].CurrentMember.Children * [Time].DefaultMember, 1))").
columns('[Measures].[First Tuple]').rows('[Customers].DefaultMember').execute
result.row_members.each_with_index do |member, i|
result.values[i].first.should == "{(#{member.children.first.full_name}, [Time].[All Time])}"
end
end
end
describe "Shared user defined functions in Ruby" do
before(:each) do
shared_schema = Mondrian::OLAP::Schema.define do
user_defined_function 'Factorial' do
ruby :shared do
parameters :numeric
returns :numeric
def call(n)
n <= 1 ? 1 : n * call(n - 1)
end
end
end
user_defined_function 'UpperName' do
ruby :shared do
parameters :member
returns :string
syntax :property
def call(member)
member.getName.upcase
end
end
end
user_defined_function 'toUpperName' do
ruby :shared do
parameters :member, :string
returns :string
syntax :method
def call(member, dummy)
member.getName.upcase
end
end
end
user_defined_cell_formatter 'Integer20Digits' do
ruby :shared do |value|
"%020d" % value
end
end
end
@schema.define do
include_schema shared_schema
cube 'Sales' do
table 'sales'
dimension 'Customers', :foreign_key => 'customer_id' do
hierarchy :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
table 'customers'
level 'Name', :column => 'fullname'
end
end
measure 'Unit Sales', :column => 'unit_sales', :aggregator => 'sum', :format_string => '#,##0'
calculated_member 'Factorial' do
dimension 'Measures'
formula 'Factorial(6)'
cell_formatter 'Integer20Digits'
end
end
end
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
end
it "should render XML" do
@schema.to_xml.should be_like <<-XML
Factorial(6)
XML
end
it "should execute user defined function" do
result = @olap.from('Sales').columns('[Measures].[Factorial]').execute
value = 1*2*3*4*5*6
result.values.should == [value]
result.formatted_values.should == ["%020d" % value]
end
it "should get measure cell formatter name" do
@olap.cube('Sales').member('[Measures].[Factorial]').cell_formatter_name.should == 'Integer20Digits'
end
it "should not get measure cell formatter name if not specified" do
@olap.cube('Sales').member('[Measures].[Unit Sales]').cell_formatter_name.should be_nil
end
it "should get measure cell formatter" do
@olap.cube('Sales').member('[Measures].[Factorial]').cell_formatter.class.name.should ==
'Mondrian::OLAP::Schema::CellFormatter::Integer20DigitsUdf'
end
it "should not get measure cell formatter if not specified" do
@olap.cube('Sales').member('[Measures].[Unit Sales]').cell_formatter.should be_nil
end
it "should get measure format string" do
@olap.cube('Sales').member('[Measures].[Unit Sales]').format_string.should == '#,##0'
end
it "should not get measure format string if not specified" do
@olap.cube('Sales').member('[Measures].[Factorial]').format_string.should be_nil
end
end
describe "Roles" do
it "should render XML" do
@schema.define do
role 'California manager' do
schema_grant :access => 'none' do
cube_grant :cube => 'Sales', :access => 'all' do
dimension_grant :dimension => '[Measures]', :access => 'all'
hierarchy_grant :hierarchy => '[Customers]', :access => 'custom',
:top_level => '[Customers].[State Province]', :bottom_level => '[Customers].[City]' do
member_grant :member => '[Customers].[USA].[CA]', :access => 'all'
member_grant :member => '[Customers].[USA].[CA].[Los Angeles]', :access => 'none'
end
end
end
end
end
@schema.to_xml.should be_like <<-XML
XML
end
end
describe "Parameters" do
before(:each) do
@schema.define do
parameter 'Current User', :type => 'String', :modifiable => true, :default_value => "'demo'"
parameter 'Current User 1', :type => 'String', :modifiable => true, :default_value => "''"
parameter 'Default User', :type => 'String', :modifiable => false, :default_value => "'default'"
cube 'Sales' do
table 'sales'
measure 'Unit Sales', :column => 'unit_sales', :aggregator => 'sum'
end
user_defined_function 'ParameterValue' do
ruby :shared do
parameters :string
returns :scalar
syntax :function
def call_with_evaluator(evaluator, parameter_name)
parameter = evaluator.getQuery.getSchemaReader(false).getParameter(parameter_name)
parameter && parameter.getValue
end
end
end
end
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
end
it "should render XML" do
@schema.to_xml.should be_like <<-XML
XML
end
it "should get parameter definition from connection" do
parameter = @olap.mondrian_parameter('Current User')
parameter.should_not be_nil
parameter.name.should == 'Current User'
parameter.description.should be_nil
parameter.should be_modifiable
parameter.scope.to_s.should == 'Schema'
parameter.type.to_s.should == 'STRING'
end
it "should not get parameter definition with invalid name" do
@olap.mondrian_parameter('Current User 2').should be_nil
end
it "should get default parameter value with ParamRef" do
result = @olap.from('Sales').
with_member('[Measures].[Current User]').as("ParamRef('Current User')").
columns('[Measures].[Current User]').execute
result.values.should == ['demo']
end
it "should execute query with schema parameter value and get value with ParamRef" do
result = @olap.from('Sales').
with_member('[Measures].[Current User]').as("ParamRef('Current User 1')").
columns('[Measures].[Current User]').execute("Current User 1" => "test")
result.values.should == ['test']
end
it "should execute query with query parameter value and get value with Parameter" do
result = @olap.from('Sales').
with_member('[Measures].[Current User]').as("Parameter('Current User 2', String, 'demo2')").
columns('[Measures].[Current User]').execute("Current User 2" => "test2")
result.values.should == ['test2']
end
# can be used in user defined functions
it "should execute query with additional defined parameter string value" do
result = @olap.from('Sales').
with_member('[Measures].[Parameter]').as("ParameterValue('String Parameter')").
columns('[Measures].[Parameter]').execute(:define_parameters => {"String Parameter" => "test"})
result.values.should == ['test']
end
it "should execute query with additional defined parameter integer value" do
result = @olap.from('Sales').
with_member('[Measures].[Parameter]').as("ParameterValue('Integer Parameter')").
columns('[Measures].[Parameter]').execute(:define_parameters => {"Integer Parameter" => 123})
result.values.should == [123]
end
it "should execute query with additional defined parameter double value" do
result = @olap.from('Sales').
with_member('[Measures].[Parameter]').as("ParameterValue('Double Parameter')").
columns('[Measures].[Parameter]').execute(:define_parameters => {"Double Parameter" => 123.456})
result.values.should == [123.456]
end
it "should execute query with additional defined parameter nil value" do
result = @olap.from('Sales').
with_member('[Measures].[Parameter]').as("ParameterValue('Nil Parameter')").
columns('[Measures].[Parameter]').execute(:define_parameters => {"Nil Parameter" => nil})
result.values.should == [nil]
end
it "should fail if executing with invalid parameter name" do
expect {
@olap.from('Sales').
with_member('[Measures].[Current User]').as("'dummy'").
columns('[Measures].[Current User]').execute("Current User 2" => "test2")
}.to raise_error {|e|
e.should be_kind_of(Mondrian::OLAP::Error)
e.message.should == "mondrian.olap.MondrianException: Mondrian Error:Unknown parameter 'Current User 2'"
e.root_cause_message.should == "Unknown parameter 'Current User 2'"
}
end
it "should fail if executing with non-modifiable parameter" do
expect {
@olap.from('Sales').
with_member('[Measures].[Current User]').as("'dummy'").
columns('[Measures].[Current User]').execute("Default User" => "test")
}.to raise_error {|e|
e.should be_kind_of(Mondrian::OLAP::Error)
e.message.should == "mondrian.olap.MondrianException: Mondrian Error:Parameter 'Default User' (defined at 'Schema' scope) is not modifiable"
e.root_cause_message.should == "Parameter 'Default User' (defined at 'Schema' scope) is not modifiable"
}
end
end
end
describe "connection with schema" do
before(:all) do
@schema = Mondrian::OLAP::Schema.define do
cube 'Sales' do
table 'sales'
dimension 'Gender', :foreign_key => 'customer_id' do
hierarchy :has_all => true, :primary_key => 'id' do
table 'customers'
level 'Gender', :column => 'gender', :unique_members => true
end
end
dimension 'Time', :foreign_key => 'time_id' do
hierarchy :has_all => false, :primary_key => 'id' do
table 'time'
level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true
level 'Quarter', :column => 'quarter', :unique_members => false
level 'Month', :column => 'month_of_year', :type => 'Numeric', :unique_members => false
end
end
measure 'Unit Sales', :column => 'unit_sales', :aggregator => 'sum'
measure 'Store Sales', :column => 'store_sales', :aggregator => 'sum'
end
end
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
end
it "should connect" do
@olap.should be_connected
end
it "should execute query" do
@olap.from('Sales').
columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
rows('descendants([Time].[2010].[Q1])').
where('[Gender].[F]').
execute.should be_a(Mondrian::OLAP::Result)
end
end
describe "errors" do
before(:each) do
@schema = Mondrian::OLAP::Schema.new
end
it "should raise error when invalid schema" do
@schema.define do
cube 'Sales' do
end
end
expect {
Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
}.to raise_error {|e|
e.should be_kind_of(Mondrian::OLAP::Error)
e.message.should == "mondrian.olap.MondrianException: Mondrian Error:Internal error: Must specify fact table of cube 'Sales'"
e.root_cause_message.should == "Internal error: Must specify fact table of cube 'Sales'"
}
end
it "should raise error when invalid calculated member formula" do
@schema.define do
cube 'Sales' do
table 'sales'
calculated_member 'Dummy' do
dimension 'Measures'
formula 'Dummy(123)'
end
end
end
expect {
Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
}.to raise_error {|e|
e.should be_kind_of(Mondrian::OLAP::Error)
e.message.should == "mondrian.olap.MondrianException: Mondrian Error:Named set in cube 'Sales' has bad formula"
e.root_cause_message.should == "No function matches signature 'Dummy()'"
}
end
end
end