Skip to content

Commit f6f505c

Browse files
committed
feat: add extra error fields
1 parent 42649b7 commit f6f505c

File tree

5 files changed

+95
-5
lines changed

5 files changed

+95
-5
lines changed

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"pg": "^8.7.1",
6767
"pg-connection-string": "^2.5.0",
6868
"pg-format": "^1.0.4",
69+
"pg-protocol": "^1.6.0",
6970
"pgsql-parser": "^13.3.0",
7071
"pino": "^8.6.1",
7172
"postgres-array": "^3.0.1",

src/lib/db.ts

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pg, { PoolConfig } from 'pg'
2+
import { DatabaseError } from 'pg-protocol'
23
import { parse as parseArray } from 'postgres-array'
34
import { PostgresMetaResult } from './types.js'
45

@@ -76,8 +77,76 @@ export const init: (config: PoolConfig) => {
7677
res = res.reverse().find((x) => x.rows.length !== 0) ?? { rows: [] }
7778
}
7879
return { data: res.rows, error: null }
79-
} catch (e: any) {
80-
return { data: null, error: { message: e.message } }
80+
} catch (error: any) {
81+
if (error instanceof DatabaseError) {
82+
// Roughly based on:
83+
// - https://github.com/postgres/postgres/blob/fc4089f3c65a5f1b413a3299ba02b66a8e5e37d0/src/interfaces/libpq/fe-protocol3.c#L1018
84+
// - https://github.com/brianc/node-postgres/blob/b1a8947738ce0af004cb926f79829bb2abc64aa6/packages/pg/lib/native/query.js#L33
85+
let formattedError = ''
86+
{
87+
if (error.severity) {
88+
formattedError += `${error.severity}: `
89+
}
90+
if (error.code) {
91+
formattedError += `${error.code}: `
92+
}
93+
if (error.message) {
94+
formattedError += error.message
95+
}
96+
formattedError += '\n'
97+
if (error.position) {
98+
// error.position is 1-based
99+
const position = Number(error.position) - 1
100+
101+
let line = ''
102+
let lineNumber = 0
103+
let lineOffset = 0
104+
105+
const lines = sql.split('\n')
106+
let currentOffset = 0
107+
for (let i = 0; i < lines.length; i++) {
108+
if (currentOffset + lines[i].length > position) {
109+
line = lines[i]
110+
lineNumber = i + 1 // 1-based
111+
lineOffset = position - currentOffset
112+
break
113+
}
114+
currentOffset += lines[i].length + 1 // 1 extra offset for newline
115+
}
116+
formattedError += `LINE ${lineNumber}: ${line}
117+
${' '.repeat(5 + lineNumber.toString().length + 2 + lineOffset)}^
118+
`
119+
}
120+
if (error.detail) {
121+
formattedError += `DETAIL: ${error.detail}
122+
`
123+
}
124+
if (error.hint) {
125+
formattedError += `HINT: ${error.hint}
126+
`
127+
}
128+
if (error.internalQuery) {
129+
formattedError += `QUERY: ${error.internalQuery}
130+
`
131+
}
132+
if (error.where) {
133+
formattedError += `CONTEXT: ${error.where}
134+
`
135+
}
136+
}
137+
138+
return {
139+
data: null,
140+
error: {
141+
...error,
142+
// error.message is non-enumerable
143+
message: error.message,
144+
formattedError,
145+
},
146+
}
147+
}
148+
149+
return { data: null, error: { message: error.message } }
81150
}
82151
},
83152

src/lib/types.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Static, Type } from '@sinclair/typebox'
2+
import { DatabaseError } from 'pg-protocol'
23
import { Options as PrettierOptions } from 'prettier'
34

45
export interface FormatterOptions extends PrettierOptions {}
@@ -10,9 +11,7 @@ export interface PostgresMetaOk<T> {
1011

1112
export interface PostgresMetaErr {
1213
data: null
13-
error: {
14-
message: string
15-
}
14+
error: Partial<DatabaseError> & { message: string; formattedError?: string }
1615
}
1716

1817
export type PostgresMetaResult<T> = PostgresMetaOk<T> | PostgresMetaErr

test/lib/query.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,27 @@ test('error', async () => {
2727
{
2828
"data": null,
2929
"error": {
30+
"code": "42P01",
31+
"column": undefined,
32+
"constraint": undefined,
33+
"dataType": undefined,
34+
"detail": undefined,
35+
"file": "tablecmds.c",
36+
"formattedError": "ERROR: 42P01: table "missing_table" does not exist
37+
",
38+
"hint": undefined,
39+
"internalPosition": undefined,
40+
"internalQuery": undefined,
41+
"length": 108,
42+
"line": "1259",
3043
"message": "table "missing_table" does not exist",
44+
"name": "error",
45+
"position": undefined,
46+
"routine": "DropErrorMsgNonExistent",
47+
"schema": undefined,
48+
"severity": "ERROR",
49+
"table": undefined,
50+
"where": undefined,
3151
},
3252
}
3353
`)

0 commit comments

Comments
 (0)