Table of Contents

Class FirebirdSqlExpressionConvertVisitor

Namespace
LinqToDB.Internal.DataProvider.Firebird
Assembly
linq2db.dll
public class FirebirdSqlExpressionConvertVisitor : SqlExpressionConvertVisitor
Inheritance
object
FirebirdSqlExpressionConvertVisitor
Derived
Inherited Members
Extension Methods

Constructors

FirebirdSqlExpressionConvertVisitor(bool)

public FirebirdSqlExpressionConvertVisitor(bool allowModify)

Parameters

allowModify bool

Fields

LikeFirebirdEscapeSymbols

protected static string[] LikeFirebirdEscapeSymbols

Field Value

string[]

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

IntervalResolution

2.5 truncates the count to a whole millisecond, so a component asked for below one is identically zero rather than merely imprecise, and is declined here instead. Overridden back to Tick on Firebird3SqlExpressionConvertVisitor, which is what the provider builds from version 3 up.

public override SqlIntervalUnit IntervalResolution { get; }

Property Value

SqlIntervalUnit

Remarks

Measured rather than taken from the release notes: against engine 2.5.9 and 3.0.10, a gap of 0.4 ms answers 0 and 0.4 respectively, and 1.5 ms answers 1 and 1.5. The hundred microseconds a TIMESTAMP stores has been so since 2.5 - what arrived later is the fractional DATEDIFF, and it arrived in 3.

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[]

LikeValueParameterSupport

public override bool LikeValueParameterSupport { 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

ConvertSqlUnaryExpression(SqlUnaryExpression)

public override ISqlExpression ConvertSqlUnaryExpression(SqlUnaryExpression element)

Parameters

element SqlUnaryExpression

Returns

ISqlExpression

ElapsedTicks(SqlIntervalDifferenceExpression)

Elapsed ticks from DATEDIFF at millisecond resolution, which Firebird answers with one decimal place.

protected override ISqlExpression? ElapsedTicks(SqlIntervalDifferenceExpression element)

Parameters

element SqlIntervalDifferenceExpression

Returns

ISqlExpression

Remarks

That tenth of a millisecond is exactly what a Firebird timestamp stores - its resolution is a hundred microseconds - so scaling the count to ticks loses nothing, and the scaled value is always whole.

Every version comes through here, including 2.5 - this class is what FirebirdSqlOptimizer builds, and the later ones derive from it - so the count is taken at whatever fidelity the version offers rather than being asked for by version. From 3 on the answer carries that tenth and is exact against what the timestamp stores. On 2.5 it is truncated to a whole millisecond, which is what IntervalResolution below declares.

GetCaseSensitiveParameter(SearchString)

protected virtual bool? GetCaseSensitiveParameter(SqlPredicate.SearchString predicate)

Parameters

predicate SqlPredicate.SearchString

Returns

bool?

LowerTemporalArithmetic(SqlTemporalArithmeticExpression)

Shifts through DATEADD(day, days, date), then DATEADD(millisecond, rest, ...) by what is left of the last day.

protected override ISqlExpression? LowerTemporalArithmetic(SqlTemporalArithmeticExpression element)

Parameters

element SqlTemporalArithmeticExpression

Returns

ISqlExpression

Remarks

Split at the day because 2.5 takes a 32-bit amount, which a millisecond count passes after 25 days; a day count and the milliseconds of one day both fit it on every version.

From 3 on the remainder keeps a tenth of a millisecond, what a TIMESTAMP stores, as a decimal(18,1) - the type ElapsedTicks(SqlIntervalDifferenceExpression) counts in - since an integer division would drop the fraction. 2.5 measures whole milliseconds only (IntervalResolution), and its remainder is divided as an integer.

A DateTime is cast to TIMESTAMP first, whatever its mapping declares: a DATE has no time of day, and shifting it by milliseconds keeps none, while a DATE column mapped as a plain DateTime is not declared as one. The target is built from the CLR type, so a mapped DbType does not render the cast as the type it was meant to leave; over a TIMESTAMP the cast changes nothing.

VisitExprPredicate(Expr)

protected override IQueryElement VisitExprPredicate(SqlPredicate.Expr predicate)

Parameters

predicate SqlPredicate.Expr

Returns

IQueryElement

WrapColumnExpression(ISqlExpression)

protected override ISqlExpression WrapColumnExpression(ISqlExpression expr)

Parameters

expr ISqlExpression

Returns

ISqlExpression