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.
@@ -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
@@ -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
- {[Product].children} ON ROWS
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. ["[Product].[Drink]", "[Product].[Food]", "[Product].[Non-Consumable]"]
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('[Product].children').
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('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]').
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 Oracle databases. When using MySQL or PostgreSQL databases then install jdbc-mysql or jdbc-postgres gem and require "jdbc/mysql" or "jdbc/postgres" to load corresponding JDBC database driver. When using Oracle then include Oracle JDBC driver (ojdbc14.jar or ojdbc6.jar) in CLASSPATH or copy to JRUBY_HOME/lib.
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
 
@@ -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 "oracle" to specify database driver that should be used.
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 has been tested with JRuby 1.5 and 1.6 release candidate versions.
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
@@ -33,8 +33,8 @@ end
33
33
 
34
34
  task :default => :spec
35
35
 
36
- require 'rake/rdoctask'
37
- Rake::RDocTask.new do |rdoc|
36
+ require 'rdoc/task'
37
+ RDoc::Task.new do |rdoc|
38
38
  version = File.exist?('VERSION') ? File.read('VERSION') : ""
39
39
 
40
40
  rdoc.rdoc_dir = 'doc'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.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
@@ -5,7 +5,10 @@ Dir["#{directory}/*.jar"].each do |file|
5
5
  require file
6
6
  end
7
7
 
8
- java.lang.System.setProperty("log4j.configuration", "file://#{directory}/log4j.properties")
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
- @raw_jdbc_connection = Java::JavaSql::DriverManager.getConnection(connection_string)
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
- (@params[:catalog] ? "Catalog=#{catalog_uri}" : "CatalogContent=#{catalog_content}")
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
- "?user=#{@params[:username]}&password=#{@params[:password]}"
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:#{@params[:username]}/#{@params[:password]}@#{@params[:database]}"
103
+ "jdbc:oracle:thin:@#{@params[:database]}"
79
104
  else
80
105
  @params[:url] ||
81
- "jdbc:oracle:thin:#{@params[:username]}/#{@params[:password]}" <<
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
@@ -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'
@@ -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
- @attributes[:name] = name || 'default' # otherwise connection with empty name fails
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
- attributes :description
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
- @attributes[:name] = name if name
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
- xml.send(tag_name(self.class.name), xmlized_attributes(options)) do
69
- self.class.elements.each do |element|
70
- instance_variable_get("@#{pluralize(element)}").each {|item| item.add_to_xml(xml, options)}
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 when using Oracle driver
79
- upcase_attributes = if options[:driver] == 'oracle'
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
  []
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{mondrian-olap}
8
- s.version = "0.1.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-03-19}
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
@@ -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
@@ -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
- class TimeDimension < ActiveRecord::Base
58
- set_table_name "time"
59
- validates_presence_of :the_date
60
- before_create do
61
- self.the_day = the_date.strftime("%A")
62
- self.the_month = the_date.strftime("%B")
63
- self.the_year = the_date.strftime("%Y").to_i
64
- self.day_of_month = the_date.strftime("%d").to_i
65
- self.week_of_year = the_date.strftime("%W").to_i
66
- self.month_of_year = the_date.strftime("%m").to_i
67
- self.quarter = "Q#{(month_of_year-1)/3+1}"
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
- TimeDimension.delete_all
91
- start_time = Time.local(2010,1,1)
92
- (2*365).times do |i|
93
- TimeDimension.create!(:the_date => start_time + i.day)
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
- Product.delete_all
100
- ProductClass.delete_all
101
- families = ["Drink", "Food", "Non-Consumable"]
102
- (1..100).each do |i|
103
- product_class = ProductClass.create!(
104
- :product_family => families[i % 3],
105
- :product_department => "Department #{i}",
106
- :product_category => "Category #{i}",
107
- :product_subcategory => "Subcategory #{i}"
108
- )
109
- Product.create!(
110
- :product_class_id => product_class.id,
111
- :brand_name => "Brand #{i}",
112
- :product_name => "Product #{i}"
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
- Customer.delete_all
120
- i = 0
121
- [
122
- ["Canada", "BC", "Burnaby"],["Canada", "BC", "Cliffside"],["Canada", "BC", "Haney"],["Canada", "BC", "Ladner"],
123
- ["Canada", "BC", "Langford"],["Canada", "BC", "Langley"],["Canada", "BC", "Metchosin"],["Canada", "BC", "N. Vancouver"],
124
- ["Canada", "BC", "Newton"],["Canada", "BC", "Oak Bay"],["Canada", "BC", "Port Hammond"],["Canada", "BC", "Richmond"],
125
- ["Canada", "BC", "Royal Oak"],["Canada", "BC", "Shawnee"],["Canada", "BC", "Sooke"],["Canada", "BC", "Vancouver"],
126
- ["Canada", "BC", "Victoria"],["Canada", "BC", "Westminster"],
127
- ["Mexico", "DF", "San Andres"],["Mexico", "DF", "Santa Anita"],["Mexico", "DF", "Santa Fe"],["Mexico", "DF", "Tixapan"],
128
- ["Mexico", "Guerrero", "Acapulco"],["Mexico", "Jalisco", "Guadalajara"],["Mexico", "Mexico", "Mexico City"],
129
- ["Mexico", "Oaxaca", "Tlaxiaco"],["Mexico", "Sinaloa", "La Cruz"],["Mexico", "Veracruz", "Orizaba"],
130
- ["Mexico", "Yucatan", "Merida"],["Mexico", "Zacatecas", "Camacho"],["Mexico", "Zacatecas", "Hidalgo"],
131
- ["USA", "CA", "Altadena"],["USA", "CA", "Arcadia"],["USA", "CA", "Bellflower"],["USA", "CA", "Berkeley"],
132
- ["USA", "CA", "Beverly Hills"],["USA", "CA", "Burbank"],["USA", "CA", "Burlingame"],["USA", "CA", "Chula Vista"],
133
- ["USA", "CA", "Colma"],["USA", "CA", "Concord"],["USA", "CA", "Coronado"],["USA", "CA", "Daly City"],
134
- ["USA", "CA", "Downey"],["USA", "CA", "El Cajon"],["USA", "CA", "Fremont"],["USA", "CA", "Glendale"],
135
- ["USA", "CA", "Grossmont"],["USA", "CA", "Imperial Beach"],["USA", "CA", "La Jolla"],["USA", "CA", "La Mesa"],
136
- ["USA", "CA", "Lakewood"],["USA", "CA", "Lemon Grove"],["USA", "CA", "Lincoln Acres"],["USA", "CA", "Long Beach"],
137
- ["USA", "CA", "Los Angeles"],["USA", "CA", "Mill Valley"],["USA", "CA", "National City"],["USA", "CA", "Newport Beach"],
138
- ["USA", "CA", "Novato"],["USA", "CA", "Oakland"],["USA", "CA", "Palo Alto"],["USA", "CA", "Pomona"],
139
- ["USA", "CA", "Redwood City"],["USA", "CA", "Richmond"],["USA", "CA", "San Carlos"],["USA", "CA", "San Diego"],
140
- ["USA", "CA", "San Francisco"],["USA", "CA", "San Gabriel"],["USA", "CA", "San Jose"],["USA", "CA", "Santa Cruz"],
141
- ["USA", "CA", "Santa Monica"],["USA", "CA", "Spring Valley"],["USA", "CA", "Torrance"],["USA", "CA", "West Covina"],
142
- ["USA", "CA", "Woodland Hills"],
143
- ["USA", "OR", "Albany"],["USA", "OR", "Beaverton"],["USA", "OR", "Corvallis"],["USA", "OR", "Lake Oswego"],
144
- ["USA", "OR", "Lebanon"],["USA", "OR", "Milwaukie"],["USA", "OR", "Oregon City"],["USA", "OR", "Portland"],
145
- ["USA", "OR", "Salem"],["USA", "OR", "W. Linn"],["USA", "OR", "Woodburn"],
146
- ["USA", "WA", "Anacortes"],["USA", "WA", "Ballard"],["USA", "WA", "Bellingham"],["USA", "WA", "Bremerton"],
147
- ["USA", "WA", "Burien"],["USA", "WA", "Edmonds"],["USA", "WA", "Everett"],["USA", "WA", "Issaquah"],
148
- ["USA", "WA", "Kirkland"],["USA", "WA", "Lynnwood"],["USA", "WA", "Marysville"],["USA", "WA", "Olympia"],
149
- ["USA", "WA", "Port Orchard"],["USA", "WA", "Puyallup"],["USA", "WA", "Redmond"],["USA", "WA", "Renton"],
150
- ["USA", "WA", "Seattle"],["USA", "WA", "Sedro Woolley"],["USA", "WA", "Spokane"],["USA", "WA", "Tacoma"],
151
- ["USA", "WA", "Walla Walla"],["USA", "WA", "Yakima"]
152
- ].each do |country, state, city|
153
- i += 1
154
- Customer.create!(
155
- :country => country,
156
- :state_province => state,
157
- :city => city,
158
- :fname => "First#{i}",
159
- :lname => "Last#{i}",
160
- :fullname => "First#{i} Last#{i}",
161
- :gender => i % 2 == 0 ? "M" : "F"
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
- Sales.delete_all
169
- count = 100
170
- products = Product.order("id").limit(count).all
171
- times = TimeDimension.order("id").limit(count).all
172
- customers = Customer.order("id").limit(count).all
173
- count.times do |i|
174
- Sales.create!(
175
- :product_id => products[i].id,
176
- :time_id => times[i].id,
177
- :customer_id => customers[i].id,
178
- :store_sales => BigDecimal("2#{i}.12"),
179
- :store_cost => BigDecimal("1#{i}.1234"),
180
- :unit_sales => i+1
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
- @schema.to_xml(:driver => 'oracle').should be_like <<-XML
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" formula="[Measures].[Store Sales] - [Measures].[Store Cost]" name="Profit"/>
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
@@ -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.1.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-03-19 00:00:00 +02:00
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: rspec
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: "2.5"
56
+ version: "0"
57
57
  requirement: *id004
58
58
  prerelease: false
59
59
  type: :development
60
60
  - !ruby/object:Gem::Dependency
61
- name: autotest
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: "0"
67
+ version: "2.5"
68
68
  requirement: *id005
69
69
  prerelease: false
70
70
  type: :development
71
71
  - !ruby/object:Gem::Dependency
72
- name: jdbc-mysql
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-postgres
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: activerecord
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: 3.0.5
100
+ version: "0"
101
101
  requirement: *id008
102
102
  prerelease: false
103
103
  type: :development
104
104
  - !ruby/object:Gem::Dependency
105
- name: activerecord-jdbc-adapter
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-oracle_enhanced-adapter
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: *id010
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