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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0fe94c6f653348bda1f6a069925e802d271e251d09be82b0e817d758296e5bf6
4
- data.tar.gz: 9a6ea705f4cde2aa3c3750c28cc66ccf7a28358e113bf930151c9877e9cacb35
3
+ metadata.gz: 858ba4566ea024a132f1f674faaabecafd97938d930329f552a2bd86530e46c7
4
+ data.tar.gz: 5cada2de36a7790563ec3347a7af8b52d3e70722387b13258b11c78b33798015
5
5
  SHA512:
6
- metadata.gz: d01a1f66e7c418e8ab3f01c1ab65e6cd4f3439e00edaa9f0e39cfa144e5cbe2d8d1a7e32cf359ab228bc40bef722e5d218619dbc8d462f1115bd0e4922ba430f
7
- data.tar.gz: f02f30fb42c96cf3a4b87a155c6a727ce703d9788e78df6aeba01f20e524309c18741c3a87ed83c3ef42983b0f3bfa7211357c934f9edbc0b64cacbf2fd715ad
6
+ metadata.gz: '036185f2584a299a98439068e1cef0dcbd5229b0fb81768029e92beb3f4e724865379dea00a84a169eeedddcbf99262ef7bb1fafaf0646310f8424669a79a4bd'
7
+ data.tar.gz: db613c022e74e739c9109b3c5d25ba3e12682083446df5aac6c84cdf2f4216298e55edbd618e95b5c541c614a558497a09433af6afe4db87f632d5ea5c374241
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- table_saw (2.2.0)
4
+ table_saw (2.3.0)
5
5
  activerecord (>= 5.2)
6
6
  pg
7
7
  thor
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
- def initialize(records, file = 'psql.dump')
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 = 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
- write_to_file <<~SQL
42
- COPY #{name} (#{quoted_columns(name)}) FROM STDIN;
43
- SQL
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
@@ -24,7 +24,7 @@ module TableSaw
24
24
  end
25
25
 
26
26
  def primary_key
27
- TableSaw.information_schema.primary_keys[table_name]
27
+ TableSaw.schema_cache.primary_keys(table_name)
28
28
  end
29
29
  end
30
30
  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.information_schema.primary_keys[table.name]] }
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
@@ -39,7 +39,7 @@ module TableSaw
39
39
  end
40
40
 
41
41
  def primary_key
42
- TableSaw.information_schema.primary_keys[name]
42
+ TableSaw.schema_cache.primary_keys(name)
43
43
  end
44
44
  end
45
45
  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.information_schema.primary_keys[table]] },
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? && !TableSaw.information_schema.primary_keys.key?(table)
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.information_schema.primary_keys[table], table: table,
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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'table_saw/formats/base'
4
+ require 'table_saw/formats/copy'
5
+ require 'table_saw/formats/insert'
@@ -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
@@ -8,10 +8,6 @@ module TableSaw
8
8
  end
9
9
  end
10
10
 
11
- def primary_keys
12
- @primary_keys ||= TableSaw::Queries::PrimaryKeys.new.call
13
- end
14
-
15
11
  private
16
12
 
17
13
  def foreign_key_relationships
@@ -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/primary_keys'
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
@@ -18,7 +18,7 @@ module TableSaw
18
18
  private
19
19
 
20
20
  def db_column
21
- @db_column ||= connection.columns(table_name).find { |c| c.name == column }
21
+ TableSaw.schema_cache.columns_hash(table_name)[column]
22
22
  end
23
23
 
24
24
  def serialized_values
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TableSaw
4
- VERSION = '2.2.0'
4
+ VERSION = '2.3.0'
5
5
  end
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.2.0
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-07 00:00:00.000000000 Z
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/primary_keys.rb
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