masamune 0.13.0 → 0.13.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -29,6 +29,8 @@ module Masamune::Transform
29
29
  columns = options[:include] || []
30
30
  columns += options[:columns] || target.denormalized_column_names
31
31
  columns -= options[:except] || []
32
+ columns -= ['last_modified_at']
33
+ columns.uniq!
32
34
  order_by = options[:order] || columns
33
35
  Operator.new(__method__, target: target, columns: columns, order_by: order_by, presenters: { postgres: Common, hive: Common })
34
36
  end
@@ -63,6 +65,7 @@ module Masamune::Transform
63
65
  column_names.each do |column_name|
64
66
  next unless column = dereference_column_name(column_name)
65
67
  next unless column.reference
68
+ next if column.reference.degenerate
66
69
  adjacent_reference = references[column.reference.id]
67
70
  next unless adjacent_reference
68
71
  adjacent_column = columns[adjacent_reference.foreign_key_name]
@@ -28,6 +28,7 @@ module Masamune::Transform
28
28
  @source = options.delete(:source)
29
29
  @target = options.delete(:target)
30
30
  @presenters = options.delete(:presenters) || {}
31
+ @helper = options.delete(:helper)
31
32
  @locals = options
32
33
  end
33
34
 
@@ -41,6 +42,14 @@ module Masamune::Transform
41
42
  @presenters.key?(target_store.try(:type)) ? @presenters[target_store.try(:type)].new(@target) : @target
42
43
  end
43
44
 
45
+ def helper
46
+ (@helper || SimpleDelegator).new(self)
47
+ end
48
+
49
+ def locals
50
+ @locals
51
+ end
52
+
44
53
  def to_s
45
54
  result = []
46
55
  @templates.each do |template|
@@ -76,7 +85,7 @@ module Masamune::Transform
76
85
  def template_eval(template)
77
86
  return File.read(template) if File.exists?(template.to_s) && template.to_s !~ /erb\Z/
78
87
  template_file = File.exists?(template.to_s) ? template : template_file(template)
79
- Masamune::Template.render_to_string(template_file, @locals.merge(source: source, target: target))
88
+ Masamune::Template.render_to_string(template_file, @locals.merge(source: source, target: target, helper: helper))
80
89
  end
81
90
 
82
91
  def template_file(template_prefix)
@@ -43,12 +43,8 @@ DROP INDEX IF EXISTS <%= index_name %>;
43
43
  ALTER TABLE <%= target.name %> RENAME TO <%= target_tmp.name %>;
44
44
  ALTER TABLE <%= source.name %> RENAME TO <%= target.name %>;
45
45
 
46
- <%- if target.parent -%>
47
- ALTER TABLE <%= target.name %> INHERIT <%= target.parent.name %>;
48
- <%- end -%>
49
- <%- if target.constraints -%>
50
- ALTER TABLE <%= target.name %> ADD CONSTRAINT <%= target.name %>_time_key_check <%= target.constraints %>;
51
- <%- end -%>
46
+ <%= render 'define_inheritance.psql.erb', target: target %>
47
+
52
48
  <%= render 'define_foreign_key.psql.erb', target: target, skip_check_exist: true, skip_check_valid: true %>
53
49
 
54
50
  <%= render 'define_index.psql.erb', target: target, skip_check_exist: true %>
@@ -21,5 +21,5 @@
21
21
  # THE SOFTWARE.
22
22
 
23
23
  module Masamune
24
- VERSION = '0.13.0'
24
+ VERSION = '0.13.1'
25
25
  end
@@ -54,6 +54,21 @@ describe Masamune::Commands::PostgresAdmin do
54
54
  it { is_expected.to eq(['dropdb', '--if-exists', '--host=localhost', '--username=postgres', '--no-password', 'zombo']) }
55
55
  end
56
56
 
