use crate::copy_metadata::TableDataRepresentation;
use std::{error::Error, fmt, io, str::Utf8Error};
#[derive(Debug)]
#[non_exhaustive]
pub enum PgDumpError {
Io { offset: u64, source: io::Error },
UnexpectedEof { offset: u64 },
InvalidArchiveMagic { offset: u64 },
UnsupportedArchiveVersion {
major: u8,
minor: u8,
revision: u8,
offset: u64,
},
UnexpectedArchiveFormat { format: u8, offset: u64 },
UnsupportedCompressionAlgorithm { algorithm: u8, offset: u64 },
UnsupportedArchiveIntegerSize { size: u8, offset: u64 },
ArchiveIntegerOutOfRange { offset: u64 },
InvalidArchiveOffsetSize { size: u8, offset: u64 },
InvalidArchiveOffsetState { state: u8, offset: u64 },
ArchiveOffsetOutOfRange { offset: u64 },
ArchiveStringLimitExceeded {
length: u64,
limit: u64,
offset: u64,
},
ArchiveStringAllocationFailed { length: u64, offset: u64 },
MetadataStringByteLimitExceeded {
limit: u64,
attempted: u64,
offset: u64,
},
MissingRequiredArchiveString { field: &'static str, offset: u64 },
InvalidTocEntryCount { value: i32, offset: u64 },
TocEntryLimitExceeded { count: u64, limit: u64, offset: u64 },
TocAllocationFailed { count: u64, offset: u64 },
InvalidDumpId { value: i32, offset: u64 },
InvalidSection {
value: i32,
entry_id: i32,
offset: u64,
},
InvalidDependencyEncoding { entry_id: i32, offset: u64 },
DependencyLimitExceeded {
entry_id: i32,
count: u64,
limit: u64,
offset: u64,
},
DependencyAllocationFailed {
entry_id: i32,
count: u64,
offset: u64,
},
MetadataDependencyLimitExceeded {
entry_id: i32,
limit: u64,
attempted: u64,
offset: u64,
},
DuplicateDumpId { dump_id: i32 },
ArchiveIndexAllocationFailed {
context: &'static str,
requested: u64,
},
MetadataIndexByteLimitExceeded {
context: &'static str,
limit: u64,
attempted: u64,
},
DuplicateTableIdentity {
first_table_id: i32,
second_table_id: i32,
},
AmbiguousTableDataRelationship { data_id: i32 },
ConflictingTableDataRelationship { table_id: i32, data_id: i32 },
DuplicateTableDataRelationship {
table_id: i32,
first_data_id: i32,
second_data_id: i32,
},
TableNotFound,
TableDataEntryUnavailable { table_id: i32 },
CopyColumnMetadataUnavailable { dump_id: i32 },
MalformedCopyStatement { dump_id: i32, reason: &'static str },
UnsupportedTableDataRepresentation {
dump_id: i32,
representation: TableDataRepresentation,
},
CopyColumnCountLimitExceeded {
dump_id: i32,
limit: u64,
actual: u64,
},
CopyColumnMetadataAllocationFailed { dump_id: i32, requested: u64 },
EntryNotFound { dump_id: i32 },
EntryHasNoData { dump_id: i32 },
EntryDataOffsetUnavailable { dump_id: i32 },
InvalidDataOffset { dump_id: i32, offset: u64 },
EntrySeekPositionMismatch {
dump_id: i32,
expected: u64,
actual: u64,
},
UnexpectedDataBlockType {
dump_id: i32,
expected: u8,
actual: u8,
offset: u64,
},
DataBlockDumpIdMismatch {
expected: i32,
actual: i32,
offset: u64,
},
InvalidDataChunkLength {
dump_id: i32,
length: i32,
offset: u64,
},
TruncatedDataChunkLength { dump_id: i32, offset: u64 },
TruncatedDataChunk {
dump_id: i32,
remaining: u64,
offset: u64,
},
UnsupportedEntryCompression {
dump_id: i32,
algorithm: &'static str,
},
EntryBufferAllocationFailed { dump_id: i32, requested: u64 },
DecompressionFailed {
dump_id: i32,
algorithm: &'static str,
source: io::Error,
},
EntryDecompressedByteLimitExceeded {
dump_id: i32,
limit: u64,
consumed: u64,
},
EntryDecompressedByteCountOverflow {
dump_id: i32,
consumed: u64,
increment: u64,
},
EntryOutputIo {
dump_id: i32,
written: u64,
source: io::Error,
},
CopyIo {
row: u64,
consumed: u64,
source: io::Error,
},
MalformedCopyEscape { row: u64, byte_offset: u64 },
MalformedCopyTerminator { row: u64, byte_offset: u64 },
CopyRowByteLimitExceeded {
row: u64,
limit: u64,
actual: u64,
byte_offset: u64,
},
CopyFieldCountLimitExceeded {
row: u64,
limit: u64,
actual: u64,
byte_offset: u64,
},
CopyRowFieldCountMismatch {
dump_id: i32,
row: u64,
expected: u64,
actual: u64,
},
ScanRowLimitExceeded { row: u64, limit: u64, consumed: u64 },
ScanDecompressedByteLimitExceeded {
row: u64,
limit: u64,
consumed: u64,
byte_offset: u64,
},
CopyRowAllocationFailed { row: u64, requested: u64 },
CopyFieldAllocationFailed { row: u64, requested: u64 },
CopyConsumedByteCountOverflow {
row: u64,
consumed: u64,
increment: u64,
},
CopyRowNumberOverflow { row: u64 },
ScanRowCountOverflow { row: u64, consumed: u64 },
InvalidUtf8 {
context: &'static str,
source: Utf8Error,
},
ArithmeticOverflow { offset: u64 },
}
impl fmt::Display for PgDumpError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io { offset, source } => write!(
formatter,
"I/O error at archive byte offset {offset}: {source}"
),
Self::UnexpectedEof { offset } => write!(
formatter,
"unexpected end of archive at byte offset {offset}"
),
Self::InvalidArchiveMagic { offset } => {
write!(formatter, "invalid archive magic at byte offset {offset}")
}
Self::UnsupportedArchiveVersion {
major,
minor,
revision,
offset,
} => write!(
formatter,
"unsupported archive version {major}.{minor}.{revision} at byte offset {offset}"
),
Self::UnexpectedArchiveFormat { format, offset } => write!(
formatter,
"unexpected archive format {format} at byte offset {offset}"
),
Self::UnsupportedCompressionAlgorithm { algorithm, offset } => write!(
formatter,
"unsupported compression algorithm {algorithm} at byte offset {offset}"
),
Self::UnsupportedArchiveIntegerSize { size, offset } => write!(
formatter,
"unsupported archive integer size {size} at byte offset {offset}"
),
Self::ArchiveIntegerOutOfRange { offset } => write!(
formatter,
"archive integer at byte offset {offset} is outside the supported i32 range"
),
Self::InvalidArchiveOffsetSize { size, offset } => write!(
formatter,
"invalid archive offset size {size} at byte offset {offset}"
),
Self::InvalidArchiveOffsetState { state, offset } => write!(
formatter,
"invalid archive offset state {state} at byte offset {offset}"
),
Self::ArchiveOffsetOutOfRange { offset } => write!(
formatter,
"archive offset cannot be represented as u64 at byte offset {offset}"
),
Self::ArchiveStringLimitExceeded {
length,
limit,
offset,
} => write!(
formatter,
"archive string length {length} at byte offset {offset} exceeds limit {limit}"
),
Self::ArchiveStringAllocationFailed { length, offset } => write!(
formatter,
"could not reserve {length} bytes for archive string at byte offset {offset}"
),
Self::MetadataStringByteLimitExceeded {
limit,
attempted,
offset,
} => write!(
formatter,
"retained metadata strings reached {attempted} bytes at archive byte offset {offset}, exceeding aggregate limit {limit}"
),
Self::MissingRequiredArchiveString { field, offset } => write!(
formatter,
"required {field} is NULL at archive byte offset {offset}"
),
Self::InvalidTocEntryCount { value, offset } => write!(
formatter,
"invalid TOC entry count {value} at archive byte offset {offset}"
),
Self::TocEntryLimitExceeded {
count,
limit,
offset,
} => write!(
formatter,
"TOC entry count {count} at byte offset {offset} exceeds limit {limit}"
),
Self::TocAllocationFailed { count, offset } => write!(
formatter,
"could not reserve metadata for {count} TOC entries at byte offset {offset}"
),
Self::InvalidDumpId { value, offset } => write!(
formatter,
"invalid dump ID {value} at archive byte offset {offset}"
),
Self::InvalidSection {
value,
entry_id,
offset,
} => write!(
formatter,
"invalid TOC section {value} for dump ID {entry_id} at byte offset {offset}"
),
Self::InvalidDependencyEncoding { entry_id, offset } => write!(
formatter,
"invalid dependency encoding for dump ID {entry_id} at byte offset {offset}"
),
Self::DependencyLimitExceeded {
entry_id,
count,
limit,
offset,
} => write!(
formatter,
"dependency count {count} for dump ID {entry_id} at byte offset {offset} exceeds limit {limit}"
),
Self::DependencyAllocationFailed {
entry_id,
count,
offset,
} => write!(
formatter,
"could not reserve {count} dependencies for dump ID {entry_id} at byte offset {offset}"
),
Self::MetadataDependencyLimitExceeded {
entry_id,
limit,
attempted,
offset,
} => write!(
formatter,
"retained metadata dependencies reached {attempted} while reading dump ID {entry_id} at byte offset {offset}, exceeding aggregate limit {limit}"
),
Self::DuplicateDumpId { dump_id } => {
write!(formatter, "duplicate dump ID {dump_id} in archive TOC")
}
Self::ArchiveIndexAllocationFailed { context, requested } => write!(
formatter,
"could not reserve {requested} elements or bytes for {context}"
),
Self::MetadataIndexByteLimitExceeded {
context,
limit,
attempted,
} => write!(
formatter,
"derived metadata/index names for {context} reached {attempted} bytes, exceeding aggregate limit {limit}"
),
Self::DuplicateTableIdentity {
first_table_id,
second_table_id,
} => write!(
formatter,
"TABLE dump IDs {first_table_id} and {second_table_id} have the same identity"
),
Self::AmbiguousTableDataRelationship { data_id } => write!(
formatter,
"TABLE DATA dump ID {data_id} depends on multiple TABLE entries"
),
Self::ConflictingTableDataRelationship { table_id, data_id } => write!(
formatter,
"TABLE dump ID {table_id} conflicts with TABLE DATA dump ID {data_id}"
),
Self::DuplicateTableDataRelationship {
table_id,
first_data_id,
second_data_id,
} => write!(
formatter,
"TABLE dump ID {table_id} is claimed by TABLE DATA dump IDs {first_data_id} and {second_data_id}"
),
Self::TableNotFound => write!(formatter, "requested table was not found"),
Self::TableDataEntryUnavailable { table_id } => write!(
formatter,
"TABLE dump ID {table_id} has no related TABLE DATA entry"
),
Self::CopyColumnMetadataUnavailable { dump_id } => write!(
formatter,
"TABLE DATA dump ID {dump_id} has no usable COPY column metadata"
),
Self::MalformedCopyStatement { dump_id, reason } => write!(
formatter,
"TABLE DATA dump ID {dump_id} has a malformed COPY statement: {reason}"
),
Self::UnsupportedTableDataRepresentation {
dump_id,
representation,
} => write!(
formatter,
"TABLE DATA dump ID {dump_id} uses unsupported {representation:?} row representation"
),
Self::CopyColumnCountLimitExceeded {
dump_id,
limit,
actual,
} => write!(
formatter,
"COPY metadata for dump ID {dump_id} has {actual} columns, exceeding limit {limit}"
),
Self::CopyColumnMetadataAllocationFailed { dump_id, requested } => write!(
formatter,
"could not reserve {requested} elements or bytes for COPY metadata of dump ID {dump_id}"
),
Self::EntryNotFound { dump_id } => {
write!(formatter, "TOC dump ID {dump_id} was not found")
}
Self::EntryHasNoData { dump_id } => {
write!(formatter, "TOC dump ID {dump_id} has no data block")
}
Self::EntryDataOffsetUnavailable { dump_id } => write!(
formatter,
"TOC dump ID {dump_id} has no recorded direct-seek data offset"
),
Self::InvalidDataOffset { dump_id, offset } => write!(
formatter,
"data offset {offset} for TOC dump ID {dump_id} cannot safely address a custom block header"
),
Self::EntrySeekPositionMismatch {
dump_id,
expected,
actual,
} => write!(
formatter,
"seek for TOC dump ID {dump_id} requested byte offset {expected} but landed at {actual}"
),
Self::UnexpectedDataBlockType {
dump_id,
expected,
actual,
offset,
} => write!(
formatter,
"TOC dump ID {dump_id} expected custom block type {expected} at byte offset {offset}, found {actual}"
),
Self::DataBlockDumpIdMismatch {
expected,
actual,
offset,
} => write!(
formatter,
"selected dump ID {expected} points to data block dump ID {actual} at byte offset {offset}"
),
Self::InvalidDataChunkLength {
dump_id,
length,
offset,
} => write!(
formatter,
"data chunk for dump ID {dump_id} has invalid length {length} at byte offset {offset}"
),
Self::TruncatedDataChunkLength { dump_id, offset } => write!(
formatter,
"archive ended while reading a data chunk length for dump ID {dump_id} at byte offset {offset}"
),
Self::TruncatedDataChunk {
dump_id,
remaining,
offset,
} => write!(
formatter,
"archive ended at byte offset {offset} with {remaining} bytes remaining in a data chunk for dump ID {dump_id}"
),
Self::UnsupportedEntryCompression { dump_id, algorithm } => write!(
formatter,
"dump ID {dump_id} uses unsupported {algorithm} entry compression"
),
Self::EntryBufferAllocationFailed { dump_id, requested } => write!(
formatter,
"could not reserve {requested} bytes for dump ID {dump_id} entry buffering"
),
Self::DecompressionFailed {
dump_id,
algorithm,
source,
} => write!(
formatter,
"could not decompress dump ID {dump_id} with {algorithm}: {source}"
),
Self::EntryDecompressedByteLimitExceeded {
dump_id,
limit,
consumed,
} => write!(
formatter,
"raw entry dump ID {dump_id} reached {consumed} decompressed bytes, exceeding limit {limit}"
),
Self::EntryDecompressedByteCountOverflow {
dump_id,
consumed,
increment,
} => write!(
formatter,
"raw entry decompressed-byte counter overflow for dump ID {dump_id}: {consumed} + {increment}"
),
Self::EntryOutputIo {
dump_id,
written,
source,
} => write!(
formatter,
"I/O error while writing dump ID {dump_id} after {written} decompressed bytes: {source}"
),
Self::CopyIo {
row,
consumed,
source,
} => write!(
formatter,
"I/O error while parsing COPY row {row} after consuming {consumed} bytes: {source}"
),
Self::MalformedCopyEscape { row, byte_offset } => write!(
formatter,
"incomplete COPY escape in row {row} at decompressed byte offset {byte_offset}"
),
Self::MalformedCopyTerminator { row, byte_offset } => write!(
formatter,
"malformed COPY end marker in row {row} at decompressed byte offset {byte_offset}"
),
Self::CopyRowByteLimitExceeded {
row,
limit,
actual,
byte_offset,
} => write!(
formatter,
"COPY row {row} reached {actual} physical bytes at offset {byte_offset}, exceeding limit {limit}"
),
Self::CopyFieldCountLimitExceeded {
row,
limit,
actual,
byte_offset,
} => write!(
formatter,
"COPY row {row} reached {actual} fields at offset {byte_offset}, exceeding limit {limit}"
),
Self::CopyRowFieldCountMismatch {
dump_id,
row,
expected,
actual,
} => write!(
formatter,
"COPY row {row} for dump ID {dump_id} has {actual} fields, expected {expected} from column metadata"
),
Self::ScanRowLimitExceeded {
row,
limit,
consumed,
} => write!(
formatter,
"row scan reached row {row} and {consumed} complete rows, exceeding limit {limit}"
),
Self::ScanDecompressedByteLimitExceeded {
row,
limit,
consumed,
byte_offset,
} => write!(
formatter,
"row scan reached {consumed} decompressed bytes in row {row} at offset {byte_offset}, exceeding limit {limit}"
),
Self::CopyRowAllocationFailed { row, requested } => write!(
formatter,
"could not reserve {requested} bytes for COPY row {row}"
),
Self::CopyFieldAllocationFailed { row, requested } => write!(
formatter,
"could not reserve {requested} field descriptors for COPY row {row}"
),
Self::CopyConsumedByteCountOverflow {
row,
consumed,
increment,
} => write!(
formatter,
"COPY consumed-byte counter overflow in row {row}: {consumed} + {increment}"
),
Self::CopyRowNumberOverflow { row } => write!(
formatter,
"COPY row-number counter overflow after row {row}"
),
Self::ScanRowCountOverflow { row, consumed } => write!(
formatter,
"row-scan counter overflow while counting row {row} after {consumed} rows"
),
Self::InvalidUtf8 { context, source } => {
write!(formatter, "invalid UTF-8 in {context}: {source}")
}
Self::ArithmeticOverflow { offset } => write!(
formatter,
"arithmetic overflow at archive byte offset {offset}"
),
}
}
}
impl Error for PgDumpError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Io { source, .. }
| Self::DecompressionFailed { source, .. }
| Self::EntryOutputIo { source, .. }
| Self::CopyIo { source, .. } => Some(source),
Self::InvalidUtf8 { source, .. } => Some(source),
_ => None,
}
}
}
pub(crate) fn into_io_error(error: PgDumpError) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, error)
}