CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-mariadb

Fast MariaDB and MySQL connector for Node.js with Promise and callback APIs

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

errors.mddocs/

Error Handling

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.

Capabilities

SqlError Interface

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;
}

SqlError Constructor

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;

Error Types and Categories

Common error categories and their characteristics:

Connection Errors:

  • ER_CONNECTION_ALREADY_CLOSED (45001) - Connection was already closed
  • ER_SOCKET_UNEXPECTED_CLOSE (45007) - Socket closed unexpectedly
  • ER_SOCKET_TIMEOUT (45009) - Socket operation timed out
  • ER_PING_TIMEOUT (45010) - Connection ping timed out

Authentication Errors:

  • ER_AUTHENTICATION_PLUGIN_NOT_SUPPORTED (45016) - Auth plugin not supported
  • ER_AUTHENTICATION_BAD_PACKET (45017) - Bad authentication packet
  • ER_AUTHENTICATION_PLUGIN_ERR (45018) - Authentication plugin error

Pool Errors:

  • ER_POOL_ALREADY_CLOSED (45011) - Pool was already closed
  • ER_GET_CONNECTION_TIMEOUT (45012) - Connection acquisition timeout
  • ER_POOL_CONNECTION_CLOSED (45013) - Pool connection was closed

Protocol Errors:

  • ER_UNEXPECTED_PACKET (45019) - Unexpected packet received
  • ER_BAD_PARAMETER_VALUE (45020) - Invalid parameter value
  • ER_LOCAL_INFILE_DISABLED (45022) - LOAD LOCAL INFILE disabled

Error Handling Patterns

// 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;
}

Error Classification and Handling

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));
  }
}

Connection Error Recovery

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;
    }
  }
}

Transaction Error Handling

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;
  }
}

Logging and Monitoring Integration

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
  }
}

Custom Error Classes

// 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);
  }
}

Error Recovery Strategies

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';
    }
  }
}

Common Error Codes Reference

// 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;

docs

callbacks.md

clustering.md

configuration.md

connections.md

errors.md

index.md

pooling.md

queries.md

types.md

tile.json