57
+ context 'action :drop with database and :output' do
58
+ let(:attrs) { {action: :drop, database: 'zombo', output: 'zombo.csv'} }
59
+ it { is_expected.to eq(['dropdb', '--if-exists', '--host=localhost', '--username=postgres', '--no-password', 'zombo']) }
60
+ end
61
+
62
+ context 'action :dump with database' do
63
+ let(:attrs) { {action: :dump, database: 'zombo'} }
64
+ it { is_expected.to eq(['pg_dump', '--no-owner', '--no-privileges', '--oids', '--schema=public', '--host=localhost', '--username=postgres', '--no-password', '--dbname=zombo']) }
65
+ end
66
+
67
+ context 'action :dump with database and :output' do
68
+ let(:attrs) { {action: :dump, database: 'zombo', output: 'zombo.csv'} }
69
+ it { is_expected.to eq(['pg_dump', '--no-owner', '--no-privileges', '--oids', '--schema=public', '--host=localhost', '--username=postgres', '--no-password', '--dbname=zombo', '--file=zombo.csv']) }
70
+ end
71
+
57
72
  context 'action :drop without database' do
58
73
  let(:attrs) { {action: :drop} }
59
74
  it { expect { subject }.to raise_error ArgumentError, ':database must be given' }
@@ -61,7 +76,7 @@ describe Masamune::Commands::PostgresAdmin do
61
76
 
62
77
  context 'action unfuddle with database' do
63
78
  let(:attrs) { {action: :unfuddle, database: 'zombo'} }
64
- it { expect { subject }.to raise_error ArgumentError, ':action must be :create or :drop' }
79
+ it { expect { subject }.to raise_error ArgumentError, ':action must be :create, :drop, or :dump' }
65
80
  end
66
81
  end
67
82
 
@@ -177,7 +177,7 @@ describe Masamune::DataPlan::Engine do
177
177
  context 'invalid target' do
178
178
  let(:rule) { 'derived_daily' }
179
179
  let(:target) { '/table/y=2013/m=01/d=01' }
180
- it { expect { subject }.to raise_error }
180
+ it { expect { subject }.to raise_error /Cannot bind_input/ }
181
181
  end
182
182
  end
183
183
 
@@ -206,7 +206,7 @@ describe Masamune::DataPlan::Engine do
206
206
 
207
207
  context 'invalid target' do
208
208
  let(:target) { '/daily' }
209
- it { expect { subject }.to raise_error }
209
+ it { expect { subject }.to raise_error /No rule matches/ }
210
210
  end
211
211
  end
212
212
 
@@ -47,30 +47,45 @@ describe Masamune::DataPlan::Rule do
47
47
  end
48
48
  end
49
49
 
50
- describe '#bind_date' do
51
- subject(:elem) { instance.bind_date(input_date) }
50
+ describe '#bind_date_or_time' do
51
+ subject(:elem) { instance.bind_date_or_time(input) }
52
52
 
53
- context 'with default' do
54
- let(:input_date) { DateTime.civil(2013,04,05,23,13) }
53
+ context 'with nil input' do
54
+ let(:input) { nil }
55
+ it { expect { elem }.to raise_error ArgumentError }
56
+ end
57
+
58
+ context 'with unknown input type' do
59
+ let(:input) { 1 }
60
+ it { expect { elem }.to raise_error ArgumentError }
61
+ end
62
+
63
+ context 'with DateTime input' do
64
+ let(:input) { DateTime.civil(2013,04,05,23,13) }
55
65
 
56
66
  describe '#path' do
57
67
  subject { elem.path }
58
68
  it { is_expected.to eq('report/2013-04-05/23') }
59
69
  end
60
- let(:start_time) { DateTime.civil(2013,04,05,23) }
61
- let(:stop_time) { DateTime.civil(2013,04,05,0) }
62
70
  end
63
71
 
64
- context 'with unix timestamp pattern' do
72
+ context 'with DateTime input and unix timestamp pattern' do
65
73
  let(:pattern) { 'logs/%H-s.log' }
66
- let(:input_date) { DateTime.civil(2013,04,05,23,13) }
74
+ let(:input) { DateTime.civil(2013,04,05,23,13) }
67
75
 
68
76
  describe '#path' do
69
77
  subject { elem.path }
70
78
  it { is_expected.to eq('logs/1365202800.log') }
71
79
  end
72
- let(:start_time) { DateTime.civil(2013,04,05,23) }
73
- let(:stop_time) { DateTime.civil(2013,04,05,0) }
80
+ end
81
+
82
+ context 'with Date input' do
83
+ let(:input) { Date.civil(2013,04,05) }
84
+
85
+ describe '#path' do
86
+ subject { elem.path }
87
+ it { is_expected.to eq('report/2013-04-05/00') }
88
+ end
74
89
  end
75
90
  end
76
91
 
@@ -40,7 +40,18 @@ describe Masamune::Schema::Fact do
40
40
  ]
41
41
  end
42
42
 
43
- let(:fact) do
43
+ let(:fact_without_partition) do
44
+ described_class.new id: 'visits', store: store,
45
+ references: [
46
+ Masamune::Schema::TableReference.new(date_dimension),
47
+ Masamune::Schema::TableReference.new(user_dimension)
48
+ ],
49
+ columns: [
50
+ Masamune::Schema::Column.new(id: 'total', type: :integer)
51
+ ]
52
+ end
53
+
54
+ let(:fact_with_partition) do
44
55
  described_class.new id: 'visits', store: store, partition: 'y%Ym%m',
45
56
  references: [
46
57
  Masamune::Schema::TableReference.new(date_dimension),
@@ -53,36 +64,22 @@ describe Masamune::Schema::Fact do
53
64
  ]
54
65
  end
55
66
 
56
- it { expect(fact.name).to eq('visits_fact') }
57
-
58
- describe '#partition_table' do
59
- let(:date) { Chronic.parse('2015-01-01') }
60
-
61
- subject(:partition_table) { fact.partition_table(date) }
62
-
63
- it { expect(partition_table.store.id).to eq(store.id) }
64
- it { expect(partition_table.name).to eq('visits_fact_y2015m01') }
65
- it { expect(partition_table.range.start_date).to eq(date.utc.to_date) }
66
-
67
- describe '#stage_table' do
68
- subject(:stage_table) { partition_table.stage_table }
69
-
70
- it { expect(stage_table.store.id).to eq(store.id) }
71
- it { expect(stage_table.name).to eq('visits_fact_y2015m01_stage') }
72
- it { expect(stage_table.range.start_date).to eq(date.utc.to_date) }
73
-
74
- context 'with optional suffix' do
75
- subject(:stage_table) { partition_table.stage_table(suffix: 'tmp') }
76
-
77
- it 'should append suffix to id' do
78
- expect(stage_table.store.id).to eq(store.id)
79
- expect(stage_table.name).to eq('visits_fact_y2015m01_stage_tmp')
80
- expect(stage_table.range.start_date).to eq(date.utc.to_date)
81
- end
82
- end
83
- end
67
+ let(:fact_with_partition_and_hourly_grain) do
68
+ described_class.new id: 'visits', store: store, grain: :hourly, partition: 'y%Ym%m',
69
+ references: [
70
+ Masamune::Schema::TableReference.new(date_dimension),
71
+ Masamune::Schema::TableReference.new(user_dimension)
72
+ ],
73
+ columns: [
74
+ Masamune::Schema::Column.new(id: 'total', type: :integer)
75
+ ]
84
76
  end
85
77
 
78
+ it { expect(fact_without_partition.name).to eq('visits_fact') }
79
+ it { expect(fact_with_partition.name).to eq('visits_fact') }
80
+ it { expect(fact_with_partition_and_hourly_grain.id).to eq(:visits_hourly) }
81
+ it { expect(fact_with_partition_and_hourly_grain.name).to eq('visits_hourly_fact') }
82
+
86
83
  context 'fact with unknown grain' do
