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.
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