mondrian-olap 0.5.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/Changelog.md +86 -0
- data/LICENSE-Mondrian.txt +87 -0
- data/LICENSE.txt +1 -1
- data/README.md +43 -40
- data/VERSION +1 -1
- data/lib/mondrian/jars/commons-collections-3.2.2.jar +0 -0
- data/lib/mondrian/jars/commons-dbcp-1.4.jar +0 -0
- data/lib/mondrian/jars/commons-io-2.2.jar +0 -0
- data/lib/mondrian/jars/commons-lang-2.6.jar +0 -0
- data/lib/mondrian/jars/commons-logging-1.2.jar +0 -0
- data/lib/mondrian/jars/commons-pool-1.5.7.jar +0 -0
- data/lib/mondrian/jars/commons-vfs2-2.2.jar +0 -0
- data/lib/mondrian/jars/eigenbase-xom-1.3.5.jar +0 -0
- data/lib/mondrian/jars/guava-17.0.jar +0 -0
- data/lib/mondrian/jars/log4j-1.2.17.jar +0 -0
- data/lib/mondrian/jars/log4j.properties +2 -4
- data/lib/mondrian/jars/mondrian-9.1.0.0.jar +0 -0
- data/lib/mondrian/jars/olap4j-1.2.0.jar +0 -0
- data/lib/mondrian/olap/connection.rb +252 -67
- data/lib/mondrian/olap/cube.rb +63 -2
- data/lib/mondrian/olap/error.rb +37 -8
- data/lib/mondrian/olap/query.rb +41 -21
- data/lib/mondrian/olap/result.rb +163 -44
- data/lib/mondrian/olap/schema.rb +42 -3
- data/lib/mondrian/olap/schema_element.rb +25 -6
- data/lib/mondrian/olap/schema_udf.rb +21 -16
- data/spec/connection_role_spec.rb +69 -13
- data/spec/connection_spec.rb +3 -2
- data/spec/cube_cache_control_spec.rb +261 -0
- data/spec/cube_spec.rb +32 -4
- data/spec/fixtures/MondrianTest.xml +1 -6
- data/spec/fixtures/MondrianTestOracle.xml +1 -6
- data/spec/mondrian_spec.rb +71 -1
- data/spec/query_spec.rb +323 -25
- data/spec/rake_tasks.rb +253 -159
- data/spec/schema_definition_spec.rb +314 -61
- data/spec/spec_helper.rb +115 -45
- data/spec/support/data/customers.csv +10902 -0
- data/spec/support/data/product_classes.csv +101 -0
- data/spec/support/data/products.csv +101 -0
- data/spec/support/data/sales.csv +101 -0
- data/spec/support/data/time.csv +731 -0
- metadata +126 -124
- data/LICENSE-Mondrian.html +0 -259
- data/lib/mondrian/jars/commons-collections-3.2.jar +0 -0
- data/lib/mondrian/jars/commons-dbcp-1.2.1.jar +0 -0
- data/lib/mondrian/jars/commons-logging-1.1.1.jar +0 -0
- data/lib/mondrian/jars/commons-pool-1.2.jar +0 -0
- data/lib/mondrian/jars/commons-vfs-1.0.jar +0 -0
- data/lib/mondrian/jars/eigenbase-xom-1.3.1.jar +0 -0
- data/lib/mondrian/jars/log4j-1.2.14.jar +0 -0
- data/lib/mondrian/jars/mondrian.jar +0 -0
- data/lib/mondrian/jars/olap4j-1.0.1.539.jar +0 -0
data/lib/mondrian/olap/schema.rb
CHANGED
@@ -50,10 +50,12 @@ module Mondrian
|
|
50
50
|
public
|
51
51
|
|
52
52
|
attributes :name, :description, :measures_caption
|
53
|
-
elements :annotations, :dimension, :cube, :virtual_cube, :role, :user_defined_function
|
53
|
+
elements :annotations, :parameter, :dimension, :cube, :virtual_cube, :role, :user_defined_function
|
54
54
|
|
55
55
|
class Cube < SchemaElement
|
56
56
|
attributes :name, :description, :caption,
|
57
|
+
# Whether this cube is visible in the user-interface. Default true.
|
58
|
+
:visible,
|
57
59
|
# The name of the measure that would be taken as the default measure of the cube.
|
58
60
|
:default_measure,
|
59
61
|
# Should the Fact table data for this Cube be cached by Mondrian or not.
|
@@ -84,6 +86,8 @@ module Mondrian
|
|
84
86
|
|
85
87
|
class Dimension < SchemaElement
|
86
88
|
attributes :name, :description, :caption,
|
89
|
+
# Whether this dimension is visible in the user-interface. Default true.
|
90
|
+
:visible,
|
87
91
|
# The dimension's type may be one of "Standard" or "Time".
|
88
92
|
# A time dimension will allow the use of the MDX time functions (WTD, YTD, QTD, etc.).
|
89
93
|
# Use a standard dimension if the dimension is not a time-related dimension.
|
@@ -111,6 +115,7 @@ module Mondrian
|
|
111
115
|
# Required in a private Dimension or a DimensionUsage, but not in a public Dimension.
|
112
116
|
:foreign_key
|
113
117
|
data_dictionary_names :usage_prefix, :foreign_key # values in XML will be uppercased when using Oracle driver
|
118
|
+
elements :annotations
|
114
119
|
|
115
120
|
def initialize(name = nil, attributes = {}, parent = nil)
|
116
121
|
super
|
@@ -121,6 +126,8 @@ module Mondrian
|
|
121
126
|
|
122
127
|
class Hierarchy < SchemaElement
|
123
128
|
attributes :name, :description, :caption,
|
129
|
+
# Whether this hierarchy is visible in the user-interface. Default true.
|
130
|
+
:visible,
|
124
131
|
# Whether this hierarchy has an 'all' member.
|
125
132
|
:has_all,
|
126
133
|
# Name of the 'all' member. If this attribute is not specified,
|
@@ -137,6 +144,8 @@ module Mondrian
|
|
137
144
|
# The name of the table which contains primary_key.
|
138
145
|
# If the hierarchy has only one table, defaults to that; it is required.
|
139
146
|
:primary_key_table,
|
147
|
+
#
|
148
|
+
:default_member,
|
140
149
|
# Should be set to the level (if such a level exists) at which depth it is known
|
141
150
|
# that all members have entirely unique rows, allowing SQL GROUP BY clauses to be completely eliminated from the query.
|
142
151
|
:unique_key_level_name
|
@@ -160,6 +169,8 @@ module Mondrian
|
|
160
169
|
|
161
170
|
class Level < SchemaElement
|
162
171
|
attributes :name, :description, :caption,
|
172
|
+
# Whether this level is visible in the user-interface. Default true.
|
173
|
+
:visible,
|
163
174
|
# The name of the table that the column comes from.
|
164
175
|
# If this hierarchy is based upon just one table, defaults to the name of that table;
|
165
176
|
# otherwise, it is required.
|
@@ -187,6 +198,12 @@ module Mondrian
|
|
187
198
|
# for example, "DATE '2006-06-01'".
|
188
199
|
# Default value: 'String'
|
189
200
|
:type,
|
201
|
+
# Indicates the Java type that Mondrian uses to store this level's key column.
|
202
|
+
# It also determines the JDBC method that Mondrian will call to retrieve the column;
|
203
|
+
# for example, if the Java type is 'int', Mondrian will call 'ResultSet.getInt(int)'.
|
204
|
+
# Usually this attribute is not needed, because Mondrian can choose a sensible type based on the type of the database column.
|
205
|
+
# Allowable values are: 'int', 'long', 'Object', 'String'.
|
206
|
+
:internal_type,
|
190
207
|
# Whether members are unique across all parents.
|
191
208
|
# For example, zipcodes are unique across all states.
|
192
209
|
# The first level's members are always unique.
|
@@ -288,8 +305,12 @@ module Mondrian
|
|
288
305
|
|
289
306
|
class CalculatedMember < SchemaElement
|
290
307
|
attributes :name, :description, :caption,
|
291
|
-
# Name of the dimension which this member belongs to.
|
308
|
+
# Name of the dimension which this member belongs to. Cannot be used if :hieararchy is specified.
|
292
309
|
:dimension,
|
310
|
+
# Full unique name of the hierarchy that this member belongs to.
|
311
|
+
:hierarchy,
|
312
|
+
# Fully-qualified name of the parent member. If not specified, the member will be at the lowest level (besides the 'all' level) in the hierarchy.
|
313
|
+
:parent,
|
293
314
|
# Format string with which to format cells of this measure. For more details, see the mondrian.util.Format class.
|
294
315
|
:format_string,
|
295
316
|
# Whether this member is visible in the user-interface. Default true.
|
@@ -312,6 +333,8 @@ module Mondrian
|
|
312
333
|
|
313
334
|
class VirtualCube < SchemaElement
|
314
335
|
attributes :name, :description, :caption,
|
336
|
+
# Whether this cube is visible in the user-interface. Default true.
|
337
|
+
:visible,
|
315
338
|
# The name of the measure that would be taken as the default measure of the cube.
|
316
339
|
:default_measure,
|
317
340
|
# Whether element is enabled - if true, then the VirtualCube is realized otherwise it is ignored.
|
@@ -322,7 +345,10 @@ module Mondrian
|
|
322
345
|
class VirtualCubeDimension < SchemaElement
|
323
346
|
attributes :name,
|
324
347
|
# Name of the cube which the dimension belongs to, or unspecified if the dimension is shared
|
325
|
-
:cube_name
|
348
|
+
:cube_name,
|
349
|
+
# Whether this dimension is visible in the user-interface. Default true.
|
350
|
+
:visible
|
351
|
+
elements :annotations
|
326
352
|
end
|
327
353
|
|
328
354
|
class VirtualCubeMeasure < SchemaElement
|
@@ -471,8 +497,21 @@ module Mondrian
|
|
471
497
|
end
|
472
498
|
|
473
499
|
class Annotation < SchemaElement
|
500
|
+
attributes :name
|
474
501
|
content :text
|
475
502
|
end
|
503
|
+
|
504
|
+
class Parameter < SchemaElement
|
505
|
+
attributes :name, :description,
|
506
|
+
# Indicates the type of this parameter: String, Numeric, Integer, Boolean, Date, Time, Timestamp, or Member.
|
507
|
+
:type,
|
508
|
+
# If false, statement cannot change the value of this parameter; the parameter becomes effectively constant
|
509
|
+
# (provided that its default value expression always returns the same value). Default is true.
|
510
|
+
:modifiable,
|
511
|
+
# Expression for the default value of this parameter.
|
512
|
+
:default_value
|
513
|
+
end
|
514
|
+
|
476
515
|
end
|
477
516
|
end
|
478
517
|
end
|
@@ -67,14 +67,30 @@ module Mondrian
|
|
67
67
|
attr_reader pluralize(name).to_sym
|
68
68
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
69
69
|
def #{name}(name=nil, attributes = {}, &block)
|
70
|
-
|
70
|
+
new_element = Schema::#{camel_case(name)}.new(name, attributes, self, &block)
|
71
|
+
@#{pluralize(name)} << new_element
|
72
|
+
new_element
|
71
73
|
end
|
72
74
|
RUBY
|
75
|
+
if name == :annotations
|
76
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
77
|
+
def annotations_hash
|
78
|
+
hash = {}
|
79
|
+
@annotationss.each do |annotations|
|
80
|
+
annotations.annotations.each do |annotation|
|
81
|
+
hash[annotation.name] = annotation.content
|
82
|
+
end
|
83
|
+
end
|
84
|
+
hash
|
85
|
+
end
|
86
|
+
RUBY
|
87
|
+
end
|
73
88
|
end
|
74
89
|
end
|
75
90
|
|
76
|
-
def self.content(type=nil)
|
91
|
+
def self.content(type = nil)
|
77
92
|
return @content if type.nil?
|
93
|
+
attr_reader :content
|
78
94
|
@content = type
|
79
95
|
end
|
80
96
|
|
@@ -86,7 +102,7 @@ module Mondrian
|
|
86
102
|
@xml_fragments << string
|
87
103
|
end
|
88
104
|
|
89
|
-
def to_xml(options={})
|
105
|
+
def to_xml(options = {})
|
90
106
|
options[:upcase_data_dictionary] = @upcase_data_dictionary unless @upcase_data_dictionary.nil?
|
91
107
|
Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
|
92
108
|
add_to_xml(xml, options)
|
@@ -124,8 +140,8 @@ module Mondrian
|
|
124
140
|
|
125
141
|
def xmlized_attributes(options)
|
126
142
|
# data dictionary values should be in uppercase if schema defined with :upcase_data_dictionary => true
|
127
|
-
# or by default when using Oracle or
|
128
|
-
upcase_attributes = if options[:upcase_data_dictionary].nil? && %w(oracle
|
143
|
+
# or by default when using Oracle or Snowflake driver (can be overridden by :upcase_data_dictionary => false)
|
144
|
+
upcase_attributes = if options[:upcase_data_dictionary].nil? && %w(oracle snowflake).include?(options[:driver]) ||
|
129
145
|
options[:upcase_data_dictionary]
|
130
146
|
self.class.data_dictionary_names
|
131
147
|
else
|
@@ -133,7 +149,10 @@ module Mondrian
|
|
133
149
|
end
|
134
150
|
hash = {}
|
135
151
|
@attributes.each do |attr, value|
|
136
|
-
|
152
|
+
# Support value calculation in parallel threads.
|
153
|
+
# value could be either Thread or a future object from concurrent-ruby
|
154
|
+
value = value.value if value.respond_to?(:value)
|
155
|
+
value = value.upcase if upcase_attributes.include?(attr) && value.is_a?(String)
|
137
156
|
hash[
|
138
157
|
# camelcase attribute name
|
139
158
|
attr.to_s.gsub(/_([^_]+)/){|m| $1.capitalize}
|
@@ -17,7 +17,7 @@ module Mondrian
|
|
17
17
|
|
18
18
|
def coffeescript_function(arguments_string, text)
|
19
19
|
# construct function to ensure that last expression is returned
|
20
|
-
coffee_text = "#{arguments_string} ->\n" << text.gsub(/^/,' ')
|
20
|
+
coffee_text = "#{arguments_string} ->\n" << text.gsub(/^/, ' ')
|
21
21
|
javascript_text = CoffeeScript.compile(coffee_text, :bare => true)
|
22
22
|
# remove function definition first and last lines
|
23
23
|
javascript_text = javascript_text.strip.lines.to_a[1..-2].join
|
@@ -67,7 +67,7 @@ module Mondrian
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def ruby_formatter_java_class_name(name)
|
70
|
-
"rubyobj.#{self.class.name.gsub('::','.')}.#{ruby_formatter_name_to_class_name(name)}"
|
70
|
+
"rubyobj.#{self.class.name.gsub('::', '.')}.#{ruby_formatter_name_to_class_name(name)}"
|
71
71
|
end
|
72
72
|
|
73
73
|
end
|
@@ -180,19 +180,21 @@ JS
|
|
180
180
|
add_method_signature("getSyntax", [Java::mondrian.olap.Syntax])
|
181
181
|
|
182
182
|
UDF_SCALAR_TYPES = {
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
183
|
+
'Numeric' => Java::mondrian.olap.type.NumericType,
|
184
|
+
'String' => Java::mondrian.olap.type.StringType,
|
185
|
+
'Boolean' => Java::mondrian.olap.type.BooleanType,
|
186
|
+
'DateTime' => Java::mondrian.olap.type.DateTimeType,
|
187
|
+
'Decimal' => Java::mondrian.olap.type.DecimalType,
|
188
|
+
'Scalar' => Java::mondrian.olap.type.ScalarType
|
189
189
|
}
|
190
190
|
UDF_OTHER_TYPES = {
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
191
|
+
'Member' => Java::mondrian.olap.type.MemberType::Unknown,
|
192
|
+
'Tuple' => Java::mondrian.olap.type.TupleType.new([].to_java(Java::mondrian.olap.type.Type)),
|
193
|
+
'Hierarchy' => Java::mondrian.olap.type.HierarchyType.new(nil, nil),
|
194
|
+
'Level' => Java::mondrian.olap.type.LevelType::Unknown
|
195
195
|
}
|
196
|
+
UDF_OTHER_TYPES['Set'] = UDF_OTHER_TYPES['MemberSet'] = Java::mondrian.olap.type.SetType.new(UDF_OTHER_TYPES['Member'])
|
197
|
+
UDF_OTHER_TYPES['TupleSet'] = Java::mondrian.olap.type.SetType.new(UDF_OTHER_TYPES['Tuple'])
|
196
198
|
|
197
199
|
def getParameterTypes
|
198
200
|
@parameterTypes ||= self.class.parameters.map{|p| get_java_type(p)}
|
@@ -214,7 +216,7 @@ JS
|
|
214
216
|
|
215
217
|
def execute(evaluator, arguments)
|
216
218
|
values = []
|
217
|
-
self.class.parameters.each_with_index do |p,i|
|
219
|
+
self.class.parameters.each_with_index do |p, i|
|
218
220
|
value = UDF_SCALAR_TYPES[p] ? arguments[i].evaluateScalar(evaluator) : arguments[i].evaluate(evaluator)
|
219
221
|
values << value
|
220
222
|
end
|
@@ -239,9 +241,12 @@ JS
|
|
239
241
|
end
|
240
242
|
|
241
243
|
def self.stringified_type(type)
|
242
|
-
|
243
|
-
|
244
|
-
|
244
|
+
type_as_string = stringify(type)
|
245
|
+
if UDF_SCALAR_TYPES[type_as_string] || UDF_OTHER_TYPES[type_as_string]
|
246
|
+
type_as_string
|
247
|
+
else
|
248
|
+
raise ArgumentError, "Invalid user defined function type #{type.inspect}"
|
249
|
+
end
|
245
250
|
end
|
246
251
|
|
247
252
|
def self.stringify(arg)
|
@@ -3,16 +3,22 @@ require "spec_helper"
|
|
3
3
|
describe "Connection role" do
|
4
4
|
|
5
5
|
describe "create connection" do
|
6
|
-
before(:
|
7
|
-
@
|
8
|
-
|
6
|
+
before(:all) do
|
7
|
+
@all_roles = [
|
8
|
+
@role_name = role_name = 'California manager',
|
9
|
+
@role_name2 = role_name2 = 'Dummy, with comma',
|
10
|
+
@simple_role_name = simple_role_name = 'USA manager',
|
11
|
+
@union_role_name = union_role_name = 'Union California manager',
|
12
|
+
@intermediate_union_role_name = intermediate_union_role_name = "Intermediate #{union_role_name}"
|
13
|
+
]
|
14
|
+
|
9
15
|
@schema = Mondrian::OLAP::Schema.define do
|
10
16
|
cube 'Sales' do
|
11
17
|
table 'sales'
|
12
18
|
dimension 'Gender', :foreign_key => 'customer_id' do
|
13
19
|
hierarchy :has_all => true, :primary_key => 'id' do
|
14
20
|
table 'customers'
|
15
|
-
level 'Gender', :column => 'gender', :unique_members => true
|
21
|
+
level 'Gender', :column => 'gender', :unique_members => true, :hide_member_if => 'IfBlankName'
|
16
22
|
end
|
17
23
|
end
|
18
24
|
dimension 'Customers', :foreign_key => 'customer_id' do
|
@@ -49,6 +55,26 @@ describe "Connection role" do
|
|
49
55
|
end
|
50
56
|
role role_name2
|
51
57
|
|
58
|
+
role simple_role_name do
|
59
|
+
schema_grant :access => 'none' do
|
60
|
+
cube_grant :cube => 'Sales', :access => 'all' do
|
61
|
+
hierarchy_grant :hierarchy => '[Customers]', :access => 'custom', :bottom_level => '[Customers].[State Province]' do
|
62
|
+
member_grant :member => '[Customers].[USA]', :access => 'all'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
role intermediate_union_role_name do
|
68
|
+
union do
|
69
|
+
role_usage role_name: simple_role_name
|
70
|
+
end
|
71
|
+
end
|
72
|
+
role union_role_name do
|
73
|
+
union do
|
74
|
+
role_usage role_name: intermediate_union_role_name
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
52
78
|
# to test that Role elements are generated before UserDefinedFunction
|
53
79
|
user_defined_function 'Factorial' do
|
54
80
|
ruby do
|
@@ -60,15 +86,22 @@ describe "Connection role" do
|
|
60
86
|
end
|
61
87
|
end
|
62
88
|
end
|
89
|
+
end
|
90
|
+
|
91
|
+
before(:each) do
|
63
92
|
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
|
64
93
|
end
|
65
94
|
|
95
|
+
after(:each) do
|
96
|
+
@olap.role_name = nil if @olap
|
97
|
+
end
|
98
|
+
|
66
99
|
it "should connect" do
|
67
100
|
@olap.should be_connected
|
68
101
|
end
|
69
102
|
|
70
103
|
it "should get available role names" do
|
71
|
-
@olap.available_role_names.should ==
|
104
|
+
@olap.available_role_names.sort.should == @all_roles.sort
|
72
105
|
end
|
73
106
|
|
74
107
|
it "should not get role name if not set" do
|
@@ -113,17 +146,40 @@ describe "Connection role" do
|
|
113
146
|
# end
|
114
147
|
|
115
148
|
it "should not get non-visible member when role name set in connection parameters" do
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
@cube.member('[Customers].[USA].[CA].[Los Angeles]').should be_nil
|
149
|
+
olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge schema: @schema, role: @role_name)
|
150
|
+
cube = olap.cube('Sales')
|
151
|
+
cube.member('[Customers].[USA].[CA].[Los Angeles]').should be_nil
|
120
152
|
end
|
121
153
|
|
122
154
|
it "should not get non-visible member when several role names set in connection parameters" do
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
155
|
+
olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge schema: @schema, roles: [@role_name, @role_name2])
|
156
|
+
cube = olap.cube('Sales')
|
157
|
+
cube.member('[Customers].[USA].[CA].[Los Angeles]').should be_nil
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should see members from ragged dimensions when using single role" do
|
161
|
+
# Workaround for a Mondrian bug which does not allow access to ragged dimensions when using single role.
|
162
|
+
# This syntax will create a union role with one role.
|
163
|
+
@olap.role_names = [@role_name]
|
164
|
+
cube = @olap.cube('Sales')
|
165
|
+
cube.member('[Customers].[USA].[CA].[Los Angeles]').should be_nil
|
166
|
+
cube.member('[Gender].[All Genders]').should_not be_nil
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should see members from ragged dimensions when using multiple roles" do
|
170
|
+
@olap.role_names = [@role_name, @role_name2]
|
171
|
+
cube = @olap.cube('Sales')
|
172
|
+
cube.member('[Customers].[USA].[CA].[Los Angeles]').should be_nil
|
173
|
+
cube.member('[Gender].[All Genders]').should_not be_nil
|
174
|
+
end
|
175
|
+
|
176
|
+
# Test patch for UnionRoleImpl getBottomLevelDepth method
|
177
|
+
it "should see member as drillable when using union of union role" do
|
178
|
+
@olap.role_names = [@union_role_name]
|
179
|
+
cube = @olap.cube('Sales')
|
180
|
+
cube.member('[Customers].[All Customers]').should be_drillable
|
181
|
+
cube.member('[Customers].[All Customers].[USA]').should be_drillable
|
182
|
+
cube.member('[Customers].[All Customers].[USA].[CA]').should_not be_drillable
|
127
183
|
end
|
128
184
|
|
129
185
|
end
|
data/spec/connection_spec.rb
CHANGED
@@ -45,13 +45,14 @@ describe "Connection" do
|
|
45
45
|
schema_field = @olap.raw_schema.getClass.getDeclaredField("schema")
|
46
46
|
schema_field.setAccessible(true)
|
47
47
|
private_schema = schema_field.get(@olap.raw_schema)
|
48
|
-
private_schema.getDialect.java_class.name.should == case MONDRIAN_DRIVER
|
48
|
+
private_schema.getDialect.java_class.name.should == case MONDRIAN_DRIVER.split('_').last
|
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 'luciddb' then 'mondrian.spi.impl.LucidDbDialect'
|
53
52
|
when 'mssql' then 'mondrian.spi.impl.MicrosoftSqlServerDialect'
|
54
53
|
when 'sqlserver' then 'mondrian.spi.impl.MicrosoftSqlServerDialect'
|
54
|
+
when 'vertica' then 'mondrian.spi.impl.VerticaDialect'
|
55
|
+
when 'snowflake' then 'mondrian.spi.impl.SnowflakeDialect'
|
55
56
|
end
|
56
57
|
end
|
57
58
|
|
@@ -0,0 +1,261 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Cube" do
|
4
|
+
before(:all) do
|
5
|
+
@schema = Mondrian::OLAP::Schema.define do
|
6
|
+
measures_caption 'Measures caption'
|
7
|
+
|
8
|
+
cube 'Sales' do
|
9
|
+
description 'Sales description'
|
10
|
+
caption 'Sales caption'
|
11
|
+
annotations :foo => 'bar'
|
12
|
+
table 'sales'
|
13
|
+
visible true
|
14
|
+
dimension 'Gender', :foreign_key => 'customer_id' do
|
15
|
+
description 'Gender description'
|
16
|
+
caption 'Gender caption'
|
17
|
+
visible true
|
18
|
+
hierarchy :has_all => true, :primary_key => 'id' do
|
19
|
+
description 'Gender hierarchy description'
|
20
|
+
caption 'Gender hierarchy caption'
|
21
|
+
all_member_name 'All Genders'
|
22
|
+
all_member_caption 'All Genders caption'
|
23
|
+
table 'customers'
|
24
|
+
visible true
|
25
|
+
level 'Gender', :column => 'gender', :unique_members => true,
|
26
|
+
:description => 'Gender level description', :caption => 'Gender level caption' do
|
27
|
+
visible true
|
28
|
+
# Dimension values SQL generated by caption_expression fails on PostgreSQL and MS SQL
|
29
|
+
if %w(mysql oracle).include?(MONDRIAN_DRIVER)
|
30
|
+
caption_expression do
|
31
|
+
sql "'dummy'"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
dimension 'Customers', :foreign_key => 'customer_id', :annotations => {:foo => 'bar'} do
|
38
|
+
hierarchy :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id', :annotations => {:foo => 'bar'} do
|
39
|
+
table 'customers'
|
40
|
+
level 'Country', :column => 'country', :unique_members => true, :annotations => {:foo => 'bar'}
|
41
|
+
level 'State Province', :column => 'state_province', :unique_members => true
|
42
|
+
level 'City', :column => 'city', :unique_members => false
|
43
|
+
level 'Name', :column => 'fullname', :unique_members => true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
calculated_member 'Non-USA', :annotations => {:foo => 'bar'} do
|
47
|
+
dimension 'Customers'
|
48
|
+
formula '[Customers].[All Customers] - [Customers].[USA]'
|
49
|
+
end
|
50
|
+
dimension 'Time', :foreign_key => 'time_id', :type => 'TimeDimension' do
|
51
|
+
hierarchy :has_all => false, :primary_key => 'id' do
|
52
|
+
table 'time'
|
53
|
+
level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true, :level_type => 'TimeYears'
|
54
|
+
level 'Quarter', :column => 'quarter', :unique_members => false, :level_type => 'TimeQuarters'
|
55
|
+
level 'Month', :column => 'month_of_year', :type => 'Numeric', :unique_members => false, :level_type => 'TimeMonths'
|
56
|
+
end
|
57
|
+
hierarchy 'Weekly', :has_all => false, :primary_key => 'id' do
|
58
|
+
table 'time'
|
59
|
+
level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true, :level_type => 'TimeYears'
|
60
|
+
level 'Week', :column => 'weak_of_year', :type => 'Numeric', :unique_members => false, :level_type => 'TimeWeeks'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
calculated_member 'Last week' do
|
64
|
+
hierarchy '[Time.Weekly]'
|
65
|
+
formula 'Tail([Time.Weekly].[Week].Members).Item(0)'
|
66
|
+
end
|
67
|
+
measure 'Unit Sales', :column => 'unit_sales', :aggregator => 'sum', :annotations => {:foo => 'bar'}
|
68
|
+
measure 'Store Sales', :column => 'store_sales', :aggregator => 'sum'
|
69
|
+
measure 'Store Cost', :column => 'store_cost', :aggregator => 'sum', :visible => false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Do not execute tests on analytical databases with slow individual inserts
|
76
|
+
describe 'cache', unless: %w(vertica snowflake).include?(MONDRIAN_DRIVER) do
|
77
|
+
def qt(name)
|
78
|
+
@connection.quote_table_name(name.to_s)
|
79
|
+
end
|
80
|
+
|
81
|
+
before(:all) do
|
82
|
+
@connection = ActiveRecord::Base.connection
|
83
|
+
@cube = @olap.cube('Sales')
|
84
|
+
@query = <<-SQL
|
85
|
+
SELECT {[Measures].[Store Cost], [Measures].[Store Sales]} ON COLUMNS
|
86
|
+
FROM [Sales]
|
87
|
+
WHERE ([Time].[2010].[Q1], [Customers].[USA].[CA])
|
88
|
+
SQL
|
89
|
+
|
90
|
+
case MONDRIAN_DRIVER
|
91
|
+
when 'mysql', 'jdbc_mysql', 'postgresql', 'oracle'
|
92
|
+
@connection.execute 'CREATE TABLE sales_copy AS SELECT * FROM sales'
|
93
|
+
when 'mssql', 'sqlserver'
|
94
|
+
# Use raw_connection.execute to avoid detecting this query as a SELECT query
|
95
|
+
# for which executeQuery JDBC method will fail
|
96
|
+
@connection.raw_connection.execute_update 'SELECT * INTO sales_copy FROM sales'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
after(:each) do
|
101
|
+
@connection.execute 'TRUNCATE TABLE sales'
|
102
|
+
@connection.execute 'INSERT INTO sales SELECT * FROM sales_copy'
|
103
|
+
|
104
|
+
@olap.flush_schema_cache
|
105
|
+
@olap.close
|
106
|
+
@olap.connect
|
107
|
+
end
|
108
|
+
|
109
|
+
after(:all) do
|
110
|
+
@connection.execute 'DROP TABLE sales_copy'
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'should clear cache for deleted data at lower level with segments' do
|
114
|
+
@olap.execute(@query).values.should == [6890.553, 11390.4]
|
115
|
+
@connection.execute <<-SQL
|
116
|
+
DELETE FROM sales
|
117
|
+
WHERE time_id IN (SELECT id
|
118
|
+
FROM #{qt :time}
|
119
|
+
WHERE the_year = 2010
|
120
|
+
AND quarter = 'Q1')
|
121
|
+
AND customer_id IN (SELECT id
|
122
|
+
FROM customers
|
123
|
+
WHERE country = 'USA'
|
124
|
+
AND state_province = 'CA'
|
125
|
+
AND city = 'Berkeley')
|
126
|
+
SQL
|
127
|
+
@cube.flush_region_cache_with_segments(%w(Time 2010 Q1), %w(Customers USA CA))
|
128
|
+
@olap.execute(@query).values.should == [6756.4296, 11156.28]
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'should clear cache for deleted data at same level with segments' do
|
132
|
+
@olap.execute(@query).values.should == [6890.553, 11390.4]
|
133
|
+
@connection.execute <<-SQL
|
134
|
+
DELETE FROM sales
|
135
|
+
WHERE time_id IN (SELECT id
|
136
|
+
FROM #{qt :time}
|
137
|
+
WHERE the_year = 2010
|
138
|
+
AND quarter = 'Q1')
|
139
|
+
AND customer_id IN (SELECT id
|
140
|
+
FROM customers
|
141
|
+
WHERE country = 'USA'
|
142
|
+
AND state_province = 'CA')
|
143
|
+
SQL
|
144
|
+
@cube.flush_region_cache_with_segments(%w(Time 2010 Q1), %w(Customers USA CA))
|
145
|
+
@olap.execute(@query).values.should == [nil, nil]
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should clear cache for update data at lower level with segments' do
|
149
|
+
@olap.execute(@query).values.should == [6890.553, 11390.4]
|
150
|
+
@connection.execute <<-SQL
|
151
|
+
UPDATE sales SET
|
152
|
+
store_sales = store_sales + 1,
|
153
|
+
store_cost = store_cost + 1
|
154
|
+
WHERE time_id IN (SELECT id
|
155
|
+
FROM #{qt :time}
|
156
|
+
WHERE the_year = 2010
|
157
|
+
AND quarter = 'Q1')
|
158
|
+
AND customer_id IN (SELECT id
|
159
|
+
FROM customers
|
160
|
+
WHERE country = 'USA'
|
161
|
+
AND state_province = 'CA'
|
162
|
+
AND city = 'Berkeley')
|
163
|
+
SQL
|
164
|
+
@cube.flush_region_cache_with_segments(%w(Time 2010 Q1), %w(Customers USA CA))
|
165
|
+
@olap.execute(@query).values.should == [6891.553, 11391.4]
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'should clear cache for update data at same level with segments' do
|
169
|
+
@olap.execute(@query).values.should == [6890.553, 11390.4]
|
170
|
+
@connection.execute <<-SQL
|
171
|
+
UPDATE sales SET
|
172
|
+
store_sales = store_sales + 1,
|
173
|
+
store_cost = store_cost + 1
|
174
|
+
WHERE time_id IN (SELECT id
|
175
|
+
FROM #{qt :time}
|
176
|
+
WHERE the_year = 2010
|
177
|
+
AND quarter = 'Q1')
|
178
|
+
AND customer_id IN (SELECT id
|
179
|
+
FROM customers
|
180
|
+
WHERE country = 'USA'
|
181
|
+
AND state_province = 'CA')
|
182
|
+
SQL
|
183
|
+
@cube.flush_region_cache_with_segments(%w(Time 2010 Q1), %w(Customers USA CA))
|
184
|
+
@olap.execute(@query).values.should == [6935.553, 11435.4]
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'should clear cache for deleted data at lower level with members' do
|
188
|
+
@olap.execute(@query).values.should == [6890.553, 11390.4]
|
189
|
+
@connection.execute <<-SQL
|
190
|
+
DELETE FROM sales
|
191
|
+
WHERE time_id IN (SELECT id
|
192
|
+
FROM #{qt :time}
|
193
|
+
WHERE the_year = 2010
|
194
|
+
AND quarter = 'Q1')
|
195
|
+
AND customer_id IN (SELECT id
|
196
|
+
FROM customers
|
197
|
+
WHERE country = 'USA'
|
198
|
+
AND state_province = 'CA'
|
199
|
+
AND city = 'Berkeley')
|
200
|
+
SQL
|
201
|
+
@cube.flush_region_cache_with_full_names('[Time].[2010].[Q1]', '[Customers].[USA].[CA]')
|
202
|
+
@olap.execute(@query).values.should == [6756.4296, 11156.28]
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'should clear cache for deleted data at same level with members' do
|
206
|
+
@olap.execute(@query).values.should == [6890.553, 11390.4]
|
207
|
+
@connection.execute <<-SQL
|
208
|
+
DELETE FROM sales
|
209
|
+
WHERE time_id IN (SELECT id
|
210
|
+
FROM #{qt :time}
|
211
|
+
WHERE the_year = 2010
|
212
|
+
AND quarter = 'Q1')
|
213
|
+
AND customer_id IN (SELECT id
|
214
|
+
FROM customers
|
215
|
+
WHERE country = 'USA'
|
216
|
+
AND state_province = 'CA')
|
217
|
+
SQL
|
218
|
+
@cube.flush_region_cache_with_full_names('[Time].[2010].[Q1]', '[Customers].[USA].[CA]')
|
219
|
+
@olap.execute(@query).values.should == [nil, nil]
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'should clear cache for update data at lower level with members' do
|
223
|
+
@olap.execute(@query).values.should == [6890.553, 11390.4]
|
224
|
+
@connection.execute <<-SQL
|
225
|
+
UPDATE sales SET
|
226
|
+
store_sales = store_sales + 1,
|
227
|
+
store_cost = store_cost + 1
|
228
|
+
WHERE time_id IN (SELECT id
|
229
|
+
FROM #{qt :time}
|
230
|
+
WHERE the_year = 2010
|
231
|
+
AND quarter = 'Q1')
|
232
|
+
AND customer_id IN (SELECT id
|
233
|
+
FROM customers
|
234
|
+
WHERE country = 'USA'
|
235
|
+
AND state_province = 'CA'
|
236
|
+
AND city = 'Berkeley')
|
237
|
+
SQL
|
238
|
+
@cube.flush_region_cache_with_full_names('[Time].[2010].[Q1]', '[Customers].[USA].[CA]')
|
239
|
+
@olap.execute(@query).values.should == [6891.553, 11391.4]
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'should clear cache for update data at same level with members' do
|
243
|
+
@olap.execute(@query).values.should == [6890.553, 11390.4]
|
244
|
+
@connection.execute <<-SQL
|
245
|
+
UPDATE sales SET
|
246
|
+
store_sales = store_sales + 1,
|
247
|
+
store_cost = store_cost + 1
|
248
|
+
WHERE time_id IN (SELECT id
|
249
|
+
FROM #{qt :time}
|
250
|
+
WHERE the_year = 2010
|
251
|
+
AND quarter = 'Q1')
|
252
|
+
AND customer_id IN (SELECT id
|
253
|
+
FROM customers
|
254
|
+
WHERE country = 'USA'
|
255
|
+
AND state_province = 'CA')
|
256
|
+
SQL
|
257
|
+
@cube.flush_region_cache_with_full_names('[Time].[2010].[Q1]', '[Customers].[USA].[CA]')
|
258
|
+
@olap.execute(@query).values.should == [6935.553, 11435.4]
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|