Skip to content

refactor(cloud): split cloud object store integration into a vortex-cloud crate - #8999

Merged
robert3005 merged 13 commits into
developfrom
claude/opendal-integration-review-1h8f3a
Jul 29, 2026
Merged

refactor(cloud): split cloud object store integration into a vortex-cloud crate#8999
robert3005 merged 13 commits into
developfrom
claude/opendal-integration-review-1h8f3a

Conversation

@robert3005

@robert3005 robert3005 commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Rationale for this change

Follow-up to #8845, which added the OpenDAL-backed cos:// store.

URL-to-ObjectStore resolution had grown three independent implementations:

Caller Schemes Caching Credentials
vortex-python's Registry file, s3, azure, gcs, http, cos per-prefix case-insensitive env
vortex-jni's make_object_store file, s3, azure, gcs, cos per-URL property-driven builders
vortex-duckdb's resolve_filesystem file, s3 only none from_env only

Only the first knew about the OpenDAL-backed schemes, and it was pub(crate), so no Rust
consumer could reach it. Adding a provider meant touching every caller — which is exactly what
#8845 had to do, and what the next provider would have to do again.

This also unblocks a concrete gap: vortex-object-store-opendal was publish = false, so no
published crate could depend on it.

What changes are included in this PR?

A new vortex-cloud crate owns all cloud object store integration. The registry and the
OpenDAL-backed services now live together in one place:

  • vortex-python's private Registry moves in and becomes public vortex_cloud::Registry
    (309 lines deleted from the binding).
  • vortex-object-store-opendal is folded in as a vortex_cloud::opendal module and deleted as a
    standalone crate. Files move with git mv, so history follows.
  • vortex-io sheds its registry, its object_store_registry / opendal features, and its url
    dependency. It keeps the three generic object_store adapters
    (ObjectStoreFileSystem, ObjectStoreReadAt, ObjectStoreWrite), which are provider-agnostic —
    they serve LocalFileSystem and InMemory exactly as they serve S3 — and compat/obj_store.rs,
    whose impl ObjectStore for Compat<T> cannot move (orphan rule: Compat is defined in
    vortex-io).
  • vortex-cloud depends on nothing in vortex-io, so the split adds no dependency edge to
    vortex-file, vortex-datafusion, or the benchmarks.

The bindings resolve through the facade's new vortex::cloud:

  • vortex-duckdb replaces its file/s3-only match with the registry, gaining Azure, GCS, HTTP
    and per-bucket client reuse.
  • vortex-jni keeps its property-driven builders — they have S3 behaviour the registry does not
    reproduce — but asks supports_scheme instead of hard-coding "cos", so the call site stays
    correct as services are added.

Alibaba Cloud OSS, alongside COS, behind per-service cos / oss features. OSS reads
ALIBABA_CLOUD_* (with the underscore) and has no disable_config_load; its nearest knob,
skip_signature, is exposed for public buckets.

Feature-gating. The registry sits behind vortex-cloud/registry so plain file IO does not pull
in the cloud HTTP stack: vortex-file with object_store still resolves zero reqwest. Making the
services opt-in (default = []) also made the crate wasm32-clean, which removed a CI exclusion —
see below.

Remove ambient process state from tests. The registry takes its configuration from an injected
EnvSource, replacing three unsafe std::env::set_var blocks. set_var became unsafe in Rust
2024 precisely because cargo test runs tests on multiple threads in one process.

Merge of develop resolves a modify/delete conflict against #8733, which fixed a
percent-encoding bug in the file this PR hoists. The deletion is kept and the fix ported into
vortex-cloud: resolve saturates the segment-count subtraction (a percent-encoded
refs%2Fconvert%2Fparquet is one raw URL segment but decodes to three, so the subtraction
underflowed and panicked), and path_suffix decodes through Path::from_url_path. Verified by
reverting the fix and watching test_resolve_percent_encoded_path panic on subtract-overflow.
datasets.py builds exactly that URL shape, so the fix is on the live HuggingFace path.

