using System; using System.Collections.Generic; using System.Runtime.Serialization; using System.Text; using Npgsql.BackendMessages; using Npgsql.Internal; namespace Npgsql; /// /// The exception that is thrown when the PostgreSQL backend reports errors (e.g. query /// SQL issues, constraint violations). /// /// /// This exception only corresponds to a PostgreSQL-delivered error. /// Other errors (e.g. network issues) will be raised via , /// and purely Npgsql-related issues which aren't related to the server will be raised /// via the standard CLR exceptions (e.g. ). /// /// See https://www.postgresql.org/docs/current/static/errcodes-appendix.html, /// https://www.postgresql.org/docs/current/static/protocol-error-fields.html /// [Serializable] public sealed class PostgresException : NpgsqlException { /// /// Creates a new instance. /// public PostgresException(string messageText, string severity, string invariantSeverity, string sqlState) : this(messageText, severity, invariantSeverity, sqlState, detail: null) {} /// /// Creates a new instance. /// public PostgresException( string messageText, string severity, string invariantSeverity, string sqlState, string? detail = null, string? hint = null, int position = 0, int internalPosition = 0, string? internalQuery = null, string? where = null, string? schemaName = null, string? tableName = null, string? columnName = null, string? dataTypeName = null, string? constraintName = null, string? file = null, string? line = null, string? routine = null) : base(GetMessage(sqlState, messageText, position, detail)) { MessageText = messageText; Severity = severity; InvariantSeverity = invariantSeverity; SqlState = sqlState; Detail = detail; Hint = hint; Position = position; InternalPosition = internalPosition; InternalQuery = internalQuery; Where = where; SchemaName = schemaName; TableName = tableName; ColumnName = columnName; DataTypeName = dataTypeName; ConstraintName = constraintName; File = file; Line = line; Routine = routine; AddData(nameof(Severity), Severity); AddData(nameof(InvariantSeverity), InvariantSeverity); AddData(nameof(SqlState), SqlState); AddData(nameof(MessageText), MessageText); AddData(nameof(Detail), Detail); AddData(nameof(Hint), Hint); AddData(nameof(Position), Position); AddData(nameof(InternalPosition), InternalPosition); AddData(nameof(InternalQuery), InternalQuery); AddData(nameof(Where), Where); AddData(nameof(SchemaName), SchemaName); AddData(nameof(TableName), TableName); AddData(nameof(ColumnName), ColumnName); AddData(nameof(DataTypeName), DataTypeName); AddData(nameof(ConstraintName), ConstraintName); AddData(nameof(File), File); AddData(nameof(Line), Line); AddData(nameof(Routine), Routine); void AddData(string key, T value) { if (!EqualityComparer.Default.Equals(value, default!)) Data.Add(key, value); } } static string GetMessage(string sqlState, string messageText, int position, string? detail) { var baseMessage = sqlState + ": " + messageText; var additionalMessage = TryAddString("POSITION", position == 0 ? null : position.ToString()) + TryAddString("DETAIL", detail); return string.IsNullOrEmpty(additionalMessage) ? baseMessage : baseMessage + Environment.NewLine + additionalMessage; } static string TryAddString(string text, string? value) => !string.IsNullOrWhiteSpace(value) ? $"{Environment.NewLine}{text}: {value}" : string.Empty; PostgresException(ErrorOrNoticeMessage msg) : this( msg.Message, msg.Severity, msg.InvariantSeverity, msg.SqlState, msg.Detail, msg.Hint, msg.Position, msg.InternalPosition, msg.InternalQuery, msg.Where, msg.SchemaName, msg.TableName, msg.ColumnName, msg.DataTypeName, msg.ConstraintName, msg.File, msg.Line, msg.Routine) {} internal static PostgresException Load(NpgsqlReadBuffer buf, bool includeDetail) => new(ErrorOrNoticeMessage.Load(buf, includeDetail)); internal PostgresException(SerializationInfo info, StreamingContext context) : base(info, context) { Severity = GetValue(nameof(Severity)); InvariantSeverity = GetValue(nameof(InvariantSeverity)); SqlState = GetValue(nameof(SqlState)); MessageText = GetValue(nameof(MessageText)); Detail = GetValue(nameof(Detail)); Hint = GetValue(nameof(Hint)); Position = GetValue(nameof(Position)); InternalPosition = GetValue(nameof(InternalPosition)); InternalQuery = GetValue(nameof(InternalQuery)); Where = GetValue(nameof(Where)); SchemaName = GetValue(nameof(SchemaName)); TableName = GetValue(nameof(TableName)); ColumnName = GetValue(nameof(ColumnName)); DataTypeName = GetValue(nameof(DataTypeName)); ConstraintName = GetValue(nameof(ConstraintName)); File = GetValue(nameof(File)); Line = GetValue(nameof(Line)); Routine = GetValue(nameof(Routine)); T GetValue(string propertyName) => (T)info.GetValue(propertyName, typeof(T))!; } /// /// Populates a with the data needed to serialize the target object. /// /// The to populate with data. /// The destination (see ) for this serialization. public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue(nameof(Severity), Severity); info.AddValue(nameof(InvariantSeverity), InvariantSeverity); info.AddValue(nameof(SqlState), SqlState); info.AddValue(nameof(MessageText), MessageText); info.AddValue(nameof(Detail), Detail); info.AddValue(nameof(Hint), Hint); info.AddValue(nameof(Position), Position); info.AddValue(nameof(InternalPosition), InternalPosition); info.AddValue(nameof(InternalQuery), InternalQuery); info.AddValue(nameof(Where), Where); info.AddValue(nameof(SchemaName), SchemaName); info.AddValue(nameof(TableName), TableName); info.AddValue(nameof(ColumnName), ColumnName); info.AddValue(nameof(DataTypeName), DataTypeName); info.AddValue(nameof(ConstraintName), ConstraintName); info.AddValue(nameof(File), File); info.AddValue(nameof(Line), Line); info.AddValue(nameof(Routine), Routine); } /// public override string ToString() { var builder = new StringBuilder(base.ToString()) .AppendLine().Append(" Exception data:"); AppendLine(nameof(Severity), Severity); AppendLine(nameof(SqlState), SqlState); AppendLine(nameof(MessageText), MessageText); AppendLine(nameof(Detail), Detail); AppendLine(nameof(Hint), Hint); AppendLine(nameof(Position), Position); AppendLine(nameof(InternalPosition), InternalPosition); AppendLine(nameof(InternalQuery), InternalQuery); AppendLine(nameof(Where), Where); AppendLine(nameof(SchemaName), SchemaName); AppendLine(nameof(TableName), TableName); AppendLine(nameof(ColumnName), ColumnName); AppendLine(nameof(DataTypeName), DataTypeName); AppendLine(nameof(ConstraintName), ConstraintName); AppendLine(nameof(File), File); AppendLine(nameof(Line), Line); AppendLine(nameof(Routine), Routine); return builder.ToString(); void AppendLine(string propertyName, T propertyValue) { if (!EqualityComparer.Default.Equals(propertyValue, default!)) builder.AppendLine().Append(" ").Append(propertyName).Append(": ").Append(propertyValue); } } /// /// Specifies whether the exception is considered transient, that is, whether retrying the operation could /// succeed (e.g. a network error). Check . /// public override bool IsTransient { get { switch (SqlState) { case PostgresErrorCodes.InsufficientResources: case PostgresErrorCodes.DiskFull: case PostgresErrorCodes.OutOfMemory: case PostgresErrorCodes.TooManyConnections: case PostgresErrorCodes.ConfigurationLimitExceeded: case PostgresErrorCodes.CannotConnectNow: case PostgresErrorCodes.SystemError: case PostgresErrorCodes.IoError: case PostgresErrorCodes.SerializationFailure: case PostgresErrorCodes.DeadlockDetected: case PostgresErrorCodes.LockNotAvailable: case PostgresErrorCodes.ObjectInUse: case PostgresErrorCodes.ObjectNotInPrerequisiteState: case PostgresErrorCodes.ConnectionException: case PostgresErrorCodes.ConnectionDoesNotExist: case PostgresErrorCodes.ConnectionFailure: case PostgresErrorCodes.SqlClientUnableToEstablishSqlConnection: case PostgresErrorCodes.SqlServerRejectedEstablishmentOfSqlConnection: case PostgresErrorCodes.TransactionResolutionUnknown: return true; default: return false; } } } #region Message Fields /// /// Severity of the error or notice. /// Always present. /// public string Severity { get; } /// /// Severity of the error or notice, not localized. /// Always present since PostgreSQL 9.6. /// public string InvariantSeverity { get; } /// /// The SQLSTATE code for the error. /// /// /// Always present. /// Constants are defined in . /// See https://www.postgresql.org/docs/current/static/errcodes-appendix.html /// #if NET5_0_OR_GREATER public override string SqlState { get; } #else public string SqlState { get; } #endif /// /// The SQLSTATE code for the error. /// /// /// Always present. /// Constants are defined in . /// See https://www.postgresql.org/docs/current/static/errcodes-appendix.html /// [Obsolete("Use SqlState instead")] public string Code => SqlState; /// /// The primary human-readable error message. This should be accurate but terse. /// /// /// Always present. /// public string MessageText { get; } /// /// An optional secondary error message carrying more detail about the problem. /// May run to multiple lines. /// public string? Detail { get; } /// /// An optional suggestion what to do about the problem. /// This is intended to differ from Detail in that it offers advice (potentially inappropriate) rather than hard facts. /// May run to multiple lines. /// public string? Hint { get; } /// /// The field value is a decimal ASCII integer, indicating an error cursor position as an index into the original query string. /// The first character has index 1, and positions are measured in characters not bytes. /// 0 means not provided. /// public int Position { get; } /// /// This is defined the same as the field, but it is used when the cursor position refers to an internally generated command rather than the one submitted by the client. /// The field will always appear when this field appears. /// 0 means not provided. /// public int InternalPosition { get; } /// /// The text of a failed internally-generated command. /// This could be, for example, a SQL query issued by a PL/pgSQL function. /// public string? InternalQuery { get; } /// /// An indication of the context in which the error occurred. /// Presently this includes a call stack traceback of active PL functions. /// The trace is one entry per line, most recent first. /// public string? Where { get; } /// /// If the error was associated with a specific database object, the name of the schema containing that object, if any. /// /// PostgreSQL 9.3 and up. public string? SchemaName { get; } /// /// Table name: if the error was associated with a specific table, the name of the table. /// (Refer to the schema name field for the name of the table's schema.) /// /// PostgreSQL 9.3 and up. public string? TableName { get; } /// /// If the error was associated with a specific table column, the name of the column. /// (Refer to the schema and table name fields to identify the table.) /// /// PostgreSQL 9.3 and up. public string? ColumnName { get; } /// /// If the error was associated with a specific data type, the name of the data type. /// (Refer to the schema name field for the name of the data type's schema.) /// /// PostgreSQL 9.3 and up. public string? DataTypeName { get; } /// /// If the error was associated with a specific constraint, the name of the constraint. /// Refer to fields listed above for the associated table or domain. /// (For this purpose, indexes are treated as constraints, even if they weren't created with constraint syntax.) /// /// PostgreSQL 9.3 and up. public string? ConstraintName { get; } /// /// The file name of the source-code location where the error was reported. /// /// PostgreSQL 9.3 and up. public string? File { get; } /// /// The line number of the source-code location where the error was reported. /// public string? Line { get; } /// /// The name of the source-code routine reporting the error. /// public string? Routine { get; } #endregion }