mondrian-olap 0.1.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 (41) hide show
  1. data/.rspec +2 -0
  2. data/Gemfile +15 -0
  3. data/LICENSE-Mondrian.html +259 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.rdoc +219 -0
  6. data/RUNNING_TESTS.rdoc +41 -0
  7. data/Rakefile +46 -0
  8. data/VERSION +1 -0
  9. data/lib/mondrian-olap.rb +1 -0
  10. data/lib/mondrian/jars/commons-collections-3.1.jar +0 -0
  11. data/lib/mondrian/jars/commons-dbcp-1.2.1.jar +0 -0
  12. data/lib/mondrian/jars/commons-logging-1.0.4.jar +0 -0
  13. data/lib/mondrian/jars/commons-math-1.0.jar +0 -0
  14. data/lib/mondrian/jars/commons-pool-1.2.jar +0 -0
  15. data/lib/mondrian/jars/commons-vfs-1.0.jar +0 -0
  16. data/lib/mondrian/jars/eigenbase-properties.jar +0 -0
  17. data/lib/mondrian/jars/eigenbase-resgen.jar +0 -0
  18. data/lib/mondrian/jars/eigenbase-xom.jar +0 -0
  19. data/lib/mondrian/jars/javacup.jar +0 -0
  20. data/lib/mondrian/jars/log4j-1.2.8.jar +0 -0
  21. data/lib/mondrian/jars/log4j.properties +18 -0
  22. data/lib/mondrian/jars/mondrian.jar +0 -0
  23. data/lib/mondrian/jars/olap4j.jar +0 -0
  24. data/lib/mondrian/olap.rb +14 -0
  25. data/lib/mondrian/olap/connection.rb +122 -0
  26. data/lib/mondrian/olap/cube.rb +236 -0
  27. data/lib/mondrian/olap/query.rb +313 -0
  28. data/lib/mondrian/olap/result.rb +155 -0
  29. data/lib/mondrian/olap/schema.rb +158 -0
  30. data/lib/mondrian/olap/schema_element.rb +123 -0
  31. data/mondrian-olap.gemspec +116 -0
  32. data/spec/connection_spec.rb +56 -0
  33. data/spec/cube_spec.rb +259 -0
  34. data/spec/fixtures/MondrianTest.xml +128 -0
  35. data/spec/fixtures/MondrianTestOracle.xml +128 -0
  36. data/spec/query_spec.rb +582 -0
  37. data/spec/rake_tasks.rb +185 -0
  38. data/spec/schema_definition_spec.rb +345 -0
  39. data/spec/spec_helper.rb +67 -0
  40. data/spec/support/matchers/be_like.rb +24 -0
  41. metadata +217 -0
