table_saw 2.5.0 → 2.9.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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +54 -0
- data/.rubocop.yml +27 -3
- data/.ruby-version +1 -1
- data/.tool-versions +1 -0
- data/Appraisals +9 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +78 -66
- data/README.md +1 -1
- data/exe/table-saw +2 -0
- data/gemfiles/activerecord_6.0.0.gemfile +8 -0
- data/gemfiles/activerecord_6.0.0.gemfile.lock +151 -0
- data/gemfiles/activerecord_6.1.0.gemfile +8 -0
- data/gemfiles/activerecord_6.1.0.gemfile.lock +150 -0
- data/lib/table_saw/associations.rb +40 -0
- data/lib/table_saw/configuration.rb +6 -1
- data/lib/table_saw/create_dump_file.rb +2 -2
- data/lib/table_saw/dependency_graph/belongs_to_directives.rb +19 -15
- data/lib/table_saw/dependency_graph/dump_table.rb +1 -1
- data/lib/table_saw/dependency_graph/has_many_directives.rb +20 -15
- data/lib/table_saw/foreign_key.rb +63 -0
- data/lib/table_saw/information_schema.rb +2 -6
- data/lib/table_saw/manifest.rb +11 -1
- data/lib/table_saw/queries/execute_insert_statement.rb +6 -5
- data/lib/table_saw/queries/foreign_key_relationships.rb +11 -12
- data/lib/table_saw/queries/prepared_insert_statement.rb +1 -1
- data/lib/table_saw/queries/serialize_sql_in_clause.rb +1 -1
- data/lib/table_saw/version.rb +1 -1
- data/table_saw.gemspec +4 -2
- metadata +21 -13
- data/.travis.yml +0 -24
@@ -0,0 +1,150 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ..
|
3
|
+
specs:
|
4
|
+
table_saw (2.9.0)
|
5
|
+
activerecord (>= 5.2)
|
6
|
+
pg
|
7
|
+
thor
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
actionpack (6.1.0)
|
13
|
+
actionview (= 6.1.0)
|
14
|
+
activesupport (= 6.1.0)
|
15
|
+
rack (~> 2.0, >= 2.0.9)
|
16
|
+
rack-test (>= 0.6.3)
|
17
|
+
rails-dom-testing (~> 2.0)
|
18
|
+
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
19
|
+
actionview (6.1.0)
|
20
|
+
activesupport (= 6.1.0)
|
21
|
+
builder (~> 3.1)
|
22
|
+
erubi (~> 1.4)
|
23
|
+
rails-dom-testing (~> 2.0)
|
24
|
+
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
25
|
+
activemodel (6.1.0)
|
26
|
+
activesupport (= 6.1.0)
|
27
|
+
activerecord (6.1.0)
|
28
|
+
activemodel (= 6.1.0)
|
29
|
+
activesupport (= 6.1.0)
|
30
|
+
activesupport (6.1.0)
|
31
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
32
|
+
i18n (>= 1.6, < 2)
|
33
|
+
minitest (>= 5.1)
|
34
|
+
tzinfo (~> 2.0)
|
35
|
+
zeitwerk (~> 2.3)
|
36
|
+
appraisal (2.3.0)
|
37
|
+
bundler
|
38
|
+
rake
|
39
|
+
thor (>= 0.14.0)
|
40
|
+
ast (2.4.1)
|
41
|
+
builder (3.2.4)
|
42
|
+
coderay (1.1.3)
|
43
|
+
combustion (1.3.1)
|
44
|
+
activesupport (>= 3.0.0)
|
45
|
+
railties (>= 3.0.0)
|
46
|
+
thor (>= 0.14.6)
|
47
|
+
concurrent-ruby (1.1.7)
|
48
|
+
crass (1.0.6)
|
49
|
+
database_cleaner (1.8.5)
|
50
|
+
diff-lcs (1.4.4)
|
51
|
+
docile (1.3.4)
|
52
|
+
erubi (1.10.0)
|
53
|
+
i18n (1.8.5)
|
54
|
+
concurrent-ruby (~> 1.0)
|
55
|
+
loofah (2.8.0)
|
56
|
+
crass (~> 1.0.2)
|
57
|
+
nokogiri (>= 1.5.9)
|
58
|
+
method_source (1.0.0)
|
59
|
+
mini_portile2 (2.4.0)
|
60
|
+
minitest (5.14.2)
|
61
|
+
nokogiri (1.10.10)
|
62
|
+
mini_portile2 (~> 2.4.0)
|
63
|
+
parallel (1.20.1)
|
64
|
+
parser (3.0.0.0)
|
65
|
+
ast (~> 2.4.1)
|
66
|
+
pg (1.2.3)
|
67
|
+
pry (0.13.1)
|
68
|
+
coderay (~> 1.1)
|
69
|
+
method_source (~> 1.0)
|
70
|
+
rack (2.2.3)
|
71
|
+
rack-test (1.1.0)
|
72
|
+
rack (>= 1.0, < 3)
|
73
|
+
rails-dom-testing (2.0.3)
|
74
|
+
activesupport (>= 4.2.0)
|
75
|
+
nokogiri (>= 1.6)
|
76
|
+
rails-html-sanitizer (1.3.0)
|
77
|
+
loofah (~> 2.3)
|
78
|
+
railties (6.1.0)
|
79
|
+
actionpack (= 6.1.0)
|
80
|
+
activesupport (= 6.1.0)
|
81
|
+
method_source
|
82
|
+
rake (>= 0.8.7)
|
83
|
+
thor (~> 1.0)
|
84
|
+
rainbow (3.0.0)
|
85
|
+
rake (13.0.3)
|
86
|
+
regexp_parser (2.0.3)
|
87
|
+
rexml (3.2.4)
|
88
|
+
rspec (3.10.0)
|
89
|
+
rspec-core (~> 3.10.0)
|
90
|
+
rspec-expectations (~> 3.10.0)
|
91
|
+
rspec-mocks (~> 3.10.0)
|
92
|
+
rspec-core (3.10.1)
|
93
|
+
rspec-support (~> 3.10.0)
|
94
|
+
rspec-expectations (3.10.1)
|
95
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
96
|
+
rspec-support (~> 3.10.0)
|
97
|
+
rspec-mocks (3.10.1)
|
98
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
99
|
+
rspec-support (~> 3.10.0)
|
100
|
+
rspec-support (3.10.1)
|
101
|
+
rubocop (0.93.1)
|
102
|
+
parallel (~> 1.10)
|
103
|
+
parser (>= 2.7.1.5)
|
104
|
+
rainbow (>= 2.2.2, < 4.0)
|
105
|
+
regexp_parser (>= 1.8)
|
106
|
+
rexml
|
107
|
+
rubocop-ast (>= 0.6.0)
|
108
|
+
ruby-progressbar (~> 1.7)
|
109
|
+
unicode-display_width (>= 1.4.0, < 2.0)
|
110
|
+
rubocop-ast (1.3.0)
|
111
|
+
parser (>= 2.7.1.5)
|
112
|
+
rubocop-rspec (1.44.1)
|
113
|
+
rubocop (~> 0.87)
|
114
|
+
rubocop-ast (>= 0.7.1)
|
115
|
+
ruby-progressbar (1.11.0)
|
116
|
+
scenic (1.5.4)
|
117
|
+
activerecord (>= 4.0.0)
|
118
|
+
railties (>= 4.0.0)
|
119
|
+
simplecov (0.20.0)
|
120
|
+
docile (~> 1.1)
|
121
|
+
simplecov-html (~> 0.11)
|
122
|
+
simplecov_json_formatter (~> 0.1)
|
123
|
+
simplecov-html (0.12.3)
|
124
|
+
simplecov_json_formatter (0.1.2)
|
125
|
+
thor (1.0.1)
|
126
|
+
tzinfo (2.0.4)
|
127
|
+
concurrent-ruby (~> 1.0)
|
128
|
+
unicode-display_width (1.7.0)
|
129
|
+
zeitwerk (2.4.2)
|
130
|
+
|
131
|
+
PLATFORMS
|
132
|
+
x86_64-darwin-20
|
133
|
+
x86_64-linux
|
134
|
+
|
135
|
+
DEPENDENCIES
|
136
|
+
activerecord (~> 6.1, < 6.2)
|
137
|
+
appraisal
|
138
|
+
bundler (~> 2.0)
|
139
|
+
combustion (~> 1.3)
|
140
|
+
database_cleaner (~> 1.7)
|
141
|
+
pry
|
142
|
+
rake (~> 13.0)
|
143
|
+
rspec (~> 3.0)
|
144
|
+
rubocop-rspec (~> 1.33)
|
145
|
+
scenic (~> 1.5)
|
146
|
+
simplecov (~> 0.16)
|
147
|
+
table_saw!
|
148
|
+
|
149
|
+
BUNDLED WITH
|
150
|
+
2.2.3
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TableSaw
|
4
|
+
class Associations
|
5
|
+
attr_reader :manifest
|
6
|
+
|
7
|
+
def initialize(manifest)
|
8
|
+
@manifest = manifest
|
9
|
+
end
|
10
|
+
|
11
|
+
def belongs_to
|
12
|
+
@belongs_to ||= foreign_keys.each_with_object(Hash.new { |h, k| h[k] = Set.new }) do |fk, memo|
|
13
|
+
memo[fk.from_table].add(fk)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def has_many
|
18
|
+
@has_many ||= foreign_keys.each_with_object(Hash.new { |h, k| h[k] = Set.new }) do |fk, memo|
|
19
|
+
memo[fk.to_table].add(fk)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def foreign_keys
|
26
|
+
@foreign_keys ||= manifest_foreign_keys + schema_foreign_keys
|
27
|
+
end
|
28
|
+
|
29
|
+
def manifest_foreign_keys
|
30
|
+
manifest.foreign_keys.map do |fk|
|
31
|
+
TableSaw::ForeignKey.new(from_table: fk['from_table'], from_column: fk['from_column'],
|
32
|
+
to_table: fk['to_table'], to_column: fk['to_column'])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def schema_foreign_keys
|
37
|
+
TableSaw.information_schema.foreign_key_relationships.foreign_keys
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module TableSaw
|
4
4
|
class Configuration
|
5
|
+
attr_writer :variables
|
5
6
|
attr_accessor :dbname, :host, :port, :user, :password, :manifest, :output, :format
|
6
7
|
|
7
8
|
def connection
|
@@ -10,12 +11,16 @@ module TableSaw
|
|
10
11
|
|
11
12
|
def url=(value)
|
12
13
|
URI.parse(value).tap do |uri|
|
13
|
-
self.dbname = uri.path[1
|
14
|
+
self.dbname = uri.path[1..]
|
14
15
|
self.host = uri.host
|
15
16
|
self.port = uri.port
|
16
17
|
self.user = uri.user
|
17
18
|
self.password = uri.password
|
18
19
|
end
|
19
20
|
end
|
21
|
+
|
22
|
+
def variables
|
23
|
+
@variables || {}
|
24
|
+
end
|
20
25
|
end
|
21
26
|
end
|
@@ -49,7 +49,7 @@ module TableSaw
|
|
49
49
|
|
50
50
|
formatter = FORMATS.fetch(format.fetch('type', 'copy'), TableSaw::Formats::Copy).new(name, options: format)
|
51
51
|
|
52
|
-
Array
|
52
|
+
Array(formatter.header).each { |line| write_to_file(line) }
|
53
53
|
|
54
54
|
TableSaw::Connection.with do |conn|
|
55
55
|
conn.copy_data "COPY (#{table.copy_statement}) TO STDOUT", formatter.coder do
|
@@ -59,7 +59,7 @@ module TableSaw
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
-
Array
|
62
|
+
Array(formatter.footer).each { |line| write_to_file(line) }
|
63
63
|
end
|
64
64
|
|
65
65
|
write_to_file 'COMMIT;'
|
@@ -3,42 +3,46 @@
|
|
3
3
|
module TableSaw
|
4
4
|
module DependencyGraph
|
5
5
|
class BelongsToDirectives
|
6
|
-
|
6
|
+
QUERY = <<~SQL
|
7
|
+
select distinct %{column} from %{table_name} where %{clause} and %{column} is not null and %{polymorphic}
|
8
|
+
SQL
|
7
9
|
|
8
|
-
|
10
|
+
attr_reader :manifest, :directive
|
11
|
+
|
12
|
+
def initialize(manifest, directive)
|
13
|
+
@manifest = manifest
|
9
14
|
@directive = directive
|
10
15
|
end
|
11
16
|
|
12
17
|
def call
|
13
|
-
associations.map do |
|
14
|
-
TableSaw::DependencyGraph::AddDirective.new(to_table, ids: ids[
|
18
|
+
associations.map do |fk|
|
19
|
+
TableSaw::DependencyGraph::AddDirective.new(fk.to_table, ids: ids[fk.column.primary_key],
|
20
|
+
partial: directive.partial?)
|
15
21
|
end
|
16
22
|
end
|
17
23
|
|
18
24
|
private
|
19
25
|
|
20
26
|
def associations
|
21
|
-
|
27
|
+
manifest.associations.belongs_to.fetch(directive.table_name, Set.new)
|
22
28
|
end
|
23
29
|
|
24
30
|
def ids
|
25
|
-
@ids ||= associations.
|
26
|
-
memo[column] = query_result(
|
31
|
+
@ids ||= associations.each_with_object({}) do |fk, memo|
|
32
|
+
memo[fk.column.primary_key] = query_result(fk).map { |row| row[fk.column.primary_key] }
|
27
33
|
end
|
28
34
|
end
|
29
35
|
|
30
36
|
# rubocop:disable Metrics/AbcSize
|
31
|
-
def query_result(
|
37
|
+
def query_result(foreign_key)
|
32
38
|
return [] unless directive.selectable?
|
33
39
|
|
34
40
|
TableSaw::Connection.exec(
|
35
|
-
format(
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
directive.ids).call
|
41
|
-
)
|
41
|
+
format(QUERY, column: foreign_key.column.primary_key, table_name: directive.table_name,
|
42
|
+
clause: TableSaw::Queries::SerializeSqlInClause.new(directive.table_name,
|
43
|
+
directive.primary_key,
|
44
|
+
directive.ids).call,
|
45
|
+
polymorphic: foreign_key.type_condition)
|
42
46
|
)
|
43
47
|
end
|
44
48
|
# rubocop:enable Metrics/AbcSize
|
@@ -3,6 +3,10 @@
|
|
3
3
|
module TableSaw
|
4
4
|
module DependencyGraph
|
5
5
|
class HasManyDirectives
|
6
|
+
QUERY = <<~SQL
|
7
|
+
select %{primary_key} from %{table} where %{clause} and %{polymorphic}
|
8
|
+
SQL
|
9
|
+
|
6
10
|
attr_reader :manifest, :directive
|
7
11
|
|
8
12
|
def initialize(manifest, directive)
|
@@ -11,10 +15,10 @@ module TableSaw
|
|
11
15
|
end
|
12
16
|
|
13
17
|
def call
|
14
|
-
valid_associations.map do |
|
18
|
+
valid_associations.map do |fk|
|
15
19
|
TableSaw::DependencyGraph::AddDirective.new(
|
16
|
-
|
17
|
-
ids: query_result(
|
20
|
+
fk.from_table,
|
21
|
+
ids: query_result(fk).map { |r| r[TableSaw.schema_cache.primary_keys(fk.from_table)] },
|
18
22
|
partial: directive.partial?
|
19
23
|
)
|
20
24
|
end
|
@@ -23,31 +27,32 @@ module TableSaw
|
|
23
27
|
private
|
24
28
|
|
25
29
|
def associations
|
26
|
-
|
30
|
+
manifest.associations.has_many.fetch(directive.table_name, Set.new)
|
27
31
|
end
|
28
32
|
|
29
33
|
# rubocop:disable Metrics/AbcSize
|
30
34
|
def valid_associations
|
31
|
-
associations.select do |
|
32
|
-
next false if directive.partial? && TableSaw.schema_cache.primary_keys(
|
33
|
-
next true if directive.has_many.include?(
|
35
|
+
associations.select do |fk|
|
36
|
+
next false if directive.partial? && TableSaw.schema_cache.primary_keys(fk.from_table).nil?
|
37
|
+
next true if directive.has_many.include?(fk.from_table)
|
34
38
|
|
35
|
-
manifest.has_many.fetch(directive.table_name, []).include?(
|
39
|
+
manifest.has_many.fetch(directive.table_name, []).include?(fk.from_table)
|
36
40
|
end
|
37
41
|
end
|
38
|
-
# rubocop:enable Metrics/AbcSize
|
39
42
|
|
40
|
-
def query_result(
|
43
|
+
def query_result(foreign_key)
|
41
44
|
return [] unless directive.selectable?
|
42
45
|
|
43
46
|
TableSaw::Connection.exec(
|
44
|
-
format(
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
47
|
+
format(QUERY, primary_key: TableSaw.schema_cache.primary_keys(foreign_key.from_table),
|
48
|
+
table: foreign_key.from_table,
|
49
|
+
clause: TableSaw::Queries::SerializeSqlInClause.new(foreign_key.from_table,
|
50
|
+
foreign_key.column.primary_key,
|
51
|
+
directive.ids).call,
|
52
|
+
polymorphic: foreign_key.type_condition)
|
49
53
|
)
|
50
54
|
end
|
55
|
+
# rubocop:enable Metrics/AbcSize
|
51
56
|
end
|
52
57
|
end
|
53
58
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TableSaw
|
4
|
+
class ForeignKey
|
5
|
+
class Column
|
6
|
+
REGEX = /(\w+)(?::(\w+)\((\w+)\))?/.freeze
|
7
|
+
|
8
|
+
attr_reader :value
|
9
|
+
|
10
|
+
def initialize(value)
|
11
|
+
@value = value
|
12
|
+
end
|
13
|
+
|
14
|
+
def primary_key
|
15
|
+
value[REGEX, 1]
|
16
|
+
end
|
17
|
+
|
18
|
+
def type_condition
|
19
|
+
polymorphic? ? "#{type_column} = '#{type_value}'" : '1 = 1'
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def type_column
|
25
|
+
value[REGEX, 2]
|
26
|
+
end
|
27
|
+
|
28
|
+
def type_value
|
29
|
+
value[REGEX, 3]
|
30
|
+
end
|
31
|
+
|
32
|
+
def polymorphic?
|
33
|
+
!(type_column.nil? || type_value.nil?)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :name, :from_table, :from_column, :to_table, :to_column
|
38
|
+
|
39
|
+
def initialize(from_table:, from_column:, to_table:, to_column:, name: nil)
|
40
|
+
@name = name
|
41
|
+
@from_table = from_table
|
42
|
+
@from_column = from_column
|
43
|
+
@to_table = to_table
|
44
|
+
@to_column = to_column
|
45
|
+
end
|
46
|
+
|
47
|
+
def type_condition
|
48
|
+
@type_condition ||= column.type_condition
|
49
|
+
end
|
50
|
+
|
51
|
+
def column
|
52
|
+
@column ||= Column.new(from_column)
|
53
|
+
end
|
54
|
+
|
55
|
+
def eql?(other)
|
56
|
+
hash == other.hash
|
57
|
+
end
|
58
|
+
|
59
|
+
def hash
|
60
|
+
[from_table, from_column, to_table, to_column].hash
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|