diff --git a/plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/ClickHouseClient.java b/plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/ClickHouseClient.java index c3453e10f66e..6326122b2508 100644 --- a/plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/ClickHouseClient.java +++ b/plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/ClickHouseClient.java @@ -30,10 +30,13 @@ import io.trino.plugin.base.expression.ConnectorExpressionRewriter; import io.trino.plugin.base.expression.ConnectorExpressionRule.RewriteContext; import io.trino.plugin.base.mapping.IdentifierMapping; +import io.trino.plugin.base.projection.ProjectFunctionRewriter; +import io.trino.plugin.base.projection.ProjectFunctionRule; import io.trino.plugin.clickhouse.expression.RewriteComparison; import io.trino.plugin.clickhouse.expression.RewriteLike; import io.trino.plugin.clickhouse.expression.RewriteStringIn; import io.trino.plugin.clickhouse.expression.RewriteTimestampConstant; +import io.trino.plugin.clickhouse.expression.RewriteTimestampExtraction; import io.trino.plugin.jdbc.BaseJdbcClient; import io.trino.plugin.jdbc.BaseJdbcConfig; import io.trino.plugin.jdbc.ColumnMapping; @@ -226,6 +229,7 @@ public class ClickHouseClient public static final int DEFAULT_DOMAIN_COMPACTION_THRESHOLD = 1_000; private final ConnectorExpressionRewriter connectorExpressionRewriter; + private final ProjectFunctionRewriter projectFunctionRewriter; private final AggregateFunctionRewriter aggregateFunctionRewriter; private final Type uuidType; private final Type ipAddressType; @@ -255,6 +259,13 @@ public ClickHouseClient( .map("$not(value: boolean)").to("NOT value") .map("$is_null(value)").to("value IS NULL") .build(); + + this.projectFunctionRewriter = new ProjectFunctionRewriter<>( + this.connectorExpressionRewriter, + ImmutableSet.>builder() + .add(new RewriteTimestampExtraction()) + .build()); + this.aggregateFunctionRewriter = new AggregateFunctionRewriter<>( this.connectorExpressionRewriter, ImmutableSet.>builder() @@ -278,6 +289,12 @@ public Optional implementAggregation(ConnectorSession session, A return aggregateFunctionRewriter.rewrite(session, aggregate, assignments); } + @Override + public Optional convertProjection(ConnectorSession session, JdbcTableHandle handle, ConnectorExpression expression, Map assignments) + { + return projectFunctionRewriter.rewrite(session, handle, expression, assignments); + } + @Override public Optional convertPredicate(ConnectorSession session, ConnectorExpression expression, Map assignments) { diff --git a/plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/expression/RewriteTimestampExtraction.java b/plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/expression/RewriteTimestampExtraction.java new file mode 100644 index 000000000000..098c1b653e50 --- /dev/null +++ b/plugin/trino-clickhouse/src/main/java/io/trino/plugin/clickhouse/expression/RewriteTimestampExtraction.java @@ -0,0 +1,100 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.plugin.clickhouse.expression; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.trino.matching.Capture; +import io.trino.matching.Captures; +import io.trino.matching.Pattern; +import io.trino.plugin.base.projection.ProjectFunctionRule; +import io.trino.plugin.jdbc.JdbcExpression; +import io.trino.plugin.jdbc.JdbcTypeHandle; +import io.trino.plugin.jdbc.expression.ParameterizedExpression; +import io.trino.spi.connector.ConnectorTableHandle; +import io.trino.spi.expression.Call; +import io.trino.spi.expression.ConnectorExpression; +import io.trino.spi.expression.Variable; +import io.trino.spi.type.BigintType; +import io.trino.spi.type.TimestampType; +import io.trino.spi.type.TimestampWithTimeZoneType; + +import java.util.Map; +import java.util.Optional; + +import static io.trino.matching.Capture.newCapture; +import static io.trino.plugin.base.expression.ConnectorExpressionPatterns.argument; +import static io.trino.plugin.base.expression.ConnectorExpressionPatterns.argumentCount; +import static io.trino.plugin.base.expression.ConnectorExpressionPatterns.call; +import static io.trino.plugin.base.expression.ConnectorExpressionPatterns.functionName; +import static io.trino.plugin.base.expression.ConnectorExpressionPatterns.type; +import static io.trino.plugin.base.expression.ConnectorExpressionPatterns.variable; +import static java.sql.Types.BIGINT; + +public class RewriteTimestampExtraction + implements ProjectFunctionRule +{ + private static final Capture ARGUMENT = newCapture(); + // timezone_hour / timezone_minute(timestamp) + // Returns the hour/minute of the time zone offset from timestamp + // are not applicable for Clickhouse as information about time zone is stored within column metadata + private static final Map TRINO_TO_CLICKHOUSE = ImmutableMap.builder() + .put("day", "toDayOfMonth(%s)") + .put("day_of_month", "toDayOfMonth(%s)") + .put("day_of_week", "toDayOfWeek(%s)") + .put("day_of_year", "toDayOfYear(%s)") + .put("dow", "toDayOfWeek(%s)") + .put("doy", "toDayOfYear(%s)") + .put("hour", "toHour(%s)") + .put("millisecond", "toMillisecond(%s)") + .put("minute", "toMinute(%s)") + .put("month", "toMonth(%s)") + .put("quarter", "toQuarter(%s)") + .put("second", "toSecond(%s)") + .put("week", "toWeek(%s, 4)") + .put("week_of_year", "toWeek(%s, 4)") + .put("year", "toYear(%s)") + // TODO no 1:1 mapping ? + // .put("year_of_week", "toYearWeek(%s, 4)") + // .put("yow", "toYearWeek(%s, 4)") + .buildOrThrow(); + + private static final Pattern PATTERN = call() + .with(functionName().matching(x -> TRINO_TO_CLICKHOUSE.containsKey(x.getName()))) + .with(argumentCount().equalTo(1)) + .with(argument(0).matching(variable().capturedAs(ARGUMENT).with(type().matching(type -> type instanceof TimestampType || type instanceof TimestampWithTimeZoneType)))); + + @Override + public Pattern getPattern() + { + return PATTERN; + } + + @Override + public Optional rewrite(ConnectorTableHandle handle, ConnectorExpression projectionExpression, Captures captures, RewriteContext context) + { + Variable argument = captures.get(ARGUMENT); + Optional translatedArgument = context.rewriteExpression(argument); + if (translatedArgument.isEmpty()) { + return Optional.empty(); + } + String functionName = ((Call) projectionExpression).getFunctionName().getName(); + String clickhouseFunctionPattern = TRINO_TO_CLICKHOUSE.get(functionName); + JdbcTypeHandle targetJdbcTypeHandle = new JdbcTypeHandle(BIGINT, Optional.of(BigintType.BIGINT.getBaseName()), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()); + return Optional.of(new JdbcExpression( + clickhouseFunctionPattern.formatted(translatedArgument.get().expression()), + ImmutableList.copyOf(translatedArgument.get().parameters()), + targetJdbcTypeHandle)); + } +} diff --git a/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/BaseClickHouseTypeMapping.java b/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/BaseClickHouseTypeMapping.java index 65cc619c70ba..ecaaa630e1c8 100644 --- a/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/BaseClickHouseTypeMapping.java +++ b/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/BaseClickHouseTypeMapping.java @@ -19,6 +19,7 @@ import io.trino.spi.type.TimestampWithTimeZoneType; import io.trino.spi.type.UuidType; import io.trino.sql.planner.plan.FilterNode; +import io.trino.sql.planner.plan.ProjectNode; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.TestingSession; import io.trino.testing.datatype.CreateAndInsertDataSetup; @@ -1128,6 +1129,8 @@ c_datetime64_9_zone_2 DateTime64(9, 'Asia/Istanbul') "'8', '2024-01-01 00:00:00', '2024-01-01 00:00:00', '2024-01-01 00:00:00.123456788', '2024-01-01 00:00:00.123456789', '2024-01-01 00:00:00.123456788', '2024-01-01 00:00:00.123456789', '2024-01-01 00:00:00.123456788', '2024-01-01 00:00:00.123456789', '2024-01-01 00:00:00.123456788', '2024-01-01 00:00:00.123456789'", "'9', '2024-01-01 00:00:00', '2024-01-01 00:00:00', '2024-01-01 00:00:00.123456789', '2024-01-01 00:00:00.123456789', '2024-01-01 00:00:00.123456789', '2024-01-01 00:00:00.123456789', '2024-01-01 00:00:00.123456789', '2024-01-01 00:00:00.123456789', '2024-01-01 00:00:00.123456789', '2024-01-01 00:00:00.123456789'" ))) { + testAggregationPushdown(table.getName()); + assertThat(query(mapStringAsVarchar, "SELECT some_column FROM " + table.getName() + " WHERE c_datetime_1 = TIMESTAMP '2024-01-01 00:00:00'")).isFullyPushedDown(); assertThat(query(mapStringAsVarchar, "SELECT some_column FROM " + table.getName() + " WHERE c_datetime_1 = TIMESTAMP '2024-01-01 00:00:00'" + withConnectorExpression)).isFullyPushedDown(); assertThat(query(mapStringAsVarchar, "SELECT some_column FROM " + table.getName() + " WHERE c_datetime64_3_1 = TIMESTAMP '2024-01-01 00:00:00.0'")).isFullyPushedDown(); @@ -1265,6 +1268,114 @@ c_datetime64_9_zone_2 DateTime64(9, 'Asia/Istanbul') } } + public void testAggregationPushdown(String name) + { + assertThat(query("SELECT count(*) FROM %s".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT count(some_column) FROM %s".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT c_datetime_1, min(some_column) FROM %s GROUP BY c_datetime_1".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT c_datetime_1, max(some_column) FROM %s GROUP BY c_datetime_1".formatted(name))).isFullyPushedDown(); + + assertThat(query("SELECT day_of_month(c_datetime_1) FROM %s GROUP BY day_of_month(c_datetime_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT day(c_datetime_1) FROM %s GROUP BY day(c_datetime_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT day_of_month(c_datetime_1) FROM %s GROUP BY day_of_month(c_datetime_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT day_of_week(c_datetime_1) FROM %s GROUP BY day_of_week(c_datetime_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT day_of_year(c_datetime_1) FROM %s GROUP BY day_of_year(c_datetime_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT dow(c_datetime_1) FROM %s GROUP BY dow(c_datetime_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT doy(c_datetime_1) FROM %s GROUP BY doy(c_datetime_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT hour(c_datetime_1) FROM %s GROUP BY hour(c_datetime_1)".formatted(name))).isFullyPushedDown(); + // v24.2 Changelog +// assertThat(query( "SELECT count(some_column) FROM %s GROUP BY millisecond(c_datetime_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT minute(c_datetime_1) FROM %s GROUP BY minute(c_datetime_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT month(c_datetime_1) FROM %s GROUP BY month(c_datetime_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT quarter(c_datetime_1) FROM %s GROUP BY quarter(c_datetime_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT second(c_datetime_1) FROM %s GROUP BY second(c_datetime_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT week(c_datetime_1) FROM %s GROUP BY week(c_datetime_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT week_of_year(c_datetime_1) FROM %s GROUP BY week_of_year(c_datetime_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT year(c_datetime_1) FROM %s GROUP BY year(c_datetime_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT year_of_week(c_datetime_1) FROM %s GROUP BY year_of_week(c_datetime_1)".formatted(name))).isNotFullyPushedDown(ProjectNode.class); + assertThat(query("SELECT yow(c_datetime_1) FROM %s GROUP BY yow(c_datetime_1)".formatted(name))).isNotFullyPushedDown(ProjectNode.class); + + assertThat(query("SELECT day_of_month(c_datetime64_3_1) FROM %s GROUP BY day_of_month(c_datetime64_3_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT day(c_datetime64_3_1) FROM %s GROUP BY day(c_datetime64_3_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT day_of_month(c_datetime64_3_1) FROM %s GROUP BY day_of_month(c_datetime64_3_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT day_of_week(c_datetime64_3_1) FROM %s GROUP BY day_of_week(c_datetime64_3_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT day_of_year(c_datetime64_3_1) FROM %s GROUP BY day_of_year(c_datetime64_3_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT dow(c_datetime64_3_1) FROM %s GROUP BY dow(c_datetime64_3_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT doy(c_datetime64_3_1) FROM %s GROUP BY doy(c_datetime64_3_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT hour(c_datetime64_3_1) FROM %s GROUP BY hour(c_datetime64_3_1)".formatted(name))).isFullyPushedDown(); + // v24.2 Changelog +// assertThat(query( "SELECT count(some_column) FROM %s GROUP BY millisecond(c_datetime64_3_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT minute(c_datetime64_3_1) FROM %s GROUP BY minute(c_datetime64_3_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT month(c_datetime64_3_1) FROM %s GROUP BY month(c_datetime64_3_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT quarter(c_datetime64_3_1) FROM %s GROUP BY quarter(c_datetime64_3_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT second(c_datetime64_3_1) FROM %s GROUP BY second(c_datetime64_3_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT week(c_datetime64_3_1) FROM %s GROUP BY week(c_datetime64_3_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT week_of_year(c_datetime64_3_1) FROM %s GROUP BY week_of_year(c_datetime64_3_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT year(c_datetime64_3_1) FROM %s GROUP BY year(c_datetime64_3_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT year_of_week(c_datetime64_3_1) FROM %s GROUP BY year_of_week(c_datetime64_3_1)".formatted(name))).isNotFullyPushedDown(ProjectNode.class); + assertThat(query("SELECT yow(c_datetime64_3_1) FROM %s GROUP BY yow(c_datetime64_3_1)".formatted(name))).isNotFullyPushedDown(ProjectNode.class); + + assertThat(query("SELECT day_of_month(c_datetime64_9_1) FROM %s GROUP BY day_of_month(c_datetime64_9_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT day(c_datetime64_9_1) FROM %s GROUP BY day(c_datetime64_9_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT day_of_month(c_datetime64_9_1) FROM %s GROUP BY day_of_month(c_datetime64_9_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT day_of_week(c_datetime64_9_1) FROM %s GROUP BY day_of_week(c_datetime64_9_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT day_of_year(c_datetime64_9_1) FROM %s GROUP BY day_of_year(c_datetime64_9_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT dow(c_datetime64_9_1) FROM %s GROUP BY dow(c_datetime64_9_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT doy(c_datetime64_9_1) FROM %s GROUP BY doy(c_datetime64_9_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT hour(c_datetime64_9_1) FROM %s GROUP BY hour(c_datetime64_9_1)".formatted(name))).isFullyPushedDown(); + // v24.2 Changelog +// assertThat(query( "SELECT count(some_column) FROM %s GROUP BY millisecond(c_datetime64_9_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT minute(c_datetime64_9_1) FROM %s GROUP BY minute(c_datetime64_9_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT month(c_datetime64_9_1) FROM %s GROUP BY month(c_datetime64_9_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT quarter(c_datetime64_9_1) FROM %s GROUP BY quarter(c_datetime64_9_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT second(c_datetime64_9_1) FROM %s GROUP BY second(c_datetime64_9_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT week(c_datetime64_9_1) FROM %s GROUP BY week(c_datetime64_9_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT week_of_year(c_datetime64_9_1) FROM %s GROUP BY week_of_year(c_datetime64_9_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT year(c_datetime64_9_1) FROM %s GROUP BY year(c_datetime64_9_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT year_of_week(c_datetime64_9_1) FROM %s GROUP BY year_of_week(c_datetime64_9_1)".formatted(name))).isNotFullyPushedDown(ProjectNode.class); + assertThat(query("SELECT yow(c_datetime64_9_1) FROM %s GROUP BY yow(c_datetime64_9_1)".formatted(name))).isNotFullyPushedDown(ProjectNode.class); + + assertThat(query("SELECT day_of_month(c_datetime64_3_zone_1) FROM %s GROUP BY day_of_month(c_datetime64_3_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT day(c_datetime64_3_zone_1) FROM %s GROUP BY day(c_datetime64_3_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT day_of_month(c_datetime64_3_zone_1) FROM %s GROUP BY day_of_month(c_datetime64_3_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT day_of_week(c_datetime64_3_zone_1) FROM %s GROUP BY day_of_week(c_datetime64_3_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT day_of_year(c_datetime64_3_zone_1) FROM %s GROUP BY day_of_year(c_datetime64_3_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT dow(c_datetime64_3_zone_1) FROM %s GROUP BY dow(c_datetime64_3_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT doy(c_datetime64_3_zone_1) FROM %s GROUP BY doy(c_datetime64_3_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT hour(c_datetime64_3_zone_1) FROM %s GROUP BY hour(c_datetime64_3_zone_1)".formatted(name))).isFullyPushedDown(); + // v24.2 Changelog +// assertThat(query( "SELECT count(some_column) FROM %s GROUP BY millisecond(c_datetime64_3_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT minute(c_datetime64_3_zone_1) FROM %s GROUP BY minute(c_datetime64_3_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT month(c_datetime64_3_zone_1) FROM %s GROUP BY month(c_datetime64_3_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT quarter(c_datetime64_3_zone_1) FROM %s GROUP BY quarter(c_datetime64_3_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT second(c_datetime64_3_zone_1) FROM %s GROUP BY second(c_datetime64_3_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT week(c_datetime64_3_zone_1) FROM %s GROUP BY week(c_datetime64_3_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT week_of_year(c_datetime64_3_zone_1) FROM %s GROUP BY week_of_year(c_datetime64_3_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT year(c_datetime64_3_zone_1) FROM %s GROUP BY year(c_datetime64_3_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT year_of_week(c_datetime64_3_zone_1) FROM %s GROUP BY year_of_week(c_datetime64_3_zone_1)".formatted(name))).isNotFullyPushedDown(ProjectNode.class); + assertThat(query("SELECT yow(c_datetime64_3_zone_1) FROM %s GROUP BY yow(c_datetime64_3_zone_1)".formatted(name))).isNotFullyPushedDown(ProjectNode.class); + + assertThat(query("SELECT day_of_month(c_datetime64_9_zone_1) FROM %s GROUP BY day_of_month(c_datetime64_9_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT day(c_datetime64_9_zone_1) FROM %s GROUP BY day(c_datetime64_9_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT day_of_month(c_datetime64_9_zone_1) FROM %s GROUP BY day_of_month(c_datetime64_9_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT day_of_week(c_datetime64_9_zone_1) FROM %s GROUP BY day_of_week(c_datetime64_9_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT day_of_year(c_datetime64_9_zone_1) FROM %s GROUP BY day_of_year(c_datetime64_9_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT dow(c_datetime64_9_zone_1) FROM %s GROUP BY dow(c_datetime64_9_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT doy(c_datetime64_9_zone_1) FROM %s GROUP BY doy(c_datetime64_9_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT hour(c_datetime64_9_zone_1) FROM %s GROUP BY hour(c_datetime64_9_zone_1)".formatted(name))).isFullyPushedDown(); + // v24.2 Changelog +// assertThat(query( "SELECT count(some_column) FROM %s GROUP BY millisecond(c_datetime64_9_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT minute(c_datetime64_9_zone_1) FROM %s GROUP BY minute(c_datetime64_9_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT month(c_datetime64_9_zone_1) FROM %s GROUP BY month(c_datetime64_9_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT quarter(c_datetime64_9_zone_1) FROM %s GROUP BY quarter(c_datetime64_9_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT second(c_datetime64_9_zone_1) FROM %s GROUP BY second(c_datetime64_9_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT week(c_datetime64_9_zone_1) FROM %s GROUP BY week(c_datetime64_9_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT week_of_year(c_datetime64_9_zone_1) FROM %s GROUP BY week_of_year(c_datetime64_9_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT year(c_datetime64_9_zone_1) FROM %s GROUP BY year(c_datetime64_9_zone_1)".formatted(name))).isFullyPushedDown(); + assertThat(query("SELECT year_of_week(c_datetime64_9_zone_1) FROM %s GROUP BY year_of_week(c_datetime64_9_zone_1)".formatted(name))).isNotFullyPushedDown(ProjectNode.class); + assertThat(query("SELECT yow(c_datetime64_9_zone_1) FROM %s GROUP BY yow(c_datetime64_9_zone_1)".formatted(name))).isNotFullyPushedDown(ProjectNode.class); + } + private List timezones() { return ImmutableList.of(