💥 Breaking changes
- Disallow mixed string <=> numeric arithmetic in eager (#30071)
🚀 Performance improvements
- Limit the memory of buckets sorted ahead in the sort flush (#30297)
- Make file readers wait for spills while over the memory budget (#30296)
- Count unspilled frames in the memory estimate right away (#30295)
- Simplify more boolean expressions and drop redundant null checks (#30293)
- Wait for spills while over the out-of-core memory budget (#30294)
- Lower the default out-of-core prefetch fraction to 0.5 (#30292)
- Aggregate unique and n_unique in the streaming group-by itself (#30281)
- Return freed memory to the OS right away while out-of-core data is spilled (#30280)
- Gather
List(Array)andArraycolumns without per-row slices (#30278) - Evaluate the streaming window node out of core (#30282)
- Avoid deep copies when building a DataFrame from flat dataclasses (#30275)
- Sort the window partitions by each order key instead of their row encoding (#30277)
- Scan the haystack for a single needle in
is_in(#30276) - Prefer fixed-width columns when only the row count is needed (#30274)
- Native gather for List arrays (#30019)
- Evaluate windows in filter predicates in the streaming window node (#30272)
- Don't busy-wait in RAYON.block_on (#30259)
- Encode nested Parquet levels directly from the level buffers (#30251)
- Reduce memory of CSV scans on wide rows (#30165)
- Avoid quadratic JSON serialization of nested lists and maps (#30203)
- Skip the gather when exploding lists without nulls (#30234)
- Speed up writing nested Parquet columns (#30245)
- Don't keep whole parquet string dictionaries alive for a few selected rows (#30248)
- Set default spill compression level to 1 (#30237)
- Reuse zstd contexts in compression and avoid memcpys (#30233)
- Remove repeated and scalar ordering keys before the sort rewrites (#30231)
- Remove repeated keys from by's (#30095)
- Keep only one guessed unique key per join order leaf (#30191)
- Don't let extra join keys shrink an estimate once one key is unique (#30190)
- Reduce binary size of asof join with
bykeys (#30192) - Reduce binary size of SQL visitors and parallel group-by collect (#30171)
- Push as-of join key filters to the right input (#30123)
- Drop null as-of right keys with a parallel filter before the join node (#30125)
- Keep as-of nearest join state small (#30126)
- Explode lists in group_by aggregations without a per-group apply (#30020)
- Streaming OOC group-by with shuffles (#29911)
- Drop scalar ordering keys in the optimizer (#30063)
- Merge union inputs that scan files of the same kind into one multi-file scan (#30056)
- Use the streaming scalar window for std, var and combined reductions in
over(#30066) - Limit the bytes of local CSV and NDJSON files read at once in multi-file scans (#30055)
- Check the matched flag of join keys in the table entry before the bitset (#30067)
- Track matched build rows of all join tables in a bitset (#30050)
- Add dedicated list gather kernels (#30049)
- Emit unmatched build rows of single-key joins in row order (#30037)
- Use
TopKfor sorts sliced at a nonzero offset (#29989) - Add constant folding for more datetime ops (#30016)
- Rewrite the sum of a column plus a literal to a sum and a count of the column (#30011)
- Push predicates through order-keeping columns (#30007)
- Fix dead early exit in
coalesce(#30013) - Count distinct non-null values in SQL with one group-by in the streaming engine (#30009)
- Skip parquet row groups with the running top-k threshold (#30006)
- Keep decompressed plain string pages as the string data buffer (#29984)
- Fix some in-memory filter regressions (#29988)
- Decompress snappy and zstd parquet pages without zeroing the buffer (#29980)
- Speed up
str.len_chars(#29972) - Speed up
str.replacefor patterns that end in.*$(#29971) - Use the Parquet string prefilter for literal
str.containsand SQLLIKE(#29953) - Keep single-chunk output when filtering wide frames in parallel (#29847)
- Add dedicated list slice kernels (#29938)
- Principled execution order of accumulating streaming nodes (#29933)
- Huge speedup creating wide
Structdata (#29813) - Speed up
arg_minandarg_max(especially on data with nulls) (#29849) - Huge reduction in
from_rowsandfrom_dictsmemory usage during nested value conversion (#29739) - Safely push down nested struct field filters to Iceberg (#29665)
- Compare Parquet dictionaries of Categorical and Enum columns as strings for
==andis_in(#29885) - Filter Categorical and Enum Parquet columns while decoding (#29882)
- Rewrite
sort().first()tomin()and single-keysort_by().first()tomin_by()(#29860) - Decode fixed-length binary Parquet decimals directly into i128 (#29866)
- Look up each dictionary value once when importing Arrow dictionaries as Categorical (#29865)
- Reduce requests and raise concurrency for cloud CSV scans (#29863)
- Reduce requests and raise concurrency for cloud NDJSON scans (#29857)
- Track sortedness through joins (#29852)
- Keep sortedness through widening numeric casts (#29851)
- Rewrite
sort_by().first()tomin_byand limit sorts read withhead()(#29831) - Cache parsed Iceberg metadata for metadata-path scans (#29791)
- Drop memory when Done in streaming asof_join/is_first_distinct (#29783)
- Drop scalar
sort_bykeys in the optimizer (#29812) - Parallel CSV chunk analysis and pread default (#29818)
- Hash inline binview values directly from their view (#29820)
- Use iceberg statistics (#29798)
- Reuse Iceberg catalog and cache REST catalog scans (#29790)
- Preserve sortedness of computed join keys (#29782)
- TopK was holding onto too much data (#29784)
- Write CSV temporal values straight into the buffer (#29778)
- Parse strftime format once and reuse buffer in regex replace (#29777)
- Reduce needless allocations in various kernels (#29775)
- Cache http object store (#29757)
- Fetch each record batch group in its own task (#29755)
✨ Enhancements
- Support SQL
CREATE TABLEwith column definitions in attached Iceberg catalogs (#30260) - Support SQL
CREATE [OR REPLACE] TEMP TABLE(#30256) - Support recursive SQL CTEs (#30188)
- Writing encrypted Parquet files (#29905)
- Support SQL
DROP TABLE [PURGE]on tables of attached catalogs (#30247) - Add conversion to/from masked numpy arrays (#30232)
- Add
overwrite_filterand dynamic partition overwrite tosink_iceberg(#29909) - Support SQL
JOIN ... ONsubqueries that read only one input (#30206) - Support full joins in join_where (#30169)
- Expose fused and nested loop join predicates in the IR visitor (#30176)
- Support LIMIT 1 in correlated SQL scalar subqueries (#30168)
- Support SQL
WITH RECURSIVEwhen no CTE reads itself (#30166) - Support correlated IN and EXISTS that read both inputs of a SQL join in ON (#30159)
- Support
CURRENT_DATE,CURRENT_TIMEandCURRENT_TIMESTAMPin SQL (#30144) - Allow
reinterpretbetweenInt128andDecimal(#30133) - Add
Expr.truncdivfor division rounding toward zero (#30138) - Support uncorrelated subqueries in SQL JOIN ... ON (#30141)
- Support correlated subqueries in SQL
JOIN ... ONand correlated scalar subqueries without an aggregate (#30131) - Support sinking to Iceberg table with bucket partitioned field (#30091)
- Add
create_namespace_if_not_existstosink_iceberg(#30087) - Attach Iceberg catalogs to
SQLContextviaregister_catalog(#30113) - Support OOC kill fraction (#30104)
- Support qualified table names in SQL (#29963)
- Reverse node for streaming engine (#29259)
- Add
pl.delete_iceberg(#30032) - Support named options in SQL
read_ipctable function (#30054) - Support named options in SQL
read_parquettable function (#29998) - Add experimental geoparquet read/write (#29987)
- Accept a list of paths in SQL read_* table functions (#30047)
- Add SQL
read_ndjsontable function with named options (#29954) - Support STRUCT and MAP type names in SQL (#29951)
- Support unstable column/schema options in SQL
read_csv(#29975) - Add sampling to KLL approx-quantile sketch (#29424)
- Rust resolver for scan_iceberg (#29753)
- Add
REGEXP_REPLACEandDATE_TRUNCto SQL (#29967) - Add
generate_seriesandrangeto SQL (#29912) - Add
create_if_not_existstosink_iceberg(#29908) - Support named options in SQL
read_csvtable function (#29917) - Add query-level IO active time and thread count metrics (#29792)
- Support time-zone-aware timestamps in SQL (#29899)
- Add support for reading encrypted Parquet files (#29606)
- Add SQL ASOF join (#29834)
🐞 Bug fixes
- Compare
Array(Struct)rows field by field (#30290) - Don't revive a dropped out-of-core spill context through a weak reference (#30291)
- Do not read the values stored under null
Arrayrows (#30286) - Return freed memory to the OS right away while out-of-core data is spilled (#30280)
- Propagate nulls through all nesting levels before row encoding (#30269)
- Add a method to merge finalized
approx_quantilesketches deterministically (#30263) - Use the supertype of both inputs for the
cov/corroutput dtype (#30262) - Keep buffered frames spillable while a streaming sort partitions (#30268)
- Respect memory limits of nested cgroups (#30266)
- Correct schema for integer literal divided by
Float16/Float32(#30158) - Correct
str.replacefor nested repetitions before.*$(#30258) - Allow
write_iceberginto Iceberg tables with required columns (#30253) - Correct operator overloading for reflect ops (#29946)
- Raise on unsupported Avro codecs in
read_avro(#30240) - Support several inputs to anonymous aggregations in the in-memory engine (#30152)
- Stop SQL
FROMitem aliases from acting as tables (#30207) - Correct unsigned parquet stats merging (#30199)
- Read Parquet schema in lockstep with arrow metadata hints (#30202)
- Raise on Delta tables with column mapping (#30204)
- Correct comparison with numpy array (#30241)
- Don't keep whole parquet string dictionaries alive for a few selected rows (#30248)
- Raise on SQL
CREATE TABLEof a name in the default catalog (#30235) - Widen truncated timestamp max statistics in
scan_deltafile skipping (#30194) - Fix wrong JSON output for
StructandArrayinside list (#30200) - Correct schema for
Float16/Float32divided byDecimal(#30093) - Pass every input of an anonymous aggregation to its streaming reduction (#30151)
- Support Array, nested List and Null in
is_first_distinctandis_last_distinct(#30162) - Refine
register_catalogforIF EXISTS, re-registration and scopes (#30160) - Support duckdb 2.0 in CI (#30161)
- Fix reading uncompressed buffers in compressed IPC files (#30084)
- Track SQL column names of every relation in a joined FROM item (#30153)
- Evaluate uncorrelated SQL EXISTS once and don't push len() into sliced unions (#30150)
- Treat a qualifier as the outer relation unless the subquery has a struct column of that name (#30149)
- Revert dropped offset check for bitmap into_mut (#30132)
- SQL EXISTS over aggregates and scope after semi/anti joins (#30146)
- Resolve SQL RIGHT SEMI and RIGHT ANTI join conditions against the right tables (#30135)
- Assign SQL join keys to the input holding the relation they name (#30137)
- Give correlated SQL aggregate expressions their value over no rows (#30130)
- Clear sorted flag on temporal expression inserting nulls (#30110)
- Ignore null right keys in asof join (#30122)
- Preserve order of sliced maintain-order group_by (#30096)
- Make ternary schema agree with type coercion (#29940)
- Raise error instead of panic when a sink is used as input of another operation (#29945)
- Reject
Stringoperands before float arms in true division type resolution (#30018) - Ensure we don't allow scan+sink on same file (#30065)
- Correct rolling window when offset is not negation of period (#30060)
- Disallow mixed string <=> numeric arithmetic in eager (#30071)
- Preserve temporal sorted flag only when allowed (#30058)
- Decimal cast to int should truncate (#30069)
- Casting far future dates shouldn't silently wrap (#30068)
- Correctly parse timestamps with offsets in JSON (#30064)
- Use the correct input schema for join expressions in the expression optimizer (#30053)
- Sliced list writes in Avro (#30061)
- Raise on String operands in eager Series true division (#30021)
- Only infer CSV/NDJSON schema from complete rows on progressive download (#29961)
- Check SQL casts to STRUCT[]/MAP[] and specify cast failure (#30026)
- Allow empty groups in multi key sort_by (#29825)
- Fix reading Parquet files with empty row groups (#30001)
- Check the last 4 bytes of inlined strings for invalid UTF-8 in parquet (#29981)
- Fix
shiftwithfill_valuereporting the wrong lazy schema (#29978) - Correct schema supertype for mixed bool <> unknown float ops (#29947)
- Sane output for full-key rows_by_key (#29943)
- More numerically stable ewm_std/var (#29942)
- Expand
$references instr.replacefor patterns without punctuation (#29966) - Fix wrong results and name clashes for computed SQL
GROUP BYkeys (#29965) - Clear null propagated flag when setting outer validity on struct (#29949)
- Accept Mapping types not just dicts where advertised (#29952)
- Check moment agg dtype support during planning (#29955)
- Fix panic in when/then that masks a multi-chunk struct column (#29804)
- Don't extract scalar common subexpressions in group-by aggregations (#29941)
- Keep key dtypes when moving predicates across join keys (#29936)
- Use upcast key dtype in schema of coalescing full join (#29931)
- Return null instead of raising for a null scalar offset in
list.slice(#29932) - Raise error instead of panicking on out-of-range datetimes in
to_list(#29918) - Don't drop filter bounds whose literal has another dtype than the column (#29916)
- Handle CSPE in in-mem projection (#29914)
- Don't wait for ooc cleanup in forked child (#29900)
- Resolve WITH clauses inside SQL derived tables and nested queries (#29897)
- Fix incorrect filter expression rewrites (#29887)
- Lost wakeup in async executor when waking an already-scheduled task (#29824)
- Resolve SQL subquery columns in their own scope first (#29889)
- Don't push filters below a select of only scalars (#29837)
- Write valid Parquet statistics and column index for NaN and nested values (#29868)
- Clamp the mask in
shift_and_fillfor inputs shorter thann(#29840) - Cast the inner values by their type in Array and List casts (#29864)
- Support empty NDJSON files read through
file://paths (#29862) - Support SQL
INwith a date against datetimes and with nested values of other types (#29861) - Support a string literal on the left of SQL
BETWEENand minus with a temporal value (#29856) - Track sortedness through joins (#29852)
- Apply SQL
SELECT *options to the wildcard columns only (#29854) - Fix row-estimation panic for aggregations over empty scan (#29850)
- Give Iceberg scans statistics before the cache and SQL subquery decisions (#29836)
- Infer Float16 from Parquet FLOAT16 logical type without Arrow schema (#29832)
- Support SQL
HAVINGwithoutGROUP BYand check columns outside aggregates (#29833) - Raise on oob gather indices (#29827)
- Call
statxvia raw syscall so wheels load on glibc < 2.28 (#29828) - Broadcast scalar inputs in
min_by/max_byandsort_by(#29808) - Fix wrong results in SQL REPLACE and ARRAY_AGG, join_where on decimals and sorted group-by keys (#29823)
- Support a string literal on the left of a SQL comparison with a temporal column (#29814)
- Livelock in TaskParker on very low thread counts (#29819)
- Compare floats with integers and decimals in SQL
IN(#29810) - Fix SQL ANY/ALL comparisons with a subquery (#29802)
- Fix SQL FILTER clauses that contain a subquery (#29794)
- Don't push filters into a cache past a projection (#29793)
- Phase logic error in merge_sorted (#29781)
- Incorrect length error in streaming group-by item() (#29787)
- NegativeSlice could hold onto more data than necessary (#29779)
- Fix small bugs in SQL aggregates and in group contexts (#29754)
- Fix
clipnot broadcasting length-1 input against full-length bounds (#29666) - Fix scan_lance CancelledError (#29774)
- Fix cross join filters on Decimals of different scales (#29776)
- Use correct bucket for CSV and NDJSON cloud scans (#29772)
- Re-entrance in PyO3 PolarsAllocator (#29763)
📖 Documentation
- List extra required deps for make pre-commit (#30145)
- Target the stub with
loc.stubin the styling example (#29996) - Migrate to Zensical (#29426)
📦 Build system
- Allow choosing the Python interpreter for the dev venv with
PYTHON(#30142) - Reduce binary size of SQL visitors and parallel group-by collect (#30171)
- Drop support for Python 3.10 and test on Python 3.15 (#29888)
🛠️ Other improvements
- Fix allocator-dependent failure in masked zero-copy
to_numpytest (#30298) - Simplify the out-of-core decay state and tests (#30283)
- Bump the clipboard-win dependency (#30264)
- Rename polars_cloud package (#30099)
- Fix Excel pyrefly lint (#30201)
- Reduce binary size of SQL visitors and parallel group-by collect (#30171)
- Sort unordered QUALIFY test query (#30167)
- Support duckdb 2.0 in CI (#30161)
- Mark large multi-column key streaming tests as slow (#30143)
- Bump
webpki-rootsandwebpki-root-certsto 1.0.10 (RUSTSEC-2026-0383) (#30129) - Don't make your private keys XKCD-221 compliant (#30115)
- Mark test as slow (#30086)
- Remove xdist_group "streaming" to speed up test suite by 50s (#30057)
- Replace the
homecrate withstd::env::home_dir(#30039) - Add polars-iceberg to Cargo.lock in Rust lint CI (#30033)
- Remove stale
polars-io-extentry from Cargo.lock (#30025) - Move SQL
read_csvoption tests from Rust to Python (#29983) - Run polars-stream unit tests in the Rust test workflow (#29703)
- Fail fast on dead cloud endpoints and re-enable Windows (#29621)
- Make
ExprIR::is_order_insensitivepublic (#29994) - Update rustls to 0.23.46 (#29944)
- A few additions to
.gitignore(#29633) - Fix flaky HTTP store cache tests (#29929)
- bump simd-json (#29838)
- Improve OOC out-of-disk error (#29761)
Thank you to all our contributors for making this release possible!
@Aidavdw, @EndPositive, @FalkWoldmann, @Hoeze, @JasonLi314, @Jesse-Bakker, @Kevin-Patyk, @TiansuYu, @adamreeve, @alexander-beedie, @borchero, @carnarez, @dancsi, @dominikandreasseitz, @dsprenkels, @fsimkovic, @jqnatividad, @jvdd, @kdn36, @lun3x, @mroeschke, @nameexhaustion, @orlp, @ritchie46, @tahakotil and @useredsa