olap4r 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.
@@ -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