Ferrosa implements the CQL native protocol with negotiation capped at v4, including tested driver paths such as cdrs-tokio. This page documents what's supported, what's different, and what Ferrosa adds on top.
Ferrosa speaks the CQL native protocol with the standard 9-byte frame header and caps negotiation at protocol v4. v5 added a modern framing layer that drivers implement inconsistently — some send plain legacy envelopes at v5, others send CRC-checksummed modern frames — so no single server mode serves both. A v5 STARTUP is therefore rejected with a protocol-version error advertising v4 as the maximum, and every compliant driver (gocql, DataStax Java/C#, scylla-rust, cassandra-driver) transparently falls back to the one well-tested v4 transport, 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 (SimpleStrategy / NetworkTopologyStrategy), IF NOT EXISTS, DURABLE_WRITES. Transient replication ('DC1': '3/1') is rejected — Ferrosa does not implement it, and accepting it would corrupt driver schema metadata. |
| 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, CREATE/DROP AGGREGATE — full DDL lifecycle with cluster replication. UDFs are WebAssembly components (LANGUAGE wasm AS '<hex>' | AS FILE '…' | AS URL '…' WITH SHA256 = '…'); Java UDFs are not supported. Keyspace-qualified calls (ks.fn(args)) work in SELECT. |
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, LIKE, 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 |
| currentDate() | Supported | Today's date (for temporal arithmetic in WHERE) |
The duration type accepts both literal forms — compact
(2d, 1mo3d, 89h4m48s) and ISO-8601
(P1Y2M3D, PT4H5M6S). A WHERE predicate may add or
subtract a duration from a date or timestamp; months
and days are applied calendar-aware (e.g. Jan 31 + 1mo clamps to
the last day of February).
-- rows from the last two days SELECT * FROM events WHERE day >= currentDate() - 2d ALLOW FILTERING;
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, LIKE
LIKE matches text patterns with the % wildcard (any sequence of characters) — 'pre%' (prefix), '%suf' (suffix), '%mid%' (contains), or an exact literal. Matching is case-sensitive. As a non-key predicate it is evaluated as a post-scan filter and requires ALLOW FILTERING.
-- names beginning with 'M' SELECT * FROM cycling.cyclist_name WHERE firstname LIKE 'M%' ALLOW FILTERING;
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).
Cells reconcile by last-write-wins on timestamp: the higher write timestamp wins. When two writes to the same cell share the same timestamp, the lexicographically greater value wins, so the result is deterministic regardless of replica or compaction order.
| Statement | Status | Notes |
|---|---|---|
| CREATE ROLE | Supported | WITH PASSWORD, HASHED PASSWORD, SUPERUSER, LOGIN, OPTIONS, IF NOT EXISTS |
| ALTER ROLE | Supported | Change PASSWORD / HASHED PASSWORD, SUPERUSER, LOGIN |
| CREATE USER / ALTER USER | Supported | Legacy alias; WITH [HASHED] PASSWORD, SUPERUSER / NOSUPERUSER |
| DROP ROLE | Supported | IF EXISTS |
| GRANT | Supported | GRANT perm[, perm…] ON resource TO role. Resource: ON TABLE ks.t / ks.t, ON KEYSPACE, ON ALL KEYSPACES, ON ALL ROLES, ON FUNCTION. Unknown permissions are rejected (no silent partial grant). |
| REVOKE | Supported | REVOKE perm[, perm…] ON resource FROM role (same resource forms as GRANT) |
| GRANT / REVOKE role | Supported | GRANT <role> TO <member> / REVOKE <role> FROM <member> — role hierarchy (the member inherits the role's permissions). Replicated additively (one membership edge per op) and cycle-checked at apply, so concurrent grants never clobber and a revoke is never lost to a racing grant. |
| LIST ROLES | Supported | LIST ROLES [OF <role>] [NORECURSIVE]; LIST USERS alias |
| LIST PERMISSIONS | Supported | LIST [ALL | <permission>] PERMISSIONS [ON <resource>] [OF <role>] [NORECURSIVE] — gated on DESCRIBE over all roles |
| ACCESS TO/FROM (network auth) | Rejected | ACCESS TO DATACENTERS / FROM CIDRS is parsed but rejected — ferrosa has no network authorizer, so an unenforced access restriction fails loud rather than being silently accepted |
HASHED PASSWORD. CREATE ROLE … WITH HASHED PASSWORD = '<hash>' (and the legacy CREATE USER … WITH HASHED PASSWORD '<hash>') stores a pre-computed bcrypt or argon2id hash verbatim — it is not re-hashed, which lets you migrate existing credentials. The hash format is validated on the coordinator and rejected if unsupported, so a role can never hold a credential the authenticator cannot verify. Password and hash literals are redacted from any log line or error message.
-- Plaintext password — hashed server-side with bcrypt / argon2id CREATE ROLE app_writer WITH PASSWORD = 'S3cret-Pass!' AND LOGIN = true; -- Migrate an existing credential: a pre-computed hash, stored verbatim CREATE ROLE legacy_user WITH HASHED PASSWORD = '$2a$10$JSJEMFm6GeaW9XxT5JIheuEtPvat6i7uKbnTcxX3c1wshIIsGyUtG' AND LOGIN = true; -- Custom authenticator OPTIONS (accepted; not interpreted by the default authenticator) CREATE ROLE svc WITH PASSWORD = 'p' AND OPTIONS = { 'ttl' : '3600' }; -- Rotate to a new hashed credential, or toggle superuser/login ALTER ROLE app_writer WITH HASHED PASSWORD = '$argon2id$v=19$m=19456,t=2,p=1$c29tZXNhbHQ$aGFzaA'; -- Legacy USER alias: no '=', trailing SUPERUSER / NOSUPERUSER CREATE USER ops WITH HASHED PASSWORD '$2a$10$JSJEMFm6GeaW9XxT5JIheuEtPvat6i7uKbnTcxX3c1wshIIsGyUtG' NOSUPERUSER; -- Network authorization is parsed but REJECTED (no network authorizer): -- CREATE ROLE r WITH PASSWORD = 'p' AND ACCESS TO DATACENTERS {'DC1'}; -- error
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 |
| Vector (HVQ) | 'vector' WITH OPTIONS = {'method':'hvq'} | Quantized approximate nearest neighbor — reads far fewer bytes per query (evaluation) |
-- 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 body = fts_match('distributed AND database'); SELECT * FROM articles WHERE body = fts_match('"S3 backed storage"'); SELECT * FROM articles WHERE body = fts_match('compac*'); SELECT * FROM articles WHERE body = fts_match('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 latency-sensitive reads, avoiding object-store fetches on the hot path:
-- 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, off the foreground write acknowledgement 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.
Supported conditional-mutation patterns for lightweight transactions:
| 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; -- Arbitrary SELECT and observability subscriptions remain verification work -- 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 response framing is still being verified against real drivers. Treat this extension as experimental until compatibility evidence is attached.
-- 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 | Experimental | Graph streaming syntax is documented as a developer-preview target pending verification |
Graph SUBSCRIBE examples are retained as experimental syntax, not a public production guarantee.
-- Experimental graph traversal subscription 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. Applications that use them require code changes before migrating:
| Feature | Status | Impact |
|---|---|---|
| GROUP BY | Not supported | SELECT statements with GROUP BY are rejected at parse time; queries must be restructured or aggregation moved to the application layer. |
| PER PARTITION LIMIT | Not supported | The PER PARTITION LIMIT n clause is not parsed; rewrite as application-side limiting or use clustering key range predicates. |
| Query tracing | Not supported | The TRACING flag in QUERY/EXECUTE frames is accepted but silently ignored — no system_traces rows are written. Tools that depend on tracing data (DevCenter, some observability agents) will see empty trace results. |
| Schema-change EVENT push | Wired but inert | REGISTER is accepted and READY is returned, but Ferrosa never sends EVENT frames to registered clients. Drivers that rely on server-push schema invalidation (some DataStax driver versions with schema_event_refresh_delay disabled) must be configured to poll instead, or schema changes will not be reflected until the connection is recycled. |
| Materialized views | Not supported | CREATE MATERIALIZED VIEW is rejected; queries that read from materialized views must be rewritten against base tables. |
| Java / JavaScript UDFs | Not supported | Cassandra CREATE FUNCTION ... LANGUAGE java and LANGUAGE javascript are rejected. Ferrosa supports LANGUAGE wasm and LANGUAGE assemblyscript only; existing Java UDFs must be recompiled to WebAssembly. |
These features exist in both Cassandra and Ferrosa but behave differently. Applications that depend on the Cassandra behavior may produce incorrect results without code changes:
| Area | Cassandra behavior | Ferrosa behavior | Impact |
|---|---|---|---|
| Write-vs-delete tie on same timestamp | Tombstone wins (delete beats write) | Write wins (data is preserved) | Applications that delete and re-insert with the same client timestamp may observe stale data. Use distinct timestamps for deletes. |
| LOGGED batch crash recovery | Uses a batchlog table on a coordinator peer for replay on coordinator failure | Single-node: atomic group commit in the commit log (no batchlog). Cluster: full 3-phase batchlog protocol. | No application behavior difference in the non-crash path. Single-node Ferrosa provides equivalent crash recovery via the commit log without a separate batchlog table. |
| Secondary index consistency window | SAI indexes are updated synchronously on the write path | Indexes are built asynchronously after memtable flush; a brief staleness window exists between write acknowledgement and index availability | Queries immediately after a write may not see the new row via an index. Use partition-key reads for read-your-writes guarantees. Monitor staleness via system_views.secondary_indexes. |
| Counter multi-node reconciliation | Distributed sharded counters with per-replica local increments and reconciliation | Counter increment/decrement is executed via the standard write path; full distributed counter reconciliation semantics are not independently verified | Counter values may diverge under concurrent multi-node increments. Validate counter workloads before production migration. |
cassandra-driver), Go (gocql), Node.js (cassandra-driver),
Java (DataStax Java Driver), C# (DataStax C# Driver), and Rust (scylla-rust-driver).
Applications that limit themselves to these paths connect and run without driver changes.
See .github/workflows/driver-tests.yml and tests/drivers/ for the
test matrix.