|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +pg_lake integrates Apache Iceberg and data lake files (Parquet, CSV, JSON) into PostgreSQL, enabling PostgreSQL to function as a lakehouse system. The architecture consists of two main components: |
| 8 | +- **PostgreSQL with pg_lake extensions**: Handles query planning, transaction boundaries, and orchestration |
| 9 | +- **pgduck_server**: A separate multi-threaded process that implements the PostgreSQL wire protocol and delegates computation to DuckDB's columnar execution engine |
| 10 | + |
| 11 | +Users connect only to PostgreSQL. The pg_lake extensions transparently delegate data scanning and computation to pgduck_server (running DuckDB) when appropriate, while maintaining full transactional guarantees. |
| 12 | + |
| 13 | +## Build Commands |
| 14 | + |
| 15 | +### First-time build |
| 16 | +```bash |
| 17 | +# Install vcpkg dependencies (required once) |
| 18 | +export VCPKG_VERSION=2025.12.12 |
| 19 | +git clone --recurse-submodules https://github.com/Microsoft/vcpkg.git |
| 20 | +./vcpkg/bootstrap-vcpkg.sh |
| 21 | +./vcpkg/vcpkg install azure-identity-cpp azure-storage-blobs-cpp azure-storage-files-datalake-cpp openssl |
| 22 | +export VCPKG_TOOLCHAIN_PATH="$(pwd)/vcpkg/scripts/buildsystems/vcpkg.cmake" |
| 23 | + |
| 24 | +# Build and install all extensions and pgduck_server |
| 25 | +make install |
| 26 | +``` |
| 27 | + |
| 28 | +### Subsequent builds |
| 29 | +```bash |
| 30 | +# Fast build that skips rebuilding DuckDB if already built |
| 31 | +make install-fast |
| 32 | +``` |
| 33 | + |
| 34 | +### Component-specific builds |
| 35 | +```bash |
| 36 | +# Build/install individual extensions |
| 37 | +make install-pg_lake_iceberg |
| 38 | +make install-pg_lake_table |
| 39 | +make install-pgduck_server |
| 40 | + |
| 41 | +# Build all extensions (top-level meta extension) |
| 42 | +make install-pg_lake |
| 43 | +``` |
| 44 | + |
| 45 | +## Running pg_lake |
| 46 | + |
| 47 | +### Required setup |
| 48 | +```sql |
| 49 | +-- In postgresql.conf: |
| 50 | +shared_preload_libraries = 'pg_extension_base' |
| 51 | + |
| 52 | +-- Connect to PostgreSQL and create extensions: |
| 53 | +CREATE EXTENSION pg_lake CASCADE; |
| 54 | +-- This installs: pg_extension_base, pg_map, pg_extension_updater, |
| 55 | +-- pg_lake_engine, pg_lake_iceberg, pg_lake_table, pg_lake_copy, pg_lake |
| 56 | + |
| 57 | +-- Set default location for Iceberg tables: |
| 58 | +SET pg_lake_iceberg.default_location_prefix TO 's3://your-bucket/pglake'; |
| 59 | +``` |
| 60 | + |
| 61 | +### Starting pgduck_server |
| 62 | +```bash |
| 63 | +# Start pgduck_server (must be running for pg_lake to work) |
| 64 | +pgduck_server |
| 65 | + |
| 66 | +# With options: |
| 67 | +pgduck_server --memory_limit '8GB' --cache_dir /tmp/cache --init_file_path /path/to/init.sql |
| 68 | + |
| 69 | +# pgduck_server listens on port 5332 (unix socket /tmp by default) |
| 70 | +# You can connect directly to pgduck_server for debugging: |
| 71 | +psql -p 5332 -h /tmp |
| 72 | +``` |
| 73 | + |
| 74 | +## Testing |
| 75 | + |
| 76 | +The project uses **pytest** for all regression testing (not traditional PostgreSQL SQL regression tests). |
| 77 | + |
| 78 | +### Running all tests |
| 79 | +```bash |
| 80 | +# Install test dependencies (first time only) |
| 81 | +pipenv install --dev |
| 82 | + |
| 83 | +# Run all local tests |
| 84 | +make check |
| 85 | + |
| 86 | +# Run end-to-end tests (requires S3/cloud access) |
| 87 | +make check-e2e |
| 88 | + |
| 89 | +# Run upgrade tests |
| 90 | +make check-upgrade |
| 91 | + |
| 92 | +# Run all tests (local + e2e) |
| 93 | +make check # includes check-local and check-e2e |
| 94 | +``` |
| 95 | + |
| 96 | +### Running tests for specific components |
| 97 | +```bash |
| 98 | +# Test specific extension |
| 99 | +make check-pg_lake_table |
| 100 | +make check-pg_lake_iceberg |
| 101 | +make check-pgduck_server |
| 102 | + |
| 103 | +# Run isolation tests |
| 104 | +make check-isolation_pg_lake_table |
| 105 | +``` |
| 106 | + |
| 107 | +### Running installcheck |
| 108 | +```bash |
| 109 | +# Start pgduck_server with test configuration |
| 110 | +pgduck_server --init_file_path pgduck_server/tests/test_secrets.sql --cache_dir /tmp/cache & |
| 111 | + |
| 112 | +# Run installcheck (tests against installed extensions) |
| 113 | +make installcheck |
| 114 | + |
| 115 | +# Run installcheck for specific component |
| 116 | +make installcheck-pg_lake_table |
| 117 | +``` |
| 118 | + |
| 119 | +### Running individual pytest tests |
| 120 | +```bash |
| 121 | +cd pg_lake_table |
| 122 | +PYTHONPATH=../test_common pipenv run pytest -v tests/pytests/test_specific.py |
| 123 | +PYTHONPATH=../test_common pipenv run pytest -v tests/pytests/test_specific.py::test_function_name |
| 124 | +``` |
| 125 | + |
| 126 | +### Testing with PostgreSQL regression suite |
| 127 | +```bash |
| 128 | +# Run PostgreSQL's own tests with pg_lake extensions loaded |
| 129 | +export PG_REGRESS_DIR=/path/to/postgres/src/test/regress |
| 130 | + |
| 131 | +# Run tests |
| 132 | +make installcheck-postgres PG_REGRESS_DIR=$PG_REGRESS_DIR |
| 133 | +make installcheck-postgres-with_extensions_created PG_REGRESS_DIR=$PG_REGRESS_DIR |
| 134 | +``` |
| 135 | + |
| 136 | +## Extension Architecture |
| 137 | + |
| 138 | +pg_lake follows a **modular design** with interoperating components. Each extension has a specific responsibility: |
| 139 | + |
| 140 | +### Extension dependency chain |
| 141 | +``` |
| 142 | +pg_lake (meta-extension) |
| 143 | +├── pg_lake_table (FDW for querying data lake files) |
| 144 | +│ └── pg_lake_iceberg (Iceberg specification implementation) |
| 145 | +│ └── pg_lake_engine (common module for pg_lake extensions) |
| 146 | +│ ├── pg_extension_base (foundation for all extensions) |
| 147 | +│ ├── pg_map (generic map type) |
| 148 | +│ └── pg_extension_updater (automatic extension updates) |
| 149 | +└── pg_lake_copy (COPY to/from data lake) |
| 150 | + └── pg_lake_engine |
| 151 | +
|
| 152 | +pg_lake_spatial (optional, depends on PostGIS) |
| 153 | +pg_lake_benchmark (optional, for benchmarking) |
| 154 | +``` |
| 155 | + |
| 156 | +### Core extensions |
| 157 | +- **pg_extension_base**: Foundation for all extensions, provides common utilities |
| 158 | +- **pg_extension_updater**: Automatically updates extensions on startup |
| 159 | +- **pg_map**: Generic map/key-value type generator for semi-structured data |
| 160 | +- **pg_lake_engine**: Common module shared by data lake extensions (depends on Apache Avro) |
| 161 | +- **pg_lake_iceberg**: Full Iceberg v2 protocol implementation with transactional support |
| 162 | +- **pg_lake_table**: Foreign data wrapper to query Parquet/CSV/JSON/Iceberg files |
| 163 | +- **pg_lake_copy**: COPY command extensions for importing/exporting to data lakes |
| 164 | +- **pg_lake**: Meta-extension that installs all required extensions via CASCADE |
| 165 | + |
| 166 | +### External components |
| 167 | +- **pgduck_server**: Standalone server implementing PostgreSQL wire protocol, executes queries via DuckDB |
| 168 | +- **duckdb_pglake**: DuckDB extension adding PostgreSQL-compatible functions to DuckDB |
| 169 | +- **avro**: Apache Avro library (patched) for Iceberg metadata handling |
| 170 | + |
| 171 | +## Important File Locations |
| 172 | + |
| 173 | +### Build system |
| 174 | +- `Makefile`: Top-level build orchestration for all components |
| 175 | +- `shared.mk`: Shared Makefile rules for extensions |
| 176 | +- Each extension has its own `Makefile` following PGXS conventions |
| 177 | + |
| 178 | +### Tests |
| 179 | +- `<extension>/tests/pytests/`: Main pytest test suites |
| 180 | +- `<extension>/tests/e2e/`: End-to-end tests requiring cloud storage |
| 181 | +- `<extension>/tests/isolation/`: Isolation tester tests for concurrency |
| 182 | +- `test_common/`: Shared test utilities and fixtures |
| 183 | +- `pytest.ini`: Root pytest configuration |
| 184 | + |
| 185 | +### Documentation |
| 186 | +- `docs/building-from-source.md`: Detailed build instructions |
| 187 | +- `docs/iceberg-tables.md`: Iceberg table usage |
| 188 | +- `docs/query-data-lake-files.md`: Foreign table usage |
| 189 | +- `docs/data-lake-import-export.md`: COPY command usage |
| 190 | + |
| 191 | +## Code Conventions |
| 192 | + |
| 193 | +### C code (PostgreSQL extensions and pgduck_server) |
| 194 | +- **Indentation**: Use `pgindent` before commits (see `make reindent`) |
| 195 | +- **Naming**: |
| 196 | + - Variables/functions: `lower_case_with_underscores` |
| 197 | + - Macros/constants: `UPPER_CASE_WITH_UNDERSCORES` |
| 198 | + - Structs/enums: `CamelCase` |
| 199 | + - Global variables: Prefix with module identifier (e.g., `IcebergTableCache`) |
| 200 | +- **Comments**: Focus on "why" not "what"; use block comments for complex algorithms |
| 201 | +- **Typedefs**: Download from buildfarm via `make typedefs` for pgindent |
| 202 | + |
| 203 | +### Python code |
| 204 | +- **Formatting**: Use `black` (see `make reindent` or `pipenv run black`) |
| 205 | +- **Style**: Follow pytest conventions for test naming and fixtures |
| 206 | + |
| 207 | +### Before committing |
| 208 | +```bash |
| 209 | +# Format all code |
| 210 | +make reindent |
| 211 | + |
| 212 | +# Check formatting |
| 213 | +make check-indent |
| 214 | +``` |
| 215 | + |
| 216 | +## Local Development with MinIO |
| 217 | + |
| 218 | +For testing without cloud S3 latency, use MinIO locally: |
| 219 | + |
| 220 | +```bash |
| 221 | +# Install and start MinIO |
| 222 | +brew install minio # or download from min.io |
| 223 | +minio server /tmp/data |
| 224 | + |
| 225 | +# Access UI at http://localhost:9000 |
| 226 | +# Create access key: testkey / testpassword |
| 227 | +# Create bucket: localbucket |
| 228 | + |
| 229 | +# Add to ~/.aws/config: |
| 230 | +[services testing-minio] |
| 231 | +s3 = |
| 232 | + endpoint_url = http://localhost:9000 |
| 233 | + |
| 234 | +[profile minio] |
| 235 | +region = us-east-1 |
| 236 | +services = testing-minio |
| 237 | +aws_access_key_id = testkey |
| 238 | +aws_secret_access_key = testpassword |
| 239 | + |
| 240 | +# Configure pgduck_server (connect to port 5332) |
| 241 | +psql -p 5332 -h /tmp -c " |
| 242 | +CREATE SECRET s3testMinio ( |
| 243 | + TYPE S3, |
| 244 | + KEY_ID 'testkey', |
| 245 | + SECRET 'testpassword', |
| 246 | + ENDPOINT 'localhost:9000', |
| 247 | + SCOPE 's3://localbucket', |
| 248 | + URL_STYLE 'path', |
| 249 | + USE_SSL false |
| 250 | +);" |
| 251 | + |
| 252 | +# Use in PostgreSQL |
| 253 | +psql -c "SET pg_lake_iceberg.default_location_prefix TO 's3://localbucket'" |
| 254 | +``` |
| 255 | + |
| 256 | +## Common Development Workflows |
| 257 | + |
| 258 | +### Adding new functionality to an extension |
| 259 | +1. Read existing code in `<extension>/src/` to understand patterns |
| 260 | +2. Modify C source files in `src/` |
| 261 | +3. If adding SQL functions, update `<extension>--<version>.sql` |
| 262 | +4. Add pytest tests in `tests/pytests/` |
| 263 | +5. Build and test: |
| 264 | + ```bash |
| 265 | + make install-<extension> |
| 266 | + make check-<extension> |
| 267 | + ``` |
| 268 | + |
| 269 | +### Creating a version upgrade script |
| 270 | +```bash |
| 271 | +# Bump all extension versions at once |
| 272 | +python tools/bump_extension_versions.py 3.1 |
| 273 | + |
| 274 | +# This creates upgrade stubs like: pg_lake_engine--3.0--3.1.sql |
| 275 | +``` |
| 276 | + |
| 277 | +### Debugging query execution |
| 278 | +```bash |
| 279 | +# Connect to PostgreSQL and check pg_lake query plans |
| 280 | +psql -c "EXPLAIN (VERBOSE) SELECT * FROM iceberg_table" |
| 281 | + |
| 282 | +# Connect directly to pgduck_server to see DuckDB execution |
| 283 | +psql -p 5332 -h /tmp |
| 284 | +postgres=> SELECT * FROM duckdb_settings(); |
| 285 | +postgres=> EXPLAIN SELECT ...; |
| 286 | +``` |
| 287 | + |
| 288 | +### Viewing Iceberg metadata |
| 289 | +```sql |
| 290 | +-- Show all Iceberg tables and their metadata locations |
| 291 | +SELECT table_name, metadata_location FROM iceberg_tables; |
| 292 | + |
| 293 | +-- View Iceberg snapshots |
| 294 | +SELECT * FROM iceberg_snapshots('<table_name>'); |
| 295 | +``` |
| 296 | + |
| 297 | +## Key PostgreSQL GUCs (Settings) |
| 298 | + |
| 299 | +```sql |
| 300 | +-- Required: preload base extension in postgresql.conf |
| 301 | +shared_preload_libraries = 'pg_extension_base' |
| 302 | + |
| 303 | +-- Iceberg settings |
| 304 | +pg_lake_iceberg.default_location_prefix = 's3://bucket/prefix' |
| 305 | +``` |
| 306 | + |
| 307 | +## CI and Testing Notes |
| 308 | + |
| 309 | +- JDBC driver path must be set for Spark verification tests: `JDBC_DRIVER_PATH=/usr/share/java/postgresql.jar` |
| 310 | +- Java 21+ required for Polaris REST catalog tests |
| 311 | +- Azure tests require `azurite` (install via npm: `npm install -g azurite`) |
0 commit comments