Skip to content

Commit 64a1afc

Browse files
committed
feat(typegen): add setof function type introspection
- Introspect the setof function fields for functions - Restore functions as unions of args + returns
1 parent 003391e commit 64a1afc

File tree

14 files changed

+3198
-469
lines changed

14 files changed

+3198
-469
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM node:20 as build
1+
FROM node:20 AS build
22
WORKDIR /usr/src/app
33
# Do `npm ci` separately so we can cache `node_modules`
44
# https://nodejs.org/en/docs/guides/nodejs-docker-webapp/

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
"gen:types:go": "PG_META_GENERATE_TYPES=go node --loader ts-node/esm src/server/server.ts",
2727
"gen:types:swift": "PG_META_GENERATE_TYPES=swift node --loader ts-node/esm src/server/server.ts",
2828
"start": "node dist/server/server.js",
29-
"dev": "trap 'npm run db:clean' INT && run-s db:clean db:run && nodemon --exec node --loader ts-node/esm src/server/server.ts | pino-pretty --colorize",
29+
"dev": "trap 'npm run db:clean' INT && run-s db:clean db:run && run-s dev:code",
30+
"dev:code": "nodemon --exec node --loader ts-node/esm src/server/server.ts | pino-pretty --colorize",
3031
"test": "run-s db:clean db:run test:run db:clean",
3132
"db:clean": "cd test/db && docker compose down",
3233
"db:run": "cd test/db && docker compose up --detach --wait",

src/lib/PostgresMetaTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export default class PostgresMetaTypes {
3333
t.typrelid = 0
3434
or (
3535
select
36-
c.relkind ${includeTableTypes ? `in ('c', 'r')` : `= 'c'`}
36+
c.relkind ${includeTableTypes ? `in ('c', 'r', 'v')` : `= 'c'`}
3737
from
3838
pg_class c
3939
where

src/lib/sql/functions.sql

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,25 @@ select
4444
pg_get_function_result(f.oid) as return_type,
4545
nullif(rt.typrelid::int8, 0) as return_type_relation_id,
4646
f.proretset as is_set_returning_function,
47+
case
48+
when f.proretset and rt.typrelid != 0 and exists (
49+
select 1 from pg_class c
50+
where c.oid = rt.typrelid
51+
-- exclude custom types relation from what is considered a set of table
52+
and c.relkind in ('r', 'p', 'v', 'm', 'f')
53+
) then true
54+
else false
55+
end as returns_set_of_table,
56+
case
57+
when rt.typrelid != 0 then
58+
(select relname from pg_class where oid = rt.typrelid)
59+
else null
60+
end as return_table_name,
61+
case
62+
when f.proretset then
63+
coalesce(f.prorows, 0) > 1
64+
else false
65+
end as returns_multiple_rows,
4766
case
4867
when f.provolatile = 'i' then 'IMMUTABLE'
4968
when f.provolatile = 's' then 'STABLE'
@@ -76,32 +95,48 @@ from
7695
select
7796
oid,
7897
jsonb_agg(jsonb_build_object(
79-
'mode', t2.mode,
98+
'mode', mode,
8099
'name', name,
81100
'type_id', type_id,
82-
'has_default', has_default
101+
'has_default', has_default,
102+
'table_name', table_name
83103
)) as args
84104
from
85105
(
86106
select
87-
oid,
88-
unnest(arg_modes) as mode,
89-
unnest(arg_names) as name,
90-
unnest(arg_types)::int8 as type_id,
91-
unnest(arg_has_defaults) as has_default
92-
from
93-
functions
94-
) as t1,
95-
lateral (
96-
select
107+
t1.oid,
108+
t2.mode,
109+
t1.name,
110+
t1.type_id,
111+
t1.has_default,
97112
case
98-
when t1.mode = 'i' then 'in'
99-
when t1.mode = 'o' then 'out'
100-
when t1.mode = 'b' then 'inout'
101-
when t1.mode = 'v' then 'variadic'
102-
else 'table'
103-
end as mode
104-
) as t2
113+
when pt.typrelid != 0 then pc.relname
114+
else null
115+
end as table_name
116+
from
117+
(
118+
select
119+
oid,
120+
unnest(arg_modes) as mode,
121+
unnest(arg_names) as name,
122+
unnest(arg_types)::int8 as type_id,
123+
unnest(arg_has_defaults) as has_default
124+
from
125+
functions
126+
) as t1
127+
cross join lateral (
128+
select
129+
case
130+
when t1.mode = 'i' then 'in'
131+
when t1.mode = 'o' then 'out'
132+
when t1.mode = 'b' then 'inout'
133+
when t1.mode = 'v' then 'variadic'
134+
else 'table'
135+
end as mode
136+
) as t2
137+
left join pg_type pt on pt.oid = t1.type_id
138+
left join pg_class pc on pc.oid = pt.typrelid
139+
) sub
105140
group by
106-
t1.oid
141+
oid
107142
) f_args on f_args.oid = f.oid

src/lib/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ const postgresFunctionSchema = Type.Object({
148148
name: Type.String(),
149149
type_id: Type.Number(),
150150
has_default: Type.Boolean(),
151+
table_name: Type.Union([Type.String(), Type.Null()]),
151152
})
152153
),
153154
argument_types: Type.String(),
@@ -156,6 +157,9 @@ const postgresFunctionSchema = Type.Object({
156157
return_type: Type.String(),
157158
return_type_relation_id: Type.Union([Type.Integer(), Type.Null()]),
158159
is_set_returning_function: Type.Boolean(),
160+
returns_set_of_table: Type.Boolean(),
161+
return_table_name: Type.Union([Type.String(), Type.Null()]),
162+
returns_multiple_rows: Type.Boolean(),
159163
behavior: Type.Union([
160164
Type.Literal('IMMUTABLE'),
161165
Type.Literal('STABLE'),

src/server/app.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@ import * as Sentry from '@sentry/node'
33
import cors from '@fastify/cors'
44
import swagger from '@fastify/swagger'
55
import { fastify, FastifyInstance, FastifyServerOptions } from 'fastify'
6-
import { PG_META_REQ_HEADER } from './constants.js'
6+
import { PG_META_REQ_HEADER, MAX_BODY_LIMIT } from './constants.js'
77
import routes from './routes/index.js'
88
import { extractRequestForLogging } from './utils.js'
99
// Pseudo package declared only for this module
1010
import pkg from '#package.json' with { type: 'json' }
1111

1212
export const build = (opts: FastifyServerOptions = {}): FastifyInstance => {
13-
const app = fastify({ disableRequestLogging: true, requestIdHeader: PG_META_REQ_HEADER, ...opts })
13+
const app = fastify({
14+
disableRequestLogging: true,
15+
requestIdHeader: PG_META_REQ_HEADER,
16+
bodyLimit: MAX_BODY_LIMIT,
17+
...opts,
18+
})
1419
Sentry.setupFastifyErrorHandler(app)
1520

1621
app.setErrorHandler((error, request, reply) => {

src/server/constants.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,21 @@ export const GENERATE_TYPES_SWIFT_ACCESS_CONTROL = process.env
5151
? (process.env.PG_META_GENERATE_TYPES_SWIFT_ACCESS_CONTROL as AccessControl)
5252
: 'internal'
5353

54+
// json/jsonb/text types
55+
export const VALID_UNNAMED_FUNCTION_ARG_TYPES = new Set([114, 3802, 25])
56+
export const VALID_FUNCTION_ARGS_MODE = new Set(['in', 'inout', 'variadic'])
57+
5458
export const PG_META_MAX_RESULT_SIZE = process.env.PG_META_MAX_RESULT_SIZE_MB
5559
? // Node-postgres get a maximum size in bytes make the conversion from the env variable
5660
// from MB to Bytes
5761
parseInt(process.env.PG_META_MAX_RESULT_SIZE_MB, 10) * 1024 * 1024
5862
: 2 * 1024 * 1024 * 1024 // default to 2GB max query size result
5963

64+
export const MAX_BODY_LIMIT = process.env.PG_META_MAX_BODY_LIMIT_MB
65+
? // Fastify server max body size allowed, is in bytes, convert from MB to Bytes
66+
parseInt(process.env.PG_META_MAX_BODY_LIMIT_MB, 10) * 1024 * 1024
67+
: 3 * 1024 * 1024
68+
6069
export const DEFAULT_POOL_CONFIG: PoolConfig = {
6170
max: 1,
6271
connectionTimeoutMillis: PG_CONN_TIMEOUT_SECS * 1000,

src/server/templates/swift.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,8 @@ const pgTypeToSwiftType = (
309309
swiftType = 'Float'
310310
} else if (pgType === 'float8') {
311311
swiftType = 'Double'
312+
} else if (['numeric', 'decimal'].includes(pgType)) {
313+
swiftType = 'Decimal'
312314
} else if (pgType === 'uuid') {
313315
swiftType = 'UUID'
314316
} else if (

0 commit comments

Comments
 (0)