"use strict";
const utils = require("../utils");
const VersionNumber = require("./version-number");
const v200 = VersionNumber.parse("2.0.0");
const v210 = VersionNumber.parse("2.1.0");
const v220 = VersionNumber.parse("2.2.0");
const v300 = VersionNumber.parse("3.0.0");
const v510 = VersionNumber.parse("5.1.0");
const v600 = VersionNumber.parse("6.0.0");
/**
* Contains information for the different protocol versions supported by the driver.
* @type {Object}
* @property {Number} v1 Cassandra protocol v1, supported in Apache Cassandra 1.2-->2.2.
* @property {Number} v2 Cassandra protocol v2, supported in Apache Cassandra 2.0-->2.2.
* @property {Number} v3 Cassandra protocol v3, supported in Apache Cassandra 2.1-->3.x.
* @property {Number} v4 Cassandra protocol v4, supported in Apache Cassandra 2.2-->3.x.
* @property {Number} v5 Cassandra protocol v5, in beta from Apache Cassandra 3.x+. Currently not supported by the
* driver.
* @property {Number} dseV1 DataStax Enterprise protocol v1, DSE 5.1+
* @property {Number} dseV2 DataStax Enterprise protocol v2, DSE 6.0+
* @property {Number} maxSupported Returns the higher protocol version that is supported by this driver.
* @property {Number} minSupported Returns the lower protocol version that is supported by this driver.
* @property {Function} isSupported A function that returns a boolean determining whether a given protocol version
* is supported.
* @alias module:types~protocolVersion
*/
const protocolVersion = {
// Strict equality operators to compare versions are allowed, other comparison operators are discouraged. Instead,
// use a function that checks if a functionality is present on a certain version, for maintainability purposes.
v1: 0x01,
v2: 0x02,
v3: 0x03,
v4: 0x04,
v5: 0x05,
v6: 0x06,
dseV1: 0x41,
dseV2: 0x42,
maxSupported: 0x42,
minSupported: 0x01,
/**
* Determines whether the protocol version is a DSE-specific protocol version.
* @param {Number} version
* @returns {Boolean}
* @ignore
*/
isDse: function (version) {
return version >= this.dseV1 && version <= this.dseV2;
},
/**
* Returns true if the protocol version represents a version of Cassandra
* supported by this driver, false otherwise
* @param {Number} version
* @returns {Boolean}
* @ignore
*/
isSupportedCassandra: function (version) {
return version <= 0x04 && version >= 0x01;
},
/**
* Determines whether the protocol version is supported by this driver.
* @param {Number} version
* @returns {Boolean}
* @ignore
*/
isSupported: function (version) {
return this.isDse(version) || this.isSupportedCassandra(version);
},
/**
* Determines whether the protocol includes flags for PREPARE messages.
* @param {Number} version
* @returns {Boolean}
* @ignore
*/
supportsPrepareFlags: function (version) {
return version === this.dseV2;
},
/**
* Determines whether the protocol supports sending the keyspace as part of PREPARE, QUERY, EXECUTE, and BATCH.
* @param {Number} version
* @returns {Boolean}
* @ignore
*/
supportsKeyspaceInRequest: function (version) {
return version === this.dseV2;
},
/**
* Determines whether the protocol supports result_metadata_id on `prepared` response and
* and `execute` request.
* @param {Number} version
* @returns {Boolean}
* @ignore
*/
supportsResultMetadataId: function (version) {
return version === this.dseV2;
},
/**
* Determines whether the protocol supports partition key indexes in the `prepared` RESULT responses.
* @param {Number} version
* @returns {Boolean}
* @ignore
*/
supportsPreparedPartitionKey: function (version) {
return version >= this.v4;
},
/**
* Determines whether the protocol supports up to 4 strings (ie: change_type, target, keyspace and table) in the
* schema change responses.
* @param version
* @return {boolean}
* @ignore
*/
supportsSchemaChangeFullMetadata: function (version) {
return version >= this.v3;
},
/**
* Determines whether the protocol supports continuous paging.
* @param version
* @return {boolean}
* @ignore
*/
supportsContinuousPaging: function (version) {
return this.isDse(version);
},
/**
* Determines whether the protocol supports paging state and serial consistency parameters in QUERY and EXECUTE
* requests.
* @param version
* @return {boolean}
* @ignore
*/
supportsPaging: function (version) {
return version >= this.v2;
},
/**
* Determines whether the protocol supports timestamps parameters in BATCH, QUERY and EXECUTE requests.
* @param {Number} version
* @return {boolean}
* @ignore
*/
supportsTimestamp: function (version) {
return version >= this.v3;
},
/**
* Determines whether the protocol supports named parameters in QUERY and EXECUTE requests.
* @param {Number} version
* @return {boolean}
* @ignore
*/
supportsNamedParameters: function (version) {
return version >= this.v3;
},
/**
* Determines whether the protocol supports unset parameters.
* @param {Number} version
* @return {boolean}
* @ignore
*/
supportsUnset: function (version) {
return version >= this.v4;
},
/**
* Determines whether the protocol provides a reason map for read and write failure errors.
* @param version
* @return {boolean}
* @ignore
*/
supportsFailureReasonMap: function (version) {
return version >= this.v5;
},
/**
* Determines whether the protocol supports timestamp and serial consistency parameters in BATCH requests.
* @param {Number} version
* @return {boolean}
* @ignore
*/
uses2BytesStreamIds: function (version) {
return version >= this.v3;
},
/**
* Determines whether the collection length is encoded using 32 bits.
* @param {Number} version
* @return {boolean}
* @ignore
*/
uses4BytesCollectionLength: function (version) {
return version >= this.v3;
},
/**
* Determines whether the QUERY, EXECUTE and BATCH flags are encoded using 32 bits.
* @param {Number} version
* @return {boolean}
* @ignore
*/
uses4BytesQueryFlags: function (version) {
return this.isDse(version);
},
/**
* Startup responses using protocol v4+ can be a SERVER_ERROR wrapping a ProtocolException, this method returns true
* when is possible to receive such error.
* @param {Number} version
* @return {boolean}
* @ignore
*/
canStartupResponseErrorBeWrapped: function (version) {
return version >= this.v4;
},
/**
* Gets the first version number that is supported, lower than the one provided.
* Returns zero when there isn't a lower supported version.
* @param {Number} version
* @return {Number}
* @ignore
*/
getLowerSupported: function (version) {
if (version >= this.v5) {
return this.v4;
}
if (version <= this.v1) {
return 0;
}
return version - 1;
},
/**
* Computes the highest supported protocol version collectively by the given hosts.
*
* Considers the cassandra_version of the input hosts to determine what protocol versions
* are supported and uses the highest common protocol version among them.
*
* If hosts >= C* 3.0 are detected, any hosts older than C* 2.1 will not be considered
* as those cannot be connected to. In general this will not be a problem as C* does
* not support clusters with nodes that have versions that are more than one major
* version away from each other.
* @param {Connection} connection Connection hosts were discovered from.
* @param {Array.<Host>} hosts The hosts to determine highest protocol version from.
* @return {Number} Highest supported protocol version among hosts.
*/
getHighestCommon: function (connection, hosts) {
const log = connection.log
? connection.log.bind(connection)
: utils.noop;
let maxVersion = connection.protocolVersion;
// whether or not protocol v3 is required (nodes detected that don't support < 3).
let v3Requirement = false;
// track the common protocol version >= v3 in case we encounter older versions.
let maxVersionWith3OrMore = maxVersion;
hosts.forEach((h) => {
let dseVersion = null;
if (h.dseVersion) {
// As of DSE 5.1, DSE has it's own specific protocol versions. If we detect 5.1+
// consider those protocol versions.
dseVersion = VersionNumber.parse(h.dseVersion);
log(
"verbose",
`Encountered host ${h.address} with dse version ${dseVersion}`,
);
if (dseVersion.compare(v510) >= 0) {
v3Requirement = true;
if (dseVersion.compare(v600) >= 0) {
maxVersion = Math.min(this.dseV2, maxVersion);
} else {
maxVersion = Math.min(this.dseV1, maxVersion);
}
maxVersionWith3OrMore = maxVersion;
return;
}
// If DSE < 5.1, we fall back on the cassandra protocol logic.
}
if (!h.cassandraVersion || h.cassandraVersion.length === 0) {
log(
"warning",
"Encountered host " +
h.address +
" with no cassandra version," +
" skipping as part of protocol version evaluation",
);
return;
}
try {
const cassandraVersion = VersionNumber.parse(
h.cassandraVersion,
);
if (!dseVersion) {
log(
"verbose",
"Encountered host " +
h.address +
" with cassandra version " +
cassandraVersion,
);
}
if (cassandraVersion.compare(v300) >= 0) {
// Anything 3.0.0+ has a max protocol version of V4 and requires at least V3.
v3Requirement = true;
maxVersion = Math.min(this.v4, maxVersion);
maxVersionWith3OrMore = maxVersion;
} else if (cassandraVersion.compare(v220) >= 0) {
// Cassandra 2.2.x has a max protocol version of V4.
maxVersion = Math.min(this.v4, maxVersion);
maxVersionWith3OrMore = maxVersion;
} else if (cassandraVersion.compare(v210) >= 0) {
// Cassandra 2.1.x has a max protocol version of V3.
maxVersion = Math.min(this.v3, maxVersion);
maxVersionWith3OrMore = maxVersion;
} else if (cassandraVersion.compare(v200) >= 0) {
// Cassandra 2.0.x has a max protocol version of V2.
maxVersion = Math.min(this.v2, maxVersion);
} else {
// Anything else is < 2.x and requires protocol version V1.
maxVersion = this.v1;
}
} catch (e) {
log(
"warning",
"Encountered host " +
h.address +
" with unparseable cassandra version " +
h.cassandraVersion +
" skipping as part of protocol version evaluation",
);
}
});
if (v3Requirement && maxVersion < this.v3) {
const addendum =
". This should not be possible as nodes within a cluster can't be separated by more than one major version";
if (maxVersionWith3OrMore < this.v3) {
log(
"error",
"Detected hosts that require at least protocol version 0x3, but currently connected to " +
connection.address +
":" +
connection.port +
" using protocol version 0x" +
maxVersionWith3OrMore +
". Will not be able to connect to these hosts" +
addendum,
);
} else {
log(
"error",
"Detected hosts with maximum protocol version of 0x" +
maxVersion.toString(16) +
" but there are some hosts that require at least version 0x3. Will not be able to connect to these older hosts" +
addendum,
);
}
maxVersion = maxVersionWith3OrMore;
}
log(
"verbose",
"Resolved protocol version 0x" +
maxVersion.toString(16) +
" as the highest common protocol version among hosts",
);
return maxVersion;
},
/**
* Determines if the protocol is a BETA version of the protocol.
* @param {Number} version
* @return {Number}
*/
isBeta: function (version) {
return version === this.v5;
},
};
module.exports = protocolVersion;