activerecord-pg-format-db-structure 0.1.4 → 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: b1360637cb5c24ce89e734dd5bd544c80cd29e019102e2ea7268e49ab32f6f71
4
- data.tar.gz: 4ff8913438ffb2991e1c23d734cd8b5b8e0327b1286ca483c5d46e2b422ac7c4
3
+ metadata.gz: 231e1ce74a64d5b68e27b122465855bf382cb24c5f573da3983b2ab1e11b3c23
4
+ data.tar.gz: 3341fb271ea14d6762cb879f28b6b526d51a76c37c83163fe26ce2955d9b1b83
5
5
  SHA512:
6
- metadata.gz: 5b95bb982ba987fed4a4ae273a337e3d00ee738012bf6c913a81c4d87b2c0e1a3e5ba5caa55bb829df6c242143ffff59e8cecfaab1ab4c517da2232d081023e1
7
- data.tar.gz: 9e6b856118e64bf1de2370206057082f9e34cfea208c201c71f0cc6cdd52ad6c9bdd1c06b0c0ea5e4183b6a9711a126724a9c6398471d1e5fd32ec3c689a4bd7
6
+ metadata.gz: 2f06ac5c8140c6766f4dd77909ba62f60fb2b86b2453d4fad3eb190221de590025eb079e6c2e74999ff3e39fcf90dcac8b1751d52fa33bf9a33ec26ea9c05b91
7
+ data.tar.gz: 790f40bbc56b3b0062e72b7793a9c1645ea286f9a3f557f180aea1fc261dc52401ef721ee2320901cf2ec30d1dc6b763eb0662d30906ab6e351e0b4ab33b1377
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.5] - 2025-02-08
4
+
5
+ - Sort table columns
6
+
3
7
  ## [0.1.4] - 2025-02-08
4
8
 
5
9
  - Sort schema migrations inserts
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`:
@@ -265,7 +270,8 @@ Rails.application.configure do
265
270
  ActiveRecordPgFormatDbStructure::Transforms::InlineSerials,
266
271
  ActiveRecordPgFormatDbStructure::Transforms::InlineConstraints,
267
272
  ActiveRecordPgFormatDbStructure::Transforms::MoveIndicesAfterCreateTable,
268
- ActiveRecordPgFormatDbStructure::Transforms::GroupAlterTableStatements
273
+ ActiveRecordPgFormatDbStructure::Transforms::GroupAlterTableStatements,
274
+ ActiveRecordPgFormatDbStructure::Transforms::SortTableColumns,
269
275
  ]
270
276
 
271
277
  config.activerecord_pg_format_db_structure.deparser = ActiveRecordPgFormatDbStructure::Deparser
@@ -339,6 +345,49 @@ table.
339
345
 
340
346
  Should be run after other operations that inline alter statements.
341
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
+
342
391
  ## Deparser
343
392
 
344
393
  Returns an SQL string from raw PgQuery statements.
@@ -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.4"
4
+ VERSION = "0.1.5"
5
5
  end
@@ -12,6 +12,7 @@ require_relative "activerecord-pg-format-db-structure/transforms/move_indices_af
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
14
  require_relative "activerecord-pg-format-db-structure/transforms/sort_schema_migrations"
15
+ require_relative "activerecord-pg-format-db-structure/transforms/sort_table_columns"
15
16
 
16
17
  module ActiveRecordPgFormatDbStructure
17
18
  DEFAULT_PREPROCESSORS = [
@@ -26,7 +27,8 @@ module ActiveRecordPgFormatDbStructure
26
27
  Transforms::InlineSerials,
27
28
  Transforms::InlineConstraints,
28
29
  Transforms::MoveIndicesAfterCreateTable,
29
- Transforms::GroupAlterTableStatements
30
+ Transforms::GroupAlterTableStatements,
31
+ Transforms::SortTableColumns
30
32
  ].freeze
31
33
 
32
34
  DEFAULT_DEPARSER = Deparser
metadata CHANGED
@@ -1,7 +1,7 @@
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.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jell
@@ -54,6 +54,7 @@ files:
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
56
  - lib/activerecord-pg-format-db-structure/transforms/sort_schema_migrations.rb
57
+ - lib/activerecord-pg-format-db-structure/transforms/sort_table_columns.rb
57
58
  - lib/activerecord-pg-format-db-structure/version.rb
58
59
  homepage: https://github.com/ReifyAB/activerecord-pg-format-db-structure
59
60
  licenses: