mondrian-olap 0.3.0 → 0.5.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 +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
|