olap4r 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +40 -0
- data/README.md +69 -0
- data/Rakefile +43 -0
- data/VERSION +1 -0
- data/lib/olap4j.jar +0 -0
- data/lib/olap4r.rb +17 -0
- data/lib/olap4r/cellset.rb +59 -0
- data/lib/olap4r/connection.rb +161 -0
- data/lib/olap4r/query_builder.rb +131 -0
- data/lib/olap4r/rowset.rb +37 -0
- data/olap4r.gemspec +75 -0
- data/spec/config.yml.example +6 -0
- data/spec/connection_spec.rb +232 -0
- data/spec/fixtures/FoodMart.xml +802 -0
- data/spec/query_builder_spec.rb +270 -0
- data/spec/spec_helper.rb +18 -0
- metadata +151 -0
data/.document
ADDED
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
|
data/Gemfile.lock
ADDED
@@ -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)
|
data/README.md
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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
|
data/lib/olap4j.jar
ADDED
Binary file
|
data/lib/olap4r.rb
ADDED
@@ -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
|