diff --git a/packages/server/src/integrations/base/errorMapping.ts b/packages/server/src/integrations/base/errorMapping.ts new file mode 100644 index 0000000000..592d368cc0 --- /dev/null +++ b/packages/server/src/integrations/base/errorMapping.ts @@ -0,0 +1,32 @@ +import { SourceName } from "@budibase/types" + +const mysqlErrorMessages: Record = { + 1045: "Access denied for the specified user. User does not have the necessary privileges or the provided credentials are incorrect. Please verify the credentials, and ensure that the user has appropriate permissions.", + 1049: "The specified database does not exist. Please verify that the database name is correct.", +} + +const postgresErrorMessages: Record = { + "23505": "Duplicate key value violates unique constraint.", + "23502": "Null value not allowed for a column.", + "23503": "Foreign key violation.", + "23514": "Check constraint violation.", + "42703": "Undefined column.", + "42P01": "Undefined table.", +} + +const sqlServerErrorMessages: Record = { + 547: "The INSERT statement conflicted with the FOREIGN KEY constraint.", + 2601: "Cannot insert duplicate key row in object.", + 515: "Cannot insert the value NULL into column.", +} + +export const getReadableErrorMessage = (type: string, errno: number) => { + switch (type) { + case SourceName.MYSQL: + return mysqlErrorMessages[errno] + case SourceName.POSTGRES: + return postgresErrorMessages[errno] + case SourceName.SQL_SERVER: + return sqlServerErrorMessages[errno] + } +} diff --git a/packages/server/src/integrations/microsoftSqlServer.ts b/packages/server/src/integrations/microsoftSqlServer.ts index f11cbd60b0..d4efacea36 100644 --- a/packages/server/src/integrations/microsoftSqlServer.ts +++ b/packages/server/src/integrations/microsoftSqlServer.ts @@ -10,6 +10,7 @@ import { DatasourcePlus, DatasourceFeature, ConnectionInfo, + SourceName, } from "@budibase/types" import { getSqlQuery, @@ -20,6 +21,7 @@ import { } from "./utils" import Sql from "./base/sql" import { MSSQLTablesResponse, MSSQLColumn } from "./base/types" +import { getReadableErrorMessage } from "./base/errorMapping" import sqlServer from "mssql" const DEFAULT_SCHEMA = "dbo" @@ -252,9 +254,16 @@ class SqlServerIntegration extends Sql implements DatasourcePlus { ? `${query.sql}; SELECT SCOPE_IDENTITY() AS id;` : query.sql return await request.query(sql) - } catch (err) { - // @ts-ignore - throw new Error(err) + } catch (err: any) { + let readableMessage = getReadableErrorMessage( + SourceName.SQL_SERVER, + err.number + ) + if (readableMessage) { + throw new Error(readableMessage) + } else { + throw new Error(err.message as string) + } } } diff --git a/packages/server/src/integrations/mysql.ts b/packages/server/src/integrations/mysql.ts index 22ad5db1b8..a01f6b71f9 100644 --- a/packages/server/src/integrations/mysql.ts +++ b/packages/server/src/integrations/mysql.ts @@ -9,6 +9,7 @@ import { DatasourcePlus, DatasourceFeature, ConnectionInfo, + SourceName, } from "@budibase/types" import { getSqlQuery, @@ -21,7 +22,7 @@ import dayjs from "dayjs" import { NUMBER_REGEX } from "../utilities" import Sql from "./base/sql" import { MySQLColumn } from "./base/types" - +import { getReadableErrorMessage } from "./base/errorMapping" import mysql from "mysql2/promise" interface MySQLConfig extends mysql.ConnectionOptions { @@ -175,7 +176,12 @@ class MySQLIntegration extends Sql implements DatasourcePlus { ) response.connected = result?.checkRes == 2 } catch (e: any) { - response.error = e.message as string + let readableMessage = getReadableErrorMessage(SourceName.MYSQL, e.errno) + if (readableMessage) { + response.error = readableMessage + } else { + response.error = e.message as string + } } return response } @@ -214,6 +220,13 @@ class MySQLIntegration extends Sql implements DatasourcePlus { // Node MySQL is callback based, so we must wrap our call in a promise const response = await this.client!.query(query.sql, bindings) return response[0] + } catch (err: any) { + let readableMessage = getReadableErrorMessage(SourceName.MYSQL, err.errno) + if (readableMessage) { + throw new Error(readableMessage) + } else { + throw new Error(err.message as string) + } } finally { if (opts?.connect && this.client) { await this.disconnect() diff --git a/packages/server/src/integrations/postgres.ts b/packages/server/src/integrations/postgres.ts index 0b34798f6b..b16f5b858b 100644 --- a/packages/server/src/integrations/postgres.ts +++ b/packages/server/src/integrations/postgres.ts @@ -9,6 +9,7 @@ import { DatasourcePlus, DatasourceFeature, ConnectionInfo, + SourceName, } from "@budibase/types" import { getSqlQuery, @@ -22,6 +23,7 @@ import { PostgresColumn } from "./base/types" import { escapeDangerousCharacters } from "../utilities" import { Client, ClientConfig, types } from "pg" +import { getReadableErrorMessage } from "./base/errorMapping" import { exec } from "child_process" import { storeTempFile } from "../utilities/fileSystem" @@ -187,6 +189,7 @@ class PostgresIntegration extends Sql implements DatasourcePlus { await this.openConnection() response.connected = true } catch (e: any) { + console.log(e) response.error = e.message as string } finally { await this.closeConnection() @@ -245,10 +248,17 @@ class PostgresIntegration extends Sql implements DatasourcePlus { } try { return await client.query(query.sql, query.bindings || []) - } catch (err) { + } catch (err: any) { await this.closeConnection() - // @ts-ignore - throw new Error(err) + let readableMessage = getReadableErrorMessage( + SourceName.POSTGRES, + err.code + ) + if (readableMessage) { + throw new Error(readableMessage) + } else { + throw new Error(err.message as string) + } } finally { if (close) { await this.closeConnection()