What APIs are changed? Are there any user-facing changes?

New public Rust API:

  • Crate vortex-cloud, re-exported as vortex::cloud under the object_store_registry feature.
  • vortex_cloud::Registry (+ Registry::new), behind registry.
  • vortex_cloud::opendal::{supports_scheme, SUPPORTED_SCHEMES, COS_SCHEME, CosConfig, make_cos_store, OSS_SCHEME, OssConfig, make_oss_store, make_opendal_store, make_opendal_store_with_env, OpenDALStoreError}, behind cos / oss / opendal.
  • vortex-duckdb gains an opendal feature.

Removed: vortex-io's object_store_registry and opendal features, and the
vortex-object-store-opendal crate (never published, so no released API is affected).

No Python-facing API change: vortex-python swaps a private implementation for the shared one.
DuckDB users gain Azure/GCS/HTTP support that previously errored with Unsupported URL scheme.

One CI change worth a look

--exclude vortex-object-store-opendal was added to the wasm32 build because that crate defaulted
to cos + oss and so pulled opendal and reqwest. vortex-cloud defaults to no services, so the
exclusion is dropped rather than renamed. Verified: cargo tree --workspace --target wasm32-unknown-unknown resolves no opendal at all, and cargo check -p vortex-cloud --target wasm32-unknown-unknown succeeds. Left in place, the stale crate name would have silently matched
nothing and the wasm32 job would have built vortex-cloud unguarded.

Checks

Check Result
cargo test -p vortex-cloud --all-features 22 passed + 2 doctests
cargo test -p vortex-io --all-features 143 passed
cargo test -p vortex-python --all-features --lib 5 passed
cargo check -p vortex-cloud × {no-default, registry, cos, oss, all} pass
cargo check -p vortex-io --all-features pass
cargo check -p vortex-file --all-features pass
cargo check -p vortex-python --all-features --all-targets pass
cargo check -p vortex-jni --all-features --all-targets pass
cargo check -p vortex --features files,tokio,object_store,object_store_registry,opendal pass
cargo check --target wasm32-unknown-unknown -p vortex-cloud -p vortex-io -p vortex-file pass
cargo clippy -p vortex-cloud -p vortex-io -p vortex-python -p vortex-jni --all-targets --all-features clean
cargo clippy -p vortex-cloud --no-default-features clean
cargo +nightly fmt --all clean
cargo metadata --locked Cargo.lock up to date
yamllint --strict -c .yamllint.yaml .github/workflows/ci.yml clean
uv run --all-packages make -C docs clean html build succeeded (--fail-on-warning)
uv run --all-packages make -C docs clean doctest 271 tests, 0 failures

Could not run locally: cargo check -p vortex-duckdb — the build script downloads DuckDB source
from github.com/duckdb/duckdb/archive, which returns 403 through this environment's proxy
(codeload.github.com too). The vortex-duckdb change is 2 files; I verified by inspection that
vortex::cloud::Registry is re-exported under the features the manifest requests, that
From<object_store::Error> for VortexError exists for the new ?, and that no import became
unused. CI's DuckDB jobs will be the first real compile of it.

cargo deny was also not run (failed to build locally). The new OSS dependencies
(opendal-service-oss, reqsign-aliyun-oss) are both Apache-2.0, matching the COS equivalents
already accepted in #8845.

🤖 Generated with Claude Code

https://claude.ai/code/session_012TyPy9MbKP2j546m1CaKLk

…d DuckDB

URL-to-ObjectStore resolution had grown three independent implementations:
`vortex-python`'s `Registry`, `vortex-jni`'s `make_object_store`, and
`vortex-duckdb`'s two-scheme match. Only the first knew about the
OpenDAL-backed schemes, and it was `pub(crate)`, so no Rust consumer could
reach it.

Hoist that registry into `vortex-io::object_store::Registry` as public API and
point the bindings at it:

