masamune 0.13.0 → 0.13.1

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.
@@ -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