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 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