masamune 0.12.3 → 0.13.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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/bin/masamune-dump +4 -0
  3. data/lib/masamune/schema/column.rb +2 -20
  4. data/lib/masamune/schema/dimension.rb +12 -11
  5. data/lib/masamune/schema/fact.rb +10 -1
  6. data/lib/masamune/schema/map.rb +3 -3
  7. data/lib/masamune/schema/row.rb +1 -1
  8. data/lib/masamune/schema/table.rb +55 -23
  9. data/lib/masamune/schema/table_reference.rb +5 -0
  10. data/lib/masamune/tasks/dump_thor.rb +58 -0
  11. data/lib/masamune/tasks/shell_thor.rb +0 -19
  12. data/lib/masamune/template.rb +1 -2
  13. data/lib/masamune/transform/define_foreign_key.psql.erb +39 -0
  14. data/lib/masamune/transform/define_index.psql.erb +7 -3
  15. data/lib/masamune/transform/define_schema.rb +3 -3
  16. data/lib/masamune/transform/define_table.psql.erb +24 -3
  17. data/lib/masamune/transform/define_table.rb +16 -2
  18. data/lib/masamune/transform/define_unique.psql.erb +1 -1
  19. data/lib/masamune/transform/denormalize_table.rb +5 -1
  20. data/lib/masamune/transform/replace_table.psql.erb +9 -13
  21. data/lib/masamune/transform/stage_fact.rb +16 -10
  22. data/lib/masamune/version.rb +1 -1
  23. data/spec/masamune/schema/map_spec.rb +1 -1
  24. data/spec/masamune/tasks/dump_thor_spec.rb +42 -0
  25. data/spec/masamune/tasks/shell_thor_spec.rb +0 -11
  26. data/spec/masamune/template_spec.rb +5 -0
  27. data/spec/masamune/transform/define_table.dimension_spec.rb +81 -52
  28. data/spec/masamune/transform/define_table.fact_spec.rb +27 -63
  29. data/spec/masamune/transform/define_table.table_spec.rb +397 -32
  30. data/spec/masamune/transform/denormalize_table_spec.rb +20 -0
  31. data/spec/masamune/transform/rollup_fact_spec.rb +54 -54
  32. data/spec/masamune/transform/stage_fact_spec.rb +57 -34
  33. metadata +9 -3
@@ -27,6 +27,7 @@ module Masamune
27
27
  class Template
28
28
  def initialize(paths = [])
29
29
  @paths = Array.wrap(paths)
30
+ @paths << File.join(File.dirname(__FILE__), 'transform')
30
31
  end
31
32
 
32
33
  def render(template, parameters = {})
@@ -48,7 +49,6 @@ module Masamune
48
49
 
49
50
  class << self
50
51
  def render_to_file(template, parameters = {})
51
- raise IOError, "File not found: #{template}" unless File.exists?(template)
52
52
  Tempfile.new('masamune').tap do |file|
53
53
  file.write(render_to_string(template, parameters))
54
54
  file.close
@@ -56,7 +56,6 @@ module Masamune
56
56
  end
57
57
 
58
58
  def render_to_string(template, parameters = {})
59
- raise IOError, "File not found: #{template}" unless File.exists?(template)
60
59
  instance = Template.new(File.dirname(template))
61
60
  combine instance.render(template, parameters)
62
61
  end
@@ -0,0 +1,39 @@
1
+ -- The MIT License (MIT)
2
+ --
3
+ -- Copyright (c) 2014-2015, VMware, Inc. All Rights Reserved.
4
+ --
5
+ -- Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ -- of this software and associated documentation files (the "Software"), to deal
7
+ -- in the Software without restriction, including without limitation the rights
8
+ -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ -- copies of the Software, and to permit persons to whom the Software is
10
+ -- furnished to do so, subject to the following conditions:
11
+ --
12
+ -- The above copyright notice and this permission notice shall be included in
13
+ -- all copies or substantial portions of the Software.
14
+ --
15
+ -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ -- THE SOFTWARE.
22
+
23
+ <%
24
+ skip_check_exist ||= false
25
+ skip_check_valid ||= false
26
+ %>
27
+
28
+ <%- target.foreign_key_constraints.each do |id, column_names, reference_name, reference_column_names| -%>
29
+ <%- foreign_key_name = "#{target.name}_#{id}_fkey" -%>
30
+ <%- unless target.temporary? || skip_check_exist -%>
31
+ DO $$ BEGIN
32
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint c WHERE c.conname = '<%= foreign_key_name %>') THEN
33
+ <%- end -%>
34
+ ALTER TABLE <%= target.name %> ADD CONSTRAINT <%= foreign_key_name %> FOREIGN KEY (<%= column_names.join(', ') %>) REFERENCES <%= reference_name %>(<%= reference_column_names.join(', ') %>)<%= ' NOT VALID DEFERRABLE INITIALLY DEFERRED' if skip_check_valid %>;
35
+ <%- unless target.temporary? || skip_check_exist -%>
36
+ END IF; END $$;
37
+
38
+ <%- end -%>
39
+ <%- end -%>
@@ -20,14 +20,18 @@
20
20
  -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  -- THE SOFTWARE.
