public final class IdentifierCodec extends Object
This class is the ONE place that knows how each vendor delimits an identifier and how delimiter characters are escaped inside it. It converts between the two textual forms of an identifier:
"x", [x],
`x`, DAX 'x', Power Query
#"x", PostgreSQL U&"x");Role context matters. Every operation takes the
IdentifierProfile and the ESQLDataObjectType: quote
rules are not purely per-vendor. DAX quotes table names with
apostrophes while columns/measures use brackets; MySQL treats " as
an identifier delimiter only under ANSI_QUOTES
(IdentifierProfile.VendorFlags.mysqlAnsiQuotes); BigQuery backtick
identifiers use backslash escape sequences, not doubling.
Strictness. decodeLexical(gudusoft.gsqlparser.sqlenv.IdentifierProfile, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType, java.lang.String) is strict: input that starts
with a recognized open delimiter but is not a well-formed quoted identifier
(unterminated, unescaped inner delimiter, trailing garbage, invalid escape)
throws IdentifierCodec.MalformedIdentifierException. This is deliberate — the
legacy first/last-char strip (TBaseType.getTextWithoutQuoted)
silently accepted malformed spellings like [a.b[ and produced
corrupted stored names. An unrecognized escape sequence throws rather than
being passed through — silently decoding it as literal text would corrupt
the stored name — EXCEPT where the vendor itself defines unknown escapes
as literal: ClickHouse deliberately preserves an unknown escape verbatim
(backslash and character), and this codec follows that dialect behavior.
Transport totality. encodeStored(gudusoft.gsqlparser.sqlenv.IdentifierProfile, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType, java.lang.String) is total: it accepts
ANY string — empty, embedded delimiters, NUL, supplementary characters,
unpaired surrogates — and always emits a spelling (quoted, except for
vendors like SOQL with no quoted-identifier syntax, where the bare name is
canonical) that decodeLexical(gudusoft.gsqlparser.sqlenv.IdentifierProfile, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType, java.lang.String) maps back to the exact input. Whether that spelling
is also legal SQL for the vendor is a stricter, separately queryable
property: isVendorValidSpelling(gudusoft.gsqlparser.sqlenv.IdentifierProfile, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType, java.lang.String). (BigQuery forbids empty quoted
identifiers, MySQL forbids NUL — such spellings still round-trip through
the codec as transport encodings.)
Canonical encoding always quotes. encodeStored does not
try to decide whether the name could be spelled bare; emitting the quoted
form is always semantically safe, total, and matches the internal
producers this codec repairs (U2b), which always wrapped.
Recognition set provenance. Canonical delimiters follow the
legacy TSQLEnv.delimitedChar choice per vendor; additional accepted
delimiters come from vendor documentation. Deltas vs the legacy
TSQLEnv.isDelimitedIdentifier are deliberate corrections:
mismatched open/close pairs are fixed ([…]), and vendors
the legacy switch mishandled get their documented forms (Spark/Databricks
backticks, PostgreSQL U&"…", per-role DAX). SQL Server keeps
apostrophe RECOGNITION in the COLUMN role only: the U2a draft dropped
'x' as "not an identifier delimiter", but the string-literal
column-alias form names a real column (AS 'REGKEY' creates column
REGKEY) and the U5 swap proved lineage edges are lost when the
spelling does not decode — see RULES_MSSQL_COLUMN (recognition
only: excluded from isVendorValidSpelling(gudusoft.gsqlparser.sqlenv.IdentifierProfile, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType, java.lang.String), never emitted).
Contract for prefix forms. A PostgreSQL U&"…" lexical is
decoded with the DEFAULT escape character \; a UESCAPE
clause is a separate token that can never be part of a single identifier
token, so it is outside this codec's contract (callers pass one token).
Versioning. CODEC_REVISION names the revision of the
delimiter/escape table ("codec/1"). It participates in the persistent-key
policyId (U3): a change to decoding behavior changes key payloads,
so it must rotate keys.
| Modifier and Type | Class and Description |
|---|---|
static class |
IdentifierCodec.MalformedIdentifierException
Thrown by
decodeLexical(gudusoft.gsqlparser.sqlenv.IdentifierProfile, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType, java.lang.String) when the input starts with a
recognized open delimiter but is not a well-formed quoted identifier. |
| Modifier and Type | Field and Description |
|---|---|
static int |
CODEC_REVISION
Revision of the per-vendor delimiter/escape table — the "1" of
"codec/1".
|
| Modifier and Type | Method and Description |
|---|---|
static String |
decodeClickHouseStringLiteral(String rawLiteral)
ClickHouse STRING LITERAL decoder (single- or double-quoted), sharing
the escape semantics of
decodeClickHouse(java.lang.String, gudusoft.gsqlparser.sqlenv.IdentifierCodec.QuoteRule) (ReadHelpers.cpp
parseComplexEscapeSequence): doubled quotes, lowercase
\xHH only, \N decodes to nothing, the mapped set is
\a \b \e \f \n \r \t \v \0 \\ \' \" \` \/ \=,
and an unknown escape keeps both characters. |
static String |
decodeLexical(IdentifierProfile profile,
ESQLDataObjectType objectType,
String lexical)
Decode one lexical identifier to its stored text.
|
static String |
encodeStored(IdentifierProfile profile,
ESQLDataObjectType objectType,
String stored)
Encode stored text to a canonical lexical spelling.
|
static boolean |
hasQuotedForm(IdentifierProfile profile,
ESQLDataObjectType objectType)
Does this vendor/role have ANY quoted-identifier syntax?
false
only for vendors like SOQL where encodeStored(gudusoft.gsqlparser.sqlenv.IdentifierProfile, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType, java.lang.String) is the identity
— callers that need a delimiter for internal-key purposes (protecting
dots from qualified-name splitting) must supply their own fallback. |
static boolean |
isQuoted(IdentifierProfile profile,
ESQLDataObjectType objectType,
String s)
Does this lexical spelling denote a QUOTED identifier for the
vendor/role — i.e.
|
static boolean |
isVendorValidSpelling(IdentifierProfile profile,
ESQLDataObjectType objectType,
String lexical)
Is this lexical spelling valid SQL for the vendor — a stricter
property than being transport-decodable?
|
public static final int CODEC_REVISION
public static String decodeLexical(IdentifierProfile profile, ESQLDataObjectType objectType, String lexical)
If the input does not start with a delimiter this vendor/role
recognizes, it is an unquoted identifier and is returned unchanged
(case folding is NOT this codec's job — see
IdentifierRules.CaseFold). If it does start with a recognized
open delimiter, it must be a complete well-formed quoted identifier;
anything else throws.
profile - vendor identifier profile (non-null)objectType - syntactic role of the identifier (non-null; DAX
quote rules depend on it)lexical - the identifier spelling as written in SQL (non-null)IdentifierCodec.MalformedIdentifierException - if lexical starts with a
recognized open delimiter but is not well-formedpublic static String encodeStored(IdentifierProfile profile, ESQLDataObjectType objectType, String stored)
Total as a TRANSPORT encoding: any string — empty, delimiters, NUL,
supplementary, unpaired surrogates — round-trips exactly through
decodeLexical(gudusoft.gsqlparser.sqlenv.IdentifierProfile, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType, java.lang.String). The result is quoted with the vendor's
canonical delimiter for the role; for a vendor with no
quoted-identifier syntax at all (SOQL) the bare name is the canonical
spelling and encode is the identity. Vendor-valid-SQL is a stricter,
separately queryable property: isVendorValidSpelling(gudusoft.gsqlparser.sqlenv.IdentifierProfile, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType, java.lang.String).
profile - vendor identifier profile (non-null)objectType - syntactic role of the identifier (non-null)stored - the stored (undelimited) text (non-null)public static boolean isQuoted(IdentifierProfile profile, ESQLDataObjectType objectType, String s)
#" or
U&")?
Recognition only: a true result does NOT imply the spelling
is well-formed ([a.b[ is recognized as quoted, and
decodeLexical(gudusoft.gsqlparser.sqlenv.IdentifierProfile, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType, java.lang.String) then rejects it). null and the empty
string are not quoted.
profile - vendor identifier profile (non-null)objectType - syntactic role of the identifier (non-null; DAX
quote RECOGNITION is role-dependent — apostrophes
open quoted table names only)s - the spelling to test (may be null)s starts with a recognized open delimiterpublic static boolean hasQuotedForm(IdentifierProfile profile, ESQLDataObjectType objectType)
false
only for vendors like SOQL where encodeStored(gudusoft.gsqlparser.sqlenv.IdentifierProfile, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType, java.lang.String) is the identity
— callers that need a delimiter for internal-key purposes (protecting
dots from qualified-name splitting) must supply their own fallback.profile - vendor identifier profile (non-null)objectType - syntactic role of the identifier (non-null)public static boolean isVendorValidSpelling(IdentifierProfile profile, ESQLDataObjectType objectType, String lexical)
Scope: character- and delimiter-level validity only. The
check affirms spellings whose delimiter form and character content are
valid under the vendor's documented identifier grammar. Reserved-word
status, length limits, and semantic restrictions (e.g. a name being
taken) are explicitly OUT of scope — a true result does not
promise the bare word SELECT is usable unquoted. Within its
scope the check is conservative: it may return false for an
exotic-but-valid spelling, never true for a spelling whose
characters or delimiters are known-invalid. Enforced facts:
decodeLexical(gudusoft.gsqlparser.sqlenv.IdentifierProfile, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType, java.lang.String)
accepts it) and — except for SQLite and Power Query M, which
both document zero-length quoted identifiers as legal — its
payload must be non-empty (BigQuery documents the empty-backtick
ban explicitly);false
everywhere);_) plus only documented vendor extensions —
e.g. _ start for PostgreSQL/BigQuery/SQL Server but NOT
Oracle or Doris table names; $/# body characters
where documented; MySQL's extended U+0080..U+FFFF range;
non-ASCII letters only where unconditionally documented
(charset-dependent vendors like Oracle stay conservative).profile - vendor identifier profile (non-null)objectType - syntactic role of the identifier (non-null)lexical - the spelling to test (may be null; null/empty is invalid)public static String decodeClickHouseStringLiteral(String rawLiteral)
decodeClickHouse(java.lang.String, gudusoft.gsqlparser.sqlenv.IdentifierCodec.QuoteRule) (ReadHelpers.cpp
parseComplexEscapeSequence): doubled quotes, lowercase
\xHH only, \N decodes to nothing, the mapped set is
\a \b \e \f \n \r \t \v \0 \\ \' \" \` \/ \=,
and an unknown escape keeps both characters. \xHH sequences
are BYTES: the decoded stream is recombined as UTF-8, so
'\xC3\xA9' is é, not two mojibake characters.