From 64a1afc7f78998847bbe485b5135b89a4c6d22bd Mon Sep 17 00:00:00 2001 From: avallete Date: Tue, 15 Jul 2025 18:18:26 +0200 Subject: [PATCH 1/4] feat(typegen): add setof function type introspection - Introspect the setof function fields for functions - Restore functions as unions of args + returns --- Dockerfile | 2 +- package.json | 3 +- src/lib/PostgresMetaTypes.ts | 2 +- src/lib/sql/functions.sql | 75 +- src/lib/types.ts | 4 + src/server/app.ts | 9 +- src/server/constants.ts | 9 + src/server/templates/swift.ts | 2 + src/server/templates/typescript.ts | 433 +++-- test/db/00-init.sql | 236 ++- test/lib/functions.ts | 159 ++ test/lib/tables.ts | 18 + test/server/query.ts | 2 + test/server/typegen.ts | 2713 ++++++++++++++++++++++++---- 14 files changed, 3198 insertions(+), 469 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8756b7ac..df79412a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20 as build +FROM node:20 AS build WORKDIR /usr/src/app # Do `npm ci` separately so we can cache `node_modules` # https://nodejs.org/en/docs/guides/nodejs-docker-webapp/ diff --git a/package.json b/package.json index 62315e9f..324e75a0 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "gen:types:go": "PG_META_GENERATE_TYPES=go node --loader ts-node/esm src/server/server.ts", "gen:types:swift": "PG_META_GENERATE_TYPES=swift node --loader ts-node/esm src/server/server.ts", "start": "node dist/server/server.js", - "dev": "trap 'npm run db:clean' INT && run-s db:clean db:run && nodemon --exec node --loader ts-node/esm src/server/server.ts | pino-pretty --colorize", + "dev": "trap 'npm run db:clean' INT && run-s db:clean db:run && run-s dev:code", + "dev:code": "nodemon --exec node --loader ts-node/esm src/server/server.ts | pino-pretty --colorize", "test": "run-s db:clean db:run test:run db:clean", "db:clean": "cd test/db && docker compose down", "db:run": "cd test/db && docker compose up --detach --wait", diff --git a/src/lib/PostgresMetaTypes.ts b/src/lib/PostgresMetaTypes.ts index 35371d55..a3d73fd6 100644 --- a/src/lib/PostgresMetaTypes.ts +++ b/src/lib/PostgresMetaTypes.ts @@ -33,7 +33,7 @@ export default class PostgresMetaTypes { t.typrelid = 0 or ( select - c.relkind ${includeTableTypes ? `in ('c', 'r')` : `= 'c'`} + c.relkind ${includeTableTypes ? `in ('c', 'r', 'v')` : `= 'c'`} from pg_class c where diff --git a/src/lib/sql/functions.sql b/src/lib/sql/functions.sql index d2258402..80ca4177 100644 --- a/src/lib/sql/functions.sql +++ b/src/lib/sql/functions.sql @@ -44,6 +44,25 @@ select pg_get_function_result(f.oid) as return_type, nullif(rt.typrelid::int8, 0) as return_type_relation_id, f.proretset as is_set_returning_function, + case + when f.proretset and rt.typrelid != 0 and exists ( + select 1 from pg_class c + where c.oid = rt.typrelid + -- exclude custom types relation from what is considered a set of table + and c.relkind in ('r', 'p', 'v', 'm', 'f') + ) then true + else false + end as returns_set_of_table, + case + when rt.typrelid != 0 then + (select relname from pg_class where oid = rt.typrelid) + else null + end as return_table_name, + case + when f.proretset then + coalesce(f.prorows, 0) > 1 + else false + end as returns_multiple_rows, case when f.provolatile = 'i' then 'IMMUTABLE' when f.provolatile = 's' then 'STABLE' @@ -76,32 +95,48 @@ from select oid, jsonb_agg(jsonb_build_object( - 'mode', t2.mode, + 'mode', mode, 'name', name, 'type_id', type_id, - 'has_default', has_default + 'has_default', has_default, + 'table_name', table_name )) as args from ( select - oid, - unnest(arg_modes) as mode, - unnest(arg_names) as name, - unnest(arg_types)::int8 as type_id, - unnest(arg_has_defaults) as has_default - from - functions - ) as t1, - lateral ( - select + t1.oid, + t2.mode, + t1.name, + t1.type_id, + t1.has_default, case - when t1.mode = 'i' then 'in' - when t1.mode = 'o' then 'out' - when t1.mode = 'b' then 'inout' - when t1.mode = 'v' then 'variadic' - else 'table' - end as mode - ) as t2 + when pt.typrelid != 0 then pc.relname + else null + end as table_name + from + ( + select + oid, + unnest(arg_modes) as mode, + unnest(arg_names) as name, + unnest(arg_types)::int8 as type_id, + unnest(arg_has_defaults) as has_default + from + functions + ) as t1 + cross join lateral ( + select + case + when t1.mode = 'i' then 'in' + when t1.mode = 'o' then 'out' + when t1.mode = 'b' then 'inout' + when t1.mode = 'v' then 'variadic' + else 'table' + end as mode + ) as t2 + left join pg_type pt on pt.oid = t1.type_id + left join pg_class pc on pc.oid = pt.typrelid + ) sub group by - t1.oid + oid ) f_args on f_args.oid = f.oid diff --git a/src/lib/types.ts b/src/lib/types.ts index bfd60250..4d4c2889 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -148,6 +148,7 @@ const postgresFunctionSchema = Type.Object({ name: Type.String(), type_id: Type.Number(), has_default: Type.Boolean(), + table_name: Type.Union([Type.String(), Type.Null()]), }) ), argument_types: Type.String(), @@ -156,6 +157,9 @@ const postgresFunctionSchema = Type.Object({ return_type: Type.String(), return_type_relation_id: Type.Union([Type.Integer(), Type.Null()]), is_set_returning_function: Type.Boolean(), + returns_set_of_table: Type.Boolean(), + return_table_name: Type.Union([Type.String(), Type.Null()]), + returns_multiple_rows: Type.Boolean(), behavior: Type.Union([ Type.Literal('IMMUTABLE'), Type.Literal('STABLE'), diff --git a/src/server/app.ts b/src/server/app.ts index 8efa733c..9df05341 100644 --- a/src/server/app.ts +++ b/src/server/app.ts @@ -3,14 +3,19 @@ import * as Sentry from '@sentry/node' import cors from '@fastify/cors' import swagger from '@fastify/swagger' import { fastify, FastifyInstance, FastifyServerOptions } from 'fastify' -import { PG_META_REQ_HEADER } from './constants.js' +import { PG_META_REQ_HEADER, MAX_BODY_LIMIT } from './constants.js' import routes from './routes/index.js' import { extractRequestForLogging } from './utils.js' // Pseudo package declared only for this module import pkg from '#package.json' with { type: 'json' } export const build = (opts: FastifyServerOptions = {}): FastifyInstance => { - const app = fastify({ disableRequestLogging: true, requestIdHeader: PG_META_REQ_HEADER, ...opts }) + const app = fastify({ + disableRequestLogging: true, + requestIdHeader: PG_META_REQ_HEADER, + bodyLimit: MAX_BODY_LIMIT, + ...opts, + }) Sentry.setupFastifyErrorHandler(app) app.setErrorHandler((error, request, reply) => { diff --git a/src/server/constants.ts b/src/server/constants.ts index 8bf66417..c64b45e6 100644 --- a/src/server/constants.ts +++ b/src/server/constants.ts @@ -51,12 +51,21 @@ export const GENERATE_TYPES_SWIFT_ACCESS_CONTROL = process.env ? (process.env.PG_META_GENERATE_TYPES_SWIFT_ACCESS_CONTROL as AccessControl) : 'internal' +// json/jsonb/text types +export const VALID_UNNAMED_FUNCTION_ARG_TYPES = new Set([114, 3802, 25]) +export const VALID_FUNCTION_ARGS_MODE = new Set(['in', 'inout', 'variadic']) + export const PG_META_MAX_RESULT_SIZE = process.env.PG_META_MAX_RESULT_SIZE_MB ? // Node-postgres get a maximum size in bytes make the conversion from the env variable // from MB to Bytes parseInt(process.env.PG_META_MAX_RESULT_SIZE_MB, 10) * 1024 * 1024 : 2 * 1024 * 1024 * 1024 // default to 2GB max query size result +export const MAX_BODY_LIMIT = process.env.PG_META_MAX_BODY_LIMIT_MB + ? // Fastify server max body size allowed, is in bytes, convert from MB to Bytes + parseInt(process.env.PG_META_MAX_BODY_LIMIT_MB, 10) * 1024 * 1024 + : 3 * 1024 * 1024 + export const DEFAULT_POOL_CONFIG: PoolConfig = { max: 1, connectionTimeoutMillis: PG_CONN_TIMEOUT_SECS * 1000, diff --git a/src/server/templates/swift.ts b/src/server/templates/swift.ts index e596610e..7bb41207 100644 --- a/src/server/templates/swift.ts +++ b/src/server/templates/swift.ts @@ -309,6 +309,8 @@ const pgTypeToSwiftType = ( swiftType = 'Float' } else if (pgType === 'float8') { swiftType = 'Double' + } else if (['numeric', 'decimal'].includes(pgType)) { + swiftType = 'Decimal' } else if (pgType === 'uuid') { swiftType = 'UUID' } else if ( diff --git a/src/server/templates/typescript.ts b/src/server/templates/typescript.ts index 29c34c1a..cd837bf2 100644 --- a/src/server/templates/typescript.ts +++ b/src/server/templates/typescript.ts @@ -8,7 +8,11 @@ import type { PostgresView, } from '../../lib/index.js' import type { GeneratorMetadata } from '../../lib/generators.js' -import { GENERATE_TYPES_DEFAULT_SCHEMA } from '../constants.js' +import { + GENERATE_TYPES_DEFAULT_SCHEMA, + VALID_FUNCTION_ARGS_MODE, + VALID_UNNAMED_FUNCTION_ARG_TYPES, +} from '../constants.js' export const apply = async ({ schemas, @@ -33,9 +37,108 @@ export const apply = async ({ .filter((c) => c.table_id in columnsByTableId) .sort(({ name: a }, { name: b }) => a.localeCompare(b)) .forEach((c) => columnsByTableId[c.table_id].push(c)) + // group types by id for quicker lookup + const typesById = types.reduce( + (acc, type) => { + acc[type.id] = type + return acc + }, + {} as Record + ) + + const getFunctionTsReturnType = (fn: PostgresFunction, returnType: string) => { + // Determine if this function should have SetofOptions + let setofOptionsInfo = '' + + // Only add SetofOptions for functions with table arguments (embedded functions) + // or specific functions that need RETURNS table-name introspection fixes + if (fn.args.length === 1 && fn.args[0].table_name) { + // Case 1: Standard embedded function with proper setof detection + if (fn.returns_set_of_table && fn.return_table_name) { + setofOptionsInfo = `SetofOptions: { + from: ${JSON.stringify(typesById[fn.args[0].type_id].format)} + to: ${JSON.stringify(fn.return_table_name)} + isOneToOne: ${fn.returns_multiple_rows ? false : true} + isSetofReturn: true + }` + } + // Case 2: Handle RETURNS table-name those are always a one to one relationship + else if (fn.return_table_name && !fn.returns_set_of_table) { + const sourceTable = typesById[fn.args[0].type_id].format + let targetTable = fn.return_table_name + setofOptionsInfo = `SetofOptions: { + from: ${JSON.stringify(sourceTable)} + to: ${JSON.stringify(targetTable)} + isOneToOne: true + isSetofReturn: false + }` + } + } + // Case 3: Special case for functions without table arguments but specific names + else if (fn.return_table_name) { + setofOptionsInfo = `SetofOptions: { + from: "*" + to: ${JSON.stringify(fn.return_table_name)} + isOneToOne: ${fn.returns_multiple_rows ? false : true} + isSetofReturn: ${fn.is_set_returning_function} + }` + } + + return `${returnType}${fn.is_set_returning_function && fn.returns_multiple_rows ? '[]' : ''} + ${setofOptionsInfo ? `${setofOptionsInfo}` : ''}` + } + + const getFunctionReturnType = (schema: PostgresSchema, fn: PostgresFunction): string => { + // Case 1: `returns table`. + const tableArgs = fn.args.filter(({ mode }) => mode === 'table') + if (tableArgs.length > 0) { + const argsNameAndType = tableArgs + .map(({ name, type_id }) => { + const type = typesById[type_id] + let tsType = 'unknown' + if (type) { + tsType = pgTypeToTsType(schema, type.name, { types, schemas, tables, views }) + } + return { name, type: tsType } + }) + .toSorted((a, b) => a.name.localeCompare(b.name)) + + return `{ + ${argsNameAndType.map(({ name, type }) => `${JSON.stringify(name)}: ${type}`)} + }` + } + + // Case 2: returns a relation's row type. + const relation = [...tables, ...views].find(({ id }) => id === fn.return_type_relation_id) + if (relation) { + return `{ + ${columnsByTableId[relation.id] + .toSorted((a, b) => a.name.localeCompare(b.name)) + .map( + (column) => + `${JSON.stringify(column.name)}: ${pgTypeToTsType(schema, column.format, { + types, + schemas, + tables, + views, + })} ${column.is_nullable ? '| null' : ''}` + ) + .sort() + .join(',\n')} + }` + } + + // Case 3: returns base/array/composite/enum type. + const type = typesById[fn.return_type_id] + if (type) { + return pgTypeToTsType(schema, type.name, { types, schemas, tables, views }) + } + + return 'unknown' + } const internal_supabase_schema = postgrestVersion - ? `// Allows to automatically instanciate createClient with right options + ? `// Allows to automatically instantiate createClient with right options // instead of createClient(URL, KEY) __InternalSupabase: { PostgrestVersion: '${postgrestVersion}' @@ -104,19 +207,10 @@ export type Database = { ), ...schemaFunctions .filter((fn) => fn.argument_types === table.name) - .map((fn) => { - const type = types.find(({ id }) => id === fn.return_type_id) - let tsType = 'unknown' - if (type) { - tsType = pgTypeToTsType(schema, type.name, { - types, - schemas, - tables, - views, - }) - } - return `${JSON.stringify(fn.name)}: ${tsType} | null` - }), + .map( + (fn) => + `${JSON.stringify(fn.name)}: ${getFunctionReturnType(schema, fn)} | null` + ), ]} } Insert: { @@ -213,15 +307,23 @@ export type Database = { : schemaViews.map( (view) => `${JSON.stringify(view.name)}: { Row: { - ${columnsByTableId[view.id].map( - (column) => - `${JSON.stringify(column.name)}: ${pgTypeToTsType(schema, column.format, { - types, - schemas, - tables, - views, - })} ${column.is_nullable ? '| null' : ''}` - )} + ${[ + ...columnsByTableId[view.id].map( + (column) => + `${JSON.stringify(column.name)}: ${pgTypeToTsType(schema, column.format, { + types, + schemas, + tables, + views, + })} ${column.is_nullable ? '| null' : ''}` + ), + ...schemaFunctions + .filter((fn) => fn.argument_types === view.name) + .map( + (fn) => + `${JSON.stringify(fn.name)}: ${getFunctionReturnType(schema, fn)} | null` + ), + ]} } ${ 'is_updatable' in view && view.is_updatable @@ -301,107 +403,218 @@ export type Database = { if (schemaFunctions.length === 0) { return '[_ in never]: never' } + const schemaFunctionsGroupedByName = schemaFunctions + .filter((func) => { + // Get all input args (in, inout, variadic modes) + const inArgs = func.args + .toSorted((a, b) => a.name.localeCompare(b.name)) + .filter(({ mode }) => VALID_FUNCTION_ARGS_MODE.has(mode)) + // Case 1: Function has no parameters + if (inArgs.length === 0) { + return true + } - const schemaFunctionsGroupedByName = schemaFunctions.reduce( - (acc, curr) => { - acc[curr.name] ??= [] - acc[curr.name].push(curr) - return acc - }, - {} as Record - ) - - return Object.entries(schemaFunctionsGroupedByName).map( - ([fnName, fns]) => - `${JSON.stringify(fnName)}: { - Args: ${fns - .map(({ args }) => { - const inArgs = args.filter(({ mode }) => mode === 'in') - - if (inArgs.length === 0) { - return 'Record' - } + // Case 2: All input args are named + if (!inArgs.some(({ name }) => name === '')) { + return true + } + + // Case 3: All unnamed args have default values AND are valid types + if ( + inArgs.every((arg) => { + if (arg.name === '') { + return arg.has_default && VALID_UNNAMED_FUNCTION_ARG_TYPES.has(arg.type_id) + } + return true + }) + ) { + return true + } + + // Case 4: Single unnamed parameter of valid type (json, jsonb, text) + // Exclude all functions definitions that have only one single argument unnamed argument that isn't + // a json/jsonb/text as it won't be considered by PostgREST + if ( + inArgs.length === 1 && + inArgs[0].name === '' && + (VALID_UNNAMED_FUNCTION_ARG_TYPES.has(inArgs[0].type_id) || + // OR if the function have a single unnamed args which is another table (embeded function) + (inArgs[0].table_name && func.return_table_name) || + // OR if the function takes a table row but doesn't qualify as embedded (for error reporting) + (inArgs[0].table_name && !func.return_table_name)) + ) { + return true + } + + // NOTE: Functions with named table arguments are generally excluded + // as they're not supported by PostgREST in the expected way + + return false + }) + .reduce( + (acc, curr) => { + acc[curr.name] ??= [] + acc[curr.name].push(curr) + return acc + }, + {} as Record + ) + + return Object.entries(schemaFunctionsGroupedByName).map(([fnName, _fns]) => { + // Check for function overload conflicts + const fns = _fns.toSorted((a, b) => b.definition.localeCompare(a.definition)) + + const functionSignatures = fns.map((fn) => { + const inArgs = fn.args.filter(({ mode }) => VALID_FUNCTION_ARGS_MODE.has(mode)) + + // Special error case for functions that take table row but don't qualify as embedded functions + const hasTableRowError = (fn: PostgresFunction) => { + if ( + inArgs.length === 1 && + inArgs[0].name === '' && + inArgs[0].table_name && + !fn.return_table_name + ) { + return true + } + return false + } + + // Check for generic conflict cases that need error reporting + const getConflictError = (fn: PostgresFunction) => { + const sameFunctions = fns.filter((f) => f.name === fn.name) + if (sameFunctions.length <= 1) return null + + // Generic conflict detection patterns - const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => { - const type = types.find(({ id }) => id === type_id) - let tsType = 'unknown' - if (type) { - tsType = pgTypeToTsType(schema, type.name, { - types, - schemas, - tables, - views, - }) - } - return { name, type: tsType, has_default } + // Pattern 1: No-args vs default-args conflicts + if (inArgs.length === 0) { + const conflictingFns = sameFunctions.filter((otherFn) => { + if (otherFn === fn) return false + const otherInArgs = otherFn.args.filter(({ mode }) => + VALID_FUNCTION_ARGS_MODE.has(mode) + ) + return ( + otherInArgs.length === 1 && + otherInArgs[0].name === '' && + otherInArgs[0].has_default + ) + }) + + if (conflictingFns.length > 0) { + const conflictingFn = conflictingFns[0] + const returnTypeName = + types.find((t) => t.id === conflictingFn.return_type_id)?.name || + 'unknown' + return `Could not choose the best candidate function between: ${schema.name}.${fn.name}(), ${schema.name}.${fn.name}( => ${returnTypeName}). Try renaming the parameters or the function itself in the database so function overloading can be resolved` + } + } + + // Pattern 2: Same parameter name but different types (unresolvable overloads) + if (inArgs.length === 1 && inArgs[0].name !== '') { + const conflictingFns = sameFunctions.filter((otherFn) => { + if (otherFn === fn) return false + const otherInArgs = otherFn.args.filter(({ mode }) => + VALID_FUNCTION_ARGS_MODE.has(mode) + ) + return ( + otherInArgs.length === 1 && + otherInArgs[0].name === inArgs[0].name && + otherInArgs[0].type_id !== inArgs[0].type_id + ) + }) + + if (conflictingFns.length > 0) { + const allConflictingFunctions = [fn, ...conflictingFns] + const conflictList = allConflictingFunctions + .sort((a, b) => { + const aArgs = a.args.filter(({ mode }) => + VALID_FUNCTION_ARGS_MODE.has(mode) + ) + const bArgs = b.args.filter(({ mode }) => + VALID_FUNCTION_ARGS_MODE.has(mode) + ) + return (aArgs[0]?.type_id || 0) - (bArgs[0]?.type_id || 0) }) - return `{ ${argsNameAndType.map(({ name, type, has_default }) => `${JSON.stringify(name)}${has_default ? '?' : ''}: ${type}`)} }` - }) - .toSorted() - // A function can have multiples definitions with differents args, but will always return the same type - .join(' | ')} - Returns: ${(() => { - // Case 1: `returns table`. - const tableArgs = fns[0].args.filter(({ mode }) => mode === 'table') - if (tableArgs.length > 0) { - const argsNameAndType = tableArgs.map(({ name, type_id }) => { - const type = types.find(({ id }) => id === type_id) - let tsType = 'unknown' - if (type) { - tsType = pgTypeToTsType(schema, type.name, { - types, - schemas, - tables, - views, - }) - } - return { name, type: tsType } + .map((f) => { + const args = f.args.filter(({ mode }) => + VALID_FUNCTION_ARGS_MODE.has(mode) + ) + return `${schema.name}.${fn.name}(${args.map((a) => `${a.name || ''} => ${types.find((t) => t.id === a.type_id)?.name || 'unknown'}`).join(', ')})` }) + .join(', ') - return `{ - ${argsNameAndType.map( - ({ name, type }) => `${JSON.stringify(name)}: ${type}` - )} - }` - } + return `Could not choose the best candidate function between: ${conflictList}. Try renaming the parameters or the function itself in the database so function overloading can be resolved` + } + } - // Case 2: returns a relation's row type. - const relation = [...tables, ...views].find( - ({ id }) => id === fns[0].return_type_relation_id - ) - if (relation) { - return `{ - ${columnsByTableId[relation.id].map( - (column) => - `${JSON.stringify(column.name)}: ${pgTypeToTsType( - schema, - column.format, - { - types, - schemas, - tables, - views, - } - )} ${column.is_nullable ? '| null' : ''}` - )} - }` - } + return null + } + + let argsType = 'never' + let returnType = getFunctionReturnType(schema, fn) - // Case 3: returns base/array/composite/enum type. - const type = types.find(({ id }) => id === fns[0].return_type_id) + // Check for specific error cases + const conflictError = getConflictError(fn) + if (conflictError) { + if (inArgs.length > 0) { + const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => { + const type = types.find(({ id }) => id === type_id) + let tsType = 'unknown' + if (type) { + tsType = pgTypeToTsType(schema, type.name, { + types, + schemas, + tables, + views, + }) + } + return { name, type: tsType, has_default } + }) + argsType = `{ ${argsNameAndType.toSorted((a, b) => a.name.localeCompare(b.name)).map(({ name, type, has_default }) => `${JSON.stringify(name)}${has_default ? '?' : ''}: ${type}`)} }` + } + returnType = `{ error: true } & ${JSON.stringify(conflictError)}` + } else if (hasTableRowError(fn)) { + // Special case for computed fields returning scalars functions + if (inArgs.length > 0) { + const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => { + const type = types.find(({ id }) => id === type_id) + let tsType = 'unknown' if (type) { - return pgTypeToTsType(schema, type.name, { + tsType = pgTypeToTsType(schema, type.name, { types, schemas, tables, views, }) } + return { name, type: tsType, has_default } + }) + argsType = `{ ${argsNameAndType.toSorted((a, b) => a.name.localeCompare(b.name)).map(({ name, type, has_default }) => `${JSON.stringify(name)}${has_default ? '?' : ''}: ${type}`)} }` + } + returnType = `{ error: true } & ${JSON.stringify(`the function ${schema.name}.${fn.name} with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache`)}` + } else if (inArgs.length > 0) { + const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => { + const type = types.find(({ id }) => id === type_id) + let tsType = 'unknown' + if (type) { + tsType = pgTypeToTsType(schema, type.name, { + types, + schemas, + tables, + views, + }) + } + return { name, type: tsType, has_default } + }) + argsType = `{ ${argsNameAndType.toSorted((a, b) => a.name.localeCompare(b.name)).map(({ name, type, has_default }) => `${JSON.stringify(name)}${has_default ? '?' : ''}: ${type}`)} }` + } + + return `{ Args: ${argsType}; Returns: ${getFunctionTsReturnType(fn, returnType)} }` + }) - return 'unknown' - })()}${fns[0].is_set_returning_function ? '[]' : ''} - }` - ) + return `${JSON.stringify(fnName)}:\n${functionSignatures.map((sig) => `| ${sig}`).join('\n')}` + }) })()} } Enums: { diff --git a/test/db/00-init.sql b/test/db/00-init.sql index 00c6a472..ae5b8200 100644 --- a/test/db/00-init.sql +++ b/test/db/00-init.sql @@ -8,7 +8,8 @@ CREATE TYPE composite_type_with_array_attribute AS (my_text_array text[]); CREATE TABLE public.users ( id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name text, - status user_status DEFAULT 'ACTIVE' + status user_status DEFAULT 'ACTIVE', + decimal numeric ); INSERT INTO public.users (name) @@ -55,6 +56,17 @@ $$ language plpgsql; CREATE VIEW todos_view AS SELECT * FROM public.todos; -- For testing typegen on view-to-view relationships create view users_view as select * from public.users; +-- Create a more complex view for testing +CREATE VIEW user_todos_summary_view AS +SELECT + u.id as user_id, + u.name as user_name, + u.status as user_status, + COUNT(t.id) as todo_count, + array_agg(t.details) FILTER (WHERE t.details IS NOT NULL) as todo_details +FROM public.users u +LEFT JOIN public.todos t ON t."user-id" = u.id +GROUP BY u.id, u.name, u.status; create materialized view todos_matview as select * from public.todos; @@ -68,6 +80,11 @@ $$ select substring($1.details, 1, 3); $$ language sql stable; +create function public.blurb_varchar(public.todos_view) returns character varying as +$$ +select substring($1.details, 1, 3); +$$ language sql stable; + create function public.details_length(public.todos) returns integer as $$ select length($1.details); @@ -100,6 +117,15 @@ as $$ select * from public.users limit 1; $$; +create or replace function public.function_returning_single_row(todos public.todos) +returns public.users +language sql +stable +as $$ + select * from public.users limit 1; +$$; + + create or replace function public.function_returning_set_of_rows() returns setof public.users language sql @@ -116,6 +142,15 @@ as $$ select id, name from public.users; $$; +create or replace function public.function_returning_table_with_args(user_id int) +returns table (id int, name text) +language sql +stable +as $$ + select id, name from public.users WHERE id = user_id; +$$; + + create or replace function public.polymorphic_function(text) returns void language sql as ''; create or replace function public.polymorphic_function(bool) returns void language sql as ''; @@ -181,3 +216,202 @@ LANGUAGE SQL STABLE AS $$ SELECT * FROM public.todos WHERE "user-id" = todo_row."user-id"; $$; + +-- SETOF composite_type - Returns multiple rows of a custom composite type +CREATE OR REPLACE FUNCTION public.get_composite_type_data() +RETURNS SETOF composite_type_with_array_attribute +LANGUAGE SQL STABLE +AS $$ + SELECT ROW(ARRAY['hello', 'world']::text[])::composite_type_with_array_attribute + UNION ALL + SELECT ROW(ARRAY['foo', 'bar']::text[])::composite_type_with_array_attribute; +$$; + +-- SETOF record - Returns multiple rows with structure defined in the function +CREATE OR REPLACE FUNCTION public.get_user_summary() +RETURNS SETOF record +LANGUAGE SQL STABLE +AS $$ + SELECT u.id, name, count(t.id) as todo_count + FROM public.users u + LEFT JOIN public.todos t ON t."user-id" = u.id + GROUP BY u.id, u.name; +$$; + +-- SETOF scalar_type - Returns multiple values of a basic type +CREATE OR REPLACE FUNCTION public.get_user_ids() +RETURNS SETOF bigint +LANGUAGE SQL STABLE +AS $$ + SELECT id FROM public.users; +$$; + + +-- Function returning view using scalar as input +CREATE OR REPLACE FUNCTION public.get_single_user_summary_from_view(search_user_id bigint) +RETURNS SETOF user_todos_summary_view +LANGUAGE SQL STABLE +ROWS 1 +AS $$ + SELECT * FROM user_todos_summary_view WHERE user_id = search_user_id; +$$; +-- Function returning view using table row as input +CREATE OR REPLACE FUNCTION public.get_single_user_summary_from_view(user_row users) +RETURNS SETOF user_todos_summary_view +LANGUAGE SQL STABLE +ROWS 1 +AS $$ + SELECT * FROM user_todos_summary_view WHERE user_id = user_row.id; +$$; +-- Function returning view using another view row as input +CREATE OR REPLACE FUNCTION public.get_single_user_summary_from_view(userview_row users_view) +RETURNS SETOF user_todos_summary_view +LANGUAGE SQL STABLE +ROWS 1 +AS $$ + SELECT * FROM user_todos_summary_view WHERE user_id = userview_row.id; +$$; + + +-- Function returning view using scalar as input +CREATE OR REPLACE FUNCTION public.get_todos_from_user(search_user_id bigint) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM todos WHERE "user-id" = search_user_id; +$$; +-- Function returning view using table row as input +CREATE OR REPLACE FUNCTION public.get_todos_from_user(user_row users) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM todos WHERE "user-id" = user_row.id; +$$; +-- Function returning view using another view row as input +CREATE OR REPLACE FUNCTION public.get_todos_from_user(userview_row users_view) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM todos WHERE "user-id" = userview_row.id; +$$; + +-- Valid postgresql function override but that produce an unresolvable postgrest function call +create function postgrest_unresolvable_function() returns void language sql as ''; +create function postgrest_unresolvable_function(a text) returns int language sql as 'select 1'; +create function postgrest_unresolvable_function(a int) returns text language sql as $$ + SELECT 'toto' +$$; +-- Valid postgresql function override with differents returns types depending of different arguments +create function postgrest_resolvable_with_override_function() returns void language sql as ''; +create function postgrest_resolvable_with_override_function(a text) returns int language sql as 'select 1'; +create function postgrest_resolvable_with_override_function(b int) returns text language sql as $$ + SELECT 'toto' +$$; +-- Function overrides returning setof tables +create function postgrest_resolvable_with_override_function(user_id bigint) returns setof users language sql stable as $$ + SELECT * FROM users WHERE id = user_id; +$$; +create function postgrest_resolvable_with_override_function(todo_id bigint, completed boolean) returns setof todos language sql stable as $$ + SELECT * FROM todos WHERE id = todo_id AND completed = completed; +$$; +-- Function override taking a table as argument and returning a setof +create function postgrest_resolvable_with_override_function(user_row users) returns setof todos language sql stable as $$ + SELECT * FROM todos WHERE "user-id" = user_row.id; +$$; + +create or replace function public.polymorphic_function_with_different_return(bool) returns int language sql as 'SELECT 1'; +create or replace function public.polymorphic_function_with_different_return(int) returns int language sql as 'SELECT 2'; +create or replace function public.polymorphic_function_with_different_return(text) returns text language sql as $$ SELECT 'foo' $$; + +create or replace function public.polymorphic_function_with_no_params_or_unnamed() returns int language sql as 'SELECT 1'; +create or replace function public.polymorphic_function_with_no_params_or_unnamed(bool) returns int language sql as 'SELECT 2'; +create or replace function public.polymorphic_function_with_no_params_or_unnamed(text) returns text language sql as $$ SELECT 'foo' $$; +-- Function with a single unnamed params that isn't a json/jsonb/text should never appears in the type gen as it won't be in postgrest schema +create or replace function public.polymorphic_function_with_unnamed_integer(int) returns int language sql as 'SELECT 1'; +create or replace function public.polymorphic_function_with_unnamed_json(json) returns int language sql as 'SELECT 1'; +create or replace function public.polymorphic_function_with_unnamed_jsonb(jsonb) returns int language sql as 'SELECT 1'; +create or replace function public.polymorphic_function_with_unnamed_text(text) returns int language sql as 'SELECT 1'; + +-- Functions with unnamed parameters that have default values +create or replace function public.polymorphic_function_with_unnamed_default() returns int language sql as 'SELECT 1'; +create or replace function public.polymorphic_function_with_unnamed_default(int default 42) returns int language sql as 'SELECT 2'; +create or replace function public.polymorphic_function_with_unnamed_default(text default 'default') returns text language sql as $$ SELECT 'foo' $$; + +-- Functions with unnamed parameters that have default values and multiple overloads +create or replace function public.polymorphic_function_with_unnamed_default_overload() returns int language sql as 'SELECT 1'; +create or replace function public.polymorphic_function_with_unnamed_default_overload(int default 42) returns int language sql as 'SELECT 2'; +create or replace function public.polymorphic_function_with_unnamed_default_overload(text default 'default') returns text language sql as $$ SELECT 'foo' $$; +create or replace function public.polymorphic_function_with_unnamed_default_overload(bool default true) returns int language sql as 'SELECT 3'; + +-- Test function with unnamed row parameter returning setof +CREATE OR REPLACE FUNCTION public.test_unnamed_row_setof(todos) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM public.todos WHERE "user-id" = $1."user-id"; +$$; + +CREATE OR REPLACE FUNCTION public.test_unnamed_row_setof(users) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM public.todos WHERE "user-id" = $1."id"; +$$; + + +CREATE OR REPLACE FUNCTION public.test_unnamed_row_setof(user_id bigint) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM public.todos WHERE "user-id" = user_id; +$$; + +-- Test function with unnamed row parameter returning scalar +CREATE OR REPLACE FUNCTION public.test_unnamed_row_scalar(todos) +RETURNS integer +LANGUAGE SQL STABLE +AS $$ + SELECT COUNT(*) FROM public.todos WHERE "user-id" = $1."user-id"; +$$; + +-- Test function with unnamed view row parameter +CREATE OR REPLACE FUNCTION public.test_unnamed_view_row(todos_view) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM public.todos WHERE "user-id" = $1."user-id"; +$$; + +-- Test function with multiple unnamed row parameters +CREATE OR REPLACE FUNCTION public.test_unnamed_multiple_rows(users, todos) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM public.todos + WHERE "user-id" = $1.id + AND id = $2.id; +$$; + +-- Test function with unnamed row parameter returning composite +CREATE OR REPLACE FUNCTION public.test_unnamed_row_composite(users) +RETURNS composite_type_with_array_attribute +LANGUAGE SQL STABLE +AS $$ + SELECT ROW(ARRAY[$1.name])::composite_type_with_array_attribute; +$$; + +-- Function that returns a single element +CREATE OR REPLACE FUNCTION public.function_using_table_returns(user_row users) +RETURNS todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM public.todos WHERE todos."user-id" = user_row.id LIMIT 1; +$$; + +CREATE OR REPLACE FUNCTION public.function_using_setof_rows_one(user_row users) +RETURNS SETOF todos +LANGUAGE SQL STABLE +ROWS 1 +AS $$ + SELECT * FROM public.todos WHERE todos."user-id" = user_row.id LIMIT 1; +$$; diff --git a/test/lib/functions.ts b/test/lib/functions.ts index 05de3244..084f2b8f 100644 --- a/test/lib/functions.ts +++ b/test/lib/functions.ts @@ -12,12 +12,14 @@ test('list', async () => { "has_default": false, "mode": "in", "name": "", + "table_name": null, "type_id": 23, }, { "has_default": false, "mode": "in", "name": "", + "table_name": null, "type_id": 23, }, ], @@ -36,9 +38,12 @@ test('list', async () => { "is_set_returning_function": false, "language": "sql", "name": "add", + "return_table_name": null, "return_type": "integer", "return_type_id": 23, "return_type_relation_id": null, + "returns_multiple_rows": false, + "returns_set_of_table": false, "schema": "public", "security_definer": false, } @@ -46,6 +51,137 @@ test('list', async () => { ) }) +test('list set-returning function with single object limit', async () => { + const res = await pgMeta.functions.list() + expect(res.data?.filter(({ name }) => name === 'get_user_audit_setof_single_row')) + .toMatchInlineSnapshot(` + [ + { + "args": [ + { + "has_default": false, + "mode": "in", + "name": "user_row", + "table_name": "users", + "type_id": 16395, + }, + ], + "argument_types": "user_row users", + "behavior": "STABLE", + "complete_statement": "CREATE OR REPLACE FUNCTION public.get_user_audit_setof_single_row(user_row users) + RETURNS SETOF users_audit + LANGUAGE sql + STABLE ROWS 1 + AS $function$ + SELECT * FROM public.users_audit WHERE user_id = user_row.id; + $function$ + ", + "config_params": null, + "definition": " + SELECT * FROM public.users_audit WHERE user_id = user_row.id; + ", + "id": 16506, + "identity_argument_types": "user_row users", + "is_set_returning_function": true, + "language": "sql", + "name": "get_user_audit_setof_single_row", + "return_table_name": "users_audit", + "return_type": "SETOF users_audit", + "return_type_id": 16418, + "return_type_relation_id": 16416, + "returns_multiple_rows": false, + "returns_set_of_table": true, + "schema": "public", + "security_definer": false, + }, + ] + `) +}) + +test('list set-returning function with multiples definitions', async () => { + const res = await pgMeta.functions.list() + expect(res.data?.filter(({ name }) => name === 'get_todos_setof_rows')).toMatchInlineSnapshot(` + [ + { + "args": [ + { + "has_default": false, + "mode": "in", + "name": "user_row", + "table_name": "users", + "type_id": 16395, + }, + ], + "argument_types": "user_row users", + "behavior": "STABLE", + "complete_statement": "CREATE OR REPLACE FUNCTION public.get_todos_setof_rows(user_row users) + RETURNS SETOF todos + LANGUAGE sql + STABLE + AS $function$ + SELECT * FROM public.todos WHERE "user-id" = user_row.id; + $function$ + ", + "config_params": null, + "definition": " + SELECT * FROM public.todos WHERE "user-id" = user_row.id; + ", + "id": 16507, + "identity_argument_types": "user_row users", + "is_set_returning_function": true, + "language": "sql", + "name": "get_todos_setof_rows", + "return_table_name": "todos", + "return_type": "SETOF todos", + "return_type_id": 16404, + "return_type_relation_id": 16402, + "returns_multiple_rows": true, + "returns_set_of_table": true, + "schema": "public", + "security_definer": false, + }, + { + "args": [ + { + "has_default": false, + "mode": "in", + "name": "todo_row", + "table_name": "todos", + "type_id": 16404, + }, + ], + "argument_types": "todo_row todos", + "behavior": "STABLE", + "complete_statement": "CREATE OR REPLACE FUNCTION public.get_todos_setof_rows(todo_row todos) + RETURNS SETOF todos + LANGUAGE sql + STABLE + AS $function$ + SELECT * FROM public.todos WHERE "user-id" = todo_row."user-id"; + $function$ + ", + "config_params": null, + "definition": " + SELECT * FROM public.todos WHERE "user-id" = todo_row."user-id"; + ", + "id": 16508, + "identity_argument_types": "todo_row todos", + "is_set_returning_function": true, + "language": "sql", + "name": "get_todos_setof_rows", + "return_table_name": "todos", + "return_type": "SETOF todos", + "return_type_id": 16404, + "return_type_relation_id": 16402, + "returns_multiple_rows": true, + "returns_set_of_table": true, + "schema": "public", + "security_definer": false, + }, + ] + `) +}) + test('list functions with included schemas', async () => { let res = await pgMeta.functions.list({ includedSchemas: ['public'], @@ -107,12 +243,14 @@ test('retrieve, create, update, delete', async () => { "has_default": false, "mode": "in", "name": "a", + "table_name": null, "type_id": 21, }, { "has_default": false, "mode": "in", "name": "b", + "table_name": null, "type_id": 21, }, ], @@ -136,9 +274,12 @@ test('retrieve, create, update, delete', async () => { "is_set_returning_function": false, "language": "sql", "name": "test_func", + "return_table_name": null, "return_type": "integer", "return_type_id": 23, "return_type_relation_id": null, + "returns_multiple_rows": false, + "returns_set_of_table": false, "schema": "public", "security_definer": true, }, @@ -157,12 +298,14 @@ test('retrieve, create, update, delete', async () => { "has_default": false, "mode": "in", "name": "a", + "table_name": null, "type_id": 21, }, { "has_default": false, "mode": "in", "name": "b", + "table_name": null, "type_id": 21, }, ], @@ -186,9 +329,12 @@ test('retrieve, create, update, delete', async () => { "is_set_returning_function": false, "language": "sql", "name": "test_func", + "return_table_name": null, "return_type": "integer", "return_type_id": 23, "return_type_relation_id": null, + "returns_multiple_rows": false, + "returns_set_of_table": false, "schema": "public", "security_definer": true, }, @@ -211,12 +357,14 @@ test('retrieve, create, update, delete', async () => { "has_default": false, "mode": "in", "name": "a", + "table_name": null, "type_id": 21, }, { "has_default": false, "mode": "in", "name": "b", + "table_name": null, "type_id": 21, }, ], @@ -240,9 +388,12 @@ test('retrieve, create, update, delete', async () => { "is_set_returning_function": false, "language": "sql", "name": "test_func_renamed", + "return_table_name": null, "return_type": "integer", "return_type_id": 23, "return_type_relation_id": null, + "returns_multiple_rows": false, + "returns_set_of_table": false, "schema": "test_schema", "security_definer": true, }, @@ -261,12 +412,14 @@ test('retrieve, create, update, delete', async () => { "has_default": false, "mode": "in", "name": "a", + "table_name": null, "type_id": 21, }, { "has_default": false, "mode": "in", "name": "b", + "table_name": null, "type_id": 21, }, ], @@ -290,9 +443,12 @@ test('retrieve, create, update, delete', async () => { "is_set_returning_function": false, "language": "sql", "name": "test_func_renamed", + "return_table_name": null, "return_type": "integer", "return_type_id": 23, "return_type_relation_id": null, + "returns_multiple_rows": false, + "returns_set_of_table": false, "schema": "test_schema", "security_definer": true, }, @@ -345,9 +501,12 @@ test('retrieve set-returning function', async () => { "is_set_returning_function": true, "language": "sql", "name": "function_returning_set_of_rows", + "return_table_name": "users", "return_type": "SETOF users", "return_type_id": Any, "return_type_relation_id": Any, + "returns_multiple_rows": true, + "returns_set_of_table": true, "schema": "public", "security_definer": false, } diff --git a/test/lib/tables.ts b/test/lib/tables.ts index c4c934e7..c35546b8 100644 --- a/test/lib/tables.ts +++ b/test/lib/tables.ts @@ -78,6 +78,24 @@ test('list', async () => { "schema": "public", "table": "users", }, + { + "check": null, + "comment": null, + "data_type": "numeric", + "default_value": null, + "enums": [], + "format": "numeric", + "identity_generation": null, + "is_generated": false, + "is_identity": false, + "is_nullable": true, + "is_unique": false, + "is_updatable": true, + "name": "decimal", + "ordinal_position": 4, + "schema": "public", + "table": "users", + }, { "check": null, "comment": null, diff --git a/test/server/query.ts b/test/server/query.ts index 2b4bc2ba..8a9d6076 100644 --- a/test/server/query.ts +++ b/test/server/query.ts @@ -10,11 +10,13 @@ test('query', async () => { expect(res.json()).toMatchInlineSnapshot(` [ { + "decimal": null, "id": 1, "name": "Joe Bloggs", "status": "ACTIVE", }, { + "decimal": null, "id": 2, "name": "Jane Doe", "status": "ACTIVE", diff --git a/test/server/typegen.ts b/test/server/typegen.ts index fa47cbec..bdcddb39 100644 --- a/test/server/typegen.ts +++ b/test/server/typegen.ts @@ -129,6 +129,12 @@ test('typegen: typescript', async () => { details_is_long: boolean | null details_length: number | null details_words: string[] | null + test_unnamed_row_scalar: number | null + test_unnamed_row_setof: { + details: string | null + id: number + "user-id": number + } | null } Insert: { details?: string | null @@ -147,6 +153,12 @@ test('typegen: typescript', async () => { referencedRelation: "a_view" referencedColumns: ["id"] }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, { foreignKeyName: "todos_user-id_fkey" columns: ["user-id"] @@ -193,6 +205,12 @@ test('typegen: typescript', async () => { referencedRelation: "a_view" referencedColumns: ["id"] }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, { foreignKeyName: "user_details_user_id_fkey" columns: ["user_id"] @@ -221,16 +239,27 @@ test('typegen: typescript', async () => { } users: { Row: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null + test_unnamed_row_composite: + | Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"] + | null + test_unnamed_row_setof: { + details: string | null + id: number + "user-id": number + } | null } Insert: { + decimal?: number | null id?: number name?: string | null status?: Database["public"]["Enums"]["user_status"] | null } Update: { + decimal?: number | null id?: number name?: string | null status?: Database["public"]["Enums"]["user_status"] | null @@ -285,6 +314,12 @@ test('typegen: typescript', async () => { referencedRelation: "a_view" referencedColumns: ["id"] }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, { foreignKeyName: "todos_user-id_fkey" columns: ["user-id"] @@ -316,6 +351,12 @@ test('typegen: typescript', async () => { details: string | null id: number | null "user-id": number | null + blurb_varchar: string | null + test_unnamed_view_row: { + details: string | null + id: number + "user-id": number + } | null } Insert: { details?: string | null @@ -334,6 +375,12 @@ test('typegen: typescript', async () => { referencedRelation: "a_view" referencedColumns: ["id"] }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, { foreignKeyName: "todos_user-id_fkey" columns: ["user-id"] @@ -360,18 +407,31 @@ test('typegen: typescript', async () => { }, ] } + user_todos_summary_view: { + Row: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + Relationships: [] + } users_view: { Row: { + decimal: number | null id: number | null name: string | null status: Database["public"]["Enums"]["user_status"] | null } Insert: { + decimal?: number | null id?: number | null name?: string | null status?: Database["public"]["Enums"]["user_status"] | null } Update: { + decimal?: number | null id?: number | null name?: string | null status?: Database["public"]["Enums"]["user_status"] | null @@ -391,57 +451,263 @@ test('typegen: typescript', async () => { Functions: { blurb: { Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: string - } - blurb_varchar: { - Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: string - } + Returns: { + error: true + } & "the function public.blurb with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + blurb_varchar: + | { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.blurb_varchar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + | { + Args: { "": Database["public"]["Views"]["todos_view"]["Row"] } + Returns: { + error: true + } & "the function public.blurb_varchar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } details_is_long: { Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: boolean + Returns: { + error: true + } & "the function public.details_is_long with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" } details_length: { Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: number + Returns: { + error: true + } & "the function public.details_length with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" } details_words: { Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: string[] + Returns: { + error: true + } & "the function public.details_words with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" } function_returning_row: { - Args: Record + Args: never Returns: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null } + SetofOptions: { + from: "*" + to: "users" + isOneToOne: true + isSetofReturn: false + } } function_returning_set_of_rows: { - Args: Record + Args: never Returns: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null }[] + SetofOptions: { + from: "*" + to: "users" + isOneToOne: false + isSetofReturn: true + } + } + function_returning_single_row: { + Args: { todos: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "todos" + to: "users" + isOneToOne: true + isSetofReturn: false + } } function_returning_table: { - Args: Record + Args: never Returns: { id: number name: string }[] } - get_todos_setof_rows: { - Args: - | { todo_row: Database["public"]["Tables"]["todos"]["Row"] } - | { user_row: Database["public"]["Tables"]["users"]["Row"] } + function_returning_table_with_args: { + Args: { user_id: number } + Returns: { + id: number + name: string + }[] + } + function_using_setof_rows_one: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } Returns: { details: string | null id: number "user-id": number - }[] + } + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: true + isSetofReturn: true + } } + function_using_table_returns: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + } + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: true + isSetofReturn: false + } + } + get_composite_type_data: { + Args: never + Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"][] + SetofOptions: { + from: "*" + to: "composite_type_with_array_attribute" + isOneToOne: false + isSetofReturn: true + } + } + get_single_user_summary_from_view: + | { + Args: { + userview_row: Database["public"]["Views"]["users_view"]["Row"] + } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "users_view" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "users" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + | { + Args: { search_user_id: number } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "*" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + get_todos_from_user: + | { + Args: { + userview_row: Database["public"]["Views"]["users_view"]["Row"] + } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users_view" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { search_user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + get_todos_setof_rows: + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { todo_row: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } get_user_audit_setof_single_row: { Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } Returns: { @@ -449,31 +715,193 @@ test('typegen: typescript', async () => { id: number previous_value: Json | null user_id: number | null - }[] - } - polymorphic_function: { - Args: { "": boolean } | { "": string } - Returns: undefined + } + SetofOptions: { + from: "users" + to: "users_audit" + isOneToOne: true + isSetofReturn: true + } } - postgres_fdw_disconnect: { + get_user_ids: { Args: never; Returns: number[] } + get_user_summary: { Args: never; Returns: Record[] } + polymorphic_function: { Args: { "": string }; Returns: undefined } + polymorphic_function_with_different_return: { Args: { "": string } - Returns: boolean + Returns: string + } + polymorphic_function_with_no_params_or_unnamed: + | { Args: never; Returns: number } + | { Args: { "": string }; Returns: string } + polymorphic_function_with_unnamed_default: + | { + Args: never + Returns: { + error: true + } & "Could not choose the best candidate function between: public.polymorphic_function_with_unnamed_default(), public.polymorphic_function_with_unnamed_default( => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { Args: { ""?: string }; Returns: string } + polymorphic_function_with_unnamed_default_overload: + | { + Args: never + Returns: { + error: true + } & "Could not choose the best candidate function between: public.polymorphic_function_with_unnamed_default_overload(), public.polymorphic_function_with_unnamed_default_overload( => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { Args: { ""?: string }; Returns: string } + polymorphic_function_with_unnamed_json: { + Args: { "": Json } + Returns: number } - postgres_fdw_disconnect_all: { - Args: Record - Returns: boolean + polymorphic_function_with_unnamed_jsonb: { + Args: { "": Json } + Returns: number } + polymorphic_function_with_unnamed_text: { + Args: { "": string } + Returns: number + } + postgres_fdw_disconnect: { Args: { "": string }; Returns: boolean } + postgres_fdw_disconnect_all: { Args: never; Returns: boolean } postgres_fdw_get_connections: { - Args: Record + Args: never Returns: Record[] } - postgres_fdw_handler: { - Args: Record - Returns: unknown - } - test_internal_query: { - Args: Record - Returns: undefined + postgres_fdw_handler: { Args: never; Returns: unknown } + postgrest_resolvable_with_override_function: + | { Args: { a: string }; Returns: number } + | { Args: { b: number }; Returns: string } + | { + Args: { user_id: number } + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + }[] + SetofOptions: { + from: "*" + to: "users" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { completed: boolean; todo_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { Args: never; Returns: undefined } + postgrest_unresolvable_function: + | { + Args: { a: string } + Returns: { + error: true + } & "Could not choose the best candidate function between: public.postgrest_unresolvable_function(a => int4), public.postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { + Args: { a: number } + Returns: { + error: true + } & "Could not choose the best candidate function between: public.postgrest_unresolvable_function(a => int4), public.postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { Args: never; Returns: undefined } + test_internal_query: { Args: never; Returns: undefined } + test_unnamed_row_composite: { + Args: { "": Database["public"]["Tables"]["users"]["Row"] } + Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"] + SetofOptions: { + from: "users" + to: "composite_type_with_array_attribute" + isOneToOne: true + isSetofReturn: false + } + } + test_unnamed_row_scalar: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.test_unnamed_row_scalar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + test_unnamed_row_setof: + | { + Args: { user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { "": Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + test_unnamed_view_row: { + Args: { "": Database["public"]["Views"]["todos_view"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos_view" + to: "todos" + isOneToOne: false + isSetofReturn: true + } } } Enums: { @@ -754,6 +1182,12 @@ test('typegen w/ one-to-one relationships', async () => { details_is_long: boolean | null details_length: number | null details_words: string[] | null + test_unnamed_row_scalar: number | null + test_unnamed_row_setof: { + details: string | null + id: number + "user-id": number + } | null } Insert: { details?: string | null @@ -773,6 +1207,13 @@ test('typegen w/ one-to-one relationships', async () => { referencedRelation: "a_view" referencedColumns: ["id"] }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, { foreignKeyName: "todos_user-id_fkey" columns: ["user-id"] @@ -824,6 +1265,13 @@ test('typegen w/ one-to-one relationships', async () => { referencedRelation: "a_view" referencedColumns: ["id"] }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, { foreignKeyName: "user_details_user_id_fkey" columns: ["user_id"] @@ -856,16 +1304,27 @@ test('typegen w/ one-to-one relationships', async () => { } users: { Row: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null + test_unnamed_row_composite: + | Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"] + | null + test_unnamed_row_setof: { + details: string | null + id: number + "user-id": number + } | null } Insert: { + decimal?: number | null id?: number name?: string | null status?: Database["public"]["Enums"]["user_status"] | null } Update: { + decimal?: number | null id?: number name?: string | null status?: Database["public"]["Enums"]["user_status"] | null @@ -921,6 +1380,13 @@ test('typegen w/ one-to-one relationships', async () => { referencedRelation: "a_view" referencedColumns: ["id"] }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, { foreignKeyName: "todos_user-id_fkey" columns: ["user-id"] @@ -956,6 +1422,12 @@ test('typegen w/ one-to-one relationships', async () => { details: string | null id: number | null "user-id": number | null + blurb_varchar: string | null + test_unnamed_view_row: { + details: string | null + id: number + "user-id": number + } | null } Insert: { details?: string | null @@ -975,6 +1447,13 @@ test('typegen w/ one-to-one relationships', async () => { referencedRelation: "a_view" referencedColumns: ["id"] }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, { foreignKeyName: "todos_user-id_fkey" columns: ["user-id"] @@ -1005,18 +1484,31 @@ test('typegen w/ one-to-one relationships', async () => { }, ] } + user_todos_summary_view: { + Row: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + Relationships: [] + } users_view: { Row: { + decimal: number | null id: number | null name: string | null status: Database["public"]["Enums"]["user_status"] | null } Insert: { + decimal?: number | null id?: number | null name?: string | null status?: Database["public"]["Enums"]["user_status"] | null } Update: { + decimal?: number | null id?: number | null name?: string | null status?: Database["public"]["Enums"]["user_status"] | null @@ -1036,57 +1528,263 @@ test('typegen w/ one-to-one relationships', async () => { Functions: { blurb: { Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: string - } - blurb_varchar: { - Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: string - } + Returns: { + error: true + } & "the function public.blurb with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + blurb_varchar: + | { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.blurb_varchar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + | { + Args: { "": Database["public"]["Views"]["todos_view"]["Row"] } + Returns: { + error: true + } & "the function public.blurb_varchar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } details_is_long: { Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: boolean + Returns: { + error: true + } & "the function public.details_is_long with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" } details_length: { Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: number + Returns: { + error: true + } & "the function public.details_length with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" } details_words: { Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: string[] + Returns: { + error: true + } & "the function public.details_words with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" } function_returning_row: { - Args: Record + Args: never Returns: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null } + SetofOptions: { + from: "*" + to: "users" + isOneToOne: true + isSetofReturn: false + } } function_returning_set_of_rows: { - Args: Record + Args: never Returns: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null }[] + SetofOptions: { + from: "*" + to: "users" + isOneToOne: false + isSetofReturn: true + } + } + function_returning_single_row: { + Args: { todos: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "todos" + to: "users" + isOneToOne: true + isSetofReturn: false + } } function_returning_table: { - Args: Record + Args: never Returns: { id: number name: string }[] } - get_todos_setof_rows: { - Args: - | { todo_row: Database["public"]["Tables"]["todos"]["Row"] } - | { user_row: Database["public"]["Tables"]["users"]["Row"] } + function_returning_table_with_args: { + Args: { user_id: number } + Returns: { + id: number + name: string + }[] + } + function_using_setof_rows_one: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } Returns: { details: string | null id: number "user-id": number - }[] + } + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: true + isSetofReturn: true + } } + function_using_table_returns: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + } + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: true + isSetofReturn: false + } + } + get_composite_type_data: { + Args: never + Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"][] + SetofOptions: { + from: "*" + to: "composite_type_with_array_attribute" + isOneToOne: false + isSetofReturn: true + } + } + get_single_user_summary_from_view: + | { + Args: { + userview_row: Database["public"]["Views"]["users_view"]["Row"] + } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "users_view" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "users" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + | { + Args: { search_user_id: number } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "*" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + get_todos_from_user: + | { + Args: { + userview_row: Database["public"]["Views"]["users_view"]["Row"] + } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users_view" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { search_user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + get_todos_setof_rows: + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { todo_row: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } get_user_audit_setof_single_row: { Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } Returns: { @@ -1094,31 +1792,193 @@ test('typegen w/ one-to-one relationships', async () => { id: number previous_value: Json | null user_id: number | null - }[] - } - polymorphic_function: { - Args: { "": boolean } | { "": string } - Returns: undefined + } + SetofOptions: { + from: "users" + to: "users_audit" + isOneToOne: true + isSetofReturn: true + } } - postgres_fdw_disconnect: { + get_user_ids: { Args: never; Returns: number[] } + get_user_summary: { Args: never; Returns: Record[] } + polymorphic_function: { Args: { "": string }; Returns: undefined } + polymorphic_function_with_different_return: { Args: { "": string } - Returns: boolean + Returns: string } - postgres_fdw_disconnect_all: { - Args: Record - Returns: boolean + polymorphic_function_with_no_params_or_unnamed: + | { Args: never; Returns: number } + | { Args: { "": string }; Returns: string } + polymorphic_function_with_unnamed_default: + | { + Args: never + Returns: { + error: true + } & "Could not choose the best candidate function between: public.polymorphic_function_with_unnamed_default(), public.polymorphic_function_with_unnamed_default( => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { Args: { ""?: string }; Returns: string } + polymorphic_function_with_unnamed_default_overload: + | { + Args: never + Returns: { + error: true + } & "Could not choose the best candidate function between: public.polymorphic_function_with_unnamed_default_overload(), public.polymorphic_function_with_unnamed_default_overload( => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { Args: { ""?: string }; Returns: string } + polymorphic_function_with_unnamed_json: { + Args: { "": Json } + Returns: number + } + polymorphic_function_with_unnamed_jsonb: { + Args: { "": Json } + Returns: number } + polymorphic_function_with_unnamed_text: { + Args: { "": string } + Returns: number + } + postgres_fdw_disconnect: { Args: { "": string }; Returns: boolean } + postgres_fdw_disconnect_all: { Args: never; Returns: boolean } postgres_fdw_get_connections: { - Args: Record + Args: never Returns: Record[] } - postgres_fdw_handler: { - Args: Record - Returns: unknown - } - test_internal_query: { - Args: Record - Returns: undefined + postgres_fdw_handler: { Args: never; Returns: unknown } + postgrest_resolvable_with_override_function: + | { Args: { a: string }; Returns: number } + | { Args: { b: number }; Returns: string } + | { + Args: { user_id: number } + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + }[] + SetofOptions: { + from: "*" + to: "users" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { completed: boolean; todo_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { Args: never; Returns: undefined } + postgrest_unresolvable_function: + | { + Args: { a: string } + Returns: { + error: true + } & "Could not choose the best candidate function between: public.postgrest_unresolvable_function(a => int4), public.postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { + Args: { a: number } + Returns: { + error: true + } & "Could not choose the best candidate function between: public.postgrest_unresolvable_function(a => int4), public.postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { Args: never; Returns: undefined } + test_internal_query: { Args: never; Returns: undefined } + test_unnamed_row_composite: { + Args: { "": Database["public"]["Tables"]["users"]["Row"] } + Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"] + SetofOptions: { + from: "users" + to: "composite_type_with_array_attribute" + isOneToOne: true + isSetofReturn: false + } + } + test_unnamed_row_scalar: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.test_unnamed_row_scalar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + test_unnamed_row_setof: + | { + Args: { user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { "": Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + test_unnamed_view_row: { + Args: { "": Database["public"]["Views"]["todos_view"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos_view" + to: "todos" + isOneToOne: false + isSetofReturn: true + } } } Enums: { @@ -1399,6 +2259,12 @@ test('typegen: typescript w/ one-to-one relationships', async () => { details_is_long: boolean | null details_length: number | null details_words: string[] | null + test_unnamed_row_scalar: number | null + test_unnamed_row_setof: { + details: string | null + id: number + "user-id": number + } | null } Insert: { details?: string | null @@ -1418,6 +2284,13 @@ test('typegen: typescript w/ one-to-one relationships', async () => { referencedRelation: "a_view" referencedColumns: ["id"] }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, { foreignKeyName: "todos_user-id_fkey" columns: ["user-id"] @@ -1469,6 +2342,13 @@ test('typegen: typescript w/ one-to-one relationships', async () => { referencedRelation: "a_view" referencedColumns: ["id"] }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, { foreignKeyName: "user_details_user_id_fkey" columns: ["user_id"] @@ -1501,16 +2381,27 @@ test('typegen: typescript w/ one-to-one relationships', async () => { } users: { Row: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null + test_unnamed_row_composite: + | Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"] + | null + test_unnamed_row_setof: { + details: string | null + id: number + "user-id": number + } | null } Insert: { + decimal?: number | null id?: number name?: string | null status?: Database["public"]["Enums"]["user_status"] | null } Update: { + decimal?: number | null id?: number name?: string | null status?: Database["public"]["Enums"]["user_status"] | null @@ -1566,6 +2457,13 @@ test('typegen: typescript w/ one-to-one relationships', async () => { referencedRelation: "a_view" referencedColumns: ["id"] }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, { foreignKeyName: "todos_user-id_fkey" columns: ["user-id"] @@ -1601,6 +2499,12 @@ test('typegen: typescript w/ one-to-one relationships', async () => { details: string | null id: number | null "user-id": number | null + blurb_varchar: string | null + test_unnamed_view_row: { + details: string | null + id: number + "user-id": number + } | null } Insert: { details?: string | null @@ -1620,6 +2524,13 @@ test('typegen: typescript w/ one-to-one relationships', async () => { referencedRelation: "a_view" referencedColumns: ["id"] }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, { foreignKeyName: "todos_user-id_fkey" columns: ["user-id"] @@ -1650,18 +2561,31 @@ test('typegen: typescript w/ one-to-one relationships', async () => { }, ] } + user_todos_summary_view: { + Row: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + Relationships: [] + } users_view: { Row: { + decimal: number | null id: number | null name: string | null status: Database["public"]["Enums"]["user_status"] | null } Insert: { + decimal?: number | null id?: number | null name?: string | null status?: Database["public"]["Enums"]["user_status"] | null } Update: { + decimal?: number | null id?: number | null name?: string | null status?: Database["public"]["Enums"]["user_status"] | null @@ -1681,57 +2605,263 @@ test('typegen: typescript w/ one-to-one relationships', async () => { Functions: { blurb: { Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: string - } - blurb_varchar: { - Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: string - } + Returns: { + error: true + } & "the function public.blurb with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + blurb_varchar: + | { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.blurb_varchar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + | { + Args: { "": Database["public"]["Views"]["todos_view"]["Row"] } + Returns: { + error: true + } & "the function public.blurb_varchar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } details_is_long: { Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: boolean + Returns: { + error: true + } & "the function public.details_is_long with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" } details_length: { Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: number + Returns: { + error: true + } & "the function public.details_length with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" } details_words: { Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: string[] + Returns: { + error: true + } & "the function public.details_words with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" } function_returning_row: { - Args: Record + Args: never Returns: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null } + SetofOptions: { + from: "*" + to: "users" + isOneToOne: true + isSetofReturn: false + } } function_returning_set_of_rows: { - Args: Record + Args: never Returns: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null }[] + SetofOptions: { + from: "*" + to: "users" + isOneToOne: false + isSetofReturn: true + } + } + function_returning_single_row: { + Args: { todos: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "todos" + to: "users" + isOneToOne: true + isSetofReturn: false + } } function_returning_table: { - Args: Record + Args: never Returns: { id: number name: string }[] } - get_todos_setof_rows: { - Args: - | { todo_row: Database["public"]["Tables"]["todos"]["Row"] } - | { user_row: Database["public"]["Tables"]["users"]["Row"] } + function_returning_table_with_args: { + Args: { user_id: number } + Returns: { + id: number + name: string + }[] + } + function_using_setof_rows_one: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } Returns: { details: string | null id: number "user-id": number - }[] + } + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: true + isSetofReturn: true + } } + function_using_table_returns: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + } + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: true + isSetofReturn: false + } + } + get_composite_type_data: { + Args: never + Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"][] + SetofOptions: { + from: "*" + to: "composite_type_with_array_attribute" + isOneToOne: false + isSetofReturn: true + } + } + get_single_user_summary_from_view: + | { + Args: { + userview_row: Database["public"]["Views"]["users_view"]["Row"] + } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "users_view" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "users" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + | { + Args: { search_user_id: number } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "*" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + get_todos_from_user: + | { + Args: { + userview_row: Database["public"]["Views"]["users_view"]["Row"] + } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users_view" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { search_user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + get_todos_setof_rows: + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { todo_row: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } get_user_audit_setof_single_row: { Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } Returns: { @@ -1739,31 +2869,193 @@ test('typegen: typescript w/ one-to-one relationships', async () => { id: number previous_value: Json | null user_id: number | null - }[] - } - polymorphic_function: { - Args: { "": boolean } | { "": string } - Returns: undefined + } + SetofOptions: { + from: "users" + to: "users_audit" + isOneToOne: true + isSetofReturn: true + } } - postgres_fdw_disconnect: { + get_user_ids: { Args: never; Returns: number[] } + get_user_summary: { Args: never; Returns: Record[] } + polymorphic_function: { Args: { "": string }; Returns: undefined } + polymorphic_function_with_different_return: { Args: { "": string } - Returns: boolean + Returns: string + } + polymorphic_function_with_no_params_or_unnamed: + | { Args: never; Returns: number } + | { Args: { "": string }; Returns: string } + polymorphic_function_with_unnamed_default: + | { + Args: never + Returns: { + error: true + } & "Could not choose the best candidate function between: public.polymorphic_function_with_unnamed_default(), public.polymorphic_function_with_unnamed_default( => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { Args: { ""?: string }; Returns: string } + polymorphic_function_with_unnamed_default_overload: + | { + Args: never + Returns: { + error: true + } & "Could not choose the best candidate function between: public.polymorphic_function_with_unnamed_default_overload(), public.polymorphic_function_with_unnamed_default_overload( => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { Args: { ""?: string }; Returns: string } + polymorphic_function_with_unnamed_json: { + Args: { "": Json } + Returns: number } - postgres_fdw_disconnect_all: { - Args: Record - Returns: boolean + polymorphic_function_with_unnamed_jsonb: { + Args: { "": Json } + Returns: number } + polymorphic_function_with_unnamed_text: { + Args: { "": string } + Returns: number + } + postgres_fdw_disconnect: { Args: { "": string }; Returns: boolean } + postgres_fdw_disconnect_all: { Args: never; Returns: boolean } postgres_fdw_get_connections: { - Args: Record + Args: never Returns: Record[] } - postgres_fdw_handler: { - Args: Record - Returns: unknown - } - test_internal_query: { - Args: Record - Returns: undefined + postgres_fdw_handler: { Args: never; Returns: unknown } + postgrest_resolvable_with_override_function: + | { Args: { a: string }; Returns: number } + | { Args: { b: number }; Returns: string } + | { + Args: { user_id: number } + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + }[] + SetofOptions: { + from: "*" + to: "users" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { completed: boolean; todo_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { Args: never; Returns: undefined } + postgrest_unresolvable_function: + | { + Args: { a: string } + Returns: { + error: true + } & "Could not choose the best candidate function between: public.postgrest_unresolvable_function(a => int4), public.postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { + Args: { a: number } + Returns: { + error: true + } & "Could not choose the best candidate function between: public.postgrest_unresolvable_function(a => int4), public.postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { Args: never; Returns: undefined } + test_internal_query: { Args: never; Returns: undefined } + test_unnamed_row_composite: { + Args: { "": Database["public"]["Tables"]["users"]["Row"] } + Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"] + SetofOptions: { + from: "users" + to: "composite_type_with_array_attribute" + isOneToOne: true + isSetofReturn: false + } + } + test_unnamed_row_scalar: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.test_unnamed_row_scalar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + test_unnamed_row_setof: + | { + Args: { user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { "": Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + test_unnamed_view_row: { + Args: { "": Database["public"]["Views"]["todos_view"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos_view" + to: "todos" + isOneToOne: false + isSetofReturn: true + } } } Enums: { @@ -1928,7 +3220,7 @@ test('typegen: typescript w/ postgrestVersion', async () => { | Json[] export type Database = { - // Allows to automatically instanciate createClient with right options + // Allows to automatically instantiate createClient with right options // instead of createClient(URL, KEY) __InternalSupabase: { PostgrestVersion: "13" @@ -2049,6 +3341,12 @@ test('typegen: typescript w/ postgrestVersion', async () => { details_is_long: boolean | null details_length: number | null details_words: string[] | null + test_unnamed_row_scalar: number | null + test_unnamed_row_setof: { + details: string | null + id: number + "user-id": number + } | null } Insert: { details?: string | null @@ -2068,6 +3366,13 @@ test('typegen: typescript w/ postgrestVersion', async () => { referencedRelation: "a_view" referencedColumns: ["id"] }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, { foreignKeyName: "todos_user-id_fkey" columns: ["user-id"] @@ -2119,6 +3424,13 @@ test('typegen: typescript w/ postgrestVersion', async () => { referencedRelation: "a_view" referencedColumns: ["id"] }, + { + foreignKeyName: "user_details_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, { foreignKeyName: "user_details_user_id_fkey" columns: ["user_id"] @@ -2151,16 +3463,27 @@ test('typegen: typescript w/ postgrestVersion', async () => { } users: { Row: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null + test_unnamed_row_composite: + | Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"] + | null + test_unnamed_row_setof: { + details: string | null + id: number + "user-id": number + } | null } Insert: { + decimal?: number | null id?: number name?: string | null status?: Database["public"]["Enums"]["user_status"] | null } Update: { + decimal?: number | null id?: number name?: string | null status?: Database["public"]["Enums"]["user_status"] | null @@ -2216,6 +3539,13 @@ test('typegen: typescript w/ postgrestVersion', async () => { referencedRelation: "a_view" referencedColumns: ["id"] }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, { foreignKeyName: "todos_user-id_fkey" columns: ["user-id"] @@ -2251,6 +3581,12 @@ test('typegen: typescript w/ postgrestVersion', async () => { details: string | null id: number | null "user-id": number | null + blurb_varchar: string | null + test_unnamed_view_row: { + details: string | null + id: number + "user-id": number + } | null } Insert: { details?: string | null @@ -2270,6 +3606,13 @@ test('typegen: typescript w/ postgrestVersion', async () => { referencedRelation: "a_view" referencedColumns: ["id"] }, + { + foreignKeyName: "todos_user-id_fkey" + columns: ["user-id"] + isOneToOne: false + referencedRelation: "user_todos_summary_view" + referencedColumns: ["user_id"] + }, { foreignKeyName: "todos_user-id_fkey" columns: ["user-id"] @@ -2300,18 +3643,31 @@ test('typegen: typescript w/ postgrestVersion', async () => { }, ] } + user_todos_summary_view: { + Row: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + Relationships: [] + } users_view: { Row: { + decimal: number | null id: number | null name: string | null status: Database["public"]["Enums"]["user_status"] | null } Insert: { + decimal?: number | null id?: number | null name?: string | null status?: Database["public"]["Enums"]["user_status"] | null } Update: { + decimal?: number | null id?: number | null name?: string | null status?: Database["public"]["Enums"]["user_status"] | null @@ -2331,57 +3687,263 @@ test('typegen: typescript w/ postgrestVersion', async () => { Functions: { blurb: { Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: string - } - blurb_varchar: { - Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: string - } + Returns: { + error: true + } & "the function public.blurb with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + blurb_varchar: + | { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.blurb_varchar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + | { + Args: { "": Database["public"]["Views"]["todos_view"]["Row"] } + Returns: { + error: true + } & "the function public.blurb_varchar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } details_is_long: { Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: boolean + Returns: { + error: true + } & "the function public.details_is_long with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" } details_length: { Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: number + Returns: { + error: true + } & "the function public.details_length with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" } details_words: { Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: string[] + Returns: { + error: true + } & "the function public.details_words with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" } function_returning_row: { - Args: Record + Args: never Returns: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null } + SetofOptions: { + from: "*" + to: "users" + isOneToOne: true + isSetofReturn: false + } } function_returning_set_of_rows: { - Args: Record + Args: never Returns: { + decimal: number | null id: number name: string | null status: Database["public"]["Enums"]["user_status"] | null }[] + SetofOptions: { + from: "*" + to: "users" + isOneToOne: false + isSetofReturn: true + } + } + function_returning_single_row: { + Args: { todos: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "todos" + to: "users" + isOneToOne: true + isSetofReturn: false + } } function_returning_table: { - Args: Record + Args: never Returns: { id: number name: string }[] } - get_todos_setof_rows: { - Args: - | { todo_row: Database["public"]["Tables"]["todos"]["Row"] } - | { user_row: Database["public"]["Tables"]["users"]["Row"] } + function_returning_table_with_args: { + Args: { user_id: number } + Returns: { + id: number + name: string + }[] + } + function_using_setof_rows_one: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } Returns: { details: string | null id: number "user-id": number - }[] + } + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: true + isSetofReturn: true + } } + function_using_table_returns: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + } + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: true + isSetofReturn: false + } + } + get_composite_type_data: { + Args: never + Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"][] + SetofOptions: { + from: "*" + to: "composite_type_with_array_attribute" + isOneToOne: false + isSetofReturn: true + } + } + get_single_user_summary_from_view: + | { + Args: { + userview_row: Database["public"]["Views"]["users_view"]["Row"] + } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "users_view" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "users" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + | { + Args: { search_user_id: number } + Returns: { + todo_count: number | null + todo_details: string[] | null + user_id: number | null + user_name: string | null + user_status: Database["public"]["Enums"]["user_status"] | null + } + SetofOptions: { + from: "*" + to: "user_todos_summary_view" + isOneToOne: true + isSetofReturn: true + } + } + get_todos_from_user: + | { + Args: { + userview_row: Database["public"]["Views"]["users_view"]["Row"] + } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users_view" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { search_user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + get_todos_setof_rows: + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { todo_row: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } get_user_audit_setof_single_row: { Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } Returns: { @@ -2389,31 +3951,193 @@ test('typegen: typescript w/ postgrestVersion', async () => { id: number previous_value: Json | null user_id: number | null - }[] - } - polymorphic_function: { - Args: { "": boolean } | { "": string } - Returns: undefined + } + SetofOptions: { + from: "users" + to: "users_audit" + isOneToOne: true + isSetofReturn: true + } } - postgres_fdw_disconnect: { + get_user_ids: { Args: never; Returns: number[] } + get_user_summary: { Args: never; Returns: Record[] } + polymorphic_function: { Args: { "": string }; Returns: undefined } + polymorphic_function_with_different_return: { Args: { "": string } - Returns: boolean + Returns: string } - postgres_fdw_disconnect_all: { - Args: Record - Returns: boolean + polymorphic_function_with_no_params_or_unnamed: + | { Args: never; Returns: number } + | { Args: { "": string }; Returns: string } + polymorphic_function_with_unnamed_default: + | { + Args: never + Returns: { + error: true + } & "Could not choose the best candidate function between: public.polymorphic_function_with_unnamed_default(), public.polymorphic_function_with_unnamed_default( => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { Args: { ""?: string }; Returns: string } + polymorphic_function_with_unnamed_default_overload: + | { + Args: never + Returns: { + error: true + } & "Could not choose the best candidate function between: public.polymorphic_function_with_unnamed_default_overload(), public.polymorphic_function_with_unnamed_default_overload( => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { Args: { ""?: string }; Returns: string } + polymorphic_function_with_unnamed_json: { + Args: { "": Json } + Returns: number } + polymorphic_function_with_unnamed_jsonb: { + Args: { "": Json } + Returns: number + } + polymorphic_function_with_unnamed_text: { + Args: { "": string } + Returns: number + } + postgres_fdw_disconnect: { Args: { "": string }; Returns: boolean } + postgres_fdw_disconnect_all: { Args: never; Returns: boolean } postgres_fdw_get_connections: { - Args: Record + Args: never Returns: Record[] } - postgres_fdw_handler: { - Args: Record - Returns: unknown - } - test_internal_query: { - Args: Record - Returns: undefined + postgres_fdw_handler: { Args: never; Returns: unknown } + postgrest_resolvable_with_override_function: + | { Args: { a: string }; Returns: number } + | { Args: { b: number }; Returns: string } + | { + Args: { user_id: number } + Returns: { + decimal: number | null + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + }[] + SetofOptions: { + from: "*" + to: "users" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { completed: boolean; todo_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { Args: never; Returns: undefined } + postgrest_unresolvable_function: + | { + Args: { a: string } + Returns: { + error: true + } & "Could not choose the best candidate function between: public.postgrest_unresolvable_function(a => int4), public.postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { + Args: { a: number } + Returns: { + error: true + } & "Could not choose the best candidate function between: public.postgrest_unresolvable_function(a => int4), public.postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } + | { Args: never; Returns: undefined } + test_internal_query: { Args: never; Returns: undefined } + test_unnamed_row_composite: { + Args: { "": Database["public"]["Tables"]["users"]["Row"] } + Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"] + SetofOptions: { + from: "users" + to: "composite_type_with_array_attribute" + isOneToOne: true + isSetofReturn: false + } + } + test_unnamed_row_scalar: { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + error: true + } & "the function public.test_unnamed_row_scalar with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache" + } + test_unnamed_row_setof: + | { + Args: { user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "*" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + | { + Args: { "": Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + isSetofReturn: true + } + } + test_unnamed_view_row: { + Args: { "": Database["public"]["Views"]["todos_view"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos_view" + to: "todos" + isOneToOne: false + isSetofReturn: true + } } } Enums: { @@ -2561,203 +4285,482 @@ test('typegen: typescript w/ postgrestVersion', async () => { ) }) +test('typegen: typescript consistent types definitions orders', async () => { + // Helper function to clean up test entities + const cleanupTestEntities = async () => { + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + -- Drop materialized views first (depend on views/tables) + DROP MATERIALIZED VIEW IF EXISTS test_matview_alpha CASCADE; + DROP MATERIALIZED VIEW IF EXISTS test_matview_beta CASCADE; + DROP MATERIALIZED VIEW IF EXISTS test_matview_gamma CASCADE; + + -- Drop views (may depend on tables) + DROP VIEW IF EXISTS test_view_alpha CASCADE; + DROP VIEW IF EXISTS test_view_beta CASCADE; + DROP VIEW IF EXISTS test_view_gamma CASCADE; + + -- Drop functions + DROP FUNCTION IF EXISTS test_func_alpha(integer, text, boolean) CASCADE; + DROP FUNCTION IF EXISTS test_func_beta(integer, text, boolean) CASCADE; + DROP FUNCTION IF EXISTS test_func_gamma(integer, text, boolean) CASCADE; + + -- Alternative signatures for functions (different parameter orders) + DROP FUNCTION IF EXISTS test_func_alpha(text, boolean, integer) CASCADE; + DROP FUNCTION IF EXISTS test_func_beta(boolean, integer, text) CASCADE; + DROP FUNCTION IF EXISTS test_func_gamma(boolean, text, integer) CASCADE; + + -- Drop tables + DROP TABLE IF EXISTS test_table_alpha CASCADE; + DROP TABLE IF EXISTS test_table_beta CASCADE; + DROP TABLE IF EXISTS test_table_gamma CASCADE; + + -- Drop types + DROP TYPE IF EXISTS test_enum_alpha CASCADE; + DROP TYPE IF EXISTS test_enum_beta CASCADE; + `, + }, + }) + } + + // Clean up any existing test entities + await cleanupTestEntities() + + // === FIRST ROUND: Create entities in order A->B->G with property order 1 === + + // Create custom types first + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE TYPE test_enum_alpha AS ENUM ('active', 'inactive', 'pending'); + CREATE TYPE test_enum_beta AS ENUM ('high', 'medium', 'low'); + `, + }, + }) + + // Create tables in order: alpha, beta, gamma with specific column orders + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE TABLE test_table_alpha ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + status test_enum_alpha DEFAULT 'active', + created_at TIMESTAMP DEFAULT NOW() + ); + + CREATE TABLE test_table_beta ( + id SERIAL PRIMARY KEY, + priority test_enum_beta DEFAULT 'medium', + description TEXT, + alpha_id INTEGER REFERENCES test_table_alpha(id) + ); + + CREATE TABLE test_table_gamma ( + id SERIAL PRIMARY KEY, + beta_id INTEGER REFERENCES test_table_beta(id), + value NUMERIC(10,2), + is_active BOOLEAN DEFAULT true + ); + `, + }, + }) + + // Create functions in order: alpha, beta, gamma with specific parameter orders + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE FUNCTION test_func_alpha(param_a integer, param_b text, param_c boolean) + RETURNS integer AS 'SELECT param_a + 1' LANGUAGE sql IMMUTABLE; + + CREATE FUNCTION test_func_beta(param_a integer, param_b text, param_c boolean) + RETURNS text AS 'SELECT param_b || ''_processed''' LANGUAGE sql IMMUTABLE; + + CREATE FUNCTION test_func_gamma(param_a integer, param_b text, param_c boolean) + RETURNS boolean AS 'SELECT NOT param_c' LANGUAGE sql IMMUTABLE; + `, + }, + }) + + // Create views in order: alpha, beta, gamma + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE VIEW test_view_alpha AS + SELECT id, name, status, created_at FROM test_table_alpha; + + CREATE VIEW test_view_beta AS + SELECT id, priority, description, alpha_id FROM test_table_beta; + + CREATE VIEW test_view_gamma AS + SELECT id, beta_id, value, is_active FROM test_table_gamma; + `, + }, + }) + + // Create materialized views in order: alpha, beta, gamma + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE MATERIALIZED VIEW test_matview_alpha AS + SELECT id, name, status FROM test_table_alpha; + + CREATE MATERIALIZED VIEW test_matview_beta AS + SELECT id, priority, description FROM test_table_beta; + + CREATE MATERIALIZED VIEW test_matview_gamma AS + SELECT id, value, is_active FROM test_table_gamma; + `, + }, + }) + + // Generate types for first configuration + const { body: firstCall } = await app.inject({ + method: 'GET', + path: '/generators/typescript', + query: { detect_one_to_one_relationships: 'true', postgrest_version: '13' }, + }) + + // === SECOND ROUND: Drop and recreate in reverse order G->B->A with different property orders === + + // Clean up all test entities + await cleanupTestEntities() + + // Create custom types in reverse order but keep the enum internal ordering (typegen is rightfully dependent on the enum order) + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE TYPE test_enum_beta AS ENUM ('high', 'medium', 'low'); + CREATE TYPE test_enum_alpha AS ENUM ('active', 'inactive', 'pending'); + `, + }, + }) + + // Create tables in reverse order: gamma, beta, alpha with different column orders + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE TABLE test_table_alpha ( + created_at TIMESTAMP DEFAULT NOW(), + status test_enum_alpha DEFAULT 'active', + name TEXT NOT NULL, + id SERIAL PRIMARY KEY + ); + + CREATE TABLE test_table_beta ( + alpha_id INTEGER REFERENCES test_table_alpha(id), + description TEXT, + priority test_enum_beta DEFAULT 'medium', + id SERIAL PRIMARY KEY + ); + + CREATE TABLE test_table_gamma ( + is_active BOOLEAN DEFAULT true, + value NUMERIC(10,2), + beta_id INTEGER REFERENCES test_table_beta(id), + id SERIAL PRIMARY KEY + ); + `, + }, + }) + + // Create functions in reverse order: gamma, beta, alpha with different parameter orders + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE FUNCTION test_func_gamma(param_c boolean, param_a integer, param_b text) + RETURNS boolean AS 'SELECT NOT param_c' LANGUAGE sql IMMUTABLE; + + CREATE FUNCTION test_func_beta(param_b text, param_c boolean, param_a integer) + RETURNS text AS 'SELECT param_b || ''_processed''' LANGUAGE sql IMMUTABLE; + + CREATE FUNCTION test_func_alpha(param_c boolean, param_b text, param_a integer) + RETURNS integer AS 'SELECT param_a + 1' LANGUAGE sql IMMUTABLE; + `, + }, + }) + + // Create views in reverse order: gamma, beta, alpha + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE VIEW test_view_gamma AS + SELECT is_active, value, beta_id, id FROM test_table_gamma; + + CREATE VIEW test_view_beta AS + SELECT alpha_id, description, priority, id FROM test_table_beta; + + CREATE VIEW test_view_alpha AS + SELECT created_at, status, name, id FROM test_table_alpha; + `, + }, + }) + + // Create materialized views in reverse order: gamma, beta, alpha + await app.inject({ + method: 'POST', + path: '/query', + payload: { + query: ` + CREATE MATERIALIZED VIEW test_matview_gamma AS + SELECT is_active, value, id FROM test_table_gamma; + + CREATE MATERIALIZED VIEW test_matview_beta AS + SELECT description, priority, id FROM test_table_beta; + + CREATE MATERIALIZED VIEW test_matview_alpha AS + SELECT status, name, id FROM test_table_alpha; + `, + }, + }) + + // Generate types for second configuration + const { body: secondCall } = await app.inject({ + method: 'GET', + path: '/generators/typescript', + query: { detect_one_to_one_relationships: 'true', postgrest_version: '13' }, + }) + + // Clean up test entities + await cleanupTestEntities() + + // The generated types should be identical regardless of: + // 1. Entity creation order (alpha->beta->gamma vs gamma->beta->alpha) + // 2. Property declaration order (columns, function parameters) + // 3. Enum value order + expect(firstCall).toEqual(secondCall) +}) + test('typegen: go', async () => { const { body } = await app.inject({ method: 'GET', path: '/generators/go' }) expect(body).toMatchInlineSnapshot(` "package database -type PublicUsersSelect struct { - Id int64 \`json:"id"\` - Name *string \`json:"name"\` - Status *string \`json:"status"\` -} - -type PublicUsersInsert struct { - Id *int64 \`json:"id"\` - Name *string \`json:"name"\` - Status *string \`json:"status"\` -} - -type PublicUsersUpdate struct { - Id *int64 \`json:"id"\` - Name *string \`json:"name"\` - Status *string \`json:"status"\` -} - -type PublicTodosSelect struct { - Details *string \`json:"details"\` - Id int64 \`json:"id"\` - UserId int64 \`json:"user-id"\` -} - -type PublicTodosInsert struct { - Details *string \`json:"details"\` - Id *int64 \`json:"id"\` - UserId int64 \`json:"user-id"\` -} - -type PublicTodosUpdate struct { - Details *string \`json:"details"\` - Id *int64 \`json:"id"\` - UserId *int64 \`json:"user-id"\` -} - -type PublicUsersAuditSelect struct { - CreatedAt *string \`json:"created_at"\` - Id int64 \`json:"id"\` - PreviousValue interface{} \`json:"previous_value"\` - UserId *int64 \`json:"user_id"\` -} - -type PublicUsersAuditInsert struct { - CreatedAt *string \`json:"created_at"\` - Id *int64 \`json:"id"\` - PreviousValue interface{} \`json:"previous_value"\` - UserId *int64 \`json:"user_id"\` -} - -type PublicUsersAuditUpdate struct { - CreatedAt *string \`json:"created_at"\` - Id *int64 \`json:"id"\` - PreviousValue interface{} \`json:"previous_value"\` - UserId *int64 \`json:"user_id"\` -} - -type PublicUserDetailsSelect struct { - Details *string \`json:"details"\` - UserId int64 \`json:"user_id"\` -} - -type PublicUserDetailsInsert struct { - Details *string \`json:"details"\` - UserId int64 \`json:"user_id"\` -} - -type PublicUserDetailsUpdate struct { - Details *string \`json:"details"\` - UserId *int64 \`json:"user_id"\` -} - -type PublicEmptySelect struct { - -} - -type PublicEmptyInsert struct { - -} - -type PublicEmptyUpdate struct { - -} - -type PublicTableWithOtherTablesRowTypeSelect struct { - Col1 interface{} \`json:"col1"\` - Col2 interface{} \`json:"col2"\` -} - -type PublicTableWithOtherTablesRowTypeInsert struct { - Col1 interface{} \`json:"col1"\` - Col2 interface{} \`json:"col2"\` -} - -type PublicTableWithOtherTablesRowTypeUpdate struct { - Col1 interface{} \`json:"col1"\` - Col2 interface{} \`json:"col2"\` -} - -type PublicTableWithPrimaryKeyOtherThanIdSelect struct { - Name *string \`json:"name"\` - OtherId int64 \`json:"other_id"\` -} - -type PublicTableWithPrimaryKeyOtherThanIdInsert struct { - Name *string \`json:"name"\` - OtherId *int64 \`json:"other_id"\` -} - -type PublicTableWithPrimaryKeyOtherThanIdUpdate struct { - Name *string \`json:"name"\` - OtherId *int64 \`json:"other_id"\` -} - -type PublicCategorySelect struct { - Id int32 \`json:"id"\` - Name string \`json:"name"\` -} - -type PublicCategoryInsert struct { - Id *int32 \`json:"id"\` - Name string \`json:"name"\` -} - -type PublicCategoryUpdate struct { - Id *int32 \`json:"id"\` - Name *string \`json:"name"\` -} - -type PublicMemesSelect struct { - Category *int32 \`json:"category"\` - CreatedAt string \`json:"created_at"\` - Id int32 \`json:"id"\` - Metadata interface{} \`json:"metadata"\` - Name string \`json:"name"\` - Status *string \`json:"status"\` -} - -type PublicMemesInsert struct { - Category *int32 \`json:"category"\` - CreatedAt string \`json:"created_at"\` - Id *int32 \`json:"id"\` - Metadata interface{} \`json:"metadata"\` - Name string \`json:"name"\` - Status *string \`json:"status"\` -} - -type PublicMemesUpdate struct { - Category *int32 \`json:"category"\` - CreatedAt *string \`json:"created_at"\` - Id *int32 \`json:"id"\` - Metadata interface{} \`json:"metadata"\` - Name *string \`json:"name"\` - Status *string \`json:"status"\` -} - -type PublicTodosViewSelect struct { - Details *string \`json:"details"\` - Id *int64 \`json:"id"\` - UserId *int64 \`json:"user-id"\` -} - -type PublicUsersViewSelect struct { - Id *int64 \`json:"id"\` - Name *string \`json:"name"\` - Status *string \`json:"status"\` -} - -type PublicAViewSelect struct { - Id *int64 \`json:"id"\` -} - -type PublicUsersViewWithMultipleRefsToUsersSelect struct { - InitialId *int64 \`json:"initial_id"\` - InitialName *string \`json:"initial_name"\` - SecondId *int64 \`json:"second_id"\` - SecondName *string \`json:"second_name"\` -} - -type PublicTodosMatviewSelect struct { - Details *string \`json:"details"\` - Id *int64 \`json:"id"\` - UserId *int64 \`json:"user-id"\` -} - -type PublicCompositeTypeWithArrayAttribute struct { - MyTextArray interface{} \`json:"my_text_array"\` -} - -type PublicCompositeTypeWithRecordAttribute struct { - Todo interface{} \`json:"todo"\` -}" + type PublicUsersSelect struct { + Decimal *float64 \`json:"decimal"\` + Id int64 \`json:"id"\` + Name *string \`json:"name"\` + Status *string \`json:"status"\` + } + + type PublicUsersInsert struct { + Decimal *float64 \`json:"decimal"\` + Id *int64 \`json:"id"\` + Name *string \`json:"name"\` + Status *string \`json:"status"\` + } + + type PublicUsersUpdate struct { + Decimal *float64 \`json:"decimal"\` + Id *int64 \`json:"id"\` + Name *string \`json:"name"\` + Status *string \`json:"status"\` + } + + type PublicTodosSelect struct { + Details *string \`json:"details"\` + Id int64 \`json:"id"\` + UserId int64 \`json:"user-id"\` + } + + type PublicTodosInsert struct { + Details *string \`json:"details"\` + Id *int64 \`json:"id"\` + UserId int64 \`json:"user-id"\` + } + + type PublicTodosUpdate struct { + Details *string \`json:"details"\` + Id *int64 \`json:"id"\` + UserId *int64 \`json:"user-id"\` + } + + type PublicUsersAuditSelect struct { + CreatedAt *string \`json:"created_at"\` + Id int64 \`json:"id"\` + PreviousValue interface{} \`json:"previous_value"\` + UserId *int64 \`json:"user_id"\` + } + + type PublicUsersAuditInsert struct { + CreatedAt *string \`json:"created_at"\` + Id *int64 \`json:"id"\` + PreviousValue interface{} \`json:"previous_value"\` + UserId *int64 \`json:"user_id"\` + } + + type PublicUsersAuditUpdate struct { + CreatedAt *string \`json:"created_at"\` + Id *int64 \`json:"id"\` + PreviousValue interface{} \`json:"previous_value"\` + UserId *int64 \`json:"user_id"\` + } + + type PublicUserDetailsSelect struct { + Details *string \`json:"details"\` + UserId int64 \`json:"user_id"\` + } + + type PublicUserDetailsInsert struct { + Details *string \`json:"details"\` + UserId int64 \`json:"user_id"\` + } + + type PublicUserDetailsUpdate struct { + Details *string \`json:"details"\` + UserId *int64 \`json:"user_id"\` + } + + type PublicEmptySelect struct { + + } + + type PublicEmptyInsert struct { + + } + + type PublicEmptyUpdate struct { + + } + + type PublicTableWithOtherTablesRowTypeSelect struct { + Col1 interface{} \`json:"col1"\` + Col2 interface{} \`json:"col2"\` + } + + type PublicTableWithOtherTablesRowTypeInsert struct { + Col1 interface{} \`json:"col1"\` + Col2 interface{} \`json:"col2"\` + } + + type PublicTableWithOtherTablesRowTypeUpdate struct { + Col1 interface{} \`json:"col1"\` + Col2 interface{} \`json:"col2"\` + } + + type PublicTableWithPrimaryKeyOtherThanIdSelect struct { + Name *string \`json:"name"\` + OtherId int64 \`json:"other_id"\` + } + + type PublicTableWithPrimaryKeyOtherThanIdInsert struct { + Name *string \`json:"name"\` + OtherId *int64 \`json:"other_id"\` + } + + type PublicTableWithPrimaryKeyOtherThanIdUpdate struct { + Name *string \`json:"name"\` + OtherId *int64 \`json:"other_id"\` + } + + type PublicCategorySelect struct { + Id int32 \`json:"id"\` + Name string \`json:"name"\` + } + + type PublicCategoryInsert struct { + Id *int32 \`json:"id"\` + Name string \`json:"name"\` + } + + type PublicCategoryUpdate struct { + Id *int32 \`json:"id"\` + Name *string \`json:"name"\` + } + + type PublicMemesSelect struct { + Category *int32 \`json:"category"\` + CreatedAt string \`json:"created_at"\` + Id int32 \`json:"id"\` + Metadata interface{} \`json:"metadata"\` + Name string \`json:"name"\` + Status *string \`json:"status"\` + } + + type PublicMemesInsert struct { + Category *int32 \`json:"category"\` + CreatedAt string \`json:"created_at"\` + Id *int32 \`json:"id"\` + Metadata interface{} \`json:"metadata"\` + Name string \`json:"name"\` + Status *string \`json:"status"\` + } + + type PublicMemesUpdate struct { + Category *int32 \`json:"category"\` + CreatedAt *string \`json:"created_at"\` + Id *int32 \`json:"id"\` + Metadata interface{} \`json:"metadata"\` + Name *string \`json:"name"\` + Status *string \`json:"status"\` + } + + type PublicAViewSelect struct { + Id *int64 \`json:"id"\` + } + + type PublicTodosViewSelect struct { + Details *string \`json:"details"\` + Id *int64 \`json:"id"\` + UserId *int64 \`json:"user-id"\` + } + + type PublicUsersViewSelect struct { + Decimal *float64 \`json:"decimal"\` + Id *int64 \`json:"id"\` + Name *string \`json:"name"\` + Status *string \`json:"status"\` + } + + type PublicUserTodosSummaryViewSelect struct { + TodoCount *int64 \`json:"todo_count"\` + TodoDetails []*string \`json:"todo_details"\` + UserId *int64 \`json:"user_id"\` + UserName *string \`json:"user_name"\` + UserStatus *string \`json:"user_status"\` + } + + type PublicUsersViewWithMultipleRefsToUsersSelect struct { + InitialId *int64 \`json:"initial_id"\` + InitialName *string \`json:"initial_name"\` + SecondId *int64 \`json:"second_id"\` + SecondName *string \`json:"second_name"\` + } + + type PublicTodosMatviewSelect struct { + Details *string \`json:"details"\` + Id *int64 \`json:"id"\` + UserId *int64 \`json:"user-id"\` + } + + type PublicCompositeTypeWithArrayAttribute struct { + MyTextArray interface{} \`json:"my_text_array"\` + } + + type PublicCompositeTypeWithRecordAttribute struct { + Todo interface{} \`json:"todo"\` + }" `) }) @@ -2991,30 +4994,36 @@ test('typegen: swift', async () => { } } internal struct UsersSelect: Codable, Hashable, Sendable, Identifiable { + internal let decimal: Decimal? internal let id: Int64 internal let name: String? internal let status: UserStatus? internal enum CodingKeys: String, CodingKey { + case decimal = "decimal" case id = "id" case name = "name" case status = "status" } } internal struct UsersInsert: Codable, Hashable, Sendable, Identifiable { + internal let decimal: Decimal? internal let id: Int64? internal let name: String? internal let status: UserStatus? internal enum CodingKeys: String, CodingKey { + case decimal = "decimal" case id = "id" case name = "name" case status = "status" } } internal struct UsersUpdate: Codable, Hashable, Sendable, Identifiable { + internal let decimal: Decimal? internal let id: Int64? internal let name: String? internal let status: UserStatus? internal enum CodingKeys: String, CodingKey { + case decimal = "decimal" case id = "id" case name = "name" case status = "status" @@ -3082,11 +5091,27 @@ test('typegen: swift', async () => { case userId = "user-id" } } + internal struct UserTodosSummaryViewSelect: Codable, Hashable, Sendable { + internal let todoCount: Int64? + internal let todoDetails: [String]? + internal let userId: Int64? + internal let userName: String? + internal let userStatus: UserStatus? + internal enum CodingKeys: String, CodingKey { + case todoCount = "todo_count" + case todoDetails = "todo_details" + case userId = "user_id" + case userName = "user_name" + case userStatus = "user_status" + } + } internal struct UsersViewSelect: Codable, Hashable, Sendable { + internal let decimal: Decimal? internal let id: Int64? internal let name: String? internal let status: UserStatus? internal enum CodingKeys: String, CodingKey { + case decimal = "decimal" case id = "id" case name = "name" case status = "status" @@ -3354,30 +5379,36 @@ test('typegen: swift w/ public access control', async () => { } } public struct UsersSelect: Codable, Hashable, Sendable, Identifiable { + public let decimal: Decimal? public let id: Int64 public let name: String? public let status: UserStatus? public enum CodingKeys: String, CodingKey { + case decimal = "decimal" case id = "id" case name = "name" case status = "status" } } public struct UsersInsert: Codable, Hashable, Sendable, Identifiable { + public let decimal: Decimal? public let id: Int64? public let name: String? public let status: UserStatus? public enum CodingKeys: String, CodingKey { + case decimal = "decimal" case id = "id" case name = "name" case status = "status" } } public struct UsersUpdate: Codable, Hashable, Sendable, Identifiable { + public let decimal: Decimal? public let id: Int64? public let name: String? public let status: UserStatus? public enum CodingKeys: String, CodingKey { + case decimal = "decimal" case id = "id" case name = "name" case status = "status" @@ -3445,11 +5476,27 @@ test('typegen: swift w/ public access control', async () => { case userId = "user-id" } } + public struct UserTodosSummaryViewSelect: Codable, Hashable, Sendable { + public let todoCount: Int64? + public let todoDetails: [String]? + public let userId: Int64? + public let userName: String? + public let userStatus: UserStatus? + public enum CodingKeys: String, CodingKey { + case todoCount = "todo_count" + case todoDetails = "todo_details" + case userId = "user_id" + case userName = "user_name" + case userStatus = "user_status" + } + } public struct UsersViewSelect: Codable, Hashable, Sendable { + public let decimal: Decimal? public let id: Int64? public let name: String? public let status: UserStatus? public enum CodingKeys: String, CodingKey { + case decimal = "decimal" case id = "id" case name = "name" case status = "status" From 3bb1b45f57cf532037e5f93479fdecc70d6dadf2 Mon Sep 17 00:00:00 2001 From: avallete Date: Wed, 6 Aug 2025 16:06:11 +0200 Subject: [PATCH 2/4] chore: update snapshots --- test/server/typegen.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/server/typegen.ts b/test/server/typegen.ts index bdcddb39..03f93b45 100644 --- a/test/server/typegen.ts +++ b/test/server/typegen.ts @@ -770,7 +770,6 @@ test('typegen: typescript', async () => { postgres_fdw_handler: { Args: never; Returns: unknown } postgrest_resolvable_with_override_function: | { Args: { a: string }; Returns: number } - | { Args: { b: number }; Returns: string } | { Args: { user_id: number } Returns: { @@ -814,6 +813,7 @@ test('typegen: typescript', async () => { isSetofReturn: true } } + | { Args: { b: number }; Returns: string } | { Args: never; Returns: undefined } postgrest_unresolvable_function: | { @@ -1847,7 +1847,6 @@ test('typegen w/ one-to-one relationships', async () => { postgres_fdw_handler: { Args: never; Returns: unknown } postgrest_resolvable_with_override_function: | { Args: { a: string }; Returns: number } - | { Args: { b: number }; Returns: string } | { Args: { user_id: number } Returns: { @@ -1891,6 +1890,7 @@ test('typegen w/ one-to-one relationships', async () => { isSetofReturn: true } } + | { Args: { b: number }; Returns: string } | { Args: never; Returns: undefined } postgrest_unresolvable_function: | { @@ -2924,7 +2924,6 @@ test('typegen: typescript w/ one-to-one relationships', async () => { postgres_fdw_handler: { Args: never; Returns: unknown } postgrest_resolvable_with_override_function: | { Args: { a: string }; Returns: number } - | { Args: { b: number }; Returns: string } | { Args: { user_id: number } Returns: { @@ -2968,6 +2967,7 @@ test('typegen: typescript w/ one-to-one relationships', async () => { isSetofReturn: true } } + | { Args: { b: number }; Returns: string } | { Args: never; Returns: undefined } postgrest_unresolvable_function: | { @@ -4006,7 +4006,6 @@ test('typegen: typescript w/ postgrestVersion', async () => { postgres_fdw_handler: { Args: never; Returns: unknown } postgrest_resolvable_with_override_function: | { Args: { a: string }; Returns: number } - | { Args: { b: number }; Returns: string } | { Args: { user_id: number } Returns: { @@ -4050,6 +4049,7 @@ test('typegen: typescript w/ postgrestVersion', async () => { isSetofReturn: true } } + | { Args: { b: number }; Returns: string } | { Args: never; Returns: undefined } postgrest_unresolvable_function: | { From 6b8c75320f136e78230df83f5f5ee1a63bed53fe Mon Sep 17 00:00:00 2001 From: avallete Date: Wed, 6 Aug 2025 18:20:25 +0200 Subject: [PATCH 3/4] chore: unify sort and dedup loops --- src/server/templates/typescript.ts | 568 +++++++++++++++-------------- 1 file changed, 288 insertions(+), 280 deletions(-) diff --git a/src/server/templates/typescript.ts b/src/server/templates/typescript.ts index cd837bf2..e9254fa6 100644 --- a/src/server/templates/typescript.ts +++ b/src/server/templates/typescript.ts @@ -30,13 +30,110 @@ export const apply = async ({ detectOneToOneRelationships: boolean postgrestVersion?: string }): Promise => { + schemas.sort((a, b) => a.name.localeCompare(b.name)) + const columnsByTableId = Object.fromEntries( [...tables, ...foreignTables, ...views, ...materializedViews].map((t) => [t.id, []]) ) - columns - .filter((c) => c.table_id in columnsByTableId) - .sort(({ name: a }, { name: b }) => a.localeCompare(b)) - .forEach((c) => columnsByTableId[c.table_id].push(c)) + for (const column of columns) { + if (column.table_id in columnsByTableId) { + columnsByTableId[column.table_id].push(column) + } + } + for (const tableId in columnsByTableId) { + columnsByTableId[tableId].sort((a, b) => a.name.localeCompare(b.name)) + } + + const introspectionBySchema = Object.fromEntries<{ + tables: Pick[] + views: PostgresView[] + functions: { fn: PostgresFunction; inArgs: PostgresFunction['args'] }[] + enums: PostgresType[] + compositeTypes: PostgresType[] + }>( + schemas.map((s) => [ + s.name, + { tables: [], views: [], functions: [], enums: [], compositeTypes: [] }, + ]) + ) + for (const table of tables) { + if (table.schema in introspectionBySchema) { + introspectionBySchema[table.schema].tables.push(table) + } + } + for (const table of foreignTables) { + if (table.schema in introspectionBySchema) { + introspectionBySchema[table.schema].tables.push(table) + } + } + for (const view of views) { + if (view.schema in introspectionBySchema) { + introspectionBySchema[view.schema].views.push(view) + } + } + for (const materializedView of materializedViews) { + if (materializedView.schema in introspectionBySchema) { + introspectionBySchema[materializedView.schema].views.push({ + ...materializedView, + is_updatable: false, + }) + } + } + for (const func of functions) { + if (func.schema in introspectionBySchema) { + func.args.sort((a, b) => a.name.localeCompare(b.name)) + // Get all input args (in, inout, variadic modes) + const inArgs = func.args.filter(({ mode }) => VALID_FUNCTION_ARGS_MODE.has(mode)) + + if ( + // Case 1: Function has no parameters + inArgs.length === 0 || + // Case 2: All input args are named + !inArgs.some(({ name }) => name === '') || + // Case 3: All unnamed args have default values AND are valid types + inArgs.every((arg) => { + if (arg.name === '') { + return arg.has_default && VALID_UNNAMED_FUNCTION_ARG_TYPES.has(arg.type_id) + } + return true + }) || + // Case 4: Single unnamed parameter of valid type (json, jsonb, text) + // Exclude all functions definitions that have only one single argument unnamed argument that isn't + // a json/jsonb/text as it won't be considered by PostgREST + (inArgs.length === 1 && + inArgs[0].name === '' && + (VALID_UNNAMED_FUNCTION_ARG_TYPES.has(inArgs[0].type_id) || + // OR if the function have a single unnamed args which is another table (embeded function) + (inArgs[0].table_name && func.return_table_name) || + // OR if the function takes a table row but doesn't qualify as embedded (for error reporting) + (inArgs[0].table_name && !func.return_table_name))) + ) { + func.args.sort((a, b) => a.name.localeCompare(b.name)) + introspectionBySchema[func.schema].functions.push({ fn: func, inArgs }) + } + + // NOTE: Functions with named table arguments are generally excluded + // as they're not supported by PostgREST in the expected way + } + } + for (const type of types) { + if (type.schema in introspectionBySchema) { + if (type.enums.length > 0) { + introspectionBySchema[type.schema].enums.push(type) + } + if (type.attributes.length > 0) { + introspectionBySchema[type.schema].compositeTypes.push(type) + } + } + } + for (const schema in introspectionBySchema) { + introspectionBySchema[schema].tables.sort((a, b) => a.name.localeCompare(b.name)) + introspectionBySchema[schema].views.sort((a, b) => a.name.localeCompare(b.name)) + introspectionBySchema[schema].functions.sort((a, b) => a.fn.name.localeCompare(b.fn.name)) + introspectionBySchema[schema].enums.sort((a, b) => a.name.localeCompare(b.name)) + introspectionBySchema[schema].compositeTypes.sort((a, b) => a.name.localeCompare(b.name)) + } + // group types by id for quicker lookup const typesById = types.reduce( (acc, type) => { @@ -51,7 +148,7 @@ export const apply = async ({ let setofOptionsInfo = '' // Only add SetofOptions for functions with table arguments (embedded functions) - // or specific functions that need RETURNS table-name introspection fixes + // or specific functions that RETURNS table-name if (fn.args.length === 1 && fn.args[0].table_name) { // Case 1: Standard embedded function with proper setof detection if (fn.returns_set_of_table && fn.return_table_name) { @@ -74,7 +171,8 @@ export const apply = async ({ }` } } - // Case 3: Special case for functions without table arguments but specific names + // Case 3: Special case for functions without table arguments still returning a table + // Those can be used in rpc to select sub fields of a table else if (fn.return_table_name) { setofOptionsInfo = `SetofOptions: { from: "*" @@ -92,16 +190,14 @@ export const apply = async ({ // Case 1: `returns table`. const tableArgs = fn.args.filter(({ mode }) => mode === 'table') if (tableArgs.length > 0) { - const argsNameAndType = tableArgs - .map(({ name, type_id }) => { - const type = typesById[type_id] - let tsType = 'unknown' - if (type) { - tsType = pgTypeToTsType(schema, type.name, { types, schemas, tables, views }) - } - return { name, type: tsType } - }) - .toSorted((a, b) => a.name.localeCompare(b.name)) + const argsNameAndType = tableArgs.map(({ name, type_id }) => { + const type = typesById[type_id] + let tsType = 'unknown' + if (type) { + tsType = pgTypeToTsType(schema, type.name, { types, schemas, tables, views }) + } + return { name, type: tsType } + }) return `{ ${argsNameAndType.map(({ name, type }) => `${JSON.stringify(name)}: ${type}`)} @@ -109,11 +205,14 @@ export const apply = async ({ } // Case 2: returns a relation's row type. - const relation = [...tables, ...views].find(({ id }) => id === fn.return_type_relation_id) + const relation = + introspectionBySchema[schema.name]?.tables.find( + ({ id }) => id === fn.return_type_relation_id + ) || + introspectionBySchema[schema.name]?.views.find(({ id }) => id === fn.return_type_relation_id) if (relation) { return `{ ${columnsByTableId[relation.id] - .toSorted((a, b) => a.name.localeCompare(b.name)) .map( (column) => `${JSON.stringify(column.name)}: ${pgTypeToTsType(schema, column.format, { @@ -123,7 +222,6 @@ export const apply = async ({ views, })} ${column.is_nullable ? '| null' : ''}` ) - .sort() .join(',\n')} }` } @@ -136,6 +234,143 @@ export const apply = async ({ return 'unknown' } + // Special error case for functions that take table row but don't qualify as embedded functions + const hasTableRowError = (fn: PostgresFunction, inArgs: PostgresFunction['args']) => { + if ( + inArgs.length === 1 && + inArgs[0].name === '' && + inArgs[0].table_name && + !fn.return_table_name + ) { + return true + } + return false + } + + // Check for generic conflict cases that need error reporting + const getConflictError = ( + schema: PostgresSchema, + fns: Array<{ fn: PostgresFunction; inArgs: PostgresFunction['args'] }>, + fn: PostgresFunction, + inArgs: PostgresFunction['args'] + ) => { + // If there is a single function definition, there is no conflict + if (fns.length <= 1) return null + + // Generic conflict detection patterns + // Pattern 1: No-args vs default-args conflicts + if (inArgs.length === 0) { + const conflictingFns = fns.filter(({ fn: otherFn, inArgs: otherInArgs }) => { + if (otherFn === fn) return false + return otherInArgs.length === 1 && otherInArgs[0].name === '' && otherInArgs[0].has_default + }) + + if (conflictingFns.length > 0) { + const conflictingFn = conflictingFns[0] + const returnTypeName = typesById[conflictingFn.fn.return_type_id]?.name || 'unknown' + return `Could not choose the best candidate function between: ${schema.name}.${fn.name}(), ${schema.name}.${fn.name}( => ${returnTypeName}). Try renaming the parameters or the function itself in the database so function overloading can be resolved` + } + } + + // Pattern 2: Same parameter name but different types (unresolvable overloads) + if (inArgs.length === 1 && inArgs[0].name !== '') { + const conflictingFns = fns.filter(({ fn: otherFn, inArgs: otherInArgs }) => { + if (otherFn === fn) return false + return ( + otherInArgs.length === 1 && + otherInArgs[0].name === inArgs[0].name && + otherInArgs[0].type_id !== inArgs[0].type_id + ) + }) + + if (conflictingFns.length > 0) { + const allConflictingFunctions = [{ fn, inArgs }, ...conflictingFns] + const conflictList = allConflictingFunctions + .sort((a, b) => { + const aArgs = a.inArgs + const bArgs = b.inArgs + return (aArgs[0]?.type_id || 0) - (bArgs[0]?.type_id || 0) + }) + .map((f) => { + const args = f.inArgs + return `${schema.name}.${fn.name}(${args.map((a) => `${a.name || ''} => ${typesById[a.type_id]?.name || 'unknown'}`).join(', ')})` + }) + .join(', ') + + return `Could not choose the best candidate function between: ${conflictList}. Try renaming the parameters or the function itself in the database so function overloading can be resolved` + } + } + + return null + } + + const getFunctionSignatures = ( + schema: PostgresSchema, + fns: Array<{ fn: PostgresFunction; inArgs: PostgresFunction['args'] }> + ) => { + return fns.map(({ fn, inArgs }) => { + let argsType = 'never' + let returnType = getFunctionReturnType(schema, fn) + + // Check for specific error cases + const conflictError = getConflictError(schema, fns, fn, inArgs) + if (conflictError) { + if (inArgs.length > 0) { + const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => { + const type = typesById[type_id] + let tsType = 'unknown' + if (type) { + tsType = pgTypeToTsType(schema, type.name, { + types, + schemas, + tables, + views, + }) + } + return { name, type: tsType, has_default } + }) + argsType = `{ ${argsNameAndType.map(({ name, type, has_default }) => `${JSON.stringify(name)}${has_default ? '?' : ''}: ${type}`)} }` + } + returnType = `{ error: true } & ${JSON.stringify(conflictError)}` + } else if (hasTableRowError(fn, inArgs)) { + // Special case for computed fields returning scalars functions + if (inArgs.length > 0) { + const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => { + const type = typesById[type_id] + let tsType = 'unknown' + if (type) { + tsType = pgTypeToTsType(schema, type.name, { + types, + schemas, + tables, + views, + }) + } + return { name, type: tsType, has_default } + }) + argsType = `{ ${argsNameAndType.map(({ name, type, has_default }) => `${JSON.stringify(name)}${has_default ? '?' : ''}: ${type}`)} }` + } + returnType = `{ error: true } & ${JSON.stringify(`the function ${schema.name}.${fn.name} with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache`)}` + } else if (inArgs.length > 0) { + const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => { + const type = typesById[type_id] + let tsType = 'unknown' + if (type) { + tsType = pgTypeToTsType(schema, type.name, { + types, + schemas, + tables, + views, + }) + } + return { name, type: tsType, has_default } + }) + argsType = `{ ${argsNameAndType.map(({ name, type, has_default }) => `${JSON.stringify(name)}${has_default ? '?' : ''}: ${type}`)} }` + } + + return `{ Args: ${argsType}; Returns: ${getFunctionTsReturnType(fn, returnType)} }` + }) + } const internal_supabase_schema = postgrestVersion ? `// Allows to automatically instantiate createClient with right options @@ -150,44 +385,15 @@ export type Json = string | number | boolean | null | { [key: string]: Json | un export type Database = { ${internal_supabase_schema} - ${schemas - .sort(({ name: a }, { name: b }) => a.localeCompare(b)) - .map((schema) => { - const schemaTables = [...tables, ...foreignTables] - .filter((table) => table.schema === schema.name) - .sort(({ name: a }, { name: b }) => a.localeCompare(b)) - const schemaViews = [...views, ...materializedViews] - .filter((view) => view.schema === schema.name) - .sort(({ name: a }, { name: b }) => a.localeCompare(b)) - const schemaFunctions = functions - .filter((func) => { - if (func.schema !== schema.name) { - return false - } - - // Either: - // 1. All input args are be named, or - // 2. There is only one input arg which is unnamed - const inArgs = func.args.filter(({ mode }) => ['in', 'inout', 'variadic'].includes(mode)) - - if (!inArgs.some(({ name }) => name === '')) { - return true - } - - if (inArgs.length === 1) { - return true - } - - return false - }) - .sort(({ name: a }, { name: b }) => a.localeCompare(b)) - const schemaEnums = types - .filter((type) => type.schema === schema.name && type.enums.length > 0) - .sort(({ name: a }, { name: b }) => a.localeCompare(b)) - const schemaCompositeTypes = types - .filter((type) => type.schema === schema.name && type.attributes.length > 0) - .sort(({ name: a }, { name: b }) => a.localeCompare(b)) - return `${JSON.stringify(schema.name)}: { + ${schemas.map((schema) => { + const { + tables: schemaTables, + views: schemaViews, + functions: schemaFunctions, + enums: schemaEnums, + compositeTypes: schemaCompositeTypes, + } = introspectionBySchema[schema.name] + return `${JSON.stringify(schema.name)}: { Tables: { ${ schemaTables.length === 0 @@ -206,9 +412,9 @@ export type Database = { })} ${column.is_nullable ? '| null' : ''}` ), ...schemaFunctions - .filter((fn) => fn.argument_types === table.name) + .filter(({ fn }) => fn.argument_types === table.name) .map( - (fn) => + ({ fn }) => `${JSON.stringify(fn.name)}: ${getFunctionReturnType(schema, fn)} | null` ), ]} @@ -318,15 +524,15 @@ export type Database = { })} ${column.is_nullable ? '| null' : ''}` ), ...schemaFunctions - .filter((fn) => fn.argument_types === view.name) + .filter(({ fn }) => fn.argument_types === view.name) .map( - (fn) => + ({ fn }) => `${JSON.stringify(fn.name)}: ${getFunctionReturnType(schema, fn)} | null` ), ]} } ${ - 'is_updatable' in view && view.is_updatable + view.is_updatable ? `Insert: { ${columnsByTableId[view.id].map((column) => { let output = JSON.stringify(column.name) @@ -403,216 +609,22 @@ export type Database = { if (schemaFunctions.length === 0) { return '[_ in never]: never' } - const schemaFunctionsGroupedByName = schemaFunctions - .filter((func) => { - // Get all input args (in, inout, variadic modes) - const inArgs = func.args - .toSorted((a, b) => a.name.localeCompare(b.name)) - .filter(({ mode }) => VALID_FUNCTION_ARGS_MODE.has(mode)) - // Case 1: Function has no parameters - if (inArgs.length === 0) { - return true - } - - // Case 2: All input args are named - if (!inArgs.some(({ name }) => name === '')) { - return true - } - - // Case 3: All unnamed args have default values AND are valid types - if ( - inArgs.every((arg) => { - if (arg.name === '') { - return arg.has_default && VALID_UNNAMED_FUNCTION_ARG_TYPES.has(arg.type_id) - } - return true - }) - ) { - return true - } - - // Case 4: Single unnamed parameter of valid type (json, jsonb, text) - // Exclude all functions definitions that have only one single argument unnamed argument that isn't - // a json/jsonb/text as it won't be considered by PostgREST - if ( - inArgs.length === 1 && - inArgs[0].name === '' && - (VALID_UNNAMED_FUNCTION_ARG_TYPES.has(inArgs[0].type_id) || - // OR if the function have a single unnamed args which is another table (embeded function) - (inArgs[0].table_name && func.return_table_name) || - // OR if the function takes a table row but doesn't qualify as embedded (for error reporting) - (inArgs[0].table_name && !func.return_table_name)) - ) { - return true - } - - // NOTE: Functions with named table arguments are generally excluded - // as they're not supported by PostgREST in the expected way - - return false - }) - .reduce( - (acc, curr) => { - acc[curr.name] ??= [] - acc[curr.name].push(curr) - return acc - }, - {} as Record + const schemaFunctionsGroupedByName = schemaFunctions.reduce( + (acc, curr) => { + acc[curr.fn.name] ??= [] + acc[curr.fn.name].push(curr) + return acc + }, + {} as Record + ) + for (const fnName in schemaFunctionsGroupedByName) { + schemaFunctionsGroupedByName[fnName].sort((a, b) => + b.fn.definition.localeCompare(a.fn.definition) ) + } - return Object.entries(schemaFunctionsGroupedByName).map(([fnName, _fns]) => { - // Check for function overload conflicts - const fns = _fns.toSorted((a, b) => b.definition.localeCompare(a.definition)) - - const functionSignatures = fns.map((fn) => { - const inArgs = fn.args.filter(({ mode }) => VALID_FUNCTION_ARGS_MODE.has(mode)) - - // Special error case for functions that take table row but don't qualify as embedded functions - const hasTableRowError = (fn: PostgresFunction) => { - if ( - inArgs.length === 1 && - inArgs[0].name === '' && - inArgs[0].table_name && - !fn.return_table_name - ) { - return true - } - return false - } - - // Check for generic conflict cases that need error reporting - const getConflictError = (fn: PostgresFunction) => { - const sameFunctions = fns.filter((f) => f.name === fn.name) - if (sameFunctions.length <= 1) return null - - // Generic conflict detection patterns - - // Pattern 1: No-args vs default-args conflicts - if (inArgs.length === 0) { - const conflictingFns = sameFunctions.filter((otherFn) => { - if (otherFn === fn) return false - const otherInArgs = otherFn.args.filter(({ mode }) => - VALID_FUNCTION_ARGS_MODE.has(mode) - ) - return ( - otherInArgs.length === 1 && - otherInArgs[0].name === '' && - otherInArgs[0].has_default - ) - }) - - if (conflictingFns.length > 0) { - const conflictingFn = conflictingFns[0] - const returnTypeName = - types.find((t) => t.id === conflictingFn.return_type_id)?.name || - 'unknown' - return `Could not choose the best candidate function between: ${schema.name}.${fn.name}(), ${schema.name}.${fn.name}( => ${returnTypeName}). Try renaming the parameters or the function itself in the database so function overloading can be resolved` - } - } - - // Pattern 2: Same parameter name but different types (unresolvable overloads) - if (inArgs.length === 1 && inArgs[0].name !== '') { - const conflictingFns = sameFunctions.filter((otherFn) => { - if (otherFn === fn) return false - const otherInArgs = otherFn.args.filter(({ mode }) => - VALID_FUNCTION_ARGS_MODE.has(mode) - ) - return ( - otherInArgs.length === 1 && - otherInArgs[0].name === inArgs[0].name && - otherInArgs[0].type_id !== inArgs[0].type_id - ) - }) - - if (conflictingFns.length > 0) { - const allConflictingFunctions = [fn, ...conflictingFns] - const conflictList = allConflictingFunctions - .sort((a, b) => { - const aArgs = a.args.filter(({ mode }) => - VALID_FUNCTION_ARGS_MODE.has(mode) - ) - const bArgs = b.args.filter(({ mode }) => - VALID_FUNCTION_ARGS_MODE.has(mode) - ) - return (aArgs[0]?.type_id || 0) - (bArgs[0]?.type_id || 0) - }) - .map((f) => { - const args = f.args.filter(({ mode }) => - VALID_FUNCTION_ARGS_MODE.has(mode) - ) - return `${schema.name}.${fn.name}(${args.map((a) => `${a.name || ''} => ${types.find((t) => t.id === a.type_id)?.name || 'unknown'}`).join(', ')})` - }) - .join(', ') - - return `Could not choose the best candidate function between: ${conflictList}. Try renaming the parameters or the function itself in the database so function overloading can be resolved` - } - } - - return null - } - - let argsType = 'never' - let returnType = getFunctionReturnType(schema, fn) - - // Check for specific error cases - const conflictError = getConflictError(fn) - if (conflictError) { - if (inArgs.length > 0) { - const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => { - const type = types.find(({ id }) => id === type_id) - let tsType = 'unknown' - if (type) { - tsType = pgTypeToTsType(schema, type.name, { - types, - schemas, - tables, - views, - }) - } - return { name, type: tsType, has_default } - }) - argsType = `{ ${argsNameAndType.toSorted((a, b) => a.name.localeCompare(b.name)).map(({ name, type, has_default }) => `${JSON.stringify(name)}${has_default ? '?' : ''}: ${type}`)} }` - } - returnType = `{ error: true } & ${JSON.stringify(conflictError)}` - } else if (hasTableRowError(fn)) { - // Special case for computed fields returning scalars functions - if (inArgs.length > 0) { - const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => { - const type = types.find(({ id }) => id === type_id) - let tsType = 'unknown' - if (type) { - tsType = pgTypeToTsType(schema, type.name, { - types, - schemas, - tables, - views, - }) - } - return { name, type: tsType, has_default } - }) - argsType = `{ ${argsNameAndType.toSorted((a, b) => a.name.localeCompare(b.name)).map(({ name, type, has_default }) => `${JSON.stringify(name)}${has_default ? '?' : ''}: ${type}`)} }` - } - returnType = `{ error: true } & ${JSON.stringify(`the function ${schema.name}.${fn.name} with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache`)}` - } else if (inArgs.length > 0) { - const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => { - const type = types.find(({ id }) => id === type_id) - let tsType = 'unknown' - if (type) { - tsType = pgTypeToTsType(schema, type.name, { - types, - schemas, - tables, - views, - }) - } - return { name, type: tsType, has_default } - }) - argsType = `{ ${argsNameAndType.toSorted((a, b) => a.name.localeCompare(b.name)).map(({ name, type, has_default }) => `${JSON.stringify(name)}${has_default ? '?' : ''}: ${type}`)} }` - } - - return `{ Args: ${argsType}; Returns: ${getFunctionTsReturnType(fn, returnType)} }` - }) - + return Object.entries(schemaFunctionsGroupedByName).map(([fnName, fns]) => { + const functionSignatures = getFunctionSignatures(schema, fns) return `${JSON.stringify(fnName)}:\n${functionSignatures.map((sig) => `| ${sig}`).join('\n')}` }) })()} @@ -637,7 +649,7 @@ export type Database = { ({ name, attributes }) => `${JSON.stringify(name)}: { ${attributes.map(({ name, type_id }) => { - const type = types.find(({ id }) => id === type_id) + const type = typesById[type_id] let tsType = 'unknown' if (type) { tsType = `${pgTypeToTsType(schema, type.name, { @@ -654,7 +666,7 @@ export type Database = { } } }` - })} + })} } type DatabaseWithoutInternals = Omit @@ -763,13 +775,9 @@ export type CompositeTypes< : never export const Constants = { - ${schemas - .sort(({ name: a }, { name: b }) => a.localeCompare(b)) - .map((schema) => { - const schemaEnums = types - .filter((type) => type.schema === schema.name && type.enums.length > 0) - .sort(({ name: a }, { name: b }) => a.localeCompare(b)) - return `${JSON.stringify(schema.name)}: { + ${schemas.map((schema) => { + const schemaEnums = introspectionBySchema[schema.name]?.enums ?? [] + return `${JSON.stringify(schema.name)}: { Enums: { ${schemaEnums.map( (enum_) => @@ -779,7 +787,7 @@ export const Constants = { )} } }` - })} + })} } as const ` From c22c83065da99c157a8d7e5e9456af75f4c54631 Mon Sep 17 00:00:00 2001 From: avallete Date: Wed, 6 Aug 2025 18:27:30 +0200 Subject: [PATCH 4/4] chore: remove duplicate sort --- src/server/templates/typescript.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server/templates/typescript.ts b/src/server/templates/typescript.ts index e9254fa6..c375f45f 100644 --- a/src/server/templates/typescript.ts +++ b/src/server/templates/typescript.ts @@ -108,7 +108,6 @@ export const apply = async ({ // OR if the function takes a table row but doesn't qualify as embedded (for error reporting) (inArgs[0].table_name && !func.return_table_name))) ) { - func.args.sort((a, b) => a.name.localeCompare(b.name)) introspectionBySchema[func.schema].functions.push({ fn: func, inArgs }) }