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

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: 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: