activerecord-pg-format-db-structure 0.1.2 → 0.1.4
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 +4 -4
- data/.rubocop.yml +2 -11
- data/CHANGELOG.md +8 -0
- data/README.md +6 -1
- data/lib/activerecord-pg-format-db-structure/deparser.rb +41 -198
- data/lib/activerecord-pg-format-db-structure/indenter.rb +278 -0
- data/lib/activerecord-pg-format-db-structure/transforms/sort_schema_migrations.rb +23 -0
- data/lib/activerecord-pg-format-db-structure/version.rb +1 -1
- data/lib/activerecord-pg-format-db-structure.rb +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b1360637cb5c24ce89e734dd5bd544c80cd29e019102e2ea7268e49ab32f6f71
|
|
4
|
+
data.tar.gz: 4ff8913438ffb2991e1c23d734cd8b5b8e0327b1286ca483c5d46e2b422ac7c4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5b95bb982ba987fed4a4ae273a337e3d00ee738012bf6c913a81c4d87b2c0e1a3e5ba5caa55bb829df6c242143ffff59e8cecfaab1ab4c517da2232d081023e1
|
|
7
|
+
data.tar.gz: 9e6b856118e64bf1de2370206057082f9e34cfea208c201c71f0cc6cdd52ad6c9bdd1c06b0c0ea5e4183b6a9711a126724a9c6398471d1e5fd32ec3c689a4bd7
|
data/.rubocop.yml
CHANGED
|
@@ -15,19 +15,10 @@ Style/StringLiteralsInInterpolation:
|
|
|
15
15
|
Naming/FileName:
|
|
16
16
|
Enabled: false
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Naming/VariableNumber:
|
|
19
19
|
Enabled: false
|
|
20
20
|
|
|
21
|
-
Metrics
|
|
22
|
-
Enabled: false
|
|
23
|
-
|
|
24
|
-
Metrics/AbcSize:
|
|
25
|
-
Enabled: false
|
|
26
|
-
|
|
27
|
-
Metrics/ClassLength:
|
|
28
|
-
Enabled: false
|
|
29
|
-
|
|
30
|
-
Metrics/CyclomaticComplexity:
|
|
21
|
+
Metrics:
|
|
31
22
|
Enabled: false
|
|
32
23
|
|
|
33
24
|
RSpec/SpecFilePathFormat:
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.1.4] - 2025-02-08
|
|
4
|
+
|
|
5
|
+
- Sort schema migrations inserts
|
|
6
|
+
|
|
7
|
+
## [0.1.3] - 2025-02-02
|
|
8
|
+
|
|
9
|
+
- Rework SQL formatting by only adding indentation to deparsed statements
|
|
10
|
+
|
|
3
11
|
## [0.1.2] - 2025-01-30
|
|
4
12
|
|
|
5
13
|
- Get rid of `anbt-sql-formatter` dependency since it breaks queries with type casting
|
data/README.md
CHANGED
|
@@ -221,7 +221,7 @@ ALTER TABLE ONLY public.comments
|
|
|
221
221
|
|
|
222
222
|
|
|
223
223
|
INSERT INTO schema_migrations (version) VALUES
|
|
224
|
-
|
|
224
|
+
('20250124155339')
|
|
225
225
|
;
|
|
226
226
|
```
|
|
227
227
|
|
|
@@ -259,6 +259,7 @@ Rails.application.configure do
|
|
|
259
259
|
|
|
260
260
|
config.activerecord_pg_format_db_structure.transforms = [
|
|
261
261
|
ActiveRecordPgFormatDbStructure::Transforms::RemoveCommentsOnExtensions,
|
|
262
|
+
ActiveRecordPgFormatDbStructure::Transforms::SortSchemaMigrations,
|
|
262
263
|
ActiveRecordPgFormatDbStructure::Transforms::InlinePrimaryKeys,
|
|
263
264
|
# ActiveRecordPgFormatDbStructure::Transforms::InlineForeignKeys,
|
|
264
265
|
ActiveRecordPgFormatDbStructure::Transforms::InlineSerials,
|
|
@@ -293,6 +294,10 @@ Remove unnecessary comment, whitespase and empty lines.
|
|
|
293
294
|
|
|
294
295
|
Remove COMMENT statement applied to extensions
|
|
295
296
|
|
|
297
|
+
### SortSchemaMigrations
|
|
298
|
+
|
|
299
|
+
Sort schema_migrations inserts to be in chronological order, helps with reducing merge conflicts.
|
|
300
|
+
|
|
296
301
|
### InlinePrimaryKeys
|
|
297
302
|
|
|
298
303
|
Inlines primary keys with the table declaration
|
|
@@ -1,245 +1,88 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "pg_query"
|
|
4
|
+
require_relative "indenter"
|
|
4
5
|
|
|
5
6
|
module ActiveRecordPgFormatDbStructure
|
|
6
7
|
# Returns a list of SQL strings from a list of PgQuery::RawStmt.
|
|
7
8
|
class Deparser
|
|
8
9
|
attr_reader :source
|
|
9
10
|
|
|
10
|
-
PRETTY_INDENT_STRING = " "
|
|
11
|
-
|
|
12
11
|
def initialize(source)
|
|
13
12
|
@source = source
|
|
14
13
|
end
|
|
15
14
|
|
|
16
15
|
def deparse_raw_statement(raw_statement)
|
|
17
16
|
case raw_statement.to_h
|
|
17
|
+
in stmt: { insert_stmt: _ }
|
|
18
|
+
deparse_insert_stmt(raw_statement.stmt.insert_stmt)
|
|
18
19
|
in stmt: { create_stmt: _ }
|
|
19
20
|
deparse_create_stmt(raw_statement.stmt.create_stmt)
|
|
20
|
-
in stmt: { index_stmt: _ }
|
|
21
|
-
deparse_index_stmt(raw_statement.stmt.index_stmt)
|
|
22
|
-
in stmt: { alter_table_stmt: _ }
|
|
23
|
-
deparse_alter_table_stmt(raw_statement.stmt.alter_table_stmt)
|
|
24
|
-
in stmt: { select_stmt: _ }
|
|
25
|
-
deparse_select_stmt(raw_statement.stmt.select_stmt)
|
|
26
|
-
in stmt: { insert_stmt: _ }
|
|
27
|
-
deparse_insert_statement(raw_statement.stmt.insert_stmt)
|
|
28
|
-
in stmt: { create_table_as_stmt: _ }
|
|
29
|
-
deparse_create_table_as_stmt(raw_statement.stmt.create_table_as_stmt)
|
|
30
21
|
in stmt: { view_stmt: _ }
|
|
31
22
|
deparse_view_stmt(raw_statement.stmt.view_stmt)
|
|
23
|
+
in stmt: { create_table_as_stmt: _ }
|
|
24
|
+
deparse_create_table_as_stmt(raw_statement.stmt.create_table_as_stmt)
|
|
25
|
+
in stmt: { index_stmt: _ }
|
|
26
|
+
deparse_stmt_compact(raw_statement.stmt.index_stmt)
|
|
32
27
|
else
|
|
33
|
-
|
|
28
|
+
deparse_stmt_generic(raw_statement.stmt.inner)
|
|
34
29
|
end
|
|
35
30
|
end
|
|
36
31
|
|
|
37
32
|
private
|
|
38
33
|
|
|
39
|
-
def
|
|
40
|
-
"\n
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
generic_query_str = +"\n\n"
|
|
45
|
-
generic_query_str << deparse_leaf_select_stmt(select_stmt)
|
|
46
|
-
generic_query_str << ";"
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def deparse_index_stmt(index_stmt)
|
|
50
|
-
deparse_stmt(index_stmt)
|
|
34
|
+
def deparse_stmt_generic(stmt)
|
|
35
|
+
generic_str = +"\n\n"
|
|
36
|
+
generic_str << deparse_stmt_and_indent(stmt)
|
|
37
|
+
generic_str << ";"
|
|
38
|
+
generic_str
|
|
51
39
|
end
|
|
52
40
|
|
|
53
|
-
def
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
cmds: []
|
|
59
|
-
)
|
|
60
|
-
).chomp(" ")
|
|
61
|
-
|
|
62
|
-
alter_table_cmds_str = alter_table_stmt.cmds.map do |cmd|
|
|
63
|
-
"\n #{deparse_alter_table_cmd(cmd)}"
|
|
64
|
-
end.join(",")
|
|
65
|
-
|
|
66
|
-
alter_table_str << alter_table_cmds_str
|
|
67
|
-
alter_table_str << ";"
|
|
68
|
-
alter_table_str
|
|
41
|
+
def deparse_stmt_compact(stmt)
|
|
42
|
+
compact_str = +"\n"
|
|
43
|
+
compact_str << deparse_stmt(stmt)
|
|
44
|
+
compact_str << ";"
|
|
45
|
+
compact_str
|
|
69
46
|
end
|
|
70
47
|
|
|
71
|
-
def
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
)
|
|
77
|
-
).gsub(/\AALTER ONLY tmp /, "")
|
|
48
|
+
def deparse_insert_stmt(stmt)
|
|
49
|
+
insert_str = +"\n\n\n"
|
|
50
|
+
insert_str << deparse_stmt_and_indent(stmt)
|
|
51
|
+
insert_str << "\n;"
|
|
52
|
+
insert_str
|
|
78
53
|
end
|
|
79
54
|
|
|
80
|
-
def deparse_create_stmt(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
colname: "placeholder_column",
|
|
84
|
-
type_name: {
|
|
85
|
-
names: [PgQuery::Node.from_string("placeholder_type")]
|
|
86
|
-
}
|
|
87
|
-
)
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
table_str = "\n\n\n-- Name: #{create_stmt.relation.relname}; Type: TABLE;\n\n"
|
|
91
|
-
table_str << PgQuery.deparse_stmt(
|
|
92
|
-
PgQuery::CreateStmt.new(
|
|
93
|
-
**create_stmt.to_h,
|
|
94
|
-
table_elts: [placeholder_column]
|
|
95
|
-
)
|
|
96
|
-
)
|
|
55
|
+
def deparse_create_stmt(stmt)
|
|
56
|
+
table_str = "\n\n\n-- Name: #{stmt.relation.relname}; Type: TABLE;\n\n"
|
|
57
|
+
table_str << deparse_stmt_and_indent(stmt)
|
|
97
58
|
table_str << ";"
|
|
98
|
-
|
|
99
|
-
table_columns = create_stmt.table_elts.map do |elt|
|
|
100
|
-
"\n #{deparse_table_elt(elt)}"
|
|
101
|
-
end.join(",")
|
|
102
|
-
table_columns << "\n"
|
|
103
|
-
|
|
104
|
-
table_str[deparse_table_elt(placeholder_column)] = table_columns
|
|
105
|
-
|
|
106
59
|
table_str
|
|
107
60
|
end
|
|
108
61
|
|
|
109
|
-
def deparse_table_elt(elt)
|
|
110
|
-
PgQuery.deparse_stmt(
|
|
111
|
-
PgQuery::CreateStmt.new(
|
|
112
|
-
relation: { relname: "tmp" }, table_elts: [elt]
|
|
113
|
-
)
|
|
114
|
-
).gsub(/\ACREATE TABLE ONLY tmp \((.*)\)\z/, '\1')
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def deparse_create_table_as_stmt(stmt)
|
|
118
|
-
create_table_as_stmt_str = +"\n\n"
|
|
119
|
-
create_table_as_stmt_str << PgQuery.deparse_stmt(
|
|
120
|
-
PgQuery::CreateTableAsStmt.new(
|
|
121
|
-
**stmt.to_h,
|
|
122
|
-
query: PgQuery::Node.from(placeholder_query_stmt)
|
|
123
|
-
)
|
|
124
|
-
)
|
|
125
|
-
create_table_as_stmt_str << ";"
|
|
126
|
-
|
|
127
|
-
query_str = +"(\n"
|
|
128
|
-
query_str << deparse_leaf_select_stmt(stmt.query.select_stmt).gsub(/^/, PRETTY_INDENT_STRING)
|
|
129
|
-
query_str << "\n)"
|
|
130
|
-
|
|
131
|
-
create_table_as_stmt_str[placeholder_query_string] = query_str
|
|
132
|
-
create_table_as_stmt_str
|
|
133
|
-
end
|
|
134
|
-
|
|
135
62
|
def deparse_view_stmt(stmt)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
query: PgQuery::Node.from(placeholder_query_stmt)
|
|
141
|
-
)
|
|
142
|
-
)
|
|
143
|
-
view_stmt_str << ";"
|
|
144
|
-
|
|
145
|
-
query_str = +"(\n"
|
|
146
|
-
query_str << deparse_leaf_select_stmt(stmt.query.select_stmt).gsub(/^/, PRETTY_INDENT_STRING)
|
|
147
|
-
query_str << "\n)"
|
|
148
|
-
|
|
149
|
-
view_stmt_str[placeholder_query_string] = query_str
|
|
150
|
-
view_stmt_str
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def deparse_insert_statement(insert_stmt)
|
|
154
|
-
insert_stmt_str = +"\n\n\n"
|
|
155
|
-
insert_stmt_str << PgQuery.deparse_stmt(
|
|
156
|
-
PgQuery::InsertStmt.new(
|
|
157
|
-
**insert_stmt.to_h,
|
|
158
|
-
select_stmt: PgQuery::Node.from(placeholder_query_stmt)
|
|
159
|
-
)
|
|
160
|
-
)
|
|
161
|
-
insert_stmt_str << "\n;"
|
|
162
|
-
|
|
163
|
-
query_str = if insert_stmt.select_stmt.inner.values_lists.any?
|
|
164
|
-
deparse_values_list_select_stmt(insert_stmt.select_stmt.inner)
|
|
165
|
-
else
|
|
166
|
-
deparse_leaf_select_stmt(insert_stmt.select_stmt.inner)
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
insert_stmt_str[placeholder_query_string] = query_str
|
|
170
|
-
insert_stmt_str
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def deparse_values_list_select_stmt(select_stmt)
|
|
174
|
-
values_str = +"VALUES\n "
|
|
175
|
-
values_str << select_stmt.values_lists.map do |values_list|
|
|
176
|
-
PgQuery.deparse_stmt(PgQuery::SelectStmt.new(values_lists: [values_list])).gsub(/\AVALUES /, "")
|
|
177
|
-
end.join("\n,")
|
|
178
|
-
values_str
|
|
63
|
+
table_str = "\n\n\n-- Name: #{stmt.view.relname}; Type: VIEW;\n\n"
|
|
64
|
+
table_str << deparse_stmt_and_indent(stmt)
|
|
65
|
+
table_str << ";"
|
|
66
|
+
table_str
|
|
179
67
|
end
|
|
180
68
|
|
|
181
|
-
def
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
if select_stmt.with_clause
|
|
187
|
-
placeholder_with_clause = PgQuery::WithClause.new(
|
|
188
|
-
**select_stmt.with_clause.to_h,
|
|
189
|
-
ctes: select_stmt.with_clause.ctes.map do |cte|
|
|
190
|
-
PgQuery::Node.from(
|
|
191
|
-
PgQuery::CommonTableExpr.new(
|
|
192
|
-
**cte.inner.to_h,
|
|
193
|
-
ctequery: PgQuery::Node.from(placeholder_query_stmt("placeholder_for_#{cte.inner.ctename}_cte"))
|
|
194
|
-
)
|
|
195
|
-
)
|
|
196
|
-
end
|
|
197
|
-
)
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
select_stmt_str = PgQuery.deparse_stmt(
|
|
201
|
-
PgQuery::SelectStmt.new(
|
|
202
|
-
**select_stmt.to_h,
|
|
203
|
-
with_clause: placeholder_with_clause,
|
|
204
|
-
target_list: ([PgQuery::Node.from(target_list_placeholder)] if select_stmt.target_list.any?)
|
|
205
|
-
)
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
if select_stmt.target_list.any?
|
|
209
|
-
target_list_str = +"\n"
|
|
210
|
-
target_list_str << select_stmt.target_list.map do |target|
|
|
211
|
-
deparse_res_target(target.inner).gsub(/^/, PRETTY_INDENT_STRING)
|
|
212
|
-
end.join(",\n")
|
|
213
|
-
target_list_str << "\n"
|
|
214
|
-
|
|
215
|
-
select_stmt_str[deparse_res_target(target_list_placeholder)] = target_list_str
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
select_stmt.with_clause&.ctes&.each do |cte|
|
|
219
|
-
cte_str = +"\n"
|
|
220
|
-
cte_str << deparse_leaf_select_stmt(cte.inner.ctequery.inner).gsub(/^/, PRETTY_INDENT_STRING)
|
|
221
|
-
cte_str << "\n"
|
|
222
|
-
|
|
223
|
-
select_stmt_str["SELECT placeholder_for_#{cte.inner.ctename}_cte"] = cte_str
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
select_stmt_str.gsub!(/ +$/, "")
|
|
69
|
+
def deparse_create_table_as_stmt(stmt)
|
|
70
|
+
table_str = "\n\n\n-- Name: #{stmt.into.rel.relname}; Type: MATERIALIZED VIEW;\n\n"
|
|
71
|
+
table_str << deparse_stmt_and_indent(stmt)
|
|
227
72
|
|
|
228
|
-
|
|
229
|
-
|
|
73
|
+
# couldn't find a better solution for this, but probably an OK workaround?
|
|
74
|
+
table_str.gsub!(/ WITH NO DATA\z/, "\nWITH NO DATA")
|
|
230
75
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
PgQuery::SelectStmt.new(target_list: [PgQuery::Node.from(res_target)])
|
|
234
|
-
).gsub(/\ASELECT /, "")
|
|
76
|
+
table_str << ";"
|
|
77
|
+
table_str
|
|
235
78
|
end
|
|
236
79
|
|
|
237
|
-
def
|
|
238
|
-
|
|
80
|
+
def deparse_stmt_and_indent(stmt)
|
|
81
|
+
Indenter.new(deparse_stmt(stmt)).indent
|
|
239
82
|
end
|
|
240
83
|
|
|
241
|
-
def
|
|
242
|
-
PgQuery.
|
|
84
|
+
def deparse_stmt(stmt)
|
|
85
|
+
PgQuery.deparse_stmt(stmt)
|
|
243
86
|
end
|
|
244
87
|
end
|
|
245
88
|
end
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pg_query"
|
|
4
|
+
|
|
5
|
+
module ActiveRecordPgFormatDbStructure
|
|
6
|
+
# Inserts newlines and whitespace on a deparsed SQL string
|
|
7
|
+
class Indenter
|
|
8
|
+
Token = Data.define(:type, :string)
|
|
9
|
+
|
|
10
|
+
# Reserved Keywords
|
|
11
|
+
ADD = :ADD_P
|
|
12
|
+
ALTER = :ALTER
|
|
13
|
+
AND = :AND
|
|
14
|
+
AS = :AS
|
|
15
|
+
CASE = :CASE
|
|
16
|
+
CREATE = :CREATE
|
|
17
|
+
CROSS = :CROSS
|
|
18
|
+
DROP = :DROP
|
|
19
|
+
ELSE = :ELSE
|
|
20
|
+
END_P = :END_P
|
|
21
|
+
EXCEPT = :EXCEPT
|
|
22
|
+
FETCH = :FETCH
|
|
23
|
+
FOR = :FOR
|
|
24
|
+
FROM = :FROM
|
|
25
|
+
GROUP = :GROUP_P
|
|
26
|
+
HAVING = :HAVING
|
|
27
|
+
INNER = :INNER
|
|
28
|
+
INSERT = :INSERT
|
|
29
|
+
INTERSECT = :INTERSECT
|
|
30
|
+
JOIN = :JOIN
|
|
31
|
+
LEFT = :LEFT
|
|
32
|
+
LIMIT = :LIMIT
|
|
33
|
+
OFFSET = :OFFSET
|
|
34
|
+
OR = :OR
|
|
35
|
+
ORDER = :ORDER
|
|
36
|
+
RIGHT = :RIGHT
|
|
37
|
+
SELECT = :SELECT
|
|
38
|
+
TABLE = :TABLE
|
|
39
|
+
THEN = :THEN
|
|
40
|
+
UNION = :UNION
|
|
41
|
+
VALUES = :VALUES
|
|
42
|
+
VIEW = :VIEW
|
|
43
|
+
WHEN = :WHEN
|
|
44
|
+
WHERE = :WHERE
|
|
45
|
+
WHITESPACE = :WHITESPACE
|
|
46
|
+
WINDOW = :WINDOW
|
|
47
|
+
WITH = :WITH
|
|
48
|
+
|
|
49
|
+
# ASCII tokens
|
|
50
|
+
COMMA = :ASCII_44
|
|
51
|
+
OPEN_PARENS = :ASCII_40
|
|
52
|
+
CLOSE_PARENS = :ASCII_41
|
|
53
|
+
|
|
54
|
+
# Helpers
|
|
55
|
+
PARENS = :PARENS
|
|
56
|
+
INDENT_STRING = " "
|
|
57
|
+
SELECT_PADDING = " "
|
|
58
|
+
TABLE_ELTS = :TABLE_ELTS
|
|
59
|
+
|
|
60
|
+
attr_reader :source
|
|
61
|
+
|
|
62
|
+
def initialize(source)
|
|
63
|
+
@source = PgQuery.deparse(PgQuery.parse(source).tree)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def indent
|
|
67
|
+
output = Output.new
|
|
68
|
+
prev_token = nil
|
|
69
|
+
tokens.each do |token|
|
|
70
|
+
output.current_token = token
|
|
71
|
+
case { current_token: token.type, prev_token: prev_token&.type, inside: output.current_scope_type }
|
|
72
|
+
in { current_token: CREATE, inside: nil }
|
|
73
|
+
output.append_scope(type: CREATE)
|
|
74
|
+
output.append_token
|
|
75
|
+
in { current_token: ALTER, inside: nil }
|
|
76
|
+
output.append_scope(type: ALTER, indent: 1)
|
|
77
|
+
output.append_token
|
|
78
|
+
in { current_token: INSERT, inside: nil }
|
|
79
|
+
output.append_scope(type: INSERT, indent: 0)
|
|
80
|
+
output.append_token
|
|
81
|
+
in { current_token: VIEW, inside: CREATE }
|
|
82
|
+
output.append_scope(type: VIEW, indent: 2)
|
|
83
|
+
output.append_token
|
|
84
|
+
in { current_token: WITH, inside: CREATE | VIEW | nil }
|
|
85
|
+
output.append_scope(type: WITH)
|
|
86
|
+
output.append_token
|
|
87
|
+
in { current_token: WHITESPACE, prev_token: AS, inside: VIEW }
|
|
88
|
+
output.append_token
|
|
89
|
+
output.newline
|
|
90
|
+
output.apply_indent
|
|
91
|
+
in { current_token: WHITESPACE, prev_token: COMMA, inside: WITH | SELECT | TABLE_ELTS }
|
|
92
|
+
output.append_token
|
|
93
|
+
output.newline
|
|
94
|
+
output.apply_indent
|
|
95
|
+
in { current_token: COMMA, inside: INSERT }
|
|
96
|
+
output.newline
|
|
97
|
+
output.apply_indent
|
|
98
|
+
output.append_token
|
|
99
|
+
in { current_token: SELECT, inside: WITH | INSERT }
|
|
100
|
+
output.pop_scope
|
|
101
|
+
output.newline
|
|
102
|
+
output.apply_indent
|
|
103
|
+
output.append_token
|
|
104
|
+
output.append_scope(type: SELECT, indent: 2, padding: SELECT_PADDING)
|
|
105
|
+
in { current_token: SELECT }
|
|
106
|
+
output.append_token
|
|
107
|
+
output.append_scope(type: SELECT, indent: 2, padding: SELECT_PADDING)
|
|
108
|
+
in { current_token: ALTER | ADD | DROP, inside: ALTER }
|
|
109
|
+
output.newline
|
|
110
|
+
output.apply_indent
|
|
111
|
+
output.append_token
|
|
112
|
+
in {
|
|
113
|
+
current_token: CROSS | INNER | LEFT | RIGHT | JOIN => type,
|
|
114
|
+
inside: SELECT | FROM | JOIN
|
|
115
|
+
}
|
|
116
|
+
output.pop_scope
|
|
117
|
+
output.newline
|
|
118
|
+
output.apply_indent
|
|
119
|
+
output.append_token
|
|
120
|
+
output.append_scope(type:, indent: 0)
|
|
121
|
+
in {
|
|
122
|
+
current_token: CROSS | INNER | LEFT | RIGHT | JOIN => type,
|
|
123
|
+
inside: CROSS | INNER | LEFT | RIGHT
|
|
124
|
+
}
|
|
125
|
+
output.append_token
|
|
126
|
+
output.pop_scope
|
|
127
|
+
output.append_scope(type:, indent: 0)
|
|
128
|
+
in {
|
|
129
|
+
current_token: FROM | WHERE | GROUP | ORDER | WINDOW | HAVING | LIMIT | OFFSET | FETCH | FOR | UNION |
|
|
130
|
+
INTERSECT | EXCEPT => token_type
|
|
131
|
+
}
|
|
132
|
+
output.pop_scope
|
|
133
|
+
output.newline
|
|
134
|
+
output.apply_indent
|
|
135
|
+
output.append_token
|
|
136
|
+
output.append_scope(type: token_type, indent: 1)
|
|
137
|
+
in { current_token: OR | AND, inside: WHERE }
|
|
138
|
+
output.newline
|
|
139
|
+
output.apply_indent
|
|
140
|
+
output.append_token(rjust: 3)
|
|
141
|
+
in { current_token: CASE }
|
|
142
|
+
output.append_token
|
|
143
|
+
output.append_scope(type: CASE, indent: 1, padding: output.current_padding)
|
|
144
|
+
in { current_token: WHEN | ELSE, inside: CASE }
|
|
145
|
+
output.newline
|
|
146
|
+
output.apply_indent
|
|
147
|
+
output.append_token
|
|
148
|
+
in { current_token: END_P }
|
|
149
|
+
output.pop_scope
|
|
150
|
+
output.newline
|
|
151
|
+
output.apply_indent
|
|
152
|
+
output.append_token
|
|
153
|
+
in { current_token: VALUES, inside: INSERT }
|
|
154
|
+
output.append_token
|
|
155
|
+
output.newline
|
|
156
|
+
output.append_whitespace
|
|
157
|
+
in { current_token: OPEN_PARENS, inside: CREATE }
|
|
158
|
+
output.append_token
|
|
159
|
+
output.newline
|
|
160
|
+
output.append_scope(type: TABLE_ELTS, indent: 2)
|
|
161
|
+
output.apply_indent
|
|
162
|
+
in { current_token: OPEN_PARENS, inside: WITH }
|
|
163
|
+
output.append_token
|
|
164
|
+
output.newline
|
|
165
|
+
output.append_scope(type: PARENS, indent: 2)
|
|
166
|
+
output.apply_indent
|
|
167
|
+
in { current_token: OPEN_PARENS, inside: JOIN }
|
|
168
|
+
output.append_token
|
|
169
|
+
output.newline
|
|
170
|
+
output.append_scope(type: PARENS, indent: 2)
|
|
171
|
+
output.apply_indent
|
|
172
|
+
in { current_token: OPEN_PARENS }
|
|
173
|
+
output.append_scope(type: PARENS)
|
|
174
|
+
output.append_token
|
|
175
|
+
in { current_token: CLOSE_PARENS, inside: TABLE_ELTS }
|
|
176
|
+
output.pop_scope
|
|
177
|
+
output.newline
|
|
178
|
+
output.apply_indent
|
|
179
|
+
output.append_token
|
|
180
|
+
in { current_token: CLOSE_PARENS, inside: PARENS }
|
|
181
|
+
output.pop_scope
|
|
182
|
+
output.append_token
|
|
183
|
+
in { current_token: CLOSE_PARENS }
|
|
184
|
+
loop do
|
|
185
|
+
break if output.pop_scope in PARENS | nil
|
|
186
|
+
end
|
|
187
|
+
output.newline
|
|
188
|
+
output.apply_indent
|
|
189
|
+
output.append_token
|
|
190
|
+
else
|
|
191
|
+
output.append_token
|
|
192
|
+
end
|
|
193
|
+
prev_token = token
|
|
194
|
+
end
|
|
195
|
+
output.to_s
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
private
|
|
199
|
+
|
|
200
|
+
def tokens
|
|
201
|
+
tmp_tokens = []
|
|
202
|
+
prev_token = Data.define(:end).new(0)
|
|
203
|
+
PgQuery.scan(source).first.tokens.each do |token|
|
|
204
|
+
if prev_token.end != token.start
|
|
205
|
+
tmp_tokens << Token.new(
|
|
206
|
+
type: WHITESPACE,
|
|
207
|
+
string: " "
|
|
208
|
+
)
|
|
209
|
+
end
|
|
210
|
+
prev_token = token
|
|
211
|
+
tmp_tokens << Token.new(
|
|
212
|
+
type: token.token,
|
|
213
|
+
string: source[token.start...token.end]
|
|
214
|
+
)
|
|
215
|
+
end
|
|
216
|
+
tmp_tokens
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Wrapper that ensures we only append whitespace, and always
|
|
220
|
+
# append the current token exactly once for each loop.
|
|
221
|
+
class Output
|
|
222
|
+
Scope = Data.define(:type, :indent, :padding)
|
|
223
|
+
|
|
224
|
+
def initialize
|
|
225
|
+
@string = +""
|
|
226
|
+
@scopes = [Scope.new(type: nil, indent: 0, padding: "")]
|
|
227
|
+
@current_token = nil
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def to_s
|
|
231
|
+
# clean extra whitespace at end of string
|
|
232
|
+
@string.gsub(/\s+\n/, "\n").freeze
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def current_scope_type
|
|
236
|
+
@scopes.last.type
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def current_token=(token)
|
|
240
|
+
raise "Previous token was not appended!" unless @current_token.nil?
|
|
241
|
+
|
|
242
|
+
@current_token = token
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def append_scope(type:, indent: 0, padding: "")
|
|
246
|
+
@scopes << Scope.new(type:, indent:, padding:)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def current_padding
|
|
250
|
+
@scopes.last.padding
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def pop_scope
|
|
254
|
+
@scopes.pop.type
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def newline
|
|
258
|
+
@string << "\n"
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def append_whitespace
|
|
262
|
+
@string << " "
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def apply_indent
|
|
266
|
+
@string << (INDENT_STRING * @scopes.sum(&:indent))
|
|
267
|
+
@string << @scopes.last.padding
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def append_token(rjust: 0)
|
|
271
|
+
raise "Token was already appended!" if @current_token.nil?
|
|
272
|
+
|
|
273
|
+
@string << @current_token.string.rjust(rjust)
|
|
274
|
+
@current_token = nil
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
@@ -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
|
|
@@ -11,6 +11,7 @@ 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"
|
|
14
15
|
|
|
15
16
|
module ActiveRecordPgFormatDbStructure
|
|
16
17
|
DEFAULT_PREPROCESSORS = [
|
|
@@ -19,6 +20,7 @@ module ActiveRecordPgFormatDbStructure
|
|
|
19
20
|
|
|
20
21
|
DEFAULT_TRANSFORMS = [
|
|
21
22
|
Transforms::RemoveCommentsOnExtensions,
|
|
23
|
+
Transforms::SortSchemaMigrations,
|
|
22
24
|
Transforms::InlinePrimaryKeys,
|
|
23
25
|
# Transforms::InlineForeignKeys,
|
|
24
26
|
Transforms::InlineSerials,
|
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.
|
|
4
|
+
version: 0.1.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jell
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2025-
|
|
10
|
+
date: 2025-02-08 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: pg_query
|
|
@@ -41,6 +41,7 @@ files:
|
|
|
41
41
|
- lib/activerecord-pg-format-db-structure.rb
|
|
42
42
|
- lib/activerecord-pg-format-db-structure/deparser.rb
|
|
43
43
|
- lib/activerecord-pg-format-db-structure/formatter.rb
|
|
44
|
+
- lib/activerecord-pg-format-db-structure/indenter.rb
|
|
44
45
|
- lib/activerecord-pg-format-db-structure/preprocessors/remove_whitespaces.rb
|
|
45
46
|
- lib/activerecord-pg-format-db-structure/railtie.rb
|
|
46
47
|
- lib/activerecord-pg-format-db-structure/tasks/clean_db_structure.rake
|
|
@@ -52,6 +53,7 @@ files:
|
|
|
52
53
|
- lib/activerecord-pg-format-db-structure/transforms/inline_serials.rb
|
|
53
54
|
- lib/activerecord-pg-format-db-structure/transforms/move_indices_after_create_table.rb
|
|
54
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
|
|
55
57
|
- lib/activerecord-pg-format-db-structure/version.rb
|
|
56
58
|
homepage: https://github.com/ReifyAB/activerecord-pg-format-db-structure
|
|
57
59
|
licenses:
|