Merge pull request #10 from M1Les/m1les/fix/non-unique-idx
Implement support for non-unique indices
This commit is contained in:
Коммит
0af298110c
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -50,7 +50,17 @@ declare class InMemoryIndex extends DbIndexFTSFromRangeQueries {
|
|||
getOnly(key: KeyType, reverseOrSortOrder?: boolean | QuerySortOrder, limit?: number, offset?: number): Promise<ItemType[]>;
|
||||
getRange(keyLowRange: KeyType, keyHighRange: KeyType, lowRangeExclusive?: boolean, highRangeExclusive?: boolean, reverseOrSortOrder?: boolean | QuerySortOrder, limit?: number, offset?: number): Promise<ItemType[]>;
|
||||
getKeysForRange(keyLowRange: KeyType, keyHighRange: KeyType, lowRangeExclusive?: boolean, highRangeExclusive?: boolean): Promise<any[]>;
|
||||
/**
|
||||
* Utility function to simplify offset/limit checks and allow a negative offset. Retrieves values associated with the given key
|
||||
* @param key primary key
|
||||
* @param limit
|
||||
* @param offset can be neagtive, treated the same way as 0
|
||||
* @param reverse
|
||||
* @returns value associated with given key, undefined if the key is not found.
|
||||
*/
|
||||
private _getKeyValues;
|
||||
private _getKeysForRange;
|
||||
private _getKeyCountForRange;
|
||||
countAll(): Promise<number>;
|
||||
countOnly(key: KeyType): Promise<number>;
|
||||
countRange(keyLowRange: KeyType, keyHighRange: KeyType, lowRangeExclusive?: boolean, highRangeExclusive?: boolean): Promise<number>;
|
||||
|
|
|
@ -11,9 +11,11 @@ declare global {
|
|||
documentMode: number;
|
||||
}
|
||||
}
|
||||
export declare const MAX_COUNT = 4294967295;
|
||||
export declare function isIE(): boolean;
|
||||
export declare function isSafari(): boolean;
|
||||
export declare function arrayify<T>(obj: T | T[]): T[];
|
||||
export declare function trimArray<TValue>(array: Array<TValue>, trimLength: number): Array<TValue>;
|
||||
export declare function getSerializedKeyForKeypath(obj: any, keyPathRaw: KeyPathType): string | undefined;
|
||||
export declare function getKeyForKeypath(obj: any, keyPathRaw: KeyPathType): KeyType | undefined;
|
||||
export declare function getValueForSingleKeypath(obj: any, singleKeyPath: string): any;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* NoSqlProvider provider setup for a non-persisted in-memory database backing provider.
|
||||
*/
|
||||
|
||||
import { attempt, isError, each, includes, compact, map, find, values, flatten } from 'lodash';
|
||||
import { attempt, isError, each, includes, compact, map, find, values, flatten, dropRight, takeRight, drop, take } from 'lodash';
|
||||
import { DbIndexFTSFromRangeQueries, getFullTextIndexWordsForItem } from './FullTextSearchHelpers';
|
||||
import {
|
||||
StoreSchema, DbProvider, DbSchema, DbTransaction,
|
||||
|
@ -14,12 +14,12 @@ import {
|
|||
} from './NoSqlProvider';
|
||||
import {
|
||||
arrayify, serializeKeyToString, formListOfSerializedKeys,
|
||||
getSerializedKeyForKeypath, getValueForSingleKeypath
|
||||
getSerializedKeyForKeypath, getValueForSingleKeypath, MAX_COUNT, trimArray
|
||||
} from './NoSqlProviderUtils';
|
||||
import { TransactionToken, TransactionLockHelper } from './TransactionLockHelper';
|
||||
import {
|
||||
empty, RedBlackTreeStructure, set, iterateFromIndex,
|
||||
iterateKeysFromFirst, get, iterateKeysFromLast, has, remove
|
||||
iterateKeysFromFirst, get, iterateKeysFromLast, has, remove, iterateFromFirst
|
||||
} from '@collectable/red-black-tree';
|
||||
export interface StoreData {
|
||||
data: Map<string, ItemType>;
|
||||
|
@ -405,20 +405,50 @@ class InMemoryIndex extends DbIndexFTSFromRangeQueries {
|
|||
}
|
||||
|
||||
getAll(reverseOrSortOrder?: boolean | QuerySortOrder, limit?: number, offset?: number): Promise<ItemType[]> {
|
||||
limit = limit ? limit : this._rbIndex._size;
|
||||
limit = limit ? limit :
|
||||
this.isUniqueIndex() ? this._rbIndex._size :
|
||||
MAX_COUNT;
|
||||
offset = offset ? offset : 0;
|
||||
const data = new Array<ItemType>(limit);
|
||||
const reverse = (reverseOrSortOrder === true || reverseOrSortOrder === QuerySortOrder.Reverse);
|
||||
const iterator = iterateFromIndex(reverse, offset, this._rbIndex);
|
||||
// when index is not unique, we cannot use offset as a starting index
|
||||
const iterator = iterateFromIndex(reverse, this.isUniqueIndex() ? offset : 0, this._rbIndex);
|
||||
let i = 0;
|
||||
for (const item of iterator) {
|
||||
data[i] = item.value[0];
|
||||
i++;
|
||||
// when index is not unique, each node may contain multiple items
|
||||
if (!this.isUniqueIndex()){
|
||||
let count = item.value.length;
|
||||
const minOffsetCount = Math.min(count, offset);
|
||||
count -= minOffsetCount;
|
||||
offset -= minOffsetCount;
|
||||
// we have skipped all values in this index, go to the next one
|
||||
if (count === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const values = this._getKeyValues(item.key, limit - i, item.value.length - count, reverse)
|
||||
|
||||
values.forEach((v, j) => {
|
||||
data[i + j] = v;
|
||||
});
|
||||
|
||||
i += values.length;
|
||||
} else {
|
||||
data[i] = item.value[0];
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i >= limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Promise.resolve(data);
|
||||
// if index is not unique, trim the empty slots in data
|
||||
// if we used MAX_COUNT to construct it.
|
||||
if (!this.isUniqueIndex() && i!==limit) {
|
||||
return Promise.resolve(trimArray(data, i));
|
||||
} else {
|
||||
return Promise.resolve(data);
|
||||
}
|
||||
}
|
||||
|
||||
getOnly(key: KeyType, reverseOrSortOrder?: boolean | QuerySortOrder, limit?: number, offset?: number)
|
||||
|
@ -428,35 +458,56 @@ class InMemoryIndex extends DbIndexFTSFromRangeQueries {
|
|||
|
||||
getRange(keyLowRange: KeyType, keyHighRange: KeyType, lowRangeExclusive?: boolean, highRangeExclusive?: boolean,
|
||||
reverseOrSortOrder?: boolean | QuerySortOrder, limit?: number, offset?: number): Promise<ItemType[]> {
|
||||
const values = attempt(() => {
|
||||
const reverse = reverseOrSortOrder === true || reverseOrSortOrder === QuerySortOrder.Reverse;
|
||||
limit = limit ? limit : this._rbIndex._size;
|
||||
offset = offset ? offset : 0;
|
||||
const keyLow = serializeKeyToString(keyLowRange, this._keyPath);
|
||||
const keyHigh = serializeKeyToString(keyHighRange, this._keyPath);
|
||||
const iterator = reverse ? iterateKeysFromLast(this._rbIndex) : iterateKeysFromFirst(this._rbIndex);
|
||||
let values = [] as ItemType[];
|
||||
for (const key of iterator) {
|
||||
if (
|
||||
(key > keyLow || (key === keyLow && !lowRangeExclusive)) &&
|
||||
(key < keyHigh || (key === keyHigh && !highRangeExclusive))) {
|
||||
if (offset > 0) {
|
||||
offset--;
|
||||
continue;
|
||||
const values = attempt(() => {
|
||||
const reverse = reverseOrSortOrder === true || reverseOrSortOrder === QuerySortOrder.Reverse;
|
||||
limit = limit ? limit :
|
||||
this.isUniqueIndex() ? this._rbIndex._size :
|
||||
MAX_COUNT;
|
||||
offset = offset ? offset : 0;
|
||||
const keyLow = serializeKeyToString(keyLowRange, this._keyPath);
|
||||
const keyHigh = serializeKeyToString(keyHighRange, this._keyPath);
|
||||
const iterator = reverse ? iterateKeysFromLast(this._rbIndex) : iterateKeysFromFirst(this._rbIndex);
|
||||
let values = [] as ItemType[];
|
||||
for (const key of iterator) {
|
||||
if (
|
||||
(key > keyLow || (key === keyLow && !lowRangeExclusive)) &&
|
||||
(key < keyHigh || (key === keyHigh && !highRangeExclusive))) {
|
||||
if (offset > 0) {
|
||||
if (this.isUniqueIndex()) {
|
||||
offset--;
|
||||
continue;
|
||||
} else {
|
||||
const idxValues = get(key, this._rbIndex) as ItemType[];
|
||||
offset -= idxValues.length;
|
||||
// if offset >= 0, we skipped just enough, or we still need to skip more
|
||||
// if offset < 0, we need to get some of the values from the index
|
||||
if (offset >= 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (values.length >= limit) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.isUniqueIndex()) {
|
||||
values = values.concat(get(key, this._rbIndex) as ItemType[]);
|
||||
} else {
|
||||
values = values.concat(this._getKeyValues(key, limit - values.length, Math.abs(offset), reverse));
|
||||
|
||||
if (offset < 0) {
|
||||
offset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (values.length >= limit) {
|
||||
break;
|
||||
}
|
||||
values = values.concat(get(key, this._rbIndex) as ItemType[]);
|
||||
}
|
||||
return values;
|
||||
});
|
||||
if (isError(values)) {
|
||||
return Promise.reject(values);
|
||||
}
|
||||
return values;
|
||||
});
|
||||
if (isError(values)) {
|
||||
return Promise.reject(values);
|
||||
}
|
||||
|
||||
return Promise.resolve(values);
|
||||
|
||||
return Promise.resolve(values);
|
||||
}
|
||||
|
||||
getKeysForRange(keyLowRange: KeyType, keyHighRange: KeyType, lowRangeExclusive?: boolean, highRangeExclusive?: boolean)
|
||||
|
@ -470,6 +521,44 @@ class InMemoryIndex extends DbIndexFTSFromRangeQueries {
|
|||
return Promise.resolve(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to simplify offset/limit checks and allow a negative offset. Retrieves values associated with the given key
|
||||
* @param key primary key
|
||||
* @param limit
|
||||
* @param offset can be neagtive, treated the same way as 0
|
||||
* @param reverse
|
||||
* @returns value associated with given key, undefined if the key is not found.
|
||||
*/
|
||||
private _getKeyValues(key: string, limit: number, offset: number, reverse: boolean): ItemType[] {
|
||||
if (limit <= 0) {
|
||||
return [];
|
||||
}
|
||||
const idxValues = get(key, this._rbIndex) as ItemType[];
|
||||
|
||||
// get may return undefined, if the key is not found
|
||||
if (!idxValues) {
|
||||
return idxValues;
|
||||
}
|
||||
|
||||
if (offset >= idxValues.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Perf optimisation. No items to skip, and limit is at least the number of items we have in the index.
|
||||
// we know that we will need the whole index values array to fulfill the results,
|
||||
// skip using take/drop, return the whole array immediately.
|
||||
if (offset <= 0 && limit >= idxValues.length) {
|
||||
return reverse ? idxValues.slice().reverse() : idxValues;
|
||||
}
|
||||
|
||||
|
||||
const itemsToDrop = Math.min(limit, offset);
|
||||
const itemsToTake = Math.min(limit, idxValues.length - offset);
|
||||
return reverse ?
|
||||
takeRight(dropRight(idxValues, itemsToDrop), itemsToTake)
|
||||
: take(drop(idxValues, itemsToDrop), itemsToTake);
|
||||
}
|
||||
|
||||
// Warning: This function can throw, make sure to trap.
|
||||
private _getKeysForRange(keyLowRange: KeyType, keyHighRange: KeyType,
|
||||
lowRangeExclusive?: boolean, highRangeExclusive?: boolean): string[] {
|
||||
|
@ -485,8 +574,43 @@ class InMemoryIndex extends DbIndexFTSFromRangeQueries {
|
|||
return keys;
|
||||
}
|
||||
|
||||
// Warning: This function can throw, make sure to trap.
|
||||
private _getKeyCountForRange(keyLowRange: KeyType, keyHighRange: KeyType,
|
||||
lowRangeExclusive?: boolean, highRangeExclusive?: boolean): number {
|
||||
const keyLow = serializeKeyToString(keyLowRange, this._keyPath);
|
||||
const keyHigh = serializeKeyToString(keyHighRange, this._keyPath);
|
||||
const iterator = iterateFromFirst(this._rbIndex);
|
||||
let keyCount = 0;
|
||||
for (const item of iterator) {
|
||||
if ((item.key > keyLow || (item.key === keyLow && !lowRangeExclusive)) && (item.key < keyHigh || (item.key === keyHigh && !highRangeExclusive))) {
|
||||
if (this.isUniqueIndex()) {
|
||||
keyCount++;
|
||||
} else {
|
||||
keyCount += item.value.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
return keyCount;
|
||||
}
|
||||
|
||||
countAll(): Promise<number> {
|
||||
return Promise.resolve(this._rbIndex._size);
|
||||
if (this.isUniqueIndex()) {
|
||||
return Promise.resolve(this._rbIndex._size);
|
||||
} else {
|
||||
const keyCount = attempt(() => {
|
||||
const iterator = iterateFromFirst(this._rbIndex);
|
||||
let keyCount = 0;
|
||||
for (const item of iterator) {
|
||||
keyCount += item.value.length;
|
||||
}
|
||||
return keyCount;
|
||||
});
|
||||
if (isError(keyCount)) {
|
||||
return Promise.reject(keyCount);
|
||||
}
|
||||
|
||||
return Promise.resolve(keyCount);
|
||||
}
|
||||
}
|
||||
|
||||
countOnly(key: KeyType): Promise<number> {
|
||||
|
@ -495,13 +619,27 @@ class InMemoryIndex extends DbIndexFTSFromRangeQueries {
|
|||
|
||||
countRange(keyLowRange: KeyType, keyHighRange: KeyType, lowRangeExclusive?: boolean, highRangeExclusive?: boolean)
|
||||
: Promise<number> {
|
||||
const keys = attempt(() => {
|
||||
return this._getKeysForRange(keyLowRange, keyHighRange, lowRangeExclusive, highRangeExclusive);
|
||||
});
|
||||
if (isError(keys)) {
|
||||
return Promise.reject(keys);
|
||||
}
|
||||
if (this.isUniqueIndex()) {
|
||||
const keys = attempt(() => {
|
||||
return this._getKeysForRange(keyLowRange, keyHighRange, lowRangeExclusive, highRangeExclusive);
|
||||
});
|
||||
|
||||
return Promise.resolve(keys.length);
|
||||
if (isError(keys)) {
|
||||
return Promise.reject(keys);
|
||||
}
|
||||
|
||||
return Promise.resolve(keys.length);
|
||||
} else {
|
||||
const keyCount = attempt(() => {
|
||||
return this._getKeyCountForRange(keyLowRange, keyHighRange, lowRangeExclusive, highRangeExclusive);
|
||||
});
|
||||
|
||||
if (isError(keyCount)) {
|
||||
return Promise.reject(keyCount);
|
||||
}
|
||||
|
||||
return Promise.resolve(keyCount);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,6 +17,9 @@ declare global {
|
|||
}
|
||||
}
|
||||
|
||||
// Max array length (uint): 2^32 - 1
|
||||
export const MAX_COUNT = 4294967295;
|
||||
|
||||
export function isIE() {
|
||||
return (typeof (document) !== 'undefined' && document.all !== null && document.documentMode <= 11) ||
|
||||
(typeof (navigator) !== 'undefined' && !!navigator.userAgent && navigator.userAgent.indexOf('Edge/') !== -1);
|
||||
|
@ -32,6 +35,18 @@ export function arrayify<T>(obj: T | T[]): T[] {
|
|||
return isArray(obj) ? <T[]>obj : [<T>obj];
|
||||
}
|
||||
|
||||
export function trimArray<TValue>(array: Array<TValue>, trimLength: number): Array<TValue> {
|
||||
if (trimLength < 0 || array.length < trimLength) {
|
||||
return array.slice();
|
||||
}
|
||||
|
||||
let ret = new Array<TValue>(trimLength);
|
||||
for(let j = 0; j < trimLength; j++) {
|
||||
ret[j] = array[j];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Constant string for joining compound keypaths for websql and IE indexeddb. There may be marginal utility in using a more obscure
|
||||
// string sequence.
|
||||
const keypathJoinerString = '%&';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as assert from 'assert';
|
||||
import { find, each, times, values, keys, some } from 'lodash';
|
||||
import { find, each, times, values, keys, some, filter } from 'lodash';
|
||||
|
||||
import { KeyComponentType, DbSchema, DbProvider, openListOfProviders, QuerySortOrder, FullTextTermResolution, IDBCloseConnectionPayload, OnCloseHandler } from '../NoSqlProvider';
|
||||
|
||||
|
@ -469,6 +469,223 @@ describe('NoSqlProvider', function () {
|
|||
});
|
||||
};
|
||||
|
||||
var nonUniqueTester = (prov: DbProvider, indexName: string | undefined, compound: boolean,
|
||||
setter: (obj: any, indexval1: string, indexval2: string) => void) => {
|
||||
var putters = [1, 1, 1, 2, 2, 3, 3, 3, 4, 4, 5, 5].map((v, idx) => {
|
||||
var obj: TestObj = { val: 'val' + v };
|
||||
if (indexName) {
|
||||
obj.id = 'id' + (idx + 1);
|
||||
}
|
||||
setter(obj, 'indexa' + v, 'indexb' + v);
|
||||
return prov.put('test', obj);
|
||||
});
|
||||
|
||||
return Promise.all(putters).then(() => {
|
||||
let formIndex = (i: number, i2: number = i): string | string[] => {
|
||||
if (compound) {
|
||||
return ['indexa' + i, 'indexb' + i2];
|
||||
} else {
|
||||
return 'indexa' + i;
|
||||
}
|
||||
};
|
||||
|
||||
let t0 = prov.getMultiple('test', compound ? formIndex(1, 1) : 'indexa1', indexName).then(retVal => {
|
||||
const ret = retVal as TestObj[];
|
||||
assert.equal(ret.length, 3, 'getMultiple');
|
||||
[1].forEach(v => { assert(find(ret, r => r.val === 'val' + v), 'cant find ' + v); });
|
||||
});
|
||||
|
||||
let t1 = prov.getAll('test', indexName).then(retVal => {
|
||||
const ret = retVal as TestObj[];
|
||||
assert.equal(ret.length, 12, 'getAll');
|
||||
[1, 2, 3, 4, 5].forEach(v => { assert(find(ret, r => r.val === 'val' + v), 'cant find ' + v); });
|
||||
});
|
||||
|
||||
let t1count = prov.countAll('test', indexName).then(ret => {
|
||||
assert.equal(ret, 12, 'countAll');
|
||||
});
|
||||
|
||||
let t1b = prov.getAll('test', indexName, false, 3).then(retVal => {
|
||||
const ret = retVal as TestObj[];
|
||||
assert.equal(ret.length, 3, 'getAll lim3');
|
||||
[1].forEach(v => { assert(filter(ret, r => r.val === 'val' + v).length === 3, 'cant find enough ' + v); });
|
||||
});
|
||||
|
||||
let t1c = prov.getAll('test', indexName, false, 3, 1).then(retVal => {
|
||||
const ret = retVal as TestObj[];
|
||||
assert.equal(ret.length, 3, 'getAll lim3 off1');
|
||||
[2, 3, 4].forEach(v => { assert(find(ret, r => r.id === 'id' + v), 'cant find id ' + v); });
|
||||
});
|
||||
|
||||
let t2 = prov.getOnly('test', indexName, formIndex(3)).then(ret => {
|
||||
assert.equal(ret.length, 3, 'getOnly');
|
||||
assert.equal((ret[0] as TestObj).val, 'val3');
|
||||
assert.equal((ret[1] as TestObj).val, 'val3');
|
||||
assert.equal((ret[2] as TestObj).val, 'val3');
|
||||
});
|
||||
|
||||
let t2count = prov.countOnly('test', indexName, formIndex(3)).then(ret => {
|
||||
assert.equal(ret, 3, 'countOnly');
|
||||
});
|
||||
|
||||
let t3 = prov.getRange('test', indexName, formIndex(2), formIndex(4)).then(retVal => {
|
||||
const ret = retVal as TestObj[];
|
||||
assert.equal(ret.length, 7, 'getRange++');
|
||||
[4, 5, 6, 7, 8, 9, 10].forEach(v => { assert(find(ret, r => r.id === 'id' + v)); });
|
||||
});
|
||||
|
||||
let t3count = prov.countRange('test', indexName, formIndex(2), formIndex(4)).then(ret => {
|
||||
assert.equal(ret, 7, 'countRange++');
|
||||
});
|
||||
|
||||
let t3b = prov.getRange('test', indexName, formIndex(2), formIndex(4), false, false, false, 1)
|
||||
.then(retVal => {
|
||||
const ret = retVal as TestObj[];
|
||||
assert.equal(ret.length, 1, 'getRange++ lim1');
|
||||
[2].forEach(v => { assert(find(ret, r => r.val === 'val' + v)); });
|
||||
});
|
||||
|
||||
let t3b2 = prov.getRange('test', indexName, formIndex(2), formIndex(4), false, false, false, 1)
|
||||
.then(retVal => {
|
||||
const ret = retVal as TestObj[];
|
||||
assert.equal(ret.length, 1, 'getRange++ lim1');
|
||||
[2].forEach(v => { assert(find(ret, r => r.val === 'val' + v)); });
|
||||
});
|
||||
|
||||
let t3b3 = prov.getRange('test', indexName, formIndex(2), formIndex(4), false, false,
|
||||
QuerySortOrder.Forward, 1)
|
||||
.then(retVal => {
|
||||
const ret = retVal as TestObj[];
|
||||
assert.equal(ret.length, 1, 'getRange++ lim1');
|
||||
[2].forEach(v => { assert(find(ret, r => r.val === 'val' + v)); });
|
||||
});
|
||||
|
||||
let t3b4 = prov.getRange('test', indexName, formIndex(2), formIndex(4), false, false,
|
||||
QuerySortOrder.Reverse, 1)
|
||||
.then(retVal => {
|
||||
const ret = retVal as TestObj[];
|
||||
assert.equal(ret.length, 1, 'getRange++ lim1 rev');
|
||||
[4].forEach(v => { assert(find(ret, r => r.val === 'val' + v)); });
|
||||
});
|
||||
|
||||
let t3c = prov.getRange('test', indexName, formIndex(2), formIndex(4), false, false, false, 1, 1)
|
||||
.then(retVal => {
|
||||
const ret = retVal as TestObj[];
|
||||
assert.equal(ret.length, 1, 'getRange++ lim1 off1');
|
||||
[5].forEach(v => { assert(find(ret, r => r.id === 'id' + v)); });
|
||||
});
|
||||
|
||||
let t3d = prov.getRange('test', indexName, formIndex(2), formIndex(4), false, false, false, 2, 1)
|
||||
.then(retVal => {
|
||||
const ret = retVal as TestObj[];
|
||||
assert.equal(ret.length, 2, 'getRange++ lim2 off1');
|
||||
[5, 6].forEach(v => { assert(find(ret, r => r.id === 'id' + v)); });
|
||||
});
|
||||
|
||||
let t3d2 = prov.getRange('test', indexName, formIndex(2), formIndex(4), false, false,
|
||||
QuerySortOrder.Forward, 2, 1)
|
||||
.then(retVal => {
|
||||
const ret = retVal as TestObj[];
|
||||
assert.equal(ret.length, 2, 'getRange++ lim2 off1');
|
||||
[5, 6].forEach(v => { assert(find(ret, r => r.id === 'id' + v)); });
|
||||
});
|
||||
|
||||
let t3d3 = prov.getRange('test', indexName, formIndex(2), formIndex(4), false, false, true, 2, 1)
|
||||
.then(retVal => {
|
||||
const ret = retVal as TestObj[];
|
||||
assert.equal(ret.length, 2, 'getRange++ lim2 off1 rev');
|
||||
assert.equal((ret[0] as TestObj).val, 'val4');
|
||||
[9, 8].forEach(v => { assert(find(ret, r => r.id === 'id' + v)); });
|
||||
});
|
||||
|
||||
let t3d4 = prov.getRange('test', indexName, formIndex(2), formIndex(4), false, false,
|
||||
QuerySortOrder.Reverse, 2, 1)
|
||||
.then(retVal => {
|
||||
const ret = retVal as TestObj[];
|
||||
assert.equal(ret.length, 2, 'getRange++ lim2 off1 rev');
|
||||
assert.equal((ret[0] as TestObj).val, 'val4');
|
||||
[9, 8].forEach(v => { assert(find(ret, r => r.id === 'id' + v)); });
|
||||
});
|
||||
|
||||
let t4 = prov.getRange('test', indexName, formIndex(2), formIndex(4), true, false).then(retVal => {
|
||||
const ret = retVal as TestObj[];
|
||||
assert.equal(ret.length, 5, 'getRange-+');
|
||||
[6, 7, 8, 9, 10].forEach(v => { assert(find(ret, r => r.id === 'id' + v)); });
|
||||
});
|
||||
|
||||
let t4count = prov.countRange('test', indexName, formIndex(2), formIndex(4), true, false).then(ret => {
|
||||
assert.equal(ret, 5, 'countRange-+');
|
||||
});
|
||||
|
||||
let t5 = prov.getRange('test', indexName, formIndex(2), formIndex(4), false, true).then(retVal => {
|
||||
const ret = retVal as TestObj[];
|
||||
assert.equal(ret.length, 5, 'getRange+-');
|
||||
[4, 5, 6, 7, 8].forEach(v => { assert(find(ret, r => r.id === 'id' + v)); });
|
||||
});
|
||||
|
||||
let t5count = prov.countRange('test', indexName, formIndex(2), formIndex(4), false, true).then(ret => {
|
||||
assert.equal(ret, 5, 'countRange+-');
|
||||
});
|
||||
|
||||
let t6 = prov.getRange('test', indexName, formIndex(2), formIndex(4), true, true).then(retVal => {
|
||||
const ret = retVal as TestObj[];
|
||||
assert.equal(ret.length, 3, 'getRange--');
|
||||
[6, 7, 8].forEach(v => { assert(find(ret, r => r.id === 'id' + v)); });
|
||||
});
|
||||
|
||||
let t6count = prov.countRange('test', indexName, formIndex(2), formIndex(4), true, true).then(ret => {
|
||||
assert.equal(ret, 3, 'countRange--');
|
||||
});
|
||||
|
||||
return Promise.all([t0, t1, t1count, t1b, t1c, t2, t2count, t3, t3count, t3b, t3b2, t3b3, t3b4, t3c, t3d, t3d2, t3d3,
|
||||
t3d4, t4, t4count, t5, t5count, t6, t6count]).then(() => {
|
||||
if (compound) {
|
||||
let tt1 = prov.getRange('test', indexName, formIndex(2, 2), formIndex(4, 3))
|
||||
.then(retVal => {
|
||||
const ret = retVal as TestObj[];
|
||||
assert.equal(ret.length, 2, 'getRange2++');
|
||||
[2, 3].forEach(v => { assert(find(ret, r => r.val === 'val' + v)); });
|
||||
});
|
||||
|
||||
let tt1count = prov.countRange('test', indexName, formIndex(2, 2), formIndex(4, 3))
|
||||
.then(ret => {
|
||||
assert.equal(ret, 2, 'countRange2++');
|
||||
});
|
||||
|
||||
let tt2 = prov.getRange('test', indexName, formIndex(2, 2), formIndex(4, 3), false, true)
|
||||
.then(retVal => {
|
||||
const ret = retVal as TestObj[];
|
||||
assert.equal(ret.length, 2, 'getRange2+-');
|
||||
[2, 3].forEach(v => { assert(find(ret, r => r.val === 'val' + v)); });
|
||||
});
|
||||
|
||||
let tt2count = prov.countRange('test', indexName, formIndex(2, 2), formIndex(4, 3), false, true)
|
||||
.then(ret => {
|
||||
assert.equal(ret, 2, 'countRange2+-');
|
||||
});
|
||||
|
||||
let tt3 = prov.getRange('test', indexName, formIndex(2, 2), formIndex(4, 3), true, false)
|
||||
.then(retVal => {
|
||||
const ret = retVal as TestObj[];
|
||||
assert.equal(ret.length, 1, 'getRange2-+');
|
||||
[3].forEach(v => { assert(find(ret, r => r.val === 'val' + v)); });
|
||||
});
|
||||
|
||||
let tt3count = prov.countRange('test', indexName, formIndex(2, 2), formIndex(4, 3), true, false)
|
||||
.then(ret => {
|
||||
assert.equal(ret, 1, 'countRange2-+');
|
||||
});
|
||||
|
||||
return Promise.all([tt1, tt1count, tt2, tt2count, tt3, tt3count]).then(() => {
|
||||
return prov.close();
|
||||
});
|
||||
} else {
|
||||
return prov.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
it('Simple primary key put/get/getAll', (done) => {
|
||||
openProvider(provName, {
|
||||
version: 1,
|
||||
|
@ -1178,6 +1395,28 @@ describe('NoSqlProvider', function () {
|
|||
}).then(() => done(), (err) => done(err));
|
||||
});
|
||||
|
||||
it('Simple non-unique index put/get, getAll, getOnly, and getRange (includeData)', (done) => {
|
||||
openProvider(provName, {
|
||||
version: 1,
|
||||
stores: [
|
||||
{
|
||||
name: 'test',
|
||||
primaryKeyPath: 'id',
|
||||
indexes: [
|
||||
{
|
||||
name: 'index',
|
||||
keyPath: 'a',
|
||||
unique: false,
|
||||
includeDataInIndex: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}, true).then(prov => {
|
||||
return nonUniqueTester(prov, 'index', false, (obj, v) => { obj.a = v; });
|
||||
}).then(() => done(), (err) => done(err));
|
||||
});
|
||||
|
||||
describe('Transaction Semantics', () => {
|
||||
it('Testing transaction expiration', (done) => {
|
||||
openProvider(provName, {
|
||||
|
@ -2572,7 +2811,8 @@ describe('NoSqlProvider', function () {
|
|||
indexes: [{
|
||||
name: 'i',
|
||||
keyPath: 'txt',
|
||||
fullText: true
|
||||
fullText: true,
|
||||
unique: false
|
||||
}]
|
||||
}
|
||||
]
|
||||
|
@ -2580,6 +2820,7 @@ describe('NoSqlProvider', function () {
|
|||
return prov.put('test', [
|
||||
{ id: 'a1', txt: 'the quick brown fox jumps over the lăzy dog who is a bro with brows' },
|
||||
{ id: 'a2', txt: 'bob likes his dog' },
|
||||
{ id: 'a8', txt: 'mark marley' },
|
||||
{ id: 'a3', txt: 'tes>ter' },
|
||||
{
|
||||
id: 'a4',
|
||||
|
@ -2599,7 +2840,9 @@ describe('NoSqlProvider', function () {
|
|||
// Test data to make sure that we don't search for empty strings (... used to put empty string to the index)
|
||||
id: 'a7',
|
||||
txt: 'User1, User2, User3 ...'
|
||||
}
|
||||
},
|
||||
{ id: 'a9', txt: 'mark dunnford' },
|
||||
{ id: 'a10', txt: 'alice cooper' },
|
||||
]).then(() => {
|
||||
const p1 = prov.fullTextSearch('test', 'i', 'brown').then((res: any[]) => {
|
||||
assert.equal(res.length, 1);
|
||||
|
@ -2716,9 +2959,17 @@ describe('NoSqlProvider', function () {
|
|||
const p31 = prov.fullTextSearch('test', 'i', '!@#$%$', FullTextTermResolution.Or).then(res => {
|
||||
assert.equal(res.length, 0);
|
||||
});
|
||||
const p32 = prov.fullTextSearch('test', 'i', 'mark').then((res: any[]) => {
|
||||
assert.equal(res.length, 2);
|
||||
assert.ok(some(res, r => r.id === 'a8') && some(res, r => r.id === 'a9'));
|
||||
});
|
||||
const p33 = prov.fullTextSearch('test', 'i', 'mark', FullTextTermResolution.Or, 10).then((res: any[]) => {
|
||||
assert.equal(res.length, 2);
|
||||
assert.ok(some(res, r => r.id === 'a8') && some(res, r => r.id === 'a9'));
|
||||
});
|
||||
|
||||
return Promise.all([p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, p20,
|
||||
p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31]).then(() => {
|
||||
p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, p32, p33]).then(() => {
|
||||
return prov.close();
|
||||
});
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче