mondrian-olap 0.3.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Changelog.md +38 -0
- data/LICENSE.txt +1 -1
- data/README.md +302 -0
- data/VERSION +1 -1
- data/lib/mondrian/jars/commons-collections-3.2.jar +0 -0
- data/lib/mondrian/jars/commons-logging-1.1.1.jar +0 -0
- data/lib/mondrian/jars/commons-math-1.1.jar +0 -0
- data/lib/mondrian/jars/eigenbase-properties-1.1.2.jar +0 -0
- data/lib/mondrian/jars/eigenbase-resgen-1.3.1.jar +0 -0
- data/lib/mondrian/jars/eigenbase-xom-1.3.1.jar +0 -0
- data/lib/mondrian/jars/{javacup.jar → javacup-10k.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.rb +2 -1
- data/lib/mondrian/olap/connection.rb +163 -32
- data/lib/mondrian/olap/cube.rb +163 -24
- data/lib/mondrian/olap/error.rb +57 -0
- data/lib/mondrian/olap/query.rb +52 -17
- data/lib/mondrian/olap/result.rb +298 -6
- data/lib/mondrian/olap/schema.rb +220 -29
- data/lib/mondrian/olap/schema_element.rb +31 -11
- data/lib/mondrian/olap/schema_udf.rb +331 -0
- data/lib/mondrian/olap/version.rb +5 -0
- data/spec/connection_role_spec.rb +130 -0
- data/spec/connection_spec.rb +36 -1
- data/spec/cube_spec.rb +137 -7
- data/spec/fixtures/MondrianTest.xml +4 -4
- data/spec/mondrian_spec.rb +53 -0
- data/spec/query_spec.rb +294 -11
- data/spec/rake_tasks.rb +8 -8
- data/spec/schema_definition_spec.rb +845 -26
- data/spec/spec_helper.rb +26 -17
- data/spec/support/matchers/be_like.rb +2 -2
- metadata +296 -237
- data/.rspec +0 -2
- data/Gemfile +0 -18
- data/README.rdoc +0 -221
- data/RUNNING_TESTS.rdoc +0 -66
- data/Rakefile +0 -46
- data/lib/mondrian/jars/commons-collections-3.1.jar +0 -0
- data/lib/mondrian/jars/commons-logging-1.0.4.jar +0 -0
- data/lib/mondrian/jars/commons-math-1.0.jar +0 -0
- data/lib/mondrian/jars/eigenbase-properties.jar +0 -0
- data/lib/mondrian/jars/eigenbase-resgen.jar +0 -0
- data/lib/mondrian/jars/eigenbase-xom.jar +0 -0
- data/lib/mondrian/jars/log4j-1.2.8.jar +0 -0
- data/lib/mondrian/jars/olap4j.jar +0 -0
- data/mondrian-olap.gemspec +0 -126
data/lib/mondrian/olap/result.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'bigdecimal'
|
2
2
|
|
3
3
|
module Mondrian
|
4
4
|
module OLAP
|
@@ -24,7 +24,8 @@ module Mondrian
|
|
24
24
|
@axis_members ||= axis_positions(:to_member)
|
25
25
|
end
|
26
26
|
|
27
|
-
|
27
|
+
AXIS_SYMBOLS = [:column, :row, :page, :section, :chapter]
|
28
|
+
AXIS_SYMBOLS.each_with_index do |axis, i|
|
28
29
|
define_method :"#{axis}_names" do
|
29
30
|
axis_names[i]
|
30
31
|
end
|
@@ -59,7 +60,7 @@ module Mondrian
|
|
59
60
|
def to_html(options = {})
|
60
61
|
case axes_count
|
61
62
|
when 1
|
62
|
-
builder = Nokogiri::XML::Builder.new do |doc|
|
63
|
+
builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |doc|
|
63
64
|
doc.table do
|
64
65
|
doc.tr do
|
65
66
|
column_full_names.each do |column_full_name|
|
@@ -76,7 +77,7 @@ module Mondrian
|
|
76
77
|
end
|
77
78
|
builder.doc.to_html
|
78
79
|
when 2
|
79
|
-
builder = Nokogiri::XML::Builder.new do |doc|
|
80
|
+
builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |doc|
|
80
81
|
doc.table do
|
81
82
|
doc.tr do
|
82
83
|
doc.th
|
@@ -102,6 +103,297 @@ module Mondrian
|
|
102
103
|
end
|
103
104
|
end
|
104
105
|
|
106
|
+
# Specify drill through cell position, for example, as
|
107
|
+
# :row => 0, :cell => 1
|
108
|
+
# Specify max returned rows with :max_rows parameter
|
109
|
+
# Specify returned fields (as list of MDX levels and measures) with :return parameter
|
110
|
+
# Specify measures which at least one should not be empty (NULL) with :nonempty parameter
|
111
|
+
def drill_through(params = {})
|
112
|
+
Error.wrap_native_exception do
|
113
|
+
cell_params = []
|
114
|
+
axes_count.times do |i|
|
115
|
+
axis_symbol = AXIS_SYMBOLS[i]
|
116
|
+
raise ArgumentError, "missing position #{axis_symbol.inspect}" unless axis_position = params[axis_symbol]
|
117
|
+
cell_params << Java::JavaLang::Integer.new(axis_position)
|
118
|
+
end
|
119
|
+
raw_cell = @raw_cell_set.getCell(cell_params)
|
120
|
+
DrillThrough.from_raw_cell(raw_cell, params)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class DrillThrough
|
125
|
+
def self.from_raw_cell(raw_cell, params = {})
|
126
|
+
max_rows = params[:max_rows] || -1
|
127
|
+
# workaround to avoid calling raw_cell.drillThroughInternal private method
|
128
|
+
# which fails when running inside TorqueBox
|
129
|
+
cell_field = raw_cell.java_class.declared_field('cell')
|
130
|
+
cell_field.accessible = true
|
131
|
+
rolap_cell = cell_field.value(raw_cell)
|
132
|
+
|
133
|
+
if params[:return] || rolap_cell.canDrillThrough
|
134
|
+
sql_statement = drill_through_internal(rolap_cell, params)
|
135
|
+
raw_result_set = sql_statement.getWrappedResultSet
|
136
|
+
new(raw_result_set)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def initialize(raw_result_set)
|
141
|
+
@raw_result_set = raw_result_set
|
142
|
+
end
|
143
|
+
|
144
|
+
def column_types
|
145
|
+
@column_types ||= (1..metadata.getColumnCount).map{|i| metadata.getColumnTypeName(i).to_sym}
|
146
|
+
end
|
147
|
+
|
148
|
+
def column_names
|
149
|
+
@column_names ||= begin
|
150
|
+
# if PostgreSQL then use getBaseColumnName as getColumnName returns empty string
|
151
|
+
if metadata.respond_to?(:getBaseColumnName)
|
152
|
+
(1..metadata.getColumnCount).map{|i| metadata.getBaseColumnName(i)}
|
153
|
+
else
|
154
|
+
(1..metadata.getColumnCount).map{|i| metadata.getColumnName(i)}
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def table_names
|
160
|
+
@table_names ||= begin
|
161
|
+
# if PostgreSQL then use getBaseTableName as getTableName returns empty string
|
162
|
+
if metadata.respond_to?(:getBaseTableName)
|
163
|
+
(1..metadata.getColumnCount).map{|i| metadata.getBaseTableName(i)}
|
164
|
+
else
|
165
|
+
(1..metadata.getColumnCount).map{|i| metadata.getTableName(i)}
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def column_labels
|
171
|
+
@column_labels ||= (1..metadata.getColumnCount).map{|i| metadata.getColumnLabel(i)}
|
172
|
+
end
|
173
|
+
|
174
|
+
def fetch
|
175
|
+
if @raw_result_set.next
|
176
|
+
row_values = []
|
177
|
+
column_types.each_with_index do |column_type, i|
|
178
|
+
row_values << Result.java_to_ruby_value(@raw_result_set.getObject(i+1), column_type)
|
179
|
+
end
|
180
|
+
row_values
|
181
|
+
else
|
182
|
+
@raw_result_set.close
|
183
|
+
nil
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def rows
|
188
|
+
@rows ||= begin
|
189
|
+
rows_values = []
|
190
|
+
while row_values = fetch
|
191
|
+
rows_values << row_values
|
192
|
+
end
|
193
|
+
rows_values
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
private
|
198
|
+
|
199
|
+
def metadata
|
200
|
+
@metadata ||= @raw_result_set.getMetaData
|
201
|
+
end
|
202
|
+
|
203
|
+
# modified RolapCell drillThroughInternal method
|
204
|
+
def self.drill_through_internal(rolap_cell, params)
|
205
|
+
max_rows = params[:max_rows] || -1
|
206
|
+
|
207
|
+
result_field = rolap_cell.java_class.declared_field('result')
|
208
|
+
result_field.accessible = true
|
209
|
+
result = result_field.value(rolap_cell)
|
210
|
+
|
211
|
+
sql = generate_drill_through_sql(rolap_cell, result, params)
|
212
|
+
|
213
|
+
# Choose the appropriate scrollability. If we need to start from an
|
214
|
+
# offset row, it is useful that the cursor is scrollable, but not
|
215
|
+
# essential.
|
216
|
+
statement = result.getExecution.getMondrianStatement
|
217
|
+
execution = Java::MondrianServer::Execution.new(statement, 0)
|
218
|
+
connection = statement.getMondrianConnection
|
219
|
+
result_set_type = Java::JavaSql::ResultSet::TYPE_FORWARD_ONLY
|
220
|
+
result_set_concurrency = Java::JavaSql::ResultSet::CONCUR_READ_ONLY
|
221
|
+
schema = statement.getSchema
|
222
|
+
dialect = schema.getDialect
|
223
|
+
|
224
|
+
Java::MondrianRolap::RolapUtil.executeQuery(
|
225
|
+
connection.getDataSource,
|
226
|
+
sql,
|
227
|
+
nil,
|
228
|
+
max_rows,
|
229
|
+
-1, # firstRowOrdinal
|
230
|
+
Java::MondrianRolap::SqlStatement::StatementLocus.new(
|
231
|
+
execution,
|
232
|
+
"RolapCell.drillThrough",
|
233
|
+
"Error in drill through",
|
234
|
+
Java::MondrianServerMonitor::SqlStatementEvent::Purpose::DRILL_THROUGH, 0
|
235
|
+
),
|
236
|
+
result_set_type,
|
237
|
+
result_set_concurrency,
|
238
|
+
nil
|
239
|
+
)
|
240
|
+
end
|
241
|
+
|
242
|
+
def self.generate_drill_through_sql(rolap_cell, result, params)
|
243
|
+
return_field_names, return_expressions, nonempty_columns = parse_return_fields(result, params)
|
244
|
+
|
245
|
+
sql_non_extended = rolap_cell.getDrillThroughSQL(return_expressions, false)
|
246
|
+
sql_extended = rolap_cell.getDrillThroughSQL(return_expressions, true)
|
247
|
+
|
248
|
+
if sql_non_extended =~ /\Aselect (.*) from (.*) where (.*) order by (.*)\Z/
|
249
|
+
non_extended_from = $2
|
250
|
+
non_extended_where = $3
|
251
|
+
else
|
252
|
+
raise ArgumentError, "cannot parse drill through SQL: #{sql_non_extended}"
|
253
|
+
end
|
254
|
+
|
255
|
+
if sql_extended =~ /\Aselect (.*) from (.*) where (.*) order by (.*)\Z/
|
256
|
+
extended_select = $1
|
257
|
+
extended_from = $2
|
258
|
+
extended_where = $3
|
259
|
+
extended_order_by = $4
|
260
|
+
# if only measures are selected then there will be no order by
|
261
|
+
elsif sql_extended =~ /\Aselect (.*) from (.*) where (.*)\Z/
|
262
|
+
extended_select = $1
|
263
|
+
extended_from = $2
|
264
|
+
extended_where = $3
|
265
|
+
extended_order_by = ''
|
266
|
+
else
|
267
|
+
raise ArgumentError, "cannot parse drill through SQL: #{sql_extended}"
|
268
|
+
end
|
269
|
+
|
270
|
+
return_column_positions = {}
|
271
|
+
|
272
|
+
if return_field_names && !return_field_names.empty?
|
273
|
+
new_select = extended_select.split(/,\s*/).map do |part|
|
274
|
+
column_name, column_alias = part.split(' as ')
|
275
|
+
field_name = column_alias[1..-2].gsub(' (Key)', '')
|
276
|
+
position = return_field_names.index(field_name) || 9999
|
277
|
+
return_column_positions[column_name] = position
|
278
|
+
[part, position]
|
279
|
+
end.sort_by(&:last).map(&:first).join(', ')
|
280
|
+
|
281
|
+
new_order_by = extended_order_by.split(/,\s*/).map do |part|
|
282
|
+
column_name, asc_desc = part.split(/\s+/)
|
283
|
+
position = return_column_positions[column_name] || 9999
|
284
|
+
[part, position]
|
285
|
+
end.sort_by(&:last).map(&:first).join(', ')
|
286
|
+
else
|
287
|
+
new_select = extended_select
|
288
|
+
new_order_by = extended_order_by
|
289
|
+
end
|
290
|
+
|
291
|
+
new_from_parts = non_extended_from.split(/,\s*/)
|
292
|
+
outer_join_from_parts = extended_from.split(/,\s*/) - new_from_parts
|
293
|
+
where_parts = extended_where.split(' and ')
|
294
|
+
|
295
|
+
# reverse outer_join_from_parts to support dimensions with several table joins
|
296
|
+
# where join with detailed level table should be constructed first
|
297
|
+
outer_join_from_parts.reverse.each do |part|
|
298
|
+
part_elements = part.split(/\s+/)
|
299
|
+
# first is original table, then optional 'as' and the last is alias
|
300
|
+
table_name = part_elements.first
|
301
|
+
table_alias = part_elements.last
|
302
|
+
join_conditions = where_parts.select do |where_part|
|
303
|
+
where_part.include?(" = #{table_alias}.")
|
304
|
+
end
|
305
|
+
outer_join = " left outer join #{part} on (#{join_conditions.join(' and ')})"
|
306
|
+
left_table_alias = join_conditions.first.split('.').first
|
307
|
+
|
308
|
+
if left_table_from_part = new_from_parts.detect{|from_part| from_part.include?(left_table_alias)}
|
309
|
+
left_table_from_part << outer_join
|
310
|
+
else
|
311
|
+
raise ArgumentError, "cannot extract outer join left table #{left_table_alias} in drill through SQL: #{sql_extended}"
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
new_from = new_from_parts.join(', ')
|
316
|
+
|
317
|
+
new_where = non_extended_where
|
318
|
+
if nonempty_columns && !nonempty_columns.empty?
|
319
|
+
not_null_condition = nonempty_columns.map{|c| "(#{c}) IS NOT NULL"}.join(' OR ')
|
320
|
+
new_where += " AND (#{not_null_condition})"
|
321
|
+
end
|
322
|
+
|
323
|
+
sql = "select #{new_select} from #{new_from} where #{new_where}"
|
324
|
+
sql << " order by #{new_order_by}" unless new_order_by.empty?
|
325
|
+
sql
|
326
|
+
end
|
327
|
+
|
328
|
+
def self.parse_return_fields(result, params)
|
329
|
+
return_field_names = []
|
330
|
+
return_expressions = nil
|
331
|
+
nonempty_columns = []
|
332
|
+
|
333
|
+
if params[:return] || params[:nonempty]
|
334
|
+
rolap_cube = result.getCube
|
335
|
+
schema_reader = rolap_cube.getSchemaReader
|
336
|
+
|
337
|
+
if return_fields = params[:return]
|
338
|
+
return_fields = return_fields.split(/,\s*/) if return_fields.is_a?(String)
|
339
|
+
return_expressions = return_fields.map do |return_field|
|
340
|
+
begin
|
341
|
+
segment_list = Java::MondrianOlap::Util.parseIdentifier(return_field)
|
342
|
+
return_field_names << segment_list.to_a.last.name
|
343
|
+
rescue Java::JavaLang::IllegalArgumentException
|
344
|
+
raise ArgumentError, "invalid return field #{return_field}"
|
345
|
+
end
|
346
|
+
|
347
|
+
level_or_member = schema_reader.lookupCompound rolap_cube, segment_list, false, 0
|
348
|
+
|
349
|
+
case level_or_member
|
350
|
+
when Java::MondrianOlap::Level
|
351
|
+
Java::MondrianMdx::LevelExpr.new level_or_member
|
352
|
+
when Java::MondrianOlap::Member
|
353
|
+
raise ArgumentError, "cannot use calculated member #{return_field} as return field" if level_or_member.isCalculated
|
354
|
+
Java::mondrian.mdx.MemberExpr.new level_or_member
|
355
|
+
else
|
356
|
+
raise ArgumentError, "return field #{return_field} should be level or measure"
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
if nonempty_fields = params[:nonempty]
|
362
|
+
nonempty_fields = nonempty_fields.split(/,\s*/) if nonempty_fields.is_a?(String)
|
363
|
+
nonempty_columns = nonempty_fields.map do |nonempty_field|
|
364
|
+
begin
|
365
|
+
segment_list = Java::MondrianOlap::Util.parseIdentifier(nonempty_field)
|
366
|
+
rescue Java::JavaLang::IllegalArgumentException
|
367
|
+
raise ArgumentError, "invalid return field #{return_field}"
|
368
|
+
end
|
369
|
+
member = schema_reader.lookupCompound rolap_cube, segment_list, false, 0
|
370
|
+
if member.is_a? Java::MondrianOlap::Member
|
371
|
+
raise ArgumentError, "cannot use calculated member #{return_field} as nonempty field" if member.isCalculated
|
372
|
+
sql_query = member.getStarMeasure.getSqlQuery
|
373
|
+
member.getStarMeasure.generateExprString(sql_query)
|
374
|
+
else
|
375
|
+
raise ArgumentError, "nonempty field #{return_field} should be measure"
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
[return_field_names, return_expressions, nonempty_columns]
|
382
|
+
end
|
383
|
+
|
384
|
+
end
|
385
|
+
|
386
|
+
def self.java_to_ruby_value(value, column_type = nil)
|
387
|
+
case value
|
388
|
+
when Numeric, String
|
389
|
+
value
|
390
|
+
when Java::JavaMath::BigDecimal
|
391
|
+
BigDecimal(value.to_s)
|
392
|
+
else
|
393
|
+
value
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
105
397
|
private
|
106
398
|
|
107
399
|
def axes
|
@@ -146,10 +438,10 @@ module Mondrian
|
|
146
438
|
recursive_values(value_method, axes_sequence, current_index + 1, cell_params)
|
147
439
|
end
|
148
440
|
else
|
149
|
-
@raw_cell_set.getCell(cell_params).send(value_method)
|
441
|
+
self.class.java_to_ruby_value(@raw_cell_set.getCell(cell_params).send(value_method))
|
150
442
|
end
|
151
443
|
end
|
152
444
|
|
153
445
|
end
|
154
446
|
end
|
155
|
-
end
|
447
|
+
end
|
data/lib/mondrian/olap/schema.rb
CHANGED
@@ -5,10 +5,10 @@ module Mondrian
|
|
5
5
|
# See http://mondrian.pentaho.com/documentation/schema.php for more detailed description
|
6
6
|
# of Mondrian Schema elements.
|
7
7
|
class Schema < SchemaElement
|
8
|
-
def initialize(name = nil, attributes = {}, &block)
|
8
|
+
def initialize(name = nil, attributes = {}, parent = nil, &block)
|
9
9
|
name, attributes = self.class.pre_process_arguments(name, attributes)
|
10
10
|
pre_process_attributes(attributes)
|
11
|
-
super(name, attributes, &block)
|
11
|
+
super(name, attributes, parent, &block)
|
12
12
|
end
|
13
13
|
|
14
14
|
def self.define(name = nil, attributes = {}, &block)
|
@@ -20,10 +20,16 @@ module Mondrian
|
|
20
20
|
name, attributes = self.class.pre_process_arguments(name, attributes)
|
21
21
|
pre_process_attributes(attributes)
|
22
22
|
@attributes[:name] = name || @attributes[:name] || 'default' # otherwise connection with empty name fails
|
23
|
-
instance_eval
|
23
|
+
instance_eval(&block) if block
|
24
24
|
self
|
25
25
|
end
|
26
26
|
|
27
|
+
def include_schema(shared_schema)
|
28
|
+
shared_schema.class.elements.each do |element|
|
29
|
+
instance_variable_get("@#{pluralize(element)}").concat shared_schema.send(pluralize(element))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
27
33
|
private
|
28
34
|
|
29
35
|
def self.pre_process_arguments(name, attributes)
|
@@ -43,11 +49,11 @@ module Mondrian
|
|
43
49
|
|
44
50
|
public
|
45
51
|
|
46
|
-
attributes :name, :description
|
47
|
-
elements :cube
|
52
|
+
attributes :name, :description, :measures_caption
|
53
|
+
elements :annotations, :dimension, :cube, :virtual_cube, :role, :user_defined_function
|
48
54
|
|
49
55
|
class Cube < SchemaElement
|
50
|
-
attributes :name, :description,
|
56
|
+
attributes :name, :description, :caption,
|
51
57
|
# The name of the measure that would be taken as the default measure of the cube.
|
52
58
|
:default_measure,
|
53
59
|
# Should the Fact table data for this Cube be cached by Mondrian or not.
|
@@ -55,7 +61,8 @@ module Mondrian
|
|
55
61
|
:cache,
|
56
62
|
# Whether element is enabled - if true, then the Cube is realized otherwise it is ignored.
|
57
63
|
:enabled
|
58
|
-
|
64
|
+
# always render xml fragment as the first element in XML output (by default it is added at the end)
|
65
|
+
elements :annotations, :xml, :table, :view, :dimension_usage, :dimension, :measure, :calculated_member
|
59
66
|
end
|
60
67
|
|
61
68
|
class Table < SchemaElement
|
@@ -70,12 +77,13 @@ module Mondrian
|
|
70
77
|
|
71
78
|
class View < SchemaElement
|
72
79
|
attributes :alias
|
80
|
+
data_dictionary_names :alias
|
73
81
|
# Defines a "table" using SQL query which can have different variants for different underlying databases
|
74
82
|
elements :sql
|
75
83
|
end
|
76
84
|
|
77
85
|
class Dimension < SchemaElement
|
78
|
-
attributes :name, :description,
|
86
|
+
attributes :name, :description, :caption,
|
79
87
|
# The dimension's type may be one of "Standard" or "Time".
|
80
88
|
# A time dimension will allow the use of the MDX time functions (WTD, YTD, QTD, etc.).
|
81
89
|
# Use a standard dimension if the dimension is not a time-related dimension.
|
@@ -85,16 +93,41 @@ module Mondrian
|
|
85
93
|
# Required in a private Dimension or a DimensionUsage, but not in a public Dimension.
|
86
94
|
:foreign_key
|
87
95
|
data_dictionary_names :foreign_key # values in XML will be uppercased when using Oracle driver
|
88
|
-
elements :hierarchy
|
96
|
+
elements :annotations, :hierarchy
|
97
|
+
end
|
98
|
+
|
99
|
+
class DimensionUsage < SchemaElement
|
100
|
+
attributes :name,
|
101
|
+
# Name of the source dimension. Must be a dimension in this schema. Case-sensitive.
|
102
|
+
:source,
|
103
|
+
# Name of the level to join to. If not specified, joins to the lowest level of the dimension.
|
104
|
+
:level,
|
105
|
+
# If present, then this is prepended to the Dimension column names
|
106
|
+
# during the building of collapse dimension aggregates allowing
|
107
|
+
# 1) different dimension usages to be disambiguated during aggregate table recognition and
|
108
|
+
# 2) multiple shared dimensions that have common column names to be disambiguated.
|
109
|
+
:usage_prefix,
|
110
|
+
# The name of the column in the fact table which joins to the leaf level of this dimension.
|
111
|
+
# Required in a private Dimension or a DimensionUsage, but not in a public Dimension.
|
112
|
+
:foreign_key
|
113
|
+
data_dictionary_names :usage_prefix, :foreign_key # values in XML will be uppercased when using Oracle driver
|
114
|
+
|
115
|
+
def initialize(name = nil, attributes = {}, parent = nil)
|
116
|
+
super
|
117
|
+
# by default specify :source as name
|
118
|
+
@attributes[:source] ||= name
|
119
|
+
end
|
89
120
|
end
|
90
121
|
|
91
122
|
class Hierarchy < SchemaElement
|
92
|
-
attributes :name, :description,
|
123
|
+
attributes :name, :description, :caption,
|
93
124
|
# Whether this hierarchy has an 'all' member.
|
94
125
|
:has_all,
|
95
126
|
# Name of the 'all' member. If this attribute is not specified,
|
96
127
|
# the all member is named 'All hierarchyName', for example, 'All Store'.
|
97
128
|
:all_member_name,
|
129
|
+
# A string being displayed instead as the all member's name
|
130
|
+
:all_member_caption,
|
98
131
|
# Name of the 'all' level. If this attribute is not specified,
|
99
132
|
# the all member is named '(All)'.
|
100
133
|
:all_level_name,
|
@@ -108,17 +141,25 @@ module Mondrian
|
|
108
141
|
# that all members have entirely unique rows, allowing SQL GROUP BY clauses to be completely eliminated from the query.
|
109
142
|
:unique_key_level_name
|
110
143
|
data_dictionary_names :primary_key, :primary_key_table # values in XML will be uppercased when using Oracle driver
|
111
|
-
elements :table, :join, :property, :level
|
144
|
+
elements :annotations, :table, :join, :view, :property, :level
|
145
|
+
|
146
|
+
def initialize(name = nil, attributes = {}, parent = nil)
|
147
|
+
super
|
148
|
+
# set :has_all => true if :all_member_name is set
|
149
|
+
if @attributes[:has_all].nil? && @attributes[:all_member_name]
|
150
|
+
@attributes[:has_all] = true
|
151
|
+
end
|
152
|
+
end
|
112
153
|
end
|
113
154
|
|
114
155
|
class Join < SchemaElement
|
115
|
-
attributes :left_key, :right_key
|
116
|
-
data_dictionary_names :left_key, :right_key # values in XML will be uppercased when using Oracle driver
|
117
|
-
elements :table
|
156
|
+
attributes :left_key, :right_key, :left_alias, :right_alias
|
157
|
+
data_dictionary_names :left_key, :right_key, :left_alias, :right_alias # values in XML will be uppercased when using Oracle driver
|
158
|
+
elements :table, :join
|
118
159
|
end
|
119
160
|
|
120
161
|
class Level < SchemaElement
|
121
|
-
attributes :name, :description,
|
162
|
+
attributes :name, :description, :caption,
|
122
163
|
# The name of the table that the column comes from.
|
123
164
|
# If this hierarchy is based upon just one table, defaults to the name of that table;
|
124
165
|
# otherwise, it is required.
|
@@ -132,6 +173,8 @@ module Mondrian
|
|
132
173
|
:ordinal_column,
|
133
174
|
# The name of the column which references the parent member in a parent-child hierarchy.
|
134
175
|
:parent_column,
|
176
|
+
# The name of the column which holds the caption for members
|
177
|
+
:caption_column,
|
135
178
|
# Value which identifies null parents in a parent-child hierarchy.
|
136
179
|
# Typical values are 'NULL' and '0'.
|
137
180
|
:null_parent_value,
|
@@ -140,11 +183,11 @@ module Mondrian
|
|
140
183
|
# When generating SQL statements, Mondrian encloses values for String columns in quotation marks,
|
141
184
|
# but leaves values for Integer and Numeric columns un-quoted.
|
142
185
|
# Date, Time, and Timestamp values are quoted according to the SQL dialect.
|
143
|
-
# For a SQL-compliant dialect, the values appear prefixed by their typename,
|
186
|
+
# For a SQL-compliant dialect, the values appear prefixed by their typename,
|
144
187
|
# for example, "DATE '2006-06-01'".
|
145
188
|
# Default value: 'String'
|
146
189
|
:type,
|
147
|
-
# Whether members are unique across all parents.
|
190
|
+
# Whether members are unique across all parents.
|
148
191
|
# For example, zipcodes are unique across all states.
|
149
192
|
# The first level's members are always unique.
|
150
193
|
# Default value: false
|
@@ -161,9 +204,20 @@ module Mondrian
|
|
161
204
|
# IfBlankName (a member doesn't appear if its name is null, empty or all whitespace);
|
162
205
|
# and IfParentsName (a member appears unless its name matches the parent's.
|
163
206
|
# Default value: 'Never'
|
164
|
-
:hide_member_if
|
165
|
-
|
166
|
-
|
207
|
+
:hide_member_if,
|
208
|
+
# The estimated number of members in this level. Setting this property improves the performance of
|
209
|
+
# MDSCHEMA_LEVELS, MDSCHEMA_HIERARCHIES and MDSCHEMA_DIMENSIONS XMLA requests
|
210
|
+
:approx_row_count
|
211
|
+
data_dictionary_names :table, :column, :name_column, :ordinal_column, :parent_column, :caption_column # values in XML will be uppercased when using Oracle driver
|
212
|
+
elements :annotations, :key_expression, :name_expression, :ordinal_expression, :caption_expression, :member_formatter, :property
|
213
|
+
|
214
|
+
def initialize(name = nil, attributes = {}, parent = nil)
|
215
|
+
super
|
216
|
+
# set :unique_members by default to true for first level and false for next levels
|
217
|
+
if @attributes[:unique_members].nil?
|
218
|
+
@attributes[:unique_members] = parent.levels.empty?
|
219
|
+
end
|
220
|
+
end
|
167
221
|
end
|
168
222
|
|
169
223
|
class KeyExpression < SchemaElement
|
@@ -178,6 +232,10 @@ module Mondrian
|
|
178
232
|
elements :sql
|
179
233
|
end
|
180
234
|
|
235
|
+
class CaptionExpression < SchemaElement
|
236
|
+
elements :sql
|
237
|
+
end
|
238
|
+
|
181
239
|
class Sql < SchemaElement
|
182
240
|
def self.name
|
183
241
|
'SQL'
|
@@ -187,7 +245,7 @@ module Mondrian
|
|
187
245
|
end
|
188
246
|
|
189
247
|
class Property < SchemaElement
|
190
|
-
attributes :name, :description,
|
248
|
+
attributes :name, :description, :caption,
|
191
249
|
:column,
|
192
250
|
# Data type of this property: String, Numeric, Integer, Boolean, Date, Time or Timestamp.
|
193
251
|
:type,
|
@@ -196,10 +254,12 @@ module Mondrian
|
|
196
254
|
# (if the database permits columns in the SELECT that are not in the GROUP BY).
|
197
255
|
# This can be a significant performance enhancement on some databases, such as MySQL.
|
198
256
|
:depends_on_level_value
|
257
|
+
data_dictionary_names :column
|
258
|
+
elements :property_formatter
|
199
259
|
end
|
200
260
|
|
201
261
|
class Measure < SchemaElement
|
202
|
-
attributes :name, :description,
|
262
|
+
attributes :name, :description, :caption,
|
203
263
|
# Column which is source of this measure's values.
|
204
264
|
# If not specified, a measure expression must be specified.
|
205
265
|
:column,
|
@@ -209,9 +269,17 @@ module Mondrian
|
|
209
269
|
# Aggregation function. Allowed values are "sum", "count", "min", "max", "avg", and "distinct-count".
|
210
270
|
:aggregator,
|
211
271
|
# Format string with which to format cells of this measure. For more details, see the mondrian.util.Format class.
|
212
|
-
:format_string
|
272
|
+
:format_string,
|
273
|
+
# Whether this member is visible in the user-interface. Default true.
|
274
|
+
:visible
|
213
275
|
data_dictionary_names :column # values in XML will be uppercased when using Oracle driver
|
214
|
-
elements :measure_expression
|
276
|
+
elements :annotations, :measure_expression, :cell_formatter
|
277
|
+
|
278
|
+
def initialize(name = nil, attributes = {}, parent = nil)
|
279
|
+
super
|
280
|
+
# by default set aggregator to sum
|
281
|
+
@attributes[:aggregator] ||= 'sum'
|
282
|
+
end
|
215
283
|
end
|
216
284
|
|
217
285
|
class MeasureExpression < SchemaElement
|
@@ -219,12 +287,14 @@ module Mondrian
|
|
219
287
|
end
|
220
288
|
|
221
289
|
class CalculatedMember < SchemaElement
|
222
|
-
attributes :name, :description,
|
290
|
+
attributes :name, :description, :caption,
|
223
291
|
# Name of the dimension which this member belongs to.
|
224
292
|
:dimension,
|
225
293
|
# Format string with which to format cells of this measure. For more details, see the mondrian.util.Format class.
|
226
|
-
:format_string
|
227
|
-
|
294
|
+
:format_string,
|
295
|
+
# Whether this member is visible in the user-interface. Default true.
|
296
|
+
:visible
|
297
|
+
elements :annotations, :formula, :calculated_member_property, :cell_formatter
|
228
298
|
end
|
229
299
|
|
230
300
|
class Formula < SchemaElement
|
@@ -232,7 +302,7 @@ module Mondrian
|
|
232
302
|
end
|
233
303
|
|
234
304
|
class CalculatedMemberProperty < SchemaElement
|
235
|
-
attributes :name, :description,
|
305
|
+
attributes :name, :description, :caption,
|
236
306
|
# MDX expression which defines the value of this property. If the expression is a constant string, you could enclose it in quotes,
|
237
307
|
# or just specify the 'value' attribute instead.
|
238
308
|
:expression,
|
@@ -240,6 +310,30 @@ module Mondrian
|
|
240
310
|
:value
|
241
311
|
end
|
242
312
|
|
313
|
+
class VirtualCube < SchemaElement
|
314
|
+
attributes :name, :description, :caption,
|
315
|
+
# The name of the measure that would be taken as the default measure of the cube.
|
316
|
+
:default_measure,
|
317
|
+
# Whether element is enabled - if true, then the VirtualCube is realized otherwise it is ignored.
|
318
|
+
:enabled
|
319
|
+
elements :annotations, :virtual_cube_dimension, :virtual_cube_measure, :calculated_member
|
320
|
+
end
|
321
|
+
|
322
|
+
class VirtualCubeDimension < SchemaElement
|
323
|
+
attributes :name,
|
324
|
+
# Name of the cube which the dimension belongs to, or unspecified if the dimension is shared
|
325
|
+
:cube_name
|
326
|
+
end
|
327
|
+
|
328
|
+
class VirtualCubeMeasure < SchemaElement
|
329
|
+
attributes :name,
|
330
|
+
# Name of the cube which the measure belongs to.
|
331
|
+
:cube_name,
|
332
|
+
# Whether this member is visible in the user-interface. Default true.
|
333
|
+
:visible
|
334
|
+
elements :annotations
|
335
|
+
end
|
336
|
+
|
243
337
|
class AggName < SchemaElement
|
244
338
|
attributes :name
|
245
339
|
data_dictionary_names :name
|
@@ -282,6 +376,103 @@ module Mondrian
|
|
282
376
|
data_dictionary_names :name, :pattern
|
283
377
|
end
|
284
378
|
|
379
|
+
class Role < SchemaElement
|
380
|
+
attributes :name
|
381
|
+
elements :schema_grant, :union
|
382
|
+
end
|
383
|
+
|
384
|
+
class SchemaGrant < SchemaElement
|
385
|
+
# access may be "all", "all_dimensions", "custom" or "none".
|
386
|
+
# If access is "all_dimensions", the role has access to all dimensions but still needs explicit access to cubes.
|
387
|
+
# If access is "custom", no access will be inherited by cubes for which no explicit rule is set.
|
388
|
+
# If access is "all_dimensions", an implicut access is given to all dimensions of the schema's cubes,
|
389
|
+
# provided the cube's access attribute is either "custom" or "all"
|
390
|
+
attributes :access
|
391
|
+
elements :cube_grant
|
392
|
+
end
|
393
|
+
|
394
|
+
class CubeGrant < SchemaElement
|
395
|
+
# access may be "all", "custom", or "none".
|
396
|
+
# If access is "custom", no access will be inherited by the dimensions of this cube,
|
397
|
+
# unless the parent SchemaGrant is set to "all_dimensions"
|
398
|
+
attributes :access,
|
399
|
+
# The unique name of the cube
|
400
|
+
:cube
|
401
|
+
elements :dimension_grant, :hierarchy_grant
|
402
|
+
end
|
403
|
+
|
404
|
+
class DimensionGrant < SchemaElement
|
405
|
+
# access may be "all", "custom" or "none".
|
406
|
+
# Note that a role is implicitly given access to a dimension when it is given "all" acess to a cube.
|
407
|
+
# If access is "custom", no access will be inherited by the hierarchies of this dimension.
|
408
|
+
# If the parent schema access is "all_dimensions", this timension will inherit access "all".
|
409
|
+
# See also the "all_dimensions" option of the "SchemaGrant" element.
|
410
|
+
attributes :access,
|
411
|
+
# The unique name of the dimension
|
412
|
+
:dimension
|
413
|
+
end
|
414
|
+
|
415
|
+
class HierarchyGrant < SchemaElement
|
416
|
+
# access may be "all", "custom" or "none".
|
417
|
+
# If access is "custom", you may also specify the attributes :top_level, :bottom_level, and the member grants.
|
418
|
+
# If access is "custom", the child levels of this hierarchy will not inherit access rights from this hierarchy,
|
419
|
+
# should there be no explicit rules defined for the said child level.
|
420
|
+
attributes :access,
|
421
|
+
# The unique name of the hierarchy
|
422
|
+
:hierarchy,
|
423
|
+
# Unique name of the highest level of the hierarchy from which this role is allowed to see members.
|
424
|
+
# May only be specified if the HierarchyGrant.access is "custom".
|
425
|
+
# If not specified, role can see members up to the top level.
|
426
|
+
:top_level,
|
427
|
+
# Unique name of the lowest level of the hierarchy from which this role is allowed to see members.
|
428
|
+
# May only be specified if the HierarchyGrant.access is "custom".
|
429
|
+
# If not specified, role can see members down to the leaf level.
|
430
|
+
:bottom_level,
|
431
|
+
# Policy which determines how cell values are calculated if not all of the children of the current cell
|
432
|
+
# are visible to the current role.
|
433
|
+
# Allowable values are "full" (the default), "partial", and "hidden".
|
434
|
+
:rollup_policy
|
435
|
+
elements :member_grant
|
436
|
+
end
|
437
|
+
|
438
|
+
class MemberGrant < SchemaElement
|
439
|
+
# The children of this member inherit that access.
|
440
|
+
# You can implicitly see a member if you can see any of its children.
|
441
|
+
attributes :access,
|
442
|
+
# The unique name of the member
|
443
|
+
:member
|
444
|
+
end
|
445
|
+
|
446
|
+
class Union < SchemaElement
|
447
|
+
elements :role_usage
|
448
|
+
end
|
449
|
+
|
450
|
+
class RoleUsage < SchemaElement
|
451
|
+
attributes :role_name
|
452
|
+
end
|
453
|
+
|
454
|
+
class Annotations < SchemaElement
|
455
|
+
elements :annotation
|
456
|
+
def initialize(name = nil, attributes = {}, parent = nil, &block)
|
457
|
+
if name.is_a?(Hash)
|
458
|
+
attributes = name
|
459
|
+
name = nil
|
460
|
+
end
|
461
|
+
if block_given?
|
462
|
+
super(name, attributes, parent, &block)
|
463
|
+
else
|
464
|
+
super(nil, {}, parent) do
|
465
|
+
attributes.each do |key, value|
|
466
|
+
annotation key.to_s, value.to_s
|
467
|
+
end
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
class Annotation < SchemaElement
|
474
|
+
content :text
|
475
|
+
end
|
285
476
|
end
|
286
477
|
end
|
287
|
-
end
|
478
|
+
end
|