- `vortex-python` drops its private copy and uses the shared one.
- `vortex-duckdb` replaces its `file`/`s3`-only match (no caching, no
  credentials beyond `from_env`) with the registry, gaining Azure, GCS, HTTP
  and per-bucket client reuse.
- `vortex-jni` keeps its property-driven builders, which have S3 behaviour the
  registry does not reproduce, but now asks `supports_scheme` instead of
  hard-coding `"cos"`.

The registry lives behind a new `object_store_registry` feature so that plain
file IO does not pull in the cloud HTTP stack: `vortex-file` with
`object_store` still resolves zero `reqwest` in its dependency tree.

Alibaba Cloud OSS support is added alongside the existing COS support, behind
per-service `cos` and `oss` features on `vortex-object-store-opendal`, which is
now publishable so that published crates can depend on it. The crate is split
into per-service modules and gains `supports_scheme` / `SUPPORTED_SCHEMES` so
callers stop matching scheme strings themselves. OSS reads `ALIBABA_CLOUD_*`
(with the underscore) and has no `disable_config_load`; its nearest knob,
`skip_signature`, is exposed for public buckets.

Store construction no longer depends on ambient process state in tests: the
registry takes its configuration from an injected source, replacing the three
`unsafe std::env::set_var` blocks the moved tests used. The suite now passes
with a deliberately hostile environment set.

Signed-off-by: "Claude" <robert@spiraldb.com>
@robert3005 robert3005 added the changelog/feature A new feature label Jul 27, 2026 — with Claude
Signed-off-by: Robert Kruszewski <github@robertk.io>
Signed-off-by: Robert Kruszewski <github@robertk.io>
@codspeed-hq

codspeed-hq Bot commented Jul 27, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 1841 untouched benchmarks
⏩ 55 skipped benchmarks1


Comparing claude/opendal-integration-review-1h8f3a (e278343) with develop (2a54a2c)

Open in CodSpeed

Footnotes

  1. 55 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

robert3005 and others added 7 commits July 27, 2026 20:58
Signed-off-by: Robert Kruszewski <github@robertk.io>
I, Claude <robert@spiraldb.com>, hereby add my Signed-off-by to this commit: c85a848

Signed-off-by: Claude <robert@spiraldb.com>
Signed-off-by: Robert Kruszewski <github@robertk.io>
Resolves the modify/delete conflict on vortex-python's registry.rs: this
branch hoists that file into vortex-io, while #8733 fixed a
percent-encoding bug in it on develop.

Keeps the deletion and ports the fix into the hoisted registry:

- `resolve` saturates the segment-count subtraction. A percent-encoded
  segment (`refs%2Fconvert%2Fparquet`) counts as one raw URL segment but
  decodes to three, so the subtraction underflowed and panicked.
- `path_suffix` joins the segments and percent-decodes them through
  `Path::from_url_path` instead of `PathPart::parse` per segment.
- Adds the regression test, using the injected `EnvSource` rather than
  the process environment.

Signed-off-by: "Claude" <robert@spiraldb.com>
…crate

Moves the URL -> ObjectStore registry out of vortex-io and folds
vortex-object-store-opendal into the same crate, so all cloud object
store integration lives in one place.

- vortex-cloud owns the registry (`registry` feature) and the
  OpenDAL-backed services, now an `opendal` module behind per-service
  `cos` / `oss` features.
- vortex-object-store-opendal is deleted; its files move with history.
- vortex-io drops its `object_store_registry` and `opendal` features and
  its `url` dependency, keeping only the generic object_store adapters.
- The facade exposes `vortex::cloud`, and vortex-python, vortex-jni and
  vortex-duckdb resolve through it.

The registry no longer depends on vortex-io at all. Inside the `opendal`
module the external crate is spelled `::opendal` to disambiguate it from
the module of the same name.

