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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/Changelog.md +38 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +302 -0
  5. data/VERSION +1 -1
  6. data/lib/mondrian/jars/commons-collections-3.2.jar +0 -0
  7. data/lib/mondrian/jars/commons-logging-1.1.1.jar +0 -0
  8. data/lib/mondrian/jars/commons-math-1.1.jar +0 -0
  9. data/lib/mondrian/jars/eigenbase-properties-1.1.2.jar +0 -0
  10. data/lib/mondrian/jars/eigenbase-resgen-1.3.1.jar +0 -0
  11. data/lib/mondrian/jars/eigenbase-xom-1.3.1.jar +0 -0
  12. data/lib/mondrian/jars/{javacup.jar → javacup-10k.jar} +0 -0
  13. data/lib/mondrian/jars/log4j-1.2.14.jar +0 -0
  14. data/lib/mondrian/jars/mondrian.jar +0 -0
  15. data/lib/mondrian/jars/olap4j-1.0.1.539.jar +0 -0
  16. data/lib/mondrian/olap.rb +2 -1
  17. data/lib/mondrian/olap/connection.rb +163 -32
  18. data/lib/mondrian/olap/cube.rb +163 -24
  19. data/lib/mondrian/olap/error.rb +57 -0
  20. data/lib/mondrian/olap/query.rb +52 -17
  21. data/lib/mondrian/olap/result.rb +298 -6
  22. data/lib/mondrian/olap/schema.rb +220 -29
  23. data/lib/mondrian/olap/schema_element.rb +31 -11
  24. data/lib/mondrian/olap/schema_udf.rb +331 -0
  25. data/lib/mondrian/olap/version.rb +5 -0
  26. data/spec/connection_role_spec.rb +130 -0
  27. data/spec/connection_spec.rb +36 -1
  28. data/spec/cube_spec.rb +137 -7
  29. data/spec/fixtures/MondrianTest.xml +4 -4
  30. data/spec/mondrian_spec.rb +53 -0
  31. data/spec/query_spec.rb +294 -11
  32. data/spec/rake_tasks.rb +8 -8
  33. data/spec/schema_definition_spec.rb +845 -26
  34. data/spec/spec_helper.rb +26 -17
  35. data/spec/support/matchers/be_like.rb +2 -2
  36. metadata +296 -237
  37. data/.rspec +0 -2
  38. data/Gemfile +0 -18
  39. data/README.rdoc +0 -221
  40. data/RUNNING_TESTS.rdoc +0 -66
  41. data/Rakefile +0 -46
  42. data/lib/mondrian/jars/commons-collections-3.1.jar +0 -0
  43. data/lib/mondrian/jars/commons-logging-1.0.4.jar +0 -0
  44. data/lib/mondrian/jars/commons-math-1.0.jar +0 -0
  45. data/lib/mondrian/jars/eigenbase-properties.jar +0 -0
  46. data/lib/mondrian/jars/eigenbase-resgen.jar +0 -0
  47. data/lib/mondrian/jars/eigenbase-xom.jar +0 -0
  48. data/lib/mondrian/jars/log4j-1.2.8.jar +0 -0
  49. data/lib/mondrian/jars/olap4j.jar +0 -0
  50. data/mondrian-olap.gemspec +0 -126
@@ -1,4 +1,4 @@
1
- require 'nokogiri'
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
- %w(column row page section chapter).each_with_index do |axis, i|
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
@@ -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 &block if block
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
- elements :table, :view, :dimension, :measure, :calculated_member
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
- data_dictionary_names :table, :column, :name_column, :ordinal_column, :parent_column # values in XML will be uppercased when using Oracle driver
166
- elements :key_expression, :name_expression, :ordinal_expression, :property
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
- elements :formula, :calculated_member_property
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