From 97253560c7cb4526a3e39142ead559eaae696487 Mon Sep 17 00:00:00 2001 From: Siew Kam Onn Date: Sat, 11 Jul 2026 12:57:57 +0800 Subject: [PATCH 1/3] refactor: update query and benchmark metrics to use octet_length() instead of length() for q27 and q28 --- benchmarks/queries/clickbench/queries/q27.sql | 2 +- benchmarks/queries/clickbench/queries/q28.sql | 2 +- benchmarks/sql_benchmarks/clickbench/benchmarks/q27.benchmark | 2 +- benchmarks/sql_benchmarks/clickbench/benchmarks/q28.benchmark | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/benchmarks/queries/clickbench/queries/q27.sql b/benchmarks/queries/clickbench/queries/q27.sql index ba234d34f8877..5fa06fb97e3d7 100644 --- a/benchmarks/queries/clickbench/queries/q27.sql +++ b/benchmarks/queries/clickbench/queries/q27.sql @@ -1,4 +1,4 @@ -- Must set for ClickBench hits_partitioned dataset. See https://github.com/apache/datafusion/issues/16591 -- set datafusion.execution.parquet.binary_as_string = true -SELECT "CounterID", AVG(length("URL")) AS l, COUNT(*) AS c FROM hits WHERE "URL" <> '' GROUP BY "CounterID" HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25; +SELECT "CounterID", AVG(octet_length("URL")) AS l, COUNT(*) AS c FROM hits WHERE "URL" <> '' GROUP BY "CounterID" HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25; diff --git a/benchmarks/queries/clickbench/queries/q28.sql b/benchmarks/queries/clickbench/queries/q28.sql index 6a3bd037bece7..5e9bfe488e912 100644 --- a/benchmarks/queries/clickbench/queries/q28.sql +++ b/benchmarks/queries/clickbench/queries/q28.sql @@ -1,4 +1,4 @@ -- Must set for ClickBench hits_partitioned dataset. See https://github.com/apache/datafusion/issues/16591 -- set datafusion.execution.parquet.binary_as_string = true -SELECT REGEXP_REPLACE("Referer", '^https?://(?:www\.)?([^/]+)/.*$', '\1') AS k, AVG(length("Referer")) AS l, COUNT(*) AS c, MIN("Referer") FROM hits WHERE "Referer" <> '' GROUP BY k HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25; +SELECT REGEXP_REPLACE("Referer", '^https?://(?:www\.)?([^/]+)/.*$', '\1') AS k, AVG(octet_length("Referer")) AS l, COUNT(*) AS c, MIN("Referer") FROM hits WHERE "Referer" <> '' GROUP BY k HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25; diff --git a/benchmarks/sql_benchmarks/clickbench/benchmarks/q27.benchmark b/benchmarks/sql_benchmarks/clickbench/benchmarks/q27.benchmark index c4531b0d6aa11..7d3bb338db1d9 100644 --- a/benchmarks/sql_benchmarks/clickbench/benchmarks/q27.benchmark +++ b/benchmarks/sql_benchmarks/clickbench/benchmarks/q27.benchmark @@ -11,6 +11,6 @@ SELECT COUNT(*) > 0 from hits; true run -SELECT "CounterID", AVG(length("URL")) AS l, COUNT(*) AS c FROM hits WHERE "URL" <> '' GROUP BY "CounterID" HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25; +SELECT "CounterID", AVG(octet_length("URL")) AS l, COUNT(*) AS c FROM hits WHERE "URL" <> '' GROUP BY "CounterID" HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25; result sql_benchmarks/clickbench/results/${CLICKBENCH_TYPE:-single}/q27.csv diff --git a/benchmarks/sql_benchmarks/clickbench/benchmarks/q28.benchmark b/benchmarks/sql_benchmarks/clickbench/benchmarks/q28.benchmark index 32599d608cc5e..94ae6fd25bfcd 100644 --- a/benchmarks/sql_benchmarks/clickbench/benchmarks/q28.benchmark +++ b/benchmarks/sql_benchmarks/clickbench/benchmarks/q28.benchmark @@ -11,6 +11,6 @@ SELECT COUNT(*) > 0 from hits; true run -SELECT REGEXP_REPLACE("Referer", '^https?://(?:www\.)?([^/]+)/.*$', '\1') AS k, AVG(length("Referer")) AS l, COUNT(*) AS c, MIN("Referer") FROM hits WHERE "Referer" <> '' GROUP BY k HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25; +SELECT REGEXP_REPLACE("Referer", '^https?://(?:www\.)?([^/]+)/.*$', '\1') AS k, AVG(octet_length("Referer")) AS l, COUNT(*) AS c, MIN("Referer") FROM hits WHERE "Referer" <> '' GROUP BY k HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25; result sql_benchmarks/clickbench/results/${CLICKBENCH_TYPE:-single}/q28.csv From 420c306e1ebfdfce8b939a0a9b4e9c68b400d593 Mon Sep 17 00:00:00 2001 From: Siew Kam Onn Date: Sat, 11 Jul 2026 13:12:58 +0800 Subject: [PATCH 2/3] feat(sql): update SQL logic tests to use octet_length function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated Q27/Q28 SQL to utilize octet_length(...). - Adjusted expected EXPLAIN output to remove character_length(hits.URL/Referer). - Modified plan labels to reflect octet_length(...). - Confirmed existing non-ASCII coverage with expr.slt containing SELECT octet_length('josé'). --- .../sqllogictest/test_files/clickbench.slt | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/datafusion/sqllogictest/test_files/clickbench.slt b/datafusion/sqllogictest/test_files/clickbench.slt index 96c4f38c653df..4a1ef833c91db 100644 --- a/datafusion/sqllogictest/test_files/clickbench.slt +++ b/datafusion/sqllogictest/test_files/clickbench.slt @@ -728,58 +728,58 @@ SELECT "SearchPhrase" FROM hits WHERE "SearchPhrase" <> '' ORDER BY "EventTime", ## Q27 query TT -EXPLAIN SELECT "CounterID", AVG(length("URL")) AS l, COUNT(*) AS c FROM hits WHERE "URL" <> '' GROUP BY "CounterID" HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25; +EXPLAIN SELECT "CounterID", AVG(octet_length("URL")) AS l, COUNT(*) AS c FROM hits WHERE "URL" <> '' GROUP BY "CounterID" HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25; ---- logical_plan 01)Sort: l DESC NULLS FIRST, fetch=25 -02)--Projection: hits.CounterID, avg(length(hits.URL)) AS l, count(Int64(1)) AS count(*) AS c +02)--Projection: hits.CounterID, avg(octet_length(hits.URL)) AS l, count(Int64(1)) AS count(*) AS c 03)----Filter: count(Int64(1)) > Int64(100000) -04)------Aggregate: groupBy=[[hits.CounterID]], aggr=[[avg(CAST(character_length(hits.URL) AS length(hits.URL) AS Float64)), count(Int64(1))]] +04)------Aggregate: groupBy=[[hits.CounterID]], aggr=[[avg(CAST(octet_length(hits.URL) AS Float64)), count(Int64(1))]] 05)--------SubqueryAlias: hits 06)----------Filter: hits_raw.URL != Utf8View("") 07)------------TableScan: hits_raw projection=[CounterID, URL], partial_filters=[hits_raw.URL != Utf8View("")] physical_plan 01)SortPreservingMergeExec: [l@1 DESC], fetch=25 -02)--ProjectionExec: expr=[CounterID@0 as CounterID, avg(length(hits.URL))@1 as l, count(Int64(1))@2 as c] -03)----SortExec: TopK(fetch=25), expr=[avg(length(hits.URL))@1 DESC], preserve_partitioning=[true] +02)--ProjectionExec: expr=[CounterID@0 as CounterID, avg(octet_length(hits.URL))@1 as l, count(Int64(1))@2 as c] +03)----SortExec: TopK(fetch=25), expr=[avg(octet_length(hits.URL))@1 DESC], preserve_partitioning=[true] 04)------FilterExec: count(Int64(1))@2 > 100000 -05)--------AggregateExec: mode=FinalPartitioned, gby=[CounterID@0 as CounterID], aggr=[avg(length(hits.URL)), count(Int64(1))] +05)--------AggregateExec: mode=FinalPartitioned, gby=[CounterID@0 as CounterID], aggr=[avg(octet_length(hits.URL)), count(Int64(1))] 06)----------RepartitionExec: partitioning=Hash([CounterID@0], 4), input_partitions=4 -07)------------AggregateExec: mode=Partial, gby=[CounterID@0 as CounterID], aggr=[avg(length(hits.URL)), count(Int64(1))] +07)------------AggregateExec: mode=Partial, gby=[CounterID@0 as CounterID], aggr=[avg(octet_length(hits.URL)), count(Int64(1))] 08)--------------FilterExec: URL@1 != 09)----------------RepartitionExec: partitioning=RoundRobinBatch(4), input_partitions=1 10)------------------DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/datafusion/core/tests/data/clickbench_hits_10.parquet]]}, projection=[CounterID, URL], file_type=parquet, predicate=URL@13 != , pruning_predicate=URL_null_count@2 != row_count@3 AND (URL_min@0 != OR != URL_max@1), required_guarantees=[URL not in ()] query IRI -SELECT "CounterID", AVG(length("URL")) AS l, COUNT(*) AS c FROM hits WHERE "URL" <> '' GROUP BY "CounterID" HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25; +SELECT "CounterID", AVG(octet_length("URL")) AS l, COUNT(*) AS c FROM hits WHERE "URL" <> '' GROUP BY "CounterID" HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25; ---- ## Q28 query TT -EXPLAIN SELECT REGEXP_REPLACE("Referer", '^https?://(?:www\.)?([^/]+)/.*$', '\1') AS k, AVG(length("Referer")) AS l, COUNT(*) AS c, MIN("Referer") FROM hits WHERE "Referer" <> '' GROUP BY k HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25; +EXPLAIN SELECT REGEXP_REPLACE("Referer", '^https?://(?:www\.)?([^/]+)/.*$', '\1') AS k, AVG(octet_length("Referer")) AS l, COUNT(*) AS c, MIN("Referer") FROM hits WHERE "Referer" <> '' GROUP BY k HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25; ---- logical_plan 01)Sort: l DESC NULLS FIRST, fetch=25 -02)--Projection: regexp_replace(hits.Referer,Utf8("^https?://(?:www\.)?([^/]+)/.*$"),Utf8("\1")) AS k, avg(length(hits.Referer)) AS l, count(Int64(1)) AS count(*) AS c, min(hits.Referer) +02)--Projection: regexp_replace(hits.Referer,Utf8("^https?://(?:www\.)?([^/]+)/.*$"),Utf8("\1")) AS k, avg(octet_length(hits.Referer)) AS l, count(Int64(1)) AS count(*) AS c, min(hits.Referer) 03)----Filter: count(Int64(1)) > Int64(100000) -04)------Aggregate: groupBy=[[regexp_replace(hits.Referer, Utf8View("^https?://(?:www\.)?([^/]+)/.*$"), Utf8View("\1")) AS regexp_replace(hits.Referer,Utf8("^https?://(?:www\.)?([^/]+)/.*$"),Utf8("\1"))]], aggr=[[avg(CAST(character_length(hits.Referer) AS length(hits.Referer) AS Float64)), count(Int64(1)), min(hits.Referer)]] +04)------Aggregate: groupBy=[[regexp_replace(hits.Referer, Utf8View("^https?://(?:www\.)?([^/]+)/.*$"), Utf8View("\1")) AS regexp_replace(hits.Referer,Utf8("^https?://(?:www\.)?([^/]+)/.*$"),Utf8("\1"))]], aggr=[[avg(CAST(octet_length(hits.Referer) AS Float64)), count(Int64(1)), min(hits.Referer)]] 05)--------SubqueryAlias: hits 06)----------Filter: hits_raw.Referer != Utf8View("") 07)------------TableScan: hits_raw projection=[Referer], partial_filters=[hits_raw.Referer != Utf8View("")] physical_plan 01)SortPreservingMergeExec: [l@1 DESC], fetch=25 -02)--ProjectionExec: expr=[regexp_replace(hits.Referer,Utf8("^https?://(?:www\.)?([^/]+)/.*$"),Utf8("\1"))@0 as k, avg(length(hits.Referer))@1 as l, count(Int64(1))@2 as c, min(hits.Referer)@3 as min(hits.Referer)] -03)----SortExec: TopK(fetch=25), expr=[avg(length(hits.Referer))@1 DESC], preserve_partitioning=[true] +02)--ProjectionExec: expr=[regexp_replace(hits.Referer,Utf8("^https?://(?:www\.)?([^/]+)/.*$"),Utf8("\1"))@0 as k, avg(octet_length(hits.Referer))@1 as l, count(Int64(1))@2 as c, min(hits.Referer)@3 as min(hits.Referer)] +03)----SortExec: TopK(fetch=25), expr=[avg(octet_length(hits.Referer))@1 DESC], preserve_partitioning=[true] 04)------FilterExec: count(Int64(1))@2 > 100000 -05)--------AggregateExec: mode=FinalPartitioned, gby=[regexp_replace(hits.Referer,Utf8("^https?://(?:www\.)?([^/]+)/.*$"),Utf8("\1"))@0 as regexp_replace(hits.Referer,Utf8("^https?://(?:www\.)?([^/]+)/.*$"),Utf8("\1"))], aggr=[avg(length(hits.Referer)), count(Int64(1)), min(hits.Referer)] +05)--------AggregateExec: mode=FinalPartitioned, gby=[regexp_replace(hits.Referer,Utf8("^https?://(?:www\.)?([^/]+)/.*$"),Utf8("\1"))@0 as regexp_replace(hits.Referer,Utf8("^https?://(?:www\.)?([^/]+)/.*$"),Utf8("\1"))], aggr=[avg(octet_length(hits.Referer)), count(Int64(1)), min(hits.Referer)] 06)----------RepartitionExec: partitioning=Hash([regexp_replace(hits.Referer,Utf8("^https?://(?:www\.)?([^/]+)/.*$"),Utf8("\1"))@0], 4), input_partitions=4 -07)------------AggregateExec: mode=Partial, gby=[regexp_replace(Referer@0, ^https?://(?:www\.)?([^/]+)/.*$, \1) as regexp_replace(hits.Referer,Utf8("^https?://(?:www\.)?([^/]+)/.*$"),Utf8("\1"))], aggr=[avg(length(hits.Referer)), count(Int64(1)), min(hits.Referer)] +07)------------AggregateExec: mode=Partial, gby=[regexp_replace(Referer@0, ^https?://(?:www\.)?([^/]+)/.*$, \1) as regexp_replace(hits.Referer,Utf8("^https?://(?:www\.)?([^/]+)/.*$"),Utf8("\1"))], aggr=[avg(octet_length(hits.Referer)), count(Int64(1)), min(hits.Referer)] 08)--------------FilterExec: Referer@0 != 09)----------------RepartitionExec: partitioning=RoundRobinBatch(4), input_partitions=1 10)------------------DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/datafusion/core/tests/data/clickbench_hits_10.parquet]]}, projection=[Referer], file_type=parquet, predicate=Referer@14 != , pruning_predicate=Referer_null_count@2 != row_count@3 AND (Referer_min@0 != OR != Referer_max@1), required_guarantees=[Referer not in ()] query TRIT -SELECT REGEXP_REPLACE("Referer", '^https?://(?:www\.)?([^/]+)/.*$', '\1') AS k, AVG(length("Referer")) AS l, COUNT(*) AS c, MIN("Referer") FROM hits WHERE "Referer" <> '' GROUP BY k HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25; +SELECT REGEXP_REPLACE("Referer", '^https?://(?:www\.)?([^/]+)/.*$', '\1') AS k, AVG(octet_length("Referer")) AS l, COUNT(*) AS c, MIN("Referer") FROM hits WHERE "Referer" <> '' GROUP BY k HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25; ---- ## Q29 From 08d730a2c19d69120885f41c220d4350eceb48ed Mon Sep 17 00:00:00 2001 From: Siew Kam Onn Date: Sat, 11 Jul 2026 13:28:26 +0800 Subject: [PATCH 3/3] feat: add comments to Q27 and Q28 query copies for byte-length semantics clarification - Updated comments in the following files: - benchmarks/queries/clickbench/queries/q27.sql - benchmarks/queries/clickbench/queries/q28.sql - benchmarks/sql_benchmarks/clickbench/benchmarks/q27.benchmark - benchmarks/sql_benchmarks/clickbench/benchmarks/q28.benchmark - Added note: "DataFusion length(...) counts characters; use octet_length(...) for ClickBench byte-length semantics." --- benchmarks/queries/clickbench/queries/q27.sql | 1 + benchmarks/queries/clickbench/queries/q28.sql | 1 + benchmarks/sql_benchmarks/clickbench/benchmarks/q27.benchmark | 1 + benchmarks/sql_benchmarks/clickbench/benchmarks/q28.benchmark | 1 + 4 files changed, 4 insertions(+) diff --git a/benchmarks/queries/clickbench/queries/q27.sql b/benchmarks/queries/clickbench/queries/q27.sql index 5fa06fb97e3d7..dbd6aeaf8128a 100644 --- a/benchmarks/queries/clickbench/queries/q27.sql +++ b/benchmarks/queries/clickbench/queries/q27.sql @@ -1,4 +1,5 @@ -- Must set for ClickBench hits_partitioned dataset. See https://github.com/apache/datafusion/issues/16591 -- set datafusion.execution.parquet.binary_as_string = true +-- DataFusion length(...) counts characters; use octet_length(...) for ClickBench byte-length semantics. SELECT "CounterID", AVG(octet_length("URL")) AS l, COUNT(*) AS c FROM hits WHERE "URL" <> '' GROUP BY "CounterID" HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25; diff --git a/benchmarks/queries/clickbench/queries/q28.sql b/benchmarks/queries/clickbench/queries/q28.sql index 5e9bfe488e912..6d00194b74929 100644 --- a/benchmarks/queries/clickbench/queries/q28.sql +++ b/benchmarks/queries/clickbench/queries/q28.sql @@ -1,4 +1,5 @@ -- Must set for ClickBench hits_partitioned dataset. See https://github.com/apache/datafusion/issues/16591 -- set datafusion.execution.parquet.binary_as_string = true +-- DataFusion length(...) counts characters; use octet_length(...) for ClickBench byte-length semantics. SELECT REGEXP_REPLACE("Referer", '^https?://(?:www\.)?([^/]+)/.*$', '\1') AS k, AVG(octet_length("Referer")) AS l, COUNT(*) AS c, MIN("Referer") FROM hits WHERE "Referer" <> '' GROUP BY k HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25; diff --git a/benchmarks/sql_benchmarks/clickbench/benchmarks/q27.benchmark b/benchmarks/sql_benchmarks/clickbench/benchmarks/q27.benchmark index 7d3bb338db1d9..84e43c2272d57 100644 --- a/benchmarks/sql_benchmarks/clickbench/benchmarks/q27.benchmark +++ b/benchmarks/sql_benchmarks/clickbench/benchmarks/q27.benchmark @@ -11,6 +11,7 @@ SELECT COUNT(*) > 0 from hits; true run +-- DataFusion length(...) counts characters; use octet_length(...) for ClickBench byte-length semantics. SELECT "CounterID", AVG(octet_length("URL")) AS l, COUNT(*) AS c FROM hits WHERE "URL" <> '' GROUP BY "CounterID" HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25; result sql_benchmarks/clickbench/results/${CLICKBENCH_TYPE:-single}/q27.csv diff --git a/benchmarks/sql_benchmarks/clickbench/benchmarks/q28.benchmark b/benchmarks/sql_benchmarks/clickbench/benchmarks/q28.benchmark index 94ae6fd25bfcd..02cbfb20c09f1 100644 --- a/benchmarks/sql_benchmarks/clickbench/benchmarks/q28.benchmark +++ b/benchmarks/sql_benchmarks/clickbench/benchmarks/q28.benchmark @@ -11,6 +11,7 @@ SELECT COUNT(*) > 0 from hits; true run +-- DataFusion length(...) counts characters; use octet_length(...) for ClickBench byte-length semantics. SELECT REGEXP_REPLACE("Referer", '^https?://(?:www\.)?([^/]+)/.*$', '\1') AS k, AVG(octet_length("Referer")) AS l, COUNT(*) AS c, MIN("Referer") FROM hits WHERE "Referer" <> '' GROUP BY k HAVING COUNT(*) > 100000 ORDER BY l DESC LIMIT 25; result sql_benchmarks/clickbench/results/${CLICKBENCH_TYPE:-single}/q28.csv