table_saw 2.2.0 → 2.3.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/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
|