87
84
  subject(:fact) do
88
85
  described_class.new id: 'visits', grain: :quarterly
@@ -91,25 +88,44 @@ describe Masamune::Schema::Fact do
91
88
  it { expect { fact }.to raise_error ArgumentError, "unknown grain 'quarterly'" }
92
89
  end
93
90
 
94
- context 'fact with :hourly grain' do
95
- let(:fact) do
96
- described_class.new id: 'visits', store: store, grain: :hourly, partition: 'y%Ym%m',
97
- references: [
98
- Masamune::Schema::TableReference.new(date_dimension),
99
- Masamune::Schema::TableReference.new(user_dimension)
100
- ],
101
- columns: [
102
- Masamune::Schema::Column.new(id: 'total', type: :integer)
103
- ]
91
+ describe '#partition_table' do
92
+ let(:date) { Chronic.parse('2015-01-01') }
93
+ subject(:partition_table) { fact.partition_table(date) }
94
+
95
+ context 'fact without partition' do
96
+ let(:fact) { fact_without_partition }
97
+
98
+ it { expect(partition_table).to be_nil }
104
99
  end
105
100
 
106
- it { expect(fact.id).to eq(:visits_hourly) }
107
- it { expect(fact.name).to eq('visits_hourly_fact') }
101
+ context 'fact with partition' do
102
+ let(:fact) { fact_with_partition }
103
+
104
+ it { expect(partition_table.store.id).to eq(store.id) }
105
+ it { expect(partition_table.name).to eq('visits_fact_y2015m01') }
106
+ it { expect(partition_table.range.start_date).to eq(date.utc.to_date) }
107
+
108
+ describe '#stage_table' do
109
+ subject(:stage_table) { partition_table.stage_table }
110
+
111
+ it { expect(stage_table.store.id).to eq(store.id) }
112
+ it { expect(stage_table.name).to eq('visits_fact_y2015m01_stage') }
113
+ it { expect(stage_table.range.start_date).to eq(date.utc.to_date) }
114
+
115
+ context 'with optional suffix' do
116
+ subject(:stage_table) { partition_table.stage_table(suffix: 'tmp') }
108
117
 
109
- describe '#partition_table' do
110
- let(:date) { Chronic.parse('2015-01-01') }
118
+ it 'should append suffix to id' do
119
+ expect(stage_table.store.id).to eq(store.id)
120
+ expect(stage_table.name).to eq('visits_fact_y2015m01_stage_tmp')
121
+ expect(stage_table.range.start_date).to eq(date.utc.to_date)
122
+ end
123
+ end
124
+ end
125
+ end
111
126
 
112
- subject(:partition_table) { fact.partition_table(date) }
127
+ context 'fact with partition and hourly grain' do
128
+ let(:fact) { fact_with_partition_and_hourly_grain }
113
129
 
114
130
  it { expect(partition_table.store.id).to eq(store.id) }
115
131
  it { expect(partition_table.name).to eq('visits_hourly_fact_y2015m01') }
@@ -126,4 +142,28 @@ describe Masamune::Schema::Fact do
126
142
  end
127
143
  end
128
144
  end
145
+
146
+ describe '#partition_tables' do
147
+ let(:start_date) { Date.civil(2015, 01, 01) }
148
+ let(:stop_date) { Date.civil(2015, 03, 15) }
149
+
150
+ subject(:partition_tables) { fact.partition_tables(start_date, stop_date) }
151
+
152
+ context 'fact without partition' do
153
+ let(:fact) { fact_without_partition }
154
+
155
+ it { expect(partition_tables).to be_nil }
156
+ end
157
+
158
+ context 'fact with partition' do
159
+ let(:fact) { fact_with_partition }
160
+
161
+ it 'yields partition tables' do
162
+ expect { |b| fact.partition_tables(start_date, stop_date, &b) }.to yield_successive_args \
163
+ fact.partition_table(Date.civil(2015, 01, 01)),
164
+ fact.partition_table(Date.civil(2015, 02, 01)),
165
+ fact.partition_table(Date.civil(2015, 03, 01))
166
+ end
167
+ end
168
+ end
129
169
  end
