true-pg 0.5.1 → 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.
@@ -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
- export const canonicalise = async (db, types) => {
22
- if (types.length === 0)
23
- return [];
24
- const placeholders = types.map((_, i) => `($${i * 2 + 1}, $${i * 2 + 2})`).join(", ");
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
- WITH RECURSIVE
27
- -- Parameters with sequence numbers to preserve order
28
- input(type_name, seq) AS (
29
- VALUES ${placeholders}
30
- ),
31
- -- Parse array dimensions and base type
32
- type_parts AS (
33
- SELECT
34
- type_name,
35
- seq,
36
- CASE
37
- WHEN type_name ~ '\\(.*\\)' THEN regexp_replace(type_name, '\\(.*\\)', '')
38
- ELSE type_name
39
- END AS clean_type,
40
- CASE
41
- WHEN type_name ~ '\\(.*\\)' THEN substring(type_name from '\\((.*\\?)\\)')
42
- ELSE NULL
43
- END AS modifiers
44
- FROM input
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
- type_name,
49
- seq,
50
- modifiers,
51
- CASE
52
- WHEN clean_type ~ '.*\\[\\].*' THEN
53
- (length(clean_type) - length(regexp_replace(clean_type, '\\[\\]', '', 'g'))) / 2
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 'c' THEN 'composite'
74
- WHEN 'd' THEN 'domain'
75
- WHEN 'e' THEN 'enum'
76
- WHEN 'p' THEN 'pseudo'
77
- WHEN 'r' THEN 'range'
78
- ELSE 'unknown'
79
- END AS type_kind
80
- FROM array_dimensions a
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
- -- Handle enum values for enum types
85
- enum_values AS (
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
- b.type_name,
88
- jsonb_agg(e.enumlabel ORDER BY e.enumsortorder) AS values
89
- FROM base_type_info b
90
- JOIN pg_enum e ON b.type_oid = e.enumtypid
91
- WHERE b.type_kind_code = 'e'
92
- GROUP BY b.type_name
93
- ),
94
- -- Enhanced composite attributes with additional metadata
95
- composite_attributes AS (
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
- b.type_name,
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(c.oid, a.attnum::int),
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
- WHEN a.attidentity = 'a' THEN 'ALWAYS'
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 base_type_info b
118
- JOIN pg_type t ON t.oid = b.type_oid
119
- JOIN pg_class c ON c.oid = t.typrelid
120
- JOIN pg_attribute a ON a.attrelid = c.oid
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 resolved = await db.query(query, types.flatMap((type, index) => [type, index]));
203
- return Promise.all(resolved
204
- .map(each => each.type_info)
205
- .map(async (each) => {
206
- if (each.kind === Canonical.Kind.Composite) {
207
- const types = each.attributes.map(each => each.type_name);
208
- const canonical = await canonicalise(db, types);
209
- const attributes = await Promise.all(each.attributes.map(async (each, index) => {
210
- return {
211
- name: each.name,
212
- index: each.index,
213
- type: canonical[index],
214
- comment: each.comment,
215
- defaultValue: each.defaultValue,
216
- isNullable: each.isNullable,
217
- isIdentity: each.isIdentity,
218
- generated: each.generated,
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 removeNulls({
222
- ...each,
223
- kind: Canonical.Kind.Composite,
224
- attributes,
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
- if (each.kind === Canonical.Kind.Domain) {
228
- const canonical = await canonicalise(db, [each.domain_base_type]);
229
- return removeNulls({
230
- ...each,
231
- kind: Canonical.Kind.Domain,
232
- domain_base_type: canonical[0],
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
- if (each.kind === Canonical.Kind.Range) {
236
- const canonical = await canonicalise(db, [each.range_subtype]);
237
- return removeNulls({
238
- ...each,
239
- kind: Canonical.Kind.Range,
240
- range_subtype: canonical[0],
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
  };
@@ -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";
@@ -9,6 +10,7 @@ import { type FunctionDetails } from "./kinds/function.ts";
9
10
  import { type DomainDetails } from "./kinds/domain.ts";
10
11
  import { type RangeDetails } from "./kinds/range.ts";
11
12
  import type { PgType } from "./pgtype.ts";
13
+ export { pgTypeKinds, type PgType, type Kind } from "./pgtype.ts";
12
14
  import { Canonical } from "./canonicalise.ts";
13
15
  export { Canonical };
14
16
  export type { TableDetails, ViewDetails, MaterializedViewDetails, EnumDetails, CompositeTypeDetails, FunctionDetails, DomainDetails, RangeDetails, };
@@ -23,14 +25,14 @@ export { FunctionReturnTypeKind } from "./kinds/function.ts";
23
25
  */
24
26
  export type Schema = {
25
27
  name: string;
26
- tables: TableDetails[];
27
- views: ViewDetails[];
28
- materializedViews: MaterializedViewDetails[];
29
- enums: EnumDetails[];
30
- composites: CompositeTypeDetails[];
31
- functions: FunctionDetails[];
32
- domains: DomainDetails[];
33
- ranges: RangeDetails[];
28
+ table: TableDetails[];
29
+ view: ViewDetails[];
30
+ materializedView: MaterializedViewDetails[];
31
+ enum: EnumDetails[];
32
+ composite: CompositeTypeDetails[];
33
+ function: FunctionDetails[];
34
+ domain: DomainDetails[];
35
+ range: RangeDetails[];
34
36
  };
35
37
  export type SchemaType = TableDetails | ViewDetails | MaterializedViewDetails | EnumDetails | CompositeTypeDetails | FunctionDetails | DomainDetails | RangeDetails;
36
38
  /**
@@ -71,7 +73,7 @@ export interface ExtractSchemaOptions {
71
73
  onProgressEnd?: () => void;
72
74
  }
73
75
  export declare class Extractor {
74
- private db;
76
+ db: DbAdapter;
75
77
  /**
76
78
  * @param connectionConfig - Connection string or configuration object for Postgres connection
77
79
  */
@@ -80,7 +82,6 @@ export declare class Extractor {
80
82
  uri?: string;
81
83
  config?: Pg.ConnectionConfig;
82
84
  });
83
- canonicalise(types: string[]): Promise<Canonical[]>;
84
85
  getBuiltinTypes(): Promise<{
85
86
  name: string;
86
87
  format: string;