table_saw 2.5.0 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal"
6
+ gem "activerecord", "~> 6.1", "< 6.2"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,150 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ table_saw (2.9.0)
5
+ activerecord (>= 5.2)
6
+ pg
7
+ thor
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ actionpack (6.1.0)
13
+ actionview (= 6.1.0)
14
+ activesupport (= 6.1.0)
15
+ rack (~> 2.0, >= 2.0.9)
16
+ rack-test (>= 0.6.3)
17
+ rails-dom-testing (~> 2.0)
18
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
19
+ actionview (6.1.0)
20
+ activesupport (= 6.1.0)
21
+ builder (~> 3.1)
22
+ erubi (~> 1.4)
23
+ rails-dom-testing (~> 2.0)
24
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
25
+ activemodel (6.1.0)
26
+ activesupport (= 6.1.0)
27
+ activerecord (6.1.0)
28
+ activemodel (= 6.1.0)
29
+ activesupport (= 6.1.0)
30
+ activesupport (6.1.0)
31
+ concurrent-ruby (~> 1.0, >= 1.0.2)
32
+ i18n (>= 1.6, < 2)
33
+ minitest (>= 5.1)
34
+ tzinfo (~> 2.0)
35
+ zeitwerk (~> 2.3)
36
+ appraisal (2.3.0)
37
+ bundler
38
+ rake
39
+ thor (>= 0.14.0)
40
+ ast (2.4.1)
41
+ builder (3.2.4)
42
+ coderay (1.1.3)
43
+ combustion (1.3.1)
44
+ activesupport (>= 3.0.0)
45
+ railties (>= 3.0.0)
46
+ thor (>= 0.14.6)
47
+ concurrent-ruby (1.1.7)
48
+ crass (1.0.6)
49
+ database_cleaner (1.8.5)
50
+ diff-lcs (1.4.4)
51
+ docile (1.3.4)
52
+ erubi (1.10.0)
53
+ i18n (1.8.5)
54
+ concurrent-ruby (~> 1.0)
55
+ loofah (2.8.0)
56
+ crass (~> 1.0.2)
57
+ nokogiri (>= 1.5.9)
58
+ method_source (1.0.0)
59
+ mini_portile2 (2.4.0)
60
+ minitest (5.14.2)
61
+ nokogiri (1.10.10)
62
+ mini_portile2 (~> 2.4.0)
63
+ parallel (1.20.1)
64
+ parser (3.0.0.0)
65
+ ast (~> 2.4.1)
66
+ pg (1.2.3)
67
+ pry (0.13.1)
68
+ coderay (~> 1.1)
69
+ method_source (~> 1.0)
70
+ rack (2.2.3)
71
+ rack-test (1.1.0)
72
+ rack (>= 1.0, < 3)
73
+ rails-dom-testing (2.0.3)
74
+ activesupport (>= 4.2.0)
75
+ nokogiri (>= 1.6)
76
+ rails-html-sanitizer (1.3.0)
77
+ loofah (~> 2.3)
78
+ railties (6.1.0)
79
+ actionpack (= 6.1.0)
80
+ activesupport (= 6.1.0)
81
+ method_source
82
+ rake (>= 0.8.7)
83
+ thor (~> 1.0)
84
+ rainbow (3.0.0)
85
+ rake (13.0.3)
86
+ regexp_parser (2.0.3)
87
+ rexml (3.2.4)
88
+ rspec (3.10.0)
89
+ rspec-core (~> 3.10.0)
90
+ rspec-expectations (~> 3.10.0)
91
+ rspec-mocks (~> 3.10.0)
92
+ rspec-core (3.10.1)
93
+ rspec-support (~> 3.10.0)
94
+ rspec-expectations (3.10.1)
95
+ diff-lcs (>= 1.2.0, < 2.0)
96
+ rspec-support (~> 3.10.0)
97
+ rspec-mocks (3.10.1)
98
+ diff-lcs (>= 1.2.0, < 2.0)
99
+ rspec-support (~> 3.10.0)
100
+ rspec-support (3.10.1)
101
+ rubocop (0.93.1)
102
+ parallel (~> 1.10)
103
+ parser (>= 2.7.1.5)
104
+ rainbow (>= 2.2.2, < 4.0)
105
+ regexp_parser (>= 1.8)
106
+ rexml
107
+ rubocop-ast (>= 0.6.0)
108
+ ruby-progressbar (~> 1.7)
109
+ unicode-display_width (>= 1.4.0, < 2.0)
110
+ rubocop-ast (1.3.0)
111
+ parser (>= 2.7.1.5)
112
+ rubocop-rspec (1.44.1)
113
+ rubocop (~> 0.87)
114
+ rubocop-ast (>= 0.7.1)
115
+ ruby-progressbar (1.11.0)
116
+ scenic (1.5.4)
117
+ activerecord (>= 4.0.0)
118
+ railties (>= 4.0.0)
119
+ simplecov (0.20.0)
120
+ docile (~> 1.1)
121
+ simplecov-html (~> 0.11)
122
+ simplecov_json_formatter (~> 0.1)
123
+ simplecov-html (0.12.3)
124
+ simplecov_json_formatter (0.1.2)
125
+ thor (1.0.1)
126
+ tzinfo (2.0.4)
127
+ concurrent-ruby (~> 1.0)
128
+ unicode-display_width (1.7.0)
129
+ zeitwerk (2.4.2)
130
+
131
+ PLATFORMS
132
+ x86_64-darwin-20
133
+ x86_64-linux
134
+
135
+ DEPENDENCIES
136
+ activerecord (~> 6.1, < 6.2)
137
+ appraisal
138
+ bundler (~> 2.0)
139
+ combustion (~> 1.3)
140
+ database_cleaner (~> 1.7)
141
+ pry
142
+ rake (~> 13.0)
143
+ rspec (~> 3.0)
144
+ rubocop-rspec (~> 1.33)
145
+ scenic (~> 1.5)
146
+ simplecov (~> 0.16)
147
+ table_saw!
148
+
149
+ BUNDLED WITH
150
+ 2.2.3
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TableSaw
4
+ class Associations
5
+ attr_reader :manifest
6
+
7
+ def initialize(manifest)
8
+ @manifest = manifest
9
+ end
10
+
11
+ def belongs_to
12
+ @belongs_to ||= foreign_keys.each_with_object(Hash.new { |h, k| h[k] = Set.new }) do |fk, memo|
13
+ memo[fk.from_table].add(fk)
14
+ end
15
+ end
16
+
17
+ def has_many
18
+ @has_many ||= foreign_keys.each_with_object(Hash.new { |h, k| h[k] = Set.new }) do |fk, memo|
19
+ memo[fk.to_table].add(fk)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def foreign_keys
26
+ @foreign_keys ||= manifest_foreign_keys + schema_foreign_keys
27
+ end
28
+
29
+ def manifest_foreign_keys
30
+ manifest.foreign_keys.map do |fk|
31
+ TableSaw::ForeignKey.new(from_table: fk['from_table'], from_column: fk['from_column'],
32
+ to_table: fk['to_table'], to_column: fk['to_column'])
33
+ end
34
+ end
35
+
36
+ def schema_foreign_keys
37
+ TableSaw.information_schema.foreign_key_relationships.foreign_keys
38
+ end
39
+ end
40
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module TableSaw
4
4
  class Configuration
5
+ attr_writer :variables
5
6
  attr_accessor :dbname, :host, :port, :user, :password, :manifest, :output, :format
6
7
 
7
8
  def connection
@@ -10,12 +11,16 @@ module TableSaw
10
11
 
11
12
  def url=(value)
12
13
  URI.parse(value).tap do |uri|
13
- self.dbname = uri.path[1..-1]
14
+ self.dbname = uri.path[1..]
14
15
  self.host = uri.host
15
16
  self.port = uri.port
16
17
  self.user = uri.user
17
18
  self.password = uri.password
18
19
  end
19
20
  end
21
+
22
+ def variables
23
+ @variables || {}
24
+ end
20
25
  end
21
26
  end
@@ -49,7 +49,7 @@ module TableSaw
49
49
 
50
50
  formatter = FORMATS.fetch(format.fetch('type', 'copy'), TableSaw::Formats::Copy).new(name, options: format)
51
51
 
52
- Array.wrap(formatter.header).each { |line| write_to_file(line) }
52
+ Array(formatter.header).each { |line| write_to_file(line) }
53
53
 
54
54
  TableSaw::Connection.with do |conn|
55
55
  conn.copy_data "COPY (#{table.copy_statement}) TO STDOUT", formatter.coder do
@@ -59,7 +59,7 @@ module TableSaw
59
59
  end
60
60
  end
61
61
 
62
- Array.wrap(formatter.footer).each { |line| write_to_file(line) }
62
+ Array(formatter.footer).each { |line| write_to_file(line) }
63
63
  end
64
64
 
65
65
  write_to_file 'COMMIT;'
@@ -3,42 +3,46 @@
3
3
  module TableSaw
4
4
  module DependencyGraph
5
5
  class BelongsToDirectives
6
- attr_reader :directive
6
+ QUERY = <<~SQL
7
+ select distinct %{column} from %{table_name} where %{clause} and %{column} is not null and %{polymorphic}
8
+ SQL
7
9
 
8
- def initialize(directive)
10
+ attr_reader :manifest, :directive
11
+
12
+ def initialize(manifest, directive)
13
+ @manifest = manifest
9
14
  @directive = directive
10
15
  end
11
16
 
12
17
  def call
13
- associations.map do |from_column, to_table|
14
- TableSaw::DependencyGraph::AddDirective.new(to_table, ids: ids[from_column], partial: directive.partial?)
18
+ associations.map do |fk|
19
+ TableSaw::DependencyGraph::AddDirective.new(fk.to_table, ids: ids[fk.column.primary_key],
20
+ partial: directive.partial?)
15
21
  end
16
22
  end
17
23
 
18
24
  private
19
25
 
20
26
  def associations
21
- TableSaw.information_schema.belongs_to.fetch(directive.table_name, {})
27
+ manifest.associations.belongs_to.fetch(directive.table_name, Set.new)
22
28
  end
23
29
 
24
30
  def ids
25
- @ids ||= associations.each_key.each_with_object({}) do |column, memo|
26
- memo[column] = query_result(column).map { |row| row[column] }
31
+ @ids ||= associations.each_with_object({}) do |fk, memo|
32
+ memo[fk.column.primary_key] = query_result(fk).map { |row| row[fk.column.primary_key] }
27
33
  end
28
34
  end
29
35
 
30
36
  # rubocop:disable Metrics/AbcSize
31
- def query_result(column)
37
+ def query_result(foreign_key)
32
38
  return [] unless directive.selectable?
33
39
 
34
40
  TableSaw::Connection.exec(
35
- format(
36
- 'select distinct %{column} from %{table_name} where %{clause} and %{column} is not null',
37
- primary_key: directive.primary_key, column: column, table_name: directive.table_name,
38
- clause: TableSaw::Queries::SerializeSqlInClause.new(directive.table_name,
39
- directive.primary_key,
40
- directive.ids).call
41
- )
41
+ format(QUERY, column: foreign_key.column.primary_key, table_name: directive.table_name,
42
+ clause: TableSaw::Queries::SerializeSqlInClause.new(directive.table_name,
43
+ directive.primary_key,
44
+ directive.ids).call,
45
+ polymorphic: foreign_key.type_condition)
42
46
  )
43
47
  end
44
48
  # rubocop:enable Metrics/AbcSize
@@ -31,7 +31,7 @@ module TableSaw
31
31
  private
32
32
 
33
33
  def fetch_belongs_to(directive)
34
- TableSaw::DependencyGraph::BelongsToDirectives.new(directive).call
34
+ TableSaw::DependencyGraph::BelongsToDirectives.new(manifest, directive).call
35
35
  end
36
36
 
37
37
  def fetch_has_many(directive)
@@ -3,6 +3,10 @@
3
3
  module TableSaw
4
4
  module DependencyGraph
5
5
  class HasManyDirectives
6
+ QUERY = <<~SQL
7
+ select %{primary_key} from %{table} where %{clause} and %{polymorphic}
8
+ SQL
9
+
6
10
  attr_reader :manifest, :directive
7
11
 
8
12
  def initialize(manifest, directive)
@@ -11,10 +15,10 @@ module TableSaw
11
15
  end
12
16
 
