olap4r 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "jruby-openssl"
5
+ gem "rspec", "~> 2.11.0"
6
+ gem "yard", "~> 0.8.2.1"
7
+ gem "bundler", "~> 1.1.0"
8
+ gem "jeweler", "~> 1.8.4"
9
+ gem "olap4r-mondrian", "~>0.1.0"
10
+ gem "olap4r-xmla", "~>0.1.0"
11
+ end
@@ -0,0 +1,40 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ bouncy-castle-java (1.5.0146.1)
5
+ diff-lcs (1.1.3)
6
+ git (1.2.5)
7
+ jeweler (1.8.4)
8
+ bundler (~> 1.0)
9
+ git (>= 1.2.5)
10
+ rake
11
+ rdoc
12
+ jruby-openssl (0.7.7)
13
+ bouncy-castle-java (>= 1.5.0146.1)
14
+ json (1.7.3-java)
15
+ olap4r-mondrian (0.1.0)
16
+ olap4r-xmla (0.1.0)
17
+ rake (0.9.2.2)
18
+ rdoc (3.12)
19
+ json (~> 1.4)
20
+ rspec (2.11.0)
21
+ rspec-core (~> 2.11.0)
22
+ rspec-expectations (~> 2.11.0)
23
+ rspec-mocks (~> 2.11.0)
24
+ rspec-core (2.11.0)
25
+ rspec-expectations (2.11.1)
26
+ diff-lcs (~> 1.1.3)
27
+ rspec-mocks (2.11.0)
28
+ yard (0.8.2.1)
29
+
30
+ PLATFORMS
31
+ java
32
+
33
+ DEPENDENCIES
34
+ bundler (~> 1.1.0)
35
+ jeweler (~> 1.8.4)
36
+ jruby-openssl
37
+ olap4r-mondrian (~> 0.1.0)
38
+ olap4r-xmla (~> 0.1.0)
39
+ rspec (~> 2.11.0)
40
+ yard (~> 0.8.2.1)
@@ -0,0 +1,69 @@
1
+ # olap4r
2
+
3
+ ## Examples
4
+
5
+ Create connection:
6
+
7
+ connection = Olap::Connection.new "jdbc:mondrian:JdbcDrivers=com.mysql.jdbc.Driver;Jdbc=jdbc:mysql://localhost/mondrian_foodmart?user=root;Catalog=file:spec/fixtures/FoodMart.xml;"
8
+
9
+ Execute queries:
10
+
11
+ results = connection.execute "SELECT [Measures].[Unit Sales] ON COLUMNS, [Store] ON ROWS FROM [Sales]"
12
+ puts results
13
+ # => [["266,773"]]
14
+
15
+ Build a query:
16
+
17
+ query_builder.select(:columns, "[Store].[All Stores]", "[Store].[All Stores].CHILDREN").
18
+ select(:rows, "[Measures].[Unit Sales]", "[Measures].[Sales Count]").
19
+ from("[Sales]").
20
+ where("[Store Type].[All Store Types].[Supermarket]")
21
+ query_builder.to_s.should == "SELECT HIERARCHIZE(UNION([Store].[All Stores], [Store].[All Stores].CHILDREN)) ON COLUMNS, { [Measures].[Unit Sales], [Measures].[Sales Count] } ON ROWS FROM [Sales] WHERE ( [Store Type].[All Store Types].[Supermarket] )"
22
+
23
+ ## Releasing new version
24
+
25
+ Install required development toolset:
26
+
27
+ bundle install
28
+
29
+ Write tests, write code. If you're ready to release a new version __first__ commit your changes, then run:
30
+
31
+ rake version:bump:patch
32
+
33
+ To pull new gem into application switch to the same exact Ruby and gemset that you're using in your application, ie. if you're using `rvm jruby@myapp` for your app then use it to install this gem too.
34
+
35
+ bundle install
36
+ rake install
37
+
38
+ Switch to your application and run:
39
+
40
+ bundle update olap4r
41
+
42
+ ## Testing
43
+
44
+ Configure ``spec/config.yml`` using your JDBC / XML/A details (you'll find example in ``spec/config.yml.example``):
45
+
46
+ mondrian:
47
+ jdbc_driver_path: "/usr/local/Cellar/tomcat/7.0.6//libexec/common/endorsed/mysql-connector-java-5.1.15-bin"
48
+ connection_string: "jdbc:mondrian:JdbcDrivers=com.mysql.jdbc.Driver;Jdbc=jdbc:mysql://localhost/mondrian_foodmart?user=root;Catalog=file:spec/fixtures/FoodMart.xml;"
49
+
50
+ xmla:
51
+ connection_string: "jdbc:xmla:Server=http://127.0.0.1:8080/mondrian/xmla;Catalog=FoodMart;"
52
+
53
+ ``jdbc_driver_path`` is a path to the JAR file which you need as specified in the ``mondrian`` ``connection_string``.
54
+
55
+ ## Copyright
56
+
57
+ Copyright 2011-2012 Freeport Metrics Inc.
58
+
59
+ Licensed under the Apache License, Version 2.0 (the "License");
60
+ you may not use this file except in compliance with the License.
61
+ You may obtain a copy of the License at
62
+
63
+ http://www.apache.org/licenses/LICENSE-2.0
64
+
65
+ Unless required by applicable law or agreed to in writing, software
66
+ distributed under the License is distributed on an "AS IS" BASIS,
67
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
68
+ See the License for the specific language governing permissions and
69
+ limitations under the License.
@@ -0,0 +1,43 @@
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
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "olap4r"
16
+ gem.homepage = "http://github.com/Freeport-Metrics/olap4r"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{olap4j wrapper for JRuby}
19
+ gem.description = %Q{olap4j wrapper for JRuby}
20
+ gem.email = "filip@tepper.pl"
21
+ gem.authors = ["Filip Tepper"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rspec/core'
30
+ require 'rspec/core/rake_task'
31
+ RSpec::Core::RakeTask.new(:spec) do |spec|
32
+ spec.pattern = FileList['spec/**/*_spec.rb']
33
+ end
34
+
35
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
36
+ spec.pattern = 'spec/**/*_spec.rb'
37
+ spec.rcov = true
38
+ end
39
+
40
+ task :default => :spec
41
+
42
+ require 'yard'
43
+ YARD::Rake::YardocTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
Binary file
@@ -0,0 +1,17 @@
1
+ require "java"
2
+ require "olap4j"
3
+
4
+ java_import "java.sql.Connection"
5
+ java_import "java.sql.DriverManager"
6
+ java_import "org.olap4j.metadata.Property"
7
+ java_import "org.olap4j.OlapConnection"
8
+ java_import "org.olap4j.OlapDatabaseMetaData"
9
+ java_import "org.olap4j.transform.StandardTransformLibrary"
10
+
11
+ module Olap #:nodoc:
12
+ end
13
+
14
+ require "olap4r/connection"
15
+ require "olap4r/cellset"
16
+ require "olap4r/rowset"
17
+ require "olap4r/query_builder"
@@ -0,0 +1,59 @@
1
+ module Olap
2
+ class CellSet
3
+ def initialize(cellset) # :nodoc:
4
+ @cellset = cellset
5
+ end
6
+
7
+ # Returns list of axes
8
+ #
9
+ def axes
10
+ @axes ||= @cellset.get_axes.map do |axis|
11
+ {
12
+ :axis => axis.get_axis_ordinal.to_s.downcase.to_sym,
13
+ :values => axis.get_positions.map do |position|
14
+ position.get_members.inject [] do |members, member|
15
+ members << {
16
+ :name => member.get_caption,
17
+ :unique_name => member.get_unique_name,
18
+ :drillable => member.get_child_members.size > 0
19
+ }
20
+ end
21
+ end
22
+ }
23
+ end
24
+ end
25
+
26
+ # Returns query values
27
+ #
28
+ # ==== Attributes
29
+ #
30
+ # * +value_type+ - Returned value type (:value or :formatted_value)
31
+ #
32
+ def values(value_type = :formatted_value)
33
+ return @values unless @values.nil?
34
+
35
+ @values = []
36
+
37
+ raise "olap4r doesn't support queries with more than 2 dimensions" if @cellset.get_axes.size > 2
38
+
39
+ columns = @cellset.get_axes[0].get_positions.size - 1
40
+ rows = @cellset.get_axes[1].get_positions.size - 1
41
+
42
+ cells = []
43
+
44
+ (0..columns).each do |i|
45
+ (0..rows).each do |j|
46
+ cells << [i, j]
47
+ end
48
+ end
49
+
50
+ cells.each do |cell|
51
+ @values[cell[1]] = [] if @values[cell[1]].nil?
52
+ @values[cell[1]][cell[0]] = @cellset.get_cell(cell.map { |coord| coord.to_java(:int) }).send :"get_#{value_type}"
53
+ @values[cell[1]][cell[0]] = "0" if @values[cell[1]][cell[0]].respond_to?(:empty?) && @values[cell[1]][cell[0]].empty?
54
+ end
55
+
56
+ @values
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,161 @@
1
+ Java::JavaClass.for_name "org.olap4j.mdx.IdentifierNode"
2
+
3
+ module Olap #:nodoc:
4
+ class InvalidConnectionStringException < Exception; end
5
+ class InvalidOlapDriverException < Exception; end
6
+
7
+ class Connection
8
+
9
+ # Returns new OLAP connection.
10
+ #
11
+ # ==== Attributes
12
+ #
13
+ # * +connection_string+ - OLAP connection string.
14
+ #
15
+ # ==== Examples
16
+ #
17
+ # To create new connection provide a JDBC connection string:
18
+ #
19
+ # @olap = Olap::Connection.new "jdbc:mondrian:JdbcDrivers=com.mysql.jdbc.Driver;Jdbc=jdbc:mysql://127.0.0.1/olap?user=olap&password=olap;Catalog=file:/home/olap/schemas/Olap.xml;"
20
+ #
21
+ def initialize(connection_string)
22
+ begin
23
+ driver = connection_string.match(/\Ajdbc\:([A-Za-z]+)\:/)[1]
24
+ rescue NoMethodError
25
+ raise Olap::InvalidConnectionStringException.new
26
+ end
27
+
28
+ begin
29
+ raise
30
+ @connection = DriverManager.get_connection connection_string
31
+ rescue
32
+ begin
33
+ driver_class = Olap.const_get("#{driver.capitalize}")
34
+ driver = driver_class.jdbc_driver
35
+ properties = java.util.Properties.new
36
+ @connection = driver_initialize(driver).connect(connection_string, properties)
37
+ rescue NameError => e
38
+ raise Olap::InvalidOlapDriverException.new
39
+ end
40
+ end
41
+ end
42
+
43
+ # Executes regular MDX query.
44
+ #
45
+ # ==== Attributes
46
+ #
47
+ # * +query+ - MDX Query.
48
+ #
49
+ def execute(query)
50
+ CellSet.new @connection.create_statement.execute_olap_query(query.to_s)
51
+ end
52
+
53
+ # Executes drillthrough MDX query.
54
+ #
55
+ # ==== Attributes
56
+ #
57
+ # * +query+ - MDX Query.
58
+ #
59
+ def drillthrough(query)
60
+ RowSet.new @connection.create_statement.execute_query(query.to_s)
61
+ end
62
+
63
+ # Returns list of all cubes.
64
+ #
65
+ def cubes
66
+ @cubes ||= @connection.get_olap_schema.get_cubes.map do |cube|
67
+ {
68
+ :unique_name => cube.get_unique_name,
69
+ :name => cube.get_caption
70
+ }
71
+ end
72
+ end
73
+
74
+ # Returns list of all measures for cube.
75
+ #
76
+ # ==== Attributes
77
+ #
78
+ # * +cube_unique_name+ - Cube name
79
+ #
80
+ def measures(cube_unique_name)
81
+ @measures = {} if @measures.nil?
82
+
83
+ @measures[cube_unique_name] ||= cube(cube_unique_name).get_measures.map do |measure|
84
+ {
85
+ :unique_name => measure.get_unique_name,
86
+ :name => measure.get_caption
87
+ }
88
+ end
89
+ end
90
+
91
+ # Returns list of all dimensions for cube.
92
+ #
93
+ # ==== Attributes
94
+ #
95
+ # * +cube_unique_name+ - Cube name
96
+ #
97
+ def dimensions(cube_unique_name)
98
+ @dimensions = {} if @dimensions.nil?
99
+
100
+ @dimensions[cube_unique_name] ||= cube(cube_unique_name).get_dimensions.map do |dimension|
101
+ {
102
+ :unique_name => dimension.get_unique_name,
103
+ :name => dimension.get_caption,
104
+ :children => true,
105
+ :type => dimension.get_dimension_type.to_s.downcase.to_sym
106
+ }
107
+ end
108
+ end
109
+
110
+ # Returns list of member children
111
+ #
112
+ # ==== Attributes
113
+ #
114
+ # * +cube_unique_name+ - Cube name
115
+ # * +member+ - Root member element
116
+ # * +recursive+ - Recursive lookup
117
+ #
118
+ def children_lookup(cube_unique_name, member = nil, recursive = false)
119
+ return dimensions cube_unique_name if member.nil?
120
+
121
+ if member.split(".").length == 1
122
+ cube(cube_unique_name).get_dimensions.reject { |dimension| dimension.get_unique_name != member }.first.get_hierarchies.map { |hierarchy| hierarchy.get_root_members.map { |member| dimension(member, recursive)} }.flatten
123
+ else
124
+ children cube(cube_unique_name).send(:lookup_member, Java::OrgOlap4jMdx::IdentifierNode.parse_identifier(member).get_segment_list()), recursive
125
+ end
126
+ end
127
+
128
+
129
+ private
130
+
131
+
132
+ def driver_initialize(klass)
133
+ constructor = klass.java_class.declared_constructor
134
+ constructor.accessible = true
135
+
136
+ begin
137
+ return constructor.new_instance.to_java
138
+ rescue TypeError
139
+ false
140
+ end
141
+ end
142
+
143
+ def cube(cube_unique_name)
144
+ @connection.get_olap_schema.get_cubes.reject { |cube| cube.get_unique_name != cube_unique_name }.first
145
+ end
146
+
147
+ def dimension(member, recursive = false)
148
+ {
149
+ :unique_name => member.get_unique_name,
150
+ :name => member.get_property_value(Java::org::olap4j::metadata::Property::StandardMemberProperty::MEMBER_CAPTION),
151
+ :children => recursive ? children(member, recursive) : member.get_child_member_count > 0
152
+ }
153
+ end
154
+
155
+ def children(member, recursive = false)
156
+ member.get_child_members.inject([]) do |children, root_member|
157
+ children << dimension(root_member, recursive)
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,131 @@
1
+ module Olap
2
+ class QueryBuilder
3
+ def initialize
4
+ @select = { :columns => [], :rows => [] }
5
+ @from = nil
6
+ @conditions = []
7
+ end
8
+
9
+ def select axis, *fields
10
+ fields.flatten.each { |field| @select[axis] << field_as_string(field) }
11
+ self
12
+ end
13
+
14
+ def from cube
15
+ @from = cube
16
+ self
17
+ end
18
+
19
+ def where *conditions
20
+ conditions.flatten.each { |condition| @conditions << condition }
21
+ self
22
+ end
23
+
24
+ def to_s
25
+ build_query
26
+ end
27
+
28
+
29
+ private
30
+
31
+
32
+ def field_as_string field
33
+ if field.is_a?(Hash)
34
+ field_with_properties field[:id], field[:properties].map { |property| property.downcase.to_sym }
35
+ else
36
+ field
37
+ end
38
+ end
39
+
40
+ def field_with_properties field, properties
41
+ if properties.include?(:children)
42
+ field = "#{field}.CHILDREN"
43
+ end
44
+
45
+ if properties.include?(:drilldownlevel) && level(field) > 1
46
+ field = "DRILLDOWNLEVEL(#{field})"
47
+ end
48
+
49
+ field
50
+ end
51
+
52
+ def build_axis axis
53
+ return nil if @select[axis].empty?
54
+
55
+ hierarchies = {}
56
+ @select[axis].each do |field|
57
+ hierarchy = field.match(/\[([^\]]+)\]/)[0]
58
+
59
+ hierarchies[hierarchy] = [] if hierarchies[hierarchy].nil?
60
+ hierarchies[hierarchy].push field
61
+ end
62
+
63
+ if hierarchies.keys.length == 1
64
+ if hierarchies.keys[0] == "[Measures]" || @select[axis].length == 1
65
+ "{ #{@select[axis].join ", "} }"
66
+ else
67
+ "HIERARCHIZE(#{build_union @select[axis]})"
68
+ end
69
+ else
70
+ "HIERARCHIZE(#{build_crossjoin hierarchies.map { |hierarchy, fields| build_union(fields) }})"
71
+ end
72
+ end
73
+
74
+ def build_field field
75
+ # Case for dimensions - dimensions need to be wrapped as sets
76
+ if field.split(".").length == 1
77
+ "{ #{field} }"
78
+ else
79
+ field
80
+ end
81
+ end
82
+
83
+ def build_union fields
84
+ return "{ #{fields.join(", ")} }" if fields.length == 1
85
+
86
+ unionized = "UNION(#{build_field fields[0]}, #{build_field fields[1]})"
87
+
88
+ 2.upto(fields.length - 1) do |i|
89
+ unionized = "UNION(#{unionized}, #{build_field fields[i]})"
90
+ end
91
+
92
+ unionized
93
+ end
94
+
95
+ def build_crossjoin hierarchy
96
+ crossjoined = "CROSSJOIN(#{hierarchy[0]}, #{hierarchy[1]})"
97
+
98
+ 2.upto(hierarchy.length - 1) do |i|
99
+ crossjoined = "CROSSJOIN(#{crossjoined}, #{hierarchy[i]})"
100
+ end
101
+
102
+ crossjoined
103
+ end
104
+
105
+ def build_query
106
+ query = []
107
+
108
+ columns = build_axis :columns
109
+ rows = build_axis :rows
110
+
111
+ unless rows.nil? && columns.nil?
112
+ query << "SELECT"
113
+
114
+ fields = []
115
+ fields << "#{columns} ON COLUMNS" unless columns.nil?
116
+ fields << "#{rows} ON ROWS" unless rows.nil?
117
+
118
+ query << fields.join(", ")
119
+ end
120
+
121
+ query << "FROM #{@from}" unless @from.nil?
122
+ query << "WHERE ( #{@conditions.join ", "} )" if @conditions.any?
123
+
124
+ query.join " "
125
+ end
126
+
127
+ def level field
128
+ field.scan(/\[([^\]]*)\]/).length
129
+ end
130
+ end
131
+ end