table_saw 2.5.0 → 2.9.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.
@@ -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