diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5c164a4..ca648e6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,11 +7,22 @@ on: jobs: build: runs-on: ubuntu-latest + strategy: + matrix: + pg: [pg15, pg17] steps: - uses: actions/checkout@v4 + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Build docker image + env: + BASE_IMAGE: ghcr.io/${{ github.repository_owner }}/pg-debyte-ci-base:latest run: ./scripts/docker-ci-build-image.sh - name: Build workspace packages - run: ./scripts/docker-ci-build-workspace.sh + run: bash -lc "source scripts/dev.sh; docker_build_workspace ${{ matrix.pg }}" - name: Build README examples - run: ./scripts/docker-ci-build-examples.sh + run: bash -lc "source scripts/dev.sh; docker_build_examples ${{ matrix.pg }}" diff --git a/.github/workflows/docker-base-image.yml b/.github/workflows/docker-base-image.yml new file mode 100644 index 0000000..f2088d5 --- /dev/null +++ b/.github/workflows/docker-base-image.yml @@ -0,0 +1,35 @@ +name: docker-base-image + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + packages: write + +jobs: + build-base: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push base image + uses: docker/build-push-action@v6 + with: + context: . + file: docker/ci-base.Dockerfile + push: true + tags: | + ghcr.io/${{ github.repository_owner }}/pg-debyte-ci-base:latest + ghcr.io/${{ github.repository_owner }}/pg-debyte-ci-base:${{ github.sha }} diff --git a/.github/workflows/pg-extension-tests.yml b/.github/workflows/pg-extension-tests.yml index ca4a83a..4f19734 100644 --- a/.github/workflows/pg-extension-tests.yml +++ b/.github/workflows/pg-extension-tests.yml @@ -8,12 +8,43 @@ jobs: pg-extension-tests: runs-on: ubuntu-latest timeout-minutes: 20 + strategy: + matrix: + pg: [pg15, pg17] steps: - name: Checkout uses: actions/checkout@v4 + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Build docker image + env: + BASE_IMAGE: ghcr.io/${{ github.repository_owner }}/pg-debyte-ci-base:latest run: ./scripts/docker-ci-build-image.sh - name: PGRX tests (pg_debyte_ext) - run: ./scripts/docker-ci-test-pg-extension.sh + run: bash -lc "source scripts/dev.sh; docker_tests_pg_extensions ${{ matrix.pg }}" + + pgrx-workspace-tests: + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + matrix: + pg: [pg15, pg17] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build docker image + env: + BASE_IMAGE: ghcr.io/${{ github.repository_owner }}/pg-debyte-ci-base:latest + run: ./scripts/docker-ci-build-image.sh - name: Workspace tests (include pg_debyte_pgrx) - run: docker run --rm -t pg-debyte-ci bash -lc "/usr/local/cargo/bin/cargo test --workspace --exclude pg_debyte_ext" + run: bash -lc "source scripts/dev.sh; docker_tests_workspace ${{ matrix.pg }}" diff --git a/README.md b/README.md index f56278a..55106a1 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # pg-debyte -[![Build](https://github.com/Xaneets/pg-debyte/actions/workflows/build.yml/badge.svg)](https://github.com/Xaneets/pg-debyte/actions/workflows/build.yml) -[![Workspace tests](https://github.com/Xaneets/pg-debyte/actions/workflows/core-tests.yml/badge.svg)](https://github.com/Xaneets/pg-debyte/actions/workflows/core-tests.yml) -[![PG extension tests](https://github.com/Xaneets/pg-debyte/actions/workflows/pg-extension-tests.yml/badge.svg)](https://github.com/Xaneets/pg-debyte/actions/workflows/pg-extension-tests.yml) +[![Build pg15/pg17](https://github.com/xaneets/pg-debyte/actions/workflows/build.yml/badge.svg?label=build%20pg15%2Fpg17)](https://github.com/xaneets/pg-debyte/actions/workflows/build.yml) +[![Core tests](https://github.com/xaneets/pg-debyte/actions/workflows/core-tests.yml/badge.svg?label=core%20tests)](https://github.com/xaneets/pg-debyte/actions/workflows/core-tests.yml) +[![PG extension tests pg15/pg17](https://github.com/xaneets/pg-debyte/actions/workflows/pg-extension-tests.yml/badge.svg?label=pg%20ext%20tests%20pg15%2Fpg17)](https://github.com/xaneets/pg-debyte/actions/workflows/pg-extension-tests.yml) [![Crates.io](https://img.shields.io/crates/v/pg_debyte_core.svg)](https://crates.io/crates/pg_debyte_core) Core building blocks for PostgreSQL extensions that decode `bytea` into JSON. @@ -12,7 +12,7 @@ This repository provides reusable Rust crates plus a small example extension. - `pg_debyte_core`: envelope parser, registry, codecs/actions, limits, errors. - `pg_debyte_macros`: helper macros for registering typed decoders. -- `pg_debyte_pgrx`: PG17-only helper glue (GUC limits and decoding helpers). +- `pg_debyte_pgrx`: PG15/PG17 helper glue (GUC limits and decoding helpers). - `pg_debyte_ext`: example PG17 extension crate with a demo registry and decoder. - `pg_debyte_tools`: helper binaries (demo payload generator). @@ -23,12 +23,13 @@ This repository provides reusable Rust crates plus a small example extension. - Bincode codec with size limits. - Static registry for decoders/codecs/actions. - Known schema SQL functions (per-type decoding without envelope). -- PG17-only helper for GUC limits and decoding (to be called from extension). +- PG15/PG17 helper for GUC limits and decoding (to be called from extension). +- Panic protection around decoding (catch_unwind in pgrx). ## Notes - `pg_debyte_ext` is only an example implementation; you will create your own extension crate. -- PG15 support will be added later as a separate focus. +- PG15/PG17 are supported via pgrx feature flags. ## Examples @@ -49,14 +50,28 @@ cd my_pg_debyte_ext ```toml [dependencies] pgrx = { version = "0.16.1", default-features = false, features = ["pg17"] } +# or: features = ["pg15"] pg_debyte_core = "0.2.0" pg_debyte_macros = "0.2.0" -pg_debyte_pgrx = "0.2.0" +pg_debyte_pgrx = { version = "0.2.0", default-features = false } serde = { version = "1.0", features = ["derive"] } uuid = "1.8" ``` -Build and install once: +Enable the matching pg_debyte_pgrx feature: + +```toml +[features] +pg15 = ["pg_debyte_pgrx/pg15"] +pg17 = ["pg_debyte_pgrx/pg17"] +``` + +Build and install once (choose your PG major, matching features): + +```bash +cargo pgrx init --pg15 /path/to/pg15 +cargo pgrx install --features pg15 --sudo +``` ```bash cargo pgrx init --pg17 /path/to/pg17 @@ -267,9 +282,14 @@ cargo run -p pg_debyte_tools --bin demo_envelope SELECT bytea_to_json_auto(decode('', 'hex')); ``` -## Example usage (PG17) +## Example usage (PG15/PG17) + +Build and install the example extension (PG15/PG17): -Build and install the example extension: +```bash +cargo pgrx init --pg15 /path/to/pg15 +cargo pgrx install -p pg_debyte_ext --features pg15 --sudo +``` ```bash cargo pgrx init --pg17 /path/to/pg17 @@ -335,5 +355,6 @@ cargo run -p pg_debyte_tools --bin demo_auto_sql If host permissions make `cargo pgrx test` difficult, use the Docker runner: ```bash -./scripts/docker-ci-test-pg-extension.sh +source scripts/dev.sh +docker_tests_pg_extensions pg17 ``` diff --git a/docker/ci-base.Dockerfile b/docker/ci-base.Dockerfile new file mode 100644 index 0000000..d76c0d7 --- /dev/null +++ b/docker/ci-base.Dockerfile @@ -0,0 +1,39 @@ +FROM postgres:17 + +USER root + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + clang \ + curl \ + git \ + llvm-dev \ + libclang-dev \ + build-essential \ + pkg-config \ + bison \ + flex \ + libicu-dev \ + libreadline-dev \ + libssl-dev \ + libxml2-dev \ + libxslt1-dev \ + liblz4-dev \ + libzstd-dev \ + zlib1g-dev \ + && rm -rf /var/lib/apt/lists/* + +ENV RUSTUP_HOME=/usr/local/rustup +ENV CARGO_HOME=/usr/local/cargo +ENV PATH=/usr/local/cargo/bin:$PATH +ENV USER=postgres +ENV LOGNAME=postgres +ENV HOME=/var/lib/postgresql + +RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable +RUN cargo install cargo-pgrx --version 0.16.1 --locked + +USER postgres +RUN cargo pgrx init --pg17 download +RUN cargo pgrx init --pg15 download diff --git a/docker/ci.Dockerfile b/docker/ci.Dockerfile index 3656170..f86f822 100644 --- a/docker/ci.Dockerfile +++ b/docker/ci.Dockerfile @@ -1,44 +1,10 @@ -FROM postgres:17 - -USER root - -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - ca-certificates \ - clang \ - curl \ - git \ - llvm-dev \ - libclang-dev \ - build-essential \ - pkg-config \ - bison \ - flex \ - libicu-dev \ - libreadline-dev \ - libssl-dev \ - libxml2-dev \ - libxslt1-dev \ - liblz4-dev \ - libzstd-dev \ - zlib1g-dev \ - && rm -rf /var/lib/apt/lists/* - -ENV RUSTUP_HOME=/usr/local/rustup -ENV CARGO_HOME=/usr/local/cargo -ENV PATH=/usr/local/cargo/bin:$PATH -ENV USER=postgres -ENV LOGNAME=postgres -ENV HOME=/var/lib/postgresql - -RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable -RUN cargo install cargo-pgrx --version 0.16.1 --locked +ARG BASE_IMAGE=pg-debyte-ci-base +FROM ${BASE_IMAGE} WORKDIR /workspace COPY . /workspace +USER root RUN chown -R postgres:postgres /workspace /usr/local/rustup /usr/local/cargo - USER postgres -RUN cargo pgrx init --pg17 download CMD ["bash", "-lc", "env -u PG_VERSION -u PG_MAJOR /usr/local/cargo/bin/cargo pgrx test -p pg_debyte_ext --features pg17"] diff --git a/examples/readme_by_id/Cargo.toml b/examples/readme_by_id/Cargo.toml index b99cc22..1bacc11 100644 --- a/examples/readme_by_id/Cargo.toml +++ b/examples/readme_by_id/Cargo.toml @@ -8,13 +8,14 @@ publish = false crate-type = ["cdylib", "lib"] [features] -default = ["pg17"] -pg17 = ["pgrx/pg17"] +default = [] +pg15 = ["pgrx/pg15", "pg_debyte_pgrx/pg15"] +pg17 = ["pgrx/pg17", "pg_debyte_pgrx/pg17"] [dependencies] pgrx = { version = "0.16.1", default-features = false } pg_debyte_core = { version = "0.2.0", path = "../../pg_debyte_core" } pg_debyte_macros = { version = "0.2.0", path = "../../pg_debyte_macros" } -pg_debyte_pgrx = { version = "0.2.0", path = "../../pg_debyte_pgrx" } +pg_debyte_pgrx = { version = "0.2.0", path = "../../pg_debyte_pgrx", default-features = false } serde = { version = "1.0", features = ["derive"] } uuid = "1.8" diff --git a/examples/readme_envelope/Cargo.toml b/examples/readme_envelope/Cargo.toml index e2f4453..6d6ea6e 100644 --- a/examples/readme_envelope/Cargo.toml +++ b/examples/readme_envelope/Cargo.toml @@ -8,13 +8,14 @@ publish = false crate-type = ["cdylib", "lib"] [features] -default = ["pg17"] -pg17 = ["pgrx/pg17"] +default = [] +pg15 = ["pgrx/pg15", "pg_debyte_pgrx/pg15"] +pg17 = ["pgrx/pg17", "pg_debyte_pgrx/pg17"] [dependencies] pgrx = { version = "0.16.1", default-features = false } pg_debyte_core = { version = "0.2.0", path = "../../pg_debyte_core" } pg_debyte_macros = { version = "0.2.0", path = "../../pg_debyte_macros" } -pg_debyte_pgrx = { version = "0.2.0", path = "../../pg_debyte_pgrx" } +pg_debyte_pgrx = { version = "0.2.0", path = "../../pg_debyte_pgrx", default-features = false } serde = { version = "1.0", features = ["derive"] } uuid = "1.8" diff --git a/examples/readme_known_schema/Cargo.toml b/examples/readme_known_schema/Cargo.toml index 72894ac..4992689 100644 --- a/examples/readme_known_schema/Cargo.toml +++ b/examples/readme_known_schema/Cargo.toml @@ -8,13 +8,14 @@ publish = false crate-type = ["cdylib", "lib"] [features] -default = ["pg17"] -pg17 = ["pgrx/pg17"] +default = [] +pg15 = ["pgrx/pg15", "pg_debyte_pgrx/pg15"] +pg17 = ["pgrx/pg17", "pg_debyte_pgrx/pg17"] [dependencies] pgrx = { version = "0.16.1", default-features = false } pg_debyte_core = { version = "0.2.0", path = "../../pg_debyte_core" } pg_debyte_macros = { version = "0.2.0", path = "../../pg_debyte_macros" } -pg_debyte_pgrx = { version = "0.2.0", path = "../../pg_debyte_pgrx" } +pg_debyte_pgrx = { version = "0.2.0", path = "../../pg_debyte_pgrx", default-features = false } serde = { version = "1.0", features = ["derive"] } uuid = "1.8" diff --git a/pg_debyte_core/Cargo.toml b/pg_debyte_core/Cargo.toml index 003c6da..a76e0e6 100644 --- a/pg_debyte_core/Cargo.toml +++ b/pg_debyte_core/Cargo.toml @@ -6,8 +6,8 @@ authors = ["Dmitriy Sergeev "] description = "Core building blocks for PostgreSQL extensions that decode bytea into JSON" license = "Apache-2.0" readme = "../README.md" -repository = "https://github.com/Xaneets/pg-debyte" -homepage = "https://github.com/Xaneets/pg-debyte" +repository = "https://github.com/xaneets/pg-debyte" +homepage = "https://github.com/xaneets/pg-debyte" keywords = ["postgres", "postgresql", "bytea", "json", "pgrx"] categories = ["database", "encoding"] diff --git a/pg_debyte_core/src/error.rs b/pg_debyte_core/src/error.rs index 4ec3aec..0d07d12 100644 --- a/pg_debyte_core/src/error.rs +++ b/pg_debyte_core/src/error.rs @@ -27,6 +27,8 @@ pub enum DecodeError { Json(String), #[error("io error: {0}")] Io(String), + #[error("panic during decode: {0}")] + Panic(String), } impl From for DecodeError { diff --git a/pg_debyte_ext/Cargo.toml b/pg_debyte_ext/Cargo.toml index b80e3cb..3574abf 100644 --- a/pg_debyte_ext/Cargo.toml +++ b/pg_debyte_ext/Cargo.toml @@ -4,11 +4,11 @@ version = "0.1.0" edition = "2021" publish = false authors = ["Dmitriy Sergeev "] -description = "Example pg_debyte PostgreSQL extension for PG17" +description = "Example pg_debyte PostgreSQL extension for PG15/PG17" license = "Apache-2.0" readme = "../README.md" -repository = "https://github.com/Xaneets/pg-debyte" -homepage = "https://github.com/Xaneets/pg-debyte" +repository = "https://github.com/xaneets/pg-debyte" +homepage = "https://github.com/xaneets/pg-debyte" [lib] crate-type = ["cdylib", "lib"] @@ -18,12 +18,13 @@ name = "pgrx_embed_pg_debyte_ext" path = "./src/bin/pgrx_embed.rs" [features] -pg17 = ["pgrx/pg17", "pgrx-tests/pg17"] +pg15 = ["pgrx/pg15", "pgrx-tests/pg15", "pg_debyte_pgrx/pg15"] +pg17 = ["pgrx/pg17", "pgrx-tests/pg17", "pg_debyte_pgrx/pg17"] pg_test = [] [dependencies] pg_debyte_core = { version = "0.2.0", path = "../pg_debyte_core" } -pg_debyte_pgrx = { version = "0.2.0", path = "../pg_debyte_pgrx" } +pg_debyte_pgrx = { version = "0.2.0", path = "../pg_debyte_pgrx", default-features = false } pg_debyte_macros = { version = "0.2.0", path = "../pg_debyte_macros" } serde = { version = "1.0", features = ["derive"] } uuid = "1.8" diff --git a/pg_debyte_macros/Cargo.toml b/pg_debyte_macros/Cargo.toml index 4a663d6..568f7ef 100644 --- a/pg_debyte_macros/Cargo.toml +++ b/pg_debyte_macros/Cargo.toml @@ -6,8 +6,8 @@ authors = ["Dmitriy Sergeev "] description = "Helper macros for registering typed decoders in pg_debyte" license = "Apache-2.0" readme = "../README.md" -repository = "https://github.com/Xaneets/pg-debyte" -homepage = "https://github.com/Xaneets/pg-debyte" +repository = "https://github.com/xaneets/pg-debyte" +homepage = "https://github.com/xaneets/pg-debyte" keywords = ["postgres", "postgresql", "bytea", "json", "pgrx"] categories = ["database", "development-tools"] diff --git a/pg_debyte_pgrx/Cargo.toml b/pg_debyte_pgrx/Cargo.toml index 200091a..6eb6c88 100644 --- a/pg_debyte_pgrx/Cargo.toml +++ b/pg_debyte_pgrx/Cargo.toml @@ -3,11 +3,11 @@ name = "pg_debyte_pgrx" version = "0.2.0" edition = "2021" authors = ["Dmitriy Sergeev "] -description = "PG17-only pgrx glue for pg_debyte (GUCs and decode helpers)" +description = "pgrx glue for pg_debyte (GUCs and decode helpers)" license = "Apache-2.0" readme = "../README.md" -repository = "https://github.com/Xaneets/pg-debyte" -homepage = "https://github.com/Xaneets/pg-debyte" +repository = "https://github.com/xaneets/pg-debyte" +homepage = "https://github.com/xaneets/pg-debyte" keywords = ["postgres", "postgresql", "bytea", "json", "pgrx"] categories = ["database"] @@ -16,4 +16,9 @@ pg_debyte_core = { version = "0.2.0", path = "../pg_debyte_core" } serde_json = "1.0" uuid = "1.8" -pgrx = { version = "0.16.1", default-features = false, features = ["pg17"] } +pgrx = { version = "0.16.1", default-features = false } + +[features] +default = [] +pg15 = ["pgrx/pg15"] +pg17 = ["pgrx/pg17"] diff --git a/pg_debyte_pgrx/src/lib.rs b/pg_debyte_pgrx/src/lib.rs index 2a6ab1f..dd78ae5 100644 --- a/pg_debyte_pgrx/src/lib.rs +++ b/pg_debyte_pgrx/src/lib.rs @@ -5,6 +5,8 @@ use pg_debyte_core::registry::Registry; use pg_debyte_core::types::{DecodeLimits, TypeKey}; use pg_debyte_core::DecoderEntry; use pgrx::guc::{GucContext, GucFlags, GucRegistry, GucSetting}; +use std::any::Any; +use std::panic::{catch_unwind, AssertUnwindSafe}; use std::sync::OnceLock; use uuid::Uuid; @@ -74,19 +76,21 @@ pub fn decode_by_id( schema_version: i16, limits: &DecodeLimits, ) -> Result { - ensure_limit("input_bytes", data.len(), limits.max_input_bytes)?; - let reg = registry()?; - let key = TypeKey { - type_id, - schema_version: schema_version as u16, - }; - let entry = reg - .lookup_decoder(key) - .ok_or(DecodeError::UnknownType(key))?; - let payload = apply_actions_refs(reg, entry.default_actions(), data, limits)?; - let value = entry.decode_payload(&payload, limits)?; - ensure_json_limit(&value, limits)?; - Ok(value) + catch_unwind_decode(|| { + ensure_limit("input_bytes", data.len(), limits.max_input_bytes)?; + let reg = registry()?; + let key = TypeKey { + type_id, + schema_version: schema_version as u16, + }; + let entry = reg + .lookup_decoder(key) + .ok_or(DecodeError::UnknownType(key))?; + let payload = apply_actions_refs(reg, entry.default_actions(), data, limits)?; + let value = entry.decode_payload(&payload, limits)?; + ensure_json_limit(&value, limits)?; + Ok(value) + }) } pub fn decode_know_schema( @@ -94,38 +98,42 @@ pub fn decode_know_schema( decoder: &dyn DecoderEntry, limits: &DecodeLimits, ) -> Result { - ensure_limit("input_bytes", data.len(), limits.max_input_bytes)?; - let value = if decoder.default_actions().is_empty() { - decoder.decode_payload(data, limits)? - } else { - let reg = registry()?; - let payload = apply_actions_refs(reg, decoder.default_actions(), data, limits)?; - decoder.decode_payload(&payload, limits)? - }; - ensure_json_limit(&value, limits)?; - Ok(value) + catch_unwind_decode(|| { + ensure_limit("input_bytes", data.len(), limits.max_input_bytes)?; + let value = if decoder.default_actions().is_empty() { + decoder.decode_payload(data, limits)? + } else { + let reg = registry()?; + let payload = apply_actions_refs(reg, decoder.default_actions(), data, limits)?; + decoder.decode_payload(&payload, limits)? + }; + ensure_json_limit(&value, limits)?; + Ok(value) + }) } pub fn decode_auto(data: &[u8], limits: &DecodeLimits) -> Result { - ensure_limit("input_bytes", data.len(), limits.max_input_bytes)?; - let reg = registry()?; - let parsed = try_parse(data)?; - let envelope = match parsed { - ParsedEnvelope::None => return Err(DecodeError::BadEnvelope("no envelope")), - ParsedEnvelope::Envelope(view) => view, - }; - - let entry = reg - .lookup_decoder(envelope.key) - .ok_or(DecodeError::UnknownType(envelope.key))?; - if envelope.codec_id != entry.codec_id() { - return Err(DecodeError::UnknownCodec(envelope.codec_id)); - } - - let payload = apply_actions(reg, &envelope.actions, envelope.payload, limits)?; - let value = entry.decode_payload(&payload, limits)?; - ensure_json_limit(&value, limits)?; - Ok(value) + catch_unwind_decode(|| { + ensure_limit("input_bytes", data.len(), limits.max_input_bytes)?; + let reg = registry()?; + let parsed = try_parse(data)?; + let envelope = match parsed { + ParsedEnvelope::None => return Err(DecodeError::BadEnvelope("no envelope")), + ParsedEnvelope::Envelope(view) => view, + }; + + let entry = reg + .lookup_decoder(envelope.key) + .ok_or(DecodeError::UnknownType(envelope.key))?; + if envelope.codec_id != entry.codec_id() { + return Err(DecodeError::UnknownCodec(envelope.codec_id)); + } + + let payload = apply_actions(reg, &envelope.actions, envelope.payload, limits)?; + let value = entry.decode_payload(&payload, limits)?; + ensure_json_limit(&value, limits)?; + Ok(value) + }) } fn apply_actions( @@ -175,3 +183,37 @@ fn ensure_json_limit(value: &serde_json::Value, limits: &DecodeLimits) -> Result let json = serde_json::to_vec(value)?; ensure_limit("json_bytes", json.len(), limits.max_json_bytes) } + +fn catch_unwind_decode(func: F) -> Result +where + F: FnOnce() -> Result, +{ + match catch_unwind(AssertUnwindSafe(func)) { + Ok(result) => result, + Err(panic_err) => Err(DecodeError::Panic(panic_message(panic_err))), + } +} + +fn panic_message(panic_err: Box) -> String { + if let Some(message) = panic_err.downcast_ref::<&str>() { + (*message).to_string() + } else if let Some(message) = panic_err.downcast_ref::() { + message.clone() + } else { + "unknown panic error".to_string() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn catch_unwind_maps_panic() { + let err = catch_unwind_decode(|| panic!("boom")).unwrap_err(); + match err { + DecodeError::Panic(message) => assert!(message.contains("boom")), + other => panic!("unexpected error: {other:?}"), + } + } +} diff --git a/pg_debyte_tools/Cargo.toml b/pg_debyte_tools/Cargo.toml index d4ca12a..453163d 100644 --- a/pg_debyte_tools/Cargo.toml +++ b/pg_debyte_tools/Cargo.toml @@ -7,8 +7,8 @@ authors = ["Dmitriy Sergeev "] description = "Helper binaries for generating pg_debyte demo payloads and envelopes" license = "Apache-2.0" readme = "../README.md" -repository = "https://github.com/Xaneets/pg-debyte" -homepage = "https://github.com/Xaneets/pg-debyte" +repository = "https://github.com/xaneets/pg-debyte" +homepage = "https://github.com/xaneets/pg-debyte" [[bin]] name = "demo_payload" diff --git a/scripts/dev.sh b/scripts/dev.sh new file mode 100755 index 0000000..c9f9e19 --- /dev/null +++ b/scripts/dev.sh @@ -0,0 +1,145 @@ +#!/usr/bin/env bash + +_pg_debyte_versions() { + local arg="${1:-pg17}" + case "${arg}" in + pg15|pg17) + echo "${arg}" + ;; + all) + echo "pg15 pg17" + ;; + *) + echo "unknown pg version: ${arg}" >&2 + return 2 + ;; + esac +} + +_pg_debyte_run() { + local arg="${1:-pg17}" + shift + local ver + for ver in $(_pg_debyte_versions "${arg}"); do + "$@" "${ver}" + done +} + +_host_tests() { + local ver="$1" + cargo test --workspace --exclude pg_debyte_ext --exclude readme_known_schema --exclude readme_by_id --exclude readme_envelope --features "${ver}" + cargo pgrx test -p pg_debyte_ext --features "${ver}" +} + +_host_build() { + local ver="$1" + cargo build --workspace --all-targets --exclude pg_debyte_ext --exclude readme_known_schema --exclude readme_by_id --exclude readme_envelope --features "${ver}" + cargo build -p pg_debyte_ext --all-targets --features "${ver}" +} + +_host_examples() { + local ver="$1" + cargo build -p readme_known_schema --all-targets --features "${ver}" + cargo build -p readme_by_id --all-targets --features "${ver}" + cargo build -p readme_envelope --all-targets --features "${ver}" +} + +_docker_run() { + local cmd="$1" + echo "[pg-debyte] docker: ${cmd}" + docker run --rm -i --entrypoint bash pg-debyte-ci -lc "CARGO_TERM_PROGRESS_WHEN=auto PGRX_BUILD_VERBOSE=1 ${cmd}" +} + +_docker_tests_pg_extensions() { + local ver="$1" + _docker_run "env -u PG_VERSION -u PG_MAJOR /usr/local/cargo/bin/cargo pgrx test -p pg_debyte_ext --features ${ver}" +} + +_docker_tests_workspace() { + local ver="$1" + _docker_run "/usr/local/cargo/bin/cargo test --workspace --exclude pg_debyte_ext --exclude readme_known_schema --exclude readme_by_id --exclude readme_envelope --features ${ver}" +} + +_docker_build_workspace() { + local ver="$1" + _docker_run "cargo build --workspace --all-targets --exclude pg_debyte_ext --exclude readme_known_schema --exclude readme_by_id --exclude readme_envelope --features ${ver} && cargo build -p pg_debyte_ext --all-targets --features ${ver}" +} + +_docker_build_examples() { + local ver="$1" + _docker_run "cargo build -p readme_known_schema --all-targets --features ${ver} && cargo build -p readme_by_id --all-targets --features ${ver} && cargo build -p readme_envelope --all-targets --features ${ver}" +} + +_host_lints() { + local ver="$1" + cargo +nightly fmt -- --check + cargo clippy --workspace --exclude pg_debyte_ext --exclude readme_known_schema --exclude readme_by_id --exclude readme_envelope --features "${ver}" + cargo clippy -p pg_debyte_ext --features "${ver}" +} + +host_tests() { + _pg_debyte_run "${1:-pg17}" _host_tests +} + +host_build() { + _pg_debyte_run "${1:-pg17}" _host_build +} + +host_examples() { + _pg_debyte_run "${1:-pg17}" _host_examples +} + +host_lints() { + _pg_debyte_run "${1:-pg17}" _host_lints +} + +docker_tests_pg_extensions() { + _pg_debyte_run "${1:-pg17}" _docker_tests_pg_extensions +} + +docker_tests_workspace() { + _pg_debyte_run "${1:-pg17}" _docker_tests_workspace +} + +docker_build_workspace() { + _pg_debyte_run "${1:-pg17}" _docker_build_workspace +} + +docker_build_examples() { + _pg_debyte_run "${1:-pg17}" _docker_build_examples +} + +docker_build_image() { + ./scripts/docker-ci-build-image.sh +} + +pgd_host_tests() { host_tests "$@"; } +pgd_host_build() { host_build "$@"; } +pgd_host_examples() { host_examples "$@"; } +pgd_host_lints() { host_lints "$@"; } +pgd_docker_tests_pg_extensions() { docker_tests_pg_extensions "$@"; } +pgd_docker_tests_workspace() { docker_tests_workspace "$@"; } +pgd_docker_build_workspace() { docker_build_workspace "$@"; } +pgd_docker_build_examples() { docker_build_examples "$@"; } +pgd_docker_build_image() { docker_build_image "$@"; } + +_pg_debyte_complete_zsh() { + _arguments '1:pg version:(pg15 pg17 all)' +} + +_pg_debyte_complete_bash() { + local opts="pg15 pg17 all" + COMPREPLY=($(compgen -W "${opts}" -- "${COMP_WORDS[COMP_CWORD]}")) +} + +if [[ -n "${ZSH_VERSION:-}" ]]; then + if ! typeset -f compdef >/dev/null; then + autoload -Uz compinit + compinit -i + fi + compdef _pg_debyte_complete_zsh host_tests host_build host_examples host_lints docker_tests_pg_extensions docker_tests_workspace docker_build_workspace docker_build_examples + compdef _pg_debyte_complete_zsh pgd_host_tests pgd_host_build pgd_host_examples pgd_host_lints pgd_docker_tests_pg_extensions pgd_docker_tests_workspace pgd_docker_build_workspace pgd_docker_build_examples +elif [[ -n "${BASH_VERSION:-}" ]]; then + complete -F _pg_debyte_complete_bash host_tests host_build host_examples host_lints docker_tests_pg_extensions docker_tests_workspace docker_build_workspace docker_build_examples + complete -F _pg_debyte_complete_bash pgd_host_tests pgd_host_build pgd_host_examples pgd_host_lints pgd_docker_tests_pg_extensions pgd_docker_tests_workspace pgd_docker_build_workspace pgd_docker_build_examples +fi diff --git a/scripts/docker-ci-build-examples.sh b/scripts/docker-ci-build-examples.sh deleted file mode 100755 index 34e17dd..0000000 --- a/scripts/docker-ci-build-examples.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -docker run --rm -t pg-debyte-ci bash -lc "\ - cargo build -p readme_known_schema --all-targets --features pg17 && \ - cargo build -p readme_by_id --all-targets --features pg17 && \ - cargo build -p readme_envelope --all-targets --features pg17 \ -" diff --git a/scripts/docker-ci-build-image.sh b/scripts/docker-ci-build-image.sh index fa0192a..3f12d30 100755 --- a/scripts/docker-ci-build-image.sh +++ b/scripts/docker-ci-build-image.sh @@ -1,4 +1,14 @@ #!/usr/bin/env bash set -euo pipefail -docker build -f docker/ci.Dockerfile -t pg-debyte-ci . +BASE_IMAGE="${BASE_IMAGE:-pg-debyte-ci-base}" + +if [[ "${BASE_IMAGE}" != "pg-debyte-ci-base" ]]; then + if ! docker pull "${BASE_IMAGE}"; then + docker build -f docker/ci-base.Dockerfile -t "${BASE_IMAGE}" . + fi +else + docker build -f docker/ci-base.Dockerfile -t "${BASE_IMAGE}" . +fi + +docker build -f docker/ci.Dockerfile --build-arg BASE_IMAGE="${BASE_IMAGE}" -t pg-debyte-ci . diff --git a/scripts/docker-ci-build-workspace.sh b/scripts/docker-ci-build-workspace.sh deleted file mode 100755 index 7498035..0000000 --- a/scripts/docker-ci-build-workspace.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -docker run --rm -t pg-debyte-ci bash -lc "\ - cargo build --workspace --all-targets --exclude pg_debyte_ext --exclude readme_known_schema --exclude readme_by_id --exclude readme_envelope && \ - cargo build -p pg_debyte_ext --all-targets --features pg17 \ -" diff --git a/scripts/docker-ci-test-pg-extension.sh b/scripts/docker-ci-test-pg-extension.sh deleted file mode 100755 index 83c29a4..0000000 --- a/scripts/docker-ci-test-pg-extension.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -docker run --rm -t pg-debyte-ci bash -lc "\ - env -u PG_VERSION -u PG_MAJOR /usr/local/cargo/bin/cargo pgrx test \ - -p pg_debyte_ext --features pg17 \ -" diff --git a/scripts/host-examples.sh b/scripts/host-examples.sh deleted file mode 100755 index 10791e9..0000000 --- a/scripts/host-examples.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -cargo build -p readme_known_schema --all-targets --features pg17 -cargo build -p readme_by_id --all-targets --features pg17 -cargo build -p readme_envelope --all-targets --features pg17 diff --git a/scripts/host-lints.sh b/scripts/host-lints.sh deleted file mode 100755 index f2233fd..0000000 --- a/scripts/host-lints.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -cargo +nightly fmt -- --check -cargo clippy --workspace --exclude pg_debyte_ext -cargo clippy -p pg_debyte_ext --features pg17 diff --git a/scripts/host-tests.sh b/scripts/host-tests.sh deleted file mode 100755 index d6abd2d..0000000 --- a/scripts/host-tests.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -cargo test --workspace --exclude pg_debyte_ext -cargo pgrx test -p pg_debyte_ext --features pg17