22
22
 
23
+ <%
24
+ skip_check_exist ||= false
25
+ %>
26
+
23
27
  <%- target.index_columns.each do |column_names, unique, id| -%>
24
28
  <%- index_name = "#{target.name}_#{id}_index" -%>
25
- <%- unless target.temporary? -%>
29
+ <%- unless target.temporary? || skip_check_exist -%>
26
30
  DO $$ BEGIN
27
31
  IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = '<%= index_name %>') THEN
28
32
  <%- end -%>
29
- CREATE <%= unique ? 'UNIQUE INDEX' : 'INDEX' %> <%= index_name %> ON <%= target.name %> (<%= column_names.join(', ') %>);
30
- <%- unless target.temporary? -%>
33
+ CREATE <%= unique ? 'UNIQUE INDEX' : 'INDEX' %> <%= index_name %> ON <%= target.name %> (<%= column_names.to_a.join(', ') %>);
34
+ <%- unless target.temporary? || skip_check_exist -%>
31
35
  END IF; END $$;
32
36
 
33
37
  <%- end -%>
@@ -28,18 +28,18 @@ module Masamune::Transform
28
28
 
29
29
  extend ActiveSupport::Concern
30
30
 
31
- def define_schema(catalog, store_id)
31
+ def define_schema(catalog, store_id, options = {})
32
32
  context = catalog[store_id]
33
33
  operators = []
34
34
 
35
35
  operators += context.extra(:pre)
36
36
 
37
37
  context.dimensions.each do |_, dimension|
38
- operators << define_table(dimension)
38
+ operators << define_table(dimension, [], options)
39
39
  end
40
40
 
41
41
  context.facts.each do |_, fact|
42
- operators << define_table(fact)
42
+ operators << define_table(fact, [], options)
43
43
  end
44
44
 
45
45
  operators += context.extra(:post)
@@ -20,10 +20,15 @@
20
20
  -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  -- THE SOFTWARE.
22
22
 
23
- <% files ||= [] %>
23
+ <%
24
+ files ||= []
25
+ with_index = locals.fetch(:with_index, true)
26
+ with_foreign_key = locals.fetch(:with_foreign_key, true)
27
+ with_unique_constraint = locals.fetch(:with_unique_constraint, true)
28
+ %>
24
29
 
25
30
  <%- target.children.each do |child| -%>
26
- <%= render 'define_table.psql.erb', target: child %>
31
+ <%= render 'define_table.psql.erb', target: child, **locals.except(:target, :files) %>
27
32
  <%- end -%>
28
33
 
29
34
  <%- target.enum_columns.each do |_, column| -%>
@@ -52,6 +57,17 @@ CREATE TABLE IF NOT EXISTS <%= target.name %>
52
57
  <%- end -%>
53
58
  );
54
59
 
60
+ <%- unless target.temporary? || target.primary_keys.empty? -%>
61
+ DO $$ BEGIN
62
+ IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = '<%= target.name %>_pkey') THEN
63
+ ALTER TABLE <%= target.name %> ADD PRIMARY KEY (<%= target.primary_keys.map(&:name).join(', ') %>);
64
+ END IF; END $$;
65
+ <%- end -%>
66
+
67
+ <%- if with_foreign_key && !target.delay_constraints? -%>
68
+ <%= render 'define_foreign_key.psql.erb', target: target %>
69
+ <%- end -%>
70
+
55
71
  <%- target.sequence_columns.each do |_, column| -%>
56
72
  DO $$ BEGIN
57
73
  IF NOT EXISTS (SELECT 1 WHERE sequence_owner('<%= column.sequence_id %>') = '<%= column.qualified_name %>') THEN
@@ -69,8 +85,13 @@ END IF; END $$;
69
85
  COPY <%= target.name %> FROM '<%= file %>' WITH (<%= copy_options.join(", ") %>);
70
86
  <%- end -%>
71
87
 
88
+ <%- if with_unique_constraint && !target.delay_constraints? -%>
72
89
  <%= render 'define_unique.psql.erb', target: target %>
90
+ <%- end -%>
91
+
92
+ <%- if with_index && !target.delay_index? -%>
73
93
  <%= render 'define_index.psql.erb', target: target %>
94
+ <%- end -%>
74
95
 
75
96
  <% target.insert_rows.each do |row| %>
76
97
  INSERT INTO <%= target.name %> (<%= row.insert_columns.join(', ') %>)
@@ -86,7 +107,7 @@ ANALYZE <%= target.name %>;
86
107
  <%- row.natural_keys.each do |column| -%>
87
108
  CREATE OR REPLACE FUNCTION <%= row.name(column) %>
88
109
  RETURNS <%= column.sql_type %> IMMUTABLE AS $$
89
- SELECT <%= row.sql_value(column) %>;
110
+ SELECT CAST(<%= row.sql_value(column) %> AS <%= column.sql_type %>);
90
111
  $$ LANGUAGE SQL;
91
112
 
92
113
  <%- end -%>
@@ -24,13 +24,27 @@ module Masamune::Transform
24
24
  module DefineTable
25
25
  extend ActiveSupport::Concern
26
26
 
27
- def define_table(target, files = [])
27
+ def define_table(target, files = [], options = {})
28
28
  return if target.implicit
29
- Operator.new(__method__, target: target, files: Masamune::Schema::Map.convert_files(files), presenters: { hive: Hive }).tap do |operator|
29
+ Operator.new(__method__, target: target, files: Masamune::Schema::Map.convert_files(files), **options.slice(:with_index, :with_foreign_key, :with_unique_constraint), presenters: { postgres: Postgres, hive: Hive }).tap do |operator|
30
30
  logger.debug("#{target.id}\n" + operator.to_s) if target.debug
31
31
  end
32
32
  end
33
33
 
34
+ class Postgres < SimpleDelegator
35
+ def children
36
+ super.map { |child| self.class.new(child) }
37
+ end
38
+
39
+ def delay_index?
40
+ type == :fact
41
+ end
42
+
43
+ def delay_constraints?
44
+ type == :fact
45
+ end
46
+ end
47
+
34
48
  class Hive < SimpleDelegator
35
49
  def partition_by
36
50
  return unless partitions.any?
@@ -24,7 +24,7 @@
24
24
  <%- constraint_name = "#{target.name}_#{id}_key" -%>
25
25
  DO $$ BEGIN
26
26
  IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = '<%= constraint_name %>') THEN
27
- ALTER TABLE <%= target.name %> ADD CONSTRAINT <%= constraint_name %> UNIQUE(<%= column_names.join(', ') %>);
27
+ ALTER TABLE <%= target.name %> ADD CONSTRAINT <%= constraint_name %> UNIQUE(<%= column_names.to_a.join(', ') %>);
28
28
  END IF; END $$;
29
29
 
30
30
  <%- end -%>
@@ -54,6 +54,10 @@ module Masamune::Transform
54
54
  end
55
55
  method_with_last_element :select_columns
56
56
 
57
+ def join_alias(reference)
58
+ reference.label ? "#{reference.name} AS #{[reference.label, reference.name].compact.join('_')}" : reference.name
59
+ end
60
+
57
61
  def join_conditions(column_names)
58
62
  {}.tap do |conditions|
59
63
  column_names.each do |column_name|
@@ -63,7 +67,7 @@ module Masamune::Transform
63
67
  next unless adjacent_reference
64
68
  adjacent_column = columns[adjacent_reference.foreign_key_name]
65
69
  next unless adjacent_column
66
- conditions[column.reference.name] = "#{column.reference.surrogate_key.qualified_name} = #{adjacent_column.qualified_name}"
70
+ conditions[join_alias(column.reference)] = "#{column.reference.surrogate_key.qualified_name(column.reference.label)} = #{adjacent_column.qualified_name}"
67
71
  end
68
72
  end
69
73
  end
@@ -30,13 +30,12 @@ BEGIN;
30
30
  SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
31
31
 
32
32
  ALTER TABLE <%= target.name %> DROP CONSTRAINT IF EXISTS <%= target.name %>_time_key_check;
33
- <%- target.foreign_key_columns.each do |column| -%>
34
- <%- if column.reference_constraint -%>
35
- ALTER TABLE <%= target.name %> DROP CONSTRAINT IF EXISTS <%= target.name %>_<%= column.name %>_fkey CASCADE;
36
- <%- end -%>
33
+ <%- target.foreign_key_constraints.each do |id, _, _, _| -%>
34
+ <%- foreign_key_name = "#{target.name}_#{id}_fkey" -%>
35
+ ALTER TABLE <%= target.name %> DROP CONSTRAINT IF EXISTS <%= foreign_key_name %> CASCADE;
37
36
  <%- end -%>
38
37
 
39
- <%- target.index_columns.each do |column_names, unique, id| -%>
38
+ <%- target.index_columns.each do |_, _, id| -%>
40
39
  <%- index_name = "#{target.name}_#{id}_index" -%>
41
40
  DROP INDEX IF EXISTS <%= index_name %>;
42
41
  <%- end -%>
@@ -44,18 +43,15 @@ DROP INDEX IF EXISTS <%= index_name %>;
44
43
  ALTER TABLE <%= target.name %> RENAME TO <%= target_tmp.name %>;
45
44
  ALTER TABLE <%= source.name %> RENAME TO <%= target.name %>;
46
45
 