@@ -0,0 +1,41 @@
1
+ == Creating test database
2
+
3
+ By default unit tests use MySQL database but PostgreSQL and Oracle databases are supported as well. Set MONDRIAN_DRIVER environment variable to "mysql" (default), "postgresql" or "oracle" to specify database driver that should be used.
4
+
5
+ If using MySQL or PostgreSQL database then create database user mondrian_test with password mondrian_test, create database mondrian_test and grant full access to this database for mondrian_test user. By default it is assumed that database is located on localhost (can be overridden with DATABASE_HOST environment variable).
6
+
7
+ If using Oracle database then create database user mondrian_test with password mondrian_test. By default it is assumed that database orcl is located on localhost (can be overridden with DATABASE_NAME and DATABASE_HOST environment variables).
8
+
9
+ See spec/spec_helper.rb for details of default connection parameters and how to override them.
10
+
11
+ == Creating test data
12
+
13
+ Install necessary gems with
14
+
15
+ bundle install
16
+
17
+ Create tables with test data using
18
+
19
+ rake db:create_data
20
+
21
+ or specify which database driver to use
22
+
23
+ rake db:create_data MONDRIAN_DRIVER=mysql
24
+ rake db:create_data MONDRIAN_DRIVER=postgresql
25
+ rake db:create_data MONDRIAN_DRIVER=oracle
26
+
27
+ == Running tests
28
+
29
+ Run tests with
30
+
31
+ rake spec
32
+
33
+ or specify which database driver to use
34
+
35
+ rake spec MONDRIAN_DRIVER=mysql
36
+ rake spec MONDRIAN_DRIVER=postgresql
37
+ rake spec MONDRIAN_DRIVER=oracle
38
+
39
+ == JRuby versions
40
+
41
+ It is recommended to use RVM (http://rvm.beginrescueend.com) to run tests with different JRuby implementations. mondrian-olap has been tested with JRuby 1.5 and 1.6 release candidate versions.
@@ -0,0 +1,46 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+
11
+ require 'rake'
12
+
13
+ require 'jeweler'
14
+ Jeweler::Tasks.new do |gem|
15
+ gem.name = "mondrian-olap"
16
+ gem.summary = "JRuby API for Mondrian OLAP Java library"
17
+ gem.description = <<-EOS
18
+ JRuby gem for performing multidimensional queries of relational database data using Mondrian OLAP Java library
19
+ EOS
20
+ gem.email = "raimonds.simanovskis@gmail.com"
21
+ gem.homepage = "http://github.com/rsim/mondrian-olap"
22
+ gem.authors = ["Raimonds Simanovskis"]
23
+ end
24
+ Jeweler::RubygemsDotOrgTasks.new
25
+
26
+ require 'rspec/core/rake_task'
27
+ RSpec::Core::RakeTask.new(:spec)
28
+
29
+ RSpec::Core::RakeTask.new(:rcov) do |t|
30
+ t.rcov = true
31
+ t.rcov_opts = ['--exclude', '/Library,spec/']
32
+ end
33
+
34
+ task :default => :spec
35
+
36
+ require 'rake/rdoctask'
37
+ Rake::RDocTask.new do |rdoc|
38
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
39
+
40
+ rdoc.rdoc_dir = 'doc'
41
+ rdoc.title = "mondrian-olap #{version}"
42
+ rdoc.rdoc_files.include('README*')
43
+ rdoc.rdoc_files.include('lib/**/*.rb')
44
+ end
45
+
46
+ require 'spec/rake_tasks'
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1 @@
1
+ require 'mondrian/olap'
@@ -0,0 +1,18 @@
1
+ #
2
+ # Log4J Konfiguration
3
+ #
4
+ # - Logs errors on the console
5
+ #
6
+
7
+ log4j.rootLogger = ERROR, A1
8
+
9
+ # Logging to console
10
+ log4j.appender.A1 = org.apache.log4j.ConsoleAppender
11
+
12
+ # Logging message format
13
+ # %d{DATE} Datum im Format dd MMM YYYY HH:mm:ss,SSS
14
+ # %-5p Priorität der Meldung 5stellig
15
+ # %m Meldung
16
+ # %n Zeilenumbruch
17
+ log4j.appender.A1.layout=org.apache.log4j.PatternLayout
18
+ # log4j.appender.A1.layout.ConversionPattern=[JPivot] %d{DATE} %-5p [Session %X{SessionID}] %C#%M: %m%n
@@ -0,0 +1,14 @@
1
+ require 'java'
2
+
3
+ directory = File.expand_path("../jars", __FILE__)
4
+ Dir["#{directory}/*.jar"].each do |file|
5
+ require file
6
+ end
7
+
8
+ java.lang.System.setProperty("log4j.configuration", "file://#{directory}/log4j.properties")
9
+ # register Mondrian olap4j driver
10
+ Java::mondrian.olap4j.MondrianOlap4jDriver
11
+
12
+ %w(connection query result schema cube).each do |file|
13
+ require "mondrian/olap/#{file}"
14
+ end
@@ -0,0 +1,122 @@
1
+ module Mondrian
2
+ module OLAP
3
+ class Connection
4
+ def self.create(params)
5
+ connection = new(params)
6
+ connection.connect
7
+ connection
8
+ end
9
+
10
+ attr_reader :raw_connection, :raw_schema, :raw_schema_reader
11
+
12
+ def initialize(params={})
13
+ @params = params
14
+ @driver = params[:driver]
15
+ @connected = false
16
+ @raw_connection = nil
17
+ end
18
+
19
+ def connect
20
+ @raw_jdbc_connection = Java::JavaSql::DriverManager.getConnection(connection_string)
21
+ @raw_connection = @raw_jdbc_connection.unwrap(Java::OrgOlap4j::OlapConnection.java_class)
22
+ @raw_schema = @raw_connection.getSchema
23
+ @connected = true
24
+ true
25
+ end
26
+
27
+ def connected?
28
+ @connected
29
+ end
30
+
31
+ def close
32
+ @raw_connection.close
33
+ @connected = false
34
+ @raw_connection = @raw_jdbc_connection = nil
35
+ true
36
+ end
37
+
38
+ def execute(query_string)
39
+ statement = @raw_connection.prepareOlapStatement(query_string)
40
+ Result.new(self, statement.executeQuery())
41
+ end
42
+
43
+ def from(cube_name)
44
+ Query.from(self, cube_name)
45
+ end
46
+
47
+ def cube_names
48
+ @raw_schema.getCubes.map{|c| c.getName}
49
+ end
50
+
51
+ def cube(name)
52
+ Cube.get(self, name)
53
+ end
54
+
55
+ # Will affect only the next created connection. If it is necessary to clear all schema cache then
56
+ # flush_schema_cache should be called, then close and then new connection should be created.
57
+ def flush_schema_cache
58
+ unwrapped_connection = @raw_connection.unwrap(Java::MondrianOlap::Connection.java_class)
59
+ raw_cache_control = unwrapped_connection.getCacheControl(nil)
60
+ raw_cache_control.flushSchemaCache
61
+ end
62
+
63
+ private
64
+
65
+ def connection_string
66
+ "jdbc:mondrian:Jdbc=#{jdbc_uri};JdbcDrivers=#{jdbc_driver};" <<
67
+ (@params[:catalog] ? "Catalog=#{catalog_uri}" : "CatalogContent=#{catalog_content}")
68
+ end
69
+
70
+ def jdbc_uri
71
+ case @driver
72
+ when 'mysql', 'postgresql'
73
+ "jdbc:#{@driver}://#{@params[:host]}#{@params[:port] && ":#{@params[:port]}"}/#{@params[:database]}" <<
74
+ "?user=#{@params[:username]}&password=#{@params[:password]}"
75
+ when 'oracle'
76
+ # connection using TNS alias
77
+ if @params[:database] && !@params[:host] && !@params[:url] && ENV['TNS_ADMIN']
78
+ "jdbc:oracle:thin:#{@params[:username]}/#{@params[:password]}@#{@params[:database]}"
79
+ else
80
+ @params[:url] ||
81
+ "jdbc:oracle:thin:#{@params[:username]}/#{@params[:password]}" <<
82
+ "@#{@params[:host] || 'localhost'}:#{@params[:port] || 1521}:#{@params[:database]}"
83
+ end
84
+ else
85
+ raise ArgumentError, 'unknown JDBC driver'
86
+ end
87
+ end
88
+
89
+ def jdbc_driver
90
+ case @driver
91
+ when 'mysql'
92
+ 'com.mysql.jdbc.Driver'
93
+ when 'postgresql'
94
+ 'org.postgresql.Driver'
95
+ when 'oracle'
96
+ 'oracle.jdbc.OracleDriver'
97
+ else
98
+ raise ArgumentError, 'unknown JDBC driver'
99
+ end
100
+ end
101
+
102
+ def catalog_uri
103
+ if @params[:catalog]
104
+ "file://#{File.expand_path(@params[:catalog])}"
105
+ else
106
+ raise ArgumentError, 'missing catalog source'
107
+ end
108
+ end
109
+
110
+ def catalog_content
111
+ if @params[:catalog_content]
112
+ @params[:catalog_content]
113
+ elsif @params[:schema]
114
+ @params[:schema].to_xml(:driver => @driver)
115
+ else
116
+ raise ArgumentError, "Specify catalog with :catalog, :catalog_content or :schema option"
117
+ end
118
+ end
119
+
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,236 @@
1
+ module Mondrian
2
+ module OLAP
3
+ class Cube
4
+ def self.get(connection, name)
5
+ if raw_cube = connection.raw_schema.getCubes.get(name)
6
+ Cube.new(connection, raw_cube)
7
+ end
8
+ end
9
+
10
+ def initialize(connection, raw_cube)
11
+ @connection = connection
12
+ @raw_cube = raw_cube
13
+ end
14
+
15
+ def name
16
+ @name ||= @raw_cube.getName
17
+ end
18
+
19
+ def dimensions
20
+ @dimenstions ||= @raw_cube.getDimensions.map{|d| Dimension.new(self, d)}
21
+ end
22
+
23
+ def dimension_names
24
+ dimensions.map{|d| d.name}
25
+ end
26
+
27
+ def dimension(name)
28
+ dimensions.detect{|d| d.name == name}
29
+ end
30
+
31
+ def query
32
+ Query.from(@connection, name)
33
+ end
34
+
35
+ def member(full_name)
36
+ segment_names = Java::OrgOlap4jMdx::IdentifierNode.parseIdentifier(full_name).map do |segment|
37
+ segment.getName
38
+ end
39
+ member_by_segments(*segment_names)
40
+ end
41
+
42
+ def member_by_segments(*segment_names)
43
+ raw_member = @raw_cube.lookupMember(*segment_names)
44
+ raw_member && Member.new(raw_member)
45
+ end
46
+ end
47
+
48
+ class Dimension
49
+ def initialize(cube, raw_dimension)
50
+ @cube = cube
51
+ @raw_dimension = raw_dimension
52
+ end
53
+
54
+ attr_reader :cube
55
+
56
+ def name
57
+ @name ||= @raw_dimension.getName
58
+ end
59
+
60
+ def full_name
61
+ @full_name ||= @raw_dimension.getUniqueName
62
+ end
63
+
64
+ def hierarchies
65
+ @hierarchies ||= @raw_dimension.getHierarchies.map{|h| Hierarchy.new(self, h)}
66
+ end
67
+
68
+ def hierarchy_names
69
+ hierarchies.map{|h| h.name}
70
+ end
71
+
72
+ def hierarchy(name = nil)
73
+ name ||= self.name
74
+ hierarchies.detect{|h| h.name == name}
75
+ end
76
+
77
+ def measures?
78
+ @raw_dimension.getDimensionType == Java::OrgOlap4jMetadata::Dimension::Type::MEASURE
79
+ end
80
+
81
+ def dimension_type
82
+ case @raw_dimension.getDimensionType
83
+ when Java::OrgOlap4jMetadata::Dimension::Type::TIME
84
+ :time
85
+ when Java::OrgOlap4jMetadata::Dimension::Type::MEASURE
86
+ :measures
87
+ else
88
+ :standard
89
+ end
90
+ end
91
+ end
92
+
93
+ class Hierarchy
94
+ def initialize(dimension, raw_hierarchy)
95
+ @dimension = dimension
96
+ @raw_hierarchy = raw_hierarchy
97
+ end
98
+
99
+ def name
100
+ @name ||= @raw_hierarchy.getName
101
+ end
102
+
103
+ def levels
104
+ @levels = @raw_hierarchy.getLevels.map{|l| Level.new(self, l)}
105
+ end
106
+
107
+ def level(name)
108
+ levels.detect{|l| l.name == name}
109
+ end
110
+
111
+ def level_names
112
+ levels.map{|l| l.name}
113
+ end
114
+
115
+ def has_all?
116
+ @raw_hierarchy.hasAll
117
+ end
118
+
119
+ def all_member_name
120
+ has_all? ? @raw_hierarchy.getRootMembers.first.getName : nil
121
+ end
122
+
123
+ def all_member
124
+ has_all? ? Member.new(@raw_hierarchy.getRootMembers.first) : nil
125
+ end
126
+
127
+ def root_members
128
+ @raw_hierarchy.getRootMembers.map{|m| Member.new(m)}
129
+ end
130
+
131
+ def root_member_names
132
+ @raw_hierarchy.getRootMembers.map{|m| m.getName}
133
+ end
134
+
135
+ def root_member_full_names
136
+ @raw_hierarchy.getRootMembers.map{|m| m.getUniqueName}
137
+ end
138
+
139
+ def child_names(*parent_member_segment_names)
140
+ parent_member = if parent_member_segment_names.empty?
141
+ return root_member_names unless has_all?
142
+ all_member
143
+ else
144
+ @dimension.cube.member_by_segments(*parent_member_segment_names)
145
+ end
146
+ parent_member && parent_member.children.map{|m| m.name}
147
+ end
148
+ end
149
+
150
+ class Level
151
+ def initialize(hierarchy, raw_level)
152
+ @hierarchy = hierarchy
153
+ @raw_level = raw_level
154
+ end
155
+
156
+ def name
157
+ @name ||= @raw_level.getName
158
+ end
159
+
160
+ def depth
161
+ @raw_level.getDepth
162
+ end
163
+
164
+ def cardinality
165
+ @cardinality = @raw_level.getCardinality
166
+ end
167
+
168
+ def members_count
169
+ @members_count ||= begin
170
+ if cardinality >= 0
171
+ cardinality
172
+ else
173
+ @raw_level.getMembers.size
174
+ end
175
+ end
176
+ end
177
+
178
+ def members
179
+ @raw_level.getMembers.map{|m| Member.new(m)}
180
+ end
181
+ end
182
+
183
+ class Member
184
+ def initialize(raw_member)
185
+ @raw_member = raw_member
186
+ end
187
+
188
+ def name
189
+ @raw_member.getName
190
+ end
191
+
192
+ def full_name
193
+ @raw_member.getUniqueName
194
+ end
195
+
196
+ def calculated?
197
+ @raw_member.isCalculated
198
+ end
199
+
200
+ def drillable?
201
+ return false if calculated?
202
+ # @raw_member.getChildMemberCount > 0
203
+ # This hopefully is faster than counting actual child members
204
+ raw_level = @raw_member.getLevel
205
+ raw_levels = raw_level.getHierarchy.getLevels
206
+ raw_levels.indexOf(raw_level) < raw_levels.size - 1
207
+ end
208
+
209
+ def depth
210
+ @raw_member.getDepth
211
+ end
212
+
213
+ def children
214
+ @raw_member.getChildMembers.map{|m| Member.new(m)}
215
+ end
216
+
217
+ def descendants_at_level(level)
218
+ raw_level = @raw_member.getLevel
219
+ raw_levels = raw_level.getHierarchy.getLevels
220
+ current_level_index = raw_levels.indexOf(raw_level)
221
+ descendants_level_index = raw_levels.indexOfName(level)
222
+
223
+ return nil unless descendants_level_index > current_level_index
224
+
225
+ members = [self]
226
+ (descendants_level_index - current_level_index).times do
227
+ members = members.map do |member|
228
+ member.children
229
+ end.flatten
230
+ end
231
+ members
232
+ end
233
+
234
+ end
235
+ end
236
+ end