- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
257 lines
8.9 KiB
PHP
257 lines
8.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Database\Exception;
|
|
|
|
use App\Framework\Database\ValueObjects\SqlState;
|
|
use App\Framework\Exception\Core\DatabaseErrorCode;
|
|
use App\Framework\Exception\ExceptionContext;
|
|
|
|
/**
|
|
* Query Syntax Exception
|
|
*
|
|
* Thrown when a database query contains syntax errors or references
|
|
* invalid database objects (tables, columns, functions).
|
|
*
|
|
* This exception represents SQLSTATE class 42 (Syntax Error or Access Violation) errors:
|
|
* - 42000: Syntax error or access violation
|
|
* - 42501: Insufficient privilege
|
|
* - 42601: Syntax error
|
|
* - 42P01: Undefined table
|
|
* - 42703: Undefined column
|
|
* - 42883: Undefined function
|
|
* - 42S02: Table not found (MySQL)
|
|
* - 42S22: Column not found (MySQL)
|
|
*/
|
|
final class QuerySyntaxException extends DatabaseException
|
|
{
|
|
/**
|
|
* Create exception for general query syntax error
|
|
*
|
|
* @param string $query The SQL query that failed
|
|
* @param SqlState $sqlState The SQLSTATE error code
|
|
* @param string|null $errorMessage Optional database error message
|
|
* @param \Throwable|null $previous Previous exception
|
|
*/
|
|
public static function forQuery(
|
|
string $query,
|
|
SqlState $sqlState,
|
|
?string $errorMessage = null,
|
|
?\Throwable $previous = null
|
|
): self {
|
|
$message = "SQL syntax error: {$errorMessage}";
|
|
|
|
$context = ExceptionContext::forOperation('query.parse', 'Database')
|
|
->withData([
|
|
'query' => self::truncateQuery($query),
|
|
'sqlstate' => $sqlState->code,
|
|
'sqlstate_class' => $sqlState->getClass(),
|
|
'error_category' => 'Syntax Error or Access Violation',
|
|
])
|
|
->withDebug([
|
|
'full_query' => $query,
|
|
'sqlstate_subclass' => $sqlState->getSubclass(),
|
|
]);
|
|
|
|
return self::fromContext($message, $context, DatabaseErrorCode::QUERY_SYNTAX_ERROR, $previous, 500, $sqlState);
|
|
}
|
|
|
|
/**
|
|
* Create exception for undefined table
|
|
*
|
|
* @param string $tableName The table name that was not found
|
|
* @param SqlState $sqlState The SQLSTATE error code (typically 42P01 or 42S02)
|
|
* @param string|null $query Optional SQL query
|
|
*/
|
|
public static function tableNotFound(
|
|
string $tableName,
|
|
SqlState $sqlState,
|
|
?string $query = null
|
|
): self {
|
|
$message = "Table '{$tableName}' does not exist";
|
|
|
|
$context = ExceptionContext::forOperation('query.validate', 'Database')
|
|
->withData([
|
|
'table_name' => $tableName,
|
|
'sqlstate' => $sqlState->code,
|
|
'error_category' => 'Syntax Error or Access Violation',
|
|
]);
|
|
|
|
if ($query !== null) {
|
|
$context = $context->withDebug(['query' => self::truncateQuery($query)]);
|
|
}
|
|
|
|
return self::fromContext($message, $context, DatabaseErrorCode::QUERY_SYNTAX_ERROR, null, 500, $sqlState);
|
|
}
|
|
|
|
/**
|
|
* Create exception for undefined column
|
|
*
|
|
* @param string $columnName The column name that was not found
|
|
* @param string|null $tableName Optional table name
|
|
* @param SqlState $sqlState The SQLSTATE error code (typically 42703 or 42S22)
|
|
* @param string|null $query Optional SQL query
|
|
*/
|
|
public static function columnNotFound(
|
|
string $columnName,
|
|
?string $tableName,
|
|
SqlState $sqlState,
|
|
?string $query = null
|
|
): self {
|
|
$message = $tableName !== null
|
|
? "Column '{$tableName}.{$columnName}' does not exist"
|
|
: "Column '{$columnName}' does not exist";
|
|
|
|
$context = ExceptionContext::forOperation('query.validate', 'Database')
|
|
->withData([
|
|
'column_name' => $columnName,
|
|
'sqlstate' => $sqlState->code,
|
|
'error_category' => 'Syntax Error or Access Violation',
|
|
]);
|
|
|
|
if ($tableName !== null) {
|
|
$context = $context->withData(['table_name' => $tableName]);
|
|
}
|
|
|
|
if ($query !== null) {
|
|
$context = $context->withDebug(['query' => self::truncateQuery($query)]);
|
|
}
|
|
|
|
return self::fromContext($message, $context, DatabaseErrorCode::QUERY_SYNTAX_ERROR, null, 500, $sqlState);
|
|
}
|
|
|
|
/**
|
|
* Create exception for undefined function
|
|
*
|
|
* @param string $functionName The function name that was not found
|
|
* @param SqlState $sqlState The SQLSTATE error code (typically 42883)
|
|
* @param string|null $query Optional SQL query
|
|
*/
|
|
public static function functionNotFound(
|
|
string $functionName,
|
|
SqlState $sqlState,
|
|
?string $query = null
|
|
): self {
|
|
$message = "Function '{$functionName}' does not exist";
|
|
|
|
$context = ExceptionContext::forOperation('query.validate', 'Database')
|
|
->withData([
|
|
'function_name' => $functionName,
|
|
'sqlstate' => $sqlState->code,
|
|
'error_category' => 'Syntax Error or Access Violation',
|
|
]);
|
|
|
|
if ($query !== null) {
|
|
$context = $context->withDebug(['query' => self::truncateQuery($query)]);
|
|
}
|
|
|
|
return self::fromContext($message, $context, DatabaseErrorCode::QUERY_SYNTAX_ERROR, null, 500, $sqlState);
|
|
}
|
|
|
|
/**
|
|
* Create exception for insufficient privileges
|
|
*
|
|
* @param string $operation The operation that was attempted (e.g., SELECT, INSERT, DROP)
|
|
* @param string|null $objectName Optional database object name
|
|
* @param SqlState $sqlState The SQLSTATE error code (typically 42501)
|
|
*/
|
|
public static function insufficientPrivileges(
|
|
string $operation,
|
|
?string $objectName,
|
|
SqlState $sqlState
|
|
): self {
|
|
$message = $objectName !== null
|
|
? "Insufficient privileges to {$operation} on '{$objectName}'"
|
|
: "Insufficient privileges to perform {$operation}";
|
|
|
|
$context = ExceptionContext::forOperation('query.authorize', 'Database')
|
|
->withData([
|
|
'operation' => $operation,
|
|
'sqlstate' => $sqlState->code,
|
|
'error_category' => 'Syntax Error or Access Violation',
|
|
]);
|
|
|
|
if ($objectName !== null) {
|
|
$context = $context->withData(['object_name' => $objectName]);
|
|
}
|
|
|
|
return self::fromContext($message, $context, DatabaseErrorCode::QUERY_SYNTAX_ERROR, null, 500, $sqlState);
|
|
}
|
|
|
|
/**
|
|
* Create exception for ambiguous column reference
|
|
*
|
|
* @param string $columnName The ambiguous column name
|
|
* @param SqlState $sqlState The SQLSTATE error code
|
|
* @param string|null $query Optional SQL query
|
|
*/
|
|
public static function ambiguousColumn(
|
|
string $columnName,
|
|
SqlState $sqlState,
|
|
?string $query = null
|
|
): self {
|
|
$message = "Ambiguous column reference '{$columnName}' - specify table name";
|
|
|
|
$context = ExceptionContext::forOperation('query.validate', 'Database')
|
|
->withData([
|
|
'column_name' => $columnName,
|
|
'sqlstate' => $sqlState->code,
|
|
'error_category' => 'Syntax Error or Access Violation',
|
|
]);
|
|
|
|
if ($query !== null) {
|
|
$context = $context->withDebug(['query' => self::truncateQuery($query)]);
|
|
}
|
|
|
|
return self::fromContext($message, $context, DatabaseErrorCode::QUERY_SYNTAX_ERROR, null, 500, $sqlState);
|
|
}
|
|
|
|
/**
|
|
* Create exception for invalid SQL syntax at specific position
|
|
*
|
|
* @param string $query The SQL query
|
|
* @param int $position Error position in query
|
|
* @param string $nearText Text near the error
|
|
* @param SqlState $sqlState The SQLSTATE error code
|
|
*/
|
|
public static function syntaxErrorAt(
|
|
string $query,
|
|
int $position,
|
|
string $nearText,
|
|
SqlState $sqlState
|
|
): self {
|
|
$message = "Syntax error at position {$position} near '{$nearText}'";
|
|
|
|
$context = ExceptionContext::forOperation('query.parse', 'Database')
|
|
->withData([
|
|
'error_position' => $position,
|
|
'near_text' => $nearText,
|
|
'query' => self::truncateQuery($query),
|
|
'sqlstate' => $sqlState->code,
|
|
'error_category' => 'Syntax Error or Access Violation',
|
|
])
|
|
->withDebug([
|
|
'full_query' => $query,
|
|
]);
|
|
|
|
return self::fromContext($message, $context, DatabaseErrorCode::QUERY_SYNTAX_ERROR, null, 500, $sqlState);
|
|
}
|
|
|
|
/**
|
|
* Truncate query for safe logging
|
|
*
|
|
* @param string $query The SQL query
|
|
* @param int $maxLength Maximum length
|
|
* @return string Truncated query
|
|
*/
|
|
private static function truncateQuery(string $query, int $maxLength = 200): string
|
|
{
|
|
if (strlen($query) <= $maxLength) {
|
|
return $query;
|
|
}
|
|
|
|
return substr($query, 0, $maxLength) . '... (truncated)';
|
|
}
|
|
}
|