Skip to content

Commit 2f6ab96

Browse files
committed
feat(/functions): adds POST /functions and DELETE /functions
implements missing routes and (POST & DELETE) #115
1 parent e74514a commit 2f6ab96

File tree

3 files changed

+125
-2
lines changed

3 files changed

+125
-2
lines changed

src/lib/PostgresMetaFunctions.ts

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { literal } from 'pg-format'
1+
import { ident, literal } from 'pg-format'
22
import { DEFAULT_SYSTEM_SCHEMAS } from './constants'
33
import { functionsSql } from './sql'
44
import { PostgresMetaResult, PostgresFunction } from './types'
@@ -22,7 +22,13 @@ export default class PostgresMetaFunctions {
2222
}
2323

2424
async retrieve({ id }: { id: number }): Promise<PostgresMetaResult<PostgresFunction>>
25-
async retrieve({ name }: { name: string }): Promise<PostgresMetaResult<PostgresFunction>>
25+
async retrieve({
26+
name,
27+
schema,
28+
}: {
29+
name: string
30+
schema: string
31+
}): Promise<PostgresMetaResult<PostgresFunction>>
2632
async retrieve({
2733
id,
2834
name,
@@ -57,4 +63,54 @@ export default class PostgresMetaFunctions {
5763
return { data: null, error: { message: 'Invalid parameters on function retrieve' } }
5864
}
5965
}
66+
67+
async create({
68+
name,
69+
schema = 'public',
70+
params,
71+
definition,
72+
rettype = 'void',
73+
language = 'sql',
74+
}: {
75+
name: string
76+
schema?: string
77+
params?: string[]
78+
definition: string
79+
rettype?: string
80+
language?: string
81+
}): Promise<PostgresMetaResult<PostgresFunction>> {
82+
const sql = `
83+
CREATE FUNCTION ${ident(schema)}.${ident(name)}
84+
${params && params.length ? `(${params.join(',')})` : '()'}
85+
RETURNS ${rettype || 'void'}
86+
AS '${definition}'
87+
LANGUAGE ${language}
88+
RETURNS NULL ON NULL INPUT;
89+
`
90+
const { error } = await this.query(sql)
91+
if (error) {
92+
return { data: null, error }
93+
}
94+
return await this.retrieve({ name, schema })
95+
}
96+
97+
async remove(
98+
id: number,
99+
{ cascade = false } = {}
100+
): Promise<PostgresMetaResult<PostgresFunction>> {
101+
const { data: func, error } = await this.retrieve({ id })
102+
if (error) {
103+
return { data: null, error }
104+
}
105+
const sql = `DROP FUNCTION ${ident(func!.schema)}.${ident(func!.name)} ${
106+
cascade ? 'CASCADE' : 'RESTRICT'
107+
};`
108+
{
109+
const { error } = await this.query(sql)
110+
if (error) {
111+
return { data: null, error }
112+
}
113+
}
114+
return { data: func!, error: null }
115+
}
60116
}

src/server/routes/functions.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,42 @@ export default async (fastify: FastifyInstance) => {
4343

4444
return data
4545
})
46+
47+
fastify.post<{
48+
Headers: { pg: string }
49+
Body: any
50+
}>('/', async (request, reply) => {
51+
const connectionString = request.headers.pg
52+
53+
const pgMeta = new PostgresMeta({ connectionString, max: 1 })
54+
const { data, error } = await pgMeta.functions.create(request.body)
55+
await pgMeta.end()
56+
if (error) {
57+
request.log.error(JSON.stringify({ error, req: request.body }))
58+
reply.code(400)
59+
return { error: error.message }
60+
}
61+
return data
62+
})
63+
64+
fastify.delete<{
65+
Headers: { pg: string }
66+
Params: {
67+
id: string
68+
}
69+
}>('/:id(\\d+)', async (request, reply) => {
70+
const connectionString = request.headers.pg
71+
const id = Number(request.params.id)
72+
73+
const pgMeta = new PostgresMeta({ connectionString, max: 1 })
74+
const { data, error } = await pgMeta.functions.remove(id)
75+
await pgMeta.end()
76+
if (error) {
77+
request.log.error(JSON.stringify({ error, req: request.body }))
78+
reply.code(400)
79+
if (error.message.startsWith('Cannot find')) reply.code(404)
80+
return { error: error.message }
81+
}
82+
return data
83+
})
4684
}

test/integration/index.spec.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,20 @@ describe('/types', () => {
157157
})
158158
})
159159
describe('/functions', () => {
160+
var func = {
161+
id: null,
162+
name: 'test_func',
163+
schema: 'public',
164+
params: ['integer', 'integer'],
165+
definition: 'select $1 + $2',
166+
rettype: 'integer',
167+
language: 'sql',
168+
}
169+
before(async () => {
170+
await axios.post(`${URL}/query`, {
171+
query: `DROP FUNCTION IF EXISTS "${func.name}";`,
172+
})
173+
})
160174
it('GET', async () => {
161175
const res = await axios.get(`${URL}/functions`)
162176
// console.log('res.data', res.data)
@@ -184,7 +198,22 @@ describe('/functions', () => {
184198

185199
assert.deepStrictEqual(functionById, functionFiltered)
186200
})
201+
it('POST', async () => {
202+
const { data: newFunc } = await axios.post(`${URL}/functions`, func)
203+
assert.equal(newFunc.name, 'test_func')
204+
assert.equal(newFunc.schema, 'public')
205+
assert.equal(newFunc.language, 'sql')
206+
assert.equal(newFunc.return_type, 'int4')
207+
func.id = newFunc.id
208+
})
209+
it('DELETE', async () => {
210+
await axios.delete(`${URL}/functions/${func.id}`)
211+
const { data: functions } = await axios.get(`${URL}/functions`)
212+
const stillExists = functions.some((x) => func.id === x.id)
213+
assert.equal(stillExists, false, 'Function is deleted')
214+
})
187215
})
216+
188217
describe('/tables', async () => {
189218
it('GET', async () => {
190219
const tables = await axios.get(`${URL}/tables`)

0 commit comments

Comments
 (0)