pg_delta 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: abb741a6c13dd86274ea00b2562b4330f31266fcf2fd24277b4929783f559bae
4
+ data.tar.gz: 397cbd53124f61b8fc3e73a7611f5a540659b41b1034632be66a8674613d6a2b
5
+ SHA512:
6
+ metadata.gz: f21a6fded139dbb9bede777f208ad3b0d3ec96424ece0e2c58be801815a9b8ec565e611beb506f5bca8ccd6e25eb84a5a60e0cd55c8ee2bb2b8fee5da34899fb
7
+ data.tar.gz: 687b9381f42f15e1d0ca61b1114d0e2f8677d7a520ecea214ef05cbf374fc92731c00e8ab713ea5443109d2e75000c12906de4286b65ebb21928bd4a6c879b94
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ # For available configuration options, see:
2
+ # https://github.com/testdouble/standard
3
+ ruby_version: 2.6
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2023-08-05
4
+
5
+ - Initial release
@@ -0,0 +1,84 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and sexual attention or
22
+ advances of any kind
23
+ * Trolling, insulting or derogatory comments, and personal or political attacks
24
+ * Public or private harassment
25
+ * Publishing others' private information, such as a physical or email
26
+ address, without their explicit permission
27
+ * Other conduct which could reasonably be considered inappropriate in a
28
+ professional setting
29
+
30
+ ## Enforcement Responsibilities
31
+
32
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33
+
34
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35
+
36
+ ## Scope
37
+
38
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39
+
40
+ ## Enforcement
41
+
42
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement. All complaints will be reviewed and investigated promptly and fairly.
43
+
44
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
+
46
+ ## Enforcement Guidelines
47
+
48
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49
+
50
+ ### 1. Correction
51
+
52
+ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53
+
54
+ **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55
+
56
+ ### 2. Warning
57
+
58
+ **Community Impact**: A violation through a single incident or series of actions.
59
+
60
+ **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61
+
62
+ ### 3. Temporary Ban
63
+
64
+ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65
+
66
+ **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67
+
68
+ ### 4. Permanent Ban
69
+
70
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71
+
72
+ **Consequence**: A permanent ban from any sort of public interaction within the community.
73
+
74
+ ## Attribution
75
+
76
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77
+ available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78
+
79
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80
+
81
+ [homepage]: https://www.contributor-covenant.org
82
+
83
+ For answers to common questions about this code of conduct, see the FAQ at
84
+ https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 rhizomic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,192 @@
1
+ # PgDelta
2
+
3
+ `pg_delta` provides an easy way to obtain information about a PostgreSQL query.
4
+ Here's a motivating example.
5
+
6
+ Given a file `/tmp/a.sql`:
7
+
8
+
9
+ ```sql
10
+ update users
11
+ set name = 'Unknown'
12
+ where name is null;
13
+
14
+ alter table users
15
+ alter column name
16
+ set not null;
17
+
18
+ truncate table passwords;
19
+ ```
20
+
21
+ Running `pg_delta` will then yield the following JSON:
22
+
23
+ ```sh
24
+ $ exe/pg_delta -f /tmp/a.sql
25
+ [
26
+ {
27
+ "mutation": "update_data",
28
+ "type": "data",
29
+ "details": {
30
+ "table_name": "users",
31
+ "column_names": [
32
+ "name"
33
+ ]
34
+ }
35
+ },
36
+ {
37
+ "mutation": "alter_table_set_not_null",
38
+ "type": "column",
39
+ "details": {
40
+ "table_name": "users",
41
+ "column_name": "name"
42
+ }
43
+ },
44
+ {
45
+ "mutation": "truncate_table",
46
+ "type": "table",
47
+ "details": {
48
+ "table_names": [
49
+ "passwords"
50
+ ]
51
+ }
52
+ }
53
+ ]
54
+ ```
55
+
56
+ ## Installation
57
+
58
+ Install the gem and add to the application's Gemfile by executing:
59
+
60
+ $ bundle add pg_delta
61
+
62
+ If bundler is not being used to manage dependencies, install the gem by executing:
63
+
64
+ $ gem install pg_delta
65
+
66
+ ## Usage
67
+
68
+ ```
69
+ Usage: pg_delta -f FILES
70
+
71
+ Example:
72
+ $ pg_delta -f 1.sql 2.sql 3.sql
73
+
74
+ Options:
75
+ -v, --version Print the version
76
+ -f, --files FILES The files to parse
77
+ -h, --help Show this message
78
+ ```
79
+
80
+ ### Types of Changes
81
+
82
+ The `type`s of changes are as follows:
83
+
84
+ ```
85
+ :column
86
+ :domain
87
+ :enum
88
+ :function
89
+ :table
90
+ :trigger
91
+ :data
92
+ :index
93
+ :type
94
+ :view
95
+ :constraint
96
+ :unknown
97
+ ```
98
+
99
+ ### Mutations
100
+
101
+ The following `mutation`s are identified:
102
+
103
+ ```
104
+ # :column
105
+ :alter_table_add_column
106
+ :alter_table_set_column_default
107
+ :alter_table_drop_column_default
108
+ :alter_table_drop_column
109
+ :alter_table_drop_not_null
110
+ :alter_table_set_not_null
111
+ :alter_table_rename_column
112
+
113
+ # :domain
114
+ :alter_domain
115
+ :create_domain
116
+ :drop_domain
117
+
118
+ # :enum
119
+ :alter_enum
120
+ :create_enum
121
+
122
+ # :function
123
+ :create_function
124
+ :drop_function
125
+ :rename_function
126
+
127
+ # :table
128
+ :create_table
129
+ :create_table_as
130
+ :drop_table
131
+ :rename_table
132
+ :truncate_table
133
+
134
+ # :trigger
135
+ :create_trigger
136
+ :drop_trigger
137
+ :disable_trigger
138
+ :enable_trigger
139
+ :rename_trigger
140
+
141
+ # :data
142
+ :delete_data
143
+ :insert_data
144
+ :update_data
145
+
146
+ # :index
147
+ :create_index
148
+ :drop_index
149
+ :rename_index
150
+
151
+ # :type
152
+ :alter_table_alter_column_type
153
+ :drop_type
154
+ :rename_type
155
+
156
+ # :view
157
+ :create_view
158
+ :drop_materialized_view
159
+ :drop_view
160
+
161
+ # :constraint
162
+ :alter_table_add_constraint
163
+ :alter_table_drop_constraint
164
+ :alter_table_validate_constraint
165
+ :alter_table_rename_constraint
166
+
167
+ # :unknown
168
+ :unknown
169
+ ```
170
+
171
+ ## Development
172
+
173
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
174
+ `rake test` to run the tests. You can also run `bin/console` for an interactive
175
+ prompt that will allow you to experiment.
176
+
177
+ To install this gem onto your local machine, run `bundle exec rake install`. To
178
+ release a new version, update the version number in `version.rb`, and then run
179
+ `bundle exec rake release`, which will create a git tag for the version, push
180
+ git commits and the created tag, and push the `.gem` file to
181
+ [rubygems.org](https://rubygems.org).
182
+
183
+ ## Contributing
184
+
185
+ Bug reports and pull requests are welcome on GitHub at
186
+ https://github.com/rhizomic/pg_delta. This project is intended to be a safe,
187
+ welcoming space for collaboration, and contributors are expected to adhere to
188
+ the [code of conduct](https://github.com/rhizomic/pg_delta/blob/master/CODE_OF_CONDUCT.md).
189
+
190
+ ## License
191
+
192
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/test_*.rb"]
10
+ end
11
+
12
+ require "standard/rake"
13
+
14
+ task default: %i[test standard]
data/exe/pg_delta ADDED
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(__dir__ + "/../lib")
4
+
5
+ require "pg_delta"
6
+ require "pg_delta/version"
7
+
8
+ require "json"
9
+ require "optparse"
10
+
11
+ class CLI
12
+ def self.parse_options(args)
13
+ options = {
14
+ files: []
15
+ }
16
+
17
+ option_parser = OptionParser.new do |opts|
18
+ opts.banner = "Usage: pg_delta -f FILES"
19
+
20
+ opts.separator ""
21
+
22
+ opts.separator "Example:"
23
+ opts.separator " $ pg_delta -f 1.sql 2.sql 3.sql"
24
+
25
+ opts.separator ""
26
+
27
+ opts.separator "Options:"
28
+
29
+ opts.on("-v", "--version", "Print the version") do |v|
30
+ puts "pg_delta v#{PgDelta::Version::VERSION}"
31
+ exit
32
+ end
33
+
34
+ opts.on_tail("-h", "--help", "Show this message") do
35
+ puts opts
36
+ exit
37
+ end
38
+
39
+ opts.on("-f", "--files FILES", Array, "The files to parse") do |files|
40
+ options[:files] |= [*files]
41
+ end
42
+ end
43
+
44
+ option_parser.parse!(args)
45
+
46
+ options
47
+ end
48
+ end
49
+
50
+ ARGV << "-h" if ARGV.empty? && $stdin.tty?
51
+ cli_options = CLI.parse_options ARGV
52
+
53
+ # Workaround to get multiple files passed to :files without needing to resort
54
+ # to commas.
55
+ # See: https://stackoverflow.com/a/29938935
56
+ cli_options[:files] |= ARGV
57
+
58
+ file_contents = cli_options[:files].map do |file|
59
+ file = File.expand_path file
60
+ File.read file
61
+ end.join ";"
62
+
63
+ statements = PgDelta.split_statements file_contents
64
+
65
+ results = statements.map do |statement|
66
+ PgDelta.parse_statement statement
67
+ end
68
+
69
+ puts JSON.pretty_generate(results)
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgDelta
4
+ module Version
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
data/lib/pg_delta.rb ADDED
@@ -0,0 +1,655 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pg_query"
4
+
5
+ require_relative "pg_delta/version"
6
+
7
+ module PgDelta
8
+ class Error < StandardError; end
9
+
10
+ # Parses the provided queries and returns a PgQuery statement for each query.
11
+ # Returns an empty list if the queries cannot be parsed.
12
+ #
13
+ # @param queries [String] The queries
14
+ # @return [Array<PgQuery::Node>] The info about the parsed query
15
+ def self.split_statements(queries)
16
+ parsed_query = PgQuery.parse queries
17
+ parsed_query.tree.stmts.map(&:stmt)
18
+ rescue
19
+ []
20
+ end
21
+
22
+ # Parses a single query and its corresponding PgQuery statement.
23
+ # Returns an empty hash if the query cannot be parsed.
24
+ #
25
+ # @param query [String] The query
26
+ # @return [Hash] The info about the parsed query
27
+ def self.parse_query(query)
28
+ parsed_query = PgQuery.parse query
29
+ statement = parsed_query.tree.stmts.first.stmt
30
+ parse_statement statement
31
+ rescue
32
+ {}
33
+ end
34
+
35
+ # Returns information about the supplied statement.
36
+ #
37
+ # @param statement [PgQuery::Node] The statement
38
+ # @return [Hash] The info about the statement
39
+ def self.parse_statement(statement)
40
+ case statement.node
41
+ when :alter_domain_stmt
42
+ alter_domain statement.alter_domain_stmt
43
+ when :alter_enum_stmt
44
+ alter_enum statement.alter_enum_stmt
45
+ when :alter_table_stmt
46
+ case statement.alter_table_stmt.cmds[0].alter_table_cmd.subtype
47
+ when :AT_SetNotNull
48
+ alter_table_set_not_null statement.alter_table_stmt
49
+ when :AT_AddColumn
50
+ alter_table_add_column statement.alter_table_stmt
51
+ when :AT_DropColumn
52
+ alter_table_drop_column statement.alter_table_stmt
53
+ when :AT_AddConstraint
54
+ alter_table_add_constraint statement.alter_table_stmt
55
+ when :AT_DropConstraint
56
+ alter_table_drop_constraint statement.alter_table_stmt
57
+ when :AT_ValidateConstraint
58
+ alter_table_validate_constraint statement.alter_table_stmt
59
+ when :AT_DropNotNull
60
+ alter_table_drop_not_null statement.alter_table_stmt
61
+ when :AT_ColumnDefault
62
+ alter_table_column_default statement.alter_table_stmt
63
+ when :AT_AlterColumnType
64
+ alter_table_alter_column_type statement.alter_table_stmt
65
+ when :AT_DisableTrig
66
+ alter_table_disable_trigger statement.alter_table_stmt
67
+ when :AT_SetRelOptions
68
+ # Don't return anything for now.
69
+ {}
70
+ when :AT_EnableTrig
71
+ alter_table_enable_trigger statement.alter_table_stmt
72
+ else
73
+ unknown statement
74
+ end
75
+ when :create_domain_stmt
76
+ create_domain statement.create_domain_stmt
77
+ when :create_enum_stmt
78
+ create_enum statement.create_enum_stmt
79
+ when :create_extension_stmt
80
+ # Don't return anything for now.
81
+ {}
82
+ when :create_function_stmt
83
+ create_function statement.create_function_stmt
84
+ when :create_stmt
85
+ create_table statement.create_stmt
86
+ when :create_table_as_stmt
87
+ create_table_as statement.create_table_as_stmt
88
+ when :create_trig_stmt
89
+ create_trigger statement.create_trig_stmt
90
+ when :delete_stmt
91
+ delete statement.delete_stmt
92
+ when :drop_stmt
93
+ case statement.drop_stmt.remove_type
94
+ when :OBJECT_INDEX
95
+ drop_index statement.drop_stmt
96
+ when :OBJECT_TABLE
97
+ drop_table statement.drop_stmt
98
+ when :OBJECT_TYPE
99
+ drop_type statement.drop_stmt
100
+ when :OBJECT_TRIGGER
101
+ drop_trigger statement.drop_stmt
102
+ when :OBJECT_FUNCTION
103
+ drop_function statement.drop_stmt
104
+ when :OBJECT_VIEW
105
+ drop_view statement.drop_stmt
106
+ when :OBJECT_DOMAIN
107
+ drop_domain statement.drop_stmt
108
+ when :OBJECT_MATVIEW
109
+ drop_materialized_view statement.drop_stmt
110
+ else
111
+ unknown statement
112
+ end
113
+ when :index_stmt
114
+ create_index statement.index_stmt
115
+ when :insert_stmt
116
+ insert statement.insert_stmt
117
+ when :rename_stmt
118
+ case statement.rename_stmt.rename_type
119
+ when :OBJECT_TABCONSTRAINT
120
+ rename_constraint statement.rename_stmt
121
+ when :OBJECT_TABLE
122
+ rename_table statement.rename_stmt
123
+ when :OBJECT_COLUMN
124
+ rename_column statement.rename_stmt
125
+ when :OBJECT_FUNCTION
126
+ rename_function statement.rename_stmt
127
+ when :OBJECT_TRIGGER
128
+ rename_trigger statement.rename_stmt
129
+ when :OBJECT_INDEX
130
+ rename_index statement.rename_stmt
131
+ when :OBJECT_TYPE
132
+ rename_type statement.rename_stmt
133
+ else
134
+ unknown statement
135
+ end
136
+ when :truncate_stmt
137
+ truncate statement.truncate_stmt
138
+ when :update_stmt
139
+ update statement.update_stmt
140
+ when :view_stmt
141
+ view statement.view_stmt
142
+ when :select_stmt
143
+ # TODO: This is tricky, because this could call a function which might
144
+ # alter the schema.
145
+ # Don't return anything for now.
146
+ {}
147
+ when :create_seq_stmt
148
+ # Don't return anything for now.
149
+ {}
150
+ when :create_stats_stmt
151
+ # Don't return anything for now.
152
+ {}
153
+ when :define_stmt
154
+ # Don't return anything for now.
155
+ {}
156
+ when :alter_seq_stmt
157
+ # Don't return anything for now.
158
+ {}
159
+ when :comment_stmt
160
+ # Don't return anything for now.
161
+ {}
162
+ when :lock_stmt
163
+ # Don't return anything for now.
164
+ {}
165
+ when :variable_set_stmt
166
+ # Don't return anything for now.
167
+ {}
168
+ when :transaction_stmt
169
+ # "begin;" and "commit;" are parsed as :transaction_stmt.
170
+ # Don't return anything for now.
171
+ {}
172
+ when :do_stmt
173
+ # Don't return anything for now.
174
+ {}
175
+ else
176
+ unknown statement
177
+ end
178
+ end
179
+
180
+ # Column mutations
181
+
182
+ def self.alter_table_add_column(statement)
183
+ {
184
+ mutation: :alter_table_add_column,
185
+ type: :column,
186
+ details: {
187
+ table_name: statement.relation.relname,
188
+ column_name: statement.cmds.first.alter_table_cmd.def.column_def.colname,
189
+ column_type: statement.cmds.first.alter_table_cmd.def.column_def.type_name.names.last.string.sval
190
+ }
191
+ }
192
+ end
193
+
194
+ def self.alter_table_column_default(statement)
195
+ # PgQuery sadly doesn't differentiate between adding and dropping
196
+ # column defaults at the node level, so we have to do it here.
197
+ if statement.cmds.first.alter_table_cmd.def
198
+ # The query is setting a default.
199
+ {
200
+ mutation: :alter_table_set_column_default,
201
+ type: :column,
202
+ details: {
203
+ table_name: statement.relation.relname,
204
+ column_name: statement.cmds.first.alter_table_cmd.name
205
+ }
206
+ }
207
+ else
208
+ {
209
+ mutation: :alter_table_drop_column_default,
210
+ type: :column,
211
+ details: {
212
+ table_name: statement.relation.relname,
213
+ column_name: statement.cmds.first.alter_table_cmd.name
214
+ }
215
+ }
216
+ end
217
+ end
218
+
219
+ def self.alter_table_drop_column(statement)
220
+ {
221
+ mutation: :alter_table_drop_column,
222
+ type: :column,
223
+ details: {
224
+ table_name: statement.relation.relname,
225
+ column_name: statement.cmds.first.alter_table_cmd.name
226
+ }
227
+ }
228
+ end
229
+
230
+ def self.alter_table_drop_not_null(statement)
231
+ {
232
+ mutation: :alter_table_drop_not_null,
233
+ type: :column,
234
+ details: {
235
+ table_name: statement.relation.relname,
236
+ column_name: statement.cmds.first.alter_table_cmd.name
237
+ }
238
+ }
239
+ end
240
+
241
+ def self.alter_table_set_not_null(statement)
242
+ {
243
+ mutation: :alter_table_set_not_null,
244
+ type: :column,
245
+ details: {
246
+ table_name: statement.relation.relname,
247
+ column_name: statement.cmds.first.alter_table_cmd.name
248
+ }
249
+ }
250
+ end
251
+
252
+ def self.rename_column(statement)
253
+ {
254
+ mutation: :alter_table_rename_column,
255
+ type: :column,
256
+ details: {
257
+ table_name: statement.relation.relname,
258
+ old_column_name: statement.subname,
259
+ new_column_name: statement.newname
260
+ }
261
+ }
262
+ end
263
+
264
+ # Domain mutations
265
+
266
+ def self.alter_domain(statement)
267
+ {
268
+ mutation: :alter_domain,
269
+ type: :domain,
270
+ details: {
271
+ domain_name: statement.type_name.first.string.sval
272
+ }
273
+ }
274
+ end
275
+
276
+ def self.create_domain(statement)
277
+ {
278
+ mutation: :create_domain,
279
+ type: :domain,
280
+ details: {
281
+ domain_name: statement.domainname.first.string.sval
282
+ }
283
+ }
284
+ end
285
+
286
+ def self.drop_domain(statement)
287
+ {
288
+ mutation: :drop_domain,
289
+ type: :domain,
290
+ details: {
291
+ domain_name: statement.objects.first.type_name.names.first.string.sval
292
+ }
293
+ }
294
+ end
295
+
296
+ # Enum mutations
297
+
298
+ def self.alter_enum(statement)
299
+ {
300
+ mutation: :alter_enum,
301
+ type: :enum,
302
+ details: {
303
+ enum_name: statement.type_name.first.string.sval
304
+ }
305
+ }
306
+ end
307
+
308
+ def self.create_enum(statement)
309
+ {
310
+ mutation: :create_enum,
311
+ type: :enum,
312
+ details: {
313
+ enum_name: statement.type_name.first.string.sval
314
+ }
315
+ }
316
+ end
317
+
318
+ # Function mutations
319
+
320
+ def self.create_function(statement)
321
+ {
322
+ mutation: :create_function,
323
+ type: :function,
324
+ details: {
325
+ function_name: statement.funcname.first.string.sval
326
+ }
327
+ }
328
+ end
329
+
330
+ def self.drop_function(statement)
331
+ {
332
+ mutation: :drop_function,
333
+ type: :function,
334
+ details: {
335
+ function_name: statement.objects.first.object_with_args.objname.first.string.sval
336
+ }
337
+ }
338
+ end
339
+
340
+ def self.rename_function(statement)
341
+ {
342
+ mutation: :rename_function,
343
+ type: :function,
344
+ details: {
345
+ old_function_name: statement.object.object_with_args.objname.first.string.sval,
346
+ new_function_name: statement.newname
347
+ }
348
+ }
349
+ end
350
+
351
+ # Table mutations
352
+
353
+ def self.create_table(statement)
354
+ {
355
+ mutation: :create_table,
356
+ type: :table,
357
+ details: {
358
+ table_name: statement.relation.relname
359
+ }
360
+ }
361
+ end
362
+
363
+ def self.create_table_as(statement)
364
+ {
365
+ mutation: :create_table_as,
366
+ type: :table,
367
+ details: {
368
+ table_name: statement.into.rel.relname
369
+ }
370
+ }
371
+ end
372
+
373
+ def self.drop_table(statement)
374
+ {
375
+ mutation: :drop_table,
376
+ type: :table,
377
+ details: {
378
+ table_name: statement.objects.first.list.items.first.string.sval
379
+ }
380
+ }
381
+ end
382
+
383
+ def self.rename_table(statement)
384
+ {
385
+ mutation: :rename_table,
386
+ type: :table,
387
+ details: {
388
+ old_table_name: statement.relation.relname,
389
+ new_table_name: statement.newname
390
+ }
391
+ }
392
+ end
393
+
394
+ def self.truncate(statement)
395
+ {
396
+ mutation: :truncate_table,
397
+ type: :table,
398
+ details: {
399
+ table_names: statement.relations.map(&:range_var).map(&:relname)
400
+ }
401
+ }
402
+ end
403
+
404
+ # Trigger mutations
405
+
406
+ def self.create_trigger(statement)
407
+ {
408
+ mutation: :create_trigger,
409
+ type: :trigger,
410
+ details: {
411
+ trigger_name: statement.trigname,
412
+ table_name: statement.relation.relname
413
+ }
414
+ }
415
+ end
416
+
417
+ def self.drop_trigger(statement)
418
+ items = statement.objects.first.list.items
419
+ {
420
+ mutation: :drop_trigger,
421
+ type: :trigger,
422
+ details: {
423
+ trigger_name: items.last.string.sval,
424
+ table_name: items.first.string.sval
425
+ }
426
+ }
427
+ end
428
+
429
+ def self.alter_table_disable_trigger(statement)
430
+ {
431
+ mutation: :disable_trigger,
432
+ type: :trigger,
433
+ details: {
434
+ table_name: statement.relation.relname,
435
+ trigger_name: statement.cmds.first.alter_table_cmd.name
436
+ }
437
+ }
438
+ end
439
+
440
+ def self.alter_table_enable_trigger(statement)
441
+ {
442
+ mutation: :enable_trigger,
443
+ type: :trigger,
444
+ details: {
445
+ table_name: statement.relation.relname,
446
+ trigger_name: statement.cmds.first.alter_table_cmd.name
447
+ }
448
+ }
449
+ end
450
+
451
+ def self.rename_trigger(statement)
452
+ {
453
+ mutation: :rename_trigger,
454
+ type: :trigger,
455
+ details: {
456
+ table_name: statement.relation.relname,
457
+ old_trigger_name: statement.subname,
458
+ new_trigger_name: statement.newname
459
+ }
460
+ }
461
+ end
462
+
463
+ # Data mutations
464
+
465
+ def self.delete(statement)
466
+ {
467
+ mutation: :delete_data,
468
+ type: :data,
469
+ details: {
470
+ table_name: statement.relation.relname
471
+ }
472
+ }
473
+ end
474
+
475
+ def self.insert(statement)
476
+ {
477
+ mutation: :insert_data,
478
+ type: :data,
479
+ details: {
480
+ table_name: statement.relation.relname
481
+ }
482
+ }
483
+ end
484
+
485
+ def self.update(statement)
486
+ {
487
+ mutation: :update_data,
488
+ type: :data,
489
+ details: {
490
+ table_name: statement.relation.relname,
491
+ column_names: statement.target_list.map(&:res_target).map(&:name)
492
+ }
493
+ }
494
+ end
495
+
496
+ # Index mutations
497
+
498
+ def self.create_index(statement)
499
+ {
500
+ mutation: :create_index,
501
+ type: :index,
502
+ details: {
503
+ table_name: statement.relation.relname,
504
+ column_names: statement.index_params.map(&:index_elem).map(&:name)
505
+ }
506
+ }
507
+ end
508
+
509
+ def self.drop_index(statement)
510
+ {
511
+ mutation: :drop_index,
512
+ type: :index,
513
+ details: {
514
+ index_name: statement.objects.first.list.items.first.string.sval
515
+ }
516
+ }
517
+ end
518
+
519
+ def self.rename_index(statement)
520
+ {
521
+ mutation: :rename_index,
522
+ type: :index,
523
+ details: {
524
+ old_index_name: statement.relation.relname,
525
+ new_index_name: statement.newname
526
+ }
527
+ }
528
+ end
529
+
530
+ # Type mutations
531
+ #
532
+ def self.alter_table_alter_column_type(statement)
533
+ {
534
+ mutation: :alter_table_alter_column_type,
535
+ type: :column,
536
+ details: {
537
+ table_name: statement.relation.relname,
538
+ column_name: statement.cmds.first.alter_table_cmd.name,
539
+ new_type_name: statement.cmds.first.alter_table_cmd.def.column_def.type_name.names.last.string.sval
540
+ }
541
+ }
542
+ end
543
+
544
+ def self.drop_type(statement)
545
+ {
546
+ mutation: :drop_type,
547
+ type: :type,
548
+ details: {
549
+ type_name: statement.objects.first.type_name.names.first.string.sval
550
+ }
551
+ }
552
+ end
553
+
554
+ def self.rename_type(statement)
555
+ {
556
+ mutation: :rename_type,
557
+ type: :type,
558
+ details: {
559
+ old_type_name: statement.object.list.items.first.string.sval,
560
+ new_type_name: statement.newname
561
+ }
562
+ }
563
+ end
564
+
565
+ # View mutations
566
+
567
+ def self.view(statement)
568
+ {
569
+ mutation: :create_view,
570
+ type: :view,
571
+ details: {
572
+ view_name: statement.view.relname
573
+ }
574
+ }
575
+ end
576
+
577
+ def self.drop_materialized_view(statement)
578
+ {
579
+ mutation: :drop_materialized_view,
580
+ type: :view,
581
+ details: {
582
+ view_name: statement.objects.first.list.items.first.string.sval
583
+ }
584
+ }
585
+ end
586
+
587
+ def self.drop_view(statement)
588
+ {
589
+ mutation: :drop_view,
590
+ type: :view,
591
+ details: {
592
+ view_name: statement.objects.first.list.items.first.string.sval
593
+ }
594
+ }
595
+ end
596
+
597
+ # Constraint mutations
598
+
599
+ def self.alter_table_add_constraint(statement)
600
+ {
601
+ mutation: :alter_table_add_constraint,
602
+ type: :constraint,
603
+ details: {
604
+ table_name: statement.relation.relname,
605
+ constraint_name: statement.cmds.first.alter_table_cmd.def.constraint.conname
606
+ }
607
+ }
608
+ end
609
+
610
+ def self.alter_table_drop_constraint(statement)
611
+ {
612
+ mutation: :alter_table_drop_constraint,
613
+ type: :constraint,
614
+ details: {
615
+ table_name: statement.relation.relname,
616
+ constraint_name: statement.cmds.first.alter_table_cmd.name
617
+ }
618
+ }
619
+ end
620
+
621
+ def self.alter_table_validate_constraint(statement)
622
+ {
623
+ mutation: :alter_table_validate_constraint,
624
+ type: :constraint,
625
+ details: {
626
+ table_name: statement.relation.relname,
627
+ constraint_name: statement.cmds.first.alter_table_cmd.name
628
+ }
629
+ }
630
+ end
631
+
632
+ def self.rename_constraint(statement)
633
+ {
634
+ mutation: :alter_table_rename_constraint,
635
+ type: :constraint,
636
+ details: {
637
+ table_name: statement.relation.relname,
638
+ old_constraint_name: statement.subname,
639
+ new_constraint_name: statement.newname
640
+ }
641
+ }
642
+ end
643
+
644
+ # Unknown mutations
645
+
646
+ def self.unknown(statement)
647
+ {
648
+ mutation: :unknown,
649
+ type: :unknown,
650
+ details: {
651
+ statement: statement.to_s
652
+ }
653
+ }
654
+ end
655
+ end
data/sig/pg_delta.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module PgDelta
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pg_delta
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - rhizomic
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-08-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 13.0.6
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 13.0.6
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 5.19.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 5.19.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: standard
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 1.30.1
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 1.30.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: pg_query
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 4.2.1
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 4.2.1
69
+ description: pg_delta concisely summarizes the changes made by PostgreSQL migrations.
70
+ email:
71
+ executables:
72
+ - pg_delta
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".standard.yml"
77
+ - CHANGELOG.md
78
+ - CODE_OF_CONDUCT.md
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - exe/pg_delta
83
+ - lib/pg_delta.rb
84
+ - lib/pg_delta/version.rb
85
+ - sig/pg_delta.rbs
86
+ homepage:
87
+ licenses:
88
+ - MIT
89
+ metadata:
90
+ source_code_uri: https://github.com/rhizomic/pg_delta
91
+ changelog_uri: https://github.com/rhizomic/pg_delta/blob/master/CHANGELOG.md
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: 2.6.0
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubygems_version: 3.4.10
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: Summarizes the changes made by PostgreSQL migrations.
111
+ test_files: []