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
ConstructorsConstructorDescriptionSchema(List<ColumnDefinition> columns) Creates a schema from the given ordered column definitions. -
Method Summary
Modifier and TypeMethodDescriptionReturns the column definition whose name matchesname(case-insensitively), orOptional.empty()if no such column exists.columns()Returns the ordered column definitions of this schema.Returns a newSchemaformed by appending all columns ofotherto this schema.booleanWhether this schema declares a column calledname— the question to ask before adding one.static Schemaempty()Returns the empty schema — a closed relation with no columns.booleaninthashCode()booleanWhether any column in this schema carries source-relationColumnProvenance.intReturns the zero-based position of the column namedname(case-insensitively), or-1if no such column exists.booleanisEmpty()Whether this schema is the empty (closed, zero-column) schema — the heading of the nullary truth relations.booleanisOpen()Whether this schema is open (dynamic / schema-on-read).static Schemaopen()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-relationColumnProvenancematches the qualified referencequalifier.columnName(both matched case-insensitively).resolvePath(String name) Resolves a possibly-dotted reference to the type it names, descending into struct fields —person.name,provenance.variables.relation.toString()intwidth()Returns the number of columns in this schema.withColumns(List<ColumnDefinition> replacement) Returns a schema withreplacementas its known columns and this schema's openness.
-
Constructor Details
-
Schema
Creates a schema from the given ordered column definitions.- Parameters:
columns- the ordered column definitions; must not benullor empty, and must not contain duplicate names (case-insensitive)- Throws:
IllegalArgumentException- ifcolumnsis empty or contains a duplicate column name
-
-
Method Details
-
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). Everycolumn(String)lookup resolves to aScalarType.ANYcolumn, 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
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'sTABLE_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:
truefor 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:
trueonly for the closed zero-column schema
-
columns
Returns the ordered column definitions of this schema. -
indexOf
Returns the zero-based position of the column namedname(case-insensitively), or-1if no such column exists.- Parameters:
name- the column name to look up; must not benull- Returns:
- the column's position, or
-1if absent
-
column
Returns the column definition whose name matchesname(case-insensitively), orOptional.empty()if no such column exists.- Parameters:
name- the column name to look up- Returns:
- the matching column definition, or empty
-
declares
Whether this schema declares a column calledname— 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 toANY, because the shape arrives with the row, while declaring nothing at all. Readingcolumn(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 benull- Returns:
trueonly if this schema names that column itself
-
resolvePath
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
ANYcolumn resolves toANYrather 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 benull- Returns:
- the type the reference names, or empty if nothing does
-
width
public int width()Returns the number of columns in this schema. -
concat
Returns a newSchemaformed by appending all columns ofotherto this schema.If a column from
otherhas 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 benull- Returns:
- the concatenated schema; never
null
-
withColumns
Returns a schema withreplacementas 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 benullor empty, and must not contain duplicate names- Returns:
- a schema over
replacement, open exactly when this one is - Throws:
IllegalArgumentException- ifreplacementis empty or contains a duplicate column name
-
hasProvenance
public boolean hasProvenance()Whether any column in this schema carries source-relationColumnProvenance. Qualified-reference resolution and validation only engage when provenance is present; a schema with none falls back to the legacy qualifier-stripping behaviour.- Returns:
trueif at least one column has non-null provenance
-
qualifiedIndices
Returns the zero-based positions of every column whose source-relationColumnProvenancematches the qualified referencequalifier.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 toRusing 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
-
hashCode
public int hashCode() -
toString
-