mondrian-olap 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Changelog.md +22 -0
- data/Gemfile +2 -0
- data/README.rdoc +11 -9
- data/RUNNING_TESTS.rdoc +21 -2
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/lib/mondrian/jars/log4j.properties +1 -14
- data/lib/mondrian/olap.rb +4 -1
- data/lib/mondrian/olap/connection.rb +42 -8
- data/lib/mondrian/olap/result.rb +3 -3
- data/lib/mondrian/olap/schema.rb +76 -13
- data/lib/mondrian/olap/schema_element.rb +24 -6
- data/mondrian-olap.gemspec +9 -2
- data/spec/fixtures/MondrianTest.xml +6 -0
- data/spec/fixtures/MondrianTestOracle.xml +6 -0
- data/spec/query_spec.rb +6 -2
- data/spec/rake_tasks.rb +177 -102
- data/spec/schema_definition_spec.rb +44 -5
- data/spec/spec_helper.rb +39 -0
- metadata +39 -16
data/Changelog.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
### 0.2.0 / 2011-07-01
|
2
|
+
|
3
|
+
* New features
|
4
|
+
* support for LucidDB database
|
5
|
+
* Improvements
|
6
|
+
* only set log4j configuration file if not set already (possible to override e.g. Mondrian debugging settings)
|
7
|
+
* `result.to_html(:formatted=>true)` will return formatted results
|
8
|
+
* set Unicode encoding for mysql connection
|
9
|
+
* `format_string` attribute and `formula` element for calculated members
|
10
|
+
* `:use_content_checksum` connection option (by default set to true)
|
11
|
+
* `key_expression`, `name_expression`, `ordinal_expression` elements with `sql` subelement support for levels
|
12
|
+
* `:upcase_data_dictionary` option for schema definition
|
13
|
+
* Bug fixes
|
14
|
+
* fixed examples in README
|
15
|
+
* correctly quote `CatalogContent` in connection string (to allow usage of semicolons in generated XML catalog)
|
16
|
+
* workarounds for issues with Java classloader when using in production mode with jruby-rack
|
17
|
+
* construct correct file path on Windows
|
18
|
+
|
19
|
+
### 0.1.0 / 2011-03-18
|
20
|
+
|
21
|
+
* Initial release
|
22
|
+
* support for MySQL, PostgreSQL and Oracle databases
|
data/Gemfile
CHANGED
@@ -5,10 +5,12 @@ gem 'nokogiri', '~> 1.5.0.beta.4'
|
|
5
5
|
group :development do
|
6
6
|
gem 'jruby-openssl'
|
7
7
|
gem 'jeweler', '~> 1.5.2'
|
8
|
+
gem 'rdoc'
|
8
9
|
gem 'rspec', '~> 2.5'
|
9
10
|
gem 'autotest'
|
10
11
|
gem 'jdbc-mysql'
|
11
12
|
gem 'jdbc-postgres'
|
13
|
+
gem 'jdbc-luciddb'
|
12
14
|
gem 'activerecord', '~> 3.0.5'
|
13
15
|
gem 'activerecord-jdbc-adapter'
|
14
16
|
gem 'activerecord-oracle_enhanced-adapter', :require => false
|
data/README.rdoc
CHANGED
@@ -100,7 +100,7 @@ When schema is defined it is necessary to establish OLAP connection to database.
|
|
100
100
|
:host => 'localhost,
|
101
101
|
:database => 'mondrian_test',
|
102
102
|
:username => 'mondrian_user',
|
103
|
-
:password => 'secret'
|
103
|
+
:password => 'secret',
|
104
104
|
:schema => schema
|
105
105
|
)
|
106
106
|
|
@@ -111,7 +111,7 @@ mondrian-olap allows executing of MDX queries, for example query for "Get sales
|
|
111
111
|
|
112
112
|
result = olap.execute <<-MDX
|
113
113
|
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
114
|
-
{[
|
114
|
+
{[Products].children} ON ROWS
|
115
115
|
FROM [Sales]
|
116
116
|
WHERE ([Time].[2010].[Q1], [Customers].[USA].[CA])
|
117
117
|
MDX
|
@@ -135,7 +135,7 @@ and then get axis and cells of result object:
|
|
135
135
|
result.column_names # => ["Unit Sales", "Store Sales"]
|
136
136
|
result.column_full_names # => ["[Measures].[Unit Sales]", "[Measures].[Store Sales]"]
|
137
137
|
result.row_names # => e.g. ["Drink", "Food", "Non-Consumable"]
|
138
|
-
result.row_full_names # => e.g. ["[
|
138
|
+
result.row_full_names # => e.g. ["[Products].[Drink]", "[Products].[Food]", "[Products].[Non-Consumable]"]
|
139
139
|
result.values # => [[..., ...], [..., ...], [..., ...]]
|
140
140
|
# (three rows, each row containing value for "unit sales" and "store sales")
|
141
141
|
|
@@ -146,8 +146,9 @@ Previous MDX query can be executed as:
|
|
146
146
|
|
147
147
|
olap.from('Sales').
|
148
148
|
columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
149
|
-
rows('[
|
150
|
-
where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]')
|
149
|
+
rows('[Products].children').
|
150
|
+
where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').
|
151
|
+
execute
|
151
152
|
|
152
153
|
Here is example of more complex query "Get sales amount and profit % of top 50 products cross-joined with USA and Canada country sales during Q1 of 2010":
|
153
154
|
|
@@ -156,9 +157,10 @@ Here is example of more complex query "Get sales amount and profit % of top 50 p
|
|
156
157
|
as('Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])',
|
157
158
|
:format_string => 'Percent').
|
158
159
|
columns('[Measures].[Store Sales]', '[Measures].[ProfitPct]').
|
159
|
-
rows('[
|
160
|
-
top_count(50, '[Measures].[Store Sales]')
|
161
|
-
where('[Time].[2010].[Q1]')
|
160
|
+
rows('[Products].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]').
|
161
|
+
top_count(50, '[Measures].[Store Sales]').
|
162
|
+
where('[Time].[2010].[Q1]').
|
163
|
+
execute
|
162
164
|
|
163
165
|
See more examples of queries in spec/query_spec.rb.
|
164
166
|
|
@@ -193,7 +195,7 @@ See more examples of dimension and member queries in spec/cube_spec.rb.
|
|
193
195
|
|
194
196
|
mondrian-olap gem is compatible with JRuby versions 1.5 and 1.6 (have not been tested with earlier versions). mondrian-olap works only with JRuby and not with other Ruby implementations as it includes Mondrian OLAP Java libraries.
|
195
197
|
|
196
|
-
mondrian-olap currently supports MySQL, PostgreSQL and
|
198
|
+
mondrian-olap currently supports MySQL, PostgreSQL, Oracle and LucidDB databases. When using MySQL, PostgreSQL or LucidDB databases then install jdbc-mysql, jdbc-postgres or jdbc-luciddb gem and require "jdbc/mysql", "jdbc/postgres" or "jdbc/luciddb" to load corresponding JDBC database driver. When using Oracle then include Oracle JDBC driver (ojdbc6.jar for Java 6) in CLASSPATH or copy to JRUBY_HOME/lib or require it in application manually.
|
197
199
|
|
198
200
|
== INSTALL
|
199
201
|
|
data/RUNNING_TESTS.rdoc
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
== Creating test database
|
2
2
|
|
3
|
-
By default unit tests use MySQL database but PostgreSQL and Oracle databases are supported as well. Set MONDRIAN_DRIVER environment variable to "mysql" (default), "postgresql" or "
|
3
|
+
By default unit tests use MySQL database but PostgreSQL and Oracle databases are supported as well. Set MONDRIAN_DRIVER environment variable to "mysql" (default), "postgresql", "oracle" or "luciddb" to specify database driver that should be used.
|
4
4
|
|
5
5
|
If using MySQL or PostgreSQL database then create database user mondrian_test with password mondrian_test, create database mondrian_test and grant full access to this database for mondrian_test user. By default it is assumed that database is located on localhost (can be overridden with DATABASE_HOST environment variable).
|
6
6
|
|
7
7
|
If using Oracle database then create database user mondrian_test with password mondrian_test. By default it is assumed that database orcl is located on localhost (can be overridden with DATABASE_NAME and DATABASE_HOST environment variables).
|
8
8
|
|
9
|
+
If using LucidDB database then create schema MONDRIAN_TEST and create user MONDRIAN_TEST with password mondrian_test and with default schema MONDRIAN_TEST. By default it is assumed that database is located on localhost (can be overridden with DATABASE_HOST environment variable).
|
10
|
+
|
9
11
|
See spec/spec_helper.rb for details of default connection parameters and how to override them.
|
10
12
|
|
11
13
|
== Creating test data
|
@@ -24,6 +26,11 @@ or specify which database driver to use
|
|
24
26
|
rake db:create_data MONDRIAN_DRIVER=postgresql
|
25
27
|
rake db:create_data MONDRIAN_DRIVER=oracle
|
26
28
|
|
29
|
+
In case of LucidDB data are not generated and inserted directly into database but are imported from MySQL mondrian_test database (because inserting individual records into LucidDB is very inefficient). Therefore at first generate test data with mysql (using default database settings) and then run data creation task for LucidDB.
|
30
|
+
|
31
|
+
rake db:create_data MONDRIAN_DRIVER=mysql
|
32
|
+
rake db:create_data MONDRIAN_DRIVER=luciddb
|
33
|
+
|
27
34
|
== Running tests
|
28
35
|
|
29
36
|
Run tests with
|
@@ -35,7 +42,19 @@ or specify which database driver to use
|
|
35
42
|
rake spec MONDRIAN_DRIVER=mysql
|
36
43
|
rake spec MONDRIAN_DRIVER=postgresql
|
37
44
|
rake spec MONDRIAN_DRIVER=oracle
|
45
|
+
rake spec MONDRIAN_DRIVER=luciddb
|
46
|
+
|
47
|
+
or also alternatively with
|
48
|
+
|
49
|
+
rake spec:mysql
|
50
|
+
rake spec:postgresql
|
51
|
+
rake spec:oracle
|
52
|
+
rake spec:luciddb
|
53
|
+
|
54
|
+
You can also run all tests on all databases with
|
55
|
+
|
56
|
+
rake spec:all
|
38
57
|
|
39
58
|
== JRuby versions
|
40
59
|
|
41
|
-
It is recommended to use RVM (http://rvm.beginrescueend.com) to run tests with different JRuby implementations. mondrian-olap
|
60
|
+
It is recommended to use RVM (http://rvm.beginrescueend.com) to run tests with different JRuby implementations. mondrian-olap is being tested with latest versions of JRuby 1.6 on Java 6.
|
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
@@ -1,18 +1,5 @@
|
|
1
|
+
# Logs errors on the console
|
1
2
|
#
|
2
|
-
# Log4J Konfiguration
|
3
|
-
#
|
4
|
-
# - Logs errors on the console
|
5
|
-
#
|
6
|
-
|
7
3
|
log4j.rootLogger = ERROR, A1
|
8
|
-
|
9
|
-
# Logging to console
|
10
4
|
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
5
|
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
|
data/lib/mondrian/olap.rb
CHANGED
@@ -5,7 +5,10 @@ Dir["#{directory}/*.jar"].each do |file|
|
|
5
5
|
require file
|
6
6
|
end
|
7
7
|
|
8
|
-
java.lang.System.
|
8
|
+
unless java.lang.System.getProperty("log4j.configuration")
|
9
|
+
file_uri = java.io.File.new("#{directory}/log4j.properties").toURI.to_s
|
10
|
+
java.lang.System.setProperty("log4j.configuration", file_uri)
|
11
|
+
end
|
9
12
|
# register Mondrian olap4j driver
|
10
13
|
Java::mondrian.olap4j.MondrianOlap4jDriver
|
11
14
|
|
@@ -17,7 +17,29 @@ module Mondrian
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def connect
|
20
|
-
|
20
|
+
# hack to call private constructor of MondrianOlap4jDriver
|
21
|
+
# to avoid using DriverManager which fails to load JDBC drivers
|
22
|
+
# because of not seeing JRuby required jar files
|
23
|
+
cons = Java::MondrianOlap4j::MondrianOlap4jDriver.java_class.declared_constructor
|
24
|
+
cons.accessible = true
|
25
|
+
driver = cons.new_instance.to_java
|
26
|
+
|
27
|
+
props = java.util.Properties.new
|
28
|
+
props.setProperty('JdbcUser', @params[:username]) if @params[:username]
|
29
|
+
props.setProperty('JdbcPassword', @params[:password]) if @params[:password]
|
30
|
+
|
31
|
+
conn_string = connection_string
|
32
|
+
|
33
|
+
# workaround for Mondrian ServiceDiscovery
|
34
|
+
current_thread = Java::JavaLang::Thread.currentThread
|
35
|
+
class_loader = current_thread.getContextClassLoader
|
36
|
+
begin
|
37
|
+
current_thread.setContextClassLoader(nil)
|
38
|
+
@raw_jdbc_connection = driver.connect(conn_string, props)
|
39
|
+
ensure
|
40
|
+
current_thread.setContextClassLoader(class_loader)
|
41
|
+
end
|
42
|
+
|
21
43
|
@raw_connection = @raw_jdbc_connection.unwrap(Java::OrgOlap4j::OlapConnection.java_class)
|
22
44
|
@raw_schema = @raw_connection.getSchema
|
23
45
|
@connected = true
|
@@ -63,24 +85,30 @@ module Mondrian
|
|
63
85
|
private
|
64
86
|
|
65
87
|
def connection_string
|
66
|
-
"jdbc:mondrian:Jdbc=#{jdbc_uri};JdbcDrivers=#{jdbc_driver};"
|
67
|
-
|
88
|
+
string = "jdbc:mondrian:Jdbc=#{quote_string(jdbc_uri)};JdbcDrivers=#{jdbc_driver};"
|
89
|
+
# by default use content checksum to reload schema when catalog has changed
|
90
|
+
string << "UseContentChecksum=true;" unless @params[:use_content_checksum] == false
|
91
|
+
string << (@params[:catalog] ? "Catalog=#{catalog_uri}" : "CatalogContent=#{quote_string(catalog_content)}")
|
68
92
|
end
|
69
93
|
|
70
94
|
def jdbc_uri
|
71
95
|
case @driver
|
72
96
|
when 'mysql', 'postgresql'
|
73
|
-
"jdbc:#{@driver}://#{@params[:host]}#{@params[:port] && ":#{@params[:port]}"}/#{@params[:database]}"
|
74
|
-
"?
|
97
|
+
uri = "jdbc:#{@driver}://#{@params[:host]}#{@params[:port] && ":#{@params[:port]}"}/#{@params[:database]}"
|
98
|
+
uri << "?useUnicode=yes&characterEncoding=UTF-8" if @driver == 'mysql'
|
99
|
+
uri
|
75
100
|
when 'oracle'
|
76
101
|
# connection using TNS alias
|
77
102
|
if @params[:database] && !@params[:host] && !@params[:url] && ENV['TNS_ADMIN']
|
78
|
-
"jdbc:oracle:thin
|
103
|
+
"jdbc:oracle:thin:@#{@params[:database]}"
|
79
104
|
else
|
80
105
|
@params[:url] ||
|
81
|
-
"jdbc:oracle:thin:#{@params[:
|
82
|
-
"@#{@params[:host] || 'localhost'}:#{@params[:port] || 1521}:#{@params[:database]}"
|
106
|
+
"jdbc:oracle:thin:@#{@params[:host] || 'localhost'}:#{@params[:port] || 1521}:#{@params[:database]}"
|
83
107
|
end
|
108
|
+
when 'luciddb'
|
109
|
+
uri = "jdbc:luciddb:http://#{@params[:host]}#{@params[:port] && ":#{@params[:port]}"}"
|
110
|
+
uri << ";schema=#{@params[:database_schema]}" if @params[:database_schema]
|
111
|
+
uri
|
84
112
|
else
|
85
113
|
raise ArgumentError, 'unknown JDBC driver'
|
86
114
|
end
|
@@ -94,6 +122,8 @@ module Mondrian
|
|
94
122
|
'org.postgresql.Driver'
|
95
123
|
when 'oracle'
|
96
124
|
'oracle.jdbc.OracleDriver'
|
125
|
+
when 'luciddb'
|
126
|
+
'org.luciddb.jdbc.LucidDbClientDriver'
|
97
127
|
else
|
98
128
|
raise ArgumentError, 'unknown JDBC driver'
|
99
129
|
end
|
@@ -117,6 +147,10 @@ module Mondrian
|
|
117
147
|
end
|
118
148
|
end
|
119
149
|
|
150
|
+
def quote_string(string)
|
151
|
+
"'#{string.gsub("'","''")}'"
|
152
|
+
end
|
153
|
+
|
120
154
|
end
|
121
155
|
end
|
122
156
|
end
|
data/lib/mondrian/olap/result.rb
CHANGED
@@ -56,7 +56,7 @@ module Mondrian
|
|
56
56
|
end
|
57
57
|
|
58
58
|
# format results in simple HTML table
|
59
|
-
def to_html
|
59
|
+
def to_html(options = {})
|
60
60
|
case axes_count
|
61
61
|
when 1
|
62
62
|
builder = Nokogiri::XML::Builder.new do |doc|
|
@@ -68,7 +68,7 @@ module Mondrian
|
|
68
68
|
end
|
69
69
|
end
|
70
70
|
doc.tr do
|
71
|
-
values.each do |value|
|
71
|
+
(options[:formatted] ? formatted_values : values).each do |value|
|
72
72
|
doc.td value, :align => 'right'
|
73
73
|
end
|
74
74
|
end
|
@@ -85,7 +85,7 @@ module Mondrian
|
|
85
85
|
doc.th column_full_name, :align => 'right'
|
86
86
|
end
|
87
87
|
end
|
88
|
-
values.each_with_index do |row, i|
|
88
|
+
(options[:formatted] ? formatted_values : values).each_with_index do |row, i|
|
89
89
|
doc.tr do
|
90
90
|
row_full_name = row_full_names[i].is_a?(Array) ? row_full_names[i].join(',') : row_full_names[i]
|
91
91
|
doc.th row_full_name, :align => 'left'
|
data/lib/mondrian/olap/schema.rb
CHANGED
@@ -5,21 +5,49 @@ module Mondrian
|
|
5
5
|
# See http://mondrian.pentaho.com/documentation/schema.php for more detailed description
|
6
6
|
# of Mondrian Schema elements.
|
7
7
|
class Schema < SchemaElement
|
8
|
+
def initialize(name = nil, attributes = {}, &block)
|
9
|
+
name, attributes = self.class.pre_process_arguments(name, attributes)
|
10
|
+
pre_process_attributes(attributes)
|
11
|
+
super(name, attributes, &block)
|
12
|
+
end
|
13
|
+
|
8
14
|
def self.define(name = nil, attributes = {}, &block)
|
15
|
+
name, attributes = pre_process_arguments(name, attributes)
|
9
16
|
new(name || 'default', attributes, &block)
|
10
17
|
end
|
11
18
|
|
12
|
-
def define(name = nil, &block)
|
13
|
-
|
19
|
+
def define(name = nil, attributes = {}, &block)
|
20
|
+
name, attributes = self.class.pre_process_arguments(name, attributes)
|
21
|
+
pre_process_attributes(attributes)
|
22
|
+
@attributes[:name] = name || @attributes[:name] || 'default' # otherwise connection with empty name fails
|
14
23
|
instance_eval &block if block
|
15
24
|
self
|
16
25
|
end
|
17
26
|
|
18
|
-
|
27
|
+
private
|
28
|
+
|
29
|
+
def self.pre_process_arguments(name, attributes)
|
30
|
+
# if is called just with attributes hash and without name
|
31
|
+
if name.is_a?(Hash) && attributes.empty?
|
32
|
+
attributes = name
|
33
|
+
name = nil
|
34
|
+
end
|
35
|
+
[name, attributes]
|
36
|
+
end
|
37
|
+
|
38
|
+
def pre_process_attributes(attributes)
|
39
|
+
unless attributes[:upcase_data_dictionary].nil?
|
40
|
+
@upcase_data_dictionary = attributes.delete(:upcase_data_dictionary)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
public
|
45
|
+
|
46
|
+
attributes :name, :description
|
19
47
|
elements :cube
|
20
48
|
|
21
49
|
class Cube < SchemaElement
|
22
|
-
attributes :description,
|
50
|
+
attributes :name, :description,
|
23
51
|
# The name of the measure that would be taken as the default measure of the cube.
|
24
52
|
:default_measure,
|
25
53
|
# Should the Fact table data for this Cube be cached by Mondrian or not.
|
@@ -31,7 +59,7 @@ module Mondrian
|
|
31
59
|
end
|
32
60
|
|
33
61
|
class Table < SchemaElement
|
34
|
-
attributes :schema, # Optional qualifier for table.
|
62
|
+
attributes :name, :schema, # Optional qualifier for table.
|
35
63
|
# Alias to be used with this table when it is used to form queries.
|
36
64
|
# If not specified, defaults to the table name, but in any case, must be unique within the schema.
|
37
65
|
# (You can use the same table in different hierarchies, but it must have different aliases.)
|
@@ -40,7 +68,7 @@ module Mondrian
|
|
40
68
|
end
|
41
69
|
|
42
70
|
class Dimension < SchemaElement
|
43
|
-
attributes :description,
|
71
|
+
attributes :name, :description,
|
44
72
|
# The dimension's type may be one of "Standard" or "Time".
|
45
73
|
# A time dimension will allow the use of the MDX time functions (WTD, YTD, QTD, etc.).
|
46
74
|
# Use a standard dimension if the dimension is not a time-related dimension.
|
@@ -54,7 +82,7 @@ module Mondrian
|
|
54
82
|
end
|
55
83
|
|
56
84
|
class Hierarchy < SchemaElement
|
57
|
-
attributes :description,
|
85
|
+
attributes :name, :description,
|
58
86
|
# Whether this hierarchy has an 'all' member.
|
59
87
|
:has_all,
|
60
88
|
# Name of the 'all' member. If this attribute is not specified,
|
@@ -83,7 +111,7 @@ module Mondrian
|
|
83
111
|
end
|
84
112
|
|
85
113
|
class Level < SchemaElement
|
86
|
-
attributes :description,
|
114
|
+
attributes :name, :description,
|
87
115
|
# The name of the table that the column comes from.
|
88
116
|
# If this hierarchy is based upon just one table, defaults to the name of that table;
|
89
117
|
# otherwise, it is required.
|
@@ -128,10 +156,31 @@ module Mondrian
|
|
128
156
|
# Default value: 'Never'
|
129
157
|
:hide_member_if
|
130
158
|
data_dictionary_names :table, :column, :name_column, :ordinal_column, :parent_column # values in XML will be uppercased when using Oracle driver
|
159
|
+
elements :key_expression, :name_expression, :ordinal_expression
|
160
|
+
end
|
161
|
+
|
162
|
+
class KeyExpression < SchemaElement
|
163
|
+
elements :sql
|
164
|
+
end
|
165
|
+
|
166
|
+
class NameExpression < SchemaElement
|
167
|
+
elements :sql
|
168
|
+
end
|
169
|
+
|
170
|
+
class OrdinalExpression < SchemaElement
|
171
|
+
elements :sql
|
172
|
+
end
|
173
|
+
|
174
|
+
class Sql < SchemaElement
|
175
|
+
def self.name
|
176
|
+
'SQL'
|
177
|
+
end
|
178
|
+
attributes :dialect
|
179
|
+
content :text
|
131
180
|
end
|
132
181
|
|
133
182
|
class Measure < SchemaElement
|
134
|
-
attributes :description,
|
183
|
+
attributes :name, :description,
|
135
184
|
# Column which is source of this measure's values.
|
136
185
|
# If not specified, a measure expression must be specified.
|
137
186
|
:column,
|
@@ -146,11 +195,25 @@ module Mondrian
|
|
146
195
|
end
|
147
196
|
|
148
197
|
class CalculatedMember < SchemaElement
|
149
|
-
attributes :description,
|
150
|
-
# MDX expression which gives the value of this member. Equivalent to the Formula sub-element.
|
151
|
-
:formula,
|
198
|
+
attributes :name, :description,
|
152
199
|
# Name of the dimension which this member belongs to.
|
153
|
-
:dimension
|
200
|
+
:dimension,
|
201
|
+
# Format string with which to format cells of this measure. For more details, see the mondrian.util.Format class.
|
202
|
+
:format_string
|
203
|
+
elements :formula, :calculated_member_property
|
204
|
+
end
|
205
|
+
|
206
|
+
class Formula < SchemaElement
|
207
|
+
content :text
|
208
|
+
end
|
209
|
+
|
210
|
+
class CalculatedMemberProperty < SchemaElement
|
211
|
+
attributes :name, :description,
|
212
|
+
# MDX expression which defines the value of this property. If the expression is a constant string, you could enclose it in quotes,
|
213
|
+
# or just specify the 'value' attribute instead.
|
214
|
+
:expression,
|
215
|
+
# Value of this property. If the value is not constant, specify the 'expression' attribute instead.
|
216
|
+
:value
|
154
217
|
end
|
155
218
|
|
156
219
|
end
|
@@ -10,7 +10,13 @@ module Mondrian
|
|
10
10
|
name = nil
|
11
11
|
end
|
12
12
|
@attributes = {}
|
13
|
-
|
13
|
+
if name
|
14
|
+
if self.class.content
|
15
|
+
@content = name
|
16
|
+
else
|
17
|
+
@attributes[:name] = name
|
18
|
+
end
|
19
|
+
end
|
14
20
|
@attributes.merge!(attributes)
|
15
21
|
self.class.elements.each do |element|
|
16
22
|
instance_variable_set("@#{pluralize(element)}", [])
|
@@ -56,7 +62,13 @@ module Mondrian
|
|
56
62
|
end
|
57
63
|
end
|
58
64
|
|
65
|
+
def self.content(type=nil)
|
66
|
+
return @content if type.nil?
|
67
|
+
@content = type
|
68
|
+
end
|
69
|
+
|
59
70
|
def to_xml(options={})
|
71
|
+
options[:upcase_data_dictionary] = @upcase_data_dictionary unless @upcase_data_dictionary.nil?
|
60
72
|
Nokogiri::XML::Builder.new do |xml|
|
61
73
|
add_to_xml(xml, options)
|
62
74
|
end.to_xml
|
@@ -65,9 +77,13 @@ module Mondrian
|
|
65
77
|
protected
|
66
78
|
|
67
79
|
def add_to_xml(xml, options)
|
68
|
-
|
69
|
-
self.class.
|
70
|
-
|
80
|
+
if self.class.content
|
81
|
+
xml.send(tag_name(self.class.name), @content, xmlized_attributes(options))
|
82
|
+
else
|
83
|
+
xml.send(tag_name(self.class.name), xmlized_attributes(options)) do
|
84
|
+
self.class.elements.each do |element|
|
85
|
+
instance_variable_get("@#{pluralize(element)}").each {|item| item.add_to_xml(xml, options)}
|
86
|
+
end
|
71
87
|
end
|
72
88
|
end
|
73
89
|
end
|
@@ -75,8 +91,10 @@ module Mondrian
|
|
75
91
|
private
|
76
92
|
|
77
93
|
def xmlized_attributes(options)
|
78
|
-
# data dictionary values should be in uppercase
|
79
|
-
|
94
|
+
# data dictionary values should be in uppercase if schema defined with :upcase_data_dictionary => true
|
95
|
+
# or by default when using Oracle or LucidDB driver (can be overridden by :upcase_data_dictionary => false)
|
96
|
+
upcase_attributes = if options[:upcase_data_dictionary].nil? && %w(oracle luciddb).include?(options[:driver]) ||
|
97
|
+
options[:upcase_data_dictionary]
|
80
98
|
self.class.data_dictionary_names
|
81
99
|
else
|
82
100
|
[]
|
data/mondrian-olap.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{mondrian-olap}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Raimonds Simanovskis"]
|
12
|
-
s.date = %q{2011-
|
12
|
+
s.date = %q{2011-07-01}
|
13
13
|
s.description = %q{JRuby gem for performing multidimensional queries of relational database data using Mondrian OLAP Java library
|
14
14
|
}
|
15
15
|
s.email = %q{raimonds.simanovskis@gmail.com}
|
@@ -20,6 +20,7 @@ Gem::Specification.new do |s|
|
|
20
20
|
]
|
21
21
|
s.files = [
|
22
22
|
".rspec",
|
23
|
+
"Changelog.md",
|
23
24
|
"Gemfile",
|
24
25
|
"LICENSE-Mondrian.html",
|
25
26
|
"LICENSE.txt",
|
@@ -81,10 +82,12 @@ Gem::Specification.new do |s|
|
|
81
82
|
s.add_runtime_dependency(%q<nokogiri>, ["~> 1.5.0.beta.4"])
|
82
83
|
s.add_development_dependency(%q<jruby-openssl>, [">= 0"])
|
83
84
|
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
85
|
+
s.add_development_dependency(%q<rdoc>, [">= 0"])
|
84
86
|
s.add_development_dependency(%q<rspec>, ["~> 2.5"])
|
85
87
|
s.add_development_dependency(%q<autotest>, [">= 0"])
|
86
88
|
s.add_development_dependency(%q<jdbc-mysql>, [">= 0"])
|
87
89
|
s.add_development_dependency(%q<jdbc-postgres>, [">= 0"])
|
90
|
+
s.add_development_dependency(%q<jdbc-luciddb>, [">= 0"])
|
88
91
|
s.add_development_dependency(%q<activerecord>, ["~> 3.0.5"])
|
89
92
|
s.add_development_dependency(%q<activerecord-jdbc-adapter>, [">= 0"])
|
90
93
|
s.add_development_dependency(%q<activerecord-oracle_enhanced-adapter>, [">= 0"])
|
@@ -92,10 +95,12 @@ Gem::Specification.new do |s|
|
|
92
95
|
s.add_dependency(%q<nokogiri>, ["~> 1.5.0.beta.4"])
|
93
96
|
s.add_dependency(%q<jruby-openssl>, [">= 0"])
|
94
97
|
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
98
|
+
s.add_dependency(%q<rdoc>, [">= 0"])
|
95
99
|
s.add_dependency(%q<rspec>, ["~> 2.5"])
|
96
100
|
s.add_dependency(%q<autotest>, [">= 0"])
|
97
101
|
s.add_dependency(%q<jdbc-mysql>, [">= 0"])
|
98
102
|
s.add_dependency(%q<jdbc-postgres>, [">= 0"])
|
103
|
+
s.add_dependency(%q<jdbc-luciddb>, [">= 0"])
|
99
104
|
s.add_dependency(%q<activerecord>, ["~> 3.0.5"])
|
100
105
|
s.add_dependency(%q<activerecord-jdbc-adapter>, [">= 0"])
|
101
106
|
s.add_dependency(%q<activerecord-oracle_enhanced-adapter>, [">= 0"])
|
@@ -104,10 +109,12 @@ Gem::Specification.new do |s|
|
|
104
109
|
s.add_dependency(%q<nokogiri>, ["~> 1.5.0.beta.4"])
|
105
110
|
s.add_dependency(%q<jruby-openssl>, [">= 0"])
|
106
111
|
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
112
|
+
s.add_dependency(%q<rdoc>, [">= 0"])
|
107
113
|
s.add_dependency(%q<rspec>, ["~> 2.5"])
|
108
114
|
s.add_dependency(%q<autotest>, [">= 0"])
|
109
115
|
s.add_dependency(%q<jdbc-mysql>, [">= 0"])
|
110
116
|
s.add_dependency(%q<jdbc-postgres>, [">= 0"])
|
117
|
+
s.add_dependency(%q<jdbc-luciddb>, [">= 0"])
|
111
118
|
s.add_dependency(%q<activerecord>, ["~> 3.0.5"])
|
112
119
|
s.add_dependency(%q<activerecord-jdbc-adapter>, [">= 0"])
|
113
120
|
s.add_dependency(%q<activerecord-oracle_enhanced-adapter>, [">= 0"])
|
@@ -61,6 +61,9 @@ fname || ' ' || lname
|
|
61
61
|
</SQL>
|
62
62
|
<SQL dialect="mysql">
|
63
63
|
CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)
|
64
|
+
</SQL>
|
65
|
+
<SQL dialect="luciddb">
|
66
|
+
"fname" || ' ' || "lname"
|
64
67
|
</SQL>
|
65
68
|
<SQL dialect="generic">
|
66
69
|
fullname
|
@@ -75,6 +78,9 @@ fname || ' ' || lname
|
|
75
78
|
</SQL>
|
76
79
|
<SQL dialect="mysql">
|
77
80
|
CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)
|
81
|
+
</SQL>
|
82
|
+
<SQL dialect="luciddb">
|
83
|
+
"fname" || ' ' || "lname"
|
78
84
|
</SQL>
|
79
85
|
<SQL dialect="generic">
|
80
86
|
fullname
|
@@ -61,6 +61,9 @@ fname || ' ' || lname
|
|
61
61
|
</SQL>
|
62
62
|
<SQL dialect="mysql">
|
63
63
|
CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)
|
64
|
+
</SQL>
|
65
|
+
<SQL dialect="luciddb">
|
66
|
+
fname || ' ' || lname
|
64
67
|
</SQL>
|
65
68
|
<SQL dialect="generic">
|
66
69
|
FULLNAME
|
@@ -75,6 +78,9 @@ fname || ' ' || lname
|
|
75
78
|
</SQL>
|
76
79
|
<SQL dialect="mysql">
|
77
80
|
CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)
|
81
|
+
</SQL>
|
82
|
+
<SQL dialect="luciddb">
|
83
|
+
fname || ' ' || lname
|
78
84
|
</SQL>
|
79
85
|
<SQL dialect="generic">
|
80
86
|
FULLNAME
|
data/spec/query_spec.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe "Query" do
|
4
|
+
def quote_table_name(name)
|
5
|
+
ActiveRecord::Base.connection.quote_table_name(name)
|
6
|
+
end
|
7
|
+
|
4
8
|
before(:all) do
|
5
9
|
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS_WITH_CATALOG)
|
6
10
|
@sql = ActiveRecord::Base.connection
|
@@ -17,9 +21,9 @@ describe "Query" do
|
|
17
21
|
FROM sales
|
18
22
|
LEFT JOIN products ON sales.product_id = products.id
|
19
23
|
LEFT JOIN product_classes ON products.product_class_id = product_classes.id
|
20
|
-
LEFT JOIN time ON sales.time_id = time.id
|
24
|
+
LEFT JOIN #{quote_table_name('time')} ON sales.time_id = #{quote_table_name('time')}.id
|
21
25
|
LEFT JOIN customers ON sales.customer_id = customers.id
|
22
|
-
WHERE time.the_year = 2010 AND time.quarter = 'Q1'
|
26
|
+
WHERE #{quote_table_name('time')}.the_year = 2010 AND #{quote_table_name('time')}.quarter = 'Q1'
|
23
27
|
AND customers.country = 'USA' AND customers.state_province = 'CA'
|
24
28
|
GROUP BY product_classes.product_family
|
25
29
|
ORDER BY product_classes.product_family
|
data/spec/rake_tasks.rb
CHANGED
@@ -53,133 +53,208 @@ namespace :db do
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
+
task :setup_luciddb => :require_spec_helper do
|
57
|
+
# create link to mysql database to import tables
|
58
|
+
# see description at http://pub.eigenbase.org/wiki/LucidDbCreateForeignServer
|
59
|
+
if MONDRIAN_DRIVER == 'luciddb'
|
60
|
+
conn = ActiveRecord::Base.connection
|
61
|
+
conn.execute "drop schema mondrian_test_source cascade" rescue nil
|
62
|
+
conn.execute "drop server mondrian_test_source" rescue nil
|
63
|
+
conn.execute "create schema mondrian_test_source"
|
64
|
+
conn.execute <<-SQL
|
65
|
+
create server mondrian_test_source
|
66
|
+
foreign data wrapper sys_jdbc
|
67
|
+
options(
|
68
|
+
driver_class 'com.mysql.jdbc.Driver',
|
69
|
+
url 'jdbc:mysql://localhost/mondrian_test?characterEncoding=utf-8&useCursorFetch=true',
|
70
|
+
user_name 'mondrian_test',
|
71
|
+
password 'mondrian_test',
|
72
|
+
login_timeout '10',
|
73
|
+
fetch_size '1000',
|
74
|
+
validation_query 'select 1',
|
75
|
+
schema_name 'MONDRIAN_TEST',
|
76
|
+
table_types 'TABLE')
|
77
|
+
SQL
|
78
|
+
conn.execute "import foreign schema mondrian_test from server mondrian_test_source into mondrian_test_source"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
56
82
|
task :define_models => :require_spec_helper do
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
83
|
+
unless MONDRIAN_DRIVER == 'luciddb'
|
84
|
+
class TimeDimension < ActiveRecord::Base
|
85
|
+
set_table_name "time"
|
86
|
+
validates_presence_of :the_date
|
87
|
+
before_create do
|
88
|
+
self.the_day = the_date.strftime("%A")
|
89
|
+
self.the_month = the_date.strftime("%B")
|
90
|
+
self.the_year = the_date.strftime("%Y").to_i
|
91
|
+
self.day_of_month = the_date.strftime("%d").to_i
|
92
|
+
self.week_of_year = the_date.strftime("%W").to_i
|
93
|
+
self.month_of_year = the_date.strftime("%m").to_i
|
94
|
+
self.quarter = "Q#{(month_of_year-1)/3+1}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
class Product < ActiveRecord::Base
|
98
|
+
belongs_to :product_class
|
99
|
+
end
|
100
|
+
class ProductClass < ActiveRecord::Base
|
101
|
+
end
|
102
|
+
class Customer < ActiveRecord::Base
|
103
|
+
end
|
104
|
+
class Sales < ActiveRecord::Base
|
105
|
+
set_table_name "sales"
|
106
|
+
belongs_to :time_by_day
|
107
|
+
belongs_to :product
|
108
|
+
belongs_to :customer
|
68
109
|
end
|
69
|
-
end
|
70
|
-
class Product < ActiveRecord::Base
|
71
|
-
belongs_to :product_class
|
72
|
-
end
|
73
|
-
class ProductClass < ActiveRecord::Base
|
74
|
-
end
|
75
|
-
class Customer < ActiveRecord::Base
|
76
|
-
end
|
77
|
-
class Sales < ActiveRecord::Base
|
78
|
-
set_table_name "sales"
|
79
|
-
belongs_to :time_by_day
|
80
|
-
belongs_to :product
|
81
|
-
belongs_to :customer
|
82
110
|
end
|
83
111
|
end
|
84
112
|
|
85
113
|
desc "Create test data"
|
86
|
-
task :create_data => [:create_tables, :create_time_data, :create_product_data, :create_customer_data, :create_sales_data]
|
114
|
+
task :create_data => [:create_tables, :setup_luciddb, :create_time_data, :create_product_data, :create_customer_data, :create_sales_data]
|
87
115
|
|
88
116
|
task :create_time_data => :define_models do
|
89
117
|
puts "==> Creating time dimension"
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
118
|
+
if MONDRIAN_DRIVER == 'luciddb'
|
119
|
+
ActiveRecord::Base.connection.execute 'truncate table "TIME"'
|
120
|
+
ActiveRecord::Base.connection.execute 'insert into "TIME" select * from mondrian_test_source."time"'
|
121
|
+
ActiveRecord::Base.connection.execute 'analyze table "TIME" compute statistics for all columns'
|
122
|
+
else
|
123
|
+
TimeDimension.delete_all
|
124
|
+
start_time = Time.local(2010,1,1)
|
125
|
+
(2*365).times do |i|
|
126
|
+
TimeDimension.create!(:the_date => start_time + i.day)
|
127
|
+
end
|
94
128
|
end
|
95
129
|
end
|
96
130
|
|
97
131
|
task :create_product_data => :define_models do
|
98
132
|
puts "==> Creating product data"
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
133
|
+
if MONDRIAN_DRIVER == 'luciddb'
|
134
|
+
ActiveRecord::Base.connection.execute 'truncate table product_classes'
|
135
|
+
ActiveRecord::Base.connection.execute 'truncate table products'
|
136
|
+
ActiveRecord::Base.connection.execute 'insert into product_classes select * from mondrian_test_source."product_classes"'
|
137
|
+
ActiveRecord::Base.connection.execute 'insert into products select * from mondrian_test_source."products"'
|
138
|
+
ActiveRecord::Base.connection.execute 'analyze table product_classes compute statistics for all columns'
|
139
|
+
ActiveRecord::Base.connection.execute 'analyze table products compute statistics for all columns'
|
140
|
+
else
|
141
|
+
Product.delete_all
|
142
|
+
ProductClass.delete_all
|
143
|
+
families = ["Drink", "Food", "Non-Consumable"]
|
144
|
+
(1..100).each do |i|
|
145
|
+
product_class = ProductClass.create!(
|
146
|
+
:product_family => families[i % 3],
|
147
|
+
:product_department => "Department #{i}",
|
148
|
+
:product_category => "Category #{i}",
|
149
|
+
:product_subcategory => "Subcategory #{i}"
|
150
|
+
)
|
151
|
+
Product.create!(
|
152
|
+
# LucidDB is not returning inserted ID therefore doing it hard way
|
153
|
+
:product_class_id => ProductClass.find_all_by_product_category("Category #{i}").first.id,
|
154
|
+
:brand_name => "Brand #{i}",
|
155
|
+
:product_name => "Product #{i}"
|
156
|
+
)
|
157
|
+
end
|
114
158
|
end
|
115
159
|
end
|
116
160
|
|
117
161
|
task :create_customer_data => :define_models do
|
118
162
|
puts "==> Creating customer data"
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
[
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
+
if MONDRIAN_DRIVER == 'luciddb'
|
164
|
+
ActiveRecord::Base.connection.execute 'truncate table customers'
|
165
|
+
ActiveRecord::Base.connection.execute 'insert into customers select * from mondrian_test_source."customers"'
|
166
|
+
ActiveRecord::Base.connection.execute 'analyze table customers compute statistics for all columns'
|
167
|
+
else
|
168
|
+
Customer.delete_all
|
169
|
+
i = 0
|
170
|
+
[
|
171
|
+
["Canada", "BC", "Burnaby"],["Canada", "BC", "Cliffside"],["Canada", "BC", "Haney"],["Canada", "BC", "Ladner"],
|
172
|
+
["Canada", "BC", "Langford"],["Canada", "BC", "Langley"],["Canada", "BC", "Metchosin"],["Canada", "BC", "N. Vancouver"],
|
173
|
+
["Canada", "BC", "Newton"],["Canada", "BC", "Oak Bay"],["Canada", "BC", "Port Hammond"],["Canada", "BC", "Richmond"],
|
174
|
+
["Canada", "BC", "Royal Oak"],["Canada", "BC", "Shawnee"],["Canada", "BC", "Sooke"],["Canada", "BC", "Vancouver"],
|
175
|
+
["Canada", "BC", "Victoria"],["Canada", "BC", "Westminster"],
|
176
|
+
["Mexico", "DF", "San Andres"],["Mexico", "DF", "Santa Anita"],["Mexico", "DF", "Santa Fe"],["Mexico", "DF", "Tixapan"],
|
177
|
+
["Mexico", "Guerrero", "Acapulco"],["Mexico", "Jalisco", "Guadalajara"],["Mexico", "Mexico", "Mexico City"],
|
178
|
+
["Mexico", "Oaxaca", "Tlaxiaco"],["Mexico", "Sinaloa", "La Cruz"],["Mexico", "Veracruz", "Orizaba"],
|
179
|
+
["Mexico", "Yucatan", "Merida"],["Mexico", "Zacatecas", "Camacho"],["Mexico", "Zacatecas", "Hidalgo"],
|
180
|
+
["USA", "CA", "Altadena"],["USA", "CA", "Arcadia"],["USA", "CA", "Bellflower"],["USA", "CA", "Berkeley"],
|
181
|
+
["USA", "CA", "Beverly Hills"],["USA", "CA", "Burbank"],["USA", "CA", "Burlingame"],["USA", "CA", "Chula Vista"],
|
182
|
+
["USA", "CA", "Colma"],["USA", "CA", "Concord"],["USA", "CA", "Coronado"],["USA", "CA", "Daly City"],
|
183
|
+
["USA", "CA", "Downey"],["USA", "CA", "El Cajon"],["USA", "CA", "Fremont"],["USA", "CA", "Glendale"],
|
184
|
+
["USA", "CA", "Grossmont"],["USA", "CA", "Imperial Beach"],["USA", "CA", "La Jolla"],["USA", "CA", "La Mesa"],
|
185
|
+
["USA", "CA", "Lakewood"],["USA", "CA", "Lemon Grove"],["USA", "CA", "Lincoln Acres"],["USA", "CA", "Long Beach"],
|
186
|
+
["USA", "CA", "Los Angeles"],["USA", "CA", "Mill Valley"],["USA", "CA", "National City"],["USA", "CA", "Newport Beach"],
|
187
|
+
["USA", "CA", "Novato"],["USA", "CA", "Oakland"],["USA", "CA", "Palo Alto"],["USA", "CA", "Pomona"],
|
188
|
+
["USA", "CA", "Redwood City"],["USA", "CA", "Richmond"],["USA", "CA", "San Carlos"],["USA", "CA", "San Diego"],
|
189
|
+
["USA", "CA", "San Francisco"],["USA", "CA", "San Gabriel"],["USA", "CA", "San Jose"],["USA", "CA", "Santa Cruz"],
|
190
|
+
["USA", "CA", "Santa Monica"],["USA", "CA", "Spring Valley"],["USA", "CA", "Torrance"],["USA", "CA", "West Covina"],
|
191
|
+
["USA", "CA", "Woodland Hills"],
|
192
|
+
["USA", "OR", "Albany"],["USA", "OR", "Beaverton"],["USA", "OR", "Corvallis"],["USA", "OR", "Lake Oswego"],
|
193
|
+
["USA", "OR", "Lebanon"],["USA", "OR", "Milwaukie"],["USA", "OR", "Oregon City"],["USA", "OR", "Portland"],
|
194
|
+
["USA", "OR", "Salem"],["USA", "OR", "W. Linn"],["USA", "OR", "Woodburn"],
|
195
|
+
["USA", "WA", "Anacortes"],["USA", "WA", "Ballard"],["USA", "WA", "Bellingham"],["USA", "WA", "Bremerton"],
|
196
|
+
["USA", "WA", "Burien"],["USA", "WA", "Edmonds"],["USA", "WA", "Everett"],["USA", "WA", "Issaquah"],
|
197
|
+
["USA", "WA", "Kirkland"],["USA", "WA", "Lynnwood"],["USA", "WA", "Marysville"],["USA", "WA", "Olympia"],
|
198
|
+
["USA", "WA", "Port Orchard"],["USA", "WA", "Puyallup"],["USA", "WA", "Redmond"],["USA", "WA", "Renton"],
|
199
|
+
["USA", "WA", "Seattle"],["USA", "WA", "Sedro Woolley"],["USA", "WA", "Spokane"],["USA", "WA", "Tacoma"],
|
200
|
+
["USA", "WA", "Walla Walla"],["USA", "WA", "Yakima"]
|
201
|
+
].each do |country, state, city|
|
202
|
+
i += 1
|
203
|
+
Customer.create!(
|
204
|
+
:country => country,
|
205
|
+
:state_province => state,
|
206
|
+
:city => city,
|
207
|
+
:fname => "First#{i}",
|
208
|
+
:lname => "Last#{i}",
|
209
|
+
:fullname => "First#{i} Last#{i}",
|
210
|
+
:gender => i % 2 == 0 ? "M" : "F"
|
211
|
+
)
|
212
|
+
end
|
163
213
|
end
|
164
214
|
end
|
165
215
|
|
166
216
|
task :create_sales_data => :define_models do
|
167
217
|
puts "==> Creating sales data"
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
218
|
+
if MONDRIAN_DRIVER == 'luciddb'
|
219
|
+
ActiveRecord::Base.connection.execute 'truncate table sales'
|
220
|
+
ActiveRecord::Base.connection.execute 'insert into sales select * from mondrian_test_source."sales"'
|
221
|
+
ActiveRecord::Base.connection.execute 'analyze table sales compute statistics for all columns'
|
222
|
+
else
|
223
|
+
Sales.delete_all
|
224
|
+
count = 100
|
225
|
+
# LucidDB does not support LIMIT therefore select all and limit in Ruby
|
226
|
+
products = Product.order("id").all[0...count]
|
227
|
+
times = TimeDimension.order("id").all[0...count]
|
228
|
+
customers = Customer.order("id").all[0...count]
|
229
|
+
count.times do |i|
|
230
|
+
Sales.create!(
|
231
|
+
:product_id => products[i].id,
|
232
|
+
:time_id => times[i].id,
|
233
|
+
:customer_id => customers[i].id,
|
234
|
+
:store_sales => BigDecimal("2#{i}.12"),
|
235
|
+
:store_cost => BigDecimal("1#{i}.1234"),
|
236
|
+
:unit_sales => i+1
|
237
|
+
)
|
238
|
+
end
|
182
239
|
end
|
183
240
|
end
|
184
241
|
|
185
242
|
end
|
243
|
+
|
244
|
+
namespace :spec do
|
245
|
+
%w(mysql postgresql oracle luciddb).each do |driver|
|
246
|
+
desc "Run specs with #{driver} driver"
|
247
|
+
task driver do
|
248
|
+
ENV['MONDRIAN_DRIVER'] = driver
|
249
|
+
Rake::Task['spec'].reenable
|
250
|
+
Rake::Task['spec'].invoke
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
desc "Run specs with all database drivers"
|
255
|
+
task :all do
|
256
|
+
%w(mysql postgresql oracle luciddb).each do |driver|
|
257
|
+
Rake::Task["spec:#{driver}"].invoke
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
@@ -83,21 +83,57 @@ describe "Schema definition" do
|
|
83
83
|
XML
|
84
84
|
end
|
85
85
|
|
86
|
-
it "should render table name in uppercase when using Oracle driver" do
|
86
|
+
it "should render table name in uppercase when using Oracle or LucidDB driver" do
|
87
87
|
@schema.define do
|
88
88
|
cube 'Sales' do
|
89
|
-
table 'sales_fact', :alias => 'sales'
|
89
|
+
table 'sales_fact', :alias => 'sales', :schema => 'facts'
|
90
90
|
end
|
91
91
|
end
|
92
|
-
|
92
|
+
%w(oracle luciddb).each do |driver|
|
93
|
+
@schema.to_xml(:driver => driver).should be_like <<-XML
|
94
|
+
<?xml version="1.0"?>
|
95
|
+
<Schema name="default">
|
96
|
+
<Cube name="Sales">
|
97
|
+
<Table alias="SALES" name="SALES_FACT" schema="FACTS"/>
|
98
|
+
</Cube>
|
99
|
+
</Schema>
|
100
|
+
XML
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should render table name in uppercase when :upcase_data_dictionary option is set to true" do
|
105
|
+
@schema.define :upcase_data_dictionary => true do
|
106
|
+
cube 'Sales' do
|
107
|
+
table 'sales_fact', :alias => 'sales', :schema => 'facts'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
@schema.to_xml.should be_like <<-XML
|
93
111
|
<?xml version="1.0"?>
|
94
112
|
<Schema name="default">
|
95
113
|
<Cube name="Sales">
|
96
|
-
<Table alias="SALES" name="SALES_FACT"/>
|
114
|
+
<Table alias="SALES" name="SALES_FACT" schema="FACTS"/>
|
97
115
|
</Cube>
|
98
116
|
</Schema>
|
99
117
|
XML
|
100
118
|
end
|
119
|
+
|
120
|
+
it "should render table name in lowercase when using Oracle or LucidDB driver but with :upcase_data_dictionary set to false" do
|
121
|
+
@schema.define :upcase_data_dictionary => false do
|
122
|
+
cube 'Sales' do
|
123
|
+
table 'sales_fact', :alias => 'sales', :schema => 'facts'
|
124
|
+
end
|
125
|
+
end
|
126
|
+
%w(oracle luciddb).each do |driver|
|
127
|
+
@schema.to_xml(:driver => driver).should be_like <<-XML
|
128
|
+
<?xml version="1.0"?>
|
129
|
+
<Schema name="default">
|
130
|
+
<Cube name="Sales">
|
131
|
+
<Table alias="sales" name="sales_fact" schema="facts"/>
|
132
|
+
</Cube>
|
133
|
+
</Schema>
|
134
|
+
XML
|
135
|
+
end
|
136
|
+
end
|
101
137
|
end
|
102
138
|
|
103
139
|
describe "Dimension" do
|
@@ -287,6 +323,7 @@ describe "Schema definition" do
|
|
287
323
|
calculated_member 'Profit' do
|
288
324
|
dimension 'Measures'
|
289
325
|
formula '[Measures].[Store Sales] - [Measures].[Store Cost]'
|
326
|
+
format_string '#,##0.00'
|
290
327
|
end
|
291
328
|
end
|
292
329
|
end
|
@@ -294,7 +331,9 @@ describe "Schema definition" do
|
|
294
331
|
<?xml version="1.0"?>
|
295
332
|
<Schema name="default">
|
296
333
|
<Cube name="Sales">
|
297
|
-
<CalculatedMember dimension="Measures"
|
334
|
+
<CalculatedMember dimension="Measures" formatString="#,##0.00" name="Profit">
|
335
|
+
<Formula>[Measures].[Store Sales] - [Measures].[Store Cost]</Formula>
|
336
|
+
</CalculatedMember>
|
298
337
|
</Cube>
|
299
338
|
</Schema>
|
300
339
|
XML
|
data/spec/spec_helper.rb
CHANGED
@@ -23,6 +23,34 @@ when 'postgresql'
|
|
23
23
|
when 'oracle'
|
24
24
|
require 'active_record/connection_adapters/oracle_enhanced_adapter'
|
25
25
|
DATABASE_NAME = ENV['DATABASE_NAME'] || 'orcl'
|
26
|
+
when 'luciddb'
|
27
|
+
require 'jdbc/luciddb'
|
28
|
+
|
29
|
+
# Hack to disable :text type for LucidDB
|
30
|
+
require 'arjdbc/jdbc/type_converter'
|
31
|
+
ActiveRecord::ConnectionAdapters::JdbcTypeConverter::AR_TO_JDBC_TYPES.delete(:text)
|
32
|
+
|
33
|
+
# patches for LucidDB minimal AR support
|
34
|
+
require 'arjdbc/jdbc/adapter'
|
35
|
+
ActiveRecord::ConnectionAdapters::JdbcAdapter.class_eval do
|
36
|
+
def modify_types(tp)
|
37
|
+
# mapping of ActiveRecord data types to LucidDB data types
|
38
|
+
# data will be imported into LucidDB therefore primary key is defined as simple integer field
|
39
|
+
tp[:primary_key] = "INT"
|
40
|
+
tp[:integer] = "INT"
|
41
|
+
end
|
42
|
+
# by default LucidDB stores table and column names in uppercase
|
43
|
+
def quote_table_name(name)
|
44
|
+
"\"#{name.to_s.upcase}\""
|
45
|
+
end
|
46
|
+
def quote_column_name(name)
|
47
|
+
"\"#{name.to_s.upcase}\""
|
48
|
+
end
|
49
|
+
end
|
50
|
+
JDBC_DRIVER = 'org.luciddb.jdbc.LucidDbClientDriver'
|
51
|
+
DATABASE_USER.upcase! if DATABASE_USER == 'mondrian_test'
|
52
|
+
DATABASE_NAME = nil
|
53
|
+
DATABASE_SCHEMA = ENV['DATABASE_SCHEMA'] || 'mondrian_test'
|
26
54
|
end
|
27
55
|
|
28
56
|
puts "==> Using #{MONDRIAN_DRIVER} driver"
|
@@ -52,6 +80,17 @@ if MONDRIAN_DRIVER == 'oracle'
|
|
52
80
|
:username => CONNECTION_PARAMS[:username],
|
53
81
|
:password => CONNECTION_PARAMS[:password]
|
54
82
|
}
|
83
|
+
elsif MONDRIAN_DRIVER == 'luciddb'
|
84
|
+
CATALOG_FILE = File.expand_path('../fixtures/MondrianTestOracle.xml', __FILE__)
|
85
|
+
CONNECTION_PARAMS[:database] = nil
|
86
|
+
CONNECTION_PARAMS[:database_schema] = DATABASE_SCHEMA
|
87
|
+
AR_CONNECTION_PARAMS = {
|
88
|
+
:adapter => 'jdbc',
|
89
|
+
:driver => JDBC_DRIVER,
|
90
|
+
:url => "jdbc:#{MONDRIAN_DRIVER}:http://#{CONNECTION_PARAMS[:host]};schema=#{CONNECTION_PARAMS[:database_schema]}",
|
91
|
+
:username => CONNECTION_PARAMS[:username],
|
92
|
+
:password => CONNECTION_PARAMS[:password]
|
93
|
+
}
|
55
94
|
else
|
56
95
|
CATALOG_FILE = File.expand_path('../fixtures/MondrianTest.xml', __FILE__)
|
57
96
|
AR_CONNECTION_PARAMS = {
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: mondrian-olap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.2.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Raimonds Simanovskis
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-
|
13
|
+
date: 2011-07-01 00:00:00 +03:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -47,29 +47,29 @@ dependencies:
|
|
47
47
|
prerelease: false
|
48
48
|
type: :development
|
49
49
|
- !ruby/object:Gem::Dependency
|
50
|
-
name:
|
50
|
+
name: rdoc
|
51
51
|
version_requirements: &id004 !ruby/object:Gem::Requirement
|
52
52
|
none: false
|
53
53
|
requirements:
|
54
|
-
- -
|
54
|
+
- - ">="
|
55
55
|
- !ruby/object:Gem::Version
|
56
|
-
version: "
|
56
|
+
version: "0"
|
57
57
|
requirement: *id004
|
58
58
|
prerelease: false
|
59
59
|
type: :development
|
60
60
|
- !ruby/object:Gem::Dependency
|
61
|
-
name:
|
61
|
+
name: rspec
|
62
62
|
version_requirements: &id005 !ruby/object:Gem::Requirement
|
63
63
|
none: false
|
64
64
|
requirements:
|
65
|
-
- -
|
65
|
+
- - ~>
|
66
66
|
- !ruby/object:Gem::Version
|
67
|
-
version: "
|
67
|
+
version: "2.5"
|
68
68
|
requirement: *id005
|
69
69
|
prerelease: false
|
70
70
|
type: :development
|
71
71
|
- !ruby/object:Gem::Dependency
|
72
|
-
name:
|
72
|
+
name: autotest
|
73
73
|
version_requirements: &id006 !ruby/object:Gem::Requirement
|
74
74
|
none: false
|
75
75
|
requirements:
|
@@ -80,7 +80,7 @@ dependencies:
|
|
80
80
|
prerelease: false
|
81
81
|
type: :development
|
82
82
|
- !ruby/object:Gem::Dependency
|
83
|
-
name: jdbc-
|
83
|
+
name: jdbc-mysql
|
84
84
|
version_requirements: &id007 !ruby/object:Gem::Requirement
|
85
85
|
none: false
|
86
86
|
requirements:
|
@@ -91,18 +91,18 @@ dependencies:
|
|
91
91
|
prerelease: false
|
92
92
|
type: :development
|
93
93
|
- !ruby/object:Gem::Dependency
|
94
|
-
name:
|
94
|
+
name: jdbc-postgres
|
95
95
|
version_requirements: &id008 !ruby/object:Gem::Requirement
|
96
96
|
none: false
|
97
97
|
requirements:
|
98
|
-
- -
|
98
|
+
- - ">="
|
99
99
|
- !ruby/object:Gem::Version
|
100
|
-
version:
|
100
|
+
version: "0"
|
101
101
|
requirement: *id008
|
102
102
|
prerelease: false
|
103
103
|
type: :development
|
104
104
|
- !ruby/object:Gem::Dependency
|
105
|
-
name:
|
105
|
+
name: jdbc-luciddb
|
106
106
|
version_requirements: &id009 !ruby/object:Gem::Requirement
|
107
107
|
none: false
|
108
108
|
requirements:
|
@@ -113,14 +113,36 @@ dependencies:
|
|
113
113
|
prerelease: false
|
114
114
|
type: :development
|
115
115
|
- !ruby/object:Gem::Dependency
|
116
|
-
name: activerecord
|
116
|
+
name: activerecord
|
117
117
|
version_requirements: &id010 !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
119
|
+
requirements:
|
120
|
+
- - ~>
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: 3.0.5
|
123
|
+
requirement: *id010
|
124
|
+
prerelease: false
|
125
|
+
type: :development
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: activerecord-jdbc-adapter
|
128
|
+
version_requirements: &id011 !ruby/object:Gem::Requirement
|
118
129
|
none: false
|
119
130
|
requirements:
|
120
131
|
- - ">="
|
121
132
|
- !ruby/object:Gem::Version
|
122
133
|
version: "0"
|
123
|
-
requirement: *
|
134
|
+
requirement: *id011
|
135
|
+
prerelease: false
|
136
|
+
type: :development
|
137
|
+
- !ruby/object:Gem::Dependency
|
138
|
+
name: activerecord-oracle_enhanced-adapter
|
139
|
+
version_requirements: &id012 !ruby/object:Gem::Requirement
|
140
|
+
none: false
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: "0"
|
145
|
+
requirement: *id012
|
124
146
|
prerelease: false
|
125
147
|
type: :development
|
126
148
|
description: |
|
@@ -137,6 +159,7 @@ extra_rdoc_files:
|
|
137
159
|
- README.rdoc
|
138
160
|
files:
|
139
161
|
- .rspec
|
162
|
+
- Changelog.md
|
140
163
|
- Gemfile
|
141
164
|
- LICENSE-Mondrian.html
|
142
165
|
- LICENSE.txt
|