@@ -32,11 +32,51 @@ describe Masamune::Tasks::DumpThor do
32
32
  end
33
33
 
34
34
  context 'with no arguments' do
35
- it 'exits with status code 0 and prints catalog' do
36
- expect { cli_invocation }.to raise_error { |e|
37
- expect(e).to be_a(SystemExit)
38
- expect(e.status).to eq(0)
39
- }
40
- end
35
+ it_behaves_like 'executes with success'
36
+ end
37
+
38
+ context 'with --type=psql' do
39
+ let(:options) { ['--type=psql'] }
40
+ it_behaves_like 'executes with success'
41
+ end
42
+
43
+ context 'with --type=hql' do
44
+ let(:options) { ['--type=hql'] }
45
+ it_behaves_like 'executes with success'
46
+ end
47
+
48
+ context 'with --type=unknown' do
49
+ let(:options) { ['--type=unknown'] }
50
+ it_behaves_like 'raises Thor::MalformattedArgumentError', %q{Expected '--type' to be one of psql, hql; got unknown}
51
+ end
52
+
53
+ context 'with --section=pre' do
54
+ let(:options) { ['--section=pre'] }
55
+ it_behaves_like 'executes with success'
56
+ end
57
+
58
+ context 'with --section=post' do
59
+ let(:options) { ['--section=post'] }
60
+ it_behaves_like 'executes with success'
61
+ end
62
+
63
+ context 'with --section=all' do
64
+ let(:options) { ['--section=all'] }
65
+ it_behaves_like 'executes with success'
66
+ end
67
+
68
+ context 'with --section=unknown' do
69
+ let(:options) { ['--section=unknown'] }
70
+ it_behaves_like 'raises Thor::MalformattedArgumentError', %q{Expected '--section' to be one of pre, post, all; got unknown}
71
+ end
72
+
73
+ context 'with --start=yesterday' do
74
+ let(:options) { ['--start=yesterday'] }
75
+ it_behaves_like 'executes with success'
76
+ end
77
+
78
+ context 'with --stop=today' do
79
+ let(:options) { ['--stop=today'] }
80
+ it_behaves_like 'executes with success'
41
81
  end
42
82
  end
@@ -99,43 +99,44 @@ describe Masamune::Thor do
99
99
 
100
100
  context 'with command and no input options' do
101
101
  let(:command) { 'command' }
102
- it { expect { cli_invocation }.to raise_error Thor::RequiredArgumentMissingError, /No value provided for required options '--start'/ }
102
+ it_behaves_like 'raises Thor::RequiredArgumentMissingError', /No value provided for required options '--start'/
103
103
  end
104
104
 
105
105
  context 'with command and invalid --start' do
106
106
  let(:command) { 'command' }
107
107
  let(:options) { ['--start', 'xxx'] }
108
- it { expect { cli_invocation }.to raise_error Thor::MalformattedArgumentError, /Expected date time value for '--start'; got/ }
108
+ it_behaves_like 'raises Thor::MalformattedArgumentError', /Expected date time value for '--start'; got/
109
109
  end
110
110
 
111
111
  context 'with command and invalid --stop' do
112
112
  let(:command) { 'command' }
113
113
  let(:options) { ['--start', '2013-01-01', '--stop', 'xxx'] }
114
- it { expect { cli_invocation }.to raise_error Thor::MalformattedArgumentError, /Expected date time value for '--stop'; got/ }
114
+ it_behaves_like 'raises Thor::MalformattedArgumentError', /Expected date time value for '--stop'; got/
115
115
  end
116
116
 
117
117
  context 'with command and invalid --sources' do
118
118
  let(:command) { 'command' }
119
119
  let(:options) { ['--sources', 'foo'] }
120
- it { expect { cli_invocation }.to raise_error Thor::MalformattedArgumentError, /Expected file value for '--sources'; got/ }
120
+ it_behaves_like 'raises Thor::MalformattedArgumentError', /Expected file value for '--sources'; got/
121
121
  end
122
122
 
123
123
  context 'with command and invalid --targets' do
124
124
  let(:command) { 'command' }
125
125
  let(:options) { ['--targets', 'foo'] }
126
- it { expect { cli_invocation }.to raise_error Thor::MalformattedArgumentError, /Expected file value for '--targets'; got/ }
126
+ it_behaves_like 'raises Thor::MalformattedArgumentError', /Expected file value for '--targets'; got/
127
127
  end
128
128
 
129
129
  context 'with command and both --sources and --targets' do
130
130
  let(:command) { 'command' }
131
131
  let(:options) { ['--sources', 'sources', '--targets', 'targets'] }
132
- it { expect { cli_invocation }.to raise_error Thor::MalformattedArgumentError, /Cannot specify both option '--sources' and option '--targets'/ }
132
+
133
+ it_behaves_like 'raises Thor::MalformattedArgumentError', /Cannot specify both option '--sources' and option '--targets'/
133
134
  end
134
135
 
135
136
  context 'with command and --start and bad --config file' do
136
137
  let(:command) { 'command' }
137
138
  let(:options) { ['--start', '2013-01-01', '--config', 'xxx'] }
138
- it { expect { cli_invocation }.to raise_error Thor::MalformattedArgumentError, /Could not load file provided for '--config'/ }
139
+ it_behaves_like 'raises Thor::MalformattedArgumentError', /Could not load file provided for '--config'/
139
140
  end
140
141
 
141
142
  context 'with command and --start and missing system --config file' do
@@ -144,7 +145,7 @@ describe Masamune::Thor do
144
145
  before do
145
146
  expect_any_instance_of(Masamune::Filesystem).to receive(:resolve_file)
146
147
  end
147
- it { expect { cli_invocation }.to raise_error Thor::RequiredArgumentMissingError, /Option --config or valid system configuration file required/ }
148
+ it_behaves_like 'raises Thor::RequiredArgumentMissingError', /Option --config or valid system configuration file required/
148
149
  end
149
150
 
150
151
  context 'with command and -- --extra --args' do
@@ -153,35 +154,19 @@ describe Masamune::Thor do
153
154
  before do
154
155
  expect_any_instance_of(thor_class).to receive(:extra=).with(['--extra', '--args'])
155
156
  end
156
- it do
157
- expect { cli_invocation }.to raise_error SystemExit
158
- end
157
+ it_behaves_like 'executes with success'
159
158
  end
160
159
 
161
160
  context 'with command and --start' do
162
161
  let(:command) { 'command' }
163
162
  let(:options) { ['--start', '2013-01-01'] }
164
- it 'exits with status code 0 without error message' do
165
- expect { cli_invocation }.to raise_error { |e|
166
- expect(e).to be_a(SystemExit)
167
- expect(e.status).to eq(0)
168
- }
169
- expect(stdout.string).to match(/\AUsing '.*' for --start/)
170
- expect(stderr.string).to eq('')
171
- end
163
+ it_behaves_like 'executes with success'
172
164
  end
173
165
 
174
166
  context 'with command and natural language --start' do
175
167
  let(:command) { 'command' }
176
168
  let(:options) { ['--start', 'yesterday'] }
177
- it 'exits with status code 0 without error message' do
178
- expect { cli_invocation }.to raise_error { |e|
179
- expect(e).to be_a(SystemExit)
180
- expect(e.status).to eq(0)
181
- }
182
- expect(stdout.string).to match(/\AUsing '.*' for --start/)
183
- expect(stderr.string).to eq('')
184
- end
169
+ it_behaves_like 'executes with success'
185
170
  end
186
171
 
187
172
  context 'with command that raises exception before initialization' do