46
+ <%- if target.parent -%>
47
47
  ALTER TABLE <%= target.name %> INHERIT <%= target.parent.name %>;
48
- ALTER TABLE <%= target.name %> ADD CONSTRAINT <%= target.name %>_time_key_check <%= target.constraints %>;
49
- <%- target.foreign_key_columns.each do |column| -%>
50
- <%- if column.reference_constraint -%>
51
- ALTER TABLE <%= target.name %> ADD CONSTRAINT <%= target.name %>_<%= column.name %>_fkey FOREIGN KEY (<%= column.name %>) <%= column.reference_constraint %> NOT VALID DEFERRABLE INITIALLY DEFERRED;
52
48
  <%- end -%>
49
+ <%- if target.constraints -%>
50
+ ALTER TABLE <%= target.name %> ADD CONSTRAINT <%= target.name %>_time_key_check <%= target.constraints %>;
53
51
  <%- end -%>
52
+ <%= render 'define_foreign_key.psql.erb', target: target, skip_check_exist: true, skip_check_valid: true %>
54
53
 
55
- <%- target.index_columns.each do |column_names, unique, id| -%>
56
- <%- index_name = "#{target.name}_#{id}_index" -%>
57
- CREATE <%= unique ? 'UNIQUE INDEX' : 'INDEX' %> <%= index_name %> ON <%= target.name %> (<%= column_names.join(', ') %>);
58
- <%- end -%>
54
+ <%= render 'define_index.psql.erb', target: target, skip_check_exist: true %>
59
55
 
60
56
  ANALYZE <%= target.name %>;
61
57
 
@@ -48,7 +48,7 @@ module Masamune::Transform
48
48
  shared_columns(source).values.map do |columns|
49
49
  column = columns.first
50
50
  if !column.degenerate? && reference = column.reference
51
- reference.surrogate_key.qualified_name
51
+ reference.surrogate_key.qualified_name(column.reference.label)
52
52
  else
53
53
  column.qualified_name
54
54
  end
@@ -56,6 +56,10 @@ module Masamune::Transform
56
56
  end
57
57
  method_with_last_element :insert_values
58
58
 
59
+ def join_alias(reference)
60
+ reference.label ? "#{reference.name} AS #{[reference.label, reference.name].compact.join('_')}" : reference.name
61
+ end
62
+
59
63
  def join_conditions(source)
60
64
  join_columns = shared_columns(source).values.flatten
61
65
  join_columns = join_columns.select { |column| column.reference }
@@ -64,15 +68,16 @@ module Masamune::Transform
64
68
  dependencies = Masamune::TopologicalHash.new
65
69
  conditions = Hash.new { |h,k| h[k] = [] }
66
70
  join_columns.each do |reference, columns|
71
+ reference_name = join_alias(reference)
67
72
  columns.each do |column|
68
73
  next if column.degenerate?
69
- dependencies[reference.name] = []
74
+ dependencies[reference_name] ||= []
70
75
  cross_references = cross_references(column)
71
76
  coalesce_values = []
72
77
 
73
78
  if cross_references.any?
74
- dependencies[reference.name] += cross_references.map { |reference, _| reference.name }
75
- coalesce_values << cross_references.map { |_, column| column.qualified_name }
79
+ dependencies[reference_name] += cross_references.map { |reference, _| join_alias(reference) }
80
+ coalesce_values << cross_references.map { |reference, column| column.qualified_name(reference.label) }
76
81
  end
77
82
 
78
83
  if column.reference && !column.reference.default.nil?
@@ -81,14 +86,14 @@ module Masamune::Transform
81
86
  coalesce_values << column.adjacent.sql_value(column.adjacent.default)
82
87
  end
83
88
 
84
- conditions[reference.name] << (coalesce_values.any? ?
89
+ conditions[reference_name] << (coalesce_values.any? ?
85
90
  "#{column.foreign_key_name} = COALESCE(#{column.qualified_name}, #{coalesce_values.join(', ')})" :
86
91
  "#{column.foreign_key_name} = #{column.qualified_name}")
87
92
  end
88
93
  if reference.type == :two || reference.type == :four
89
- join_key_a = "TO_TIMESTAMP(#{source.time_key.qualified_name}) BETWEEN #{reference.start_key.qualified_name} AND COALESCE(#{reference.end_key.qualified_name}, 'INFINITY')"
90
- join_key_b = "TO_TIMESTAMP(#{source.time_key.qualified_name}) < #{reference.start_key.qualified_name} AND #{reference.version_key.qualified_name} = 1"
91
- conditions[reference.name] << "((#{join_key_a}) OR (#{join_key_b}))"
94
+ join_key_a = "TO_TIMESTAMP(#{source.time_key.qualified_name}) BETWEEN #{reference.start_key.qualified_name(reference.label)} AND COALESCE(#{reference.end_key.qualified_name(reference.label)}, 'INFINITY')"
95
+ join_key_b = "TO_TIMESTAMP(#{source.time_key.qualified_name}) < #{reference.start_key.qualified_name(reference.label)} AND #{reference.version_key.qualified_name(reference.label)} = 1"
96
+ conditions[reference_name] << "((#{join_key_a}) OR (#{join_key_b}))"
92
97
  end
93
98
  end
94
99
  conditions.slice(*dependencies.tsort)
@@ -99,8 +104,9 @@ module Masamune::Transform
99
104
  def cross_references(column)
100
105
  return {} unless column.natural_key || column.adjacent.try(:natural_key)
101
106
  {}.tap do |result|
102
- references.each do |_, reference|
103
- if reference.id != column.reference.id && reference.columns[column.id]
107
+ column.reference.through.each do |reference_id|
108
+ reference = references[reference_id]
109
+ if reference.columns[column.id]
104
110
  result[reference] = reference.columns[column.id]
105
111
  end
106
112
  end
@@ -21,5 +21,5 @@
21
21
  # THE SOFTWARE.
22
22
 
23
23
  module Masamune
24
- VERSION = '0.12.3'
24
+ VERSION = '0.13.0'
25
25
  end
@@ -223,7 +223,7 @@ describe Masamune::Schema::Map do
223
223
  end
224
224
 
225
225
  before do
226
- expect(environment.logger).to receive(:warn).with(/failed to process '{.*}' for #{target.name}/).ordered
226
+ expect(environment.logger).to receive(:warn).with(/failed to process row for #{target.name}/).ordered
227
227
  expect(environment.logger).to receive(:warn).with(/failed to parse '{.*}' for #{source.name}/).ordered
228
228
  end
229
229
 
@@ -0,0 +1,42 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2014-2015, VMware, Inc. All Rights Reserved.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'spec_helper'
24
+ require 'thor'
25
+
26
+ require 'masamune/tasks/dump_thor'
27
+
28
+ describe Masamune::Tasks::DumpThor do
29
+ context 'with help command ' do
30
+ let(:command) { 'help' }
31
+ it_behaves_like 'command usage'
32
+ end
33
+
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
41
+ end
42
+ end
@@ -37,15 +37,4 @@ describe Masamune::Tasks::ShellThor do
37
37
  cli_invocation
38
38
  end
39
39
  end
40
-
41
- context 'with --dump' do
42
- let(:options) { ['--dump'] }
43
-
44
- it 'exits with status code 0 and prints catalog' do
45
- expect { cli_invocation }.to raise_error { |e|
46
- expect(e).to be_a(SystemExit)
47
- expect(e.status).to eq(0)
48
- }
49
- end
50
- end
51
40
  end
@@ -73,5 +73,10 @@ describe Masamune::Template do
73
73
  let(:template) { File.join(File.dirname(__FILE__), '..', 'fixtures', 'relative.sql.erb') }
74
74
  it { is_expected.to eq("SELECT * FROM relative;\n") }
75
75
  end
76
+
77
+ context 'with packaged template' do
78
+ let(:template) { 'define_schema.hql.erb' }
79
+ it { is_expected.to_not be_nil }
80
+ end
76
81
  end
77
82
  end
@@ -157,11 +157,16 @@ describe Masamune::Transform::DefineTable do
157
157
  is_expected.to eq <<-EOS.strip_heredoc
158
158
  CREATE TABLE IF NOT EXISTS user_dimension
159
159
  (
160
- id SERIAL PRIMARY KEY,
160
+ id SERIAL,
161
161
  tenant_id INTEGER NOT NULL,
162
162
  user_id INTEGER NOT NULL,
163
163
  last_modified_at TIMESTAMP NOT NULL DEFAULT NOW()
164
164
  );
165
+
166
+ DO $$ BEGIN
167
+ IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_pkey') THEN
168
+ ALTER TABLE user_dimension ADD PRIMARY KEY (id);
169
+ END IF; END $$;
165
170
  EOS
166
171
  end
167
172
  end
@@ -170,8 +175,8 @@ describe Masamune::Transform::DefineTable do
170
175
  before do
171
176
  catalog.schema :postgres do
172
177
  dimension 'user', type: :two do
173
- column 'tenant_id', index: true, natural_key: true
174
- column 'user_id', index: true, natural_key: true
178
+ column 'tenant_id', natural_key: true
179
+ column 'user_id', natural_key: true
175
180
  end
176
181
  end
177
182
  end
@@ -182,7 +187,7 @@ describe Masamune::Transform::DefineTable do
182
187
  is_expected.to eq <<-EOS.strip_heredoc
183
188
  CREATE TABLE IF NOT EXISTS user_dimension
184
189
  (
185
- id SERIAL PRIMARY KEY,
190
+ id SERIAL,
186
191
  tenant_id INTEGER NOT NULL,
187
192
  user_id INTEGER NOT NULL,
188
193
  start_at TIMESTAMP NOT NULL DEFAULT TO_TIMESTAMP(0),
@@ -192,18 +197,18 @@ describe Masamune::Transform::DefineTable do
192
197
  );
193
198
 
194
199
  DO $$ BEGIN
195
- IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_e6c3d91_key') THEN
196
- ALTER TABLE user_dimension ADD CONSTRAINT user_dimension_e6c3d91_key UNIQUE(tenant_id, user_id, start_at);
200
+ IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_pkey') THEN
201
+ ALTER TABLE user_dimension ADD PRIMARY KEY (id);
197
202
  END IF; END $$;
198
203
 
199
204
  DO $$ BEGIN
200
- IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_3854361_index') THEN
201
- CREATE INDEX user_dimension_3854361_index ON user_dimension (tenant_id);
205
+ IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_e6c3d91_key') THEN
206
+ ALTER TABLE user_dimension ADD CONSTRAINT user_dimension_e6c3d91_key UNIQUE(tenant_id, user_id, start_at);
202
207
  END IF; END $$;
203
208
 
204
209
  DO $$ BEGIN
205
- IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_e8701ad_index') THEN
206
- CREATE INDEX user_dimension_e8701ad_index ON user_dimension (user_id);
210
+ IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_2c8e908_index') THEN
211
+ CREATE INDEX user_dimension_2c8e908_index ON user_dimension (end_at);
207
212
  END IF; END $$;
208
213
 
209
214
  DO $$ BEGIN
@@ -212,13 +217,18 @@ describe Masamune::Transform::DefineTable do
212
217
  END IF; END $$;
213
218
 
214
219
  DO $$ BEGIN
215
- IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_2c8e908_index') THEN
216
- CREATE INDEX user_dimension_2c8e908_index ON user_dimension (end_at);
220
+ IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_3854361_index') THEN
221
+ CREATE INDEX user_dimension_3854361_index ON user_dimension (tenant_id);
222
+ END IF; END $$;
223
+
224
+ DO $$ BEGIN
225
+ IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_e8701ad_index') THEN
226
+ CREATE INDEX user_dimension_e8701ad_index ON user_dimension (user_id);
217
227
  END IF; END $$;
218
228
 
219
229
  DO $$ BEGIN
220
- IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_2af72f1_index') THEN
221
- CREATE INDEX user_dimension_2af72f1_index ON user_dimension (version);
230
+ IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_e6c3d91_index') THEN
231
+ CREATE UNIQUE INDEX user_dimension_e6c3d91_index ON user_dimension (tenant_id, user_id, start_at);
222
232
  END IF; END $$;
223
233
  EOS
224
234
  end
@@ -242,8 +252,8 @@ describe Masamune::Transform::DefineTable do
242
252
  dimension 'user', type: :four do
243
253
  references :cluster
244
254
  references :user_account_state
245
- column 'tenant_id', index: true, natural_key: true
246
- column 'user_id', index: true, natural_key: true
255
+ column 'tenant_id', natural_key: true
256
+ column 'user_id', natural_key: true
247
257
  column 'preferences', type: :key_value, null: true
248
258
  end
249
259
  end
@@ -255,9 +265,9 @@ describe Masamune::Transform::DefineTable do
255
265
  is_expected.to eq <<-EOS.strip_heredoc
256
266
  CREATE TABLE IF NOT EXISTS user_dimension_ledger
257
267
  (
258
- id SERIAL PRIMARY KEY,
259
- cluster_type_id INTEGER NOT NULL REFERENCES cluster_type(id) DEFAULT default_cluster_type_id(),
260
- user_account_state_type_id INTEGER REFERENCES user_account_state_type(id),
268
+ id SERIAL,
269
+ cluster_type_id INTEGER NOT NULL DEFAULT default_cluster_type_id(),
270
+ user_account_state_type_id INTEGER,
261
271
  tenant_id INTEGER NOT NULL,
262
272
  user_id INTEGER NOT NULL,
263
273
  preferences HSTORE,
@@ -269,18 +279,23 @@ describe Masamune::Transform::DefineTable do
269
279
  );
270
280
 
271
281
  DO $$ BEGIN
272
- IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_ledger_370d6dd_key') THEN
273
- ALTER TABLE user_dimension_ledger ADD CONSTRAINT user_dimension_ledger_370d6dd_key UNIQUE(tenant_id, user_id, source_kind, source_uuid, start_at);
282
+ IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_ledger_pkey') THEN
283
+ ALTER TABLE user_dimension_ledger ADD PRIMARY KEY (cluster_type_id, id);
274
284
  END IF; END $$;
275
285
 
276
286
  DO $$ BEGIN
277
- IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_ledger_d6b9b38_index') THEN
278
- CREATE INDEX user_dimension_ledger_d6b9b38_index ON user_dimension_ledger (cluster_type_id);
287
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint c WHERE c.conname = 'user_dimension_ledger_d6b9b38_fkey') THEN
288
+ ALTER TABLE user_dimension_ledger ADD CONSTRAINT user_dimension_ledger_d6b9b38_fkey FOREIGN KEY (cluster_type_id) REFERENCES cluster_type(id);
279
289
  END IF; END $$;
280
290
 
281
291
  DO $$ BEGIN
282
- IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_ledger_7988187_index') THEN
283
- CREATE INDEX user_dimension_ledger_7988187_index ON user_dimension_ledger (user_account_state_type_id);
292
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint c WHERE c.conname = 'user_dimension_ledger_7988187_fkey') THEN
293
+ ALTER TABLE user_dimension_ledger ADD CONSTRAINT user_dimension_ledger_7988187_fkey FOREIGN KEY (user_account_state_type_id) REFERENCES user_account_state_type(id);
294
+ END IF; END $$;
295
+
296
+ DO $$ BEGIN
297
+ IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_ledger_ff54dba_key') THEN
298
+ ALTER TABLE user_dimension_ledger ADD CONSTRAINT user_dimension_ledger_ff54dba_key UNIQUE(cluster_type_id, tenant_id, user_id, source_kind, source_uuid, start_at);
284
299
  END IF; END $$;
285
300
 
286
301
  DO $$ BEGIN
@@ -294,20 +309,20 @@ describe Masamune::Transform::DefineTable do
294
309
  END IF; END $$;
295
310
 
296
311
  DO $$ BEGIN
297
- IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_ledger_23563d3_index') THEN
298
- CREATE INDEX user_dimension_ledger_23563d3_index ON user_dimension_ledger (start_at);
312
+ IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_ledger_370d6dd_index') THEN
313
+ CREATE INDEX user_dimension_ledger_370d6dd_index ON user_dimension_ledger (tenant_id, user_id, source_kind, source_uuid, start_at);
299
314
  END IF; END $$;
300
315
 
301
316
  CREATE TABLE IF NOT EXISTS user_dimension
302
317
  (
303
- id SERIAL PRIMARY KEY,
304
- cluster_type_id INTEGER NOT NULL REFERENCES cluster_type(id) DEFAULT default_cluster_type_id(),
305
- user_account_state_type_id INTEGER NOT NULL REFERENCES user_account_state_type(id) DEFAULT default_user_account_state_type_id(),
318
+ id SERIAL,
319
+ cluster_type_id INTEGER NOT NULL DEFAULT default_cluster_type_id(),
320
+ user_account_state_type_id INTEGER NOT NULL DEFAULT default_user_account_state_type_id(),
306
321
  tenant_id INTEGER NOT NULL,
307
322
  user_id INTEGER NOT NULL,
308
323
  preferences HSTORE,
309
- parent_id INTEGER REFERENCES user_dimension_ledger(id),
310
- record_id INTEGER REFERENCES user_dimension_ledger(id),
324
+ parent_id INTEGER,
325
+ record_id INTEGER,
311
326
  start_at TIMESTAMP NOT NULL DEFAULT TO_TIMESTAMP(0),
312
327
  end_at TIMESTAMP,
313
328
  version INTEGER DEFAULT 1,
@@ -315,33 +330,33 @@ describe Masamune::Transform::DefineTable do
315
330
  );
316
331
 
317
332
  DO $$ BEGIN
318
- IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_e6c3d91_key') THEN
319
- ALTER TABLE user_dimension ADD CONSTRAINT user_dimension_e6c3d91_key UNIQUE(tenant_id, user_id, start_at);
333
+ IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_pkey') THEN
334
+ ALTER TABLE user_dimension ADD PRIMARY KEY (cluster_type_id, id);
320
335
  END IF; END $$;
321
336
 
322
337
  DO $$ BEGIN
323
- IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_d6b9b38_index') THEN
324
- CREATE INDEX user_dimension_d6b9b38_index ON user_dimension (cluster_type_id);
338
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint c WHERE c.conname = 'user_dimension_d6b9b38_fkey') THEN
339
+ ALTER TABLE user_dimension ADD CONSTRAINT user_dimension_d6b9b38_fkey FOREIGN KEY (cluster_type_id) REFERENCES cluster_type(id);
325
340
  END IF; END $$;
326
341
 
327
342
  DO $$ BEGIN
328
- IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_7988187_index') THEN
329
- CREATE INDEX user_dimension_7988187_index ON user_dimension (user_account_state_type_id);
343
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint c WHERE c.conname = 'user_dimension_7988187_fkey') THEN
344
+ ALTER TABLE user_dimension ADD CONSTRAINT user_dimension_7988187_fkey FOREIGN KEY (user_account_state_type_id) REFERENCES user_account_state_type(id);
330
345
  END IF; END $$;
