graphile-scoped-introspection 0.2.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,705 @@
1
+ /**
2
+ * Scoped catalog SQL adapted from Graphile Crystal's
3
+ * utils/pg-introspection/src/scopedIntrospection.ts at commit
4
+ * 441cef73b0a12ed24343b7f38241af28b6454280, which is MIT licensed
5
+ * (Copyright © 2023 Benjie Gillam). This is a static, CNC-owned query
6
+ * generator: it does not inspect or rewrite the upstream query at runtime.
7
+ */
8
+ const SCOPED_CTES = `recursive
9
+ requested_schema_names(schema_name) as (
10
+ select distinct requested.schema_name
11
+ from pg_catalog.unnest($1::text[]) as requested(schema_name)
12
+ ),
13
+
14
+ capability_extension_names(extension_name) as (
15
+ select distinct capability.extension_name
16
+ from pg_catalog.unnest($2::text[]) as capability(extension_name)
17
+ ),
18
+
19
+ requested_namespaces as (
20
+ select pg_namespace.oid as _id, pg_namespace.nspname
21
+ from pg_catalog.pg_namespace
22
+ inner join requested_schema_names
23
+ on requested_schema_names.schema_name = pg_namespace.nspname
24
+ ),
25
+
26
+ root_objects(object_class, object_id) as (
27
+ select 'pg_catalog.pg_class'::regclass::oid, pg_class.oid
28
+ from pg_catalog.pg_class
29
+ where pg_class.relnamespace in (select requested_namespaces._id from requested_namespaces)
30
+
31
+ union
32
+
33
+ select 'pg_catalog.pg_constraint'::regclass::oid, pg_constraint.oid
34
+ from pg_catalog.pg_constraint
35
+ where pg_constraint.connamespace in (select requested_namespaces._id from requested_namespaces)
36
+
37
+ union
38
+
39
+ select 'pg_catalog.pg_proc'::regclass::oid, pg_proc.oid
40
+ from pg_catalog.pg_proc
41
+ where pg_proc.pronamespace in (select requested_namespaces._id from requested_namespaces)
42
+ and pg_proc.prorettype operator(pg_catalog.<>) 2279
43
+
44
+ union
45
+
46
+ select 'pg_catalog.pg_type'::regclass::oid, pg_type.oid
47
+ from pg_catalog.pg_type
48
+ where pg_type.typnamespace in (select requested_namespaces._id from requested_namespaces)
49
+ ),
50
+
51
+ object_closure(object_class, object_id) as (
52
+ select root_objects.object_class, root_objects.object_id
53
+ from root_objects
54
+
55
+ union
56
+
57
+ select dependency.object_class, dependency.object_id
58
+ from object_closure
59
+ cross join lateral (
60
+ select
61
+ 'pg_catalog.pg_type'::regclass::oid as object_class,
62
+ pg_class.reltype as object_id
63
+ from pg_catalog.pg_class
64
+ where object_closure.object_class = 'pg_catalog.pg_class'::regclass
65
+ and pg_class.oid = object_closure.object_id
66
+
67
+ union all
68
+
69
+ select 'pg_catalog.pg_type'::regclass::oid, pg_class.reloftype
70
+ from pg_catalog.pg_class
71
+ where object_closure.object_class = 'pg_catalog.pg_class'::regclass
72
+ and pg_class.oid = object_closure.object_id
73
+
74
+ union all
75
+
76
+ select 'pg_catalog.pg_type'::regclass::oid, pg_attribute.atttypid
77
+ from pg_catalog.pg_attribute
78
+ where object_closure.object_class = 'pg_catalog.pg_class'::regclass
79
+ and pg_attribute.attrelid = object_closure.object_id
80
+
81
+ union all
82
+
83
+ select 'pg_catalog.pg_constraint'::regclass::oid, pg_constraint.oid
84
+ from pg_catalog.pg_constraint
85
+ where object_closure.object_class = 'pg_catalog.pg_class'::regclass
86
+ and pg_constraint.conrelid = object_closure.object_id
87
+
88
+ union all
89
+
90
+ select 'pg_catalog.pg_class'::regclass::oid, pg_index.indexrelid
91
+ from pg_catalog.pg_index
92
+ where object_closure.object_class = 'pg_catalog.pg_class'::regclass
93
+ and pg_index.indrelid = object_closure.object_id
94
+
95
+ union all
96
+
97
+ select 'pg_catalog.pg_class'::regclass::oid, pg_inherits.inhparent
98
+ from pg_catalog.pg_inherits
99
+ where object_closure.object_class = 'pg_catalog.pg_class'::regclass
100
+ and pg_inherits.inhrelid = object_closure.object_id
101
+
102
+ union all
103
+
104
+ select 'pg_catalog.pg_class'::regclass::oid, constraint_class.oid
105
+ from pg_catalog.pg_constraint
106
+ cross join lateral pg_catalog.unnest(
107
+ array[
108
+ pg_constraint.conrelid,
109
+ pg_constraint.confrelid,
110
+ pg_constraint.conindid
111
+ ]::oid[]
112
+ ) as constraint_class(oid)
113
+ where object_closure.object_class = 'pg_catalog.pg_constraint'::regclass
114
+ and pg_constraint.oid = object_closure.object_id
115
+
116
+ union all
117
+
118
+ select 'pg_catalog.pg_type'::regclass::oid, pg_constraint.contypid
119
+ from pg_catalog.pg_constraint
120
+ where object_closure.object_class = 'pg_catalog.pg_constraint'::regclass
121
+ and pg_constraint.oid = object_closure.object_id
122
+
123
+ union all
124
+
125
+ select 'pg_catalog.pg_constraint'::regclass::oid, pg_constraint.conparentid
126
+ from pg_catalog.pg_constraint
127
+ where object_closure.object_class = 'pg_catalog.pg_constraint'::regclass
128
+ and pg_constraint.oid = object_closure.object_id
129
+
130
+ union all
131
+
132
+ select 'pg_catalog.pg_type'::regclass::oid, procedure_type.oid
133
+ from pg_catalog.pg_proc
134
+ cross join lateral pg_catalog.unnest(
135
+ coalesce(pg_proc.proallargtypes, pg_proc.proargtypes::oid[])
136
+ || array[pg_proc.prorettype]::oid[]
137
+ ) as procedure_type(oid)
138
+ where object_closure.object_class = 'pg_catalog.pg_proc'::regclass
139
+ and pg_proc.oid = object_closure.object_id
140
+
141
+ union all
142
+
143
+ select 'pg_catalog.pg_type'::regclass::oid, dependency_type.oid
144
+ from pg_catalog.pg_type
145
+ cross join lateral pg_catalog.unnest(
146
+ array[
147
+ pg_type.typbasetype,
148
+ pg_type.typelem,
149
+ pg_type.typarray
150
+ ]::oid[]
151
+ ) as dependency_type(oid)
152
+ where object_closure.object_class = 'pg_catalog.pg_type'::regclass
153
+ and pg_type.oid = object_closure.object_id
154
+
155
+ union all
156
+
157
+ select 'pg_catalog.pg_class'::regclass::oid, pg_type.typrelid
158
+ from pg_catalog.pg_type
159
+ where object_closure.object_class = 'pg_catalog.pg_type'::regclass
160
+ and pg_type.oid = object_closure.object_id
161
+
162
+ union all
163
+
164
+ select 'pg_catalog.pg_constraint'::regclass::oid, pg_constraint.oid
165
+ from pg_catalog.pg_constraint
166
+ where object_closure.object_class = 'pg_catalog.pg_type'::regclass
167
+ and pg_constraint.contypid = object_closure.object_id
168
+
169
+ union all
170
+
171
+ select 'pg_catalog.pg_type'::regclass::oid, range_type.oid
172
+ from pg_catalog.pg_range
173
+ cross join lateral pg_catalog.unnest(
174
+ array[
175
+ pg_range.rngtypid,
176
+ pg_range.rngsubtype,
177
+ pg_range.rngmultitypid
178
+ ]::oid[]
179
+ ) as range_type(oid)
180
+ where object_closure.object_class = 'pg_catalog.pg_type'::regclass
181
+ and object_closure.object_id in (pg_range.rngtypid, pg_range.rngmultitypid)
182
+ ) as dependency
183
+ where dependency.object_id operator(pg_catalog.<>) 0
184
+ ),
185
+
186
+ retained_index_metadata(indexrelid, indclass, indcollation) as (
187
+ select pg_index.indexrelid, pg_index.indclass, pg_index.indcollation
188
+ from object_closure
189
+ inner join pg_catalog.pg_class retained_index
190
+ on object_closure.object_class = 'pg_catalog.pg_class'::regclass
191
+ and retained_index.oid = object_closure.object_id
192
+ and retained_index.relkind in ('i', 'I')
193
+ inner join pg_catalog.pg_index
194
+ on pg_index.indexrelid = retained_index.oid
195
+ ),
196
+
197
+ retained_index_opclasses(_id, opcfamily) as (
198
+ select pg_opclass.oid, pg_opclass.opcfamily
199
+ from retained_index_metadata
200
+ cross join lateral pg_catalog.unnest(
201
+ retained_index_metadata.indclass::oid[]
202
+ ) as index_opclass(_id)
203
+ inner join pg_catalog.pg_opclass
204
+ on pg_opclass.oid = index_opclass._id
205
+ ),
206
+
207
+ retained_index_support_objects(object_class, object_id) as (
208
+ select 'pg_catalog.pg_opclass'::regclass::oid, retained_index_opclasses._id
209
+ from retained_index_opclasses
210
+
211
+ union
212
+
213
+ select 'pg_catalog.pg_opfamily'::regclass::oid, retained_index_opclasses.opcfamily
214
+ from retained_index_opclasses
215
+
216
+ union
217
+
218
+ select 'pg_catalog.pg_operator'::regclass::oid, pg_amop.amopopr
219
+ from retained_index_opclasses
220
+ inner join pg_catalog.pg_amop
221
+ on pg_amop.amopfamily = retained_index_opclasses.opcfamily
222
+
223
+ union
224
+
225
+ select 'pg_catalog.pg_proc'::regclass::oid, pg_amproc.amproc
226
+ from retained_index_opclasses
227
+ inner join pg_catalog.pg_amproc
228
+ on pg_amproc.amprocfamily = retained_index_opclasses.opcfamily
229
+
230
+ union
231
+
232
+ select 'pg_catalog.pg_collation'::regclass::oid, index_collation._id
233
+ from retained_index_metadata
234
+ cross join lateral pg_catalog.unnest(
235
+ retained_index_metadata.indcollation::oid[]
236
+ ) as index_collation(_id)
237
+ where index_collation._id operator(pg_catalog.<>) 0
238
+ ),
239
+
240
+ installed_extensions(_id, extnamespace) as (
241
+ select pg_extension.oid, pg_extension.extnamespace
242
+ from pg_catalog.pg_extension
243
+ where pg_extension.extname in (
244
+ select capability_extension_names.extension_name
245
+ from capability_extension_names
246
+ )
247
+ or exists (
248
+ select 1
249
+ from object_closure
250
+ inner join pg_catalog.pg_depend
251
+ on pg_depend.classid = object_closure.object_class
252
+ and pg_depend.objid = object_closure.object_id
253
+ and pg_depend.refclassid = 'pg_catalog.pg_extension'::regclass
254
+ and pg_depend.refobjid = pg_extension.oid
255
+ and pg_depend.deptype = 'e'
256
+ )
257
+ or exists (
258
+ select 1
259
+ from retained_index_support_objects
260
+ inner join pg_catalog.pg_depend
261
+ on pg_depend.classid = retained_index_support_objects.object_class
262
+ and pg_depend.objid = retained_index_support_objects.object_id
263
+ and pg_depend.refclassid = 'pg_catalog.pg_extension'::regclass
264
+ and pg_depend.refobjid = pg_extension.oid
265
+ and pg_depend.deptype = 'e'
266
+ )
267
+ or exists (
268
+ select 1
269
+ from object_closure
270
+ inner join pg_catalog.pg_class retained_index
271
+ on object_closure.object_class = 'pg_catalog.pg_class'::regclass
272
+ and retained_index.oid = object_closure.object_id
273
+ and retained_index.relkind = 'i'
274
+ inner join pg_catalog.pg_depend
275
+ on pg_depend.classid = 'pg_catalog.pg_am'::regclass
276
+ and pg_depend.objid = retained_index.relam
277
+ and pg_depend.refclassid = 'pg_catalog.pg_extension'::regclass
278
+ and pg_depend.refobjid = pg_extension.oid
279
+ and pg_depend.deptype = 'e'
280
+ )
281
+ ),
282
+
283
+ scoped_namespaces(_id) as (
284
+ select requested_namespaces._id
285
+ from requested_namespaces
286
+
287
+ union
288
+
289
+ select pg_class.relnamespace
290
+ from object_closure
291
+ inner join pg_catalog.pg_class
292
+ on object_closure.object_class = 'pg_catalog.pg_class'::regclass
293
+ and pg_class.oid = object_closure.object_id
294
+
295
+ union
296
+
297
+ select pg_constraint.connamespace
298
+ from object_closure
299
+ inner join pg_catalog.pg_constraint
300
+ on object_closure.object_class = 'pg_catalog.pg_constraint'::regclass
301
+ and pg_constraint.oid = object_closure.object_id
302
+
303
+ union
304
+
305
+ select pg_proc.pronamespace
306
+ from object_closure
307
+ inner join pg_catalog.pg_proc
308
+ on object_closure.object_class = 'pg_catalog.pg_proc'::regclass
309
+ and pg_proc.oid = object_closure.object_id
310
+
311
+ union
312
+
313
+ select pg_type.typnamespace
314
+ from object_closure
315
+ inner join pg_catalog.pg_type
316
+ on object_closure.object_class = 'pg_catalog.pg_type'::regclass
317
+ and pg_type.oid = object_closure.object_id
318
+
319
+ union
320
+
321
+ select installed_extensions.extnamespace
322
+ from installed_extensions
323
+ where installed_extensions.extnamespace operator(pg_catalog.<>) 0
324
+
325
+ union
326
+
327
+ select pg_namespace.oid
328
+ from pg_catalog.pg_namespace
329
+ where pg_namespace.nspname = 'pg_catalog'
330
+ ),
331
+
332
+ `;
333
+ // We might want this to take options in future, so we've made it a function.
334
+ /**
335
+ * Builds a PostgreSQL introspection SQL query to return an object with the same shape as `Introspection` above.
336
+ */
337
+ const buildIntrospectionQuery = (scope) => `\
338
+ with
339
+ ${scope?.ctes ?? ''}\
340
+ database as (
341
+ select pg_database.oid as _id, *
342
+ from pg_catalog.pg_database
343
+ where datname = current_database()
344
+ ),
345
+
346
+ namespaces as (
347
+ select pg_namespace.oid as _id, *
348
+ from pg_catalog.pg_namespace
349
+ where ${scope.namespacePredicate}
350
+ ),
351
+
352
+ classes as (
353
+ select pg_class.oid as _id, *,
354
+ pg_catalog.pg_relation_is_updatable(oid, true)::bit(8)::int4 as "updatable_mask"
355
+ from pg_catalog.pg_class
356
+ where ${scope.classPredicate}
357
+ ),
358
+
359
+ attributes as (
360
+ select *
361
+ from pg_catalog.pg_attribute
362
+ where attrelid in (select classes._id from classes) AND attnum > 0
363
+ ),
364
+
365
+ constraints as (
366
+ select pg_constraint.oid as _id, *
367
+ from pg_catalog.pg_constraint
368
+ where ${scope.constraintPredicate}
369
+ ),
370
+
371
+ procs as (
372
+ select pg_proc.oid as _id, *
373
+ from pg_catalog.pg_proc
374
+ where ${scope.procPredicate}
375
+ and prorettype operator(pg_catalog.<>) 2279
376
+ ),
377
+
378
+ roles as (
379
+ select pg_roles.oid as _id, *
380
+ from pg_catalog.pg_roles
381
+ ${scope?.rolePredicate
382
+ ? ` where ${scope.rolePredicate}
383
+ `
384
+ : ''}\
385
+ ),
386
+
387
+ auth_members as (
388
+ select *
389
+ from pg_catalog.pg_auth_members
390
+ where ${scope.authMemberPredicate ?? 'roleid in (select roles._id from roles)'}
391
+ ),
392
+
393
+ types as (
394
+ select pg_type.oid as _id, *
395
+ from pg_catalog.pg_type
396
+ where ${scope.typePredicate}
397
+ ),
398
+
399
+ enums as (
400
+ select pg_enum.oid as _id, *
401
+ from pg_catalog.pg_enum
402
+ where enumtypid in (select types._id from types)
403
+ ),
404
+
405
+ extensions as (
406
+ select pg_extension.oid as _id, *
407
+ from pg_catalog.pg_extension
408
+ ${scope?.extensionPredicate
409
+ ? ` where ${scope.extensionPredicate}
410
+ `
411
+ : ''}\
412
+ ),
413
+
414
+ indexes as (
415
+ select *
416
+ from pg_catalog.pg_index
417
+ where indrelid in (select classes._id from classes)
418
+ ),
419
+
420
+ inherits as (
421
+ select *
422
+ from pg_catalog.pg_inherits
423
+ where inhrelid in (select classes._id from classes)
424
+ ),
425
+
426
+ languages as (
427
+ select pg_language.oid as _id, *
428
+ from pg_catalog.pg_language
429
+ ${scope?.languagePredicate
430
+ ? ` where ${scope.languagePredicate}
431
+ `
432
+ : ''}\
433
+ ),
434
+
435
+ policies as (
436
+ select *
437
+ from pg_catalog.pg_policy
438
+ where polrelid in (select classes._id from classes)
439
+ ),
440
+
441
+ ranges as (
442
+ select *
443
+ from pg_catalog.pg_range
444
+ where rngtypid in (select types._id from types)
445
+ ),
446
+
447
+ depends as (
448
+ select *
449
+ from pg_catalog.pg_depend
450
+ where deptype IN ('a', 'e') and (
451
+ (classid = 'pg_catalog.pg_namespace'::regclass and objid in (select namespaces._id from namespaces))
452
+ or (classid = 'pg_catalog.pg_class'::regclass and objid in (select classes._id from classes))
453
+ or (classid = 'pg_catalog.pg_attribute'::regclass and objid in (select classes._id from classes) and objsubid > 0)
454
+ or (classid = 'pg_catalog.pg_constraint'::regclass and objid in (select constraints._id from constraints))
455
+ or (classid = 'pg_catalog.pg_proc'::regclass and objid in (select procs._id from procs))
456
+ or (classid = 'pg_catalog.pg_type'::regclass and objid in (select types._id from types))
457
+ or (classid = 'pg_catalog.pg_enum'::regclass and objid in (select enums._id from enums))
458
+ or (classid = 'pg_catalog.pg_extension'::regclass and objid in (select extensions._id from extensions))
459
+ )
460
+ ),
461
+
462
+ descriptions as (
463
+ select *
464
+ from pg_catalog.pg_description
465
+ where (
466
+ (classoid = 'pg_catalog.pg_namespace'::regclass and objoid in (select namespaces._id from namespaces))
467
+ or (classoid = 'pg_catalog.pg_class'::regclass and objoid in (select classes._id from classes))
468
+ or (classoid = 'pg_catalog.pg_attribute'::regclass and objoid in (select classes._id from classes) and objsubid > 0)
469
+ or (classoid = 'pg_catalog.pg_constraint'::regclass and objoid in (select constraints._id from constraints))
470
+ or (classoid = 'pg_catalog.pg_proc'::regclass and objoid in (select procs._id from procs))
471
+ or (classoid = 'pg_catalog.pg_type'::regclass and objoid in (select types._id from types))
472
+ or (classoid = 'pg_catalog.pg_enum'::regclass and objoid in (select enums._id from enums))
473
+ or (classoid = 'pg_catalog.pg_extension'::regclass and objoid in (select extensions._id from extensions))
474
+ )
475
+ ),
476
+
477
+ am as (
478
+ select pg_am.oid as _id, *
479
+ from pg_catalog.pg_am
480
+ where ${scope.accessMethodPredicate}
481
+ )
482
+ select json_build_object(
483
+ 'database',
484
+ (select row_to_json(database) from database),
485
+
486
+ 'namespaces',
487
+ (select coalesce((select json_agg(row_to_json(namespaces) order by nspname) from namespaces), '[]'::json)),
488
+
489
+ 'classes',
490
+ (select coalesce((select json_agg(row_to_json(classes) order by relnamespace, relname) from classes), '[]'::json)),
491
+
492
+ 'attributes',
493
+ (select coalesce((select json_agg(row_to_json(attributes) order by attrelid, attnum) from attributes), '[]'::json)),
494
+
495
+ 'constraints',
496
+ (select coalesce((select json_agg(row_to_json(constraints) order by connamespace, conrelid, conname) from constraints), '[]'::json)),
497
+
498
+ 'procs',
499
+ (select coalesce((select json_agg(row_to_json(procs) order by pronamespace, proname, pg_get_function_identity_arguments(procs._id)) from procs), '[]'::json)),
500
+
501
+ 'roles',
502
+ (select coalesce((select json_agg(row_to_json(roles) order by rolname) from roles), '[]'::json)),
503
+
504
+ 'auth_members',
505
+ (select coalesce((select json_agg(row_to_json(auth_members) order by roleid, member, grantor) from auth_members), '[]'::json)),
506
+
507
+ 'types',
508
+ (select coalesce((select json_agg(row_to_json(types) order by typnamespace, typname) from types), '[]'::json)),
509
+
510
+ 'enums',
511
+ (select coalesce((select json_agg(row_to_json(enums) order by enumtypid, enumsortorder) from enums), '[]'::json)),
512
+
513
+ 'extensions',
514
+ (select coalesce((select json_agg(row_to_json(extensions) order by extname) from extensions), '[]'::json)),
515
+
516
+ 'indexes',
517
+ (select coalesce((select json_agg(row_to_json(indexes) order by indrelid, indexrelid) from indexes), '[]'::json)),
518
+
519
+ 'inherits',
520
+ (select coalesce((select json_agg(row_to_json(inherits) order by inhrelid, inhseqno) from inherits), '[]'::json)),
521
+
522
+ 'languages',
523
+ (select coalesce((select json_agg(row_to_json(languages) order by lanname) from languages), '[]'::json)),
524
+
525
+ 'policies',
526
+ (select coalesce((select json_agg(row_to_json(policies) order by polrelid, polname) from policies), '[]'::json)),
527
+
528
+ 'ranges',
529
+ (select coalesce((select json_agg(row_to_json(ranges) order by rngtypid) from ranges), '[]'::json)),
530
+
531
+ 'depends',
532
+ (select coalesce((select json_agg(row_to_json(depends) order by classid, objid, objsubid, refclassid, refobjid, refobjsubid) from depends), '[]'::json)),
533
+
534
+ 'descriptions',
535
+ (select coalesce((select json_agg(row_to_json(descriptions) order by objoid, classoid, objsubid) from descriptions), '[]'::json)),
536
+
537
+ 'am',
538
+ (select coalesce((select json_agg(row_to_json(am) order by amname) from am), '[]'::json)),
539
+
540
+ 'catalog_by_oid',
541
+ (
542
+ select json_object_agg(oid::text, relname order by relname asc)
543
+ from pg_class
544
+ where relnamespace = (
545
+ select oid
546
+ from pg_namespace
547
+ where nspname = 'pg_catalog'
548
+ )
549
+ and relkind = 'r'
550
+ ),
551
+
552
+ 'current_user',
553
+ current_user,
554
+ 'pg_version',
555
+ version(),
556
+ 'introspection_version',
557
+ 1
558
+ )::text as introspection
559
+ `;
560
+ /**
561
+ * Builds a parameterized introspection plan scoped to the requested schemas
562
+ * and the transitive object dependencies required by their objects.
563
+ */
564
+ export const makeSchemaScopedIntrospectionPlan = (schemas, options = {}) => {
565
+ if (!Array.isArray(schemas) || schemas.length === 0) {
566
+ throw new Error('Schema-scoped introspection requires at least one schema');
567
+ }
568
+ if (options === null ||
569
+ typeof options !== 'object' ||
570
+ Array.isArray(options)) {
571
+ throw new Error('Schema-scoped introspection options must be an object');
572
+ }
573
+ const unsupportedOptions = Object.keys(options).filter((key) => key !== 'catalogTypes' && key !== 'capabilityExtensions');
574
+ if (unsupportedOptions.length > 0) {
575
+ throw new Error(`Unsupported schema-scoped introspection option(s): ${unsupportedOptions.join(', ')}`);
576
+ }
577
+ const catalogTypes = options.catalogTypes ?? 'all';
578
+ if (catalogTypes !== 'all' && catalogTypes !== 'dependency-closure') {
579
+ throw new Error(`Unsupported schema-scoped catalog type policy '${catalogTypes}'`);
580
+ }
581
+ const capabilityExtensions = options.capabilityExtensions ?? [];
582
+ if (!Array.isArray(capabilityExtensions)) {
583
+ throw new Error('Schema-scoped introspection capabilityExtensions must be an array');
584
+ }
585
+ const normalizedCapabilityExtensions = Array.from(new Set(capabilityExtensions.map((extension) => {
586
+ if (typeof extension !== 'string' ||
587
+ extension.length === 0 ||
588
+ extension.trim() !== extension ||
589
+ extension.includes('\0')) {
590
+ throw new Error('Schema-scoped introspection capabilityExtensions must contain exact non-empty extension names');
591
+ }
592
+ return extension;
593
+ })));
594
+ const normalized = Array.from(new Set(schemas.map((schema) => {
595
+ if (typeof schema !== 'string' || schema.length === 0) {
596
+ throw new Error('Schema-scoped introspection schemas must be non-empty strings');
597
+ }
598
+ if (schema.includes('\0')) {
599
+ throw new Error('Schema-scoped introspection schemas must not contain NUL bytes');
600
+ }
601
+ if (schema === 'information_schema' || schema.startsWith('pg_')) {
602
+ throw new Error(`Schema-scoped introspection cannot expose system schema '${schema}'`);
603
+ }
604
+ return schema;
605
+ })));
606
+ const dependencyClosureTypePredicate = "pg_type.oid = any (array(select object_id from object_closure where object_class = 'pg_catalog.pg_type'::regclass))";
607
+ const query = {
608
+ text: buildIntrospectionQuery({
609
+ ctes: SCOPED_CTES,
610
+ namespacePredicate: 'pg_namespace.oid = any (array(select scoped_namespaces._id from scoped_namespaces))',
611
+ classPredicate: "pg_class.oid = any (array(select object_id from object_closure where object_class = 'pg_catalog.pg_class'::regclass))",
612
+ constraintPredicate: "pg_constraint.oid = any (array(select object_id from object_closure where object_class = 'pg_catalog.pg_constraint'::regclass))",
613
+ procPredicate: "pg_proc.oid = any (array(select object_id from object_closure where object_class = 'pg_catalog.pg_proc'::regclass))",
614
+ typePredicate: catalogTypes === 'all'
615
+ ? `${dependencyClosureTypePredicate} or pg_type.typnamespace = 'pg_catalog'::regnamespace`
616
+ : dependencyClosureTypePredicate,
617
+ extensionPredicate: 'pg_extension.oid = any (array(select installed_extensions._id from installed_extensions))',
618
+ languagePredicate: 'true',
619
+ accessMethodPredicate: 'true',
620
+ }),
621
+ values: [normalized, normalizedCapabilityExtensions],
622
+ };
623
+ return {
624
+ query,
625
+ scope: {
626
+ schemas: normalized,
627
+ catalogTypes,
628
+ capabilityExtensions: normalizedCapabilityExtensions,
629
+ },
630
+ };
631
+ };
632
+ /**
633
+ * Builds only the query portion of a schema-scoped introspection plan.
634
+ *
635
+ * Prefer `makeSchemaScopedIntrospectionPlan()` when the results will be
636
+ * parsed and validated by this package.
637
+ */
638
+ export const makeSchemaScopedIntrospectionQuery = (schemas, options = {}) => makeSchemaScopedIntrospectionPlan(schemas, options).query;
639
+ function assertScopedNamespaces(introspection, requiredSchemas) {
640
+ const found = new Set(introspection.namespaces.map((namespace) => namespace.nspname));
641
+ const missing = requiredSchemas.filter((schema) => !found.has(schema));
642
+ if (missing.length > 0) {
643
+ throw new Error(`Schema-scoped introspection did not find required schema(s): ${missing.join(', ')}`);
644
+ }
645
+ }
646
+ function assertDependencyClosureTypes(introspection) {
647
+ const retainedTypeOids = new Set(introspection.types.map((type) => String(type._id)));
648
+ const introspectionLookups = introspection._lookups;
649
+ const requireType = (oid, objectKind, objectContext, field) => {
650
+ if (oid === null || oid === undefined || String(oid) === '0')
651
+ return;
652
+ const normalizedOid = String(oid);
653
+ // pg-introspection removes extension-owned composite resources from its
654
+ // public arrays after lookup hydration; the lookup remains available.
655
+ const resolves = retainedTypeOids.has(normalizedOid) ||
656
+ introspectionLookups?.typeById?.has(normalizedOid) === true;
657
+ if (!resolves) {
658
+ throw new Error(`Dependency-closure introspection retained ${objectKind} '${objectContext}' field '${field}' referencing missing pg_type OID '${normalizedOid}'`);
659
+ }
660
+ };
661
+ const requireTypes = (oids, objectKind, objectContext, field) => {
662
+ for (const oid of oids ?? []) {
663
+ requireType(oid, objectKind, objectContext, field);
664
+ }
665
+ };
666
+ for (const entity of introspection.classes) {
667
+ const context = `${entity.relname} (${entity._id})`;
668
+ requireType(entity.reltype, 'pg_class', context, 'reltype');
669
+ requireType(entity.reloftype, 'pg_class', context, 'reloftype');
670
+ }
671
+ for (const entity of introspection.attributes) {
672
+ requireType(entity.atttypid, 'pg_attribute', `${entity.attrelid}.${entity.attname}`, 'atttypid');
673
+ }
674
+ for (const entity of introspection.constraints) {
675
+ requireType(entity.contypid, 'pg_constraint', `${entity.conname} (${entity._id})`, 'contypid');
676
+ }
677
+ for (const entity of introspection.procs) {
678
+ const context = `${entity.proname} (${entity._id})`;
679
+ requireType(entity.prorettype, 'pg_proc', context, 'prorettype');
680
+ requireTypes(entity.proargtypes, 'pg_proc', context, 'proargtypes');
681
+ requireTypes(entity.proallargtypes, 'pg_proc', context, 'proallargtypes');
682
+ }
683
+ for (const entity of introspection.types) {
684
+ const context = `${entity.typname} (${entity._id})`;
685
+ requireType(entity.typbasetype, 'pg_type', context, 'typbasetype');
686
+ requireType(entity.typelem, 'pg_type', context, 'typelem');
687
+ requireType(entity.typarray, 'pg_type', context, 'typarray');
688
+ }
689
+ for (const entity of introspection.enums) {
690
+ requireType(entity.enumtypid, 'pg_enum', `${entity.enumlabel} (${entity._id})`, 'enumtypid');
691
+ }
692
+ for (const entity of introspection.ranges) {
693
+ const context = `range ${entity.rngtypid ?? 'unknown'}`;
694
+ requireType(entity.rngtypid, 'pg_range', context, 'rngtypid');
695
+ requireType(entity.rngsubtype, 'pg_range', context, 'rngsubtype');
696
+ requireType(entity.rngmultitypid, 'pg_range', context, 'rngmultitypid');
697
+ }
698
+ }
699
+ /** Validates that an introspection result satisfies its scoped query plan. */
700
+ export function validateSchemaScopedIntrospection(introspection, plan) {
701
+ assertScopedNamespaces(introspection, plan.scope.schemas);
702
+ if (plan.scope.catalogTypes === 'dependency-closure') {
703
+ assertDependencyClosureTypes(introspection);
704
+ }
705
+ }