mongo/jstests/libs/raw_operation_utils.js

143 lines
6.2 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Utility library to simplify using raw operations.
*
* Raw operations are supported starting with binary version 8.2.
*
* To use raw operations in a test without excluding it
* from multiversion suites, use the following pattern:
* ```javascript
*   import {
* getTimeseriesCollForRawOps,
* getRawOperationSpec,
* } from "jstests/libs/raw_operation_utils.js";
*
*   const bucketsColl = getTimeseriesCollForRawOps(db, coll);
*   let rawBucketsDocs = bucketsColl.find().rawData().toArray();
*   let rawBucketsDocs = bucketsColl.aggregate([{$match: {}}], getRawOperationSpec(db)).toArray();
* ```
* This approach ensures the code works correctly even in versions that
* do not support raw operations. Specifically:
* - In versions where raw operations are supported:
*    - `getTimeseriesCollForRawOps` acts as a no-op and simply returns the original `coll`.
*    - `rawData()` and `kRawOperationSpec` effectively add the `rawData` parameter
* to the operation.
*  - On the other hand, in versions where raw operations are not supported:
*    - `getTimeseriesCollForRawOps` returns the underlying `system.buckets` collection.
*    - `rawData()` and `kRawOperationSpec` act as no-ops and do not attach any
*      parameter to the operation.
*/
import {getTimeseriesBucketsColl} from "jstests/core/timeseries/libs/viewless_timeseries_util.js";
import {FeatureFlagUtil} from "jstests/libs/feature_flag_util.js";
// TODO SERVER-103187 remove this constant once 9.0 becomes last LTS
export const kRawOperationFieldName = "rawData";
// TODO (SERVER-103187): Remove these functions once v9.0 becomes last-LTS.
export function isRawOperationSupported(db) {
if (TestData.doNotUseRawDataOperations) {
assert(!FeatureFlagUtil.isEnabled(db, "CreateViewlessTimeseriesCollections"));
return false;
}
// getParameter can't be used inside transactions, so issue the command directly on the
// connection, rather than using the session potentially linked to the DB object.
const conn = db.getMongo();
// In multiversion suites, the flag is stable only if *all* binaries in the cluster have the
// binary-compatible flag enabled. We detect this through an FCV-gated feature flag, which works
// because the FCV can only be upgraded once all binaries in the cluster are upgraded.
const isMultiversion =
Boolean(jsTest.options().useRandomBinVersionsWithinReplicaSet) || Boolean(TestData.multiversionBinVersion);
if (isMultiversion) {
const flagDoc = FeatureFlagUtil.getFeatureFlagDoc(conn, "AllBinariesSupportRawDataOperations");
if (
!flagDoc ||
FeatureFlagUtil.getFeatureFlagDocStatus(conn, flagDoc) !== FeatureFlagUtil.FlagStatus.kEnabled
) {
return false;
}
assert.hasFields(flagDoc, ["fcv_gated"]);
assert(flagDoc.fcv_gated);
return true;
}
// For non-multiversion suites, check the status of the binary-compatible flag directly
// Note that we may return true here even if 'AllBinariesSupportRawDataOperations' is disabled
// (e.g. in FCV upgrade/downgrade suites, where all binaries are always on the latest version)
const flagDoc = FeatureFlagUtil.getFeatureFlagDoc(conn, "RawDataCrudOperations");
if (!flagDoc || FeatureFlagUtil.getFeatureFlagDocStatus(conn, flagDoc) !== FeatureFlagUtil.FlagStatus.kEnabled) {
return false;
}
assert.hasFields(flagDoc, ["fcv_gated"]);
assert(!flagDoc.fcv_gated);
return true;
}
export function getRawOperationSpec(db) {
return isRawOperationSupported(db) ? {[kRawOperationFieldName]: true} : {};
}
/**
* Given a timeseries collection, returns its corresponding collection that exposes raw data.
*
* When rawData is supported, returns the original collection as the user can simply invoke
* rawData operations on the main collection.
*
* When rawData is not supported this function returns the underlying system.buckets collection.
*/
export function getTimeseriesCollForRawOps(db, coll) {
return isRawOperationSupported(db) ? coll : getTimeseriesBucketsColl(coll);
}
/**
* Helper function to create an index directly on the raw timeseries buckets.
*/
export function createRawTimeseriesIndex(coll, spec, options, commitQuorum, cmdArgs) {
return getTimeseriesCollForRawOps(coll.getDB(), coll).createIndex(spec, options, commitQuorum, {
...getRawOperationSpec(coll.getDB()),
...cmdArgs,
});
}
/**
* Override the rawData function on the DBQuery object so that it becomes a no-op on versions where
* rawData parameter is not supported (< 9.0)
*
* TODO SERVER-103187 remove this override once 9.0 becomes last LTS
*/
(function () {
if (typeof DBQuery !== "undefined" && typeof DBQuery.prototype.rawData === "function") {
const originalRawData = DBQuery.prototype.rawData;
// Override the rawData function
DBQuery.prototype.rawData = function (value) {
if (isRawOperationSupported(this._db)) {
// Call the original rawData function with the (potentially) modified arguments
return originalRawData.apply(this, value);
} else {
assert(
value === undefined || value === true,
"Detected illegal usage of rawData function. rawData(false) can't be used on a cluster with binary versions that do not support 'rawData' parameter.",
);
if (!this._collection.getName().startsWith("system.buckets.")) {
// Fast path
const collMetadata = this._collection.getMetadata();
assert(
!collMetadata || collMetadata.type === "collection",
`Detected illegal usage of rawData function. The query target '${this._collection.getName()}' that is not a regular collection and the cluster contains binaries that do not support 'rawData' parameter.`,
);
}
return this;
}
};
} else {
throw new Error(
"Failed to override DBQuery.rawData method becuase DBQuery class or its rawData method were not found",
);
}
})();