You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-06 02:15:48 +03:00
Merge branch 'master' of github.com:redis/node-redis
This commit is contained in:
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
node-version: ['12', '14', '16']
|
node-version: ['12', '14', '16']
|
||||||
redis-version: ['5', '6.0', '6.2']
|
redis-version: ['5', '6.0', '6.2', '7.0-rc2']
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2.3.4
|
- uses: actions/checkout@v2.3.4
|
||||||
with:
|
with:
|
||||||
|
@@ -13,20 +13,32 @@ describe('ACL GETUSER', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testUtils.testWithClient('client.aclGetUser', async client => {
|
testUtils.testWithClient('client.aclGetUser', async client => {
|
||||||
|
const expectedReply: any = {
|
||||||
|
passwords: [],
|
||||||
|
commands: '+@all',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (testUtils.isVersionGreaterThan([7])) {
|
||||||
|
expectedReply.flags = ['on', 'nopass'];
|
||||||
|
expectedReply.keys = '~*';
|
||||||
|
expectedReply.channels = '&*';
|
||||||
|
expectedReply.selectors = [];
|
||||||
|
} else {
|
||||||
|
expectedReply.keys = ['*'];
|
||||||
|
expectedReply.selectors = undefined;
|
||||||
|
|
||||||
|
if (testUtils.isVersionGreaterThan([6, 2])) {
|
||||||
|
expectedReply.flags = ['on', 'allkeys', 'allchannels', 'allcommands', 'nopass'];
|
||||||
|
expectedReply.channels = ['*'];
|
||||||
|
} else {
|
||||||
|
expectedReply.flags = ['on', 'allkeys', 'allcommands', 'nopass'];
|
||||||
|
expectedReply.channels = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
await client.aclGetUser('default'),
|
await client.aclGetUser('default'),
|
||||||
{
|
expectedReply
|
||||||
passwords: [],
|
|
||||||
commands: '+@all',
|
|
||||||
keys: ['*'],
|
|
||||||
...(testUtils.isVersionGreaterThan([6, 2]) ? {
|
|
||||||
flags: ['on', 'allkeys', 'allchannels', 'allcommands', 'nopass'],
|
|
||||||
channels: ['*']
|
|
||||||
} : {
|
|
||||||
flags: ['on', 'allkeys', 'allcommands', 'nopass'],
|
|
||||||
channels: undefined
|
|
||||||
})
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}, GLOBAL.SERVERS.OPEN);
|
}, GLOBAL.SERVERS.OPEN);
|
||||||
});
|
});
|
||||||
|
@@ -5,24 +5,27 @@ export function transformArguments(username: RedisCommandArgument): RedisCommand
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AclGetUserRawReply = [
|
type AclGetUserRawReply = [
|
||||||
_: RedisCommandArgument,
|
'flags',
|
||||||
flags: Array<RedisCommandArgument>,
|
Array<RedisCommandArgument>,
|
||||||
_: RedisCommandArgument,
|
'passwords',
|
||||||
passwords: Array<RedisCommandArgument>,
|
Array<RedisCommandArgument>,
|
||||||
_: RedisCommandArgument,
|
'commands',
|
||||||
commands: RedisCommandArgument,
|
RedisCommandArgument,
|
||||||
_: RedisCommandArgument,
|
'keys',
|
||||||
keys: Array<RedisCommandArgument>,
|
Array<RedisCommandArgument> | RedisCommandArgument,
|
||||||
_: RedisCommandArgument,
|
'channels',
|
||||||
channels: Array<RedisCommandArgument>
|
Array<RedisCommandArgument> | RedisCommandArgument,
|
||||||
|
'selectors' | undefined,
|
||||||
|
Array<Array<string>> | undefined
|
||||||
];
|
];
|
||||||
|
|
||||||
interface AclUser {
|
interface AclUser {
|
||||||
flags: Array<RedisCommandArgument>;
|
flags: Array<RedisCommandArgument>;
|
||||||
passwords: Array<RedisCommandArgument>;
|
passwords: Array<RedisCommandArgument>;
|
||||||
commands: RedisCommandArgument;
|
commands: RedisCommandArgument;
|
||||||
keys: Array<RedisCommandArgument>;
|
keys: Array<RedisCommandArgument> | RedisCommandArgument;
|
||||||
channels: Array<RedisCommandArgument>
|
channels: Array<RedisCommandArgument> | RedisCommandArgument;
|
||||||
|
selectors?: Array<Array<string>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformReply(reply: AclGetUserRawReply): AclUser {
|
export function transformReply(reply: AclGetUserRawReply): AclUser {
|
||||||
@@ -31,6 +34,7 @@ export function transformReply(reply: AclGetUserRawReply): AclUser {
|
|||||||
passwords: reply[3],
|
passwords: reply[3],
|
||||||
commands: reply[5],
|
commands: reply[5],
|
||||||
keys: reply[7],
|
keys: reply[7],
|
||||||
channels: reply[9]
|
channels: reply[9],
|
||||||
|
selectors: reply[11]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -22,7 +22,7 @@ export function transformArguments(
|
|||||||
range.end.toString()
|
range.end.toString()
|
||||||
);
|
);
|
||||||
|
|
||||||
if (range?.mode) {
|
if (range.mode) {
|
||||||
args.push(range.mode);
|
args.push(range.mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,11 @@ export function assertPingCommand(commandInfo: CommandReply | null | undefined):
|
|||||||
{
|
{
|
||||||
name: 'ping',
|
name: 'ping',
|
||||||
arity: -1,
|
arity: -1,
|
||||||
flags: new Set([CommandFlags.STALE, CommandFlags.FAST]),
|
flags: new Set(
|
||||||
|
testUtils.isVersionGreaterThan([7]) ?
|
||||||
|
[CommandFlags.FAST] :
|
||||||
|
[CommandFlags.STALE, CommandFlags.FAST]
|
||||||
|
),
|
||||||
firstKeyIndex: 0,
|
firstKeyIndex: 0,
|
||||||
lastKeyIndex: 0,
|
lastKeyIndex: 0,
|
||||||
step: 0,
|
step: 0,
|
||||||
|
@@ -434,6 +434,26 @@ describe('AGGREGATE', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('with PARAMS', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('index', '*', {
|
||||||
|
PARAMS: {
|
||||||
|
param: 'value'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
['FT.AGGREGATE', 'index', '*', 'PARAMS', '2', 'param', 'value']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with DIALECT', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('index', '*', {
|
||||||
|
DIALECT: 1
|
||||||
|
}),
|
||||||
|
['FT.AGGREGATE', 'index', '*', 'DIALECT', '1']
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
testUtils.testWithClient('client.ft.aggregate', async client => {
|
testUtils.testWithClient('client.ft.aggregate', async client => {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '@node-redis/client/dist/lib/commands';
|
import { RedisCommandArgument, RedisCommandArguments } from '@node-redis/client/dist/lib/commands';
|
||||||
import { pushVerdictArgument, transformTuplesReply } from '@node-redis/client/dist/lib/commands/generic-transformers';
|
import { pushVerdictArgument, transformTuplesReply } from '@node-redis/client/dist/lib/commands/generic-transformers';
|
||||||
import { PropertyName, pushArgumentsWithLength, pushSortByArguments, SortByProperty } from '.';
|
import { Params, PropertyName, pushArgumentsWithLength, pushParamsArgs, pushSortByArguments, SortByProperty } from '.';
|
||||||
|
|
||||||
export enum AggregateSteps {
|
export enum AggregateSteps {
|
||||||
GROUPBY = 'GROUPBY',
|
GROUPBY = 'GROUPBY',
|
||||||
@@ -122,6 +122,8 @@ export interface AggregateOptions {
|
|||||||
VERBATIM?: true;
|
VERBATIM?: true;
|
||||||
LOAD?: LoadField | Array<LoadField>;
|
LOAD?: LoadField | Array<LoadField>;
|
||||||
STEPS?: Array<GroupByStep | SortStep | ApplyStep | LimitStep | FilterStep>;
|
STEPS?: Array<GroupByStep | SortStep | ApplyStep | LimitStep | FilterStep>;
|
||||||
|
PARAMS?: Params;
|
||||||
|
DIALECT?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformArguments(
|
export function transformArguments(
|
||||||
@@ -129,17 +131,16 @@ export function transformArguments(
|
|||||||
query: string,
|
query: string,
|
||||||
options?: AggregateOptions
|
options?: AggregateOptions
|
||||||
): RedisCommandArguments {
|
): RedisCommandArguments {
|
||||||
|
return pushAggregatehOptions(
|
||||||
const args = ['FT.AGGREGATE', index, query];
|
['FT.AGGREGATE', index, query],
|
||||||
pushAggregatehOptions(args, options);
|
options
|
||||||
return args;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pushAggregatehOptions(
|
export function pushAggregatehOptions(
|
||||||
args: RedisCommandArguments,
|
args: RedisCommandArguments,
|
||||||
options?: AggregateOptions
|
options?: AggregateOptions
|
||||||
): RedisCommandArguments {
|
): RedisCommandArguments {
|
||||||
|
|
||||||
if (options?.VERBATIM) {
|
if (options?.VERBATIM) {
|
||||||
args.push('VERBATIM');
|
args.push('VERBATIM');
|
||||||
}
|
}
|
||||||
@@ -202,6 +203,12 @@ export function pushAggregatehOptions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pushParamsArgs(args, options?.PARAMS);
|
||||||
|
|
||||||
|
if (options?.DIALECT) {
|
||||||
|
args.push('DIALECT', options.DIALECT.toString());
|
||||||
|
}
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,7 +264,6 @@ function pushGroupByReducer(args: RedisCommandArguments, reducer: GroupByReducer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import testUtils, { GLOBAL } from '../test-utils';
|
import testUtils, { GLOBAL } from '../test-utils';
|
||||||
import { transformArguments } from './CREATE';
|
import { transformArguments } from './CREATE';
|
||||||
import { SchemaFieldTypes, SchemaTextFieldPhonetics, RedisSearchLanguages } from '.';
|
import { SchemaFieldTypes, SchemaTextFieldPhonetics, RedisSearchLanguages, VectorAlgorithms } from '.';
|
||||||
|
|
||||||
describe('CREATE', () => {
|
describe('CREATE', () => {
|
||||||
describe('transformArguments', () => {
|
describe('transformArguments', () => {
|
||||||
@@ -126,6 +126,52 @@ describe('CREATE', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('VECTOR', () => {
|
||||||
|
it('Flat algorithm', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('index', {
|
||||||
|
field: {
|
||||||
|
type: SchemaFieldTypes.VECTOR,
|
||||||
|
ALGORITHM: VectorAlgorithms.FLAT,
|
||||||
|
TYPE: 'FLOAT32',
|
||||||
|
DIM: 2,
|
||||||
|
DISTANCE_METRIC: 'L2',
|
||||||
|
INITIAL_CAP: 1000000,
|
||||||
|
BLOCK_SIZE: 1000
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
'FT.CREATE', 'index', 'SCHEMA', 'field', 'VECTOR', 'FLAT', '10', 'TYPE',
|
||||||
|
'FLOAT32', 'DIM', '2', 'DISTANCE_METRIC', 'L2', 'INITIAL_CAP', '1000000',
|
||||||
|
'BLOCK_SIZE', '1000'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('HNSW algorithm', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('index', {
|
||||||
|
field: {
|
||||||
|
type: SchemaFieldTypes.VECTOR,
|
||||||
|
ALGORITHM: VectorAlgorithms.HNSW,
|
||||||
|
TYPE: 'FLOAT32',
|
||||||
|
DIM: 2,
|
||||||
|
DISTANCE_METRIC: 'L2',
|
||||||
|
INITIAL_CAP: 1000000,
|
||||||
|
M: 40,
|
||||||
|
EF_CONSTRUCTION: 250,
|
||||||
|
EF_RUNTIME: 20
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
'FT.CREATE', 'index', 'SCHEMA', 'field', 'VECTOR', 'HNSW', '14', 'TYPE',
|
||||||
|
'FLOAT32', 'DIM', '2', 'DISTANCE_METRIC', 'L2', 'INITIAL_CAP', '1000000',
|
||||||
|
'M', '40', 'EF_CONSTRUCTION', '250', 'EF_RUNTIME', '20'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('with generic options', () => {
|
describe('with generic options', () => {
|
||||||
it('with AS', () => {
|
it('with AS', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
|
@@ -2,10 +2,32 @@ import { strict as assert } from 'assert';
|
|||||||
import { transformArguments } from './EXPLAIN';
|
import { transformArguments } from './EXPLAIN';
|
||||||
|
|
||||||
describe('EXPLAIN', () => {
|
describe('EXPLAIN', () => {
|
||||||
it('transformArguments', () => {
|
describe('transformArguments', () => {
|
||||||
assert.deepEqual(
|
it('simple', () => {
|
||||||
transformArguments('index', '*'),
|
assert.deepEqual(
|
||||||
['FT.EXPLAIN', 'index', '*']
|
transformArguments('index', '*'),
|
||||||
);
|
['FT.EXPLAIN', 'index', '*']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with PARAMS', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('index', '*', {
|
||||||
|
PARAMS: {
|
||||||
|
param: 'value'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
['FT.EXPLAIN', 'index', '*', 'PARAMS', '2', 'param', 'value']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with DIALECT', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('index', '*', {
|
||||||
|
DIALECT: 1
|
||||||
|
}),
|
||||||
|
['FT.EXPLAIN', 'index', '*', 'DIALECT', '1']
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,26 @@
|
|||||||
|
import { Params, pushParamsArgs } from ".";
|
||||||
|
|
||||||
export const IS_READ_ONLY = true;
|
export const IS_READ_ONLY = true;
|
||||||
|
|
||||||
export function transformArguments(index: string, query: string): Array<string> {
|
interface ExplainOptions {
|
||||||
return ['FT.EXPLAIN', index, query];
|
PARAMS?: Params;
|
||||||
|
DIALECT?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformArguments(
|
||||||
|
index: string,
|
||||||
|
query: string,
|
||||||
|
options?: ExplainOptions
|
||||||
|
): Array<string> {
|
||||||
|
const args = ['FT.EXPLAIN', index, query];
|
||||||
|
|
||||||
|
pushParamsArgs(args, options?.PARAMS);
|
||||||
|
|
||||||
|
if (options?.DIALECT) {
|
||||||
|
args.push('DIALECT', options.DIALECT.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare function transformReply(): string;
|
export declare function transformReply(): string;
|
||||||
|
@@ -15,32 +15,56 @@ describe('INFO', () => {
|
|||||||
await client.ft.create('index', {
|
await client.ft.create('index', {
|
||||||
field: SchemaFieldTypes.TEXT
|
field: SchemaFieldTypes.TEXT
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
await client.ft.info('index'),
|
await client.ft.info('index'),
|
||||||
{
|
{
|
||||||
indexName: 'index',
|
indexName: 'index',
|
||||||
indexOptions: [],
|
indexOptions: [],
|
||||||
indexDefinition: {
|
indexDefinition: Object.create(null, {
|
||||||
defaultScore: '1',
|
default_score: {
|
||||||
keyType: 'HASH',
|
value: '1',
|
||||||
prefixes: ['']
|
configurable: true,
|
||||||
},
|
enumerable: true
|
||||||
attributes: [[
|
},
|
||||||
'identifier',
|
key_type: {
|
||||||
'field',
|
value: 'HASH',
|
||||||
'attribute',
|
configurable: true,
|
||||||
'field',
|
enumerable: true
|
||||||
'type',
|
},
|
||||||
'TEXT',
|
prefixes: {
|
||||||
'WEIGHT',
|
value: [''],
|
||||||
'1'
|
configurable: true,
|
||||||
]],
|
enumerable: true
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
attributes: [Object.create(null, {
|
||||||
|
identifier: {
|
||||||
|
value: 'field',
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true
|
||||||
|
},
|
||||||
|
attribute: {
|
||||||
|
value: 'field',
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
value: 'TEXT',
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true
|
||||||
|
},
|
||||||
|
WEIGHT: {
|
||||||
|
value: '1',
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true
|
||||||
|
}
|
||||||
|
})],
|
||||||
numDocs: '0',
|
numDocs: '0',
|
||||||
maxDocId: '0',
|
maxDocId: '0',
|
||||||
numTerms: '0',
|
numTerms: '0',
|
||||||
numRecords: '0',
|
numRecords: '0',
|
||||||
invertedSzMb: '0',
|
invertedSzMb: '0',
|
||||||
|
vectorIndexSzMb: '0',
|
||||||
totalInvertedIndexBlocks: '0',
|
totalInvertedIndexBlocks: '0',
|
||||||
offsetVectorsSzMb: '0',
|
offsetVectorsSzMb: '0',
|
||||||
docTableSizeMb: '0',
|
docTableSizeMb: '0',
|
||||||
@@ -67,7 +91,8 @@ describe('INFO', () => {
|
|||||||
globalTotal: 0,
|
globalTotal: 0,
|
||||||
indexCapacity: 128,
|
indexCapacity: 128,
|
||||||
idnexTotal: 0
|
idnexTotal: 0
|
||||||
}
|
},
|
||||||
|
stopWords: undefined
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}, GLOBAL.SERVERS.OPEN);
|
}, GLOBAL.SERVERS.OPEN);
|
||||||
|
@@ -1,121 +1,118 @@
|
|||||||
|
import { RedisCommandArgument } from '@node-redis/client/dist/lib/commands';
|
||||||
|
import { transformTuplesReply } from '@node-redis/client/dist/lib/commands/generic-transformers';
|
||||||
|
|
||||||
export function transformArguments(index: string): Array<string> {
|
export function transformArguments(index: string): Array<string> {
|
||||||
return ['FT.INFO', index];
|
return ['FT.INFO', index];
|
||||||
}
|
}
|
||||||
|
|
||||||
type InfoRawReply = [
|
type InfoRawReply = [
|
||||||
_: string,
|
'index_name',
|
||||||
indexName: string,
|
RedisCommandArgument,
|
||||||
_: string,
|
'index_options',
|
||||||
indexOptions: Array<string>,
|
Array<RedisCommandArgument>,
|
||||||
_: string,
|
'index_definition',
|
||||||
indexDefinition: [
|
Array<RedisCommandArgument>,
|
||||||
_: string,
|
'attributes',
|
||||||
keyType: string,
|
Array<Array<RedisCommandArgument>>,
|
||||||
_: string,
|
'num_docs',
|
||||||
prefixes: Array<string>,
|
RedisCommandArgument,
|
||||||
_: string,
|
'max_doc_id',
|
||||||
defaultScore: string
|
RedisCommandArgument,
|
||||||
|
'num_terms',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'num_records',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'inverted_sz_mb',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'vector_index_sz_mb',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'total_inverted_index_blocks',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'offset_vectors_sz_mb',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'doc_table_size_mb',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'sortable_values_size_mb',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'key_table_size_mb',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'records_per_doc_avg',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'bytes_per_record_avg',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'offsets_per_term_avg',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'offset_bits_per_record_avg',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'hash_indexing_failures',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'indexing',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'percent_indexed',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'gc_stats',
|
||||||
|
[
|
||||||
|
'bytes_collected',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'total_ms_run',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'total_cycles',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'average_cycle_time_ms',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'last_run_time_ms',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'gc_numeric_trees_missed',
|
||||||
|
RedisCommandArgument,
|
||||||
|
'gc_blocks_denied',
|
||||||
|
RedisCommandArgument
|
||||||
],
|
],
|
||||||
_: string,
|
'cursor_stats',
|
||||||
attributes: Array<Array<string>>,
|
[
|
||||||
_: string,
|
'global_idle',
|
||||||
numDocs: string,
|
number,
|
||||||
_: string,
|
'global_total',
|
||||||
maxDocId: string,
|
number,
|
||||||
_: string,
|
'index_capacity',
|
||||||
numTerms: string,
|
number,
|
||||||
_: string,
|
'index_total',
|
||||||
numRecords: string,
|
number
|
||||||
_: string,
|
|
||||||
invertedSzMb: string,
|
|
||||||
_: string,
|
|
||||||
totalInvertedIndexBlocks: string,
|
|
||||||
_: string,
|
|
||||||
offsetVectorsSzMb: string,
|
|
||||||
_: string,
|
|
||||||
docTableSizeMb: string,
|
|
||||||
_: string,
|
|
||||||
sortableValuesSizeMb: string,
|
|
||||||
_: string,
|
|
||||||
keyTableSizeMb: string,
|
|
||||||
_: string,
|
|
||||||
recordsPerDocAvg: string,
|
|
||||||
_: string,
|
|
||||||
bytesPerRecordAvg: string,
|
|
||||||
_: string,
|
|
||||||
offsetsPerTermAvg: string,
|
|
||||||
_: string,
|
|
||||||
offsetBitsPerRecordAvg: string,
|
|
||||||
_: string,
|
|
||||||
hashIndexingFailures: string,
|
|
||||||
_: string,
|
|
||||||
indexing: string,
|
|
||||||
_: string,
|
|
||||||
percentIndexed: string,
|
|
||||||
_: string,
|
|
||||||
gcStats: [
|
|
||||||
_: string,
|
|
||||||
bytesCollected: string,
|
|
||||||
_: string,
|
|
||||||
totalMsRun: string,
|
|
||||||
_: string,
|
|
||||||
totalCycles: string,
|
|
||||||
_: string,
|
|
||||||
averageCycleTimeMs: string,
|
|
||||||
_: string,
|
|
||||||
lastRunTimeMs: string,
|
|
||||||
_: string,
|
|
||||||
gcNumericTreesMissed: string,
|
|
||||||
_: string,
|
|
||||||
gcBlocksDenied: string
|
|
||||||
],
|
],
|
||||||
_: string,
|
'stopwords_list'?,
|
||||||
cursorStats: [
|
Array<RedisCommandArgument>?
|
||||||
_: string,
|
|
||||||
globalIdle: number,
|
|
||||||
_: string,
|
|
||||||
globalTotal: number,
|
|
||||||
_: string,
|
|
||||||
indexCapacity: number,
|
|
||||||
_: string,
|
|
||||||
idnexTotal: number
|
|
||||||
]
|
|
||||||
];
|
];
|
||||||
|
|
||||||
interface InfoReply {
|
interface InfoReply {
|
||||||
indexName: string;
|
indexName: RedisCommandArgument;
|
||||||
indexOptions: Array<string>;
|
indexOptions: Array<RedisCommandArgument>;
|
||||||
indexDefinition: {
|
indexDefinition: Record<string, RedisCommandArgument>;
|
||||||
keyType: string;
|
attributes: Array<Record<string, RedisCommandArgument>>;
|
||||||
prefixes: Array<string>;
|
numDocs: RedisCommandArgument;
|
||||||
defaultScore: string;
|
maxDocId: RedisCommandArgument;
|
||||||
};
|
numTerms: RedisCommandArgument;
|
||||||
attributes: Array<Array<string>>;
|
numRecords: RedisCommandArgument;
|
||||||
numDocs: string;
|
invertedSzMb: RedisCommandArgument;
|
||||||
maxDocId: string;
|
vectorIndexSzMb: RedisCommandArgument;
|
||||||
numTerms: string;
|
totalInvertedIndexBlocks: RedisCommandArgument;
|
||||||
numRecords: string;
|
offsetVectorsSzMb: RedisCommandArgument;
|
||||||
invertedSzMb: string;
|
docTableSizeMb: RedisCommandArgument;
|
||||||
totalInvertedIndexBlocks: string;
|
sortableValuesSizeMb: RedisCommandArgument;
|
||||||
offsetVectorsSzMb: string;
|
keyTableSizeMb: RedisCommandArgument;
|
||||||
docTableSizeMb: string;
|
recordsPerDocAvg: RedisCommandArgument;
|
||||||
sortableValuesSizeMb: string;
|
bytesPerRecordAvg: RedisCommandArgument;
|
||||||
keyTableSizeMb: string;
|
offsetsPerTermAvg: RedisCommandArgument;
|
||||||
recordsPerDocAvg: string;
|
offsetBitsPerRecordAvg: RedisCommandArgument;
|
||||||
bytesPerRecordAvg: string;
|
hashIndexingFailures: RedisCommandArgument;
|
||||||
offsetsPerTermAvg: string;
|
indexing: RedisCommandArgument;
|
||||||
offsetBitsPerRecordAvg: string;
|
percentIndexed: RedisCommandArgument;
|
||||||
hashIndexingFailures: string;
|
|
||||||
indexing: string;
|
|
||||||
percentIndexed: string;
|
|
||||||
gcStats: {
|
gcStats: {
|
||||||
bytesCollected: string;
|
bytesCollected: RedisCommandArgument;
|
||||||
totalMsRun: string;
|
totalMsRun: RedisCommandArgument;
|
||||||
totalCycles: string;
|
totalCycles: RedisCommandArgument;
|
||||||
averageCycleTimeMs: string;
|
averageCycleTimeMs: RedisCommandArgument;
|
||||||
lastRunTimeMs: string;
|
lastRunTimeMs: RedisCommandArgument;
|
||||||
gcNumericTreesMissed: string;
|
gcNumericTreesMissed: RedisCommandArgument;
|
||||||
gcBlocksDenied: string;
|
gcBlocksDenied: RedisCommandArgument;
|
||||||
};
|
};
|
||||||
cursorStats: {
|
cursorStats: {
|
||||||
globalIdle: number;
|
globalIdle: number;
|
||||||
@@ -123,49 +120,49 @@ interface InfoReply {
|
|||||||
indexCapacity: number;
|
indexCapacity: number;
|
||||||
idnexTotal: number;
|
idnexTotal: number;
|
||||||
};
|
};
|
||||||
|
stopWords: Array<RedisCommandArgument> | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformReply(rawReply: InfoRawReply): InfoReply {
|
export function transformReply(rawReply: InfoRawReply): InfoReply {
|
||||||
|
console.log(rawReply);
|
||||||
return {
|
return {
|
||||||
indexName: rawReply[1],
|
indexName: rawReply[1],
|
||||||
indexOptions: rawReply[3],
|
indexOptions: rawReply[3],
|
||||||
indexDefinition: {
|
indexDefinition: transformTuplesReply(rawReply[5]),
|
||||||
keyType: rawReply[5][1],
|
attributes: rawReply[7].map(attribute => transformTuplesReply(attribute)),
|
||||||
prefixes: rawReply[5][3],
|
|
||||||
defaultScore: rawReply[5][5]
|
|
||||||
},
|
|
||||||
attributes: rawReply[7],
|
|
||||||
numDocs: rawReply[9],
|
numDocs: rawReply[9],
|
||||||
maxDocId: rawReply[11],
|
maxDocId: rawReply[11],
|
||||||
numTerms: rawReply[13],
|
numTerms: rawReply[13],
|
||||||
numRecords: rawReply[15],
|
numRecords: rawReply[15],
|
||||||
invertedSzMb: rawReply[17],
|
invertedSzMb: rawReply[17],
|
||||||
totalInvertedIndexBlocks: rawReply[19],
|
vectorIndexSzMb: rawReply[19],
|
||||||
offsetVectorsSzMb: rawReply[21],
|
totalInvertedIndexBlocks: rawReply[21],
|
||||||
docTableSizeMb: rawReply[23],
|
offsetVectorsSzMb: rawReply[23],
|
||||||
sortableValuesSizeMb: rawReply[25],
|
docTableSizeMb: rawReply[25],
|
||||||
keyTableSizeMb: rawReply[27],
|
sortableValuesSizeMb: rawReply[27],
|
||||||
recordsPerDocAvg: rawReply[29],
|
keyTableSizeMb: rawReply[29],
|
||||||
bytesPerRecordAvg: rawReply[31],
|
recordsPerDocAvg: rawReply[31],
|
||||||
offsetsPerTermAvg: rawReply[33],
|
bytesPerRecordAvg: rawReply[33],
|
||||||
offsetBitsPerRecordAvg: rawReply[35],
|
offsetsPerTermAvg: rawReply[35],
|
||||||
hashIndexingFailures: rawReply[37],
|
offsetBitsPerRecordAvg: rawReply[37],
|
||||||
indexing: rawReply[39],
|
hashIndexingFailures: rawReply[39],
|
||||||
percentIndexed: rawReply[41],
|
indexing: rawReply[41],
|
||||||
|
percentIndexed: rawReply[43],
|
||||||
gcStats: {
|
gcStats: {
|
||||||
bytesCollected: rawReply[43][1],
|
bytesCollected: rawReply[45][1],
|
||||||
totalMsRun: rawReply[43][3],
|
totalMsRun: rawReply[45][3],
|
||||||
totalCycles: rawReply[43][5],
|
totalCycles: rawReply[45][5],
|
||||||
averageCycleTimeMs: rawReply[43][7],
|
averageCycleTimeMs: rawReply[45][7],
|
||||||
lastRunTimeMs: rawReply[43][9],
|
lastRunTimeMs: rawReply[45][9],
|
||||||
gcNumericTreesMissed: rawReply[43][11],
|
gcNumericTreesMissed: rawReply[45][11],
|
||||||
gcBlocksDenied: rawReply[43][13]
|
gcBlocksDenied: rawReply[45][13]
|
||||||
},
|
},
|
||||||
cursorStats: {
|
cursorStats: {
|
||||||
globalIdle: rawReply[45][1],
|
globalIdle: rawReply[47][1],
|
||||||
globalTotal: rawReply[45][3],
|
globalTotal: rawReply[47][3],
|
||||||
indexCapacity: rawReply[45][5],
|
indexCapacity: rawReply[47][5],
|
||||||
idnexTotal: rawReply[45][7]
|
idnexTotal: rawReply[47][7]
|
||||||
}
|
},
|
||||||
|
stopWords: rawReply[49]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { SearchOptions, SearchRawReply, transformReply as transformSearchReply } from './SEARCH';
|
import { SearchOptions, SearchRawReply, transformReply as transformSearchReply } from './SEARCH';
|
||||||
import { pushSearchOptions, ProfileOptions, ProfileRawReply, ProfileReply, transformProfile } from '.';
|
import { pushSearchOptions, ProfileOptions, ProfileRawReply, ProfileReply, transformProfile } from '.';
|
||||||
|
import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands';
|
||||||
|
|
||||||
export const IS_READ_ONLY = true;
|
export const IS_READ_ONLY = true;
|
||||||
|
|
||||||
@@ -7,7 +8,7 @@ export function transformArguments(
|
|||||||
index: string,
|
index: string,
|
||||||
query: string,
|
query: string,
|
||||||
options?: ProfileOptions & SearchOptions
|
options?: ProfileOptions & SearchOptions
|
||||||
): Array<string> {
|
): RedisCommandArguments {
|
||||||
const args = ['FT.PROFILE', index, 'SEARCH'];
|
const args = ['FT.PROFILE', index, 'SEARCH'];
|
||||||
|
|
||||||
if (options?.LIMITED) {
|
if (options?.LIMITED) {
|
||||||
@@ -15,8 +16,7 @@ export function transformArguments(
|
|||||||
}
|
}
|
||||||
|
|
||||||
args.push('QUERY', query);
|
args.push('QUERY', query);
|
||||||
pushSearchOptions(args, options)
|
return pushSearchOptions(args, options);
|
||||||
return args;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProfileSearchRawReply = ProfileRawReply<SearchRawReply>;
|
type ProfileSearchRawReply = ProfileRawReply<SearchRawReply>;
|
||||||
|
@@ -213,31 +213,98 @@ describe('SEARCH', () => {
|
|||||||
['FT.SEARCH', 'index', 'query', 'LIMIT', '0', '1']
|
['FT.SEARCH', 'index', 'query', 'LIMIT', '0', '1']
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('with PARAMS', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('index', 'query', {
|
||||||
|
PARAMS: {
|
||||||
|
param: 'value'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
['FT.SEARCH', 'index', 'query', 'PARAMS', '2', 'param', 'value']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with DIALECT', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('index', 'query', {
|
||||||
|
DIALECT: 1
|
||||||
|
}),
|
||||||
|
['FT.SEARCH', 'index', 'query', 'DIALECT', '1']
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
testUtils.testWithClient('client.ft.search', async client => {
|
describe('client.ft.search', () => {
|
||||||
await Promise.all([
|
testUtils.testWithClient('DIALECT 1', async client => {
|
||||||
client.ft.create('index', {
|
await Promise.all([
|
||||||
field: SchemaFieldTypes.NUMERIC
|
client.ft.create('index', {
|
||||||
}),
|
field: SchemaFieldTypes.NUMERIC
|
||||||
client.hSet('1', 'field', '1')
|
}),
|
||||||
]);
|
client.hSet('1', 'field', '1')
|
||||||
|
]);
|
||||||
|
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
await client.ft.search('index', '*'),
|
await client.ft.search('index', '*', {
|
||||||
{
|
DIALECT: 1
|
||||||
total: 1,
|
}),
|
||||||
documents: [{
|
{
|
||||||
id: '1',
|
total: 1,
|
||||||
value: Object.create(null, {
|
documents: [{
|
||||||
field: {
|
id: '1',
|
||||||
value: '1',
|
value: Object.create(null, {
|
||||||
configurable: true,
|
field: {
|
||||||
enumerable: true
|
value: '1',
|
||||||
}
|
configurable: true,
|
||||||
})
|
enumerable: true
|
||||||
}]
|
}
|
||||||
}
|
})
|
||||||
);
|
}]
|
||||||
}, GLOBAL.SERVERS.OPEN);
|
}
|
||||||
|
);
|
||||||
|
}, GLOBAL.SERVERS.OPEN);
|
||||||
|
|
||||||
|
testUtils.testWithClient('DIALECT 2', async client => {
|
||||||
|
await Promise.all([
|
||||||
|
client.ft.create('index', {
|
||||||
|
field: SchemaFieldTypes.NUMERIC
|
||||||
|
}),
|
||||||
|
client.hSet('1', 'field', '1'),
|
||||||
|
client.hSet('2', 'field', '2'),
|
||||||
|
client.hSet('3', 'field', '3')
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
await client.ft.search('index', '@field:[$min $max]', {
|
||||||
|
PARAMS: {
|
||||||
|
min: 1,
|
||||||
|
max: 2
|
||||||
|
},
|
||||||
|
DIALECT: 2
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
total: 2,
|
||||||
|
documents: [{
|
||||||
|
id: '1',
|
||||||
|
value: Object.create(null, {
|
||||||
|
field: {
|
||||||
|
value: '1',
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, {
|
||||||
|
id: '2',
|
||||||
|
value: Object.create(null, {
|
||||||
|
field: {
|
||||||
|
value: '2',
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, GLOBAL.SERVERS.OPEN);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands';
|
import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands';
|
||||||
import { transformTuplesReply } from '@node-redis/client/dist/lib/commands/generic-transformers';
|
import { transformTuplesReply } from '@node-redis/client/dist/lib/commands/generic-transformers';
|
||||||
import { pushSearchOptions, RedisSearchLanguages, PropertyName, SortByProperty, SearchReply } from '.';
|
import { pushSearchOptions, RedisSearchLanguages, Params, PropertyName, SortByProperty, SearchReply } from '.';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 1;
|
export const FIRST_KEY_INDEX = 1;
|
||||||
|
|
||||||
@@ -54,6 +54,8 @@ export interface SearchOptions {
|
|||||||
from: number | string;
|
from: number | string;
|
||||||
size: number | string;
|
size: number | string;
|
||||||
};
|
};
|
||||||
|
PARAMS?: Params;
|
||||||
|
DIALECT?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformArguments(
|
export function transformArguments(
|
||||||
@@ -61,9 +63,10 @@ export function transformArguments(
|
|||||||
query: string,
|
query: string,
|
||||||
options?: SearchOptions
|
options?: SearchOptions
|
||||||
): RedisCommandArguments {
|
): RedisCommandArguments {
|
||||||
const args: RedisCommandArguments = ['FT.SEARCH', index, query];
|
return pushSearchOptions(
|
||||||
pushSearchOptions(args, options);
|
['FT.SEARCH', index, query],
|
||||||
return args;
|
options
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SearchRawReply = Array<any>;
|
export type SearchRawReply = Array<any>;
|
||||||
|
@@ -47,6 +47,15 @@ describe('SPELLCHECK', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('with DIALECT', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
transformArguments('index', 'query', {
|
||||||
|
DIALECT: 1
|
||||||
|
}),
|
||||||
|
['FT.SPELLCHECK', 'index', 'query', 'DIALECT', '1']
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
testUtils.testWithClient('client.ft.spellCheck', async client => {
|
testUtils.testWithClient('client.ft.spellCheck', async client => {
|
||||||
|
@@ -6,6 +6,7 @@ interface SpellCheckTerms {
|
|||||||
interface SpellCheckOptions {
|
interface SpellCheckOptions {
|
||||||
DISTANCE?: number;
|
DISTANCE?: number;
|
||||||
TERMS?: SpellCheckTerms | Array<SpellCheckTerms>;
|
TERMS?: SpellCheckTerms | Array<SpellCheckTerms>;
|
||||||
|
DIALECT?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformArguments(index: string, query: string, options?: SpellCheckOptions): Array<string> {
|
export function transformArguments(index: string, query: string, options?: SpellCheckOptions): Array<string> {
|
||||||
@@ -25,6 +26,10 @@ export function transformArguments(index: string, query: string, options?: Spell
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options?.DIALECT) {
|
||||||
|
args.push('DIALECT', options.DIALECT.toString());
|
||||||
|
}
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -28,7 +28,7 @@ import * as SUGLEN from './SUGLEN';
|
|||||||
import * as SYNDUMP from './SYNDUMP';
|
import * as SYNDUMP from './SYNDUMP';
|
||||||
import * as SYNUPDATE from './SYNUPDATE';
|
import * as SYNUPDATE from './SYNUPDATE';
|
||||||
import * as TAGVALS from './TAGVALS';
|
import * as TAGVALS from './TAGVALS';
|
||||||
import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands';
|
import { RedisCommandArgument, RedisCommandArguments } from '@node-redis/client/dist/lib/commands';
|
||||||
import { pushOptionalVerdictArgument, pushVerdictArgument } from '@node-redis/client/dist/lib/commands/generic-transformers';
|
import { pushOptionalVerdictArgument, pushVerdictArgument } from '@node-redis/client/dist/lib/commands/generic-transformers';
|
||||||
import { SearchOptions } from './SEARCH';
|
import { SearchOptions } from './SEARCH';
|
||||||
|
|
||||||
@@ -172,16 +172,29 @@ export enum SchemaFieldTypes {
|
|||||||
TEXT = 'TEXT',
|
TEXT = 'TEXT',
|
||||||
NUMERIC = 'NUMERIC',
|
NUMERIC = 'NUMERIC',
|
||||||
GEO = 'GEO',
|
GEO = 'GEO',
|
||||||
TAG = 'TAG'
|
TAG = 'TAG',
|
||||||
|
VECTOR = 'VECTOR'
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateSchemaField<T extends SchemaFieldTypes, E = Record<keyof any, any>> = T | ({
|
type CreateSchemaField<
|
||||||
|
T extends SchemaFieldTypes,
|
||||||
|
E = Record<keyof any, any>
|
||||||
|
> = T | ({
|
||||||
type: T;
|
type: T;
|
||||||
AS?: string;
|
AS?: string;
|
||||||
SORTABLE?: true | 'UNF';
|
|
||||||
NOINDEX?: true;
|
|
||||||
} & E);
|
} & E);
|
||||||
|
|
||||||
|
type CreateSchemaCommonField<
|
||||||
|
T extends SchemaFieldTypes,
|
||||||
|
E = Record<string, never>
|
||||||
|
> = CreateSchemaField<
|
||||||
|
T,
|
||||||
|
({
|
||||||
|
SORTABLE?: true | 'UNF';
|
||||||
|
NOINDEX?: true;
|
||||||
|
} & E)
|
||||||
|
>;
|
||||||
|
|
||||||
export enum SchemaTextFieldPhonetics {
|
export enum SchemaTextFieldPhonetics {
|
||||||
DM_EN = 'dm:en',
|
DM_EN = 'dm:en',
|
||||||
DM_FR = 'dm:fr',
|
DM_FR = 'dm:fr',
|
||||||
@@ -189,27 +202,55 @@ export enum SchemaTextFieldPhonetics {
|
|||||||
DM_ES = 'dm:es'
|
DM_ES = 'dm:es'
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateSchemaTextField = CreateSchemaField<SchemaFieldTypes.TEXT, {
|
type CreateSchemaTextField = CreateSchemaCommonField<SchemaFieldTypes.TEXT, {
|
||||||
NOSTEM?: true;
|
NOSTEM?: true;
|
||||||
WEIGHT?: number;
|
WEIGHT?: number;
|
||||||
PHONETIC?: SchemaTextFieldPhonetics;
|
PHONETIC?: SchemaTextFieldPhonetics;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
type CreateSchemaNumericField = CreateSchemaField<SchemaFieldTypes.NUMERIC>;
|
type CreateSchemaNumericField = CreateSchemaCommonField<SchemaFieldTypes.NUMERIC>;
|
||||||
|
|
||||||
type CreateSchemaGeoField = CreateSchemaField<SchemaFieldTypes.GEO>;
|
type CreateSchemaGeoField = CreateSchemaCommonField<SchemaFieldTypes.GEO>;
|
||||||
|
|
||||||
type CreateSchemaTagField = CreateSchemaField<SchemaFieldTypes.TAG, {
|
type CreateSchemaTagField = CreateSchemaCommonField<SchemaFieldTypes.TAG, {
|
||||||
SEPARATOR?: string;
|
SEPARATOR?: string;
|
||||||
CASESENSITIVE?: true;
|
CASESENSITIVE?: true;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export enum VectorAlgorithms {
|
||||||
|
FLAT = 'FLAT',
|
||||||
|
HNSW = 'HNSW'
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateSchemaVectorField<
|
||||||
|
T extends VectorAlgorithms,
|
||||||
|
A extends Record<string, unknown>
|
||||||
|
> = CreateSchemaField<SchemaFieldTypes.VECTOR, {
|
||||||
|
ALGORITHM: T;
|
||||||
|
TYPE: string;
|
||||||
|
DIM: number;
|
||||||
|
DISTANCE_METRIC: 'L2' | 'IP' | 'COSINE';
|
||||||
|
INITIAL_CAP?: number;
|
||||||
|
} & A>;
|
||||||
|
|
||||||
|
type CreateSchemaFlatVectorField = CreateSchemaVectorField<VectorAlgorithms.FLAT, {
|
||||||
|
BLOCK_SIZE?: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type CreateSchemaHNSWVectorField = CreateSchemaVectorField<VectorAlgorithms.HNSW, {
|
||||||
|
M?: number;
|
||||||
|
EF_CONSTRUCTION?: number;
|
||||||
|
EF_RUNTIME?: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
export interface RediSearchSchema {
|
export interface RediSearchSchema {
|
||||||
[field: string]:
|
[field: string]:
|
||||||
CreateSchemaTextField |
|
CreateSchemaTextField |
|
||||||
CreateSchemaNumericField |
|
CreateSchemaNumericField |
|
||||||
CreateSchemaGeoField |
|
CreateSchemaGeoField |
|
||||||
CreateSchemaTagField;
|
CreateSchemaTagField |
|
||||||
|
CreateSchemaFlatVectorField |
|
||||||
|
CreateSchemaHNSWVectorField;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pushSchema(args: RedisCommandArguments, schema: RediSearchSchema) {
|
export function pushSchema(args: RedisCommandArguments, schema: RediSearchSchema) {
|
||||||
@@ -257,6 +298,47 @@ export function pushSchema(args: RedisCommandArguments, schema: RediSearchSchema
|
|||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SchemaFieldTypes.VECTOR:
|
||||||
|
args.push(fieldOptions.ALGORITHM);
|
||||||
|
|
||||||
|
pushArgumentsWithLength(args, () => {
|
||||||
|
args.push(
|
||||||
|
'TYPE', fieldOptions.TYPE,
|
||||||
|
'DIM', fieldOptions.DIM.toString(),
|
||||||
|
'DISTANCE_METRIC', fieldOptions.DISTANCE_METRIC
|
||||||
|
);
|
||||||
|
|
||||||
|
if (fieldOptions.INITIAL_CAP) {
|
||||||
|
args.push('INITIAL_CAP', fieldOptions.INITIAL_CAP.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (fieldOptions.ALGORITHM) {
|
||||||
|
case VectorAlgorithms.FLAT:
|
||||||
|
if (fieldOptions.BLOCK_SIZE) {
|
||||||
|
args.push('BLOCK_SIZE', fieldOptions.BLOCK_SIZE.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VectorAlgorithms.HNSW:
|
||||||
|
if (fieldOptions.M) {
|
||||||
|
args.push('M', fieldOptions.M.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldOptions.EF_CONSTRUCTION) {
|
||||||
|
args.push('EF_CONSTRUCTION', fieldOptions.EF_CONSTRUCTION.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldOptions.EF_RUNTIME) {
|
||||||
|
args.push('EF_RUNTIME', fieldOptions.EF_RUNTIME.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
continue; // vector fields do not contain SORTABLE and NOINDEX options
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fieldOptions.SORTABLE) {
|
if (fieldOptions.SORTABLE) {
|
||||||
@@ -273,11 +355,27 @@ export function pushSchema(args: RedisCommandArguments, schema: RediSearchSchema
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Params = Record<string, RedisCommandArgument | number>;
|
||||||
|
|
||||||
|
export function pushParamsArgs(
|
||||||
|
args: RedisCommandArguments,
|
||||||
|
params?: Params
|
||||||
|
): RedisCommandArguments {
|
||||||
|
if (params) {
|
||||||
|
const enrties = Object.entries(params);
|
||||||
|
args.push('PARAMS', (enrties.length * 2).toString());
|
||||||
|
for (const [key, value] of enrties) {
|
||||||
|
args.push(key, value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
export function pushSearchOptions(
|
export function pushSearchOptions(
|
||||||
args: RedisCommandArguments,
|
args: RedisCommandArguments,
|
||||||
options?: SearchOptions
|
options?: SearchOptions
|
||||||
): RedisCommandArguments {
|
): RedisCommandArguments {
|
||||||
|
|
||||||
if (options?.VERBATIM) {
|
if (options?.VERBATIM) {
|
||||||
args.push('VERBATIM');
|
args.push('VERBATIM');
|
||||||
}
|
}
|
||||||
@@ -381,6 +479,16 @@ export function pushSearchOptions(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options?.PARAMS) {
|
||||||
|
pushParamsArgs(args, options.PARAMS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.DIALECT) {
|
||||||
|
args.push('DIALECT', options.DIALECT.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('!@#', args);
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
export { default } from './commands';
|
export { default } from './commands';
|
||||||
|
|
||||||
export { RediSearchSchema, SchemaFieldTypes, SchemaTextFieldPhonetics, SearchReply } from './commands';
|
export { RediSearchSchema, SchemaFieldTypes, SchemaTextFieldPhonetics, SearchReply, VectorAlgorithms } from './commands';
|
||||||
export { AggregateSteps, AggregateGroupByReducers } from './commands/AGGREGATE';
|
export { AggregateSteps, AggregateGroupByReducers } from './commands/AGGREGATE';
|
||||||
export { SearchOptions } from './commands/SEARCH';
|
export { SearchOptions } from './commands/SEARCH';
|
||||||
|
@@ -4,7 +4,7 @@ import RediSearch from '.';
|
|||||||
export default new TestUtils({
|
export default new TestUtils({
|
||||||
dockerImageName: 'redislabs/redisearch',
|
dockerImageName: 'redislabs/redisearch',
|
||||||
dockerImageVersionArgument: 'redisearch-version',
|
dockerImageVersionArgument: 'redisearch-version',
|
||||||
defaultDockerVersion: '2.2.7'
|
defaultDockerVersion: '2.4.3'
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GLOBAL = {
|
export const GLOBAL = {
|
||||||
|
@@ -38,7 +38,7 @@ const portIterator = (async function*(): AsyncIterableIterator<number> {
|
|||||||
|
|
||||||
export interface RedisServerDockerConfig {
|
export interface RedisServerDockerConfig {
|
||||||
image: string;
|
image: string;
|
||||||
version: Array<number>;
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RedisServerDocker {
|
export interface RedisServerDocker {
|
||||||
@@ -54,7 +54,7 @@ async function spawnRedisServerDocker({ image, version }: RedisServerDockerConfi
|
|||||||
{ stdout, stderr } = await execAsync(
|
{ stdout, stderr } = await execAsync(
|
||||||
'docker run -d --network host $(' +
|
'docker run -d --network host $(' +
|
||||||
`docker build ${DOCKER_FODLER_PATH} -q ` +
|
`docker build ${DOCKER_FODLER_PATH} -q ` +
|
||||||
`--build-arg IMAGE=${image}:${version.join('.')} ` +
|
`--build-arg IMAGE=${image}:${version} ` +
|
||||||
`--build-arg REDIS_ARGUMENTS="--save --port ${port.toString()} ${serverArguments.join(' ')}"` +
|
`--build-arg REDIS_ARGUMENTS="--save --port ${port.toString()} ${serverArguments.join(' ')}"` +
|
||||||
')'
|
')'
|
||||||
);
|
);
|
||||||
|
@@ -27,49 +27,61 @@ interface ClusterTestOptions<M extends RedisModules, S extends RedisScripts> ext
|
|||||||
numberOfNodes?: number;
|
numberOfNodes?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Version {
|
||||||
|
string: string;
|
||||||
|
numbers: Array<number>;
|
||||||
|
}
|
||||||
|
|
||||||
export default class TestUtils {
|
export default class TestUtils {
|
||||||
static #getVersion(argumentName: string, defaultVersion: string): Array<number> {
|
static #getVersion(argumentName: string, defaultVersion: string): Version {
|
||||||
return yargs(hideBin(process.argv))
|
return yargs(hideBin(process.argv))
|
||||||
.option(argumentName, {
|
.option(argumentName, {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: defaultVersion
|
default: defaultVersion
|
||||||
})
|
})
|
||||||
.coerce(argumentName, (arg: string) => {
|
.coerce(argumentName, (arg: string) => {
|
||||||
return arg.split('.').map(x => {
|
const indexOfDash = arg.indexOf('-');
|
||||||
const value = Number(x);
|
return {
|
||||||
if (Number.isNaN(value)) {
|
string: arg,
|
||||||
throw new TypeError(`${arg} is not a valid redis version`);
|
numbers: (indexOfDash === -1 ? arg : arg.substring(0, indexOfDash)).split('.').map(x => {
|
||||||
}
|
const value = Number(x);
|
||||||
|
if (Number.isNaN(value)) {
|
||||||
|
throw new TypeError(`${arg} is not a valid redis version`);
|
||||||
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
});
|
})
|
||||||
|
};
|
||||||
})
|
})
|
||||||
.demandOption(argumentName)
|
.demandOption(argumentName)
|
||||||
.parseSync()[argumentName];
|
.parseSync()[argumentName];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readonly #VERSION_NUMBERS: Array<number>;
|
||||||
readonly #DOCKER_IMAGE: RedisServerDockerConfig;
|
readonly #DOCKER_IMAGE: RedisServerDockerConfig;
|
||||||
|
|
||||||
constructor(config: TestUtilsConfig) {
|
constructor(config: TestUtilsConfig) {
|
||||||
|
const { string, numbers } = TestUtils.#getVersion(config.dockerImageVersionArgument, config.defaultDockerVersion);
|
||||||
|
this.#VERSION_NUMBERS = numbers;
|
||||||
this.#DOCKER_IMAGE = {
|
this.#DOCKER_IMAGE = {
|
||||||
image: config.dockerImageName,
|
image: config.dockerImageName,
|
||||||
version: TestUtils.#getVersion(config.dockerImageVersionArgument, config.defaultDockerVersion)
|
version: string
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
isVersionGreaterThan(minimumVersion: Array<number> | undefined): boolean {
|
isVersionGreaterThan(minimumVersion: Array<number> | undefined): boolean {
|
||||||
if (minimumVersion === undefined) return true;
|
if (minimumVersion === undefined) return true;
|
||||||
|
|
||||||
const lastIndex = Math.min(this.#DOCKER_IMAGE.version.length, minimumVersion.length) - 1;
|
const lastIndex = Math.min(this.#VERSION_NUMBERS.length, minimumVersion.length) - 1;
|
||||||
for (let i = 0; i < lastIndex; i++) {
|
for (let i = 0; i < lastIndex; i++) {
|
||||||
if (this.#DOCKER_IMAGE.version[i] > minimumVersion[i]) {
|
if (this.#VERSION_NUMBERS[i] > minimumVersion[i]) {
|
||||||
return true;
|
return true;
|
||||||
} else if (minimumVersion[i] > this.#DOCKER_IMAGE.version[i]) {
|
} else if (minimumVersion[i] > this.#VERSION_NUMBERS[i]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.#DOCKER_IMAGE.version[lastIndex] >= minimumVersion[lastIndex];
|
return this.#VERSION_NUMBERS[lastIndex] >= minimumVersion[lastIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
isVersionGreaterThanHook(minimumVersion: Array<number> | undefined): void {
|
isVersionGreaterThanHook(minimumVersion: Array<number> | undefined): void {
|
||||||
|
Reference in New Issue
Block a user