From 0dc16923cf0c09868adbf65622c2a5493ee9f75e Mon Sep 17 00:00:00 2001 From: Yuma Takino <36617106+tappe9@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:11:40 +0900 Subject: [PATCH 1/4] test(docs): add public documentation contract --- scripts/test-public-documentation.py | 154 +++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 scripts/test-public-documentation.py diff --git a/scripts/test-public-documentation.py b/scripts/test-public-documentation.py new file mode 100644 index 0000000..327178e --- /dev/null +++ b/scripts/test-public-documentation.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +"""Verify that public documentation matches pgdumpx 0.2.0.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] + +REQUIRED_SNIPPETS = { + "README.md": [ + "Current source version: `0.2.0`", + "## Installation", + "### From crates.io", + "### From source", + "Registry commands require", + "`Archive::open_path`", + "`TableSelector`", + "`ExtractionPlan`", + "`MetadataFilter`", + "`find_first_equal_by_name_with_limits`", + ], + "README.ja.md": [ + "現在のソースバージョン: `0.2.0`", + "## インストール", + "### crates.io から", + "### ソースから", + "crates.ioでpackage versionを確認してから", + "`Archive::open_path`", + "`TableSelector`", + "`ExtractionPlan`", + "`MetadataFilter`", + "`find_first_equal_by_name_with_limits`", + ], + "ROADMAP.md": [ + "## v0.2 — Completed", + "### Delivered", + "#57–#63", + "#70–#78", + "#89", + "#92", + "## v0.3+ — Deferred candidates", + ], + "docs/API-DESIGN.md": [ + "## 2.1 File-oriented convenience", + "## 10.1 Owned table selectors", + "## 11.1 Reusable extraction plans", + "## 11.2 Sequential multi-table execution", + "## 15.1 Metadata filtering", + "## 16.1 Exact named-column equality", + ], + "SECURITY.md": [ + "latest published 0.2.x release", + "current `main`", + "If no package has been published yet", + ], + "CONTRIBUTING.md": [ + "python3 scripts/test-public-documentation.py", + "python3 scripts/test-release-readiness.py", + "python3 scripts/verify-release-packaging.py", + "[Release procedure](docs/RELEASING.md)", + ], +} + +FORBIDDEN_SNIPPETS = { + "README.md": [ + "v0.2 is planned", + "no v0.2 production code has merged yet", + "**Planned next:** v0.2", + ], + "README.ja.md": [ + "v0.2は[Tracking Issue #56]", + "v0.2のproduction codeはまだ`main`へmergeされていません", + "**次に実装するv0.2:**", + ], + "ROADMAP.md": [ + "## v0.2 candidate", + "No production implementation exists yet", + ], + "docs/API-DESIGN.md": [ + "The current repository is still in the design phase", + ], + "SECURITY.md": [ + "pgdumpx has no published release version yet", + "## Current v0.1 scope boundary", + ], +} + +ALIGNED_README_IDENTIFIERS = [ + "0.2.0", + "Archive::open_path", + "TableSelector", + "ExtractionPlan", + "MetadataFilter", + "find_first_equal_by_name_with_limits", + "100,000", + "64 MiB", + "Binary COPY", +] + + +def read_document(relative: str, errors: list[str]) -> str: + path = ROOT / relative + if not path.is_file(): + errors.append(f"missing public document: {relative}") + return "" + return path.read_text(encoding="utf-8") + + +def main() -> int: + errors: list[str] = [] + documents = { + relative: read_document(relative, errors) + for relative in set(REQUIRED_SNIPPETS) | set(FORBIDDEN_SNIPPETS) + } + + for relative, snippets in REQUIRED_SNIPPETS.items(): + text = documents[relative] + for snippet in snippets: + if snippet not in text: + errors.append(f"{relative}: missing required text {snippet!r}") + + for relative, snippets in FORBIDDEN_SNIPPETS.items(): + text = documents[relative] + for snippet in snippets: + if snippet in text: + errors.append(f"{relative}: stale text remains {snippet!r}") + + english = documents["README.md"] + japanese = documents["README.ja.md"] + for identifier in ALIGNED_README_IDENTIFIERS: + missing = [ + relative + for relative, text in (("README.md", english), ("README.ja.md", japanese)) + if identifier not in text + ] + if missing: + errors.append( + f"README alignment: {identifier!r} missing from {', '.join(missing)}" + ) + + if errors: + print("public documentation contract failed:", file=sys.stderr) + for error in errors: + print(f" - {error}", file=sys.stderr) + return 1 + + print("public documentation contract passed for pgdumpx 0.2.0") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 24da9b807ef79b13e434914ce7181c3db9631bc2 Mon Sep 17 00:00:00 2001 From: Yuma Takino <36617106+tappe9@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:13:27 +0900 Subject: [PATCH 2/4] ci: run public documentation contract --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f36cf01..dc377a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,9 @@ jobs: - name: Documentation links run: python3 scripts/verify-doc-links.py + - name: Public documentation contract + run: python3 scripts/test-public-documentation.py + - name: Advisory policy contract run: python3 scripts/test-advisory-policy.py From 848c34f2ee28c2a9190b97ea3212658a4466ad2c Mon Sep 17 00:00:00 2001 From: Yuma Takino <36617106+tappe9@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:17:41 +0900 Subject: [PATCH 3/4] ci: stage documentation audit source --- .github/workflows/ci.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc377a9..f4b2aed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -235,3 +235,25 @@ jobs: - name: Check workspace run: cargo +1.85.0 check --workspace --all-targets --all-features + + documentation-audit-source: + name: Documentation audit source + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + + - name: Archive tracked files + run: git archive --format=tar.gz --output=pgdumpx-documentation-audit.tar.gz HEAD + + - name: Upload documentation audit source + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: documentation-audit-source-${{ github.sha }} + path: pgdumpx-documentation-audit.tar.gz + if-no-files-found: error + retention-days: 1 + include-hidden-files: false From 9f0f9a26e9972e3a5d0085a30b6dc6190177ffef Mon Sep 17 00:00:00 2001 From: y_takino Date: Tue, 1 Sep 2026 21:29:01 +0900 Subject: [PATCH 4/4] docs: complete 0.2.0 public documentation --- .github/workflows/ci.yml | 22 ---- CONTRIBUTING.md | 10 +- README.ja.md | 114 ++++++++++++------- README.md | 114 ++++++++++++------- ROADMAP.md | 90 ++++----------- SECURITY.md | 4 +- docs/API-DESIGN.md | 157 +++++++++++++++++++++++++-- scripts/test-public-documentation.py | 6 +- 8 files changed, 331 insertions(+), 186 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4b2aed..dc377a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -235,25 +235,3 @@ jobs: - name: Check workspace run: cargo +1.85.0 check --workspace --all-targets --all-features - - documentation-audit-source: - name: Documentation audit source - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - with: - persist-credentials: false - - - name: Archive tracked files - run: git archive --format=tar.gz --output=pgdumpx-documentation-audit.tar.gz HEAD - - - name: Upload documentation audit source - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: documentation-audit-source-${{ github.sha }} - path: pgdumpx-documentation-audit.tar.gz - if-no-files-found: error - retention-days: 1 - include-hidden-files: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b03735f..341eae6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -117,17 +117,17 @@ Run the same baseline quality gates used by CI before opening a pull request: ```bash cargo fmt --check python3 scripts/verify-doc-links.py +python3 scripts/test-public-documentation.py +python3 scripts/test-release-readiness.py cargo clippy --workspace --all-targets --all-features -- -D warnings cargo test --workspace --all-features RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps -``` - -Verify the declared MSRV separately: - -```bash cargo +1.85.0 check --workspace --all-targets --all-features +python3 scripts/verify-release-packaging.py ``` +The packaging verifier performs additional package-content, license, feature, CLI, install, and publish-dry-run checks. Follow the [Release procedure](docs/RELEASING.md) for the staged registry, tag, and GitHub Release sequence; these commands do not publish anything. + ### Dependency advisory changes Dependency or advisory-policy changes must also run the repository policy tests and the pinned advisory command: diff --git a/README.ja.md b/README.ja.md index 03b8343..924534d 100644 --- a/README.ja.md +++ b/README.ja.md @@ -2,7 +2,7 @@ **PostgreSQL Custom Formatをrestoreせず、byte-orientedなrowとして安全にscanするRustライブラリ / CLI。** -> ステータス: v0.1の実装とrelease-readiness作業は完了しています。v0.2は[Tracking Issue #56](https://github.com/tappe9/pgdumpx/issues/56)で計画済みですが、v0.2のproduction codeはまだ`main`へmergeされていません。crate / CLIの公開releaseもまだ行っていません。 +> 現在のソースバージョン: `0.2.0`。v0.1のfoundationとv0.2の機能は実装済みです。registry commandを使うには、対象package versionがcrates.ioに存在する必要があります。ソースからのinstallはregistry公開状態に依存しません。 pgdumpxは、PostgreSQL Custom Format (`pg_dump -Fc`) archiveを、データベースへrestoreせずに検査するread-onlyのRustライブラリ / CLIです。 @@ -16,9 +16,10 @@ pgdumpxは、PostgreSQL Custom Format (`pg_dump -Fc`) archiveを、データベ ## 現在の開発状況 -- **`main`で実装済み:** metadata inspection、4種類のcompression backend、bounded raw extraction、COPY row parsing、first-match search、各種limits、fuzz / benchmark / CI evidence、rustdoc、packaging / release-readiness verificationを含むv0.1全体。 -- **次に実装するv0.2:** file-oriented convenience API ([#57](https://github.com/tappe9/pgdumpx/issues/57))、owned byte-oriented table selector ([#58](https://github.com/tappe9/pgdumpx/issues/58))、reusable extraction plan ([#59](https://github.com/tappe9/pgdumpx/issues/59))をfoundationとし、sequential multi-table executionとselection helperを[#60](https://github.com/tappe9/pgdumpx/issues/60)〜[#62](https://github.com/tappe9/pgdumpx/issues/62)で追加する計画です。 -- **activeなv0.2実装範囲から明示的に除外:** parallel extraction、sidecar index / restart-point scheme、data-only archive identity support、v0.3以降のdata ecosystem integration。 +- **v0.1 foundation完了:** metadata inspection、4種類のcompression backend、bounded raw extraction、COPY row parsing、first-match search、各種limits、fuzz / benchmark / CI evidence、rustdoc、packaging verification。 +- **v0.2完了:** file-oriented opening、owned byte-oriented selector、reusable extraction plan、deterministicなsequential multi-table extraction、metadata filtering、exact named-column equality helperを実装済みです。 +- **correctness / maintenance follow-up完了:** destination flush failure、aggregate metadata budget、terminal row-reader error、field-count validation、linear duplicate detection、finite CLI scan default、feature matrix test、scheduled fuzz、advisory policy、workflow hardeningを現在のsourceに含みます。 +- **v0.3以降のcandidateはdeferred:** parallel extraction、sidecar index / restart-point scheme、data-only archive identity support、data ecosystem integrationはactive commitmentではありません。 実装順と依存関係は[ROADMAP.md](ROADMAP.md)を参照してください。 @@ -43,7 +44,7 @@ flowchart TD J --> K["sequential predicate scan
first matchでearly stop"] ``` -v0.1では次を重視します。 +実装では次を重視します。 - PostgreSQL Custom Format (`-Fc`) の**read-only parser** - 実行時にPostgreSQL server、`libpq`、`pg_restore`を必要としない構成 @@ -71,15 +72,15 @@ PostgreSQL dumpをrestore専用ファイルではなく、offline data sourceと - 選択したrow streamをCSV、JSON Lines、Arrow、Parquet等へ変換する - Rust coreを共通基盤としてCLIや将来のPython bindingを構築する -## v0.1の対象 +## 対応範囲 -v0.1はPostgreSQL Custom Formatだけを対象にします。 +pgdumpxはPostgreSQL Custom Formatだけを対象にします。 ```bash pg_dump -Fc mydb > backup.dump ``` -実装済みのv0.1互換範囲はArchive Format Version **1.14〜1.16**と、次のcompressionです。 +実装済みの互換範囲はArchive Format Version **1.14〜1.16**と、次のcompressionです。 - none - gzip @@ -88,7 +89,7 @@ pg_dump -Fc mydb > backup.dump older archive versionについて、すべてのbackendのfixtureが存在するとは限らないため、fixture-backed verificationは意図的により狭く記述しています。version / compressionごとの正確なevidence matrixは[docs/COMPATIBILITY.md](docs/COMPATIBILITY.md)を参照してください。そこに記載するclaimはofficial PostgreSQL-generated fixtureとproduction-path differential checkで裏付けた範囲に限定します。 -row-aware APIが対象にするのは、通常のpg_dumpが生成するPostgreSQL `COPY` text形式のtable dataです。次はv0.1 row parserの対象外です。 +row-aware APIが対象にするのは、通常のpg_dumpが生成するPostgreSQL `COPY` text形式のtable dataです。次はrow parserの対象外です。 - `--inserts` - `--column-inserts` @@ -97,50 +98,77 @@ row-aware APIが対象にするのは、通常のpg_dumpが生成するPostgreSQ unsupported representationはCOPY textとして推測せず、row APIからtyped errorで失敗させます。archive entry自体が読める場合はraw extractionを利用できる可能性があります。 -## Rust API +## v0.2で追加したlibrary機能 -v0.1 libraryではmetadata inspection、selected-entry streaming、COPY row access、first-match search、structural / scan / raw-extraction limits、contextual typed error、4種類の対応compression backendを実装済みです。 +- `Archive::open_path` / `Archive::open_path_with_limits`は、同じgeneric `Read + Seek` parserへ委譲するfile-oriented openingです。 +- `TableSelector`はschema / table nameのexact bytesを所有し、複数archiveで再利用できます。 +- `ExtractionPlan`はordered selectorと`EntryReadLimits`を保持します。全targetのmetadata-only preflightをdestination作成前に完了し、その後deterministicなselector orderでbounded raw extractionをsequentialに実行します。 +- multi-table executionは1つのmutable seekable sourceを維持します。2つ目のhandleを開いたり、concurrent extractionを保証したりしません。 +- 後続targetが失敗しても、先に完了したtargetは結果へ残ります。失敗したdestinationにはpartial bytesが存在し得て、destinationの`flush` failureも成功ではなくoutput errorとして返します。 +- `MetadataFilter`はpayloadを読まず、parse済みTOC metadataをexact schema / object type / nameでfilterします。namespace absentとpresent empty namespaceは別状態です。 +- `TableRowReader`は`find_first_equal` / `find_first_equal_with_limits`を提供します。どちらもcolumnを1回resolveし、logical COPY bytesをexact比較します。SQL NULL、empty bytes、literal `b"\\N"` bytesは区別されます。 -```rust -use pgdumpx::{Archive, FieldRef}; +すべてのrow searchはsequential scanのままです。helperは再利用可能なselection policyを追加しますが、row-level indexは追加しません。 -let file = std::fs::File::open("backup.dump")?; -let mut archive = Archive::open(file)?; +## インストール -println!("archive version: {:?}", archive.header().version()); +### crates.io から -for entry in archive.entries() { - println!("{entry:?}"); -} +crates.ioでpackage versionを確認してからinstallまたはdependency追加を行ってください。次のregistry commandには`0.2.0`がcrates.ioに存在することが必要です。 -let mut rows = archive.table_rows(b"public", b"orders")?; -while let Some(row) = rows.next_row()? { - println!("{:?}", row); -} +```bash +cargo info pgdumpx@0.2.0 +cargo info pgdumpx-cli@0.2.0 +cargo install pgdumpx-cli --version 0.2.0 --locked ``` -`next_row(&mut self)`を通常の`Iterator`にしないのは意図的です。borrowed `Row`は再利用可能な内部storageを参照し、次のmutable reader operationまでだけ有効です。 +library consumerは次を追加できます。 + +```toml +[dependencies] +pgdumpx = "0.2.0" +``` -明示的なtotal-work budgetの下で、最初に一致する1行を取得できます。 +### ソースから -```rust -use pgdumpx::ScanLimits; +この手順はcrates.ioの公開状態に依存しません。 -let mut rows = archive.table_rows(b"public", b"orders")?; -let order_number = rows - .column_index(b"order_number")? - .ok_or(/* application error */)?; +```bash +git clone https://github.com/tappe9/pgdumpx.git +cd pgdumpx +cargo install --path crates/pgdumpx-cli --locked +pgdumpx --help +``` + +## Rust API -let scan_limits = ScanLimits::unlimited() - .with_max_rows(100_000) - .with_max_decompressed_bytes(64 * 1024 * 1024); +libraryではmetadata inspection、selected-entry streaming、COPY row access、reusable extraction policy、exact equality search、structural / scan / raw-extraction limits、contextual typed error、4種類の対応compression backendを実装済みです。 -let row = rows.find_first_with_limits(scan_limits, |row| { - row.field(order_number) == Some(FieldRef::Bytes(b"123456")) -})?; +```rust +use pgdumpx::{Archive, ColumnEqualityResult, FieldRef, ScanLimits}; + +fn main() -> Result<(), Box> { + let mut archive = Archive::open_path("backup.dump")?; + let mut rows = archive.table_rows(b"public", b"orders")?; + let scan_limits = ScanLimits::unlimited() + .with_max_rows(100_000) + .with_max_decompressed_bytes(64 * 1024 * 1024); + + let result = rows.find_first_equal_with_limits( + scan_limits, + b"order_number", + FieldRef::Bytes(b"123456"), + )?; + + if let ColumnEqualityResult::Match(row) = result { + println!("{row:?}"); + } + + Ok(()) +} ``` -`find_first`は追加のoperation-level scan budgetを適用しないconvenience pathです。`find_first_with_limits`は同じstreaming parser / predicate loopへ`ScanLimits`を適用します。 +`next_row(&mut self)`を通常の`Iterator`にしないのは意図的です。borrowed `Row`は再利用可能な内部storageを参照し、次のmutable reader operationまでだけ有効です。`find_first` / `find_first_with_limits`はgeneric predicate pathとして残り、equality helperも同じscanへ委譲します。 `column_index()`は次を区別します。 @@ -259,6 +287,14 @@ benchmark harnessはdataset / generator、command/API path、hardware/OS、exact v0.1の最終Definition of Done evidence mappingは[docs/V0.1-RELEASE-AUDIT.md](docs/V0.1-RELEASE-AUDIT.md)に記録しています。 +## Contribution / support + +- code / documentation changeを提案する前に[CONTRIBUTING.md](CONTRIBUTING.md)を確認してください。 +- 再現可能なbugは[GitHub Issues](https://github.com/tappe9/pgdumpx/issues)で報告してください。 +- vulnerabilityは[SECURITY.md](SECURITY.md)に従ってprivateに報告し、public issueへexploit detailを投稿しないでください。 +- compatibility / performance claimの前に、evidence-backedな[compatibility matrix](docs/COMPATIBILITY.md)と[benchmark methodology](benchmarks/README.md)を確認してください。 +- package / publication boundaryは[packaging constraints](docs/PACKAGING.md)と[release process](docs/RELEASING.md)を参照してください。 + ## Related projects - [`libpgdump`](https://github.com/gmr/libpgdump) — PostgreSQL custom / directory / tar dumpをread/writeするRust library @@ -281,7 +317,7 @@ v0.1の最終Definition of Done evidence mappingは[docs/V0.1-RELEASE-AUDIT.md]( - [`find` scan-budget policy](docs/FIND-SCAN-LIMITS.md) — finite CLI default、根拠、boundary、migration guidance - [Packaging audit](docs/PACKAGING.md) — package/license/runtime dependency boundary - [v0.1 release audit](docs/V0.1-RELEASE-AUDIT.md) — 最終DoD-to-evidence mapping -- [Roadmap](ROADMAP.md) — delivered v0.1 slice、planned v0.2 issue sequence、later candidate scope +- [Roadmap](ROADMAP.md) — delivered v0.1 / v0.2 work、deferred candidate scope - [Architecture Decision Records](docs/adr/) — accepted / superseded design decisions - [Contributing](CONTRIBUTING.md) — contribution / document-update policy - [Security policy](SECURITY.md) — vulnerability reporting / resource-threat model diff --git a/README.md b/README.md index f34972f..ca2ead2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **A bounded, byte-oriented row scanner for PostgreSQL custom-format dumps.** -> Status: the v0.1 implementation and release-readiness work are complete. v0.2 is planned in [Tracking Issue #56](https://github.com/tappe9/pgdumpx/issues/56), but no v0.2 production code has merged yet. No crate or CLI release has been published yet. +> Current source version: `0.2.0`. The v0.1 foundation and v0.2 functionality are implemented. Registry commands require the exact package version to be available on crates.io; the source installation path works independently of registry publication. pgdumpx is a read-only Rust library and CLI for inspecting PostgreSQL custom-format (`pg_dump -Fc`) archives without restoring them into a database. @@ -16,9 +16,10 @@ A representative use case is: ## Current development status -- **Implemented on `main`:** the complete v0.1 bounded row-scanning scope, including metadata inspection, four compression backends, bounded raw extraction, COPY row parsing, first-match search, limits, fuzz/benchmark/CI evidence, rustdoc, and packaging/release-readiness verification. -- **Planned next:** v0.2 starts with file-oriented convenience APIs ([#57](https://github.com/tappe9/pgdumpx/issues/57)), owned byte-oriented table selectors ([#58](https://github.com/tappe9/pgdumpx/issues/58)), and reusable extraction plans ([#59](https://github.com/tappe9/pgdumpx/issues/59)); sequential multi-table execution and selection helpers follow in [#60](https://github.com/tappe9/pgdumpx/issues/60)–[#62](https://github.com/tappe9/pgdumpx/issues/62). -- **Explicitly deferred from the active v0.2 implementation plan:** parallel extraction, sidecar indexes/restart-point schemes, data-only archive identity support, and v0.3+ data-ecosystem integrations. +- **v0.1 foundation completed:** metadata inspection, four compression backends, bounded raw extraction, COPY row parsing, first-match search, limits, fuzz/benchmark/CI evidence, rustdoc, and packaging verification. +- **v0.2 completed:** file-oriented opening, owned byte-oriented selectors, reusable extraction plans, deterministic sequential multi-table extraction, metadata filtering, and exact named-column equality helpers are implemented. +- **Correctness and maintenance follow-ups completed:** destination flush failures, aggregate metadata budgets, terminal row-reader errors, field-count validation, linear duplicate detection, finite CLI scan defaults, feature-matrix testing, scheduled fuzzing, advisory policy, and workflow hardening are included in the current source. +- **v0.3+ candidates remain deferred:** parallel extraction, sidecar indexes/restart-point schemes, data-only archive identity support, and data-ecosystem integrations are not active commitments. See [ROADMAP.md](ROADMAP.md) for delivery order and dependency boundaries. @@ -43,7 +44,7 @@ flowchart TD J --> K["Sequential predicate scan
early stop on first match"] ``` -The v0.1 design emphasizes: +The implemented design emphasizes: - **read-only parsing** of PostgreSQL Custom Format archives; - no running PostgreSQL server, `libpq`, or `pg_restore` requirement at runtime; @@ -71,15 +72,15 @@ pgdumpx targets situations where a PostgreSQL dump is useful as an offline data - convert selected row streams into CSV, JSON Lines, Arrow, or Parquet in downstream tools; - build Rust, CLI, Python, or analytical tooling on one reusable row-scanning core. -## v0.1 scope +## Supported scope -v0.1 targets PostgreSQL Custom Format only: +pgdumpx targets PostgreSQL Custom Format only: ```bash pg_dump -Fc mydb > backup.dump ``` -The implemented v0.1 compatibility range is archive format versions **1.14 through 1.16** with: +The implemented compatibility range is archive format versions **1.14 through 1.16** with: - none; - gzip; @@ -88,7 +89,7 @@ The implemented v0.1 compatibility range is archive format versions **1.14 throu Fixture-backed verification is deliberately narrower where an older archive version does not have a committed fixture for every backend. The exact version/compression evidence matrix lives in [docs/COMPATIBILITY.md](docs/COMPATIBILITY.md); claims there are backed by official PostgreSQL-generated fixtures and production-path differential checks. -Row-aware access targets normal pg_dump table data represented as PostgreSQL `COPY` text. The following are explicitly outside the v0.1 row parser: +Row-aware access targets normal pg_dump table data represented as PostgreSQL `COPY` text. The following are explicitly outside the row parser: - `--inserts`; - `--column-inserts`; @@ -97,50 +98,77 @@ Row-aware access targets normal pg_dump table data represented as PostgreSQL `CO Unsupported logical representations fail through a typed row-API error rather than being guessed as COPY text. Raw entry extraction may still be available when the archive entry itself is structurally readable. -## Rust API +## v0.2 library additions -The v0.1 library implements metadata inspection, selected-entry streaming, COPY row access, first-match search, structural/scan/raw-extraction limits, contextual typed errors, and all four supported compression backends. +- `Archive::open_path` and `Archive::open_path_with_limits` provide file-oriented opening while delegating to the same generic `Read + Seek` parser. +- `TableSelector` owns exact schema and table-name bytes and can be reused across archives. +- `ExtractionPlan` stores ordered selectors and `EntryReadLimits`. It completes metadata-only preflight for every target before requesting a destination, then executes bounded raw extraction sequentially in deterministic selector order. +- Multi-table execution keeps one mutable seekable source. It does not open a second handle or claim concurrent extraction. +- Completed targets remain reported if a later target fails. The current destination may already contain partial bytes, and a destination `flush` failure is returned as an output error rather than successful completion. +- `MetadataFilter` matches already-parsed TOC metadata by exact schema, object type, and name without reading payloads. An absent namespace is distinct from a present empty namespace. +- `TableRowReader` provides `find_first_equal` and `find_first_equal_with_limits`; both resolve one column once and compare exact logical COPY bytes. SQL NULL, empty bytes, and literal `b"\\N"` bytes remain distinct. -```rust -use pgdumpx::{Archive, FieldRef}; +All row searches remain sequential scans. The helpers add reusable selection policy, not a row-level index. -let file = std::fs::File::open("backup.dump")?; -let mut archive = Archive::open(file)?; +## Installation -println!("archive version: {:?}", archive.header().version()); +### From crates.io -for entry in archive.entries() { - println!("{entry:?}"); -} +Registry commands require `0.2.0` to be available on crates.io. Check the exact versions before installing or adding the dependency: -let mut rows = archive.table_rows(b"public", b"orders")?; -while let Some(row) = rows.next_row()? { - println!("{:?}", row); -} +```bash +cargo info pgdumpx@0.2.0 +cargo info pgdumpx-cli@0.2.0 +cargo install pgdumpx-cli --version 0.2.0 --locked ``` -`next_row(&mut self)` is intentionally not a normal `Iterator` method: each borrowed `Row` references reusable internal storage and remains valid only until the next mutable reader operation. +Library consumers can add: + +```toml +[dependencies] +pgdumpx = "0.2.0" +``` -A primary v0.1 use case is finding the first matching row with explicit total-work budgets: +### From source -```rust -use pgdumpx::ScanLimits; +This path works independently of crates.io publication: -let mut rows = archive.table_rows(b"public", b"orders")?; -let order_number = rows - .column_index(b"order_number")? - .ok_or(/* application error */)?; +```bash +git clone https://github.com/tappe9/pgdumpx.git +cd pgdumpx +cargo install --path crates/pgdumpx-cli --locked +pgdumpx --help +``` + +## Rust API -let scan_limits = ScanLimits::unlimited() - .with_max_rows(100_000) - .with_max_decompressed_bytes(64 * 1024 * 1024); +The library implements metadata inspection, selected-entry streaming, COPY row access, reusable extraction policy, exact equality search, structural/scan/raw-extraction limits, contextual typed errors, and all four supported compression backends. -let row = rows.find_first_with_limits(scan_limits, |row| { - row.field(order_number) == Some(FieldRef::Bytes(b"123456")) -})?; +```rust +use pgdumpx::{Archive, ColumnEqualityResult, FieldRef, ScanLimits}; + +fn main() -> Result<(), Box> { + let mut archive = Archive::open_path("backup.dump")?; + let mut rows = archive.table_rows(b"public", b"orders")?; + let scan_limits = ScanLimits::unlimited() + .with_max_rows(100_000) + .with_max_decompressed_bytes(64 * 1024 * 1024); + + let result = rows.find_first_equal_with_limits( + scan_limits, + b"order_number", + FieldRef::Bytes(b"123456"), + )?; + + if let ColumnEqualityResult::Match(row) = result { + println!("{row:?}"); + } + + Ok(()) +} ``` -`find_first` is the convenience path without additional operation-level scan budgets. `find_first_with_limits` uses the same streaming parser and predicate loop while enforcing the supplied `ScanLimits`. +`next_row(&mut self)` is intentionally not a normal `Iterator` method: each borrowed `Row` references reusable internal storage and remains valid only until the next mutable reader operation. `find_first` / `find_first_with_limits` retain the generic predicate path; the equality helpers delegate to that same scan. Column lookup distinguishes three states: @@ -259,6 +287,14 @@ The benchmark harness records the dataset/generator, command/API path, hardware/ The final v0.1 Definition of Done evidence mapping is recorded in [docs/V0.1-RELEASE-AUDIT.md](docs/V0.1-RELEASE-AUDIT.md). +## Contributing and support + +- Read [CONTRIBUTING.md](CONTRIBUTING.md) before proposing code or documentation changes. +- Report reproducible bugs through [GitHub Issues](https://github.com/tappe9/pgdumpx/issues). +- Report vulnerabilities privately as described in [SECURITY.md](SECURITY.md); do not publish exploit details in a public issue. +- Check the evidence-backed [compatibility matrix](docs/COMPATIBILITY.md) and [benchmark methodology](benchmarks/README.md) before making compatibility or performance claims. +- See [packaging constraints](docs/PACKAGING.md) and the [release process](docs/RELEASING.md) for package and publication boundaries. + ## Related projects - [`libpgdump`](https://github.com/gmr/libpgdump) — a Rust library for reading and writing PostgreSQL custom, directory, and tar dump formats. @@ -281,7 +317,7 @@ Each document has one primary responsibility to reduce duplication and drift: - [`find` scan-budget policy](docs/FIND-SCAN-LIMITS.md) — finite CLI defaults, evidence, boundary semantics, and migration guidance; - [Packaging audit](docs/PACKAGING.md) — package/license/runtime dependency boundary; - [v0.1 release audit](docs/V0.1-RELEASE-AUDIT.md) — final DoD-to-evidence mapping; -- [Roadmap](ROADMAP.md) — delivered v0.1 slices, the planned v0.2 issue sequence, and later candidate scope; +- [Roadmap](ROADMAP.md) — delivered v0.1/v0.2 work and deferred candidate scope; - [Architecture Decision Records](docs/adr/) — accepted and superseded design decisions; - [Contributing](CONTRIBUTING.md) — contribution and document-update policy; - [Security policy](SECURITY.md) — vulnerability reporting and resource-threat model. diff --git a/ROADMAP.md b/ROADMAP.md index 11c6cad..9c630c8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,8 +1,8 @@ # pgdumpx Roadmap -Status: **v0.1 implementation and release-readiness complete; v0.2 planned, with no v0.2 production code merged yet; publication remains separate work.** +Status: **v0.1 foundation complete; v0.2 implementation complete; the 0.2.0 publication process is tracked separately from source delivery.** -This roadmap records the delivered v0.1 vertical slices, the active v0.2 implementation plan, and possible later directions. The normative v0.1 contract remains in `docs/REQUIREMENTS.md`, final v0.1 evidence is mapped in `docs/V0.1-RELEASE-AUDIT.md`, GitHub Tracking Issue #30 records the completed v0.1 implementation sequence, and [Tracking Issue #56](https://github.com/tappe9/pgdumpx/issues/56) owns the v0.2 delivery sequence. +This roadmap records completed v0.1/v0.2 work and explicitly deferred candidates. The normative v0.1 contract remains in `docs/REQUIREMENTS.md`, final v0.1 evidence is mapped in `docs/V0.1-RELEASE-AUDIT.md`, GitHub Tracking Issue #30 records the v0.1 sequence, and [Tracking Issue #56](https://github.com/tappe9/pgdumpx/issues/56) records the completed v0.2 sequence. ## v0.1 — Bounded row scanning for Custom Format archives @@ -179,83 +179,33 @@ Stable exit behavior: No-match remains distinct from failure. -## v0.2 — Extraction ergonomics and multi-table workflows — planned +## v0.2 — Completed -No v0.2 production behavior has merged into `main` yet. The implementation plan is tracked in [Issue #56](https://github.com/tappe9/pgdumpx/issues/56) and is deliberately built on the existing v0.1 `Read + Seek`, validated-entry, decompression, row-parser, and raw-output paths rather than introducing parallel or indexed alternatives prematurely. +The v0.2 implementation builds on the v0.1 `Read + Seek`, validated-entry, decompression, row-parser, and raw-output paths. It preserves the single mutable seekable-source invariant and does not add parallel or indexed alternatives. -### Sprint 1 — File and reusable selection foundations +### Delivered -- [#57 — file-oriented archive convenience APIs](https://github.com/tappe9/pgdumpx/issues/57): add path/file ergonomics while delegating parsing to the existing generic archive-open path. -- [#58 — owned byte-oriented table selectors](https://github.com/tappe9/pgdumpx/issues/58): introduce archive-independent exact `(schema, table)` selection values without assuming UTF-8. -- [#59 — reusable extraction plans](https://github.com/tappe9/pgdumpx/issues/59): compose owned selectors with extraction policy and metadata-only preflight. **Depends on #58.** +- #57–#63 ([tracking issue #56](https://github.com/tappe9/pgdumpx/issues/56)) delivered file-oriented archive opening, owned exact-byte `TableSelector` values, reusable `ExtractionPlan` preflight, bounded deterministic sequential multi-table execution, metadata-only `MetadataFilter`, exact named-column equality helpers, and benchmark-driven evaluation through production paths. +- #70–#78 ([first follow-up](https://github.com/tappe9/pgdumpx/issues/70)) delivered correctness, security, and maintenance follow-ups: destination flush-error propagation, aggregate metadata budgets, terminal COPY reader failures, row field-count validation, linear plan duplicate detection, finite CLI scan defaults, warning-clean feature coverage, scheduled fuzzing, and dependency advisory policy. +- [#89](https://github.com/tappe9/pgdumpx/issues/89) hardened GitHub Actions with pinned dependencies, bounded execution, least-privilege permissions, and cancellation policy. +- [#92](https://github.com/tappe9/pgdumpx/issues/92) prepared reproducible `pgdumpx 0.2.0` and `pgdumpx-cli 0.2.0` package metadata, changelog, release notes, packaging verification, and staged release instructions. -Preferred implementation order for the first sprint is **#57 → #58 → #59**. Only the selector-to-plan edge is a hard dependency; #57 remains an independent convenience foundation. +The `0.2.0` source and package contract is complete. Registry publication, the annotated tag, and the GitHub Release follow the independently verified process in `docs/RELEASING.md`; this roadmap does not infer their live state. -### Sprint 2 — Sequential multi-table extraction +## v0.3+ — Deferred candidates -- [#60 — bounded sequential multi-table plan execution](https://github.com/tappe9/pgdumpx/issues/60): execute preflighted plans deterministically on one mutable seekable source, reusing v0.1 validation, decompression, and `EntryReadLimits`. **Depends on #59.** - -This is the first multi-table production slice. It remains sequential and does not introduce shared-source concurrent seeking, source cloning, or independently reopened handles. - -### Sprint 3 — Selection and query ergonomics - -- [#61 — reusable metadata filtering](https://github.com/tappe9/pgdumpx/issues/61): filter TOC metadata by exact byte-oriented schema/name plus object type, and feed matched normal tables into the same selector/plan model. **Depends on #58.** -- [#62 — reusable byte-equality row matcher helpers](https://github.com/tappe9/pgdumpx/issues/62): factor the common exact-column-equality search pattern over existing `find_first` semantics without creating a SQL/value system. - -The generic predicate API remains the row-query foundation; the equality helper is narrowly justified by existing `pgdumpx find` usage. - -### Sprint 4 — Benchmark-driven tuning - -After the multi-table production path exists, use the reproducible benchmark/peak-RSS harness to evaluate buffer-size or orchestration tuning. Production defaults change only when recorded evidence justifies the tradeoff; ordinary CI continues to compile/smoke benchmark targets rather than run full performance campaigns. - -### Explicitly deferred from active v0.2 scope - -The current v0.2 issues intentionally do **not** include: +The following are candidates, not active commitments: - parallel extraction, concurrent seeking, or source-factory/reopen designs; - persistent/sidecar row indexes or decompression restart-point schemes; -- data-only archive identity synthesis or support changes for standalone `TABLE DATA` without a normal `TABLE` identity; -- SQL `WHERE`, a general predicate/query DSL, SQL coercion/collation, or a broad typed-value system; -- CSV/JSONL/Arrow/Polars/Parquet integrations; -- Directory/Tar format expansion; -- publish/tag/release work unless requested separately. - -These can be reconsidered only as later roadmap work with independent requirements and evidence. - -## v0.3 — Data ecosystem integrations - -Candidate companion crates or optional features: - -- CSV output; -- JSON Lines output; -- Apache Arrow integration; -- Polars integration; -- Parquet export. - -These integrations consume the core row stream and must not move DataFrame dependencies into the mandatory parser core. - -## v0.4 — Optional format expansion - -Only if demonstrated demand exists, evaluate PostgreSQL Directory Format (`pg_dump -Fd`) or other archive formats behind the same conceptual archive/entry API where semantics genuinely align. - -Custom Format remains the primary specialization; broad format coverage is not a success criterion by itself. - -## v0.5 — Language bindings - -Candidate scope: - -- PyO3-based Python package; -- Python iteration over archive metadata and table rows; -- first-match queries using Python callables or narrowly scoped filters; -- wheels for common platforms; -- optional Arrow handoff for analytical workloads. - -## v0.6 — Broader archive compatibility - -Candidate scope only if real-world demand exists: +- data-only archive identity synthesis for standalone `TABLE DATA`; +- SQL `WHERE`, a predicate DSL, SQL coercion/collation, or a broad typed-value system; +- CSV, JSON Lines, Arrow, Polars, or Parquet integrations; +- Directory/Tar archive formats; +- Python or other language bindings; +- archive versions older than 1.14 or newly discovered row representations. -- archive versions older than 1.14; -- additional COPY/data representations discovered in real archives. +Any candidate needs independent requirements, compatibility/resource analysis, and evidence before it becomes scheduled work. ## v1.0 — Stable read API @@ -274,4 +224,4 @@ Potential criteria: ## Guiding rule -Prefer a small, excellent read/extract/query engine for PostgreSQL Custom Format over becoming a second implementation of all `pg_dump`/`pg_restore` behavior. \ No newline at end of file +Prefer a small, excellent read/extract/query engine for PostgreSQL Custom Format over becoming a second implementation of all `pg_dump`/`pg_restore` behavior. diff --git a/SECURITY.md b/SECURITY.md index 5bb437b..a759e44 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,7 +4,9 @@ pgdumpx parses archive files that may be attacker controlled. Parser safety and ## Supported versions -The v0.1 implementation and release-readiness audit are complete, but pgdumpx has no supported published release yet. This section will be updated when releases begin. +Security fixes target the latest published 0.2.x release, when one exists, and the current `main` branch. A published release is the stable package source for users; `main` may contain unreleased changes and should be identified by exact commit when reporting or testing an issue. + +If no package has been published yet, use the current source tree and report the exact commit. Source preparation, a Cargo version, or a changelog entry does not by itself prove that a version is available from crates.io. Check the registry and repository release state independently. ## Reporting a vulnerability diff --git a/docs/API-DESIGN.md b/docs/API-DESIGN.md index 54aca25..b2eeecc 100644 --- a/docs/API-DESIGN.md +++ b/docs/API-DESIGN.md @@ -1,6 +1,6 @@ # pgdumpx Public API Design -Status: **Implemented v0.1 public API contract** +Status: **Implemented `0.2.0` public API contract** This document describes the implemented public Rust API shape and the ownership, safety, streaming, row-search, resource-budget, and compatibility contracts that must remain deliberate as the pre-1.0 API evolves. The crate rustdoc is authoritative for exact current signatures and error variants. @@ -43,7 +43,24 @@ impl Archive { } ``` -Opening parses supported archive metadata, the TOC, relationships, and lookup indexes under finite structural limits. It does not decompress selected entry bodies. A path convenience constructor is not required by the v0.1 public contract; callers can open a `File` and pass it to `Archive::open`. +Opening parses supported archive metadata, the TOC, relationships, and lookup indexes under finite structural limits. It does not decompress selected entry bodies. Callers can provide any `Read + Seek` source or use the file-oriented convenience API. + +## 2.1 File-oriented convenience + +The implemented filesystem constructors are specialized on `Archive>`: + +```rust +impl Archive> { + pub fn open_path(path: impl AsRef) -> Result; + + pub fn open_path_with_limits( + path: impl AsRef, + limits: Limits, + ) -> Result; +} +``` + +`Archive::open_path` opens the file, wraps it in `BufReader`, and delegates to the same generic parser. `Archive::open_path_with_limits` does the same with caller-supplied structural limits. Neither constructor introduces a separate parser, eager payload read, row-scan budget, or raw-output policy. ## 3. Structural limits @@ -307,6 +324,28 @@ impl TableRef<'_> { The type is a metadata handle only. It does not borrow entry payload bytes. Representation and column access are derived from stored TOC metadata and therefore do not decompress the table-data entry. +## 10.1 Owned table selectors + +`TableSelector` stores an archive-independent table identity as owned exact bytes: + +```rust +pub struct TableSelector { + // private owned schema and table-name bytes +} + +impl TableSelector { + pub fn new(schema: impl AsRef<[u8]>, name: impl AsRef<[u8]>) -> Self; + pub fn schema(&self) -> &[u8]; + pub fn name(&self) -> &[u8]; +} + +impl Archive { + pub fn resolve_table(&self, selector: &TableSelector) -> Option>; +} +``` + +Construction performs no UTF-8 conversion, case folding, SQL identifier parsing, or archive lookup. Resolution uses the same metadata-only exact-byte index as `Archive::table`, so selectors can be cloned, stored, and reused across archives without retaining archive-specific dump IDs or offsets. + ## 11. Raw entry data Entry reads are lazy and coordinated through a mutable archive borrow. @@ -317,6 +356,55 @@ Raw entry access is lower-level than row-aware access. A readable table-data ent Large-object entries with internal OID framing may require a different future API and are not forced into a flat `Read` abstraction. +## 11.1 Reusable extraction plans + +`ExtractionPlan` owns logical selectors and one raw-output policy, while archive-specific preflight results borrow the target archive: + +```rust +impl ExtractionPlan { + pub fn new( + selectors: Vec, + ) -> Result; + + pub fn with_entry_read_limits( + selectors: Vec, + entry_read_limits: EntryReadLimits, + ) -> Result; + + pub fn selectors(&self) -> &[TableSelector]; + pub const fn entry_read_limits(&self) -> EntryReadLimits; + + pub fn preflight<'a, R>( + &self, + archive: &'a Archive, + ) -> Result, PgDumpError>; +} +``` + +Plan construction preserves caller order and rejects duplicate exact `(schema, table)` selectors. Preflight resolves every selector and related `TABLE DATA` entry from already-parsed metadata before any payload seek, decompression, output creation, or write. The owned plan never stores dump IDs, offsets, sources, or destinations and can therefore be reused against another archive. + +## 11.2 Sequential multi-table execution + +The execution signature is: + +```rust +impl ExtractionPlan { + pub fn execute( + &self, + archive: &mut Archive, + destination_for: F, + ) -> Result, ExtractionExecutionError> + where + R: Read + Seek, + W: Write, + F: FnMut(&ExtractionTarget) -> io::Result; +} +``` + +Execution completes the full preflight before requesting the first destination. It then copies targets sequentially in deterministic selector order through `Archive::copy_entry_to` and the plan's `EntryReadLimits`. This preserves the single mutable seekable-source invariant: there is no concurrent seek, cloned source, or independently reopened handle. + +Each target is complete only after bounded copying and destination `flush` both succeed. If a destination cannot be created, copying fails, or flushing fails, later targets are not started. `ExtractionExecutionError` retains earlier completed outcomes and identifies the failed target. Bytes already accepted by the current destination cannot be rolled back, so partial output may coexist with failure; the API never reports that target as successfully extracted. + ## 12. COPY rows and lending semantics The high-performance streaming row model is borrowed: @@ -475,6 +563,30 @@ Binary COPY decoding is also deferred. This distinction keeps low-level archive readability separate from logical row-parser support. The official PostgreSQL 18.4 `--inserts` fixture verifies that raw selected-entry extraction remains available while `table_rows` rejects the representation before COPY parsing. +## 15.1 Metadata filtering + +`MetadataFilter` filters already-parsed TOC entries without seeking or decompressing payloads: + +```rust +impl MetadataFilter { + pub fn new() -> Self; + pub fn with_schema(self, schema: impl AsRef<[u8]>) -> Self; + pub fn with_absent_schema(self) -> Self; + pub fn with_object_type(self, object_type: impl AsRef<[u8]>) -> Self; + pub fn with_name(self, name: impl AsRef<[u8]>) -> Self; + pub fn matches(&self, entry: &TocEntry) -> bool; +} + +impl Archive { + pub fn filter_metadata<'a>( + &'a self, + filter: &MetadataFilter, + ) -> Vec>; +} +``` + +All configured values use exact byte equality, and matches retain archive order. `with_absent_schema()` matches encoded namespace absence only. `with_schema(b"")` instead requires a present empty namespace; the two states are never conflated. `MetadataMatch::table_selector()` converts only a normal `TABLE` with a present namespace into `TableSelector`; it does not synthesize identity from `TABLE DATA`. + ## 16. First-match filtering A primary v0.1 API is first-match filtering over the selected table stream: @@ -527,6 +639,37 @@ Semantics: The closure API deliberately avoids a SQL parser. Callers implement equality, prefix, numeric parsing, or compound application-specific conditions themselves. +## 16.1 Exact named-column equality + +The maintained equality convenience API is: + +```rust +pub enum ColumnEqualityResult { + Match(OwnedRow), + NoMatch, + ColumnNotFound, +} + +impl TableRowReader<'_, R> { + pub fn find_first_equal( + &mut self, + column: &[u8], + expected: FieldRef<'_>, + ) -> Result; + + pub fn find_first_equal_with_limits( + &mut self, + scan_limits: ScanLimits, + column: &[u8], + expected: FieldRef<'_>, + ) -> Result; +} +``` + +The column is resolved exactly once from parsed metadata before scanning. An absent valid name returns `ColumnNotFound` without consuming a row; metadata failures remain typed errors. For a valid column, both methods delegate to the existing sequential `find_first` path and preserve early termination, limits, field-count checks, reader failure semantics, and owned matches. + +Equality is byte-exact after COPY-text unescaping. `FieldRef::Null` matches only SQL NULL. It is distinct from `FieldRef::Bytes(b"")` and from literal logical bytes `FieldRef::Bytes(b"\\N")`. There is no UTF-8 conversion, collation, SQL coercion, or typed-value comparison. + ## 17. Row-search performance contract `find_first` is **not** an indexed database lookup. @@ -586,13 +729,13 @@ Limit exhaustion identifies the resource and, where practical, consumed work. Lo The CLI is not part of the core Rust type system, but its encoding, limit, output, and exit choices align with the byte-oriented API. -Implemented v0.1 commands: +Implemented commands: ```text pgdumpx inspect pgdumpx list pgdumpx extract [--max-decompressed-bytes ] -pgdumpx find [--max-rows ] [--max-decompressed-bytes ] +pgdumpx find [--unlimited | [--max-rows ] [--max-decompressed-bytes ]] ``` Rules: @@ -606,7 +749,7 @@ Rules: - `find` compares its UTF-8 value bytes with logical post-unescape field bytes; - `find` exposes optional positive-`u64` row and parser-consumed decompressed-byte scan budgets; - matched `find` output is one normalized ASCII-safe COPY text record; -- exit `0` means match/success, `find` exit `1` means completed no-match, and exit `2+` means usage/runtime/resource failure. +- exit `0` means match/success, `find` exit `1` means completed no-match, and exit `2` means usage, input, runtime, or resource failure. A byte-literal CLI syntax, JSON representation, or full restorable SQL output requires a separate design. @@ -616,7 +759,7 @@ Serde support is not required and is not part of the v0.1 public contract. Prese ## 22. Threading and parallel access -`Archive` does not promise concurrent reads from one seekable source. +`Archive` does not promise concurrent reads from one seekable source. Its mutable entry/row access enforces one coordinated seek position, and `ExtractionPlan::execute` deliberately preserves that invariant while processing multiple targets sequentially. Future parallel extraction should use APIs that can produce independent sources, such as reopening a path or accepting a source factory. This avoids mutex-protected seek thrashing and preserves simple borrowing semantics. @@ -635,7 +778,7 @@ Before v1.0: ## 24. Deferred APIs -Intentionally deferred beyond v0.1: +Intentionally deferred beyond the current public API: ```text archive writing diff --git a/scripts/test-public-documentation.py b/scripts/test-public-documentation.py index 327178e..be0a365 100644 --- a/scripts/test-public-documentation.py +++ b/scripts/test-public-documentation.py @@ -19,7 +19,7 @@ "`TableSelector`", "`ExtractionPlan`", "`MetadataFilter`", - "`find_first_equal_by_name_with_limits`", + "`find_first_equal_with_limits`", ], "README.ja.md": [ "現在のソースバージョン: `0.2.0`", @@ -31,7 +31,7 @@ "`TableSelector`", "`ExtractionPlan`", "`MetadataFilter`", - "`find_first_equal_by_name_with_limits`", + "`find_first_equal_with_limits`", ], "ROADMAP.md": [ "## v0.2 — Completed", @@ -93,7 +93,7 @@ "TableSelector", "ExtractionPlan", "MetadataFilter", - "find_first_equal_by_name_with_limits", + "find_first_equal_with_limits", "100,000", "64 MiB", "Binary COPY",