Was this page helpful?
Migration Guide¶
This guide describes the differences between the ScyllaDB Node.js RS Driver
and the Apache cassandra-driver (formerly the DataStax cassandra-driver, which was transferred to Apache),
to help you migrate existing applications.
Shutdown behavior¶
In the Apache cassandra-driver, you can explicitly close the connection to the database using client.shutdown().
The ScyllaDB Node.js RS Driver behaves differently: it is not possible to explicitly close the connection.
The connection is closed when the Client object is garbage collected.
When client.shutdown() is called in the Node.js RS Driver:
It prevents execution of any new statements with the given client.
It does not close the connection to the database.
It does not deallocate connection-related structures.
It does not stop queries that are currently in flight.
See Shutdown for more details.
Query options¶
The following options’ remain unchanged:
autoPageconsistencyfetchSizehintsprepareserialConsistencytimestamp
The following option is no longer supported:
graphOptions: those options configure DSx specific features, that are not supported in this driver
Request deadlines¶
The Apache cassandra-driver applies readTimeout separately to each
coordinator attempt. This driver does not support that behavior. Custom
readTimeout values in query options, execution profiles, or
socketOptions raise an error directing you to requestTimeout. The legacy
socketOptions.readTimeout default of 12000 is accepted but does not set a
request deadline.
Use requestTimeout for one client-side deadline for each native statement or
batch execution (one page of results). It can be set on the client,
execution profile, or query; the query setting has highest precedence, followed
by the execution profile, then the client setting. When omitted, the Rust
driver’s default request timeout applies.
Coordinator attempts, retries, and speculative executions share that budget, and deadline expiry is not passed to the retry policy for another attempt. On expiry the client stops waiting and starts no further attempts, but work already sent to the database may still complete. Each page fetch receives a fresh deadline.
This deadline is not a wall-clock upper bound for the complete client call.
Post-response work performed by the native driver—such as propagating USE to
existing connections, waiting for schema agreement, or refreshing metadata—is
outside it. Connection setup, metadata requests, and preparation requests
performed before statement execution are outside it as well. Expiry
rejects with an Error whose name is ExecutionError and whose message
identifies the configured client timeout. A value of 0 disables the deadline.
All three requestTimeout settings require an integer from 0 through
2147483647; other values throw a TypeError.
Client options¶
The following options remain unchanged:
contactPointskeyspacecredentialscredentials.usernamecredentials.passwordapplicationNameapplicationVersionencoding.mapencoding.setencoding.copyBufferencoding.useUndefinedAsUnsetmaxPreparedprotocolOptions.port: applied to every contact point that does not carry its ownipAddress:portsuffix, and defaulting to 9042
The following option implementation has changed significantly, but the meaning of the option remains unchanged:
id: Now accepts bothUuidand string types. When aUuidis provided, it will be passed to the database in its standard string representation.
The following options’ default values have changed:
encoding.useBigIntAsLong: New default -true(previously -false),encoding.useBigIntAsVarint: New default -true(previously -false)
With the update of encoding options, we encourage usage of the builtin types. The ability to use the driver with types is kept as a legacy option, and may be removed in the future.
The following option is new, with no cassandra-driver equivalent:
protocolOptions.autoAwaitSchemaAgreement: determines whether the driver automatically waits for schema agreement after a DDL statement before resolving the query, retrying for up toprotocolOptions.maxSchemaAgreementWaitSeconds. See the Schema Agreement section for more information. Defaults totrue.protocolOptions.metadataRequestServersideTimeoutSecs: a ScyllaDB-only server-side timeout applied to schema and topology metadata queries, decoupling them from whatever timeout applies to ordinary requests. Has no effect against a non-ScyllaDB cluster. Defaults to30.protocolOptions.metadataRequestClientsideTimeoutSecs: a client-side timeout applied to each page fetch of a metadata query, guarding against a node that stops responding without closing the connection. Defaults toprotocolOptions.metadataRequestServersideTimeoutSecs+ 1, or30if that is not set either.
Client internal members¶
In the cassandra-driver, several Client members were accessible at runtime
from JavaScript even though they were marked as private (internal) in the
driver’s TypeScript type definitions. Because the two APIs disagreed, any change
would break at least one of them at any given moment.
The ScyllaDB Node.js RS Driver unifies both APIs by treating these members as
internal. They are no longer part of the public API and may change or be removed
without notice. The following Client members are now internal:
optionsconnectedconnectingisShuttingDown
If your application read any of these members, you should stop relying on them.
Unprepared statements with bind markers¶
When an unprepared statement contains bind markers (?), the driver silently
prepares the statement before execution. This is especially important in batches: for each
statement with a non-empty list of values, the driver sends a prepare request sequentially,
and results are not cached between client.batch() calls.
Avoid using unprepared batches unless all statements take no bind markers.
Subclassing driver base classes¶
Some base classes that were constructor functions in the cassandra-driver are now
ES2015 classes:
auth.AuthProviderauth.Authenticatorpolicies.reconnection.ReconnectionPolicypolicies.reconnection.ConstantReconnectionPolicypolicies.reconnection.ExponentialReconnectionPolicy
An ES2015 class cannot be called without new, so the constructor-function inheritance
pattern no longer works. Extending one of these with Base.call(this) throws
TypeError: Class constructor Base cannot be invoked without 'new' as soon as your
subclass is instantiated:
const AuthProvider = require("@scylladb/driver").auth.AuthProvider;
// Old version: throws TypeError when `new MyAuthProvider()` is called
function MyAuthProvider() {
AuthProvider.call(this);
}
MyAuthProvider.prototype = Object.create(AuthProvider.prototype);
MyAuthProvider.prototype.newAuthenticator = function (endpoint, name) {
return new MyAuthenticator();
};
// New version
class MyAuthProvider extends AuthProvider {
newAuthenticator(endpoint, name) {
return new MyAuthenticator();
}
}
If you cannot convert your subclass to class syntax, Reflect.construct() can
instantiate the base class from a constructor function. Keep the prototype chain
in place: the driver validates the provider with
authProvider instanceof AuthProvider, which fails without it.
function MyAuthProvider() {
return Reflect.construct(AuthProvider, [], MyAuthProvider);
}
MyAuthProvider.prototype = Object.create(AuthProvider.prototype);
MyAuthProvider.prototype.newAuthenticator = function (endpoint, name) {
return new MyAuthenticator();
};
Load balancing policies¶
Unless you have specific requirements about load balancing policies, we recommend using the default
load balancing policy. Below you can find a detailed list of changes made to the load balancing
policies compared to the cassandra-driver.
Supported load balancing policies¶
The following policies are still supported by the driver:
AllowListPolicyDCAwareRoundRobinPolicyRoundRobinPolicyTokenAwarePolicy
For the DCAwareRoundRobinPolicy and RoundRobinPolicy the following change was made to their behavior:
Instead of trying hosts in a fixed order, the next host is chosen at random. This is done to avoid situations,
where a traffic from a closed node is redirected to a single next node, potentially overloading it.
If for some reason you rely on this specific order of queueing hosts, you may still replicate old
behavior of those policies through proper configuration of (new) DefaultLoadBalancingPolicy.
The (new) DefaultLoadBalancingPolicy can be used as a child policy to TokenAwarePolicy and AllowListPolicy policies.
Not supported load balancing policies¶
The following policies that were present in the cassandra-driver are not supported:
WhiteListPolicy(legacy)
DefaultLoadBalancingPolicycustom load balancing policies
WhiteListPolicy¶
This policy was deprecated in the cassandra-driver, and for this reason was removed from this driver.
You can use AllowListPolicy instead.
legacy DefaultLoadBalancingPolicy¶
The DefaultLoadBalancingPolicy as present in the cassandra-driver is no longer supported.
It was replaced with a new implementation. There are no plans for
re-creating its functionality.
To achieve similar behavior, you can update your code in the following way:
const DefaultLoadBalancingPolicy = require("@scylladb/driver").policies.loadBalancing.DefaultLoadBalancingPolicy;
// Old version
let policy = new DefaultLoadBalancingPolicy({
localDc: "my_dc",
filter: filterAsFunction
});
// New version
let policy = new DefaultLoadBalancingPolicy({
preferDatacenter: "my_dc",
// The new policy no longer accepts the filter as a function
// You must convert it to a list of hosts that are accepted (in the `ip:port` format)
allowList: filterAsListOfAcceptedHosts
});
defaultLoadBalancingPolicy()¶
The policy returned from defaultLoadBalancingPolicy() is changed from
legacy to new DefaultLoadBalancingPolicy. When localDc option is provided,
the load-balancing policy uses the provided datacenter for coordinator
selection and excludes remote datacenters from query plans.
Some earlier releases silently ignored the localDc argument and allowed
coordinators from all datacenters. The argument is now enforced. Datacenter
names are case-sensitive; an unknown or case-mismatched localDc leaves no
eligible nodes and requests fail with an empty load-balancing plan.
When localDc is omitted, null, or "", the policy has no explicit datacenter
preference. This preserves the previous meaning of these values.
ClientOptions.localDataCenter can supply the preference in each case. For
precedence, failover, validation, and coordinator selection rules, see
Load Balancing.
For token-aware requests to SimpleStrategy keyspaces, a preferred datacenter
with datacenter failover disabled emits a warning for every query plan. With
the default logLevel, each warning is delivered as a client 'log' event.
Retry policies¶
A new version of the default retry policy was introduced. We recommend using it as a replacement for the legacy default policy.
Supported retry policies¶
FallthroughRetryPolicy
Not supported retry policy¶
(legacy)
RetryPolicyIdempotenceAwareRetryPolicycustom retry policies
legacy RetryPolicy¶
The RetryPolicy as present in the cassandra-driver is no longer supported.
It was replaced with a new implementation. There are no plans for
re-implementing its functionality. You do not have to update your code to migrate to the new policy.
IdempotenceAwareRetryPolicy¶
This policy was deprecated in the cassandra-driver, and for this reason was removed from this driver.
Metadata¶
Hosts¶
See the Topology Metadata page for the full description of the new API.
Hosts order¶
The cassandra-driver driver had some undocumented assumptions about the order of hosts,
when using client.hosts.keys() - see issue #282
(they were checked in the driver tests). Those assumptions no longer hold true,
the hosts returned from client.hosts.keys() may be in a random order, that may vary from run to run.
UP/DOWN distinction¶
Previously, the Host was set as UP or DOWN as a result of received events. Now, whether a Host is considered as UP depends on whether it has an established open connection with the driver.
Host.address is now a net.SocketAddress¶
In the cassandra-driver, host.address was a plain string in the ip:port format.
In the ScyllaDB Node.js RS Driver, it is a real
net.SocketAddress object.
HostMap is now keyed by host id, not address¶
In the cassandra-driver, HostMap was keyed by a host’s address. In the ScyllaDB Node.js RS
Driver, it is keyed by the host’s Uuid instead, since a host’s id never changes, while its
address can (a node can be assigned a new address, or a given address can later be reused by a
different node).
This affects:
client.hosts.keys(): now returns an array ofUuid, not addresses.client.hosts.forEach((host, key) => ...):keyis now aUuid, not an address.
client.hosts.get(key) is unaffected for existing callers: it still accepts an address
(net.SocketAddress or the ip:port string), and now also accepts a Uuid or the host id as a
raw Buffer.
Removed Host/HostMap members¶
Host:
dseVersion(getter/setter) andgetDseVersion(): specific to DataStax Enterprise (DSE). This driver targets open-source Cassandra and ScyllaDB clusters, which have no DSE version to report.workloads(getter/setter): also DSE-specific (e.g. the Search/Analytics/Graph workloads DSE assigns to nodes); not applicable outside DSE.canBeConsideredAsUp(): determines if the host can be considered as UP – useHost.isUp()instead.
HostMap:
remove()/removeMultiple()/set()/clear():HostMapis a read-only snapshot of the cluster’s current topology, built and owned by the driver - hosts cannot be removed or added to it directly and it cannot be cleared.
If your code called any of these, remove the call, as there is no plan to support them in the future.
Tokens¶
newToken() now requires a keyspace and table¶
newToken(components, keyspaceName, tableName) computes the token of a partition key, given the
already-serialized values of its columns. The partitioner is read from the table’s own metadata.
components is a single Buffer for a single-column partition key, or an array of Buffers, in
the order the table declares its partition key columns, for a composite one:
// CREATE TABLE my_keyspace.my_table (
// user_id text,
// name text,
// created_at timestamp,
// PRIMARY KEY ((user_id, name), created_at)
// )
//
// The partition key is (user_id, name), so the components must be provided in that order:
// the encoded user_id, then the encoded name.
const uderId = Buffer.from("123", "utf8");
const name = Buffer.from("alice", "utf8");
const token = client.metadata.newToken(
[userId, name],
"my_keyspace",
"my_table",
);
The string form (parsing a token’s own decimal representation) is gone too, so construct a
Token directly from a bigint instead.
// cassandra-driver
const token = client.metadata.newToken(Buffer.from("key"));
const token = client.metadata.newToken("12345"); // parse a token's own string form
// ScyllaDB Node.js RS Driver
const token = client.metadata.newToken(Buffer.from("key"), "ks", "tbl");
const token = new (require("@scylladb/driver").token.Token)(12345n); // wrap a raw value directly
Replicas¶
getReplicas(keyspaceName, tableName, token) returns an array of Replica objects (node + shard
pairs) that replicate a partition, given a keyspace, table, and token. See the
Replicas page for the full description of the new API.
const replicas = client.metadata.getReplicas("my_keyspace", "my_table", token);
for (const replica of replicas) {
console.log(replica.host.address, "shard", replica.shard);
}
Key differences from similar methods in cassandra-driver:
The token parameter can be a
Tokenor aTokenRange.Each replica includes both the host and its shard number.
The replica’s
hostis the same object instance asclient.hosts.get(hostId), so you can compare by identity.
Schema¶
The schema metadata API was rewritten from scratch, and almost nothing carries over unchanged. See the Schema Metadata page for the full description of the new API; this section lists what changes for you as a caller.
Everything is synchronous now¶
In the cassandra-driver, schema lookups queried the system_schema tables on demand, so they
were asynchronous and took a callback or returned a promise. This driver keeps a schema snapshot
in memory, refreshed in the background, so every lookup returns the value directly:
// cassandra-driver
const table = await client.metadata.getTable("ks", "tbl");
client.metadata.getTable("ks", "tbl", (err, table) => { /* ... */ });
// ScyllaDB Node.js RS Driver
const table = client.metadata.getTable("ks", "tbl");
metadata.keyspaces is replaced by getKeyspace() / getKeyspaces()¶
The keyspaces property, a plain object keyed by keyspace name, no longer exists:
// cassandra-driver
const ks = client.metadata.keyspaces["ks"];
const all = Object.values(client.metadata.keyspaces);
// ScyllaDB Node.js RS Driver
const ks = client.metadata.getKeyspace("ks"); // null if absent
const all = client.metadata.getKeyspaces(); // Readonly<Record<string, KeyspaceMetadata>>
getKeyspaces() returns a read-only object keyed by keyspace name, like the old keyspaces
property, so Object.entries() still iterates it. Unlike the old property it is not refreshed by
hand: within one cluster state the same record is returned on every call, and a schema change
replaces it.
KeyspaceMetadata has strategy, durableWrites, tables, views and udts.
Replication strategy is an object, not a class name plus options¶
The cassandra-driver reported the strategy as the server’s Java class name and a bag of options
whose values were strings. It is now a discriminated union, with the replication factors as
numbers:
// cassandra-driver
ks.strategy; // "org.apache.cassandra.locator.NetworkTopologyStrategy"
ks.strategyOptions; // { dc1: "3" }
// ScyllaDB Node.js RS Driver
ks.strategy.kind; // StrategyKind.NetworkTopologyStrategy
ks.strategy.datacenterRepfactors; // { dc1: 3 }
strategyOptions is gone; each variant carries its own fields – replicationFactor for
SimpleStrategy, datacenterRepfactors for NetworkTopologyStrategy, nothing for
LocalStrategy, and name/data for a strategy the driver does not recognise. Switch on
kind (a StrategyKind value) to pick the variant.
Iterating columns changes shape¶
// cassandra-driver
table.columns.forEach((column) => console.log(column.name, column.type.code));
// ScyllaDB Node.js RS Driver
for (const [name, column] of Object.entries(table.columns)) {
console.log(name, column.type.code, column.kind);
}
Looking up the partition key columns changes too, since the keys are now names:
// cassandra-driver
const pkTypes = table.partitionKeys.map((column) => column.type);
// ScyllaDB Node.js RS Driver
const pkTypes = table.partitionKey.map((name) => table.columns[name].type);
Table storage options – caching, comment, compactionClass, compactionOptions,
compression, defaultTtl, gcGraceSeconds, id, bloomFilterFalsePositive,
speculativeRetry, extensions, indexes and the rest – are not exposed. Query
system_schema directly if you need any of them.
Materialized views and user-defined types¶
MaterializedView still extends the table metadata and still has tableName, but
whereClause and includeAllColumns are not exposed.
Udt keeps name and fields (an array of { name, type }) and gains keyspace.
Query tracing¶
See the Query Tracing page for the full description of the tracing API.
Below are the key differences from the cassandra-driver.
The returned QueryTrace keeps the shape it had in the cassandra-driver – requestType,
coordinator, parameters, startedAt, duration, clientAddress and events, with each event
carrying id, activity, source, elapsed and thread. A trace that cannot be read still
reports an error rather than resolving to null.
Type changes¶
startedAt is a Date (instead of the previous number), both coordinator and clientAddress are
InetAddress instances, and each event’s id is a TimeUuid (previously Uuid).
Trace objects are read-only¶
Unlike the plain objects the cassandra-driver returned, every field of QueryTrace and
TracingEvent is readonly, and events is a readonly array. Nothing about a trace is meant to be
edited after it is read; in TypeScript this is enforced at compile time.
The consistency argument has no effect¶
getTrace() still accepts an optional consistency level between the trace id and the callback, so
existing call sites keep compiling and running:
const trace = await client.metadata.getTrace(traceId, types.consistencies.all);
It is accepted for compatibility only and is not plumbed through to the request, so the trace is read at the driver’s own consistency level regardless of what you pass.
Schema agreement¶
checkSchemaAgreement() computes a different answer¶
With one node stopped, right after a schema change, the cassandra-driver reported false
(the stopped node’s stale row disagrees). Now the driver reports true (the stopped node is
simply not asked, and the reachable nodes agree).
waitForSchemaAgreement() is new¶
client.metadata.waitForSchemaAgreement(), unlike checkSchemaAgreement(), actively retries and
rejects, rather than resolving false, when agreement is not reached.
ResultSet.info.isSchemaInAgreement is removed¶
The cassandra-driver reports whether the automatic wait after a DDL statement converged, as
isSchemaInAgreement on result.info. This driver has no equivalent field: when
protocolOptions.autoAwaitSchemaAgreement (described in client options) is enabled, the driver
rejects the query outright if agreement is not reached in time, so a successful ResultSet with
isSchemaInAgreement: false can never exist to read; and when it is disabled, nothing in the
result identifies whether the cluster has converged. Use waitForSchemaAgreement() after the query instead.
Logging¶
See the Logging page for the full documentation of the new logging system.
Below are the key differences from the cassandra-driver.
ScyllaDB Node.js RS Driver introduces a concept of configurable logging levels.
While logging levels were already present in cassandra-driver, you could only filter according to those
levels after receiving the log information. To allow for better performance, ScyllaDB Node.js RS Driver allows you to
configure received log levels before the logs are emitted, at the client settings level.
Default log level¶
When no logLevel is specified, events at warning level and above are captured.
This is different from cassandra-driver, where all events were always emitted.
To receive all events (including trace and debug), set logLevel explicitly:
const { Client, types } = require('@scylladb/driver');
const client = new Client({
contactPoints: ['127.0.0.1'],
logLevel: types.logLevels.trace
});
verbose level removed¶
The old verbose level has been removed and replaced by two separate
levels — trace and debug — giving finer control over diagnostic output.
See Log levels for the full list.
target replaces className¶
The cassandra-driver passed a JS class name (e.g. "Client",
"Connection") as the second argument of the 'log' event. This driver
passes a target string instead:
For Rust driver events it is a Rust module path (e.g.
scylla::network::connection).For JS-side events it is
"Client".
This change affects the actual value passed as the second argument of the 'log' event.
While the function signature is unchanged, existing code that filters or routes events based on
the target value may need to be updated.
See Event arguments for details.
Event interface preserved¶
The 'log' event signature is unchanged:
client.on('log', (level, target, message, furtherInfo) => { ... });
Cross-client event visibility¶
All clients share the same underlying Rust tracing subscriber. Each client
receives the log events of its own Node.js environment (the main thread or
one worker thread). This includes events triggered by other Client
instances in the same environment. A client does not receive the events of
clients in other worker threads.
Order of log events and results¶
Rust log events reach the 'log' listener asynchronously, on a later tick
of the event loop. But a log event that the driver emits before an operation
completes always reaches the listener before the Promise of that operation
settles (or before its callback is called). So you can read the collected
events right after await:
const warnings = [];
client.on('log', (level, target, message) => {
if (level === 'warning') warnings.push(message);
});
try {
await client.connect();
} catch (err) {
// The warnings logged during connect() are already here.
console.log(warnings);
}
After client.shutdown(), the client receives no more log events.
This includes the events that the driver emitted before the shutdown,
but that did not reach the listener yet.