No constraints to discover, and a column order to rebuild
The catalog reports no position for regular columns and sorts by name, so the tree rebuilds the order a where clause actually needs.
Open the ER diagram on a Cassandra connection and the canvas has boxes and no
lines. Open one of those boxes and the columns sit in an order nobody typed. Both
are the same fact seen twice: a Cassandra table schema publishes a column order
for the partition key and the clustering columns, and nothing else - and it
publishes no relations at all. Every engine answer quoted below was measured
against Apache Cassandra 5.0.9, the version system.local.release_version
reports on the official cassandra:5.0.9 image.
Why the relation list is always empty here
An empty edge list is usually ambiguous. It can mean the read found nothing, the
read was refused, or the concept does not exist on this engine, and a client that
reports [] for all three has told you nothing.
Cassandra settles it. The provider declares no foreign keys and the relation list
is always empty, because the clause does not exist in this model, and index
uniqueness is always false for the same reason. Neither is a gap in the read.
ALTER TABLE ... ADD CONSTRAINT ... FOREIGN KEY answers line 1:48 mismatched input 'FOREIGN' expecting EOF, and CREATE UNIQUE INDEX answers no viable alternative at input 'UNIQUE'. The keywords are not in the grammar, so there is
no statement that could create the object the diagram is looking for.
That is recorded in the capability set rather than left to the canvas to imply:
declaresForeignKeys is false, so a reader knows foreignKeys: [] means this
engine has none rather than this schema declares none. The ER diagram
feature publishes the matching limit on the product side - edges come
from declared foreign keys, and a relationship your application enforces in code
but never declares has nothing to discover.
A primary key that means something else
The relational reflex is to read a primary key as an identity. In CQL it is an access path, and it is two things wearing one name.
The partition key decides which partition a row lives in. The clustering columns decide the order of rows inside that partition. Only the brackets in the DDL say which columns do which job, and the difference is not cosmetic:
-- probe.composite_pk
PRIMARY KEY ((tenant, day), ts)
-- probe.pk_flat
PRIMARY KEY (tenant, day, ts)
Those two tables differ by one pair of brackets and nothing else. SELECT * FROM probe.pk_flat WHERE tenant = 'a' is served, because tenant is the whole
partition key there. The
identical restriction on probe.composite_pk answers code 2200, Cannot execute this query as it might involve data filtering and thus may have unpredictable performance - tenant is half a partition key, and half is not a partition.
Ordering follows the same rule. SELECT * FROM t ORDER BY name answers ORDER BY is only supported when the partition key is restricted by an EQ or an IN, because
a sort order only exists inside a partition.
So the useful thing a schema tree can tell you about a Cassandra table is not which column is “the key”. It is which columns are the partition key, in what order, and which are the clustering columns, in what order.
What the catalog does not preserve
Introspection is four statements against system_schema, scoped to the pinned
keyspace: tables, views, columns and indexes. The column read is the one that
matters here:
SELECT table_name, column_name, type, kind, position, clustering_order
FROM system_schema.columns WHERE keyspace_name = 'probe'
Two properties of that result set decide what a tree can honestly show:
| Column kind | position |
Ordering in the result |
|---|---|---|
partition_key |
0-based within its kind | by column name |
clustering |
0-based within its kind | by column name |
regular, static |
always -1 |
by column name |
The server returns rows sorted by column name, and position is -1 for every
regular and static column. So the order the table was written in cannot be
reconstructed from the catalog.
nullable comes out of the same read and is false for exactly the primary-key
components, because CQL has no NOT NULL to declare on anything else and every
regular column of an existing row may be absent entirely.
Rebuilding a Cassandra table schema column order from the partition key out
Declaration order is gone, so the tree does not pretend to have it. It builds an
order that means something instead: partition key by position, then clustering
columns by position, then everything else alphabetically.
That is not an arbitrary tie-break. It is the order DESCRIBE TABLE prints, and
it is the order the primary key has to be written in a WHERE clause. Reading a
table node top to bottom gives you the prefix rules in the sequence you need them,
and clustering_order comes along on each clustering column, so the direction the
rows are stored in is on the same line as the column that stores them.
The same distinction is why the schema-diff migration generator will not emit a
Cassandra CREATE TABLE. system_schema.columns separates the two roles by
kind, but ColumnDiff keeps a single boolean for “is primary”, so both tables
in the bracket example above reduce to the same three key columns and the shared
serializer would pick the flat layout. Generating that would repartition the data
and succeed while doing it, so a comment naming the reason is emitted in place of
the DDL. One boolean is not enough to describe this key, in the diff generator or
anywhere else.
Reading the tree as a query guide
The tree for a Cassandra keyspace is a list of access paths. Each table names its
partition key first, so you can see which restriction any query against it has to
carry; then its clustering columns in storage order, so you can see what may be
ranged over and what may be sorted; then the rest of the columns, which you can
select and cannot filter on without ALLOW FILTERING.
Secondary indexes are read from system_schema.indexes, with options.target
naming the indexed column, and each one carries unique: false - the engine’s
answer, not the schema’s. Materialized views come from system_schema.views and
appear as tables with their own columns; they are disabled by default in 5.0
(materialized_views_enabled: false), so on a stock install that list is empty
and the tree shows tables only.