masamune 0.13.8 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/lib/masamune.rb +8 -5
  3. data/lib/masamune/actions.rb +1 -13
  4. data/lib/masamune/actions/data_flow.rb +2 -1
  5. data/lib/masamune/actions/date_parse.rb +0 -1
  6. data/lib/masamune/actions/elastic_mapreduce.rb +0 -2
  7. data/lib/masamune/actions/filesystem.rb +0 -2
  8. data/lib/masamune/actions/hive.rb +0 -2
  9. data/lib/masamune/actions/invoke_parallel.rb +2 -1
  10. data/lib/masamune/actions/postgres.rb +0 -1
  11. data/lib/masamune/actions/s3cmd.rb +2 -0
  12. data/lib/masamune/actions/transform.rb +0 -2
  13. data/lib/masamune/after_initialize_callbacks.rb +0 -2
  14. data/lib/masamune/commands.rb +1 -11
  15. data/lib/masamune/commands/postgres.rb +1 -0
  16. data/lib/masamune/commands/postgres_admin.rb +2 -0
  17. data/lib/masamune/configuration.rb +2 -0
  18. data/lib/masamune/data_plan/engine.rb +2 -0
  19. data/lib/masamune/filesystem.rb +2 -0
  20. data/lib/masamune/helpers.rb +1 -1
  21. data/lib/masamune/last_element.rb +0 -2
  22. data/lib/masamune/schema/dimension.rb +1 -3
  23. data/lib/masamune/schema/store.rb +2 -0
  24. data/lib/masamune/schema/table.rb +2 -0
  25. data/lib/masamune/template.rb +4 -1
  26. data/lib/masamune/thor.rb +1 -1
  27. data/lib/masamune/transform.rb +1 -21
  28. data/lib/masamune/transform/bulk_upsert.rb +1 -22
  29. data/lib/masamune/transform/common.rb +27 -0
  30. data/lib/masamune/transform/common/denormalize_table.rb +90 -0
  31. data/lib/masamune/transform/deduplicate_dimension.rb +1 -41
  32. data/lib/masamune/transform/define_table.rb +1 -113
  33. data/lib/masamune/transform/denormalize_table.rb +1 -50
  34. data/lib/masamune/transform/hive.rb +27 -0
  35. data/lib/masamune/transform/{define_schema.hql.erb → hive/define_schema.hql.erb} +0 -0
  36. data/lib/masamune/transform/{define_table.hql.erb → hive/define_table.hql.erb} +0 -0
  37. data/lib/masamune/transform/hive/define_table.rb +46 -0
  38. data/lib/masamune/transform/{denormalize_table.hql.erb → hive/denormalize_table.hql.erb} +0 -0
  39. data/lib/masamune/transform/hive/denormalize_table.rb +27 -0
  40. data/lib/masamune/transform/insert_reference_values.rb +1 -30
  41. data/lib/masamune/transform/operator.rb +36 -37
  42. data/lib/masamune/transform/postgres.rb +27 -0
  43. data/lib/masamune/transform/{bulk_upsert.psql.erb → postgres/bulk_upsert.psql.erb} +0 -0
  44. data/lib/masamune/transform/postgres/bulk_upsert.rb +62 -0
  45. data/lib/masamune/transform/{deduplicate_dimension.psql.erb → postgres/deduplicate_dimension.psql.erb} +1 -7
  46. data/lib/masamune/transform/postgres/deduplicate_dimension.rb +79 -0
  47. data/lib/masamune/transform/{define_foreign_key.psql.erb → postgres/define_foreign_key.psql.erb} +0 -0
  48. data/lib/masamune/transform/{define_index.psql.erb → postgres/define_index.psql.erb} +0 -0
  49. data/lib/masamune/transform/{define_inheritance.psql.erb → postgres/define_inheritance.psql.erb} +0 -0
  50. data/lib/masamune/transform/{define_schema.psql.erb → postgres/define_schema.psql.erb} +0 -0
  51. data/lib/masamune/transform/{define_table.psql.erb → postgres/define_table.psql.erb} +0 -0
  52. data/lib/masamune/transform/postgres/define_table.rb +142 -0
  53. data/lib/masamune/transform/{define_unique.psql.erb → postgres/define_unique.psql.erb} +0 -0
  54. data/lib/masamune/transform/{denormalize_table.psql.erb → postgres/denormalize_table.psql.erb} +0 -0
  55. data/lib/masamune/transform/postgres/denormalize_table.rb +27 -0
  56. data/lib/masamune/transform/{insert_reference_values.psql.erb → postgres/insert_reference_values.psql.erb} +1 -1
  57. data/lib/masamune/transform/postgres/insert_reference_values.rb +69 -0
  58. data/lib/masamune/transform/{relabel_dimension.psql.erb → postgres/relabel_dimension.psql.erb} +4 -1
  59. data/lib/masamune/transform/postgres/relabel_dimension.rb +45 -0
  60. data/lib/masamune/transform/{replace_table.psql.erb → postgres/replace_table.psql.erb} +0 -0
  61. data/lib/masamune/transform/{rollup_fact.psql.erb → postgres/rollup_fact.psql.erb} +0 -0
  62. data/lib/masamune/transform/postgres/rollup_fact.rb +123 -0
  63. data/lib/masamune/transform/{snapshot_dimension.psql.erb → postgres/snapshot_dimension.psql.erb} +3 -10
  64. data/lib/masamune/transform/postgres/snapshot_dimension.rb +83 -0
  65. data/lib/masamune/transform/{stage_dimension.psql.erb → postgres/stage_dimension.psql.erb} +0 -0
  66. data/lib/masamune/transform/postgres/stage_dimension.rb +90 -0
  67. data/lib/masamune/transform/{stage_fact.psql.erb → postgres/stage_fact.psql.erb} +0 -0
  68. data/lib/masamune/transform/postgres/stage_fact.rb +134 -0
  69. data/lib/masamune/transform/relabel_dimension.rb +1 -9
  70. data/lib/masamune/transform/rollup_fact.rb +1 -86
  71. data/lib/masamune/transform/snapshot_dimension.rb +1 -44
  72. data/lib/masamune/transform/stage_dimension.rb +1 -53
  73. data/lib/masamune/transform/stage_fact.rb +1 -96
  74. data/lib/masamune/version.rb +1 -1
  75. data/spec/masamune/template_spec.rb +1 -1
  76. data/spec/masamune/transform/bulk_upsert.dimension_spec.rb +1 -3
  77. data/spec/masamune/transform/deduplicate_dimension_spec.rb +1 -7
  78. data/spec/masamune/transform/define_table.dimension_spec.rb +0 -14
  79. data/spec/masamune/transform/denormalize_table_spec.rb +34 -0
  80. data/spec/masamune/transform/relabel_dimension_spec.rb +6 -1
  81. data/spec/masamune/transform/snapshot_dimension_spec.rb +3 -10
  82. metadata +37 -21
@@ -25,15 +25,7 @@ module Masamune::Transform
25
25
  extend ActiveSupport::Concern
26
26
 
27
27
  def relabel_dimension(target)
28
- Operator.new(__method__, target: target, presenters: { postgres: Postgres })
29
- end
30
-
31
- private
32
-
33
- class Postgres < SimpleDelegator
34
- def window(*extra)
35
- (columns.values.select { |column| extra.delete(column.name) || column.natural_key || column.auto_reference }.map(&:name) + extra).uniq
36
- end
28
+ Operator.new(__method__, target: target)
37
29
  end
38
30
  end
39
31
  end
@@ -27,92 +27,7 @@ module Masamune::Transform
27
27
  def rollup_fact(source, target, date)
28
28
  raise ArgumentError, "#{source.name} must have date_column to rollup" unless source.date_column
29
29
  raise ArgumentError, "#{target.name} must have date_column to rollup" unless target.date_column
30
- Operator.new __method__, source: source.partition_table(date), target: target.partition_table(date), presenters: { postgres: Postgres }
31
- end
32
-
33
- private
34
-
35
- class Postgres < SimpleDelegator
36
- include Masamune::LastElement
37
-
38
- def insert_columns(source)
39
- values = []
40
- shared_columns(source).values.map do |columns|
41
- column = columns.first
42
- next if column.id == :last_modified_at
43
- next if column.auto_reference
44
- values << column.name
45
- end
46
- measures.each do |_ ,measure|
47
- values << measure.name
48
- end
49
- values << time_key.name
50
- values.compact
51
- end
52
-
53
- def insert_values(source)
54
- values = []
55
- values << calculated_date_key(source)
56
- shared_columns(source).values.map do |columns|
57
- column = columns.first
58
- next unless column.reference
59
- next if column.reference.type == :date
60
- next if column.auto_reference
61
- values << column.qualified_name
62
- end
63
- source.measures.each do |_ ,measure|
64
- values << measure.aggregate_value
65
- end
66
- values << calculated_time_key(source)
67
- values
68
- end
69
- method_with_last_element :insert_values
70
-
71
- def join_conditions(source)
72
- {
73
- source.date_column.reference.name => [
74
- "#{source.date_column.reference.surrogate_key.qualified_name} = #{source.date_column.qualified_name}"
75
- ]
76
- }
77
- end
78
-
79
- def group_by(source)
80
- group_by = []
81
- group_by << calculated_date_key(source)
82
- shared_columns(source).values.map do |columns|
83
- column = columns.first
84
- next unless column.reference
85
- next if column.reference.type == :date
86
- next if column.auto_reference
87
- group_by << column.qualified_name
88
- end
89
- group_by << calculated_time_key(source)
90
- group_by
91
- end
92
- method_with_last_element :group_by
93
-
94
- private
95
-
96
- def calculated_date_key(source)
97
- case grain
98
- when :hourly, :daily
99
- "#{source.date_column.qualified_name}"
100
- when :monthly
101
- "to_char(date_trunc('month',#{source.date_column.qualified_name}::text::date),'YYYYMMDD')::integer"
102
- end
103
- end
104
-
105
- def calculated_time_key(source)
106
- case grain
107
- when :hourly
108
- "(#{source.time_key.qualified_name} - (#{source.time_key.qualified_name} % #{1.hour.seconds}))"
109
- when :daily
110
- "extract(EPOCH from #{source.date_column.qualified_name}::text::date)"
111
- when :monthly
112
- "extract(EPOCH from date_trunc('month',#{source.date_column.qualified_name}::text::date))"
113
- end
114
- end
115
-
30
+ Operator.new __method__, source: source.partition_table(date), target: target.partition_table(date)
116
31
  end
117
32
  end
118
33
  end
@@ -25,50 +25,7 @@ module Masamune::Transform
25
25
  extend ActiveSupport::Concern
26
26
 
27
27
  def snapshot_dimension(source, target, order = 'DESC')
28
- Operator.new(__method__, source: source, target: target, order: order, presenters: { postgres: Postgres })
29
- end
30
-
31
- private
32
-
33
- class Postgres < SimpleDelegator
34
- include Masamune::LastElement
35
-
36
- def insert_columns(source = nil)
37
- consolidated_columns.map { |_, column| column.name }
38
- end
39
-
40
- def insert_view_values
41
- consolidated_columns.map { |_, column| column.name }
42
- end
43
-
44
- def insert_view_constraints
45
- consolidated_columns.reject { |_, column| !column.default.nil? || column.null }.map { |_, column| "#{column.name} IS NOT NULL" }
46
- end
47
- method_with_last_element :insert_view_constraints
48
-
49
- def window(*extra)
50
- (columns.values.select { |column| extra.delete(column.name) || column.natural_key || column.auto_reference }.map(&:name) + extra).uniq
51
- end
52
-
53
- def insert_values(opts = {})
54
- window = opts[:window]
55
- consolidated_columns.map do |_, column|
56
- if column.natural_key
57
- "#{column.name} AS #{column.name}"
58
- elsif column.type == :key_value
59
- "hstore_merge(#{column.name}) OVER #{window} AS #{column.name}"
60
- else
61
- "coalesce_merge(#{column.name}) OVER #{window} AS #{column.name}"
62
- end
63
- end
64
- end
65
- method_with_last_element :insert_values
66
-
67
- private
68
-
69
- def consolidated_columns
70
- unreserved_columns.reject { |_, column| column.surrogate_key }
71
- end
28
+ Operator.new(__method__, source: source, target: target, order: order)
72
29
  end
73
30
  end
74
31
  end
