mondrian-olap 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +9 -0
- data/LICENSE.txt +1 -1
- data/README.md +1 -1
- data/VERSION +1 -1
- data/lib/mondrian/jars/{mondrian-3.11.0.0-353.jar → mondrian-3.12.0.6-237.jar} +0 -0
- data/lib/mondrian/olap/connection.rb +3 -1
- data/lib/mondrian/olap/cube.rb +38 -0
- data/lib/mondrian/olap/result.rb +110 -37
- data/spec/cube_cache_control_spec.rb +267 -0
- data/spec/cube_spec.rb +0 -2
- data/spec/fixtures/MondrianTest.xml +1 -0
- data/spec/fixtures/MondrianTestOracle.xml +1 -0
- data/spec/query_spec.rb +79 -8
- data/spec/rake_tasks.rb +10 -1
- data/spec/spec_helper.rb +1 -0
- metadata +13 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ebf052cd103202ea4f2a37a914d13379776349bf
|
4
|
+
data.tar.gz: af98519e88cc3f8a98c8fdf9538847115b499f7a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 801ab9633c7b0e4efeb83ddc12d7ffebfe1966c6e65065c02692b0aa662efb5552c4fda1acc83a407bb82eaacaf76223ed03d6a4c863a0c7f8673bf32413a55e
|
7
|
+
data.tar.gz: 0969711c0ffe2f39b4616b016718294b9b759333890e56f8ba3191f2b64472da645426ddc3e97e7b22b84afae7cb9e0599254f28be14db0875247f3968f37c4a
|
data/Changelog.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
### 0.8.0 / 2016-10-26
|
2
|
+
|
3
|
+
* New features
|
4
|
+
* upgraded to the latest Mondrian version 3.12.0.6
|
5
|
+
* added flush_region_cache_with_segments and flush_region_cache_with_full_names methods for partial clearing of the cache
|
6
|
+
* added Name() and Property() extensions for drill_through return fields
|
7
|
+
* Bug fixes
|
8
|
+
* fixed retrieving of drill through results with Clob values
|
9
|
+
|
1
10
|
### 0.7.0 / 2015-12-12
|
2
11
|
|
3
12
|
* New features
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -12,7 +12,7 @@ One of the most popular open-source OLAP engines is [Mondrian](http://mondrian.p
|
|
12
12
|
|
13
13
|
mondrian-olap is JRuby gem which includes Mondrian OLAP engine and provides Ruby DSL for creating OLAP schemas on top of relational database schemas and provides MDX query language and query builder Ruby methods for making analytical queries.
|
14
14
|
|
15
|
-
mondrian-olap is used in [eazyBI data analysis and reporting web application](https://eazybi.com). [Private eazyBI](https://eazybi.com/help/private-eazybi) can be used to create easy-to-use web based reports and dashboards on top of mondrian-olap based backend database. There is also [mondrian-olap demo Rails application for trying MDX queries](https://github.com/rsim/mondrian_demo).
|
15
|
+
mondrian-olap is used in [eazyBI data analysis and reporting web application](https://eazybi.com). [Private eazyBI](https://eazybi.com/help/private-eazybi) can be used to create easy-to-use web based reports and dashboards on top of mondrian-olap based backend database. There is also [mondrian-olap demo Rails application for trying MDX queries](https://github.com/rsim/mondrian_demo). The [mondrian-rest](https://github.com/jazzido/mondrian-rest) uses mondrian-olap to implement a REST API interface for a Mondrian schema.
|
16
16
|
|
17
17
|
USAGE
|
18
18
|
-----
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.8.0
|
Binary file
|
@@ -7,7 +7,8 @@ module Mondrian
|
|
7
7
|
connection
|
8
8
|
end
|
9
9
|
|
10
|
-
attr_reader :raw_connection, :raw_catalog, :raw_schema,
|
10
|
+
attr_reader :raw_connection, :raw_catalog, :raw_schema,
|
11
|
+
:raw_schema_reader, :raw_cache_control
|
11
12
|
|
12
13
|
def initialize(params={})
|
13
14
|
@params = params
|
@@ -55,6 +56,7 @@ module Mondrian
|
|
55
56
|
# currently it is assumed that there is just one schema per connection catalog
|
56
57
|
@raw_schema = @raw_catalog.getSchemas.first
|
57
58
|
@raw_schema_reader = @raw_connection.getMondrianConnection.getSchemaReader
|
59
|
+
@raw_cache_control = @raw_connection.getMondrianConnection.getCacheControl(nil)
|
58
60
|
@connected = true
|
59
61
|
true
|
60
62
|
end
|
data/lib/mondrian/olap/cube.rb
CHANGED
@@ -16,6 +16,8 @@ module Mondrian
|
|
16
16
|
end
|
17
17
|
|
18
18
|
class Cube
|
19
|
+
extend Forwardable
|
20
|
+
|
19
21
|
def self.get(connection, name)
|
20
22
|
if raw_cube = connection.raw_schema.getCubes.get(name)
|
21
23
|
Cube.new(connection, raw_cube)
|
@@ -25,6 +27,7 @@ module Mondrian
|
|
25
27
|
def initialize(connection, raw_cube)
|
26
28
|
@connection = connection
|
27
29
|
@raw_cube = raw_cube
|
30
|
+
@cache_control = CacheControl.new(@connection, self)
|
28
31
|
end
|
29
32
|
|
30
33
|
attr_reader :raw_cube
|
@@ -77,6 +80,9 @@ module Mondrian
|
|
77
80
|
raw_member = @raw_cube.lookupMember(segment_list)
|
78
81
|
raw_member && Member.new(raw_member)
|
79
82
|
end
|
83
|
+
|
84
|
+
def_delegators :@cache_control, :flush_region_cache_with_segments, :flush_region_cache_with_segments
|
85
|
+
def_delegators :@cache_control, :flush_region_cache_with_full_names, :flush_region_cache_with_full_names
|
80
86
|
end
|
81
87
|
|
82
88
|
class Dimension
|
@@ -374,6 +380,10 @@ module Mondrian
|
|
374
380
|
end
|
375
381
|
end
|
376
382
|
|
383
|
+
def mondrian_member
|
384
|
+
@raw_member.unwrap(Java::MondrianOlap::Member.java_class)
|
385
|
+
end
|
386
|
+
|
377
387
|
include Annotated
|
378
388
|
def annotations
|
379
389
|
annotations_for(@raw_member)
|
@@ -400,7 +410,35 @@ module Mondrian
|
|
400
410
|
end
|
401
411
|
end
|
402
412
|
end
|
413
|
+
end
|
414
|
+
|
415
|
+
class CacheControl
|
416
|
+
def initialize(connection, cube)
|
417
|
+
@connection = connection
|
418
|
+
@cube = cube
|
419
|
+
@mondrian_cube = @cube.raw_cube.unwrap(Java::MondrianOlap::Cube.java_class)
|
420
|
+
@cache_control = @connection.raw_cache_control
|
421
|
+
end
|
403
422
|
|
423
|
+
def flush_region_cache_with_segments(*segment_names)
|
424
|
+
members = segment_names.map { |name| @cube.member_by_segments(*name).mondrian_member }
|
425
|
+
flush(members)
|
426
|
+
end
|
427
|
+
|
428
|
+
def flush_region_cache_with_full_names(*full_names)
|
429
|
+
members = full_names.map { |name| @cube.member(*name).mondrian_member }
|
430
|
+
flush(members)
|
431
|
+
end
|
432
|
+
|
433
|
+
private
|
434
|
+
|
435
|
+
def flush(members)
|
436
|
+
regions = members.map do |member|
|
437
|
+
@cache_control.create_member_region(member, true)
|
438
|
+
end
|
439
|
+
regions << @cache_control.create_measures_region(@mondrian_cube)
|
440
|
+
@cache_control.flush(@cache_control.create_crossjoin_region(*regions))
|
441
|
+
end
|
404
442
|
end
|
405
443
|
end
|
406
444
|
end
|
data/lib/mondrian/olap/result.rb
CHANGED
@@ -242,7 +242,8 @@ module Mondrian
|
|
242
242
|
end
|
243
243
|
|
244
244
|
def self.generate_drill_through_sql(rolap_cell, result, params)
|
245
|
-
|
245
|
+
nonempty_columns, return_fields = parse_return_fields(result, params)
|
246
|
+
return_expressions = return_fields.map{|field| field[:member]}
|
246
247
|
|
247
248
|
sql_non_extended = rolap_cell.getDrillThroughSQL(return_expressions, false)
|
248
249
|
sql_extended = rolap_cell.getDrillThroughSQL(return_expressions, true)
|
@@ -273,25 +274,30 @@ module Mondrian
|
|
273
274
|
raise ArgumentError, "cannot parse drill through SQL: #{sql_extended}"
|
274
275
|
end
|
275
276
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
[
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
end
|
277
|
+
if return_fields.present?
|
278
|
+
new_select_columns = []
|
279
|
+
new_order_by_columns = []
|
280
|
+
new_group_by_columns = []
|
281
|
+
group_by = params[:group_by]
|
282
|
+
|
283
|
+
return_fields.size.times do |i|
|
284
|
+
column_alias = return_fields[i][:column_alias]
|
285
|
+
new_select_columns << if column_expression = return_fields[i][:column_expression]
|
286
|
+
new_order_by_columns << column_expression
|
287
|
+
new_group_by_columns << column_expression if group_by && return_fields[i][:type] != :measure
|
288
|
+
"#{column_expression} AS #{column_alias}"
|
289
|
+
else
|
290
|
+
"'' AS #{column_alias}"
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
new_select = new_select_columns.join(', ')
|
295
|
+
new_order_by = new_order_by_columns.join(', ')
|
296
|
+
new_group_by = new_group_by_columns.join(', ')
|
292
297
|
else
|
293
298
|
new_select = extended_select
|
294
299
|
new_order_by = extended_order_by
|
300
|
+
new_group_by = ''
|
295
301
|
end
|
296
302
|
|
297
303
|
new_from_parts = non_extended_from.split(/,\s*/)
|
@@ -327,40 +333,89 @@ module Mondrian
|
|
327
333
|
end
|
328
334
|
|
329
335
|
sql = "select #{new_select} from #{new_from} where #{new_where}"
|
336
|
+
sql << " group by #{new_group_by}" unless new_group_by.empty?
|
330
337
|
sql << " order by #{new_order_by}" unless new_order_by.empty?
|
331
338
|
sql
|
332
339
|
end
|
333
340
|
|
334
341
|
def self.parse_return_fields(result, params)
|
335
|
-
return_field_names = []
|
336
|
-
return_expressions = []
|
337
342
|
nonempty_columns = []
|
343
|
+
return_fields = []
|
338
344
|
|
339
345
|
if params[:return] || params[:nonempty]
|
340
346
|
rolap_cube = result.getCube
|
341
347
|
schema_reader = rolap_cube.getSchemaReader
|
348
|
+
dialect = result.getCube.getSchema.getDialect
|
349
|
+
sql_query = Java::mondrian.rolap.sql.SqlQuery.new(dialect)
|
350
|
+
|
351
|
+
if fields = params[:return]
|
352
|
+
fields = fields.split(/,\s*/) if fields.is_a? String
|
353
|
+
fields.each do |field|
|
354
|
+
return_fields << case field
|
355
|
+
when /\AName\((.*)\)\z/i then
|
356
|
+
{ member_full_name: $1, type: :name }
|
357
|
+
when /\AProperty\((.*)\s*,\s*'(.*)'\)\z/i then
|
358
|
+
{ member_full_name: $1, type: :property, name: $2 }
|
359
|
+
else
|
360
|
+
{ member_full_name: field }
|
361
|
+
end
|
362
|
+
end
|
342
363
|
|
343
|
-
|
344
|
-
|
345
|
-
return_expressions = return_fields.map do |return_field|
|
364
|
+
return_fields.size.times do | i |
|
365
|
+
member_full_name = return_fields[i][:member_full_name]
|
346
366
|
begin
|
347
|
-
segment_list = Java::MondrianOlap::Util.parseIdentifier(
|
348
|
-
return_field_names << segment_list.to_a.last.name
|
367
|
+
segment_list = Java::MondrianOlap::Util.parseIdentifier(member_full_name)
|
349
368
|
rescue Java::JavaLang::IllegalArgumentException
|
350
|
-
raise ArgumentError, "invalid return field #{
|
369
|
+
raise ArgumentError, "invalid return field #{member_full_name}"
|
351
370
|
end
|
352
371
|
|
372
|
+
# if this is property field then the name is initilized already
|
373
|
+
return_fields[i][:name] ||= segment_list.to_a.last.name
|
353
374
|
level_or_member = schema_reader.lookupCompound rolap_cube, segment_list, false, 0
|
375
|
+
return_fields[i][:member] = level_or_member
|
376
|
+
|
377
|
+
if level_or_member.is_a? Java::MondrianOlap::Member
|
378
|
+
raise ArgumentError, "cannot use calculated member #{member_full_name} as return field" if level_or_member.isCalculated
|
379
|
+
elsif !level_or_member.is_a? Java::MondrianOlap::Level
|
380
|
+
raise ArgumentError, "return field #{member_full_name} should be level or measure"
|
381
|
+
end
|
354
382
|
|
355
|
-
case
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
383
|
+
return_fields[i][:column_expression] = case return_fields[i][:type]
|
384
|
+
when :name
|
385
|
+
if level_or_member.respond_to? :getNameExp
|
386
|
+
level_or_member.getNameExp.getExpression sql_query
|
387
|
+
end
|
388
|
+
when :property
|
389
|
+
if property = level_or_member.getProperties.to_a.detect{|p| p.getName == return_fields[i][:name]}
|
390
|
+
# property.getExp is a protected method therefore
|
391
|
+
# use a workaround to get the value from the field
|
392
|
+
f = property.java_class.declared_field("exp")
|
393
|
+
f.accessible = true
|
394
|
+
if column = f.value(property)
|
395
|
+
column.getExpression sql_query
|
396
|
+
end
|
397
|
+
end
|
398
|
+
else
|
399
|
+
if level_or_member.respond_to? :getKeyExp
|
400
|
+
return_fields[i][:type] = :key
|
401
|
+
level_or_member.getKeyExp.getExpression sql_query
|
402
|
+
else
|
403
|
+
return_fields[i][:type] = :measure
|
404
|
+
column_expression = level_or_member.getMondrianDefExpression.getExpression sql_query
|
405
|
+
if params[:group_by]
|
406
|
+
level_or_member.getAggregator.getExpression column_expression
|
407
|
+
else
|
408
|
+
column_expression
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
column_alias = if return_fields[i][:type] == :key
|
414
|
+
"#{return_fields[i][:name]} (Key)"
|
361
415
|
else
|
362
|
-
|
416
|
+
return_fields[i][:name]
|
363
417
|
end
|
418
|
+
return_fields[i][:column_alias] = dialect.quoteIdentifier(column_alias)
|
364
419
|
end
|
365
420
|
end
|
366
421
|
|
@@ -370,23 +425,22 @@ module Mondrian
|
|
370
425
|
begin
|
371
426
|
segment_list = Java::MondrianOlap::Util.parseIdentifier(nonempty_field)
|
372
427
|
rescue Java::JavaLang::IllegalArgumentException
|
373
|
-
raise ArgumentError, "invalid return field #{
|
428
|
+
raise ArgumentError, "invalid return field #{nonempty_field}"
|
374
429
|
end
|
375
430
|
member = schema_reader.lookupCompound rolap_cube, segment_list, false, 0
|
376
431
|
if member.is_a? Java::MondrianOlap::Member
|
377
|
-
raise ArgumentError, "cannot use calculated member #{
|
432
|
+
raise ArgumentError, "cannot use calculated member #{nonempty_field} as nonempty field" if member.isCalculated
|
378
433
|
sql_query = member.getStarMeasure.getSqlQuery
|
379
434
|
member.getStarMeasure.generateExprString(sql_query)
|
380
435
|
else
|
381
|
-
raise ArgumentError, "nonempty field #{
|
436
|
+
raise ArgumentError, "nonempty field #{nonempty_field} should be measure"
|
382
437
|
end
|
383
438
|
end
|
384
439
|
end
|
385
440
|
end
|
386
441
|
|
387
|
-
[
|
442
|
+
[nonempty_columns, return_fields]
|
388
443
|
end
|
389
|
-
|
390
444
|
end
|
391
445
|
|
392
446
|
def self.java_to_ruby_value(value, column_type = nil)
|
@@ -395,6 +449,8 @@ module Mondrian
|
|
395
449
|
value
|
396
450
|
when Java::JavaMath::BigDecimal
|
397
451
|
BigDecimal(value.to_s)
|
452
|
+
when Java::JavaSql::Clob
|
453
|
+
clob_to_string(value)
|
398
454
|
else
|
399
455
|
value
|
400
456
|
end
|
@@ -402,6 +458,23 @@ module Mondrian
|
|
402
458
|
|
403
459
|
private
|
404
460
|
|
461
|
+
def self.clob_to_string(value)
|
462
|
+
if reader = value.getCharacterStream
|
463
|
+
buffered_reader = Java::JavaIo::BufferedReader.new(reader)
|
464
|
+
result = []
|
465
|
+
while str = buffered_reader.readLine
|
466
|
+
result << str
|
467
|
+
end
|
468
|
+
result.join("\n")
|
469
|
+
end
|
470
|
+
ensure
|
471
|
+
if buffered_reader
|
472
|
+
buffered_reader.close
|
473
|
+
elsif reader
|
474
|
+
reader.close
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
405
478
|
def axes
|
406
479
|
@axes ||= @raw_cell_set.getAxes
|
407
480
|
end
|
@@ -0,0 +1,267 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Cube" do
|
4
|
+
before(:all) do
|
5
|
+
@schema = Mondrian::OLAP::Schema.define do
|
6
|
+
measures_caption 'Measures caption'
|
7
|
+
|
8
|
+
cube 'Sales' do
|
9
|
+
description 'Sales description'
|
10
|
+
caption 'Sales caption'
|
11
|
+
annotations :foo => 'bar'
|
12
|
+
table 'sales'
|
13
|
+
visible true
|
14
|
+
dimension 'Gender', :foreign_key => 'customer_id' do
|
15
|
+
description 'Gender description'
|
16
|
+
caption 'Gender caption'
|
17
|
+
visible true
|
18
|
+
hierarchy :has_all => true, :primary_key => 'id' do
|
19
|
+
description 'Gender hierarchy description'
|
20
|
+
caption 'Gender hierarchy caption'
|
21
|
+
all_member_name 'All Genders'
|
22
|
+
all_member_caption 'All Genders caption'
|
23
|
+
table 'customers'
|
24
|
+
visible true
|
25
|
+
level 'Gender', :column => 'gender', :unique_members => true,
|
26
|
+
:description => 'Gender level description', :caption => 'Gender level caption' do
|
27
|
+
visible true
|
28
|
+
# Dimension values SQL generated by caption_expression fails on PostgreSQL and MS SQL
|
29
|
+
if %w(mysql oracle).include?(MONDRIAN_DRIVER)
|
30
|
+
caption_expression do
|
31
|
+
sql "'dummy'"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
dimension 'Customers', :foreign_key => 'customer_id', :annotations => {:foo => 'bar'} do
|
38
|
+
hierarchy :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id', :annotations => {:foo => 'bar'} do
|
39
|
+
table 'customers'
|
40
|
+
level 'Country', :column => 'country', :unique_members => true, :annotations => {:foo => 'bar'}
|
41
|
+
level 'State Province', :column => 'state_province', :unique_members => true
|
42
|
+
level 'City', :column => 'city', :unique_members => false
|
43
|
+
level 'Name', :column => 'fullname', :unique_members => true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
calculated_member 'Non-USA', :annotations => {:foo => 'bar'} do
|
47
|
+
dimension 'Customers'
|
48
|
+
formula '[Customers].[All Customers] - [Customers].[USA]'
|
49
|
+
end
|
50
|
+
dimension 'Time', :foreign_key => 'time_id', :type => 'TimeDimension' do
|
51
|
+
hierarchy :has_all => false, :primary_key => 'id' do
|
52
|
+
table 'time'
|
53
|
+
level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true, :level_type => 'TimeYears'
|
54
|
+
level 'Quarter', :column => 'quarter', :unique_members => false, :level_type => 'TimeQuarters'
|
55
|
+
level 'Month', :column => 'month_of_year', :type => 'Numeric', :unique_members => false, :level_type => 'TimeMonths'
|
56
|
+
end
|
57
|
+
hierarchy 'Weekly', :has_all => false, :primary_key => 'id' do
|
58
|
+
table 'time'
|
59
|
+
level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true, :level_type => 'TimeYears'
|
60
|
+
level 'Week', :column => 'weak_of_year', :type => 'Numeric', :unique_members => false, :level_type => 'TimeWeeks'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
calculated_member 'Last week' do
|
64
|
+
hierarchy '[Time.Weekly]'
|
65
|
+
formula 'Tail([Time.Weekly].[Week].Members).Item(0)'
|
66
|
+
end
|
67
|
+
measure 'Unit Sales', :column => 'unit_sales', :aggregator => 'sum', :annotations => {:foo => 'bar'}
|
68
|
+
measure 'Store Sales', :column => 'store_sales', :aggregator => 'sum'
|
69
|
+
measure 'Store Cost', :column => 'store_cost', :aggregator => 'sum', :visible => false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
|
73
|
+
end
|
74
|
+
|
75
|
+
describe 'cache', unless: MONDRIAN_DRIVER == 'luciddb' do
|
76
|
+
before(:all) do
|
77
|
+
@connection = ActiveRecord::Base.connection
|
78
|
+
@cube = @olap.cube('Sales')
|
79
|
+
@query = <<-SQL
|
80
|
+
SELECT {[Measures].[Store Cost], [Measures].[Store Sales]} ON COLUMNS
|
81
|
+
FROM [Sales]
|
82
|
+
WHERE ([Time].[2010].[Q1], [Customers].[USA].[CA])
|
83
|
+
SQL
|
84
|
+
|
85
|
+
case MONDRIAN_DRIVER
|
86
|
+
when 'mysql', 'jdbc_mysql', 'postgresql', 'oracle'
|
87
|
+
@connection.execute 'CREATE TABLE sales_copy AS SELECT * FROM sales'
|
88
|
+
when 'mssql', 'sqlserver'
|
89
|
+
# Use raw_connection.execute to avoid detecting this query as a SELECT query
|
90
|
+
# for which executeQuery JDBC method will fail
|
91
|
+
@connection.raw_connection.execute 'SELECT * INTO sales_copy FROM sales'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
after(:each) do
|
96
|
+
case MONDRIAN_DRIVER
|
97
|
+
when 'mysql', 'jdbc_mysql', 'postgresql', 'oracle'
|
98
|
+
@connection.execute 'TRUNCATE TABLE sales'
|
99
|
+
@connection.execute 'INSERT INTO sales SELECT * FROM sales_copy'
|
100
|
+
when 'mssql', 'sqlserver'
|
101
|
+
@connection.execute 'TRUNCATE TABLE sales'
|
102
|
+
@connection.execute 'INSERT INTO sales SELECT * FROM sales_copy'
|
103
|
+
end
|
104
|
+
|
105
|
+
@olap.flush_schema_cache
|
106
|
+
@olap.close
|
107
|
+
@olap.connect
|
108
|
+
end
|
109
|
+
|
110
|
+
after(:all) do
|
111
|
+
case MONDRIAN_DRIVER
|
112
|
+
when 'mysql', 'jdbc_mysql', 'postgresql', 'oracle'
|
113
|
+
@connection.execute 'DROP TABLE sales_copy'
|
114
|
+
when 'mssql', 'sqlserver'
|
115
|
+
@connection.execute 'DROP TABLE sales_copy'
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'should clear cache for deleted data at lower level with segments' do
|
120
|
+
@olap.execute(@query).values.should == [6890.553, 11390.4]
|
121
|
+
@connection.execute <<-SQL
|
122
|
+
DELETE FROM sales
|
123
|
+
WHERE time_id IN (SELECT id
|
124
|
+
FROM TIME
|
125
|
+
WHERE the_year = 2010
|
126
|
+
AND quarter = 'Q1')
|
127
|
+
AND customer_id IN (SELECT id
|
128
|
+
FROM customers
|
129
|
+
WHERE country = 'USA'
|
130
|
+
AND state_province = 'CA'
|
131
|
+
AND city = 'Berkeley')
|
132
|
+
SQL
|
133
|
+
@cube.flush_region_cache_with_segments(%w(Time 2010 Q1), %w(Customers USA CA))
|
134
|
+
@olap.execute(@query).values.should == [6756.4296, 11156.28]
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'should clear cache for deleted data at same level with segments' do
|
138
|
+
@olap.execute(@query).values.should == [6890.553, 11390.4]
|
139
|
+
@connection.execute <<-SQL
|
140
|
+
DELETE FROM sales
|
141
|
+
WHERE time_id IN (SELECT id
|
142
|
+
FROM TIME
|
143
|
+
WHERE the_year = 2010
|
144
|
+
AND quarter = 'Q1')
|
145
|
+
AND customer_id IN (SELECT id
|
146
|
+
FROM customers
|
147
|
+
WHERE country = 'USA'
|
148
|
+
AND state_province = 'CA')
|
149
|
+
SQL
|
150
|
+
@cube.flush_region_cache_with_segments(%w(Time 2010 Q1), %w(Customers USA CA))
|
151
|
+
@olap.execute(@query).values.should == [nil, nil]
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'should clear cache for update data at lower level with segments' do
|
155
|
+
@olap.execute(@query).values.should == [6890.553, 11390.4]
|
156
|
+
@connection.execute <<-SQL
|
157
|
+
UPDATE sales SET
|
158
|
+
store_sales = store_sales + 1,
|
159
|
+
store_cost = store_cost + 1
|
160
|
+
WHERE time_id IN (SELECT id
|
161
|
+
FROM TIME
|
162
|
+
WHERE the_year = 2010
|
163
|
+
AND quarter = 'Q1')
|
164
|
+
AND customer_id IN (SELECT id
|
165
|
+
FROM customers
|
166
|
+
WHERE country = 'USA'
|
167
|
+
AND state_province = 'CA'
|
168
|
+
AND city = 'Berkeley')
|
169
|
+
SQL
|
170
|
+
@cube.flush_region_cache_with_segments(%w(Time 2010 Q1), %w(Customers USA CA))
|
171
|
+
@olap.execute(@query).values.should == [6891.553, 11391.4]
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'should clear cache for update data at same level with segments' do
|
175
|
+
@olap.execute(@query).values.should == [6890.553, 11390.4]
|
176
|
+
@connection.execute <<-SQL
|
177
|
+
UPDATE sales SET
|
178
|
+
store_sales = store_sales + 1,
|
179
|
+
store_cost = store_cost + 1
|
180
|
+
WHERE time_id IN (SELECT id
|
181
|
+
FROM TIME
|
182
|
+
WHERE the_year = 2010
|
183
|
+
AND quarter = 'Q1')
|
184
|
+
AND customer_id IN (SELECT id
|
185
|
+
FROM customers
|
186
|
+
WHERE country = 'USA'
|
187
|
+
AND state_province = 'CA')
|
188
|
+
SQL
|
189
|
+
@cube.flush_region_cache_with_segments(%w(Time 2010 Q1), %w(Customers USA CA))
|
190
|
+
@olap.execute(@query).values.should == [6935.553, 11435.4]
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'should clear cache for deleted data at lower level with members' do
|
194
|
+
@olap.execute(@query).values.should == [6890.553, 11390.4]
|
195
|
+
@connection.execute <<-SQL
|
196
|
+
DELETE FROM sales
|
197
|
+
WHERE time_id IN (SELECT id
|
198
|
+
FROM TIME
|
199
|
+
WHERE the_year = 2010
|
200
|
+
AND quarter = 'Q1')
|
201
|
+
AND customer_id IN (SELECT id
|
202
|
+
FROM customers
|
203
|
+
WHERE country = 'USA'
|
204
|
+
AND state_province = 'CA'
|
205
|
+
AND city = 'Berkeley')
|
206
|
+
SQL
|
207
|
+
@cube.flush_region_cache_with_full_names('[Time].[2010].[Q1]', '[Customers].[USA].[CA]')
|
208
|
+
@olap.execute(@query).values.should == [6756.4296, 11156.28]
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'should clear cache for deleted data at same level with members' do
|
212
|
+
@olap.execute(@query).values.should == [6890.553, 11390.4]
|
213
|
+
@connection.execute <<-SQL
|
214
|
+
DELETE FROM sales
|
215
|
+
WHERE time_id IN (SELECT id
|
216
|
+
FROM TIME
|
217
|
+
WHERE the_year = 2010
|
218
|
+
AND quarter = 'Q1')
|
219
|
+
AND customer_id IN (SELECT id
|
220
|
+
FROM customers
|
221
|
+
WHERE country = 'USA'
|
222
|
+
AND state_province = 'CA')
|
223
|
+
SQL
|
224
|
+
@cube.flush_region_cache_with_full_names('[Time].[2010].[Q1]', '[Customers].[USA].[CA]')
|
225
|
+
@olap.execute(@query).values.should == [nil, nil]
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'should clear cache for update data at lower level with members' do
|
229
|
+
@olap.execute(@query).values.should == [6890.553, 11390.4]
|
230
|
+
@connection.execute <<-SQL
|
231
|
+
UPDATE sales SET
|
232
|
+
store_sales = store_sales + 1,
|
233
|
+
store_cost = store_cost + 1
|
234
|
+
WHERE time_id IN (SELECT id
|
235
|
+
FROM TIME
|
236
|
+
WHERE the_year = 2010
|
237
|
+
AND quarter = 'Q1')
|
238
|
+
AND customer_id IN (SELECT id
|
239
|
+
FROM customers
|
240
|
+
WHERE country = 'USA'
|
241
|
+
AND state_province = 'CA'
|
242
|
+
AND city = 'Berkeley')
|
243
|
+
SQL
|
244
|
+
@cube.flush_region_cache_with_full_names('[Time].[2010].[Q1]', '[Customers].[USA].[CA]')
|
245
|
+
@olap.execute(@query).values.should == [6891.553, 11391.4]
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'should clear cache for update data at same level with members' do
|
249
|
+
@olap.execute(@query).values.should == [6890.553, 11390.4]
|
250
|
+
@connection.execute <<-SQL
|
251
|
+
UPDATE sales SET
|
252
|
+
store_sales = store_sales + 1,
|
253
|
+
store_cost = store_cost + 1
|
254
|
+
WHERE time_id IN (SELECT id
|
255
|
+
FROM TIME
|
256
|
+
WHERE the_year = 2010
|
257
|
+
AND quarter = 'Q1')
|
258
|
+
AND customer_id IN (SELECT id
|
259
|
+
FROM customers
|
260
|
+
WHERE country = 'USA'
|
261
|
+
AND state_province = 'CA')
|
262
|
+
SQL
|
263
|
+
@cube.flush_region_cache_with_full_names('[Time].[2010].[Q1]', '[Customers].[USA].[CA]')
|
264
|
+
@olap.execute(@query).values.should == [6935.553, 11435.4]
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
data/spec/cube_spec.rb
CHANGED
data/spec/query_spec.rb
CHANGED
@@ -817,8 +817,7 @@ describe "Query" do
|
|
817
817
|
end
|
818
818
|
|
819
819
|
it "should return correct row value types" do
|
820
|
-
|
821
|
-
case MONDRIAN_DRIVER
|
820
|
+
expected_value_types = case MONDRIAN_DRIVER
|
822
821
|
when "oracle"
|
823
822
|
[
|
824
823
|
BigDecimal, String, BigDecimal, BigDecimal, BigDecimal,
|
@@ -831,7 +830,8 @@ describe "Query" do
|
|
831
830
|
[
|
832
831
|
Fixnum, String, Fixnum, Fixnum, Fixnum,
|
833
832
|
String, String, String, String, String, String,
|
834
|
-
|
833
|
+
# last one can be BigDecimal or Fixnum, probably depends on MS SQL version
|
834
|
+
String, String, String, Numeric,
|
835
835
|
String,
|
836
836
|
BigDecimal
|
837
837
|
]
|
@@ -844,6 +844,10 @@ describe "Query" do
|
|
844
844
|
BigDecimal
|
845
845
|
]
|
846
846
|
end
|
847
|
+
|
848
|
+
@drill_through.rows.first.each_with_index do |value, i|
|
849
|
+
value.should be_a expected_value_types[i]
|
850
|
+
end
|
847
851
|
end
|
848
852
|
|
849
853
|
it "should return only specified max rows" do
|
@@ -857,8 +861,7 @@ describe "Query" do
|
|
857
861
|
@query = @olap.from('Sales')
|
858
862
|
@result = @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
859
863
|
rows('[Product].children').
|
860
|
-
|
861
|
-
where('[Time].[2010].[Q1]').
|
864
|
+
where('[Time].[2010].[Q1]', '[Time].[2010].[Q2]').
|
862
865
|
execute
|
863
866
|
end
|
864
867
|
|
@@ -870,9 +873,9 @@ describe "Query" do
|
|
870
873
|
'[Measures].[Unit Sales]', '[Measures].[Store Sales]'
|
871
874
|
])
|
872
875
|
@drill_through.column_labels.should == [
|
873
|
-
"Month",
|
874
|
-
"City",
|
875
|
-
"Product Family",
|
876
|
+
"Month (Key)",
|
877
|
+
"City (Key)",
|
878
|
+
"Product Family (Key)",
|
876
879
|
"Unit Sales", "Store Sales"
|
877
880
|
]
|
878
881
|
end
|
@@ -888,6 +891,74 @@ describe "Query" do
|
|
888
891
|
@drill_through.rows.all?{|r| r.any?{|c| c}}.should be_true
|
889
892
|
end
|
890
893
|
|
894
|
+
it "should return member name and property values" do
|
895
|
+
@drill_through = @result.drill_through(row: 0, column: 0,
|
896
|
+
return: [
|
897
|
+
"Name([Customers].[Name])",
|
898
|
+
"Property([Customers].[Name], 'Gender')",
|
899
|
+
"Property([Customers].[Name], 'Description')"
|
900
|
+
]
|
901
|
+
)
|
902
|
+
@drill_through.column_labels.should == [ "Name", "Gender", "Description" ]
|
903
|
+
@drill_through.rows.should == @sql.select_rows(<<-SQL
|
904
|
+
SELECT
|
905
|
+
customers.fullname,
|
906
|
+
customers.gender,
|
907
|
+
customers.description
|
908
|
+
FROM
|
909
|
+
sales,
|
910
|
+
customers,
|
911
|
+
time,
|
912
|
+
products,
|
913
|
+
product_classes
|
914
|
+
WHERE
|
915
|
+
(time.quarter = 'Q1' OR time.quarter = 'Q2') AND
|
916
|
+
time.the_year = 2010 AND
|
917
|
+
product_classes.product_family = 'Drink' AND
|
918
|
+
products.product_class_id = product_classes.id AND
|
919
|
+
sales.product_id = products.id AND
|
920
|
+
sales.time_id = time.id AND
|
921
|
+
customers.id = sales.customer_id
|
922
|
+
ORDER BY
|
923
|
+
customers.fullname,
|
924
|
+
customers.gender,
|
925
|
+
customers.description
|
926
|
+
SQL
|
927
|
+
)
|
928
|
+
end
|
929
|
+
|
930
|
+
it "should group by" do
|
931
|
+
@drill_through = @result.drill_through(row: 0, column: 0,
|
932
|
+
return: [
|
933
|
+
"[Product].[Product Family]",
|
934
|
+
"[Measures].[Unit Sales]",
|
935
|
+
"[Measures].[Store Cost]"
|
936
|
+
],
|
937
|
+
group_by: true
|
938
|
+
)
|
939
|
+
@drill_through.column_labels.should == [ "Product Family (Key)", "Unit Sales", "Store Cost" ]
|
940
|
+
@drill_through.rows.should == @sql.select_rows(<<-SQL
|
941
|
+
SELECT
|
942
|
+
product_classes.product_family,
|
943
|
+
SUM(sales.unit_sales) AS unit_sales,
|
944
|
+
SUM(sales.store_cost) AS store_cost
|
945
|
+
FROM
|
946
|
+
sales,
|
947
|
+
time,
|
948
|
+
products,
|
949
|
+
product_classes
|
950
|
+
WHERE
|
951
|
+
(time.quarter = 'Q1' OR time.quarter = 'Q2') AND
|
952
|
+
time.the_year = 2010 AND
|
953
|
+
product_classes.product_family = 'Drink' AND
|
954
|
+
products.product_class_id = product_classes.id AND
|
955
|
+
sales.product_id = products.id AND
|
956
|
+
sales.time_id = time.id
|
957
|
+
GROUP BY
|
958
|
+
product_classes.product_family
|
959
|
+
SQL
|
960
|
+
)
|
961
|
+
end
|
891
962
|
end
|
892
963
|
|
893
964
|
describe "drill through statement" do
|
data/spec/rake_tasks.rb
CHANGED
@@ -40,6 +40,14 @@ namespace :db do
|
|
40
40
|
t.string :lname, :limit => 30
|
41
41
|
t.string :fullname, :limit => 60
|
42
42
|
t.string :gender, :limit => 30
|
43
|
+
# Mondrian does not support properties with Oracle CLOB type
|
44
|
+
# as it tries to GROUP BY all columns when loading a dimension table
|
45
|
+
if MONDRIAN_DRIVER == 'oracle'
|
46
|
+
t.string :description, :limit => 4000
|
47
|
+
else
|
48
|
+
t.text :description
|
49
|
+
end
|
50
|
+
|
43
51
|
end
|
44
52
|
|
45
53
|
create_table :sales, :force => true, :id => false do |t|
|
@@ -207,7 +215,8 @@ namespace :db do
|
|
207
215
|
:fname => "First#{i}",
|
208
216
|
:lname => "Last#{i}",
|
209
217
|
:fullname => "First#{i} Last#{i}",
|
210
|
-
:gender => i % 2 == 0 ? "M" : "F"
|
218
|
+
:gender => i % 2 == 0 ? "M" : "F",
|
219
|
+
:description => 100.times.map{"1234567890"}.join("\n")
|
211
220
|
)
|
212
221
|
end
|
213
222
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -119,6 +119,7 @@ when 'mssql'
|
|
119
119
|
url << ";instance=#{DATABASE_INSTANCE}" if DATABASE_INSTANCE
|
120
120
|
AR_CONNECTION_PARAMS = {
|
121
121
|
:adapter => 'jdbc',
|
122
|
+
:dialect => 'Microsoft SQL Server',
|
122
123
|
:driver => JDBC_DRIVER,
|
123
124
|
:url => url,
|
124
125
|
:username => CONNECTION_PARAMS[:username],
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mondrian-olap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Raimonds Simanovskis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-10-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -56,14 +56,14 @@ dependencies:
|
|
56
56
|
name: rspec
|
57
57
|
version_requirements: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - '='
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '2.14'
|
62
62
|
requirement: !ruby/object:Gem::Requirement
|
63
63
|
requirements:
|
64
|
-
- -
|
64
|
+
- - '='
|
65
65
|
- !ruby/object:Gem::Version
|
66
|
-
version: '
|
66
|
+
version: '2.14'
|
67
67
|
prerelease: false
|
68
68
|
type: :development
|
69
69
|
- !ruby/object:Gem::Dependency
|
@@ -168,14 +168,14 @@ dependencies:
|
|
168
168
|
name: activerecord-oracle_enhanced-adapter
|
169
169
|
version_requirements: !ruby/object:Gem::Requirement
|
170
170
|
requirements:
|
171
|
-
- - "
|
171
|
+
- - "~>"
|
172
172
|
- !ruby/object:Gem::Version
|
173
|
-
version:
|
173
|
+
version: 1.5.6
|
174
174
|
requirement: !ruby/object:Gem::Requirement
|
175
175
|
requirements:
|
176
|
-
- - "
|
176
|
+
- - "~>"
|
177
177
|
- !ruby/object:Gem::Version
|
178
|
-
version:
|
178
|
+
version: 1.5.6
|
179
179
|
prerelease: false
|
180
180
|
type: :development
|
181
181
|
- !ruby/object:Gem::Dependency
|
@@ -250,7 +250,7 @@ files:
|
|
250
250
|
- lib/mondrian/jars/javacup-10k.jar
|
251
251
|
- lib/mondrian/jars/log4j-1.2.17.jar
|
252
252
|
- lib/mondrian/jars/log4j.properties
|
253
|
-
- lib/mondrian/jars/mondrian-3.
|
253
|
+
- lib/mondrian/jars/mondrian-3.12.0.6-237.jar
|
254
254
|
- lib/mondrian/jars/olap4j-1.2.0.jar
|
255
255
|
- lib/mondrian/olap.rb
|
256
256
|
- lib/mondrian/olap/connection.rb
|
@@ -264,6 +264,7 @@ files:
|
|
264
264
|
- lib/mondrian/olap/version.rb
|
265
265
|
- spec/connection_role_spec.rb
|
266
266
|
- spec/connection_spec.rb
|
267
|
+
- spec/cube_cache_control_spec.rb
|
267
268
|
- spec/cube_spec.rb
|
268
269
|
- spec/fixtures/MondrianTest.xml
|
269
270
|
- spec/fixtures/MondrianTestOracle.xml
|
@@ -300,6 +301,7 @@ summary: JRuby API for Mondrian OLAP Java library
|
|
300
301
|
test_files:
|
301
302
|
- spec/connection_role_spec.rb
|
302
303
|
- spec/connection_spec.rb
|
304
|
+
- spec/cube_cache_control_spec.rb
|
303
305
|
- spec/cube_spec.rb
|
304
306
|
- spec/fixtures/MondrianTest.xml
|
305
307
|
- spec/fixtures/MondrianTestOracle.xml
|