Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ jobs:
- name: Workflow policy contract
run: python3 scripts/test-workflow-policy.py

- name: Release readiness contract
run: python3 scripts/test-release-readiness.py

- name: Clippy
run: cargo clippy --workspace --all-targets --all-features -- -D warnings

Expand Down Expand Up @@ -82,7 +85,7 @@ jobs:
- name: Test workspace with all compression backends
run: cargo test --workspace --all-features

- name: Verify default CLI supports all v0.1 compression backends
- name: Verify default CLI supports all compression backends
run: cargo test -p pgdumpx-cli --test compression_defaults

feature-matrix:
Expand Down
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Changelog

This changelog records user-visible behavior, compatibility boundaries, and release-engineering changes for published versions of `pgdumpx`.

## [0.2.0]

This is the first public release of the `pgdumpx` library and `pgdumpx-cli` package. It includes the completed archive-reading foundation and the subsequent reusable extraction, filtering, scan-budget, security, and maintenance work.

### Added

- Read-only parsing of PostgreSQL custom-format archive metadata for archive versions 1.14 through 1.16.
- Bounded selected-entry streaming for none, gzip, LZ4, and Zstandard compression.
- Byte-oriented PostgreSQL COPY text row parsing with explicit NULL handling and exact named-column equality helpers.
- File-oriented archive helpers, owned table selectors, reusable extraction plans, sequential multi-table extraction, and metadata filtering.
- `pgdumpx` CLI commands for `inspect`, `list`, `extract`, and `find`, with finite default structural, raw-output, row, and decompressed-byte budgets.
- Reproducible official fixture provenance, compatibility differential checks, benchmark harnesses, fuzz targets, scheduled fuzz campaigns, and dependency advisory checks.

### Compatibility

- Rust 1.85.0 is the minimum supported Rust version.
- Linux, macOS, and Windows are exercised by the stable CI matrix.
- Row-aware APIs support PostgreSQL COPY text table data. Binary COPY and INSERT-based row decoding are intentionally unsupported; readable INSERT entries remain available through raw extraction.

### Security and reliability

- Project-authored Rust code forbids `unsafe`.
- Structural parsing, raw extraction, and row scanning expose separate typed resource limits.
- GitHub workflows use immutable action revisions, read-only permissions, disabled checkout credential persistence, concurrency cancellation, and finite job timeouts.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ exclude = ["fuzz"]
resolver = "3"

[workspace.package]
version = "0.1.0"
version = "0.2.0"
edition = "2024"
rust-version = "1.85.0"
license = "MIT OR Apache-2.0"
repository = "https://github.com/tappe9/pgdumpx"
homepage = "https://github.com/tappe9/pgdumpx"
readme = "README.md"

[workspace.lints.rust]
Expand Down
12 changes: 9 additions & 3 deletions crates/pgdumpx-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
[package]
name = "pgdumpx-cli"
description = "Command-line interface for pgdumpx"
description = "Command-line inspection, extraction, and row scanning for PostgreSQL custom-format dumps"
autobins = false
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
readme.workspace = true
documentation = "https://docs.rs/pgdumpx-cli"
keywords = ["postgresql", "pg-dump", "backup", "cli", "forensics"]
categories = ["database", "command-line-utilities"]
publish = ["crates-io"]

[[bin]]
name = "pgdumpx"
path = "src/main.rs"
path = "src/entrypoint.rs"
doc = false

[features]
Expand All @@ -19,7 +25,7 @@ lz4 = ["pgdumpx/lz4"]
zstd = ["pgdumpx/zstd"]

[dependencies]
pgdumpx = { path = "../pgdumpx", version = "0.1.0", default-features = false }
pgdumpx = { path = "../pgdumpx", version = "0.2.0", default-features = false }

[lints]
workspace = true
93 changes: 93 additions & 0 deletions crates/pgdumpx-cli/src/entrypoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use std::{
env,
ffi::{OsStr, OsString},
io::{self, BufWriter, Write},
process::ExitCode,
};

const FAILURE_EXIT: u8 = 2;
const VERSION: &str = concat!("pgdumpx ", env!("CARGO_PKG_VERSION"));

fn main() -> ExitCode {
match global_option(env::args_os()) {
Some(GlobalOption::Help) => write_global_output(application::usage()),
Some(GlobalOption::Version) => write_global_output(VERSION),
None => application::launch(),
}
}

fn global_option<I>(arguments: I) -> Option<GlobalOption>
where
I: IntoIterator<Item = OsString>,
{
let mut arguments = arguments.into_iter();
let _program = arguments.next();
let option = arguments.next()?;
if arguments.next().is_some() {
return None;
}

match option.as_os_str() {
value if value == OsStr::new("--help") || value == OsStr::new("-h") => {
Some(GlobalOption::Help)
}
value if value == OsStr::new("--version") || value == OsStr::new("-V") => {
Some(GlobalOption::Version)
}
_ => None,
}
}

fn write_global_output(value: &str) -> ExitCode {
let stdout = io::stdout();
let mut stdout = BufWriter::new(stdout.lock());
if let Err(source) = writeln!(stdout, "{value}").and_then(|()| stdout.flush()) {
eprintln!("pgdumpx: stdout error: {source}");
return ExitCode::from(FAILURE_EXIT);
}
ExitCode::SUCCESS
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum GlobalOption {
Help,
Version,
}

mod application {
pub(super) fn launch() -> ExitCode {
main()
}

pub(super) fn usage() -> &'static str {
USAGE
}

include!("main.rs");
}

#[cfg(test)]
mod tests {
use super::{GlobalOption, global_option};
use std::ffi::OsString;

#[test]
fn global_options_require_exactly_one_option_argument() {
assert_eq!(parse(&["--help"]), Some(GlobalOption::Help));
assert_eq!(parse(&["-h"]), Some(GlobalOption::Help));
assert_eq!(parse(&["--version"]), Some(GlobalOption::Version));
assert_eq!(parse(&["-V"]), Some(GlobalOption::Version));
assert_eq!(parse(&[]), None);
assert_eq!(parse(&["--help", "unexpected"]), None);
assert_eq!(parse(&["inspect"]), None);
}

fn parse(arguments: &[&str]) -> Option<GlobalOption> {
global_option(
["pgdumpx"]
.into_iter()
.chain(arguments.iter().copied())
.map(OsString::from),
)
}
}
43 changes: 43 additions & 0 deletions crates/pgdumpx-cli/tests/global_options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use std::process::{Command, Output};

fn run(arguments: &[&str]) -> Output {
Command::new(env!("CARGO_BIN_EXE_pgdumpx"))
.args(arguments)
.output()
.expect("run pgdumpx")
}

#[test]
fn version_reports_the_package_version_on_stdout() {
let output = run(&["--version"]);

assert!(
output.status.success(),
"status: {:?}",
output.status.code()
);
assert_eq!(output.stdout, b"pgdumpx 0.2.0\n");
assert!(output.stderr.is_empty());
}

#[test]
fn help_reports_global_usage_on_stdout() {
let output = run(&["--help"]);

assert!(
output.status.success(),
"status: {:?}",
output.status.code()
);
assert!(output.stderr.is_empty());

let stdout = String::from_utf8(output.stdout).expect("help is UTF-8");
assert!(stdout.starts_with("usage:\n"));
for command in ["inspect", "list", "extract", "find"] {
assert!(
stdout.contains(command),
"missing command {command:?}: {stdout}"
);
}
assert!(stdout.ends_with('\n'));
}
7 changes: 6 additions & 1 deletion crates/pgdumpx/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
[package]
name = "pgdumpx"
description = "A bounded, byte-oriented row scanner for PostgreSQL custom-format dumps"
description = "Read-only, bounded inspection, extraction, and row scanning for PostgreSQL custom-format dumps"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
readme.workspace = true
documentation = "https://docs.rs/pgdumpx"
keywords = ["postgresql", "pg-dump", "backup", "parser", "forensics"]
categories = ["database", "parser-implementations"]
publish = ["crates-io"]

[features]
default = ["lz4", "zstd"]
Expand Down
Loading