Signed-off-by: "Claude" <robert@spiraldb.com>
…w-1h8f3a' into claude/opendal-integration-review-1h8f3a
vortex-object-store-opendal was excluded from the wasm32 build because it
defaulted to `cos` + `oss`, pulling opendal and reqwest, neither of which
builds for wasm32-unknown-unknown.

vortex-cloud replaces it with `default = []`, so a default-feature build
pulls neither: `cargo tree --workspace --target wasm32-unknown-unknown`
resolves no opendal at all, and `cargo check -p vortex-cloud --target
wasm32-unknown-unknown` succeeds. Nothing enables the service features by
default, so the exclusion is no longer needed and the stale crate name
would have silently matched nothing.

Signed-off-by: "Claude" <robert@spiraldb.com>
@robert3005 robert3005 changed the title refactor(object-store): share one URL registry across Python, Java and DuckDB refactor(cloud): split cloud object store integration into a vortex-cloud crate Jul 29, 2026

@AdamGS AdamGS left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

Docs:

- Record vortex-io and vortex-cloud in the repository layout, so the
  crate map reflects where object store code now lives.
- Drop a duplicated section from the OpenDAL store page: "Passing a store
  object directly" repeated the CosStore + `store=` example already shown
  under "Reading from COS", down to the same code block.

Manifests:

- vortex-cloud gates `parking_lot` behind `registry` and `tracing` behind
  the service features, the only configurations that use them. A
  featureless build now resolves neither as a direct dependency.
- Drop `dep:vortex-cloud` where a `vortex-cloud/<feature>` entry sits
  beside it; the latter already activates the optional dependency.
- vortex-jni selects the OpenDAL services in its `opendal` feature rather
  than pinning them on the dependency line, matching vortex-python.

The hand-written `py:class` directive for CosStore stays as it is: the
docs build compiles vortex-python without the `opendal` feature, so the
class is absent and `autoclass` cannot resolve it the way the sibling
store pages do.

Signed-off-by: "Claude" <robert@spiraldb.com>
claude and others added 2 commits July 29, 2026 18:01
Two failures on f1b94b1, both mine.

`Rust (docs)`: the crate-level docs linked the `opendal` module and the
registry module linked `crate::opendal`, but that module is feature-gated
on `cos`/`oss`. The workspace docs build enables `registry` (vortex-duckdb
asks for `vortex/object_store_registry`) while leaving the services off, so
both links dangled and `-D warnings` turned
`rustdoc::broken_intra_doc_links` into an error. The overview now names the
two feature-gated items in code spans instead of linking them, which holds
for every feature combination.

`lint-toml`: `taplo.toml` sets `reorder_keys = true` for `dependencies`,
and `vortex-cloud` was inserted positionally rather than alphabetically in
vortex-jni and vortex. Fixed by `taplo fmt`.

Verified with `cargo doc --no-deps --document-private-items` under
`-D warnings` across every vortex-cloud feature combination (none,
registry, cos, oss, cos+oss, all), and for
vortex-cloud/vortex-io/vortex-file/vortex in the registry-on,
services-off combination the docs job actually builds. `taplo fmt --check`
is clean.

DCO Remediation Commit for Claude <noreply@anthropic.com>

I, Claude <noreply@anthropic.com>, hereby add my Signed-off-by to this commit: fa2251f
I, Claude <noreply@anthropic.com>, hereby add my Signed-off-by to this commit: a34da0c
I, Claude <noreply@anthropic.com>, hereby add my Signed-off-by to this commit: f1b94b1

Signed-off-by: Claude <noreply@anthropic.com>
I, Claude <robert@spiraldb.com>, hereby add my Signed-off-by to this commit: c85a848

Signed-off-by: Claude <robert@spiraldb.com>
@robert3005
robert3005 merged commit 070b34e into develop Jul 29, 2026
73 checks passed
@robert3005
robert3005 deleted the claude/opendal-integration-review-1h8f3a branch July 29, 2026 18:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/feature A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants