From 4a114f3e3b145285c010bfaef3de17fe855769f2 Mon Sep 17 00:00:00 2001 From: Matt Butrovich Date: Mon, 8 Jun 2026 15:12:35 -0400 Subject: [PATCH 1/7] Blog post for DataFusion 54.0.0 --- content/blog/2026-06-08-datafusion-54.0.0.md | 358 ++++++++++++++++++ .../performance_over_time_clickbench.png | Bin 0 -> 64999 bytes 2 files changed, 358 insertions(+) create mode 100644 content/blog/2026-06-08-datafusion-54.0.0.md create mode 100644 content/images/datafusion-54.0.0/performance_over_time_clickbench.png diff --git a/content/blog/2026-06-08-datafusion-54.0.0.md b/content/blog/2026-06-08-datafusion-54.0.0.md new file mode 100644 index 00000000..7cab434c --- /dev/null +++ b/content/blog/2026-06-08-datafusion-54.0.0.md @@ -0,0 +1,358 @@ +--- +layout: post +title: Apache DataFusion 54.0.0 Released +date: 2026-06-08 +author: pmc +categories: [release] +--- + + + +[TOC] + +We are proud to announce the release of [DataFusion 54.0.0]. This post highlights +some of the major improvements since [DataFusion 53.0.0]. The complete list of +changes is available in the [changelog]. Thanks to the [139 contributors] for +making this release possible. + +[DataFusion 54.0.0]: https://crates.io/crates/datafusion/54.0.0 +[DataFusion 53.0.0]: https://datafusion.apache.org/blog/2026/04/02/datafusion-53.0.0/ +[changelog]: https://github.com/apache/datafusion/blob/branch-54/dev/changelog/54.0.0.md +[139 contributors]: https://github.com/apache/datafusion/blob/branch-54/dev/changelog/54.0.0.md#credits + +## Performance Improvements 🚀 + + + +**Figure 1**: Average and median normalized execution times for DataFusion 54.0.0 on ClickBench queries, compared to previous releases. +Query times are normalized using the ClickBench definition. See the +[DataFusion Benchmarking Page](https://alamb.github.io/datafusion-benchmarking/) +for more details. + +We continue to make significant performance improvements in DataFusion, as +explained below. This release simplifies more predicates before execution, +prunes more redundant work out of plans, and makes joins, repartitioning, and +many built-in functions faster. + +### Pruning Functionally-Redundant Sort Keys + +Sorting is expensive, so it pays to sort by as few columns as possible. +DataFusion 54 now drops sort keys from `ORDER BY` that are functionally +redundant: when an earlier key functionally determines a later one, the later key +cannot change the ordering, so removing it reduces comparison and sorting cost +without affecting results. + +Thanks to [@xiedeyantu] for implementing this feature, with reviews from +[@alamb] and [@neilconway]. Related PRs: [#21362] + +### Statistics-Driven Sort Pushdown and TopK + +This release continues the sort pushdown work begun in earlier releases. Sort +pushdown phase 2 sorts file groups by statistics so that ordered scans can be +satisfied with less work, and DataFusion can now globally reorder files and row +groups by statistics for TopK (`ORDER BY ... LIMIT`) queries. This lets the most +promising data be read first, often satisfying the `LIMIT` before scanning the +rest. + +Thanks to [@zhuqi-lucas] for driving this work, with reviews from [@adriangb]. +Related PRs: [#21182], [#21426], [#21956] + +### Physical Execution of Uncorrelated Scalar Subqueries + +Previously, DataFusion executed an uncorrelated scalar subquery (one that does +not depend on the outer query) by rewriting it into a join. DataFusion 54 +instead executes such subqueries directly with a new physical operator, +evaluating each subquery once, with subqueries at the same query level running in +parallel. This unlocks several improvements. Functions can now use their +specialized scalar code paths, which makes some queries dramatically faster; in +one case execution drops from roughly 800 ms to 30 ms. Uncorrelated scalar +subqueries also now work in `ORDER BY`, `JOIN ON`, and as arguments to aggregate +functions. Finally, a subquery that returns more than one row now raises a +runtime error instead of silently producing incorrect results. + +Thanks to [@neilconway] for implementing this feature, with reviews from +[@Dandandan], [@alamb], and [@timsaucer]. Related PRs: [#21240] + +### Faster Sort-Merge Joins + +Sort-merge joins (SMJ) saw substantial work this cycle. Semi, anti, and mark +joins now run on a specialized stream that tracks matches with a per-row bitset +instead of materializing `(outer, inner)` row pairs. For outer joins, batched +deferred filtering makes near-unique `LEFT` and `FULL` joins 20-50x faster. +Finally, join-key comparisons now use a `DynComparator`, which resolves the +column type once instead of on every row, making microbenchmark queries up to +12% faster and TPC-H roughly 5% faster overall. + +Thanks to [@mbutrovich] for this work, with reviews from [@Dandandan], +[@comphead], and [@rluvaton]. Related PRs: [#20806], [#21184], [#21484], [#21517] + +### Faster Repartitioning + +`RepartitionExec` now coalesces batches before sending them to distributor +channels, reducing per-batch overhead in plans that repartition data. + +Thanks to [@gabotechs] for this work, with reviews from [@Dandandan] and +[@alamb]. Related PRs: [#22010] + +### Faster Functions and Hashing + +DataFusion ships hundreds of built-in functions, so making them faster pays off +across nearly every workload. This release optimizes a large number of them, +including [`array_to_string`], [`array_concat`], [`array_sort`], [`split_part`], +[`substr`], [`strpos`], [`left`], [`right`], [`string_agg`], and +[`approx_distinct`], along with better `NULL` handling across many array and +datetime functions. It also replaces `ahash` with [`foldhash`] for faster +hashing in `datafusion-common`, and optimizes [`regexp_replace`] by stripping +trailing `.*` from anchored patterns, which yields a 2.4x improvement on +ClickBench Q28. + +Thanks to the many contributors who drove this work, especially [@neilconway], +[@Dandandan], [@zhangxffff], [@lyne7-sc], [@CuteChuanChuan], [@kumarUjjawal], and +[@coderfender]. + +## New Features ✨ + +### `LATERAL` Joins + +Lateral joins have been a long-requested feature ([#10048]). DataFusion 54 adds +basic support for `CROSS JOIN LATERAL`, `INNER JOIN LATERAL`, and `LEFT JOIN +LATERAL` ([#21202], [#21352]). A lateral subquery in the `FROM` clause can +reference columns from preceding tables, which is handy for patterns such as +expanding a per-row series or correlating against a set-returning function. The +implementation uses decorrelation, so the subquery is evaluated once rather than +re-executed for every outer row. + +```sql +SELECT t1_id, t1_name, i +FROM join_t1 t1 +CROSS JOIN LATERAL ( + SELECT * FROM unnest(generate_series(1, t1_int)) +) AS series(i); +``` + +Thanks to [@neilconway] for implementing this feature, with reviews from +[@Dandandan], [@alamb], and [@crm26]. + +### Lambda Functions + +DataFusion now supports lambda expressions (`v -> expr`), including lambda column +capture, along with new higher-order array UDFs such as [`array_transform`], +`array_filter`, and `array_any_match` ([#21323], [#21679]). Lambdas make it +possible to express per-element transformations directly in SQL: + +```sql +SELECT array_transform(array_filter([1, 2, 3, 4, 5], v -> v > 2), v -> v * 10); +-- [30, 40, 50] +``` + +Thanks to [@gstvg] and [@rluvaton] for leading this effort, with reviews from +[@comphead] and [@martin-g]. + +### New Avro Reader + +The Avro reader has been migrated to the `arrow-avro` crate ([#17861]), removing +internal conversion code in favor of a faster, better maintained implementation +shared with the broader Arrow ecosystem. Thanks to [@getChan] for this work, +with reviews from [@adriangb], [@alamb], and [@jecsand838]. + +### Extension Type Registry + +Arrow extension types let users layer their own semantics on top of a physical +storage type. DataFusion 54 introduces an extension type registry so that +behavior can be registered for them ([#18223], [#20312]), adds several more +canonical extension types ([#21291]), and allows logical expressions to cast to +an extension type ([#18136]). Thanks to [@tschwarzinger] and [@paleolimbot] for +driving this work, with reviews from [@adriangb] and [@cetra3]. + +### Parquet Content-Defined Chunking + +DataFusion's Parquet writer can now be configured to use content-defined +chunking (CDC) ([#21110]), which aligns data page boundaries with the data +itself rather than with fixed row counts. This improves deduplication and +incremental storage, since inserting or editing a few rows no longer shifts +every subsequent page boundary. Thanks to [@kszucs] for this feature, with +reviews from [@alamb]. + +### New SQL and Scalar Functions + +This release adds new scalar functions such as [`array_compact`], +[`cosine_distance`], [`cast_to_type`], and [`with_metadata`], nanosecond +`date_part` support, and the `:` JSON access operator. Thanks to [@comphead], +[@crm26], [@adriangb], [@mhilton], and [@Samyak2] for these contributions. + +### Spark-Compatible Functions + +This release adds many new or improved Spark-compatible functions and behaviors +in the [datafusion-spark crate], including `round`, `floor`, `ceil`, `soundex`, +`xxhash64`, `array_contains`, `array_compact`, Spark-compatible +int/float-to-timestamp casts, and UTF-8 validation functions. Thanks to the +contributors who drove this work, especially [@comphead], [@coderfender], +[@SubhamSinghal], [@kazantsev-maksim], [@andygrove], [@buraksenn], and +[@davidlghellin]. + +## Statistics and Cardinality Estimation + +Good plans depend on good statistics, and this release includes a substantial +body of work in that area. Highlights include extracting NDV (number of distinct +values) statistics from Parquet metadata, using NDV for equality-filter +selectivity, a pluggable `StatisticsRegistry` for operator-level statistics +propagation, and better cardinality estimation for semi and anti joins. Together +these help the optimizer make better choices. + +Thanks to [@asolimando], [@jonathanc-n], and [@buraksenn] for driving this work. +Related PRs: [#19957], [#20789], [#21077], [#21081], [#21483], [#20904] + +## Upgrade Guide and Changelog + +Upgrading to 54.0.0 should be straightforward for most users, though this +release does include some breaking changes, such as the removal of `as_any` from +several trait definitions, changes to the pruning and statistics APIs, the new +`Operator::Colon` for JSON access, and the switch from `ahash` to `foldhash`. +Please review the [Upgrade Guide] for details on breaking changes and code +snippets to help with the transition. For a comprehensive list of all changes, +please refer to the [changelog]. + +## About DataFusion + +[Apache DataFusion] is an extensible query engine, written in [Rust], that uses +[Apache Arrow] as its in-memory format. DataFusion is used by developers to +create new, fast, data-centric systems such as databases, dataframe libraries, +and machine learning and streaming applications. While [DataFusion's primary +design goal] is to accelerate the creation of other data-centric systems, it +provides a reasonable experience directly out of the box as a [dataframe +library], [Python library], and [command-line SQL tool]. + +DataFusion's core thesis is that, as a community, together we can build much +more advanced technology than any of us as individuals or companies could build +alone. Without DataFusion, highly performant vectorized query engines would +remain the domain of a few large companies and world-class research +institutions. With DataFusion, we can all build on top of a shared foundation +and focus on what makes our projects unique. + +## How to Get Involved + +DataFusion is not a project built or driven by a single person, company, or +foundation. Rather, our community of users and contributors works together to +build a shared technology that none of us could have built alone. + +If you are interested in joining us, we would love to have you. You can try out +DataFusion on some of your own data and projects and let us know how it goes, +contribute suggestions, documentation, bug reports, or a PR with documentation, +tests, or code. A list of open issues suitable for beginners is [here], and you +can find out how to reach us on the [communication doc]. + +[Apache DataFusion]: https://datafusion.apache.org/ +[Rust]: https://www.rust-lang.org/ +[Apache Arrow]: https://arrow.apache.org +[DataFusion's primary design goal]: https://datafusion.apache.org/user-guide/introduction.html#project-goals +[dataframe library]: https://datafusion.apache.org/user-guide/dataframe.html +[Python library]: https://datafusion.apache.org/python/ +[command-line SQL tool]: https://datafusion.apache.org/user-guide/cli/ +[Upgrade Guide]: https://datafusion.apache.org/library-user-guide/upgrading.html +[here]: https://github.com/apache/arrow-datafusion/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22 +[communication doc]: https://datafusion.apache.org/contributor-guide/communication.html + +[@xiedeyantu]: https://github.com/xiedeyantu +[@zhuqi-lucas]: https://github.com/zhuqi-lucas +[@neilconway]: https://github.com/neilconway +[@mbutrovich]: https://github.com/mbutrovich +[@gabotechs]: https://github.com/gabotechs +[@Dandandan]: https://github.com/Dandandan +[@zhangxffff]: https://github.com/zhangxffff +[@lyne7-sc]: https://github.com/lyne7-sc +[@CuteChuanChuan]: https://github.com/CuteChuanChuan +[@kumarUjjawal]: https://github.com/kumarUjjawal +[@coderfender]: https://github.com/coderfender +[@gstvg]: https://github.com/gstvg +[@rluvaton]: https://github.com/rluvaton +[@getChan]: https://github.com/getChan +[@tschwarzinger]: https://github.com/tschwarzinger +[@paleolimbot]: https://github.com/paleolimbot +[@kszucs]: https://github.com/kszucs +[@comphead]: https://github.com/comphead +[@crm26]: https://github.com/crm26 +[@adriangb]: https://github.com/adriangb +[@mhilton]: https://github.com/mhilton +[@Samyak2]: https://github.com/Samyak2 +[@SubhamSinghal]: https://github.com/SubhamSinghal +[@kazantsev-maksim]: https://github.com/kazantsev-maksim +[@andygrove]: https://github.com/andygrove +[@buraksenn]: https://github.com/buraksenn +[@davidlghellin]: https://github.com/davidlghellin +[@asolimando]: https://github.com/asolimando +[@jonathanc-n]: https://github.com/jonathanc-n +[@alamb]: https://github.com/alamb +[@timsaucer]: https://github.com/timsaucer +[@martin-g]: https://github.com/martin-g +[@jecsand838]: https://github.com/jecsand838 +[@cetra3]: https://github.com/cetra3 + +[`array_to_string`]: https://github.com/apache/datafusion/pull/20639 +[`array_concat`]: https://github.com/apache/datafusion/pull/20620 +[`array_sort`]: https://github.com/apache/datafusion/pull/21083 +[`split_part`]: https://github.com/apache/datafusion/pull/21119 +[`substr`]: https://github.com/apache/datafusion/pull/21366 +[`strpos`]: https://github.com/apache/datafusion/pull/20754 +[`left`]: https://github.com/apache/datafusion/pull/21442 +[`right`]: https://github.com/apache/datafusion/pull/21442 +[`string_agg`]: https://github.com/apache/datafusion/pull/21154 +[`approx_distinct`]: https://github.com/apache/datafusion/pull/21037 +[`foldhash`]: https://github.com/apache/datafusion/pull/20958 +[`regexp_replace`]: https://github.com/apache/datafusion/pull/21379 +[`array_transform`]: https://github.com/apache/datafusion/pull/21679 +[`array_compact`]: https://github.com/apache/datafusion/pull/21522 +[`cosine_distance`]: https://github.com/apache/datafusion/pull/21542 +[`cast_to_type`]: https://github.com/apache/datafusion/pull/21322 +[`with_metadata`]: https://github.com/apache/datafusion/pull/21509 + +[#17861]: https://github.com/apache/datafusion/pull/17861 +[#10048]: https://github.com/apache/datafusion/issues/10048 +[#18223]: https://github.com/apache/datafusion/issues/18223 +[#18136]: https://github.com/apache/datafusion/pull/18136 +[#20312]: https://github.com/apache/datafusion/pull/20312 +[#20789]: https://github.com/apache/datafusion/pull/20789 +[#20806]: https://github.com/apache/datafusion/pull/20806 +[#20904]: https://github.com/apache/datafusion/pull/20904 +[#19957]: https://github.com/apache/datafusion/pull/19957 +[#21077]: https://github.com/apache/datafusion/pull/21077 +[#21081]: https://github.com/apache/datafusion/pull/21081 +[#21110]: https://github.com/apache/datafusion/pull/21110 +[#21182]: https://github.com/apache/datafusion/pull/21182 +[#21184]: https://github.com/apache/datafusion/pull/21184 +[#21202]: https://github.com/apache/datafusion/pull/21202 +[#21240]: https://github.com/apache/datafusion/pull/21240 +[#21291]: https://github.com/apache/datafusion/pull/21291 +[#21323]: https://github.com/apache/datafusion/pull/21323 +[#21352]: https://github.com/apache/datafusion/pull/21352 +[#21362]: https://github.com/apache/datafusion/pull/21362 +[#21426]: https://github.com/apache/datafusion/pull/21426 +[#21483]: https://github.com/apache/datafusion/pull/21483 +[#21484]: https://github.com/apache/datafusion/pull/21484 +[#21517]: https://github.com/apache/datafusion/pull/21517 +[#21679]: https://github.com/apache/datafusion/pull/21679 +[#21956]: https://github.com/apache/datafusion/pull/21956 +[#22010]: https://github.com/apache/datafusion/pull/22010 + +[datafusion-spark crate]: https://docs.rs/datafusion-spark/latest/datafusion_spark/index.html diff --git a/content/images/datafusion-54.0.0/performance_over_time_clickbench.png b/content/images/datafusion-54.0.0/performance_over_time_clickbench.png new file mode 100644 index 0000000000000000000000000000000000000000..d2f039fd950dfcba8875a8c365f61de422db9f95 GIT binary patch literal 64999 zcmdqJ^D{{hje#`fD%$7Il$0Lmy|Tp-9v{cNOun<9YZ$^@jZC& zeZB5|-M61V;QIp)X6Br;&)#eAv-Wd6*K?g<6(wmbOkzv~1OzNu83{E61Qc8Z1jJ)> zRNxm$RK$D0KSW10X>o+ofv20m7l}7IvgV442#mmIbOgjeD+J`bQ-D8W;12=eUIrq< zuZ!+}%Ru_;C<<=Iy}v#q9^ak#BQLWI0YMZ&Rzgg}4RJda?X$Y%%tvE)ZB;xHMO8d2 zOiI(+hZ@`T+!DuCCSuG!D2?Y}%uJmM|I8^|53y|&WE+b7d>=wQCW_`;ka_B=&D1vS zG<9-Jb0E{oN(#f?tmB%^TDJhl>V&i(9t8p-x+nq?t`7q8|9m7A&+vS<&WrN90e#JjX^*%Rp2!EZR@zW^=SXo;lHj$ z@coR8i))s-i1fch`s)lIpDv-_j=P=O$LBEt7SZooM?{v`{jE(XXc3XyKU>!Qwsi`M z_c(ub<6kGSV^C21kob=Bn;8*BK#cbIKj-SM#UNP(M0Q$Y*59^{9yELJcatHifP@~z zKKu^x|5^!mEdodE8!i_Bq@Wwkl- z2~a4+xgQX6wRxtruvjy*kX5rf_$4>LcotjWO z28Si;jMH>3>-fgoLhUj#i^A=Qn)j=6>dlNA`L9-cEf)4Bx2y+6ZkxbI{X7%ZR-@v! zV@E%MW@j?#}**0dlZj@-%d>l^!o?*f~R9Lk9}II!;dF+xMl8d7-gzGuMRqQzTU&B z2A@vnb(#9_WCr0|a`M{D)Rd$;jmXyYbI%Ya3P*<$Z=7z9cIGN1E`5>})vdOSW@3u= z4ASRC3?|x=ZoRoIBEWel?|r0Ezzjl^7UUk8%8 z=aafjnP_E6Vacx^nN5|OOf-QUVN(@ZY^6hI%aJ1U*F!(q3YFz%ZAP+#Q{VS6Ok_&l z_b$+wr^(*btvQ?bm_bB)8dZ(e<}T+mfg_k~QyI);$pKA< z)5ynXQ}0!#b-%|fJo2ue+5z7GNZ#?sE-?2rm-U%!PQTZh(AT>)62`xA+>{a%% zDoLcUn|)MF;T<-dahb7A71g3JS$RpDMn3=YJU^M2FZ^|gj1(^GdI~lpc@Xm zIvQkSmi@NAHjoM%=~|W(kM?Wwc|j`ojUm3IppRp?X{|5OjIC+k@2F6%5%PANaX}%N za?;qlSuss;It_fPD*)fFIx34>^KIvD_KeDKnFp=&9h4ildfhnPz`NmR{8SlBQ+tm#_h^0W|}Rp<6&tR_)cxIAor>*i~XF0|7E(w zMDr=Mm*!-!+AZz6V(Sy&MYiD7@rs2hX>)AN$DfvS$Pp@4hm@~ zvB7xPEWEtne4^ij@o1s%!oPB~8+9(8>9@-nc2tJ3e_sv;NbW?kgg zfgEs?4^|X&YH;t9n?gQJu&FoFTgqi3vwZ*t)3KU!0$nM{Ir+ZQmIay7&{i}&78`wX z=a}eTka}!a4BVHRH*O#rGrvg^WN=$V@>lLBPQ7eE#Z!FMF-Ol%EA}zUgwKwlkvl_Z<~qFSb;sq;L9Ke zD6yMKL*3~9{SQJSo`N8>BBAm16G4^Rks~oOmkPh_u_~nbskh&1`By7%;+E8 zcGVYH@ioR5R{HbTbJK|4icAwfkUWX}>1b3O3bStW3>A1(dylGDFkd8}{J~S+p7FB4PR%U%Y;mniG8w8@1?qWFVDWCF-aSs~k$OK3(>&$!_~8@| z3a}d#f19zN??)ARobzsP;P#FC-|j1tDd^ibLrBKn;@(V|MJDFvpbGKO{OC3cM#9JD ziX!E)Gk6Zqj^N_eRPn-OeEFt;Rc~3p*VMXK%Mb8LU`mqqH#mdt6wmVzXCVkSak->NTsab7hh1*R^vl$pnNn zN?T3b^~1DO_&os1Noaj0+O+f3FkS*Xj?3fJhK^G{<@j4RRBh*^liXXU+5IHtGc%%= zcyTU45gYZ$eCapUTQD$KyH{+BrR5nOGGH;Ju%1WDJ#kC|V%kld<0EKuq4;cs(%(YF z1#R~d2`9LP_dYc-8AHJDdKz0NJTf+8Vp zY9@m?tIM`pk=HZ+ThE5AG zBO)A($xFNr5rR+{`Ayn{qwXLi^D?QHW7rjhy%k7dyPLgAayn&(lkj2vDJ(IsRsKe| zlsxC^NM^A98+70d?>3CFkhKkND1v#>bTWI~+2Z|3ODl75%+2P|z*#xpW)6-Xl!m$Y z3v1)z_+n9Ix|tH7{ei4;pHV13*(qk&BLw+^*orBhVvynob8^`XWr$teGh(4;+pCPG zlIB04`7C9ckvjuGmo&S5CRIiCoQKs6GG#{1DeH+6O^yS6ouEuvonesTz0phWq@@C1 zx99XFhZyeia@DF0uJfQyQ;eWC!K;dZwpT-<%InmA7j2)B`GN!S2dlrfHVMD3+M=aR zny0l_u#NvR7r%Nz&E8iAp~sUUv!KC%;);K9SpK0D5I4mX$)MPKG&(`OE4LDdg^%F> zrMxEUIn9CS$tUzp|1BoG6#x^vl6mr%9plfhu;&!K7i)WIngZ1!q3WV7NniI5kSiIb z!J%*%ZO-(NN?FCA`y#!*Dnx~&T++KCnIJKv`=_e# zJE3n$5=@A1V75O=|6CG^8J@=&!Y}`b>_VtA3e1w$3dC|yzkfgh$;(9VuR~?M*)9KQ-b1DH0Cikiy@Lj|=aJ$=Lb~8MaFKY2FnqNR(E$Yq(3MGgF`W!DKTr zsgn_SRKzi}j1E3MQ*4!OB&cCjDK-xt7IU%eDM~ZMM|O^sLW4B!A~sV$EnHyQ=tL?> zE{N&_7g`iu#`>kjc}bs3Tn5TNy(p_|SzD%om##A8eAwh_~=+y2isJI0cBn!RW``xjVCT#{2B z6_V@Ak`y2+hV$y-1N@6^SE>^$PEtKGU;ISQR3)*r40p-EZ>Z4+H6ON$xP$C0;uZ%l zJlYaS?B_%FX4L(+UhS{1I&Zy{ozeZ4(ar-&_uHbylNG@BuzzVB@2#BO3{Lvl81OO< zmZyeVh7#GFkc+I15`2!IPxDirdAOODU2`d7V*6T(Ybo_m zmbP7v&V+{Fa_70N=um^R_4>oS%?x6vbms}(Vb$fE;)1Td7IyWnC4JCjD`^Th+(s0!tIk=zOUmkRJ}XC?5dhIy@{7v3H2!YYt|tC;1w!T z#TLb)jF=$xWgK-rY39t=_4VfW)CNC7mX~QScV~2s)P~!XonvuhcB3L(PR~lt%o&TZ zgR5OlHl6L(Q(axucOkSnE@Y9|(0p;qs9lDVC!79tZ-oR)?iKV05=y7pV%=+8$*tOH zCe1l98fmcVzQ=wtZ#{exg`T&b5PxsKu}Hcz8oj_m&xG=|lL7b?seG5g+dxM!bu>c{*@U|xSKjY&M!yaBZ!FcMsHN*o~0rVHmc$m&HZH1xwYPg zkviJ7v$wF}9)3JwW;0)P=-%OKF9EBTkw5X6Jns-;o8=%iC`#7tGq-J${_^t%1VxTG zitL{q6s-pzB8$Qi)SfhH4-hB|F6P>x9>v%Fbj`#{i#*d&H=@z8!jiI0S@zHU*aAc; zN(#0B6CsjXbEZ*S^GM-S)9_CPkL*mDFS$%CP~?_Z*o%6%&wOuFG~Na14I`fh8~Fa$}knPl-hMVGTz^lQ^Uz z$b}=4=e%DpHZLu?Eu&zgEo0q(!o7JlUVk?7IaEc)I^_pJbw!BxX4?<6g_|6@WQypiwWAO)Q5pC9hW*GTT73eu8l7 zL)p*uiyui+YA5>=IZ|umB}ygyCxpg`HG8x(#Z`1v`1Goc1v1~}nPP-P6?N{pmE>^0 z=^?CDniD#kHPh==62P=x*Te|IKcs#vBN#R>$*)S1wOkAEVzw<5UtTt9TgTul>Zb{V z8)gtB$=-QYecGr#@SMR!mJ&HU{WSEL zw7TL$Q;?IgoZArRV+Y2qT+))&X4-?9vgu9OGZTY8a_=jLGc(^N*VV<` zRU#)G8IAaNIW?g?mX0JKB&MA=35ILjFFt7}amS5t$dQmpqOsr{0GssSPEhgshcwcy z@4}W@l)-(NmC{R#E8Q`vsqWbDaBVsF^vgrPy^a%{TPoS-ij|#(>!hx;i(5JTZ?Sae z&M@6kQ4+J4=4B=ZbT*G&)kOHa2#ml+Ml)V#+Z&vlp1E5$;|oZj97+nQn01a{ffk`8 z;I`@c|CnQ6Po%>M)&0F2KVkU?#{xl@F!#v}?c@Z@ciXOp6V>tiC&Qh>5#Et4yCOvo zZ0in27rEOiT zCEN3tsa)%1C44UMCR|%h>1xI+j$u>ZYdf0Px+M$Cb8I`h;r(o6x;XUZ4->ru8yc@4|8YR7kQT6jxm*2-#S0^l26ur8TF2pwV(wOCr< zyuZ?p!Bo{3>ID?$GFsR|Ij#?`X9Db}26I5OIdyf?6-h0}^g#)fm8TR=kiZ_ejU^4CO*w8Ylw$Xt3N9!Ry#Xy&O2bq47i7r0(A znVUiX(X+6pIh(D9b1A6D1JGVf2X`Y(E)s;pn#hAA7=cMCI3(Mi((zfw5@p(sEhsAJOJ?d z@5$84Kr#*Fk{$5JHuSe6zdni%0R8K`pw0FBPm}+>_U;UsGyqQ>Zp&c&qt*X3hP;yk56^YOgHOhbF~GH6ABDLplzA;G6sS7ty=l*B5O5K#`_y;qDlwufa25VHjA+ zZXy20Fq}Z9QQ@9OAKM=v(A<#xAa8{BX1(s(~6hQOHzHx7lIR-gL&1u@Si?l$rEc)gX^;Nn%Q1g?mL@qm< z(>d3Qenr=-DH=x-L3>r6+jzkluV2tv`uY`3OtD)=a4VVxPG? z*MD;}vpufOQ=|WFNl744=*DdxqMFqgAu_*xws6$mzClQ?^xXco3>T* zeiVI-(Y|GUSTiz$2n0`bLz7EiBvPK^294AC)mUf17X!@?^MfC55L9%%BSXUNqCx5q z;t0gz5IB^ns-1t)c(FFcrF?c40jwn&1!U7Xdp(Pzy5&9~yRe;~6;g)hofT>!@c=#6 z1bE<0-d;_V95rE*a6X6McvNLYa>pdsfAd2zA1_=bg?W2FwHAXrbuPTZkj6zP_szW2 z%Y=L>qmc_cpO&GR7ygpN;dL{;{gK3U(oYyK8A}_8uxBZ72RYQq0$Xq1=8_v78z^YX z96w1eeBYgSjNT4x-j0ft&Izobw7>(M&l}et`N;_KX)LG~EOXo5>qd^2Kud z>YUAR&wRe#_n%JlNV3X`>j*t!Zr*KZ@=!AC-iZ3u#f`)B2Z-naAqZqaU*49|o7K(? z*mQ(6#mv~J*S>3xq{NX#>0n^a7WlJkG`QQ=!4tkM@$~FVfxe>U%EgA(E{h+`i62Bh zwQ^YsmYnnvR^=ow1w3)RG{t#eSgGM?F32dz`{oIlrhX9US`5<%jcJ9i3--`ecwtfB z)+A5N=emnEke2*$k#5yKF}3?GHF4{{8sB}OTLHkvAw%4gI1MS9hVLa`pX$$F1}cl# zy6m@sxHe{|w?7aax>%&~u-T*aU7yaB`}RE4Ua_rQf+cUA3meZri@J{-vSp1gKZbVg zL`BMGQ1@lx7=p#j+d~RC91IZGthe`kX(r&Ozz34^y&n8Sbnn-NGiY&rL_Lv1+|7IR zw-YV;*813!IT`G2`B{{+*Ym`WIXK1_1P+Z`FX;R3dUX~PW zQaN!+z0}xyLKilvU0A*7Sh$R~Gpm9LEKMBWpF##2#>sq+FGY^7D-0X4PO{5vU$v>$ zVYJWC>DA2EDbw%h+pmtN3*+s=5n#Y=&(YiAeS+&LCom?Bsr}j6Hwh;&I)y0}sXw~z z=VC`6msO63USp=ZpznWjcs8=qO)?<0U>}CZQ@)6fggX@^%9qMuptoY9g-frkH)RufgXfA4PxP3vHMDlaR7 z4CPpt`zpf76u7iV!GatR?0P|5$mW;a4EZ{*z{SSEg=gv*xf${tU zpT`Vp@Bf0VKM|427J${{n=zjM6CwQZ zrWLS{t5g3zhef}JSr9crRX%}P*R)8xj5P~_V*$}F)-mZ#V8h8fcr&&I=q{#~IzoFb zcdCk@TLN1zflG%L=&wsCU-KY>*GFAN#NE)JV`D4pn=l|(CWYqq2Pjx{>+y_{qXYT(b_XkvY0y{7mMWUesY+W?6@8; zzDTIMhHmMZ?~wT;^cOw+1kjaz7MmjcjiAoA9FWsJ&<{0X`we7!Ap@AnznlT{H;~x` zNU=YzdGy`r?>P2vllD3a@GPh%>V&^(|CH z*%;dCpKbo3C!%2kC-M4r6&;~O$%44cSb$WZa=b`eKb)9rNm0B>jiTIXQ>{p&;FD&l zK|P^dUe{}MnOWmTmlDk~Bca5V>~36NK<=Ugh^!25dsLzc)y0vRw2Gf!Hy3M_=wUcg zf^yLmli6ZA{;ATKnjSKBEvTIvOGkPhaht1)z)8xJ=b7g4(~)aS5RrLUbd@}M!` zxyBlEO|Tgk4{^Pguj@IW!Hos%3pF21BQ^FDEe6=0Vd1O&myLkb=$-}N{|T5EpOxFq z@e8cRs@t^rp>TVizq`}-xP`H`tBtP$>M|$!@jJO!$N5T(@-`qUs>)0EYKS6C6gs(t zl{JzR(NF+CHgdoFDe3`aaZ}$VV9L`frC9E@-rh(dWv1{&2<+B=o@!P#!Y~V;+1q_v8t=747zS9FY|J2}vTu*LT){ z*v?F(5>z#x&Cm2C42CpE9PKtQ1?Mpw>{80Ks%6ZDWrE+u2@Ljg7bZoMY&cALh?`#3J5*S4##Hf8h0?!gOr6%V*67C zCQIvMZ0C|@JTzuAXP>9=kOT&7$u7cE|9S^`7XpMCZUWeUuGv^1(wtzSZ z5U1ID0N-tUVi{P$sr=5x){~_d2jgYrZa)!uM%9=3S8D+V7^p#QyoXwsvuM|P?J`$tAB)(Sj;uL zx^f87WXm>bobNX7DR`))07}k@LQN)*9mwRJn1TCsS5#Ba!^7K0`Zgw=PaVUmya6>M z8}7@KjmfffuD4@dc1;ITF)>rXz1NV(Xcd#{nqL64>OJeZ2FTICj2C>z7FumZ3#V3) ztBZM=D4`adyR<){Z(m>eIze338H<=|P#L2b6Q7*shCwEua1zjUHV)#iQER-VdUY+s zT%jqRxgA|zNOMX(wOcb|$NhdqhOW9H0OKYPEZ(FZar*K6bjIGcsHCQ{m#uX+u1rX@ zUB>nRklfXC^8(~+%ADl%=Zq)pSqX(c^dB_XP?}+p(Pv>#*i8c(#j^9^`@_=YiLqNY zsW`hkD}N^P|IStUZc|byf4$z?ONdfk_I_gDtIam3Jo>tqw#K=?wh?WQ9wJ1KIv}0-M<}DZF`ghwh{NUYL3uDyJJM z0rD(#d5-vvA$n!17qT{Emws)DPw*c=q?S@$=QmF~qEzx5x>1A8Fy9k59`-QJRooE7 zM34&bm&tmt1T^Q$qn9)_jAKRGg9ov%3gO~nMukv9DN0*a z?uIUg^Rh8yiGmz8OYp{6K@5z-h<(C&$7tE#Yd8U(OXz6ZBK}{@_&4_F06)s-v!d8& z0+*pHkTD-%=F^g`6F8f7OwB7hj=U4WHaNr90(gTh@rh~3ngrb0Uape$RtZjz zeC8XKNmVak6KsrB+g5v&M9Dk@9ogLo0_n6+1i%FoM+Va-Mf$zQ-ExMFY`clW_NW|6i*L zhz~#cOq|3}G9nTat=SGdd`OWSB=kGWQhKQLxD??kV2dNHOH9?fTYx%~1J;|(z) zSNs0ffjV!pVV(}$xrGME?HbVv&ce~gsFXLAj}lkXw^F)+OmGloxlyBYK`Y8lxuoPG z6><-@Z&w3vsmSYBj8fCSa*9B-nnIg_*xVw0)wn3CG#S?uCyW@5wV0Zo?F*|YI|gLT)MfEgw1SH z?zld%yP9WY-Dy!GKp(CBfKy)!#>*JgoC`YJ82-NFLYiW_O-a3PTx~m?o>evCOrA$i zk~K;LuQ$kkPeQ9gmn<(cQgE+u8HH%SPjB=DP0af#W$62OhzPdY#cD(e7fK~xKCC$O zYP1nfzK=im z`$xAO=YvdMw3(%`QM;-ijtSG>Fvy|i9*<^en3MO~By2}7pI%aC+(-hz3miP$TB=*^N0G3&6TQO?o z`yopN2jcBL4BdW|1QSbB!pCl^i-Mi>H(XkU=JZFVX7!O$WK+YDcJu_@6X?pO! zcg;sUTr=|~v$E!KYbw=TpLc2j&zB^NJP&d|*9EU6N7Pk1&Ow8wv{;oVu|ot~SZ{<{ zl~nz-Ostl|^)r+@R(ion0>}}DsoDwH%1+gG)@V~5JBKbDy{W`%1pf%fD2Oo5xv+rb zzq_k}wmjeeHrOSTptNKq(@?FP`7zxzrI#y=mTv}Id1HIh^ z<>6+>S#ta^{7742C1#TAlTS@l6!8H@Nh7Q}6<<*!$!(y4n~`t4r{*FK2 z`@~27cU8o*5&c>f_dRdU^r9WIMmkA&a#QDXMO!+4U}YaTOqTT$crt0_>$s6pUBK%) z9Ox^Rd@JLWWtXKmP2t-KgT;C|%2c)A-s?kLa!!cndUvm{j9@b@WvR`b(=_#xG5{O* zxA0k%0)}CESuS`VQV}ZnWTx6GrnN$Tf+W)S$(^*Fee8hf_`9*l)E5D=N3zdnw`?n$ zo$W#8kog)&b_~*zg27UEtJPsgvX2U3>VlM?0-nRs(GmAUJl8DGP}wX7k2oeR7xmBg z(QSkcol-Lgh~jCDJVIX+Yj&RNbDo*KZHS(&fymg=f8SxDQjesa@xi{glO&Q38C(b3 ztv9(x%n7h7%Lqb1o}(|tbzQ@L$)o<${T@!l;E$vNF3h73@;wg#HYU^}_TaIHZEb?? z@@Dk=z!@*B)7t9@(s&0FxvH<_#lAX8Ve|q*$k=lgS)}d;_8y)i%iKRl5xK2ysA2qt zMRNkrD5-ZxCnId%+qh%W{w>A$=PgAa5lp#xBr|hA8oi?dUi*oj#n0Jpp{VcNQ0>-! zOy;U~aO|9R7$mbm6LBaHPZQ=RpjAQr7cW6!4P-FP71dv%18kKo@_@Z#OaC_4sNJ4h z<9LbcB& zJ8S-SHF|N@)7BzQ=5U<31tnt8Coub7s6ip4v+uu>el`*rvzIbc8#WPXI*_+zqUzrM{NM zs$1D!s8L`FFbh4Ni?N>x5p5ZBL`IxCE$y26FC~dU5!P*8ov51yqhDaAS)N1QQpM_fl^MvQSWbEa@mG0r1T?qeDQE9J{I}CE&U6;QmkkLMiqwC10|-dZ zX!eiLtH^iR1@^J3dLZgr7%ZApZSMfJhRcMmMZM>OJB$Usp6;!t_QS|y=M7-BTKy3! znkT3)qIZ(*Tk5e$`NuRO;<1MLnk!kdV_rHLLSE0`kq+LMnW3zeqsk(4&>CCVK=3pW zpxUnX1`ej&|4kilh2?y=480R(a-K=q-^Ujg-mK|I36&`X?Y zpvZ3Bbh6Y?!25c8x(Z0Z?EwVQyGc7>gG+w#CM0>nAWw-D289%ndhWOFLV>cur2u)* zOQj^vZh$LWljsM8MF+-A++H!YUq-+{C63EaUo<;HlOJJEl)heV=A|pFrUKPBC_P06 z^_BYSn)99jJ5|PIH#;?5RM{E!WO<9x#CbSV@)Qv7Bu*p=@766B%O|jwGJ~E71N!Nj zcZ=e+YX?ARX+dP<_C=nEleSB2KIjnX!ADdq;;C_MvIKsO$kp@ z5^!8YZF5R6bYaJcKlHn+6SV#6gP42BGv}lZuy+eKvvm$YzKRaY-uZMnpxz=4^yBgb zMpV_JZ&UFhl!@f7QVi*U+J@0KODcRecCM`DvPW@d4&aW80l-<>gd=?ZRj<}=r|GC> z!OLUxW8s1qyu=nVuH|yEn$XA(Ec+7Hn>#KT2v`%ap1f$s=3BvyQE8YqPo4&pBUV84 zS_@EsqvO()D|pgRRlmkl1U_QXtE&##2Wk)-IhRVJm@N``eQ(>L_J{G7siBqZhykdFaxu%8ez8v#DoQ=_8V3V*duLS7SWC7Pw0^En1FxIyXC4K=V9=pK`Gpw}v zvjA(`e^-1+XLq@Q|A1y|4v1}P0AZ%U7nRH$!TU56j9u`99;UKm*_8bHZrbFP(D;+B zaYvA`^SCChgXUsEUfK)WD9X0?>EKg#5RsfyY+cv==Tm>Z3n0H5I-8wOVK3UOaVreI zoFensjy7J`a9}OG`c_J#ww`Ij%)F4lDFs{L6m`WI6V03M8>?f}jb-wnm#V`EUfRbgoptmW!Z-n%o<*`3RNUQooV_#TEIKI|@hwjM8xBN!&srZhGA;(oGK19*pTtPgqV4&;+t z1TR)%E`R{RuW5T_n*Lx24J3MV1OM1ht0|@0=CO$~sX4}HpDbjHidVu;*8R?%6d_*o|vA`TliJM7Vn59D_U!_e^IbJ(ug7?8k z!2dKjCVeaF=gIsI*`qQr5LXTu zm0)e!%0FDc5?6Ek2{T9uXJD=zDjsn>Bhn+JBcR0H9K(AuOIh@mHa4sa7$QD0Wvfp8 z4L9gLelUUjNQop5eC^79+n0*Gob>J9t2=h7Q^d82^!QvX_XHmUm);pEnt`QMr2RnN zrC=rm@z*}*EBZPFP$@{|BH~j-xgZgfzfH+MpnO(^JVH%&DOU|70TjDW(ytraC3h&V zfE~7BvjP7JunSAq9(-J~#4OFlRR*dP?`r?<$X)OV(jJsl$74ATED8&*+!cC>11LH7p3%;f$-Vqrh|g`PJ9% zB~eKy{YDRQ024kd5UJ8!F1_pOIcc8_J+Q%bK z>n-qO$UWkxDoZcA{+Ug)$^lRs6^{AYX8pHkuRI9&hWaF`! z8tMqXu?H&FonW|c6HFB`CM-~lTMi$5+!$=LzM0A!x3F!>ocjQdLNf(?@D4x}x(d+4 znsWKA^Im_@xj;K|!ppiDb#7+VEPM?J2_4m21F=ZVonaFY;Fp!oh`2$3cVEsGwZ5z} z!qn4OQ~#pg#<$=@iJfU1dh;|@V-lQ_X%nWDur;_!$un@NVUbjF0i=?8YE-@Gh&RsX?%!Z>aucY5+BoEu_s~`NUL&#^M(jZR8T+^ zJ@DsCo>gT?o)R%?D zVGhthzSJF8*f6si%9awvewP14VD(EdN5O#fuvfdf=&TWHZp=S(xidM)sF}aQZD<9T z(HFSR8MW=%!xIbqTNnU*jI}~~Um2Zes3J&+t7tpU+R+gl7{X-Vs1O{;sBD^JBOmCN ze-&4Dct+;VuRRj=&%58nrewcvo*ng-R+GEp=5}(y-1x-|>kRxw!qL9$_mYdKwTWd2 z8Mj$m7-77W)?bMFKj0A%DFCnc0OVA6M_x$1f+gwCWJLJe{OI5)mQE%sokeSP8**v# zkZ!a&e#7nEeHAt>F7~fh{`2|~UAYX^9VzaWpl95xehe<|y`!mdP^X;v)RW=`kL$xp z`98HH_WS<;+`xU`-|0&tIH||)J|}#&b9Slfj0Dfprf{@1aPF8womN}PZzs1FbQkO{ z&L8PF+d1!Qr6t|%zIW*JFM|TA08vrq6WXr`cN^{j-jw5v&`v$;;B=scr=|3^Q^0lA zA$j9kvvo=o{h-9Z-@`&d`YU{HbBpg8;M<9IA!eK+48!Aqr*4rgn@3s=qrSw;1)yYY+Ea(bj1Ya~0D9Zzv}Doj6Y z4mtrf^p&0T%nYBt0-oLG;&4?9$O2O?JFs6Pk6}Zj*$o?ob6Zj0lm!5Z;p7(eF(ByK z))bcDGODpjoH@{3U;ujYm6!MFPCy)PmkUsZVD53>gdDJd?_*?aYtPTklh;BXi|s_k zbEi8tYkr9?H?Vd6DtPiVK}=dubjavG%ynlu-*#a3X@7)H-5sfn5M4JEn5nD!F&&ed zUj4{fe`-u5SMMyLL4PK22HIsWZJyfv_^w#~ABJC~1XBE^r{DDMerLyUvYj8NJ^B0^ zhx8~5p^UX4hebnLK?{iTh1Qd*!8}G30c29JSl&Vg?Q?o&G z+kF#%+eW>aY3VBAN-F86R;k7tHspPm6aie{UjI)waOo9+m^w!`z4Gp1%#Ic#lA@&FV&m*aV7YJUwC{t!h6?^M8wsTSf1AUf7<|rZsn8e_uJT@h=61oDxuwu=1e$&RU|D z=wk&&ov1F4Ek({Qr&*_tkSxx=8w=YA%vHr09!}73+aELyjiJ2!-@Z_A0L%)w0-mw5b?@{{UIB9Kb<%*^QsEf#W)YXJ#)Vm0?-4CZgX5WtVOOCLXBH< zMK*0AD8cO1F1xs&U(l3^aN4H{IA;RVECl2c*$kuRruDVi`c38!P0ymFMVDWO6L1;* z3QfBxe2Z~$CyRAsRN@|b1NwcD~u8@f^ti)dC^+ zi&7_vKjh|_7}fK+5Fy8J9H$3~JcfOJTA z3(_eae)k34b^U$+sO#br_qj83=FFM7Fxqv_#af_%p0LK-sd zFeGgWAV**76Veor;XoxJ+js(#H<$;0rib`gF$m9wwRbHSB0FF;|_5IWal z2Fx;Xx;p)?@$>4^62w~DbSms?fBHalK90J5JoD%2@H1lmW_8P%u7>jiZtjC+mi%5J z%gyTSeyZ3<*?+zd)C-d*>Y&JD33A~t z%0`!GhYT{HBHc^Zi}jdZ$77O0@g+`g(2D0=$JP@+AW#Gg9kjCFD)PIvl0C$W0z?h)(0w}J08*b!;5TlIM~ z*O8Ii;!O*7@4FLQ%w2bLrEk^(8G?Rd{8=sFj!!WllAvD7#3YTegDFQL+z z^HeS{WXZBHja=MBHvgH8dwTx35S_ldtN@*d-F;49sC#jH^@nC5>8XtDqj!fT9jc~| z3CAC-OD%p*rKtr1VHMDy`96aTdmRG;qIb?lL{LALL2Gl$y0N^%a!MVbs=#zQTOmf2 z@Cni`k&G9#ig}8U3Tlj8-LZ?UCrXTzAIV$S17A;WE2J+3f(D@8qJTp6JEwt4p}sYmi1t8@9D-N6?yT^7}PmT5G#%WJve18hMh-eE5Af(b){Mc@E?| zCJxVuf09yY{>Z;O`?S;WaA(eR3Zlu{M}~v{~BEWu2KJ{b8lOGyAo|^YVbHw-XA_mleyi(BVto zK@3Bb#^%(^IwTHCCbFQ3!9;9d#m1c~FrzESn9-eV!eP1i$Y~udb8_sUvCnv@m>$pD zxOe}og?+9y@UzRc$s=ROu{_+VuD(3_TuLkiq?S+lhB@SxM4;Hk8y%a5^-e437wZPR z*td%ur}g1Zbz#Jv*NC_6zB}+%iFZYal9~egq#o4LUHXi)hjO6I$}u(FDT3_{W_m;= zTQBceyU;|mnNopYZQ8pp;r6O)Xxnjg+azANEwOT(Gu`MN!L>{HBj_83LaI9^C0w|J z3=4X&qaW-D(O>0|B;X{Hrm-r0JM5?trZV#>d#Frgekqhc@(fsT{!oSyLUV<&9LH)# zpM)iLYI)OOeZy*k=L*n(KuN;6laH3J>PhZrs`b+A%SlVg&amn&(w-SDH4QKJSbOU4 zy_f!U_*s>|i(cexkc^DZet>{?g?*hR#SQQkkaibuu&4E<1a>oAiK^ar0K*`*- zUTkdZi`{1$4X<1TPk#lyKdvP41|Z%wn@mOM6^#6wwl#LF?uU};C0%`2;5Lpmu$Wjk za%w?hSH1jvXgQw=*Nu+!81=u%i#!j6_LfPz@$2qrL-@%tb8E)Li+3SwY;9bdDn@N|t(nu70^I-f`)v&X-marBp1)cP0LdQ2^{jZ_c2(ex?BF`5tAH$(Q&ZCdZC7{e%{iO>KPy=Iwm; zd5-sHKezSV!{Md?{Re-2hJ5fDh5}=Iu8{<}94!8(i}$5lXFpp}3nW*eKD3@?iDZML zE|uh5(!07A z>6ak|smbX^?AoI!&A*|G?tO3(B8mn0YkM_B^UlG|opeOfG_hxhH&EGjO^;?&nf;h} zmRIhPRet$>`R6zg=_S!;=p+5tz`YZwK=H6tTJj6@UB6*?PmY*fqv+lS!wRjsu`dPs zLXD(mwX>DET0h!E*vtp=rnBi-|JoYiMqph^^8}vURKN6b#;qc^HVay|OUPbb>6+}= zRs=cEwVvlK%=?d;NRgczOpq1hfX<}9mgXJEE`C$3UJz64anpmu&4pCb3UOX)wXqeg z#7C83^)t}au;AzXD~%d6Q`7gN6MtRAE)ZCo4C}*a=rhVzAeUuLHGkZ^NDJXMPaqll z#-UMc><6?b(#oDT?zM-J8kPW@7@;AT`~8t!LLnO>X#0GfODU>iztU3-mAb|-XqiD7 z7Qh&*j^ro}=}MfHYW`7+yK1JPFomrdgK{!JNG!v32irKN2@D#t1jA&M!DONyJSkHY zEY%Xjw@TUaUk(d>vHfZGU0LQzq2;>Oq-Wf@tjpEb=MASD)c#qI`-X98l{v7=z&txW zminH_TH{_qCRNN1M6yJFkyrMC2NULuaUUz% z%ss-ElK3G{*~C#5)uccNZx;-af`Rs)vc339PQ+p?1#>5qzGA$&pl)9RaHvYFvb*!> zqLCEj{;7#R%9P}YriMGVYM7Y3mt&sJGbnG#de1r%2cw|uSFaBHKzsMmOZZj`>rWwD zLXhb=annQr2p#=?XKWB?eyo+s5Qg+T*cy)d!ok5x+=&2dlnCy=2N?QtQBs%I8izG~ z%c+WT`pW4+Ak`vr1uXs+P}M*yZIlkP&v-amY-{iT_eroZ-D!D#ZXH0Y!AGZc{WIAy zjv8DPd&4W4XEbV;p_Fkq=Iq!cXHB#>-w)ZGRuZddM$-Ijcl{*M(v0Q!N=Qav6~bos{K&ruvq zC%;gCWElX(?4I|dre={IK^8U4Ik{VxtFuRo$Bf3F2t|K<S<@10IpD<3sXY?vX%e zVpV<&7XHmhfCjRf%1tW;#yB&f(0lqJZ03eZy_Y>){8JMc9*ep}yu8-+txtyGi2kzw zC`9i#*hQ#BlFV(_TN<=cmyxFU2)F39_*X9hoKo}h>KF0s>jw#Af7&=?vq4HEfV8!0Q)y(-iN5nSs{=+AZ}wR!p!WTWpG)TV+Fe zuE*Q)B}_)kMMpwivKg%RMTgrLm*eqXA4kuX3+K(pK_T7yikmW@%ndR$~A z%EYo_(0GbXgsOL(brjNbZ#@dPl`!CuklJD}==24B4j*C6$&HC!vN{T*)Z*`EBw*H75u(tdmXBrDr zL4?`<^a(~EvAKi~gaL;}9@0}DcmVDYMEU1d1a}y)JlI&&Net&WDgAtk)epo0hZC7$ z8thCz4H55vOdOe8M1+Do1KAL@L;&pjR&*0@uQk^%mn_w7SVumD zOGC#Q&lqKM`u^$VoIj!|Cc8H*>>{Y_8MnN-QweJO)9;^}>Vu01#>)XWWT3tDdqUnO{Cmrho{t>c zD79HzA~Irb3$^%pKSeJS!^bn-$sSTG)9K9jzZlOAzWgf?pi;?##-T5PxHHcl0e@Tp z9UTbm9RQc7-5nfWhSu{zKAa@T^6kORvysOlm(3IaYTGuge(utp+by%j5(p|rcj`T$ z(E_a?HcrwpgW<3H_`m~Y`)9PJ_OMFEh7(HXE)qb53_S}aHU-MmxmCi4zuvoAV!p{A zX_6@O*(hRYodYN%Fp#)svo_Qg^ZxiERILu=DgQ+qP5P(-#pA#m-PwNU``cqA=V688 zr_^CBN7L-IuAya2fqI$=y?@!0{MhU7_s1HCo5QoI*JXpU37bhDF}p~$+vz^_b73-& z{2K(}(U*V>VG8s`Y(780xVbTnJ$&fgq4SVr%7WK(q;d8}PlL=x=T}$8v^9TE*#=F1 zY}lAn6_(~9MC?w@pGJi(Kv41Er3J%-LEc{q=HK%EBu2VfD(@1hWQ`!JjMxk&NwI|8 zZg}p6YMkk(*Cmt|A~NlmJ=s4741i}LM?$uEK<>0Q_(e%lP`}!4IS*6?IAx`Ce!ZO- zqqn_AlKCrB16&Z2E-t)X9VfFRO~;J$Km5H2n9pFrm~lZle@2~#=@>e-61ga7ynFhE zQ%Vo)go;;(X1Q{y&*FL*>?;@m0St}aleZHjcUd3=01A!t1n$o5*qA8=&!R&de#mzrVYCzQ9qo&Y)g!9#o@oZZW zP`~mOzKbImXH0cwBRNgTDAx@-sH5ur+lU%~@ijGm-=KimEZ}8p!!e0MOA)>W557Jc zK0_IiRn_MP(1gkE{I;M2$eclk_ec<5;z*K72JOSi@FO*$1$jAW`Ll2<8;O(jVc->>n9t?TorCM_l z`6$uWhIv@*Ix!Ad6FdP)?<7coH$iWPqiG-Cxkc^KqCdYiUzSJ)^x!*_y-wMej^s@8swBKQ<-wkK|J*t#>gqr9T)0O<38rtBscply)}p%pZJJk@y$ZCn7o;yUHf)EwFiWugSkK*2b1 z6piv%65WOB4b)eYMFwvsTJ5RTV>7g*J``LWFa1;p>0Ul?WCvrCbhJEK5C7vIL7OFb zc&H`mc=mpfAE~xCxgKHdw%^I-ysJ9}CdgWX$uBbEy=|c+=3s`BVzCS8Hc=S`LuEs> z8^L}lHhueR**l)WT2@#Z^gFur@$oDPaqEA-znK*MbG&>SgxUb-Kev}f zL}{J&<9z?+mlUf4H2O|~a=#wHqz;9v&U+rC4E=yBDLXy*Uh6gss7uO7P==3n0wY$( zfs)8#xhuLmjMf!M#KM18XTdooJr0yP(lo$d*)$Durq{z?aTbrZ;&IYr(7M`-;ycS6 zuo_!AHg5Qy!aiO0Nvmco-Dz(Yza8`ts6o86;!2aTu2{Xn@0s+A<-S^ROH@iVd*go9 z4yO+lxR&M(vmy|I?3o&K2#$$MA8;bju|`j&O}3{hts7qIJJdPzn+N`A@`lR?#qd39 z+dV_kUED(FI9UqeUpht5Hz`C!X14S3svv~RG^%{6A@Q%<&>Zz|EZp3 z@W-)D=bWA!mU(rB5bKR>;2KT!AFmHas~GU(wR(i+*t4@Qi^SV^9w8lD`(L@->py7>qhH1Gw1_sL<7w$`9#%o|V3JWbHs8JKFNrv1UUXNastBIo5d zsNx&t4K@+T-|#SAr{>We?{MXSZr8GomIqLdcla!2pLC3{0$OL!tZ(>nJe`^8I>WnQ zMRc+TAv*6?J?3&~7s}t^bKgq#M@lacTt|vXrV?B{oqAv6V}Ji4FB)r>Q-9=imi!BI ziUnEzxG0BFsTVN5Uu{A}eoRAyTvi^V3ZJ#SO1^qTl8)VptFQH4-lZSxDRxGks=iV< zG{-ylvegaiRO$Q9=|SyW3ND=oY$+>Pgl7?;lcZ8Sd}Fr0R=h}o1B^D89`e@!lh#4I zlKZF|#jJi1eS*6Op*)TdGzo-?@2*!p_CG38AU1s$P0Z*3H{OWrXE+hD2Hz+o@ zqBgb^R4?5=98TuOLV+Wq8&?xzD9MY|%rGZLX_uJrEJqwkVp#X4SdB%VZe7Hp?vKwh z=}Vy2{~*tDWfOx(Xer-Xb;++hvAK(TZrgK*={SV-^LYqccV-6+0I~4iu;%1l+#&)) ze(t-o{`-PbhlOibv6#{6OY^US?o*m`y@)j?)Al$u0#nWV6LKqj`xaW>)RKJK7|vcw z-Tj4!I$U6vyQ3>z$iK8%DCL=~@p1{Q7tOikY`Ed@icsiI$=5`w3 z3MGnU#HI_BSH>9nY&R8uh3%V%HVOphRctj=qa5d~dJn4+QBQAd&`}2M5ICTUwXTnD zzjE78R9|(6u4|L~V`9W-hqAS<7c78{UuAB(v;t(Q7&JgnrRC2yZ#U{;X7S5>X!TTdt5-pS0H6QC1L1Qiy^r!pPU0?P#N%U0<(XcY@Cn9x zM~WFeZ(#2x-$I~)z!)HFhC9Q=glUIHv*)#jGYHwx{KU-2fA25T@8}a8+aU~tD(m(8 zhf!sd*8F@`972EXIHd8F;EkB$41_H3~Re{Z<*IR(qZArG<00O|nr5Mk#R z9EK~#YRy7<{gxY=I%8#F71H~ta;J(brmlGS@|k>hw{ahKMn|BTe~h_a(|q&_Aka8Y05vNtvU>~OM@Oeyt`BG9 ze0;V3D43L&5Q-_c`b2*(mK2@Mlv?G&U?hV`p~|aIMP;(N;>vn>-}rUXzs}2EHW?X_ z50XaZQCF!cJQ>2PDno#5opQjlTWm*-=md3{kh4#A10qa zDsn$R8n3dw4Pw2GkR5j9w>}#F22(trtZvSJcyNO=#J%-+AjTPVW#T6}P zy1+_SjIkn6nWH*S!p178>(x~JQ2Z=2AzBk1ix*4z{PKJE1Ff1ZdPWj;Bd&r{c(t+( zJQ7l^^KbkZF5~K2lWpx~^-i~~x}2^b=h~xS(!kchw6gSZ2%(WT0P77$QFPj#&W?vR zi&p{ZKHQf#hiTUi4*CXs_~8saaxpZPdq#Q>yl7y=U7H43zpn1qOae2&x1U)8Hy$ku zE9;0t5NQ|+?{wURkzC``exn?Bv}A^?xxT$*6TI1CwcKz{FdzTu#vyq_SLLqn=hsLE zpeY@|NPaszyE5fBwzjt3-&$(^@HmR7VnvUs>7Hr0y``M_3_jYSy57pS?=NOLx?gwY zpXbbH@Y1YdW|b`~D9}os+hV}T=WDoRuPYbe>12e@V~TBvxi%HQ*Ug}>Qd{DSp6N_H z$`-=6pE}8ID4VbT^)z>PKSDxaggGs3Y>MzW3}cOUwBT2y{`H*m4PI<46{uEU{5Cv` zwPan$O7BIJM0Z_jQYVker2ps|6cf0oHMT6|*W&p=sz7 zTJ=`d=$Gn%J-tL1vm3_*F@Qb=5R7_(6Ok6csX}caam2#LUQ{G?GICm+N4xp4n@#hy z#>;{B&TTZ2va|kJaoB*Prc|Mz&h^m=*pEkG&or?#J|J@uars)4sDjAg7*?3UnrYD8 z+g8>==Zgn%b8p$GN^fZ-JQ(J|zcGvC0eApnD|Ljlb%lAAa zXEEqr?6X*YRZjcpk${{WmcIV_*vCRx+@oTq@}^Yt4e>Y0sr1gKXk0Z76-W%ZoNc4> z3T=%SOXFlYT%)#6k}i(KHEm{JBuwrr)<3r6y}5H!8Prlhzda>mf2BcooN_cm=6`NX zsMG|uhJ%9D@DDTC@KSs#BdV&M12Bf*kc@SG&4u}&)q~$i{02so_y>DdSx&KakPjCG zp%eFh6Y*v;xWyzReYhwd$BICcjAAiq=-zm^EsY7_g30rZbG>dn^=k~_#SLp#cfucf z8JdiNax!=HQ*XaLa)vYTTsGMQ*3OgW_^n;zHQ-3bLU7O^r>3S(0jyLN=&j13?*qD< z5MTI1OQOFZf%h%E)!{T8yinBuZ+oQ0=)*#CCO)ho#yHX@$~>;?H8}YIjSHZGOVr7U ztpJ>n0}c<%=z8!NG>0FuqmBN9g?_zhJv^cVaWVo{I|E6Or^nv&2dxjut5xjk_ZJ0z z?b1{fu#;YHP5K2f)F4+*+XW4D_3Zx%lR!**lHdi8&QikiWGRRs9FkHdEF~eutjVcH z{_8GU2oCL#O%T6Bi8Et_G>}WH!s&{|JfAFL7q8XHrs8HsNJ#rDS@WV8v zaBtqj%M<}@>@iLI+kkQAnF$1Ww%|)5_P-Vb+0grB*Euaz?2A_vjI+rG_oQ<`>6;!5 zL+sJS>l;bY0dKlFnone?_g}N1fq2)T{V*GmM5EMhSheFh+)_}-wC##lSpSF>b^~Ei zPgI*s(OdKK=R+8!k@H;JOeFGy2dbXd+cO%L-ft3l&!yewyN?fYu6d&^+j~X)f*OLnDCO3u0{eN{`wO8BSk7 zyjiM$tj@_><9seKI02m};>wqeRR7G^Az^mK;oOPu)6LJ^(`tI;<=pis^Tp>Dz+KM+ zen~E5?uSMrbY#$~qrvH4-}s*3{9X{LsIe0@o|SeM0@e_9+9wx7d!eWhzh6rO-1?_OSUgYetSO65m@`{ zo4PU1KSk8{@Ze*!7{BFW`1j|6=|}D8=}G+H(3Z)~|An4xW^tk?p6Hh^l7y9k5oSz$ zJ@H3)ym1ZKtC(XjXrgq&VzS7y<0puu(ymbqzqV)#nj#UTr?c~Ou|d0;W>X$ z@c(X5uK{>y_K|&GpKBwakw~7rwU{NB`W5sh5OxoPRV1vmzyDv$ zg+X0Klj631&&G0qwxRvi*jR=vDYT%sW}U2w{?`kUp};F;8FGUo$7-2u7lrEp|Gqky z9%NZQMkXmKiAAVxNjH@Goq>VjnYcJgN@^w zy1KeBadDT+?Vl|9qkUUhq2%GwboFHAVPE-r57y7mFPwiUA|fKhmg{2+9PEC4Mso5k zfQd;vLe!oLLkfgsWicr!DGA@*4zm=7IZa?ldZe$fPq>%Y#xXLoqZwi}6|=B;WOCn9 zkg!=W_?%OfdKw)CWq?oNo={kNmbBOL=W-NVZjhjaLdp%uYwVLVkJL(yZ-W!kv@ zoWd6v!M*tSct@^K?muuk^ou`v@72@e_H2q}_cCeDU~{ne87*nI&<$Ym>W2+bQ&Xz~ zgZ6iU_Hty6IW0Y14>a&Pa{2$yb#fpMs!tyTj8oW9Z3=GY{z4jN@|5nfiVPniqEgQM3s9VI9x!ZHxVjn4l=HX%&`N z{l{0I0Qo+XF}|Shjh?fsBE~n?NA3lm02{z86CBKmO}tq)NlR>307e-#&2*|_kn&`e zD}!SMu|db$ax^oO!e0)c0lO+~gUZ?9glKYY=i-M9-KAr8VPNyYFRMs^uj99WJG8cb zEQt8o12n3Z+1`1BK@tcmsw$nU?nk~U^f@7?C<{kV# zT~EQY=UPX1F>uM6r;}Fn2Xn;5lEOok2l6#v9(0=bHE-e^Atpv0f4;rA^!aWwB;D)y z{IRQ74alnQ@xFQr3d%lJRUEBtZD!TaL`Cn@(e=QY=;Hmdt=~%>d_UYy36){p4Xc7| zqM%&(sZ6IT_Vxcn7KWL*l_Hg~zZ|P0*dTta{LY%da%Sz@z@8?ko;HImQIMz(8*p9s z9n*T5SmE)lBZ33yBkGiRRwV!0AttcQEC=GfGR@y>`P-$tt@qV9o%L^?0e}u$pviUy zCz3`aBm{%gRTozWQb|X+?7jtzUOEFtP<>vuhH?(HaUa=tIIUG^YP~~IeK0|qJJtSU zL-Cf966QK=T*KqMTL|ckzk}97P#3Ix)5cBEN+{~t80LWi2KW#b1!Z*ZgX{6DIzdbQ z$rmgvZ{GOcUv9tKb7mIP-v1e0nwtFhja?GWanNO*yqmZL42!y+iS0 ztI@Ls%&G`(JrSOEK-9SZeowum2l`=p=#r;kw^pN zxL-d95rtHum2Nfy7(u7gKnxnxo(Twev*~q$nrV1clrIqDl1RqTeFiP*ndLIWok_XB zbFg2r7BIxHKMBsr42@0nI}0Vt;^RR)rs%?6_D+aLxOu%~W3UAmK}Yag@bOj59XUYh zPZRX&LZbk0W#n@{wT;R~ano41L zT>+NUsBmV}S$PI^;5rD*EcuP>+zu~(5Qg%*^N=>|>TT6AlPGkSBnxI&HXmHTx;=~plLoBVKDbmOP(+%*La=&@Z-#Z?1AFYn*;rF7f11o_0_|%ok@{sl)XkeLp@&e*rSI47Dhv)0(M^NNI zC+`S5s9&9zhb|{4H)6TM^&0sfU_g*2M)!;!3Jh{$(+%$2Akj(5e@7&Hn)>-S zANLO=Zo_z1p{=0Qz6(0^r^fbB{x+Et?fD!l6}>(q`!XM?i#cJp zk^`JM{6MN3(;q?upBmcQjDGLQEg(P?0`X)Mmlw>;E88=~Am-5hf9zQo-FvFPhD4$N zLk=#riz%}Q-;Yuz)HeAW)4I;Cm?;E-1(yYO-WslW04Lw=wM|SsP2Kzt)%$BNwqZM; zEoL&Um&tJ`+n{$!zO$`cttv+C7`(YuHk5$??N#mTBe}Ml<4otyZxU4 z;oq|W2r8lHNaqB;FDMGa6JR}re-|mO;(!+in}M@B)PCkMRX5=vV&eU1-qt%`BY)?O zc0JEWRu(8go{#+W#5;#wR&S;yw*f5CMitK4_b_cs-H)li`-C(G*C(r%=#Tw z;#9lzdAHr8}6_kV3?-Q{JgGMZVN!AdZ3nngnsgu54wdA{OD`7xCtF` zu`KO$H-BC5#9SYnv4Gm+oA6Qwc_E!1sq7EkWM-3oQ@_prA6MT53&)Mo>SK;K?|;Xt z=|_JiG;07wiPB20t(`@Ql$663fWQcn2>CK~GN)W=l7t3m+Hv(~Z+Ke(cl~ z&C1S>zp$_{c}_e3*m@8Z9}iFPN=o$Ev-f3)-YOOZE9p0>l(&k@>k-xKOzPn!%eZ59 zHpf1v7N0_EYwK_sQKgIQ{L&WLQs5Vr7n}`GH>srf?CtWayLt7l-DS#XV)bhZcxN(; zfbJ$i*`^<`H3sCMEwOS^T=6}dCF)w6MaBH5wZgl!zazvi=hpNZHa5wQQ@i29Nb1U& zIU_I5$M*yto!_VmslO@jX4~mN?HxFw7F-q8#*h*vHcCoZ&^S2K&|9AWznCaaL0A5# zIM^-EJr2^?NK>S81~VPW?3Mk~Gp_Gh2&KHH%FU6~%p^oZi3fb%Lg@ojcB|B1kMz%{ zevQh*bb}WRX!f-sViL3K4gP zN}=}ZOf5T9k9K~~2XI@=p`^TlKQF%XH_&kRi~kpPd%==os&eRm8pUSfy|0BvSsNf1Fz*fgp|C>I8(VzJ=otvzY+E4j#$ zH;g?gzKxkT%}jKq^;~G}c1^Ctu58~l(5Xz@wv#|-%kFjD4+HF|Ka6mg&bu!3Zd9*w z_FJ5dE*t1Q|0&zuP4OjS3tGDDTIQj0Vuo!B*PvH32 z4_Lt*SogDAAJ~O%AEB~pD2{At3VB1Y$7^t9aWX%dJkOdKhRt5dqXqHX`wZp5gDl32 zV^}jf7l&2ZMjmu<^x5~hASh<5+7{X9>A@KsNH@iYi+S1<7?prdrw*T%qnu*U2CurT zrUgJ`EMTo#3$$v@jKIK|=j8P0xssr7q_|?T!rJlNMhgx>CK1s$(EcN9YCr`f{FS-` zJWX#ogNy4lzj$~}PmO>mu5KQ-Lms*yqQ*2o((%7_DX2YMNX+|nLun%I2Fq&RfK?S- zL!%o5wuH&Nr4JBITaN`b#-;sdQL9jo+R^?QhGWrz+vjTo$sjb0d)HQsa`Hy zW%Ndk0n3QrMVV(#>Ne8*Lxg7*91%j|p{(Jmc|LEkWurt~3)CM^CUF<2^e@LX!;LNr z%arq%w&u>KNO}!qJElsSi%uB7&F9tG{%;(J6NI!0Nr9R=fXzcb0gqbbrR1#eZa88d zT0kvE zOgu%am$T}Zxpt0Qfr?(m5?=?XzOE8_(RC_+7A^mFrS0A@l7Cy7pRr;zH1u=Z<9;pJ zNCAqGmoHS>%c^0D@QtmQA$Ly<#7Wu4KK zW@A{9?hB=E*dcEJmo69_4U;~+MIA+j#jw``ElfzV}6A_9AsgwwQgr&Yh?ZmSBkFZ6z7xC#N^`Bg489| zFl-09ZlwL6Wekp-u^VYjN82Mg8s(SO-6x?G5ssX2zY*Qds?4EGPahj^a@C=gj`fX# zo%kW#-^23!REt-Q;`&9h9?zHF(sy&?`RH+G3ia_o38^{qt~!t4>5K3oLQXyghCWjg zoj6r;DW8{SZ+QufB3zJ_VGI;Q8?!ZNQOG682P!x!%p8!#xWX=-DP`X-)o`lJkk6!V zeHMv@q-sOtYm8X_ba_o-@T7I~0vGY;5Am$jUL?l5xN)gJ%l-5w?#6Zt5!~<~=%}cI zpwTEiB0}!rJdhXNr>DOS@J%c9EKvT6l$7Qx&7HTFWK*fL9?#Us9w5jaGE2Jfu2DM{ zl+S+7QPMR;+F0D~r0r5tS?eO#H)>+)Aaz#U*y>l(9X!$`l|0Ck^dRxje(giz6Iy{6 z^3W#?H`H5{o+G=Wvu57rrMW@erNvm+(Tg~rYBkKQ`i!HIuAR0WZ1z&tZ&vc6rP>dL zd|p+{R&0!9jY(YwbJRbq(-igWo^{H%2)k0|XBlaCx@{3={Ma=)W(4|o#Ql?_csWqioO3}iwCyo-;Zj~u9@4$s?r=eV?UTENikYg(5JF=guqpqN^o^I!S`oi!Rra+(1FRW3b9HJSKeWSNor zWtv-M7*?TN_)<#1 zEbe?GX#yp4|G84n_VJ;%ML!Bg=LNc+K?PW2XX{QLB*2+@*&;~cL(1elzzGJ<8~K&H z3vbA3Sp8FoRWvp8dgqBrdM8{hL!Y_zba%fFb|FyKz&E~+H(M-)o6azko2bF3Ja?#G z?nK9E+w-OV`{x{{;`*TS{PNUS4i?L*%hhIk_ws52UCD88Kqzw%ccd+XoxMF4V6^HR zgEb^#dgPLeie$~12njlERY|mhFY3z56hG|Jv$2hKd_fN=>1=h~Pq!8JB}|I%ybDW3 zMHSwEmx+lfgT^`;kC~rehbU5;Pr>cy_v3rv%(uQ6eTj2ipyPSE`w3#iYmPn{UE>>Spsjd-Q zlTiDhozS_=@ly;FDM?aoLkz=&ozwyh)F-Mf<_okv{w#vRr|gvNbX!W@SwRzD{|}dV zp)?PzE`YqCzze84REKSq2cvZUff(miU_H4Ht({linb`Dqx!MesddngH${fZQ{UwxCn799GZyV&|x zh7;Okb&ubJ2OQ$@8c4%9EDw&*Ow*lLzfxGzeK77-4F44R)_{K)t4FllX$LpAlYg80mA?(P;Yqg;8{5ma5r%;V+Mpvuc#4YUH@{ zUlQyT=iTa_Yi0cdE+0u4L;`$;!BZ8nt3U~c>5U;rlLy@>$4v#&4w=6JEtheHlNK2m^NSRKEp zIRlAB1cJrq+M1rv^6z)FlLttHqngr!PqWnTcFB+ZIJ8OL=xyqZ^vZBwfA#eE-%>9O zJvluFRR9eOi=<>LH-9Zo|7~}MWNB3w=ytuL1POWFk3mV>lU+R^^STEeAUvkx^9CG! za0i^_3M|u01wk-%SS;V{t%S|br?|^!0_~J5V7MuSLW!aFM3@MNiHQM=`PnbEXPgF;+;NECE*IR=q-0YO?kYWzBVBrKKWpWq0yYpl zerI|B9|4^0hH`Q&YTWTAiiMoPYvvJgF9Y(?x81qiU;`HJzoN1q3iZx!(;t^pryDfxBs5@Q4OAS;U?QG9O@A zN$OaSiCL)^KNseI7Dl02F}dYBTh37OCKzr)9sye;*DHmm?YM}v-SZCyWf}m1_EDgg%W^by*4ikx zTn)=L>Ow~L<``*~jftdI&;4+D@K!gaOtD90Cgw_3OWZKx?u;D5UD{f1NnNHR!;M#0 z|6*=fvH4!wxnXt6g2{SRK!_HfwT-|ix++73X;3NXhSD;S>wifWcuii)#2so59SjYR zfMAM~w@T7|c%7YTL)suw6Yu*~@-Yi1kkF^le|Tbv)nSCAsO}%#YV1d9_=&U|aaLmF zfn|EDNYPhiJ-5l8pnp1 zGAsh?U*Yhr1IyAK9Cv1sdve3nF-w-xWl9yR8(P9*h{c+WaLLv1e!sHPa_TR)3fszQ zlToCb^_)jZ_$=@qm-_8w#+JQV;fUeksddd%ejK5C**oh-z0raBQu3<2eEu% z+*-?`Hc4rcNKoy3A|LO^9A00gaNOjxx} zN?JQNXg>K0w)YIC#G!HOeR~910$dAxJ6+k;(C zlb>YFcFemy?@ewBfD6Mh>-HEo;k>3DEr6c}R3V2Y11)U}AiaD*JB2`- z_)~>oS&jrp)JcUKnqkmi0vq`#B2wq?(i6%JH)bd zyxR$qksn8kaV%MQMJsG)wR>}?r!_lbh4!!XncqP4^ggi#iy0cGaiaFfr<9f5W&Rd~ ze%$;`1Ev~-Ih5VC_vDCcczN@hy{ww`9HzRnz2WW+s8kSnUL-vtw5yQY32XNBykeBG zw&xmrn0BAK=d_`e!sS5K1ZEbA+jSQgfku|!TerRi+J6HrshnK7I^t}ELmheTP6H7V z2CnZRYvqG_J+&({#ozhiA$dwy$+0ZuU0D=)t(G}hZ%6o#w}K{;y= zZ!C8Yk7X7K_0Fp@nx5c)KC!bplv3_nv%&C~{24ilJur02!ha37cJrVGBbobi(|o&4 zaH>@{pWG>N`#*lHzKN|Gu=IUOFkjXlCszp~+&%CL8uI1~gRvZ`5}+`Xn#}8MI>Pt1 zEK;)BKqPMET>Ci;IOX9R~cluXV6iO<@Kwj8nv1Ln~j~}TvSaMpOf2(NYV(q_? zupDB3C4rOMC**RV0duWGs_nn)19m%(&z%eI?IC1^0=fh|3}5$QNBq?2Gkc4S>2Zq3 z@mYrsij(rXE57GXeK-_xjbHwzN>ZXF zZdcojdUUF9uUsnKlJDK7YcE$0N`G6t-f~k%0(37XCM>>F z4dy{%e3&g+aEp7=+#5h-1aEV4k+1hjK;6)j$m_Da$l3AE9ReI%Aa#fX$}3A&vg@eS z6b>u!n8x{&W5m8vl&N>?VtrCB`63}aiSkTRSY9PVBdp;xE_my4dLpeUZ678*ayg-TwrQ-#{FtodPcxDE7 zM4t^#EmdiE6+u%$Um3oKOsC2aYPhHcXG+RpVFjrSOh90HyJZBteaj@`0#_b>dU_h( zM!g^3Ok}FM{sdaQd_YA_Vd+EY@0a}G~G4I(8es30BEjdXWQiIlW- zBi-Ho&IjH5?6beW^Zs!>T-S!@d1hwKnl)?P>%K)YO4eBVMQKaMkqHC}@e<`Q455DJ zT-&B14_MZw7>h7J2eqr)SGlQy3!kiI_B zu|t5w@%REy`>S5pP@$34GptPCG!P5$s=h^ol$kGn4T$eX7s`|`P7@~HZ8u_58Cnc)?*F;en<4xtM|Lkl(wv+Ed3%u-pj_h8rI$; z%9L;1ilouV+(O2z2om|cCvq@KT{+Uc5Yt`kN}gZv7}db~7sE5D=+Rg(-A9HP@7Bsc z5jy-Vb4usMsIJ}q9(#hdF98J!57k&k+Z$<1O{6>ni3dAD|4UNO0Mzfx2%8O*!fwFn zEW1>6pusLW^nZ%XJO|E!C$ zk9w98oh$(0qqiMgQUWHRgF^=N@u`Hgb5(x@Diya#eb+UpZ|2{M4@BmD2ccKgy5rf2Ws}K-3Ev1dvO4#3LDZQ96$Aq0G>xb&aRf57aVFMAB zgW81hdyamFw0Ln+`7wSrZV)EmF3Vy{i!;_4_)p;L{h?)yuM@}q5F%ld5I~E?y zh13uLn%w|srrF}8xgM?B?sO*q&aSq=w`xHlQx*luW9P^0PQ?X)Wdl@PeR`=Hc#|ZA zMKFQYV*&YDhCvP{On=WS^b@ira#LfoUx~qXm1K*ZMtl$CkyR5R3%qxaOhia${3{MG zK^Crrd00}#{O0rfn+MP$qdU*9%=G6`wkW2V%7D$Rn)PV@JrWpkP-X?7wm@PA){P|z zNc9&c_q6Z{WM1YW`52v`BLKz+j6~>NeSJ=|9|55v$Ar3wFT?$4()wW{Y`jA=3WFYN zmkv;;oipsO-4zB|*W}ttY2L@MB#Ei?R`3WYu z!{LPOz3vz6&~`+l<9a#k`ZYio=TH4PKfp9j zn*49TPJZ#3G#?J=15H(-q@eK7e2_BZ32jkHb`Rv8;rDmvsu%F`Y8vYj=2JR<(s%33 zmHm!S=f#cMQgdrd=q9Q#jSh(2wuW8TA zqyowS4D|ZCf-ocSPYg6T0rQjCr=J_jtQi??{(*KK(K9pVK@%9v;ZdF~hgC)W@5wE+ zW>8XEu=BZWcs0#PT_2hLvPYCS&0kGNd*8wKv~q%+pI!r(vm+-43y^&oCkXI&4(8@` z%hS{n05;J!R-|PQQccrZ#FA>Jf6UZ-0N#+Npyi8Z{fs%2XZK%I%_RJJSRQnMz%UD7C9U;ZaAuOLDMzF4TG4jDoA&%Ve z>NU#a^LqB&TunOM>kSXTb>TH{!)^Nh>BjTG$lSMB<5C(94{=z&jZtdJrYmARB_(Z@ zQ1J;6s&1;A=aciecfV)zDYRy2JLyF;fnHBI&BOp{hB;> zGEPScP&Zdi)@Y9tU2wj$C%o89q9cO!r)BIt42BkV(56SgoNbVuwESH9Ar!j3ISC_p z*O4M^Let4JW_L(e5}fkLV?mSb-s>VWJf?k6hQ!~{N?*YGBh(_)UxFEO|Oy!*`)13}%qYT^R6&?mP z0_ID&N&iV!?+O2js-!^ZHcfA|+IVPTSluYgK;sdpbnFvQre7*M?!>eS5S{w?^w_4y z{;J*g>{~jdhxMs4$c(i`MPXY{mcs+|4kb`?(#onb07YU#H`}X&Ljx^S&tfK!|615z z`0gJRBxG!CZ1dIO52!Y60Y=6Rx;*JkB4>$FBDlx0LoU%uh0`VtBAwobj-7t7KF=Xe z4`Mp<1uYqPF9Si5ym$VoUZS#ysi`g2O5N3%CTa^Y#M7UbdE1Wc0)3rMUp!rZtCGgC zf>Awy{wD!S%z({?mNP|+=Cv!RlvIv|KeUEm?g4zkB@Ij^CyCiqh~=0S6)mh$2UK2O zb`b)qEVL5R?=INUWy1B@X>1Wwa!2&-WtakYP{6DyTk)5l7T-WY28bedjtQ>pqNi~zIzno>|Q0wTSre5R)zP0`8^fA zmyA9A+=bIiZb?XV)vD0#U8hdGx9lK43`%O1Tpf!*!PHEzszgeewZ@^8pSAT%RLd-;KNhF#V22d7~$LDDqo8Mp{xNz3b5Et6epiN{Z|K4LX z%TG4_AvSiV6MuhKS1$7v;kb&j@-HCG{R+aReI?;)CG{qWad%rwOLuOr*tAA4oF_ol zlzVe^=kTfNt!ce%$cEvvL;Otwe8E{_<{Q`JrL)AHvs3~vM8b=;#(op4{PzLnjui;l zA3{Cs(CJ7dg~H*mO~%YLoEr@LG(&3Ce?qdN)X)cOX%vMfOSiL%dmr@a1KgGeB$G?3 zO-CpniZjCp~d1?iSUs(FkR?qh3r?)cNg(qGv7RuE|E^~F&YKN`?D8-?)% zk&7%QU<`lbHXFyH>v^KGJzayrVb))ndJ0s~PuJY!mhp0^Tuw)OGRnrZP*Ab5fEcsl zdk^l-G4&P8;G-_5&jC)xrwTL|S(BubjM~vnj?sQO6pa zDZoKe*!zJ0yu?j!`SRfETcZC%IJWO*=yrtoO3Uel8h@6+wdeI?~)YDVs3>SJRyNc$P2V|0-|a`Q!GMN5r~{mq*B$kA zC=ZXT^|RX`qkeedR3UW!?a{@)Ntrrk_GZ8F10P5#jB!Ij;o&KlLH_l7DjHE=nd|cp zQnt3tt@n`?a>?gA+~>Cs(;_vkHwL*uek{vr(WeCKbA^gl>uv7#;mk1_a3ZtU;x1+c zLO+M0MO;a9sq`KrOwE%{Q=4~Xctj}5%X?2uO!ygpH+o4%HZJJaAS^6Sm+}J*raCR^ z^lSnRzB;1=5Lt9o;FZO!T(p17AWKAHrZpEPuJynS}U3h9*Zf!r|_RlFk&)2Y9E$;eJmzCq`!Y zQ>!%hZ-Hb(d$*+_SpTWs-i@Wv`7wD1H~}I>I}%9Jyd@G6Yg>4p0|oMho3nWr%MG&^ zt|hvSgx4X(9zAqHRIX8W0YXAWBAS`m*?M1{nSdfRyv-F*tSYSHP2#d4oT{?QaWaTO zXVR?Hm*SN1zq>z8y!lb0YUIDQeGDp6DUe^fk?awUpPf8?>j+EMV>h6AX5kf6<&<72 zALX4+-A}5a{s){QkPK(d!Ev|0h5?Z9H(PTh!v{0*kN_wR0<#8WM_H2%rEb3lVJm9kZ8@O|*Biybc@Gn(R=YyyGn)hXaCP}!n@z_+Hxe?$U8toilfoUmb@ zCdg3-In1&I!U;_ZPJG8io~SFhQG}~ct`I2EWp9vDiJ{1>`S+ z)1Bs9&rd)6EEYdmS-|%n{fh5ew9zT>l)V_S2UIm*`cH~${P@UyXvS0iMteq6POF$E zsI*FYgha(kWPD_?q1)SVM8Ybp=k|#DGAGU_xk;!+V{)OSCt;A6j_4JUVZ3n;1k7fW-s)%x)>}`v;D)i~WWGBJ#SIvr7_u+_uj{qi;Ld8I} z3I}fwMcz|Acx;;`+WfJAPvcMzm7`YHI*YD^E8$Qupxtrv%5b=0h>Mk%=sL7$%_5LB zUp@a?iWB%dh3KsTZ_Bep19~ieEtHUof?EKUv^Z#B8pUt@eLYEoI3-B9QfAnW@|fT2 zaamb|#vslz!k+_B11|IHo3A~EK~Z(e=qiR7Al6%4*em)bG)eQQ`ueicDPp`DJClz` zoJ;{Xh&TUN`ssRY|4N+=KLVLepvQ0UA1P{Y{cBuy2X+9=Hi``u0g+y1fW^|1;=wUw zw7>mgeCX2R;y&nrF#v3CDwbr>qBWhy`EQ=8{S~79a)6GP&r06RXAH`0K>HOGD*`}5-s#`R+UwP3Z%v@eZFOfL2?8ki2Ach`XgdogEEDZEkQg~P zoWaw5fyV)X`szDF4lmID5C;4&ITI7;S7DeM)QHnLTC(~*15ofDjF`zt+Ly4Un&`~) zqD(pb3RMCt)U@vdLVZMbT&o>Qf7-u*4Od~R`(0!7U=HV@zyawz^_we8X>~4WdbOK)i2d3xXuqol`& zn*!s9HE82Akty-0%TNk5OCUXXP~EuvyL<;0;r7X|qF-O$!fScI=2Qq`JdsF25^yU+ zb$%`WxD^w;m8#RbZ~$kG?S9gk2b*=?Wrj+(`@4w!iYOr!G}NZJ{dq>J%g-VQ zJcfLWG{pbrwE?Q^U`WXTz&yh3?6XD}K!Nf#XlHY{7UIw`iTOjT@rwlp0Yji$t!RN$ zL(Kv@uc^=}X=Q5c@aL&{YN6;3?W3`gA(--PC5PWw{B>0A;Tu9;mko#7qv03!M`GNN zPK8*`UP@C*cLV}lPK)stU&(x-HZXpiF(c zU8CBXK-c3yWP7fWNT=ShtYqyETM^_mJ%g!&BkPfpQpk0)Q8zRy$Qj&vZ-c>A&xVjG z_EmoxCOF!NS9SdGr+sqoOdsShZ3%-`lvG~#qt>ieYL}>Bho38Q}?EPqg@oXJ(CM?80qK|lEjozSU z18I>j3H*NF^SoaN#&l3v(ccPsEcaIv?G^!N(kKey2$JU^PSVy#bHw zd$Li^7qw3j>GHNe>m(q)HF=oOO_sUzk)wL8S`1mf9NyTw8Cup%GSIktFMt6Rnrvs3afM61^CXho>tDB(6lZC^BY&Ltv zAwZWd#dHE%T(U<+zl(jqDDT}1HjERJfT~vN6X-5N(Se zv?YA%Q5u}vtu&?DpJU(_5 z4_6qvXJ`5N@yQjwtBAclo1~;9CNZ(ivLS-U(OiZXR;j#-z(u{wR zL>1N>pM3<~ArmjRrm#fgJIX% zFO|%y{oOBdKZk$~i@pUnJS4BrL7`;{rjH`gf`bD?oWR>R5zjI*pV#@P5eYxWeW_Mo z7}TiJt)L-Y96+@T6+-?4;|HXU)3{J8w!A!KfG7ErYXPhCR- z6&Gu*g05~c*;h*|q|By!#Vs2)vg7al)tVfrN#x#XCn~Ru-PY8*KRbgo;OJ{YdTIdE zg1Cf)kE4d|WI6S2)0Hh6F%R~)Z{MjX+0`{B$q)^lS7Pq9~5CtI8^Fm&- z1O5s~=kj9~hQW7|>hJ#Z7BHsgP#U^#;Y@tR4Dz~Qd!Hirb$slfs5>PQFqYtWPAPd2?ExZ zk=2lqDMowQWb`cF! z1b0M7{CBzdR*>F`77+jeq3(2IW+CQ&{;~$1C|Q;88a}6l`y4)>QCC+tW$cm@5F0#( zH>Fqjt3+Hj9UAfO@o!XczX5%)M}&5_s=7MT#n;CK1RLePEFn++E(~-KR{29KG1omf zbmau?hWeYb ze0!jlKh88Vgo1yJjG1Q|&U`NAWHtBT@ipND7vi(ajQTA_z2MGN%TQDWDf*R)dgerweZfO~U&2Bj26i8ewEn*JuLjqa2==tj zr@s!FEE(IP1#-$%{rO{IH{QYWNyBl?+MzQe;46?RtHJmUC2I@?vzU@O1)bD&ZZZEi zErU07n`ql=-XCZ4+thrZFwHY)l%Lx{vmRm&o)M(wU6!-+KRUl)`;}qaqNWDreoF*} z>7&xl*4XhY;G3LzGzqAw44r1e<3d@1i~V^O6s*6Or>n;Ef9_8C7BiTk#=5K7788Ex z4{8PWSe*swxno9Gd3tNs+vyF!0BP*q;{m;e5NK;-y zJ3u_oFG_IF^Mo51{tKV+dt*RT4Z2~zmPOgGp%iFd^EUMyeH(D)t(O9Ko6{k7eW>^g z!&&O&Q>uULr^a9g**X}|ttzgY5PTT;=3OrC7I+3+E%;{WTiBLONLCSCS^jdrbeLn< z>&}R;HxTr@#s$WkVZY)2DjD$RwDkCzW#dh{2VXuCS1Fy16Mlf{Ub7p*8E;idmTtk& z`X2I}Tx@-)2>#y<;D7C4aGfC^w1j$nnrK-lJo=>U4prcY!>1Ltr9=*v!40}#R6Ve{ zpbR@+nidm}Bu6HH=xI9r+~nnh-K%T9;ZYxW$ffBQT?u#Ht zL8q4Xc_`Xhn2*9xSNw=$p|CNzW!T(vu#cxlCa9M#2+0O3ROGIDd*wW@uywW13}N3a zeSwVHg8@9)?#A^G-|FAfKntgtf{}ERP^GdMUjoI{4?r-q69wS~r0h#g$Qppk7(zefFQX8%m`Jkm!|Xxe!c z?Q&5jt5FBe3On@|2eMtF_9jqEm6)Dc1TF8 zRXn@XC+7b75b?1zY}4H*uq1ArF^gE7F%*#taI1P8q>v8rl22YDhYvZIxb740kB|Xm zBt57-{l9k&_8?rsNlpLEH8&^U;-npP5dsW#r_T-xbZRnsp`sD}uCft*49>$|0T{q@ z^|Q{x%%ggXW9Bd-f$`J*p(O!_h9wZS%jYEFG~3MV>Y(?JPlo)MjfI2)Uq1(j24IBN zl!H660OYW_SlA(}ozo%jq30BNBN$@MW)8RjUNT7t3o56+RS(JNT6XX`E!zR{v(N{j zgN_dFeeo$uK9)zQkQr$qhE7F<2*iGX&}=KI+N9R+?m2hNMErBlf>SacJ|PAA_zHup z=;1&zYGspnl|V0|bFPNBT1t*4O-e>eGg{PYLS0NY%T*n5|0rNwia>1kz&zsWywNdknPwLr6=3=o2R^c&L+2CqxhJShB!hk*PDNZu|)Ir9QF%ek~@bs~PK zf$D$`*Nn;Rn21dM&>;*gHrIaO>sgC&a>^I5!>Tgh@9Dmn0o@k{3NO-F97x25cn@d- zP3_L5jhxhukvyf@*8HtoTa)hJn2rDVr1J`a$hj7RI|NY@xPC39%0_%T76j+UFTgTj z_+CDmIDJpZ&o-TkgQiiFwVwL%>;NJoJnRay55w_Lq1nH?KJzH?i-Z9`^6T< z2rQ@PlkKrUzYAdTfOa``Dv+P~!2t6nWLfio$JKY7A^xy&1xGc^?8#mcz>jt9DS_I+ zexpg~*0Q>!YuG`BUxxo)O3#&;*ZjfJ$Gu`foq^snIi2>+vUi^pyfxpy@e&*)Ma8`S zghEH61LkJVH&B*4O`8da331)De_*rz>B-docDPxDJtJ&SHOu!nYNP$p+7e9MhpHtF z#}C+h){tG?Xf{V9Ag%g)sOOB{>l^tv*w`ZpSvz<+FRsQC2we6PyglAVWo2fzS*_`J zq~Me`)+|T-sD-H};%KS~b`Q~(GIf`mB+;oksycwP<{rB)T)46_a-Z+osmJ#V)X z()$j$63-3hF~oI02VZcSEppQm}|#Oh{I5L0pmClHfl zV_DedI`uZ-+higZbI-sX98ZD$LlX-Q^%q?H{G|5*Tws2@y^JTEAy!cPICBSRHa~ye z`6;f-85blk;x{Wky9?K;MM3^?q>SI>n+UmljR*D_Hw+&U>`(5|6^;LPd7#rIP(YhI zZD6To^a;M(w|73LYU(Z*I#qsJFX7a`G#THV%p*PFQC^WRHm?;p!=2sX;Nbsys=<3U z>wrEGbzfKBPxF?8^*?0FC}K$FpXPAI5J_ zo@s~_D*Z+NWRgn3F=-C@VWe#7H7m)vmB~wvH3K&oJeNj%JKc-y>Wkum-PR$!*v*~p zdjLP7PKz0I8(?+OpC~nmNgoUJI=`FpuKgn<;QhB8R|w@`R!RI-;^h{S72m3t75h}tx3f;9iZHpNJ1xtw z%c_Sj?51na_A8UWZq%KAC*_~mA)U8%e_0>fSawG=Rwg6X1yQ=8WAG_=C5z!4ZFu}| zS;s>fOsm;Z(;^SQ$p`asCvL3-I9H>e4jPXnJ&I~3hZ*8MSH~2S*lG4kUG*RMOglI9 zRja$9URk!Pd6}&~S9=B#_?9Pu!N=_Ro?P=U2^N1YA8O~5rI9|vE<6va9w7A0+P>0ET%G%cd}N$skVj+vWjdo+ekD)pB>n<2ob9ne^6T3{(i zrscG`NNGA#@5yGmaa@HiLC;NYi&R7sNLxhG7bvV{B!Bg<7f?v*AMEsUxwTlYm6^Vr zQC_{6jaqGP;v!n*LU2tlm`t=~YG(@*dupE*<~-2TmVJC5ZX2kw}l?jkW z+LNLd8ckeRKt9YG08qvN2(!E)HFi}L(kubs)ErhojNKOFwb~>yAXJ+*9Z0bOTH4I+ z+WrD=FrbgN9I!8*A4cCY7b`#X0c^MN2^&E1K}E6BUkU*Rw3w9VDdlK_MhEtp_AEtA zdA^a+Q4NsalRDs;Q-FdQ8qQLx1ehnNFId855W1yyz`3{R6qq#6w(DA9x5s{U+w?rF za>KH^x9Nnaq*BMU-!)Y6VeRE?!A>n+=+SuSy6Z>u?U$@8o_VrE7_uV14#0Su;PwT2 zWAV>~fK~m+Ou>KHg~Ofhu>S7ecNxm9wWL^aT2Qyqo6*bna(w%T$?VfL;y$mpRA>8^ z`~61aFUCT(n&-UV-VAQ-6w4(E;@+!)z5+%hg_NiURIC5_=v)mLrWXlC9-k+jQxj}% z(>2B$Hx+_Chh%K#+HD#Z#}pp}1HJAk%JT0O(rSE$U8Zg7VK3=<7XH^xK#)W){w8VwuTChl`IC%K~V2KbXnT7AY6Qew)8Jd znl1R4L4MWd&_)7`gi{T$9o})=<>ma|&}H}Eh+-=s?Xe|SGDh^Uxp6r8W?u28h-bVc zlFn%Hnt^t%&ps49brmUanheS(7&0O(47`5{QVOI|!SUFdR?qF2ewAj`+On+h9ebL1 znK9`f)R&^$zO7OdvzYraL|yVBy)Fz8DuRNc@P>|<2U;!v?U0lo_^hHP6rH~_o%YbL zUfDA3Az61T?(6Mn)aQCDS0VNy-qW#svTY&yk(nXnIceR`^Nb6!o+mInZtdB)OPCV9c3af#l39O6EHwg>Ky<77U6>O z(WD+MOrAs$i??~%woPgm&*b8-5gB8;q{r^wSrm5OMPW*s!g?mkSk)cq2(ec>7-%$q z5pT_Kvc1Of!s7nIcX#jM#K?g0BEY3KMf+(TL!KhKqaDb>6S9f0e>+^T^S(6S0iH!|)lQk~2p6a*E zy;o1@u}?5YWeb}9@0~C?cs=#UzJh>F3+^?S zGZ)0fdwscv+8FMc#aR}mVrIBgJX#D7-uM~f8liT984p}>kO@zo+d z(65nQOti&Y`*N~H!<9I{y~f=;_ab+W;lLBv3tUYM#cx}A{;0(}j^l~#zm(XZe7Z2z zyIe^u*H(3CYLko^mA0JLF2uygeq( z+vZB&gwt1vWd(T-JPmkGivaS2e?NypSl?&Y)JofSY|pF$eV0qinxRJP$g#%isLwXJ zEo7Ft|K)6b061;p-wD$x2;+di7cf8uM+-JrS2wy%M=${wXd<^7&X!pb{Tw&B{3Egm zz;4DGTpi4XKeili4BG%kk1@d3;NMoumGRtSh|20`x-W}fl?0hvUJB^v$&0rI4V%Xl zdKqjfAhLp})dB#BS_a$`+R5%u_4PbfljSBW!hvzdRAeu8ctilh7{^>^`{Bblc9TF( zi=nnH?3ECSi22)2Gt>E5lhdNBM6RY=6DOJ8n7K}Cg0q_SY>`lbh4(q6+RtJ>!ue>@ zcq1Azt?^m>OLG*;#}&C!Z`Arks`oOKV|t&jqJKJ}-SowW#T`=?8`IO71XDe28?L1q z*VGj>;Fx@#P=P5$;fSXC*LmSB;Df|i^=UGDAi?I&cQQXGu}XJl`z)-NkGI$ham{5$ zGjM5EGZ5+i8IWZO|hNIb9DL4OV_mGV(PYzdq%(-<^3j)0aCnEhZXMzfT(Kt_!{7IwazPa0+ z$*=(xapM34$jn@Wz^FG2&*k6eJQUo}BlEA79Aqg=XaV!jJ}f>m)wvvM;E3$G2B;2QWbLj ztnLj#lUiVs%jCou6@>9%FmpvfXu!ZZ{Wuk!L>r)A2UP-jn@CeQvdRy+>c^``&rl zaBW*x(AFn3Zh+8a>CZ$cV*=lg6Gb2l|L?`M{3yUsQ`ViI6K``@H)(3?5S=wc!|<7u zgu~s)%uHH)YI_*kG;$`J@!nwB!Q!f~qWwDg>m$Gi-v21SbRT~SaWmEJy{+J6=Co=~ zNbKkBCcqrvbbq74-RCN1rtSf5CSfOOrq{YlE#iE@A}k>xmrT;xzs(;CP7%v|@~8Dm zWN-D=i1m@6sm@t`v(qE3DSxBt-Ax7RD*huS-|x@=f886|pgBx(_0#EJ&89bA z_EyxlJvJ#r#cG3d6}{Q*bGqAV zthiE2>BObU}ILevG%UiCV&jQ5BMxMntVQ_J<> zNzEmRdzVLxXS@kp`H|i5DqTq6rGC9?`t( zag}PSsG`F)kzzLgz~W*Jb70Z(b#N3$0=<6n!^Gth>;5Zm?5ay^b$MaBY3Zr(c>+6Y z2g#MHQDiEgE%=5`dK`&#Tr@l*qrkSzMI8Minl1?5c{|A2ovQccnvOr*95d}&m@-wl z`jxH-Wk;Lh1+`B4&@>I2|FtT){3KjH2&{WF=?ibYA{Fi;@+$Lmzn>$?x($lqu}s$ z1O{`34!-h=wVw# z{l8xi8N1KJhrq49nrJ}z@9qC*UZ7wUD7~TCET4=1rAGMg_sIW`yW^h)5FoRJTp^e` z{aOBp@k@$B!5ShP96k9TODG%yMwq*TfAHVZjek220c1!_7PasBKZk(nj|t6-Ng1Va zAH)4UvVV*u3lfZsw7HW0k8h+9PD@Aghrm2RkXDs@4#^kr-yAD-oLvKsREv_kgHF=y ztt!TZ6F#r&^U7et9vY%0dZp}%kI0V{n@p!FEl!uh_z5|VloshXCAw7=4sbzTOYNI{ z3{{%7lP3A5P*r2|-L1RC(4(qY|Gt8frUbQ0DFwX#1kRosr%O+=cw~7$_M^PYf-E}z zNLsm5H^zFyC5L8g9(Wt&r6UP);{sHn$t!?5Tyr(3jVVf%olHO^rX`bif=h^bA(C%y z+jL<{1lm#ae8L&)*VCO_pb;l#&Ol(L4P6aDvno&a=I6O4jUT<+1Dek^ z0{}X#eV}8J6}>%wcYCJ2mVRvlk}%GIDtQ`kvu_ZLjgC&f>0vUXO?v&F>FahmK_u6k zWpQ_))UXY*dD8BBTpfTfU` zVB!KSyMR&Q3MHY}Fs3Ms@3Kpl?_$|?(H&$qwE%2z?Kx@HBl1*`wK2S$1^O40)w9l8 z)0PEAC<)lzYJ&Dt08!}SnJKTP8x zB@zHX*>xTiM9d136-fc)Zd)=*q5QXR^~Qb0^F7t-c$R`WJg$iQxmP3ENATV9QX7E= z$_|jvDHnS1yv-TBc!ZmC-@2?P%#m!=U2tIB&K{^x+v!^~Y_W~n4cP+#_ubTcROWTc zoZJ2s_uY+w=KWSwn#M+XMeF4Bn5o=1TaPYfcmZKi(>t=+bkJ_=yj|8g1%z}q-Z4*h zXW8bjmamZS13l9?p_Y)QXqDpH#lScvZzIsv8uVN)a00@6;~<(p0hGlR_TScZvz-E1 zTE~3F-R)H;@PfA_IlUc8J`7%-Imym}FF^z_p_0I3bez0a}AprqaRED`2 ze8|bhFzql?nRPH=8a+@4Qj3QDgIh=IfX(crS11j_+}!JQ>M;Wd#7^R@&J9xm?_urs z(k+lnX+IKkT@jUC8ZX!pSh1{`bq;z?&A%%8$n!AiU^KSoRZdl$$rflW+;`2ZCol8x zEmvEwjN;*4iRAFRoUhsx9Ip#_zGScsCY9b?nNAPBoxlP13EQ|v&@5o|C^HIuo?Ys5 z$5JWnq-nGz#9fN%@Ijw#oNfO^zp6?tk6qYArrvk+^Zpv|n9?jEf6i$?I$wLU*$X;T zopZ1QE^Ecd-vWJe!~RE9s|^PgB6*9&QhS%3{I^)sV9J+lh5`8XG0@W9@zA*g^M}pY zANA$^mt*L>IVIN#Ai2c}9_O#8!B9ay)pjm_5?p8jxb0>USJt(0)_K~-3niif0pPi;97Gwd#*33Z z0V(|)r7h4$f2!u{I>0gRoxs7~o+`)tI@;j6V6Fn#wR;$6aujM42bXKuXOM-BFSe`v zDY}Xl>h)2ZS{ZOBMn8ovYC{v=k6@_5}sE!k`(2~1G zl?bG>rdo!9KE^Gv%|}AK0BJ0}lh7TwN#vT3Xy+E4>@=p)nRL+UKGV~qGiSu4RV|H1 z;gyspYbEWu9&9!jsdZAbDf#32Xn^#c9VaPc!+z^gpAvq2 zsg%Hb%=`8=Z$iQ-xt;5Hz{$o8V3zls%`T_CkxNlM^d2rm%1NFE!tmJdxE{;aV3LZMIQQP{*P6YOk8RluYD2Ke93NT4 zP?;*jn{`&CE83EVKLXJ;3b+OSpgg4!O`>SYRQczwivhITK-(qQVcbqFW%@8d#ns`1 zWK)ADXw1xR3cWPgF#TwfkE*hlueXC>fBn^Y#$Ifmgl7$7ey?AdS>qKTU+-MXg=b&dYHnqBNYm|yT%JK%) zsqmo)ipW69%N?MKa*)GWBpH|$T)!#aGk6JTJ>m#c0eP=0Whx;jMq; zp)`tBIbBot@Uow>?Kz(IYoEJD3#ML6JOYIe6yXio0om2&`RbsN*fS;X$QfAV`SoY4 zEzsM2y5$=fu^UZMaoEjZCZZZ#o)!CK9=n;s}N(o$pji;d`chMb~E6AO@tCXXmlweXhUMY<(FQF`FiHv~mW$qT9jqw6wI%zIa zJBKryPuWap1=Q=lG}(-@V_y^>#VTbo?9*gj++OX4-Tio$RjqXrG)2$N&|0H%iAG^m znvH<)^1EyD<=%o@{lKen-)?RK0TqmVN_tPB<{-L2M?`c42A0eo3jtNwmVk609B!pH zBF5kZ1N?R@xOfY+>ju| zj+kT0ut?8jP$TqEewkB_-{#7CX`=EuLI_=MKYt%j@p?ExY|2^) z0XyJ{rGz16o)LZCkM#XnN7Fre0@1rsixKnGl%EL_fswZ}l|IXzlovWgoxOn9&MTyP z8BUzwXIGZ+1@#`iQzexm6*Zy6Frgj$;VMNQeot*u5TaO|hq^YOv0ikBvE@+4{na4Sr7VH=x*t^{;;v&tvu znkg^cj=KVV52JAuKG=nejl}ACZM=N_be-aRV}ol|Q&yXd%jwqMraA(aQ~kp&`eeBn zWpE&G3UTX9S@DlOHueDgSe7dh>#f)0I0b`G!JdT>AsvnRnr_*T&kL5q9eF)JMdy2O1=SWAY zjA)sr?$kV#V$M3+3rmpu@Z|lz^&zbZUA*}1e3e0@*OdX2)rRs=#_~xfmsGlgAb+e_ zBtlhdf@OWNStPn9PpTw}hL`AbIf;Nx1A@uSqaG{C=7Pab``p6UsfU7>-V+ioL-t*X z;gyank~~ekgHbt~i2)&(%7=WtG`?f>mUSWB9Xtp12#Hx4Qp%kx<-1>HSGWJNAg7it$nBj{O0gd&s;Poxe>RN|%7FuL6(Pn20l)>_{mMx$o= z%~G077xh_!)~yrKR(Td0_dCDA(IzUwiq?y^0mu80Et(Rfy8o0k zgY+;;QkHuz--s!_pfXC$C}qr65#1fS+c2OWg7WBYUNDxG|oB64NeQb(YP#$vjQ z%ts$8ryQCzK1L^sHa_A(V`CJ(P?$g@^(f^o+)!_(C73@+9I~(S-*h90Q9vr$kK*lQ$B&RgI33nMNb%@-AEQx=r3@<94fx zGo)hXI>DH5lUWKYMW+zjx$6NQ*kkC zT?ux^b#8xT?loMp!U}EFmpUWN9lN1XPW|Fa34ZY?>lI1sW%7Lb!6AnCE?S280iEqb z^p;g2k|VTHVR@UUX?9erLd>12uiWbpB0!oD#i8L5%MxN zBWI2z*qU%U!bks_%q*NMpAIED@K(YaYN-N0 z?T$z3tkifpT=r49XntN&W|}u)jP5KP3r3+>ipu_F^&OiY*RbFahgZYcJ9^TO_}U#vNy&I#-26% zl9Xr=$+1&mY?F14ouOic$Z>2l(@8O7LYnMC&NNdB$MU| z+}Cn{-k73w7ZY2E&vW%tT5J?Qa6ddiLZZ3$EO zFmvjFor704+A_IEV|cncO$et`5(f7&_ZvQ29{tPOZm;8#j`_^&KguBI9J8$A)+dqF z7qjVAFlo!^>OZmu%HLGxWSS|T^NfafK%Mz?oVhfIK6`R!KubT_29@U+`B&-7Iyq7> z&YLA@_{ABk`kA98?24}I2s5NyFHPRRI{kblvI)2O)(6;vX= zrwm@hk7W?Tpz2Id|Cj!Rz;Z=_g2b~Y59CGq*6_*O$yCZKz_!e2QaQUyHJ|?kiKpe~ zJe0PaJ`~nO3sDC8N(@EOgFn3fh&D z8&+x^r&x{-2sfoISVb#t4WCF(c~H?!NfI?Qb|(~%b8P>O73xL{IBgxGM3#_JkkCmE9};Q8?uA4BVFeUR_WZWtpS zcR21!HWb}*<}wsDAH+sOUMp7}9Zayo*(wA(4{NMLd1q0@lj^MYPWc7bW=(?9N-15J zb{5C8OSupcVl$c>e&b|H=stdRKT*P%5bN&C4iUJgxaDJ+8wNqRv8a=|nVfer|NY1W z2(fo4Tlq)(CH48G$!rBixpuBSk5z{qfB;}oD5#DGG1$}E)a=H11IT%!NAB~UZn@!( zA}4rDecS3(Phme34UJt0kL7MStlM}v5jH3}KD{UO{1tcciA?NjA3k(ft$e^H%X9i3 zyfw4bhKq#TD)39b>9Ht8q#2h}<&<0Ss&AX979ZAwFZ=Rh3R(=aqPxj&1Y=%~f>~;i zCMT+Xx#Z>Ek6MR}_~QSxcHCSZZ(P&>)t(_>_tz*)+6EJR&xwx4hH~ zj*%7E-HT+^*_hb~@CYjiih2nH`*Iw3(N^B{@ol8Fp11KvKR3-k74KeIx>ioBF0#!> z&r8{84hdEbC{pZ(&-iy{y*i$)^T6s=#-+n^QB}%KP@2|EhFqb^(o63}Qf?tSMMM{ku<4=P%JB{XcG6#E^;k5w^m1&T zf_}k2Jucrk#2%B9caKjWg3ABQ{q5Q}RsX9>FN6cy|2e^9+W*1^&<6nBu)*dZ{SUVL z*B^@K2>UM#d4W**H<0*$oYRH_{ttS|ntk!E{AV-HmI52q31!RQ{-qaZ4;}#c#3K`rt`$nOB0q5O|z`p;t?;8O;Vr%K0DfBO0{NF9u{a?5RC4L9X3}LI=S5}VQ=$IgwDu!FXV=s@sE4CvJKmAdse|W2G3C=#q#lu1P;usB1A!Nm2)0^kuL!z?H_?9*f*PNx(wqE3Xz6$Kwnk(v3i6bo9T6)J z0T)o2zwX1AmiqnUT4lMjckpe1kSsaj>#!)3em@q_FbYsdFrY?{)x=eS87O*f_Ru|G zBO(AHDGX$}z5*43-+`^q13;b_V*9Uk#B6VceH!(7Wh@qiRTx=q1(l|rC+s`yzvnZ6 zDb4mL=J>riq=(QthhzOI8hM!mg+K}1{Hz_8eJ_CYG@=RwV?1O(vvpn~>wr>L9l#Pf z$o4bw!+8Py;#bqugn^2_kj|Un^SuWWB@70@(_4kPUku6s>8}D2Xc?e~_X+K>T)W1k zQ%_7~=>jMp3EI32l5HrM?3Ip&4|lpPKdb%o;+=wRHpx%%PZWW9!iCn!5v));xX%4k*B9P_te4!@~Yw_*F-aNEG*QVJ`6SQp;8# zx4Vfm*Pp&``E}>s5#XJE2Lk&(Jqmif+}sQf4Mkp@1}+RwkoJBbRP(lo-V?T4PEhZ) z4A*;b$qn?cQ6PpmY}*L**KtmYotTP0HvKSg9P6(8N*1sk{PaMXuuaTtR!alGRbeCU zi4MRZv3Y|(Il!%z)h8DDoCYG?hlCw{rGnjtnLEFqlY#sB%2gSX8v2jF&1=6R9dZUu zB|?(>x($_KGiDXY;!B_kYCMMkZbjPrTn4qYnh~m_N0*enw^6@!N=C1C<=A^9x3Ex4 zM~gth9^>+L4$}e{bTZu;JXwt~=L2N@(~Sv-L<|$2$Q4Hod5U03yE)``{UV}Y7a+Gl z;OGnMIbgE7Kxgw{SqQue#H(e`74VW8mu0{X_tIQNvom=SP%J3!`+7f+hXqY13zSNNfZFI zK~#hqG0Q@e+-Yp=3Slr}wr@xl2X26-D>B!P-x-+bY6TvS)%!ip5;r*%mTa1LoP&b5 z6>?w{N>%ko+4E0A_@67B?3za18zr4pFqkh*ln}i%Dfs;+K?5x8d4jT+IEO5Nkz@v0 za&3veCAHipZs?N09^Z>s5tz+?X}< zy;ZC7^|;>@nQEAK*Fpme@ULTu23MsF8F6EVM%Ia5 z+O#=&whPTM8s}K&M*tZuX_23gJ!u5Qc&oO3xM`9EW|}3Pm!7NCh^T7AJ{3d^-Qp>Z zp%_dC0RC0e)5{siP#Q6s_WEzq=AU;xcmwiI8X$kSV|`UInB9~aFtvtdN*|(C6%sA`PUFDHYp^B1!>*U)#Su>*oxdBZJuhO^-MYY z0yQza7&axFQlXGqN-yL4DJ^F*vQ(G&$1n77tfkmLCd3iLW3C>5o9{?*H`5GYLW|Ze z5o|g&)Sh5hRhU3E%bQZ1{)KKX4)Zi^bDGy^aL)5;ABu#wOzdQ0=nbxV+qwu1`kd@~ zB$x^}pk2v3e3$hI)Z+r0hj&eTOzCJwgI&NzM69D0it6FTM@x`m-qgaxm{zI0a-SVfpbl;{B3(~7!4HB$pH-Q{Bp+~fSs2ZFxX=6QdZ5u3O z^Wj&ZLITUDbpR-S?%c@*Ny@I|I4r+ChwNj(r~IU+Me^%-yt`}1@~zrx%2=r#k91I< zR7Pcf*gS&qLVD2c5m@f;g$)fu`(fueOXb$+g)MR;^Cs86_e^@z3}+zI3FC5bN{-nBqV0wUG?z}q+|gcW8il_%<@+4}gXarCLicuL zAm+iF+nrueo$F_(4Ic%8_L=*05l8w@k=>8R*qtWNf7B_U)>GIx^M2%b=t*-VbCG@* z%?M*MUiYJl3Skl{aqYT*fJJqBmuU;BtX!D?=rri93hR?SpJY&RH93U;Gyh>tnli!> ze}6U~=dbhhvR?7`TR&5ik^%X;2cd^F{`Od&o!nhT&K^WqJ7`^7oKa(vgxiV+Wu@7s zd{kVcPS$dp_K&lhau=;GzU-l*eK3P$Dm1%PQ3HxIRunLK-S7;KKfbUUzma`Fxvtrfssx4^`Qf$<>CLGGws6^d23MQdWH7m+f%g#rkEV8gLNXYs2(HuFlbC5Pp*7`b{ z&T{jT)P=%?2UON0j2Rc>g_irAj+-$$)SX&jKQEr5WZrg#uGJRSW*JSE?*&`3sLuI& ldTR;KH^1v5QGa54M|I!qvnM?a!5_evnURHI{W-T={|7XZ9Q^ Date: Wed, 10 Jun 2026 16:05:50 -0400 Subject: [PATCH 2/7] Add some additional content, tighen headers --- content/blog/2026-06-08-datafusion-54.0.0.md | 277 ++++++++++++------- 1 file changed, 175 insertions(+), 102 deletions(-) diff --git a/content/blog/2026-06-08-datafusion-54.0.0.md b/content/blog/2026-06-08-datafusion-54.0.0.md index 7cab434c..bc72babb 100644 --- a/content/blog/2026-06-08-datafusion-54.0.0.md +++ b/content/blog/2026-06-08-datafusion-54.0.0.md @@ -29,8 +29,8 @@ limitations under the License. We are proud to announce the release of [DataFusion 54.0.0]. This post highlights some of the major improvements since [DataFusion 53.0.0]. The complete list of -changes is available in the [changelog]. Thanks to the [139 contributors] for -making this release possible. +changes is available in the [changelog]. Thanks to the [139 contributors] +(a new record!) for making this release possible. [DataFusion 54.0.0]: https://crates.io/crates/datafusion/54.0.0 [DataFusion 53.0.0]: https://datafusion.apache.org/blog/2026/04/02/datafusion-53.0.0/ @@ -47,7 +47,7 @@ alt="Performance over time" /> **Figure 1**: Average and median normalized execution times for DataFusion 54.0.0 on ClickBench queries, compared to previous releases. -Query times are normalized using the ClickBench definition. See the +normalized using the ClickBench definition. See the [DataFusion Benchmarking Page](https://alamb.github.io/datafusion-benchmarking/) for more details. @@ -56,82 +56,102 @@ explained below. This release simplifies more predicates before execution, prunes more redundant work out of plans, and makes joins, repartitioning, and many built-in functions faster. -### Pruning Functionally-Redundant Sort Keys -Sorting is expensive, so it pays to sort by as few columns as possible. -DataFusion 54 now drops sort keys from `ORDER BY` that are functionally -redundant: when an earlier key functionally determines a later one, the later key -cannot change the ordering, so removing it reduces comparison and sorting cost -without affecting results. -Thanks to [@xiedeyantu] for implementing this feature, with reviews from -[@alamb] and [@neilconway]. Related PRs: [#21362] +### Execution Operator Improvements -### Statistics-Driven Sort Pushdown and TopK - -This release continues the sort pushdown work begun in earlier releases. Sort -pushdown phase 2 sorts file groups by statistics so that ordered scans can be -satisfied with less work, and DataFusion can now globally reorder files and row -groups by statistics for TopK (`ORDER BY ... LIMIT`) queries. This lets the most -promising data be read first, often satisfying the `LIMIT` before scanning the -rest. - -Thanks to [@zhuqi-lucas] for driving this work, with reviews from [@adriangb]. -Related PRs: [#21182], [#21426], [#21956] - -### Physical Execution of Uncorrelated Scalar Subqueries - -Previously, DataFusion executed an uncorrelated scalar subquery (one that does +**Physical Execution of Uncorrelated Scalar Subqueries**: +Previously, DataFusion executed uncorrelated scalar subqueries (that does not depend on the outer query) by rewriting it into a join. DataFusion 54 -instead executes such subqueries directly with a new physical operator, -evaluating each subquery once, with subqueries at the same query level running in -parallel. This unlocks several improvements. Functions can now use their -specialized scalar code paths, which makes some queries dramatically faster; in -one case execution drops from roughly 800 ms to 30 ms. Uncorrelated scalar -subqueries also now work in `ORDER BY`, `JOIN ON`, and as arguments to aggregate -functions. Finally, a subquery that returns more than one row now raises a -runtime error instead of silently producing incorrect results. - +now executes such subqueries directly with a new physical operator, +evaluating each subquery once. This is better because 1) functions can now use +specialized scalar code paths and 2) uncorrelated scalar +subqueries now work in `ORDER BY`, `JOIN ON`, and as arguments to aggregate +functions. Thanks to [@neilconway] for implementing this feature, with reviews from [@Dandandan], [@alamb], and [@timsaucer]. Related PRs: [#21240] -### Faster Sort-Merge Joins - -Sort-merge joins (SMJ) saw substantial work this cycle. Semi, anti, and mark -joins now run on a specialized stream that tracks matches with a per-row bitset -instead of materializing `(outer, inner)` row pairs. For outer joins, batched -deferred filtering makes near-unique `LEFT` and `FULL` joins 20-50x faster. -Finally, join-key comparisons now use a `DynComparator`, which resolves the -column type once instead of on every row, making microbenchmark queries up to -12% faster and TPC-H roughly 5% faster overall. - +**Faster Sort-Merge Joins**: +Semi, anti, and mark joins now run on a specialized stream that tracks matches +with a per-row bitset instead of materializing `(outer, inner)` row pairs. For +outer joins, batched deferred filtering makes near-unique `LEFT` and `FULL` +joins 20-50x faster. Finally, join-key comparisons now use a `DynComparator`, +which resolves the column type once instead of on every row, making +microbenchmark queries up to 12% faster and TPC-H roughly 5% faster overall. Thanks to [@mbutrovich] for this work, with reviews from [@Dandandan], [@comphead], and [@rluvaton]. Related PRs: [#20806], [#21184], [#21484], [#21517] -### Faster Repartitioning - +**Faster Repartitioning**: `RepartitionExec` now coalesces batches before sending them to distributor -channels, reducing per-batch overhead in plans that repartition data. - +channels, reducing per-batch overhead in plans that repartition data, resulting +in up to 50% faster execution for some repartition heavy queries. Thanks to [@gabotechs] for this work, with reviews from [@Dandandan] and [@alamb]. Related PRs: [#22010] -### Faster Functions and Hashing - +**Faster Functions and Hashing**: DataFusion ships hundreds of built-in functions, so making them faster pays off -across nearly every workload. This release optimizes a large number of them, -including [`array_to_string`], [`array_concat`], [`array_sort`], [`split_part`], -[`substr`], [`strpos`], [`left`], [`right`], [`string_agg`], and -[`approx_distinct`], along with better `NULL` handling across many array and -datetime functions. It also replaces `ahash` with [`foldhash`] for faster -hashing in `datafusion-common`, and optimizes [`regexp_replace`] by stripping -trailing `.*` from anchored patterns, which yields a 2.4x improvement on -ClickBench Q28. - +across many workloads. This release optimizes a large number of functions, +including [array_to_string], [array_concat], [array_sort], [split_part], +[substr], [strpos], [left], [right], [string_agg], and +[approx_distinct], along with better `NULL` handling across many array and +datetime functions. It also replaces `ahash` with [foldhash] for faster +hashing in `datafusion-common`, and optimizes [regexp_replace] by stripping +trailing `.*` from anchored patterns. Thanks to the many contributors who drove this work, especially [@neilconway], [@Dandandan], [@zhangxffff], [@lyne7-sc], [@CuteChuanChuan], [@kumarUjjawal], and [@coderfender]. +### Planner Improvements + +**Pruning Functionally-Redundant Sort Keys**: +Sorting is expensive, so it pays to sort by as few columns as possible. +DataFusion 54 now drops sort keys from `ORDER BY` that are functionally +redundant: when an earlier key functionally determines a later one, the later +key cannot change the ordering, so removing it reduces comparison and sorting +cost without affecting results. +Thanks to [@xiedeyantu] for implementing this feature, with reviews from +[@alamb] and [@neilconway]. Related PRs: [#21362] + +**Skip Redundant Parquet Filters** +When DataFusion can prove from statistics that a filter matches every row in a +Parquet row group, it now skips evaluating that filter — both row filters and +page-level pruning — for the row group entirely rather than re-checking each row. +Thanks to [@xudong963] for implementing this, building on a suggestion from +[@crepererum]. Related issues and PRs: [#19028], [#21637] + +**Statistics-Driven Sort Pushdown and TopK**: +When possible, files Parquet row groups are sorted using statistics, potentially +avoiding the need to sort the data, well as improving the effectiveness of +dynamic filters and early stopping for TopK (`ORDER BY ... LIMIT`) queries. This +lets the most promising data be read first, often satisfying the `LIMIT` before +scanning the rest. +Thanks to [@zhuqi-lucas] for driving this work, with reviews from [@adriangb]. +Related PRs: [#21182], [#21426], [#21956] + +**Improved Statistics and Cardinality Estimation**: +Good plans depend on good statistics, and this release includes changes to help +the optimizer make better choices. Highlights include extracting NDV (number of distinct values) +statistics from Parquet metadata, using NDV for equality-filter selectivity, a +pluggable [StatisticsRegistry] for operator-level statistics propagation, and +better cardinality estimation for semi and anti joins. +Thanks to [@asolimando], [@jonathanc-n], and [@buraksenn] for driving this work. +Related PRs: [#19957], [#20789], [#21077], [#21081], [#21483], [#20904] + + +### Morsel-Driven Parquet Scans + +Parquet scan parallelism was previously bounded by the slowest scan thread, so +for some workloads with data skew (large row groups, less-selective filters, or variable object store +latency) all cores were not fully utilized . DataFusion 54 reworked the +Parquet scan around a [morsel-driven design], where idle threads dynamically pull +small units of work ("morsels") instead of being assigned a fixed partition up +front. This spreads work more evenly and can be up to ~2x faster for some skewed +scans such as those found in ClickBench. + +Thanks to [@Dandandan], [@alamb], [@adriangb], [@xudong963], and [@zhuqi-lucas] +for collaborating on this substantial effort. Related issues and PRs: [#20529], +[#21327], [#21342], [#21351] + ## New Features ✨ ### `LATERAL` Joins @@ -158,25 +178,55 @@ Thanks to [@neilconway] for implementing this feature, with reviews from ### Lambda Functions DataFusion now supports lambda expressions (`v -> expr`), including lambda column -capture, along with new higher-order array UDFs such as [`array_transform`], -`array_filter`, and `array_any_match` ([#21323], [#21679]). Lambdas make it -possible to express per-element transformations directly in SQL: +capture, along with new higher-order array UDFs such as [array_transform], +[array_filter], and [array_any_match] ([#21323], [#21679]). Lambdas make it +possible to express per-element computation directly in SQL: + +```sql +-- Apply the expression `x * 10` to every array element +SELECT array_transform([1, 2, 3, 4, 5], x -> x * 10); +-- [10, 20, 30, 40, 50] + +-- Keep only the elements for which the `x > 2` returns true +SELECT array_filter([1, 2, 3, 4, 5], x -> x > 2); +-- [3, 4, 5] + +-- returns true if any element satisfies `x > 2` +SELECT array_any_match([1, 2, 3], x -> x > 2); +-- true +``` + +Because lambdas are just expressions, they compose. You can filter and then +transform in a single expression: ```sql -SELECT array_transform(array_filter([1, 2, 3, 4, 5], v -> v > 2), v -> v * 10); +-- keep elements > 2, then multiply each survivor by 10 +SELECT array_transform(array_filter([1, 2, 3, 4, 5], x -> x > 2), x -> x * 10); -- [30, 40, 50] ``` Thanks to [@gstvg] and [@rluvaton] for leading this effort, with reviews from [@comphead] and [@martin-g]. +### Spilling Nested Loop Joins + +`NestedLoopJoinExec` previously failed with an out-of-memory error when the build +side exceeded the memory budget. DataFusion 54 adds a memory-limited execution +path that transparently spills to disk and completes the query instead of +erroring ([#21448]). The fallback adds zero overhead when memory is sufficient, +and currently covers `INNER`, `LEFT`, `LEFT SEMI`, `LEFT ANTI`, and `LEFT MARK` +joins. Thanks to [@viirya] for implementing this feature, with reviews from +[@2010YOUY01]. + ### New Avro Reader The Avro reader has been migrated to the `arrow-avro` crate ([#17861]), removing internal conversion code in favor of a faster, better maintained implementation -shared with the broader Arrow ecosystem. Thanks to [@getChan] for this work, +as described in [Introducing arrow-avro]. Thanks to [@getChan] for this work, with reviews from [@adriangb], [@alamb], and [@jecsand838]. +[Introducing arrow-avro]: https://arrow.apache.org/blog/2025/10/23/introducing-arrow-avro/ + ### Extension Type Registry Arrow extension types let users layer their own semantics on top of a physical @@ -186,24 +236,29 @@ canonical extension types ([#21291]), and allows logical expressions to cast to an extension type ([#18136]). Thanks to [@tschwarzinger] and [@paleolimbot] for driving this work, with reviews from [@adriangb] and [@cetra3]. -### Parquet Content-Defined Chunking +### Content-Defined Chunking for Parquet DataFusion's Parquet writer can now be configured to use content-defined chunking (CDC) ([#21110]), which aligns data page boundaries with the data itself rather than with fixed row counts. This improves deduplication and incremental storage, since inserting or editing a few rows no longer shifts -every subsequent page boundary. Thanks to [@kszucs] for this feature, with +every subsequent page boundary. For background on the technique and the dedupe +gains it enables, see [Parquet Content-Defined Chunking] and [Improving Parquet +Dedupe on Hugging Face Hub]. Thanks to [@kszucs] for this feature, with reviews from [@alamb]. -### New SQL and Scalar Functions +[Parquet Content-Defined Chunking]: https://huggingface.co/blog/parquet-cdc +[Improving Parquet Dedupe on Hugging Face Hub]: https://huggingface.co/blog/improve_parquet_dedupe + +### New Functions -This release adds new scalar functions such as [`array_compact`], -[`cosine_distance`], [`cast_to_type`], and [`with_metadata`], nanosecond +**SQL and Scalar Functions**: +This release adds new scalar functions such as [array_compact], +[cosine_distance], [cast_to_type], and [with_metadata], nanosecond `date_part` support, and the `:` JSON access operator. Thanks to [@comphead], [@crm26], [@adriangb], [@mhilton], and [@Samyak2] for these contributions. -### Spark-Compatible Functions - +**Spark-Compatible Functions**: This release adds many new or improved Spark-compatible functions and behaviors in the [datafusion-spark crate], including `round`, `floor`, `ceil`, `soundex`, `xxhash64`, `array_contains`, `array_compact`, Spark-compatible @@ -212,19 +267,8 @@ contributors who drove this work, especially [@comphead], [@coderfender], [@SubhamSinghal], [@kazantsev-maksim], [@andygrove], [@buraksenn], and [@davidlghellin]. -## Statistics and Cardinality Estimation - -Good plans depend on good statistics, and this release includes a substantial -body of work in that area. Highlights include extracting NDV (number of distinct -values) statistics from Parquet metadata, using NDV for equality-filter -selectivity, a pluggable `StatisticsRegistry` for operator-level statistics -propagation, and better cardinality estimation for semi and anti joins. Together -these help the optimizer make better choices. - -Thanks to [@asolimando], [@jonathanc-n], and [@buraksenn] for driving this work. -Related PRs: [#19957], [#20789], [#21077], [#21081], [#21483], [#20904] -## Upgrade Guide and Changelog +## Upgrade Guide and Changelog 📖 Upgrading to 54.0.0 should be straightforward for most users, though this release does include some breaking changes, such as the removal of `as_any` from @@ -308,27 +352,56 @@ can find out how to reach us on the [communication doc]. [@martin-g]: https://github.com/martin-g [@jecsand838]: https://github.com/jecsand838 [@cetra3]: https://github.com/cetra3 - -[`array_to_string`]: https://github.com/apache/datafusion/pull/20639 -[`array_concat`]: https://github.com/apache/datafusion/pull/20620 -[`array_sort`]: https://github.com/apache/datafusion/pull/21083 -[`split_part`]: https://github.com/apache/datafusion/pull/21119 -[`substr`]: https://github.com/apache/datafusion/pull/21366 -[`strpos`]: https://github.com/apache/datafusion/pull/20754 -[`left`]: https://github.com/apache/datafusion/pull/21442 -[`right`]: https://github.com/apache/datafusion/pull/21442 -[`string_agg`]: https://github.com/apache/datafusion/pull/21154 -[`approx_distinct`]: https://github.com/apache/datafusion/pull/21037 -[`foldhash`]: https://github.com/apache/datafusion/pull/20958 -[`regexp_replace`]: https://github.com/apache/datafusion/pull/21379 -[`array_transform`]: https://github.com/apache/datafusion/pull/21679 -[`array_compact`]: https://github.com/apache/datafusion/pull/21522 -[`cosine_distance`]: https://github.com/apache/datafusion/pull/21542 -[`cast_to_type`]: https://github.com/apache/datafusion/pull/21322 -[`with_metadata`]: https://github.com/apache/datafusion/pull/21509 +[@sdf-jkl]: https://github.com/sdf-jkl +[@devanshu0987]: https://github.com/devanshu0987 +[@xudong963]: https://github.com/xudong963 +[@crepererum]: https://github.com/crepererum +[@theirix]: https://github.com/theirix +[@bert-beyondloops]: https://github.com/bert-beyondloops +[@viirya]: https://github.com/viirya +[@2010YOUY01]: https://github.com/2010YOUY01 + +[array_to_string]: https://github.com/apache/datafusion/pull/20639 +[array_concat]: https://github.com/apache/datafusion/pull/20620 +[array_sort]: https://github.com/apache/datafusion/pull/21083 +[split_part]: https://github.com/apache/datafusion/pull/21119 +[substr]: https://github.com/apache/datafusion/pull/21366 +[strpos]: https://github.com/apache/datafusion/pull/20754 +[left]: https://github.com/apache/datafusion/pull/21442 +[right]: https://github.com/apache/datafusion/pull/21442 +[string_agg]: https://github.com/apache/datafusion/pull/21154 +[approx_distinct]: https://github.com/apache/datafusion/pull/21037 +[foldhash]: https://github.com/apache/datafusion/pull/20958 +[regexp_replace]: https://github.com/apache/datafusion/pull/21379 +[array_transform]: https://github.com/apache/datafusion/pull/21679 +[array_filter]: https://github.com/apache/datafusion/pull/21895 +[array_any_match]: https://github.com/apache/datafusion/pull/21903 +[array_compact]: https://github.com/apache/datafusion/pull/21522 +[cosine_distance]: https://github.com/apache/datafusion/pull/21542 +[cast_to_type]: https://github.com/apache/datafusion/pull/21322 +[with_metadata]: https://github.com/apache/datafusion/pull/21509 + +[morsel-driven design]: https://db.in.tum.de/~leis/papers/morsels.pdf +[ClickHouse paper]: https://www.vldb.org/pvldb/vol17/p3731-schulze.pdf +[PruningPredicate]: https://docs.rs/datafusion/latest/datafusion/physical_optimizer/pruning/struct.PruningPredicate.html +[StatisticsRegistry]: https://docs.rs/datafusion/latest/datafusion/physical_plan/operator_statistics/struct.StatisticsRegistry.html [#17861]: https://github.com/apache/datafusion/pull/17861 [#10048]: https://github.com/apache/datafusion/issues/10048 +[#19028]: https://github.com/apache/datafusion/issues/19028 +[#19946]: https://github.com/apache/datafusion/issues/19946 +[#20051]: https://github.com/apache/datafusion/pull/20051 +[#20059]: https://github.com/apache/datafusion/pull/20059 +[#20350]: https://github.com/apache/datafusion/pull/20350 +[#20529]: https://github.com/apache/datafusion/issues/20529 +[#21075]: https://github.com/apache/datafusion/pull/21075 +[#21327]: https://github.com/apache/datafusion/pull/21327 +[#21342]: https://github.com/apache/datafusion/pull/21342 +[#21351]: https://github.com/apache/datafusion/pull/21351 +[#21383]: https://github.com/apache/datafusion/pull/21383 +[#21448]: https://github.com/apache/datafusion/pull/21448 +[#21637]: https://github.com/apache/datafusion/pull/21637 +[#21934]: https://github.com/apache/datafusion/pull/21934 [#18223]: https://github.com/apache/datafusion/issues/18223 [#18136]: https://github.com/apache/datafusion/pull/18136 [#20312]: https://github.com/apache/datafusion/pull/20312 From bba114e6969ed1aaf7d7a0a1986bcf6e9fcbdeaf Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Wed, 10 Jun 2026 16:53:38 -0400 Subject: [PATCH 3/7] Add some other features, improve concision and grammar --- content/blog/2026-06-08-datafusion-54.0.0.md | 280 ++++++++++--------- 1 file changed, 142 insertions(+), 138 deletions(-) diff --git a/content/blog/2026-06-08-datafusion-54.0.0.md b/content/blog/2026-06-08-datafusion-54.0.0.md index bc72babb..b8435070 100644 --- a/content/blog/2026-06-08-datafusion-54.0.0.md +++ b/content/blog/2026-06-08-datafusion-54.0.0.md @@ -29,7 +29,7 @@ limitations under the License. We are proud to announce the release of [DataFusion 54.0.0]. This post highlights some of the major improvements since [DataFusion 53.0.0]. The complete list of -changes is available in the [changelog]. Thanks to the [139 contributors] +changes is available in the [changelog]. Thanks to the [139 contributors] (a new record!) for making this release possible. [DataFusion 54.0.0]: https://crates.io/crates/datafusion/54.0.0 @@ -47,7 +47,7 @@ alt="Performance over time" /> **Figure 1**: Average and median normalized execution times for DataFusion 54.0.0 on ClickBench queries, compared to previous releases. -normalized using the ClickBench definition. See the +Query times are normalized using the ClickBench definition. See the [DataFusion Benchmarking Page](https://alamb.github.io/datafusion-benchmarking/) for more details. @@ -56,113 +56,113 @@ explained below. This release simplifies more predicates before execution, prunes more redundant work out of plans, and makes joins, repartitioning, and many built-in functions faster. - - ### Execution Operator Improvements **Physical Execution of Uncorrelated Scalar Subqueries**: -Previously, DataFusion executed uncorrelated scalar subqueries (that does -not depend on the outer query) by rewriting it into a join. DataFusion 54 -now executes such subqueries directly with a new physical operator, -evaluating each subquery once. This is better because 1) functions can now use -specialized scalar code paths and 2) uncorrelated scalar -subqueries now work in `ORDER BY`, `JOIN ON`, and as arguments to aggregate -functions. +DataFusion previously executed an uncorrelated scalar subquery (one that doesn't +depend on the outer query) by rewriting it into a join. DataFusion 54 instead +evaluates it once with a new physical operator. This lets functions use their +specialized scalar code paths, and allows uncorrelated scalar subqueries in +`ORDER BY`, `JOIN ON`, and as arguments to aggregate functions. Thanks to [@neilconway] for implementing this feature, with reviews from [@Dandandan], [@alamb], and [@timsaucer]. Related PRs: [#21240] **Faster Sort-Merge Joins**: -Semi, anti, and mark joins now run on a specialized stream that tracks matches -with a per-row bitset instead of materializing `(outer, inner)` row pairs. For -outer joins, batched deferred filtering makes near-unique `LEFT` and `FULL` -joins 20-50x faster. Finally, join-key comparisons now use a `DynComparator`, -which resolves the column type once instead of on every row, making -microbenchmark queries up to 12% faster and TPC-H roughly 5% faster overall. +Semi, anti, and mark joins now track matches with a per-row bitset instead of +materializing `(outer, inner)` pairs. Batched deferred filtering makes +near-unique `LEFT` and `FULL` joins 20-50x faster. Finally, join-key comparisons +now use a `DynComparator` that resolves the column type once rather than per row, +making microbenchmarks up to 12% faster and TPC-H ~5% faster overall. Thanks to [@mbutrovich] for this work, with reviews from [@Dandandan], [@comphead], and [@rluvaton]. Related PRs: [#20806], [#21184], [#21484], [#21517] **Faster Repartitioning**: `RepartitionExec` now coalesces batches before sending them to distributor -channels, reducing per-batch overhead in plans that repartition data, resulting -in up to 50% faster execution for some repartition heavy queries. +channels, cutting per-batch overhead for up to 50% faster execution on some +repartition-heavy queries. Thanks to [@gabotechs] for this work, with reviews from [@Dandandan] and [@alamb]. Related PRs: [#22010] **Faster Functions and Hashing**: -DataFusion ships hundreds of built-in functions, so making them faster pays off -across many workloads. This release optimizes a large number of functions, -including [array_to_string], [array_concat], [array_sort], [split_part], -[substr], [strpos], [left], [right], [string_agg], and -[approx_distinct], along with better `NULL` handling across many array and -datetime functions. It also replaces `ahash` with [foldhash] for faster -hashing in `datafusion-common`, and optimizes [regexp_replace] by stripping -trailing `.*` from anchored patterns. -Thanks to the many contributors who drove this work, especially [@neilconway], -[@Dandandan], [@zhangxffff], [@lyne7-sc], [@CuteChuanChuan], [@kumarUjjawal], and -[@coderfender]. +DataFusion ships hundreds of built-in functions, so speeding them up pays off +across many workloads. This release optimizes many, including [array_to_string], +[array_concat], [array_sort], [split_part], [substr], [strpos], [left], [right], +[string_agg], and [approx_distinct], plus better `NULL` handling across many +array and datetime functions. The `first_value` and `last_value` aggregates are +also substantially faster over `Utf8` and `Binary` columns thanks to a new +`GroupsAccumulator` ([#21090]). DataFusion 54 also swaps `ahash` for [foldhash] in +`datafusion-common`, and optimizes [regexp_replace] by stripping trailing `.*` +from anchored patterns. +Thanks to the many contributors who drove this work, especially [@UBarney], +[@neilconway], [@Dandandan], [@zhangxffff], [@lyne7-sc], [@CuteChuanChuan], +[@kumarUjjawal], and [@coderfender]. ### Planner Improvements -**Pruning Functionally-Redundant Sort Keys**: +**Pruning Functionally Redundant Sort Keys**: Sorting is expensive, so it pays to sort by as few columns as possible. -DataFusion 54 now drops sort keys from `ORDER BY` that are functionally -redundant: when an earlier key functionally determines a later one, the later -key cannot change the ordering, so removing it reduces comparison and sorting -cost without affecting results. +DataFusion 54 now drops functionally redundant `ORDER BY` keys: when an earlier +key determines a later one, the later key can't change the ordering, so removing +it cuts sorting cost without affecting results. Thanks to [@xiedeyantu] for implementing this feature, with reviews from [@alamb] and [@neilconway]. Related PRs: [#21362] -**Skip Redundant Parquet Filters** -When DataFusion can prove from statistics that a filter matches every row in a -Parquet row group, it now skips evaluating that filter — both row filters and -page-level pruning — for the row group entirely rather than re-checking each row. +**Skip Redundant Parquet Filters**: +When statistics prove a filter matches every row in a Parquet row group, +DataFusion now skips evaluating it — both row filters and page-level pruning — +for that row group instead of re-checking each row. Thanks to [@xudong963] for implementing this, building on a suggestion from [@crepererum]. Related issues and PRs: [#19028], [#21637] **Statistics-Driven Sort Pushdown and TopK**: -When possible, files Parquet row groups are sorted using statistics, potentially -avoiding the need to sort the data, well as improving the effectiveness of -dynamic filters and early stopping for TopK (`ORDER BY ... LIMIT`) queries. This -lets the most promising data be read first, often satisfying the `LIMIT` before -scanning the rest. +Files and Parquet row groups are now ordered using statistics, which can avoid +sorting entirely and improve dynamic filtering and early stopping for TopK +(`ORDER BY ... LIMIT`) queries. The most promising data is read first, often +satisfying the `LIMIT` before scanning the rest. Thanks to [@zhuqi-lucas] for driving this work, with reviews from [@adriangb]. Related PRs: [#21182], [#21426], [#21956] **Improved Statistics and Cardinality Estimation**: -Good plans depend on good statistics, and this release includes changes to help -the optimizer make better choices. Highlights include extracting NDV (number of distinct values) -statistics from Parquet metadata, using NDV for equality-filter selectivity, a -pluggable [StatisticsRegistry] for operator-level statistics propagation, and -better cardinality estimation for semi and anti joins. +Good plans depend on good statistics. This release extracts NDV (number of +distinct values) statistics from Parquet metadata, uses NDV for equality-filter +selectivity, adds a pluggable [StatisticsRegistry] for operator-level statistics +propagation, and improves cardinality estimation for semi and anti joins. Thanks to [@asolimando], [@jonathanc-n], and [@buraksenn] for driving this work. Related PRs: [#19957], [#20789], [#21077], [#21081], [#21483], [#20904] +### Scan Improvements -### Morsel-Driven Parquet Scans - -Parquet scan parallelism was previously bounded by the slowest scan thread, so -for some workloads with data skew (large row groups, less-selective filters, or variable object store -latency) all cores were not fully utilized . DataFusion 54 reworked the -Parquet scan around a [morsel-driven design], where idle threads dynamically pull -small units of work ("morsels") instead of being assigned a fixed partition up -front. This spreads work more evenly and can be up to ~2x faster for some skewed -scans such as those found in ClickBench. - +**Morsel-Driven Parquet Scans**: +Parquet scan parallelism was previously bounded by the slowest scan thread, so +data skew (large row groups, less-selective filters, or variable object store +latency) left cores underutilized. DataFusion 54 reworks the Parquet scan around +a [morsel-driven design], where idle threads dynamically pull small units of work +("morsels") instead of each being assigned a fixed partition up front. This +spreads work more evenly and can be up to ~2x faster for skewed scans such as +ClickBench. Thanks to [@Dandandan], [@alamb], [@adriangb], [@xudong963], and [@zhuqi-lucas] for collaborating on this substantial effort. Related issues and PRs: [#20529], [#21327], [#21342], [#21351] +**Struct Field Filter Pushdown and Leaf-Level Projection**: +Filters on struct fields (e.g. `WHERE s['foo'] > 67`) are now pushed down into the +Parquet decoder rather than evaluated after a full scan, and both filtering and +projection read only the struct leaves they actually access, +significantly improving performance for nested and `Variant` data in large +Parquet files. +Thanks to [@friendlymatthew] for this work, with reviews from [@adriangb], +[@cetra3], and [@AdamGS]. Related PRs: [#20822], [#20854], [#20925] + ## New Features ✨ ### `LATERAL` Joins -Lateral joins have been a long-requested feature ([#10048]). DataFusion 54 adds -basic support for `CROSS JOIN LATERAL`, `INNER JOIN LATERAL`, and `LEFT JOIN -LATERAL` ([#21202], [#21352]). A lateral subquery in the `FROM` clause can -reference columns from preceding tables, which is handy for patterns such as -expanding a per-row series or correlating against a set-returning function. The -implementation uses decorrelation, so the subquery is evaluated once rather than -re-executed for every outer row. +Lateral joins have been long requested ([#10048]). DataFusion 54 adds basic +support for `CROSS JOIN LATERAL`, `INNER JOIN LATERAL`, and `LEFT JOIN LATERAL` +([#21202], [#21352]). A lateral subquery in the `FROM` clause can reference +columns from preceding tables — handy for expanding a per-row series or +correlating against a set-returning function. It uses decorrelation, so the +subquery is evaluated once rather than re-executed per outer row. ```sql SELECT t1_id, t1_name, i @@ -177,106 +177,101 @@ Thanks to [@neilconway] for implementing this feature, with reviews from ### Lambda Functions -DataFusion now supports lambda expressions (`v -> expr`), including lambda column -capture, along with new higher-order array UDFs such as [array_transform], -[array_filter], and [array_any_match] ([#21323], [#21679]). Lambdas make it -possible to express per-element computation directly in SQL: +DataFusion now supports lambda expressions (`x -> expr`) with column capture, +plus new higher-order array UDFs like [array_transform], [array_filter], and +[array_any_match] ([#21323], [#21679]). Lambdas express per-element computation +directly in SQL: ```sql --- Apply the expression `x * 10` to every array element +-- Apply `x * 10` to every element SELECT array_transform([1, 2, 3, 4, 5], x -> x * 10); -- [10, 20, 30, 40, 50] --- Keep only the elements for which the `x > 2` returns true +-- Keep only elements where `x > 2` SELECT array_filter([1, 2, 3, 4, 5], x -> x > 2); -- [3, 4, 5] --- returns true if any element satisfies `x > 2` +-- True if any element satisfies `x > 2` SELECT array_any_match([1, 2, 3], x -> x > 2); -- true ``` -Because lambdas are just expressions, they compose. You can filter and then -transform in a single expression: +Lambdas compose, so you can filter then transform in one expression: ```sql --- keep elements > 2, then multiply each survivor by 10 +-- Keep elements > 2, then multiply each survivor by 10 SELECT array_transform(array_filter([1, 2, 3, 4, 5], x -> x > 2), x -> x * 10); -- [30, 40, 50] ``` -Thanks to [@gstvg] and [@rluvaton] for leading this effort, with reviews from -[@comphead] and [@martin-g]. +Thanks to [@gstvg] and [@rluvaton] for leading this effort, the [array_filter] +and [array_any_match] functions from [@ologlogn] and [@LiaCastaneda], and reviews +from [@comphead] and [@martin-g]. ### Spilling Nested Loop Joins `NestedLoopJoinExec` previously failed with an out-of-memory error when the build -side exceeded the memory budget. DataFusion 54 adds a memory-limited execution -path that transparently spills to disk and completes the query instead of -erroring ([#21448]). The fallback adds zero overhead when memory is sufficient, -and currently covers `INNER`, `LEFT`, `LEFT SEMI`, `LEFT ANTI`, and `LEFT MARK` -joins. Thanks to [@viirya] for implementing this feature, with reviews from -[@2010YOUY01]. +side exceeded the memory budget. DataFusion 54 adds a memory-limited path that +transparently spills to disk and completes the query instead ([#21448]), with +zero overhead when memory is sufficient. It currently covers `INNER`, `LEFT`, +`LEFT SEMI`, `LEFT ANTI`, and `LEFT MARK` joins. Thanks to [@viirya] for +implementing this feature, with reviews from [@2010YOUY01]. ### New Avro Reader -The Avro reader has been migrated to the `arrow-avro` crate ([#17861]), removing -internal conversion code in favor of a faster, better maintained implementation -as described in [Introducing arrow-avro]. Thanks to [@getChan] for this work, -with reviews from [@adriangb], [@alamb], and [@jecsand838]. +The Avro reader now uses the `arrow-avro` crate ([#17861]), replacing internal +conversion code with a faster, better-maintained implementation shared with the +Arrow ecosystem (see [Introducing arrow-avro]). Thanks to [@getChan] for this +work, with reviews from [@adriangb], [@alamb], and [@jecsand838]. [Introducing arrow-avro]: https://arrow.apache.org/blog/2025/10/23/introducing-arrow-avro/ ### Extension Type Registry Arrow extension types let users layer their own semantics on top of a physical -storage type. DataFusion 54 introduces an extension type registry so that -behavior can be registered for them ([#18223], [#20312]), adds several more -canonical extension types ([#21291]), and allows logical expressions to cast to -an extension type ([#18136]). Thanks to [@tschwarzinger] and [@paleolimbot] for -driving this work, with reviews from [@adriangb] and [@cetra3]. +storage type. DataFusion 54 adds a registry for registering their behavior +([#18223], [#20312]), several more canonical extension types ([#21291]), and the +ability to cast to an extension type in logical expressions ([#18136]). Thanks to +[@tschwarzinger] and [@paleolimbot] for driving this work, with reviews from +[@adriangb] and [@cetra3]. ### Content-Defined Chunking for Parquet -DataFusion's Parquet writer can now be configured to use content-defined -chunking (CDC) ([#21110]), which aligns data page boundaries with the data -itself rather than with fixed row counts. This improves deduplication and -incremental storage, since inserting or editing a few rows no longer shifts -every subsequent page boundary. For background on the technique and the dedupe -gains it enables, see [Parquet Content-Defined Chunking] and [Improving Parquet -Dedupe on Hugging Face Hub]. Thanks to [@kszucs] for this feature, with -reviews from [@alamb]. +DataFusion's Parquet writer can now use content-defined chunking (CDC) +([#21110]), which aligns data page boundaries with the data rather than fixed row +counts. This improves deduplication and incremental storage, since inserting or +editing a few rows no longer shifts every later page boundary. For background, +see [Parquet Content-Defined Chunking] and [Improving Parquet Dedupe on Hugging +Face Hub]. Thanks to [@kszucs] for this feature, with reviews from [@alamb]. [Parquet Content-Defined Chunking]: https://huggingface.co/blog/parquet-cdc [Improving Parquet Dedupe on Hugging Face Hub]: https://huggingface.co/blog/improve_parquet_dedupe -### New Functions - -**SQL and Scalar Functions**: -This release adds new scalar functions such as [array_compact], -[cosine_distance], [cast_to_type], and [with_metadata], nanosecond -`date_part` support, and the `:` JSON access operator. Thanks to [@comphead], -[@crm26], [@adriangb], [@mhilton], and [@Samyak2] for these contributions. - -**Spark-Compatible Functions**: -This release adds many new or improved Spark-compatible functions and behaviors -in the [datafusion-spark crate], including `round`, `floor`, `ceil`, `soundex`, -`xxhash64`, `array_contains`, `array_compact`, Spark-compatible -int/float-to-timestamp casts, and UTF-8 validation functions. Thanks to the -contributors who drove this work, especially [@comphead], [@coderfender], -[@SubhamSinghal], [@kazantsev-maksim], [@andygrove], [@buraksenn], and -[@davidlghellin]. - +### New Functions + +**SQL and Scalar Functions**: +DataFusion 54 adds new scalar functions including [array_compact], +[cosine_distance], [inner_product], [array_normalize], [cast_to_type], and +[with_metadata], plus nanosecond `date_part` support ([#20674]) and the `:` JSON +access operator ([#20628]). The +[cosine_distance], [inner_product], and [array_normalize] additions round out +DataFusion's vector-search building blocks. Thanks to [@comphead], [@crm26], +[@adriangb], [@mhilton], and [@Samyak2] for these contributions. + +**Spark-Compatible Functions**: +The [datafusion-spark crate] gains many new or improved Spark-compatible +functions, including [round], [floor], [ceil], [soundex], [xxhash64], +[array_contains], [array_compact], int/float-to-timestamp casts, and UTF-8 +validation functions. +Thanks to the contributors who drove this work, especially [@comphead], +[@coderfender], [@SubhamSinghal], [@kazantsev-maksim], [@andygrove], [@buraksenn], +[@davidlghellin], [@athlcode], and [@shivbhatia10]. ## Upgrade Guide and Changelog 📖 -Upgrading to 54.0.0 should be straightforward for most users, though this -release does include some breaking changes, such as the removal of `as_any` from -several trait definitions, changes to the pruning and statistics APIs, the new -`Operator::Colon` for JSON access, and the switch from `ahash` to `foldhash`. -Please review the [Upgrade Guide] for details on breaking changes and code -snippets to help with the transition. For a comprehensive list of all changes, -please refer to the [changelog]. +Upgrading to 54.0.0 should be straightforward for most users, though there are +some breaking changes. See the [Upgrade Guide] for details and +migration snippets, and the [changelog] for the full list of changes. ## About DataFusion @@ -352,14 +347,17 @@ can find out how to reach us on the [communication doc]. [@martin-g]: https://github.com/martin-g [@jecsand838]: https://github.com/jecsand838 [@cetra3]: https://github.com/cetra3 -[@sdf-jkl]: https://github.com/sdf-jkl -[@devanshu0987]: https://github.com/devanshu0987 [@xudong963]: https://github.com/xudong963 [@crepererum]: https://github.com/crepererum -[@theirix]: https://github.com/theirix -[@bert-beyondloops]: https://github.com/bert-beyondloops [@viirya]: https://github.com/viirya [@2010YOUY01]: https://github.com/2010YOUY01 +[@UBarney]: https://github.com/UBarney +[@friendlymatthew]: https://github.com/friendlymatthew +[@AdamGS]: https://github.com/AdamGS +[@athlcode]: https://github.com/athlcode +[@shivbhatia10]: https://github.com/shivbhatia10 +[@ologlogn]: https://github.com/ologlogn +[@LiaCastaneda]: https://github.com/LiaCastaneda [array_to_string]: https://github.com/apache/datafusion/pull/20639 [array_concat]: https://github.com/apache/datafusion/pull/20620 @@ -378,30 +376,36 @@ can find out how to reach us on the [communication doc]. [array_any_match]: https://github.com/apache/datafusion/pull/21903 [array_compact]: https://github.com/apache/datafusion/pull/21522 [cosine_distance]: https://github.com/apache/datafusion/pull/21542 +[inner_product]: https://github.com/apache/datafusion/pull/21861 +[array_normalize]: https://github.com/apache/datafusion/pull/22013 [cast_to_type]: https://github.com/apache/datafusion/pull/21322 [with_metadata]: https://github.com/apache/datafusion/pull/21509 +[round]: https://github.com/apache/datafusion/pull/21062 +[floor]: https://github.com/apache/datafusion/pull/21933 +[ceil]: https://github.com/apache/datafusion/pull/20593 +[soundex]: https://github.com/apache/datafusion/pull/20725 +[xxhash64]: https://github.com/apache/datafusion/pull/21967 +[array_contains]: https://github.com/apache/datafusion/pull/20685 + [morsel-driven design]: https://db.in.tum.de/~leis/papers/morsels.pdf -[ClickHouse paper]: https://www.vldb.org/pvldb/vol17/p3731-schulze.pdf -[PruningPredicate]: https://docs.rs/datafusion/latest/datafusion/physical_optimizer/pruning/struct.PruningPredicate.html [StatisticsRegistry]: https://docs.rs/datafusion/latest/datafusion/physical_plan/operator_statistics/struct.StatisticsRegistry.html [#17861]: https://github.com/apache/datafusion/pull/17861 [#10048]: https://github.com/apache/datafusion/issues/10048 +[#20674]: https://github.com/apache/datafusion/pull/20674 +[#20628]: https://github.com/apache/datafusion/pull/20628 [#19028]: https://github.com/apache/datafusion/issues/19028 -[#19946]: https://github.com/apache/datafusion/issues/19946 -[#20051]: https://github.com/apache/datafusion/pull/20051 -[#20059]: https://github.com/apache/datafusion/pull/20059 -[#20350]: https://github.com/apache/datafusion/pull/20350 [#20529]: https://github.com/apache/datafusion/issues/20529 -[#21075]: https://github.com/apache/datafusion/pull/21075 [#21327]: https://github.com/apache/datafusion/pull/21327 [#21342]: https://github.com/apache/datafusion/pull/21342 [#21351]: https://github.com/apache/datafusion/pull/21351 -[#21383]: https://github.com/apache/datafusion/pull/21383 [#21448]: https://github.com/apache/datafusion/pull/21448 [#21637]: https://github.com/apache/datafusion/pull/21637 -[#21934]: https://github.com/apache/datafusion/pull/21934 +[#21090]: https://github.com/apache/datafusion/pull/21090 +[#20822]: https://github.com/apache/datafusion/pull/20822 +[#20854]: https://github.com/apache/datafusion/pull/20854 +[#20925]: https://github.com/apache/datafusion/pull/20925 [#18223]: https://github.com/apache/datafusion/issues/18223 [#18136]: https://github.com/apache/datafusion/pull/18136 [#20312]: https://github.com/apache/datafusion/pull/20312 From 4f4cf4403a540d719bc3e94d09d285fea5fa34a8 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Wed, 10 Jun 2026 17:26:11 -0400 Subject: [PATCH 4/7] Intro, summary wordsmith --- content/blog/2026-06-08-datafusion-54.0.0.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/content/blog/2026-06-08-datafusion-54.0.0.md b/content/blog/2026-06-08-datafusion-54.0.0.md index b8435070..12fd89c4 100644 --- a/content/blog/2026-06-08-datafusion-54.0.0.md +++ b/content/blog/2026-06-08-datafusion-54.0.0.md @@ -28,9 +28,12 @@ limitations under the License. [TOC] We are proud to announce the release of [DataFusion 54.0.0]. This post highlights -some of the major improvements since [DataFusion 53.0.0]. The complete list of -changes is available in the [changelog]. Thanks to the [139 contributors] -(a new record!) for making this release possible. +some of the major improvements since [DataFusion 53.0.0]. Notable additions +include `LATERAL` joins, SQL lambda functions, and a new Avro reader, alongside +significant join, scan, and planning performance improvements. The complete list +of changes is available in the [changelog]. This release represents roughly 11 +weeks of development and 740 commits. Thanks to the [139 contributors] +(a new record!) for making it possible. [DataFusion 54.0.0]: https://crates.io/crates/datafusion/54.0.0 [DataFusion 53.0.0]: https://datafusion.apache.org/blog/2026/04/02/datafusion-53.0.0/ @@ -52,9 +55,8 @@ Query times are normalized using the ClickBench definition. See the for more details. We continue to make significant performance improvements in DataFusion, as -explained below. This release simplifies more predicates before execution, -prunes more redundant work out of plans, and makes joins, repartitioning, and -many built-in functions faster. +explained below. This release prunes more redundant work out of plans, +and makes joins, repartitioning, scans, and many built-in functions faster. ### Execution Operator Improvements @@ -165,6 +167,7 @@ correlating against a set-returning function. It uses decorrelation, so the subquery is evaluated once rather than re-executed per outer row. ```sql +-- For each row in t1, expand a series 1..t1_int and join the values back SELECT t1_id, t1_name, i FROM join_t1 t1 CROSS JOIN LATERAL ( From 8809e40d17921950ad051c5a78f7d04d9134c2a2 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Wed, 10 Jun 2026 17:37:35 -0400 Subject: [PATCH 5/7] obsess --- content/blog/2026-06-08-datafusion-54.0.0.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/content/blog/2026-06-08-datafusion-54.0.0.md b/content/blog/2026-06-08-datafusion-54.0.0.md index 12fd89c4..5d56c87f 100644 --- a/content/blog/2026-06-08-datafusion-54.0.0.md +++ b/content/blog/2026-06-08-datafusion-54.0.0.md @@ -55,8 +55,8 @@ Query times are normalized using the ClickBench definition. See the for more details. We continue to make significant performance improvements in DataFusion, as -explained below. This release prunes more redundant work out of plans, -and makes joins, repartitioning, scans, and many built-in functions faster. +explained below. This release prunes more redundant work out of plans and +makes joins, repartitioning, scans, and many built-in functions faster. ### Execution Operator Improvements @@ -207,9 +207,9 @@ SELECT array_transform(array_filter([1, 2, 3, 4, 5], x -> x > 2), x -> x * 10); -- [30, 40, 50] ``` -Thanks to [@gstvg] and [@rluvaton] for leading this effort, the [array_filter] -and [array_any_match] functions from [@ologlogn] and [@LiaCastaneda], and reviews -from [@comphead] and [@martin-g]. +Thanks to [@gstvg] and [@rluvaton] for leading this effort, [@ologlogn] and +[@LiaCastaneda] for the [array_filter] and [array_any_match] functions, and +[@comphead] and [@martin-g] for reviews. ### Spilling Nested Loop Joins @@ -256,9 +256,8 @@ Face Hub]. Thanks to [@kszucs] for this feature, with reviews from [@alamb]. DataFusion 54 adds new scalar functions including [array_compact], [cosine_distance], [inner_product], [array_normalize], [cast_to_type], and [with_metadata], plus nanosecond `date_part` support ([#20674]) and the `:` JSON -access operator ([#20628]). The -[cosine_distance], [inner_product], and [array_normalize] additions round out -DataFusion's vector-search building blocks. Thanks to [@comphead], [@crm26], +access operator ([#20628]). The [cosine_distance], [inner_product], and +[array_normalize] additions round out DataFusion's vector-search building blocks. Thanks to [@comphead], [@crm26], [@adriangb], [@mhilton], and [@Samyak2] for these contributions. **Spark-Compatible Functions**: From cf8e6eb0314110eab3c4b293d80a3afa15623501 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Thu, 11 Jun 2026 11:15:47 -0400 Subject: [PATCH 6/7] Add additional background and review list fo rlambda --- content/blog/2026-06-08-datafusion-54.0.0.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/content/blog/2026-06-08-datafusion-54.0.0.md b/content/blog/2026-06-08-datafusion-54.0.0.md index 5d56c87f..fa7337d6 100644 --- a/content/blog/2026-06-08-datafusion-54.0.0.md +++ b/content/blog/2026-06-08-datafusion-54.0.0.md @@ -207,9 +207,10 @@ SELECT array_transform(array_filter([1, 2, 3, 4, 5], x -> x > 2), x -> x * 10); -- [30, 40, 50] ``` -Thanks to [@gstvg] and [@rluvaton] for leading this effort, [@ologlogn] and -[@LiaCastaneda] for the [array_filter] and [array_any_match] functions, and -[@comphead] and [@martin-g] for reviews. +Thanks to [@gstvg] and [@rluvaton] for leading this effort (first prototyped in +[#18921]), [@ologlogn] and [@LiaCastaneda] for the [array_filter] and +[array_any_match] functions, and [@benbellick], [@comphead], [@martin-g], +[@pepijnve], and [@shehabgamin] for reviews. ### Spilling Nested Loop Joins @@ -360,6 +361,9 @@ can find out how to reach us on the [communication doc]. [@shivbhatia10]: https://github.com/shivbhatia10 [@ologlogn]: https://github.com/ologlogn [@LiaCastaneda]: https://github.com/LiaCastaneda +[@benbellick]: https://github.com/benbellick +[@pepijnve]: https://github.com/pepijnve +[@shehabgamin]: https://github.com/shehabgamin [array_to_string]: https://github.com/apache/datafusion/pull/20639 [array_concat]: https://github.com/apache/datafusion/pull/20620 @@ -394,6 +398,7 @@ can find out how to reach us on the [communication doc]. [StatisticsRegistry]: https://docs.rs/datafusion/latest/datafusion/physical_plan/operator_statistics/struct.StatisticsRegistry.html [#17861]: https://github.com/apache/datafusion/pull/17861 +[#18921]: https://github.com/apache/datafusion/pull/18921 [#10048]: https://github.com/apache/datafusion/issues/10048 [#20674]: https://github.com/apache/datafusion/pull/20674 [#20628]: https://github.com/apache/datafusion/pull/20628 From 0d2a51f26c2413ecfbc1919913da23c35bacdbca Mon Sep 17 00:00:00 2001 From: Matt Butrovich Date: Thu, 11 Jun 2026 12:39:57 -0400 Subject: [PATCH 7/7] update date --- ...-08-datafusion-54.0.0.md => 2026-06-12-datafusion-54.0.0.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename content/blog/{2026-06-08-datafusion-54.0.0.md => 2026-06-12-datafusion-54.0.0.md} (99%) diff --git a/content/blog/2026-06-08-datafusion-54.0.0.md b/content/blog/2026-06-12-datafusion-54.0.0.md similarity index 99% rename from content/blog/2026-06-08-datafusion-54.0.0.md rename to content/blog/2026-06-12-datafusion-54.0.0.md index fa7337d6..6febb8d2 100644 --- a/content/blog/2026-06-08-datafusion-54.0.0.md +++ b/content/blog/2026-06-12-datafusion-54.0.0.md @@ -1,7 +1,7 @@ --- layout: post title: Apache DataFusion 54.0.0 Released -date: 2026-06-08 +date: 2026-06-12 author: pmc categories: [release] ---