mondrian-olap 0.5.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|