mondrian-olap 0.1.0

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