true-pg 0.6.0 → 0.7.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.
- package/lib/extractor/adapter.d.ts +6 -1
- package/lib/extractor/adapter.js +15 -1
- package/lib/extractor/canonicalise.d.ts +56 -10
- package/lib/extractor/canonicalise.js +341 -202
- package/lib/extractor/index.d.ts +2 -2
- package/lib/extractor/index.js +1 -4
- package/lib/extractor/kinds/composite.d.ts +1 -1
- package/lib/extractor/kinds/composite.js +1 -2
- package/lib/extractor/kinds/domain.d.ts +1 -1
- package/lib/extractor/kinds/domain.js +1 -2
- package/lib/extractor/kinds/function.d.ts +1 -1
- package/lib/extractor/kinds/function.js +5 -6
- package/lib/extractor/kinds/materialized-view.d.ts +1 -1
- package/lib/extractor/kinds/materialized-view.js +1 -2
- package/lib/extractor/kinds/range.d.ts +1 -1
- package/lib/extractor/kinds/range.js +1 -2
- package/lib/extractor/kinds/table.d.ts +1 -1
- package/lib/extractor/kinds/table.js +1 -2
- package/lib/extractor/kinds/view.d.ts +1 -1
- package/lib/extractor/kinds/view.js +1 -2
- package/lib/extractor/pgtype.d.ts +1 -1
- package/lib/imports.js +6 -3
- package/lib/imports.test.d.ts +1 -0
- package/lib/imports.test.js +188 -0
- package/lib/index.js +17 -1
- package/lib/kysely/index.js +2 -0
- package/lib/types.d.ts +1 -1
- package/lib/util.d.ts +7 -0
- package/lib/util.js +14 -0
- package/lib/zod/index.js +2 -10
- package/package.json +1 -1
@@ -1,3 +1,4 @@
|
|
1
|
+
import { Deferred, unreachable } from "../util.js";
|
1
2
|
import { DbAdapter } from "./adapter.js";
|
2
3
|
const removeNulls = (o) => {
|
3
4
|
for (const key in o)
|
@@ -5,6 +6,56 @@ const removeNulls = (o) => {
|
|
5
6
|
delete o[key];
|
6
7
|
return o;
|
7
8
|
};
|
9
|
+
/**
|
10
|
+
* Parses a PostgreSQL type name string to extract its base name,
|
11
|
+
* modifiers, and dimensions from explicit '[]' brackets.
|
12
|
+
*
|
13
|
+
* Examples:
|
14
|
+
*
|
15
|
+
* - `parseTypeName("varchar(50)")`
|
16
|
+
*
|
17
|
+
* `⤷ { baseTypeName: "varchar", modifiers: "50", dimensions: 0, originalTypeName: "varchar(50)" }`
|
18
|
+
*
|
19
|
+
* - `parseTypeName("int[]")`
|
20
|
+
*
|
21
|
+
* `⤷ { baseTypeName: "int", modifiers: null, dimensions: 1, originalTypeName: "int[]" }`
|
22
|
+
*
|
23
|
+
* - `parseTypeName("public.my_table[][]")`
|
24
|
+
*
|
25
|
+
* `⤷ { baseTypeName: "public.my_table", modifiers: null, dimensions: 2, originalTypeName: "public.my_table[][]" }`
|
26
|
+
*
|
27
|
+
* - `parseTypeName("numeric(10, 2)[]")`
|
28
|
+
*
|
29
|
+
* `⤷ { baseTypeName: "numeric", modifiers: "10, 2", dimensions: 1, originalTypeName: "numeric(10, 2)[]" }`
|
30
|
+
*
|
31
|
+
* - `parseTypeName("geometry(Point, 4326)")`
|
32
|
+
*
|
33
|
+
* `⤷ { baseTypeName: "geometry", modifiers: "Point, 4326", dimensions: 0, originalTypeName: "geometry(Point, 4326)" }`
|
34
|
+
*
|
35
|
+
* - `parseTypeName("_text")`
|
36
|
+
*
|
37
|
+
* `⤷ { baseTypeName: "_text", modifiers: null, dimensions: 0, originalTypeName: "_text" }`
|
38
|
+
*
|
39
|
+
* Internal arrays aren't handled here
|
40
|
+
*/
|
41
|
+
export function parseTypeName(type) {
|
42
|
+
let base = type;
|
43
|
+
let modifiers = null;
|
44
|
+
let dimensions = 0;
|
45
|
+
// 1. Extract modifiers (content within the last parentheses)
|
46
|
+
const modifierMatch = base.match(/\(([^)]*)\)$/);
|
47
|
+
if (modifierMatch) {
|
48
|
+
modifiers = modifierMatch[1];
|
49
|
+
base = base.substring(0, modifierMatch.index).trim();
|
50
|
+
}
|
51
|
+
// 2. Count and remove explicit array brackets '[]'
|
52
|
+
// Repeatedly remove '[]' from the end and count dimensions
|
53
|
+
while (base.endsWith("[]")) {
|
54
|
+
dimensions++;
|
55
|
+
base = base.slice(0, -2);
|
56
|
+
}
|
57
|
+
return { original: type, base, modifiers, dimensions };
|
58
|
+
}
|
8
59
|
export var Canonical;
|
9
60
|
(function (Canonical) {
|
10
61
|
let Kind;
|
@@ -18,228 +69,316 @@ export var Canonical;
|
|
18
69
|
Kind["Unknown"] = "unknown";
|
19
70
|
})(Kind = Canonical.Kind || (Canonical.Kind = {}));
|
20
71
|
})(Canonical || (Canonical = {}));
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
72
|
+
/**
|
73
|
+
* Takes base type names (without modifiers/brackets), resolves them to their ultimate base type OID
|
74
|
+
* and internal array dimensions, and fetches basic kind information
|
75
|
+
*/
|
76
|
+
async function resolveBasicInfo(db, types) {
|
25
77
|
const query = `
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
array_dimensions AS (
|
78
|
+
WITH RECURSIVE
|
79
|
+
input(base_type_name, seq) AS (
|
80
|
+
SELECT * FROM unnest($1::text[], $2::int[])
|
81
|
+
),
|
82
|
+
type_resolution(seq, current_oid, level) AS (
|
83
|
+
-- Base case: Look up the initial base type name
|
84
|
+
SELECT i.seq, t.oid, 1
|
85
|
+
FROM input i JOIN pg_type t ON t.oid = i.base_type_name::regtype
|
86
|
+
UNION ALL
|
87
|
+
-- Recursive step: Follow typelem for standard arrays (_)
|
88
|
+
SELECT r.seq, t.typelem, r.level + 1
|
89
|
+
FROM type_resolution r JOIN pg_type t ON r.current_oid = t.oid
|
90
|
+
WHERE t.typelem != 0 AND left(t.typname, 1) = '_'
|
91
|
+
),
|
92
|
+
final_resolution AS (
|
93
|
+
-- Get the OID and max level (depth) for each sequence number
|
94
|
+
SELECT DISTINCT ON (seq) seq, current_oid AS base_type_oid, level
|
95
|
+
FROM type_resolution ORDER BY seq, level DESC
|
96
|
+
)
|
97
|
+
-- Combine resolution with basic type info fetching
|
47
98
|
SELECT
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
ELSE 0
|
55
|
-
END AS dimensions,
|
56
|
-
regexp_replace(clean_type, '\\[\\]', '', 'g') AS base_type_name
|
57
|
-
FROM type_parts
|
58
|
-
),
|
59
|
-
-- Get base type information
|
60
|
-
base_type_info AS (
|
61
|
-
SELECT
|
62
|
-
a.type_name,
|
63
|
-
a.seq,
|
64
|
-
a.modifiers,
|
65
|
-
a.dimensions,
|
66
|
-
t.oid AS type_oid,
|
67
|
-
t.typname AS internal_name,
|
68
|
-
n.nspname AS schema_name,
|
69
|
-
t.typtype AS type_kind_code,
|
70
|
-
t.typbasetype,
|
99
|
+
fr.seq,
|
100
|
+
fr.base_type_oid AS oid,
|
101
|
+
(fr.level - 1) AS internal_dimensions,
|
102
|
+
n.nspname AS schema,
|
103
|
+
t.typname AS name,
|
104
|
+
n.nspname || '.' || t.typname AS canonical_name,
|
71
105
|
CASE t.typtype
|
72
|
-
WHEN 'b' THEN 'base'
|
73
|
-
WHEN '
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
JOIN pg_type t ON t.oid = a.base_type_name::regtype
|
106
|
+
WHEN 'b' THEN 'base'::text WHEN 'c' THEN 'composite'::text WHEN 'd' THEN 'domain'::text
|
107
|
+
WHEN 'e' THEN 'enum'::text WHEN 'p' THEN 'pseudo'::text WHEN 'r' THEN 'range'::text
|
108
|
+
ELSE 'unknown'::text
|
109
|
+
END AS kind,
|
110
|
+
t.typrelid,
|
111
|
+
t.typbasetype,
|
112
|
+
COALESCE(r.rngsubtype, 0) AS rngsubtype
|
113
|
+
FROM final_resolution fr
|
114
|
+
JOIN pg_type t ON t.oid = fr.base_type_oid
|
82
115
|
JOIN pg_namespace n ON t.typnamespace = n.oid
|
83
|
-
|
84
|
-
|
85
|
-
|
116
|
+
LEFT JOIN pg_range r ON t.oid = r.rngtypid AND t.typtype = 'r'
|
117
|
+
ORDER BY fr.seq;
|
118
|
+
`;
|
119
|
+
// Need to handle the string 'kind' coming back from the DB
|
120
|
+
const results = await db.query(query, [
|
121
|
+
types.map(t => t.parsed.base),
|
122
|
+
types.map(t => t.seq),
|
123
|
+
]);
|
124
|
+
return results;
|
125
|
+
}
|
126
|
+
async function resolveBasicInfo1(db, type) {
|
127
|
+
const query = `
|
128
|
+
WITH RECURSIVE
|
129
|
+
input(base_type_name) AS (
|
130
|
+
SELECT $1::text
|
131
|
+
),
|
132
|
+
type_resolution(current_oid, level) AS (
|
133
|
+
-- Base case: Look up the initial base type name
|
134
|
+
SELECT t.oid, 1
|
135
|
+
FROM input i JOIN pg_type t ON t.oid = i.base_type_name::regtype
|
136
|
+
UNION ALL
|
137
|
+
-- Recursive step: Follow typelem for standard arrays (_)
|
138
|
+
SELECT t.typelem, r.level + 1
|
139
|
+
FROM type_resolution r JOIN pg_type t ON r.current_oid = t.oid
|
140
|
+
WHERE t.typelem != 0 AND left(t.typname, 1) = '_'
|
141
|
+
),
|
142
|
+
final_resolution AS (
|
143
|
+
-- Get the OID and max level (depth) for each sequence number
|
144
|
+
SELECT DISTINCT ON (current_oid) current_oid AS base_type_oid, level
|
145
|
+
FROM type_resolution ORDER BY current_oid, level DESC
|
146
|
+
)
|
147
|
+
-- Combine resolution with basic type info fetching
|
86
148
|
SELECT
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
149
|
+
fr.base_type_oid AS oid,
|
150
|
+
(fr.level - 1) AS internal_dimensions,
|
151
|
+
n.nspname AS schema,
|
152
|
+
t.typname AS name,
|
153
|
+
n.nspname || '.' || t.typname AS canonical_name,
|
154
|
+
CASE t.typtype
|
155
|
+
WHEN 'b' THEN 'base'::text WHEN 'c' THEN 'composite'::text WHEN 'd' THEN 'domain'::text
|
156
|
+
WHEN 'e' THEN 'enum'::text WHEN 'p' THEN 'pseudo'::text WHEN 'r' THEN 'range'::text
|
157
|
+
ELSE 'unknown'::text
|
158
|
+
END AS kind,
|
159
|
+
t.typrelid,
|
160
|
+
t.typbasetype,
|
161
|
+
COALESCE(r.rngsubtype, 0) AS rngsubtype
|
162
|
+
FROM final_resolution fr
|
163
|
+
JOIN pg_type t ON t.oid = fr.base_type_oid
|
164
|
+
JOIN pg_namespace n ON t.typnamespace = n.oid
|
165
|
+
LEFT JOIN pg_range r ON t.oid = r.rngtypid AND t.typtype = 'r';
|
166
|
+
`;
|
167
|
+
// Need to handle the string 'kind' coming back from the DB
|
168
|
+
const results = await db.query(query, [type.base]);
|
169
|
+
return results[0];
|
170
|
+
}
|
171
|
+
/** Fetches enum values for given enum type OIDs */
|
172
|
+
async function getEnumValues(db, oid) {
|
173
|
+
const query = `
|
174
|
+
SELECT array_agg(e.enumlabel ORDER BY e.enumsortorder) AS values
|
175
|
+
FROM pg_enum e
|
176
|
+
WHERE e.enumtypid = $1::oid
|
177
|
+
GROUP BY e.enumtypid;
|
178
|
+
`;
|
179
|
+
const results = await db.query(query, [oid]);
|
180
|
+
return results[0]?.values ?? [];
|
181
|
+
}
|
182
|
+
/** Fetches composite attributes for given composite type OIDs (typrelid) */
|
183
|
+
async function getCompositeAttributes(db, relid) {
|
184
|
+
const query = `
|
96
185
|
SELECT
|
97
|
-
|
186
|
+
a.attrelid AS relid,
|
98
187
|
jsonb_agg(
|
99
188
|
jsonb_build_object(
|
100
189
|
'name', a.attname,
|
101
190
|
'index', a.attnum,
|
102
191
|
'type_oid', a.atttypid,
|
103
192
|
'type_name', format_type(a.atttypid, null),
|
104
|
-
'comment', col_description(
|
193
|
+
'comment', col_description(a.attrelid, a.attnum::int),
|
105
194
|
'defaultValue', pg_get_expr(d.adbin, d.adrelid),
|
106
195
|
'isNullable', NOT a.attnotnull,
|
107
196
|
'isIdentity', a.attidentity IS NOT NULL AND a.attidentity != '',
|
108
|
-
'generated', CASE
|
109
|
-
|
110
|
-
WHEN a.attidentity = 'd' THEN 'BY DEFAULT'
|
111
|
-
WHEN a.attgenerated = 's' THEN 'ALWAYS'
|
112
|
-
ELSE 'NEVER'
|
113
|
-
END
|
114
|
-
)
|
115
|
-
ORDER BY a.attnum
|
197
|
+
'generated', CASE WHEN a.attidentity = 'a' THEN 'ALWAYS' WHEN a.attidentity = 'd' THEN 'BY DEFAULT' WHEN a.attgenerated = 's' THEN 'ALWAYS' ELSE 'NEVER' END
|
198
|
+
) ORDER BY a.attnum
|
116
199
|
) AS attributes
|
117
|
-
FROM
|
118
|
-
JOIN
|
119
|
-
|
120
|
-
|
121
|
-
LEFT JOIN pg_attrdef d ON d.adrelid = c.oid AND d.adnum = a.attnum
|
122
|
-
WHERE b.type_kind_code = 'c' AND a.attnum > 0 AND NOT a.attisdropped
|
123
|
-
GROUP BY b.type_name
|
124
|
-
),
|
125
|
-
-- Recursive CTE to resolve domain base types
|
126
|
-
domain_types AS (
|
127
|
-
-- Base case: start with initial domain type
|
128
|
-
SELECT
|
129
|
-
b.type_name AS original_type,
|
130
|
-
b.type_oid AS domain_oid,
|
131
|
-
b.typbasetype AS base_type_oid,
|
132
|
-
1 AS level
|
133
|
-
FROM base_type_info b
|
134
|
-
WHERE b.type_kind_code = 'd'
|
135
|
-
|
136
|
-
UNION ALL
|
137
|
-
|
138
|
-
-- Recursive case: follow chain of domains
|
139
|
-
SELECT
|
140
|
-
d.original_type,
|
141
|
-
t.oid AS domain_oid,
|
142
|
-
t.typbasetype AS base_type_oid,
|
143
|
-
d.level + 1 AS level
|
144
|
-
FROM domain_types d
|
145
|
-
JOIN pg_type t ON d.base_type_oid = t.oid
|
146
|
-
WHERE t.typtype = 'd'-- Only continue if the base is also a domain
|
147
|
-
),
|
148
|
-
-- Get ultimate base type for domains
|
149
|
-
domain_base_types AS (
|
150
|
-
SELECT DISTINCT ON (original_type)
|
151
|
-
d.original_type,
|
152
|
-
format('%s.%s', n.nspname, t.typname) AS base_canonical_name
|
153
|
-
FROM (
|
154
|
-
-- Get the max level for each original type
|
155
|
-
SELECT original_type, MAX(level) AS max_level
|
156
|
-
FROM domain_types
|
157
|
-
GROUP BY original_type
|
158
|
-
) m
|
159
|
-
JOIN domain_types d ON d.original_type = m.original_type AND d.level = m.max_level
|
160
|
-
JOIN pg_type t ON d.base_type_oid = t.oid
|
161
|
-
JOIN pg_namespace n ON t.typnamespace = n.oid
|
162
|
-
),
|
163
|
-
-- Range type subtype information
|
164
|
-
range_subtypes AS (
|
165
|
-
SELECT
|
166
|
-
b.type_name,
|
167
|
-
format('%s.%s', n.nspname, t.typname) AS subtype_canonical_name
|
168
|
-
FROM base_type_info b
|
169
|
-
JOIN pg_range r ON b.type_oid = r.rngtypid
|
170
|
-
JOIN pg_type t ON t.oid = r.rngsubtype -- Join to get subtype details
|
171
|
-
JOIN pg_namespace n ON n.oid = t.typnamespace -- Join to get subtype schema
|
172
|
-
WHERE b.type_kind_code = 'r'
|
173
|
-
)
|
174
|
-
-- Final result as JSON
|
175
|
-
SELECT jsonb_build_object(
|
176
|
-
'canonical_name', b.schema_name || '.' || b.internal_name,
|
177
|
-
'schema', b.schema_name,
|
178
|
-
'name', b.internal_name,
|
179
|
-
'kind', b.type_kind,
|
180
|
-
'dimensions', b.dimensions,
|
181
|
-
'original_type', b.type_name,
|
182
|
-
'modifiers', b.modifiers,
|
183
|
-
'enum_values', e.values,
|
184
|
-
'attributes', c.attributes,
|
185
|
-
'domain_base_type', CASE
|
186
|
-
WHEN b.type_kind_code = 'd' THEN d.base_canonical_name
|
187
|
-
ELSE NULL
|
188
|
-
END,
|
189
|
-
'range_subtype', CASE
|
190
|
-
WHEN b.type_kind_code = 'r' THEN r.subtype_canonical_name
|
191
|
-
ELSE NULL
|
192
|
-
END
|
193
|
-
) AS type_info,
|
194
|
-
b.seq
|
195
|
-
FROM base_type_info b
|
196
|
-
LEFT JOIN enum_values e ON b.type_name = e.type_name
|
197
|
-
LEFT JOIN composite_attributes c ON b.type_name = c.type_name
|
198
|
-
LEFT JOIN domain_base_types d ON b.type_name = d.original_type
|
199
|
-
LEFT JOIN range_subtypes r ON b.type_name = r.type_name
|
200
|
-
ORDER BY b.seq::integer;
|
200
|
+
FROM pg_attribute a
|
201
|
+
LEFT JOIN pg_attrdef d ON d.adrelid = a.attrelid AND d.adnum = a.attnum
|
202
|
+
WHERE a.attrelid = $1::oid AND a.attnum > 0 AND NOT a.attisdropped
|
203
|
+
GROUP BY a.attrelid;
|
201
204
|
`;
|
202
|
-
const
|
203
|
-
return
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
205
|
+
const results = await db.query(query, [relid]);
|
206
|
+
return results[0]?.attributes ?? [];
|
207
|
+
}
|
208
|
+
/** Recursive helper to find the ultimate base type OID for a domain */
|
209
|
+
async function findUltimateDomainBaseOid(db, oid) {
|
210
|
+
const query = `
|
211
|
+
WITH RECURSIVE domain_chain(oid, base_oid, level) AS (
|
212
|
+
SELECT $1::oid, t.typbasetype, 1
|
213
|
+
FROM pg_type t WHERE t.oid = $1::oid AND t.typtype = 'd'
|
214
|
+
UNION ALL
|
215
|
+
SELECT t.oid, t.typbasetype, dc.level + 1
|
216
|
+
FROM domain_chain dc JOIN pg_type t ON dc.base_oid = t.oid
|
217
|
+
WHERE t.typtype = 'd'
|
218
|
+
)
|
219
|
+
SELECT base_oid FROM domain_chain ORDER BY level DESC LIMIT 1;
|
220
|
+
`;
|
221
|
+
const result = await db.query(query, [oid]);
|
222
|
+
return result[0]?.base_oid ?? oid; // Return original if not a domain or chain ends
|
223
|
+
}
|
224
|
+
/** Fetches the canonical name of the ultimate base type for given domain OIDs */
|
225
|
+
async function getDomainBaseTypeName(db, typbasetype) {
|
226
|
+
const ultimateBaseOid = await findUltimateDomainBaseOid(db, typbasetype);
|
227
|
+
const query = `
|
228
|
+
SELECT t.oid, format('%I.%I', n.nspname, t.typname) AS name
|
229
|
+
FROM pg_type t JOIN pg_namespace n ON t.typnamespace = n.oid
|
230
|
+
WHERE t.oid = $1::oid
|
231
|
+
`;
|
232
|
+
const results = await db.query(query, [ultimateBaseOid]);
|
233
|
+
return results[0]?.name ?? "";
|
234
|
+
}
|
235
|
+
/** Fetches the canonical name of the subtype for given range OIDs */
|
236
|
+
async function getRangeSubtypeName(db, oid) {
|
237
|
+
const query = `
|
238
|
+
SELECT t.oid, format('%I.%I', n.nspname, t.typname) AS name
|
239
|
+
FROM pg_type t JOIN pg_namespace n ON t.typnamespace = n.oid
|
240
|
+
WHERE t.oid = $1::oid
|
241
|
+
`;
|
242
|
+
const results = await db.query(query, [oid]);
|
243
|
+
return results[0]?.name ?? "";
|
244
|
+
}
|
245
|
+
async function canonicaliseType(db, basic, rawTypeCache, canonicalCache) {
|
246
|
+
switch (basic.kind) {
|
247
|
+
case Canonical.Kind.Base:
|
248
|
+
return { kind: Canonical.Kind.Base };
|
249
|
+
case Canonical.Kind.Enum:
|
250
|
+
const enumValues = await getEnumValues(db, basic.oid);
|
251
|
+
if (enumValues.length === 0) {
|
252
|
+
throw new Error(`Enum ${basic.canonical_name} (OID: ${basic.oid}) lacks values.`);
|
253
|
+
}
|
254
|
+
return { kind: Canonical.Kind.Enum, enum_values: enumValues };
|
255
|
+
case Canonical.Kind.Composite: {
|
256
|
+
const rawAttributes = await getCompositeAttributes(db, basic.typrelid);
|
257
|
+
const attributeTypes = rawAttributes.map(attr => attr.type_name);
|
258
|
+
const canonicalAttributeTypes = await canonicalise(db, attributeTypes, rawTypeCache, canonicalCache); // Recursive call
|
259
|
+
const attributes = await Promise.all(rawAttributes.map(async (attr, index) => {
|
260
|
+
return removeNulls({
|
261
|
+
name: attr.name,
|
262
|
+
index: attr.index,
|
263
|
+
type: canonicalAttributeTypes[index],
|
264
|
+
comment: attr.comment,
|
265
|
+
defaultValue: attr.defaultValue,
|
266
|
+
isNullable: attr.isNullable,
|
267
|
+
isIdentity: attr.isIdentity,
|
268
|
+
generated: attr.generated,
|
269
|
+
});
|
220
270
|
}));
|
221
|
-
return
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
271
|
+
return { kind: Canonical.Kind.Composite, attributes };
|
272
|
+
}
|
273
|
+
case Canonical.Kind.Domain: {
|
274
|
+
const baseTypeName = await getDomainBaseTypeName(db, basic.typbasetype);
|
275
|
+
if (!baseTypeName) {
|
276
|
+
throw new Error(`Domain ${basic.canonical_name} (OID: ${basic.oid}) lacks a resolved base type name.`);
|
277
|
+
}
|
278
|
+
const canonicalBaseType = await canonicalise(db, [baseTypeName], rawTypeCache, canonicalCache); // Recursive call
|
279
|
+
return { kind: Canonical.Kind.Domain, domain_base_type: canonicalBaseType[0] };
|
280
|
+
}
|
281
|
+
case Canonical.Kind.Range: {
|
282
|
+
const subtypeName = await getRangeSubtypeName(db, basic.rngsubtype);
|
283
|
+
if (!subtypeName) {
|
284
|
+
throw new Error(`Range ${basic.canonical_name} (OID: ${basic.oid}) lacks a resolved subtype name.`);
|
285
|
+
}
|
286
|
+
const canonicalSubtype = await canonicalise(db, [subtypeName], rawTypeCache, canonicalCache); // Recursive call
|
287
|
+
return { kind: Canonical.Kind.Range, range_subtype: canonicalSubtype[0] };
|
226
288
|
}
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
289
|
+
case Canonical.Kind.Pseudo:
|
290
|
+
return { kind: Canonical.Kind.Pseudo };
|
291
|
+
case Canonical.Kind.Unknown:
|
292
|
+
throw new Error(`Canonicalising "${basic.original_type}" resulted in unknown kind: ${basic.canonical_name}`);
|
293
|
+
default:
|
294
|
+
return unreachable(basic.kind);
|
295
|
+
}
|
296
|
+
}
|
297
|
+
export const canonicalise = async (db, types, rawTypeCache, canonicalCache) => {
|
298
|
+
if (types.length === 0)
|
299
|
+
return [];
|
300
|
+
const withSeq = types.map((type, seq) => ({ type, seq }));
|
301
|
+
// final list of resolved canonical types
|
302
|
+
const results = [];
|
303
|
+
// unresolved types, awaiting resolution
|
304
|
+
const unresolved = [];
|
305
|
+
for (const { type, seq } of withSeq) {
|
306
|
+
// if the type is already resolved, add it to the results
|
307
|
+
const cached = rawTypeCache.get(type);
|
308
|
+
if (cached)
|
309
|
+
results.push(cached);
|
310
|
+
else {
|
311
|
+
// if the type is not resolved, create a deferred promise and add it to the unresolved list
|
312
|
+
const deferred = new Deferred();
|
313
|
+
rawTypeCache.set(type, deferred.promise);
|
314
|
+
results.push(deferred.promise);
|
315
|
+
const parsed = parseTypeName(type);
|
316
|
+
unresolved.push({ seq, parsed, deferred });
|
234
317
|
}
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
318
|
+
}
|
319
|
+
const resolved = await resolveBasicInfo(db, unresolved);
|
320
|
+
Promise.all(resolved.map(async (info, index) => {
|
321
|
+
const { parsed, deferred } = unresolved[index];
|
322
|
+
try {
|
323
|
+
const dimensions = parsed.dimensions + info.internal_dimensions;
|
324
|
+
const common = {
|
325
|
+
kind: info.kind,
|
326
|
+
oid: info.oid,
|
327
|
+
typrelid: info.typrelid,
|
328
|
+
typbasetype: info.typbasetype,
|
329
|
+
rngsubtype: info.rngsubtype,
|
330
|
+
canonical_name: info.canonical_name,
|
331
|
+
schema: info.schema,
|
332
|
+
name: info.name,
|
333
|
+
original_type: parsed.original,
|
334
|
+
modifiers: parsed.modifiers,
|
335
|
+
dimensions,
|
336
|
+
};
|
337
|
+
let cached = canonicalCache.get(info.canonical_name);
|
338
|
+
if (cached) {
|
339
|
+
const exclusive = await cached;
|
340
|
+
const result = { ...common, ...exclusive };
|
341
|
+
deferred.resolve(result);
|
342
|
+
}
|
343
|
+
else {
|
344
|
+
const deferred2 = new Deferred();
|
345
|
+
canonicalCache.set(info.canonical_name, deferred2.promise);
|
346
|
+
cached = deferred2.promise;
|
347
|
+
const exclusive = await canonicaliseType(db, common, rawTypeCache, canonicalCache);
|
348
|
+
deferred2.resolve(exclusive);
|
349
|
+
const result = { ...common, ...exclusive };
|
350
|
+
deferred.resolve(result);
|
351
|
+
}
|
352
|
+
}
|
353
|
+
catch (error) {
|
354
|
+
deferred.reject(error);
|
242
355
|
}
|
243
|
-
return removeNulls(each);
|
244
356
|
}));
|
357
|
+
const ret = await Promise.all(results);
|
358
|
+
return ret;
|
359
|
+
};
|
360
|
+
export const oidsToQualifiedNames = async (db, oids) => {
|
361
|
+
if (oids.length === 0)
|
362
|
+
return [];
|
363
|
+
const query = `
|
364
|
+
SELECT
|
365
|
+
input.ord,
|
366
|
+
format('%I.%I', n.nspname, t.typname) AS qualified_name
|
367
|
+
-- Use unnest WITH ORDINALITY because SQL doesn't guarantee order of SELECT results
|
368
|
+
FROM unnest($1::oid[]) WITH ORDINALITY AS input(oid, ord)
|
369
|
+
JOIN pg_type t ON t.oid = input.oid
|
370
|
+
JOIN pg_namespace n ON t.typnamespace = n.oid
|
371
|
+
ORDER BY input.ord;
|
372
|
+
`;
|
373
|
+
const results = await db.query(query, [oids]);
|
374
|
+
return results.map(r => r.qualified_name);
|
375
|
+
};
|
376
|
+
export const canonicaliseFromOids = async (db, oids) => {
|
377
|
+
if (oids.length === 0)
|
378
|
+
return [];
|
379
|
+
const types = await oidsToQualifiedNames(db, oids);
|
380
|
+
const unknown = types.filter(name => name == undefined);
|
381
|
+
if (unknown.length > 0)
|
382
|
+
throw new Error(`Failed to resolve OIDs to type names: ${unknown.join(", ")}`);
|
383
|
+
return db.canonicalise(types);
|
245
384
|
};
|
package/lib/extractor/index.d.ts
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import Pg from "pg";
|
2
2
|
import { PGlite as Pglite } from "@electric-sql/pglite";
|
3
|
+
import { DbAdapter } from "./adapter.ts";
|
3
4
|
import { type TableDetails } from "./kinds/table.ts";
|
4
5
|
import { type ViewDetails } from "./kinds/view.ts";
|
5
6
|
import { type MaterializedViewDetails } from "./kinds/materialized-view.ts";
|
@@ -72,7 +73,7 @@ export interface ExtractSchemaOptions {
|
|
72
73
|
onProgressEnd?: () => void;
|
73
74
|
}
|
74
75
|
export declare class Extractor {
|
75
|
-
|
76
|
+
db: DbAdapter;
|
76
77
|
/**
|
77
78
|
* @param connectionConfig - Connection string or configuration object for Postgres connection
|
78
79
|
*/
|
@@ -81,7 +82,6 @@ export declare class Extractor {
|
|
81
82
|
uri?: string;
|
82
83
|
config?: Pg.ConnectionConfig;
|
83
84
|
});
|
84
|
-
canonicalise(types: string[]): Promise<Canonical[]>;
|
85
85
|
getBuiltinTypes(): Promise<{
|
86
86
|
name: string;
|
87
87
|
format: string;
|
package/lib/extractor/index.js
CHANGED
@@ -11,7 +11,7 @@ import extractDomain, {} from "./kinds/domain.js";
|
|
11
11
|
import extractRange, {} from "./kinds/range.js";
|
12
12
|
import fetchTypes from "./fetchTypes.js";
|
13
13
|
export { pgTypeKinds } from "./pgtype.js";
|
14
|
-
import { canonicalise, Canonical } from "./canonicalise.js";
|
14
|
+
import { canonicalise, Canonical, canonicaliseFromOids } from "./canonicalise.js";
|
15
15
|
export { Canonical };
|
16
16
|
export { FunctionReturnTypeKind } from "./kinds/function.js";
|
17
17
|
const emptySchema = {
|
@@ -63,9 +63,6 @@ export class Extractor {
|
|
63
63
|
}
|
64
64
|
this.db = new DbAdapter(pg, opts.pg ? true : false);
|
65
65
|
}
|
66
|
-
async canonicalise(types) {
|
67
|
-
return canonicalise(this.db, types);
|
68
|
-
}
|
69
66
|
async getBuiltinTypes() {
|
70
67
|
await this.db.connect();
|
71
68
|
const db = this.db;
|
@@ -1,9 +1,8 @@
|
|
1
|
-
import { Canonical, canonicalise } from "../canonicalise.js";
|
2
1
|
const extractComposite = async (db, composite) => {
|
3
2
|
// Form the fully qualified type name
|
4
3
|
const fullTypeName = `"${composite.schemaName}"."${composite.name}"`;
|
5
4
|
// Get canonical type information with all the metadata
|
6
|
-
const [canonical] = await canonicalise(
|
5
|
+
const [canonical] = await db.canonicalise([fullTypeName]);
|
7
6
|
// Return the composite type with its canonical representation
|
8
7
|
return {
|
9
8
|
...composite,
|
@@ -1,9 +1,8 @@
|
|
1
|
-
import { Canonical, canonicalise } from "../canonicalise.js";
|
2
1
|
const extractDomain = async (db, domain) => {
|
3
2
|
// Form the fully qualified type name
|
4
3
|
const fullTypeName = `"${domain.schemaName}"."${domain.name}"`;
|
5
4
|
// Get canonical type information with all the metadata
|
6
|
-
const [canonical] = await canonicalise(
|
5
|
+
const [canonical] = await db.canonicalise([fullTypeName]);
|
7
6
|
// Return the composite type with its canonical representation
|
8
7
|
return {
|
9
8
|
...domain,
|