activerecord-pg-format-db-structure 0.1.3 → 0.1.5

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: 9eadcc90f7e5d8ec76f9f9494e7049646bf68ed37df502df31a1dc9fae87d2b6
4
- data.tar.gz: bee026074f768393231546155fc52e0d00098cbdec981729a63c5a8d1efeb1c4
3
+ metadata.gz: 231e1ce74a64d5b68e27b122465855bf382cb24c5f573da3983b2ab1e11b3c23
4
+ data.tar.gz: 3341fb271ea14d6762cb879f28b6b526d51a76c37c83163fe26ce2955d9b1b83
5
5
  SHA512:
6
- metadata.gz: efe9804695f586ea988faa923d5db83483b0a0e1395bdaaaabd3dcdbd93358d96a7c5a0f7688b5bf1512d4e14d49bf68f46c65000c9700cb3ad8ef7d63094908
7
- data.tar.gz: dbdfa7873bd2551f2c1284befbd039c194cc87aaafea7690df8daabc84724c78a06ff3f021940204aa80cdef569566b9c81bfe4cc0aa3aa6180cb7a660e0bb21
6
+ metadata.gz: 2f06ac5c8140c6766f4dd77909ba62f60fb2b86b2453d4fad3eb190221de590025eb079e6c2e74999ff3e39fcf90dcac8b1751d52fa33bf9a33ec26ea9c05b91
7
+ data.tar.gz: 790f40bbc56b3b0062e72b7793a9c1645ea286f9a3f557f180aea1fc261dc52401ef721ee2320901cf2ec30d1dc6b763eb0662d30906ab6e351e0b4ab33b1377
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.5] - 2025-02-08
4
+
5
+ - Sort table columns
6
+
7
+ ## [0.1.4] - 2025-02-08
8
+
9
+ - Sort schema migrations inserts
10
+
3
11
  ## [0.1.3] - 2025-02-02
4
12
 
5
13
  - Rework SQL formatting by only adding indentation to deparsed statements
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
1
  # activerecord-pg-format-db-structure
