Enum Class Dialect
- All Implemented Interfaces:
Serializable,Comparable<Dialect>,Constable
- Identifier quoting —
quote(String)wraps a column or alias in the dialect's quote characters ("x"for PostgreSQL,`x`for MySQL). The generic dialect leaves a plain identifier bare, relying on the database folding unquoted identifiers case-insensitively, and delimits any other name with standard SQL's"x", since no backend readsorder-linesbare.table(String)does the same for each part of a possibly schema-qualified table name. - Row limiting —
limit(long, long)renders theLIMIT/OFFSETclause. - String literals —
stringLiteral(String)writes a value as literal text. Doubling the quote is standard SQL and not sufficient: MySQL reads a backslash as an escape, so the two backends need different rules for the same value.
The GENERIC dialect emits the exact SQL the planner produced before
dialects existed for every name that is a plain identifier (unquoted identifiers,
LIMIT n [OFFSET m]), so it is a safe default for H2, PostgreSQL and MySQL.
The POSTGRES, MYSQL, DUCKDB, SQLITE and
SQLSERVER dialects add identifier quoting, which is correct when the relix
schema's column names match the database's stored case (always true for
introspected conn.table references).
Every per-backend question here is an exhaustive switch (this), so a
constant added to this enum does not compile until it has answered each one. That is
the point rather than a style: an answer inherited by majority is indistinguishable
from an answer nobody considered, and the two differ exactly when the new backend is
the odd one out — which is the case a default is least able to get right. The two
methods that do not switch are the two whose answer is not the backend's to give:
quote(java.lang.String) reads the quoting each constant declares, and boolAnd(java.lang.String)
renders SQL-92 that no backend lacks.
Every constant but SQLSERVER and DB2 renders
LIMIT n [OFFSET m]; Db2 renders the standard's FETCH FIRST, which it
accepts with or without an ordering. SQL Server's
row-limiting is OFFSET … FETCH, which needs more than an arm of limit(long, long):
that form is legal only after an ORDER BY, so the pushdown planner also
declines a limit fold over an unordered sub-tree — limitNeedsOrderBy().
-
Nested Class Summary
Nested classes/interfaces inherited from class java.lang.Enum
Enum.EnumDesc<E extends Enum<E>> -
Enum Constant Summary
Enum ConstantsEnum ConstantDescriptionIBM Db2 for Linux, UNIX and Windows, 11.5 and later.DuckDB: double-quoted identifiers, and SQL deliberately shaped like PostgreSQL's —NULLS LAST,date_trunc,LATERAL, window functions.Unquoted identifiers andLIMIT n [OFFSET m]— the default.MySQL / MariaDB: back-tick-quoted identifiers.PostgreSQL: double-quoted identifiers.SQLite: double-quoted identifiers and a binary default collation, so it compares and orders strings as the engine does.Microsoft SQL Server: bracket-quoted identifiers, and the first backend here whose differences are structural rather than spellings. -
Method Summary
Modifier and TypeMethodDescriptionReturns the SQL boolean-aggregate expression for universal quantification (∀) over the given rendered predicate, or empty when this dialect has no supported spelling and the operator must run in-engine instead.booleanLiteral(boolean value) Writes a boolean value as a SQL literal.booleanWhether this backend compares two strings the way the engine does — exactly, character for character.static booleancomparesStringsExactly(ConnectionDeclaration connection) Whetherconnection's backend compares strings as the engine does: its declaredcollationwhen it has one, else its dialect's default.dateLiteral(LocalDate value) Renders aDATEvalue as a SQL literal, or empty where this dialect has no literal that means what the engine means:GENERICa quoted ISO string (relies on implicit cast),POSTGRES/MYSQL/DUCKDBa typedDATE '…'literal.durationLiteral(Duration value) Renders aDURATIONvalue as a SQLINTERVALliteral, or empty where this dialect has no spelling for one.exactStringComparison(String expression) Wraps a rendered string expression so this backend compares it exactly, or returns it unchanged where the backend already does.exactStringOrder(String expression) Wraps a rendered string expression so this backend orders it by code point, or returns it unchanged where the backend already does.Renderssubject LIKE pattern(orNOT LIKE) so that it matches what the engine'sLIKEmatches, or empty where this dialect cannot say it.limit(long count, long offset) Renders the row-limit clause:LIMIT count, plusOFFSET offsetwhenoffsetis positive.booleanWhether this dialect's row-limiting clause is legal only after anORDER BY, so that a limit over an unordered sub-tree must not fold.nearestRowJoin(String left, String columns, String source, String orderBy, String alias, boolean inner) Renders theFROMclause of an AS-OF join folded as a correlated nearest-row lookup — for each left row, the one right row a sub-select filters by the match condition and orders by the match column — or empty where this dialect has no such construct (supportsLateralAsOf()).static Dialectof(ConnectionDeclaration connection) Resolves the dialect for a connection: its declareddialect, if any, otherwise inferred from the JDBC URL, otherwiseGENERIC.orderByTerms(String expr, boolean descending) Renders oneORDER BYkey so the database places NULLs where the engine does — last, in both directions.booleanWhether this backend puts two strings in the order the engine puts them — by code point, which is the order a binary collation gives and the order UTF-8 bytes already sort in.static booleanordersStringsExactly(ConnectionDeclaration connection) Whetherconnection's backend orders strings as the engine does: its declaredcollationwhen it has one, else its dialect's default.The statement that pins a session's time zone to UTC, or empty for a backend this has not been confirmed against.Names this dialect to a function library, so a function can supply its own spelling here without the planner knowing anything about it.Quotes an identifier for this dialect, doubling any embedded quote character.stringLiteral(String value) Writesvalueas a SQL string literal for this dialect.booleanReturns whether this dialect's target database reliably supports aLATERALderived table in a join — the construct used to push an AS-OF join down as a correlated "nearest row" lookup (LEFT JOIN LATERAL (SELECT … ORDER BY … LIMIT 1) ON TRUE).booleanReturns whether this dialect's target database reliably supports SQL window functions (OVER (PARTITION BY … ORDER BY … ROWS …)).Writes a table name for this dialect, quoting each dot-separated part withquote(java.lang.String):public.ordersbecomes"public"."orders"on PostgreSQL.timeLiteral(LocalTime value) Renders aTIMEvalue as a SQL literal, or empty where this dialect declines it (seedateLiteral(java.time.LocalDate)).timestampLiteral(Instant value) Renders aTIMESTAMP(an absolute instant) as a SQL literal.static DialectReturns the enum constant of this class with the specified name.static Dialect[]values()Returns an array containing the constants of this enum class, in the order they are declared.windowOverClause(List<String> partitionCols, List<String> orderByExprs, WindowFrame frame) Renders theOVER (…)clause for a SQL window function, given already- rendered partition and sort expressions.
-
Enum Constant Details
-
GENERIC
Unquoted identifiers andLIMIT n [OFFSET m]— the default. A name that is not a plain identifier is double-quoted, as standard SQL delimits one. -
POSTGRES
PostgreSQL: double-quoted identifiers. -
MYSQL
MySQL / MariaDB: back-tick-quoted identifiers. -
DUCKDB
DuckDB: double-quoted identifiers, and SQL deliberately shaped like PostgreSQL's —NULLS LAST,date_trunc,LATERAL, window functions. Where it differs from PostgreSQL it differs in the engine's favour: its default collation is binary, so it compares and orders strings exactly as the engine does. -
SQLITE
SQLite: double-quoted identifiers and a binary default collation, so it compares and orders strings as the engine does. What sets it apart is what it lacks — date and time types,EXTRACT,LATERAL, a case-sensitiveLIKE— and each of those is declined or respelled here rather than inherited fromGENERIC, which is what a SQLite connection resolved to before it had a constant of its own. -
SQLSERVER
Microsoft SQL Server: bracket-quoted identifiers, and the first backend here whose differences are structural rather than spellings. It has noLIMIT— itsOFFSET … FETCHis legal only after anORDER BY, so a limit folds only over a sort (limitNeedsOrderBy()) — noNULLS LAST, no boolean values, noLATERAL(itsAPPLYis the same thing spelled otherwise), no session time zone, and a case-insensitive default collation. -
DB2
IBM Db2 for Linux, UNIX and Windows, 11.5 and later. Unlike every other named dialect it leaves a plain identifier bare: Db2 folds an unquoted name to upper case, so a table created asordersis stored asORDERSand the quoted"orders"would name a different, absent one. A name that is not a plain identifier is double-quoted exactly as written, as the generic dialect does.A Db2 database created in UTF-8, the default, compares strings with the
IDENTITYcollation — by their bytes, which is code-point order — so it answers both string questions as the engine does. A database created with a locale collation does not, and says so withcollation: database.
-
-
Method Details
-
values
Returns an array containing the constants of this enum class, in the order they are declared.- Returns:
- an array containing the constants of this enum class, in the order they are declared
-
valueOf
Returns the enum constant of this class with the specified name. The string must match exactly an identifier used to declare an enum constant in this class. (Extraneous whitespace characters are not permitted.)- Parameters:
name- the name of the enum constant to be returned.- Returns:
- the enum constant with the specified name
- Throws:
IllegalArgumentException- if this enum class has no constant with the specified nameNullPointerException- if the argument is null
-
quote
Quotes an identifier for this dialect, doubling any embedded quote character. The generic dialect returns a plain identifier ([A-Za-z_][A-Za-z0-9_]*) unchanged and double-quotes anything else.Generic leaves a plain identifier bare because it does not know how the backend folds case, and a bare name lets the backend decide. A name like
order-linesis different: no backend reads it bare, and a table or column with that name can only have been created delimited, which also preserved its case, so the delimited form written exactly as named is the one that finds it.One of the two methods here that does not switch on the dialect, and it needs no arm for the same reason a switch would give one: the quoting is given by constructor arguments, so a new constant states its own answer where it is declared. Doubling the closing character is the rule every quoting backend follows, bracket-quoting included.
- Parameters:
identifier- the column or alias name; must not be null- Returns:
- the identifier as this dialect writes it
-
table
Writes a table name for this dialect, quoting each dot-separated part withquote(java.lang.String):public.ordersbecomes"public"."orders"on PostgreSQL. A dot therefore always separates a schema from a table.A
FROMclause, pushed or not, writes its table through this, so that the engine's own scan of a table and a query pushed to the same table name the same thing.- Parameters:
table- the table name as declared, possibly schema-qualified; must not be null- Returns:
- the table name as this dialect writes it
-
stringLiteral
Writesvalueas a SQL string literal for this dialect.Doubling the embedded quote is standard SQL and is the whole of the rule for PostgreSQL and the generic dialect, both of which read a backslash as the character it is (
standard_conforming_strings).MySQL and MariaDB also read a backslash as an escape, unless the session sets
NO_BACKSLASH_ESCAPES, which is not the default. Doubling only the quote therefore lets a value end its own literal:x\followed by' OR 1=1 --renders as'x\'' OR 1=1 -- ', where the value's backslash escapes the quote that was doubled to contain the apostrophe, the literal closes at the second quote instead of the first, and everything after it is parsed as SQL. Escaping the backslash too is what closes that.The two rules are not interchangeable in either direction: doubling a backslash for PostgreSQL would put a second one into the value, and not doubling it for MySQL is the hole above. That is what makes this a per-backend question rather than one answer with an exception, and why it is an exhaustive
switchhere rather than a private helper in the renderer — where it was, and where a new backend could inherit an answer nobody had considered for it.This is the escaping rule, not a substitute for one. A literal is inlined into the statement text rather than bound as a parameter, so the rule has to be right for the backend the text is going to.
- Parameters:
value- the string the literal denotes; must not be null- Returns:
- the literal, quotes included; never null
-
limit
Renders the row-limit clause:LIMIT count, plusOFFSET offsetwhenoffsetis positive.Every constant renders that form, which H2, PostgreSQL and MySQL all accept. It is written as a switch rather than as one return because the clause is not universal — see the class documentation on
OFFSET … FETCH— and a constant that cannot spellLIMITshould not be able to inherit it.SQL Server has no
LIMIT: it rendersOFFSET m ROWS FETCH NEXT n ROWS ONLY, which it accepts only after anORDER BY— seelimitNeedsOrderBy(), which is what stops the planner asking for one without.- Parameters:
count- the maximum number of rowsoffset- the number of leading rows to skip (0 = none)- Returns:
- the SQL clause, without a leading space
-
limitNeedsOrderBy
public boolean limitNeedsOrderBy()Whether this dialect's row-limiting clause is legal only after anORDER BY, so that a limit over an unordered sub-tree must not fold.OFFSET … FETCHis the standard's row limiting and requires an ordering on SQL Server, as it does on Oracle. TheORDER BY (SELECT NULL)idiom would make it legal, and would also make it the one fold here whose rows are chosen by the database's plan rather than by anything the query said — so the limit runs in the engine instead, over the rows the rest of the statement returns.- Returns:
truewhen a limit folds only onto a statement that has anORDER BY
-
orderByTerms
Renders oneORDER BYkey so the database places NULLs where the engine does — last, in both directions.This is not a preference either: an in-engine
τand a pushed one are the same operator, so a query that sorts differently depending on whether the planner folded it is wrong however the rows come out. SQL's own default is not one answer to copy — H2 and MySQL put NULLs first onASC, Postgres puts them last — so the placement is always stated explicitly rather than inherited.Postgres gets the standard
NULLS LAST. Everything else gets the portable form, an extra leading key on the NULL-ness itself:falsesorts beforetrue, so ascending that expression puts the present values first whichever way the real key runs. MySQL has noNULLS LASTsyntax at all, and GENERIC is an unidentified backend that may be MySQL-shaped, so neither can be given it. SQLite has hadNULLS LASTonly since 3.30, and the driver — which is the database, for an embedded backend — is the user's to choose, so it gets the portable form too.SQL Server has neither
NULLS LASTnor a boolean value to sort on, so the portable form is itself a syntax error there. Its leading key is the NULL test spelled as a number:CASE WHEN e IS NULL THEN 1 ELSE 0 END.- Parameters:
expr- the rendered key expression (already quoted)descending- whether the key itself sorts descending- Returns:
- the
ORDER BYterms for this key, in order; never empty
-
like
public Optional<String> like(String subject, String pattern, Optional<String> literal, boolean negated) Renderssubject LIKE pattern(orNOT LIKE) so that it matches what the engine'sLIKEmatches, or empty where this dialect cannot say it.The engine's
LIKEis exact:%and_are its only wildcards, and every other character — case included — matches only itself. That is standard SQL, and most backends need nothing more than the subject rendered in comparison position, which the caller has already done.SQLITE'sLIKEis case-insensitive for ASCII letters, and no collation changes that:'a' LIKE 'A'is true there whatever the column says. ItsGLOBis the case-sensitive matcher, with*and?for wildcards, so a literal pattern is translated into one — its own*,?and[bracketed so that they match themselves. A pattern that is not a literal cannot be translated here and declines.SQLSERVER'sLIKEhonours the collation, which the caller has already made exact, but also reads[as opening a character class, so a literal pattern has each one written[[]and a computed one declines.- Parameters:
subject- the rendered expression being matched; must not be nullpattern- the rendered pattern, used where the dialect readsLIKEas the engine does; must not be nullliteral- the pattern's text when it is a string literal, else emptynegated- whether this isNOT LIKE- Returns:
- the predicate, parenthesised; or empty when this dialect declines it
-
booleanLiteral
Writes a boolean value as a SQL literal.TRUEandFALSEeverywhere but SQL Server, which has no boolean type — itsBITholds1and0, andTRUEis read as a column name.- Parameters:
value- the value- Returns:
- the literal
-
boolAnd
Returns the SQL boolean-aggregate expression for universal quantification (∀) over the given rendered predicate, or empty when this dialect has no supported spelling and the operator must run in-engine instead.The returned expression is placed in a
HAVINGclause immediately afterGROUP BY. Every dialect renders the same strict form,COUNT(*) = COUNT(CASE WHEN P THEN 1 END): a row whose predicate is UNKNOWN disqualifies its group.That is not a dialect quirk but the reading ∀ has everywhere else — it is a conjunction across the group's rows, and
TRUE ∧ UNKNOWNis UNKNOWN, which keeps nothing. Postgres used to renderbool_and(P)here, which is lenient: like every aggregate it skips NULL inputs, so a group whose only interesting row is UNKNOWN comes back TRUE and is kept — the same query answering differently on Postgres than in-engine or on any other backend. The shorter spelling is not worth a third answer to the same question.No dialect declines, and this is therefore the one answering method here that does not switch on the dialect: the form is the standard's rather than the backend's, so there is nothing for a new constant to decide. The
Optionalreturn survives because declining stays expressible if a backend ever needs to.That the strict form is the one every backend gets is also what makes it available everywhere:
COUNT,CASEand a comparison inHAVINGare SQL-92, so there is no dialect this has to be withheld from. MySQL was withheld from it while the Postgres arm still saidbool_and— an aggregate MySQL genuinely lacks — and stayed withheld after the arm changed, because no test rendered a MySQL∀and no MySQL ran one.- Parameters:
predicateSql- the predicate rendered as a SQL expression; must not be null- Returns:
- the aggregate expression for
HAVING; never empty
-
dateLiteral
Renders aDATEvalue as a SQL literal, or empty where this dialect has no literal that means what the engine means:GENERICa quoted ISO string (relies on implicit cast),POSTGRES/MYSQL/DUCKDBa typedDATE '…'literal.SQLITEdeclines all four temporal literals. SQLite has no date or time types: a "date" column holds TEXT, a REAL or an INTEGER by the application's convention, and a comparison against it is a comparison of text or of numbers. A quoted ISO string agrees with the engine only while every stored value is written in exactly that form, which nothing the planner can reach reports — so a temporal predicate runs in the engine over the values the connector read, where it means what it says whatever the convention.- Parameters:
value- the date; must not be null- Returns:
- the literal, or empty when this dialect declines it
-
timeLiteral
Renders aTIMEvalue as a SQL literal, or empty where this dialect declines it (seedateLiteral(java.time.LocalDate)).- Parameters:
value- the time; must not be null- Returns:
- the literal, or empty when this dialect declines it
-
timestampLiteral
Renders aTIMESTAMP(an absolute instant) as a SQL literal. A relixTIMESTAMPis UTC:GENERICemits the UTC wall-clock as a quoted string (matching how a zone-less SQLTIMESTAMPcolumn is read);POSTGRESemits an offset-awareTIMESTAMP WITH TIME ZONE '…Z';MYSQLemits aTIMESTAMP '…'of the UTC wall-clock.DUCKDBtakes PostgreSQL's form. Against a zone-lessTIMESTAMPcolumn DuckDB converts the literal to the session's wall clock, which relix pins to UTC (pinSessionToUtcSql()), so it compares the instant the engine reads the column as; against aTIMESTAMPTZcolumn it compares instants directly.SQLITEdeclines (seedateLiteral(java.time.LocalDate)).SQLSERVERgets aDATETIME2of the UTC wall clock: aDATETIME2column holds the wall clock the engine reads as UTC, and against aDATETIMEOFFSETcolumn the literal is converted at offset+00:00, so it names the same instant either way — and the column, not the literal, keeps its type, so an index on it still serves the comparison.- Parameters:
value- the instant; must not be null- Returns:
- the literal, or empty when this dialect declines it
-
durationLiteral
Renders aDURATIONvalue as a SQLINTERVALliteral, or empty where this dialect has no spelling for one.PostgreSQL accepts an ISO-8601 interval string directly, so a duration renders as it prints:
INTERVAL 'PT30M'.MYSQLandGENERICdecline, because theirINTERVALsyntax names a unit (INTERVAL 30 MINUTE) and a duration does not carry one — ninety minutes is as truly90 MINUTEas1.5 HOUR, and picking for the user is how a literal starts meaning something the engine did not say.DuckDB rejects the ISO-8601 form (
INTERVAL 'PT30M'is a conversion error there) but builds an interval from an exact count of microseconds, which names no calendar unit and so picks none for the user:to_microseconds(n). A duration finer than a microsecond has no such count and declines.Alone among the four temporal literals this returns an
Optional, because declining is a real answer for it: no fold is a slower plan, where a wrong literal is a wrong result.- Parameters:
value- the duration; must not be null- Returns:
- the
INTERVALliteral, or empty when this dialect has no spelling
-
comparesStringsExactly
public boolean comparesStringsExactly()Whether this backend compares two strings the way the engine does — exactly, character for character.It is not a question about SQL but about the column's collation, and a collation may say that two values differing in case or in accent are the same value. MySQL's default is
utf8mb4_0900_ai_ci, which says exactly that, so a folded=,IN,LIKE,GROUP BY,DISTINCT,ORDER BYorMIN/MAXover a string answers a different question there than the same operator answers in the engine — the query returning different rows depending on where the planner put the work.POSTGRESandGENERICanswertrue, and for Postgres that answer was checked against a server rather than reasoned about: a deterministic collation falls back to comparing bytes when it is asked whether two strings are equal, and Postgres's defaults are deterministic, so'Gold' = 'gold'is false there as it is here.DUCKDB's default collation is binary, so it answerstruefor the plainer reason.SQLSERVER's default,SQL_Latin1_General_CP1_CI_AS, is case-insensitive, so it answersfalseas MySQL does.DB2answerstrue— a UTF-8 database's defaultIDENTITYcollation compares bytes — with SQL Server's one caveat: it compares padded, so'a' = 'a 'holds there too, and no collation of its own changes that.This is a claim about equality alone, and the separation is the whole reason there are two methods: the same Postgres that compares equal strings exactly does not order them as the engine does, because ordering is what a locale collation is for. See
ordersStringsExactly(), which is the question<,ORDER BYandMIN/MAXask.The answer is a default, not a fact, because the truth is per column and the planner has no channel to a column's collation. A connection whose string columns really are binary says so with
collation: exact, and a Postgres deployment that wants the conservative reading sayscollation: database— seecomparesStringsExactly(ConnectionDeclaration).Of every question on this enum this is the one that must never be inherited. A wrong
truedoes not cost a fold, it folds a comparison the backend answers differently — so the query returns different rows depending on where the planner put the work, which is a wrong result rather than a slow one.- Returns:
truewhen a string equality test may be handed to this backend
-
ordersStringsExactly
public boolean ordersStringsExactly()Whether this backend puts two strings in the order the engine puts them — by code point, which is the order a binary collation gives and the order UTF-8 bytes already sort in.It is a different question from
comparesStringsExactly(), and Postgres is the backend that separates them. A deterministic collation decides equality by bytes, so=is exact there; it decides order by the locale, and a locale sorts the way a dictionary does — case-insensitively at the primary level, punctuation weighed differently,'Straße'filed beside'Strasse'. Underen_US.UTF-8, which is what a default install initialises with,'Alan' < 'a😀b' < 'grace'; by code point the emoji sorts after both. So aτon a string key, a<between two strings and aMINover one all answer differently there than here.MySQL answers
falsefor the reason it answersfalseto the equality question — its default collation is neither exact nor code-point ordered — so on that backend the two questions have never disagreed, which is why one boolean was enough until a Postgres was run.DuckDB answers
trueto both questions: its default collation compares UTF-8 bytes, which is code-point order. That was run rather than read — over the agreement fixture'sU+FF5E/emoji pair, the one that tells code-point order from UTF-16 order. SQLite'sBINARYcollation ismemcmpover UTF-8, and answers the same, measured over the same pair; so does a UTF-8 Db2 database under its defaultIDENTITYcollation.A
falseanswer does not decline the fold: it renders the ordering under an explicit exact collation,exactStringOrder(java.lang.String).- Returns:
truewhen a string ordering may be handed to this backend as written
-
exactStringComparison
Wraps a rendered string expression so this backend compares it exactly, or returns it unchanged where the backend already does.MySQL's answer is
CONVERT(e USING utf8mb4) COLLATE utf8mb4_0900_bin, and both halves are load-bearing. TheCOLLATEis what replaces the column's own case- and accent-insensitive collation with an exact one. TheCONVERTis what makes that legal for any column: a collation belongs to a character set, soCOLLATE utf8mb4_0900_binapplied to alatin1column is not a wrong answer but a rejected query, and transcoding first removes the question. Both were checked against a server rather than reasoned about, accented values included.It costs the column's index for this comparison, and that is the right trade by a wide margin: the alternative is not an indexed lookup but no fold at all, so the comparison is between a scan inside the database and every row of the table crossing the wire to be scanned here.
This is the wrapping for equality —
=,IN,LIKE,GROUP BY,DISTINCT, a join condition. Ordering is wrapped separately byexactStringOrder(java.lang.String), and separately because the two are not the same set of backends: Postgres needs the second and not the first.SQL Server's is
(e) COLLATE Latin1_General_100_BIN2. A binary collation makes'Gold' = 'gold'false, and it is still not all the way to exact: SQL Server compares strings padded, as the standard'sPAD SPACEsays, so'a' = 'a 'is true there under every collation it has. relix compares them as different values. That difference is recorded rather than worked round — no collation removes it, and declining every string comparison to avoid values that differ only in trailing spaces would cost every such query a full read.- Parameters:
expression- an already-rendered expression of string type; must not be null- Returns:
- the expression, wrapped if this dialect needs it to compare exactly
-
exactStringOrder
Wraps a rendered string expression so this backend orders it by code point, or returns it unchanged where the backend already does.Postgres spells that
(e) COLLATE "C"— the C collation compares the UTF-8 bytes, which for UTF-8 is code-point order — and it is applied in ordering position only: a<, anORDER BYkey, aMINorMAX. Its equality is already exact, so wrapping an=there would cost the column's index and buy nothing.MySQL reuses its comparison wrapping, which is already binary and therefore already code-point ordered. That the two coincide on one backend and not on the other is exactly why they are two methods.
SQL Server reuses its comparison wrapping too, and it orders by UTF-16 code unit rather than by code point — the same difference H2 has as the generic dialect, confined to a character outside the basic multilingual plane compared with one in
U+E000..U+FFFF. A code-point order exists there only through aVARCHARunder a_UTF8collation, which would change what aMINreturns and what a<against a literal compares.Like the comparison wrapping, this costs the column's index for the term it wraps, and the alternative is not an indexed sort but no fold at all — every row crossing the wire to be sorted here.
- Parameters:
expression- an already-rendered expression of string type; must not be null- Returns:
- the expression, wrapped if this dialect needs it to order by code point
-
comparesStringsExactly
Whetherconnection's backend compares strings as the engine does: its declaredcollationwhen it has one, else its dialect's default.collation: exactis how a MySQL whose string columns are declared with a binary collation gets its string predicates folded again. It is the user's claim rather than the engine's, because a collation is a property of each column and nothing the planner can reach reports it — which is also why the safe answer is the default and the fast one is opted into.- Parameters:
connection- the connection declaration; must not be null- Returns:
- whether a string comparison may be folded into this connection's SQL
-
ordersStringsExactly
Whetherconnection's backend orders strings as the engine does: its declaredcollationwhen it has one, else its dialect's default.The declaration answers both questions at once, and that is right rather than a shortcut:
collation: exactis the claim that these columns are declared with a binary collation, and a binary collation is both exact and code-point ordered.- Parameters:
connection- the connection declaration; must not be null- Returns:
- whether a string ordering may be folded into this connection's SQL as written
-
pinSessionToUtcSql
The statement that pins a session's time zone to UTC, or empty for a backend this has not been confirmed against.Why a session needs pinning at all. A relix
TIMESTAMPis an instant, and a zone-less SQLTIMESTAMPis read as UTC. A pusheddate_truncorEXTRACTruns on the server, over whatever wall clock the session presents — so unless that clock is UTC, the folded query and the in-engine one truncate different numbers and the same query answers differently depending on where the planner put the work.Why it is not left to the connection string. On PostgreSQL it cannot be: the driver sends the session's
TimeZonein its startup packet, taken from the client JVM's default zone, and aTimeZonenamed in the URL is silently ignored. So a Postgres deployment's answers moved with the time zone of the machine the engine happened to run on, and there was no property a user could set to stop it. That is the shape of bug this exists to remove.GENERICanswers empty, and that is deliberate rather than an omission: an unidentified backend is one whose spelling of this is unknown, and a rejected statement would break the connection outright — a much worse failure than the wall clock it would have corrected.- Returns:
- the SQL to run once on a new connection, or empty to leave the session alone
-
pushdownTarget
Names this dialect to a function library, so a function can supply its own spelling here without the planner knowing anything about it.A
PushdownTargetis deliberately weaker than aDialect: it is a family and a variant string, and it carries no quoting, no limit syntax and no knowledge of query planning. That is what lets a spelling live in a library that has never heard of this module — the engine keeps every structural decision and hands over arguments already rendered.- Returns:
- the target a function's spelling is asked for
-
supportsWindowFunctions
public boolean supportsWindowFunctions()Returns whether this dialect's target database reliably supports SQL window functions (OVER (PARTITION BY … ORDER BY … ROWS …)).GENERIC(H2 2.x),POSTGRESandDUCKDB:true.SQLITE:true— window functions arrived in SQLite 3.25 (2018), and the corpus's window cases run against it in the gate.SQLSERVER:true—ROWS BETWEENframes arrived in SQL Server 2012, which predates every release still in support.MYSQL:false— MySQL 5.x predates window-function support; since the declared dialect cannot confirm the server version, window operators fall back to in-engine execution for safety.
- Returns:
truewhen the dialect can execute a pushedOVERclause
-
supportsLateralAsOf
public boolean supportsLateralAsOf()Returns whether this dialect's target database reliably supports aLATERALderived table in a join — the construct used to push an AS-OF join down as a correlated "nearest row" lookup (LEFT JOIN LATERAL (SELECT … ORDER BY … LIMIT 1) ON TRUE).POSTGRES:true—LATERALhas been supported since PostgreSQL 9.3 and is the canonical AS-OF spelling.DUCKDB:true— it spellsLATERALas PostgreSQL does, and the AS-OF corpus runs against it in the gate.GENERIC:false— the generic target is H2, whose 2.x releases have noLATERALsupport, so AS-OF runs in-engine.SQLITE:false— SQLite has noLATERAL.SQLSERVER:true— it has noLATERALeither, butOUTER APPLY/CROSS APPLYis the same correlated join, andnearestRowJoin(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean)spells it.DB2:true— it takes PostgreSQL's spelling unchanged.MYSQL:false—LATERALarrived only in MySQL 8.0.14, and the declared dialect cannot confirm the server version, so AS-OF falls back to in-engine execution for safety (as window functions do).
Answered by whether
nearestRowJoin(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean)has a spelling, which is the exhaustive switch: there is one list of the backends that fold an AS-OF, not two.- Returns:
truewhen the dialect can execute a pushed nearest-row AS-OF
-
nearestRowJoin
public Optional<String> nearestRowJoin(String left, String columns, String source, String orderBy, String alias, boolean inner) Renders theFROMclause of an AS-OF join folded as a correlated nearest-row lookup — for each left row, the one right row a sub-select filters by the match condition and orders by the match column — or empty where this dialect has no such construct (supportsLateralAsOf()).PostgreSQL and DuckDB spell it
LEFT JOIN LATERAL (… LIMIT 1) r ON TRUE, orJOIN LATERALto drop an unmatched left row. SQL Server has the same construct under another name —OUTER APPLY (SELECT TOP 1 …) r, orCROSS APPLY— with noONand noLIMIT; both of its halves were checked against a running server.- Parameters:
left- the left table and its alias, as they appear inFROMcolumns- the sub-select's select listsource- the sub-select'sFROM … WHERE …orderBy- the sub-select's ordering, nearest row firstalias- the quoted alias the sub-select's columns are read throughinner- whether a left row with no match is dropped rather than NULL-padded- Returns:
- the
FROMclause, or empty when this dialect cannot fold the join
-
windowOverClause
public String windowOverClause(List<String> partitionCols, List<String> orderByExprs, WindowFrame frame) Renders theOVER (…)clause for a SQL window function, given already- rendered partition and sort expressions.The ANSI SQL:2003 syntax produced here is identical across
GENERIC,POSTGRES, andMYSQL(8.0+):OVER (PARTITION BY k1, k2 ORDER BY col ASC ROWS BETWEEN n PRECEDING AND CURRENT ROW). Only call this method after verifyingsupportsWindowFunctions().- Parameters:
partitionCols- already-quoted partition-key column expressions; may be emptyorderByExprs- already-renderedcol [ASC|DESC]expressions; must not be emptyframe- the row scope (WindowFrame.BoundedFrame,WindowFrame.CumulativeFrame, orWindowFrame.PartitionFrame); never null- Returns:
- the complete
OVER (…)string, starting with a space
-
of
Resolves the dialect for a connection: its declareddialect, if any, otherwise inferred from the JDBC URL, otherwiseGENERIC.Reads the two properties directly rather than through
ConnectionDeclaration.config(), which requires aurland raises when there is none. A connection may legitimately have no URL — one whose coordinates are a live handle supplied by an embedder, or one that is not JDBC at all — and the right answer for it isGENERIC, not an exception. Getting a dialect wrong costs pushdown; raising here would cost the query.- Parameters:
connection- the connection declaration; must not be null- Returns:
- the resolved dialect; never null
-