mondrian-olap 0.4.0-java

Sign up to get free protection for your applications and to get access to all the features.
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