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