{"record":{"id":"aeb98431fd93d1b3","repo":"Budibase/budibase","slug":"readablemessage-aeb984","errorCode":null,"errorMessage":"${readableMessage}","messagePattern":"\\$\\{readableMessage\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/server/src/integrations/mysql.ts","lineNumber":281,"sourceCode":"      disableCoercion: false,\n    }\n  ): Promise<any[] | any> {\n    try {\n      if (opts?.connect) {\n        await this.connect()\n      }\n      const baseBindings = query.bindings || []\n      const bindings = opts?.disableCoercion\n        ? baseBindings\n        : bindingTypeCoerce(baseBindings)\n      this.log(query.sql, bindings)\n      // Node MySQL is callback based, so we must wrap our call in a promise\n      const response = await this.client!.query(query.sql, bindings)\n      return response[0]\n    } catch (err: any) {\n      let readableMessage = getReadableErrorMessage(SourceName.MYSQL, err.errno)\n      if (readableMessage) {\n        throw new Error(readableMessage, { cause: err })\n      } else {\n        throw err\n      }\n    } finally {\n      if (opts?.connect && this.client) {\n        await this.disconnect()\n      }\n    }\n  }\n\n  async buildSchema(\n    datasourceId: string,\n    entities: Record<string, Table>\n  ): Promise<Schema> {\n    const tables: { [key: string]: Table } = {}\n    await this.connect()\n\n    try {","sourceCodeStart":263,"sourceCodeEnd":299,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/integrations/mysql.ts#L263-L299","documentation":"MySQL integration's internalQuery() wraps the callback-based mysql client in a promise and maps driver errors via getReadableErrorMessage(SourceName.MYSQL, err.errno). When errno matches a known MySQL error code, it throws a new Error with the readable message and attaches the original driver error as cause; otherwise it rethrows the original error untouched.","triggerScenarios":"Any MySQL error during query.sql execution whose err.errno is in the mapping — classically ER_DUP_ENTRY (1062) duplicate key, ER_NO_SUCH_TABLE (1146), ER_ACCESS_DENIED_ERROR (1045), syntax errors (1064), and connection losses.","commonSituations":"Inserting a duplicate unique key; querying a table that doesn't exist or is in another database; wrong credentials or host not whitelisted for the MySQL user; malformed SQL from dynamic query building; connection dropped by wait_timeout.","solutions":["Read the readableMessage — it is the mapped MySQL error (e.g. duplicate entry) — and fix the SQL or data","For ER_DUP_ENTRY, deduplicate or use INSERT ... ON DUPLICATE KEY UPDATE / INSERT IGNORE","For access-denied errors, verify credentials and grant privileges / allow the host","Inspect error.cause for the full driver error including the raw errno and SQL state"],"exampleFix":"// before: duplicate key on re-insert\nINSERT INTO users (email) VALUES ('a@example.com')\n// after\nINSERT INTO users (email) VALUES ('a@example.com') ON DUPLICATE KEY UPDATE email = VALUES(email)","handlingStrategy":"try-catch","validationCode":"// pre-flight: ensure table exists before inserting\nconst tables = await ds.getTableNames?.() ?? []\nif (!tables.includes(tableName)) {\n  throw new Error(`Table ${tableName} does not exist in MySQL database`)\n}","typeGuard":"function hasMysqlErrno(err: unknown): err is { errno: number; message: string } {\n  return typeof err === \"object\" && err !== null && typeof (err as any).errno === \"number\"\n}","tryCatchPattern":"try {\n  return await ds.query(sqlQuery)\n} catch (err) {\n  const cause = (err as any).cause\n  if (cause?.errno === 1062) {\n    // duplicate entry: retry with a different key or upsert\n  } else if (cause?.errno === 1146) {\n    // missing table: fix schema\n  } else if (cause?.errno === 1045) {\n    // access denied: fix credentials\n  }\n  throw err\n}","preventionTips":["Design unique keys and use ON DUPLICATE KEY UPDATE for idempotent inserts","Verify MySQL credentials, privileges and host allowlist before deploying","Use parameterized bindings to avoid syntax errors from interpolation","Keep connections alive or reconnect after wait_timeout drops; inspect err.cause.errno for specifics"],"tags":["mysql","query","errno","error-mapping"],"backgroundTag":"sql-query-error","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}