@@ -25,59 +25,7 @@ module Masamune::Transform
25
25
  extend ActiveSupport::Concern
26
26
 
27
27
  def stage_dimension(source, target)
28
- Operator.new(__method__, source: source, target: target, presenters: { postgres: Postgres })
29
- end
30
-
31
- private
32
-
33
- class Postgres < SimpleDelegator
34
- include Masamune::LastElement
35
-
36
- def insert_columns(source)
37
- shared_columns(source).values.map do |columns|
38
- column = columns.first
39
- if reference = column.reference
40
- reference.foreign_key_name
41
- else
42
- column.name
43
- end
44
- end.compact
45
- end
46
-
47
- def insert_values(source)
48
- shared_columns(source).values.map do |columns|
49
- column = columns.first
50
- if reference = column.reference
51
- reference.surrogate_key.qualified_name(reference.label)
52
- elsif column.type == :json || column.type == :yaml || column.type == :key_value
53
- "json_to_hstore(#{column.qualified_name})"
54
- else
55
- column.qualified_name
56
- end
57
- end.compact
58
- end
59
- method_with_last_element :insert_values
60
-
61
- def join_conditions(source)
62
- join_columns = shared_columns(source).values.flatten
63
- join_columns = join_columns.select { |column| column.reference }
64
- join_columns = join_columns.group_by { |column| column.reference }
65
-
66
- conditions = Hash.new { |h,k| h[k] = Set.new }
67
- join_columns.each do |reference, columns|
68
- left_uniq = Set.new
69
- (columns + lateral_references(source, reference)).each do |column|
70
- left = reference.columns[column.id]
71
- next unless left_uniq.add?(left.qualified_name(reference.label))
72
- conditions[[reference.name, reference.alias]] << "#{left.qualified_name(reference.label)} = #{column.qualified_name}"
73
- end
74
- end
75
- conditions
76
- end
77
-
78
- def lateral_references(source, reference)
79
- source.shared_columns(reference).keys.reject { |column| column.auto_reference }
80
- end
28
+ Operator.new(__method__, source: source, target: target)
81
29
  end
82
30
  end
83
31
  end
@@ -25,102 +25,7 @@ module Masamune::Transform
25
25
  extend ActiveSupport::Concern
26
26
 
27
27
  def stage_fact(source, target, date)
28
- Operator.new(__method__, source: source, target: target, date: date, presenters: { postgres: Postgres })
29
- end
30
-
31
- private
32
-
33
- class Postgres < SimpleDelegator
34
- include Masamune::LastElement
35
-
36
- def insert_columns(source)
37
- shared_columns(source).values.map do |columns|
38
- column = columns.first
39
- if reference = column.reference
40
- reference.foreign_key_name
41
- else
42
- column.name
43
- end
44
- end
45
- end
46
-
47
- def insert_values(source)
48
- shared_columns(source).values.map do |columns|
49
- column = columns.first
50
- if !column.degenerate? && reference = column.reference
51
- reference.surrogate_key.qualified_name(column.reference.label)
52
- else
53
- column.qualified_name
54
- end
55
- end
56
- end
57
- method_with_last_element :insert_values
58
-
59
- def join_alias(reference)
60
- reference.label ? "#{reference.name} AS #{[reference.label, reference.name].compact.join('_')}" : reference.name
61
- end
62
-
63
- def join_conditions(source)
64
- join_columns = shared_columns(source).values.flatten
65
- join_columns = join_columns.select { |column| column.reference }
66
- join_columns = join_columns.group_by { |column| column.reference }
67
-
68
- dependencies = Masamune::TopologicalHash.new
69
- conditions = Hash.new { |h,k| h[k] = [] }
70
- join_columns.each do |reference, columns|
71
- reference_name = join_alias(reference)
72
- columns.each do |column|
73
- next if column.degenerate?
74
- dependencies[reference_name] ||= []
75
- cross_references = cross_references(column)
76
-
77
- coalesce_values = []
78
-
79
- if cross_references.any?
80
- dependencies[reference_name] += cross_references.map { |reference, _| join_alias(reference) }
81
- coalesce_values << cross_references.map { |reference, column| column.qualified_name(reference.label) }
82
- end
83
-
84
- column.reference.auto_surrogate_keys.each do |auto_surrogate_key|
85
- next unless auto_surrogate_key.default
86
- conditions[reference_name] << "#{auto_surrogate_key.qualified_name(reference.label)} = #{auto_surrogate_key.default}"
87
- end if column.reference
88
-
89
- if column.reference && !column.reference.default.nil? && column.adjacent.natural_key
90
- coalesce_values << column.reference.default(column.adjacent)
91
- elsif column.adjacent && !column.adjacent.default.nil?
92
- coalesce_values << column.adjacent.sql_value(column.adjacent.default)
93
- end
94
-
95
- conditions[reference_name] << (coalesce_values.any? ?
96
- "#{column.foreign_key_name} = COALESCE(#{column.qualified_name}, #{coalesce_values.join(', ')})" :
97
- "#{column.foreign_key_name} = #{column.qualified_name}")
98
- end
99
-
100
- if reference.type == :two || reference.type == :four
101
- 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')"
102
- 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"
103
- conditions[reference_name] << "((#{join_key_a}) OR (#{join_key_b}))"
104
- end
105
-
106
- conditions[reference_name].uniq!
107
- end
108
- conditions.slice(*dependencies.tsort)
109
- end
110
-
111
- private
112
-
113
- def cross_references(column)
114
- return {} unless column.natural_key || column.adjacent.try(:natural_key)
115
- {}.tap do |result|
116
- column.reference.through.each do |reference_id|
117
- reference = references[reference_id]
118
- if reference.columns[column.id]
119
- result[reference] = reference.columns[column.id]
120
- end
121
- end
122
- end
123
- end
28
+ Operator.new(__method__, source: source, target: target, date: date)
124
29
  end
125
30
  end
126
31
  end
@@ -21,5 +21,5 @@
21
21
  # THE SOFTWARE.
22
22
 
23
23
  module Masamune
24
- VERSION = '0.13.8'
24
+ VERSION = '0.14.0'
25
25
  end
@@ -75,7 +75,7 @@ describe Masamune::Template do
75
75
  end
76
76
 
77
77
  context 'with packaged template' do
78
- let(:template) { 'define_schema.hql.erb' }
78
+ let(:template) { 'hive/define_schema.hql.erb' }
79
79
  it { is_expected.to_not be_nil }
80
80
  end
81
81
  end
@@ -101,7 +101,7 @@ describe Masamune::Transform::BulkUpsert do
101
101
  ;
102
102
 
103
103
  INSERT INTO
104
- user_dimension (department_type_id, user_account_state_type_id, hr_user_account_state_type_id, tenant_id, user_id, name, preferences, parent_id, record_id, start_at, end_at, version, last_modified_at)
104
+ user_dimension (department_type_id, user_account_state_type_id, hr_user_account_state_type_id, tenant_id, user_id, name, preferences, start_at, end_at, version, last_modified_at)
105
105
  SELECT
106
106
  user_dimension_stage.department_type_id,
107
107
  user_dimension_stage.user_account_state_type_id,
@@ -110,8 +110,6 @@ describe Masamune::Transform::BulkUpsert do
110
110
  user_dimension_stage.user_id,
111
111
  user_dimension_stage.name,
112
112
  user_dimension_stage.preferences,
113
- user_dimension_stage.parent_id,
114
- user_dimension_stage.record_id,
115
113
  user_dimension_stage.start_at,
116
114
  user_dimension_stage.end_at,
117
115
  user_dimension_stage.version,
@@ -52,21 +52,17 @@ describe Masamune::Transform::DeduplicateDimension do
52
52
  tenant_id,
53
53
  user_id,
54
54
  preferences,
55
- parent_id,
56
- record_id,
57
55
  start_at
58
56
  FROM
59
57
  user_consolidated_dimension_stage
60
58
  )
61
59
  INSERT INTO
62
- user_deduplicated_dimension_stage (user_account_state_type_id, tenant_id, user_id, preferences, parent_id, record_id, start_at)
60
+ user_deduplicated_dimension_stage (user_account_state_type_id, tenant_id, user_id, preferences, start_at)
63
61
  SELECT DISTINCT
64
62
  user_account_state_type_id,
65
63
  tenant_id,
66
64
  user_id,
67
65
  preferences,
