@pgsql/transform 18.1.1 → 18.3.1
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.
- package/README.md +24 -76
- package/esm/facts.js +289 -0
- package/esm/index.js +5 -22
- package/esm/qualify.js +297 -0
- package/esm/round-trip-core.js +126 -0
- package/esm/round-trip.js +116 -0
- package/esm/transform.js +1169 -0
- package/facts.d.ts +69 -0
- package/facts.js +292 -0
- package/index.d.ts +10 -28
- package/index.js +37 -76
- package/package.json +22 -14
- package/qualify.d.ts +125 -0
- package/qualify.js +303 -0
- package/round-trip-core.d.ts +60 -0
- package/round-trip-core.js +133 -0
- package/round-trip.d.ts +47 -0
- package/round-trip.js +123 -0
- package/transform.d.ts +259 -0
- package/transform.js +1190 -0
- package/13/enums.d.ts +0 -62
- package/13/enums.js +0 -2
- package/13/types.d.ts +0 -2103
- package/13/types.js +0 -2
- package/14/enums.d.ts +0 -62
- package/14/enums.js +0 -2
- package/14/types.d.ts +0 -2164
- package/14/types.js +0 -2
- package/15/enums.d.ts +0 -64
- package/15/enums.js +0 -2
- package/15/types.d.ts +0 -2222
- package/15/types.js +0 -2
- package/16/enums.d.ts +0 -69
- package/16/enums.js +0 -2
- package/16/types.d.ts +0 -2328
- package/16/types.js +0 -2
- package/17/enums.d.ts +0 -76
- package/17/enums.js +0 -2
- package/17/types.d.ts +0 -2488
- package/17/types.js +0 -2
- package/18/enums.d.ts +0 -78
- package/18/enums.js +0 -2
- package/18/types.d.ts +0 -2538
- package/18/types.js +0 -2
- package/esm/13/enums.js +0 -1
- package/esm/13/types.js +0 -1
- package/esm/14/enums.js +0 -1
- package/esm/14/types.js +0 -1
- package/esm/15/enums.js +0 -1
- package/esm/15/types.js +0 -1
- package/esm/16/enums.js +0 -1
- package/esm/16/types.js +0 -1
- package/esm/17/enums.js +0 -1
- package/esm/17/types.js +0 -1
- package/esm/18/enums.js +0 -1
- package/esm/18/types.js +0 -1
- package/esm/multi-version-transformer.js +0 -80
- package/esm/transformers/context.js +0 -1
- package/esm/transformers/v13-to-v14.js +0 -2767
- package/esm/transformers/v14-to-v15.js +0 -1218
- package/esm/transformers/v15-to-v16.js +0 -2935
- package/esm/transformers/v16-to-v17.js +0 -1526
- package/esm/transformers/v17-to-v18.js +0 -204
- package/esm/transformers-direct/index.js +0 -13
- package/esm/transformers-direct/v13-to-v17/index.js +0 -74
- package/esm/transformers-direct/v13-to-v18/index.js +0 -84
- package/esm/transformers-direct/v14-to-v17/index.js +0 -63
- package/esm/transformers-direct/v14-to-v18/index.js +0 -73
- package/esm/transformers-direct/v15-to-v17/index.js +0 -53
- package/esm/transformers-direct/v15-to-v18/index.js +0 -62
- package/esm/transformers-direct/v16-to-v17/index.js +0 -40
- package/esm/transformers-direct/v16-to-v18/index.js +0 -51
- package/esm/transformers-direct/v17-to-v18/index.js +0 -39
- package/multi-version-transformer.d.ts +0 -14
- package/multi-version-transformer.js +0 -84
- package/transformers/context.d.ts +0 -4
- package/transformers/context.js +0 -2
- package/transformers/v13-to-v14.d.ts +0 -255
- package/transformers/v13-to-v14.js +0 -2771
- package/transformers/v14-to-v15.d.ts +0 -615
- package/transformers/v14-to-v15.js +0 -1222
- package/transformers/v15-to-v16.d.ts +0 -632
- package/transformers/v15-to-v16.js +0 -2939
- package/transformers/v16-to-v17.d.ts +0 -640
- package/transformers/v16-to-v17.js +0 -1530
- package/transformers/v17-to-v18.d.ts +0 -54
- package/transformers/v17-to-v18.js +0 -208
- package/transformers-direct/index.d.ts +0 -13
- package/transformers-direct/index.js +0 -25
- package/transformers-direct/v13-to-v17/index.d.ts +0 -27
- package/transformers-direct/v13-to-v17/index.js +0 -78
- package/transformers-direct/v13-to-v18/index.d.ts +0 -28
- package/transformers-direct/v13-to-v18/index.js +0 -88
- package/transformers-direct/v14-to-v17/index.d.ts +0 -26
- package/transformers-direct/v14-to-v17/index.js +0 -67
- package/transformers-direct/v14-to-v18/index.d.ts +0 -27
- package/transformers-direct/v14-to-v18/index.js +0 -77
- package/transformers-direct/v15-to-v17/index.d.ts +0 -25
- package/transformers-direct/v15-to-v17/index.js +0 -57
- package/transformers-direct/v15-to-v18/index.d.ts +0 -26
- package/transformers-direct/v15-to-v18/index.js +0 -66
- package/transformers-direct/v16-to-v17/index.d.ts +0 -24
- package/transformers-direct/v16-to-v17/index.js +0 -44
- package/transformers-direct/v16-to-v18/index.d.ts +0 -25
- package/transformers-direct/v16-to-v18/index.js +0 -55
- package/transformers-direct/v17-to-v18/index.d.ts +0 -24
- package/transformers-direct/v17-to-v18/index.js +0 -43
package/README.md
CHANGED
|
@@ -12,25 +12,9 @@
|
|
|
12
12
|
<a href="https://www.npmjs.com/package/@pgsql/transform"><img height="20" src="https://img.shields.io/github/package-json/v/constructive-io/pgsql-parser?filename=packages%2Ftransform%2Fpackage.json"/></a>
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
AST-based SQL transformation, qualification, classification, and dependency-closure analysis for PostgreSQL. Works on plain SQL **and inside PL/pgSQL function bodies** (via `plpgsql-parser` hydration) — no regexes.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
The transform package enables you to:
|
|
20
|
-
|
|
21
|
-
- **Transform legacy ASTs**: Convert ASTs from PostgreSQL versions 13-16 to version 17
|
|
22
|
-
- **Build unified deparsers**: Create a single deparser pipeline that works with multiple PostgreSQL versions
|
|
23
|
-
- **Maintain backward compatibility**: Support legacy codebases while leveraging the latest PostgreSQL features
|
|
24
|
-
|
|
25
|
-
## Key Limitation
|
|
26
|
-
|
|
27
|
-
This package only supports ASTs derived from SQL that is parseable by PostgreSQL 17. This means:
|
|
28
|
-
|
|
29
|
-
- ✅ **Supported**: SQL from PG13-16 that remains valid in PG17
|
|
30
|
-
- ❌ **Not supported**: Deprecated syntax from older versions that was removed
|
|
31
|
-
- ❌ **Not supported**: SQL that cannot be parsed by the PG17 parser
|
|
32
|
-
|
|
33
|
-
This design ensures all transformed ASTs can be reliably deparsed using the latest PostgreSQL grammar.
|
|
17
|
+
> Looking for the PG13→17/18 version-upgrade AST transformer previously published under this name? It now lives at [`@pgsql/transform-ast`](../transform-ast).
|
|
34
18
|
|
|
35
19
|
## Installation
|
|
36
20
|
|
|
@@ -38,82 +22,46 @@ This design ensures all transformed ASTs can be reliably deparsed using the late
|
|
|
38
22
|
npm install @pgsql/transform
|
|
39
23
|
```
|
|
40
24
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
### Multi-Version Transformer
|
|
44
|
-
|
|
45
|
-
```typescript
|
|
46
|
-
import { ASTTransformer } from '@pgsql/transform';
|
|
47
|
-
|
|
48
|
-
const transformer = new ASTTransformer();
|
|
49
|
-
|
|
50
|
-
// Transform any version to PG17
|
|
51
|
-
const pg17Ast = transformer.transformToLatest(pg13Ast, 13);
|
|
25
|
+
The parser runs on a WASM build of the real PostgreSQL parser; call `loadModule()` from `plpgsql-parser` once before using any synchronous API.
|
|
52
26
|
|
|
53
|
-
|
|
54
|
-
const pg15Ast = transformer.transform(pg13Ast, 13, 15);
|
|
27
|
+
## Features
|
|
55
28
|
|
|
56
|
-
|
|
57
|
-
const result = transformer.transform13To17(pg13Ast);
|
|
58
|
-
```
|
|
29
|
+
### `transformSql` — schema-name rewriting
|
|
59
30
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
For better performance when you know source and target versions:
|
|
31
|
+
Rewrite schema names everywhere they can appear (DDL, DML, function bodies, trigger definitions, grants, policies, comments, type casts, string-embedded types inside PL/pgSQL, ...):
|
|
63
32
|
|
|
64
33
|
```typescript
|
|
65
|
-
import {
|
|
34
|
+
import { loadModule } from 'plpgsql-parser';
|
|
35
|
+
import { transformSql } from '@pgsql/transform';
|
|
66
36
|
|
|
67
|
-
|
|
68
|
-
const
|
|
37
|
+
await loadModule();
|
|
38
|
+
const mapping = new Map([['my-schema', 'my_schema']]);
|
|
39
|
+
const { sql } = transformSql(inputSql, mapping);
|
|
69
40
|
```
|
|
70
41
|
|
|
71
|
-
###
|
|
42
|
+
### `classifyStatements` — per-statement AST facts
|
|
72
43
|
|
|
73
44
|
```typescript
|
|
74
|
-
import {
|
|
75
|
-
import { deparse } from 'pgsql-deparser';
|
|
76
|
-
import { PG13ToPG17Transformer } from '@pgsql/transform';
|
|
77
|
-
|
|
78
|
-
// Parse with older version
|
|
79
|
-
const pg13Ast = await parse('SELECT * FROM users', { version: 13 });
|
|
45
|
+
import { classifyStatements } from '@pgsql/transform';
|
|
80
46
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// Deparse with latest grammar
|
|
86
|
-
const sql = await deparse(pg17Ast);
|
|
47
|
+
const facts = classifyStatements(sql);
|
|
48
|
+
// per statement: kind (schema|table|view|index|type|function|trigger|policy|grant|...),
|
|
49
|
+
// creates, references (incl. inside PL/pgSQL bodies), referencedSchemas, roles,
|
|
50
|
+
// fkTargets, securityRelevant, securityDefiner, dynamicSql
|
|
87
51
|
```
|
|
88
52
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
**Incremental**: PG13 → PG14 → PG15 → PG16 → PG17
|
|
92
|
-
- Step-by-step version upgrades
|
|
93
|
-
- Useful for debugging transformation issues
|
|
94
|
-
|
|
95
|
-
**Direct**: PG13 → PG17, PG14 → PG17, etc.
|
|
96
|
-
- Single-step transformations
|
|
97
|
-
- Optimized for performance
|
|
98
|
-
|
|
99
|
-
## 📋 Supported Transformations
|
|
53
|
+
### `qualifyUnqualified` — add schema qualification
|
|
100
54
|
|
|
101
|
-
|
|
102
|
-
|------|----|-----------|
|
|
103
|
-
| PG13 | PG14, PG15, PG16, PG17 | `V13ToV14Transformer`, `PG13ToPG17Transformer` |
|
|
104
|
-
| PG14 | PG15, PG16, PG17 | `V14ToV15Transformer`, `PG14ToPG17Transformer` |
|
|
105
|
-
| PG15 | PG16, PG17 | `V15ToV16Transformer`, `PG15ToPG17Transformer` |
|
|
106
|
-
| PG16 | PG17 | `V16ToV17Transformer`, `PG16ToPG17Transformer` |
|
|
55
|
+
Qualify unqualified object references against an inventory of known objects, with multi-schema routing support.
|
|
107
56
|
|
|
108
|
-
|
|
57
|
+
### Round-trip validation
|
|
109
58
|
|
|
110
|
-
|
|
59
|
+
`normalizeTree` / `cleanTree` / `validateRoundTrip` — dependency-free AST normalization and mutation-aware parse→deparse→re-parse validation.
|
|
111
60
|
|
|
112
|
-
|
|
113
|
-
2. **Transform** ASTs to the latest version
|
|
114
|
-
3. **Deparse** using the unified, latest-version deparser
|
|
61
|
+
## Scripts
|
|
115
62
|
|
|
116
|
-
|
|
63
|
+
- `npm run fixtures` — regenerate `__fixtures__/output/` golden files from `__fixtures__/input/`
|
|
64
|
+
- `npm run scan-corpus <dir> [dir...]` — audit node-type coverage over a SQL corpus
|
|
117
65
|
|
|
118
66
|
---
|
|
119
67
|
|
package/esm/facts.js
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { walk as walkSql } from '@pgsql/traverse';
|
|
2
|
+
import { transformSync, walk as walkPlpgsql } from 'plpgsql-parser';
|
|
3
|
+
const SECURITY_TAGS = new Set([
|
|
4
|
+
'CreatePolicyStmt',
|
|
5
|
+
'AlterPolicyStmt',
|
|
6
|
+
'GrantStmt',
|
|
7
|
+
'GrantRoleStmt',
|
|
8
|
+
'AlterDefaultPrivilegesStmt',
|
|
9
|
+
'SecLabelStmt',
|
|
10
|
+
'AlterOwnerStmt',
|
|
11
|
+
'CreateRoleStmt',
|
|
12
|
+
'AlterRoleStmt'
|
|
13
|
+
]);
|
|
14
|
+
const KIND_BY_TAG = {
|
|
15
|
+
CreateSchemaStmt: 'schema',
|
|
16
|
+
CreateStmt: 'table',
|
|
17
|
+
ViewStmt: 'view',
|
|
18
|
+
IndexStmt: 'index',
|
|
19
|
+
CompositeTypeStmt: 'type',
|
|
20
|
+
CreateEnumStmt: 'type',
|
|
21
|
+
CreateDomainStmt: 'type',
|
|
22
|
+
CreateRangeStmt: 'type',
|
|
23
|
+
DefineStmt: 'type',
|
|
24
|
+
CreateFunctionStmt: 'function',
|
|
25
|
+
CreateTrigStmt: 'trigger',
|
|
26
|
+
CreateEventTrigStmt: 'trigger',
|
|
27
|
+
CreatePolicyStmt: 'policy',
|
|
28
|
+
AlterPolicyStmt: 'policy',
|
|
29
|
+
GrantStmt: 'grant',
|
|
30
|
+
GrantRoleStmt: 'grant',
|
|
31
|
+
AlterDefaultPrivilegesStmt: 'grant',
|
|
32
|
+
CommentStmt: 'comment',
|
|
33
|
+
InsertStmt: 'seed_dml',
|
|
34
|
+
UpdateStmt: 'seed_dml',
|
|
35
|
+
DeleteStmt: 'seed_dml'
|
|
36
|
+
};
|
|
37
|
+
function qn(schema, name) {
|
|
38
|
+
return { schema: schema ?? null, name };
|
|
39
|
+
}
|
|
40
|
+
function nameListToQualified(names) {
|
|
41
|
+
if (!Array.isArray(names) || names.length === 0)
|
|
42
|
+
return null;
|
|
43
|
+
const parts = names
|
|
44
|
+
.map((n) => n?.String?.sval)
|
|
45
|
+
.filter((s) => typeof s === 'string');
|
|
46
|
+
if (parts.length === 0)
|
|
47
|
+
return null;
|
|
48
|
+
if (parts.length === 1)
|
|
49
|
+
return qn(null, parts[0]);
|
|
50
|
+
return qn(parts[parts.length - 2], parts[parts.length - 1]);
|
|
51
|
+
}
|
|
52
|
+
function isCatalogSchema(schema) {
|
|
53
|
+
return schema === 'pg_catalog' || schema === 'information_schema';
|
|
54
|
+
}
|
|
55
|
+
function pushRef(refs, ref) {
|
|
56
|
+
if (!ref || !ref.schema || isCatalogSchema(ref.schema))
|
|
57
|
+
return;
|
|
58
|
+
if (refs.some(r => r.schema === ref.schema && r.name === ref.name))
|
|
59
|
+
return;
|
|
60
|
+
refs.push(ref);
|
|
61
|
+
}
|
|
62
|
+
function collectRoles(node, roles) {
|
|
63
|
+
const push = (role) => {
|
|
64
|
+
if (typeof role === 'string' && role.length > 0 && !roles.includes(role)) {
|
|
65
|
+
roles.push(role);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
if (Array.isArray(node?.grantees)) {
|
|
69
|
+
for (const g of node.grantees)
|
|
70
|
+
push(g?.RoleSpec?.rolename);
|
|
71
|
+
}
|
|
72
|
+
if (Array.isArray(node?.roles)) {
|
|
73
|
+
for (const r of node.roles)
|
|
74
|
+
push(r?.RoleSpec?.rolename);
|
|
75
|
+
}
|
|
76
|
+
push(node?.newowner?.rolename);
|
|
77
|
+
push(node?.role?.rolename);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Create a read-only visitor that accumulates references, roles and FK
|
|
81
|
+
* targets into the provided facts object.
|
|
82
|
+
*/
|
|
83
|
+
function createFactsVisitor(facts, bodyRefs) {
|
|
84
|
+
const push = (ref) => {
|
|
85
|
+
pushRef(facts.references, ref);
|
|
86
|
+
if (bodyRefs)
|
|
87
|
+
pushRef(bodyRefs, ref);
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
RangeVar: (path) => {
|
|
91
|
+
const node = path.node;
|
|
92
|
+
if (node.schemaname) {
|
|
93
|
+
push(qn(node.schemaname, node.relname));
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
FuncCall: (path) => {
|
|
97
|
+
push(nameListToQualified(path.node.funcname));
|
|
98
|
+
},
|
|
99
|
+
TypeName: (path) => {
|
|
100
|
+
push(nameListToQualified(path.node.names));
|
|
101
|
+
},
|
|
102
|
+
ColumnRef: (path) => {
|
|
103
|
+
// schema.table.column references carry cross-schema information
|
|
104
|
+
const fields = path.node.fields;
|
|
105
|
+
if (Array.isArray(fields) && fields.length >= 3) {
|
|
106
|
+
const parts = fields
|
|
107
|
+
.map((f) => f?.String?.sval)
|
|
108
|
+
.filter((s) => typeof s === 'string');
|
|
109
|
+
if (parts.length >= 3) {
|
|
110
|
+
push(qn(parts[0], parts[1]));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
Constraint: (path) => {
|
|
115
|
+
const node = path.node;
|
|
116
|
+
if (node.contype === 'CONSTR_FOREIGN' && node.pktable) {
|
|
117
|
+
const target = qn(node.pktable.schemaname ?? null, node.pktable.relname);
|
|
118
|
+
if (!facts.fkTargets.some(t => t.schema === target.schema && t.name === target.name)) {
|
|
119
|
+
facts.fkTargets.push(target);
|
|
120
|
+
}
|
|
121
|
+
if (target.schema)
|
|
122
|
+
pushRef(facts.references, target);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function classifyOne(nodeTag, node) {
|
|
128
|
+
const facts = {
|
|
129
|
+
kind: KIND_BY_TAG[nodeTag] ?? 'other',
|
|
130
|
+
nodeTag,
|
|
131
|
+
creates: [],
|
|
132
|
+
references: [],
|
|
133
|
+
referencedSchemas: [],
|
|
134
|
+
roles: [],
|
|
135
|
+
fkTargets: [],
|
|
136
|
+
bodyReferences: [],
|
|
137
|
+
securityRelevant: SECURITY_TAGS.has(nodeTag),
|
|
138
|
+
securityDefiner: false,
|
|
139
|
+
dynamicSql: false
|
|
140
|
+
};
|
|
141
|
+
switch (nodeTag) {
|
|
142
|
+
case 'CreateSchemaStmt':
|
|
143
|
+
facts.creates.push(qn(null, node.schemaname));
|
|
144
|
+
break;
|
|
145
|
+
case 'CreateStmt':
|
|
146
|
+
case 'ViewStmt': {
|
|
147
|
+
const rel = nodeTag === 'ViewStmt' ? node.view : node.relation;
|
|
148
|
+
if (rel)
|
|
149
|
+
facts.creates.push(qn(rel.schemaname ?? null, rel.relname));
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case 'IndexStmt':
|
|
153
|
+
if (node.relation) {
|
|
154
|
+
facts.creates.push(qn(node.relation.schemaname ?? null, node.idxname ?? node.relation.relname));
|
|
155
|
+
}
|
|
156
|
+
break;
|
|
157
|
+
case 'CreateFunctionStmt': {
|
|
158
|
+
const name = nameListToQualified(node.funcname);
|
|
159
|
+
if (name)
|
|
160
|
+
facts.creates.push(name);
|
|
161
|
+
for (const opt of node.options ?? []) {
|
|
162
|
+
const def = opt?.DefElem;
|
|
163
|
+
if (def?.defname === 'security' && def?.arg?.Boolean?.boolval === true) {
|
|
164
|
+
facts.securityDefiner = true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
case 'CreateTrigStmt':
|
|
170
|
+
if (node.relation) {
|
|
171
|
+
// Trigger names are only unique per table; qualify with the table.
|
|
172
|
+
facts.creates.push(qn(node.relation.schemaname ?? null, `${node.relation.relname}.${node.trigname}`));
|
|
173
|
+
}
|
|
174
|
+
pushRef(facts.references, nameListToQualified(node.funcname));
|
|
175
|
+
break;
|
|
176
|
+
case 'CreatePolicyStmt':
|
|
177
|
+
case 'AlterPolicyStmt':
|
|
178
|
+
if (node.table) {
|
|
179
|
+
// Policy names are only unique per table; qualify with the table.
|
|
180
|
+
facts.creates.push(qn(node.table.schemaname ?? null, `${node.table.relname}.${node.policy_name}`));
|
|
181
|
+
// The guarded table lives on `node.table`; the generic RangeVar walker
|
|
182
|
+
// does not descend into it, so capture the reference explicitly.
|
|
183
|
+
if (node.table.schemaname) {
|
|
184
|
+
pushRef(facts.references, qn(node.table.schemaname, node.table.relname));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
break;
|
|
188
|
+
case 'CreateSeqStmt':
|
|
189
|
+
if (node.sequence) {
|
|
190
|
+
facts.creates.push(qn(node.sequence.schemaname ?? null, node.sequence.relname));
|
|
191
|
+
}
|
|
192
|
+
break;
|
|
193
|
+
case 'CompositeTypeStmt':
|
|
194
|
+
if (node.typevar) {
|
|
195
|
+
facts.creates.push(qn(node.typevar.schemaname ?? null, node.typevar.relname));
|
|
196
|
+
}
|
|
197
|
+
break;
|
|
198
|
+
case 'CreateEnumStmt':
|
|
199
|
+
case 'CreateDomainStmt':
|
|
200
|
+
case 'CreateRangeStmt': {
|
|
201
|
+
const name = nameListToQualified(node.typeName ?? node.domainname);
|
|
202
|
+
if (name)
|
|
203
|
+
facts.creates.push(name);
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
case 'AlterTableStmt': {
|
|
207
|
+
if (node.relation) {
|
|
208
|
+
facts.creates.push(qn(node.relation.schemaname ?? null, node.relation.relname));
|
|
209
|
+
}
|
|
210
|
+
const cmds = node.cmds ?? [];
|
|
211
|
+
for (const cmd of cmds) {
|
|
212
|
+
const subtype = cmd?.AlterTableCmd?.subtype;
|
|
213
|
+
if (subtype === 'AT_EnableRowSecurity' || subtype === 'AT_ForceRowSecurity' ||
|
|
214
|
+
subtype === 'AT_DisableRowSecurity' || subtype === 'AT_NoForceRowSecurity') {
|
|
215
|
+
facts.kind = 'rls_enable';
|
|
216
|
+
facts.securityRelevant = true;
|
|
217
|
+
}
|
|
218
|
+
else if (subtype === 'AT_AddConstraint') {
|
|
219
|
+
const contype = cmd?.AlterTableCmd?.def?.Constraint?.contype;
|
|
220
|
+
facts.kind = contype === 'CONSTR_FOREIGN' ? 'fk_constraint' : 'constraint';
|
|
221
|
+
}
|
|
222
|
+
else if (subtype === 'AT_ChangeOwner') {
|
|
223
|
+
facts.securityRelevant = true;
|
|
224
|
+
const owner = cmd?.AlterTableCmd?.newowner?.rolename;
|
|
225
|
+
if (owner && !facts.roles.includes(owner))
|
|
226
|
+
facts.roles.push(owner);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
case 'InsertStmt':
|
|
232
|
+
case 'UpdateStmt':
|
|
233
|
+
case 'DeleteStmt':
|
|
234
|
+
if (node.relation) {
|
|
235
|
+
facts.creates.push(qn(node.relation.schemaname ?? null, node.relation.relname));
|
|
236
|
+
}
|
|
237
|
+
break;
|
|
238
|
+
default:
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
collectRoles(node, facts.roles);
|
|
242
|
+
return facts;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Classify each top-level statement in a SQL script into {@link StatementFacts}.
|
|
246
|
+
*
|
|
247
|
+
* Uses the same parser stack as the schema transformer (pgsql AST walk plus
|
|
248
|
+
* hydrated PL/pgSQL body walk), so references inside function bodies are
|
|
249
|
+
* included. The input SQL is never modified.
|
|
250
|
+
*/
|
|
251
|
+
export function classifyStatements(sql) {
|
|
252
|
+
const allFacts = [];
|
|
253
|
+
transformSync(sql, (ctx) => {
|
|
254
|
+
const stmts = ctx.sql?.stmts ?? [];
|
|
255
|
+
for (const stmt of stmts) {
|
|
256
|
+
const stmtNode = stmt?.stmt;
|
|
257
|
+
const nodeTag = stmtNode ? Object.keys(stmtNode)[0] : 'other';
|
|
258
|
+
const node = stmtNode?.[nodeTag] ?? {};
|
|
259
|
+
const facts = classifyOne(nodeTag, node);
|
|
260
|
+
if (stmtNode) {
|
|
261
|
+
walkSql(stmtNode, createFactsVisitor(facts));
|
|
262
|
+
}
|
|
263
|
+
allFacts.push(facts);
|
|
264
|
+
}
|
|
265
|
+
for (const fn of ctx.functions ?? []) {
|
|
266
|
+
const facts = allFacts[fn.stmtIndex];
|
|
267
|
+
if (!facts || !fn.plpgsql?.hydrated)
|
|
268
|
+
continue;
|
|
269
|
+
// Refs already found outside the body (signature types, defaults)
|
|
270
|
+
// constrain deploy order and are not body-only.
|
|
271
|
+
const outer = new Set(facts.references.map(r => `${r.schema ?? '?'}.${r.name}`));
|
|
272
|
+
walkPlpgsql(fn.plpgsql.hydrated, {
|
|
273
|
+
PLpgSQL_stmt_dynexecute: () => { facts.dynamicSql = true; },
|
|
274
|
+
PLpgSQL_stmt_dynfors: () => { facts.dynamicSql = true; }
|
|
275
|
+
}, {
|
|
276
|
+
walkSqlExpressions: true,
|
|
277
|
+
sqlVisitor: createFactsVisitor(facts, facts.bodyReferences)
|
|
278
|
+
});
|
|
279
|
+
facts.bodyReferences = facts.bodyReferences.filter(r => !outer.has(`${r.schema ?? '?'}.${r.name}`));
|
|
280
|
+
}
|
|
281
|
+
}, { hydrate: true });
|
|
282
|
+
for (const facts of allFacts) {
|
|
283
|
+
const selfRef = (r) => facts.creates.some(c => c.schema === r.schema && c.name === r.name);
|
|
284
|
+
facts.references = facts.references.filter(r => !selfRef(r));
|
|
285
|
+
facts.bodyReferences = facts.bodyReferences.filter(r => !selfRef(r));
|
|
286
|
+
facts.referencedSchemas = [...new Set(facts.references.map(r => r.schema).filter(Boolean))];
|
|
287
|
+
}
|
|
288
|
+
return allFacts;
|
|
289
|
+
}
|
package/esm/index.js
CHANGED
|
@@ -1,22 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import * as PG18Types from './18/types';
|
|
7
|
-
export { ASTTransformer } from './multi-version-transformer';
|
|
8
|
-
export { V13ToV14Transformer } from './transformers/v13-to-v14';
|
|
9
|
-
export { V14ToV15Transformer } from './transformers/v14-to-v15';
|
|
10
|
-
export { V15ToV16Transformer } from './transformers/v15-to-v16';
|
|
11
|
-
export { V16ToV17Transformer } from './transformers/v16-to-v17';
|
|
12
|
-
export { V17ToV18Transformer } from './transformers/v17-to-v18';
|
|
13
|
-
export { PG13ToPG17Transformer } from './transformers-direct/v13-to-v17';
|
|
14
|
-
export { PG14ToPG17Transformer } from './transformers-direct/v14-to-v17';
|
|
15
|
-
export { PG15ToPG17Transformer } from './transformers-direct/v15-to-v17';
|
|
16
|
-
export { PG16ToPG17Transformer } from './transformers-direct/v16-to-v17';
|
|
17
|
-
export { PG13ToPG18Transformer } from './transformers-direct/v13-to-v18';
|
|
18
|
-
export { PG14ToPG18Transformer } from './transformers-direct/v14-to-v18';
|
|
19
|
-
export { PG15ToPG18Transformer } from './transformers-direct/v15-to-v18';
|
|
20
|
-
export { PG16ToPG18Transformer } from './transformers-direct/v16-to-v18';
|
|
21
|
-
export { PG17ToPG18Transformer } from './transformers-direct/v17-to-v18';
|
|
22
|
-
export { PG13Types, PG14Types, PG15Types, PG16Types, PG17Types, PG18Types };
|
|
1
|
+
export { classifyStatements } from './facts';
|
|
2
|
+
export { collectCreatedObjects, createQualifyVisitor, mergeInventories, qualifyUnqualified, } from './qualify';
|
|
3
|
+
export { captureAstsFromSql, captureTransformAsts, firstDifference, normalizeParseTree, validateRoundTrip, } from './round-trip';
|
|
4
|
+
export { CLEAN_TREE_VOLATILE_KEYS, cleanTree, normalizeTree, trimDefElemBody, } from './round-trip-core';
|
|
5
|
+
export { createPlpgsqlVisitor, createSqlVisitor, escapeRegexp, extractPgpmHeader, shouldTransformSchema, transformComments, transformJsonStringValues, transformNameList, transformPlpgsqlTypeAst, transformPlpgsqlTypeString, transformRelation, transformSchemaName, transformSql, transformSqlStatement, transformVerifyCalls, validateNoUntransformedSchemas, walkPlpgsqlForSchemas, } from './transform';
|