Fast MariaDB and MySQL connector for Node.js with Promise and callback APIs
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Structured error handling with detailed SQL error information, error codes, and connection state management for robust application development. The MariaDB connector provides comprehensive error reporting and recovery mechanisms.
Main error class for all database-related errors with detailed diagnostic information.
interface SqlError extends Error {
/** Error name (always 'SqlError') */
name: string;
/** Human-readable error message */
message: string;
/** Original SQL error message from server */
sqlMessage: string | null;
/** Deprecated alias for sqlMessage */
text: string | null;
/** SQL command that caused the error */
sql: string | null;
/** MySQL/MariaDB error number */
errno: number;
/** SQL state code (SQLSTATE) */
sqlState?: string | null;
/** Error code string */
code: string | null;
/** Whether error is fatal to connection */
fatal: boolean;
}Constructor for creating SqlError instances with detailed error information.
interface SqlErrorConstructor extends ErrorConstructor {
new (
msg: string,
sql?: string,
fatal?: boolean,
info?: { threadId?: number },
sqlState?: string | null,
errno?: number,
additionalStack?: string,
addHeader?: boolean,
cause?: unknown
): SqlError;
readonly prototype: SqlError;
}
declare const SqlError: SqlErrorConstructor;Common error categories and their characteristics:
Connection Errors:
ER_CONNECTION_ALREADY_CLOSED (45001) - Connection was already closedER_SOCKET_UNEXPECTED_CLOSE (45007) - Socket closed unexpectedlyER_SOCKET_TIMEOUT (45009) - Socket operation timed outER_PING_TIMEOUT (45010) - Connection ping timed outAuthentication Errors:
ER_AUTHENTICATION_PLUGIN_NOT_SUPPORTED (45016) - Auth plugin not supportedER_AUTHENTICATION_BAD_PACKET (45017) - Bad authentication packetER_AUTHENTICATION_PLUGIN_ERR (45018) - Authentication plugin errorPool Errors:
ER_POOL_ALREADY_CLOSED (45011) - Pool was already closedER_GET_CONNECTION_TIMEOUT (45012) - Connection acquisition timeoutER_POOL_CONNECTION_CLOSED (45013) - Pool connection was closedProtocol Errors:
ER_UNEXPECTED_PACKET (45019) - Unexpected packet receivedER_BAD_PARAMETER_VALUE (45020) - Invalid parameter valueER_LOCAL_INFILE_DISABLED (45022) - LOAD LOCAL INFILE disabled// Basic try-catch error handling
try {
const result = await connection.query("SELECT * FROM users WHERE id = ?", [userId]);
return result;
} catch (error) {
if (error instanceof SqlError) {
console.error('SQL Error:', {
code: error.code,
errno: error.errno,
sqlState: error.sqlState,
message: error.sqlMessage,
sql: error.sql,
fatal: error.fatal
});
// Handle specific error types
if (error.errno === 1062) { // Duplicate entry
throw new Error('User already exists');
} else if (error.errno === 1054) { // Unknown column
throw new Error('Invalid column name in query');
}
}
throw error;
}class DatabaseService {
private isRetryableError(error: SqlError): boolean {
// Network/connection errors that can be retried
const retryableCodes = [
'ECONNRESET',
'ENOTFOUND',
'ETIMEDOUT',
'ECONNREFUSED'
];
const retryableErrno = [
2013, // Lost connection during query
2006, // MySQL server has gone away
1205, // Lock wait timeout
1213 // Deadlock found
];
return retryableCodes.includes(error.code || '') ||
retryableErrno.includes(error.errno);
}
private isConnectionError(error: SqlError): boolean {
return error.fatal ||
error.errno >= 45001 && error.errno <= 45010;
}
async executeWithRetry<T>(operation: () => Promise<T>, maxRetries = 3): Promise<T> {
let lastError: SqlError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as SqlError;
if (!this.isRetryableError(lastError) || attempt === maxRetries) {
throw lastError;
}
console.warn(`Attempt ${attempt} failed, retrying:`, lastError.message);
await this.delay(Math.pow(2, attempt) * 1000); // Exponential backoff
}
}
throw lastError!;
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}class ConnectionManager {
private connection: Connection | null = null;
private pool: Pool;
constructor(config: PoolConfig) {
this.pool = mariadb.createPool(config);
// Handle pool errors
this.pool.on('error', (err: SqlError) => {
console.error('Pool error:', err);
if (this.isPoolFatalError(err)) {
this.recreatePool();
}
});
}
private isPoolFatalError(error: SqlError): boolean {
return error.errno === 45011; // ER_POOL_ALREADY_CLOSED
}
private async recreatePool(): Promise<void> {
try {
await this.pool.end();
} catch (error) {
console.warn('Error closing old pool:', error);
}
this.pool = mariadb.createPool(this.pool.config);
}
async getConnection(): Promise<PoolConnection> {
try {
return await this.pool.getConnection();
} catch (error) {
if (error instanceof SqlError && error.errno === 45012) {
// Connection acquisition timeout
throw new Error('Database is overloaded - try again later');
}
throw error;
}
}
}async function safeTransaction<T>(
connection: Connection,
operation: (conn: Connection) => Promise<T>
): Promise<T> {
const savepoint = `sp_${Date.now()}_${Math.random().toString(36).substring(2)}`;
try {
await connection.beginTransaction();
// Create savepoint for nested error handling
await connection.query(`SAVEPOINT ${savepoint}`);
const result = await operation(connection);
await connection.commit();
return result;
} catch (error) {
try {
if (error instanceof SqlError) {
if (error.errno === 1213) { // Deadlock
console.warn('Deadlock detected, rolling back transaction');
await connection.rollback();
throw new Error('Transaction failed due to deadlock - please retry');
} else if (error.errno === 1205) { // Lock timeout
console.warn('Lock timeout, rolling back to savepoint');
await connection.query(`ROLLBACK TO SAVEPOINT ${savepoint}`);
throw new Error('Transaction timed out waiting for lock');
} else {
// General rollback
await connection.rollback();
}
} else {
await connection.rollback();
}
} catch (rollbackError) {
console.error('Error during rollback:', rollbackError);
// Connection may be in bad state
if (connection.isValid()) {
try {
await connection.reset();
} catch (resetError) {
console.error('Error resetting connection:', resetError);
}
}
}
throw error;
}
}class DatabaseLogger {
private logger: any; // Your logging library
constructor(logger: any) {
this.logger = logger;
}
logError(error: SqlError, context?: any): void {
const errorData = {
type: 'sql_error',
code: error.code,
errno: error.errno,
sqlState: error.sqlState,
message: error.sqlMessage,
sql: error.sql,
fatal: error.fatal,
context: context
};
if (error.fatal || this.isCriticalError(error)) {
this.logger.error('Critical database error', errorData);
// Send alert to monitoring system
this.sendAlert('critical_db_error', errorData);
} else {
this.logger.warn('Database error', errorData);
}
}
private isCriticalError(error: SqlError): boolean {
const criticalErrorNumbers = [
1040, // Too many connections
1203, // User has more than max_user_connections
1226, // User has exceeded max_updates_per_hour
2006, // MySQL server has gone away
2013 // Lost connection during query
];
return criticalErrorNumbers.includes(error.errno);
}
private sendAlert(type: string, data: any): void {
// Integration with monitoring system
// e.g., Datadog, New Relic, custom alerting
}
}// Application-specific error classes
class DatabaseError extends Error {
constructor(
message: string,
public readonly originalError: SqlError,
public readonly operation: string
) {
super(message);
this.name = 'DatabaseError';
}
get isRetryable(): boolean {
return [1205, 1213, 2006, 2013].includes(this.originalError.errno);
}
get isConnectionIssue(): boolean {
return this.originalError.fatal ||
this.originalError.errno >= 45001 && this.originalError.errno <= 45010;
}
}
class ValidationError extends DatabaseError {
constructor(originalError: SqlError, field?: string) {
const message = field
? `Validation failed for field '${field}': ${originalError.sqlMessage}`
: `Validation failed: ${originalError.sqlMessage}`;
super(message, originalError, 'validation');
this.name = 'ValidationError';
}
}
class DuplicateEntryError extends DatabaseError {
constructor(originalError: SqlError, duplicateKey?: string) {
const message = duplicateKey
? `Duplicate entry for key '${duplicateKey}'`
: 'Duplicate entry detected';
super(message, originalError, 'insert');
this.name = 'DuplicateEntryError';
}
}
// Error factory
function createApplicationError(sqlError: SqlError, operation: string): DatabaseError {
switch (sqlError.errno) {
case 1062: // Duplicate entry
return new DuplicateEntryError(sqlError);
case 1452: // Foreign key constraint fails
case 1048: // Column cannot be null
case 1406: // Data too long for column
return new ValidationError(sqlError);
default:
return new DatabaseError(sqlError.sqlMessage || sqlError.message, sqlError, operation);
}
}class ResilientDatabaseService {
private pool: Pool;
private circuitBreaker: CircuitBreaker;
constructor(config: PoolConfig) {
this.pool = mariadb.createPool(config);
this.circuitBreaker = new CircuitBreaker({
failureThreshold: 5,
resetTimeout: 30000
});
}
async query<T>(sql: string, values?: any): Promise<T> {
return this.circuitBreaker.execute(async () => {
try {
return await this.pool.query(sql, values);
} catch (error) {
if (error instanceof SqlError) {
// Transform to application error
throw createApplicationError(error, 'query');
}
throw error;
}
});
}
async healthCheck(): Promise<boolean> {
try {
await this.pool.query('SELECT 1');
return true;
} catch (error) {
console.error('Database health check failed:', error);
return false;
}
}
}
// Simple circuit breaker implementation
class CircuitBreaker {
private failures = 0;
private lastFailureTime = 0;
private state: 'closed' | 'open' | 'half-open' = 'closed';
constructor(
private options: {
failureThreshold: number;
resetTimeout: number;
}
) {}
async execute<T>(operation: () => Promise<T>): Promise<T> {
if (this.state === 'open') {
if (Date.now() - this.lastFailureTime > this.options.resetTimeout) {
this.state = 'half-open';
} else {
throw new Error('Circuit breaker is OPEN');
}
}
try {
const result = await operation();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
private onSuccess(): void {
this.failures = 0;
this.state = 'closed';
}
private onFailure(): void {
this.failures++;
this.lastFailureTime = Date.now();
if (this.failures >= this.options.failureThreshold) {
this.state = 'open';
}
}
}// MySQL/MariaDB Standard Error Codes
const MYSQL_ERRORS = {
// Constraint violations
DUPLICATE_ENTRY: 1062,
FOREIGN_KEY_CONSTRAINT: 1452,
COLUMN_CANNOT_BE_NULL: 1048,
DATA_TOO_LONG: 1406,
// Connection issues
TOO_MANY_CONNECTIONS: 1040,
ACCESS_DENIED: 1045,
UNKNOWN_DATABASE: 1049,
TABLE_DOESNT_EXIST: 1146,
COLUMN_DOESNT_EXIST: 1054,
// Lock and transaction issues
LOCK_WAIT_TIMEOUT: 1205,
DEADLOCK_FOUND: 1213,
// Server issues
SERVER_GONE_AWAY: 2006,
LOST_CONNECTION: 2013,
SERVER_SHUTDOWN: 1053
} as const;
// MariaDB Connector Specific Error Codes (45001-45062)
const CONNECTOR_ERRORS = {
// Connection Management (45001-45010)
CONNECTION_ALREADY_CLOSED: 45001,
MYSQL_CHANGE_USER_BUG: 45003,
CMD_NOT_EXECUTED_DESTROYED: 45004,
ADD_CONNECTION_CANCELLED: 45005,
CONN_PVT_KEY_ENCRYPTED: 45006,
SOCKET_UNEXPECTED_CLOSE: 45007,
SOCKET_BAD_PORT: 45008,
SOCKET_TIMEOUT: 45009,
PING_TIMEOUT: 45010,
// Pool Management (45011-45013)
POOL_ALREADY_CLOSED: 45011,
GET_CONNECTION_TIMEOUT: 45012,
POOL_CONNECTION_CLOSED: 45013,
// File Operations (45014-45015)
FILE_NOT_EXISTS: 45014,
FILE_EMPTY: 45015,
// Authentication (45016-45018)
AUTHENTICATION_PLUGIN_NOT_SUPPORTED: 45016,
AUTHENTICATION_BAD_PACKET: 45017,
AUTHENTICATION_PLUGIN_ERR: 45018,
// Protocol Errors (45019-45062)
UNEXPECTED_PACKET: 45019,
BAD_PARAMETER_VALUE: 45020,
BUFFER_PARAMETER_CONVERSION: 45021,
LOCAL_INFILE_DISABLED: 45022,
LOCAL_INFILE_NOT_READABLE: 45023,
PARAMETER_UNDEFINED: 45024,
PARAMETER_NOT_DEFINED_STMT: 45025,
PARAMETER_NOT_DEFINED_BULK: 45026,
NOT_IMPLEMENTED_FORMAT: 45027,
WRONG_PARAMETER_FORMAT: 45028,
NOT_SUPPORTED_AUTH: 45029,
SOCKET_DESTROYED: 45030,
SOCKET_NOT_WRITABLE: 45031,
AUTHENTICATION_SWITCH_PLUGIN: 45032,
AUTHENTICATION_WRONG_METHOD: 45033,
AUTHENTICATION_WRONG_RESPONSE: 45034,
AUTHENTICATION_EMPTY_RESPONSE: 45035,
AUTHENTICATION_FAIL_RESPONSE: 45036,
CHANGE_USER_RESET_PREPARE: 45037,
AUTHENTICATION_TRY_OTHER: 45038,
SOCKET_NO_CONNECTION: 45039,
HANDSHAKE_UNEXPECTED_PACKET: 45040,
HANDSHAKE_FAIL_SSL: 45041,
CONN_PVT_KEY_INCORRECT_PASSPHRASE: 45042,
STREAM_PARAMETER_NOT_READABLE: 45043,
STREAM_PARAMETER_NOT_ITERABLE: 45044,
COMMAND_NOT_EXECUTABLE: 45045,
COMMAND_CLOSED: 45046,
EMPTY_PASSWORD: 45047,
AUTHENTICATION_KERBEROS_PRINCIPAL: 45048,
AUTHENTICATION_KERBEROS_SERVICE: 45049,
AUTHENTICATION_KERBEROS_TARGET: 45050,
AUTHENTICATION_KERBEROS_TIMEOUT: 45051,
QUERY_INTERRUPTED: 45052,
QUERY_TIMEOUT: 45053,
MAX_ALLOWED_PACKET: 45054,
BATCH_WITH_NO_VALUES: 45055,
BATCH_BULK_NO_BATCH: 45056,
BATCH_WRONG_PARAMETER_NUMBER: 45057,
BATCH_VALIDATION_ERROR: 45058,
BATCH_WITHOUT_PARAMETERS: 45059,
CLUSTER_NO_NODES: 45060,
CLUSTER_NODE_NOT_FOUND: 45061,
CLUSTER_ALL_NODES_FAILED: 45062
} as const;