1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
//! Bounded, byte-oriented access to PostgreSQL custom-format (`pg_dump -Fc`) archives.
//!
//! `pgdumpx` opens supported archive versions 1.14 through 1.16, parses the header and
//! table of contents eagerly, and keeps entry payload access lazy. Selected entries are
//! validated before they are exposed and are decompressed as streams through
//! [`EntryDataReader`]. The default feature set supports PostgreSQL's none, gzip, LZ4,
//! and Zstandard archive compression modes.
//!
//! # Byte-oriented contract
//!
//! Archive metadata and COPY fields are bytes first. [`ArchiveString::as_bytes`], table
//! lookup, column lookup, [`Row`], and [`FieldRef`] do not require UTF-8. Callers opt into
//! text conversion with fallible helpers such as [`ArchiveString::to_str`] and
//! [`Column::name_str`]. [`FieldRef::Bytes`] and [`OwnedField::Bytes`] contain logical
//! field bytes *after* PostgreSQL COPY-text backslash decoding; they are not the escaped
//! on-wire spelling. `\N` is represented as [`FieldRef::Null`] rather than as bytes.
//!
//! # Lending rows
//!
//! [`CopyRowReader`] and [`TableRowReader`] reuse their current-row storage. A [`Row`]
//! therefore borrows from the reader and remains valid only until that reader is mutably
//! borrowed again. The row readers intentionally expose `next_row(&mut self)` instead of
//! implementing `Iterator`. [`OwnedRow`] is used when a matched row must outlive reader
//! advancement; normal iteration stays borrowed.
//!
//! # Three independent limit classes
//!
//! Resource controls are intentionally separated because they protect different work:
//!
//! - [`Limits`] contains finite structural bounds used while opening archives and parsing
//! individual COPY rows/column layouts.
//! - [`ScanLimits`] bounds total rows and parser-consumed decompressed COPY bytes for a row
//! scan. These limits do not measure decoder read-ahead that the COPY parser has not
//! consumed.
//! - [`EntryReadLimits`] bounds decompressed bytes returned by raw selected-entry access.
//! Crossing a raw-output limit is an error, never successful truncation.
//!
//! [`Limits::default_compatible`] supplies the finite `Limits` default. In contrast,
//! [`ScanLimits::unlimited`] and [`EntryReadLimits::unlimited`] are also their respective
//! library defaults so trusted callers can choose operation policy explicitly. The
//! `pgdumpx extract` CLI applies its own finite 1 GiB default when its raw-output option
//! is omitted.
//!
//! # Sequential row search
//!
//! [`CopyRowReader::find_first`] and [`TableRowReader::find_first`] are sequential scans
//! from the reader's current position. [`TableRowReader::find_first_equal`] adds a reusable
//! named-column exact-equality convenience layer without changing that predicate API or
//! scan path. Column names and [`FieldRef::Bytes`] targets are exact bytes, targets are
//! compared after COPY-text unescaping, and [`FieldRef::Null`] remains distinct from empty
//! or literal `b"\\N"` bytes. No collation, SQL coercion, or typed-value comparison occurs.
//!
//! A freshly created [`TableRowReader`] starts at the beginning of the selected
//! `TABLE DATA` entry, but there is no row-level value index in the archive. An early match
//! terminates immediately; a late or absent match can process the rest of the selected
//! entry unless [`ScanLimits`] stops it. Worst-case unrestricted work is proportional to
//! the remaining selected table-data size.
//!
//! # Raw extraction and partial output
//!
//! [`Archive::copy_entry_to`] streams decompressed bytes incrementally. If a later input,
//! decompression, limit, or destination error occurs, bytes already accepted by the
//! destination cannot be rolled back. The operation still returns an error and never
//! reports a partial stream as successful extraction.
//!
//! [`ExtractionPlan::execute`] extends that same bounded raw path to multiple tables while
//! preserving the archive's single mutable seek invariant. It completes metadata preflight
//! for every selector before requesting the first destination, then executes targets in
//! deterministic plan order. If a target fails, earlier completed outcomes are retained,
//! the current target may already have partial output, and later targets are not started.
//!
//! # Typed errors
//!
//! [`PgDumpError`] is the detailed error type. [`PgDumpError::category`] exposes a stable
//! high-level [`ErrorCategory`], while helpers such as [`PgDumpError::dump_id`],
//! [`PgDumpError::row_number`], [`PgDumpError::byte_offset`], and
//! [`PgDumpError::limit_context`] expose machine-readable context without parsing display
//! text. Variants wrapping lower-level I/O, decompression, output, COPY-input, or UTF-8
//! failures preserve those failures through [`std::error::Error::source`].
//!
//! # CLI encoding boundary
//!
//! The Rust API remains byte-oriented. The `pgdumpx find` and `pgdumpx extract` CLI
//! commands accept UTF-8 schema/table arguments and use an exact `SCHEMA.TABLE` selector:
//! exactly one ASCII `.` separator, two non-empty components, and no SQL identifier
//! quoting. `find` also requires UTF-8 column/value arguments and compares their UTF-8
//! bytes with logical post-unescape field bytes. Raw `extract` output remains binary-safe.
//!
//! # Example: production row path
//!
//! The following compiles against the same public path used by the CLI. The search is a
//! bounded sequential scan of one selected table-data entry.
//!
//! ```no_run
//! use pgdumpx::{Archive, ColumnEqualityResult, FieldRef, ScanLimits};
//! use std::{fs::File, io::BufReader};
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let file = File::open("backup.dump")?;
//! let mut archive = Archive::open(BufReader::new(file))?;
//! let mut rows = archive.table_rows(b"public", b"orders")?;
//!
//! let limits = ScanLimits::unlimited()
//! .with_max_rows(100_000)
//! .with_max_decompressed_bytes(64 * 1024 * 1024);
//! let matched = rows.find_first_equal_with_limits(
//! limits,
//! b"order_number",
//! FieldRef::Bytes(b"123456"),
//! )?;
//!
//! if let ColumnEqualityResult::Match(row) = matched {
//! assert!(!row.fields().is_empty());
//! }
//! # Ok(())
//! # }
//! ```
pub use Archive;
pub use ;
pub use ;
pub use EntryDataReader;
pub use PgDumpError;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use TableSelector;
pub use ;