diff --git a/package-lock.json b/package-lock.json index 752051a3..921e6f4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7836,51 +7836,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tinypool": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", @@ -8175,18 +8130,15 @@ } }, "node_modules/vite": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz", - "integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==", + "version": "6.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz", + "integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "rollup": "^4.30.1" }, "bin": { "vite": "bin/vite.js" @@ -8272,34 +8224,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/vite/node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/vitest": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.9.tgz", diff --git a/package.json b/package.json index e521801c..5e268b9c 100644 --- a/package.json +++ b/package.json @@ -72,5 +72,6 @@ "ts-node": "^10.9.1", "typescript": "^5.6.3", "vitest": "^3.0.5" - } + }, + "packageManager": "pnpm@9.15.5+sha512.845196026aab1cc3f098a0474b64dfbab2afe7a1b4e91dd86895d8e4aa32a7a6d03049e2d0ad770bbe4de023a7122fb68c1a1d6e0d033c7076085f9d5d4800d4" } 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..8e50515a 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 f.proretset and 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 + t1.oid, + t2.mode, + t1.name, + t1.type_id, + t1.has_default, + case + when pt.typrelid != 0 then pc.relname + else null + end as table_name from - functions - ) as t1, - 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 + ( + 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/constants.ts b/src/server/constants.ts index 4d1965f9..8c4f0748 100644 --- a/src/server/constants.ts +++ b/src/server/constants.ts @@ -49,6 +49,9 @@ export const GENERATE_TYPES_SWIFT_ACCESS_CONTROL = process.env .PG_META_GENERATE_TYPES_SWIFT_ACCESS_CONTROL ? (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 diff --git a/src/server/routes/column-privileges.ts b/src/server/routes/column-privileges.ts index d1b1f194..ddde2c57 100644 --- a/src/server/routes/column-privileges.ts +++ b/src/server/routes/column-privileges.ts @@ -6,7 +6,7 @@ import { postgresColumnPrivilegesRevokeSchema, postgresColumnPrivilegesSchema, } from '../../lib/types.js' -import { createConnectionConfig } from '../utils.js' +import { DEFAULT_POOL_CONFIG } from '../constants.js' import { extractRequestForLogging, translateErrorToResponseCode } from '../utils.js' const route: FastifyPluginAsyncTypebox = async (fastify) => { @@ -16,7 +16,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), querystring: Type.Object({ include_system_schemas: Type.Optional(Type.Boolean()), @@ -35,14 +34,14 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const includeSystemSchemas = request.query.include_system_schemas const includedSchemas = request.query.included_schemas?.split(',') const excludedSchemas = request.query.excluded_schemas?.split(',') const limit = request.query.limit const offset = request.query.offset - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.columnPrivileges.list({ includeSystemSchemas, includedSchemas, @@ -67,7 +66,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), body: Type.Array(postgresColumnPrivilegesGrantSchema), response: { @@ -79,9 +77,9 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.columnPrivileges.grant(request.body) await pgMeta.end() if (error) { @@ -100,7 +98,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), body: Type.Array(postgresColumnPrivilegesRevokeSchema), response: { @@ -115,9 +112,9 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.columnPrivileges.revoke(request.body) await pgMeta.end() if (error) { diff --git a/src/server/routes/columns.ts b/src/server/routes/columns.ts index 21e12364..54a7ba2a 100644 --- a/src/server/routes/columns.ts +++ b/src/server/routes/columns.ts @@ -1,5 +1,6 @@ import { PostgresMeta } from '../../lib/index.js' -import { createConnectionConfig, extractRequestForLogging } from '../utils.js' +import { DEFAULT_POOL_CONFIG } from '../constants.js' +import { extractRequestForLogging } from '../utils.js' import { Type } from '@sinclair/typebox' import { postgresColumnCreateSchema, @@ -15,7 +16,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), querystring: Type.Object({ include_system_schemas: Type.Optional(Type.Boolean()), @@ -33,14 +33,14 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { + const connectionString = request.headers.pg const includeSystemSchemas = request.query.include_system_schemas const includedSchemas = request.query.included_schemas?.split(',') const excludedSchemas = request.query.excluded_schemas?.split(',') const limit = request.query.limit const offset = request.query.offset - const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.columns.list({ includeSystemSchemas, includedSchemas, @@ -65,7 +65,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), params: Type.Object({ tableId: Type.String(), @@ -87,13 +86,13 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { async (request, reply) => { if (request.params.ordinalPosition === '') { const { + headers: { pg: connectionString }, query: { limit, offset }, params: { tableId }, } = request const includeSystemSchemas = request.query.include_system_schemas - const config = createConnectionConfig(request) - const pgMeta: PostgresMeta = new PostgresMeta(config) + const pgMeta: PostgresMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.columns.list({ tableId: Number(tableId), includeSystemSchemas, @@ -110,12 +109,12 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { return data[0] } else if (/^\.\d+$/.test(request.params.ordinalPosition)) { const { + headers: { pg: connectionString }, params: { tableId, ordinalPosition: ordinalPositionWithDot }, } = request const ordinalPosition = ordinalPositionWithDot.slice(1) - const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.columns.retrieve({ id: `${tableId}.${ordinalPosition}`, }) @@ -140,7 +139,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), body: postgresColumnCreateSchema, response: { @@ -152,8 +150,9 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta(config) + const connectionString = request.headers.pg + + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.columns.create(request.body) await pgMeta.end() if (error) { @@ -173,7 +172,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), params: Type.Object({ id: Type.String(), @@ -188,8 +186,9 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta(config) + const connectionString = request.headers.pg + + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.columns.update(request.params.id, request.body) await pgMeta.end() if (error) { @@ -209,7 +208,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), params: Type.Object({ id: Type.String(), @@ -226,10 +224,10 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { + const connectionString = request.headers.pg const cascade = request.query.cascade === 'true' - const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.columns.remove(request.params.id, { cascade }) await pgMeta.end() if (error) { diff --git a/src/server/routes/config.ts b/src/server/routes/config.ts index a2faf9db..f24e9628 100644 --- a/src/server/routes/config.ts +++ b/src/server/routes/config.ts @@ -1,21 +1,21 @@ import { FastifyInstance } from 'fastify' import { PostgresMeta } from '../../lib/index.js' -import { createConnectionConfig } from '../utils.js' +import { DEFAULT_POOL_CONFIG } from '../constants.js' import { extractRequestForLogging } from '../utils.js' export default async (fastify: FastifyInstance) => { fastify.get<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Querystring: { limit?: number offset?: number } }>('/', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const limit = request.query.limit const offset = request.query.offset - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.config.list({ limit, offset }) await pgMeta.end() if (error) { @@ -28,11 +28,11 @@ export default async (fastify: FastifyInstance) => { }) fastify.get<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } }>('/version', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.version.retrieve() await pgMeta.end() if (error) { diff --git a/src/server/routes/extensions.ts b/src/server/routes/extensions.ts index b202c251..71c9308a 100644 --- a/src/server/routes/extensions.ts +++ b/src/server/routes/extensions.ts @@ -1,21 +1,21 @@ import { FastifyInstance } from 'fastify' import { PostgresMeta } from '../../lib/index.js' -import { createConnectionConfig } from '../utils.js' +import { DEFAULT_POOL_CONFIG } from '../constants.js' import { extractRequestForLogging } from '../utils.js' export default async (fastify: FastifyInstance) => { fastify.get<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Querystring: { limit?: number offset?: number } }>('/', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const limit = request.query.limit const offset = request.query.offset - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.extensions.list({ limit, offset }) await pgMeta.end() if (error) { @@ -28,14 +28,14 @@ export default async (fastify: FastifyInstance) => { }) fastify.get<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Params: { name: string } }>('/:name', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.extensions.retrieve({ name: request.params.name }) await pgMeta.end() if (error) { @@ -48,12 +48,12 @@ export default async (fastify: FastifyInstance) => { }) fastify.post<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Body: any }>('/', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.extensions.create(request.body as any) await pgMeta.end() if (error) { @@ -66,15 +66,15 @@ export default async (fastify: FastifyInstance) => { }) fastify.patch<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Params: { name: string } Body: any }>('/:name', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.extensions.update(request.params.name, request.body as any) await pgMeta.end() if (error) { @@ -88,7 +88,7 @@ export default async (fastify: FastifyInstance) => { }) fastify.delete<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Params: { name: string } @@ -96,10 +96,10 @@ export default async (fastify: FastifyInstance) => { cascade?: string } }>('/:name', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const cascade = request.query.cascade === 'true' - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.extensions.remove(request.params.name, { cascade }) await pgMeta.end() if (error) { diff --git a/src/server/routes/foreign-tables.ts b/src/server/routes/foreign-tables.ts index 4de924f6..931295b4 100644 --- a/src/server/routes/foreign-tables.ts +++ b/src/server/routes/foreign-tables.ts @@ -2,7 +2,7 @@ import { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox' import { Type } from '@sinclair/typebox' import { PostgresMeta } from '../../lib/index.js' import { postgresForeignTableSchema } from '../../lib/types.js' -import { createConnectionConfig } from '../utils.js' +import { DEFAULT_POOL_CONFIG } from '../constants.js' import { extractRequestForLogging } from '../utils.js' const route: FastifyPluginAsyncTypebox = async (fastify) => { @@ -12,7 +12,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), querystring: Type.Object({ limit: Type.Optional(Type.Integer()), @@ -28,12 +27,12 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const limit = request.query.limit const offset = request.query.offset const includeColumns = request.query.include_columns - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.foreignTables.list({ limit, offset, includeColumns }) await pgMeta.end() if (error) { @@ -52,7 +51,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), params: Type.Object({ id: Type.Integer(), @@ -66,10 +64,10 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = request.params.id - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.foreignTables.retrieve({ id }) await pgMeta.end() if (error) { diff --git a/src/server/routes/functions.ts b/src/server/routes/functions.ts index 51fd5737..2e494be3 100644 --- a/src/server/routes/functions.ts +++ b/src/server/routes/functions.ts @@ -1,11 +1,11 @@ import { FastifyInstance } from 'fastify' import { PostgresMeta } from '../../lib/index.js' -import { createConnectionConfig } from '../utils.js' +import { DEFAULT_POOL_CONFIG } from '../constants.js' import { extractRequestForLogging } from '../utils.js' export default async (fastify: FastifyInstance) => { fastify.get<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Querystring: { include_system_schemas?: string // Note: this only supports comma separated values (e.g., ".../functions?included_schemas=public,core") @@ -15,14 +15,14 @@ export default async (fastify: FastifyInstance) => { offset?: number } }>('/', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const includeSystemSchemas = request.query.include_system_schemas === 'true' const includedSchemas = request.query.included_schemas?.split(',') const excludedSchemas = request.query.excluded_schemas?.split(',') const limit = request.query.limit const offset = request.query.offset - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.functions.list({ includeSystemSchemas, includedSchemas, @@ -41,15 +41,15 @@ export default async (fastify: FastifyInstance) => { }) fastify.get<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Params: { id: string } }>('/:id(\\d+)', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = Number(request.params.id) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.functions.retrieve({ id }) await pgMeta.end() if (error) { @@ -62,12 +62,12 @@ export default async (fastify: FastifyInstance) => { }) fastify.post<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Body: any }>('/', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.functions.create(request.body as any) await pgMeta.end() if (error) { @@ -79,16 +79,16 @@ export default async (fastify: FastifyInstance) => { }) fastify.patch<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Params: { id: string } Body: any }>('/:id(\\d+)', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = Number(request.params.id) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.functions.update(id, request.body as any) await pgMeta.end() if (error) { @@ -101,15 +101,15 @@ export default async (fastify: FastifyInstance) => { }) fastify.delete<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Params: { id: string } }>('/:id(\\d+)', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = Number(request.params.id) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.functions.remove(id) await pgMeta.end() if (error) { diff --git a/src/server/routes/generators/go.ts b/src/server/routes/generators/go.ts index fa85fa46..aaf1bb07 100644 --- a/src/server/routes/generators/go.ts +++ b/src/server/routes/generators/go.ts @@ -1,24 +1,25 @@ import type { FastifyInstance } from 'fastify' import { PostgresMeta } from '../../../lib/index.js' -import { createConnectionConfig, extractRequestForLogging } from '../../utils.js' +import { DEFAULT_POOL_CONFIG } from '../../constants.js' +import { extractRequestForLogging } from '../../utils.js' import { apply as applyGoTemplate } from '../../templates/go.js' import { getGeneratorMetadata } from '../../../lib/generators.js' export default async (fastify: FastifyInstance) => { fastify.get<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Querystring: { excluded_schemas?: string included_schemas?: string } }>('/', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const excludedSchemas = request.query.excluded_schemas?.split(',').map((schema) => schema.trim()) ?? [] const includedSchemas = request.query.included_schemas?.split(',').map((schema) => schema.trim()) ?? [] - const pgMeta: PostgresMeta = new PostgresMeta(config) + const pgMeta: PostgresMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data: generatorMeta, error: generatorMetaError } = await getGeneratorMetadata(pgMeta, { includedSchemas, excludedSchemas, diff --git a/src/server/routes/generators/swift.ts b/src/server/routes/generators/swift.ts index e02839fb..2c802fe2 100644 --- a/src/server/routes/generators/swift.ts +++ b/src/server/routes/generators/swift.ts @@ -1,26 +1,27 @@ import type { FastifyInstance } from 'fastify' import { PostgresMeta } from '../../../lib/index.js' -import { createConnectionConfig, extractRequestForLogging } from '../../utils.js' +import { DEFAULT_POOL_CONFIG } from '../../constants.js' +import { extractRequestForLogging } from '../../utils.js' import { apply as applySwiftTemplate, AccessControl } from '../../templates/swift.js' import { getGeneratorMetadata } from '../../../lib/generators.js' export default async (fastify: FastifyInstance) => { fastify.get<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Querystring: { excluded_schemas?: string included_schemas?: string access_control?: AccessControl } }>('/', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const excludedSchemas = request.query.excluded_schemas?.split(',').map((schema) => schema.trim()) ?? [] const includedSchemas = request.query.included_schemas?.split(',').map((schema) => schema.trim()) ?? [] const accessControl = request.query.access_control ?? 'internal' - const pgMeta: PostgresMeta = new PostgresMeta(config) + const pgMeta: PostgresMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data: generatorMeta, error: generatorMetaError } = await getGeneratorMetadata(pgMeta, { includedSchemas, excludedSchemas, diff --git a/src/server/routes/generators/typescript.ts b/src/server/routes/generators/typescript.ts index 3e615b32..b7a75248 100644 --- a/src/server/routes/generators/typescript.ts +++ b/src/server/routes/generators/typescript.ts @@ -1,26 +1,27 @@ import type { FastifyInstance } from 'fastify' import { PostgresMeta } from '../../../lib/index.js' -import { createConnectionConfig, extractRequestForLogging } from '../../utils.js' +import { DEFAULT_POOL_CONFIG } from '../../constants.js' +import { extractRequestForLogging } from '../../utils.js' import { apply as applyTypescriptTemplate } from '../../templates/typescript.js' import { getGeneratorMetadata } from '../../../lib/generators.js' export default async (fastify: FastifyInstance) => { fastify.get<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Querystring: { excluded_schemas?: string included_schemas?: string detect_one_to_one_relationships?: string } }>('/', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const excludedSchemas = request.query.excluded_schemas?.split(',').map((schema) => schema.trim()) ?? [] const includedSchemas = request.query.included_schemas?.split(',').map((schema) => schema.trim()) ?? [] const detectOneToOneRelationships = request.query.detect_one_to_one_relationships === 'true' - const pgMeta: PostgresMeta = new PostgresMeta(config) + const pgMeta: PostgresMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data: generatorMeta, error: generatorMetaError } = await getGeneratorMetadata(pgMeta, { includedSchemas, excludedSchemas, diff --git a/src/server/routes/indexes.ts b/src/server/routes/indexes.ts index b024ea09..69e88ef6 100644 --- a/src/server/routes/indexes.ts +++ b/src/server/routes/indexes.ts @@ -1,10 +1,11 @@ import { FastifyInstance } from 'fastify' import { PostgresMeta } from '../../lib/index.js' -import { createConnectionConfig, extractRequestForLogging } from '../utils.js' +import { DEFAULT_POOL_CONFIG } from '../constants.js' +import { extractRequestForLogging } from '../utils.js' export default async (fastify: FastifyInstance) => { fastify.get<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Querystring: { include_system_schemas?: string // Note: this only supports comma separated values (e.g., ".../functions?included_schemas=public,core") @@ -14,14 +15,14 @@ export default async (fastify: FastifyInstance) => { offset?: number } }>('/', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const includeSystemSchemas = request.query.include_system_schemas === 'true' const includedSchemas = request.query.included_schemas?.split(',') const excludedSchemas = request.query.excluded_schemas?.split(',') const limit = request.query.limit const offset = request.query.offset - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.indexes.list({ includeSystemSchemas, includedSchemas, @@ -40,15 +41,15 @@ export default async (fastify: FastifyInstance) => { }) fastify.get<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Params: { id: string } }>('/:id(\\d+)', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = Number(request.params.id) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.indexes.retrieve({ id }) await pgMeta.end() if (error) { diff --git a/src/server/routes/materialized-views.ts b/src/server/routes/materialized-views.ts index 9a5a0b4f..48c2fee7 100644 --- a/src/server/routes/materialized-views.ts +++ b/src/server/routes/materialized-views.ts @@ -2,7 +2,7 @@ import { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox' import { Type } from '@sinclair/typebox' import { PostgresMeta } from '../../lib/index.js' import { postgresMaterializedViewSchema } from '../../lib/types.js' -import { createConnectionConfig } from '../utils.js' +import { DEFAULT_POOL_CONFIG } from '../constants.js' import { extractRequestForLogging } from '../utils.js' const route: FastifyPluginAsyncTypebox = async (fastify) => { @@ -12,7 +12,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), querystring: Type.Object({ included_schemas: Type.Optional(Type.String()), @@ -30,14 +29,14 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const includedSchemas = request.query.included_schemas?.split(',') const excludedSchemas = request.query.excluded_schemas?.split(',') const limit = request.query.limit const offset = request.query.offset const includeColumns = request.query.include_columns - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.materializedViews.list({ includedSchemas, excludedSchemas, @@ -62,7 +61,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), params: Type.Object({ id: Type.Integer(), @@ -76,10 +74,10 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = request.params.id - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.materializedViews.retrieve({ id }) await pgMeta.end() if (error) { diff --git a/src/server/routes/policies.ts b/src/server/routes/policies.ts index 4e951ad3..bb369a53 100644 --- a/src/server/routes/policies.ts +++ b/src/server/routes/policies.ts @@ -1,11 +1,11 @@ import { FastifyInstance } from 'fastify' import { PostgresMeta } from '../../lib/index.js' -import { createConnectionConfig } from '../utils.js' +import { DEFAULT_POOL_CONFIG } from '../constants.js' import { extractRequestForLogging } from '../utils.js' export default async (fastify: FastifyInstance) => { fastify.get<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Querystring: { include_system_schemas?: string // Note: this only supports comma separated values (e.g., ".../policies?included_schemas=public,core") @@ -15,14 +15,14 @@ export default async (fastify: FastifyInstance) => { offset?: number } }>('/', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const includeSystemSchemas = request.query.include_system_schemas === 'true' const includedSchemas = request.query.included_schemas?.split(',') const excludedSchemas = request.query.excluded_schemas?.split(',') const limit = request.query.limit const offset = request.query.offset - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.policies.list({ includeSystemSchemas, includedSchemas, @@ -41,15 +41,15 @@ export default async (fastify: FastifyInstance) => { }) fastify.get<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Params: { id: string } }>('/:id(\\d+)', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = Number(request.params.id) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.policies.retrieve({ id }) await pgMeta.end() if (error) { @@ -62,12 +62,12 @@ export default async (fastify: FastifyInstance) => { }) fastify.post<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Body: any }>('/', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.policies.create(request.body as any) await pgMeta.end() if (error) { @@ -80,16 +80,16 @@ export default async (fastify: FastifyInstance) => { }) fastify.patch<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Params: { id: string } Body: any }>('/:id(\\d+)', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = Number(request.params.id) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.policies.update(id, request.body as any) await pgMeta.end() if (error) { @@ -103,15 +103,15 @@ export default async (fastify: FastifyInstance) => { }) fastify.delete<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Params: { id: string } }>('/:id(\\d+)', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = Number(request.params.id) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.policies.remove(id) await pgMeta.end() if (error) { diff --git a/src/server/routes/publications.ts b/src/server/routes/publications.ts index 68cf45b2..4aa63796 100644 --- a/src/server/routes/publications.ts +++ b/src/server/routes/publications.ts @@ -1,21 +1,21 @@ import { FastifyInstance } from 'fastify' import { PostgresMeta } from '../../lib/index.js' -import { createConnectionConfig } from '../utils.js' +import { DEFAULT_POOL_CONFIG } from '../constants.js' import { extractRequestForLogging } from '../utils.js' export default async (fastify: FastifyInstance) => { fastify.get<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Querystring: { limit?: number offset?: number } }>('/', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const limit = request.query.limit const offset = request.query.offset - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.publications.list({ limit, offset }) await pgMeta.end() if (error) { @@ -28,15 +28,15 @@ export default async (fastify: FastifyInstance) => { }) fastify.get<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Params: { id: string } }>('/:id(\\d+)', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = Number(request.params.id) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.publications.retrieve({ id }) await pgMeta.end() if (error) { @@ -49,12 +49,12 @@ export default async (fastify: FastifyInstance) => { }) fastify.post<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Body: any }>('/', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.publications.create(request.body as any) await pgMeta.end() if (error) { @@ -67,16 +67,16 @@ export default async (fastify: FastifyInstance) => { }) fastify.patch<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Params: { id: string } Body: any }>('/:id(\\d+)', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = Number(request.params.id) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.publications.update(id, request.body as any) await pgMeta.end() if (error) { @@ -90,15 +90,15 @@ export default async (fastify: FastifyInstance) => { }) fastify.delete<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Params: { id: string } }>('/:id(\\d+)', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = Number(request.params.id) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.publications.remove(id) await pgMeta.end() if (error) { diff --git a/src/server/routes/query.ts b/src/server/routes/query.ts index 21788ce8..6fa775c7 100644 --- a/src/server/routes/query.ts +++ b/src/server/routes/query.ts @@ -1,11 +1,8 @@ import { FastifyInstance, FastifyRequest } from 'fastify' import { PostgresMeta } from '../../lib/index.js' import * as Parser from '../../lib/Parser.js' -import { - createConnectionConfig, - extractRequestForLogging, - translateErrorToResponseCode, -} from '../utils.js' +import { DEFAULT_POOL_CONFIG } from '../constants.js' +import { extractRequestForLogging, translateErrorToResponseCode } from '../utils.js' const errorOnEmptyQuery = (request: FastifyRequest) => { if (!(request.body as any).query) { @@ -15,14 +12,15 @@ const errorOnEmptyQuery = (request: FastifyRequest) => { export default async (fastify: FastifyInstance) => { fastify.post<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Body: { query: string } }>('/', async (request, reply) => { errorOnEmptyQuery(request) - const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta(config) + const connectionString = request.headers.pg + + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.query(request.body.query, false) await pgMeta.end() if (error) { @@ -35,7 +33,7 @@ export default async (fastify: FastifyInstance) => { }) fastify.post<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Body: { query: string } @@ -53,7 +51,7 @@ export default async (fastify: FastifyInstance) => { }) fastify.post<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Body: { query: string } @@ -71,7 +69,7 @@ export default async (fastify: FastifyInstance) => { }) fastify.post<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Body: { ast: object } diff --git a/src/server/routes/roles.ts b/src/server/routes/roles.ts index a8809be2..f9d125db 100644 --- a/src/server/routes/roles.ts +++ b/src/server/routes/roles.ts @@ -1,6 +1,6 @@ import { FastifyInstance } from 'fastify' import { PostgresMeta } from '../../lib/index.js' -import { createConnectionConfig } from '../utils.js' +import { DEFAULT_POOL_CONFIG } from '../constants.js' import { extractRequestForLogging } from '../utils.js' import { PostgresRoleCreate, @@ -24,7 +24,6 @@ export default async (fastify: FastifyInstance) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), querystring: Type.Object({ include_system_schemas: Type.Optional(Type.String()), @@ -40,12 +39,12 @@ export default async (fastify: FastifyInstance) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const includeDefaultRoles = request.query.include_default_roles === 'true' const limit = request.query.limit const offset = request.query.offset - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.roles.list({ includeDefaultRoles, limit, @@ -73,7 +72,6 @@ export default async (fastify: FastifyInstance) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), params: Type.Object({ id: Type.RegExp(/\d+/), @@ -87,10 +85,10 @@ export default async (fastify: FastifyInstance) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = Number(request.params.id) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.roles.retrieve({ id }) await pgMeta.end() if (error) { @@ -112,7 +110,6 @@ export default async (fastify: FastifyInstance) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), body: postgresRoleCreateSchema, response: { @@ -124,9 +121,9 @@ export default async (fastify: FastifyInstance) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.roles.create(request.body) await pgMeta.end() if (error) { @@ -151,7 +148,6 @@ export default async (fastify: FastifyInstance) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), params: Type.Object({ id: Type.RegExp(/\d+/), @@ -169,10 +165,10 @@ export default async (fastify: FastifyInstance) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = Number(request.params.id) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.roles.update(id, request.body) await pgMeta.end() if (error) { @@ -197,7 +193,6 @@ export default async (fastify: FastifyInstance) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), params: Type.Object({ id: Type.RegExp(/\d+/), @@ -217,10 +212,10 @@ export default async (fastify: FastifyInstance) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = Number(request.params.id) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.roles.remove(id) await pgMeta.end() if (error) { diff --git a/src/server/routes/schemas.ts b/src/server/routes/schemas.ts index a65d104a..16cd70e5 100644 --- a/src/server/routes/schemas.ts +++ b/src/server/routes/schemas.ts @@ -6,7 +6,7 @@ import { postgresSchemaCreateSchema, postgresSchemaUpdateSchema, } from '../../lib/types.js' -import { createConnectionConfig } from '../utils.js' +import { DEFAULT_POOL_CONFIG } from '../constants.js' import { extractRequestForLogging } from '../utils.js' const route: FastifyPluginAsyncTypebox = async (fastify) => { @@ -16,7 +16,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), querystring: Type.Object({ include_system_schemas: Type.Optional(Type.Boolean()), @@ -32,12 +31,12 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const includeSystemSchemas = request.query.include_system_schemas const limit = request.query.limit const offset = request.query.offset - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.schemas.list({ includeSystemSchemas, limit, offset }) await pgMeta.end() if (error) { @@ -56,7 +55,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), params: Type.Object({ id: Type.Integer(), @@ -70,10 +68,10 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = request.params.id - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.schemas.retrieve({ id }) await pgMeta.end() if (error) { @@ -92,7 +90,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), body: postgresSchemaCreateSchema, response: { @@ -104,9 +101,9 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.schemas.create(request.body) await pgMeta.end() if (error) { @@ -125,7 +122,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), params: Type.Object({ id: Type.Integer(), @@ -143,10 +139,10 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = request.params.id - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.schemas.update(id, request.body) await pgMeta.end() if (error) { @@ -166,7 +162,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), params: Type.Object({ id: Type.Integer(), @@ -186,11 +181,11 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = request.params.id const cascade = request.query.cascade - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.schemas.remove(id, { cascade }) await pgMeta.end() if (error) { diff --git a/src/server/routes/table-privileges.ts b/src/server/routes/table-privileges.ts index 615d6efa..31d7b2b7 100644 --- a/src/server/routes/table-privileges.ts +++ b/src/server/routes/table-privileges.ts @@ -6,7 +6,7 @@ import { postgresTablePrivilegesRevokeSchema, postgresTablePrivilegesSchema, } from '../../lib/types.js' -import { createConnectionConfig } from '../utils.js' +import { DEFAULT_POOL_CONFIG } from '../constants.js' import { extractRequestForLogging, translateErrorToResponseCode } from '../utils.js' const route: FastifyPluginAsyncTypebox = async (fastify) => { @@ -16,7 +16,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), querystring: Type.Object({ include_system_schemas: Type.Optional(Type.Boolean()), @@ -35,14 +34,14 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const includeSystemSchemas = request.query.include_system_schemas const includedSchemas = request.query.included_schemas?.split(',') const excludedSchemas = request.query.excluded_schemas?.split(',') const limit = request.query.limit const offset = request.query.offset - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.tablePrivileges.list({ includeSystemSchemas, includedSchemas, @@ -67,7 +66,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), body: Type.Array(postgresTablePrivilegesGrantSchema), response: { @@ -79,9 +77,9 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.tablePrivileges.grant(request.body) await pgMeta.end() if (error) { @@ -100,7 +98,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), body: Type.Array(postgresTablePrivilegesRevokeSchema), response: { @@ -115,9 +112,9 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.tablePrivileges.revoke(request.body) await pgMeta.end() if (error) { diff --git a/src/server/routes/tables.ts b/src/server/routes/tables.ts index ff5cb2c9..2efd3d05 100644 --- a/src/server/routes/tables.ts +++ b/src/server/routes/tables.ts @@ -6,11 +6,8 @@ import { postgresTableSchema, postgresTableUpdateSchema, } from '../../lib/types.js' -import { - createConnectionConfig, - extractRequestForLogging, - translateErrorToResponseCode, -} from '../utils.js' +import { DEFAULT_POOL_CONFIG } from '../constants.js' +import { extractRequestForLogging, translateErrorToResponseCode } from '../utils.js' const route: FastifyPluginAsyncTypebox = async (fastify) => { fastify.get( @@ -19,7 +16,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), querystring: Type.Object({ include_system_schemas: Type.Optional(Type.Boolean()), @@ -39,6 +35,7 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { + const connectionString = request.headers.pg const includeSystemSchemas = request.query.include_system_schemas const includedSchemas = request.query.included_schemas?.split(',') const excludedSchemas = request.query.excluded_schemas?.split(',') @@ -46,8 +43,7 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { const offset = request.query.offset const includeColumns = request.query.include_columns - const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.tables.list({ includeSystemSchemas, includedSchemas, @@ -73,7 +69,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), params: Type.Object({ id: Type.Integer(), @@ -87,10 +82,10 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { + const connectionString = request.headers.pg const id = request.params.id - const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.tables.retrieve({ id }) await pgMeta.end() if (error) { @@ -109,7 +104,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), body: postgresTableCreateSchema, response: { @@ -121,8 +115,9 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta(config) + const connectionString = request.headers.pg + + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.tables.create(request.body) await pgMeta.end() if (error) { @@ -141,7 +136,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), params: Type.Object({ id: Type.Integer(), @@ -159,10 +153,10 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { + const connectionString = request.headers.pg const id = request.params.id - const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.tables.update(id, request.body) await pgMeta.end() if (error) { @@ -182,7 +176,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), params: Type.Object({ id: Type.Integer(), @@ -202,11 +195,11 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { + const connectionString = request.headers.pg const id = request.params.id const cascade = request.query.cascade - const config = createConnectionConfig(request) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.tables.remove(id, { cascade }) await pgMeta.end() if (error) { diff --git a/src/server/routes/triggers.ts b/src/server/routes/triggers.ts index 3eb2212a..138b6061 100644 --- a/src/server/routes/triggers.ts +++ b/src/server/routes/triggers.ts @@ -1,11 +1,11 @@ import { FastifyInstance } from 'fastify' import { PostgresMeta } from '../../lib/index.js' -import { createConnectionConfig } from '../utils.js' +import { DEFAULT_POOL_CONFIG } from '../constants.js' import { extractRequestForLogging } from '../utils.js' export default async (fastify: FastifyInstance) => { fastify.get<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Querystring: { include_system_schemas?: string // Note: this only supports comma separated values (e.g., ".../columns?included_schemas=public,core") @@ -15,14 +15,14 @@ export default async (fastify: FastifyInstance) => { offset?: number } }>('/', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const includeSystemSchemas = request.query.include_system_schemas === 'true' const includedSchemas = request.query.included_schemas?.split(',') const excludedSchemas = request.query.excluded_schemas?.split(',') const limit = request.query.limit const offset = request.query.offset - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.triggers.list({ includeSystemSchemas, includedSchemas, @@ -41,15 +41,15 @@ export default async (fastify: FastifyInstance) => { }) fastify.get<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Params: { id: string } }>('/:id(\\d+)', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = Number(request.params.id) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.triggers.retrieve({ id }) await pgMeta.end() if (error) { @@ -62,12 +62,12 @@ export default async (fastify: FastifyInstance) => { }) fastify.post<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Body: any }>('/', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.triggers.create(request.body as any) await pgMeta.end() if (error) { @@ -80,16 +80,16 @@ export default async (fastify: FastifyInstance) => { }) fastify.patch<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Params: { id: string } Body: any }>('/:id(\\d+)', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = Number(request.params.id) - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.triggers.update(id, request.body as any) await pgMeta.end() if (error) { @@ -103,7 +103,7 @@ export default async (fastify: FastifyInstance) => { }) fastify.delete<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Params: { id: string } @@ -111,11 +111,11 @@ export default async (fastify: FastifyInstance) => { cascade?: string } }>('/:id(\\d+)', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = Number(request.params.id) const cascade = request.query.cascade === 'true' - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.triggers.remove(id, { cascade }) await pgMeta.end() if (error) { diff --git a/src/server/routes/types.ts b/src/server/routes/types.ts index c4e0dfbd..168f39ac 100644 --- a/src/server/routes/types.ts +++ b/src/server/routes/types.ts @@ -1,11 +1,11 @@ import { FastifyInstance } from 'fastify' import { PostgresMeta } from '../../lib/index.js' -import { createConnectionConfig } from '../utils.js' +import { DEFAULT_POOL_CONFIG } from '../constants.js' import { extractRequestForLogging } from '../utils.js' export default async (fastify: FastifyInstance) => { fastify.get<{ - Headers: { pg: string; 'x-pg-application-name'?: string } + Headers: { pg: string } Querystring: { include_array_types?: string include_system_schemas?: string @@ -16,7 +16,7 @@ export default async (fastify: FastifyInstance) => { offset?: number } }>('/', async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const includeArrayTypes = request.query.include_array_types === 'true' const includeSystemSchemas = request.query.include_system_schemas === 'true' const includedSchemas = request.query.included_schemas?.split(',') @@ -24,7 +24,7 @@ export default async (fastify: FastifyInstance) => { const limit = request.query.limit const offset = request.query.offset - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.types.list({ includeArrayTypes, includeSystemSchemas, diff --git a/src/server/routes/views.ts b/src/server/routes/views.ts index 119088e3..f8794f36 100644 --- a/src/server/routes/views.ts +++ b/src/server/routes/views.ts @@ -2,7 +2,7 @@ import { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox' import { Type } from '@sinclair/typebox' import { PostgresMeta } from '../../lib/index.js' import { postgresViewSchema } from '../../lib/types.js' -import { createConnectionConfig } from '../utils.js' +import { DEFAULT_POOL_CONFIG } from '../constants.js' import { extractRequestForLogging } from '../utils.js' const route: FastifyPluginAsyncTypebox = async (fastify) => { @@ -12,7 +12,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), querystring: Type.Object({ include_system_schemas: Type.Optional(Type.Boolean()), @@ -32,7 +31,7 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const includeSystemSchemas = request.query.include_system_schemas const includedSchemas = request.query.included_schemas?.split(',') const excludedSchemas = request.query.excluded_schemas?.split(',') @@ -40,7 +39,7 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { const offset = request.query.offset const includeColumns = request.query.include_columns - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.views.list({ includeSystemSchemas, includedSchemas, @@ -66,7 +65,6 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { schema: { headers: Type.Object({ pg: Type.String(), - 'x-pg-application-name': Type.Optional(Type.String()), }), params: Type.Object({ id: Type.Integer(), @@ -80,10 +78,10 @@ const route: FastifyPluginAsyncTypebox = async (fastify) => { }, }, async (request, reply) => { - const config = createConnectionConfig(request) + const connectionString = request.headers.pg const id = request.params.id - const pgMeta = new PostgresMeta(config) + const pgMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) const { data, error } = await pgMeta.views.retrieve({ id }) await pgMeta.end() if (error) { diff --git a/src/server/templates/typescript.ts b/src/server/templates/typescript.ts index 6e3fc750..97aa27eb 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 { + VALID_UNNAMED_FUNCTION_ARG_TYPES, + GENERATE_TYPES_DEFAULT_SCHEMA, + VALID_FUNCTION_ARGS_MODE, +} from '../constants.js' export const apply = async ({ schemas, @@ -27,6 +31,75 @@ export const apply = async ({ const columnsByTableId = Object.fromEntries( [...tables, ...foreignTables, ...views, ...materializedViews].map((t) => [t.id, []]) ) + // group types by id for quicker lookup + const typesById = types.reduce( + (acc, type) => { + acc[type.id] = type + return acc + }, + {} as Record + ) + + const getTsReturnType = (fn: PostgresFunction, returnType: string) => { + return `${returnType}${fn.is_set_returning_function && fn.returns_multiple_rows ? '[]' : ''} + ${ + fn.returns_set_of_table && fn.args.length === 1 && fn.args[0].table_name + ? `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} + }` + : '' + }` + } + const getReturnType = (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(type.name, { types, schemas, tables, views }) + } + return { name, type: tsType } + }) + .sort((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] + .map( + (column) => + `${JSON.stringify(column.name)}: ${pgTypeToTsType(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(type.name, { types, schemas, tables, views }) + } + + return 'unknown' + } + columns .filter((c) => c.table_id in columnsByTableId) .sort(({ name: a }, { name: b }) => a.localeCompare(b)) @@ -45,6 +118,7 @@ export type Database = { 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) { @@ -54,7 +128,7 @@ export type Database = { // 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)) + const inArgs = func.args.filter(({ mode }) => VALID_FUNCTION_ARGS_MODE.has(mode)) if (!inArgs.some(({ name }) => name === '')) { return true @@ -84,7 +158,7 @@ export type Database = { ${[ ...columnsByTableId[table.id].map( (column) => - `${JSON.stringify(column.name)}: ${pgTypeToTsType(schema, column.format, { + `${JSON.stringify(column.name)}: ${pgTypeToTsType(column.format, { types, schemas, tables, @@ -93,19 +167,7 @@ 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)}: ${getReturnType(fn)} | null`), ]} } Insert: { @@ -126,12 +188,7 @@ export type Database = { output += ':' } - output += pgTypeToTsType(schema, column.format, { - types, - schemas, - tables, - views, - }) + output += pgTypeToTsType(column.format, { types, schemas, tables, views }) if (column.is_nullable) { output += '| null' @@ -148,12 +205,7 @@ export type Database = { return `${output}?: never` } - output += `?: ${pgTypeToTsType(schema, column.format, { - types, - schemas, - tables, - views, - })}` + output += `?: ${pgTypeToTsType(column.format, { types, schemas, tables, views })}` if (column.is_nullable) { output += '| null' @@ -204,7 +256,7 @@ export type Database = { Row: { ${columnsByTableId[view.id].map( (column) => - `${JSON.stringify(column.name)}: ${pgTypeToTsType(schema, column.format, { + `${JSON.stringify(column.name)}: ${pgTypeToTsType(column.format, { types, schemas, tables, @@ -222,12 +274,7 @@ export type Database = { return `${output}?: never` } - output += `?: ${pgTypeToTsType(schema, column.format, { - types, - schemas, - tables, - views, - })} | null` + output += `?: ${pgTypeToTsType(column.format, { types, schemas, tables, views })} | null` return output })} @@ -240,12 +287,7 @@ export type Database = { return `${output}?: never` } - output += `?: ${pgTypeToTsType(schema, column.format, { - types, - schemas, - tables, - views, - })} | null` + output += `?: ${pgTypeToTsType(column.format, { types, schemas, tables, views })} | null` return output })} @@ -290,107 +332,269 @@ 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.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 + } - 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 } - }) - return `{ ${argsNameAndType.map(({ name, type, has_default }) => `${JSON.stringify(name)}${has_default ? '?' : ''}: ${type}`)} }` + // Case 3: All unnamed args have default values + if (inArgs.every((arg) => (arg.name === '' ? arg.has_default : 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.length === 1 && + inArgs[0].name === '' && + inArgs[0].table_name && + func.return_table_name) + ) { + return true + } + + return false + }) + .reduce( + (acc, curr) => { + acc[curr.name] ??= [] + acc[curr.name].push(curr) + return acc + }, + {} as Record + ) + + return Object.entries(schemaFunctionsGroupedByName).map(([fnName, fns]) => { + // Group functions by their argument names signature to detect conflicts + const fnsByArgNames = new Map() + fns.sort((fn1, fn2) => fn1.id - fn2.id) + + fns.forEach((fn) => { + const namedInArgs = fn.args + .filter(({ mode, name }) => VALID_FUNCTION_ARGS_MODE.has(mode) && name !== '') + .map((arg) => arg.name) + .sort() + .join(',') + + if (!fnsByArgNames.has(namedInArgs)) { + fnsByArgNames.set(namedInArgs, []) + } + fnsByArgNames.get(namedInArgs)!.push(fn) + }) + + // For each group of functions sharing the same argument names, check if they have conflicting types + const conflictingSignatures = new Set() + fnsByArgNames.forEach((groupedFns, argNames) => { + if (groupedFns.length > 1) { + // Check if any args have different types within this group + const firstFn = groupedFns[0] + const firstFnArgTypes = new Map( + firstFn.args + .filter( + ({ mode, name }) => VALID_FUNCTION_ARGS_MODE.has(mode) && name !== '' + ) + .map((arg) => [arg.name, String(arg.type_id)]) + ) + + const hasConflict = groupedFns.some((fn) => { + const fnArgTypes = new Map( + fn.args + .filter( + ({ mode, name }) => VALID_FUNCTION_ARGS_MODE.has(mode) && name !== '' + ) + .map((arg) => [arg.name, String(arg.type_id)]) + ) + + return [...firstFnArgTypes.entries()].some( + ([name, typeId]) => fnArgTypes.get(name) !== typeId + ) + }) + + if (hasConflict) { + conflictingSignatures.add(argNames) + } + } + }) + + // Generate all possible function signatures as a union + const signatures = (() => { + const allSignatures: string[] = [] + + // First check if we have a no-param function + const noParamFns = fns.filter( + (fn) => + fn.args.length === 0 || + // If all the params of a function have non potgrest proxyable arguments + fn.args.every((arg) => !VALID_FUNCTION_ARGS_MODE.has(arg.mode)) + ) + const unnamedFns = fns.filter((fn) => { + // Only include unnamed functions that: + // 1. Have a single unnamed parameter + // 2. The parameter is of a valid type (json, jsonb, text) + // 3. All parameters have default values + const inArgs = fn.args.filter(({ mode }) => VALID_FUNCTION_ARGS_MODE.has(mode)) + return ( + inArgs.length === 1 && + inArgs[0].name === '' && + (VALID_UNNAMED_FUNCTION_ARG_TYPES.has(inArgs[0].type_id) || + inArgs[0].has_default) && + !fn.return_table_name + ) + }) + + // Special case: one no-param function and unnamed param function exist + if (noParamFns.length > 0) { + const noParamFn = noParamFns[0] + const unnamedWithDefaultsFn = unnamedFns.find((fn) => + fn.args.every((arg) => arg.has_default) + ) + + // If we have a function with unnamed params that all have defaults, it creates a conflict + if (unnamedWithDefaultsFn) { + // Only generate the error signature in this case + const conflictDesc = [ + `${fnName}()`, + `${fnName}( => ${typesById[unnamedWithDefaultsFn.args[0].type_id]?.name || 'unknown'})`, + ] + .sort() + .join(', ') + + allSignatures.push(`{ + Args: Record + Returns: { error: true } & "Could not choose the best candidate function between: ${conflictDesc}. Try renaming the parameters or the function itself in the database so function overloading can be resolved" + }`) + } else { + // No conflict - just add the no params signature + allSignatures.push(`{ + Args: Record + Returns: ${getTsReturnType(noParamFn, getReturnType(noParamFn))} + }`) + } + } + if (unnamedFns.length > 0) { + // If we don't have a no-param function, process the unnamed args + // Take only the first function with unnamed parameters that has a valid type + const validUnnamedFn = unnamedFns.find( + (fn) => + fn.args.length === 1 && + fn.args[0].name === '' && + VALID_UNNAMED_FUNCTION_ARG_TYPES.has(fn.args[0].type_id) + ) + + if (validUnnamedFn) { + const firstArgType = typesById[validUnnamedFn.args[0].type_id] + const tsType = firstArgType + ? pgTypeToTsType(firstArgType.name, { types, schemas, tables, views }) + : 'unknown' + + allSignatures.push(`{ + Args: { "": ${tsType} } + Returns: ${getTsReturnType(validUnnamedFn, getReturnType(validUnnamedFn))} + }`) + } + } + const unnamedSetofFunctions = fns.filter((fn) => { + // Only include unnamed functions that: + // 1. Have a single unnamed parameter + // 2. The parameter is of a valid type (json, jsonb, text) + // 3. All parameters have default values + const inArgs = fn.args.filter(({ mode }) => VALID_FUNCTION_ARGS_MODE.has(mode)) + return inArgs.length === 1 && inArgs[0].name === '' && fn.return_table_name + }) + if (unnamedSetofFunctions.length > 0) { + const unnamedEmbededFunctionsSignatures = unnamedSetofFunctions.map( + (fn) => + `{ IsUnnamedEmbededTable: true, Args: Record, Returns: ${getTsReturnType(fn, getReturnType(fn))} }` + ) + allSignatures.push(...unnamedEmbededFunctionsSignatures) + } + + // For functions with named parameters, generate all signatures + const namedFns = fns.filter((fn) => !fn.args.some(({ name }) => name === '')) + namedFns.forEach((fn) => { + const inArgs = fn.args.filter(({ mode }) => mode === 'in') + const namedInArgs = inArgs + .filter((arg) => arg.name !== '') + .map((arg) => arg.name) + .sort() + .join(',') + + // If this argument combination would cause a conflict, return an error type signature + if (conflictingSignatures.has(namedInArgs)) { + const conflictingFns = fnsByArgNames.get(namedInArgs)! + const conflictDesc = conflictingFns + .map((cfn) => { + const argsStr = cfn.args + .filter(({ mode }) => mode === 'in') + .map((arg) => { + const type = typesById[arg.type_id] + return `${arg.name} => ${type?.name || 'unknown'}` + }) + .sort() + .join(', ') + return `${fnName}(${argsStr})` }) - .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) + .sort() + .join(', ') + + allSignatures.push(`{ + Args: { ${inArgs + .map((arg) => `${JSON.stringify(arg.name)}: unknown`) + .sort() + .join(', ')} } + Returns: { error: true } & "Could not choose the best candidate function between: ${conflictDesc}. Try renaming the parameters or the function itself in the database so function overloading can be resolved" + }`) + } else if (inArgs.length > 0) { + // Generate normal function signature + const returnType = getReturnType(fn) + allSignatures.push(`{ + Args: ${`{ ${inArgs + .map(({ name, type_id, has_default }) => { + const type = typesById[type_id] let tsType = 'unknown' if (type) { - tsType = pgTypeToTsType(schema, type.name, { + tsType = pgTypeToTsType(type.name, { types, schemas, tables, views, }) } - return { name, type: tsType } + return `${JSON.stringify(name)}${has_default ? '?' : ''}: ${tsType}` }) - - return `{ - ${argsNameAndType.map( - ({ name, type }) => `${JSON.stringify(name)}: ${type}` - )} - }` - } - - // 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' : ''}` - )} - }` - } - - // Case 3: returns base/array/composite/enum type. - const type = types.find(({ id }) => id === fns[0].return_type_id) - if (type) { - return pgTypeToTsType(schema, type.name, { - types, - schemas, - tables, - views, - }) - } - - return 'unknown' - })()}${fns[0].is_set_returning_function ? '[]' : ''} - }` - ) + .sort() + .join(', ')} }`} + Returns: ${getTsReturnType(fn, returnType)} + }`) + } + }) + + // Remove duplicates and sort + return Array.from(new Set(allSignatures)).sort() + })() + + if (signatures.length > 0) { + // Remove duplicates, sort, and join with | + return `${JSON.stringify(fnName)}: ${signatures.join('\n | ')}` + } else { + console.log('fns', fns) + return `${JSON.stringify(fnName)}: ${fns.map((fn) => `{ Args: unknown, Returns: ${getTsReturnType(fn, getReturnType(fn))} }`).join('\n |')}` + } + }) })()} } Enums: { @@ -413,15 +617,10 @@ 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, { - types, - schemas, - tables, - views, - })} | null` + tsType = `${pgTypeToTsType(type.name, { types, schemas, tables, views })} | null` } return `${JSON.stringify(name)}: ${tsType}` })} @@ -559,16 +758,20 @@ export const Constants = { } as const ` - output = await prettier.format(output, { - parser: 'typescript', - semi: false, - }) - return output + try { + return await prettier.format(output, { + parser: 'typescript', + semi: false, + }) + } catch (error) { + console.log('Error: ', output) + console.log(error) + throw error + } } // TODO: Make this more robust. Currently doesn't handle range types - returns them as unknown. const pgTypeToTsType = ( - schema: PostgresSchema, pgType: string, { types, @@ -610,16 +813,10 @@ const pgTypeToTsType = ( } else if (pgType === 'record') { return 'Record' } else if (pgType.startsWith('_')) { - return `(${pgTypeToTsType(schema, pgType.substring(1), { - types, - schemas, - tables, - views, - })})[]` + return `(${pgTypeToTsType(pgType.substring(1), { types, schemas, tables, views })})[]` } else { - const enumTypes = types.filter((type) => type.name === pgType && type.enums.length > 0) - if (enumTypes.length > 0) { - const enumType = enumTypes.find((type) => type.schema === schema.name) || enumTypes[0] + const enumType = types.find((type) => type.name === pgType && type.enums.length > 0) + if (enumType) { if (schemas.some(({ name }) => name === enumType.schema)) { return `Database[${JSON.stringify(enumType.schema)}]['Enums'][${JSON.stringify( enumType.name @@ -628,12 +825,8 @@ const pgTypeToTsType = ( return enumType.enums.map((variant) => JSON.stringify(variant)).join('|') } - const compositeTypes = types.filter( - (type) => type.name === pgType && type.attributes.length > 0 - ) - if (compositeTypes.length > 0) { - const compositeType = - compositeTypes.find((type) => type.schema === schema.name) || compositeTypes[0] + const compositeType = types.find((type) => type.name === pgType && type.attributes.length > 0) + if (compositeType) { if (schemas.some(({ name }) => name === compositeType.schema)) { return `Database[${JSON.stringify( compositeType.schema @@ -642,10 +835,8 @@ const pgTypeToTsType = ( return 'unknown' } - const tableRowTypes = tables.filter((table) => table.name === pgType) - if (tableRowTypes.length > 0) { - const tableRowType = - tableRowTypes.find((type) => type.schema === schema.name) || tableRowTypes[0] + const tableRowType = tables.find((table) => table.name === pgType) + if (tableRowType) { if (schemas.some(({ name }) => name === tableRowType.schema)) { return `Database[${JSON.stringify(tableRowType.schema)}]['Tables'][${JSON.stringify( tableRowType.name @@ -654,10 +845,8 @@ const pgTypeToTsType = ( return 'unknown' } - const viewRowTypes = views.filter((view) => view.name === pgType) - if (viewRowTypes.length > 0) { - const viewRowType = - viewRowTypes.find((type) => type.schema === schema.name) || viewRowTypes[0] + const viewRowType = views.find((view) => view.name === pgType) + if (viewRowType) { if (schemas.some(({ name }) => name === viewRowType.schema)) { return `Database[${JSON.stringify(viewRowType.schema)}]['Views'][${JSON.stringify( viewRowType.name diff --git a/src/server/utils.ts b/src/server/utils.ts index ebb8ec90..cc8de839 100644 --- a/src/server/utils.ts +++ b/src/server/utils.ts @@ -1,7 +1,5 @@ import pgcs from 'pg-connection-string' import { FastifyRequest } from 'fastify' -import { DEFAULT_POOL_CONFIG } from './constants.js' -import { PoolConfig } from '../lib/types.js' export const extractRequestForLogging = (request: FastifyRequest) => { let pg: string = 'unknown' @@ -23,18 +21,6 @@ export const extractRequestForLogging = (request: FastifyRequest) => { } } -export function createConnectionConfig(request: FastifyRequest): PoolConfig { - const connectionString = request.headers.pg as string - const config = { ...DEFAULT_POOL_CONFIG, connectionString } - - // Override application_name if custom one provided in header - if (request.headers['x-pg-application-name']) { - config.application_name = request.headers['x-pg-application-name'] as string - } - - return config -} - export function translateErrorToResponseCode( error: { message: string }, defaultResponseCode = 400 diff --git a/test/db/00-init.sql b/test/db/00-init.sql index 00c6a472..b2045e89 100644 --- a/test/db/00-init.sql +++ b/test/db/00-init.sql @@ -55,6 +55,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; @@ -116,6 +127,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 +201,187 @@ 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; +$$; + diff --git a/test/lib/functions.ts b/test/lib/functions.ts index 05de3244..ec2c4338 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": 16503, + "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": 16504, + "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": 16505, + "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/server/query.ts b/test/server/query.ts index 2b4bc2ba..25c4c262 100644 --- a/test/server/query.ts +++ b/test/server/query.ts @@ -730,24 +730,3 @@ test('error with internalQuery property', async () => { } `) }) - -test('custom application_name', async () => { - const res = await app.inject({ - method: 'POST', - path: '/query', - headers: { - 'x-pg-application-name': 'test', - }, - payload: { - query: 'SHOW application_name;', - }, - }) - - expect(res.json()).toMatchInlineSnapshot(` - [ - { - "application_name": "test", - }, - ] - `) -}) diff --git a/test/server/typegen.ts b/test/server/typegen.ts index c0851ef1..557296ff 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"] @@ -224,6 +242,14 @@ test('typegen: typescript', async () => { 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: { id?: number @@ -285,6 +311,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"] @@ -334,6 +366,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,6 +398,16 @@ 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: { id: number | null @@ -389,26 +437,6 @@ test('typegen: typescript', async () => { } } Functions: { - blurb: { - Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: string - } - blurb_varchar: { - Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: string - } - details_is_long: { - Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: boolean - } - details_length: { - Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: number - } - details_words: { - Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: string[] - } function_returning_row: { Args: Record Returns: { @@ -432,16 +460,124 @@ test('typegen: typescript', async () => { 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: { - details: string | null id: number - "user-id": number + name: string }[] } + get_composite_type_data: { + Args: Record + Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"][] + } + get_single_user_summary_from_view: + | { + 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 + } + } + | { + 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 + } + } + | { + 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 + } + } + get_todos_from_user: + | { + Args: { search_user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + } + } + | { + 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 + } + } + get_todos_setof_rows: + | { + Args: { todo_row: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + } + } get_user_audit_setof_single_row: { Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } Returns: { @@ -449,12 +585,72 @@ test('typegen: typescript', async () => { id: number previous_value: Json | null user_id: number | null - }[] + } + SetofOptions: { + from: "users" + to: "users_audit" + isOneToOne: true + } + } + get_user_ids: { + Args: Record + Returns: number[] + } + get_user_summary: { + Args: Record + Returns: Record[] } polymorphic_function: { - Args: { "": boolean } | { "": string } + Args: { "": string } Returns: undefined } + polymorphic_function_with_different_return: { + Args: { "": string } + Returns: string + } + polymorphic_function_with_no_params_or_unnamed: + | { + Args: Record + Returns: number + } + | { + Args: { "": string } + Returns: string + } + polymorphic_function_with_unnamed_default: + | { + Args: Record + Returns: { + error: true + } & "Could not choose the best candidate function between: polymorphic_function_with_unnamed_default( => int4), polymorphic_function_with_unnamed_default(). 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: Record + Returns: { + error: true + } & "Could not choose the best candidate function between: polymorphic_function_with_unnamed_default_overload( => int4), polymorphic_function_with_unnamed_default_overload(). 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 @@ -471,10 +667,111 @@ test('typegen: typescript', async () => { Args: Record Returns: unknown } + postgrest_resolvable_with_override_function: + | { + Args: Record + Returns: undefined + } + | { + Args: { a: string } + Returns: number + } + | { + Args: { b: number } + Returns: string + } + | { + Args: { completed: boolean; todo_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + } + | { + Args: { user_id: number } + Returns: { + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + }[] + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + } + } + postgrest_unresolvable_function: + | { + Args: Record + Returns: undefined + } + | { + Args: { a: unknown } + Returns: { + error: true + } & "Could not choose the best candidate function between: postgrest_unresolvable_function(a => int4), postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } test_internal_query: { Args: Record Returns: undefined } + test_unnamed_row_setof: + | { + Args: { user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + } + | { + Args: never + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + } + } + | { + Args: never + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + } + } + test_unnamed_view_row: { + Args: never + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos_view" + to: "todos" + isOneToOne: false + } + } } Enums: { meme_status: "new" | "old" | "retired" @@ -742,6 +1039,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 @@ -761,6 +1064,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"] @@ -812,6 +1122,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"] @@ -847,6 +1164,14 @@ test('typegen w/ one-to-one relationships', async () => { 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: { id?: number @@ -909,6 +1234,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"] @@ -963,6 +1295,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"] @@ -993,6 +1332,16 @@ 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: { id: number | null @@ -1022,26 +1371,6 @@ 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 - } - details_is_long: { - Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: boolean - } - details_length: { - Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: number - } - details_words: { - Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: string[] - } function_returning_row: { Args: Record Returns: { @@ -1065,16 +1394,124 @@ test('typegen w/ one-to-one relationships', async () => { 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: { - details: string | null id: number - "user-id": number + name: string }[] } + get_composite_type_data: { + Args: Record + Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"][] + } + get_single_user_summary_from_view: + | { + 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 + } + } + | { + 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 + } + } + | { + 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 + } + } + get_todos_from_user: + | { + Args: { search_user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + } + } + | { + 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 + } + } + get_todos_setof_rows: + | { + Args: { todo_row: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + } + } get_user_audit_setof_single_row: { Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } Returns: { @@ -1082,12 +1519,72 @@ test('typegen w/ one-to-one relationships', async () => { id: number previous_value: Json | null user_id: number | null - }[] + } + SetofOptions: { + from: "users" + to: "users_audit" + isOneToOne: true + } + } + get_user_ids: { + Args: Record + Returns: number[] + } + get_user_summary: { + Args: Record + Returns: Record[] } polymorphic_function: { - Args: { "": boolean } | { "": string } + Args: { "": string } Returns: undefined } + polymorphic_function_with_different_return: { + Args: { "": string } + Returns: string + } + polymorphic_function_with_no_params_or_unnamed: + | { + Args: Record + Returns: number + } + | { + Args: { "": string } + Returns: string + } + polymorphic_function_with_unnamed_default: + | { + Args: Record + Returns: { + error: true + } & "Could not choose the best candidate function between: polymorphic_function_with_unnamed_default( => int4), polymorphic_function_with_unnamed_default(). 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: Record + Returns: { + error: true + } & "Could not choose the best candidate function between: polymorphic_function_with_unnamed_default_overload( => int4), polymorphic_function_with_unnamed_default_overload(). 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 @@ -1104,10 +1601,111 @@ test('typegen w/ one-to-one relationships', async () => { Args: Record Returns: unknown } + postgrest_resolvable_with_override_function: + | { + Args: Record + Returns: undefined + } + | { + Args: { a: string } + Returns: number + } + | { + Args: { b: number } + Returns: string + } + | { + Args: { completed: boolean; todo_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + } + | { + Args: { user_id: number } + Returns: { + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + }[] + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + } + } + postgrest_unresolvable_function: + | { + Args: Record + Returns: undefined + } + | { + Args: { a: unknown } + Returns: { + error: true + } & "Could not choose the best candidate function between: postgrest_unresolvable_function(a => int4), postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } test_internal_query: { Args: Record Returns: undefined } + test_unnamed_row_setof: + | { + Args: { user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + } + | { + Args: never + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + } + } + | { + Args: never + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + } + } + test_unnamed_view_row: { + Args: never + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos_view" + to: "todos" + isOneToOne: false + } + } } Enums: { meme_status: "new" | "old" | "retired" @@ -1375,6 +1973,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 @@ -1394,6 +1998,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"] @@ -1445,6 +2056,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"] @@ -1480,6 +2098,14 @@ test('typegen: typescript w/ one-to-one relationships', async () => { 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: { id?: number @@ -1542,6 +2168,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"] @@ -1596,6 +2229,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"] @@ -1626,6 +2266,16 @@ 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: { id: number | null @@ -1655,26 +2305,6 @@ 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 - } - details_is_long: { - Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: boolean - } - details_length: { - Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: number - } - details_words: { - Args: { "": Database["public"]["Tables"]["todos"]["Row"] } - Returns: string[] - } function_returning_row: { Args: Record Returns: { @@ -1698,16 +2328,124 @@ test('typegen: typescript w/ one-to-one relationships', async () => { 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: { - details: string | null id: number - "user-id": number + name: string }[] } + get_composite_type_data: { + Args: Record + Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"][] + } + get_single_user_summary_from_view: + | { + 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 + } + } + | { + 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 + } + } + | { + 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 + } + } + get_todos_from_user: + | { + Args: { search_user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + } + } + | { + 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 + } + } + get_todos_setof_rows: + | { + Args: { todo_row: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + } + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + } + } get_user_audit_setof_single_row: { Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } Returns: { @@ -1715,12 +2453,72 @@ test('typegen: typescript w/ one-to-one relationships', async () => { id: number previous_value: Json | null user_id: number | null - }[] + } + SetofOptions: { + from: "users" + to: "users_audit" + isOneToOne: true + } + } + get_user_ids: { + Args: Record + Returns: number[] + } + get_user_summary: { + Args: Record + Returns: Record[] } polymorphic_function: { - Args: { "": boolean } | { "": string } + Args: { "": string } Returns: undefined } + polymorphic_function_with_different_return: { + Args: { "": string } + Returns: string + } + polymorphic_function_with_no_params_or_unnamed: + | { + Args: Record + Returns: number + } + | { + Args: { "": string } + Returns: string + } + polymorphic_function_with_unnamed_default: + | { + Args: Record + Returns: { + error: true + } & "Could not choose the best candidate function between: polymorphic_function_with_unnamed_default( => int4), polymorphic_function_with_unnamed_default(). 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: Record + Returns: { + error: true + } & "Could not choose the best candidate function between: polymorphic_function_with_unnamed_default_overload( => int4), polymorphic_function_with_unnamed_default_overload(). 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 @@ -1737,10 +2535,111 @@ test('typegen: typescript w/ one-to-one relationships', async () => { Args: Record Returns: unknown } + postgrest_resolvable_with_override_function: + | { + Args: Record + Returns: undefined + } + | { + Args: { a: string } + Returns: number + } + | { + Args: { b: number } + Returns: string + } + | { + Args: { completed: boolean; todo_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + } + | { + Args: { user_id: number } + Returns: { + id: number + name: string | null + status: Database["public"]["Enums"]["user_status"] | null + }[] + } + | { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + } + } + postgrest_unresolvable_function: + | { + Args: Record + Returns: undefined + } + | { + Args: { a: unknown } + Returns: { + error: true + } & "Could not choose the best candidate function between: postgrest_unresolvable_function(a => int4), postgrest_unresolvable_function(a => text). Try renaming the parameters or the function itself in the database so function overloading can be resolved" + } test_internal_query: { Args: Record Returns: undefined } + test_unnamed_row_setof: + | { + Args: { user_id: number } + Returns: { + details: string | null + id: number + "user-id": number + }[] + } + | { + Args: never + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos" + to: "todos" + isOneToOne: false + } + } + | { + Args: never + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "users" + to: "todos" + isOneToOne: false + } + } + test_unnamed_view_row: { + Args: never + Returns: { + details: string | null + id: number + "user-id": number + }[] + SetofOptions: { + from: "todos_view" + to: "todos" + isOneToOne: false + } + } } Enums: { meme_status: "new" | "old" | "retired" @@ -1880,198 +2779,206 @@ test('typegen: go', async () => { 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 { + 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 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 { + 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"\` + }" `) }) @@ -2396,6 +3303,20 @@ 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 id: Int64? internal let name: String? @@ -2759,6 +3680,20 @@ 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 id: Int64? public let name: String?