masamune 0.12.3 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
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