331
346
 
332
347
  DO $$ BEGIN
333
- IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_3854361_index') THEN
334
- CREATE INDEX user_dimension_3854361_index ON user_dimension (tenant_id);
348
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint c WHERE c.conname = 'user_dimension_e0538bc_fkey') THEN
349
+ ALTER TABLE user_dimension ADD CONSTRAINT user_dimension_e0538bc_fkey FOREIGN KEY (cluster_type_id, parent_id) REFERENCES user_dimension_ledger(cluster_type_id, id);
335
350
  END IF; END $$;
336
351
 
337
352
  DO $$ BEGIN
338
- IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_e8701ad_index') THEN
339
- CREATE INDEX user_dimension_e8701ad_index ON user_dimension (user_id);
353
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint c WHERE c.conname = 'user_dimension_824002d_fkey') THEN
354
+ ALTER TABLE user_dimension ADD CONSTRAINT user_dimension_824002d_fkey FOREIGN KEY (cluster_type_id, record_id) REFERENCES user_dimension_ledger(cluster_type_id, id);
340
355
  END IF; END $$;
341
356
 
342
357
  DO $$ BEGIN
343
- IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_23563d3_index') THEN
344
- CREATE INDEX user_dimension_23563d3_index ON user_dimension (start_at);
358
+ IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_3fcebfa_key') THEN
359
+ ALTER TABLE user_dimension ADD CONSTRAINT user_dimension_3fcebfa_key UNIQUE(cluster_type_id, tenant_id, user_id, start_at);
345
360
  END IF; END $$;
346
361
 
347
362
  DO $$ BEGIN
@@ -350,8 +365,23 @@ describe Masamune::Transform::DefineTable do
350
365
  END IF; END $$;
351
366
 
352
367
  DO $$ BEGIN
353
- IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_2af72f1_index') THEN
354
- CREATE INDEX user_dimension_2af72f1_index ON user_dimension (version);
368
+ IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_23563d3_index') THEN
369
+ CREATE INDEX user_dimension_23563d3_index ON user_dimension (start_at);
370
+ END IF; END $$;
371
+
372
+ DO $$ BEGIN
373
+ IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_3854361_index') THEN
374
+ CREATE INDEX user_dimension_3854361_index ON user_dimension (tenant_id);
375
+ END IF; END $$;
376
+
377
+ DO $$ BEGIN
378
+ IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_e8701ad_index') THEN
379
+ CREATE INDEX user_dimension_e8701ad_index ON user_dimension (user_id);
380
+ END IF; END $$;
381
+
382
+ DO $$ BEGIN
383
+ IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_e6c3d91_index') THEN
384
+ CREATE INDEX user_dimension_e6c3d91_index ON user_dimension (tenant_id, user_id, start_at);
355
385
  END IF; END $$;
356
386
  EOS
357
387
  end
@@ -368,8 +398,8 @@ describe Masamune::Transform::DefineTable do
368
398
 