68
- parent_id,
69
- record_id,
70
66
  start_at
71
67
  FROM (
72
68
  SELECT
@@ -74,8 +70,6 @@ describe Masamune::Transform::DeduplicateDimension do
74
70
  tenant_id,
75
71
  user_id,
76
72
  preferences,
77
- parent_id,
78
- record_id,
79
73
  start_at,
80
74
  CASE
81
75
  WHEN (LAG(user_account_state_type_id) OVER w = user_account_state_type_id) AND (LAG(tenant_id) OVER w = tenant_id) AND (LAG(user_id) OVER w = user_id) AND ((LAG(preferences) OVER w = preferences) OR (LAG(preferences) OVER w IS NULL AND preferences IS NULL)) THEN
@@ -321,8 +321,6 @@ describe Masamune::Transform::DefineTable do
321
321
  tenant_id INTEGER NOT NULL,
322
322
  user_id INTEGER NOT NULL,
323
323
  preferences HSTORE,
324
- parent_id INTEGER,
325
- record_id INTEGER,
326
324
  start_at TIMESTAMP NOT NULL DEFAULT TO_TIMESTAMP(0),
327
325
  end_at TIMESTAMP,
328
326
  version INTEGER DEFAULT 1,
@@ -344,16 +342,6 @@ describe Masamune::Transform::DefineTable do
344
342
  ALTER TABLE user_dimension ADD CONSTRAINT user_dimension_7988187_fkey FOREIGN KEY (user_account_state_type_id) REFERENCES user_account_state_type(id);
345
343
  END IF; END $$;
346
344
 
347
- DO $$ BEGIN
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);
350
- END IF; END $$;
351
-
352
- DO $$ BEGIN
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);
355
- END IF; END $$;
356
-
357
345
  DO $$ BEGIN
358
346
  IF NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.relname = 'user_dimension_3fcebfa_key') THEN
359
347
  ALTER TABLE user_dimension ADD CONSTRAINT user_dimension_3fcebfa_key UNIQUE(cluster_type_id, tenant_id, user_id, start_at);
@@ -415,8 +403,6 @@ describe Masamune::Transform::DefineTable do
415
403
  tenant_id INTEGER,
416
404
  user_id INTEGER,
417
405
  preferences HSTORE,
418
- parent_id INTEGER,
419
- record_id INTEGER,
420
406
  start_at TIMESTAMP DEFAULT TO_TIMESTAMP(0),
421
407
  end_at TIMESTAMP,
422
408
  version INTEGER DEFAULT 1,
@@ -73,6 +73,39 @@ describe Masamune::Transform::DenormalizeTable do
73
73
 
74
74
  subject(:result) { transform.denormalize_table(target, options).to_s }
75
75
 
76
+ context 'with postgres dimension' do
77
+ let(:target) { catalog.postgres.user_dimension }
78
+ let(:options) { { } }
79
+
80
+ it 'should eq render denormalize_table template' do
81
+ is_expected.to eq <<-EOS.strip_heredoc
82
+ SELECT
83
+ cluster_type.name AS cluster_type_name,
84
+ user_dimension.tenant_id,
85
+ user_dimension.user_id,
86
+ user_dimension.name,
87
+ user_dimension.start_at,
88
+ user_dimension.end_at,
89
+ user_dimension.version
90
+ FROM
91
+ user_dimension
92
+ LEFT JOIN
93
+ cluster_type
94
+ ON
95
+ cluster_type.id = user_dimension.cluster_type_id
96
+ ORDER BY
97
+ cluster_type_name,
98
+ tenant_id,
99
+ user_id,
100
+ name,
101
+ start_at,
102
+ end_at,
103
+ version
104
+ ;
105
+ EOS
106
+ end
107
+ end
108
+
76
109
  context 'with postgres fact without :columns' do
77
110
  let(:target) { catalog.postgres.visits_fact }
78
111
  let(:options) { { } }
@@ -137,6 +170,7 @@ describe Masamune::Transform::DenormalizeTable do
137
170
  EOS
138
171
  end
139
172
  end
173
+
140
174
  context 'with postgres fact with :columns' do
141
175
  let(:target) { catalog.postgres.visits_fact }
142
176
  let(:options) do