masamune 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +54 -0
- data/Rakefile +15 -0
- data/bin/masamune-elastic-mapreduce +4 -0
- data/bin/masamune-hive +4 -0
- data/bin/masamune-psql +4 -0
- data/bin/masamune-shell +4 -0
- data/lib/masamune.rb +56 -0
- data/lib/masamune/accumulate.rb +60 -0
- data/lib/masamune/actions.rb +38 -0
- data/lib/masamune/actions/data_flow.rb +131 -0
- data/lib/masamune/actions/date_parse.rb +75 -0
- data/lib/masamune/actions/elastic_mapreduce.rb +68 -0
- data/lib/masamune/actions/execute.rb +52 -0
- data/lib/masamune/actions/filesystem.rb +37 -0
- data/lib/masamune/actions/hadoop_filesystem.rb +40 -0
- data/lib/masamune/actions/hadoop_streaming.rb +41 -0
- data/lib/masamune/actions/hive.rb +74 -0
- data/lib/masamune/actions/postgres.rb +76 -0
- data/lib/masamune/actions/postgres_admin.rb +34 -0
- data/lib/masamune/actions/s3cmd.rb +44 -0
- data/lib/masamune/actions/transform.rb +89 -0
- data/lib/masamune/after_initialize_callbacks.rb +55 -0
- data/lib/masamune/cached_filesystem.rb +110 -0
- data/lib/masamune/commands.rb +37 -0
- data/lib/masamune/commands/elastic_mapreduce.rb +119 -0
- data/lib/masamune/commands/hadoop_filesystem.rb +57 -0
- data/lib/masamune/commands/hadoop_streaming.rb +116 -0
- data/lib/masamune/commands/hive.rb +178 -0
- data/lib/masamune/commands/interactive.rb +37 -0
- data/lib/masamune/commands/postgres.rb +128 -0
- data/lib/masamune/commands/postgres_admin.rb +72 -0
- data/lib/masamune/commands/postgres_common.rb +33 -0
- data/lib/masamune/commands/retry_with_backoff.rb +60 -0
- data/lib/masamune/commands/s3cmd.rb +70 -0
- data/lib/masamune/commands/shell.rb +202 -0
- data/lib/masamune/configuration.rb +195 -0
- data/lib/masamune/data_plan.rb +31 -0
- data/lib/masamune/data_plan/builder.rb +66 -0
- data/lib/masamune/data_plan/elem.rb +190 -0
- data/lib/masamune/data_plan/engine.rb +162 -0
- data/lib/masamune/data_plan/rule.rb +292 -0
- data/lib/masamune/data_plan/set.rb +176 -0
- data/lib/masamune/environment.rb +164 -0
- data/lib/masamune/filesystem.rb +567 -0
- data/lib/masamune/has_environment.rb +40 -0
- data/lib/masamune/helpers.rb +27 -0
- data/lib/masamune/helpers/postgres.rb +84 -0
- data/lib/masamune/io.rb +33 -0
- data/lib/masamune/last_element.rb +53 -0
- data/lib/masamune/method_logger.rb +41 -0
- data/lib/masamune/multi_io.rb +39 -0
- data/lib/masamune/schema.rb +36 -0
- data/lib/masamune/schema/catalog.rb +233 -0
- data/lib/masamune/schema/column.rb +527 -0
- data/lib/masamune/schema/dimension.rb +133 -0
- data/lib/masamune/schema/event.rb +121 -0
- data/lib/masamune/schema/fact.rb +133 -0
- data/lib/masamune/schema/map.rb +265 -0
- data/lib/masamune/schema/row.rb +133 -0
- data/lib/masamune/schema/store.rb +115 -0
- data/lib/masamune/schema/table.rb +308 -0
- data/lib/masamune/schema/table_reference.rb +76 -0
- data/lib/masamune/spec_helper.rb +23 -0
- data/lib/masamune/string_format.rb +34 -0
- data/lib/masamune/tasks/elastic_mapreduce_thor.rb +60 -0
- data/lib/masamune/tasks/hive_thor.rb +55 -0
- data/lib/masamune/tasks/postgres_thor.rb +47 -0
- data/lib/masamune/tasks/shell_thor.rb +63 -0
- data/lib/masamune/template.rb +77 -0
- data/lib/masamune/thor.rb +186 -0
- data/lib/masamune/thor_loader.rb +38 -0
- data/lib/masamune/topological_hash.rb +34 -0
- data/lib/masamune/transform.rb +47 -0
- data/lib/masamune/transform/bulk_upsert.psql.erb +64 -0
- data/lib/masamune/transform/bulk_upsert.rb +52 -0
- data/lib/masamune/transform/consolidate_dimension.rb +54 -0
- data/lib/masamune/transform/deduplicate_dimension.psql.erb +52 -0
- data/lib/masamune/transform/deduplicate_dimension.rb +53 -0
- data/lib/masamune/transform/define_event_view.hql.erb +51 -0
- data/lib/masamune/transform/define_event_view.rb +60 -0
- data/lib/masamune/transform/define_index.psql.erb +34 -0
- data/lib/masamune/transform/define_schema.hql.erb +23 -0
- data/lib/masamune/transform/define_schema.psql.erb +79 -0
- data/lib/masamune/transform/define_schema.rb +56 -0
- data/lib/masamune/transform/define_table.hql.erb +34 -0
- data/lib/masamune/transform/define_table.psql.erb +95 -0
- data/lib/masamune/transform/define_table.rb +40 -0
- data/lib/masamune/transform/define_unique.psql.erb +30 -0
- data/lib/masamune/transform/insert_reference_values.psql.erb +43 -0
- data/lib/masamune/transform/insert_reference_values.rb +64 -0
- data/lib/masamune/transform/load_dimension.rb +47 -0
- data/lib/masamune/transform/load_fact.rb +45 -0
- data/lib/masamune/transform/operator.rb +96 -0
- data/lib/masamune/transform/relabel_dimension.psql.erb +76 -0
- data/lib/masamune/transform/relabel_dimension.rb +39 -0
- data/lib/masamune/transform/rollup_fact.psql.erb +79 -0
- data/lib/masamune/transform/rollup_fact.rb +149 -0
- data/lib/masamune/transform/snapshot_dimension.psql.erb +75 -0
- data/lib/masamune/transform/snapshot_dimension.rb +74 -0
- data/lib/masamune/transform/stage_dimension.psql.erb +39 -0
- data/lib/masamune/transform/stage_dimension.rb +83 -0
- data/lib/masamune/transform/stage_fact.psql.erb +80 -0
- data/lib/masamune/transform/stage_fact.rb +111 -0
- data/lib/masamune/version.rb +25 -0
- data/spec/fixtures/aggregate.sql.erb +25 -0
- data/spec/fixtures/comment.sql.erb +27 -0
- data/spec/fixtures/invalid.sql.erb +23 -0
- data/spec/fixtures/relative.sql.erb +23 -0
- data/spec/fixtures/simple.sql.erb +28 -0
- data/spec/fixtures/whitespace.sql.erb +30 -0
- data/spec/masamune/actions/elastic_mapreduce_spec.rb +108 -0
- data/spec/masamune/actions/execute_spec.rb +50 -0
- data/spec/masamune/actions/hadoop_filesystem_spec.rb +44 -0
- data/spec/masamune/actions/hadoop_streaming_spec.rb +74 -0
- data/spec/masamune/actions/hive_spec.rb +117 -0
- data/spec/masamune/actions/postgres_admin_spec.rb +58 -0
- data/spec/masamune/actions/postgres_spec.rb +134 -0
- data/spec/masamune/actions/s3cmd_spec.rb +44 -0
- data/spec/masamune/actions/transform_spec.rb +144 -0
- data/spec/masamune/after_initialization_callbacks_spec.rb +61 -0
- data/spec/masamune/cached_filesystem_spec.rb +167 -0
- data/spec/masamune/commands/hadoop_filesystem_spec.rb +50 -0
- data/spec/masamune/commands/hadoop_streaming_spec.rb +106 -0
- data/spec/masamune/commands/hive_spec.rb +117 -0
- data/spec/masamune/commands/postgres_admin_spec.rb +69 -0
- data/spec/masamune/commands/postgres_spec.rb +100 -0
- data/spec/masamune/commands/retry_with_backoff_spec.rb +116 -0
- data/spec/masamune/commands/s3cmd_spec.rb +50 -0
- data/spec/masamune/commands/shell_spec.rb +101 -0
- data/spec/masamune/configuration_spec.rb +102 -0
- data/spec/masamune/data_plan/builder_spec.rb +91 -0
- data/spec/masamune/data_plan/elem_spec.rb +102 -0
- data/spec/masamune/data_plan/engine_spec.rb +356 -0
- data/spec/masamune/data_plan/rule_spec.rb +407 -0
- data/spec/masamune/data_plan/set_spec.rb +517 -0
- data/spec/masamune/environment_spec.rb +65 -0
- data/spec/masamune/filesystem_spec.rb +1421 -0
- data/spec/masamune/helpers/postgres_spec.rb +95 -0
- data/spec/masamune/schema/catalog_spec.rb +613 -0
- data/spec/masamune/schema/column_spec.rb +696 -0
- data/spec/masamune/schema/dimension_spec.rb +137 -0
- data/spec/masamune/schema/event_spec.rb +75 -0
- data/spec/masamune/schema/fact_spec.rb +117 -0
- data/spec/masamune/schema/map_spec.rb +593 -0
- data/spec/masamune/schema/row_spec.rb +28 -0
- data/spec/masamune/schema/store_spec.rb +49 -0
- data/spec/masamune/schema/table_spec.rb +395 -0
- data/spec/masamune/string_format_spec.rb +60 -0
- data/spec/masamune/tasks/elastic_mapreduce_thor_spec.rb +57 -0
- data/spec/masamune/tasks/hive_thor_spec.rb +75 -0
- data/spec/masamune/tasks/postgres_thor_spec.rb +42 -0
- data/spec/masamune/tasks/shell_thor_spec.rb +51 -0
- data/spec/masamune/template_spec.rb +77 -0
- data/spec/masamune/thor_spec.rb +238 -0
- data/spec/masamune/transform/bulk_upsert.dimension_spec.rb +200 -0
- data/spec/masamune/transform/consolidate_dimension_spec.rb +62 -0
- data/spec/masamune/transform/deduplicate_dimension_spec.rb +84 -0
- data/spec/masamune/transform/define_event_view_spec.rb +84 -0
- data/spec/masamune/transform/define_schema_spec.rb +83 -0
- data/spec/masamune/transform/define_table.dimension_spec.rb +306 -0
- data/spec/masamune/transform/define_table.fact_spec.rb +291 -0
- data/spec/masamune/transform/define_table.table_spec.rb +525 -0
- data/spec/masamune/transform/insert_reference_values.dimension_spec.rb +111 -0
- data/spec/masamune/transform/insert_reference_values.fact_spec.rb +149 -0
- data/spec/masamune/transform/load_dimension_spec.rb +76 -0
- data/spec/masamune/transform/load_fact_spec.rb +89 -0
- data/spec/masamune/transform/relabel_dimension_spec.rb +102 -0
- data/spec/masamune/transform/rollup_fact_spec.rb +333 -0
- data/spec/masamune/transform/snapshot_dimension_spec.rb +103 -0
- data/spec/masamune/transform/stage_dimension_spec.rb +115 -0
- data/spec/masamune/transform/stage_fact_spec.rb +204 -0
- data/spec/masamune_spec.rb +32 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/support/masamune/example_group.rb +36 -0
- data/spec/support/masamune/mock_command.rb +99 -0
- data/spec/support/masamune/mock_delegate.rb +51 -0
- data/spec/support/masamune/mock_filesystem.rb +96 -0
- data/spec/support/masamune/thor_mute.rb +35 -0
- data/spec/support/rspec/example/action_example_group.rb +34 -0
- data/spec/support/rspec/example/task_example_group.rb +80 -0
- data/spec/support/rspec/example/transform_example_group.rb +36 -0
- data/spec/support/shared_examples/postgres_common_examples.rb +53 -0
- metadata +462 -0
@@ -0,0 +1,133 @@
|
|
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
|
+
module Masamune::Schema
|
24
|
+
class Row
|
25
|
+
DEFAULT_ATTRIBUTES =
|
26
|
+
{
|
27
|
+
id: nil,
|
28
|
+
values: {},
|
29
|
+
default: false,
|
30
|
+
strict: true,
|
31
|
+
parent: nil,
|
32
|
+
debug: false
|
33
|
+
}
|
34
|
+
|
35
|
+
DEFAULT_ATTRIBUTES.keys.each do |attr|
|
36
|
+
attr_accessor attr
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(opts = {})
|
40
|
+
opts.symbolize_keys!
|
41
|
+
DEFAULT_ATTRIBUTES.merge(opts).each do |name, value|
|
42
|
+
public_send("#{name}=", value)
|
43
|
+
end
|
44
|
+
self.id ||= :default if default
|
45
|
+
end
|
46
|
+
|
47
|
+
def id=(id)
|
48
|
+
@id = id.to_sym if id
|
49
|
+
end
|
50
|
+
|
51
|
+
def values=(values)
|
52
|
+
@values = values.symbolize_keys
|
53
|
+
end
|
54
|
+
|
55
|
+
def parent=(parent)
|
56
|
+
@parent = parent
|
57
|
+
normalize_values! if @parent
|
58
|
+
end
|
59
|
+
|
60
|
+
def name(column = nil)
|
61
|
+
return unless @id
|
62
|
+
if column
|
63
|
+
"#{@id}_#{column.name}()"
|
64
|
+
else
|
65
|
+
"#{@id}_#{parent.name}_#{parent.surrogate_key.name}()"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def natural_keys
|
70
|
+
parent.natural_keys.select do |column|
|
71
|
+
values.keys.include?(column.name) && !column.sql_function?(values[column.name])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def insert_constraints
|
76
|
+
values.map { |key, value| "#{key} = #{parent.columns[key].sql_value(value)}" }.compact
|
77
|
+
end
|
78
|
+
|
79
|
+
def insert_columns
|
80
|
+
values.keys
|
81
|
+
end
|
82
|
+
|
83
|
+
def insert_values
|
84
|
+
values.map { |key, value| parent.columns[key].sql_value(value) }
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_hash
|
88
|
+
values.with_indifferent_access
|
89
|
+
end
|
90
|
+
|
91
|
+
def headers
|
92
|
+
values.keys
|
93
|
+
end
|
94
|
+
|
95
|
+
def serialize
|
96
|
+
[].tap do |result|
|
97
|
+
values.each do |key, value|
|
98
|
+
result << @columns[key].csv_value(value)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def sql_value(column)
|
104
|
+
column.sql_value(values[column.name])
|
105
|
+
end
|
106
|
+
|
107
|
+
def missing_required_columns
|
108
|
+
Set.new.tap do |missing|
|
109
|
+
values.select do |key, value|
|
110
|
+
column = @columns[key]
|
111
|
+
missing << column if column.required_value? && value.nil?
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def normalize_values!
|
119
|
+
result = {}
|
120
|
+
@columns = {}
|
121
|
+
values.each do |key, value|
|
122
|
+
next unless key
|
123
|
+
if column = parent.dereference_column_name(key)
|
124
|
+
@columns[column.name] = column
|
125
|
+
result[column.name] = column.ruby_value(value)
|
126
|
+
elsif strict
|
127
|
+
raise ArgumentError, "#{@values} contains undefined columns #{key}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
@values = result
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,115 @@
|
|
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 'active_support/core_ext/hash'
|
24
|
+
|
25
|
+
module Masamune::Schema
|
26
|
+
class Store
|
27
|
+
include Masamune::HasEnvironment
|
28
|
+
|
29
|
+
SUPPORTED_ATTRIBUTES = %(table dimension fact file event)
|
30
|
+
|
31
|
+
DEFAULT_ATTRIBUTES =
|
32
|
+
{
|
33
|
+
type: nil,
|
34
|
+
format: ->(store) { store.type == :postgres ? :csv : :tsv },
|
35
|
+
json_encoding: ->(store) { store.type == :postgres ? :quoted : :raw },
|
36
|
+
headers: ->(store) { store.type == :postgres ? true : false },
|
37
|
+
debug: false
|
38
|
+
}
|
39
|
+
|
40
|
+
DEFAULT_ATTRIBUTES.keys.each do |attr|
|
41
|
+
attr_accessor attr
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_accessor :tables
|
45
|
+
attr_accessor :dimensions
|
46
|
+
attr_accessor :facts
|
47
|
+
attr_accessor :files
|
48
|
+
attr_accessor :events
|
49
|
+
attr_accessor :references
|
50
|
+
|
51
|
+
class << self
|
52
|
+
def types
|
53
|
+
[:postgres, :hive, :files]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def initialize(environment, opts = {})
|
58
|
+
self.environment = environment
|
59
|
+
opts.symbolize_keys!
|
60
|
+
raise ArgumentError, 'required parameter type: missing' unless opts.key?(:type)
|
61
|
+
raise ArgumentError, "unknown type: '#{opts[:type]}'" unless self.class.types.include?(opts[:type])
|
62
|
+
DEFAULT_ATTRIBUTES.merge(opts).each do |name, value|
|
63
|
+
public_send("#{name}=", value.respond_to?(:call) ? value.call(self) : value)
|
64
|
+
end
|
65
|
+
|
66
|
+
@tables = {}.with_indifferent_access
|
67
|
+
@dimensions = {}.with_indifferent_access
|
68
|
+
@facts = {}.with_indifferent_access
|
69
|
+
@files = {}.with_indifferent_access
|
70
|
+
@events = {}.with_indifferent_access
|
71
|
+
@references = {}.with_indifferent_access
|
72
|
+
@extra = []
|
73
|
+
end
|
74
|
+
|
75
|
+
def method_missing(method, *args, &block)
|
76
|
+
if type == :files
|
77
|
+
files[method]
|
78
|
+
else
|
79
|
+
*attribute_name, attribute_type = method.to_s.split('_')
|
80
|
+
raise ArgumentError, "unknown attribute type '#{attribute_type}'" unless SUPPORTED_ATTRIBUTES.include?(attribute_type)
|
81
|
+
self.send(attribute_type.pluralize)[attribute_name.join('_')]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def dereference_column(id, options = {})
|
86
|
+
column_id, reference_id = id.to_s.split(/\./).reverse
|
87
|
+
column_options = options.dup
|
88
|
+
column_options.merge!(id: column_id)
|
89
|
+
|
90
|
+
if reference = references[reference_id]
|
91
|
+
column_options.merge!(reference: reference)
|
92
|
+
else
|
93
|
+
raise ArgumentError, "dimension #{reference_id} not defined"
|
94
|
+
end if reference_id
|
95
|
+
|
96
|
+
Masamune::Schema::Column.new(column_options)
|
97
|
+
end
|
98
|
+
|
99
|
+
def extra(order = nil)
|
100
|
+
return @extra unless order
|
101
|
+
result = Set.new
|
102
|
+
@extra.each do |file|
|
103
|
+
filename = File.basename(file)
|
104
|
+
if filename =~ /\A\d+_/
|
105
|
+
number = filename.split('_').first.to_i
|
106
|
+
result << file if number <= 0 && order == :pre
|
107
|
+
result << file if number > 0 && order == :post
|
108
|
+
else
|
109
|
+
result << file if order == :pre
|
110
|
+
end
|
111
|
+
end
|
112
|
+
result.to_a
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,308 @@
|
|
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
|
+
module Masamune::Schema
|
24
|
+
class Table
|
25
|
+
include Masamune::LastElement
|
26
|
+
|
27
|
+
attr_reader :children
|
28
|
+
|
29
|
+
DEFAULT_ATTRIBUTES =
|
30
|
+
{
|
31
|
+
id: nil,
|
32
|
+
name: nil,
|
33
|
+
type: :table,
|
34
|
+
store: nil,
|
35
|
+
parent: nil,
|
36
|
+
suffix: nil,
|
37
|
+
implicit: false,
|
38
|
+
references: {},
|
39
|
+
columns: {},
|
40
|
+
rows: [],
|
41
|
+
inherit: false,
|
42
|
+
debug: false
|
43
|
+
}
|
44
|
+
|
45
|
+
DEFAULT_ATTRIBUTES.keys.each do |attr|
|
46
|
+
attr_accessor attr
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(opts = {})
|
50
|
+
opts.symbolize_keys!
|
51
|
+
raise ArgumentError, 'required parameter id: missing' unless opts.key?(:id)
|
52
|
+
DEFAULT_ATTRIBUTES.merge(opts).each do |name, value|
|
53
|
+
public_send("#{name}=", value)
|
54
|
+
end
|
55
|
+
@children = Set.new
|
56
|
+
end
|
57
|
+
|
58
|
+
def id=(id)
|
59
|
+
@id = id.to_sym
|
60
|
+
end
|
61
|
+
|
62
|
+
def references=(instance)
|
63
|
+
@references = {}
|
64
|
+
references = (instance.is_a?(Hash) ? instance.values : instance).compact
|
65
|
+
references.each do |reference|
|
66
|
+
raise ArgumentError, "table #{name} contains invalid table references" unless reference.is_a?(TableReference)
|
67
|
+
@references[reference.id] = reference
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def columns=(instance)
|
72
|
+
@columns = {}
|
73
|
+
columns = (instance.is_a?(Hash) ? instance.values : instance).compact
|
74
|
+
raise ArgumentError, "table #{name} contains reserved columns" if columns.any? { |column| reserved_column_ids.include?(column.id) }
|
75
|
+
|
76
|
+
initialize_surrogate_key_column! unless columns.any? { |column| column.surrogate_key }
|
77
|
+
initialize_reference_columns! unless columns.any? { |column| column.reference }
|
78
|
+
columns.each do |column|
|
79
|
+
raise ArgumentError, "table #{name} contains invalid columns" unless column.is_a?(Column)
|
80
|
+
@columns[column.name] = column.dup
|
81
|
+
@columns[column.name].parent = self
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def rows=(rows)
|
86
|
+
@rows = []
|
87
|
+
rows.each do |row|
|
88
|
+
@rows << row.dup
|
89
|
+
@rows.last.parent = self
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def name
|
94
|
+
@name || [id, suffix].compact.join('_')
|
95
|
+
end
|
96
|
+
|
97
|
+
def suffix
|
98
|
+
((parent ? parent.suffix.split('_') : []) + [type.to_s, @suffix]).compact.uniq.join('_')
|
99
|
+
end
|
100
|
+
|
101
|
+
def temporary?
|
102
|
+
type == :stage
|
103
|
+
end
|
104
|
+
|
105
|
+
def surrogate_key
|
106
|
+
columns.values.detect { |column| column.surrogate_key }
|
107
|
+
end
|
108
|
+
|
109
|
+
def natural_keys
|
110
|
+
columns.values.select { |column| column.natural_key }
|
111
|
+
end
|
112
|
+
|
113
|
+
def defined_columns
|
114
|
+
columns.values
|
115
|
+
end
|
116
|
+
method_with_last_element :defined_columns
|
117
|
+
|
118
|
+
def unique_constraints
|
119
|
+
return [] if temporary?
|
120
|
+
unique_constraints_map.map do |_, column_names|
|
121
|
+
[column_names, short_md5(column_names)]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# TODO: Add optional USING
|
126
|
+
# TODO: Default to GIN for array columns
|
127
|
+
def index_columns
|
128
|
+
index_column_map.map do |_, column_names|
|
129
|
+
[column_names, reverse_unique_constraints_map.key?(column_names.sort), short_md5(column_names)]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def unique_columns
|
134
|
+
return {} if temporary?
|
135
|
+
columns.select { |_, column| column.unique }
|
136
|
+
end
|
137
|
+
|
138
|
+
def enum_columns
|
139
|
+
return {} if temporary?
|
140
|
+
columns.select { |_, column| column.type == :enum }
|
141
|
+
end
|
142
|
+
|
143
|
+
def sequence_columns
|
144
|
+
return {} if temporary?
|
145
|
+
columns.select { |_, column| column.reference.nil? && column.type == :sequence }
|
146
|
+
end
|
147
|
+
|
148
|
+
def reference_columns
|
149
|
+
columns.values.select { | column| column.reference }
|
150
|
+
end
|
151
|
+
|
152
|
+
def foreign_key_columns
|
153
|
+
columns.values.select { | column| column.reference && column.reference.foreign_key }
|
154
|
+
end
|
155
|
+
|
156
|
+
def insert_rows
|
157
|
+
rows.select { |row| row.insert_values.any? }
|
158
|
+
end
|
159
|
+
|
160
|
+
def aliased_rows
|
161
|
+
rows.select { |row| row.name }
|
162
|
+
end
|
163
|
+
|
164
|
+
def insert_references
|
165
|
+
references.select { |_, reference| reference.insert }
|
166
|
+
end
|
167
|
+
|
168
|
+
def reserved_columns
|
169
|
+
columns.select { |_, column| reserved_column_ids.include?(column.id) }
|
170
|
+
end
|
171
|
+
|
172
|
+
def unreserved_columns
|
173
|
+
columns.reject { |_, column| reserved_column_ids.include?(column.id) }
|
174
|
+
end
|
175
|
+
|
176
|
+
def stage_table(options = {})
|
177
|
+
selected = options[:columns] if options[:columns]
|
178
|
+
selected ||= options[:target].columns.values.map(&:compact_name) if options[:target]
|
179
|
+
selected ||= []
|
180
|
+
stage_id = [id, options[:suffix]].compact.join('_')
|
181
|
+
parent = options[:table] ? options[:table] : self
|
182
|
+
type = options[:type] ? options[:type] : :stage
|
183
|
+
@stage_tables ||= {}
|
184
|
+
@stage_tables[options] ||= parent.class.new id: stage_id, type: type, store: store, columns: stage_table_columns(parent, selected, options.fetch(:inherit, true)), references: stage_table_references(parent, selected), parent: parent, inherit: options.fetch(:inherit, true)
|
185
|
+
end
|
186
|
+
|
187
|
+
def shared_columns(other)
|
188
|
+
Hash.new { |h,k| h[k] = [] }.tap do |shared|
|
189
|
+
columns.each do |_, column|
|
190
|
+
other.columns.each do |_, other_column|
|
191
|
+
shared[column] << other_column if column.references?(other_column)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def dereference_column_name(name)
|
198
|
+
reference_name, column_name = Column::dereference_column_name(name)
|
199
|
+
if reference = references[reference_name]
|
200
|
+
if column = reference.columns[column_name]
|
201
|
+
dereference_column(column.dup, reference)
|
202
|
+
end
|
203
|
+
elsif column = columns[column_name]
|
204
|
+
column
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def dereference_column(column, reference)
|
209
|
+
column.surrogate_key = false
|
210
|
+
column.reference = reference
|
211
|
+
column
|
212
|
+
end
|
213
|
+
|
214
|
+
def reserved_column_ids
|
215
|
+
inherit ? parent.reserved_column_ids : []
|
216
|
+
end
|
217
|
+
|
218
|
+
private
|
219
|
+
|
220
|
+
def stage_table_columns(parent, selected = [], inherit = true)
|
221
|
+
selected = columns.keys if selected.empty?
|
222
|
+
{}.tap do |result|
|
223
|
+
selected.each do |name|
|
224
|
+
column = dereference_column_name(name)
|
225
|
+
next unless column
|
226
|
+
next if inherit && parent.reserved_column_ids.include?(column.id)
|
227
|
+
if column.parent == self
|
228
|
+
next if column.surrogate_key
|
229
|
+
result[name] = column
|
230
|
+
else
|
231
|
+
result[name] = column
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def stage_table_references(parent, selected = [])
|
238
|
+
selected = references.keys if selected.empty?
|
239
|
+
{}.tap do |result|
|
240
|
+
selected.each do |name|
|
241
|
+
column = dereference_column_name(name)
|
242
|
+
next unless column
|
243
|
+
next if column.parent == self
|
244
|
+
result[name] = column.reference
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def initialize_surrogate_key_column!
|
250
|
+
case type
|
251
|
+
when :table
|
252
|
+
initialize_column! id: 'id', type: :integer, surrogate_key: true
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def initialize_reference_columns!
|
257
|
+
references.map do |_, reference|
|
258
|
+
if reference.denormalize
|
259
|
+
reference.unreserved_columns.each do |_, column|
|
260
|
+
next if column.surrogate_key
|
261
|
+
next if column.ignore
|
262
|
+
initialize_column! id: column.id, type: column.type, reference: reference, default: reference.default, index: true, null: reference.null, natural_key: reference.natural_key
|
263
|
+
end
|
264
|
+
elsif reference.foreign_key
|
265
|
+
# FIXME column.reference should point to reference.surrogate_key, only allow column references to Columns
|
266
|
+
initialize_column! id: reference.foreign_key_name, type: reference.foreign_key_type, reference: reference, default: reference.default, index: true, null: reference.null, natural_key: reference.natural_key
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def initialize_column!(options = {})
|
272
|
+
column = Masamune::Schema::Column.new(options.merge(parent: self))
|
273
|
+
@columns[column.name.to_sym] = column
|
274
|
+
end
|
275
|
+
|
276
|
+
def index_column_map
|
277
|
+
@index_column_map ||= begin
|
278
|
+
map = Hash.new { |h,k| h[k] = [] }
|
279
|
+
columns.each do |_, column|
|
280
|
+
column.index.each do |index|
|
281
|
+
map[index] << column.name
|
282
|
+
end
|
283
|
+
end
|
284
|
+
Hash[map.sort_by { |k, v| v.length }]
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
def unique_constraints_map
|
289
|
+
@unique_constraints_map ||= begin
|
290
|
+
map = Hash.new { |h,k| h[k] = [] }
|
291
|
+
columns.each do |_, column|
|
292
|
+
column.unique.each do |unique|
|
293
|
+
map[unique] << column.name
|
294
|
+
end
|
295
|
+
end
|
296
|
+
Hash[map.sort_by { |k, v| v.length }]
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def reverse_unique_constraints_map
|
301
|
+
@reverse_unique_constraints_map ||= Hash[unique_constraints_map.to_a.map { |k,v| [v.sort, k] }]
|
302
|
+
end
|
303
|
+
|
304
|
+
def short_md5(*a)
|
305
|
+
Digest::MD5.hexdigest(a.join('_'))[0..6]
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|