369
399
  dimension 'user', type: :four do
370
400
  references :user_account_state
371
- column 'tenant_id', index: true, natural_key: true
372
- column 'user_id', index: true, natural_key: true
401
+ column 'tenant_id', natural_key: true
402
+ column 'user_id', natural_key: true
373
403
  column 'preferences', type: :key_value, null: true
374
404
  end
375
405
  end
@@ -393,12 +423,11 @@ describe Masamune::Transform::DefineTable do
393
423
  last_modified_at TIMESTAMP DEFAULT NOW()
394
424
  );
395
425
 
396
- CREATE INDEX user_consolidated_forward_dimension_stage_7988187_index ON user_consolidated_forward_dimension_stage (user_account_state_type_id);
426
+ CREATE INDEX user_consolidated_forward_dimension_stage_2c8e908_index ON user_consolidated_forward_dimension_stage (end_at);
427
+ CREATE INDEX user_consolidated_forward_dimension_stage_23563d3_index ON user_consolidated_forward_dimension_stage (start_at);
397
428
  CREATE INDEX user_consolidated_forward_dimension_stage_3854361_index ON user_consolidated_forward_dimension_stage (tenant_id);
398
429
  CREATE INDEX user_consolidated_forward_dimension_stage_e8701ad_index ON user_consolidated_forward_dimension_stage (user_id);
399
- CREATE INDEX user_consolidated_forward_dimension_stage_23563d3_index ON user_consolidated_forward_dimension_stage (start_at);
400
- CREATE INDEX user_consolidated_forward_dimension_stage_2c8e908_index ON user_consolidated_forward_dimension_stage (end_at);
401
- CREATE INDEX user_consolidated_forward_dimension_stage_2af72f1_index ON user_consolidated_forward_dimension_stage (version);
430
+ CREATE INDEX user_consolidated_forward_dimension_stage_e6c3d91_index ON user_consolidated_forward_dimension_stage (tenant_id, user_id, start_at);
402
431
  EOS
403
432
  end
404
433
  end