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