Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add conditions to errors, export POSTGRES_ERRORS_BY_CODE and P… #2611

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/pg-protocol/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
"@types/chai": "^4.2.7",
"@types/mocha": "^5.2.7",
"@types/node": "^12.12.21",
"@types/node-fetch": "2.5.12",
"chai": "^4.2.0",
"chunky": "^0.0.0",
"mocha": "^7.1.2",
"node-fetch": "2.6.2",
"ts-node": "^8.5.4",
"typescript": "^4.0.3"
},
Expand All @@ -20,7 +22,8 @@
"build": "tsc",
"build:watch": "tsc --watch",
"prepublish": "yarn build",
"pretest": "yarn build"
"pretest": "yarn build",
"refresh-error-codes": "ts-node scripts/populate-error-codes"
},
"repository": {
"type": "git",
Expand Down
32 changes: 32 additions & 0 deletions packages/pg-protocol/scripts/populate-error-codes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import fetch from 'node-fetch'
import { writeFileSync } from 'fs'
import { join } from 'path'

const ErrorRegexp = /(.*[A-Z0-9]{5}) +[A-Z] +ERRCODE_([A-Z_]+) .*/g

const main = async () => {
const result = await fetch(
'https://raw.githubusercontent.com/postgres/postgres/master/src/backend/utils/errcodes.txt'
).then((res) => res.text())
const codes = result
.split('\n')
.filter((x) => x.match(ErrorRegexp))
.map((line) => {
const parsedLine = line.replace(ErrorRegexp, ($0, $1, $2) => `${$1}:${$2}`).split(':')
return { code: parsedLine[0], condition: parsedLine[1] }
})
let exportString = `export const POSTGRES_ERRORS_BY_CODE = {\n${codes
.map(({ code, condition }) => ` ${wrapInQuotesIfNeeded(code)}: '${condition}'`)
.join(',\n')},\n} as const\n\n`
exportString = `${exportString}export const POSTGRES_ERRORS = {\n${codes
.map(({ code, condition }) => ` ${condition}: '${code}'`)
.join(',\n')},\n} as const\n`
exportString += `\nexport type PgErrorCondition = keyof typeof POSTGRES_ERRORS\nexport type PgErrorCode = typeof POSTGRES_ERRORS[PgErrorCondition]\n`
writeFileSync(join(__dirname, '..', 'src', 'postgres-error-codes.ts'), exportString)
}

const wrapInQuotesIfNeeded = (str: string) => {
return str.match(/^[0-9]/) ? `'${str}'` : str
}

main()
5 changes: 3 additions & 2 deletions packages/pg-protocol/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { BackendMessage, DatabaseError } from './messages'
import { DatabaseError } from './messages'
import { serialize } from './serializer'
import { Parser, MessageCallback } from './parser'
import { POSTGRES_ERRORS_BY_CODE, POSTGRES_ERRORS } from './postgres-error-codes'

export function parse(stream: NodeJS.ReadableStream, callback: MessageCallback): Promise<void> {
const parser = new Parser()
stream.on('data', (buffer: Buffer) => parser.parse(buffer, callback))
return new Promise((resolve) => stream.on('end', () => resolve()))
}

export { serialize, DatabaseError }
export { serialize, DatabaseError, POSTGRES_ERRORS_BY_CODE, POSTGRES_ERRORS }
62 changes: 59 additions & 3 deletions packages/pg-protocol/src/messages.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { PgErrorCode, PgErrorCondition } from './postgres-error-codes'

export type Mode = 'text' | 'binary'

export type MessageName =
Expand Down Expand Up @@ -77,7 +79,8 @@ export const copyDone: BackendMessage = {
interface NoticeOrError {
message: string | undefined
severity: string | undefined
code: string | undefined
code: PgErrorCode | undefined
condition: PgErrorCondition | undefined
detail: string | undefined
hint: string | undefined
position: string | undefined
Expand All @@ -95,21 +98,73 @@ interface NoticeOrError {
}

export class DatabaseError extends Error implements NoticeOrError {
/**
* The field contents are ERROR, FATAL, or PANIC (in an error message), or WARNING, NOTICE, DEBUG, INFO, or LOG (in a notice message), or a localized translation of one of these.
*/
public severity: string | undefined
public code: string | undefined
/**
* The SQLSTATE code for the error. See [PostgreSQL Error Codes](https://www.postgresql.org/docs/current/errcodes-appendix.html)
*/
public code: PgErrorCode | undefined
/**
* The condition name matching the code from [PostgreSQL Error Codes](https://www.postgresql.org/docs/current/errcodes-appendix.html)
*/
public condition: PgErrorCondition | undefined
/**
* An optional secondary error message carrying more detail about the problem
*/
public detail: string | undefined
/**
* An optional suggestion what to do about the problem. This is intended to differ from Detail in that it offers advice (potentially inappropriate) rather than hard facts
*/
public hint: string | undefined
/**
* Indicates an error cursor position as an index into the original query string. The first character has index 1
*/
public position: string | undefined
/**
* Same as the position field, but it is used when the cursor position refers to an internally generated command rather than the one submitted by the client
*/
public internalPosition: string | undefined
/**
* The text of a failed internally-generated command. This could be, for example, a SQL query issued by a PL/pgSQL function
*/
public internalQuery: string | undefined
/**
* An indication of the context in which the error occurred. Presently this includes a call stack traceback of active procedural language functions and internally-generated queries
*/
public where: string | undefined
/**
* If the error was associated with a specific database object, the name of the schema containing that object, if any
*/
public schema: string | undefined
/**
* If the error was associated with a specific table, the name of the table
*/
public table: string | undefined
/**
* If the error was associated with a specific table column, the name of the column
*/
public column: string | undefined
/**
* If the error was associated with a specific data type, the name of the data type
*/
public dataType: string | undefined
/**
* If the error was associated with a specific constraint, the name of the constraint
*/
public constraint: string | undefined
/**
* The file name of the source-code location where the error was reported
*/
public file: string | undefined
/**
* The line number of the source-code location where the error was reported
*/
public line: string | undefined
/**
* The name of the source-code routine reporting the error
*/
public routine: string | undefined
constructor(message: string, public readonly length: number, public readonly name: MessageName) {
super(message)
Expand Down Expand Up @@ -212,7 +267,8 @@ export class NoticeMessage implements BackendMessage, NoticeOrError {
constructor(public readonly length: number, public readonly message: string | undefined) {}
public readonly name = 'notice'
public severity: string | undefined
public code: string | undefined
public code: PgErrorCode | undefined
public condition: PgErrorCondition | undefined
public detail: string | undefined
public hint: string | undefined
public position: string | undefined
Expand Down
4 changes: 3 additions & 1 deletion packages/pg-protocol/src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
} from './messages'
import { BufferReader } from './buffer-reader'
import assert from 'assert'
import { PgErrorCode, POSTGRES_ERRORS_BY_CODE } from './postgres-error-codes'

// every message is prefixed with a single bye
const CODE_LENGTH = 1
Expand Down Expand Up @@ -369,7 +370,8 @@ export class Parser {
name === 'notice' ? new NoticeMessage(length, messageValue) : new DatabaseError(messageValue, length, name)

message.severity = fields.S
message.code = fields.C
message.code = fields.C as PgErrorCode
message.condition = POSTGRES_ERRORS_BY_CODE[message.code]
message.detail = fields.D
message.hint = fields.H
message.position = fields.P
Expand Down
Loading