table_saw 2.10.0 → 3.0.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: 2523b019080dccb116fed85c38ecb0a629cd6f8630abd65a1a30e2214c6a71b3
4
- data.tar.gz: 03e53e17ef16f72e976772bc027ca4698ba8eaa7bf4498a2fae5ab7e7dfd3f7d
3
+ metadata.gz: 5e8e8f428d4b3d28ddec794d07a0d052e6280bfd96ee0b58735b3bcefa5a7bee
4
+ data.tar.gz: 64249bc2ae252e9b361c32b376f1679837ac1fd82bde70b11eda9fad66050a00
5
5
  SHA512:
6
- metadata.gz: ab502f4b52e5e5214cca044d4ca450b9c885e63595423161e4ad130f85da6a5eb28fca008c77e49d70b675448ebe54a63bf6dfba465db6f47572b5d9b7fddbf3
7
- data.tar.gz: 63a25f77f34251c801ba0f21aa5649988e763c1e8516dc4c1d3fc7976c2bd97c5bbc50d4fe6085b24032780ea7c93203d27c856c1304d681f6d2065a9a60f2f3
6
+ metadata.gz: 11b993bf2a9f723783b3f67648af15b794b807678db845b6c57b1ac8e5b57762ac41a3c1530696f5f3f99d6a7649061a22a88267aab23fb4e5cf158533f014e6
7
+ data.tar.gz: 84eb90d309a2dc4a8ddd04c4898c748f81f6c2f62223780b9e5d272913d2e990c840880e49e17dbe8832726b0eb6ea38f7f21970a44fd49ebd85d3b7e12b91e6
@@ -10,11 +10,12 @@ jobs:
10
10
  strategy:
11
11
  matrix:
12
12
  ruby:
13
- - '2.7.2'
14
- - '3.0.0'
13
+ - '3.0.5'
14
+ - '3.1.4'
15
+ - '3.2.2'
15
16
  activerecord:
16
- - '6.0.0'
17
17
  - '6.1.0'
18
+ - '7.0.0'
18
19
 
19
20
  env:
20
21
  BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/activerecord_${{ matrix.activerecord }}.gemfile
data/.rubocop.yml CHANGED
@@ -2,7 +2,7 @@ require: rubocop-rspec
2
2
 
3
3
  AllCops:
4
4
  NewCops: enable
5
- TargetRubyVersion: 2.6
5
+ TargetRubyVersion: 3.0
6
6
  Exclude:
7
7
  - gemfiles/*
8
8
 
@@ -34,6 +34,9 @@ Style/EmptyMethod:
34
34
  Style/ExponentialNotation:
35
35
  Enabled: true
36
36
 
37
+ Style/FetchEnvVar:
38
+ Enabled: false
39
+
37
40
  Style/FormatStringToken:
38
41
  EnforcedStyle: template
39
42
 
data/.tool-versions CHANGED
@@ -1 +1 @@
1
- ruby 3.0.2
1
+ ruby 3.2.2
data/Appraisals CHANGED
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- appraise 'activerecord-6.0.0' do
4
- gem 'activerecord', '~> 6.0', '< 6.1'
5
- end
6
-
7
3
  appraise 'activerecord-6.1.0' do
8
4
  gem 'activerecord', '~> 6.1', '< 6.2'
9
5
  end
6
+
7
+ appraise 'activerecord-7.0.0' do
8
+ gem 'activerecord', '~> 7.0', '< 7.1'
9
+ end
data/Gemfile CHANGED
@@ -6,3 +6,12 @@ source 'https://rubygems.org'
6
6
  gemspec
7
7
 
8
8
  gem 'appraisal'
9
+ gem 'bundler', '~> 2.0'
10
+ gem 'combustion', '~> 1.3'
11
+ gem 'database_cleaner', '~> 2'
12
+ gem 'pry'
13
+ gem 'rake', '13.0.3'
14
+ gem 'rspec', '~> 3.0'
15
+ gem 'rubocop-rspec', '~> 2.3'
16
+ gem 'scenic', '~> 1.5'
17
+ gem 'simplecov', '~> 0.16'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- table_saw (2.10.0)
4
+ table_saw (3.0.0)
5
5
  activerecord (>= 5.2)
6
6
  pg
7
7
  thor
@@ -9,131 +9,138 @@ PATH
9
9
  GEM
10
10
  remote: https://rubygems.org/
11
11
  specs:
12
- actionpack (6.1.4.1)
13
- actionview (= 6.1.4.1)
14
- activesupport (= 6.1.4.1)
15
- rack (~> 2.0, >= 2.0.9)
12
+ actionpack (7.0.5)
13
+ actionview (= 7.0.5)
14
+ activesupport (= 7.0.5)
15
+ rack (~> 2.0, >= 2.2.4)
16
16
  rack-test (>= 0.6.3)
17
17
  rails-dom-testing (~> 2.0)
18
18
  rails-html-sanitizer (~> 1.0, >= 1.2.0)
19
- actionview (6.1.4.1)
20
- activesupport (= 6.1.4.1)
19
+ actionview (7.0.5)
20
+ activesupport (= 7.0.5)
21
21
  builder (~> 3.1)
22
22
  erubi (~> 1.4)
23
23
  rails-dom-testing (~> 2.0)
24
24
  rails-html-sanitizer (~> 1.1, >= 1.2.0)
25
- activemodel (6.1.4.1)
26
- activesupport (= 6.1.4.1)
27
- activerecord (6.1.4.1)
28
- activemodel (= 6.1.4.1)
29
- activesupport (= 6.1.4.1)
30
- activesupport (6.1.4.1)
25
+ activemodel (7.0.5)
26
+ activesupport (= 7.0.5)
27
+ activerecord (7.0.5)
28
+ activemodel (= 7.0.5)
29
+ activesupport (= 7.0.5)
30
+ activesupport (7.0.5)
31
31
  concurrent-ruby (~> 1.0, >= 1.0.2)
32
32
  i18n (>= 1.6, < 2)
33
33
  minitest (>= 5.1)
34
34
  tzinfo (~> 2.0)
35
- zeitwerk (~> 2.3)
36
- appraisal (2.4.0)
35
+ appraisal (2.4.1)
37
36
  bundler
38
37
  rake
39
38
  thor (>= 0.14.0)
40
39
  ast (2.4.2)
41
40
  builder (3.2.4)
42
41
  coderay (1.1.3)
43
- combustion (1.3.1)
42
+ combustion (1.3.7)
44
43
  activesupport (>= 3.0.0)
45
44
  railties (>= 3.0.0)
46
45
  thor (>= 0.14.6)
47
- concurrent-ruby (1.1.9)
46
+ concurrent-ruby (1.2.2)
48
47
  crass (1.0.6)
49
- database_cleaner (2.0.1)
50
- database_cleaner-active_record (~> 2.0.0)
51
- database_cleaner-active_record (2.0.1)
48
+ database_cleaner (2.0.2)
49
+ database_cleaner-active_record (>= 2, < 3)
50
+ database_cleaner-active_record (2.1.0)
52
51
  activerecord (>= 5.a)
53
52
  database_cleaner-core (~> 2.0.0)
54
53
  database_cleaner-core (2.0.1)
55
- diff-lcs (1.4.4)
54
+ diff-lcs (1.5.0)
56
55
  docile (1.4.0)
57
- erubi (1.10.0)
58
- i18n (1.8.10)
56
+ erubi (1.12.0)
57
+ i18n (1.13.0)
59
58
  concurrent-ruby (~> 1.0)
60
- loofah (2.12.0)
59
+ json (2.6.3)
60
+ loofah (2.21.3)
61
61
  crass (~> 1.0.2)
62
- nokogiri (>= 1.5.9)
62
+ nokogiri (>= 1.12.0)
63
63
  method_source (1.0.0)
64
- mini_portile2 (2.6.1)
65
- minitest (5.14.4)
66
- nokogiri (1.12.5)
67
- mini_portile2 (~> 2.6.1)
64
+ mini_portile2 (2.8.2)
65
+ minitest (5.18.0)
66
+ nokogiri (1.15.2)
67
+ mini_portile2 (~> 2.8.2)
68
68
  racc (~> 1.4)
69
- parallel (1.20.1)
70
- parser (3.0.1.1)
69
+ parallel (1.23.0)
70
+ parser (3.2.2.1)
71
71
  ast (~> 2.4.1)
72
- pg (1.2.3)
73
- pry (0.14.1)
72
+ pg (1.5.3)
73
+ pry (0.14.2)
74
74
  coderay (~> 1.1)
75
75
  method_source (~> 1.0)
76
- racc (1.5.2)
77
- rack (2.2.3)
78
- rack-test (1.1.0)
79
- rack (>= 1.0, < 3)
76
+ racc (1.6.2)
77
+ rack (2.2.7)
78
+ rack-test (2.1.0)
79
+ rack (>= 1.3)
80
80
  rails-dom-testing (2.0.3)
81
81
  activesupport (>= 4.2.0)
82
82
  nokogiri (>= 1.6)
83
- rails-html-sanitizer (1.4.2)
84
- loofah (~> 2.3)
85
- railties (6.1.4.1)
86
- actionpack (= 6.1.4.1)
87
- activesupport (= 6.1.4.1)
83
+ rails-html-sanitizer (1.5.0)
84
+ loofah (~> 2.19, >= 2.19.1)
85
+ railties (7.0.5)
86
+ actionpack (= 7.0.5)
87
+ activesupport (= 7.0.5)
88
88
  method_source
89
- rake (>= 0.13)
89
+ rake (>= 12.2)
90
90
  thor (~> 1.0)
91
- rainbow (3.0.0)
91
+ zeitwerk (~> 2.5)
92
+ rainbow (3.1.1)
92
93
  rake (13.0.3)
93
- regexp_parser (2.1.1)
94
+ regexp_parser (2.8.0)
94
95
  rexml (3.2.5)
95
- rspec (3.10.0)
96
- rspec-core (~> 3.10.0)
97
- rspec-expectations (~> 3.10.0)
98
- rspec-mocks (~> 3.10.0)
99
- rspec-core (3.10.1)
100
- rspec-support (~> 3.10.0)
101
- rspec-expectations (3.10.1)
96
+ rspec (3.12.0)
97
+ rspec-core (~> 3.12.0)
98
+ rspec-expectations (~> 3.12.0)
99
+ rspec-mocks (~> 3.12.0)
100
+ rspec-core (3.12.2)
101
+ rspec-support (~> 3.12.0)
102
+ rspec-expectations (3.12.3)
102
103
  diff-lcs (>= 1.2.0, < 2.0)
103
- rspec-support (~> 3.10.0)
104
- rspec-mocks (3.10.2)
104
+ rspec-support (~> 3.12.0)
105
+ rspec-mocks (3.12.5)
105
106
  diff-lcs (>= 1.2.0, < 2.0)
106
- rspec-support (~> 3.10.0)
107
- rspec-support (3.10.2)
108
- rubocop (1.14.0)
107
+ rspec-support (~> 3.12.0)
108
+ rspec-support (3.12.0)
109
+ rubocop (1.51.0)
110
+ json (~> 2.3)
109
111
  parallel (~> 1.10)
110
- parser (>= 3.0.0.0)
112
+ parser (>= 3.2.0.0)
111
113
  rainbow (>= 2.2.2, < 4.0)
112
114
  regexp_parser (>= 1.8, < 3.0)
113
- rexml
114
- rubocop-ast (>= 1.5.0, < 2.0)
115
+ rexml (>= 3.2.5, < 4.0)
116
+ rubocop-ast (>= 1.28.0, < 2.0)
115
117
  ruby-progressbar (~> 1.7)
116
- unicode-display_width (>= 1.4.0, < 3.0)
117
- rubocop-ast (1.5.0)
118
- parser (>= 3.0.1.1)
119
- rubocop-rspec (2.3.0)
120
- rubocop (~> 1.0)
121
- rubocop-ast (>= 1.1.0)
122
- ruby-progressbar (1.11.0)
123
- scenic (1.5.4)
118
+ unicode-display_width (>= 2.4.0, < 3.0)
119
+ rubocop-ast (1.28.1)
120
+ parser (>= 3.2.1.0)
121
+ rubocop-capybara (2.18.0)
122
+ rubocop (~> 1.41)
123
+ rubocop-factory_bot (2.23.1)
124
+ rubocop (~> 1.33)
125
+ rubocop-rspec (2.22.0)
126
+ rubocop (~> 1.33)
127
+ rubocop-capybara (~> 2.17)
128
+ rubocop-factory_bot (~> 2.22)
129
+ ruby-progressbar (1.13.0)
130
+ scenic (1.7.0)
124
131
  activerecord (>= 4.0.0)
125
132
  railties (>= 4.0.0)
126
- simplecov (0.21.2)
133
+ simplecov (0.22.0)
127
134
  docile (~> 1.1)
128
135
  simplecov-html (~> 0.11)
129
136
  simplecov_json_formatter (~> 0.1)
130
137
  simplecov-html (0.12.3)
131
- simplecov_json_formatter (0.1.3)
132
- thor (1.1.0)
133
- tzinfo (2.0.4)
138
+ simplecov_json_formatter (0.1.4)
139
+ thor (1.2.2)
140
+ tzinfo (2.0.6)
134
141
  concurrent-ruby (~> 1.0)
135
- unicode-display_width (2.0.0)
136
- zeitwerk (2.4.2)
142
+ unicode-display_width (2.4.2)
143
+ zeitwerk (2.6.8)
137
144
 
138
145
  PLATFORMS
139
146
  ruby
@@ -152,4 +159,4 @@ DEPENDENCIES
152
159
  table_saw!
153
160
 
154
161
  BUNDLED WITH
155
- 2.2.22
162
+ 2.4.13
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  [![Maintainability](https://api.codeclimate.com/v1/badges/abd5b5451c764d3249f1/maintainability)](https://codeclimate.com/github/hasghari/table_saw/maintainability)
3
3
  [![Test Coverage](https://api.codeclimate.com/v1/badges/abd5b5451c764d3249f1/test_coverage)](https://codeclimate.com/github/hasghari/table_saw/test_coverage)
4
4
 
5
- # TableSaw
5
+ # table-saw
6
6
 
7
7
  This gem creates a PSQL dump file (data only) from a Postgres database by only dumping a subset of data defined by a
8
8
  manifest file.
@@ -50,52 +50,188 @@ Options:
50
50
  -o, [--output=OUTPUT] # Default value is 'output.dump'
51
51
  ```
52
52
 
53
- The manifest file describes which tables you want to dump from your Postgres database:
53
+ ### Manifest
54
+
55
+ The manifest is a YAML file that describes the dataset to be exported to a dump file. At the top level, the manifest
56
+ file supports 4 nodes:
57
+
58
+ - [variables](#variables)
59
+ - [tables](#tables)
60
+ - [has_many](#has_many)
61
+ - [foreign_keys](#foreign_keys)
62
+
63
+ The examples that follow assume you have a database set up as follows:
64
+ ```sql
65
+ CREATE TABLE authors (
66
+ id bigint NOT NULL,
67
+ name character varying NOT NULL,
68
+ PRIMARY KEY (id)
69
+ );
70
+
71
+ CREATE TABLE books (
72
+ id bigint NOT NULL,
73
+ author_id bigint NOT NULL,
74
+ name character varying NOT NULL,
75
+ votes integer DEFAULT 0 NOT NULL,
76
+ PRIMARY KEY (id),
77
+ CONSTRAINT fk_author FOREIGN KEY (author_id) REFERENCES authors(id)
78
+ );
79
+
80
+ INSERT INTO authors (id, name) VALUES (1, 'Dan Brown');
81
+ INSERT INTO authors (id, name) VALUES (2, 'J. K. Rowling');
82
+
83
+ INSERT INTO books (id, author_id, name, votes) VALUES (1, 1, 'Angels and Demons', 10);
84
+ INSERT INTO books (id, author_id, name, votes) VALUES (2, 1, 'Digital Fortress', 35);
85
+ INSERT INTO books (id, author_id, name, votes) VALUES (3, 1, 'The Da Vinci Code', 50);
86
+ INSERT INTO books (id, author_id, name, votes) VALUES (4, 2, 'Philosopher''s Stone', 55);
87
+ INSERT INTO books (id, author_id, name, votes) VALUES (5, 2, 'Chamber of Secrets', 5);
88
+ INSERT INTO books (id, author_id, name, votes) VALUES (6, 2, 'Prisoner of Azkaban', 25);
89
+ ```
90
+
91
+ #### variables
92
+ Variables allow you parameterize the queries in the manifest. You can use the `%{variable}` substitution pattern in your
93
+ query strings:
54
94
 
55
95
  ```yaml
56
96
  variables:
57
- author_id: 24
97
+ author_id: 2
58
98
  tables:
59
99
  - table: books
60
100
  query: "select * from books where author_id = %{author_id}"
61
101
  ```
62
102
 
63
- This will only fetch records from the `books` table where `author_id = 24` and will also fetch the record from the
64
- `authors` table where `id = 24`.
65
-
66
- Assuming there is a `chapters` table with a foreign key reference of `book_id` to the `books` table, the above manifest
67
- file will **not** automatically retrieve those records. If `chapters` records are also desired, there a couple ways to
68
- accomplish this:
103
+ Additionally, you can now use the `%{variable}` substitution pattern in other variables:
69
104
 
70
105
  ```yaml
71
106
  variables:
72
- author_id: 24
107
+ author_id: '1,3,4',
108
+ book_ids: 'select * from books where author_id in (%{author_id})'
109
+ ```
110
+
111
+ #### tables
112
+ This is where you list the specific tables that you want to export. If you only specify the `table` without providing a
113
+ `query`, then the **entire** table will be exported. However, if you specify a `query`, then only rows matching that
114
+ query will be exported:
115
+
116
+ ```yaml
73
117
  tables:
74
118
  - table: books
75
- query: "select * from books where author_id = %{author_id}"
119
+ query: "select * from books where author_id = 2"
120
+ ```
121
+
122
+ The above manifest will only export rows from the `books` table where `author_id = 2`. In addition, due to the
123
+ foreign key constraint defined from `books(author_id)` to `authors(id)`, table-saw will automatically export the row
124
+ from the `authors` table where `id = 2` in order to preserve referential integrity.
125
+
126
+ The above manifest can alternatively be written as follows where exactly the same rows would be exported:
127
+
128
+ ```yaml
129
+ tables:
130
+ - table: authors
131
+ query: "select * from authors where id = 2"
132
+ has_many:
133
+ - books
134
+ ```
135
+
136
+ Notice we have to explicitly list the `has_many` association to the `books` table. Since `authors` does not have a
137
+ dependency on `books` to preserve referential integrity, table-saw by design will not export the associated `books`
138
+ rows in order to keep the output dump file as small as possible. In other words, if we eliminate the `has_many` node
139
+ from the manifest above, table-saw will only export a single author with `id = 2`.
140
+
141
+ #### has_many
142
+ This is where we define which optional associations we want to export for each table:
143
+
144
+ ```yaml
145
+ tables:
146
+ - table: authors
147
+ query: "select * from authors where id = 1"
148
+ - table: books
149
+ query: "select * from books where id = 6"
76
150
  has_many:
77
- books:
78
- - chapters
151
+ authors:
152
+ - books
79
153
  ```
80
154
 
81
- or
155
+ The above manifest would export the following rows:
156
+
157
+ * authors:
158
+ - `id = 1` because of the explicit query in the manifest
159
+ - `id = 2` because it is the `author_id` for `books` with `id = 6`
160
+
161
+ * books:
162
+ - `id = [1, 2, 3]` due to the `has_many` association for `authors` with `id = 1`
163
+ - `id = [4, 5, 6]` due to the `has_many` association for `authors` with `id = 2`
164
+
165
+ Now, if instead of defining the `has_many` node at the top level, we define it under the `authors` table as follows:
82
166
 
83
167
  ```yaml
84
- variables:
85
- author_id: 24
86
168
  tables:
87
- - table: chapters
88
- query: "select * from chapters inner join books on books.id = chapters.book_id where books.author_id = %{author_id}"
169
+ - table: authors
170
+ query: "select * from authors where id = 1"
171
+ has_many:
172
+ - books
173
+ - table: books
174
+ query: "select * from books where id = 6"
175
+ ```
176
+
177
+ The above manifest would export the following rows:
178
+
179
+ * authors:
180
+ - `id = 1` because of the explicit query in the manifest
181
+ - `id = 2` because it is the `author_id` for `books` with `id = 6`
182
+
183
+ * books:
184
+ - `id = [1, 2, 3]` due to the `has_many` association for `authors` with `id = 1`
185
+ - `id = 6` due to the explicit query in the manifest
186
+
187
+ One potential pitfall with using `has_many` is that you end up pulling in too many associated rows when all you wanted
188
+ was a limited number. table-saw allows you to specify a `scope` and `limit`:
189
+
190
+ ```yaml
191
+ tables:
192
+ - table: authors
193
+ query: "select * from authors where id = 1"
194
+ - table: books
195
+ query: "select * from books where id = 6"
196
+ has_many:
197
+ authors:
198
+ - books:
199
+ scope: "votes > 30"
200
+ limit: 1
201
+ ```
202
+
203
+ The above manifest would export the following rows:
204
+
205
+ * authors:
206
+ - `id = 1` because of the explicit query in the manifest
207
+ - `id = 2` because it is the `author_id` for `books` with `id = 6`
208
+
209
+ * books:
210
+ - Either `id = 2` or `id = 3` for `author_id = 1` since they both have `vote > 30` and the limit of 1 will randomly
211
+ choose one of them
212
+ - Only `id = 4` for `author_id = 2` since it's the only book for that author with `vote > 30`
213
+
214
+ #### foreign_keys
215
+ By default, table-saw will query the Postgres `information_schema` to look up the foreign key constraints and determine
216
+ whether it needs to export associated rows. However, if your database schema does not define foreign key constraints for
217
+ the tables you would like to export, you can manually define them in the manifest. Assuming we had not defined any
218
+ foreign key constraints for the `books` table, we could specify it in the manifest as follows:
219
+
220
+ ```yaml
221
+ foreign_keys:
222
+ - from_table: books
223
+ from_column: author_id
224
+ to_table: authors
225
+ to_column: id
89
226
  ```
90
227
 
91
- The output of the 2 manifest files above are exactly the same. The dump file will contain all the relevant records from
92
- the `authors`, `books` and `chapters` tables.
228
+ ## Dump and Restore
93
229
 
94
230
  Once your dump file has been created, you can import the data using `psql`:
95
231
 
96
232
  ```bash
97
- table-saw dump -m manifest.yml -o chapters.dump
98
- psql -h localhost -U postgres -d library < chapters.dump
233
+ table-saw dump -m manifest.yml -o library.dump
234
+ psql -h localhost -U postgres -d library < library.dump
99
235
  ```
100
236
 
101
237
  ## Development
@@ -3,6 +3,15 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "appraisal"
6
+ gem "bundler", "~> 2.0"
7
+ gem "combustion", "~> 1.3"
8
+ gem "database_cleaner", "~> 2"
9
+ gem "pry"
10
+ gem "rake", "13.0.3"
11
+ gem "rspec", "~> 3.0"
12
+ gem "rubocop-rspec", "~> 2.3"
13
+ gem "scenic", "~> 1.5"
14
+ gem "simplecov", "~> 0.16"
6
15
  gem "activerecord", "~> 6.1", "< 6.2"
7
16
 
8
17
  gemspec path: "../"