mondrian-olap 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Changelog.md +13 -0
- data/LICENSE-Mondrian.txt +87 -0
- data/LICENSE.txt +1 -1
- data/README.md +2 -2
- data/VERSION +1 -1
- data/lib/mondrian/jars/guava-17.0.jar +0 -0
- data/lib/mondrian/jars/{mondrian-8.3.0.5.jar → mondrian-9.1.0.0.jar} +0 -0
- data/lib/mondrian/olap/connection.rb +111 -71
- data/lib/mondrian/olap/result.rb +9 -2
- data/lib/mondrian/olap/schema_element.rb +6 -3
- data/spec/connection_role_spec.rb +4 -1
- data/spec/connection_spec.rb +1 -2
- data/spec/cube_cache_control_spec.rb +6 -16
- data/spec/fixtures/MondrianTest.xml +0 -6
- data/spec/fixtures/MondrianTestOracle.xml +0 -6
- data/spec/mondrian_spec.rb +71 -1
- data/spec/query_spec.rb +10 -6
- data/spec/rake_tasks.rb +244 -159
- data/spec/schema_definition_spec.rb +8 -6
- data/spec/spec_helper.rb +30 -54
- data/spec/support/data/customers.csv +10902 -0
- data/spec/support/data/product_classes.csv +101 -0
- data/spec/support/data/products.csv +101 -0
- data/spec/support/data/sales.csv +101 -0
- data/spec/support/data/time.csv +731 -0
- metadata +43 -47
- data/LICENSE-Mondrian.html +0 -259
@@ -72,7 +72,8 @@ describe "Cube" do
|
|
72
72
|
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
|
73
73
|
end
|
74
74
|
|
75
|
-
|
75
|
+
# Do not execute tests on analytical databases with slow individual inserts
|
76
|
+
describe 'cache', unless: %w(vertica snowflake).include?(MONDRIAN_DRIVER) do
|
76
77
|
def qt(name)
|
77
78
|
@connection.quote_table_name(name.to_s)
|
78
79
|
end
|
@@ -87,7 +88,7 @@ describe "Cube" do
|
|
87
88
|
SQL
|
88
89
|
|
89
90
|
case MONDRIAN_DRIVER
|
90
|
-
when 'mysql', 'jdbc_mysql', 'postgresql', 'oracle'
|
91
|
+
when 'mysql', 'jdbc_mysql', 'postgresql', 'oracle'
|
91
92
|
@connection.execute 'CREATE TABLE sales_copy AS SELECT * FROM sales'
|
92
93
|
when 'mssql', 'sqlserver'
|
93
94
|
# Use raw_connection.execute to avoid detecting this query as a SELECT query
|
@@ -97,14 +98,8 @@ describe "Cube" do
|
|
97
98
|
end
|
98
99
|
|
99
100
|
after(:each) do
|
100
|
-
|
101
|
-
|
102
|
-
@connection.execute 'TRUNCATE TABLE sales'
|
103
|
-
@connection.execute 'INSERT INTO sales SELECT * FROM sales_copy'
|
104
|
-
when 'mssql', 'sqlserver'
|
105
|
-
@connection.execute 'TRUNCATE TABLE sales'
|
106
|
-
@connection.execute 'INSERT INTO sales SELECT * FROM sales_copy'
|
107
|
-
end
|
101
|
+
@connection.execute 'TRUNCATE TABLE sales'
|
102
|
+
@connection.execute 'INSERT INTO sales SELECT * FROM sales_copy'
|
108
103
|
|
109
104
|
@olap.flush_schema_cache
|
110
105
|
@olap.close
|
@@ -112,12 +107,7 @@ describe "Cube" do
|
|
112
107
|
end
|
113
108
|
|
114
109
|
after(:all) do
|
115
|
-
|
116
|
-
when 'mysql', 'jdbc_mysql', 'postgresql', 'oracle', 'vertica', 'snowflake'
|
117
|
-
@connection.execute 'DROP TABLE sales_copy'
|
118
|
-
when 'mssql', 'sqlserver'
|
119
|
-
@connection.execute 'DROP TABLE sales_copy'
|
120
|
-
end
|
110
|
+
@connection.execute 'DROP TABLE sales_copy'
|
121
111
|
end
|
122
112
|
|
123
113
|
it 'should clear cache for deleted data at lower level with segments' do
|
@@ -61,9 +61,6 @@ fname || ' ' || lname
|
|
61
61
|
</SQL>
|
62
62
|
<SQL dialect="mysql">
|
63
63
|
CONCAT(`customers`.`fname`, ' ', `customers`.`lname`)
|
64
|
-
</SQL>
|
65
|
-
<SQL dialect="luciddb">
|
66
|
-
"fname" || ' ' || "lname"
|
67
64
|
</SQL>
|
68
65
|
<SQL dialect="generic">
|
69
66
|
fullname
|
@@ -78,9 +75,6 @@ fname || ' ' || lname
|
|
78
75
|
</SQL>
|
79
76
|
<SQL dialect="mysql">
|
80
77
|
CONCAT(`customers`.`fname`, ' ', `customers`.`lname`)
|
81
|
-
</SQL>
|
82
|
-
<SQL dialect="luciddb">
|
83
|
-
"fname" || ' ' || "lname"
|
84
78
|
</SQL>
|
85
79
|
<SQL dialect="generic">
|
86
80
|
fullname
|
@@ -61,9 +61,6 @@ 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
|
67
64
|
</SQL>
|
68
65
|
<SQL dialect="generic">
|
69
66
|
FULLNAME
|
@@ -78,9 +75,6 @@ fname || ' ' || lname
|
|
78
75
|
</SQL>
|
79
76
|
<SQL dialect="mysql">
|
80
77
|
CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)
|
81
|
-
</SQL>
|
82
|
-
<SQL dialect="luciddb">
|
83
|
-
fname || ' ' || lname
|
84
78
|
</SQL>
|
85
79
|
<SQL dialect="generic">
|
86
80
|
FULLNAME
|
data/spec/mondrian_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require "spec_helper"
|
2
4
|
|
3
5
|
describe "Mondrian features" do
|
@@ -11,13 +13,39 @@ describe "Mondrian features" do
|
|
11
13
|
level 'Gender', :column => 'gender', :unique_members => true
|
12
14
|
end
|
13
15
|
end
|
16
|
+
dimension 'Promotions', :foreign_key => 'promotion_id' do
|
17
|
+
hierarchy :has_all => true, :primary_key => 'id' do
|
18
|
+
table 'promotions'
|
19
|
+
level 'Promotion', :column => 'id', :name_column => 'promotion', :unique_members => true, :ordinal_column => 'sequence', :type => 'Numeric'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
dimension 'Linked Promotions', :foreign_key => 'customer_id' do
|
23
|
+
hierarchy :has_all => true, :primary_key => 'id', :primary_key_table => 'customers' do
|
24
|
+
join :left_key => 'related_fullname', :right_key => 'fullname' do
|
25
|
+
table "customers"
|
26
|
+
join :left_key => "promotion_id", :right_key => "id" do
|
27
|
+
table "customers", :alias => "customers_bt"
|
28
|
+
table "promotions"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
level 'Promotion', :column => 'id', :name_column => 'promotion', :unique_members => true, :table => 'promotions', :ordinal_column => 'sequence', :type => 'Numeric', :approx_row_count => 10
|
32
|
+
end
|
33
|
+
end
|
14
34
|
dimension 'Customers', :foreign_key => 'customer_id' do
|
15
35
|
hierarchy :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
|
16
36
|
table 'customers'
|
17
37
|
level 'Country', :column => 'country', :unique_members => true
|
18
38
|
level 'State Province', :column => 'state_province', :unique_members => true
|
19
39
|
level 'City', :column => 'city', :unique_members => false
|
20
|
-
level 'Name', :column => 'fullname', :unique_members => true
|
40
|
+
level 'Name', :column => 'fullname', :unique_members => true do
|
41
|
+
property 'Related name', :column => 'related_fullname', :type => "String"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
hierarchy 'ID', :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
|
45
|
+
table 'customers'
|
46
|
+
level 'ID', :column => 'id', :type => 'Numeric', :internal_type => 'long', :unique_members => true do
|
47
|
+
property 'Name', :column => 'fullname'
|
48
|
+
end
|
21
49
|
end
|
22
50
|
end
|
23
51
|
dimension 'Time', :foreign_key => 'time_id', :type => 'TimeDimension' do
|
@@ -50,4 +78,46 @@ describe "Mondrian features" do
|
|
50
78
|
end.should_not raise_error
|
51
79
|
end
|
52
80
|
|
81
|
+
# test for https://jira.pentaho.com/browse/MONDRIAN-2683
|
82
|
+
it "should order crossjoin of rows" do
|
83
|
+
lambda do
|
84
|
+
@olap.from('Sales').
|
85
|
+
columns('[Measures].[Unit Sales]').
|
86
|
+
rows('[Customers].[Country].Members').crossjoin('[Gender].[Gender].Members').
|
87
|
+
order('[Measures].[Unit Sales]', :bdesc).
|
88
|
+
execute
|
89
|
+
end.should_not raise_error
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should generate correct member name from large number key" do
|
93
|
+
result = @olap.from('Sales').
|
94
|
+
columns("Filter([Customers.ID].[ID].Members, [Customers.ID].CurrentMember.Properties('Name') = 'Big Number')").
|
95
|
+
execute
|
96
|
+
result.column_names.should == ["10000000000"]
|
97
|
+
end
|
98
|
+
|
99
|
+
# test for https://jira.pentaho.com/browse/MONDRIAN-990
|
100
|
+
it "should return result when diacritical marks used" do
|
101
|
+
full_name = '[Customers].[USA].[CA].[Rīga]'
|
102
|
+
result = @olap.from('Sales').columns(full_name).execute
|
103
|
+
result.column_full_names.should == [full_name]
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should execute MDX with join tables" do
|
107
|
+
# Load dimension members in Mondrian cache as the problem occurred when searching members in the cache
|
108
|
+
@olap.from('Sales').columns('CROSSJOIN({[Linked Promotions].[Promotion].[Promotion 2]}, [Customers].[Name].Members)').execute
|
109
|
+
|
110
|
+
mdx = <<~MDX
|
111
|
+
SELECT
|
112
|
+
NON EMPTY FILTER(
|
113
|
+
CROSSJOIN({[Linked Promotions].[Promotion].[Promotion 2]}, [Customers].[Name].Members),
|
114
|
+
(([Measures].[Unit Sales]) <> 0)
|
115
|
+
) ON ROWS,
|
116
|
+
[Measures].[Unit Sales] ON COLUMNS
|
117
|
+
FROM [Sales]
|
118
|
+
MDX
|
119
|
+
|
120
|
+
expect { @olap.execute mdx }.not_to raise_error
|
121
|
+
end
|
122
|
+
|
53
123
|
end
|
data/spec/query_spec.rb
CHANGED
@@ -788,7 +788,7 @@ describe "Query" do
|
|
788
788
|
@drill_through.column_types.should == [
|
789
789
|
:INT, :VARCHAR, :INT, :INT, :INT,
|
790
790
|
:VARCHAR, :VARCHAR, :VARCHAR, :VARCHAR, :VARCHAR, :VARCHAR,
|
791
|
-
:VARCHAR, :VARCHAR, :VARCHAR, :
|
791
|
+
:VARCHAR, :VARCHAR, :VARCHAR, :BIGINT,
|
792
792
|
:VARCHAR,
|
793
793
|
:DECIMAL
|
794
794
|
]
|
@@ -912,15 +912,20 @@ describe "Query" do
|
|
912
912
|
return: [
|
913
913
|
"Name([Customers].[Name])",
|
914
914
|
"Property([Customers].[Name], 'Gender')",
|
915
|
-
"Property([Customers].[Name], 'Description')"
|
915
|
+
"Property([Customers].[Name], 'Description')",
|
916
|
+
"Property([Customers].[Name], 'Very long non-existing property name')"
|
916
917
|
]
|
917
918
|
)
|
918
|
-
@drill_through.column_labels.should == [
|
919
|
-
|
919
|
+
@drill_through.column_labels.should == [
|
920
|
+
"Name", "Gender", "Description",
|
921
|
+
"Very long non-existing property name"[0, MONDRIAN_DRIVER == 'oracle' ? 30 : 9999]
|
922
|
+
]
|
923
|
+
@drill_through.rows.should == @sql.select_rows(<<-SQL)
|
920
924
|
SELECT
|
921
925
|
customers.fullname,
|
922
926
|
customers.gender,
|
923
|
-
customers.description
|
927
|
+
customers.description,
|
928
|
+
'' as non_existing
|
924
929
|
FROM
|
925
930
|
sales,
|
926
931
|
customers,
|
@@ -940,7 +945,6 @@ describe "Query" do
|
|
940
945
|
customers.gender,
|
941
946
|
customers.description
|
942
947
|
SQL
|
943
|
-
)
|
944
948
|
end
|
945
949
|
|
946
950
|
it "should group by" do
|
data/spec/rake_tasks.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
namespace :db do
|
2
4
|
task :require_spec_helper do
|
3
5
|
require File.expand_path("../spec_helper", __FILE__)
|
@@ -40,6 +42,8 @@ namespace :db do
|
|
40
42
|
t.string :lname, :limit => 30
|
41
43
|
t.string :fullname, :limit => 60
|
42
44
|
t.string :gender, :limit => 30
|
45
|
+
t.integer :promotion_id
|
46
|
+
t.string :related_fullname, :limit => 60
|
43
47
|
# Mondrian does not support properties with Oracle CLOB type
|
44
48
|
# as it tries to GROUP BY all columns when loading a dimension table
|
45
49
|
if MONDRIAN_DRIVER == 'oracle'
|
@@ -47,13 +51,47 @@ namespace :db do
|
|
47
51
|
else
|
48
52
|
t.text :description
|
49
53
|
end
|
54
|
+
end
|
55
|
+
|
56
|
+
if MONDRIAN_DRIVER == 'oracle'
|
57
|
+
|
58
|
+
execute "DROP TABLE PROMOTIONS" rescue nil
|
59
|
+
execute "DROP SEQUENCE PROMOTIONS_SEQ" rescue nil
|
60
|
+
|
61
|
+
execute <<~SQL
|
62
|
+
CREATE TABLE PROMOTIONS(
|
63
|
+
ID NUMBER(*,0) NOT NULL,
|
64
|
+
PROMOTION VARCHAR2(30 CHAR),
|
65
|
+
SEQUENCE NUMBER(38,0),
|
66
|
+
PRIMARY KEY ("ID")
|
67
|
+
)
|
68
|
+
SQL
|
69
|
+
execute "CREATE SEQUENCE PROMOTIONS_SEQ"
|
70
|
+
else
|
71
|
+
create_table :promotions, :force => true do |t|
|
72
|
+
t.string :promotion, :limit => 30
|
73
|
+
t.integer :sequence
|
74
|
+
end
|
75
|
+
end
|
50
76
|
|
77
|
+
case MONDRIAN_DRIVER
|
78
|
+
when /mysql/
|
79
|
+
execute "ALTER TABLE customers MODIFY COLUMN id BIGINT NOT NULL AUTO_INCREMENT"
|
80
|
+
when /postgresql/
|
81
|
+
execute "ALTER TABLE customers ALTER COLUMN id SET DATA TYPE bigint"
|
82
|
+
when /mssql|sqlserver/
|
83
|
+
sql = "SELECT name FROM sysobjects WHERE xtype = 'PK' AND parent_obj=OBJECT_ID('customers')"
|
84
|
+
primary_key_constraint = select_value(sql)
|
85
|
+
execute "ALTER TABLE customers DROP CONSTRAINT #{primary_key_constraint}"
|
86
|
+
execute "ALTER TABLE customers ALTER COLUMN id BIGINT"
|
87
|
+
execute "ALTER TABLE customers ADD CONSTRAINT #{primary_key_constraint} PRIMARY KEY (id)"
|
51
88
|
end
|
52
89
|
|
53
90
|
create_table :sales, :force => true, :id => false do |t|
|
54
91
|
t.integer :product_id
|
55
92
|
t.integer :time_id
|
56
93
|
t.integer :customer_id
|
94
|
+
t.integer :promotion_id
|
57
95
|
t.decimal :store_sales, :precision => 10, :scale => 4
|
58
96
|
t.decimal :store_cost, :precision => 10, :scale => 4
|
59
97
|
t.decimal :unit_sales, :precision => 10, :scale => 4
|
@@ -61,189 +99,236 @@ namespace :db do
|
|
61
99
|
end
|
62
100
|
end
|
63
101
|
|
64
|
-
task :setup_luciddb => :require_spec_helper do
|
65
|
-
# create link to mysql database to import tables
|
66
|
-
# see description at http://pub.eigenbase.org/wiki/LucidDbCreateForeignServer
|
67
|
-
if MONDRIAN_DRIVER == 'luciddb'
|
68
|
-
conn = ActiveRecord::Base.connection
|
69
|
-
conn.execute "drop schema mondrian_test_source cascade" rescue nil
|
70
|
-
conn.execute "drop server mondrian_test_source" rescue nil
|
71
|
-
conn.execute "create schema mondrian_test_source"
|
72
|
-
conn.execute <<-SQL
|
73
|
-
create server mondrian_test_source
|
74
|
-
foreign data wrapper sys_jdbc
|
75
|
-
options(
|
76
|
-
driver_class 'com.mysql.jdbc.Driver',
|
77
|
-
url 'jdbc:mysql://localhost/mondrian_test?characterEncoding=utf-8&useCursorFetch=true',
|
78
|
-
user_name 'mondrian_test',
|
79
|
-
password 'mondrian_test',
|
80
|
-
login_timeout '10',
|
81
|
-
fetch_size '1000',
|
82
|
-
validation_query 'select 1',
|
83
|
-
schema_name 'MONDRIAN_TEST',
|
84
|
-
table_types 'TABLE')
|
85
|
-
SQL
|
86
|
-
conn.execute "import foreign schema mondrian_test from server mondrian_test_source into mondrian_test_source"
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
102
|
task :define_models => :require_spec_helper do
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
self.quarter = "Q#{(month_of_year-1)/3+1}"
|
103
|
-
end
|
104
|
-
end
|
105
|
-
class Product < ActiveRecord::Base
|
106
|
-
belongs_to :product_class
|
107
|
-
end
|
108
|
-
class ProductClass < ActiveRecord::Base
|
109
|
-
end
|
110
|
-
class Customer < ActiveRecord::Base
|
111
|
-
end
|
112
|
-
class Sales < ActiveRecord::Base
|
113
|
-
self.table_name = "sales"
|
114
|
-
belongs_to :time_by_day
|
115
|
-
belongs_to :product
|
116
|
-
belongs_to :customer
|
103
|
+
class TimeDimension < ActiveRecord::Base
|
104
|
+
self.table_name = "time"
|
105
|
+
validates_presence_of :the_date
|
106
|
+
before_create do
|
107
|
+
self.the_day = the_date.strftime("%A")
|
108
|
+
self.the_month = the_date.strftime("%B")
|
109
|
+
self.the_year = the_date.strftime("%Y").to_i
|
110
|
+
self.day_of_month = the_date.strftime("%d").to_i
|
111
|
+
self.week_of_year = the_date.strftime("%W").to_i
|
112
|
+
self.month_of_year = the_date.strftime("%m").to_i
|
113
|
+
self.quarter = "Q#{(month_of_year-1)/3+1}"
|
117
114
|
end
|
118
115
|
end
|
116
|
+
class Product < ActiveRecord::Base
|
117
|
+
belongs_to :product_class
|
118
|
+
end
|
119
|
+
class ProductClass < ActiveRecord::Base
|
120
|
+
end
|
121
|
+
class Customer < ActiveRecord::Base
|
122
|
+
end
|
123
|
+
class Promotion < ActiveRecord::Base
|
124
|
+
end
|
125
|
+
class Sales < ActiveRecord::Base
|
126
|
+
self.table_name = "sales"
|
127
|
+
belongs_to :time_by_day
|
128
|
+
belongs_to :product
|
129
|
+
belongs_to :customer
|
130
|
+
end
|
119
131
|
end
|
120
132
|
|
121
133
|
desc "Create test data"
|
122
|
-
task :create_data => [:create_tables
|
134
|
+
task :create_data => [:create_tables] + ( %w(vertica snowflake).include?(ENV['MONDRIAN_DRIVER']) ? [:import_data] :
|
135
|
+
[ :create_time_data, :create_product_data, :create_promotion_data, :create_customer_data, :create_sales_data ] )
|
123
136
|
|
124
137
|
task :create_time_data => :define_models do
|
125
138
|
puts "==> Creating time dimension"
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
else
|
131
|
-
TimeDimension.delete_all
|
132
|
-
start_time = Time.local(2010,1,1)
|
133
|
-
(2*365).times do |i|
|
134
|
-
TimeDimension.create!(:the_date => start_time + i.day)
|
135
|
-
end
|
139
|
+
TimeDimension.delete_all
|
140
|
+
start_time = Time.utc(2010,1,1)
|
141
|
+
(2*365).times do |i|
|
142
|
+
TimeDimension.create!(:the_date => start_time + i.day)
|
136
143
|
end
|
137
144
|
end
|
138
145
|
|
139
146
|
task :create_product_data => :define_models do
|
140
147
|
puts "==> Creating product data"
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
)
|
165
|
-
end
|
148
|
+
Product.delete_all
|
149
|
+
ProductClass.delete_all
|
150
|
+
families = ["Drink", "Food", "Non-Consumable"]
|
151
|
+
(1..100).each do |i|
|
152
|
+
product_class = ProductClass.create!(
|
153
|
+
:product_family => families[i % 3],
|
154
|
+
:product_department => "Department #{i}",
|
155
|
+
:product_category => "Category #{i}",
|
156
|
+
:product_subcategory => "Subcategory #{i}"
|
157
|
+
)
|
158
|
+
Product.create!(
|
159
|
+
:product_class_id => ProductClass.where(:product_category => "Category #{i}").to_a.first.id,
|
160
|
+
:brand_name => "Brand #{i}",
|
161
|
+
:product_name => "Product #{i}"
|
162
|
+
)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
task :create_promotion_data => :define_models do
|
167
|
+
puts "==> Creating promotion data"
|
168
|
+
Promotion.delete_all
|
169
|
+
(1..10).each do |i|
|
170
|
+
Promotion.create!(promotion: "Promotion #{i}", sequence: i)
|
166
171
|
end
|
167
172
|
end
|
168
173
|
|
169
174
|
task :create_customer_data => :define_models do
|
170
175
|
puts "==> Creating customer data"
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
[
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
176
|
+
Customer.delete_all
|
177
|
+
promotions = Promotion.order("id").to_a
|
178
|
+
i = 0
|
179
|
+
[
|
180
|
+
["Canada", "BC", "Burnaby"],["Canada", "BC", "Cliffside"],["Canada", "BC", "Haney"],["Canada", "BC", "Ladner"],
|
181
|
+
["Canada", "BC", "Langford"],["Canada", "BC", "Langley"],["Canada", "BC", "Metchosin"],["Canada", "BC", "N. Vancouver"],
|
182
|
+
["Canada", "BC", "Newton"],["Canada", "BC", "Oak Bay"],["Canada", "BC", "Port Hammond"],["Canada", "BC", "Richmond"],
|
183
|
+
["Canada", "BC", "Royal Oak"],["Canada", "BC", "Shawnee"],["Canada", "BC", "Sooke"],["Canada", "BC", "Vancouver"],
|
184
|
+
["Canada", "BC", "Victoria"],["Canada", "BC", "Westminster"],
|
185
|
+
["Mexico", "DF", "San Andres"],["Mexico", "DF", "Santa Anita"],["Mexico", "DF", "Santa Fe"],["Mexico", "DF", "Tixapan"],
|
186
|
+
["Mexico", "Guerrero", "Acapulco"],["Mexico", "Jalisco", "Guadalajara"],["Mexico", "Mexico", "Mexico City"],
|
187
|
+
["Mexico", "Oaxaca", "Tlaxiaco"],["Mexico", "Sinaloa", "La Cruz"],["Mexico", "Veracruz", "Orizaba"],
|
188
|
+
["Mexico", "Yucatan", "Merida"],["Mexico", "Zacatecas", "Camacho"],["Mexico", "Zacatecas", "Hidalgo"],
|
189
|
+
["USA", "CA", "Altadena"],["USA", "CA", "Arcadia"],["USA", "CA", "Bellflower"],["USA", "CA", "Berkeley"],
|
190
|
+
["USA", "CA", "Beverly Hills"],["USA", "CA", "Burbank"],["USA", "CA", "Burlingame"],["USA", "CA", "Chula Vista"],
|
191
|
+
["USA", "CA", "Colma"],["USA", "CA", "Concord"],["USA", "CA", "Coronado"],["USA", "CA", "Daly City"],
|
192
|
+
["USA", "CA", "Downey"],["USA", "CA", "El Cajon"],["USA", "CA", "Fremont"],["USA", "CA", "Glendale"],
|
193
|
+
["USA", "CA", "Grossmont"],["USA", "CA", "Imperial Beach"],["USA", "CA", "La Jolla"],["USA", "CA", "La Mesa"],
|
194
|
+
["USA", "CA", "Lakewood"],["USA", "CA", "Lemon Grove"],["USA", "CA", "Lincoln Acres"],["USA", "CA", "Long Beach"],
|
195
|
+
["USA", "CA", "Los Angeles"],["USA", "CA", "Mill Valley"],["USA", "CA", "National City"],["USA", "CA", "Newport Beach"],
|
196
|
+
["USA", "CA", "Novato"],["USA", "CA", "Oakland"],["USA", "CA", "Palo Alto"],["USA", "CA", "Pomona"],
|
197
|
+
["USA", "CA", "Redwood City"],["USA", "CA", "Richmond"],["USA", "CA", "San Carlos"],["USA", "CA", "San Diego"],
|
198
|
+
["USA", "CA", "San Francisco"],["USA", "CA", "San Gabriel"],["USA", "CA", "San Jose"],["USA", "CA", "Santa Cruz"],
|
199
|
+
["USA", "CA", "Santa Monica"],["USA", "CA", "Spring Valley"],["USA", "CA", "Torrance"],["USA", "CA", "West Covina"],
|
200
|
+
["USA", "CA", "Woodland Hills"],
|
201
|
+
["USA", "OR", "Albany"],["USA", "OR", "Beaverton"],["USA", "OR", "Corvallis"],["USA", "OR", "Lake Oswego"],
|
202
|
+
["USA", "OR", "Lebanon"],["USA", "OR", "Milwaukie"],["USA", "OR", "Oregon City"],["USA", "OR", "Portland"],
|
203
|
+
["USA", "OR", "Salem"],["USA", "OR", "W. Linn"],["USA", "OR", "Woodburn"],
|
204
|
+
["USA", "WA", "Anacortes"],["USA", "WA", "Ballard"],["USA", "WA", "Bellingham"],["USA", "WA", "Bremerton"],
|
205
|
+
["USA", "WA", "Burien"],["USA", "WA", "Edmonds"],["USA", "WA", "Everett"],["USA", "WA", "Issaquah"],
|
206
|
+
["USA", "WA", "Kirkland"],["USA", "WA", "Lynnwood"],["USA", "WA", "Marysville"],["USA", "WA", "Olympia"],
|
207
|
+
["USA", "WA", "Port Orchard"],["USA", "WA", "Puyallup"],["USA", "WA", "Redmond"],["USA", "WA", "Renton"],
|
208
|
+
["USA", "WA", "Seattle"],["USA", "WA", "Sedro Woolley"],["USA", "WA", "Spokane"],["USA", "WA", "Tacoma"],
|
209
|
+
["USA", "WA", "Walla Walla"],["USA", "WA", "Yakima"]
|
210
|
+
].each do |country, state, city|
|
211
|
+
i += 1
|
212
|
+
Customer.create!(
|
213
|
+
:country => country,
|
214
|
+
:state_province => state,
|
215
|
+
:city => city,
|
216
|
+
:fname => "First#{i}",
|
217
|
+
:lname => "Last#{i}",
|
218
|
+
:fullname => "First#{i} Last#{i}",
|
219
|
+
:gender => i % 2 == 0 ? "M" : "F",
|
220
|
+
:promotion_id => promotions[i % 10].id,
|
221
|
+
:related_fullname => "First#{i} Last#{i}",
|
222
|
+
:description => 100.times.map{"1234567890"}.join("\n")
|
223
|
+
)
|
224
|
+
end
|
225
|
+
# Create additional customer with large ID
|
226
|
+
attributes = {
|
227
|
+
:id => 10_000_000_000,
|
228
|
+
:country => "USA",
|
229
|
+
:state_province => "CA",
|
230
|
+
:city => "Rīga", # For testing UTF-8 characters
|
231
|
+
:fname => "Big",
|
232
|
+
:lname => "Number",
|
233
|
+
:fullname => "Big Number",
|
234
|
+
:gender => "M",
|
235
|
+
:promotion_id => promotions.first.id,
|
236
|
+
:related_fullname => "Big Number"
|
237
|
+
}
|
238
|
+
case MONDRIAN_DRIVER
|
239
|
+
when /mssql|sqlserver/
|
240
|
+
Customer.connection.with_identity_insert_enabled("customers") do
|
241
|
+
Customer.create!(attributes)
|
221
242
|
end
|
243
|
+
else
|
244
|
+
Customer.create!(attributes)
|
222
245
|
end
|
223
246
|
end
|
224
247
|
|
225
248
|
task :create_sales_data => :define_models do
|
226
249
|
puts "==> Creating sales data"
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
250
|
+
Sales.delete_all
|
251
|
+
count = 100
|
252
|
+
products = Product.order("id").to_a[0...count]
|
253
|
+
times = TimeDimension.order("id").to_a[0...count]
|
254
|
+
customers = Customer.order("id").to_a[0...count]
|
255
|
+
promotions = Promotion.order("id").to_a[0...count]
|
256
|
+
count.times do |i|
|
257
|
+
Sales.create!(
|
258
|
+
:product_id => products[i].id,
|
259
|
+
:time_id => times[i].id,
|
260
|
+
:customer_id => customers[i].id,
|
261
|
+
:promotion_id => promotions[i % 10].id,
|
262
|
+
:store_sales => BigDecimal("2#{i}.12"),
|
263
|
+
:store_cost => BigDecimal("1#{i}.1234"),
|
264
|
+
:unit_sales => i+1
|
265
|
+
)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
export_data_dir = File.expand_path("spec/support/data")
|
270
|
+
table_names = %w(time product_classes products customers sales)
|
271
|
+
|
272
|
+
desc "Export test data"
|
273
|
+
task :export_data => :create_data do
|
274
|
+
require "csv"
|
275
|
+
puts "==> Exporting data"
|
276
|
+
conn = ActiveRecord::Base.connection
|
277
|
+
table_names.each do |table_name|
|
278
|
+
column_names = conn.columns(table_name).map(&:name)
|
279
|
+
csv_content = conn.select_rows("SELECT #{column_names.join(',')} FROM #{table_name}").map do |row|
|
280
|
+
row.map do |value|
|
281
|
+
case value
|
282
|
+
when Time
|
283
|
+
value.utc.to_s(:db)
|
284
|
+
else
|
285
|
+
value
|
286
|
+
end
|
287
|
+
end.to_csv
|
288
|
+
end.join
|
289
|
+
file_path = File.expand_path("#{table_name}.csv", export_data_dir)
|
290
|
+
File.open(file_path, "w") do |file|
|
291
|
+
file.write column_names.to_csv
|
292
|
+
file.write csv_content
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
task :import_data => :require_spec_helper do
|
298
|
+
puts "==> Importing data"
|
299
|
+
conn = ActiveRecord::Base.connection
|
300
|
+
|
301
|
+
case MONDRIAN_DRIVER
|
302
|
+
when 'vertica'
|
303
|
+
table_names.each do |table_name|
|
304
|
+
puts "==> Truncate #{table_name}"
|
305
|
+
conn.execute "TRUNCATE TABLE #{table_name}"
|
306
|
+
puts "==> Copy into #{table_name}"
|
307
|
+
file_path = "#{export_data_dir}/#{table_name}.csv"
|
308
|
+
columns_string = File.open(file_path) { |f| f.gets }.chomp
|
309
|
+
count = conn.execute "COPY #{table_name}(#{columns_string}) FROM LOCAL '#{file_path}' " \
|
310
|
+
"PARSER public.fcsvparser(header='true') ABORT ON ERROR REJECTMAX 0"
|
311
|
+
puts "==> Loaded #{count} records"
|
312
|
+
end
|
313
|
+
|
314
|
+
when 'snowflake'
|
315
|
+
conn.execute <<-SQL
|
316
|
+
CREATE OR REPLACE FILE FORMAT csv
|
317
|
+
TYPE = 'CSV' COMPRESSION = 'AUTO' FIELD_DELIMITER = ',' RECORD_DELIMITER = '\\n' SKIP_HEADER = 1
|
318
|
+
FIELD_OPTIONALLY_ENCLOSED_BY = '\\042' TRIM_SPACE = FALSE ERROR_ON_COLUMN_COUNT_MISMATCH = TRUE ESCAPE = 'NONE'
|
319
|
+
ESCAPE_UNENCLOSED_FIELD = 'NONE' DATE_FORMAT = 'AUTO' TIMESTAMP_FORMAT = 'AUTO' NULL_IF = ('')
|
320
|
+
SQL
|
321
|
+
conn.execute "CREATE OR REPLACE STAGE csv_stage FILE_FORMAT = csv"
|
322
|
+
conn.execute "PUT file://#{export_data_dir}/*.csv @csv_stage AUTO_COMPRESS = TRUE"
|
323
|
+
table_names.each do |table_name|
|
324
|
+
puts "==> Truncate #{table_name}"
|
325
|
+
conn.execute "TRUNCATE TABLE #{table_name}"
|
326
|
+
puts "==> Copy into #{table_name}"
|
327
|
+
file_path = "#{export_data_dir}/#{table_name}.csv"
|
328
|
+
columns_string = File.open(file_path) { |f| f.gets }.chomp
|
329
|
+
count = conn.execute "COPY INTO #{table_name}(#{columns_string}) FROM @csv_stage/#{table_name}.csv.gz " \
|
330
|
+
"FILE_FORMAT = (FORMAT_NAME = csv)"
|
331
|
+
puts "==> Loaded #{count} records"
|
247
332
|
end
|
248
333
|
end
|
249
334
|
end
|
@@ -251,7 +336,7 @@ namespace :db do
|
|
251
336
|
end
|
252
337
|
|
253
338
|
namespace :spec do
|
254
|
-
%w(mysql jdbc_mysql postgresql oracle
|
339
|
+
%w(mysql jdbc_mysql postgresql oracle mssql sqlserver vertica snowflake).each do |driver|
|
255
340
|
desc "Run specs with #{driver} driver"
|
256
341
|
task driver do
|
257
342
|
ENV['MONDRIAN_DRIVER'] = driver
|