using System.Text;
namespace Strata.SqlTools.SqlServer.Exceptions;
///
/// Exception thrown when SQL parsing fails.
/// Provides detailed context about the parse failure including position and surrounding text.
///
public class SqlParseException : Exception
{
///
/// Gets the position in the SQL string where the parse error occurred.
///
public int Position { get; }
///
/// Gets the SQL statement that failed to parse.
///
public string Sql { get; }
///
/// Gets the text near the error position (up to 40 characters).
///
public string NearText { get; }
///
/// Initializes a new instance of the class.
///
/// The error message describing the parse failure.
/// The SQL statement that failed to parse.
/// The position in the SQL where the error occurred.
///
///
/// throw new SqlParseException(
/// "Unexpected token 'FROM'",
/// "SELECT * FRM users",
/// 10
/// );
/// // Message will include:
/// // - Error description
/// // - Position: 10
/// // - Near: "* FRM users"
/// // - Full SQL statement
///
///
public SqlParseException(string message, string sql, int position)
: base(FormatMessage(message, sql, position))
{
Position = position;
Sql = sql ?? string.Empty;
NearText = ExtractNearText(Sql, position);
}
///
/// Initializes a new instance of the class with an inner exception.
///
/// The error message describing the parse failure.
/// The SQL statement that failed to parse.
/// The position in the SQL where the error occurred.
/// The exception that caused this parse failure.
public SqlParseException(string message, string sql, int position, Exception innerException)
: base(FormatMessage(message, sql, position), innerException)
{
Position = position;
Sql = sql ?? string.Empty;
NearText = ExtractNearText(Sql, position);
}
private static string FormatMessage(string message, string sql, int position)
{
if (string.IsNullOrEmpty(sql))
{
return $"{message}\nSQL statement is empty or null.";
}
var nearText = ExtractNearText(sql, position);
var sb = new StringBuilder();
sb.AppendLine(message);
sb.AppendLine($"Position: {position}");
sb.AppendLine($"Near: '{nearText}'");
// Show full SQL for short statements, truncated for long ones
if (sql.Length <= 200)
{
sb.AppendLine($"Full SQL: {sql}");
}
else
{
sb.AppendLine($"SQL (truncated): {sql.Substring(0, 197)}...");
}
return sb.ToString();
}
private static string ExtractNearText(string sql, int position)
{
if (string.IsNullOrEmpty(sql))
{
return string.Empty;
}
// Clamp position to valid range
position = Math.Max(0, Math.Min(position, sql.Length));
// Extract up to 20 chars before and 20 chars after the position
var start = Math.Max(0, position - 20);
var length = Math.Min(40, sql.Length - start);
var nearText = sql.Substring(start, length);
// Add ellipsis if truncated
if (start > 0)
{
nearText = "..." + nearText;
}
if (start + length < sql.Length)
{
nearText = nearText + "...";
}
return nearText;
}
}