table_saw 0.3.0 → 0.4.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: 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