Compare commits

..

No commits in common. "d58ea1863a8a88739b0b1053f2dffddadbf7b793" and "7c1bd453ee6b167b21777222afeda17b51c4b0c8" have entirely different histories.

8 changed files with 1011 additions and 1143 deletions

View File

@ -2,41 +2,21 @@
const Extension = require('./Extension').default; const Extension = require('./Extension').default;
const { inc, listingParamsMasterKeysV0ToV1, const { inc, listingParamsMasterKeysV0ToV1,
FILTER_END, FILTER_ACCEPT, FILTER_SKIP, SKIP_NONE } = require('./tools'); FILTER_END, FILTER_ACCEPT, FILTER_SKIP } = require('./tools');
const VSConst = require('../../versioning/constants').VersioningConstants; const VSConst = require('../../versioning/constants').VersioningConstants;
const { DbPrefixes, BucketVersioningKeyFormat } = VSConst; const { DbPrefixes, BucketVersioningKeyFormat } = VSConst;
export interface FilterState { /**
id: number, * Find the common prefix in the path
}; *
* @param {String} key - path of the object
export const enum DelimiterFilterStateId { * @param {String} delimiter - separator
NotSkipping = 1, * @param {Number} delimiterIndex - 'folder' index in the path
SkippingPrefix = 2, * @return {String} - CommonPrefix
}; */
function getCommonPrefix(key, delimiter, delimiterIndex) {
export interface DelimiterFilterState_NotSkipping extends FilterState { return key.substring(0, delimiterIndex + delimiter.length);
id: DelimiterFilterStateId.NotSkipping, }
};
export interface DelimiterFilterState_SkippingPrefix extends FilterState {
id: DelimiterFilterStateId.SkippingPrefix,
prefix: string;
};
type KeyHandler = (key: string, value: string) => number;
type ResultObject = {
CommonPrefixes: string[];
Contents: {
key: string;
value: string;
}[];
IsTruncated: boolean;
Delimiter ?: string;
NextMarker ?: string;
NextContinuationToken ?: string;
};
/** /**
* Handle object listing with parameters * Handle object listing with parameters
@ -50,11 +30,7 @@ type ResultObject = {
* @prop {String|undefined} prefix - prefix per amazon format * @prop {String|undefined} prefix - prefix per amazon format
* @prop {Number} maxKeys - number of keys to list * @prop {Number} maxKeys - number of keys to list
*/ */
export class Delimiter extends Extension { class Delimiter extends Extension {
state: FilterState;
keyHandlers: { [id: number]: KeyHandler };
/** /**
* Create a new Delimiter instance * Create a new Delimiter instance
* @constructor * @constructor
@ -72,6 +48,9 @@ export class Delimiter extends Extension {
* format * format
* @param {String} [parameters.continuationToken] - obfuscated amazon * @param {String} [parameters.continuationToken] - obfuscated amazon
* token * token
* @param {Boolean} [parameters.alphabeticalOrder] - Either the result is
* alphabetically ordered
* or not
* @param {RequestLogger} logger - The logger of the * @param {RequestLogger} logger - The logger of the
* request * request
* @param {String} [vFormat] - versioning key format * @param {String} [vFormat] - versioning key format
@ -81,21 +60,38 @@ export class Delimiter extends Extension {
// original listing parameters // original listing parameters
this.delimiter = parameters.delimiter; this.delimiter = parameters.delimiter;
this.prefix = parameters.prefix; this.prefix = parameters.prefix;
this.marker = parameters.marker;
this.maxKeys = parameters.maxKeys || 1000; this.maxKeys = parameters.maxKeys || 1000;
this.startAfter = parameters.startAfter;
if (parameters.v2) { this.continuationToken = parameters.continuationToken;
this.marker = parameters.continuationToken || parameters.startAfter; this.alphabeticalOrder =
} else { typeof parameters.alphabeticalOrder !== 'undefined' ?
this.marker = parameters.marker; parameters.alphabeticalOrder : true;
}
this.nextMarker = this.marker;
this.vFormat = vFormat || BucketVersioningKeyFormat.v0; this.vFormat = vFormat || BucketVersioningKeyFormat.v0;
// results // results
this.CommonPrefixes = []; this.CommonPrefixes = [];
this.Contents = []; this.Contents = [];
this.IsTruncated = false; this.IsTruncated = false;
this.keyHandlers = {}; this.NextMarker = parameters.marker;
this.NextContinuationToken =
parameters.continuationToken || parameters.startAfter;
this.startMarker = parameters.v2 ? 'startAfter' : 'marker';
this.continueMarker = parameters.v2 ? 'continuationToken' : 'marker';
this.nextContinueMarker = parameters.v2 ?
'NextContinuationToken' : 'NextMarker';
if (this.delimiter !== undefined &&
this[this.nextContinueMarker] !== undefined &&
this[this.nextContinueMarker].startsWith(this.prefix || '')) {
const nextDelimiterIndex =
this[this.nextContinueMarker].indexOf(this.delimiter,
this.prefix ? this.prefix.length : 0);
this[this.nextContinueMarker] =
this[this.nextContinueMarker].slice(0, nextDelimiterIndex +
this.delimiter.length);
}
Object.assign(this, { Object.assign(this, {
[BucketVersioningKeyFormat.v0]: { [BucketVersioningKeyFormat.v0]: {
@ -109,49 +105,21 @@ export class Delimiter extends Extension {
skipping: this.skippingV1, skipping: this.skippingV1,
}, },
}[this.vFormat]); }[this.vFormat]);
// if there is a delimiter, we may skip ranges by prefix,
// hence using the NotSkippingPrefix flavor that checks the
// subprefix up to the delimiter for the NotSkipping state
if (this.delimiter) {
this.setKeyHandler(
DelimiterFilterStateId.NotSkipping,
this.keyHandler_NotSkippingPrefix.bind(this));
this.setKeyHandler(
DelimiterFilterStateId.SkippingPrefix,
this.keyHandler_SkippingPrefix.bind(this));
} else {
// listing without a delimiter never has to skip over any
// prefix -> use NeverSkipping flavor for the NotSkipping
// state
this.setKeyHandler(
DelimiterFilterStateId.NotSkipping,
this.keyHandler_NeverSkipping.bind(this));
}
this.state = <DelimiterFilterState_NotSkipping> {
id: DelimiterFilterStateId.NotSkipping,
};
} }
genMDParamsV0() { genMDParamsV0() {
const params: { gt ?: string, gte ?: string, lt ?: string } = {}; const params = {};
if (this.prefix) { if (this.prefix) {
params.gte = this.prefix; params.gte = this.prefix;
params.lt = inc(this.prefix); params.lt = inc(this.prefix);
} }
if (this.marker && this.delimiter) { const startVal = this[this.continueMarker] || this[this.startMarker];
const commonPrefix = this.getCommonPrefix(this.marker); if (startVal) {
if (commonPrefix) { if (params.gte && params.gte > startVal) {
const afterPrefix = inc(commonPrefix); return params;
if (!params.gte || afterPrefix > params.gte) {
params.gte = afterPrefix;
}
} }
}
if (this.marker && (!params.gte || this.marker >= params.gte)) {
delete params.gte; delete params.gte;
params.gt = this.marker; params.gt = startVal;
} }
return params; return params;
} }
@ -183,62 +151,21 @@ export class Delimiter extends Extension {
* @param {String} value - The value of the key * @param {String} value - The value of the key
* @return {number} - indicates if iteration should continue * @return {number} - indicates if iteration should continue
*/ */
addContents(key: string, value: string): number { addContents(key, value) {
if (this._reachedMaxKeys()) { if (this._reachedMaxKeys()) {
return FILTER_END; return FILTER_END;
} }
this.Contents.push({ key, value: this.trimMetadata(value) }); this.Contents.push({ key, value: this.trimMetadata(value) });
this[this.nextContinueMarker] = key;
++this.keys; ++this.keys;
this.nextMarker = key;
return FILTER_ACCEPT; return FILTER_ACCEPT;
} }
getCommonPrefix(key: string): string | undefined { getObjectKeyV0(obj) {
const baseIndex = this.prefix ? this.prefix.length : 0;
const delimiterIndex = key.indexOf(this.delimiter, baseIndex);
if (delimiterIndex === -1) {
return undefined;
}
return key.substring(0, delimiterIndex + this.delimiter.length);
}
/**
* Add a Common Prefix in the list
* @param {String} commonPrefix - common prefix to add
* @param {String} key - full key starting with commonPrefix
* @return {Boolean} - indicates if iteration should continue
*/
addCommonPrefix(commonPrefix: string, key: string): number {
if (this._reachedMaxKeys()) {
return FILTER_END;
}
// add the new prefix to the list
this.CommonPrefixes.push(commonPrefix);
++this.keys;
this.nextMarker = key;
// transition into SkippingPrefix state to skip all following keys
// while they start with the same prefix
this.setState(<DelimiterFilterState_SkippingPrefix> {
id: DelimiterFilterStateId.SkippingPrefix,
prefix: commonPrefix,
});
return FILTER_ACCEPT;
}
addCommonPrefixOrContents(key: string, value: string): number {
// add the subprefix to the common prefixes if the key has the delimiter
const commonPrefix = this.getCommonPrefix(key);
if (commonPrefix) {
return this.addCommonPrefix(commonPrefix, key);
}
return this.addContents(key, value);
}
getObjectKeyV0(obj: { key: string }): string {
return obj.key; return obj.key;
} }
getObjectKeyV1(obj: { key: string }): string { getObjectKeyV1(obj) {
return obj.key.slice(DbPrefixes.Master.length); return obj.key.slice(DbPrefixes.Master.length);
} }
@ -253,65 +180,67 @@ export class Delimiter extends Extension {
* @param {String} obj.value - The value of the element * @param {String} obj.value - The value of the element
* @return {number} - indicates if iteration should continue * @return {number} - indicates if iteration should continue
*/ */
filter(obj: { key: string, value: string }): number { filter(obj) {
const key = this.getObjectKey(obj); const key = this.getObjectKey(obj);
const value = obj.value; const value = obj.value;
if ((this.prefix && !key.startsWith(this.prefix))
return this.handleKey(key, value); || (this.alphabeticalOrder
} && typeof this[this.nextContinueMarker] === 'string'
&& key <= this[this.nextContinueMarker])) {
setState(state: FilterState): void { return FILTER_SKIP;
this.state = state; }
} if (this.delimiter) {
const baseIndex = this.prefix ? this.prefix.length : 0;
setKeyHandler(stateId: number, keyHandler: KeyHandler): void { const delimiterIndex = key.indexOf(this.delimiter, baseIndex);
this.keyHandlers[stateId] = keyHandler; if (delimiterIndex === -1) {
} return this.addContents(key, value);
}
handleKey(key: string, value: string): number { return this.addCommonPrefix(key, delimiterIndex);
return this.keyHandlers[this.state.id](key, value); }
}
keyHandler_NeverSkipping(key, value) {
return this.addContents(key, value); return this.addContents(key, value);
} }
keyHandler_NotSkippingPrefix(key, value) { /**
return this.addCommonPrefixOrContents(key, value); * Add a Common Prefix in the list
} * @param {String} key - object name
* @param {Number} index - after prefix starting point
keyHandler_SkippingPrefix(key, value) { * @return {Boolean} - indicates if iteration should continue
const { prefix } = <DelimiterFilterState_SkippingPrefix> this.state; */
if (key.startsWith(prefix)) { addCommonPrefix(key, index) {
return FILTER_SKIP; const commonPrefix = getCommonPrefix(key, this.delimiter, index);
} if (this.CommonPrefixes.indexOf(commonPrefix) === -1
this.setState(<DelimiterFilterState_NotSkipping> { && this[this.nextContinueMarker] !== commonPrefix) {
id: DelimiterFilterStateId.NotSkipping, if (this._reachedMaxKeys()) {
}); return FILTER_END;
return this.handleKey(key, value); }
} this.CommonPrefixes.push(commonPrefix);
this[this.nextContinueMarker] = commonPrefix;
skippingBase(): string | undefined { ++this.keys;
switch (this.state.id) { return FILTER_ACCEPT;
case DelimiterFilterStateId.SkippingPrefix:
const { prefix } = <DelimiterFilterState_SkippingPrefix> this.state;
return prefix;
default:
return SKIP_NONE;
} }
return FILTER_SKIP;
} }
/**
* If repd happens to want to skip listing on a bucket in v0
* versioning key format, here is an idea.
*
* @return {string} - the present range (NextMarker) if repd believes
* that it's enough and should move on
*/
skippingV0() { skippingV0() {
return this.skippingBase(); return this[this.nextContinueMarker];
} }
/**
* If repd happens to want to skip listing on a bucket in v1
* versioning key format, here is an idea.
*
* @return {string} - the present range (NextMarker) if repd believes
* that it's enough and should move on
*/
skippingV1() { skippingV1() {
const skipTo = this.skippingBase(); return DbPrefixes.Master + this[this.nextContinueMarker];
if (skipTo === SKIP_NONE) {
return SKIP_NONE;
}
return DbPrefixes.Master + skipTo;
} }
/** /**
@ -320,12 +249,12 @@ export class Delimiter extends Extension {
* isn't truncated * isn't truncated
* @return {Object} - following amazon format * @return {Object} - following amazon format
*/ */
result(): ResultObject { result() {
/* NextMarker is only provided when delimiter is used. /* NextMarker is only provided when delimiter is used.
* specified in v1 listing documentation * specified in v1 listing documentation
* http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGET.html * http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGET.html
*/ */
const result: ResultObject = { const result = {
CommonPrefixes: this.CommonPrefixes, CommonPrefixes: this.CommonPrefixes,
Contents: this.Contents, Contents: this.Contents,
IsTruncated: this.IsTruncated, IsTruncated: this.IsTruncated,
@ -333,11 +262,13 @@ export class Delimiter extends Extension {
}; };
if (this.parameters.v2) { if (this.parameters.v2) {
result.NextContinuationToken = this.IsTruncated result.NextContinuationToken = this.IsTruncated
? this.nextMarker : undefined; ? this.NextContinuationToken : undefined;
} else { } else {
result.NextMarker = (this.IsTruncated && this.delimiter) result.NextMarker = (this.IsTruncated && this.delimiter)
? this.nextMarker : undefined; ? this.NextMarker : undefined;
} }
return result; return result;
} }
} }
module.exports = { Delimiter, getCommonPrefix };

View File

@ -0,0 +1,230 @@
'use strict'; // eslint-disable-line strict
const Delimiter = require('./delimiter').Delimiter;
const Version = require('../../versioning/Version').Version;
const VSConst = require('../../versioning/constants').VersioningConstants;
const { BucketVersioningKeyFormat } = VSConst;
const { FILTER_ACCEPT, FILTER_SKIP, SKIP_NONE } = require('./tools');
const VID_SEP = VSConst.VersionId.Separator;
const { DbPrefixes } = VSConst;
/**
* Handle object listing with parameters. This extends the base class Delimiter
* to return the raw master versions of existing objects.
*/
class DelimiterMaster extends Delimiter {
/**
* Delimiter listing of master versions.
* @param {Object} parameters - listing parameters
* @param {String} parameters.delimiter - delimiter per amazon format
* @param {String} parameters.prefix - prefix per amazon format
* @param {String} parameters.marker - marker per amazon format
* @param {Number} parameters.maxKeys - number of keys to list
* @param {Boolean} parameters.v2 - indicates whether v2 format
* @param {String} parameters.startAfter - marker per amazon v2 format
* @param {String} parameters.continuationToken - obfuscated amazon token
* @param {RequestLogger} logger - The logger of the request
* @param {String} [vFormat] - versioning key format
*/
constructor(parameters, logger, vFormat) {
super(parameters, logger, vFormat);
// non-PHD master version or a version whose master is a PHD version
this.prvKey = undefined;
this.prvPHDKey = undefined;
this.inReplayPrefix = false;
this.prefixKeySeen = false;
this.prefixEndsWithDelim = this.prefix && this.prefix.endsWith(this.delimiter);
Object.assign(this, {
[BucketVersioningKeyFormat.v0]: {
filter: this.filterV0,
skipping: this.skippingV0,
},
[BucketVersioningKeyFormat.v1]: {
filter: this.filterV1,
skipping: this.skippingV1,
},
}[this.vFormat]);
}
/**
* Filter to apply on each iteration for buckets in v0 format,
* based on:
* - prefix
* - delimiter
* - maxKeys
* The marker is being handled directly by levelDB
* @param {Object} obj - The key and value of the element
* @param {String} obj.key - The key of the element
* @param {String} obj.value - The value of the element
* @return {number} - indicates if iteration should continue
*/
filterV0(obj) {
let key = obj.key;
const value = obj.value;
if (key === this.prefix) {
this.prefixKeySeen = true;
}
if (key.startsWith(DbPrefixes.Replay)) {
this.inReplayPrefix = true;
return FILTER_SKIP;
}
this.inReplayPrefix = false;
/* Skip keys not starting with the prefix or not alphabetically
* ordered. */
if ((this.prefix && !key.startsWith(this.prefix))
|| (typeof this[this.nextContinueMarker] === 'string' &&
key <= this[this.nextContinueMarker])) {
return FILTER_SKIP;
}
/* Skip version keys (<key><versionIdSeparator><version>) if we already
* have a master version. */
const versionIdIndex = key.indexOf(VID_SEP);
if (versionIdIndex >= 0) {
key = key.slice(0, versionIdIndex);
/* - key === this.prvKey is triggered when a master version has
* been accepted for this key,
* - key === this.NextMarker or this.NextContinueToken is triggered
* when a listing page ends on an accepted obj and the next page
* starts with a version of this object.
* In that case prvKey is default set to undefined
* in the constructor and comparing to NextMarker is the only
* way to know we should not accept this version. This test is
* not redundant with the one at the beginning of this function,
* we are comparing here the key without the version suffix,
* - key startsWith the previous NextMarker happens because we set
* NextMarker to the common prefix instead of the whole key
* value. (TODO: remove this test once ZENKO-1048 is fixed)
* */
if (key === this.prvKey || key === this[this.nextContinueMarker]
|| (this.delimiter && key.startsWith(this[this.nextContinueMarker]))) {
/* master version already filtered */
return FILTER_SKIP;
}
}
if (Version.isPHD(value)) {
/* master version is a PHD version, we want to wait for the next
* one:
* - Set the prvKey to undefined to not skip the next version,
* - return accept to avoid users to skip the next values in range
* (skip scan mechanism in metadata backend like Metadata or
* MongoClient). */
this.prvKey = undefined;
this.prvPHDKey = key;
return FILTER_ACCEPT;
}
if (Version.isDeleteMarker(value)) {
/* This entry is a deleteMarker which has not been filtered by the
* version test. Either :
* - it is a deleteMarker on the master version, we want to SKIP
* all the following entries with this key (no master version),
* - or a deleteMarker following a PHD (setting prvKey to undefined
* when an entry is a PHD avoids the skip on version for the
* next entry). In that case we expect the master version to
* follow. */
if (key === this.prvPHDKey) {
this.prvKey = undefined;
return FILTER_ACCEPT;
}
this.prvKey = key;
if (this.prefixEndsWithDelim) {
/* When the prefix ends with a delimiter, update nextContinueMarker
* to be able to skip ranges of the form prefix/subprefix/ as an optimization.
* The marker may also end up being prefix/, in which case .skipping will determine
* if a skip over the full range is allowed or a smaller skipping range of prefix/{VID_SEP}
* must be used. */
this[this.nextContinueMarker] = key.slice(0, key.lastIndexOf(this.delimiter) + 1);
}
return FILTER_SKIP;
}
this.prvKey = key;
if (this.delimiter) {
// check if the key has the delimiter
const baseIndex = this.prefix ? this.prefix.length : 0;
const delimiterIndex = key.indexOf(this.delimiter, baseIndex);
if (delimiterIndex >= 0) {
// try to add the prefix to the list
return this.addCommonPrefix(key, delimiterIndex);
}
}
return this.addContents(key, value);
}
/**
* Filter to apply on each iteration for buckets in v1 format,
* based on:
* - prefix
* - delimiter
* - maxKeys
* The marker is being handled directly by levelDB
* @param {Object} obj - The key and value of the element
* @param {String} obj.key - The key of the element
* @param {String} obj.value - The value of the element
* @return {number} - indicates if iteration should continue
*/
filterV1(obj) {
// Filtering master keys in v1 is simply listing the master
// keys, as the state of version keys do not change the
// result, so we can use Delimiter method directly.
return super.filter(obj);
}
/**
* Determine if a nextContinueMarker ending with a delimiter
* can be skipped.
* @returns {bool}
*/
allowDelimiterRangeSkip() {
if (!this.prefixKeySeen) {
// A prefix key is a master key equal to the prefix. If it has
// not been encountered, can skip.
return true;
}
const marker = this[this.nextContinueMarker];
// prefix = prefix, key = prefix/. Can skip since key will be part of commonPrefixes.
if (marker.length > this.prefix.length && marker.startsWith(this.prefix)) {
return true;
}
const lastIdx = marker.lastIndexOf(this.delimiter); // prefix/foo is a masterKey following a prefix key.
return marker.slice(0, lastIdx + 1) !== this.prefix; // cannot skip the full range prefix/ range.
}
skippingBase() {
if (this[this.nextContinueMarker]) {
// next marker or next continuation token:
// - foo/ : skipping foo/
// - foo : skipping foo.
const index = this[this.nextContinueMarker].
lastIndexOf(this.delimiter);
if (index === this[this.nextContinueMarker].length - 1 && this.allowDelimiterRangeSkip()) {
return this[this.nextContinueMarker];
}
return this[this.nextContinueMarker] + VID_SEP;
}
return SKIP_NONE;
}
skippingV0() {
if (this.inReplayPrefix) {
return DbPrefixes.Replay;
}
return this.skippingBase();
}
skippingV1() {
const skipTo = this.skippingBase();
if (skipTo === SKIP_NONE) {
return SKIP_NONE;
}
return DbPrefixes.Master + skipTo;
}
}
module.exports = { DelimiterMaster };

View File

@ -1,171 +0,0 @@
import {
Delimiter,
FilterState,
DelimiterFilterStateId,
DelimiterFilterState_NotSkipping,
DelimiterFilterState_SkippingPrefix,
} from './delimiter';
const Version = require('../../versioning/Version').Version;
const VSConst = require('../../versioning/constants').VersioningConstants;
const { BucketVersioningKeyFormat } = VSConst;
const { FILTER_ACCEPT, FILTER_SKIP } = require('./tools');
const VID_SEP = VSConst.VersionId.Separator;
const { DbPrefixes } = VSConst;
const enum DelimiterMasterFilterStateId {
SkippingVersionsV0 = 101,
WaitVersionAfterPHDV0 = 102,
};
interface DelimiterMasterFilterState_SkippingVersionsV0 extends FilterState {
id: DelimiterMasterFilterStateId.SkippingVersionsV0,
masterKey: string,
};
interface DelimiterMasterFilterState_WaitVersionAfterPHDV0 extends FilterState {
id: DelimiterMasterFilterStateId.WaitVersionAfterPHDV0,
masterKey: string,
};
/**
* Handle object listing with parameters. This extends the base class Delimiter
* to return the raw master versions of existing objects.
*/
export class DelimiterMaster extends Delimiter {
/**
* Delimiter listing of master versions.
* @param {Object} parameters - listing parameters
* @param {String} parameters.delimiter - delimiter per amazon format
* @param {String} parameters.prefix - prefix per amazon format
* @param {String} parameters.marker - marker per amazon format
* @param {Number} parameters.maxKeys - number of keys to list
* @param {Boolean} parameters.v2 - indicates whether v2 format
* @param {String} parameters.startAfter - marker per amazon v2 format
* @param {String} parameters.continuationToken - obfuscated amazon token
* @param {RequestLogger} logger - The logger of the request
* @param {String} [vFormat] - versioning key format
*/
constructor(parameters, logger, vFormat) {
super(parameters, logger, vFormat);
Object.assign(this, {
[BucketVersioningKeyFormat.v0]: {
skipping: this.skippingV0,
},
[BucketVersioningKeyFormat.v1]: {
skipping: this.skippingV1,
},
}[this.vFormat]);
if (vFormat === BucketVersioningKeyFormat.v0) {
// override Delimiter's implementation of NotSkipping for
// DelimiterMaster logic (skipping versions and special
// handling of delete markers and PHDs)
this.setKeyHandler(
DelimiterFilterStateId.NotSkipping,
this.keyHandler_NotSkippingPrefixNorVersionsV0.bind(this));
// add extra state handlers specific to DelimiterMaster with v0 format
this.setKeyHandler(
DelimiterMasterFilterStateId.SkippingVersionsV0,
this.keyHandler_SkippingVersionsV0.bind(this));
this.setKeyHandler(
DelimiterMasterFilterStateId.WaitVersionAfterPHDV0,
this.keyHandler_WaitVersionAfterPHDV0.bind(this));
if (this.marker) {
// distinct initial state to include some special logic
// before the first master key is found that does not have
// to be checked afterwards
this.state = <DelimiterMasterFilterState_SkippingVersionsV0> {
id: DelimiterMasterFilterStateId.SkippingVersionsV0,
masterKey: this.marker,
};
} else {
this.state = <DelimiterFilterState_NotSkipping> {
id: DelimiterFilterStateId.NotSkipping,
};
}
}
// in v1, we can directly use Delimiter's implementation,
// which is already set to the proper state
}
filter_onNewMasterKeyV0(key, value) {
// update the state to start skipping versions of the new master key
this.setState(<DelimiterMasterFilterState_SkippingVersionsV0> {
id: DelimiterMasterFilterStateId.SkippingVersionsV0,
masterKey: key,
});
// if this master key is a delete marker, accept it without
// adding the version to the contents
if (Version.isDeleteMarker(value)) {
return FILTER_ACCEPT;
}
if (Version.isPHD(value)) {
// master version is a PHD version: wait for the first
// following version that will be considered as the actual
// master key
this.setState(<DelimiterMasterFilterState_WaitVersionAfterPHDV0> {
id: DelimiterMasterFilterStateId.WaitVersionAfterPHDV0,
masterKey: key,
});
return FILTER_ACCEPT;
}
if (key.startsWith(DbPrefixes.Replay)) {
// skip internal replay prefix entirely
this.setState(<DelimiterFilterState_SkippingPrefix> {
id: DelimiterFilterStateId.SkippingPrefix,
prefix: DbPrefixes.Replay,
});
return FILTER_SKIP;
}
return this.addCommonPrefixOrContents(key, value);
}
keyHandler_NotSkippingPrefixNorVersionsV0(key, value) {
return this.filter_onNewMasterKeyV0(key, value);
}
keyHandler_SkippingVersionsV0(key, value) {
/* In the SkippingVersionsV0 state, skip all version keys
* (<key><versionIdSeparator><version>) */
const versionIdIndex = key.indexOf(VID_SEP);
if (versionIdIndex !== -1) {
return FILTER_SKIP;
}
return this.filter_onNewMasterKeyV0(key, value);
}
keyHandler_WaitVersionAfterPHDV0(key, value) {
// After a PHD key is encountered, the next version key of the
// same object if it exists is the new master key, hence
// consider it as such and call 'onNewMasterKeyV0' (the test
// 'masterKey == phdKey' is probably redundant when we already
// know we have a versioned key, since all objects in v0 have
// a master key, but keeping it in doubt)
const { masterKey: phdKey } = <DelimiterMasterFilterState_WaitVersionAfterPHDV0> this.state;
const versionIdIndex = key.indexOf(VID_SEP);
if (versionIdIndex !== -1) {
const masterKey = key.slice(0, versionIdIndex);
if (masterKey === phdKey) {
return this.filter_onNewMasterKeyV0(masterKey, value);
}
}
return this.filter_onNewMasterKeyV0(key, value);
}
skippingBase(): string | undefined {
switch (this.state.id) {
case DelimiterMasterFilterStateId.SkippingVersionsV0:
const { masterKey } = <DelimiterMasterFilterState_SkippingVersionsV0> this.state;
return masterKey + VID_SEP;
default:
return super.skippingBase();
}
}
}

View File

@ -151,27 +151,6 @@ class DelimiterVersions extends Delimiter {
return FILTER_ACCEPT; return FILTER_ACCEPT;
} }
/**
* Add a Common Prefix in the list
* @param {String} key - object name
* @param {Number} index - after prefix starting point
* @return {Boolean} - indicates if iteration should continue
*/
addCommonPrefix(key, index) {
const commonPrefix = key.substring(0, index + this.delimiter.length);
if (this.CommonPrefixes.indexOf(commonPrefix) === -1
&& this.NextMarker !== commonPrefix) {
if (this._reachedMaxKeys()) {
return FILTER_END;
}
this.CommonPrefixes.push(commonPrefix);
this.NextMarker = commonPrefix;
++this.keys;
return FILTER_ACCEPT;
}
return FILTER_SKIP;
}
/** /**
* Filter to apply on each iteration if bucket is in v0 * Filter to apply on each iteration if bucket is in v0
* versioning key format, based on: * versioning key format, based on:

View File

@ -62,6 +62,7 @@
"@types/jest": "^27.4.1", "@types/jest": "^27.4.1",
"@types/node": "^17.0.21", "@types/node": "^17.0.21",
"@types/xml2js": "^0.4.11", "@types/xml2js": "^0.4.11",
"chance": "^1.1.8",
"eslint": "^8.12.0", "eslint": "^8.12.0",
"eslint-config-airbnb": "6.2.0", "eslint-config-airbnb": "6.2.0",
"eslint-config-scality": "scality/Guidelines#7.10.2", "eslint-config-scality": "scality/Guidelines#7.10.2",

View File

@ -7,17 +7,23 @@ const DelimiterMaster =
require('../../../../lib/algos/list/delimiterMaster').DelimiterMaster; require('../../../../lib/algos/list/delimiterMaster').DelimiterMaster;
const Werelogs = require('werelogs').Logger; const Werelogs = require('werelogs').Logger;
const logger = new Werelogs('listTest'); const logger = new Werelogs('listTest');
const performListing = require('../../../utils/performListing');
const zpad = require('../../helpers').zpad; const zpad = require('../../helpers').zpad;
const { inc } = require('../../../../lib/algos/list/tools'); const { inc } = require('../../../../lib/algos/list/tools');
const VSConst = require('../../../../lib/versioning/constants').VersioningConstants; const VSConst = require('../../../../lib/versioning/constants').VersioningConstants;
const { DbPrefixes } = VSConst; const { DbPrefixes } = VSConst;
class Test { class Test {
constructor(name, input, genMDParams, output) { constructor(name, input, genMDParams, output, filter) {
this.name = name; this.name = name;
this.input = input; this.input = input;
this.genMDParams = genMDParams; this.genMDParams = genMDParams;
this.output = output; this.output = output;
this.filter = filter || this._defaultFilter;
}
_defaultFilter() {
return true;
} }
} }
@ -27,7 +33,7 @@ const valueDeleteMarker = '{"hello":"world","isDeleteMarker":"true"}';
const data = [ const data = [
{ key: 'Pâtisserie=中文-español-English', value }, { key: 'Pâtisserie=中文-español-English', value },
{ key: 'notes/spring/1.txt', value }, { key: 'notes/spring/1.txt', value },
{ key: 'notes/spring/4.txt', value }, { key: 'notes/spring/2.txt', value },
{ key: 'notes/spring/march/1.txt', value }, { key: 'notes/spring/march/1.txt', value },
{ key: 'notes/summer/1.txt', value }, { key: 'notes/summer/1.txt', value },
{ key: 'notes/summer/2.txt', value }, { key: 'notes/summer/2.txt', value },
@ -50,9 +56,6 @@ const dataVersioned = [
{ key: 'notes/spring/2.txt\0foo', value }, { key: 'notes/spring/2.txt\0foo', value },
{ key: 'notes/spring/3.txt', value: valueDeleteMarker }, { key: 'notes/spring/3.txt', value: valueDeleteMarker },
{ key: 'notes/spring/3.txt\0foo', value }, { key: 'notes/spring/3.txt\0foo', value },
{ key: 'notes/spring/4.txt', value: valuePHD },
{ key: 'notes/spring/4.txt\0bar', value },
{ key: 'notes/spring/4.txt\0foo', value },
{ key: 'notes/spring/march/1.txt', value }, { key: 'notes/spring/march/1.txt', value },
{ key: 'notes/spring/march/1.txt\0bar', value }, { key: 'notes/spring/march/1.txt\0bar', value },
{ key: 'notes/spring/march/1.txt\0foo', value }, { key: 'notes/spring/march/1.txt\0foo', value },
@ -75,8 +78,15 @@ const dataVersioned = [
{ key: 'notes/yore.rs', value }, { key: 'notes/yore.rs', value },
{ key: 'notes/zaphod/Beeblebrox.txt', value }, { key: 'notes/zaphod/Beeblebrox.txt', value },
]; ];
const nonAlphabeticalData = [
{ key: 'zzz', value },
{ key: 'aaa', value },
];
const receivedData = data.map(item => ({ key: item.key, value: item.value })); const receivedData = data.map(item => ({ key: item.key, value: item.value }));
const receivedNonAlphaData = nonAlphabeticalData.map(
item => ({ key: item.key, value: item.value }),
);
const tests = [ const tests = [
new Test('all elements', {}, { new Test('all elements', {}, {
@ -114,7 +124,7 @@ const tests = [
Delimiter: undefined, Delimiter: undefined,
IsTruncated: false, IsTruncated: false,
NextMarker: undefined, NextMarker: undefined,
}), }, (e, input) => e.key > input.marker),
new Test('with bad marker', { new Test('with bad marker', {
marker: 'zzzz', marker: 'zzzz',
delimiter: '/', delimiter: '/',
@ -132,7 +142,7 @@ const tests = [
Delimiter: '/', Delimiter: '/',
IsTruncated: false, IsTruncated: false,
NextMarker: undefined, NextMarker: undefined,
}), }, (e, input) => e.key > input.marker),
new Test('with makKeys', { new Test('with makKeys', {
maxKeys: 3, maxKeys: 3,
}, { }, {
@ -209,12 +219,12 @@ const tests = [
marker: 'notes/summer0', marker: 'notes/summer0',
}, { }, {
v0: { v0: {
gt: 'notes/summer0', gt: `notes/summer${inc('/')}`,
lt: 'notes/summer0', lt: `notes/summer${inc('/')}`,
}, },
v1: { v1: {
gt: `${DbPrefixes.Master}notes/summer0`, gt: `${DbPrefixes.Master}notes/summer${inc('/')}`,
lt: `${DbPrefixes.Master}notes/summer0`, lt: `${DbPrefixes.Master}notes/summer${inc('/')}`,
}, },
}, { }, {
Contents: [], Contents: [],
@ -222,18 +232,18 @@ const tests = [
Delimiter: '/', Delimiter: '/',
IsTruncated: false, IsTruncated: false,
NextMarker: undefined, NextMarker: undefined,
}), }, (e, input) => e.key > input.marker),
new Test('delimiter and prefix (related to #147)', { new Test('delimiter and prefix (related to #147)', {
delimiter: '/', delimiter: '/',
prefix: 'notes/', prefix: 'notes/',
}, { }, {
v0: { v0: {
gte: 'notes/', gte: 'notes/',
lt: 'notes0', lt: `notes${inc('/')}`,
}, },
v1: { v1: {
gte: `${DbPrefixes.Master}notes/`, gte: `${DbPrefixes.Master}notes/`,
lt: `${DbPrefixes.Master}notes0`, lt: `${DbPrefixes.Master}notes${inc('/')}`,
}, },
}, { }, {
Contents: [ Contents: [
@ -256,11 +266,11 @@ const tests = [
}, { }, {
v0: { v0: {
gt: 'notes/year.txt', gt: 'notes/year.txt',
lt: 'notes0', lt: `notes${inc('/')}`,
}, },
v1: { v1: {
gt: `${DbPrefixes.Master}notes/year.txt`, gt: `${DbPrefixes.Master}notes/year.txt`,
lt: `${DbPrefixes.Master}notes0`, lt: `${DbPrefixes.Master}notes${inc('/')}`,
}, },
}, { }, {
Contents: [ Contents: [
@ -272,8 +282,8 @@ const tests = [
Delimiter: '/', Delimiter: '/',
IsTruncated: false, IsTruncated: false,
NextMarker: undefined, NextMarker: undefined,
}), }, (e, input) => e.key > input.marker),
new Test('all parameters 1/5', { new Test('all parameters 1/3', {
delimiter: '/', delimiter: '/',
prefix: 'notes/', prefix: 'notes/',
marker: 'notes/', marker: 'notes/',
@ -281,55 +291,55 @@ const tests = [
}, { }, {
v0: { v0: {
gt: 'notes/', gt: 'notes/',
lt: 'notes0', lt: `notes${inc('/')}`,
}, },
v1: { v1: {
gt: `${DbPrefixes.Master}notes/`, gt: `${DbPrefixes.Master}notes/`,
lt: `${DbPrefixes.Master}notes0`, lt: `${DbPrefixes.Master}notes${inc('/')}`,
}, },
}, { }, {
Contents: [], Contents: [],
CommonPrefixes: ['notes/spring/'], CommonPrefixes: ['notes/spring/'],
Delimiter: '/', Delimiter: '/',
IsTruncated: true, IsTruncated: true,
NextMarker: 'notes/spring/1.txt', NextMarker: 'notes/spring/',
}), }, (e, input) => e.key > input.marker),
new Test('all parameters 2/5', { new Test('all parameters 2/3', {
delimiter: '/', delimiter: '/',
prefix: 'notes/', prefix: 'notes/', // prefix
marker: 'notes/spring/1.txt', marker: 'notes/spring/',
maxKeys: 1, maxKeys: 1,
}, { }, {
v0: { v0: {
gte: 'notes/spring0', gt: 'notes/spring/',
lt: 'notes0', lt: `notes${inc('/')}`,
}, },
v1: { v1: {
gte: `${DbPrefixes.Master}notes/spring0`, gt: `${DbPrefixes.Master}notes/spring/`,
lt: `${DbPrefixes.Master}notes0`, lt: `${DbPrefixes.Master}notes${inc('/')}`,
}, },
}, { }, {
Contents: [], Contents: [],
CommonPrefixes: ['notes/summer/'], CommonPrefixes: ['notes/summer/'],
Delimiter: '/', Delimiter: '/',
IsTruncated: true, IsTruncated: true,
NextMarker: 'notes/summer/1.txt', NextMarker: 'notes/summer/',
}), }, (e, input) => e.key > input.marker),
new Test('all parameters 3/5', { new Test('all parameters 3/3', {
delimiter: '/', delimiter: '/',
prefix: 'notes/', prefix: 'notes/', // prefix
marker: 'notes/summer/1.txt', marker: 'notes/summer/',
maxKeys: 1, maxKeys: 1,
}, { }, {
v0: { v0: {
gte: 'notes/summer0', gt: 'notes/summer/',
lt: 'notes0', lt: `notes${inc('/')}`,
}, },
v1: { v1: {
gte: `${DbPrefixes.Master}notes/summer0`, gt: `${DbPrefixes.Master}notes/summer/`,
lt: `${DbPrefixes.Master}notes0`, lt: `${DbPrefixes.Master}notes${inc('/')}`,
}, },
}, { }, {
Contents: [ Contents: [
@ -339,21 +349,21 @@ const tests = [
Delimiter: '/', Delimiter: '/',
IsTruncated: true, IsTruncated: true,
NextMarker: 'notes/year.txt', NextMarker: 'notes/year.txt',
}), }, (e, input) => e.key > input.marker),
new Test('all parameters 4/5', { new Test('all parameters 4/3', {
delimiter: '/', delimiter: '/',
prefix: 'notes/', prefix: 'notes/', // prefix
marker: 'notes/year.txt', marker: 'notes/year.txt',
maxKeys: 1, maxKeys: 1,
}, { }, {
v0: { v0: {
gt: 'notes/year.txt', gt: 'notes/year.txt',
lt: 'notes0', lt: `notes${inc('/')}`,
}, },
v1: { v1: {
gt: `${DbPrefixes.Master}notes/year.txt`, gt: `${DbPrefixes.Master}notes/year.txt`,
lt: `${DbPrefixes.Master}notes0`, lt: `${DbPrefixes.Master}notes${inc('/')}`,
}, },
}, { }, {
Contents: [ Contents: [
@ -363,9 +373,9 @@ const tests = [
Delimiter: '/', Delimiter: '/',
IsTruncated: true, IsTruncated: true,
NextMarker: 'notes/yore.rs', NextMarker: 'notes/yore.rs',
}), }, (e, input) => e.key > input.marker),
new Test('all parameters 5/5', { new Test('all parameters 5/3', {
delimiter: '/', delimiter: '/',
prefix: 'notes/', prefix: 'notes/',
marker: 'notes/yore.rs', marker: 'notes/yore.rs',
@ -373,11 +383,11 @@ const tests = [
}, { }, {
v0: { v0: {
gt: 'notes/yore.rs', gt: 'notes/yore.rs',
lt: 'notes0', lt: `notes${inc('/')}`,
}, },
v1: { v1: {
gt: `${DbPrefixes.Master}notes/yore.rs`, gt: `${DbPrefixes.Master}notes/yore.rs`,
lt: `${DbPrefixes.Master}notes0`, lt: `${DbPrefixes.Master}notes${inc('/')}`,
}, },
}, { }, {
Contents: [], Contents: [],
@ -385,7 +395,7 @@ const tests = [
Delimiter: '/', Delimiter: '/',
IsTruncated: false, IsTruncated: false,
NextMarker: undefined, NextMarker: undefined,
}), }, (e, input) => e.key > input.marker),
new Test('all elements v2', { new Test('all elements v2', {
v2: true, v2: true,
@ -425,7 +435,7 @@ const tests = [
Delimiter: undefined, Delimiter: undefined,
IsTruncated: false, IsTruncated: false,
NextContinuationToken: undefined, NextContinuationToken: undefined,
}), }, (e, input) => e.key > input.startAfter),
new Test('with bad startAfter', { new Test('with bad startAfter', {
startAfter: 'zzzz', startAfter: 'zzzz',
delimiter: '/', delimiter: '/',
@ -444,7 +454,7 @@ const tests = [
Delimiter: '/', Delimiter: '/',
IsTruncated: false, IsTruncated: false,
NextContinuationToken: undefined, NextContinuationToken: undefined,
}), }, (e, input) => e.key > input.startAfter),
new Test('with valid continuationToken', { new Test('with valid continuationToken', {
continuationToken: receivedData[4].key, continuationToken: receivedData[4].key,
v2: true, v2: true,
@ -468,7 +478,7 @@ const tests = [
Delimiter: undefined, Delimiter: undefined,
IsTruncated: false, IsTruncated: false,
NextContinuationToken: undefined, NextContinuationToken: undefined,
}), }, (e, input) => e.key > input.continuationToken),
new Test('with bad continuationToken', { new Test('with bad continuationToken', {
continuationToken: 'zzzz', continuationToken: 'zzzz',
delimiter: '/', delimiter: '/',
@ -487,49 +497,47 @@ const tests = [
Delimiter: '/', Delimiter: '/',
IsTruncated: false, IsTruncated: false,
NextContinuationToken: undefined, NextContinuationToken: undefined,
}), }, (e, input) => e.key > input.continuationToken),
new Test('bad startAfter and good prefix', { new Test('bad startAfter and good prefix', {
delimiter: '/', delimiter: '/',
prefix: 'notes/summer/', prefix: 'notes/summer/',
startAfter: 'notes/summer0', startAfter: 'notes/summer0',
v2: true,
}, { }, {
v0: { v0: {
gt: 'notes/summer0', gte: 'notes/summer/',
lt: 'notes/summer0', lt: `notes/summer${inc('/')}`,
}, },
v1: { v1: {
gt: `${DbPrefixes.Master}notes/summer0`, gte: `${DbPrefixes.Master}notes/summer/`,
lt: `${DbPrefixes.Master}notes/summer0`, lt: `${DbPrefixes.Master}notes/summer${inc('/')}`,
}, },
}, { }, {
Contents: [], Contents: [],
CommonPrefixes: [], CommonPrefixes: [],
Delimiter: '/', Delimiter: '/',
IsTruncated: false, IsTruncated: false,
NextContinuationToken: undefined, NextMarker: undefined,
}), }, (e, input) => e.key > input.startAfter),
new Test('bad continuation token and good prefix', { new Test('bad continuation token and good prefix', {
delimiter: '/', delimiter: '/',
prefix: 'notes/summer/', prefix: 'notes/summer/',
continuationToken: 'notes/summer0', continuationToken: 'notes/summer0',
v2: true,
}, { }, {
v0: { v0: {
gt: 'notes/summer0', gte: 'notes/summer/',
lt: 'notes/summer0', lt: `notes/summer${inc('/')}`,
}, },
v1: { v1: {
gt: `${DbPrefixes.Master}notes/summer0`, gte: `${DbPrefixes.Master}notes/summer/`,
lt: `${DbPrefixes.Master}notes/summer0`, lt: `${DbPrefixes.Master}notes/summer${inc('/')}`,
}, },
}, { }, {
Contents: [], Contents: [],
CommonPrefixes: [], CommonPrefixes: [],
Delimiter: '/', Delimiter: '/',
IsTruncated: false, IsTruncated: false,
NextContinuationToken: undefined, NextMarker: undefined,
}), }, (e, input) => e.key > input.continuationToken),
new Test('no delimiter v2', { new Test('no delimiter v2', {
startAfter: 'notes/year.txt', startAfter: 'notes/year.txt',
@ -551,9 +559,9 @@ const tests = [
Delimiter: undefined, Delimiter: undefined,
IsTruncated: true, IsTruncated: true,
NextContinuationToken: 'notes/yore.rs', NextContinuationToken: 'notes/yore.rs',
}), }, (e, input) => e.key > input.startAfter),
new Test('all parameters v2 1/5', { new Test('all parameters v2 1/6', {
delimiter: '/', delimiter: '/',
prefix: 'notes/', prefix: 'notes/',
startAfter: 'notes/', startAfter: 'notes/',
@ -562,57 +570,57 @@ const tests = [
}, { }, {
v0: { v0: {
gt: 'notes/', gt: 'notes/',
lt: 'notes0', lt: `notes${inc('/')}`,
}, },
v1: { v1: {
gt: `${DbPrefixes.Master}notes/`, gt: `${DbPrefixes.Master}notes/`,
lt: `${DbPrefixes.Master}notes0`, lt: `${DbPrefixes.Master}notes${inc('/')}`,
}, },
}, { }, {
Contents: [], Contents: [],
CommonPrefixes: ['notes/spring/'], CommonPrefixes: ['notes/spring/'],
Delimiter: '/', Delimiter: '/',
IsTruncated: true, IsTruncated: true,
NextContinuationToken: 'notes/spring/1.txt', NextContinuationToken: 'notes/spring/',
}), }, (e, input) => e.key > input.startAfter),
new Test('all parameters v2 2/5', { new Test('all parameters v2 2/6', {
delimiter: '/', delimiter: '/',
prefix: 'notes/', prefix: 'notes/',
continuationToken: 'notes/spring/1.txt', continuationToken: 'notes/spring/',
maxKeys: 1, maxKeys: 1,
v2: true, v2: true,
}, { }, {
v0: { v0: {
gte: 'notes/spring0', gt: 'notes/spring/',
lt: 'notes0', lt: `notes${inc('/')}`,
}, },
v1: { v1: {
gte: `${DbPrefixes.Master}notes/spring0`, gt: `${DbPrefixes.Master}notes/spring/`,
lt: `${DbPrefixes.Master}notes0`, lt: `${DbPrefixes.Master}notes${inc('/')}`,
}, },
}, { }, {
Contents: [], Contents: [],
CommonPrefixes: ['notes/summer/'], CommonPrefixes: ['notes/summer/'],
Delimiter: '/', Delimiter: '/',
IsTruncated: true, IsTruncated: true,
NextContinuationToken: 'notes/summer/1.txt', NextContinuationToken: 'notes/summer/',
}), }, (e, input) => e.key > input.continuationToken),
new Test('all parameters v2 3/5', { new Test('all parameters v2 3/5', {
delimiter: '/', delimiter: '/',
prefix: 'notes/', prefix: 'notes/',
continuationToken: 'notes/summer/1.txt', continuationToken: 'notes/summer/',
maxKeys: 1, maxKeys: 1,
v2: true, v2: true,
}, { }, {
v0: { v0: {
gte: 'notes/summer0', gt: 'notes/summer/',
lt: 'notes0', lt: `notes${inc('/')}`,
}, },
v1: { v1: {
gte: `${DbPrefixes.Master}notes/summer0`, gt: `${DbPrefixes.Master}notes/summer/`,
lt: `${DbPrefixes.Master}notes0`, lt: `${DbPrefixes.Master}notes${inc('/')}`,
}, },
}, { }, {
Contents: [ Contents: [
@ -622,7 +630,7 @@ const tests = [
Delimiter: '/', Delimiter: '/',
IsTruncated: true, IsTruncated: true,
NextContinuationToken: 'notes/year.txt', NextContinuationToken: 'notes/year.txt',
}), }, (e, input) => e.key > input.continuationToken),
new Test('all parameters v2 4/5', { new Test('all parameters v2 4/5', {
delimiter: '/', delimiter: '/',
@ -633,11 +641,11 @@ const tests = [
}, { }, {
v0: { v0: {
gt: 'notes/year.txt', gt: 'notes/year.txt',
lt: 'notes0', lt: `notes${inc('/')}`,
}, },
v1: { v1: {
gt: `${DbPrefixes.Master}notes/year.txt`, gt: `${DbPrefixes.Master}notes/year.txt`,
lt: `${DbPrefixes.Master}notes0`, lt: `${DbPrefixes.Master}notes${inc('/')}`,
}, },
}, { }, {
Contents: [ Contents: [
@ -647,7 +655,7 @@ const tests = [
Delimiter: '/', Delimiter: '/',
IsTruncated: true, IsTruncated: true,
NextContinuationToken: 'notes/yore.rs', NextContinuationToken: 'notes/yore.rs',
}), }, (e, input) => e.key > input.startAfter),
new Test('all parameters v2 5/5', { new Test('all parameters v2 5/5', {
delimiter: '/', delimiter: '/',
@ -658,11 +666,11 @@ const tests = [
}, { }, {
v0: { v0: {
gt: 'notes/yore.rs', gt: 'notes/yore.rs',
lt: 'notes0', lt: `notes${inc('/')}`,
}, },
v1: { v1: {
gt: `${DbPrefixes.Master}notes/yore.rs`, gt: `${DbPrefixes.Master}notes/yore.rs`,
lt: `${DbPrefixes.Master}notes0`, lt: `${DbPrefixes.Master}notes${inc('/')}`,
}, },
}, { }, {
Contents: [], Contents: [],
@ -670,11 +678,35 @@ const tests = [
Delimiter: '/', Delimiter: '/',
IsTruncated: false, IsTruncated: false,
NextContinuationToken: undefined, NextContinuationToken: undefined,
}), }, (e, input) => e.key > input.startAfter),
]; ];
function getTestListing(mdParams, data, vFormat) { const alphabeticalOrderTests = [
{
params: {},
expectedValue: true,
}, {
params: {
alphabeticalOrder: undefined,
},
expectedValue: true,
}, {
params: {
alphabeticalOrder: true,
},
expectedValue: true,
}, {
params: {
alphabeticalOrder: false,
},
expectedValue: false,
},
];
function getTestListing(test, data, vFormat) {
return data return data
.filter(e => test.filter(e, test.input))
.map(obj => { .map(obj => {
if (vFormat === 'v0') { if (vFormat === 'v0') {
return obj; return obj;
@ -686,12 +718,7 @@ function getTestListing(mdParams, data, vFormat) {
}; };
} }
return assert.fail(`bad format ${vFormat}`); return assert.fail(`bad format ${vFormat}`);
}) });
.filter(e =>
(!mdParams.gt || e.key > mdParams.gt) &&
(!mdParams.gte || e.key >= mdParams.gte) &&
(!mdParams.lt || e.key < mdParams.lt),
);
} }
['v0', 'v1'].forEach(vFormat => { ['v0', 'v1'].forEach(vFormat => {
@ -708,6 +735,15 @@ function getTestListing(mdParams, data, vFormat) {
`${vFormat === 'v1' ? DbPrefixes.Master : ''}foo/`); `${vFormat === 'v1' ? DbPrefixes.Master : ''}foo/`);
}); });
it('Should set Delimiter alphabeticalOrder field to the expected value', () => {
alphabeticalOrderTests.forEach(test => {
const delimiter = new Delimiter(test.params);
assert.strictEqual(delimiter.alphabeticalOrder,
test.expectedValue,
`${JSON.stringify(test.params)}`);
});
});
tests.forEach(test => { tests.forEach(test => {
it(`Should return metadata listing params to list ${test.name}`, () => { it(`Should return metadata listing params to list ${test.name}`, () => {
const listing = new Delimiter(test.input, logger, vFormat); const listing = new Delimiter(test.input, logger, vFormat);
@ -715,13 +751,9 @@ function getTestListing(mdParams, data, vFormat) {
assert.deepStrictEqual(params, test.genMDParams[vFormat]); assert.deepStrictEqual(params, test.genMDParams[vFormat]);
}); });
it(`Should list ${test.name}`, () => { it(`Should list ${test.name}`, () => {
const listing = new Delimiter(test.input, logger, vFormat); // Simulate skip scan done by LevelDB
const mdParams = listing.genMDParams(); const d = getTestListing(test, data, vFormat);
const rawEntries = getTestListing(mdParams, data, vFormat); const res = performListing(d, Delimiter, test.input, logger, vFormat);
for (const entry of rawEntries) {
listing.filter(entry);
}
const res = listing.result();
assert.deepStrictEqual(res, test.output); assert.deepStrictEqual(res, test.output);
}); });
}); });
@ -730,16 +762,49 @@ function getTestListing(mdParams, data, vFormat) {
if (vFormat === 'v0') { if (vFormat === 'v0') {
tests.forEach(test => { tests.forEach(test => {
it(`Should list master versions ${test.name}`, () => { it(`Should list master versions ${test.name}`, () => {
const listing = new DelimiterMaster(test.input, logger, vFormat); // Simulate skip scan done by LevelDB
const mdParams = listing.genMDParams(); const d = dataVersioned.filter(e => test.filter(e, test.input));
const rawEntries = getTestListing(mdParams, dataVersioned, vFormat); const res = performListing(d, DelimiterMaster, test.input, logger, vFormat);
for (const entry of rawEntries) {
listing.filter(entry);
}
const res = listing.result();
assert.deepStrictEqual(res, test.output); assert.deepStrictEqual(res, test.output);
}); });
}); });
} }
it('Should filter values according to alphabeticalOrder parameter', () => {
let test = new Test('alphabeticalOrder parameter set', {
delimiter: '/',
alphabeticalOrder: true,
}, {
}, {
Contents: [
receivedNonAlphaData[0],
],
Delimiter: '/',
CommonPrefixes: [],
IsTruncated: false,
NextMarker: undefined,
});
let d = getTestListing(test, nonAlphabeticalData, vFormat);
let res = performListing(d, Delimiter, test.input, logger, vFormat);
assert.deepStrictEqual(res, test.output);
test = new Test('alphabeticalOrder parameter set', {
delimiter: '/',
alphabeticalOrder: false,
}, {
}, {
Contents: [
receivedNonAlphaData[0],
receivedNonAlphaData[1],
],
Delimiter: '/',
CommonPrefixes: [],
IsTruncated: false,
NextMarker: undefined,
});
d = getTestListing(test, nonAlphabeticalData, vFormat);
res = performListing(d, Delimiter, test.input, logger, vFormat);
assert.deepStrictEqual(res, test.output);
});
}); });
}); });

File diff suppressed because it is too large Load Diff

View File

@ -2144,6 +2144,11 @@ chalk@^4.0.0:
ansi-styles "^4.1.0" ansi-styles "^4.1.0"
supports-color "^7.1.0" supports-color "^7.1.0"
chance@^1.1.8:
version "1.1.8"
resolved "https://registry.yarnpkg.com/chance/-/chance-1.1.8.tgz#5d6c2b78c9170bf6eb9df7acdda04363085be909"
integrity sha512-v7fi5Hj2VbR6dJEGRWLmJBA83LJMS47pkAbmROFxHWd9qmE1esHRZW8Clf1Fhzr3rjxnNZVCjOEv/ivFxeIMtg==
char-regex@^1.0.2: char-regex@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"