chore: prepare 0.5.0 release - #68
Merged
Merged
Conversation
Bump the version in pyproject.toml and uv.lock so a release can be cut from main. 0.4.0 was released on 2026-06-05 and main has 11 unreleased commits since, including Lance Namespace support (daft-engine#35), atomic insert_overwrite writes (daft-engine#62), and segmented bitmap indexes (daft-engine#31). Claude-Session: https://claude.ai/code/session_017ZCLiXSVbAbtEV7z8smf8D
rchowell
approved these changes
Sep 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Bump the version to 0.5.0 so a release can be cut from
main. 0.4.0 went out on 2026-06-05 andmainhas 11 unreleased commits since.Same shape as the 0.4.0 prep (deeb798): the version in
pyproject.tomland in thedaft-lanceentry ofuv.lock. No other changes.Note on #66
#66 (pylance floor to >=11.0.0) is deliberately not included. Releasing it together would make this release require a Lance upgrade for anyone on pylance 8–10. This PR keeps
pylance>=8.0.0so the backlog ships on its own, and #66 can go into the next release. Happy to reorder if you would rather have it in 0.5.0.Draft release notes
GitHub's auto-generated notes are just PR titles, and several of these (for example "support segmented bitmap indexes") don't tell a reader what changed for them. Draft below to paste into the Release instead — please edit freely.
Breaking changes
create_scalar_index(..., replace=...)now defaults toFalse— it wasTruein 0.4.0 (Add segmented index workflow for INVERTED and FTS #28). Code that re-ran index creation under the same name to refresh an index, relying on the default, now fails withValueError: Index with name '<name>' already exists. Set replace=True to replace it.Passreplace=Trueexplicitly to keep the old behavior.segmented=Truenow refuses to build under an existing index name (Add segmented index workflow for INVERTED and FTS #28), including withreplace=True. Lance's publiccommit_existing_index_segments(...)exposes no atomic replacement, and 0.4.0 did not check: committing segments under a name that already existed silently left the dataset with two indexes of that name (verified on pylance 8 — the original entry disappeared and two same-named ones took its place). 0.5.0 rejects it up front:ValueError: Index with name '<name>' already exists and cannot atomically replace existing index with Lance's public segmented index API.Drop the index first, or use a different name. Non-segmentedBITMAPwithreplace=Truestill replaces through Lance's scalar path (Segment index bitmap #31). This newly affectsINVERTED/FTScallers: in 0.4.0segmented=Truewas ignored for those types, so the call went down the legacy path, wherereplace=Truedid replace the index. The flag now takes effect, and with it this restriction.pylance>=8.0.0(0.4.0 allowed>=7.0.0). The distributed scalar-index path now uses Lance's public segment-index API, which needs Lance 8 (Add segmented index workflow for INVERTED and FTS #28).Features
New
daft_lance.write_lance(df, ...)(#35) — a module-level write entry point alongsideread_lance, taking a DataFrame plusmode="create" | "append" | "overwrite" | "insert_overwrite", an optional explicit schema, and either a URI or a namespace-addressed table. 0.4.0 exportedread_lanceonly.Lance Namespace support across table operations (#35) — Tables can now be addressed through a Lance Namespace (for example a Gravitino Lance REST server) instead of only by URI.
daft_lance.read_lance,write_lance,LanceDataSink, and the merge / index / compaction operations all accept namespace-addressed tables, resolve them through a shared resolver, and merge namespace-vended storage credentials with caller-supplied options. Distributed workers reopen the dataset from a serializableDatasetOpenContextrather than receiving a liveLanceDataset, so maintenance tasks keep their namespace identity and vended credentials on the worker side.Atomic insert overwrite (#62) —
write_lance(..., mode="insert_overwrite", overwrite_where="<SQL filter>")replaces the rows matching a filter with the DataFrame's rows in a single Lance commit: matching rows are deleted and the new rows added, with no window where a reader sees one without the other. The delete runs as a distributed Daft job and uses scalar-index pruning when the filter plan supports it, so it visits only the fragments that can contain matches. Deletes write deletion files instead of rewriting data, so existing row addresses and indexes stay valid. If nothing matches and no data is written, no new version is created.Segmented index building for
INVERTED,FTS, andBITMAP(#28, #31) —segmented=Truenow builds these index types the wayBTREEalready did: each worker builds a fully independent index segment over its own fragments through Lance's publiccreate_index_uncommitted(...), and the coordinator commits them together withcommit_existing_index_segments(...). In 0.4.0 the flag only took effect forBTREE— passingsegmented=TruewithINVERTEDorFTSwas silently ignored and fell back to the legacy partitioned workflow, andBITMAPalways fell back to single-threaded creation inside Lance. Bitmap segments carry a shard id so independently built segments stay distinguishable before the merge.Two consequences worth knowing: indexes built this way carry real index details, so
describe_indices()works on them (the legacy partitioned path leaves those details empty anddescribe_indices()still raises on indexes it produced); and the legacy path was incompatible with Lance 8 forBTREE, which is why 0.5.0 requirespylance>=8.0.0. See the breaking changes above for thereplacechanges that come with this.Fixes
merge_columns_dffast path delegated to PyLance (#60, closes #59 and #20) — daft-lance had its own implementation of Lance's merge-column write path (file writing, field-ID allocation, fragment metadata, file URI composition), which drifted from Lance itself. Two user-visible bugs came out of that: merges against datasets with deletion vectors could commit a version that could not be read back, and merges on object-store datasets (s3://) crashed because the writer calledos.path.getsize()on a URI. Both are structurally gone now that PyLance'sfragment.merge_columns(...)owns file creation and metadata.Untouched fragments preserved in slow merges (#56) — When
merge_columns_dftook the slow path with input covering only some fragments, the commit succeeded but the new version contained only the touched fragments. Untouched fragments silently disappeared from the manifest, reducing visible row and fragment counts. The slow path now completes the fragment list from the driver snapshot before committing, sharing that logic with the fast path.Correct field IDs for new columns (#46, fixes #41) — The fast-path writer derived the next field ID from top-level schema fields only. Nested fields (struct children) and field IDs left behind by schema evolution have their own IDs, so a new column could be committed with an ID colliding with an existing nested or historical field, and reads of the new column could return nulls or wrong values. The writer now uses the manifest's
max_field_idhigh-water mark.Arrow types preserved for merged columns (#44, fixes #40) — The fast-path writer rebuilt new columns through
pa.array(series.to_pylist()), which drops Arrow type information: Python floats becomefloat64and lists lose their fixed-size constraint. Afixed_size_list<float32>[128]embedding column from a UDF was silently widened tolist<double>and committed with the wrong schema type. The writer now goes throughto_arrow()and keeps the type the UDF declared.Blob reads no longer exhausted by the fast-path check (#45, fixes #42) —
_can_use_fast_pathcounted rows withlen(df.collect()), which populates Daft's result cache. Withtake_blobs()in the pipeline, that cache holds one-shotBlobFilestreams; the subsequent merge reused those exhausted objects, so downstream UDFs callingblob.read()got empty bytes and produced nulls or wrong output. The check now usescount_rows(), which leaves the cache unset.Internal
uv.lockexcluded from thepretty-format-tomlpre-commit hook, which had been failing the style job on every PR (chore: exclude uv.lock from pretty-format-toml hook #48, fixes CI style job fails on every PR: uv.lock conflicts with pretty-format-toml hook #47)segmented=Falsehonored again increate_scalar_index(fix: respect segmented scalar index flag #49). No effect for anyone upgrading from 0.4.0: this fixes a regression introduced by Add segmented index workflow for INVERTED and FTS #28, and both landed after 0.4.0 shipped.https://claude.ai/code/session_017ZCLiXSVbAbtEV7z8smf8D