Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

initial version

  • Loading branch information...
commit 426f14309e0cea6a8d4c58f24893e0a406031ea0 0 parents
Raimonds Simanovskis authored

Showing 35 changed files with 330,484 additions and 0 deletions. Show diff stats Hide diff stats

  1. 10  .gitignore
  2. 2  .rspec
  3. 12  Gemfile
  4. 259  LICENSE-Mondrian.html
  5. 22  LICENSE.txt
  6. 31  README.rdoc
  7. 41  Rakefile
  8. 1  lib/mondrian-olap.rb
  9. BIN  lib/mondrian/jars/commons-collections-3.1.jar
  10. BIN  lib/mondrian/jars/commons-dbcp-1.2.1.jar
  11. BIN  lib/mondrian/jars/commons-logging-1.0.4.jar
  12. BIN  lib/mondrian/jars/commons-math-1.0.jar
  13. BIN  lib/mondrian/jars/commons-pool-1.2.jar
  14. BIN  lib/mondrian/jars/commons-vfs-1.0.jar
  15. BIN  lib/mondrian/jars/eigenbase-properties.jar
  16. BIN  lib/mondrian/jars/eigenbase-resgen.jar
  17. BIN  lib/mondrian/jars/eigenbase-xom.jar
  18. BIN  lib/mondrian/jars/javacup.jar
  19. BIN  lib/mondrian/jars/log4j-1.2.8.jar
  20. 18  lib/mondrian/jars/log4j.properties
  21. BIN  lib/mondrian/jars/mondrian.jar
  22. BIN  lib/mondrian/jars/olap4j.jar
  23. 12  lib/mondrian/olap.rb
  24. 91  lib/mondrian/olap/connection.rb
  25. 140  lib/mondrian/olap/query.rb
  26. 107  lib/mondrian/olap/result.rb
  27. 145  lib/mondrian/olap/schema.rb
  28. 110  lib/mondrian/olap/schema_element.rb
  29. 70  spec/connection_spec.rb
  30. 802  spec/fixtures/FoodMart.xml
  31. 328,060  spec/fixtures/FoodMartCreateData.sql
  32. 252  spec/query_spec.rb
  33. 236  spec/schema_spec.rb
  34. 39  spec/spec_helper.rb
  35. 24  spec/support/matchers/be_like.rb
10  .gitignore
... ...
@@ -0,0 +1,10 @@
  1
+.bundle
  2
+.rvmrc
  3
+.DS_Store
  4
+coverage
  5
+doc
  6
+pkg
  7
+log
  8
+tmp
  9
+sqlnet.log
  10
+Gemfile.lock
2  .rspec
... ...
@@ -0,0 +1,2 @@
  1
+--color
  2
+--backtrace
12  Gemfile
... ...
@@ -0,0 +1,12 @@
  1
+source 'http://rubygems.org'
  2
+
  3
+gem 'jeweler'
  4
+gem 'rspec', '~> 2.0.0'
  5
+gem 'nokogiri', '>= 1.4.3'
  6
+gem 'jdbc-mysql'
  7
+gem 'jruby-openssl'
  8
+
  9
+group :test do
  10
+  gem 'activerecord', '~> 3.0.0'
  11
+  gem 'activerecord-jdbc-adapter'
  12
+end
259  LICENSE-Mondrian.html
... ...
@@ -0,0 +1,259 @@
  1
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  2
+<html xmlns="http://www.w3.org/1999/xhtml"><head>
  3
+
  4
+
  5
+
  6
+
  7
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
  8
+<title>Eclipse Public License - Version 1.0</title>
  9
+<style type="text/css">
  10
+  body {
  11
+    size: 8.5in 11.0in;
  12
+    margin: 0.25in 0.5in 0.25in 0.5in;
  13
+    tab-interval: 0.5in;
  14
+    }
  15
+  p {  	
  16
+    margin-left: auto;
  17
+    margin-top:  0.5em;
  18
+    margin-bottom: 0.5em;
  19
+    }
  20
+  p.list {
  21
+  	margin-left: 0.5in;
  22
+    margin-top:  0.05em;
  23
+    margin-bottom: 0.05em;
  24
+    }
  25
+  </style>
  26
+
  27
+</head><body lang="EN-US">
  28
+
  29
+<h2>Eclipse Public License - v 1.0</h2>
  30
+
  31
+<p>THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
  32
+PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR
  33
+DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS
  34
+AGREEMENT.</p>
  35
+
  36
+<p><b>1. DEFINITIONS</b></p>
  37
+
  38
+<p>"Contribution" means:</p>
  39
+
  40
+<p class="list">a) in the case of the initial Contributor, the initial
  41
+code and documentation distributed under this Agreement, and</p>
  42
+<p class="list">b) in the case of each subsequent Contributor:</p>
  43
+<p class="list">i) changes to the Program, and</p>
  44
+<p class="list">ii) additions to the Program;</p>
  45
+<p class="list">where such changes and/or additions to the Program
  46
+originate from and are distributed by that particular Contributor. A
  47
+Contribution 'originates' from a Contributor if it was added to the
  48
+Program by such Contributor itself or anyone acting on such
  49
+Contributor's behalf. Contributions do not include additions to the
  50
+Program which: (i) are separate modules of software distributed in
  51
+conjunction with the Program under their own license agreement, and (ii)
  52
+are not derivative works of the Program.</p>
  53
+
  54
+<p>"Contributor" means any person or entity that distributes
  55
+the Program.</p>
  56
+
  57
+<p>"Licensed Patents" mean patent claims licensable by a
  58
+Contributor which are necessarily infringed by the use or sale of its
  59
+Contribution alone or when combined with the Program.</p>
  60
+
  61
+<p>"Program" means the Contributions distributed in accordance
  62
+with this Agreement.</p>
  63
+
  64
+<p>"Recipient" means anyone who receives the Program under
  65
+this Agreement, including all Contributors.</p>
  66
+
  67
+<p><b>2. GRANT OF RIGHTS</b></p>
  68
+
  69
+<p class="list">a) Subject to the terms of this Agreement, each
  70
+Contributor hereby grants Recipient a non-exclusive, worldwide,
  71
+royalty-free copyright license to reproduce, prepare derivative works
  72
+of, publicly display, publicly perform, distribute and sublicense the
  73
+Contribution of such Contributor, if any, and such derivative works, in
  74
+source code and object code form.</p>
  75
+
  76
+<p class="list">b) Subject to the terms of this Agreement, each
  77
+Contributor hereby grants Recipient a non-exclusive, worldwide,
  78
+royalty-free patent license under Licensed Patents to make, use, sell,
  79
+offer to sell, import and otherwise transfer the Contribution of such
  80
+Contributor, if any, in source code and object code form. This patent
  81
+license shall apply to the combination of the Contribution and the
  82
+Program if, at the time the Contribution is added by the Contributor,
  83
+such addition of the Contribution causes such combination to be covered
  84
+by the Licensed Patents. The patent license shall not apply to any other
  85
+combinations which include the Contribution. No hardware per se is
  86
+licensed hereunder.</p>
  87
+
  88
+<p class="list">c) Recipient understands that although each Contributor
  89
+grants the licenses to its Contributions set forth herein, no assurances
  90
+are provided by any Contributor that the Program does not infringe the
  91
+patent or other intellectual property rights of any other entity. Each
  92
+Contributor disclaims any liability to Recipient for claims brought by
  93
+any other entity based on infringement of intellectual property rights
  94
+or otherwise. As a condition to exercising the rights and licenses
  95
+granted hereunder, each Recipient hereby assumes sole responsibility to
  96
+secure any other intellectual property rights needed, if any. For
  97
+example, if a third party patent license is required to allow Recipient
  98
+to distribute the Program, it is Recipient's responsibility to acquire
  99
+that license before distributing the Program.</p>
  100
+
  101
+<p class="list">d) Each Contributor represents that to its knowledge it
  102
+has sufficient copyright rights in its Contribution, if any, to grant
  103
+the copyright license set forth in this Agreement.</p>
  104
+
  105
+<p><b>3. REQUIREMENTS</b></p>
  106
+
  107
+<p>A Contributor may choose to distribute the Program in object code
  108
+form under its own license agreement, provided that:</p>
  109
+
  110
+<p class="list">a) it complies with the terms and conditions of this
  111
+Agreement; and</p>
  112
+
  113
+<p class="list">b) its license agreement:</p>
  114
+
  115
+<p class="list">i) effectively disclaims on behalf of all Contributors
  116
+all warranties and conditions, express and implied, including warranties
  117
+or conditions of title and non-infringement, and implied warranties or
  118
+conditions of merchantability and fitness for a particular purpose;</p>
  119
+
  120
+<p class="list">ii) effectively excludes on behalf of all Contributors
  121
+all liability for damages, including direct, indirect, special,
  122
+incidental and consequential damages, such as lost profits;</p>
  123
+
  124
+<p class="list">iii) states that any provisions which differ from this
  125
+Agreement are offered by that Contributor alone and not by any other
  126
+party; and</p>
  127
+
  128
+<p class="list">iv) states that source code for the Program is available
  129
+from such Contributor, and informs licensees how to obtain it in a
  130
+reasonable manner on or through a medium customarily used for software
  131
+exchange.</p>
  132
+
  133
+<p>When the Program is made available in source code form:</p>
  134
+
  135
+<p class="list">a) it must be made available under this Agreement; and</p>
  136
+
  137
+<p class="list">b) a copy of this Agreement must be included with each
  138
+copy of the Program.</p>
  139
+
  140
+<p>Contributors may not remove or alter any copyright notices contained
  141
+within the Program.</p>
  142
+
  143
+<p>Each Contributor must identify itself as the originator of its
  144
+Contribution, if any, in a manner that reasonably allows subsequent
  145
+Recipients to identify the originator of the Contribution.</p>
  146
+
  147
+<p><b>4. COMMERCIAL DISTRIBUTION</b></p>
  148
+
  149
+<p>Commercial distributors of software may accept certain
  150
+responsibilities with respect to end users, business partners and the
  151
+like. While this license is intended to facilitate the commercial use of
  152
+the Program, the Contributor who includes the Program in a commercial
  153
+product offering should do so in a manner which does not create
  154
+potential liability for other Contributors. Therefore, if a Contributor
  155
+includes the Program in a commercial product offering, such Contributor
  156
+("Commercial Contributor") hereby agrees to defend and
  157
+indemnify every other Contributor ("Indemnified Contributor")
  158
+against any losses, damages and costs (collectively "Losses")
  159
+arising from claims, lawsuits and other legal actions brought by a third
  160
+party against the Indemnified Contributor to the extent caused by the
  161
+acts or omissions of such Commercial Contributor in connection with its
  162
+distribution of the Program in a commercial product offering. The
  163
+obligations in this section do not apply to any claims or Losses
  164
+relating to any actual or alleged intellectual property infringement. In
  165
+order to qualify, an Indemnified Contributor must: a) promptly notify
  166
+the Commercial Contributor in writing of such claim, and b) allow the
  167
+Commercial Contributor to control, and cooperate with the Commercial
  168
+Contributor in, the defense and any related settlement negotiations. The
  169
+Indemnified Contributor may participate in any such claim at its own
  170
+expense.</p>
  171
+
  172
+<p>For example, a Contributor might include the Program in a commercial
  173
+product offering, Product X. That Contributor is then a Commercial
  174
+Contributor. If that Commercial Contributor then makes performance
  175
+claims, or offers warranties related to Product X, those performance
  176
+claims and warranties are such Commercial Contributor's responsibility
  177
+alone. Under this section, the Commercial Contributor would have to
  178
+defend claims against the other Contributors related to those
  179
+performance claims and warranties, and if a court requires any other
  180
+Contributor to pay any damages as a result, the Commercial Contributor
  181
+must pay those damages.</p>
  182
+
  183
+<p><b>5. NO WARRANTY</b></p>
  184
+
  185
+<p>EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
  186
+PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
  187
+OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION,
  188
+ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
  189
+OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
  190
+responsible for determining the appropriateness of using and
  191
+distributing the Program and assumes all risks associated with its
  192
+exercise of rights under this Agreement , including but not limited to
  193
+the risks and costs of program errors, compliance with applicable laws,
  194
+damage to or loss of data, programs or equipment, and unavailability or
  195
+interruption of operations.</p>
  196
+
  197
+<p><b>6. DISCLAIMER OF LIABILITY</b></p>
  198
+
  199
+<p>EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
  200
+NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
  201
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
  202
+WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
  203
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  204
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
  205
+DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
  206
+HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.</p>
  207
+
  208
+<p><b>7. GENERAL</b></p>
  209
+
  210
+<p>If any provision of this Agreement is invalid or unenforceable under
  211
+applicable law, it shall not affect the validity or enforceability of
  212
+the remainder of the terms of this Agreement, and without further action
  213
+by the parties hereto, such provision shall be reformed to the minimum
  214
+extent necessary to make such provision valid and enforceable.</p>
  215
+
  216
+<p>If Recipient institutes patent litigation against any entity
  217
+(including a cross-claim or counterclaim in a lawsuit) alleging that the
  218
+Program itself (excluding combinations of the Program with other
  219
+software or hardware) infringes such Recipient's patent(s), then such
  220
+Recipient's rights granted under Section 2(b) shall terminate as of the
  221
+date such litigation is filed.</p>
  222
+
  223
+<p>All Recipient's rights under this Agreement shall terminate if it
  224
+fails to comply with any of the material terms or conditions of this
  225
+Agreement and does not cure such failure in a reasonable period of time
  226
+after becoming aware of such noncompliance. If all Recipient's rights
  227
+under this Agreement terminate, Recipient agrees to cease use and
  228
+distribution of the Program as soon as reasonably practicable. However,
  229
+Recipient's obligations under this Agreement and any licenses granted by
  230
+Recipient relating to the Program shall continue and survive.</p>
  231
+
  232
+<p>Everyone is permitted to copy and distribute copies of this
  233
+Agreement, but in order to avoid inconsistency the Agreement is
  234
+copyrighted and may only be modified in the following manner. The
  235
+Agreement Steward reserves the right to publish new versions (including
  236
+revisions) of this Agreement from time to time. No one other than the
  237
+Agreement Steward has the right to modify this Agreement. The Eclipse
  238
+Foundation is the initial Agreement Steward. The Eclipse Foundation may
  239
+assign the responsibility to serve as the Agreement Steward to a
  240
+suitable separate entity. Each new version of the Agreement will be
  241
+given a distinguishing version number. The Program (including
  242
+Contributions) may always be distributed subject to the version of the
  243
+Agreement under which it was received. In addition, after a new version
  244
+of the Agreement is published, Contributor may elect to distribute the
  245
+Program (including its Contributions) under the new version. Except as
  246
+expressly stated in Sections 2(a) and 2(b) above, Recipient receives no
  247
+rights or licenses to the intellectual property of any Contributor under
  248
+this Agreement, whether expressly, by implication, estoppel or
  249
+otherwise. All rights in the Program not expressly granted under this
  250
+Agreement are reserved.</p>
  251
+
  252
+<p>This Agreement is governed by the laws of the State of New York and
  253
+the intellectual property laws of the United States of America. No party
  254
+to this Agreement will bring a legal action under this Agreement more
  255
+than one year after the cause of action arose. Each party waives its
  256
+rights to a jury trial in any resulting litigation.</p>
  257
+
  258
+</body></html>
  259
+
22  LICENSE.txt
... ...
@@ -0,0 +1,22 @@
  1
+(The MIT License)
  2
+
  3
+Copyright (c) 2010 Raimonds Simanovskis
  4
+
  5
+Permission is hereby granted, free of charge, to any person obtaining
  6
+a copy of this software and associated documentation files (the
  7
+'Software'), to deal in the Software without restriction, including
  8
+without limitation the rights to use, copy, modify, merge, publish,
  9
+distribute, sublicense, and/or sell copies of the Software, and to
  10
+permit persons to whom the Software is furnished to do so, subject to
  11
+the following conditions:
  12
+
  13
+The above copyright notice and this permission notice shall be
  14
+included in all copies or substantial portions of the Software.
  15
+
  16
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
  17
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  18
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  19
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  20
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  21
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  22
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31  README.rdoc
Source Rendered
... ...
@@ -0,0 +1,31 @@
  1
+= mondrian-olap
  2
+
  3
+Ruby DSL for Mondrian OLAP Java library
  4
+
  5
+== DESCRIPTION
  6
+
  7
+mondrian-olap provides OLAP queries from relational databases using Mondrian OLAP engine.
  8
+
  9
+== USAGE
  10
+
  11
+...
  12
+
  13
+== REQUIREMENTS:
  14
+
  15
+...
  16
+
  17
+== INSTALL:
  18
+
  19
+...
  20
+
  21
+== LINKS
  22
+
  23
+...
  24
+
  25
+
  26
+== LICENSE:
  27
+
  28
+mondrian-olap is released under the terms of MIT license; see LICENSE.txt.
  29
+
  30
+Mondrian OLAP Engine is released under the terms of the Eclipse Public
  31
+License v1.0 (EPL); see LICENSE-Mondrian.html.
41  Rakefile
... ...
@@ -0,0 +1,41 @@
  1
+require 'rubygems'
  2
+require 'rake'
  3
+
  4
+begin
  5
+  require 'jeweler'
  6
+  Jeweler::Tasks.new do |gem|
  7
+    gem.name = "mondrian-olap"
  8
+    gem.summary = "Ruby DSL for Mondrian OLAP Java library"
  9
+    gem.description = <<-EOS
  10
+mondrian-olap provides OLAP queries from relational databases using Mondrian OLAP engine.
  11
+EOS
  12
+    gem.email = "raimonds.simanovskis@gmail.com"
  13
+    gem.homepage = "http://github.com/rsim/mondrian-olap"
  14
+    gem.authors = ["Raimonds Simanovskis"]
  15
+  end
  16
+  Jeweler::GemcutterTasks.new
  17
+rescue LoadError
  18
+  puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
  19
+end
  20
+
  21
+require 'rspec/core/rake_task'
  22
+RSpec::Core::RakeTask.new(:spec)
  23
+
  24
+RSpec::Core::RakeTask.new(:rcov) do |t|
  25
+  t.rcov = true
  26
+  t.rcov_opts =  ['--exclude', '/Library,spec/']
  27
+end
  28
+
  29
+task :spec => :check_dependencies
  30
+
  31
+task :default => :spec
  32
+
  33
+require 'rake/rdoctask'
  34
+Rake::RDocTask.new do |rdoc|
  35
+  version = File.exist?('VERSION') ? File.read('VERSION') : ""
  36
+
  37
+  rdoc.rdoc_dir = 'doc'
  38
+  rdoc.title = "mondrian-olap #{version}"
  39
+  rdoc.rdoc_files.include('README*')
  40
+  rdoc.rdoc_files.include('lib/**/*.rb')
  41
+end
1  lib/mondrian-olap.rb
... ...
@@ -0,0 +1 @@
  1
+require 'mondrian/olap'
BIN  lib/mondrian/jars/commons-collections-3.1.jar
Binary file not shown
BIN  lib/mondrian/jars/commons-dbcp-1.2.1.jar
Binary file not shown
BIN  lib/mondrian/jars/commons-logging-1.0.4.jar
Binary file not shown
BIN  lib/mondrian/jars/commons-math-1.0.jar
Binary file not shown
BIN  lib/mondrian/jars/commons-pool-1.2.jar
Binary file not shown
BIN  lib/mondrian/jars/commons-vfs-1.0.jar
Binary file not shown
BIN  lib/mondrian/jars/eigenbase-properties.jar
Binary file not shown
BIN  lib/mondrian/jars/eigenbase-resgen.jar
Binary file not shown
BIN  lib/mondrian/jars/eigenbase-xom.jar
Binary file not shown
BIN  lib/mondrian/jars/javacup.jar
Binary file not shown
BIN  lib/mondrian/jars/log4j-1.2.8.jar
Binary file not shown
18  lib/mondrian/jars/log4j.properties
... ...
@@ -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
BIN  lib/mondrian/jars/mondrian.jar
Binary file not shown
BIN  lib/mondrian/jars/olap4j.jar
Binary file not shown
12  lib/mondrian/olap.rb
... ...
@@ -0,0 +1,12 @@
  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
+
  10
+%w(connection query result schema).each do |file|
  11
+  require "mondrian/olap/#{file}"
  12
+end
91  lib/mondrian/olap/connection.rb
... ...
@@ -0,0 +1,91 @@
  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
  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_connection = Java::mondrian.olap.DriverManager.getConnection(connection_string, nil)
  21
+        @connected = true
  22
+        true
  23
+      end
  24
+
  25
+      def connected?
  26
+        @connected
  27
+      end
  28
+
  29
+      def close
  30
+        @raw_connection.close
  31
+        @connected = false
  32
+        @raw_connection = nil
  33
+        true
  34
+      end
  35
+
  36
+      def execute(query_string)
  37
+        query = @raw_connection.parseQuery(query_string)
  38
+        Result.new(@raw_connection.execute(query))
  39
+      end
  40
+
  41
+      def from(cube_name)
  42
+        Query.from(self, cube_name)
  43
+      end
  44
+
  45
+      private
  46
+
  47
+      def connection_string
  48
+        "Provider=mondrian;Jdbc=#{jdbc_uri};JdbcDrivers=#{jdbc_driver};" <<
  49
+          (@params[:catalog] ? "Catalog=#{catalog_uri}" : "CatalogContent=#{catalog_content}")
  50
+      end
  51
+
  52
+      def jdbc_uri
  53
+        case @driver
  54
+        when 'mysql'
  55
+          "jdbc:mysql://#{@params[:host]}#{@params[:port] && ":#{@params[:port]}"}/#{@params[:database]}" <<
  56
+          "?user=#{@params[:username]}&password=#{@params[:password]}"
  57
+        else
  58
+          raise ArgumentError, 'unknown JDBC driver'
  59
+        end
  60
+      end
  61
+
  62
+      def jdbc_driver
  63
+        case @driver
  64
+        when 'mysql'
  65
+          'com.mysql.jdbc.Driver'
  66
+        else
  67
+          raise ArgumentError, 'unknown JDBC driver'
  68
+        end
  69
+      end
  70
+
  71
+      def catalog_uri
  72
+        if @params[:catalog]
  73
+          "file://#{File.expand_path(@params[:catalog])}"
  74
+        else
  75
+          raise ArgumentError, 'missing catalog source'
  76
+        end
  77
+      end
  78
+
  79
+      def catalog_content
  80
+        if @params[:catalog_content]
  81
+          @params[:catalog_content]
  82
+        elsif @params[:schema]
  83
+          @params[:schema].to_xml
  84
+        else
  85
+          raise ArgumentError, "Specify catalog with :catalog, :catalog_content or :schema option"
  86
+        end
  87
+      end
  88
+
  89
+    end
  90
+  end
  91
+end
140  lib/mondrian/olap/query.rb
... ...
@@ -0,0 +1,140 @@
  1
+module Mondrian
  2
+  module OLAP
  3
+    class Query
  4
+      def self.from(connection, cube_name)
  5
+        query = self.new(connection)
  6
+        query.cube_name = cube_name
  7
+        query
  8
+      end
  9
+
  10
+      attr_accessor :cube_name
  11
+
  12
+      def initialize(connection)
  13
+        @connection = connection
  14
+        @cube = nil
  15
+        @axes = []
  16
+        @where = []
  17
+        @with_members = []
  18
+      end
  19
+
  20
+      # Add new axis(i) to query
  21
+      # or return array of axis(i) members if no arguments specified
  22
+      def axis(i, *axis_members)
  23
+        if axis_members.empty?
  24
+          @axes[i]
  25
+        else
  26
+          @axes[i] ||= []
  27
+          if axis_members.length == 1 && axis_members[0].is_a?(Array)
  28
+            @axes[i].concat(axis_members[0])
  29
+          else
  30
+            @axes[i].concat(axis_members)
  31
+          end
  32
+          self
  33
+        end
  34
+      end
  35
+
  36
+      AXIS_ALIASES = %w(columns rows pages sections chapters)
  37
+      AXIS_ALIASES.each_with_index do |axis, i|
  38
+        class_eval <<-RUBY, __FILE__, __LINE__ + 1
  39
+          def #{axis}(*axis_members)
  40
+            axis(#{i}, *axis_members)
  41
+          end
  42
+        RUBY
  43
+      end
  44
+
  45
+      # Add new WHERE condition to query
  46
+      # or return array of existing conditions if no arguments specified
  47
+      def where(*members)
  48
+        if members.empty?
  49
+          @where
  50
+        else
  51
+          if members.length == 1 && members[0].is_a?(Array)
  52
+            @where.concat(members[0])
  53
+          else
  54
+            @where.concat(members)
  55
+          end
  56
+          self
  57
+        end
  58
+      end
  59
+
  60
+      # Add definition of calculated member
  61
+      def with_member(member=nil, options={})
  62
+        if member.nil?
  63
+          @with_members
  64
+        elsif member.is_a?(Array)
  65
+          member.each{|m, o| with_member(m, o)}
  66
+          self
  67
+        else
  68
+          raise ArgumentError, ":as option is mandatory" unless options[:as]
  69
+          @with_members << [member, options]
  70
+          self
  71
+        end
  72
+      end
  73
+
  74
+      def to_mdx
  75
+        mdx = ""
  76
+        mdx << "WITH #{with_to_mdx}\n" unless @with_members.empty?
  77
+        mdx << "SELECT #{axis_to_mdx}\n"
  78
+        mdx << "FROM #{from_to_mdx}"
  79
+        mdx << "\nWHERE #{where_to_mdx}" unless @where.empty?
  80
+        mdx
  81
+      end
  82
+
  83
+      def execute
  84
+        @connection.execute to_mdx
  85
+      end
  86
+
  87
+      private
  88
+
  89
+      def with_to_mdx
  90
+        @with_members.map do |member, options|
  91
+          options_string = ''
  92
+          options.each do |option, value|
  93
+            unless option == :as
  94
+              options_string << ", #{option.to_s.upcase} = #{quote_value(value)}"
  95
+            end
  96
+          end
  97
+          "MEMBER #{member} AS #{quote_value(options[:as])}#{options_string}"
  98
+        end.join("\n")
  99
+      end
  100
+
  101
+      def axis_to_mdx
  102
+        i = 0
  103
+        @axes.map do |axis_members|
  104
+          axis_name = AXIS_ALIASES[i] ? AXIS_ALIASES[i].upcase : "AXIS(#{i})"
  105
+          i += 1
  106
+          if axis_members.length == 1
  107
+            "#{axis_members[0]} ON #{axis_name}"
  108
+          else
  109
+            "{#{axis_members.join(', ')}} ON #{axis_name}"
  110
+          end
  111
+        end.join(",\n")
  112
+      end
  113
+
  114
+      def from_to_mdx
  115
+        "[#{@cube_name}]"
  116
+      end
  117
+
  118
+      def where_to_mdx
  119
+        mdx = '('
  120
+        mdx << @where.map do |condition|
  121
+          condition
  122
+        end.join(', ')
  123
+        mdx << ')'
  124
+      end
  125
+
  126
+      def quote_value(value)
  127
+        case value
  128
+        when String
  129
+          "'#{value.gsub("'", "''")}'"
  130
+        when TrueClass, FalseClass
  131
+          value ? 'TRUE' : 'FALSE'
  132
+        when NilClass
  133
+          'NULL'
  134
+        else
  135
+          "#{value}"
  136
+        end
  137
+      end
  138
+    end
  139
+  end
  140
+end
107  lib/mondrian/olap/result.rb
... ...
@@ -0,0 +1,107 @@
  1
+require 'nokogiri'
  2
+
  3
+module Mondrian
  4
+  module OLAP
  5
+    class Result
  6
+      def initialize(raw_result)
  7
+        @raw_result = raw_result
  8
+      end
  9
+
  10
+      def axes_count
  11
+        axes.length
  12
+      end
  13
+
  14
+      def axis_names
  15
+        @axis_names ||= axis_positions(:"getName")
  16
+      end
  17
+
  18
+      def axis_full_names
  19
+        @axis_full_names ||= axis_positions(:"getUniqueName")
  20
+      end
  21
+
  22
+      %w(column row page section chapter).each_with_index do |axis, i|
  23
+        define_method :"#{axis}_names" do
  24
+          axis_names[i]
  25
+        end
  26
+
  27
+        define_method :"#{axis}_full_names" do
  28
+          axis_full_names[i]
  29
+        end
  30
+      end
  31
+
  32
+      def values(*axes_sequence)
  33
+        if axes_sequence.empty?
  34
+          axes_sequence = (0...axes_count).to_a.reverse
  35
+        elsif axes_sequence.size != axes_count
  36
+          raise ArgumentError, "axes sequence size is not equal to result axes count"
  37
+        end
  38
+        recursive_values(axes_sequence, 0)
  39
+      end
  40
+
  41
+      # format results in simple HTML table
  42
+      def to_html
  43
+        if axes_count == 2
  44
+          builder = Nokogiri::XML::Builder.new do |doc|
  45
+            doc.table do
  46
+              doc.tr do
  47
+                doc.th
  48
+                column_full_names.each do |column_full_name|
  49
+                  doc.th column_full_name, :align => 'right'
  50
+                end
  51
+              end
  52
+              values.each_with_index do |row, i|
  53
+                doc.tr do
  54
+                  doc.th row_full_names[i], :align => 'left'
  55
+                  row.each do |cell|
  56
+                    doc.td cell, :align => 'right'
  57
+                  end
  58
+                end
  59
+              end
  60
+            end
  61
+          end
  62
+          builder.doc.to_html
  63
+        else
  64
+          raise ArgumentError, "just columns and rows axes are supported"
  65
+        end
  66
+      end
  67
+
  68
+      private
  69
+
  70
+      def axes
  71
+        @axes ||= @raw_result.getAxes
  72
+      end
  73
+
  74
+      def axis_positions(map_method, join_with=',')
  75
+        axes.map do |axis|
  76
+          axis.getPositions.map do |position|
  77
+            position.map do |member|
  78
+              member.send(map_method)
  79
+            end.join(join_with)
  80
+          end
  81
+        end
  82
+      end
  83
+
  84
+      AXIS_SYMBOL_TO_NUMBER = {
  85
+        :columns => 0,
  86
+        :rows => 1,
  87
+        :pages => 2,
  88
+        :sections => 3,
  89
+        :chapters => 4
  90
+      }.freeze
  91
+
  92
+      def recursive_values(axes_sequence, current_index, cell_params=[])
  93
+        if axis_number = axes_sequence[current_index]
  94
+          axis_number = AXIS_SYMBOL_TO_NUMBER[axis_number] if axis_number.is_a?(Symbol)
  95
+          positions_size = axes[axis_number].getPositions.size
  96
+          (0...positions_size).map do |i|
  97
+            cell_params[axis_number] = i
  98
+            recursive_values(axes_sequence, current_index + 1, cell_params)
  99
+          end
  100
+        else
  101
+          @raw_result.getCell(cell_params).getValue
  102
+        end
  103
+      end
  104
+
  105
+    end
  106
+  end
  107
+end
145  lib/mondrian/olap/schema.rb
... ...
@@ -0,0 +1,145 @@
  1
+require 'mondrian/olap/schema_element'
  2
+
  3
+module Mondrian
  4
+  module OLAP
  5
+    # See http://mondrian.pentaho.com/documentation/schema.php for more detailed description
  6
+    # of Mondrian Schema elements.
  7
+    class Schema < SchemaElement
  8
+      def self.define(name = nil, attributes = {}, &block)
  9
+        new(name, attributes, &block)
  10
+      end
  11
+
  12
+      def define(name = nil, &block)
  13
+        @attributes[:name] = name || 'default' # otherwise connection with empty name fails
  14
+        instance_eval &block if block
  15
+        self
  16
+      end
  17
+
  18
+      attributes :description
  19
+      elements :cube
  20
+
  21
+      class Cube < SchemaElement
  22
+        attributes :description,
  23
+          # The name of the measure that would be taken as the default measure of the cube.
  24
+          :default_measure,
  25
+          # Should the Fact table data for this Cube be cached by Mondrian or not.
  26
+          # The default action is to cache the data.
  27
+          :cache,
  28
+          # Whether element is enabled - if true, then the Cube is realized otherwise it is ignored.
  29
+          :enabled
  30
+        elements :table, :dimension, :measure, :calculated_member
  31
+      end
  32
+
  33
+      class Table < SchemaElement
  34
+        attributes :schema, # Optional qualifier for table.
  35
+          # Alias to be used with this table when it is used to form queries.
  36
+          # If not specified, defaults to the table name, but in any case, must be unique within the schema.
  37
+          # (You can use the same table in different hierarchies, but it must have different aliases.)
  38
+          :alias
  39
+      end
  40
+
  41
+      class Dimension < SchemaElement
  42
+        attributes :description,
  43
+          # The dimension's type may be one of "Standard" or "Time".
  44
+          # A time dimension will allow the use of the MDX time functions (WTD, YTD, QTD, etc.).
  45
+          # Use a standard dimension if the dimension is not a time-related dimension.
  46
+          # The default value is "Standard".
  47
+          :type,
  48
+          # The name of the column in the fact table which joins to the leaf level of this dimension.
  49
+          # Required in a private Dimension or a DimensionUsage, but not in a public Dimension.
  50
+          :foreign_key
  51
+        elements :hierarchy
  52
+      end
  53
+
  54
+      class Hierarchy < SchemaElement
  55
+        attributes :description,
  56
+          # Whether this hierarchy has an 'all' member.
  57
+          :has_all,
  58
+          # Name of the 'all' member. If this attribute is not specified,
  59
+          # the all member is named 'All hierarchyName', for example, 'All Store'.
  60
+          :all_member_name,
  61
+          # Name of the 'all' level. If this attribute is not specified,
  62
+          # the all member is named '(All)'.
  63
+          :all_level_name,
  64
+          # The name of the column which identifies members, and which is referenced by rows in the fact table.
  65
+          # If not specified, the key of the lowest level is used. See also Dimension foreign_key.
  66
+          :primary_key,
  67
+          # The name of the table which contains primary_key.
  68
+          # If the hierarchy has only one table, defaults to that; it is required.
  69
+          :primary_key_table,
  70
+          # Should be set to the level (if such a level exists) at which depth it is known
  71
+          # that all members have entirely unique rows, allowing SQL GROUP BY clauses to be completely eliminated from the query.
  72
+          :unique_key_level_name
  73
+        elements :table, :level
  74
+      end
  75
+
  76
+      class Level < SchemaElement
  77
+        attributes :description,
  78
+          # The name of the table that the column comes from.
  79
+          # If this hierarchy is based upon just one table, defaults to the name of that table;
  80
+          # otherwise, it is required.
  81
+          :table,
  82
+          # The name of the column which holds the unique identifier of this level.
  83
+          :column,
  84
+          # The name of the column which holds the user identifier of this level.
  85
+          :name_column,
  86
+          # The name of the column which holds member ordinals.
  87
+          # If this column is not specified, the key column is used for ordering.
  88
+          :ordinal_column,
  89
+          # The name of the column which references the parent member in a parent-child hierarchy.
  90
+          :parent_column,
  91
+          # Value which identifies null parents in a parent-child hierarchy.
  92
+          # Typical values are 'NULL' and '0'.
  93
+          :null_parent_value,
  94
+          # Indicates the type of this level's key column:
  95
+          # String, Numeric, Integer, Boolean, Date, Time or Timestamp.
  96
+          # When generating SQL statements, Mondrian encloses values for String columns in quotation marks,
  97
+          # but leaves values for Integer and Numeric columns un-quoted.
  98
+          # Date, Time, and Timestamp values are quoted according to the SQL dialect.
  99
+          # For a SQL-compliant dialect, the values appear prefixed by their typename, 
  100
+          # for example, "DATE '2006-06-01'".
  101
+          # Default value: 'String'
  102
+          :type,
  103
+          # Whether members are unique across all parents. 
  104
+          # For example, zipcodes are unique across all states.
  105
+          # The first level's members are always unique.
  106
+          # Default value: false
  107
+          :unique_members,
  108
+          # Whether this is a regular or a time-related level.
  109
+          # The value makes a difference to time-related functions such as YTD (year-to-date).
  110
+          # Default value: 'Regular'
  111
+          :level_type,
  112
+          # Condition which determines whether a member of this level is hidden.
  113
+          # If a hierarchy has one or more levels with hidden members,
  114
+          # then it is possible that not all leaf members are the same distance from the root,
  115
+          # and it is termed a ragged hierarchy.
  116
+          # Allowable values are: Never (a member always appears; the default);
  117
+          # IfBlankName (a member doesn't appear if its name is null, empty or all whitespace);
  118
+          # and IfParentsName (a member appears unless its name matches the parent's.
  119
+          # Default value: 'Never'
  120
+          :hide_member_if
  121
+      end
  122
+
  123
+      class Measure < SchemaElement
  124
+        attributes :description,
  125
+          # Column which is source of this measure's values.
  126
+          # If not specified, a measure expression must be specified.
  127
+          :column,
  128
+          # The datatype of this measure: String, Numeric, Integer, Boolean, Date, Time or Timestamp.
  129
+          # The default datatype of a measure is 'Integer' if the measure's aggregator is 'Count', otherwise it is 'Numeric'.
  130
+          :datatype,
  131
+          # Aggregation function. Allowed values are "sum", "count", "min", "max", "avg", and "distinct-count".
  132
+          :aggregator
  133
+      end
  134
+
  135
+      class CalculatedMember < SchemaElement
  136
+        attributes :description,
  137
+          # MDX expression which gives the value of this member. Equivalent to the Formula sub-element.
  138
+          :formula,
  139
+          # Name of the dimension which this member belongs to.
  140
+          :dimension
  141
+      end
  142
+
  143
+    end
  144
+  end
  145
+end
110  lib/mondrian/olap/schema_element.rb
... ...
@@ -0,0 +1,110 @@
  1
+require 'nokogiri'
  2
+
  3
+module Mondrian
  4
+  module OLAP
  5
+    class SchemaElement
  6
+      def initialize(name = nil, attributes = {}, &block)
  7
+        # if just attributes hash provided
  8
+        if name.is_a?(Hash) && attributes == {}
  9
+          attributes = name
  10
+          name = nil
  11
+        end
  12
+        @attributes = {}
  13
+        @attributes[:name] = name if name
  14
+        @attributes.merge!(attributes)
  15
+        self.class.elements.each do |element|
  16
+          instance_variable_set("@#{pluralize(element)}", [])
  17
+        end
  18
+        instance_eval &block if block
  19
+      end
  20
+
  21
+      def self.attributes(*names)
  22
+        names.each do |name|
  23
+          class_eval <<-RUBY, __FILE__, __LINE__ + 1
  24
+            def #{name}(*args)
  25
+              if args.empty?
  26
+                @attributes[:#{name}]
  27
+              elsif args.size == 1
  28
+                @attributes[:#{name}] = args[0]
  29
+              else
  30
+                raise ArgumentError, "too many arguments"
  31
+              end
  32
+            end
  33
+          RUBY
  34
+        end
  35
+      end
  36
+
  37
+      def self.elements(*names)
  38
+        return @elements || [] if names.empty?
  39
+
  40
+        @elements ||= []
  41
+        @elements.concat(names)
  42
+
  43
+        names.each do |name|
  44
+          attr_reader pluralize(name).to_sym
  45
+          class_eval <<-RUBY, __FILE__, __LINE__ + 1
  46
+            def #{name}(name=nil, attributes = {}, &block)
  47
+              @#{pluralize(name)} << Schema::#{camel_case(name)}.new(name, attributes, &block)
  48
+            end
  49
+          RUBY
  50
+        end
  51
+      end
  52
+
  53
+      def to_xml
  54
+        Nokogiri::XML::Builder.new do |xml|
  55
+          add_to_xml(xml)
  56
+        end.to_xml
  57
+      end
  58
+
  59
+      protected
  60
+
  61
+      def add_to_xml(xml)
  62
+        xml.send(tag_name(self.class.name), camel_case_attributes) do
  63
+          self.class.elements.each do |element|
  64
+            instance_variable_get("@#{pluralize(element)}").each {|item| item.add_to_xml(xml)}
  65
+          end
  66
+        end
  67
+      end
  68
+
  69
+      private
  70
+
  71
+
  72
+      def camel_case_attributes
  73
+        hash = {}
  74
+        @attributes.each do |attr, value|
  75
+          hash[
  76
+            attr.to_s.gsub(/_([^_]+)/){|m| $1.capitalize}
  77
+          ] = value
  78
+        end
  79
+        hash
  80
+      end
  81
+
  82
+      def self.pluralize(string)
  83
+        string = string.to_s
  84
+        case string
  85
+        when /^(.*)y$/
  86
+          "#{$1}ies"
  87
+        else
  88
+          "#{string}s"
  89
+        end
  90
+      end
  91
+
  92
+      def pluralize(string)
  93
+        self.class.pluralize(string)
  94
+      end
  95
+
  96
+      def self.camel_case(string)
  97
+        string.to_s.split('_').map{|s| s.capitalize}.join('')
  98
+      end
  99
+
  100
+      def camel_case(string)
  101
+        self.class.camel_case(string)
  102
+      end
  103
+
  104
+      def tag_name(string)
  105
+        string.split('::').last << '_'
  106
+      end
  107
+    end
  108
+
  109
+  end
  110
+end
70  spec/connection_spec.rb
... ...
@@ -0,0 +1,70 @@
  1
+require "spec_helper"
  2
+
  3
+describe "Connection" do
  4
+
  5
+  describe "create" do
  6
+    before(:each) do
  7
+      @olap = Mondrian::OLAP::Connection.new(CONNECTION_PARAMS_WITH_CATALOG)
  8
+    end
  9
+
  10
+    it "should not be connected before connection" do
  11
+      @olap.should_not be_connected
  12
+    end
  13
+
  14
+    it "should be successful" do
  15
+      @olap.connect.should be_true
  16
+    end
  17
+
  18
+  end
  19
+
  20
+  describe "create with catalog content" do
  21
+    before(:all) do
  22
+      @schema_xml = <<-XML
  23
+<?xml version="1.0"?>
  24
+<Schema name="FoodMart">
  25
+  <Cube name="Sales">
  26
+    <Table name="sales_fact_1997"/>
  27
+    <Dimension name="Gender" foreignKey="customer_id">
  28
+      <Hierarchy hasAll="true" allMemberName="All Genders" primaryKey="customer_id">
  29
+        <Table name="customer"/>
  30
+        <Level name="Gender" column="gender" uniqueMembers="true"/>
  31
+      </Hierarchy>
  32
+    </Dimension>
  33
+    <Measure name="Unit Sales" column="unit_sales" aggregator="sum" formatString="#,###"/>
  34
+  </Cube>
  35
+</Schema>
  36
+XML
  37
+    end
  38
+    it "should be successful" do
  39
+      @olap = Mondrian::OLAP::Connection.new(CONNECTION_PARAMS.merge(
  40
+        :catalog_content => @schema_xml
  41
+      ))
  42
+      @olap.connect.should be_true
  43
+    end
  44
+
  45
+  end
  46
+
  47
+  describe "properties" do
  48
+    before(:all) do
  49
+      @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS_WITH_CATALOG)
  50
+    end
  51
+
  52
+    it "should be connected" do
  53
+      @olap.should be_connected
  54
+    end
  55
+
  56
+  end
  57
+
  58
+  describe "close" do
  59
+    before(:all) do
  60
+      @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS_WITH_CATALOG)
  61
+    end
  62
+
  63
+    it "should not be connected after close" do
  64
+      @olap.close
  65
+      @olap.should_not be_connected
  66
+    end
  67
+
  68
+  end
  69
+
  70
+end
802  spec/fixtures/FoodMart.xml
... ...
@@ -0,0 +1,802 @@
  1
+<?xml version="1.0"?>
  2
+<Schema name="FoodMart">
  3
+<!--
  4
+  == $Id: //open/mondrian-release/3.2/demo/FoodMart.xml#2 $
  5
+  == This software is subject to the terms of the Eclipse Public License v1.0
  6
+  == Agreement, available at the following URL:
  7
+  == http://www.eclipse.org/legal/epl-v10.html.
  8
+  == Copyright (C) 2000-2002 Kana Software, Inc.
  9
+  == Copyright (C) 2002-2009 Julian Hyde and others
  10
+  == All Rights Reserved.
  11
+  == You must accept the terms of that agreement to use this software.
  12
+  -->
  13
+
  14
+<!-- Shared dimensions -->
  15
+
  16
+  <Dimension name="Store">
  17
+    <Hierarchy hasAll="true" primaryKey="store_id">
  18
+      <Table name="store"/>
  19
+      <Level name="Store Country" column="store_country" uniqueMembers="true"/>
  20
+      <Level name="Store State" column="store_state" uniqueMembers="true"/>
  21
+      <Level name="Store City" column="store_city" uniqueMembers="false"/>
  22
+      <Level name="Store Name" column="store_name" uniqueMembers="true">
  23
+        <Property name="Store Type" column="store_type"/>
  24
+        <Property name="Store Manager" column="store_manager"/>
  25
+        <Property name="Store Sqft" column="store_sqft" type="Numeric"/>
  26
+        <Property name="Grocery Sqft" column="grocery_sqft" type="Numeric"/>
  27
+        <Property name="Frozen Sqft" column="frozen_sqft" type="Numeric"/>
  28
+        <Property name="Meat Sqft" column="meat_sqft" type="Numeric"/>
  29
+        <Property name="Has coffee bar" column="coffee_bar" type="Boolean"/>
  30
+        <Property name="Street address" column="store_street_address" type="String"/>
  31
+      </Level>
  32
+    </Hierarchy>
  33
+  </Dimension>
  34
+
  35
+  <Dimension name="Store Size in SQFT">
  36
+    <Hierarchy hasAll="true" primaryKey="store_id">
  37
+      <Table name="store"/>
  38
+      <Level name="Store Sqft" column="store_sqft" type="Numeric" uniqueMembers="true"/>
  39
+    </Hierarchy>