table_saw 2.2.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/exe/table-saw +2 -1
- data/lib/table_saw.rb +5 -0
- data/lib/table_saw/configuration.rb +1 -1
- data/lib/table_saw/create_dump_file.rb +18 -16
- data/lib/table_saw/dependency_graph/add_directive.rb +1 -1
- data/lib/table_saw/dependency_graph/build.rb +1 -1
- data/lib/table_saw/dependency_graph/dump_table.rb +1 -1
- data/lib/table_saw/dependency_graph/has_many_directives.rb +3 -3
- data/lib/table_saw/formats.rb +5 -0
- data/lib/table_saw/formats/base.rb +29 -0
- data/lib/table_saw/formats/copy.rb +32 -0
- data/lib/table_saw/formats/insert.rb +29 -0
- data/lib/table_saw/information_schema.rb +0 -4
- data/lib/table_saw/queries.rb +2 -2
- data/lib/table_saw/queries/execute_insert_statement.rb +34 -0
- data/lib/table_saw/queries/prepared_insert_statement.rb +50 -0
- data/lib/table_saw/queries/serialize_sql_in_clause.rb +1 -1
- data/lib/table_saw/version.rb +1 -1
- metadata +8 -4
- data/lib/table_saw/queries/primary_keys.rb +0 -20
- data/lib/table_saw/queries/table_columns.rb +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 858ba4566ea024a132f1f674faaabecafd97938d930329f552a2bd86530e46c7
|
4
|
+
data.tar.gz: 5cada2de36a7790563ec3347a7af8b52d3e70722387b13258b11c78b33798015
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '036185f2584a299a98439068e1cef0dcbd5229b0fb81768029e92beb3f4e724865379dea00a84a169eeedddcbf99262ef7bb1fafaf0646310f8424669a79a4bd'
|
7
|
+
data.tar.gz: db613c022e74e739c9109b3c5d25ba3e12682083446df5aac6c84cdf2f4216298e55edbd618e95b5c541c614a558497a09433af6afe4db87f632d5ea5c374241
|
data/Gemfile.lock
CHANGED
data/exe/table-saw
CHANGED
@@ -24,10 +24,11 @@ class CLI < Thor
|
|
24
24
|
method_option :password, default: ENV['PGPASSWORD']
|
25
25
|
method_option :manifest, aliases: '-m', required: true
|
26
26
|
method_option :output, aliases: '-o', default: 'output.dump'
|
27
|
+
method_option :format, type: :hash, default: { 'type' => 'copy' }
|
27
28
|
def dump
|
28
29
|
TableSaw.configure(options.to_hash)
|
29
30
|
records = TableSaw::DependencyGraph::Build.new(TableSaw::Manifest.instance).call
|
30
|
-
TableSaw::CreateDumpFile.new(records, options[:output]).call
|
31
|
+
TableSaw::CreateDumpFile.new(records, output: options[:output], format: options[:format]).call
|
31
32
|
end
|
32
33
|
|
33
34
|
desc 'version', 'Print version of table-saw'
|
data/lib/table_saw.rb
CHANGED
@@ -6,6 +6,7 @@ require 'table_saw/dependency_graph'
|
|
6
6
|
require 'table_saw/information_schema'
|
7
7
|
require 'table_saw/manifest'
|
8
8
|
require 'table_saw/queries'
|
9
|
+
require 'table_saw/formats'
|
9
10
|
|
10
11
|
module TableSaw
|
11
12
|
def self.configuration
|
@@ -25,4 +26,8 @@ module TableSaw
|
|
25
26
|
def self.information_schema
|
26
27
|
@information_schema ||= TableSaw::InformationSchema.new
|
27
28
|
end
|
29
|
+
|
30
|
+
def self.schema_cache
|
31
|
+
TableSaw::Connection.adapter.schema_cache
|
32
|
+
end
|
28
33
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module TableSaw
|
4
4
|
class Configuration
|
5
|
-
attr_accessor :dbname, :host, :port, :user, :password, :manifest, :output
|
5
|
+
attr_accessor :dbname, :host, :port, :user, :password, :manifest, :output, :format
|
6
6
|
|
7
7
|
def connection
|
8
8
|
{ dbname: dbname, host: host, port: port, user: user, password: password }
|
@@ -2,11 +2,17 @@
|
|
2
2
|
|
3
3
|
module TableSaw
|
4
4
|
class CreateDumpFile
|
5
|
-
attr_reader :records, :file
|
5
|
+
attr_reader :records, :file, :format
|
6
6
|
|
7
|
-
|
7
|
+
FORMATS = {
|
8
|
+
'copy' => TableSaw::Formats::Copy,
|
9
|
+
'insert' => TableSaw::Formats::Insert
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
def initialize(records, output:, format:)
|
8
13
|
@records = records
|
9
|
-
@file =
|
14
|
+
@file = output
|
15
|
+
@format = format
|
10
16
|
end
|
11
17
|
|
12
18
|
# rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
@@ -38,27 +44,27 @@ module TableSaw
|
|
38
44
|
|
39
45
|
COMMENT
|
40
46
|
|
41
|
-
|
42
|
-
|
43
|
-
|
47
|
+
formatter = FORMATS.fetch(format.fetch('type', 'copy'), TableSaw::Formats::Copy).new(name, options: format)
|
48
|
+
|
49
|
+
Array.wrap(formatter.header).each { |line| write_to_file(line) }
|
44
50
|
|
45
51
|
TableSaw::Connection.with do |conn|
|
46
|
-
conn.copy_data "COPY (#{table.copy_statement}) TO STDOUT" do
|
52
|
+
conn.copy_data "COPY (#{table.copy_statement}) TO STDOUT", formatter.coder do
|
47
53
|
while (row = conn.get_copy_data)
|
48
|
-
write_to_file row
|
54
|
+
write_to_file formatter.dump_row(row)
|
49
55
|
end
|
50
56
|
end
|
51
57
|
end
|
52
58
|
|
53
|
-
write_to_file
|
54
|
-
write_to_file "\n"
|
59
|
+
Array.wrap(formatter.footer).each { |line| write_to_file(line) }
|
55
60
|
end
|
56
61
|
|
62
|
+
write_to_file 'COMMIT;'
|
63
|
+
write_to_file "\n"
|
64
|
+
|
57
65
|
refresh_materialized_views
|
58
66
|
restart_sequences
|
59
67
|
|
60
|
-
write_to_file 'COMMIT;'
|
61
|
-
|
62
68
|
alter_constraints_deferrability keyword: 'NOT DEFERRABLE'
|
63
69
|
end
|
64
70
|
# rubocop:enable Metrics/MethodLength,Metrics/AbcSize
|
@@ -114,9 +120,5 @@ module TableSaw
|
|
114
120
|
def write_to_file(data)
|
115
121
|
File.open(file, 'ab') { |f| f.puts(data) }
|
116
122
|
end
|
117
|
-
|
118
|
-
def quoted_columns(table)
|
119
|
-
TableSaw::Queries::TableColumns.new(table).call.map { |c| "\"#{c}\"" }.join(', ')
|
120
|
-
end
|
121
123
|
end
|
122
124
|
end
|
@@ -41,7 +41,7 @@ module TableSaw
|
|
41
41
|
def select_ids(table)
|
42
42
|
return [] unless table.partial?
|
43
43
|
|
44
|
-
TableSaw::Connection.exec(table.query).map { |row| row[TableSaw.
|
44
|
+
TableSaw::Connection.exec(table.query).map { |row| row[TableSaw.schema_cache.primary_keys(table.name)] }
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
@@ -14,7 +14,7 @@ module TableSaw
|
|
14
14
|
valid_associations.map do |table, column|
|
15
15
|
TableSaw::DependencyGraph::AddDirective.new(
|
16
16
|
table,
|
17
|
-
ids: query_result(table, column).map { |r| r[TableSaw.
|
17
|
+
ids: query_result(table, column).map { |r| r[TableSaw.schema_cache.primary_keys(table)] },
|
18
18
|
partial: directive.partial?
|
19
19
|
)
|
20
20
|
end
|
@@ -29,7 +29,7 @@ module TableSaw
|
|
29
29
|
# rubocop:disable Metrics/AbcSize
|
30
30
|
def valid_associations
|
31
31
|
associations.select do |table, _column|
|
32
|
-
next false if directive.partial? &&
|
32
|
+
next false if directive.partial? && TableSaw.schema_cache.primary_keys(table).nil?
|
33
33
|
next true if directive.has_many.include?(table)
|
34
34
|
|
35
35
|
manifest.has_many.fetch(directive.table_name, []).include?(table)
|
@@ -43,7 +43,7 @@ module TableSaw
|
|
43
43
|
TableSaw::Connection.exec(
|
44
44
|
format(
|
45
45
|
'select %{primary_key} from %{table} where %{clause}',
|
46
|
-
primary_key: TableSaw.
|
46
|
+
primary_key: TableSaw.schema_cache.primary_keys(table), table: table,
|
47
47
|
clause: TableSaw::Queries::SerializeSqlInClause.new(table, column, directive.ids).call
|
48
48
|
)
|
49
49
|
)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TableSaw
|
4
|
+
module Formats
|
5
|
+
class Base
|
6
|
+
attr_reader :table_name, :options
|
7
|
+
|
8
|
+
def initialize(table_name, options: {})
|
9
|
+
@table_name = table_name
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def header
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
|
17
|
+
def footer
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
|
21
|
+
def dump_row(_row)
|
22
|
+
raise NotImplementedError
|
23
|
+
end
|
24
|
+
|
25
|
+
def coder
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TableSaw
|
4
|
+
module Formats
|
5
|
+
class Copy < TableSaw::Formats::Base
|
6
|
+
def header
|
7
|
+
"COPY #{table_name} (#{quoted_columns}) FROM STDIN;"
|
8
|
+
end
|
9
|
+
|
10
|
+
def footer
|
11
|
+
['\.', "\n"]
|
12
|
+
end
|
13
|
+
|
14
|
+
def dump_row(row)
|
15
|
+
row
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def quoted_columns
|
21
|
+
TableSaw.schema_cache.columns_hash(table_name)
|
22
|
+
.each_key
|
23
|
+
.map { |name| connection.quote_column_name(name) }
|
24
|
+
.join(', ')
|
25
|
+
end
|
26
|
+
|
27
|
+
def connection
|
28
|
+
TableSaw.schema_cache.connection
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TableSaw
|
4
|
+
module Formats
|
5
|
+
class Insert < TableSaw::Formats::Base
|
6
|
+
def header
|
7
|
+
prepared_statement.sql
|
8
|
+
end
|
9
|
+
|
10
|
+
def footer
|
11
|
+
"\n"
|
12
|
+
end
|
13
|
+
|
14
|
+
def dump_row(row)
|
15
|
+
TableSaw::Queries::ExecuteInsertStatement.new(prepared_statement, row).call
|
16
|
+
end
|
17
|
+
|
18
|
+
def coder
|
19
|
+
PG::TextDecoder::CopyRow.new
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def prepared_statement
|
25
|
+
@prepared_statement ||= TableSaw::Queries::PreparedInsertStatement.new(table_name, options: options).call
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/table_saw/queries.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'table_saw/queries/execute_insert_statement'
|
3
4
|
require 'table_saw/queries/foreign_key_relationships'
|
4
5
|
require 'table_saw/queries/materialized_views'
|
5
|
-
require 'table_saw/queries/
|
6
|
+
require 'table_saw/queries/prepared_insert_statement'
|
6
7
|
require 'table_saw/queries/serial_sequences'
|
7
8
|
require 'table_saw/queries/serialize_sql_in_clause'
|
8
|
-
require 'table_saw/queries/table_columns'
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TableSaw
|
4
|
+
module Queries
|
5
|
+
class ExecuteInsertStatement
|
6
|
+
attr_reader :statement, :row
|
7
|
+
|
8
|
+
def initialize(statement, row)
|
9
|
+
@statement = statement
|
10
|
+
@row = row
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
"EXECUTE #{statement.name}(#{values});"
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def values
|
20
|
+
TableSaw.schema_cache.columns(statement.table_name).zip(row)
|
21
|
+
.map { |column, value| connection.quote(connection.type_cast_from_column(column, value)) }
|
22
|
+
.join(', ')
|
23
|
+
end
|
24
|
+
|
25
|
+
def schema_cache
|
26
|
+
TableSaw.schema_cache
|
27
|
+
end
|
28
|
+
|
29
|
+
def connection
|
30
|
+
schema_cache.connection
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TableSaw
|
4
|
+
module Queries
|
5
|
+
class PreparedInsertStatement
|
6
|
+
attr_reader :table_name, :options
|
7
|
+
|
8
|
+
Statement = Struct.new(:name, :table_name, :sql)
|
9
|
+
|
10
|
+
def initialize(table_name, options: {})
|
11
|
+
@table_name = table_name
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
Statement.new(name, table_name, sql)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def name
|
22
|
+
"#{table_name}_insert_plan"
|
23
|
+
end
|
24
|
+
|
25
|
+
def sql
|
26
|
+
[prepare_statement, conflict_statement].compact.join(' ') + ';'
|
27
|
+
end
|
28
|
+
|
29
|
+
def column_types
|
30
|
+
TableSaw.schema_cache.columns(table_name).map(&:sql_type).join(', ')
|
31
|
+
end
|
32
|
+
|
33
|
+
def values_clause
|
34
|
+
1.upto(TableSaw.schema_cache.columns(table_name).size).map { |i| "$#{i}" }.join(', ')
|
35
|
+
end
|
36
|
+
|
37
|
+
def prepare_statement
|
38
|
+
<<~SQL.squish
|
39
|
+
PREPARE #{name} (#{column_types}) AS INSERT INTO #{table_name} VALUES (#{values_clause})
|
40
|
+
SQL
|
41
|
+
end
|
42
|
+
|
43
|
+
def conflict_statement
|
44
|
+
return unless options['ignore_conflict'] == 'true'
|
45
|
+
|
46
|
+
'ON CONFLICT DO NOTHING'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/table_saw/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: table_saw
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hamed Asghari
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-12-
|
11
|
+
date: 2019-12-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -211,15 +211,19 @@ files:
|
|
211
211
|
- lib/table_saw/dependency_graph/build.rb
|
212
212
|
- lib/table_saw/dependency_graph/dump_table.rb
|
213
213
|
- lib/table_saw/dependency_graph/has_many_directives.rb
|
214
|
+
- lib/table_saw/formats.rb
|
215
|
+
- lib/table_saw/formats/base.rb
|
216
|
+
- lib/table_saw/formats/copy.rb
|
217
|
+
- lib/table_saw/formats/insert.rb
|
214
218
|
- lib/table_saw/information_schema.rb
|
215
219
|
- lib/table_saw/manifest.rb
|
216
220
|
- lib/table_saw/queries.rb
|
221
|
+
- lib/table_saw/queries/execute_insert_statement.rb
|
217
222
|
- lib/table_saw/queries/foreign_key_relationships.rb
|
218
223
|
- lib/table_saw/queries/materialized_views.rb
|
219
|
-
- lib/table_saw/queries/
|
224
|
+
- lib/table_saw/queries/prepared_insert_statement.rb
|
220
225
|
- lib/table_saw/queries/serial_sequences.rb
|
221
226
|
- lib/table_saw/queries/serialize_sql_in_clause.rb
|
222
|
-
- lib/table_saw/queries/table_columns.rb
|
223
227
|
- lib/table_saw/version.rb
|
224
228
|
- table_saw.gemspec
|
225
229
|
homepage: https://github.com/hasghari/table_saw
|
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module TableSaw
|
4
|
-
module Queries
|
5
|
-
class PrimaryKeys
|
6
|
-
QUERY = <<~SQL
|
7
|
-
select tc.table_name, kcu.column_name
|
8
|
-
from information_schema.table_constraints tc
|
9
|
-
join information_schema.key_column_usage kcu using (constraint_schema, constraint_name)
|
10
|
-
where tc.constraint_type = 'PRIMARY KEY';
|
11
|
-
SQL
|
12
|
-
|
13
|
-
def call
|
14
|
-
TableSaw::Connection.exec(QUERY).each_with_object({}) do |row, memo|
|
15
|
-
memo[row['table_name']] = row['column_name']
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module TableSaw
|
4
|
-
module Queries
|
5
|
-
class TableColumns
|
6
|
-
QUERY = <<~SQL
|
7
|
-
select attname as colname
|
8
|
-
from pg_catalog.pg_attribute
|
9
|
-
where
|
10
|
-
attrelid = $1::regclass
|
11
|
-
and attnum > 0
|
12
|
-
and attisdropped = false
|
13
|
-
order by attnum
|
14
|
-
SQL
|
15
|
-
|
16
|
-
attr_reader :table
|
17
|
-
|
18
|
-
def initialize(table)
|
19
|
-
@table = table
|
20
|
-
end
|
21
|
-
|
22
|
-
def call
|
23
|
-
TableSaw::Connection.with { |conn| conn.exec_params(QUERY, [table]) }.map { |r| r['colname'] }
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|