From fc5235cbfcbd298aa2c8d79382528e3e43e94a6a Mon Sep 17 00:00:00 2001 From: Pavlo Golub Date: Thu, 11 Dec 2025 12:23:02 +0100 Subject: [PATCH 1/3] feat: add DevContainer setup (#28) --- .devcontainer/Dockerfile | 35 +++++++++++++++++++++++++++++++++ .devcontainer/devcontainer.json | 26 ++++++++++++++++++++++++ README.md | 22 ++++++++++++++++++++- 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..0b4decb --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,35 @@ +# --- base image --- +FROM postgres:17-bookworm AS base + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates curl \ + && update-ca-certificates \ + && sh /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y \ + && apt-get update + +# --- pgrx builder base image +FROM base AS pgrx_builder_base + +ENV DEBIAN_FRONTEND=noninteractive +ENV PG_MAJOR=17 + +RUN apt-get -qy install curl gnupg apt-transport-https \ + libreadline-dev zlib1g-dev flex bison libxml2-dev libxslt-dev \ + libssl-dev libxml2-utils xsltproc ccache \ + build-essential git pkg-config libssl-dev clang libclang-dev \ + postgresql-server-dev-$PG_MAJOR postgresql-common \ + postgresql-common-dev protobuf-compiler libprotobuf-dev \ + && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + +# --- etcd_fdw image --- +FROM pgrx_builder_base AS etcd_fdw_builder + +ENV CARGO_PGRX_VERSION=0.16.1 +ENV PATH="/root/.cargo/bin:${PATH}" + +RUN . $HOME/.cargo/env \ + && rustup component add clippy rustfmt rust-src \ + && cargo install --force --locked cargo-pgrx@"${CARGO_PGRX_VERSION}" \ + && cargo pgrx init --pg$PG_MAJOR $(which pg_config) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..3365e43 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,26 @@ +{ + "name": "rust-dev", + "build": { + "dockerfile": "Dockerfile" + }, + "features": { + "ghcr.io/devcontainers/features/common-utils:2": {}, + "git": "latest" + }, + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + }, + "extensions": [ + "rust-lang.rust-analyzer", + "serayuzgur.crates" + ] + } + }, + "mounts": [ + "source=cargo-home,target=/usr/local/cargo,type=volume", + "source=cargo-registry,target=/usr/local/cargo/registry,type=volume", + "source=cargo-target,target=/workspace/target,type=volume" + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 067c22b..744bdf7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,27 @@ A foreign data wrapper around etcd for postgres -## Setup +## Development Setup + +### Option 1: Using DevContainer + +The easiest way to get started is using the provided DevContainer configuration: + +1. **Prerequisites** + - [Docker Desktop](https://www.docker.com/products/docker-desktop/) or Docker Engine + - [VS Code](https://code.visualstudio.com/) with the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) + +2. **Open in DevContainer** + - Open this project in VS Code + - Press `F1` and select "Dev Containers: Reopen in Container" + - Wait for the container to build (~4 minutes first time) + +3. **Start Developing** + - All dependencies are pre-installed (Rust, cargo-pgrx, PostgreSQL 17, protobuf) + - Cargo cache volumes ensure fast rebuilds + - Run `cargo pgrx run` to build and test + +### Option 2: Manual Setup - Install pgrx on your machine `cargo install --locked cargo-pgrx --version 0.16.1` - Setup pgrx `cargo pgrx init` From 0822652565cf543c5e92e78e7610dfcaa8884f07 Mon Sep 17 00:00:00 2001 From: Hunaid2000 Date: Thu, 11 Dec 2025 18:02:37 +0500 Subject: [PATCH 2/3] Fix column order of returned tuples in iter_scan --- src/lib.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7f6d02c..5df7f2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ pub(crate) struct EtcdFdw { client: Client, rt: Runtime, fetch_results: Vec, + tgt_cols: Vec, fetch_key: bool, fetch_value: bool, } @@ -231,6 +232,7 @@ impl ForeignDataWrapper for EtcdFdw { client, rt, fetch_results, + tgt_cols: Vec::new(), fetch_key: false, fetch_value: false, }) @@ -434,6 +436,7 @@ impl ForeignDataWrapper for EtcdFdw { }; let result_vec = result_unwrapped.take_kvs(); self.fetch_results = result_vec; + self.tgt_cols = columns.to_vec(); Ok(()) } @@ -448,11 +451,13 @@ impl ForeignDataWrapper for EtcdFdw { let value = x .value_str() .expect("Expected a value, but the value was empty"); - if self.fetch_key { - row.push("key", Some(Cell::String(key.to_string()))); - } - if self.fetch_value { - row.push("value", Some(Cell::String(value.to_string()))); + for tgt_col in &self.tgt_cols { + if tgt_col.name == "key" { + row.push(&tgt_col.name, Some(Cell::String(key.to_string()))); + } + if tgt_col.name == "value" { + row.push(&tgt_col.name, Some(Cell::String(value.to_string()))); + } } })) } From 6e9a7797ac7473a61ffc53a895e072d99fdca8d2 Mon Sep 17 00:00:00 2001 From: Pavlo Golub Date: Thu, 11 Dec 2025 19:33:15 +0100 Subject: [PATCH 3/3] add tests --- src/lib.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 5df7f2a..2768973 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -771,4 +771,62 @@ mod tests { assert_eq!(Err(spi::SpiError::InvalidPosition), query_result); } + + #[pg_test] + fn test_select_value_only_with_key_filter() { + // Test for issue #26: selecting only value column with WHERE clause on key + let (_container, url) = create_container(); + + create_fdt(url); + + // Insert test data + Spi::run("INSERT INTO test (key, value) VALUES ('key1','value1'),('key2','value2'),('key3','value3')") + .expect("INSERT should work"); + + // Test 1: SELECT value WHERE key = 'key2' should return the value + let query_result = Spi::get_one::("SELECT value FROM test WHERE key = 'key2'") + .expect("SELECT value with key filter should work"); + + assert_eq!(Some(format!("value2")), query_result); + + // Test 2: SELECT key WHERE key = 'key1' should also work + let query_result = Spi::get_one::("SELECT key FROM test WHERE key = 'key1'") + .expect("SELECT key with key filter should work"); + + assert_eq!(Some(format!("key1")), query_result); + + // Test 3: SELECT * WHERE key = 'key3' should work (baseline) + let query_result = Spi::get_two::("SELECT * FROM test WHERE key = 'key3'") + .expect("SELECT * with key filter should work"); + + assert_eq!((Some(format!("key3")), Some(format!("value3"))), query_result); + } + + #[pg_test] + fn test_update_value_only_with_key_filter() { + // Test for issue #26: UPDATE with only value column when key is in WHERE clause + let (_container, url) = create_container(); + + create_fdt(url); + + // Insert test data + Spi::run("INSERT INTO test (key, value) VALUES ('gather/78','original_value'),('gather/84','other_value')") + .expect("INSERT should work"); + + // Update without including key column in SET clause + Spi::run("UPDATE test SET value = 'updated_value' WHERE key = 'gather/84'") + .expect("UPDATE with key filter should work"); + + // Verify the update worked + let query_result = Spi::get_one::("SELECT value FROM test WHERE key = 'gather/84'") + .expect("SELECT after UPDATE should work"); + + assert_eq!(Some(format!("updated_value")), query_result); + + // Verify other row was not affected + let query_result = Spi::get_one::("SELECT value FROM test WHERE key = 'gather/78'") + .expect("SELECT other row should work"); + + assert_eq!(Some(format!("original_value")), query_result); + } }