public final class CatalogIdentifierPolicy extends Object
IdentifierService that keeps the catalog/** dependency
direction one-way: the package depends on IdentifierService and
IdentifierConfig but on no other sqlenv type.
Plan §9.2 / §9.5. Every case-folding, normalization, qualified-name comparison, and
map key inside catalog/** routes through this class (or directly through
IdentifierService); the forbidden-apis Maven plugin enforces this mechanically.
IdentifierConfig fields that map to IdentifierProfile.VendorFlags
(currently mysqlLowerCaseTableNames and mssqlCollation) are honored by
constructing a per-call IdentifierService when they differ from defaults.
Round 1 scope cuts (tracked for P1B Round 2):
foldUnquotedToUpper / foldUnquotedToLower on
IdentifierConfig are recorded but not yet projected onto
IdentifierProfile — the per-call profile is still constructed via
IdentifierProfile.forVendor(vendor, flags) which uses vendor-default fold
rules. Callers who declare a non-default fold get vendor-default behavior.tableCaseSensitive / columnCaseSensitive (BigQuery split policy)
are accepted but not honored: extending IdentifierProfile with per-object-
group sensitivity flags is part of Round 2.areEqual(gudusoft.gsqlparser.catalog.runtime.CatalogQualifiedName, gudusoft.gsqlparser.catalog.runtime.CatalogQualifiedName) Tier-2 fallback for COLLATION_BASED dialects routes through
IdentifierService.areEqualStatic(gudusoft.gsqlparser.EDbVendor, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType, java.lang.String, java.lang.String) (cached vendor defaults) and therefore
does not honor a custom mssqlCollation or MySQL
lower_case_table_names=2; Tier 1 (cfg-aware normalized comparison)
handles the lctn=1 / lctn=2 case-insensitive cases since their normalize folds
to a single canonical form.keyForMap(gudusoft.gsqlparser.catalog.runtime.CatalogQualifiedName) can produce different keys for two names that
areEqual(gudusoft.gsqlparser.catalog.runtime.CatalogQualifiedName, gudusoft.gsqlparser.catalog.runtime.CatalogQualifiedName) considers equal in COLLATION_BASED dialects — same contract as
IdentifierService.canUseCompositeKey. Snapshot lookups must fall back to
areEqual on a key miss.The defaults-driven path (the primary use of this class in P1B/P1C) is fully covered by tests and is the one used by every Phase 1 reader and provider.
| Modifier and Type | Method and Description |
|---|---|
static boolean |
areEqual(CatalogQualifiedName a,
CatalogQualifiedName b)
Vendor-aware equality test for two qualified names.
|
static ESQLDataObjectType |
comparisonTypeOfKind(CatalogObjectKind kind)
The comparison/quoting
ESQLDataObjectType for an object of this entity kind — the
SINGLE owner of this mapping (stage S0R of
docs/refactor/identifier_identity_display_separation_plan.md). |
static CatalogQualifiedName |
fromAlreadyNormalizedSegments(List<String> segments,
CatalogObjectKind kind,
EDbVendor vendor)
Build a
CatalogQualifiedName from segments that have already been normalized
by IdentifierService. |
static IdentifierService |
identifierServiceFor(IdentifierConfig cfg,
EDbVendor vendor)
Public façade over
serviceFor(IdentifierConfig, EDbVendor) for callers
outside this package (validator, readers, providers) that need an
IdentifierService matching a IdentifierConfig. |
static String |
keyForMap(CatalogQualifiedName name)
Map-friendly stable key for a
CatalogQualifiedName. |
static String |
normalize(String raw,
IdentifierConfig cfg)
Normalize a single identifier segment per
IdentifierConfig's vendor. |
static String |
normalizeSegment(String raw,
boolean wasQuoted,
IdentifierConfig cfg)
Normalize a segment with explicit quoted-ness (advisory).
|
static CatalogQualifiedName |
parse(String raw,
CatalogObjectKind kind,
IdentifierConfig cfg,
EDbVendor vendor)
Parse a possibly-quoted, possibly-multi-segment qualified name into a
CatalogQualifiedName with both raw and normalized segments tracked. |
public static CatalogQualifiedName parse(String raw, CatalogObjectKind kind, IdentifierConfig cfg, EDbVendor vendor)
CatalogQualifiedName with both raw and normalized segments tracked.
Each segment is normalized using the ESQLDataObjectType appropriate for its
position in the qualified name (catalog → schema → table → column), so vendor-specific
dual policies like BigQuery's case-sensitive tables vs case-insensitive columns are
applied per-segment rather than en bloc.
public static CatalogQualifiedName fromAlreadyNormalizedSegments(List<String> segments, CatalogObjectKind kind, EDbVendor vendor)
CatalogQualifiedName from segments that have already been normalized
by IdentifierService. Used by the lazy TSQLEnv bridge, where
TSQLEnv.doSearchSchemaObject hands over already-normalized catalog/schema/
object segments and rejoining + re-parsing would mis-handle quoted-embedded-dot
names like Oracle "a.b"."c".
Each segment is taken verbatim — no further normalization is applied — and the
resulting name carries identical raw and normalized segments. Quoted-flag bits are
empty by default; callers that have lost quoting information at this layer
(the legacy bridge path) accept the limitation that quoted-vs-unquoted distinction
is no longer recoverable. Snapshot lookups on this name still match because the
snapshot keys also derive from the same IdentifierService normalization.
public static String keyForMap(CatalogQualifiedName name)
CatalogQualifiedName.
Length-prefixes each normalized segment so quoted identifiers that contain dots —
e.g., Oracle "a.b"."c" vs "a"."b.c" — produce distinct keys despite
having the same flat join.
COLLATION_BASED limitation: for SQL Server / Azure SQL under a
case-insensitive collation, the legacy IdentifierService.normalize preserves
case (the legacy code-path uses Collator-bucketed lookup for those dialects).
Consequently keyForMap("dbo.Orders") and keyForMap("dbo.orders") can
differ even though areEqual(gudusoft.gsqlparser.catalog.runtime.CatalogQualifiedName, gudusoft.gsqlparser.catalog.runtime.CatalogQualifiedName) considers them equal. Snapshot/index lookups
that key by keyForMap on those dialects must therefore fall back to
areEqual(gudusoft.gsqlparser.catalog.runtime.CatalogQualifiedName, gudusoft.gsqlparser.catalog.runtime.CatalogQualifiedName) on a key miss; this is the same contract that
IdentifierService.canUseCompositeKey() exposes for the legacy code-path.
P1B/P1C may add a Collator-aware key bucket; tracked as a separate task.
public static boolean areEqual(CatalogQualifiedName a, CatalogQualifiedName b)
ESQLDataObjectType that matches each segment's role (catalog/schema/table/
column) so dual-policy dialects compare correctly.
Comparison is two-tier: first the cfg-aware normalized segments are
compared directly (which handles every dialect whose normalize captures
equality, including non-default IdentifierConfigs like MySQL
lower_case_table_names=1/2); on a mismatch we fall through to
IdentifierService.areEqualStatic(gudusoft.gsqlparser.EDbVendor, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType, java.lang.String, java.lang.String) so Collator-based dialects (SQL Server / Azure
SQL with case-insensitive collations) still report equality correctly.
public static String normalize(String raw, IdentifierConfig cfg)
IdentifierConfig's vendor.public static String normalizeSegment(String raw, boolean wasQuoted, IdentifierConfig cfg)
public static IdentifierService identifierServiceFor(IdentifierConfig cfg, EDbVendor vendor)
serviceFor(IdentifierConfig, EDbVendor) for callers
outside this package (validator, readers, providers) that need an
IdentifierService matching a IdentifierConfig. Vendor defaults
to cfg.vendor() when vendor is null.public static ESQLDataObjectType comparisonTypeOfKind(CatalogObjectKind kind)
ESQLDataObjectType for an object of this entity kind — the
SINGLE owner of this mapping (stage S0R of
docs/refactor/identifier_identity_display_separation_plan.md).
It lives here, not in sqlenv, because CatalogObjectKind is a catalog
concept and catalog.runtime already depends on sqlenv. Putting the mapping
in sqlenv would make the low-level identifier layer depend on the high-level catalog
layer and close a package cycle. sqlenv.IdentifierRolePolicy therefore maps the
ROLES it owns and refuses OBJECT, whose comparison type only a catalog caller knows.
Several kinds deliberately share a type: a view and a materialized view fold like a table because no dialect gives them separate case rules. Their IDENTITY stays separate because the key carries the kind itself. The kind discriminates the entity; the type decides how its spelling folds.
IllegalStateException - for an unmapped kind. Falling through to dotUnknown is
how a whole entity class acquires the wrong case semantics unnoticed, which is
exactly what happened to SEQUENCE, INDEX and CONSTRAINT before the comparison types
for them existed.