Skip to content

Commit 1f25d55

Browse files
kiwicopplew3b6x9
authored andcommitted
Mocks up the triggers endpoint
At this point we are returning tiggers but the rest of the CRUD is not working
1 parent fe5711d commit 1f25d55

File tree

7 files changed

+319
-3
lines changed

7 files changed

+319
-3
lines changed

src/lib/PostgresMeta.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import PostgresMetaPublications from './PostgresMetaPublications'
88
import PostgresMetaRoles from './PostgresMetaRoles'
99
import PostgresMetaSchemas from './PostgresMetaSchemas'
1010
import PostgresMetaTables from './PostgresMetaTables'
11+
import PostgresMetaTriggers from './PostgresMetaTriggers'
1112
import PostgresMetaTypes from './PostgresMetaTypes'
1213
import PostgresMetaVersion from './PostgresMetaVersion'
1314
import { init } from './db'
@@ -25,6 +26,7 @@ export default class PostgresMeta {
2526
roles: PostgresMetaRoles
2627
schemas: PostgresMetaSchemas
2728
tables: PostgresMetaTables
29+
triggers: PostgresMetaTriggers
2830
types: PostgresMetaTypes
2931
version: PostgresMetaVersion
3032

@@ -41,6 +43,7 @@ export default class PostgresMeta {
4143
this.roles = new PostgresMetaRoles(this.query)
4244
this.schemas = new PostgresMetaSchemas(this.query)
4345
this.tables = new PostgresMetaTables(this.query)
46+
this.triggers = new PostgresMetaTriggers(this.query)
4447
this.types = new PostgresMetaTypes(this.query)
4548
this.version = new PostgresMetaVersion(this.query)
4649
}

src/lib/PostgresMetaTriggers.ts

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import { ident, literal } from 'pg-format'
2+
import { DEFAULT_SYSTEM_SCHEMAS } from './constants'
3+
import { coalesceRowsToArray } from './helpers'
4+
import {
5+
columnsSql,
6+
grantsSql,
7+
policiesSql,
8+
primaryKeysSql,
9+
relationshipsSql,
10+
tablesSql,
11+
triggersSql,
12+
} from './sql'
13+
import { PostgresMetaResult, PostgresTable } from './types'
14+
15+
export default class PostgresMetaTriggers {
16+
query: (sql: string) => Promise<PostgresMetaResult<any>>
17+
18+
constructor(query: (sql: string) => Promise<PostgresMetaResult<any>>) {
19+
this.query = query
20+
}
21+
22+
async list({ includeSystemSchemas = false } = {}): Promise<PostgresMetaResult<PostgresTable[]>> {
23+
const sql = enrichedTriggersSql
24+
return await this.query(sql)
25+
}
26+
27+
async retrieve({ id }: { id: number }): Promise<PostgresMetaResult<PostgresTable>>
28+
async retrieve({
29+
name,
30+
schema,
31+
}: {
32+
name: string
33+
schema: string
34+
}): Promise<PostgresMetaResult<PostgresTable>>
35+
async retrieve({
36+
id,
37+
name,
38+
schema = 'public',
39+
}: {
40+
id?: number
41+
name?: string
42+
schema?: string
43+
}): Promise<PostgresMetaResult<PostgresTable>> {
44+
if (id) {
45+
const sql = `${enrichedTriggersSql} WHERE tables.id = ${literal(id)};`
46+
const { data, error } = await this.query(sql)
47+
if (error) {
48+
return { data, error }
49+
} else if (data.length === 0) {
50+
return { data: null, error: { message: `Cannot find a table with ID ${id}` } }
51+
} else {
52+
return { data: data[0], error }
53+
}
54+
} else if (name) {
55+
const sql = `${enrichedTriggersSql} WHERE tables.name = ${literal(
56+
name
57+
)} AND tables.schema = ${literal(schema)};`
58+
const { data, error } = await this.query(sql)
59+
if (error) {
60+
return { data, error }
61+
} else if (data.length === 0) {
62+
return {
63+
data: null,
64+
error: { message: `Cannot find a table named ${name} in schema ${schema}` },
65+
}
66+
} else {
67+
return { data: data[0], error }
68+
}
69+
} else {
70+
return { data: null, error: { message: 'Invalid parameters on table retrieve' } }
71+
}
72+
}
73+
74+
async create({
75+
name,
76+
schema = 'public',
77+
comment,
78+
}: {
79+
name: string
80+
schema?: string
81+
comment?: string
82+
}): Promise<PostgresMetaResult<PostgresTable>> {
83+
const tableSql = `CREATE TABLE ${ident(schema)}.${ident(name)} ();`
84+
const commentSql =
85+
comment === undefined
86+
? ''
87+
: `COMMENT ON TABLE ${ident(schema)}.${ident(name)} IS ${literal(comment)};`
88+
const sql = `BEGIN; ${tableSql} ${commentSql} COMMIT;`
89+
const { error } = await this.query(sql)
90+
if (error) {
91+
return { data: null, error }
92+
}
93+
return await this.retrieve({ name, schema })
94+
}
95+
96+
async update(
97+
id: number,
98+
{
99+
name,
100+
schema,
101+
rls_enabled,
102+
rls_forced,
103+
replica_identity,
104+
replica_identity_index,
105+
comment,
106+
}: {
107+
name?: string
108+
schema?: string
109+
rls_enabled?: boolean
110+
rls_forced?: boolean
111+
replica_identity?: 'DEFAULT' | 'INDEX' | 'FULL' | 'NOTHING'
112+
replica_identity_index?: string
113+
comment?: string
114+
}
115+
): Promise<PostgresMetaResult<PostgresTable>> {
116+
const { data: old, error } = await this.retrieve({ id })
117+
if (error) {
118+
return { data: null, error }
119+
}
120+
121+
const alter = `ALTER TABLE ${ident(old!.schema)}.${ident(old!.name)}`
122+
const schemaSql = schema === undefined ? '' : `${alter} SET SCHEMA ${ident(schema)};`
123+
let nameSql = ''
124+
if (name !== undefined && name !== old!.name) {
125+
const currentSchema = schema === undefined ? old!.schema : schema
126+
nameSql = `ALTER TABLE ${ident(currentSchema)}.${ident(old!.name)} RENAME TO ${ident(name)};`
127+
}
128+
let enableRls = ''
129+
if (rls_enabled !== undefined) {
130+
const enable = `${alter} ENABLE ROW LEVEL SECURITY;`
131+
const disable = `${alter} DISABLE ROW LEVEL SECURITY;`
132+
enableRls = rls_enabled ? enable : disable
133+
}
134+
let forceRls = ''
135+
if (rls_forced !== undefined) {
136+
const enable = `${alter} FORCE ROW LEVEL SECURITY;`
137+
const disable = `${alter} NO FORCE ROW LEVEL SECURITY;`
138+
forceRls = rls_forced ? enable : disable
139+
}
140+
let replicaSql: string
141+
if (replica_identity === undefined) {
142+
replicaSql = ''
143+
} else if (replica_identity === 'INDEX') {
144+
replicaSql = `${alter} REPLICA IDENTITY USING INDEX ${replica_identity_index};`
145+
} else {
146+
replicaSql = `${alter} REPLICA IDENTITY ${replica_identity};`
147+
}
148+
const commentSql =
149+
comment === undefined
150+
? ''
151+
: `COMMENT ON TABLE ${ident(old!.schema)}.${ident(old!.name)} IS ${literal(comment)};`
152+
// nameSql must be last, right below schemaSql
153+
const sql = `
154+
BEGIN;
155+
${enableRls}
156+
${forceRls}
157+
${replicaSql}
158+
${commentSql}
159+
${schemaSql}
160+
${nameSql}
161+
COMMIT;`
162+
{
163+
const { error } = await this.query(sql)
164+
if (error) {
165+
return { data: null, error }
166+
}
167+
}
168+
return await this.retrieve({ id })
169+
}
170+
171+
async remove(id: number, { cascade = false } = {}): Promise<PostgresMetaResult<PostgresTable>> {
172+
const { data: table, error } = await this.retrieve({ id })
173+
if (error) {
174+
return { data: null, error }
175+
}
176+
const sql = `DROP TABLE ${ident(table!.schema)}.${ident(table!.name)} ${
177+
cascade ? 'CASCADE' : 'RESTRICT'
178+
};`
179+
{
180+
const { error } = await this.query(sql)
181+
if (error) {
182+
return { data: null, error }
183+
}
184+
}
185+
return { data: table!, error: null }
186+
}
187+
}
188+
189+
const enrichedTriggersSql = `
190+
WITH triggers AS (${triggersSql})
191+
SELECT
192+
*
193+
FROM triggers`

src/lib/db.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ import { PostgresMetaResult } from './types'
33

44
types.setTypeParser(20, parseInt)
55

6-
export const init: (
7-
config: PoolConfig
8-
) => {
6+
export const init: (config: PoolConfig) => {
97
query: (sql: string) => Promise<PostgresMetaResult<any>>
108
end: () => Promise<void>
119
} = (config) => {

src/lib/sql/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ export const relationshipsSql = readFileSync(resolve(__dirname, 'relationships.s
1313
export const rolesSql = readFileSync(resolve(__dirname, 'roles.sql'), 'utf-8')
1414
export const schemasSql = readFileSync(resolve(__dirname, 'schemas.sql'), 'utf-8')
1515
export const tablesSql = readFileSync(resolve(__dirname, 'tables.sql'), 'utf-8')
16+
export const triggersSql = readFileSync(resolve(__dirname, 'triggers.sql'), 'utf-8')
1617
export const typesSql = readFileSync(resolve(__dirname, 'types.sql'), 'utf-8')
1718
export const versionSql = readFileSync(resolve(__dirname, 'version.sql'), 'utf-8')

src/lib/sql/triggers.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
select
2+
trigger_schema as "schema",
3+
trigger_name as "name",
4+
trigger_catalog as "catalog"
5+
from
6+
information_schema.triggers

src/server/routes/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export default async (fastify: FastifyInstance) => {
2626
fastify.register(require('./query'), { prefix: '/query' })
2727
fastify.register(require('./schemas'), { prefix: '/schemas' })
2828
fastify.register(require('./tables'), { prefix: '/tables' })
29+
fastify.register(require('./triggers'), { prefix: '/triggers' })
2930
fastify.register(require('./types'), { prefix: '/types' })
3031
fastify.register(require('./roles'), { prefix: '/roles' })
3132
}

src/server/routes/triggers.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { FastifyInstance } from 'fastify'
2+
import { PostgresMeta } from '../../lib'
3+
4+
export default async (fastify: FastifyInstance) => {
5+
fastify.get<{
6+
Headers: { pg: string }
7+
Querystring: {
8+
include_system_schemas?: string
9+
}
10+
}>('/', async (request, reply) => {
11+
const connectionString = request.headers.pg
12+
const includeSystemSchemas = request.query.include_system_schemas === 'true'
13+
14+
15+
const pgMeta = new PostgresMeta({ connectionString, max: 1 })
16+
const { data, error } = await pgMeta.triggers.list({ includeSystemSchemas })
17+
await pgMeta.end()
18+
if (error) {
19+
request.log.error(JSON.stringify({ error, req: request.body }))
20+
reply.code(500)
21+
return { error: error.message }
22+
}
23+
24+
return data
25+
})
26+
27+
fastify.get<{
28+
Headers: { pg: string }
29+
Params: {
30+
id: string
31+
}
32+
}>('/:id(\\d+)', async (request, reply) => {
33+
// const connectionString = request.headers.pg
34+
// const id = Number(request.params.id)
35+
36+
// const pgMeta = new PostgresMeta({ connectionString, max: 1 })
37+
// const { data, error } = await pgMeta.tables.retrieve({ id })
38+
// await pgMeta.end()
39+
// if (error) {
40+
// request.log.error({ error, req: request.body })
41+
// reply.code(404)
42+
// return { error: error.message }
43+
// }
44+
45+
return { hello: 'world' }
46+
})
47+
48+
fastify.post<{
49+
Headers: { pg: string }
50+
Body: any
51+
}>('/', async (request, reply) => {
52+
const connectionString = request.headers.pg
53+
54+
const pgMeta = new PostgresMeta({ connectionString, max: 1 })
55+
const { data, error } = await pgMeta.tables.create(request.body)
56+
await pgMeta.end()
57+
if (error) {
58+
request.log.error(JSON.stringify({ error, req: request.body }))
59+
reply.code(400)
60+
return { error: error.message }
61+
}
62+
63+
return data
64+
})
65+
66+
fastify.patch<{
67+
Headers: { pg: string }
68+
Params: {
69+
id: string
70+
}
71+
Body: any
72+
}>('/:id(\\d+)', async (request, reply) => {
73+
const connectionString = request.headers.pg
74+
const id = Number(request.params.id)
75+
76+
const pgMeta = new PostgresMeta({ connectionString, max: 1 })
77+
const { data, error } = await pgMeta.tables.update(id, request.body)
78+
await pgMeta.end()
79+
if (error) {
80+
request.log.error(JSON.stringify({ error, req: request.body }))
81+
reply.code(400)
82+
if (error.message.startsWith('Cannot find')) reply.code(404)
83+
return { error: error.message }
84+
}
85+
86+
return data
87+
})
88+
89+
fastify.delete<{
90+
Headers: { pg: string }
91+
Params: {
92+
id: string
93+
}
94+
Querystring: {
95+
cascade?: string
96+
}
97+
}>('/:id(\\d+)', async (request, reply) => {
98+
const connectionString = request.headers.pg
99+
const id = Number(request.params.id)
100+
const cascade = request.query.cascade === 'true'
101+
102+
const pgMeta = new PostgresMeta({ connectionString, max: 1 })
103+
const { data, error } = await pgMeta.tables.remove(id, { cascade })
104+
await pgMeta.end()
105+
if (error) {
106+
request.log.error(JSON.stringify({ error, req: request.body }))
107+
reply.code(400)
108+
if (error.message.startsWith('Cannot find')) reply.code(404)
109+
return { error: error.message }
110+
}
111+
112+
return data
113+
})
114+
}

0 commit comments

Comments
 (0)