13
17
  def call
14
- valid_associations.map do |table, column|
18
+ valid_associations.map do |fk|
15
19
  TableSaw::DependencyGraph::AddDirective.new(
16
- table,
17
- ids: query_result(table, column).map { |r| r[TableSaw.schema_cache.primary_keys(table)] },
20
+ fk.from_table,
21
+ ids: query_result(fk).map { |r| r[TableSaw.schema_cache.primary_keys(fk.from_table)] },
18
22
  partial: directive.partial?
19
23
  )
20
24
  end
@@ -23,31 +27,32 @@ module TableSaw
23
27
  private
24
28
 
25
29
  def associations
26
- TableSaw.information_schema.has_many.fetch(directive.table_name, [])
30
+ manifest.associations.has_many.fetch(directive.table_name, Set.new)
27
31
  end
28
32
 
29
33
  # rubocop:disable Metrics/AbcSize
30
34
  def valid_associations
31
- associations.select do |table, _column|
32
- next false if directive.partial? && TableSaw.schema_cache.primary_keys(table).nil?
33
- next true if directive.has_many.include?(table)
35
+ associations.select do |fk|
36
+ next false if directive.partial? && TableSaw.schema_cache.primary_keys(fk.from_table).nil?
37
+ next true if directive.has_many.include?(fk.from_table)
34
38
 
35
- manifest.has_many.fetch(directive.table_name, []).include?(table)
39
+ manifest.has_many.fetch(directive.table_name, []).include?(fk.from_table)
36
40
  end
37
41
  end
38
- # rubocop:enable Metrics/AbcSize
39
42
 
40
- def query_result(table, column)
43
+ def query_result(foreign_key)
41
44
  return [] unless directive.selectable?
42
45
 
43
46
  TableSaw::Connection.exec(
44
- format(
45
- 'select %{primary_key} from %{table} where %{clause}',
46
- primary_key: TableSaw.schema_cache.primary_keys(table), table: table,
47
- clause: TableSaw::Queries::SerializeSqlInClause.new(table, column, directive.ids).call
48
- )
47
+ format(QUERY, primary_key: TableSaw.schema_cache.primary_keys(foreign_key.from_table),
48
+ table: foreign_key.from_table,
49
+ clause: TableSaw::Queries::SerializeSqlInClause.new(foreign_key.from_table,
50
+ foreign_key.column.primary_key,
51
+ directive.ids).call,
52
+ polymorphic: foreign_key.type_condition)
49
53
  )
50
54
  end
55
+ # rubocop:enable Metrics/AbcSize
51
56
  end
52
57
  end
53
58
  end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TableSaw
4
+ class ForeignKey
5
+ class Column
6
+ REGEX = /(\w+)(?::(\w+)\((\w+)\))?/.freeze
7
+
8
+ attr_reader :value
9
+
10
+ def initialize(value)
11
+ @value = value
12
+ end
13
+
14
+ def primary_key
15
+ value[REGEX, 1]
16
+ end
17
+
18
+ def type_condition
19
+ polymorphic? ? "#{type_column} = '#{type_value}'" : '1 = 1'
20
+ end
21
+
22
+ private
23
+
24
+ def type_column
25
+ value[REGEX, 2]
26
+ end
27
+
28
+ def type_value
29
+ value[REGEX, 3]
30
+ end
31
+
32
+ def polymorphic?
33
+ !(type_column.nil? || type_value.nil?)
34
+ end
35
+ end
36
+
37
+ attr_reader :name, :from_table, :from_column, :to_table, :to_column
38
+
39
+ def initialize(from_table:, from_column:, to_table:, to_column:, name: nil)
40
+ @name = name
41
+ @from_table = from_table
42
+ @from_column = from_column
43
+ @to_table = to_table
44
+ @to_column = to_column
45
+ end
46
+
47
+ def type_condition
48
+ @type_condition ||= column.type_condition
49
+ end
50
+
51
+ def column
52
+ @column ||= Column.new(from_column)
53
+ end
54
+
55
+ def eql?(other)
56
+ hash == other.hash
57
+ end
58
+
59
+ def hash
60
+ [from_table, from_column, to_table, to_column].hash
61
+ end
62
+ end
63
+ end