Table of Contents

Class ClickHouseSqlExpressionConvertVisitor

Namespace
LinqToDB.Internal.DataProvider.ClickHouse
Assembly
linq2db.dll
public class ClickHouseSqlExpressionConvertVisitor : SqlExpressionConvertVisitor
Inheritance
object
ClickHouseSqlExpressionConvertVisitor
Inherited Members
Extension Methods

Constructors

ClickHouseSqlExpressionConvertVisitor(bool, ClickHouseOptions)

public ClickHouseSqlExpressionConvertVisitor(bool allowModify, ClickHouseOptions providerOptions)

Parameters

allowModify bool
providerOptions ClickHouseOptions

Properties

CanLowerIntervalDifference

Whether an elapsed date difference can be lowered to a value here.

public override bool CanLowerIntervalDifference { get; }

Property Value

bool

Remarks

Declared beside the lowering it describes, and read by the member translator through ITranslationContext.ProviderFlags. The translator has to ask before it builds anything, because a difference it does not build stays an ordinary .NET subtraction and is computed on materialisation - and by the time this visitor runs, the read expression is already bound to its columns, so there is no going back.

CanLowerIntervalShift

Whether LowerTemporalArithmetic(SqlTemporalArithmeticExpression) can express a date shifted by an interval at all.

public override bool CanLowerIntervalShift { get; }

Property Value

bool

Remarks

Read by the member translator so a declared duration added to a date is declined while the expression is still being built, rather than reaching the builder as a node nothing can render - a refusal there has no client-side fallback left. The default follows what the base implementation needs: it spends the amount through ShiftDate(SqlIntervalUnit, ISqlExpression, ISqlExpression) at FinestDateUnit, so a provider that names no finest unit cannot lower one.

Only the declared half is declined early. A shift by a computed difference is left to be built, because start + (end - start) cancels against the difference it came from and asks the provider for nothing - refusing it here would sink a query that works everywhere.

ConcatRequiresExplicitStringCast

When true (default), ConvertConcat(SqlConcatExpression) wraps every non-string operand in an explicit CAST(... AS VARCHAR(N)) before adding it to the concat chain. Required for providers whose concat operator is + (SQL Server pre-2025, SqlCe, Access) — SQL-standard data-type precedence would otherwise try to coerce string operands to the non-string side's type. Most providers whose final concat operator is || (PostgreSQL / Oracle / SQLite / SAP HANA / DuckDB / Firebird / DB2 / Informix / SQL Server 2025+) or CONCAT(...) function (MySQL / ClickHouse) auto-coerce non-string operands and override this to false for cleaner SQL. Sybase ASE is the exception: it emits || but keeps this true, since ASE requires an explicit convert() for non-character operands under both + and ||.

protected override bool ConcatRequiresExplicitStringCast { get; }

Property Value

bool

LikeCharactersToEscape

Characters with special meaning in LIKE predicate (defined by LikeCharactersToEscape) that should be escaped to be used as matched character. Default: ["%", "_", "?", "*", "#", "[", "]"].

public override string[] LikeCharactersToEscape { get; }

Property Value

string[]

LikeEscapeCharacter

Escape sequence/character to escape special characters in LIKE predicate (defined by LikeCharactersToEscape). Default: "~".

public override string LikeEscapeCharacter { get; }

Property Value

string

LikeIsEscapeSupported

Should be true for provider with LIKE ... ESCAPE modifier support. Default: true.

public override bool LikeIsEscapeSupported { get; }

Property Value

bool

SupportsNullInColumn

protected override bool SupportsNullInColumn { get; }

Property Value

bool

Methods

ConvertDefaultValue(SqlDefaultValueExpression)

Writes the default a reader substitutes for a NULL: as its value here, and as the least value of the column's type where a provider's type cannot hold it.

public override ISqlExpression ConvertDefaultValue(SqlDefaultValueExpression expression)

Parameters

expression SqlDefaultValueExpression

The default to write.

Returns

ISqlExpression

The expression the default is written as.

ConvertLikePredicate(Like)

public override ISqlPredicate ConvertLikePredicate(SqlPredicate.Like predicate)

Parameters

predicate SqlPredicate.Like

Returns

ISqlPredicate

ConvertSearchStringPredicate(SearchString)

public override ISqlPredicate ConvertSearchStringPredicate(SqlPredicate.SearchString predicate)

Parameters

predicate SqlPredicate.SearchString

Returns

ISqlPredicate

ConvertSqlBinaryExpression(SqlBinaryExpression)

public override IQueryElement ConvertSqlBinaryExpression(SqlBinaryExpression element)

Parameters

element SqlBinaryExpression

Returns

IQueryElement

ConvertSqlExtendedFunction(SqlExtendedFunction)

public override ISqlExpression ConvertSqlExtendedFunction(SqlExtendedFunction func)

Parameters

func SqlExtendedFunction

Returns

ISqlExpression

ConvertSqlFunction(SqlFunction)

public override ISqlExpression ConvertSqlFunction(SqlFunction func)

Parameters

func SqlFunction

Returns

ISqlExpression

ConvertSqlUnaryExpression(SqlUnaryExpression)

public override ISqlExpression ConvertSqlUnaryExpression(SqlUnaryExpression element)

Parameters

element SqlUnaryExpression

Returns

ISqlExpression

ElapsedTicks(SqlIntervalDifferenceExpression)

Elapsed ticks from the nanosecond timestamps, divided by a hundred.

protected override ISqlExpression? ElapsedTicks(SqlIntervalDifferenceExpression element)

Parameters

element SqlIntervalDifferenceExpression

Returns

ISqlExpression

Remarks

An operand is coerced to DateTime64(7), which is a tick exactly, so its nanosecond value is a whole multiple of a hundred and the division is exact. A DateTime64 finer than a tick keeps its own precision, so it is truncated to whole ticks. date_diff is not used because its finest unit is the second.

Nanoseconds in an long reach from 1678 to 2262, narrower than what a DateTime64(7) column itself holds. That is the same boundary the millisecond form of DateAdd already works within on this provider.

The span between two of them is narrower still, because the subtraction is taken in nanoseconds as well: two endpoints inside that window can differ by more than an long holds, so the usable span is about 292 years rather than the 584 the endpoints cover. ClickHouse wraps instead of raising, so a wider difference comes back as a plausible-looking wrong number.

IsWindowOrderByRequired(SqlExtendedFunction)

NTILE alone, and by way of the frame rather than the ordering: unordered, the window defaults to a frame ClickHouse will not accept for it (Unsupported window frame type for function 'NTILE'). Giving it a sort key restores the default frame it wants.

protected override bool IsWindowOrderByRequired(SqlExtendedFunction func)

Parameters

func SqlExtendedFunction

Returns

bool

LowerTemporalArithmetic(SqlTemporalArithmeticExpression)

Shifts by the interval expressed in nanoseconds, the unit that matches what a tick is.

protected override ISqlExpression? LowerTemporalArithmetic(SqlTemporalArithmeticExpression element)

Parameters

element SqlTemporalArithmeticExpression

Returns

ISqlExpression

Remarks

toIntervalNanosecond takes an expression, so the count needs no decomposition. Scaling ticks up by a hundred caps the amount at long.MaxValue / 100 - about 292 years, far short of what a TimeSpan holds, but past any span these timestamps measure exactly: ElapsedTicks(SqlIntervalDifferenceExpression) above carries the same ceiling, and for the same reason.

The date being shifted is coerced as well: a sub-second interval cannot be added to a Date or a Date32, which ClickHouse refuses with addNanoseconds cannot be used with Date32.

TruncateDivide(ISqlExpression, long)

intDiv, because ClickHouse's / produces a float even between two integers.

protected override ISqlExpression TruncateDivide(ISqlExpression value, long divisor)

Parameters

value ISqlExpression
divisor long

Returns

ISqlExpression

VisitInListPredicate(InList)

Coerces a date/time operand of IN to DateTime64 when a value in the list is one, or may be one: the values of a captured collection beside a computed operand keep their own type when written. A DateOnly operand is left as is: its values have no time part and are written as dates.

protected override IQueryElement VisitInListPredicate(SqlPredicate.InList predicate)

Parameters

predicate SqlPredicate.InList

Returns

IQueryElement

Remarks

ClickHouse converts the list to the operand's type and refuses a lossy conversion with Type mismatch in IN or VALUES section, where a comparison would promote both sides.

WrapColumnExpression(ISqlExpression)

protected override ISqlExpression WrapColumnExpression(ISqlExpression expr)

Parameters

expr ISqlExpression

Returns

ISqlExpression