mondrian 0.0.1
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.
- data/.gitignore +18 -0
- data/.rspec +4 -0
- data/Gemfile +19 -0
- data/Guardfile +9 -0
- data/LICENSE +22 -0
- data/README.md +107 -0
- data/Rakefile +2 -0
- data/lib/mondrian.rb +6 -0
- data/lib/mondrian/schema.rb +297 -0
- data/lib/mondrian/schema_element.rb +151 -0
- data/lib/mondrian/version.rb +3 -0
- data/mondrian.gemspec +19 -0
- data/spec/mondrian/schema_spec.rb +642 -0
- data/spec/mondrian/test_spec.rb +7 -0
- data/spec/spec_helper.rb +13 -0
- metadata +74 -0
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module Mondrian
|
4
|
+
class SchemaElement
|
5
|
+
def initialize(name = nil, attributes = {}, &block)
|
6
|
+
# if just attributes hash provided
|
7
|
+
if name.is_a?(Hash) && attributes == {}
|
8
|
+
attributes = name
|
9
|
+
name = nil
|
10
|
+
end
|
11
|
+
@attributes = {}
|
12
|
+
if name
|
13
|
+
if self.class.content
|
14
|
+
@content = name
|
15
|
+
else
|
16
|
+
@attributes[:name] = name
|
17
|
+
end
|
18
|
+
end
|
19
|
+
@attributes.merge!(attributes)
|
20
|
+
self.class.elements.each do |element|
|
21
|
+
instance_variable_set("@#{pluralize(element)}", [])
|
22
|
+
end
|
23
|
+
@xml_fragments = []
|
24
|
+
instance_eval &block if block
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.attributes(*names)
|
28
|
+
names.each do |name|
|
29
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
30
|
+
def #{name}(*args)
|
31
|
+
if args.empty?
|
32
|
+
@attributes[:#{name}]
|
33
|
+
elsif args.size == 1
|
34
|
+
@attributes[:#{name}] = args[0]
|
35
|
+
else
|
36
|
+
raise ArgumentError, "too many arguments"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
RUBY
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.data_dictionary_names(*names)
|
44
|
+
return @data_dictionary_names || [] if names.empty?
|
45
|
+
@data_dictionary_names ||= []
|
46
|
+
@data_dictionary_names.concat(names)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.elements(*names)
|
50
|
+
return @elements || [] if names.empty?
|
51
|
+
|
52
|
+
@elements ||= []
|
53
|
+
@elements.concat(names)
|
54
|
+
|
55
|
+
names.each do |name|
|
56
|
+
attr_reader pluralize(name).to_sym
|
57
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
58
|
+
def #{name}(name=nil, attributes = {}, &block)
|
59
|
+
@#{pluralize(name)} << Schema::#{camel_case(name)}.new(name, attributes, &block)
|
60
|
+
end
|
61
|
+
RUBY
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.content(type=nil)
|
66
|
+
return @content if type.nil?
|
67
|
+
@content = type
|
68
|
+
end
|
69
|
+
|
70
|
+
attr_reader :xml_fragments
|
71
|
+
def xml(string)
|
72
|
+
string = string.strip
|
73
|
+
fragment = Nokogiri::XML::DocumentFragment.parse(string)
|
74
|
+
raise ArgumentError, "Invalid XML fragment:\n#{string}" if fragment.children.empty?
|
75
|
+
@xml_fragments << string
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_xml(options={})
|
79
|
+
options[:upcase_data_dictionary] = @upcase_data_dictionary unless @upcase_data_dictionary.nil?
|
80
|
+
Nokogiri::XML::Builder.new do |xml|
|
81
|
+
add_to_xml(xml, options)
|
82
|
+
end.to_xml
|
83
|
+
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
|
87
|
+
def add_to_xml(xml, options)
|
88
|
+
if self.class.content
|
89
|
+
xml.send(tag_name(self.class.name), @content, xmlized_attributes(options))
|
90
|
+
else
|
91
|
+
xml.send(tag_name(self.class.name), xmlized_attributes(options)) do
|
92
|
+
self.class.elements.each do |element|
|
93
|
+
instance_variable_get("@#{pluralize(element)}").each {|item| item.add_to_xml(xml, options)}
|
94
|
+
end
|
95
|
+
@xml_fragments.each do |xml_fragment|
|
96
|
+
xml.send(:insert, Nokogiri::XML::DocumentFragment.parse(xml_fragment))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def xmlized_attributes(options)
|
105
|
+
# data dictionary values should be in uppercase if schema defined with :upcase_data_dictionary => true
|
106
|
+
# or by default when using Oracle or LucidDB driver (can be overridden by :upcase_data_dictionary => false)
|
107
|
+
upcase_attributes = if options[:upcase_data_dictionary].nil? && %w(oracle luciddb).include?(options[:driver]) ||
|
108
|
+
options[:upcase_data_dictionary]
|
109
|
+
self.class.data_dictionary_names
|
110
|
+
else
|
111
|
+
[]
|
112
|
+
end
|
113
|
+
hash = {}
|
114
|
+
@attributes.each do |attr, value|
|
115
|
+
value = value.upcase if upcase_attributes.include?(attr)
|
116
|
+
hash[
|
117
|
+
# camelcase attribute name
|
118
|
+
attr.to_s.gsub(/_([^_]+)/){|m| $1.capitalize}
|
119
|
+
] = value
|
120
|
+
end
|
121
|
+
hash
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.pluralize(string)
|
125
|
+
string = string.to_s
|
126
|
+
case string
|
127
|
+
when /^(.*)y$/
|
128
|
+
"#{$1}ies"
|
129
|
+
else
|
130
|
+
"#{string}s"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def pluralize(string)
|
135
|
+
self.class.pluralize(string)
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.camel_case(string)
|
139
|
+
string.to_s.split('_').map{|s| s.capitalize}.join('')
|
140
|
+
end
|
141
|
+
|
142
|
+
def camel_case(string)
|
143
|
+
self.class.camel_case(string)
|
144
|
+
end
|
145
|
+
|
146
|
+
def tag_name(string)
|
147
|
+
string.split('::').last << '_'
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
data/mondrian.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/mondrian/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["stellard"]
|
6
|
+
gem.email = ["scott.ellard@gmail.com"]
|
7
|
+
gem.description = %q{Schema DSL for mondrian}
|
8
|
+
gem.summary = %q{Ruby DSL for Mondrian Schema Definitions, Based on https://github.com/rsim/mondrian-olap}
|
9
|
+
gem.homepage = "https://github.com/kujilabs/mondrian"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "mondrian"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Mondrian::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency 'nokogiri', '~> 1.5.0'
|
19
|
+
end
|
@@ -0,0 +1,642 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Mondrian::Schema do
|
4
|
+
|
5
|
+
describe "elements" do
|
6
|
+
before(:each) do
|
7
|
+
@schema = Mondrian::Schema.new
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "root element" do
|
11
|
+
it "should render to XML" do
|
12
|
+
@schema.to_xml.should be_equivalent_to <<-XML
|
13
|
+
<?xml version="1.0"?>
|
14
|
+
<Schema/>
|
15
|
+
XML
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should render to XML with attributes" do
|
19
|
+
@schema.define('FoodMart') do
|
20
|
+
description 'Demo "FoodMart" schema'
|
21
|
+
end
|
22
|
+
@schema.to_xml.should be_equivalent_to <<-XML
|
23
|
+
<?xml version="1.0"?>
|
24
|
+
<Schema description="Demo "FoodMart" schema" name="FoodMart"/>
|
25
|
+
XML
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should render to XML using class method" do
|
29
|
+
schema = Mondrian::Schema.define('FoodMart')
|
30
|
+
schema.to_xml.should be_equivalent_to <<-XML
|
31
|
+
<?xml version="1.0"?>
|
32
|
+
<Schema name="FoodMart"/>
|
33
|
+
XML
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "Cube" do
|
38
|
+
it "should render to XML" do
|
39
|
+
@schema.define do
|
40
|
+
cube 'Sales' do
|
41
|
+
default_measure 'Unit Sales'
|
42
|
+
description 'Sales cube'
|
43
|
+
cache false
|
44
|
+
enabled true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
@schema.to_xml.should be_equivalent_to <<-XML
|
48
|
+
<?xml version="1.0"?>
|
49
|
+
<Schema name="default">
|
50
|
+
<Cube cache="false" defaultMeasure="Unit Sales" description="Sales cube" enabled="true" name="Sales"/>
|
51
|
+
</Schema>
|
52
|
+
XML
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should render to XML using options hash" do
|
56
|
+
@schema.define do
|
57
|
+
cube 'Sales', :default_measure => 'Unit Sales',
|
58
|
+
:description => 'Sales cube', :cache => false, :enabled => true
|
59
|
+
end
|
60
|
+
@schema.to_xml.should be_equivalent_to <<-XML
|
61
|
+
<?xml version="1.0"?>
|
62
|
+
<Schema name="default">
|
63
|
+
<Cube cache="false" defaultMeasure="Unit Sales" description="Sales cube" enabled="true" name="Sales"/>
|
64
|
+
</Schema>
|
65
|
+
XML
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "Table" do
|
70
|
+
it "should render to XML" do
|
71
|
+
@schema.define do
|
72
|
+
cube 'Sales' do
|
73
|
+
table 'sales_fact', :alias => 'sales'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
@schema.to_xml.should be_equivalent_to <<-XML
|
77
|
+
<?xml version="1.0"?>
|
78
|
+
<Schema name="default">
|
79
|
+
<Cube name="Sales">
|
80
|
+
<Table alias="sales" name="sales_fact"/>
|
81
|
+
</Cube>
|
82
|
+
</Schema>
|
83
|
+
XML
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should render table name in uppercase when using Oracle or LucidDB driver" do
|
87
|
+
@schema.define do
|
88
|
+
cube 'Sales' do
|
89
|
+
table 'sales_fact', :alias => 'sales', :schema => 'facts'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
%w(oracle luciddb).each do |driver|
|
93
|
+
@schema.to_xml(:driver => driver).should be_equivalent_to <<-XML
|
94
|
+
<?xml version="1.0"?>
|
95
|
+
<Schema name="default">
|
96
|
+
<Cube name="Sales">
|
97
|
+
<Table alias="SALES" name="SALES_FACT" schema="FACTS"/>
|
98
|
+
</Cube>
|
99
|
+
</Schema>
|
100
|
+
XML
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should render table name in uppercase when :upcase_data_dictionary option is set to true" do
|
105
|
+
@schema.define :upcase_data_dictionary => true do
|
106
|
+
cube 'Sales' do
|
107
|
+
table 'sales_fact', :alias => 'sales', :schema => 'facts'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
@schema.to_xml.should be_equivalent_to <<-XML
|
111
|
+
<?xml version="1.0"?>
|
112
|
+
<Schema name="default">
|
113
|
+
<Cube name="Sales">
|
114
|
+
<Table alias="SALES" name="SALES_FACT" schema="FACTS"/>
|
115
|
+
</Cube>
|
116
|
+
</Schema>
|
117
|
+
XML
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should render table name in lowercase when using Oracle or LucidDB driver but with :upcase_data_dictionary set to false" do
|
121
|
+
@schema.define :upcase_data_dictionary => false do
|
122
|
+
cube 'Sales' do
|
123
|
+
table 'sales_fact', :alias => 'sales', :schema => 'facts'
|
124
|
+
end
|
125
|
+
end
|
126
|
+
%w(oracle luciddb).each do |driver|
|
127
|
+
@schema.to_xml(:driver => driver).should be_equivalent_to <<-XML
|
128
|
+
<?xml version="1.0"?>
|
129
|
+
<Schema name="default">
|
130
|
+
<Cube name="Sales">
|
131
|
+
<Table alias="sales" name="sales_fact" schema="facts"/>
|
132
|
+
</Cube>
|
133
|
+
</Schema>
|
134
|
+
XML
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should render table with where condition" do
|
139
|
+
@schema.define do
|
140
|
+
cube 'Sales' do
|
141
|
+
table 'sales_fact', :alias => 'sales' do
|
142
|
+
sql 'customer_id IS NOT NULL'
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
@schema.to_xml.should be_equivalent_to <<-XML
|
147
|
+
<?xml version="1.0"?>
|
148
|
+
<Schema name="default">
|
149
|
+
<Cube name="Sales">
|
150
|
+
<Table alias="sales" name="sales_fact">
|
151
|
+
<SQL>customer_id IS NOT NULL</SQL>
|
152
|
+
</Table>
|
153
|
+
</Cube>
|
154
|
+
</Schema>
|
155
|
+
XML
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe "View" do
|
160
|
+
it "should render to XML" do
|
161
|
+
@schema.define do
|
162
|
+
cube 'Sales' do
|
163
|
+
view :alias => 'sales' do
|
164
|
+
sql 'select * from sales_fact'
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
@schema.to_xml.should be_equivalent_to <<-XML
|
169
|
+
<?xml version="1.0"?>
|
170
|
+
<Schema name="default">
|
171
|
+
<Cube name="Sales">
|
172
|
+
<View alias="sales">
|
173
|
+
<SQL>select * from sales_fact</SQL>
|
174
|
+
</View>
|
175
|
+
</Cube>
|
176
|
+
</Schema>
|
177
|
+
XML
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe "Dimension" do
|
182
|
+
it "should render to XML" do
|
183
|
+
@schema.define do
|
184
|
+
cube 'Sales' do
|
185
|
+
dimension 'Gender' do
|
186
|
+
foreign_key 'customer_id'
|
187
|
+
hierarchy do
|
188
|
+
has_all true
|
189
|
+
all_member_name 'All Genders'
|
190
|
+
primary_key 'customer_id'
|
191
|
+
table 'customer'
|
192
|
+
level 'Gender', :column => 'gender', :unique_members => true
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
@schema.to_xml.should be_equivalent_to <<-XML
|
198
|
+
<?xml version="1.0"?>
|
199
|
+
<Schema name="default">
|
200
|
+
<Cube name="Sales">
|
201
|
+
<Dimension foreignKey="customer_id" name="Gender">
|
202
|
+
<Hierarchy allMemberName="All Genders" hasAll="true" primaryKey="customer_id">
|
203
|
+
<Table name="customer"/>
|
204
|
+
<Level column="gender" name="Gender" uniqueMembers="true"/>
|
205
|
+
</Hierarchy>
|
206
|
+
</Dimension>
|
207
|
+
</Cube>
|
208
|
+
</Schema>
|
209
|
+
XML
|
210
|
+
end
|
211
|
+
|
212
|
+
it "should render time dimension" do
|
213
|
+
@schema.define do
|
214
|
+
cube 'Sales' do
|
215
|
+
dimension 'Time' do
|
216
|
+
foreign_key 'time_id'
|
217
|
+
hierarchy do
|
218
|
+
has_all false
|
219
|
+
primary_key 'time_id'
|
220
|
+
table 'time_by_day'
|
221
|
+
level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true
|
222
|
+
level 'Quarter', :column => 'quarter', :unique_members => false
|
223
|
+
level 'Month', :column => 'month_of_year', :type => 'Numeric', :unique_members => false
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
@schema.to_xml.should be_equivalent_to <<-XML
|
229
|
+
<?xml version="1.0"?>
|
230
|
+
<Schema name="default">
|
231
|
+
<Cube name="Sales">
|
232
|
+
<Dimension foreignKey="time_id" name="Time">
|
233
|
+
<Hierarchy hasAll="false" primaryKey="time_id">
|
234
|
+
<Table name="time_by_day"/>
|
235
|
+
<Level column="the_year" name="Year" type="Numeric" uniqueMembers="true"/>
|
236
|
+
<Level column="quarter" name="Quarter" uniqueMembers="false"/>
|
237
|
+
<Level column="month_of_year" name="Month" type="Numeric" uniqueMembers="false"/>
|
238
|
+
</Hierarchy>
|
239
|
+
</Dimension>
|
240
|
+
</Cube>
|
241
|
+
</Schema>
|
242
|
+
XML
|
243
|
+
end
|
244
|
+
|
245
|
+
it "should render dimension hierarchy with join" do
|
246
|
+
@schema.define do
|
247
|
+
cube 'Sales' do
|
248
|
+
dimension 'Products', :foreign_key => 'product_id' do
|
249
|
+
hierarchy :has_all => true, :all_member_name => 'All Products',
|
250
|
+
:primary_key => 'product_id', :primary_key_table => 'product' do
|
251
|
+
join :left_key => 'product_class_id', :right_key => 'product_class_id' do
|
252
|
+
table 'product'
|
253
|
+
table 'product_class'
|
254
|
+
end
|
255
|
+
level 'Product Family', :table => 'product_class', :column => 'product_family', :unique_members => true
|
256
|
+
level 'Brand Name', :table => 'product', :column => 'brand_name', :unique_members => false
|
257
|
+
level 'Product Name', :table => 'product', :column => 'product_name', :unique_members => true
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
@schema.to_xml.should be_equivalent_to <<-XML
|
263
|
+
<?xml version="1.0"?>
|
264
|
+
<Schema name="default">
|
265
|
+
<Cube name="Sales">
|
266
|
+
<Dimension foreignKey="product_id" name="Products">
|
267
|
+
<Hierarchy allMemberName="All Products" hasAll="true" primaryKey="product_id" primaryKeyTable="product">
|
268
|
+
<Join leftKey="product_class_id" rightKey="product_class_id">
|
269
|
+
<Table name="product"/>
|
270
|
+
<Table name="product_class"/>
|
271
|
+
</Join>
|
272
|
+
<Level column="product_family" name="Product Family" table="product_class" uniqueMembers="true"/>
|
273
|
+
<Level column="brand_name" name="Brand Name" table="product" uniqueMembers="false"/>
|
274
|
+
<Level column="product_name" name="Product Name" table="product" uniqueMembers="true"/>
|
275
|
+
</Hierarchy>
|
276
|
+
</Dimension>
|
277
|
+
</Cube>
|
278
|
+
</Schema>
|
279
|
+
XML
|
280
|
+
end
|
281
|
+
|
282
|
+
it "should render table and column names in uppercase when using Oracle driver" do
|
283
|
+
@schema.define do
|
284
|
+
cube 'Sales' do
|
285
|
+
dimension 'Products', :foreign_key => 'product_id' do
|
286
|
+
hierarchy :has_all => true, :all_member_name => 'All Products',
|
287
|
+
:primary_key => 'product_id', :primary_key_table => 'product' do
|
288
|
+
join :left_key => 'product_class_id', :right_key => 'product_class_id' do
|
289
|
+
table 'product'
|
290
|
+
table 'product_class'
|
291
|
+
end
|
292
|
+
level 'Product Family', :table => 'product_class', :column => 'product_family', :unique_members => true
|
293
|
+
level 'Brand Name', :table => 'product', :column => 'brand_name', :unique_members => false
|
294
|
+
level 'Product Name', :table => 'product', :column => 'product_name', :unique_members => true
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
@schema.to_xml(:driver => 'oracle').should be_equivalent_to <<-XML
|
300
|
+
<?xml version="1.0"?>
|
301
|
+
<Schema name="default">
|
302
|
+
<Cube name="Sales">
|
303
|
+
<Dimension foreignKey="PRODUCT_ID" name="Products">
|
304
|
+
<Hierarchy allMemberName="All Products" hasAll="true" primaryKey="PRODUCT_ID" primaryKeyTable="PRODUCT">
|
305
|
+
<Join leftKey="PRODUCT_CLASS_ID" rightKey="PRODUCT_CLASS_ID">
|
306
|
+
<Table name="PRODUCT"/>
|
307
|
+
<Table name="PRODUCT_CLASS"/>
|
308
|
+
</Join>
|
309
|
+
<Level column="PRODUCT_FAMILY" name="Product Family" table="PRODUCT_CLASS" uniqueMembers="true"/>
|
310
|
+
<Level column="BRAND_NAME" name="Brand Name" table="PRODUCT" uniqueMembers="false"/>
|
311
|
+
<Level column="PRODUCT_NAME" name="Product Name" table="PRODUCT" uniqueMembers="true"/>
|
312
|
+
</Hierarchy>
|
313
|
+
</Dimension>
|
314
|
+
</Cube>
|
315
|
+
</Schema>
|
316
|
+
XML
|
317
|
+
end
|
318
|
+
|
319
|
+
it "should render dimension hierarchy with nested joins" do
|
320
|
+
@schema.define do
|
321
|
+
cube 'Sales' do
|
322
|
+
dimension 'Products', :foreign_key => 'product_id' do
|
323
|
+
hierarchy :has_all => true, :all_member_name => 'All Products',
|
324
|
+
:primary_key => 'product_id', :primary_key_table => 'product' do
|
325
|
+
join :left_key => 'product_class_id', :right_alias => 'product_class', :right_key => 'product_class_id' do
|
326
|
+
table 'product'
|
327
|
+
join :left_key => 'product_type_id', :right_key => 'product_type_id' do
|
328
|
+
table 'product_class'
|
329
|
+
table 'product_type'
|
330
|
+
end
|
331
|
+
end
|
332
|
+
level 'Product Family', :table => 'product_type', :column => 'product_family', :unique_members => true
|
333
|
+
level 'Product Category', :table => 'product_class', :column => 'product_category', :unique_members => false
|
334
|
+
level 'Brand Name', :table => 'product', :column => 'brand_name', :unique_members => false
|
335
|
+
level 'Product Name', :table => 'product', :column => 'product_name', :unique_members => true
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
@schema.to_xml.should be_equivalent_to <<-XML
|
341
|
+
<?xml version="1.0"?>
|
342
|
+
<Schema name="default">
|
343
|
+
<Cube name="Sales">
|
344
|
+
<Dimension foreignKey="product_id" name="Products">
|
345
|
+
<Hierarchy allMemberName="All Products" hasAll="true" primaryKey="product_id" primaryKeyTable="product">
|
346
|
+
<Join leftKey="product_class_id" rightAlias="product_class" rightKey="product_class_id">
|
347
|
+
<Table name="product"/>
|
348
|
+
<Join leftKey="product_type_id" rightKey="product_type_id">
|
349
|
+
<Table name="product_class"/>
|
350
|
+
<Table name="product_type"/>
|
351
|
+
</Join>
|
352
|
+
</Join>
|
353
|
+
<Level column="product_family" name="Product Family" table="product_type" uniqueMembers="true"/>
|
354
|
+
<Level column="product_category" name="Product Category" table="product_class" uniqueMembers="false"/>
|
355
|
+
<Level column="brand_name" name="Brand Name" table="product" uniqueMembers="false"/>
|
356
|
+
<Level column="product_name" name="Product Name" table="product" uniqueMembers="true"/>
|
357
|
+
</Hierarchy>
|
358
|
+
</Dimension>
|
359
|
+
</Cube>
|
360
|
+
</Schema>
|
361
|
+
XML
|
362
|
+
end
|
363
|
+
|
364
|
+
end
|
365
|
+
|
366
|
+
describe "Measure" do
|
367
|
+
it "should render XML" do
|
368
|
+
@schema.define do
|
369
|
+
cube 'Sales' do
|
370
|
+
measure 'Unit Sales' do
|
371
|
+
column 'unit_sales'
|
372
|
+
aggregator 'sum'
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
@schema.to_xml.should be_equivalent_to <<-XML
|
377
|
+
<?xml version="1.0"?>
|
378
|
+
<Schema name="default">
|
379
|
+
<Cube name="Sales">
|
380
|
+
<Measure aggregator="sum" column="unit_sales" name="Unit Sales"/>
|
381
|
+
</Cube>
|
382
|
+
</Schema>
|
383
|
+
XML
|
384
|
+
end
|
385
|
+
|
386
|
+
it "should render column name in uppercase when using Oracle driver" do
|
387
|
+
@schema.define do
|
388
|
+
cube 'Sales' do
|
389
|
+
measure 'Unit Sales' do
|
390
|
+
column 'unit_sales'
|
391
|
+
aggregator 'sum'
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
@schema.to_xml(:driver => 'oracle').should be_equivalent_to <<-XML
|
396
|
+
<?xml version="1.0"?>
|
397
|
+
<Schema name="default">
|
398
|
+
<Cube name="Sales">
|
399
|
+
<Measure aggregator="sum" column="UNIT_SALES" name="Unit Sales"/>
|
400
|
+
</Cube>
|
401
|
+
</Schema>
|
402
|
+
XML
|
403
|
+
end
|
404
|
+
|
405
|
+
it "should render with measure expression" do
|
406
|
+
@schema.define do
|
407
|
+
cube 'Sales' do
|
408
|
+
measure 'Double Unit Sales', :aggregator => 'sum' do
|
409
|
+
measure_expression do
|
410
|
+
sql 'unit_sales * 2'
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
415
|
+
@schema.to_xml.should be_equivalent_to <<-XML
|
416
|
+
<?xml version="1.0"?>
|
417
|
+
<Schema name="default">
|
418
|
+
<Cube name="Sales">
|
419
|
+
<Measure aggregator="sum" name="Double Unit Sales">
|
420
|
+
<MeasureExpression>
|
421
|
+
<SQL>unit_sales * 2</SQL>
|
422
|
+
</MeasureExpression>
|
423
|
+
</Measure>
|
424
|
+
</Cube>
|
425
|
+
</Schema>
|
426
|
+
XML
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
describe "Calculated Member" do
|
431
|
+
it "should render XML" do
|
432
|
+
@schema.define do
|
433
|
+
cube 'Sales' do
|
434
|
+
calculated_member 'Profit' do
|
435
|
+
dimension 'Measures'
|
436
|
+
formula '[Measures].[Store Sales] - [Measures].[Store Cost]'
|
437
|
+
format_string '#,##0.00'
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
@schema.to_xml.should be_equivalent_to <<-XML
|
442
|
+
<?xml version="1.0"?>
|
443
|
+
<Schema name="default">
|
444
|
+
<Cube name="Sales">
|
445
|
+
<CalculatedMember dimension="Measures" formatString="#,##0.00" name="Profit">
|
446
|
+
<Formula>[Measures].[Store Sales] - [Measures].[Store Cost]</Formula>
|
447
|
+
</CalculatedMember>
|
448
|
+
</Cube>
|
449
|
+
</Schema>
|
450
|
+
XML
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
describe "Aggregates" do
|
455
|
+
it "should render named aggregate to XML" do
|
456
|
+
@schema.define do
|
457
|
+
cube 'Sales' do
|
458
|
+
table 'sales_fact_1997' do
|
459
|
+
agg_name 'agg_c_special_sales_fact_1997' do
|
460
|
+
agg_fact_count :column => 'fact_count'
|
461
|
+
agg_measure '[Measures].[Store Cost]', :column => 'store_cost_sum'
|
462
|
+
agg_measure '[Measures].[Store Sales]', :column => 'store_sales_sum'
|
463
|
+
agg_level '[Product].[Product Family]', :column => 'product_family'
|
464
|
+
agg_level '[Time].[Quarter]', :column => 'time_quarter'
|
465
|
+
agg_level '[Time].[Year]', :column => 'time_year'
|
466
|
+
agg_level '[Time].[Quarter]', :column => 'time_quarter'
|
467
|
+
agg_level '[Time].[Month]', :column => 'time_month'
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
@schema.to_xml.should be_equivalent_to <<-XML
|
473
|
+
<?xml version="1.0"?>
|
474
|
+
<Schema name="default">
|
475
|
+
<Cube name="Sales">
|
476
|
+
<Table name="sales_fact_1997">
|
477
|
+
<AggName name="agg_c_special_sales_fact_1997">
|
478
|
+
<AggFactCount column="fact_count"/>
|
479
|
+
<AggMeasure column="store_cost_sum" name="[Measures].[Store Cost]"/>
|
480
|
+
<AggMeasure column="store_sales_sum" name="[Measures].[Store Sales]"/>
|
481
|
+
<AggLevel column="product_family" name="[Product].[Product Family]"/>
|
482
|
+
<AggLevel column="time_quarter" name="[Time].[Quarter]"/>
|
483
|
+
<AggLevel column="time_year" name="[Time].[Year]"/>
|
484
|
+
<AggLevel column="time_quarter" name="[Time].[Quarter]"/>
|
485
|
+
<AggLevel column="time_month" name="[Time].[Month]"/>
|
486
|
+
</AggName>
|
487
|
+
</Table>
|
488
|
+
</Cube>
|
489
|
+
</Schema>
|
490
|
+
XML
|
491
|
+
end
|
492
|
+
|
493
|
+
it "should render aggregate pattern to XML" do
|
494
|
+
@schema.define do
|
495
|
+
cube 'Sales' do
|
496
|
+
table 'sales_fact_1997' do
|
497
|
+
agg_pattern :pattern => 'agg_.*_sales_fact_1997' do
|
498
|
+
agg_fact_count :column => 'fact_count'
|
499
|
+
agg_measure '[Measures].[Store Cost]', :column => 'store_cost_sum'
|
500
|
+
agg_measure '[Measures].[Store Sales]', :column => 'store_sales_sum'
|
501
|
+
agg_level '[Product].[Product Family]', :column => 'product_family'
|
502
|
+
agg_level '[Time].[Quarter]', :column => 'time_quarter'
|
503
|
+
agg_level '[Time].[Year]', :column => 'time_year'
|
504
|
+
agg_level '[Time].[Quarter]', :column => 'time_quarter'
|
505
|
+
agg_level '[Time].[Month]', :column => 'time_month'
|
506
|
+
agg_exclude 'agg_c_14_sales_fact_1997'
|
507
|
+
agg_exclude 'agg_lc_100_sales_fact_1997'
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end
|
511
|
+
end
|
512
|
+
@schema.to_xml.should be_equivalent_to <<-XML
|
513
|
+
<?xml version="1.0"?>
|
514
|
+
<Schema name="default">
|
515
|
+
<Cube name="Sales">
|
516
|
+
<Table name="sales_fact_1997">
|
517
|
+
<AggPattern pattern="agg_.*_sales_fact_1997">
|
518
|
+
<AggFactCount column="fact_count"/>
|
519
|
+
<AggMeasure column="store_cost_sum" name="[Measures].[Store Cost]"/>
|
520
|
+
<AggMeasure column="store_sales_sum" name="[Measures].[Store Sales]"/>
|
521
|
+
<AggLevel column="product_family" name="[Product].[Product Family]"/>
|
522
|
+
<AggLevel column="time_quarter" name="[Time].[Quarter]"/>
|
523
|
+
<AggLevel column="time_year" name="[Time].[Year]"/>
|
524
|
+
<AggLevel column="time_quarter" name="[Time].[Quarter]"/>
|
525
|
+
<AggLevel column="time_month" name="[Time].[Month]"/>
|
526
|
+
<AggExclude name="agg_c_14_sales_fact_1997"/>
|
527
|
+
<AggExclude name="agg_lc_100_sales_fact_1997"/>
|
528
|
+
</AggPattern>
|
529
|
+
</Table>
|
530
|
+
</Cube>
|
531
|
+
</Schema>
|
532
|
+
XML
|
533
|
+
end
|
534
|
+
|
535
|
+
it "should render embedded aggregate XML defintion to XML" do
|
536
|
+
@schema.define do
|
537
|
+
cube 'Sales' do
|
538
|
+
table 'sales_fact_1997' do
|
539
|
+
xml <<-XML
|
540
|
+
<AggName name="agg_c_special_sales_fact_1997">
|
541
|
+
<AggFactCount column="fact_count"/>
|
542
|
+
<AggMeasure column="store_cost_sum" name="[Measures].[Store Cost]"/>
|
543
|
+
<AggMeasure column="store_sales_sum" name="[Measures].[Store Sales]"/>
|
544
|
+
<AggLevel column="product_family" name="[Product].[Product Family]"/>
|
545
|
+
<AggLevel column="time_quarter" name="[Time].[Quarter]"/>
|
546
|
+
<AggLevel column="time_year" name="[Time].[Year]"/>
|
547
|
+
<AggLevel column="time_quarter" name="[Time].[Quarter]"/>
|
548
|
+
<AggLevel column="time_month" name="[Time].[Month]"/>
|
549
|
+
</AggName>
|
550
|
+
<AggPattern pattern="agg_.*_sales_fact_1997">
|
551
|
+
<AggFactCount column="fact_count"/>
|
552
|
+
<AggMeasure column="store_cost_sum" name="[Measures].[Store Cost]"/>
|
553
|
+
<AggMeasure column="store_sales_sum" name="[Measures].[Store Sales]"/>
|
554
|
+
<AggLevel column="product_family" name="[Product].[Product Family]"/>
|
555
|
+
<AggLevel column="time_quarter" name="[Time].[Quarter]"/>
|
556
|
+
<AggLevel column="time_year" name="[Time].[Year]"/>
|
557
|
+
<AggLevel column="time_quarter" name="[Time].[Quarter]"/>
|
558
|
+
<AggLevel column="time_month" name="[Time].[Month]"/>
|
559
|
+
<AggExclude name="agg_c_14_sales_fact_1997"/>
|
560
|
+
<AggExclude name="agg_lc_100_sales_fact_1997"/>
|
561
|
+
</AggPattern>
|
562
|
+
XML
|
563
|
+
end
|
564
|
+
end
|
565
|
+
end
|
566
|
+
@schema.to_xml.should be_equivalent_to <<-XML
|
567
|
+
<?xml version="1.0"?>
|
568
|
+
<Schema name="default">
|
569
|
+
<Cube name="Sales">
|
570
|
+
<Table name="sales_fact_1997">
|
571
|
+
<AggName name="agg_c_special_sales_fact_1997">
|
572
|
+
<AggFactCount column="fact_count"/>
|
573
|
+
<AggMeasure column="store_cost_sum" name="[Measures].[Store Cost]"/>
|
574
|
+
<AggMeasure column="store_sales_sum" name="[Measures].[Store Sales]"/>
|
575
|
+
<AggLevel column="product_family" name="[Product].[Product Family]"/>
|
576
|
+
<AggLevel column="time_quarter" name="[Time].[Quarter]"/>
|
577
|
+
<AggLevel column="time_year" name="[Time].[Year]"/>
|
578
|
+
<AggLevel column="time_quarter" name="[Time].[Quarter]"/>
|
579
|
+
<AggLevel column="time_month" name="[Time].[Month]"/>
|
580
|
+
</AggName>
|
581
|
+
<AggPattern pattern="agg_.*_sales_fact_1997">
|
582
|
+
<AggFactCount column="fact_count"/>
|
583
|
+
<AggMeasure column="store_cost_sum" name="[Measures].[Store Cost]"/>
|
584
|
+
<AggMeasure column="store_sales_sum" name="[Measures].[Store Sales]"/>
|
585
|
+
<AggLevel column="product_family" name="[Product].[Product Family]"/>
|
586
|
+
<AggLevel column="time_quarter" name="[Time].[Quarter]"/>
|
587
|
+
<AggLevel column="time_year" name="[Time].[Year]"/>
|
588
|
+
<AggLevel column="time_quarter" name="[Time].[Quarter]"/>
|
589
|
+
<AggLevel column="time_month" name="[Time].[Month]"/>
|
590
|
+
<AggExclude name="agg_c_14_sales_fact_1997"/>
|
591
|
+
<AggExclude name="agg_lc_100_sales_fact_1997"/>
|
592
|
+
</AggPattern>
|
593
|
+
</Table>
|
594
|
+
</Cube>
|
595
|
+
</Schema>
|
596
|
+
XML
|
597
|
+
end
|
598
|
+
|
599
|
+
end
|
600
|
+
|
601
|
+
describe "Member properties" do
|
602
|
+
it "should render XML" do
|
603
|
+
@schema.define do
|
604
|
+
cube 'Sales' do
|
605
|
+
dimension 'Employees', :foreign_key => 'employee_id' do
|
606
|
+
hierarchy :has_all => true, :all_member_name => 'All Employees', :primary_key => 'employee_id' do
|
607
|
+
table 'employee'
|
608
|
+
level 'Employee Id', :unique_members => true, :type => 'Numeric', :column => 'employee_id', :name_column => 'full_name',
|
609
|
+
:parent_column => 'supervisor_id', :null_parent_value => 0 do
|
610
|
+
property 'Marital Status', :column => 'marital_status'
|
611
|
+
property 'Position Title', :column => 'position_title'
|
612
|
+
property 'Gender', :column => 'gender'
|
613
|
+
property 'Salary', :column => 'salary'
|
614
|
+
property 'Education Level', :column => 'education_level'
|
615
|
+
end
|
616
|
+
end
|
617
|
+
end
|
618
|
+
end
|
619
|
+
end
|
620
|
+
@schema.to_xml.should be_equivalent_to <<-XML
|
621
|
+
<?xml version="1.0"?>
|
622
|
+
<Schema name="default">
|
623
|
+
<Cube name="Sales">
|
624
|
+
<Dimension foreignKey="employee_id" name="Employees">
|
625
|
+
<Hierarchy allMemberName="All Employees" hasAll="true" primaryKey="employee_id">
|
626
|
+
<Table name="employee"/>
|
627
|
+
<Level column="employee_id" name="Employee Id" nameColumn="full_name" nullParentValue="0" parentColumn="supervisor_id" type="Numeric" uniqueMembers="true">
|
628
|
+
<Property column="marital_status" name="Marital Status"/>
|
629
|
+
<Property column="position_title" name="Position Title"/>
|
630
|
+
<Property column="gender" name="Gender"/>
|
631
|
+
<Property column="salary" name="Salary"/>
|
632
|
+
<Property column="education_level" name="Education Level"/>
|
633
|
+
</Level>
|
634
|
+
</Hierarchy>
|
635
|
+
</Dimension>
|
636
|
+
</Cube>
|
637
|
+
</Schema>
|
638
|
+
XML
|
639
|
+
end
|
640
|
+
end
|
641
|
+
end
|
642
|
+
end
|