table_saw 0.3.0 → 0.4.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: ce7f117d5db32cc1d9617f4f09126a60636519a1b8445ce2c65575120deac9db
4
- data.tar.gz: 212a3f010918b397329ed3510c831898370865a60ee8949948b60f70f05f6a6d
3
+ metadata.gz: 1339fbe2ad8b61d13d84595da082e4bd8886f9ecb56e1c8ce55ba9b5d9d33304
4
+ data.tar.gz: 79e448b1ee7d8f77a019b491ed5cbe11e4b5ca353721643211543813a779b856
5
5
  SHA512:
6
- metadata.gz: e11664a36c6c5a649d4f79ed4018b24b9681d68053a0187b5927582d24dbf1f62fa706511bd493fb19ff583316083f1fbac17173efdfec6feb13f8432188cd54
7
- data.tar.gz: 6375f829abd5d21a7aee6f36891d1e0a1f2778dbf59eab9c42cde7817d22a34f0831cf733a9053d9a429149338cae782ee64029288197ec0414afc084450339a
6
+ metadata.gz: 49bf13e6be2584987b29755ca760fced988ff01f7f8ad362a2cb35122f83da8de7b1de37c0f02d32bee9dee733b0d9c517d515dc5134cb00a4e08c26f57af9c1
7
+ data.tar.gz: 290706ba80434334c9d4af235e84972018bad3d2ef009eb85ee288ce6e7739fb669e1ba428e8060e6c1509910a0cdb0bffe05f26fdeee562772b69d41c1fe687
data/.rubocop.yml CHANGED
@@ -3,6 +3,9 @@ require: rubocop-rspec
3
3
  AllCops:
4
4
  TargetRubyVersion: 2.6
5
5
 
6
+ Layout/MultilineMethodCallIndentation:
7
+ EnforcedStyle: indented_relative_to_receiver
8
+
6
9
  Metrics/LineLength:
7
10
  Max: 120
8
11
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- table_saw (0.3.0)
4
+ table_saw (0.4.0)
5
5
  connection_pool
6
6
  pg
7
7
  thor
@@ -36,6 +36,7 @@ GEM
36
36
  arel (9.0.0)
37
37
  ast (2.4.0)
38
38
  builder (3.2.3)
39
+ coderay (1.1.2)
39
40
  combustion (1.1.0)
40
41
  activesupport (>= 3.0.0)
41
42
  railties (>= 3.0.0)
@@ -63,6 +64,9 @@ GEM
63
64
  parser (2.6.3.0)
64
65
  ast (~> 2.4.0)
65
66
  pg (1.1.4)
67
+ pry (0.12.2)
68
+ coderay (~> 1.1.0)
69
+ method_source (~> 0.9.0)
66
70
  rack (2.0.7)
67
71
  rack-test (1.1.0)
68
72
  rack (>= 1.0, < 3)
@@ -124,6 +128,7 @@ DEPENDENCIES
124
128
  bundler (~> 2.0)
125
129
  combustion (~> 1.1)
126
130
  database_cleaner (~> 1.7)
131
+ pry
127
132
  rake (~> 10.0)
128
133
  rspec (~> 3.0)
129
134
  rubocop-rspec (~> 1.33)
data/exe/table-saw CHANGED
@@ -5,9 +5,7 @@ lib = File.expand_path('../lib', __dir__)
5
5
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
6
 
7
7
  require 'table_saw'
8
- require 'table_saw/build_dependency_graph'
9
8
  require 'table_saw/create_dump_file'
10
- require 'table_saw/manifest'
11
9
  require 'thor'
12
10
 
13
11
  class CLI < Thor
@@ -27,7 +25,7 @@ class CLI < Thor
27
25
  method_option :output, aliases: '-o', default: 'output.dump'
28
26
  def dump
29
27
  TableSaw.configure(options.to_hash)
30
- records = TableSaw::BuildDependencyGraph.new(TableSaw::Manifest.instance).call
28
+ records = TableSaw::DependencyGraph::Build.new(TableSaw::Manifest.instance).call
31
29
  TableSaw::CreateDumpFile.new(records, options[:output]).call
32
30
  end
33
31
  end
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'table_saw/queries/table_columns'
4
- require 'table_saw/queries/materialized_views'
5
-
6
3
  module TableSaw
7
4
  class CreateDumpFile
8
5
  attr_reader :records, :file
@@ -28,7 +25,7 @@ module TableSaw
28
25
  SET search_path = public, pg_catalog;
29
26
  SQL
30
27
 
31
- records.each do |name, ids|
28
+ records.each do |name, table|
32
29
  write_to_file <<~COMMENT
33
30
  --
34
31
  -- Data for Name: #{name}; Type: TABLE DATA
@@ -41,7 +38,7 @@ module TableSaw
41
38
  SQL
42
39
 
43
40
  TableSaw::Connection.with do |conn|
44
- conn.copy_data "COPY (select * from #{name} where id in (#{ids.join(',')})) TO STDOUT" do
41
+ conn.copy_data "COPY (#{table.copy_statement}) TO STDOUT" do
45
42
  while (row = conn.get_copy_data)
46
43
  write_to_file row
47
44
  end
@@ -53,6 +50,7 @@ module TableSaw
53
50
  end
54
51
 
55
52
  refresh_materialized_views
53
+ restart_sequences
56
54
 
57
55
  write_to_file 'COMMIT;'
58
56
  end
@@ -62,8 +60,20 @@ module TableSaw
62
60
 
63
61
  def refresh_materialized_views
64
62
  TableSaw::Queries::MaterializedViews.new.call.each do |view|
65
- write_to_file "REFRESH MATERIALIZED VIEW #{view};"
63
+ write_to_file "refresh materialized view #{view};"
64
+ end
65
+
66
+ write_to_file "\n"
67
+ end
68
+
69
+ def restart_sequences
70
+ records.each_key do |table|
71
+ write_to_file <<~SQL
72
+ select setval(pg_get_serial_sequence('#{table}', 'id'), (select max(id) from #{table}), true);
73
+ SQL
66
74
  end
75
+
76
+ write_to_file "\n"
67
77
  end
68
78
 
69
79
  def write_to_file(data)
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TableSaw
4
+ module DependencyGraph
5
+ class AddDirective
6
+ attr_reader :table_name, :partial
7
+ attr_accessor :ids
8
+
9
+ def initialize(table_name, ids: [], partial: true)
10
+ @table_name = table_name
11
+ @ids = ids
12
+ @partial = partial
13
+ end
14
+
15
+ alias partial? partial
16
+
17
+ def selectable?
18
+ partial? && Array(ids).size.positive?
19
+ end
20
+
21
+ def queryable?
22
+ !partial || selectable?
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TableSaw
4
+ module DependencyGraph
5
+ class BelongsToDirectives
6
+ attr_reader :context, :directive
7
+
8
+ def initialize(context, directive)
9
+ @context = context
10
+ @directive = directive
11
+ end
12
+
13
+ def call
14
+ associations.map do |from_column, to_table|
15
+ TableSaw::DependencyGraph::AddDirective.new(to_table, ids: ids[from_column].to_a, partial: directive.partial?)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def associations
22
+ context.belongs_to.fetch(directive.table_name, {})
23
+ end
24
+
25
+ def ids
26
+ @ids ||= query_result.each_with_object(Hash.new { |h, k| h[k] = Set.new }) do |row, memo|
27
+ associations.each_key { |key| memo[key].add row[key] unless row[key].nil? }
28
+ end
29
+ end
30
+
31
+ def query_result
32
+ return [] unless directive.selectable?
33
+
34
+ context.perform_query(
35
+ format('select %{columns} from %{table_name} where id in (%{ids})',
36
+ columns: associations.keys.join(','), table_name: directive.table_name, ids: directive.ids.join(','))
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TableSaw
4
+ module DependencyGraph
5
+ class Build
6
+ attr_reader :manifest, :records
7
+
8
+ def initialize(manifest)
9
+ @manifest = manifest
10
+ @records = {}
11
+ end
12
+
13
+ def call
14
+ manifest.tables.values.sort_by { |t| t.partial? ? 1 : 0 }.each do |table|
15
+ add TableSaw::DependencyGraph::AddDirective.new(table.name, ids: select_ids(table), partial: table.partial?)
16
+ end
17
+
18
+ records
19
+ end
20
+
21
+ private
22
+
23
+ def add(directive)
24
+ return [] unless directive.queryable?
25
+
26
+ directives(directive).select(&:queryable?).each(&method(:add))
27
+ end
28
+
29
+ def directives(dir)
30
+ record = records[dir.table_name]
31
+
32
+ if record
33
+ dir.partial? ? record.fetch_associations(dir) : []
34
+ else
35
+ TableSaw::DependencyGraph::DumpTable.new(context: context, name: dir.table_name, partial: dir.partial?)
36
+ .tap { |table| records[dir.table_name] = table }.fetch_associations(dir)
37
+ end
38
+ end
39
+
40
+ def context
41
+ @context ||= TableSaw::DependencyGraph::Context.new(manifest)
42
+ end
43
+
44
+ def select_ids(table)
45
+ return [] unless table.partial?
46
+
47
+ context.perform_query(table.query).map { |row| row['id'] }
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TableSaw
4
+ module DependencyGraph
5
+ class Context
6
+ attr_reader :manifest
7
+
8
+ def initialize(manifest)
9
+ @manifest = manifest
10
+ end
11
+
12
+ def belongs_to
13
+ foreign_key_relationships.belongs_to
14
+ end
15
+
16
+ # rubocop:disable Naming/PredicateName
17
+ def has_many
18
+ foreign_key_relationships.has_many
19
+ end
20
+
21
+ def has_many_mapping
22
+ @has_many_mapping ||= manifest.tables.transform_values(&:has_many)
23
+ end
24
+
25
+ # rubocop:enable Naming/PredicateName
26
+
27
+ def foreign_key_relationships
28
+ @foreign_key_relationships ||= TableSaw::Queries::ForeignKeyRelationships.new
29
+ end
30
+
31
+ def tables_with_no_ids
32
+ @tables_with_no_ids ||= TableSaw::Queries::NoIdTables.new.call
33
+ end
34
+
35
+ def perform_query(sql)
36
+ TableSaw::Connection.with do |conn|
37
+ conn.exec(sql)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TableSaw
4
+ module DependencyGraph
5
+ class DumpTable
6
+ attr_reader :context, :name, :partial, :ids
7
+
8
+ def initialize(context:, name:, partial: true)
9
+ @context = context
10
+ @name = name
11
+ @partial = partial
12
+ @ids = Set.new
13
+ end
14
+
15
+ def copy_statement
16
+ if partial
17
+ "select * from #{name} where id in (#{ids.to_a.join(',')})"
18
+ else
19
+ "select * from #{name}"
20
+ end
21
+ end
22
+
23
+ def fetch_associations(directive)
24
+ directive.ids = directive.ids - ids.to_a
25
+ ids.merge(directive.ids)
26
+ fetch_belongs_to(directive) + fetch_has_many(directive)
27
+ end
28
+
29
+ private
30
+
31
+ def fetch_belongs_to(directive)
32
+ TableSaw::DependencyGraph::BelongsToDirectives.new(context, directive).call
33
+ end
34
+
35
+ def fetch_has_many(directive)
36
+ TableSaw::DependencyGraph::HasManyDirectives.new(context, directive).call
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TableSaw
4
+ module DependencyGraph
5
+ class HasManyDirectives
6
+ attr_reader :context, :directive
7
+
8
+ def initialize(context, directive)
9
+ @context = context
10
+ @directive = directive
11
+ end
12
+
13
+ def call
14
+ valid_associations.map do |table, column|
15
+ TableSaw::DependencyGraph::AddDirective.new(
16
+ table, ids: query_result(table, column).map { |r| r['id'] }, partial: directive.partial?
17
+ )
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def associations
24
+ context.has_many.fetch(directive.table_name, [])
25
+ end
26
+
27
+ def valid_associations
28
+ associations.select do |table, _column|
29
+ next false if directive.partial? && context.tables_with_no_ids.include?(table)
30
+
31
+ context.has_many_mapping.fetch(directive.table_name, []).include?(table)
32
+ end
33
+ end
34
+
35
+ def query_result(table, column)
36
+ return [] unless directive.selectable?
37
+
38
+ context.perform_query(
39
+ format('select id from %{table} where %{column} in (%{ids})',
40
+ table: table, column: column, ids: directive.ids.join(','))
41
+ )
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'table_saw/dependency_graph/add_directive'
4
+ require 'table_saw/dependency_graph/belongs_to_directives'
5
+ require 'table_saw/dependency_graph/build'
6
+ require 'table_saw/dependency_graph/context'
7
+ require 'table_saw/dependency_graph/dump_table'
8
+ require 'table_saw/dependency_graph/has_many_directives'
@@ -31,6 +31,10 @@ module TableSaw
31
31
  config.fetch('has_many', [])
32
32
  end
33
33
  # rubocop:enable Naming/PredicateName
34
+
35
+ def partial?
36
+ config.key?('query')
37
+ end
34
38
  end
35
39
 
36
40
  def self.instance
@@ -46,7 +50,7 @@ module TableSaw
46
50
  end
47
51
 
48
52
  def variables
49
- config['variables']
53
+ config.fetch('variables', {})
50
54
  end
51
55
 
52
56
  def tables
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'table_saw/connection'
4
-
5
3
  module TableSaw
6
4
  module Queries
7
5
  class ForeignKeyRelationships
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'table_saw/connection'
4
-
5
3
  module TableSaw
6
4
  module Queries
7
5
  class MaterializedViews
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'table_saw/connection'
4
-
5
3
  module TableSaw
6
4
  module Queries
7
5
  class TableColumns
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'table_saw/queries/foreign_key_relationships'
4
+ require 'table_saw/queries/materialized_views'
5
+ require 'table_saw/queries/no_id_tables'
6
+ require 'table_saw/queries/table_columns'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TableSaw
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.0'
5
5
  end
data/lib/table_saw.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'table_saw/version'
4
3
  require 'table_saw/configuration'
4
+ require 'table_saw/connection'
5
+ require 'table_saw/dependency_graph'
6
+ require 'table_saw/manifest'
7
+ require 'table_saw/queries'
5
8
 
6
9
  module TableSaw
7
10
  def self.configuration
data/table_saw.gemspec CHANGED
@@ -31,6 +31,7 @@ Gem::Specification.new do |spec|
31
31
  spec.add_development_dependency 'bundler', '~> 2.0'
32
32
  spec.add_development_dependency 'combustion', '~> 1.1'
33
33
  spec.add_development_dependency 'database_cleaner', '~> 1.7'
34
+ spec.add_development_dependency 'pry'
34
35
  spec.add_development_dependency 'rake', '~> 10.0'
35
36
  spec.add_development_dependency 'rspec', '~> 3.0'
36
37
  spec.add_development_dependency 'rubocop-rspec', '~> 1.33'
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: 0.3.0
4
+ version: 0.4.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-06-11 00:00:00.000000000 Z
11
+ date: 2019-06-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: connection_pool
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '1.7'
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: rake
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -202,11 +216,18 @@ files:
202
216
  - bin/setup
203
217
  - exe/table-saw
204
218
  - lib/table_saw.rb
205
- - lib/table_saw/build_dependency_graph.rb
206
219
  - lib/table_saw/configuration.rb
207
220
  - lib/table_saw/connection.rb
208
221
  - lib/table_saw/create_dump_file.rb
222
+ - lib/table_saw/dependency_graph.rb
223
+ - lib/table_saw/dependency_graph/add_directive.rb
224
+ - lib/table_saw/dependency_graph/belongs_to_directives.rb
225
+ - lib/table_saw/dependency_graph/build.rb
226
+ - lib/table_saw/dependency_graph/context.rb
227
+ - lib/table_saw/dependency_graph/dump_table.rb
228
+ - lib/table_saw/dependency_graph/has_many_directives.rb
209
229
  - lib/table_saw/manifest.rb
230
+ - lib/table_saw/queries.rb
210
231
  - lib/table_saw/queries/foreign_key_relationships.rb
211
232
  - lib/table_saw/queries/materialized_views.rb
212
233
  - lib/table_saw/queries/no_id_tables.rb
@@ -1,97 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'table_saw/connection'
4
- require 'table_saw/queries/foreign_key_relationships'
5
- require 'table_saw/queries/no_id_tables'
6
-
7
- module TableSaw
8
- class BuildDependencyGraph
9
- attr_reader :manifest, :records
10
-
11
- def initialize(manifest)
12
- @manifest = manifest
13
- @records = Hash.new { |h, k| h[k] = [] }
14
- end
15
-
16
- def call
17
- manifest.tables.each_value do |table|
18
- add(table.name, perform_query(table.query).map { |row| row['id'] })
19
- end
20
-
21
- records
22
- end
23
-
24
- def add(table_name, ids)
25
- return if ids.empty?
26
-
27
- ids_to_add = ids - records[table_name]
28
- return if ids_to_add.empty?
29
-
30
- records[table_name].concat ids_to_add
31
- fetch_belongs_to_associations(table_name, ids_to_add)
32
- fetch_has_many_associations(table_name, ids_to_add)
33
- end
34
-
35
- # rubocop:disable Metrics/AbcSize
36
- def fetch_belongs_to_associations(table_name, ids)
37
- associations = belongs_to[table_name]
38
- return if associations.empty?
39
-
40
- rows = perform_query(
41
- format('select %{columns} from %{table_name} where id in (%{ids})',
42
- columns: associations.keys.join(','), table_name: table_name, ids: ids.join(','))
43
- )
44
-
45
- values = rows.each_with_object(Hash.new { |h, k| h[k] = Set.new }) do |row, memo|
46
- associations.each_key { |key| memo[key].add row[key] unless row[key].nil? }
47
- end
48
-
49
- associations.each { |from_column, to_table| add to_table, values[from_column].to_a }
50
- end
51
- # rubocop:enable Metrics/AbcSize
52
-
53
- def fetch_has_many_associations(table_name, ids)
54
- has_many.fetch(table_name, []).each do |table, column|
55
- next if tables_with_no_ids.include?(table)
56
- next unless has_many_mapping.fetch(table_name, []).include?(table)
57
-
58
- rows = perform_query(
59
- format('select id from %{table} where %{column} in (%{ids})',
60
- table: table, column: column, ids: ids.join(','))
61
- )
62
-
63
- add(table, rows.map { |row| row['id'] })
64
- end
65
- end
66
-
67
- private
68
-
69
- def perform_query(sql)
70
- TableSaw::Connection.with do |conn|
71
- conn.exec(sql)
72
- end
73
- end
74
-
75
- def belongs_to
76
- foreign_key_relationships.belongs_to
77
- end
78
-
79
- # rubocop:disable Naming/PredicateName
80
- def has_many
81
- foreign_key_relationships.has_many
82
- end
83
-
84
- def has_many_mapping
85
- @has_many_mapping ||= manifest.tables.transform_values(&:has_many)
86
- end
87
- # rubocop:enable Naming/PredicateName
88
-
89
- def foreign_key_relationships
90
- @foreign_key_relationships ||= TableSaw::Queries::ForeignKeyRelationships.new
91
- end
92
-
93
- def tables_with_no_ids
94
- @tables_with_no_ids ||= TableSaw::Queries::NoIdTables.new.call
95
- end
96
- end
97
- end