@@ -23,32 +23,11 @@ import {
2323 EXPORTABLE_OBJECT_CLONE ,
2424 gatherConfig ,
2525} from "graphile-build" ;
26- import type { PgProc , PgProcArgument , PgType } from "pg-introspection" ;
26+ import type { PgProc , PgProcArgument } from "pg-introspection" ;
2727
2828import { exportNameHint , forbidRequired } from "../utils.ts" ;
2929import { version } from "../version.ts" ;
3030
31- /**
32- * Given a type OID, walk through domain chains and return the type if
33- * it resolves to a composite type (typtype 'c' with a typrelid), or
34- * null otherwise.
35- */
36- function resolveToCompositeType (
37- typeOid : string ,
38- types : ReadonlyArray < PgType > ,
39- ) : PgType | null {
40- let type = types . find ( ( t ) => t . _id === typeOid ) ;
41- // Follow domain chains (typtype 'd') to reach the underlying type
42- while ( type && type . typtype === "d" && type . typbasetype ) {
43- type = types . find ( ( t ) => t . _id === type ! . typbasetype ! ) ;
44- }
45- // Return only if it resolved to a composite (table) type
46- if ( type && type . typtype === "c" && type . typrelid ) {
47- return type ;
48- }
49- return null ;
50- }
51-
5231declare global {
5332 namespace GraphileBuild {
5433 interface BehaviorStrings {
@@ -151,6 +130,20 @@ export const PgProceduresPlugin: GraphileConfig.Plugin = {
151130 }
152131 const pgNamespace = pgProc . getNamespace ( ) ! ;
153132 const schemaPrefix = this . _schemaPrefix ( { serviceName, pgNamespace } ) ;
133+ // For computed column functions whose name doesn't follow the
134+ // tablename_funcname convention, prefix with the composite type
135+ // name to ensure unique resource names. This allows overloaded
136+ // functions like code(a.pets) and code(a.buildings) to produce
137+ // distinct names (pets_code, buildings_code).
138+ if ( pgProc . provolatile !== "v" ) {
139+ const firstArg = pgProc . getArguments ( ) . find ( ( a ) => a . isIn ) ;
140+ if ( firstArg && firstArg . type . typtype === "c" ) {
141+ const compositePrefix = firstArg . type . typname + "_" ;
142+ if ( ! pgProc . proname . startsWith ( compositePrefix ) ) {
143+ return `${ schemaPrefix } ${ firstArg . type . typname } _${ pgProc . proname } ` ;
144+ }
145+ }
146+ }
154147 return `${ schemaPrefix } ${ pgProc . proname } ` ;
155148 } ,
156149 functionRecordReturnCodecName ( options , details ) {
@@ -235,68 +228,10 @@ export const PgProceduresPlugin: GraphileConfig.Plugin = {
235228 const executor =
236229 info . helpers . pgIntrospection . getExecutorForService ( serviceName ) ;
237230
238- let name = info . inflection . functionResourceName ( {
231+ const name = info . inflection . functionResourceName ( {
239232 serviceName,
240233 pgProc,
241234 } ) ;
242-
243- // When overloaded functions target distinct composite types, each
244- // is allowed through the overload check in pgIntrospection_proc.
245- // However, the resource registry keys by name (see datasource.ts),
246- // so we must ensure each resource has a unique name. We suffix with
247- // the target table's schema and name, for example `code(a.pets)`
248- // becomes `code_a_pets`.
249- // GraphQL field names are unaffected since computedAttributeField
250- // inflection uses resource.extensions.pg.name (the raw SQL name).
251- {
252- const introspection = (
253- await info . helpers . pgIntrospection . getIntrospection ( )
254- ) . find ( ( n ) => n . pgService . name === serviceName ) ! . introspection ;
255- // Check if another proc in the same namespace shares this name
256- let hasOverload = false ;
257- for ( const p of introspection . procs ) {
258- if (
259- p . pronamespace === pgProc . pronamespace &&
260- p . proname === pgProc . proname &&
261- p . _id !== pgProc . _id
262- ) {
263- hasOverload = true ;
264- break ;
265- }
266- }
267- if ( hasOverload ) {
268- // Resolve the first argument to its target table type
269- const firstArgType = pgProc . proargtypes ?. [ 0 ] ;
270- if ( firstArgType ) {
271- const composite = resolveToCompositeType (
272- firstArgType ,
273- introspection . types ,
274- ) ;
275- if ( composite ) {
276- // Look up the pg_class and pg_namespace for the target
277- // table so we can build the suffix
278- let className : string | undefined ;
279- let schemaName : string | undefined ;
280- for ( const c of introspection . classes ) {
281- if ( c . _id === composite . typrelid ) {
282- className = c . relname ;
283- for ( const n of introspection . namespaces ) {
284- if ( n . _id === c . relnamespace ) {
285- schemaName = n . nspname ;
286- break ;
287- }
288- }
289- break ;
290- }
291- }
292- if ( className && schemaName ) {
293- name = `${ name } _${ schemaName } _${ className } ` ;
294- }
295- }
296- }
297- }
298- }
299-
300235 const identifier = `${ serviceName } .${ namespace . nspname } .${
301236 pgProc . proname
302237 } (${ pgProc . getArguments ( ) . map ( argTypeName ) . join ( "," ) } )`;
@@ -685,7 +620,10 @@ export const PgProceduresPlugin: GraphileConfig.Plugin = {
685620 resourceOptionsByPgProcByService : new Map ( ) ,
686621 } ) ,
687622 hooks : {
688- async pgIntrospection_proc ( { helpers, resolvedPreset } , event ) {
623+ async pgIntrospection_proc (
624+ { helpers, resolvedPreset, inflection } ,
625+ event ,
626+ ) {
689627 const { entity : pgProc , serviceName } = event ;
690628
691629 const pgService = resolvedPreset . pgServices ?. find (
@@ -727,65 +665,26 @@ export const PgProceduresPlugin: GraphileConfig.Plugin = {
727665 return ;
728666 }
729667
730- // We don’t want procedures that have been defined in our namespace
731- // twice, as this leads to duplicate fields in the API. However, we
732- // allow overloads where each targets a distinct composite type as its
733- // first argument (i.e. computed columns on different tables), since
734- // each will be exposed on a different GraphQL type.
735- {
736- let hasOverload = false ;
737- for ( const p of introspection . procs ) {
738- if (
739- p . pronamespace === pgProc . pronamespace &&
740- p . proname === pgProc . proname &&
741- p . _id !== pgProc . _id
742- ) {
743- hasOverload = true ;
744- break ;
745- }
746- }
747- if ( hasOverload ) {
748- // This proc has overloads — check if it qualifies as a computed
749- // column on a unique composite type.
750- const thisFirstArgType = pgProc . proargtypes ?. [ 0 ] ;
751- if ( ! thisFirstArgType ) {
752- // No first arg — not a computed column candidate
753- return ;
754- }
755- const thisComposite = resolveToCompositeType (
756- thisFirstArgType ,
757- introspection . types ,
758- ) ;
759- if ( ! thisComposite ) {
760- // First arg doesn’t resolve to a composite type
761- return ;
762- }
763- // Check that no other overload targets the same composite type;
764- // if two overloads both target, they'd produce duplicate fields on
765- // the same GraphQL type so we must skip.
766- for ( const p of introspection . procs ) {
767- if (
768- p . pronamespace === pgProc . pronamespace &&
769- p . proname === pgProc . proname &&
770- p . _id !== pgProc . _id
771- ) {
772- const otherFirstArgType = p . proargtypes ?. [ 0 ] ;
773- if ( ! otherFirstArgType ) continue ;
774- const otherComposite = resolveToCompositeType (
775- otherFirstArgType ,
776- introspection . types ,
777- ) ;
778- if (
779- otherComposite &&
780- otherComposite . _id === thisComposite . _id
781- ) {
782- // Another overload targets the same composite type — skip
783- return ;
784- }
785- }
786- }
787- // This proc targets a unique composite type — allow it through
788- }
668+ // We don’t want procedures whose inflected resource name clashes
669+ // with another overload — this would produce duplicate fields.
670+ // Overloads targeting distinct composite types get unique names
671+ // from the inflector (e.g. pets_code vs buildings_code).
672+ const overload = introspection . procs . find (
673+ ( p ) =>
674+ p . pronamespace === pgProc . pronamespace &&
675+ p . proname === pgProc . proname &&
676+ p . _id !== pgProc . _id &&
677+ inflection . functionResourceName ( {
678+ serviceName,
679+ pgProc : p ,
680+ } ) ===
681+ inflection . functionResourceName ( {
682+ serviceName,
683+ pgProc,
684+ } ) ,
685+ ) ;
686+ if ( overload ) {
687+ return ;
789688 }
790689
791690 await helpers . pgProcedures . getResourceOptions ( serviceName , pgProc ) ;
0 commit comments