java.lang.Object
io.confluent.kafka.schemaregistry.type.logical.LogicalType

public class LogicalType extends Object
Represents a logical type with its root schema, locally-defined named types, and external schema references. Per-field default values live on Schema.Field; a path-keyed mirror is also exposed via getDefaultValues() for callers that need positional access (e.g., Connect-style serialization).

Default-value path semantics (matching the Flink converters): the key is a list of indices that walks from the root into the field. For STRUCT(a INT, b STRUCT(b1 STRING)), a's default is at [0] and b.b1's is at [1, 0]. ARRAY elements use index 0; MAP key/value use 0/1.

  • Constructor Details

  • Method Details

    • getRootSchema

      public Schema getRootSchema()
    • getNamedTypes

      public Map<String,Schema> getNamedTypes()
      Locally-defined named types, keyed by their qualified name.

      Nesting convention: a key like Outer.Inner where Outer is also a key in this map indicates that Inner is nested inside Outer. The dot acts as the nesting separator. When a key has dots but no prefix matches another key, the dots are interpreted as a namespace prefix and the type is top-level. See parentOf(java.lang.String) and nestingTree() for accessors that interpret the convention.

      Only the Protobuf writer emits the nesting structurally (as proto's native nested messages). Avro and JSON Schema emit dotted-name types as flat top-level entries (their full names just contain dots).

    • getExternalTypes

      public Set<String> getExternalTypes()
      FQNs in namedTypes whose body originated from an external schema (an entry in resolvedReferences) rather than from a locally- defined type. Writers use this set to decide whether to re-emit the type as an inline definition (for entries NOT in this set) or as an import / $ref (for entries IN this set).

      The set is populated by readers when they lazily promote external bodies into namedTypes, and by the DDL visitor by inferring external-ness from usage (any NAMED_TYPE_REF FQN not declared locally).

    • isExternal

      public boolean isExternal(String fqn)
      True iff fqn is a key in getExternalTypes().
    • getExternalImports

      public Map<String,String> getExternalImports()
      Per-FQN binding from a synthetic-wrapper external typeName to the original $ref URI it stands in for. Populated by the JSON reader when it synthesizes Ref<N>-style names for non-canonical $refs (whole-doc, arbitrary JSON Pointers, and other shapes that don't extract cleanly to a $defs key), and by the DDL visitor when it parses USING TYPE x FOR '<uri>' statements.

      The JSON writer consults this map to emit those externals as a local #/$defs/<name> reference whose body is a single {"$ref": "<uri>", "logical.ref": true} passthrough — keeping the indirection scoped to the active document instead of requiring a separately-published wrapper subject. Empty for LT-canonical workspaces.

      Avro and Proto writers reject any LT carrying entries here: synthetic wrappers are JSON-specific (the URI-vs-name impedance only exists for JSON's $ref model). Use a fresh, format-appropriate LT instead.

      Independence from getReferences(). externalImports carries wire-format binding (typeName → URI used in $ref); the references list carries SR coordinates (subject + version used by the SR client to fetch external content). They are populated independently — DDL's USING TYPE populates externalImports but never the references list. To produce an SR-publishable LT from DDL, attach a references list (with matching resolvedReferences content) separately after the visitor runs.

    • getLocalNamedTypes

      public Map<String,Schema> getLocalNamedTypes()
      Subset of getNamedTypes() that are NOT in getExternalTypes() — the entries the user authored locally. Writers use this accessor to iterate "the local stuff to inline-emit" without re-emitting external bodies that should appear as imports.
    • getReferences

      public List<SchemaReference> getReferences()
    • getResolvedReferences

      public Map<String,String> getResolvedReferences()
    • getNamespace

      public String getNamespace()
      The document-level namespace, or null if none.
    • getName

      public String getName()
      The root type's own name, or null when the root has no otherwise-lost name (a NAMED_TYPE_REF root, whose name is the getNamedTypes() key, or an anonymous root). Set only for an inline root whose name would otherwise be discarded — a Protobuf message unwrapped to a bare struct, or a JSON root object's title. Used by the DDL writer to emit a named root declaration.
    • findOrphanNamedTypes

      public Set<String> findOrphanNamedTypes()
      Names of locally-defined types that are not reachable from the root via the named-type-ref graph. These are dropped by single-root encoders such as Avro; multi-root encoders (Protobuf top-level messages, JSON $defs) may still emit them.
    • parentOf

      public String parentOf(String key)
      Returns the parent named-type key of key under the dotted-path nesting convention, or null if key is top-level.

      The parent is the longest dotted prefix of key that itself exists in namedTypes. Example: with named types Outer and Outer.Inner, parentOf("Outer.Inner") returns "Outer"; parentOf("Outer") returns null (no matching prefix → top-level).

      Dots that do not correspond to a defined parent are namespace dots (top-level). So parentOf("com.example.Foo") with no com.example type defined returns null — the dots are just the namespace prefix.

    • parentOf

      public static String parentOf(String key, Set<String> definedTypes)
      Static form of parentOf(String) for use during validation before a LogicalType instance exists (e.g., inside the visitor).
    • topLevelAncestor

      public String topLevelAncestor(String key)
      Walks parent links via parentOf(java.lang.String) until reaching a top-level type. Returns key itself if key is already top-level.
    • isNested

      public boolean isNested(String key)
      Returns true if key represents a nested type — i.e. it has a parent in namedTypes per the dotted-path convention.
    • nestingTree

      public Map<String,List<String>> nestingTree()
      Group named types by parent. The map keys cover every entry of namedTypes; the values are the immediate children (one nesting level deep). Top-level types appear under the null parent.

      Used by writers that emit nesting natively (Protobuf): walk the entry for null (top-level messages), then recursively walk each child's entry to nest its children inside.

    • collectNamedRefs

      public static void collectNamedRefs(Schema schema, Set<String> sink)
      Walk schema and collect the qualified names of every NAMED_TYPE_REF encountered. Recurses through structural composites (struct, union, array, multiset, map) but does not follow the named-type-ref itself — callers do that via namedTypes.
    • isCyclic

      public boolean isCyclic(String fqn)
      True iff fqn is part of a cycle in the named-type-ref graph formed by namedTypes — i.e., a self-reference or mutual recursion through other peers.
    • isCyclic

      public static boolean isCyclic(String fqn, Map<String,Schema> namedTypes)
      Static form of isCyclic(String) for use during conversion when a builder map is being populated and the LogicalType instance does not yet exist.
    • unwrapLeafNamedRoot

      public static LogicalType.RootUnwrap unwrapLeafNamedRoot(Schema rootSchema, Map<String,Schema> namedTypes, Set<String> externalTypes, String namespace)
      If rootSchema is a NAMED_TYPE_REF to a local leaf named type, unwrap it to the bare STRUCT/ENUM body carrying its simple name, mirroring the canonical named-root shape shared by every format reader and the DDL visitor. The body is removed from namedTypes (mutated in place); peers referencing the root still resolve by name.

      The unwrap is gated so it stays lossless through LT → format/DDL → LT; otherwise the root keeps its NAMED_TYPE_REF form:

      • unresolved (no body in namedTypes), cyclic, or has nested named types — unwrapping would orphan the namedTypes entry needed for resolution or recurse forever;
      • external (in externalTypes) — the body was promoted from a referenced schema; emitting it locally would misrepresent it as locally-defined;
      • referenced by a local peer — removing the body would dangle the peer's reference;
      • the root's own namespace differs from namespace — simple name + namespace would reconstruct a different FQN, so the full FQN is kept as the namedTypes key.

      The root's nullability is preserved onto the bare body.

    • isReferencedByPeer

      public static boolean isReferencedByPeer(String fqn, Map<String,Schema> namedTypes)
      True iff a named type other than fqn carries a NAMED_TYPE_REF to fqn. Used to keep a peer-referenced root as a NAMED_TYPE_REF so its definition stays in namedTypes where writers resolve the peer's reference.
    • getDefaultValues

      public Map<List<Integer>,Object> getDefaultValues()
      Path-keyed map of field-default values collected during conversion.

      Each key is a list of zero-based indices that walks from the root schema into the field carrying the default. Each container type contributes indices to the path according to a fixed convention:

      • STRUCT: the field's positional index. A struct with three fields contributes 0, 1, or 2 for its first, second, or third field respectively.
      • MAP: 0 for the key type, 1 for the value type.
      • MULTISET: 0 for the element type.
      • ARRAY: format-dependent. The Avro reader contributes 0 for the element type; the Proto and JSON readers contribute no index (descending into the element type uses the parent path directly). See each *ToLogicalTypeConverter's class-level Javadoc for the format-specific rule. (TODO: unify across formats.)
      • Wrapper structs (proto _RepeatedWrapper, _ElementWrapper, _OneofWrapper marked with flink.wrapped=true) are transparent: they contribute no index — descending into the wrapped payload uses the parent path. This keeps default-value paths aligned with the structure of the returned schema (which doesn't surface the wrapper layer).
      • Leaf types (primitives, enum, named-type-ref) carry the value and do not extend the path.

      Worked examples (assuming the root is a STRUCT):

       STRUCT { a: INT (default 1) }
         → { [0] = 1 }
      
       STRUCT { a: INT, m: MAP<STRING, INT> (key default "k", value default 5) }
         → { [1, 0] = "k", [1, 1] = 5 }
      
       STRUCT { nested: STRUCT { value: STRUCT { a: INT (default 17), b: INT (default 18) } } }
         → { [0, 0, 0] = 17, [0, 0, 1] = 18 }
      
       STRUCT {
         arr: ARRAY<STRUCT { value: STRUCT { a: INT (default 17), b: INT (default 18) } }>
       }
         → Proto:      { [0, 0, 0] = 17, [0, 0, 1] = 18 }      // ARRAY adds no index
         → Avro/JSON:  { [0, 0, 0, 0] = 17, [0, 0, 0, 1] = 18 } // ARRAY adds [0]
      
       STRUCT {
         wrappedMap: MAP<BIGINT, BIGINT> (proto-wrapped, key default 15, value default 16)
       }
         → { [0, 0] = 15, [0, 1] = 16 }   // wrapper is transparent; just MAP key/value
       
    • equals

      public boolean equals(Object o)
      Overrides:
      equals in class Object
    • hashCode

      public int hashCode()
      Overrides:
      hashCode in class Object
    • toString

      public String toString()
      Overrides:
      toString in class Object