java.lang.Object
com.darkcollective.relix.symbol.Schema

public final class Schema extends Object
The structural description of a relation — an ordered list of named, typed columns.

Column names within a schema are unique in a case-insensitive sense: "UserId" and "userid" are considered the same name and will cause an IllegalArgumentException at construction time.

The column list is defensive-copied on construction; instances are therefore immutable. A lowercased name → position index is precomputed once at construction so that indexOf(String) and column(String) (and, in turn, per-row name lookups during query execution) run in constant time without re-scanning or re-lowercasing the column list on every access.

Equality and hash code are defined purely over the ordered column list, so the type behaves like the value object it conceptually is (and the way it did when it was declared as a record).

Example:


 Schema schema = new Schema(List.of(
     new ColumnDefinition("id",   ScalarType.NUMBER),
     new ColumnDefinition("name", ScalarType.STRING)
 ));
 schema.column("ID");  // Optional.of(ColumnDefinition("id", NUMBER))
 schema.indexOf("ID"); // 0
 
  • Constructor Summary

    Constructors
    Constructor
    Description
    Creates a schema from the given ordered column definitions.
  • Method Summary

    Modifier and Type
    Method
    Description
    column(String name)
    Returns the column definition whose name matches name (case-insensitively), or Optional.empty() if no such column exists.
    Returns the ordered column definitions of this schema.
    concat(Schema other)
    Returns a new Schema formed by appending all columns of other to this schema.
    boolean
    Whether this schema declares a column called name — the question to ask before adding one.
    static Schema
    Returns the empty schema — a closed relation with no columns.
    boolean
     
    int
     
    boolean
    Whether any column in this schema carries source-relation ColumnProvenance.
    int
    Returns the zero-based position of the column named name (case-insensitively), or -1 if no such column exists.
    boolean
    Whether this schema is the empty (closed, zero-column) schema — the heading of the nullary truth relations.
    boolean
    Whether this schema is open (dynamic / schema-on-read).
    static Schema
    Returns an open schema — a dynamic document with no fixed columns, for schema-on-read sources (e.g.
    qualifiedIndices(String qualifier, String columnName)
    Returns the zero-based positions of every column whose source-relation ColumnProvenance matches the qualified reference qualifier.columnName (both matched case-insensitively).
    Resolves a possibly-dotted reference to the type it names, descending into struct fields — person.name, provenance.variables.relation.
     
    int
    Returns the number of columns in this schema.
    Returns a schema with replacement as its known columns and this schema's openness.

    Methods inherited from class java.lang.Object

    clone, finalize, getClass, notify, notifyAll, wait, wait, wait
  • Constructor Details

    • Schema

      public Schema(List<ColumnDefinition> columns)
      Creates a schema from the given ordered column definitions.
      Parameters:
      columns - the ordered column definitions; must not be null or empty, and must not contain duplicate names (case-insensitive)
      Throws:
      IllegalArgumentException - if columns is empty or contains a duplicate column name
  • Method Details

    • open

      public static Schema open()
      Returns an open schema — a dynamic document with no fixed columns, for schema-on-read sources (e.g. a NoSQL collection or JSON endpoint with no declared schema). Every column(String) lookup resolves to a ScalarType.ANY column, so attribute/path references against it are never reported as missing; they resolve at runtime.

      An open schema is distinct from an absent/unresolved schema: it is a legitimate, queryable state, not an error placeholder.

      Returns:
      the singleton-style open schema (a fresh instance; all are equal)
    • empty

      public static Schema empty()
      Returns the empty schema — a closed relation with no columns. This is the heading of the two nullary "truth" relations of the relational algebra (Tutorial D's TABLE_DEE/TABLE_DUM): a zero-column relation holds either one tuple (the empty tuple — "true") or no tuples ("false"). It is produced today by the no-key whole-relation universal quantifier ∀ : P (R).

      The empty schema is the opposite of an open schema: both have zero columns, but an open schema resolves every column reference (to ScalarType.ANY), whereas the empty schema resolves none. equals(Object) keeps the two distinct.

      Returns:
      the empty (closed, zero-column) schema; a fresh instance, all equal
    • isOpen

      public boolean isOpen()
      Whether this schema is open (dynamic / schema-on-read).
      Returns:
      true for an open schema
    • isEmpty

      public boolean isEmpty()
      Whether this schema is the empty (closed, zero-column) schema — the heading of the nullary truth relations. Distinct from an open schema, which is also column-less but resolves every reference.
      Returns:
      true only for the closed zero-column schema
    • columns

      public List<ColumnDefinition> columns()
      Returns the ordered column definitions of this schema.
      Returns:
      an immutable list of column definitions; empty for the empty schema and for open(), but not necessarily for an open heading a join has added known columns to
    • indexOf

      public int indexOf(String name)
      Returns the zero-based position of the column named name (case-insensitively), or -1 if no such column exists.
      Parameters:
      name - the column name to look up; must not be null
      Returns:
      the column's position, or -1 if absent
    • column

      public Optional<ColumnDefinition> column(String name)
      Returns the column definition whose name matches name (case-insensitively), or Optional.empty() if no such column exists.
      Parameters:
      name - the column name to look up
      Returns:
      the matching column definition, or empty
    • declares

      public boolean declares(String name)
      Whether this schema declares a column called name — the question to ask before adding one.

      Not the same question as column(String), which answers what a read of that name would give. The two differ on an open schema: a schema-on-read relation resolves any name to ANY, because the shape arrives with the row, while declaring nothing at all. Reading column(name).isPresent() as "the name is taken" therefore made every name taken, and every operator that appends a column — SESSIONIZE, TRACE, WINDOW, TREE, WHY — refused to run over any JSON, HTTP or MongoDB source.

      Parameters:
      name - the column name to test; must not be null
      Returns:
      true only if this schema names that column itself
    • resolvePath

      public Optional<Type> resolvePath(String name)
      Resolves a possibly-dotted reference to the type it names, descending into struct fields — person.name, provenance.variables.relation.

      A whole-name match wins first, so a column whose name really does contain a dot (written with a delimited identifier) is still reachable. Only then is the name read as a path: its head must name a column, and each remaining segment a field of the type before it.

      A path into an ANY column resolves to ANY rather than failing. Schema-on-read data has no declared shape to check a field name against, so the alternative to trusting the reference is refusing every reference — which would make a nested column readable only by relations that declared their shape up front, exactly the case that cannot.

      This is not the relation-qualified form (Orders.amount), which resolves by source-relation provenance and is checked before this; the two are spelled alike and a caller tries them in that order.

      Parameters:
      name - a column name, or a dotted path into one; must not be null
      Returns:
      the type the reference names, or empty if nothing does
    • width

      public int width()
      Returns the number of columns in this schema.
      Returns:
      column count — the columns that are known, which for an open heading is not all a row may carry; 0 for the empty and open() schemas
    • concat

      public Schema concat(Schema other)
      Returns a new Schema formed by appending all columns of other to this schema.

      If a column from other has the same name (case-insensitively) as an already-present column, it is renamed by appending _r; if that still clashes, _r1, _r2, … are tried until a unique name is found.

      Openness survives. The result is open if either side is, whether or not the other side has columns: a reference into a dynamic document still resolves through the join, and the columns the declared side does name keep their own types. Concatenating two column-less schemas is the same rule at its limit — open if either is, otherwise the empty schema, which is what a join of two nullary truth relations has to be. Both are outside what the column-list constructor accepts, so this is the one place the two zero-column headings survive being combined.

      Parameters:
      other - the schema whose columns to append; must not be null
      Returns:
      the concatenated schema; never null
    • withColumns

      public Schema withColumns(List<ColumnDefinition> replacement)
      Returns a schema with replacement as its known columns and this schema's openness.

      A rewrite of an open heading's known columns (re-anchoring their provenance under a rename, say) has to leave the heading open: a document row under it still carries fields the heading does not name. Building the result with Schema(List) would close it, and the executor would then size a document row against a fixed width.

      Parameters:
      replacement - the new known columns, in order; must not be null or empty, and must not contain duplicate names
      Returns:
      a schema over replacement, open exactly when this one is
      Throws:
      IllegalArgumentException - if replacement is empty or contains a duplicate column name
    • hasProvenance

      public boolean hasProvenance()
      Whether any column in this schema carries source-relation ColumnProvenance. Qualified-reference resolution and validation only engage when provenance is present; a schema with none falls back to the legacy qualifier-stripping behaviour.
      Returns:
      true if at least one column has non-null provenance
    • qualifiedIndices

      public List<Integer> qualifiedIndices(String qualifier, String columnName)
      Returns the zero-based positions of every column whose source-relation ColumnProvenance matches the qualified reference qualifier.columnName (both matched case-insensitively).

      By construction this yields at most one match for a well-formed schema: distinct source relations keep their columns distinct, and a whole-relation rename (ρ R (…)) re-anchors every origin to R using the (unique) physical names. More than one match can only arise from a raw self-join of the same relation with no intervening rename — a genuine ambiguity the validator reports.

      Parameters:
      qualifier - the relation qualifier (e.g. rooms)
      columnName - the column name within that relation (e.g. name)
      Returns:
      the matching positions in column order; empty for an open schema or when nothing matches
    • 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