Ferrosa implements CQL native protocol v4 and v5 with full driver compatibility (including cdrs-tokio). This page documents what's supported, what's different, and what Ferrosa adds on top.
Ferrosa implements CQL native protocol versions 4 and 5 with the standard 9-byte frame header. Protocol v4 compatibility enables broad CQL driver support including cdrs-tokio. All 16 opcodes are handled:
| Opcode | Name | Direction |
|---|---|---|
| 0x00 | ERROR | Response |
| 0x01 | STARTUP | Request |
| 0x02 | READY | Response |
| 0x03 | AUTHENTICATE | Response |
| 0x05 | OPTIONS | Request |
| 0x06 | SUPPORTED | Response |
| 0x07 | QUERY | Request |
| 0x08 | RESULT | Response |
| 0x09 | PREPARE | Request |
| 0x0A | EXECUTE | Request |
| 0x0B | REGISTER | Request |
| 0x0C | EVENT | Response |
| 0x0D | BATCH | Request |
| 0x0E | AUTH_CHALLENGE | Response |
| 0x0F | AUTH_RESPONSE | Request |
| 0x10 | AUTH_SUCCESS | Response |
Frame-level compression is negotiated during STARTUP via the COMPRESSION option:
Compression is optional. Uncompressed frames are always accepted.
SASL PLAIN authentication via the standard org.apache.cassandra.auth.PasswordAuthenticator flow. Passwords are hashed with bcrypt or argon2. Disable with FERROSA_AUTH_DISABLED=true for development.
Driver bootstrap follows the standard Cassandra-compatible sequence. There is no Ferrosa-specific handshake required to unlock topology metadata:
OPTIONS / SUPPORTED.STARTUP.AUTHENTICATE → AUTH_RESPONSE → AUTH_SUCCESS. Otherwise the server returns READY directly.READY or AUTH_SUCCESS does the driver query system.local, system.peers, and system.peers_v2.This means post-auth hangs that occur while a driver is opening peer connections usually indicate bad topology metadata, not a missing bootstrap message. Ferrosa must advertise peer addresses that are reachable from the client's network.
Explicitly: OPTIONS does not control whether the client receives public or internal peer addresses. Neither do STARTUP, compression settings, or any Ferrosa-specific extension flag. The server chooses which addresses to return based on the source IP of the established client connection when servicing the topology queries.
| CQL Type | Protocol ID | Status |
|---|---|---|
| ascii | 0x0001 | Supported |
| bigint | 0x0002 | Supported |
| blob | 0x0003 | Supported |
| boolean | 0x0004 | Supported |
| counter | 0x0005 | Supported |
| decimal | 0x0006 | Supported |
| double | 0x0007 | Supported |
| float | 0x0008 | Supported |
| int | 0x0009 | Supported |
| timestamp | 0x000B | Supported |
| uuid | 0x000C | Supported |
| varchar / text | 0x000D | Supported |
| varint | 0x000E | Supported |
| timeuuid | 0x000F | Supported |
| inet | 0x0010 | Supported |
| date | 0x0011 | Supported |
| time | 0x0012 | Supported |
| smallint | 0x0013 | Supported |
| tinyint | 0x0014 | Supported |
| list<T> | 0x0020 | Supported |
| map<K, V> | 0x0021 | Supported |
| set<T> | 0x0022 | Supported |
| tuple<...> | 0x0031 | Supported |
| frozen<T> | — | Supported |
| duration | 0x0015 | Supported |
| UDT | 0x0030 | Supported |
| vector<float, N> | 0x0033 | Supported |
Collection bind values in prepared statements are fully supported. Map, set, and list values bound via EXECUTE are stored in Cassandra-compatible wire format and survive flush, compaction, and S3 upload without format loss.
| Statement | Status | Notes |
|---|---|---|
| CREATE KEYSPACE | Supported | WITH replication, IF NOT EXISTS, DURABLE_WRITES |
| ALTER KEYSPACE | Supported | Change replication, durable_writes |
| DROP KEYSPACE | Supported | IF EXISTS |
| CREATE TABLE | Supported | Partition key, clustering key with order, IF NOT EXISTS, table options (compaction, compression, comment, TTL, gc_grace) |
| ALTER TABLE | Supported | ADD column, DROP column |
| DROP TABLE | Supported | IF EXISTS |
| CREATE INDEX | Supported | USING type, WITH OPTIONS, IF NOT EXISTS — see below |
| DROP INDEX | Supported | IF EXISTS |
| DROP (without TABLE keyword) | Supported | DROP keyspace_name.table_name shorthand |
| CREATE MATERIALIZED VIEW | Not yet | — |
| CREATE TYPE (UDT) | Supported | CREATE TYPE, ALTER TYPE, DROP TYPE — full UDT DDL lifecycle |
| CREATE FUNCTION / AGGREGATE | Supported | CREATE/DROP FUNCTION (WASM), CREATE/DROP AGGREGATE — full DDL lifecycle with cluster replication |
Prepared statements remain valid after ALTER TABLE ADD or DROP column — the schema snapshot updates atomically and subsequent PREPARE calls reflect the current column set.
CREATE KEYSPACE social WITH replication = { 'class': 'SimpleStrategy', 'replication_factor': 3 } AND durable_writes = true; CREATE TABLE social.users ( user_id uuid, name text, email text, created_at timestamp, tags set<text>, PRIMARY KEY (user_id) ); -- Per-table compaction strategy (default: STCS) CREATE TABLE social.events ( event_id uuid, payload text, PRIMARY KEY (event_id) ) WITH compaction = { 'class': 'UnifiedCompactionStrategy', 'fan_factor': '4' };
| Strategy | DDL Class Name | Behavior |
|---|---|---|
| STCS (default) | none required | Size-Tiered — groups SSTables by similar size |
| UCS | UnifiedCompactionStrategy | Density-based levels with fan factor: W=2 (LCS-like), W=4 (balanced), W=32 (STCS-like) |
| Statement | Status | Notes |
|---|---|---|
| SELECT | Supported | WHERE, ORDER BY, LIMIT, DISTINCT, bind markers (?), named bind markers (:name), IN clause, ALLOW FILTERING, token(), CONTAINS / CONTAINS KEY, SOUNDS LIKE, fts_match() |
| INSERT | Supported | IF NOT EXISTS (LWT), USING TTL, USING TIMESTAMP |
| UPDATE | Supported | SET, WHERE, IF conditions (LWT), collection +/- operators, counter increment/decrement |
| DELETE | Supported | WHERE, IF EXISTS / IF conditions (LWT), column-level delete, map element delete syntax |
| BATCH | Supported | BEGIN BATCH ... APPLY BATCH (unlogged), batch CAS (conditional batch) |
| BEGIN TRANSACTION | Supported | BEGIN TRANSACTION ... COMMIT TRANSACTION / ROLLBACK TRANSACTION (Accord) |
| TRUNCATE | Supported | — |
| USE | Supported | Set default keyspace for session |
| Function | Status | Notes |
|---|---|---|
| toJson() | Supported | Serialize any column to JSON string |
| token() | Supported | Token-range queries in WHERE clauses |
| uuid() | Supported | Generate random UUID |
| now() | Supported | Current timeuuid |
| toTimestamp() | Supported | Convert timeuuid to timestamp |
Full PREPARE/EXECUTE support with positional bind values. PREPARE responses include pk_count metadata for driver routing. The prepared statement cache uses moka with W-TinyLFU eviction (64 MB default). Bind markers (?) and named bind markers (:name) are both supported.
Bind values are fully supported in QUERY frames — positional (?) and named (:name) markers work in all statement types: SELECT, INSERT, UPDATE, DELETE. Collection-typed bind values (map, set, list) are passed through in Cassandra wire format for correct round-trip storage.
-- Prepare PREPARE stmt FROM 'SELECT * FROM social.users WHERE user_id = ?'; -- Execute with positional bind EXECUTE stmt USING 550e8400-e29b-41d4-a716-446655440000;
WHERE clauses support: =, <, >, <=, >=, !=, IN, CONTAINS, CONTAINS KEY
Ferrosa supports the following consistency levels for reads and writes:
ONETWOTHREEQUORUMALLLOCAL_ONELOCAL_QUORUMEACH_QUORUMSERIAL — for lightweight transaction reads (linearizable)LOCAL_SERIAL — for lightweight transaction reads (local DC linearizable)Set per-query via the standard CQL protocol flags, or configure a default with FERROSA_DEFAULT_CL (default: QUORUM).
| Statement | Status | Notes |
|---|---|---|
| CREATE ROLE | Supported | WITH PASSWORD, SUPERUSER, LOGIN, IF NOT EXISTS |
| ALTER ROLE | Supported | Change password, superuser, login |
| DROP ROLE | Supported | IF EXISTS |
| GRANT | Supported | GRANT permission ON resource TO role |
| REVOKE | Supported | REVOKE permission ON resource FROM role |
Ferrosa supports column-level permissions, rate limiting per role, and audit logging to configurable sinks (log file, audit table).
Standard Cassandra system schema tables for driver compatibility:
system_schema.keyspacessystem_schema.tablessystem_schema.columnssystem_schema.typessystem_schema.functionssystem_schema.aggregatessystem_schema.triggerssystem_schema.viewssystem_schema.indexesNode metadata including tokens column for driver topology awareness. Drivers typically read this immediately after READY or AUTH_SUCCESS as part of topology discovery.
Virtual tables backed by live in-memory state, not persisted:
system_observability.connections — active CQL client connectionssystem_observability.active_queries — currently executing queriessystem_observability.storage_stats — storage engine metricssystem_views.secondary_indexes — per-index build status, staleness, pending work-- Check active connections SELECT * FROM system_observability.connections; -- Monitor running queries SELECT * FROM system_observability.active_queries; -- Storage engine stats SELECT * FROM system_observability.storage_stats;
| Type | USING clause | Use case |
|---|---|---|
| B-tree | 'btree' (default) | Ordered range queries and sorted scans |
| Hash | 'hash' | O(1) equality point lookups |
| Composite | 'composite' | Multi-column prefix-based lookups |
| Phonetic | 'phonetic' | Fuzzy name matching (Soundex, Metaphone, Double Metaphone, Caverphone) |
| Filtered | Any + WHERE | Partial index over a subset of rows |
| Vector (HNSW) | 'vector' | Approximate nearest neighbor — graph-based, best query performance |
| Vector (IVFFlat) | 'vector' | Approximate nearest neighbor — k-means clustering, faster builds |
-- B-tree index (default when USING is omitted) CREATE INDEX idx_email ON users (email) USING 'btree'; -- Hash index for fast equality lookups CREATE INDEX idx_user_id ON sessions (user_id) USING 'hash'; -- Composite index across multiple columns CREATE INDEX idx_name ON users (last_name, first_name) USING 'composite'; -- Phonetic index for "sounds like" matching CREATE INDEX idx_name_phonetic ON users (last_name) USING 'phonetic' WITH OPTIONS = {'algorithm': 'double_metaphone'}; -- Filtered index — only index active users CREATE INDEX idx_active_email ON users (email) USING 'btree' WHERE status = 'active'; -- IF NOT EXISTS is supported CREATE INDEX IF NOT EXISTS idx_email ON users (email); -- Drop an index DROP INDEX idx_email; DROP INDEX IF EXISTS idx_email;
Ferrosa includes two vector index algorithms for approximate nearest neighbor (ANN) search, supporting AI embeddings, semantic search, and recommendation systems:
-- HNSW index — best query performance, incremental builds CREATE INDEX idx_embed ON documents (embedding) USING 'vector' WITH OPTIONS = { 'method': 'hnsw', 'metric': 'cosine', 'dimensions': '768', 'm': '16', 'ef_construction': '200' }; -- IVFFlat index — faster builds, good for batch imports CREATE INDEX idx_embed_ivf ON documents (embedding) USING 'vector' WITH OPTIONS = { 'method': 'ivfflat', 'metric': 'l2', 'dimensions': '1536', 'lists': '100' };
| Metric | Value | Use case |
|---|---|---|
| L2 (Euclidean) | 'l2' | Standard distance — smaller = more similar |
| Cosine | 'cosine' | Angle-based similarity — ideal for text embeddings |
| Inner product | 'inner_product' | Dot product — larger = more similar |
Supports up to 4,096 dimensions (f32) or 8,192 dimensions (f16 half-precision).
Ferrosa includes built-in full-text search with inverted index sidecars and BM25 ranked retrieval. No external search engine required.
-- Create a full-text index on a text column CREATE INDEX idx_body ON articles (body) USING 'fulltext'; -- Query with fts_match() — boolean operators, phrase, prefix SELECT * FROM articles WHERE fts_match(body, 'distributed AND database'); SELECT * FROM articles WHERE fts_match(body, '"S3 backed storage"'); SELECT * FROM articles WHERE fts_match(body, 'compac*'); SELECT * FROM articles WHERE fts_match(body, 'NOT deprecated');
| Query syntax | Example | Description |
|---|---|---|
| Single term | 'rust' | Match documents containing the analyzed term |
| AND | 'rust AND database' | Both terms must be present |
| OR | 'rust OR go' | Either term must be present |
| NOT | 'NOT deprecated' | Exclude documents matching the term |
| Phrase | '"exact phrase"' | All words must appear (proximity approximation) |
| Prefix | 'compac*' | Wildcard prefix expansion (capped at 10K terms) |
Results are ranked by BM25 relevance score. The default analyzer lowercases, removes English stop words, and applies Porter stemming. Custom stop words can be configured via index options.
Pin hot tables to local NVMe storage for sub-millisecond reads, bypassing S3 entirely:
-- Create a pinned table CREATE TABLE session_cache ( session_id uuid PRIMARY KEY, user_id uuid, data blob ) WITH extensions = {'storage.pin': 'nvme'}; -- Pin with size cap (evict oldest beyond 10 GB) CREATE TABLE hot_lookups ( key text PRIMARY KEY, value blob ) WITH extensions = {'storage.pin': 'nvme', 'storage.pin_max_bytes': '10737418240'}; -- Toggle pin on a live table ALTER TABLE session_cache WITH extensions = {'storage.pin': 'none'};
Pinned tables trade durability for latency — data is lost on node replacement unless replicated. Commit log still provides crash recovery within a single node.
| Algorithm | Value | Best for |
|---|---|---|
| Soundex | 'soundex' | Standard American English names |
| Metaphone | 'metaphone' | General English pronunciation |
| Double Metaphone | 'double_metaphone' | Multi-origin names (returns primary + alternate codes) |
| Caverphone | 'caverphone' | New Zealand English names |
Ferrosa indexes are storage-attached — built as companion files alongside SSTables. Indexes are built asynchronously after memtable flush, with zero impact on the write path. This means:
system_views.secondary_indexes lets you monitor how far behind each index is-- Check index build status and staleness SELECT index_name, status, pending_sstable_count, lag_seconds FROM system_views.secondary_indexes; -- View index metadata SELECT index_name, kind, target, options FROM system_schema.indexes WHERE keyspace_name = 'myapp';
All index DDL (CREATE INDEX, DROP INDEX) replicates automatically in pair mode. Each node independently builds indexes for its local SSTables — no cross-node index coordination needed.
Full Cassandra LWT compatibility for conditional mutations:
| Pattern | Status | Notes |
|---|---|---|
| INSERT ... IF NOT EXISTS | Supported | Returns [applied] boolean column |
| UPDATE ... IF condition | Supported | Compare-and-set on any column; supports =, !=, <, >, <=, >=, IN |
| DELETE ... IF EXISTS | Supported | Conditional delete |
| DELETE ... IF condition | Supported | Conditional delete with column checks |
| Batch CAS | Supported | BEGIN BATCH with IF conditions across statements |
-- Insert only if the row doesn't exist INSERT INTO accounts (id, balance, owner) VALUES ('acct-1', 1000, 'Alice') IF NOT EXISTS; -- Conditional update (compare-and-set) UPDATE accounts SET balance = 900 WHERE id = 'acct-1' IF balance = 1000; -- Conditional delete DELETE FROM accounts WHERE id = 'acct-1' IF balance = 0;
Ferrosa extends CQL with explicit transaction blocks for multi-partition atomic operations:
-- Atomic transfer across partitions BEGIN TRANSACTION UPDATE accounts SET balance = balance - 100 WHERE id = 'acct-1' IF balance >= 100; UPDATE accounts SET balance = balance + 100 WHERE id = 'acct-2'; COMMIT TRANSACTION; -- Rollback on failure ROLLBACK TRANSACTION;
LWT operations use SERIAL or LOCAL_SERIAL consistency for the read phase, combined with any standard write consistency level. This matches Cassandra's LWT behavior exactly.
Ferrosa uses the Accord consensus protocol (not Paxos) for transaction coordination:
-- Subscribe to a table with polling interval SUBSCRIBE keyspace.table EVERY 5s; -- Subscribe with push-on-write (change data capture) SUBSCRIBE keyspace.table DELTA; -- Subscribe to a SELECT query SUBSCRIBE SELECT name, email FROM social.users WHERE active = true EVERY 10s; -- Subscribe to observability tables SUBSCRIBE system_observability.connections EVERY 5s; -- Unsubscribe from a specific stream UNSUBSCRIBE 42; -- Unsubscribe from all streams UNSUBSCRIBE;
| Mode | Syntax | Behavior |
|---|---|---|
| EVERY | EVERY 5s | Server re-executes the query at the given interval and pushes results |
| DELTA | DELTA | Push-on-write via SubscriptionObserver in the commit log; only changed rows are sent |
SUBSCRIBE responses use the standard CQL RESULT opcode (0x08) with the STREAMING flag set in the frame header. Drivers that don't understand the flag will simply see it as a normal result set. The subscription lifecycle is managed server-side — clients don't need to implement any new protocol logic.
-- Mark a table as a vertex type ALTER TABLE social.users WITH extensions = {'graph.type': 'vertex', 'graph.label': 'Person'}; -- Mark a table as an edge type ALTER TABLE social.follows WITH extensions = { 'graph.type': 'edge', 'graph.label': 'FOLLOWS', 'graph.source': 'Person', 'graph.target': 'Person' };
| Statement | Status | Notes |
|---|---|---|
| MATCH ... RETURN | Supported | Pattern matching, WHERE clause, multi-hop |
| CREATE | Parsed | Parsed; mutate via CQL INSERT (see Cypher reference) |
| SET | Parsed | Parsed; mutate via CQL UPDATE |
| DELETE / DETACH DELETE | Parsed | Parsed; mutate via CQL DELETE |
| SUBSCRIBE MATCH | Supported | Real-time subscription to graph traversals with EVERY/DELTA |
-- Subscribe to a graph traversal SUBSCRIBE MATCH (a:Person {name: 'Alice'})-[:FOLLOWS]->(b:Person) RETURN b.name, b.email EVERY 10s; -- Push-on-write for graph changes SUBSCRIBE MATCH (a:Person)-[r:FOLLOWS]->(b:Person) RETURN a.name, b.name DELTA;
The following Cassandra features are not yet implemented. They are planned for future releases:
| Feature | Status |
|---|---|
| Logged batch atomicity | Planned |
| Query tracing | Planned |
| Materialized views | Planned |