2
+ [![Gem Version](https://img.shields.io/gem/v/activerecord-pg-format-db-structure)](https://rubygems.org/gems/activerecord-pg-format-db-structure)
3
+ ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ReifyAB/activerecord-pg-format-db-structure/main.yml)
4
+
2
5
 
3
6
  Automatically cleans up your `structure.sql` file after each rails migration.
4
7
 
@@ -9,6 +12,8 @@ By default, it will:
9
12
  * Inline table constraints
10
13
  * Move index creation below their corresponding tables
11
14
  * Group `ALTER TABLE` statements into a single statement per table
15
+ * Sorts table column declarations (primary key / foreign keys / data / timestamp / constraints)
16
+ * Sorts `schema_migrations` inserts
12
17
  * Removes unnecessary whitespace
13
18
 
14
19
  The task will transform this raw `structure.sql`:
@@ -259,12 +264,14 @@ Rails.application.configure do
259
264
 
260
265
  config.activerecord_pg_format_db_structure.transforms = [
261
266
  ActiveRecordPgFormatDbStructure::Transforms::RemoveCommentsOnExtensions,
267
+ ActiveRecordPgFormatDbStructure::Transforms::SortSchemaMigrations,
262
268
  ActiveRecordPgFormatDbStructure::Transforms::InlinePrimaryKeys,
263
269
  # ActiveRecordPgFormatDbStructure::Transforms::InlineForeignKeys,
264
270
  ActiveRecordPgFormatDbStructure::Transforms::InlineSerials,
265
271
  ActiveRecordPgFormatDbStructure::Transforms::InlineConstraints,
266
272
  ActiveRecordPgFormatDbStructure::Transforms::MoveIndicesAfterCreateTable,
267
- ActiveRecordPgFormatDbStructure::Transforms::GroupAlterTableStatements
273
+ ActiveRecordPgFormatDbStructure::Transforms::GroupAlterTableStatements,
274
+ ActiveRecordPgFormatDbStructure::Transforms::SortTableColumns,
268
275
  ]
269
276
 
270
277
  config.activerecord_pg_format_db_structure.deparser = ActiveRecordPgFormatDbStructure::Deparser
@@ -293,6 +300,10 @@ Remove unnecessary comment, whitespase and empty lines.
293
300
 
294
301
  Remove COMMENT statement applied to extensions
295
302
 
303
+ ### SortSchemaMigrations
304
+
305
+ Sort schema_migrations inserts to be in chronological order, helps with reducing merge conflicts.
306
+
296
307
  ### InlinePrimaryKeys
297
308
 
298
309
  Inlines primary keys with the table declaration
@@ -334,6 +345,49 @@ table.
334
345
 
335
346
  Should be run after other operations that inline alter statements.
336
347
 
348
+ ### SortTableColumns
349
+
350
+ Sort table columns, by order of priority and alphabetically:
351
+
352
+ 1. primary key
353
+ 2. foreign keys
354
+ 3. generic columns
355
+ 4. timestamps
356
+ 5. constraints
357
+
358
+ Note that you can define your own ordering by replacing the default `priority_mapping`:
359
+
360
+ ```ruby
361
+ ActiveRecordPgFormatDbStructure::Transforms::SortTableColumns.priority_mapping = lambda do |sortable_entry|
362
+ case sortable_entry
363
+ in is_column: true, is_primary_key: true, name:
364
+ [0, name]
365
+ in is_column: true, is_foreign_key: true, name:
366
+ [1, name]
367
+ in is_column: true, is_timestamp: false, name:
368
+ [2, name]
369
+ in is_column: true, is_timestamp: true, name:
370
+ [3, name]
371
+ in is_constraint: true, name:
372
+ [5, name]
373
+ end
374
+ end
375
+ ```
376
+
377
+ where `sortable_entry` is an instance of:
378
+
379
+ ```ruby
380
+ SORTABLE_ENTRY = Data.define(
381
+ :name,
382
+ :is_column,
383
+ :is_constraint,
384
+ :is_primary_key,
385
+ :is_foreign_key,
386
+ :is_timestamp,
387
+ :raw_entry
388
+ )
389
+ ```
390
+
337
391
  ## Deparser
338
392
 
339
393
  Returns an SQL string from raw PgQuery statements.
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module ActiveRecordPgFormatDbStructure
6
+ module Transforms
7
+ # Sort schema migration inserts to reduce merge conflicts
8
+ class SortSchemaMigrations < Base
9
+ def transform!
10
+ raw_statements.each do |raw_statement|
11
+ next unless raw_statement.stmt.to_h in insert_stmt: {
12
+ relation: { relname: "schema_migrations" },
13
+ select_stmt: { select_stmt: { values_lists: _ } }
14
+ }
15
+
16
+ raw_statement.stmt.insert_stmt.select_stmt.select_stmt.values_lists.sort_by! do |list|
17
+ list.list.items.first.a_const.sval.sval
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module ActiveRecordPgFormatDbStructure
6
+ module Transforms
7
+ # Sort table columns
8
+ class SortTableColumns < Base
9
+ SORTABLE_ENTRY = Data.define(
10
+ :name,
11
+ :is_column,
12
+ :is_constraint,
13
+ :is_primary_key,
14
+ :is_foreign_key,
15
+ :is_timestamp,
16
+ :raw_entry
17
+ )
18
+
19
+ class << self
20
+ attr_accessor :priority_mapping
21
+ end
22
+
23
+ self.priority_mapping = lambda do |sortable_entry|
24
+ case sortable_entry
25
+ in is_column: true, is_primary_key: true, name:
26
+ [0, name]
27
+ in is_column: true, is_foreign_key: true, name:
28
+ [1, name]
29
+ in is_column: true, is_timestamp: false, name:
30
+ [2, name]
31
+ in is_column: true, is_timestamp: true, name:
32
+ [3, name]
33
+ in is_constraint: true, name:
34
+ [5, name]
35
+ # :nocov:
36
+ # non-reachable else
37
+ # :nocov:
38
+ end
39
+ end
40
+
41
+ def transform!
42
+ foreign_keys = extract_foreign_keys
43
+ primary_keys = extract_primary_keys
44
+ raw_statements.each do |raw_statement|
45
+ next unless raw_statement.stmt.to_h in create_stmt: { relation: { schemaname:, relname: } }
46
+
47
+ raw_statement.stmt.create_stmt.table_elts.sort_by! do |table_elt|
48
+ elt_priority(
49
+ table_elt:,
50
+ primary_keys: primary_keys.fetch({ schemaname:, relname: }, Set.new),
51
+ foreign_keys: foreign_keys.fetch({ schemaname:, relname: }, Set.new)
52
+ )
53
+ end
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def extract_primary_keys
60
+ primary_keys = {}
61
+ raw_statements.each do |raw_statement|
62
+ case raw_statement.stmt.to_h
63
+ in alter_table_stmt: {
64
+ objtype: :OBJECT_TABLE,
65
+ relation: {
66
+ schemaname:,
67
+ relname:
68
+ }
69
+ }
70
+ primary_keys[{ schemaname:, relname: }] ||= Set.new
71
+ raw_statement.stmt.alter_table_stmt.cmds.each do |cmd|
72
+ extract_primary_keys_from_alter_table_cmd(cmd:).each do |key|
73
+ primary_keys[{ schemaname:, relname: }] << key
74
+ end
75
+ end
76
+ in create_stmt: { relation: { schemaname:, relname: } }
77
+ primary_keys[{ schemaname:, relname: }] ||= Set.new
78
+ raw_statement.stmt.create_stmt.table_elts.each do |table_elt|
79
+ extract_primary_keys_from_table_elt(table_elt:).each do |key|
80
+ primary_keys[{ schemaname:, relname: }] << key
81
+ end
82
+ end
83
+ else
84
+ end
85
+ end
86
+ primary_keys
87
+ end
88
+
89
+ def extract_primary_keys_from_alter_table_cmd(cmd:)
90
+ if cmd.to_h in {
91
+ alter_table_cmd: {
92
+ subtype: :AT_AddConstraint,
93
+ def: {
94
+ constraint: {
95
+ contype: :CONSTR_PRIMARY
96
+ }
97
+ }
98
+ }
99
+ }
100
+ cmd.alter_table_cmd.def.constraint.keys.map do |key|
101
+ key.string.sval
102
+ end
103
+ else
104
+ []
105
+ end
106
+ end
107
+
108
+ def extract_primary_keys_from_table_elt(table_elt:)
109
+ case table_elt.to_h
110
+ in column_def: { constraints: [*, {constraint: {contype: :CONSTR_PRIMARY}}, *] }
111
+ [table_elt.column_def.colname]
112
+ in constraint: { contype: :CONSTR_PRIMARY }
113
+ table_elt.constraint.keys.map do |key|
114
+ key.string.sval
115
+ end
116
+ else
117
+ []
118
+ end
119
+ end
120
+
121
+ def extract_foreign_keys
122
+ foreign_keys = {}
123
+ raw_statements.each do |raw_statement|
124
+ case raw_statement.stmt.to_h
125
+ in alter_table_stmt: {
126
+ objtype: :OBJECT_TABLE,
127
+ relation: {
128
+ schemaname:,
129
+ relname:
130
+ }
131
+ }
132
+ foreign_keys[{ schemaname:, relname: }] ||= Set.new
133
+ raw_statement.stmt.alter_table_stmt.cmds.each do |cmd|
134
+ extract_foreign_keys_from_alter_table_cmd(cmd:).each do |key|
135
+ foreign_keys[{ schemaname:, relname: }] << key
136
+ end
137
+ end
138
+ in create_stmt: { relation: { schemaname:, relname: } }
139
+ foreign_keys[{ schemaname:, relname: }] ||= Set.new
140
+ raw_statement.stmt.create_stmt.table_elts.each do |table_elt|
141
+ extract_foreign_keys_from_table_elt(table_elt:).each do |key|
142
+ foreign_keys[{ schemaname:, relname: }] << key
143
+ end
144
+ end
145
+ else
146
+ end
147
+ end
148
+ foreign_keys
149
+ end
150
+
151
+ def extract_foreign_keys_from_alter_table_cmd(cmd:)
152
+ if cmd.to_h in {
153
+ alter_table_cmd: {
154
+ subtype: :AT_AddConstraint,
155
+ def: {
156
+ constraint: {
157
+ contype: :CONSTR_FOREIGN
158
+ }
159
+ }
160
+ }
161
+ }
162
+ cmd.alter_table_cmd.def.constraint.fk_attrs.map do |fk_attr|
163
+ fk_attr.string.sval
164
+ end
165
+ else
166
+ []
167
+ end
168
+ end
169
+
170
+ def extract_foreign_keys_from_table_elt(table_elt:)
171
+ case table_elt.to_h
172
+ in column_def: { constraints: [*, {constraint: {contype: :CONSTR_FOREIGN}}, *] }
173
+ [table_elt.column_def.colname]
174
+ in constraint: { contype: :CONSTR_FOREIGN }
175
+ table_elt.constraint.fk_attrs.map do |fk_attr|
176
+ fk_attr.string.sval
177
+ end
178
+ else
179
+ []
180
+ end
181
+ end
182
+
183
+ def elt_priority(table_elt:, primary_keys:, foreign_keys:)
184
+ case table_elt.to_h
185
+ in column_def: _
186
+ self.class.priority_mapping[
187
+ SORTABLE_ENTRY.new(
188
+ name: table_elt.column_def.colname,
189
+ is_column: true,
190
+ is_constraint: false,
191
+ is_primary_key: primary_keys.include?(table_elt.column_def.colname),
192
+ is_timestamp: timestamp?(table_elt.column_def),
193
+ is_foreign_key: foreign_keys.include?(table_elt.column_def.colname),
194
+ raw_entry: table_elt
195
+ )
196
+ ]
197
+ in constraint: _
198
+ self.class.priority_mapping[
199
+ SORTABLE_ENTRY.new(
200
+ name: table_elt.constraint.conname,
201
+ is_column: false,
202
+ is_constraint: true,
203
+ is_primary_key: false,
204
+ is_timestamp: false,
205
+ is_foreign_key: false,
206
+ raw_entry: table_elt
207
+ )
208
+ ]
209
+ # :nocov:
210
+ # non-reachable else
211
+ # :nocov:
212
+ end
213
+ end
214
+
215
+ def timestamp?(table_elt)
216
+ table_elt.type_name.names.any? { |name| name.to_h in string: { sval: "timestamp" } }
217
+ end
218
+ end
219
+ end
220
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordPgFormatDbStructure
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.5"
5
5
  end
@@ -11,6 +11,8 @@ require_relative "activerecord-pg-format-db-structure/transforms/inline_foreign_
11
11
  require_relative "activerecord-pg-format-db-structure/transforms/move_indices_after_create_table"
12
12
  require_relative "activerecord-pg-format-db-structure/transforms/inline_constraints"
13
13
  require_relative "activerecord-pg-format-db-structure/transforms/group_alter_table_statements"
14
+ require_relative "activerecord-pg-format-db-structure/transforms/sort_schema_migrations"
15
+ require_relative "activerecord-pg-format-db-structure/transforms/sort_table_columns"
14
16
 
15
17
  module ActiveRecordPgFormatDbStructure
16
18
  DEFAULT_PREPROCESSORS = [
@@ -19,12 +21,14 @@ module ActiveRecordPgFormatDbStructure
19
21
 
20
22
  DEFAULT_TRANSFORMS = [
21
23
  Transforms::RemoveCommentsOnExtensions,
24
+ Transforms::SortSchemaMigrations,
22
25
  Transforms::InlinePrimaryKeys,
23
26
  # Transforms::InlineForeignKeys,
24
27
  Transforms::InlineSerials,
25
28
  Transforms::InlineConstraints,
26
29
  Transforms::MoveIndicesAfterCreateTable,
27
- Transforms::GroupAlterTableStatements
30
+ Transforms::GroupAlterTableStatements,
31
+ Transforms::SortTableColumns
28
32
  ].freeze
29
33
 
30
34
  DEFAULT_DEPARSER = Deparser
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-pg-format-db-structure
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jell
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-02-02 00:00:00.000000000 Z
10
+ date: 2025-02-08 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: pg_query
@@ -53,6 +53,8 @@ files:
53
53
  - lib/activerecord-pg-format-db-structure/transforms/inline_serials.rb
54
54
  - lib/activerecord-pg-format-db-structure/transforms/move_indices_after_create_table.rb
55
55
  - lib/activerecord-pg-format-db-structure/transforms/remove_comments_on_extensions.rb
56
+ - lib/activerecord-pg-format-db-structure/transforms/sort_schema_migrations.rb
57
+ - lib/activerecord-pg-format-db-structure/transforms/sort_table_columns.rb
56
58
  - lib/activerecord-pg-format-db-structure/version.rb
57
59
  homepage: https://github.com/ReifyAB/activerecord-pg-format-db-structure
58
60
  licenses: