Table of Contents

Class PostgreSQLSqlExpressionConvertVisitor

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

Constructors

PostgreSQLSqlExpressionConvertVisitor(bool)

public PostgreSQLSqlExpressionConvertVisitor(bool allowModify)

Parameters

allowModify bool

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

SupportsNullInColumn

protected override bool SupportsNullInColumn { get; }

Property Value

bool

Methods

ConvertConversion(SqlCastExpression)

Implements SqlCastExpression conversion.

protected override ISqlExpression ConvertConversion(SqlCastExpression cast)

Parameters

cast SqlCastExpression

Returns

ISqlExpression

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

ConvertSqlFunction(SqlFunction)

public override ISqlExpression ConvertSqlFunction(SqlFunction func)

Parameters

func SqlFunction

Returns

ISqlExpression

ElapsedTicks(SqlIntervalDifferenceExpression)

Ticks summed field by field out of the interval, rather than from its epoch.

protected override ISqlExpression? ElapsedTicks(SqlIntervalDifferenceExpression element)

Parameters

element SqlIntervalDifferenceExpression

Returns

ISqlExpression

Remarks

EXTRACT(EPOCH ...) is the obvious form and the wrong one: it returns a double before PostgreSQL 14, whose spacing grows past a tick over a range of decades. Every other field is bounded - seconds stay below sixty - so a double carries them exactly however far apart the two timestamps are, and the day count is whole. A timestamp difference never carries months, so days are the coarsest field there is.

Fields of a negative interval are all negative, so the sum needs no sign handling.

LowerIntervalDifference(SqlIntervalDifferenceExpression)

The elapsed time itself, as a native interval: PostgreSQL subtracts two date/time values into one directly, and TimeSpan is already mapped to it, so the value needs no decomposition on either side.

protected override ISqlExpression? LowerIntervalDifference(SqlIntervalDifferenceExpression element)

Parameters

element SqlIntervalDifferenceExpression

Returns

ISqlExpression

LowerIntervalPart(SqlIntervalPartExpression)

Subtracting two timestamps in PostgreSQL yields a real interval, already split into days and a time of day, so the components can be read straight out of it - no boundary counting, no anchoring.

protected override ISqlExpression? LowerIntervalPart(SqlIntervalPartExpression element)

Parameters

element SqlIntervalPartExpression

Returns

ISqlExpression

Remarks

The split is what makes this match the CLR: '2026-01-03 13:30' - '2026-01-01 10:00' is 2 days 03:30:00, so EXTRACT(HOUR ...) is 3 - the hours within the day, exactly what TimeSpan.Hours means - and negatives come back as -2 days -03:30:00, giving -3 as the CLR does. A constructed interval is not normalised the same way, which is why this is applied only to a difference of two timestamps.

Totals go through EPOCH, which is the whole interval in seconds and needs no decomposition - PostgreSQL stores microseconds, so the double it returns carries the full stored precision. Ticks are the exception, and take the decomposition instead - see below.

LowerTemporalArithmetic(SqlTemporalArithmeticExpression)

A timestamp takes an interval directly, so the shift is the operator itself.

protected override ISqlExpression? LowerTemporalArithmetic(SqlTemporalArithmeticExpression element)

Parameters

element SqlTemporalArithmeticExpression

Returns

ISqlExpression

Remarks

A difference lowers to a native interval here, so it is added as it stands. Every other interval arrives as a tick count, which PostgreSQL reads as an ordinary number and refuses to add to a date - so it is turned into an interval first.

VisitExprExprPredicate(ExprExpr)

protected override IQueryElement VisitExprExprPredicate(SqlPredicate.ExprExpr predicate)

Parameters

predicate SqlPredicate.ExprExpr

Returns

IQueryElement

WrapColumnExpression(ISqlExpression)

protected override ISqlExpression WrapColumnExpression(ISqlExpression expr)

Parameters

expr ISqlExpression

Returns

ISqlExpression