Skip to content

Add EF.Functions translations for the remaining ClickHouse date/time functions #58

Description

@alex-clickhouse

Problem

PR #57 adds the ClickHouse toStartOf* family as EF.Functions extension methods. It closes #56.

Many other ClickHouse date/time functions stay unavailable from LINQ. These functions have no .NET equivalent, so an EF.Functions entry point is the correct shape for them.

This issue is different from #55. Issue #55 covers the translation of standard .NET members and methods, such as .Year and .AddDays(1). Those must translate without an EF.Functions call. This issue covers only the ClickHouse-specific functions.

Suggested work

Use the same shape as PR #57 for each function:

  • an extension method on DbFunctions in ClickHouseDateTimeDbFunctionsExtensions
  • a MethodInfo entry in ClickHouseDateTimeMethodTranslator
  • a test in DateTimeFunctionsTranslationTests

Add the functions in this order. The order shows the expected value to users.

#FunctionsNotes
1dateDiff, ageAdd both. They give different results. Re-use the ClickHouseInterval enum from PR #57 for the unit. The return type is Int64, thus declare long.
2dateTruncThe general form of toStartOf*. Uses the same interval enum.
3toLastDayOfMonth, toLastDayOfWeekCompanions to toStartOf*. The same translator can hold them.
4toTimeZone, toUTCTimestamp, fromUTCTimestampDo this work after #53 is merged. The DateTimeOffset mapping changes the correct return type.
5toWeek, toYearWeek, toISOWeek, toISOYearWeek numbers with a mode argument. .NET has no equivalent.
6toUnixTimestamp, fromUnixTimestamptoUnixTimestamp returns UInt32.
7formatDateTimeThe only way to format on the server. ToString(format) cannot translate.
8changeYear, changeMonth, changeDay, changeHour, changeMinute, changeSecondSix methods with one shape.
9toRelativeYearNum ... toRelativeSecondNumInteger bucket keys for GROUP BY.
10dateName, monthNameName extraction.

Out of scope

Do not add these functions now. They have a small audience. Add them if a user asks.

formatDateTimeInJodaSyntax, fromUnixTimestampInJodaSyntax, toModifiedJulianDay, fromModifiedJulianDay, toDaysSinceYearZero, fromDaysSinceYearZero, nowInBlock, serverTimezone, UTCTimestamp, localtime, addInterval, addTupleOfIntervals, timeSlot, timeSlots, toYYYYMMDD, toYYYYMMDDhhmmss, YYYYMMDDToDate.

Measured ClickHouse behaviour

These results come from a ClickHouse 26.7.1 server. Use them when you write the tests.

Return types are narrow. The translator must attach the correct type mapping, or the read fails:

ExpressionReturn type
toYear(...), toDayOfYear(...), toMillisecond(...)UInt16
toMonth(...), toDayOfMonth(...), toHour(...), toDayOfWeek(...)UInt8
dateDiff(...)Int64
toUnixTimestamp(...)UInt32
toStartOfMonth(DateTime)Date, not DateTime
toDate(...) - toDate(...)Int32, a count of days

dateDiff and age give different results. dateDiff counts the unit boundaries between the two values. age gives the number of full units.

SELECT dateDiff('month', toDate('2021-12-29'), toDate('2022-01-01')); -- 1SELECT age('month', toDate('2021-12-29'), toDate('2022-01-01')); -- 0

Other measured values:

SELECT toLastDayOfMonth(toDate('2026-02-03')); -- 2026-02-28SELECT toLastDayOfWeek(toDate('2026-08-12')); -- 2026-08-15, a SaturdaySELECT toISOWeek(toDate('2026-08-12')); -- 33SELECT toWeek(toDate('2026-08-12')); -- 32SELECT toTimeZone(toDateTime('2026-08-12 12:00:00', 'UTC'), 'Europe/Paris'); -- 2026-08-12 14:00:00

toLastDayOfWeek uses mode 0 by default, and mode 0 starts the week on Sunday. This agrees with toStartOfWeek, which PR #57 documents. Be careful: toDayOfWeek also has a mode 0, but that mode starts the week on Monday. The default mode is not the same across the family.

Related change: the evaluatable expression filter

ClickHouseEvaluatableExpressionFilter.IsEvaluatableExpression has one hard-coded type test for each class of EF.Functions methods. PR #57 adds the second test:

MethodCallExpressionmethodCallExpressionwhenmethodCallExpression.Method.DeclaringType ==typeof(ClickHouseJsonDbFunctionsExtensions)=>false,MethodCallExpressionmethodCallExpressionwhenmethodCallExpression.Method.DeclaringType ==typeof(ClickHouseDateTimeDbFunctionsExtensions)=>false,

Each new class of EF.Functions methods adds one more test. If a contributor forgets the test, the calls evaluate on the client and become constants. The failure is silent.

Replace the tests with one general rule. There are two options:

  • a marker attribute on the extensions class
  • a static set of types, which each extensions class registers itself in

Make this change before a third class exists.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions