mondrian-olap 0.4.0-java

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 (46) hide show
  1. data/.rspec +2 -0
  2. data/Changelog.md +60 -0
  3. data/Gemfile +21 -0
  4. data/LICENSE-Mondrian.html +259 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +302 -0
  7. data/RUNNING_TESTS.rdoc +66 -0
  8. data/Rakefile +48 -0
  9. data/VERSION +1 -0
  10. data/lib/mondrian-olap.rb +1 -0
  11. data/lib/mondrian/jars/commons-collections-3.1.jar +0 -0
  12. data/lib/mondrian/jars/commons-dbcp-1.2.1.jar +0 -0
  13. data/lib/mondrian/jars/commons-logging-1.0.4.jar +0 -0
  14. data/lib/mondrian/jars/commons-math-1.0.jar +0 -0
  15. data/lib/mondrian/jars/commons-pool-1.2.jar +0 -0
  16. data/lib/mondrian/jars/commons-vfs-1.0.jar +0 -0
  17. data/lib/mondrian/jars/eigenbase-properties.jar +0 -0
  18. data/lib/mondrian/jars/eigenbase-resgen.jar +0 -0
  19. data/lib/mondrian/jars/eigenbase-xom.jar +0 -0
  20. data/lib/mondrian/jars/javacup.jar +0 -0
  21. data/lib/mondrian/jars/log4j-1.2.8.jar +0 -0
  22. data/lib/mondrian/jars/log4j.properties +5 -0
  23. data/lib/mondrian/jars/mondrian.jar +0 -0
  24. data/lib/mondrian/jars/olap4j.jar +0 -0
  25. data/lib/mondrian/olap.rb +17 -0
  26. data/lib/mondrian/olap/connection.rb +201 -0
  27. data/lib/mondrian/olap/cube.rb +297 -0
  28. data/lib/mondrian/olap/error.rb +57 -0
  29. data/lib/mondrian/olap/query.rb +342 -0
  30. data/lib/mondrian/olap/result.rb +264 -0
  31. data/lib/mondrian/olap/schema.rb +378 -0
  32. data/lib/mondrian/olap/schema_element.rb +153 -0
  33. data/lib/mondrian/olap/schema_udf.rb +282 -0
  34. data/mondrian-olap.gemspec +128 -0
  35. data/spec/connection_role_spec.rb +130 -0
  36. data/spec/connection_spec.rb +72 -0
  37. data/spec/cube_spec.rb +318 -0
  38. data/spec/fixtures/MondrianTest.xml +134 -0
  39. data/spec/fixtures/MondrianTestOracle.xml +134 -0
  40. data/spec/mondrian_spec.rb +53 -0
  41. data/spec/query_spec.rb +807 -0
  42. data/spec/rake_tasks.rb +260 -0
  43. data/spec/schema_definition_spec.rb +1249 -0
  44. data/spec/spec_helper.rb +134 -0
  45. data/spec/support/matchers/be_like.rb +24 -0
  46. metadata +278 -0
@@ -0,0 +1,378 @@
1
+ require 'mondrian/olap/schema_element'
2
+
3
+ module Mondrian
4
+ module OLAP
5
+ # See http://mondrian.pentaho.com/documentation/schema.php for more detailed description
6
+ # of Mondrian Schema elements.
7
+ class Schema < SchemaElement
8
+ def initialize(name = nil, attributes = {}, &block)
9
+ name, attributes = self.class.pre_process_arguments(name, attributes)
10
+ pre_process_attributes(attributes)
11
+ super(name, attributes, &block)
12
+ end
13
+
14
+ def self.define(name = nil, attributes = {}, &block)
15
+ name, attributes = pre_process_arguments(name, attributes)
16
+ new(name || 'default', attributes, &block)
17
+ end
18
+
19
+ def define(name = nil, attributes = {}, &block)
20
+ name, attributes = self.class.pre_process_arguments(name, attributes)
21
+ pre_process_attributes(attributes)
22
+ @attributes[:name] = name || @attributes[:name] || 'default' # otherwise connection with empty name fails
23
+ instance_eval(&block) if block
24
+ self
25
+ end
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
+
33
+ private
34
+
35
+ def self.pre_process_arguments(name, attributes)
36
+ # if is called just with attributes hash and without name
37
+ if name.is_a?(Hash) && attributes.empty?
38
+ attributes = name
39
+ name = nil
40
+ end
41
+ [name, attributes]
42
+ end
43
+
44
+ def pre_process_attributes(attributes)
45
+ unless attributes[:upcase_data_dictionary].nil?
46
+ @upcase_data_dictionary = attributes.delete(:upcase_data_dictionary)
47
+ end
48
+ end
49
+
50
+ public
51
+
52
+ attributes :name, :description
53
+ elements :cube, :role, :user_defined_function
54
+
55
+ class Cube < SchemaElement
56
+ attributes :name, :description,
57
+ # The name of the measure that would be taken as the default measure of the cube.
58
+ :default_measure,
59
+ # Should the Fact table data for this Cube be cached by Mondrian or not.
60
+ # The default action is to cache the data.
61
+ :cache,
62
+ # Whether element is enabled - if true, then the Cube is realized otherwise it is ignored.
63
+ :enabled
64
+ elements :table, :view, :dimension, :measure, :calculated_member
65
+ end
66
+
67
+ class Table < SchemaElement
68
+ attributes :name, :schema, # Optional qualifier for table.
69
+ # Alias to be used with this table when it is used to form queries.
70
+ # If not specified, defaults to the table name, but in any case, must be unique within the schema.
71
+ # (You can use the same table in different hierarchies, but it must have different aliases.)
72
+ :alias
73
+ data_dictionary_names :name, :schema, :alias # values in XML will be uppercased when using Oracle driver
74
+ elements :agg_exclude, :agg_name, :agg_pattern, :sql
75
+ end
76
+
77
+ class View < SchemaElement
78
+ attributes :alias
79
+ data_dictionary_names :alias
80
+ # Defines a "table" using SQL query which can have different variants for different underlying databases
81
+ elements :sql
82
+ end
83
+
84
+ class Dimension < SchemaElement
85
+ attributes :name, :description,
86
+ # The dimension's type may be one of "Standard" or "Time".
87
+ # A time dimension will allow the use of the MDX time functions (WTD, YTD, QTD, etc.).
88
+ # Use a standard dimension if the dimension is not a time-related dimension.
89
+ # The default value is "Standard".
90
+ :type,
91
+ # The name of the column in the fact table which joins to the leaf level of this dimension.
92
+ # Required in a private Dimension or a DimensionUsage, but not in a public Dimension.
93
+ :foreign_key
94
+ data_dictionary_names :foreign_key # values in XML will be uppercased when using Oracle driver
95
+ elements :hierarchy
96
+ end
97
+
98
+ class Hierarchy < SchemaElement
99
+ attributes :name, :description,
100
+ # Whether this hierarchy has an 'all' member.
101
+ :has_all,
102
+ # Name of the 'all' member. If this attribute is not specified,
103
+ # the all member is named 'All hierarchyName', for example, 'All Store'.
104
+ :all_member_name,
105
+ # Name of the 'all' level. If this attribute is not specified,
106
+ # the all member is named '(All)'.
107
+ :all_level_name,
108
+ # The name of the column which identifies members, and which is referenced by rows in the fact table.
109
+ # If not specified, the key of the lowest level is used. See also Dimension foreign_key.
110
+ :primary_key,
111
+ # The name of the table which contains primary_key.
112
+ # If the hierarchy has only one table, defaults to that; it is required.
113
+ :primary_key_table,
114
+ # Should be set to the level (if such a level exists) at which depth it is known
115
+ # that all members have entirely unique rows, allowing SQL GROUP BY clauses to be completely eliminated from the query.
116
+ :unique_key_level_name
117
+ data_dictionary_names :primary_key, :primary_key_table # values in XML will be uppercased when using Oracle driver
118
+ elements :table, :join, :property, :level
119
+ end
120
+
121
+ class Join < SchemaElement
122
+ attributes :left_key, :right_key, :left_alias, :right_alias
123
+ data_dictionary_names :left_key, :right_key, :left_alias, :right_alias # values in XML will be uppercased when using Oracle driver
124
+ elements :table, :join
125
+ end
126
+
127
+ class Level < SchemaElement
128
+ attributes :name, :description,
129
+ # The name of the table that the column comes from.
130
+ # If this hierarchy is based upon just one table, defaults to the name of that table;
131
+ # otherwise, it is required.
132
+ :table,
133
+ # The name of the column which holds the unique identifier of this level.
134
+ :column,
135
+ # The name of the column which holds the user identifier of this level.
136
+ :name_column,
137
+ # The name of the column which holds member ordinals.
138
+ # If this column is not specified, the key column is used for ordering.
139
+ :ordinal_column,
140
+ # The name of the column which references the parent member in a parent-child hierarchy.
141
+ :parent_column,
142
+ # Value which identifies null parents in a parent-child hierarchy.
143
+ # Typical values are 'NULL' and '0'.
144
+ :null_parent_value,
145
+ # Indicates the type of this level's key column:
146
+ # String, Numeric, Integer, Boolean, Date, Time or Timestamp.
147
+ # When generating SQL statements, Mondrian encloses values for String columns in quotation marks,
148
+ # but leaves values for Integer and Numeric columns un-quoted.
149
+ # Date, Time, and Timestamp values are quoted according to the SQL dialect.
150
+ # For a SQL-compliant dialect, the values appear prefixed by their typename,
151
+ # for example, "DATE '2006-06-01'".
152
+ # Default value: 'String'
153
+ :type,
154
+ # Whether members are unique across all parents.
155
+ # For example, zipcodes are unique across all states.
156
+ # The first level's members are always unique.
157
+ # Default value: false
158
+ :unique_members,
159
+ # Whether this is a regular or a time-related level.
160
+ # The value makes a difference to time-related functions such as YTD (year-to-date).
161
+ # Default value: 'Regular'
162
+ :level_type,
163
+ # Condition which determines whether a member of this level is hidden.
164
+ # If a hierarchy has one or more levels with hidden members,
165
+ # then it is possible that not all leaf members are the same distance from the root,
166
+ # and it is termed a ragged hierarchy.
167
+ # Allowable values are: Never (a member always appears; the default);
168
+ # IfBlankName (a member doesn't appear if its name is null, empty or all whitespace);
169
+ # and IfParentsName (a member appears unless its name matches the parent's.
170
+ # Default value: 'Never'
171
+ :hide_member_if,
172
+ # The estimated number of members in this level. Setting this property improves the performance of
173
+ # MDSCHEMA_LEVELS, MDSCHEMA_HIERARCHIES and MDSCHEMA_DIMENSIONS XMLA requests
174
+ :approx_row_count
175
+ data_dictionary_names :table, :column, :name_column, :ordinal_column, :parent_column # values in XML will be uppercased when using Oracle driver
176
+ elements :key_expression, :name_expression, :ordinal_expression, :member_formatter, :property
177
+ end
178
+
179
+ class KeyExpression < SchemaElement
180
+ elements :sql
181
+ end
182
+
183
+ class NameExpression < SchemaElement
184
+ elements :sql
185
+ end
186
+
187
+ class OrdinalExpression < SchemaElement
188
+ elements :sql
189
+ end
190
+
191
+ class Sql < SchemaElement
192
+ def self.name
193
+ 'SQL'
194
+ end
195
+ attributes :dialect
196
+ content :text
197
+ end
198
+
199
+ class Property < SchemaElement
200
+ attributes :name, :description,
201
+ :column,
202
+ # Data type of this property: String, Numeric, Integer, Boolean, Date, Time or Timestamp.
203
+ :type,
204
+ # Should be set to true if the value of the property is functionally dependent on the level value.
205
+ # This permits the associated property column to be omitted from the GROUP BY clause
206
+ # (if the database permits columns in the SELECT that are not in the GROUP BY).
207
+ # This can be a significant performance enhancement on some databases, such as MySQL.
208
+ :depends_on_level_value
209
+ data_dictionary_names :column
210
+ elements :property_formatter
211
+ end
212
+
213
+ class Measure < SchemaElement
214
+ attributes :name, :description,
215
+ # Column which is source of this measure's values.
216
+ # If not specified, a measure expression must be specified.
217
+ :column,
218
+ # The datatype of this measure: String, Numeric, Integer, Boolean, Date, Time or Timestamp.
219
+ # The default datatype of a measure is 'Integer' if the measure's aggregator is 'Count', otherwise it is 'Numeric'.
220
+ :datatype,
221
+ # Aggregation function. Allowed values are "sum", "count", "min", "max", "avg", and "distinct-count".
222
+ :aggregator,
223
+ # Format string with which to format cells of this measure. For more details, see the mondrian.util.Format class.
224
+ :format_string,
225
+ # Whether this member is visible in the user-interface. Default true.
226
+ :visible
227
+ data_dictionary_names :column # values in XML will be uppercased when using Oracle driver
228
+ elements :measure_expression, :cell_formatter
229
+ end
230
+
231
+ class MeasureExpression < SchemaElement
232
+ elements :sql
233
+ end
234
+
235
+ class CalculatedMember < SchemaElement
236
+ attributes :name, :description,
237
+ # Name of the dimension which this member belongs to.
238
+ :dimension,
239
+ # Format string with which to format cells of this measure. For more details, see the mondrian.util.Format class.
240
+ :format_string,
241
+ # Whether this member is visible in the user-interface. Default true.
242
+ :visible
243
+ elements :formula, :calculated_member_property, :cell_formatter
244
+ end
245
+
246
+ class Formula < SchemaElement
247
+ content :text
248
+ end
249
+
250
+ class CalculatedMemberProperty < SchemaElement
251
+ attributes :name, :description,
252
+ # MDX expression which defines the value of this property. If the expression is a constant string, you could enclose it in quotes,
253
+ # or just specify the 'value' attribute instead.
254
+ :expression,
255
+ # Value of this property. If the value is not constant, specify the 'expression' attribute instead.
256
+ :value
257
+ end
258
+
259
+ class AggName < SchemaElement
260
+ attributes :name
261
+ data_dictionary_names :name
262
+ elements :agg_fact_count, :agg_measure, :agg_level, :agg_foreign_key
263
+ end
264
+
265
+ class AggFactCount < SchemaElement
266
+ attributes :column
267
+ data_dictionary_names :column
268
+ end
269
+
270
+ class AggMeasure < SchemaElement
271
+ attributes :name, :column
272
+ data_dictionary_names :column
273
+ end
274
+
275
+ class AggLevel < SchemaElement
276
+ attributes :name, :column
277
+ data_dictionary_names :column
278
+ end
279
+
280
+ class AggForeignKey < SchemaElement
281
+ attributes :fact_column, :agg_column
282
+ data_dictionary_names :fact_column, :agg_column
283
+ end
284
+
285
+ class AggIgnoreColumn < SchemaElement
286
+ attributes :column
287
+ data_dictionary_names :column
288
+ end
289
+
290
+ class AggPattern < SchemaElement
291
+ attributes :pattern
292
+ data_dictionary_names :pattern
293
+ elements :agg_fact_count, :agg_measure, :agg_level, :agg_foreign_key, :agg_exclude
294
+ end
295
+
296
+ class AggExclude < SchemaElement
297
+ attributes :name, :pattern, :ignorecase
298
+ data_dictionary_names :name, :pattern
299
+ end
300
+
301
+ class Role < SchemaElement
302
+ attributes :name
303
+ elements :schema_grant, :union
304
+ end
305
+
306
+ class SchemaGrant < SchemaElement
307
+ # access may be "all", "all_dimensions", "custom" or "none".
308
+ # If access is "all_dimensions", the role has access to all dimensions but still needs explicit access to cubes.
309
+ # If access is "custom", no access will be inherited by cubes for which no explicit rule is set.
310
+ # If access is "all_dimensions", an implicut access is given to all dimensions of the schema's cubes,
311
+ # provided the cube's access attribute is either "custom" or "all"
312
+ attributes :access
313
+ elements :cube_grant
314
+ end
315
+
316
+ class CubeGrant < SchemaElement
317
+ # access may be "all", "custom", or "none".
318
+ # If access is "custom", no access will be inherited by the dimensions of this cube,
319
+ # unless the parent SchemaGrant is set to "all_dimensions"
320
+ attributes :access,
321
+ # The unique name of the cube
322
+ :cube
323
+ elements :dimension_grant, :hierarchy_grant
324
+ end
325
+
326
+ class DimensionGrant < SchemaElement
327
+ # access may be "all", "custom" or "none".
328
+ # Note that a role is implicitly given access to a dimension when it is given "all" acess to a cube.
329
+ # If access is "custom", no access will be inherited by the hierarchies of this dimension.
330
+ # If the parent schema access is "all_dimensions", this timension will inherit access "all".
331
+ # See also the "all_dimensions" option of the "SchemaGrant" element.
332
+ attributes :access,
333
+ # The unique name of the dimension
334
+ :dimension
335
+ end
336
+
337
+ class HierarchyGrant < SchemaElement
338
+ # access may be "all", "custom" or "none".
339
+ # If access is "custom", you may also specify the attributes :top_level, :bottom_level, and the member grants.
340
+ # If access is "custom", the child levels of this hierarchy will not inherit access rights from this hierarchy,
341
+ # should there be no explicit rules defined for the said child level.
342
+ attributes :access,
343
+ # The unique name of the hierarchy
344
+ :hierarchy,
345
+ # Unique name of the highest level of the hierarchy from which this role is allowed to see members.
346
+ # May only be specified if the HierarchyGrant.access is "custom".
347
+ # If not specified, role can see members up to the top level.
348
+ :top_level,
349
+ # Unique name of the lowest level of the hierarchy from which this role is allowed to see members.
350
+ # May only be specified if the HierarchyGrant.access is "custom".
351
+ # If not specified, role can see members down to the leaf level.
352
+ :bottom_level,
353
+ # Policy which determines how cell values are calculated if not all of the children of the current cell
354
+ # are visible to the current role.
355
+ # Allowable values are "full" (the default), "partial", and "hidden".
356
+ :rollup_policy
357
+ elements :member_grant
358
+ end
359
+
360
+ class MemberGrant < SchemaElement
361
+ # The children of this member inherit that access.
362
+ # You can implicitly see a member if you can see any of its children.
363
+ attributes :access,
364
+ # The unique name of the member
365
+ :member
366
+ end
367
+
368
+ class Union < SchemaElement
369
+ elements :role_usage
370
+ end
371
+
372
+ class RoleUsage < SchemaElement
373
+ attributes :role_name
374
+ end
375
+
376
+ end
377
+ end
378
+ end
@@ -0,0 +1,153 @@
1
+ require 'nokogiri'
2
+
3
+ module Mondrian
4
+ module OLAP
5
+ class SchemaElement
6
+ def initialize(name = nil, attributes = {}, &block)
7
+ # if just attributes hash provided
8
+ if name.is_a?(Hash) && attributes == {}
9
+ attributes = name
10
+ name = nil
11
+ end
12
+ @attributes = {}
13
+ if name
14
+ if self.class.content
15
+ @content = name
16
+ else
17
+ @attributes[:name] = name
18
+ end
19
+ end
20
+ @attributes.merge!(attributes)
21
+ self.class.elements.each do |element|
22
+ instance_variable_set("@#{pluralize(element)}", [])
23
+ end
24
+ @xml_fragments = []
25
+ instance_eval(&block) if block
26
+ end
27
+
28
+ def self.attributes(*names)
29
+ names.each do |name|
30
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
31
+ def #{name}(*args)
32
+ if args.empty?
33
+ @attributes[:#{name}]
34
+ elsif args.size == 1
35
+ @attributes[:#{name}] = args[0]
36
+ else
37
+ raise ArgumentError, "too many arguments"
38
+ end
39
+ end
40
+ RUBY
41
+ end
42
+ end
43
+
44
+ def self.data_dictionary_names(*names)
45
+ return @data_dictionary_names || [] if names.empty?
46
+ @data_dictionary_names ||= []
47
+ @data_dictionary_names.concat(names)
48
+ end
49
+
50
+ def self.elements(*names)
51
+ return @elements || [] if names.empty?
52
+
53
+ @elements ||= []
54
+ @elements.concat(names)
55
+
56
+ names.each do |name|
57
+ attr_reader pluralize(name).to_sym
58
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
59
+ def #{name}(name=nil, attributes = {}, &block)
60
+ @#{pluralize(name)} << Schema::#{camel_case(name)}.new(name, attributes, &block)
61
+ end
62
+ RUBY
63
+ end
64
+ end
65
+
66
+ def self.content(type=nil)
67
+ return @content if type.nil?
68
+ @content = type
69
+ end
70
+
71
+ attr_reader :xml_fragments
72
+ def xml(string)
73
+ string = string.strip
74
+ fragment = Nokogiri::XML::DocumentFragment.parse(string)
75
+ raise ArgumentError, "Invalid XML fragment:\n#{string}" if fragment.children.empty?
76
+ @xml_fragments << string
77
+ end
78
+
79
+ def to_xml(options={})
80
+ options[:upcase_data_dictionary] = @upcase_data_dictionary unless @upcase_data_dictionary.nil?
81
+ Nokogiri::XML::Builder.new do |xml|
82
+ add_to_xml(xml, options)
83
+ end.to_xml
84
+ end
85
+
86
+ protected
87
+
88
+ def add_to_xml(xml, options)
89
+ if self.class.content
90
+ xml.send(tag_name(self.class.name), @content, xmlized_attributes(options))
91
+ else
92
+ xml.send(tag_name(self.class.name), xmlized_attributes(options)) do
93
+ self.class.elements.each do |element|
94
+ instance_variable_get("@#{pluralize(element)}").each {|item| item.add_to_xml(xml, options)}
95
+ end
96
+ @xml_fragments.each do |xml_fragment|
97
+ xml.send(:insert, Nokogiri::XML::DocumentFragment.parse(xml_fragment))
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ private
104
+
105
+ def xmlized_attributes(options)
106
+ # data dictionary values should be in uppercase if schema defined with :upcase_data_dictionary => true
107
+ # or by default when using Oracle or LucidDB driver (can be overridden by :upcase_data_dictionary => false)
108
+ upcase_attributes = if options[:upcase_data_dictionary].nil? && %w(oracle luciddb).include?(options[:driver]) ||
109
+ options[:upcase_data_dictionary]
110
+ self.class.data_dictionary_names
111
+ else
112
+ []
113
+ end
114
+ hash = {}
115
+ @attributes.each do |attr, value|
116
+ value = value.upcase if upcase_attributes.include?(attr)
117
+ hash[
118
+ # camelcase attribute name
119
+ attr.to_s.gsub(/_([^_]+)/){|m| $1.capitalize}
120
+ ] = value
121
+ end
122
+ hash
123
+ end
124
+
125
+ def self.pluralize(string)
126
+ string = string.to_s
127
+ case string
128
+ when /^(.*)y$/
129
+ "#{$1}ies"
130
+ else
131
+ "#{string}s"
132
+ end
133
+ end
134
+
135
+ def pluralize(string)
136
+ self.class.pluralize(string)
137
+ end
138
+
139
+ def self.camel_case(string)
140
+ string.to_s.split('_').map{|s| s.capitalize}.join('')
141
+ end
142
+
143
+ def camel_case(string)
144
+ self.class.camel_case(string)
145
+ end
146
+
147
+ def tag_name(string)
148
+ string.split('::').last << '_'
149
+ end
150
+ end
151
+
152
+ end
153
+ end