Chapter 08 · CQL Data Types, Collections, Tuples, UDTs, Static Columns, and Frozen Values
Scalar Types, Timestamps / Dates, UUID / timeuuid, Decimal / Varint, Blob, Duration, and Conversion
Choose Cassandra scalar types from exact range, encoding, ordering, time, precision, and driver-decoding semantics rather than familiar SQL names.
Learning outcomes
AtlasMart is ingesting order totals, event clocks, identifiers,
checksums, service windows, and high-precision counters from
multiple services. Choosing text for everything
would move validation bugs into application code and make
ordering, arithmetic, range checks, and driver decoding
ambiguous. Cassandra Query Language (CQL) scalar types are part
of the storage contract: they define accepted values, binary
representation, comparison behavior, and the native-protocol
type a driver decodes.
Choose scalar types from value domain, precision, ordering, range, and interoperability requirements.
Distinguish timestamp/date/time from timeuuid and explain what each can and cannot encode.
Use decimal for arbitrary-precision decimal arithmetic and varint for arbitrary-precision integers without assuming fixed-width storage.
Represent opaque bytes with blob and calendar/clock components with duration without pretending either is ordinary text.
Inspect server values in cqlsh and map representative columns to Apache Java Driver 4.19.3 decoding APIs.
The mandatory labs use the pinned
cassandra:5.0.9 image. Java 17,
cqlsh, and nodetool are the versions
bundled by that image. The course topology is three disposable
nodes (atlasmart-cass-1..3) in cluster
atlasmart-course, datacenter dc1,
racks rack1..rack3, 16 vnodes per node,
replication factor (RF) 3, and LOCAL_QUORUM for
consistency-sensitive examples. Authentication, client TLS,
internode TLS, and remote JMX are not enabled in this isolated
learning network; production must secure those boundaries
separately. Chapter 08 uses keyspace
atlasmart_types; new tables explicitly use
UnifiedCompactionStrategy (UCS), default table TTL is zero
unless stated, and gc_grace_seconds is not
changed. Storage-Attached Indexing (SAI) and vector search are
not required. Apache Cassandra Java Driver 4.19.3 is used only
in optional decoding snippets; the mandatory path remains
free/local with cqlsh.
The commands and CQL below are documentation- and syntax-reviewed but were not executed in this generation environment. Treat output as an expected shape, then capture exact UUIDs, time values, SSTable paths, tombstone counters, and driver-decoded values on your machine. If three nodes are too heavy, use one disposable node and RF=1 to learn type/mutation semantics, but do not treat that reduced topology as evidence about RF=3 availability or repair behavior.
1. A type is a storage and comparison contract
Cassandra types are not cosmetic aliases. int and
bigint are fixed-width signed integers with
different ranges; varint is arbitrary precision.
decimal stores arbitrary-precision decimal values
and is a better semantic fit for exact monetary quantities than
binary floating point. uuid is an opaque
universally unique identifier. timeuuid is
specifically a version-1 UUID whose timestamp component can
support time ordering and conversion functions.
blob stores uninterpreted bytes, expressed in CQL
with a 0x-prefixed hexadecimal literal.
duration stores months, days, and nanoseconds as
separate components; because calendar months do not have one
fixed number of seconds, do not reduce duration to a single
millisecond count without a reference date.
| AtlasMart value | Candidate CQL type | Reason | Boundary to test |
|---|---|---|---|
| order total | decimal |
exact decimal quantity | scale/rounding belongs to business logic |
| inventory sequence | varint |
can exceed fixed integer width | driver big-integer decoding |
| event instant | timestamp |
absolute millisecond instant | timezone formatting is client-side presentation |
| business date | date |
calendar date without time-of-day | do not smuggle timezone into it |
| ordered event id | timeuuid |
UUID identity + embedded v1 time | not every UUID is a timeuuid |
| checksum bytes | blob |
opaque binary | application owns interpretation |
| fulfilment SLA | duration |
months/days/nanos components | calendar arithmetic is context-dependent |
2. Lab: store and inspect representative scalar values
# Verify the shared course lab if it already exists.docker exec atlasmart-cass-1 nodetool versiondocker exec atlasmart-cass-1 nodetool status# Standalone local recreation path. Skip resources that already exist.docker network inspect atlasmart-cassandra >/dev/null 2>&1 || docker network create atlasmart-cassandradocker volume create atlasmart-cass-1-datadocker volume create atlasmart-cass-2-datadocker volume create atlasmart-cass-3-datadocker inspect atlasmart-cass-1 >/dev/null 2>&1 || docker run -d --name atlasmart-cass-1 --hostname atlasmart-cass-1 --network atlasmart-cassandra -e CASSANDRA_CLUSTER_NAME=atlasmart-course -e CASSANDRA_DC=dc1 -e CASSANDRA_RACK=rack1 -e CASSANDRA_ENDPOINT_SNITCH=GossipingPropertyFileSnitch -e CASSANDRA_NUM_TOKENS=16 -v atlasmart-cass-1-data:/var/lib/cassandra cassandra:5.0.9# Wait for node 1 to answer before starting peers.docker exec atlasmart-cass-1 nodetool statusdocker inspect atlasmart-cass-2 >/dev/null 2>&1 || docker run -d --name atlasmart-cass-2 --hostname atlasmart-cass-2 --network atlasmart-cassandra -e CASSANDRA_CLUSTER_NAME=atlasmart-course -e CASSANDRA_DC=dc1 -e CASSANDRA_RACK=rack2 -e CASSANDRA_ENDPOINT_SNITCH=GossipingPropertyFileSnitch -e CASSANDRA_NUM_TOKENS=16 -e CASSANDRA_SEEDS=atlasmart-cass-1 -v atlasmart-cass-2-data:/var/lib/cassandra cassandra:5.0.9docker inspect atlasmart-cass-3 >/dev/null 2>&1 || docker run -d --name atlasmart-cass-3 --hostname atlasmart-cass-3 --network atlasmart-cassandra -e CASSANDRA_CLUSTER_NAME=atlasmart-course -e CASSANDRA_DC=dc1 -e CASSANDRA_RACK=rack3 -e CASSANDRA_ENDPOINT_SNITCH=GossipingPropertyFileSnitch -e CASSANDRA_NUM_TOKENS=16 -e CASSANDRA_SEEDS=atlasmart-cass-1 -v atlasmart-cass-3-data:/var/lib/cassandra cassandra:5.0.9# Continue only after all nodes show UN.docker exec atlasmart-cass-1 nodetool statusdocker exec atlasmart-cass-1 cqlsh -e "CREATE KEYSPACE IF NOT EXISTS atlasmart_types WITH replication = {'class':'NetworkTopologyStrategy','dc1':3};"docker exec atlasmart-cass-1 cqlsh -e "DESCRIBE KEYSPACE atlasmart_types"
CREATE TABLE atlasmart_types.scalar_probe ( tenant_id uuid, event_id timeuuid, recorded_at timestamp, business_date date, opening_time time, order_total decimal, huge_sequence varint, checksum blob, fulfilment_sla duration, note text, PRIMARY KEY ((tenant_id), event_id)) WITH CLUSTERING ORDER BY (event_id DESC) AND compaction = {'class':'UnifiedCompactionStrategy'};DESCRIBE TABLE atlasmart_types.scalar_probe;
CONSISTENCY LOCAL_QUORUM;INSERT INTO atlasmart_types.scalar_probe(tenant_id,event_id,recorded_at,business_date,opening_time,order_total,huge_sequence,checksum,fulfilment_sla,note)VALUES(11111111-1111-1111-1111-111111111111, now(), '2026-09-07T12:00:00Z', '2026-09-07', '12:34:56.123456789', 1299.95, 123456789012345678901234567890, 0x41424344, 2d3h, 'paid');SELECT tenant_id,event_id,to_timestamp(event_id),to_unix_timestamp(event_id),recorded_at,business_date,opening_time,order_total,huge_sequence,checksum,fulfilment_slaFROM atlasmart_types.scalar_probeWHERE tenant_id=11111111-1111-1111-1111-111111111111;
The exact generated event_id differs every run.
What matters is that to_timestamp(event_id) and
to_unix_timestamp(event_id) decode the embedded
version-1 UUID time, while the separately stored
recorded_at remains an ordinary timestamp.
Cassandra 5.0 uses the snake_case conversion names; older
camelCase spellings are deprecated.
3. Driver decoding is part of the contract
An application driver does not receive a bag of strings. Native-protocol metadata identifies each CQL type, and the codec layer maps it to language types. The optional Java example below uses Apache Cassandra Java Driver 4.19.3; exact imports/configuration should be rechecked if you use another driver or release.
Row row = session.execute( "SELECT event_id, recorded_at, order_total, huge_sequence, checksum, fulfilment_sla " + "FROM atlasmart_types.scalar_probe WHERE tenant_id=?", UUID.fromString("11111111-1111-1111-1111-111111111111")).one();UUID eventId = row.getUuid("event_id");Instant recordedAt = row.getInstant("recorded_at");BigDecimal total = row.getBigDecimal("order_total");BigInteger sequence = row.getBigInteger("huge_sequence");ByteBuffer checksum = row.getByteBuffer("checksum");CqlDuration sla = row.getCqlDuration("fulfilment_sla");
That hides precision/range violations until later, loses binary type metadata, makes ordering semantics application-dependent, and encourages accidental timezone or money bugs. Repair the design by choosing the narrowest semantic type that preserves the business invariant, then test both CQL literals and driver codecs.
4. Production judgment
Scalar choice affects partition-key serialization, clustering
comparison, payload size, CPU for numeric conversion,
application serialization, and migration compatibility. Do not
change a column's type casually: modern CQL removed arbitrary
ALTER TYPE changes for table columns, so
incompatible type redesign normally means a new column/table and
controlled migration. For identifiers, use
uuid unless time ordering is part of the contract;
for event ordering, timeuuid can help but does not
replace a separately modeled business timestamp. For money,
avoid binary floating point when exact decimal semantics are
required. For blob, validate size/content at the
application boundary because Cassandra cannot interpret your
encoding. Duration arithmetic and cross-language decoding
deserve explicit tests.
Verification checklist
- The scalar table schema matches the intended value domains rather than generic text columns.
- timeuuid conversion produces a timestamp/epoch value derived from the generated identifier.
- Decimal/varint values round-trip without fixed-width truncation.
- Blob output is still opaque bytes; interpretation remains outside Cassandra.
- Optional driver decoding uses typed getters rather than string parsing.
Check your understanding
- Why is timeuuid not interchangeable with any UUID?
- Why prefer decimal over double for exact money?
- What does a blob tell Cassandra about its contents?
- Why is duration not simply a millisecond count?
- What should a driver test verify?
Review the answers
1. A timeuuid is specifically a version-1 UUID with time semantics; an arbitrary UUID may not contain that ordered timestamp structure.
2. Decimal preserves base-10 arbitrary-precision semantics; binary floating point can introduce representation error.
3. Nothing beyond bytes. The application owns the encoding and validation.
4. It carries months, days, and nanoseconds separately; month length depends on calendar context.
5. That server types decode into the intended language types and preserve precision/range semantics.
# Destructive only to this disposable chapter keyspace.docker exec atlasmart-cass-1 cqlsh -e "DROP KEYSPACE IF EXISTS atlasmart_types;"# Keep shared course containers for Chapter 09, or remove them for a full reset:# docker rm -f atlasmart-cass-1 atlasmart-cass-2 atlasmart-cass-3# docker volume rm atlasmart-cass-1-data atlasmart-cass-2-data atlasmart-cass-3-data# docker network rm atlasmart-cassandra
Summary and next bridge
Scalar types are executable domain constraints, not decoration. Next we move from one typed value per cell to lists, sets, and maps—where growth, element mutation, TTL, and tombstone behavior become part of the storage cost.
Authoritative references
- CQL data types — scalar, collection, tuple, user-defined type, frozen, and literal semantics.
- Creating collections — bounded collection guidance and current collection guardrails.
- CQL data definition — static-column behavior, table/type definitions, and schema restrictions.
- CREATE TABLE reference — frozen/non-frozen UDT and static-column examples.
- Tombstones — deletion markers, grace, reads, and compaction implications.
- Apache Cassandra downloads — current server and Java-driver release baselines.