Compare commits

..

No commits in common. "694c41ded4d9dc3b81755d525e0f3ba0c6042746" and "8ad0ea73a75c861c594e3e59b9224ee5fafe1486" have entirely different histories.

2 changed files with 22 additions and 157 deletions

View File

@ -3,7 +3,7 @@ import { VersioningConstants } from './constants';
const VID_SEP = VersioningConstants.VersionId.Separator; const VID_SEP = VersioningConstants.VersionId.Separator;
/** /**
* Class for manipulating an object version. * Class for manipulating an object version.
* The format of a version: { isNull, isNull2, isDeleteMarker, versionId, otherInfo } * The format of a version: { isNull, isDeleteMarker, versionId, otherInfo }
* *
* @note Some of these functions are optimized based on string search * @note Some of these functions are optimized based on string search
* prior to a full JSON parse/stringify. (Vinh: 18K op/s are achieved * prior to a full JSON parse/stringify. (Vinh: 18K op/s are achieved
@ -13,7 +13,6 @@ const VID_SEP = VersioningConstants.VersionId.Separator;
export class Version { export class Version {
version: { version: {
isNull?: boolean; isNull?: boolean;
isNull2?: boolean;
isDeleteMarker?: boolean; isDeleteMarker?: boolean;
versionId?: string; versionId?: string;
isPHD?: boolean; isPHD?: boolean;
@ -23,16 +22,12 @@ export class Version {
* Create a new version instantiation from its data object. * Create a new version instantiation from its data object.
* @param version - the data object to instantiate * @param version - the data object to instantiate
* @param version.isNull - is a null version * @param version.isNull - is a null version
* @param version.isNull2 - Whether new version is null or not AND has
* been put with a Cloudserver handling null keys (i.e. supporting
* S3C-7352)
* @param version.isDeleteMarker - is a delete marker * @param version.isDeleteMarker - is a delete marker
* @param version.versionId - the version id * @param version.versionId - the version id
* @constructor * @constructor
*/ */
constructor(version?: { constructor(version?: {
isNull?: boolean; isNull?: boolean;
isNull2?: boolean;
isDeleteMarker?: boolean; isDeleteMarker?: boolean;
versionId?: string; versionId?: string;
isPHD?: boolean; isPHD?: boolean;
@ -88,31 +83,6 @@ export class Version {
return `{ "isPHD": true, "versionId": "${versionId}" }`; return `{ "isPHD": true, "versionId": "${versionId}" }`;
} }
/**
* Appends a key-value pair to a JSON object represented as a string. It intelligently adds
* a comma if the object is not empty (i.e., not just '{}'). This function assumes the input
* string is properly formatted as a JSON object.
*
* @param {string} stringifiedObject The JSON object as a string to which the key-value pair will be appended.
* @param {string} key The key to append to the JSON object.
* @param {string} value The value associated with the key to append to the JSON object.
* @returns {string} The updated JSON object as a string with the new key-value pair appended.
* @example
* _jsonAppend('{"existingKey":"existingValue"}', 'newKey', 'newValue');
* // returns '{"existingKey":"existingValue","newKey":"newValue"}'
*/
static _jsonAppend(stringifiedObject: string, key: string, value: string): string {
// stringifiedObject value has the format of '{...}'
let index = stringifiedObject.length - 2;
while (stringifiedObject.charAt(index--) === ' ');
const comma = stringifiedObject.charAt(index + 1) !== '{';
return (
`${stringifiedObject.slice(0, stringifiedObject.length - 1)}` + // eslint-disable-line
(comma ? ',' : '') +
`"${key}":"${value}"}`
);
}
/** /**
* Put versionId into an object in the (cheap) way of string manipulation, * Put versionId into an object in the (cheap) way of string manipulation,
* instead of the more expensive alternative parsing and stringification. * instead of the more expensive alternative parsing and stringification.
@ -123,35 +93,14 @@ export class Version {
*/ */
static appendVersionId(value: string, versionId: string): string { static appendVersionId(value: string, versionId: string): string {
// assuming value has the format of '{...}' // assuming value has the format of '{...}'
return Version._jsonAppend(value, 'versionId', versionId); let index = value.length - 2;
} while (value.charAt(index--) === ' ');
const comma = value.charAt(index + 1) !== '{';
/** return (
* Updates or appends a `nullVersionId` property to a JSON-formatted string. `${value.slice(0, value.length - 1)}` + // eslint-disable-line
* This function first checks if the `nullVersionId` property already exists within the input string. (comma ? ',' : '') +
* If it exists, the function updates the `nullVersionId` with the new value provided. `"versionId":"${versionId}"}`
* If it does not exist, the function appends a `nullVersionId` property with the provided value. );
*
* NOTE: This function assumes the input string is in a valid JSON format and handles the `nullVersionId`
* property as a string value.
*
* @static
* @param {string} value - The JSON-formatted string that may already contain a `nullVersionId` property.
* @param {string} nullVersionId - The new value for the `nullVersionId` property to be updated or appended.
* @returns {string} The updated JSON-formatted string with the new `nullVersionId` value.
*/
static updateOrAppendNullVersionId(value: string, nullVersionId: string): string {
// Check if "nullVersionId" already exists in the string
const nullVersionIdPattern = /"nullVersionId":"[^"]*"/;
const nullVersionIdExists = nullVersionIdPattern.test(value);
if (nullVersionIdExists) {
// Replace the existing nullVersionId with the new one
return value.replace(nullVersionIdPattern, `"nullVersionId":"${nullVersionId}"`);
} else {
// Append nullVersionId in the cheap way as before
return Version._jsonAppend(value, 'nullVersionId', nullVersionId);
}
} }
/** /**
@ -241,16 +190,6 @@ export class Version {
return this; return this;
} }
/**
* Mark that the null version has been put with a Cloudserver handling null keys (i.e. supporting S3C-7352)
*
* @return - the updated version
*/
setNull2Version() {
this.version.isNull2 = true;
return this;
}
/** /**
* Serialize the version. * Serialize the version.
* *

View File

@ -1,6 +1,6 @@
import errors, { ArsenalError } from '../errors'; import errors, { ArsenalError } from '../errors';
import { Version } from './Version'; import { Version } from './Version';
import { generateVersionId as genVID, getInfVid } from './VersionID'; import { generateVersionId as genVID } from './VersionID';
import WriteCache from './WriteCache'; import WriteCache from './WriteCache';
import WriteGatheringManager from './WriteGatheringManager'; import WriteGatheringManager from './WriteGatheringManager';
@ -481,93 +481,19 @@ export default class VersioningRequestProcessor {
const versionId = request.options.versionId; const versionId = request.options.versionId;
const versionKey = formatVersionKey(key, versionId); const versionKey = formatVersionKey(key, versionId);
const ops: any = []; const ops: any = [];
const masterVersion = data !== undefined && if (!request.options.isNull) {
Version.from(data); ops.push({ key: versionKey, value: request.value });
// push a version key if we're not updating the null
// version (or in legacy Cloudservers not sending the
// 'isNull' parameter, but this has an issue, see S3C-7526)
if (request.options.isNull !== true) {
const versionOp = { key: versionKey, value: request.value };
ops.push(versionOp);
} }
if (masterVersion) { if (data === undefined ||
// master key exists (Version.from(data).getVersionId() ?? '') >= versionId) {
// note that older versions have a greater version ID // master does not exist or is not newer than put
const versionIdFromMaster = masterVersion.getVersionId(); // version and needs to be updated as well.
if (versionIdFromMaster === undefined || // Note that older versions have a greater version ID.
versionIdFromMaster >= versionId) { ops.push({ key: request.key, value: request.value });
let value = request.value; } else if (request.options.isNull) {
logger.debug('version to put is not older than master'); logger.debug('create or update null key');
// new behavior when isNull is defined is to only const nullKey = formatVersionKey(key, '');
// update the master key if it is the latest ops.push({ key: nullKey, value: request.value });
// version, old behavior needs to copy master to
// the null version because older Cloudservers
// rely on version-specific PUT to copy master
// contents to a new null version key (newer ones
// use special versionId="null" requests for this
// purpose).
if (versionIdFromMaster !== versionId ||
request.options.isNull === undefined) {
// master key is strictly older than the put version
let masterVersionId;
if (masterVersion.isNullVersion() && versionIdFromMaster) {
logger.debug('master key is a null version');
masterVersionId = versionIdFromMaster;
} else if (versionIdFromMaster === undefined) {
logger.debug('master key is nonversioned');
// master key does not have a versionID
// => create one with the "infinite" version ID
masterVersionId = getInfVid(this.replicationGroupId);
masterVersion.setVersionId(masterVersionId);
} else {
logger.debug('master key is a regular version');
}
if (request.options.isNull === true) {
if (!masterVersionId) {
// master is a regular version: delete the null key that
// may exist (older null version)
logger.debug('delete null key');
const nullKey = formatVersionKey(key, '');
ops.push({ key: nullKey, type: 'del' });
}
} else if (masterVersionId) {
logger.debug('create version key from master version');
// isNull === false means Cloudserver supports null keys,
// so create a null key in this case, and a version key otherwise
const masterKeyVersionId = request.options.isNull === false ?
'' : masterVersionId;
const masterVersionKey = formatVersionKey(key, masterKeyVersionId);
masterVersion.setNullVersion();
// isNull === false means Cloudserver supports null keys,
// so create a null key with the isNull2 flag
if (request.options.isNull === false) {
masterVersion.setNull2Version();
// else isNull === undefined means Cloudserver does not support null keys,
// hence set/update the new master nullVersionId for backward compatibility
} else {
value = Version.updateOrAppendNullVersionId(request.value, masterVersionId);
}
ops.push({ key: masterVersionKey,
value: masterVersion.toString() });
}
} else {
logger.debug('version to put is the master');
}
ops.push({ key, value: value });
} else {
logger.debug('version to put is older than master');
if (request.options.isNull === true && !masterVersion.isNullVersion()) {
logger.debug('create or update null key');
const nullKey = formatVersionKey(key, '');
const nullKeyOp = { key: nullKey, value: request.value };
ops.push(nullKeyOp);
// for backward compatibility: remove null version key
ops.push({ key: versionKey, type: 'del' });
}
}
} else {
// master key does not exist: create it
ops.push({ key, value: request.value });
} }
return callback(null, ops, versionId); return callback(null, ops, versionId);
}); });