snowflake-jdbc v0.0.1
Pre-release
Pre-release
SNOW-3704961: chunked streaming PUT — upload RPC + Python file_stream **Jira:** SNOW-3704961 ## What First of a stacked series replacing the buffered `ConnectionUploadStream` RPC (single `bytes` field, whole payload materialized in the wrapper) with a **chunked** one that keeps wrapper memory bounded to one chunk. Reference-matched to legacy JDBC's `FileBackedOutputStream`: the wrapper streams the source in bounded chunks across the FFI; the core re-materializes them into a re-readable hybrid buffer (RAM up to 128 MiB, spill to temp file beyond) and runs the **existing** upload path unchanged — so SHA-256 digest, retry, CSE encryption, auto-compress, and multipart all keep working exactly as for a file-path PUT. ## Design decision: store-and-forward upload (not true streaming) The wrapper streams the source across the FFI in bounded chunks, but the **core re-materializes the whole payload** (RAM up to 128 MiB, then spilled to a temp file) **before** the GS round-trip and cloud upload begin. This deliberately matches the reference drivers (JDBC `FileBackedOutputStream`, legacy Python); it is **not** a true byte-for-byte streaming pipeline. Documented in code at `sf_core/src/apis/database_driver_v1/stream_transfer.rs` (module "Operational constraints"). **Pluses** - Reuses the existing, battle-tested file-path PUT pipeline **unchanged**: SHA-256 digest, client-side encryption, auto-compress, multipart, and **full retry** — including whole-transfer restart on mid-upload credential/stage-token expiry (which requires a re-readable source). - Behavioral parity with JDBC / legacy Python; low risk — no new code in the crypto/retry-critical path. - Fixes the original problem: the **wrapper** never buffers the whole file (bounded to ~one chunk). **Minuses** - The **core** is not zero-disk for uploads above the 128 MiB in-RAM threshold: it needs local temp-disk headroom ≈ payload size, does full-file disk I/O before the first byte reaches cloud storage, and cannot overlap receive with upload (no streamed progress). A small / read-only / full tmpdir fails the upload. **Alternative considered & rejected:** a genuinely-streaming multipart upload (overlap receive + upload, zero core disk). Rejected for this stack: it *loses* whole-transfer retry (a consumed stream can't be replayed), forces the client to pre-hash + pre-measure and drops core-side auto-compress, and needs a new streaming-multipart implementation in the crypto-critical path — substantially bigger and riskier. Left as a documented future escape hatch. (Download, by contrast, **is** true zero-disk streaming — see the download PRs.) ## Changes - **Proto:** `UploadStreamHandle` + `ConnectionUploadStreamBegin/Chunk/Finish/Abort` messages & RPCs (old `ConnectionUploadStream` kept — JDBC still uses it until the JDBC-migration PR). - **Rust core:** `SpooledBuffer` (RAM→temp-file), `UploadStreamSession` + begin/chunk/finish/abort handlers, `run_put_stream_via_gs(ByteSource)` extraction (old RPC delegates to it), handle-manager + proto glue. - **Python:** `CHUNK_SIZE` (8 MiB) + `upload_stream_*` wrappers on `CoreDriver`/`AsyncCoreDriver`; `cursor.execute(sql, file_stream=<BinaryIO>)` (sync + async) via `_execute_upload_stream`. Best-effort abort on failure never masks the original error. No client-side PUT regex — the core validates. ## Tests - **Unit** (mocked core): chunk-loop reassembly, abort-on-error, finish-fails-then-abort-fails (original error propagates), large/real-file/empty payloads, disk-spill (Mem→File) via the RPC handler, sync + async. - **e2e** (live Snowflake): upload+LS, multi-chunk round-trip, `AUTO_COMPRESS`→`.gz`, non-PUT `ProgrammingError`. - `cargo test -p sf_core` green (via `lld`; the sandbox's system `ld` can't link the nix glibc/openssl — CI/proper build hosts unaffected). ## Full stack (draft series — review & merge bottom-up) | # | PR | Scope | |---|----|-------| | 1 | #767 | chunked streaming **PUT** — upload RPC + Python `file_stream` (base: `main`) | | 2 | #813 | zero-disk **S3** streaming in `file_manager` (1/3 of former #774) | | 3 | #814 | chunked **download-stream RPCs** (S3) — proto + handlers + FFI + cancellation (2/3) | | 4 | #774 | Python **`cursor.download_stream`** over the chunk RPC (3/3) | | 5 | #780 | chunked streaming **GET (GCS + Azure)** — zero-disk producers | | 6 | #782 | migrate **JDBC** to the chunked RPCs; delete the old buffered RPCs | ➡️ **You are here: #767** 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> GitOrigin-RevId: 75a52a16fca742f1ecbbd5de126d0840db51d508