pg-dump-parser 1.0.3 → 1.1.0

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.
@@ -0,0 +1,418 @@
1
+ import { type AttributedHeader, type SchemaObject } from './parsePgDump';
2
+ import { z } from 'zod';
3
+
4
+ type TableTarget = {
5
+ name: string;
6
+ schema: string;
7
+ };
8
+
9
+ const extractOwnedByTarget = (fragment: string): TableTarget => {
10
+ const { schema, name } =
11
+ fragment.match(/OWNED BY\s(?<schema>[^.]+)\.(?<name>[^.]+)/u)?.groups ?? {};
12
+
13
+ if (!schema) {
14
+ throw new Error('Invalid OWNED BY target (missing schema)');
15
+ }
16
+
17
+ if (!name) {
18
+ throw new Error('Invalid OWNED BY target (missing name)');
19
+ }
20
+
21
+ return {
22
+ name,
23
+ schema,
24
+ };
25
+ };
26
+
27
+ const extractOnTableTarget = (fragment: string): TableTarget => {
28
+ const { schema, name } =
29
+ fragment.match(/ON TABLE\s(?<schema>[^.]+)\.(?<name>\S+)/u)?.groups ?? {};
30
+
31
+ if (!schema) {
32
+ throw new Error('Invalid ON TABLE target');
33
+ }
34
+
35
+ if (!name) {
36
+ throw new Error('Invalid ON TABLE target');
37
+ }
38
+
39
+ return {
40
+ name,
41
+ schema,
42
+ };
43
+ };
44
+
45
+ const extractCreateIndexTarget = (fragment: string): TableTarget => {
46
+ const { schema, name } =
47
+ fragment.match(/ON\s(?<schema>[^.]+)\.(?<name>\S+)/u)?.groups ?? {};
48
+
49
+ if (!schema) {
50
+ throw new Error('Invalid CREATE INDEX target');
51
+ }
52
+
53
+ if (!name) {
54
+ throw new Error('Invalid CREATE INDEX target');
55
+ }
56
+
57
+ return {
58
+ name,
59
+ schema,
60
+ };
61
+ };
62
+
63
+ const extractCreateViewTarget = (fragment: string): TableTarget => {
64
+ const { schema, name } =
65
+ fragment.match(
66
+ /CREATE VIEW (?:IF NOT EXISTS\s)?(?<schema>[^.]+)\.(?<name>\S+)/u,
67
+ )?.groups ?? {};
68
+
69
+ if (!schema) {
70
+ throw new Error('Invalid CREATE VIEW target');
71
+ }
72
+
73
+ if (!name) {
74
+ throw new Error('Invalid CREATE VIEW target');
75
+ }
76
+
77
+ return {
78
+ name,
79
+ schema,
80
+ };
81
+ };
82
+
83
+ const extractCreateTableTarget = (fragment: string): TableTarget => {
84
+ const { schema, name } =
85
+ fragment.match(
86
+ /CREATE TABLE (?:IF NOT EXISTS\s)?(?<schema>[^.]+)\.(?<name>\S+)/u,
87
+ )?.groups ?? {};
88
+
89
+ if (!schema) {
90
+ throw new Error('Invalid CREATE TABLE target');
91
+ }
92
+
93
+ if (!name) {
94
+ throw new Error('Invalid CREATE TABLE target');
95
+ }
96
+
97
+ return {
98
+ name,
99
+ schema,
100
+ };
101
+ };
102
+
103
+ const extractAlterTableTarget = (fragment: string): TableTarget => {
104
+ const { schema, name } =
105
+ fragment.match(/ALTER TABLE (?:ONLY\s)?(?<schema>[^.]+)\.(?<name>\S+)/u)
106
+ ?.groups ?? {};
107
+
108
+ if (!schema) {
109
+ throw new Error('Invalid ALTER TABLE target');
110
+ }
111
+
112
+ if (!name) {
113
+ throw new Error('Invalid ALTER TABLE target');
114
+ }
115
+
116
+ return {
117
+ name,
118
+ schema,
119
+ };
120
+ };
121
+
122
+ type CommentOnTarget = {
123
+ target: string;
124
+ type: 'COLUMN' | 'EXTENSION' | 'INDEX' | 'SEQUENCE' | 'TABLE';
125
+ };
126
+
127
+ const extractCommentOnTarget = (fragment: string): CommentOnTarget => {
128
+ const { target, type } =
129
+ fragment.match(
130
+ /COMMENT ON (?<type>TABLE|EXTENSION|COLUMN|SEQUENCE|INDEX)\s(?<target>\S+)/u,
131
+ )?.groups ?? {};
132
+
133
+ if (!target) {
134
+ throw new Error('Invalid COMMENT ON target (missing target)');
135
+ }
136
+
137
+ if (!type) {
138
+ throw new Error('Invalid COMMENT ON target (missing type)');
139
+ }
140
+
141
+ return {
142
+ target,
143
+ type: type as CommentOnTarget['type'],
144
+ };
145
+ };
146
+
147
+ export type SchemaObjectScope =
148
+ | {
149
+ name: string;
150
+ schema: null;
151
+ type: 'EXTENSION';
152
+ }
153
+ | {
154
+ name: string;
155
+ schema: string;
156
+ type:
157
+ | 'AGGREGATE'
158
+ | 'FUNCTION'
159
+ | 'MATERIALIZED VIEW'
160
+ | 'PROCEDURE'
161
+ | 'TABLE'
162
+ | 'TYPE'
163
+ | 'VIEW';
164
+ };
165
+
166
+ type AttributedSchemaObject = {
167
+ header: AttributedHeader;
168
+ sql: string;
169
+ };
170
+
171
+ // eslint-disable-next-line complexity
172
+ const scopeAttributedSchemaObject = (
173
+ schemaObjects: AttributedSchemaObject[],
174
+ subject: AttributedSchemaObject,
175
+ ): SchemaObjectScope | null => {
176
+ if (subject.header.Type === 'FUNCTION') {
177
+ return {
178
+ name: subject.header.Name.split('(')[0],
179
+ schema: subject.header.Schema ?? 'public',
180
+ type: 'FUNCTION',
181
+ };
182
+ }
183
+
184
+ if (subject.header.Type === 'PROCEDURE') {
185
+ return {
186
+ name: subject.header.Name.split('(')[0],
187
+ schema: subject.header.Schema ?? 'public',
188
+ type: 'PROCEDURE',
189
+ };
190
+ }
191
+
192
+ if (subject.header.Type === 'TRIGGER') {
193
+ return {
194
+ name: subject.header.Name.split(' ')[0],
195
+ schema: subject.header.Schema ?? 'public',
196
+ type: 'TABLE',
197
+ };
198
+ }
199
+
200
+ if (subject.header.Type === 'TYPE') {
201
+ return {
202
+ name: subject.header.Name,
203
+ schema: subject.header.Schema ?? 'public',
204
+ type: 'TYPE',
205
+ };
206
+ }
207
+
208
+ if (subject.header.Type === 'AGGREGATE') {
209
+ return {
210
+ name: subject.header.Name.split('(')[0],
211
+ schema: subject.header.Schema ?? 'public',
212
+ type: 'AGGREGATE',
213
+ };
214
+ }
215
+
216
+ if (subject.header.Type === 'INDEX') {
217
+ const target = extractCreateIndexTarget(subject.sql);
218
+
219
+ return {
220
+ name: target.name,
221
+ schema: target.schema,
222
+ type: 'TABLE',
223
+ };
224
+ }
225
+
226
+ if (subject.header.Type === 'EXTENSION') {
227
+ return {
228
+ name: subject.header.Name,
229
+ schema: null,
230
+ type: 'EXTENSION',
231
+ };
232
+ }
233
+
234
+ if (subject.header.Type === 'MATERIALIZED VIEW') {
235
+ return {
236
+ name: subject.header.Name,
237
+ schema: subject.header.Schema ?? 'public',
238
+ type: 'MATERIALIZED VIEW',
239
+ };
240
+ }
241
+
242
+ if (subject.sql.startsWith('CREATE VIEW ')) {
243
+ const target = extractCreateViewTarget(subject.sql);
244
+
245
+ return {
246
+ name: target.name,
247
+ schema: target.schema,
248
+ type: 'VIEW',
249
+ };
250
+ }
251
+
252
+ if (subject.sql.startsWith('CREATE TABLE ')) {
253
+ const target = extractCreateTableTarget(subject.sql);
254
+
255
+ return {
256
+ name: target.name,
257
+ schema: target.schema,
258
+ type: 'TABLE',
259
+ };
260
+ }
261
+
262
+ if (subject.sql.startsWith('ALTER TABLE ')) {
263
+ const target = extractAlterTableTarget(subject.sql);
264
+
265
+ return {
266
+ name: target.name,
267
+ schema: target.schema,
268
+ type: 'TABLE',
269
+ };
270
+ }
271
+
272
+ if (subject.sql.startsWith('COMMENT ON ')) {
273
+ const target = extractCommentOnTarget(subject.sql);
274
+
275
+ if (target.type === 'EXTENSION') {
276
+ return {
277
+ name: target.target,
278
+ schema: null,
279
+ type: 'EXTENSION',
280
+ };
281
+ }
282
+
283
+ if (target.type === 'TABLE') {
284
+ const [schema, name] = z
285
+ .tuple([z.string(), z.string()])
286
+ .parse(target.target.split('.'));
287
+
288
+ return {
289
+ name,
290
+ schema,
291
+ type: 'TABLE',
292
+ };
293
+ }
294
+
295
+ if (target.type === 'COLUMN') {
296
+ const [schema, name] = z
297
+ .tuple([z.string(), z.string(), z.string()])
298
+ .parse(target.target.split('.'));
299
+
300
+ return {
301
+ name,
302
+ schema,
303
+ type: 'TABLE',
304
+ };
305
+ }
306
+
307
+ if (target.type === 'INDEX') {
308
+ const [schema, indexName] = z
309
+ .tuple([z.string(), z.string()])
310
+ .parse(target.target.split('.'));
311
+
312
+ const indexSchemaObject = schemaObjects.find((schemaObject) => {
313
+ if (schemaObject.header.Type !== 'INDEX') {
314
+ return false;
315
+ }
316
+
317
+ return schemaObject.header.Name === indexName;
318
+ });
319
+
320
+ if (indexSchemaObject) {
321
+ throw new Error('Not implemented');
322
+ }
323
+
324
+ const constraintSchemaObject = schemaObjects.find((schemaObject) => {
325
+ if (schemaObject.header.Type !== 'CONSTRAINT') {
326
+ return false;
327
+ }
328
+
329
+ return schemaObject.header.Name.split(' ')[1] === indexName;
330
+ });
331
+
332
+ if (constraintSchemaObject) {
333
+ const [tableName] = constraintSchemaObject.header.Name.split(' ');
334
+
335
+ return {
336
+ name: tableName,
337
+ schema,
338
+ type: 'TABLE',
339
+ };
340
+ }
341
+ }
342
+
343
+ if (target.type === 'SEQUENCE') {
344
+ const [schemaName, sequenceName] = z
345
+ .tuple([z.string(), z.string()])
346
+ .parse(target.target.split('.'));
347
+
348
+ const sequenceSchemaObject = schemaObjects.find((schemaObject) => {
349
+ if (schemaObject.header.Type !== 'SEQUENCE') {
350
+ return false;
351
+ }
352
+
353
+ return (
354
+ schemaObject.header.Name === sequenceName &&
355
+ schemaObject.header.Schema === schemaName
356
+ );
357
+ });
358
+
359
+ if (!sequenceSchemaObject) {
360
+ throw new Error('Sequence not found');
361
+ }
362
+
363
+ const alterTableTarget = extractAlterTableTarget(
364
+ sequenceSchemaObject.sql,
365
+ );
366
+
367
+ return {
368
+ name: alterTableTarget.name,
369
+ schema: alterTableTarget.schema,
370
+ type: 'TABLE',
371
+ };
372
+ }
373
+ }
374
+
375
+ try {
376
+ const target = extractOwnedByTarget(subject.sql);
377
+
378
+ return {
379
+ name: target.name,
380
+ schema: target.schema,
381
+ type: 'TABLE',
382
+ };
383
+ } catch {
384
+ // ignore
385
+ }
386
+
387
+ try {
388
+ const target = extractOnTableTarget(subject.sql);
389
+
390
+ return {
391
+ name: target.name,
392
+ schema: target.schema,
393
+ type: 'TABLE',
394
+ };
395
+ } catch {
396
+ // ignore
397
+ }
398
+
399
+ return null;
400
+ };
401
+
402
+ export const scopeSchemaObject = (
403
+ schemaObjects: SchemaObject[],
404
+ subject: SchemaObject,
405
+ ): SchemaObjectScope | null => {
406
+ if (!('Type' in subject.header)) {
407
+ return null;
408
+ }
409
+
410
+ const attributedSchemaObjects = schemaObjects.filter(
411
+ (schemaObject) => 'Type' in schemaObject.header,
412
+ ) as AttributedSchemaObject[];
413
+
414
+ return scopeAttributedSchemaObject(
415
+ attributedSchemaObjects,
416
+ subject as AttributedSchemaObject,
417
+ );
418
+ };