Compare commits
No commits in common. "694c41ded4d9dc3b81755d525e0f3ba0c6042746" and "8ad0ea73a75c861c594e3e59b9224ee5fafe1486" have entirely different histories.
694c41ded4
...
8ad0ea73a7
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue