1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-06 02:15:48 +03:00

refactor!: redis 8 compatibility improvements and test infrastructure updates (#2893)

* churn(test): use redislabs/client-libs-test for testing

This  switches our testing infrastructure from redis/redis-stack to
redislabs/client-libs-test Docker image across all packages. This change
also updates the default Docker version from 7.4.0-v1 to 8.0-M04-pre.

* churn(test): verify CONFIG SET / GET compatibility with Redis 8

- Add tests for Redis 8 search configuration settings
- Deprecate Redis Search CONFIG commands in favor of standard CONFIG
- Test read-only config restrictions for Redis 8

* churn(test): handle Redis 8 coordinate precision in GEOPOS

- Update GEOPOS tests to handle increased precision in Redis 8 (17 decimal places vs 14)
- Add precision-aware coordinate comparison helper
- Add comprehensive test suite for coordinate comparison function

* test(search): adapt SUGGET tests for Redis 8 empty results

- Update tests to expect empty array ([]) instead of null for SUGGET variants
- Affects sugGet, sugGetWithPayloads, sugGetWithScores, and sugGetWithScoresWithPayloads

* test(search): support Redis 8 INFO indexes_all field

- Add indexes_all field introduced in Redis 8 to index definition test

* refactor!(search): simplify PROFILE commands to return raw response

- BREAKING CHANGE: FT.PROFILE now returns raw response, letting users implement their own parsing

* test: improve version-specific test coverage

- Add `testWithClientIfVersionWithinRange` method to run tests for specific Redis versions
- Refactor TestUtils to handle version comparisons more accurately
- Update test utilities across Redis modules to run tests against multiple versions, and not against latest only
This commit is contained in:
Bobby I.
2025-02-27 10:56:58 +02:00
committed by GitHub
parent 33cdc00746
commit 69d507a572
28 changed files with 1083 additions and 422 deletions

View File

@@ -17,4 +17,40 @@ describe('FT.CONFIG SET', () => {
'OK'
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClientIfVersionWithinRange([[8], 'LATEST'], 'setSearchConfigGloballyTest', async client => {
const normalizeObject = obj => JSON.parse(JSON.stringify(obj));
assert.equal(await client.configSet('search-default-dialect', '3'),
'OK', 'CONFIG SET should return OK');
assert.deepEqual(
normalizeObject(await client.configGet('search-default-dialect')),
{ 'search-default-dialect': '3' },
'CONFIG GET should return 3'
);
assert.deepEqual(
normalizeObject(await client.ft.configGet('DEFAULT_DIALECT')),
{ 'DEFAULT_DIALECT': '3' },
'FT.CONFIG GET should return 3'
);
const ftConfigSetResult = await client.ft.configSet('DEFAULT_DIALECT', '2');
assert.equal(normalizeObject(ftConfigSetResult), 'OK', 'FT.CONFIG SET should return OK');
assert.deepEqual(
normalizeObject(await client.ft.configGet('DEFAULT_DIALECT')),
{ 'DEFAULT_DIALECT': '2' },
'FT.CONFIG GET should return 2'
);
assert.deepEqual(
normalizeObject(await client.configGet('search-default-dialect')),
{ 'search-default-dialect': '2' },
'CONFIG GET should return 22'
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -5,105 +5,305 @@ import { SCHEMA_FIELD_TYPE } from './CREATE';
import { parseArgs } from '@redis/client/lib/commands/generic-transformers';
describe('INFO', () => {
it('transformArguments', () => {
assert.deepEqual(
parseArgs(INFO, 'index'),
['FT.INFO', 'index']
);
it('transformArguments', () => {
assert.deepEqual(
parseArgs(INFO, 'index'),
['FT.INFO', 'index']
);
});
testUtils.testWithClientIfVersionWithinRange([[8], 'LATEST'], 'client.ft.info', async client => {
await client.ft.create('index', {
field: SCHEMA_FIELD_TYPE.TEXT
});
const ret = await client.ft.info('index');
// effectively testing that stopwords_list is not in ret
assert.deepEqual(
ret,
{
index_name: 'index',
index_options: [],
index_definition: Object.create(null, {
testUtils.testWithClient('client.ft.info', async client => {
await client.ft.create('index', {
field: SCHEMA_FIELD_TYPE.TEXT
});
const ret = await client.ft.info('index');
// effectively testing that stopwords_list is not in ret
assert.deepEqual(
ret,
{
index_name: 'index',
index_options: [],
index_definition: Object.create(null, {
default_score: {
value: '1',
configurable: true,
enumerable: true
},
key_type: {
value: 'HASH',
configurable: true,
enumerable: true
},
prefixes: {
value: [''],
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
}
})],
num_docs: 0,
max_doc_id: 0,
num_terms: 0,
num_records: 0,
inverted_sz_mb: 0,
vector_index_sz_mb: 0,
total_inverted_index_blocks: 0,
offset_vectors_sz_mb: 0,
doc_table_size_mb: 0,
sortable_values_size_mb: 0,
key_table_size_mb: 0,
records_per_doc_avg: NaN,
bytes_per_record_avg: NaN,
cleaning: 0,
offsets_per_term_avg: NaN,
offset_bits_per_record_avg: NaN,
geoshapes_sz_mb: 0,
hash_indexing_failures: 0,
indexing: 0,
percent_indexed: 1,
number_of_uses: 1,
tag_overhead_sz_mb: 0,
text_overhead_sz_mb: 0,
total_index_memory_sz_mb: 0,
total_indexing_time: 0,
gc_stats: {
bytes_collected: 0,
total_ms_run: 0,
total_cycles: 0,
average_cycle_time_ms: NaN,
last_run_time_ms: 0,
gc_numeric_trees_missed: 0,
gc_blocks_denied: 0
},
cursor_stats: {
global_idle: 0,
global_total: 0,
index_capacity: 128,
index_total: 0
},
}
);
indexes_all: {
value: 'false',
configurable: true,
enumerable: true
},
}, GLOBAL.SERVERS.OPEN);
default_score: {
value: '1',
configurable: true,
enumerable: true
},
key_type: {
value: 'HASH',
configurable: true,
enumerable: true
},
prefixes: {
value: [''],
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
}
})],
num_docs: 0,
max_doc_id: 0,
num_terms: 0,
num_records: 0,
inverted_sz_mb: 0,
vector_index_sz_mb: 0,
total_inverted_index_blocks: 0,
offset_vectors_sz_mb: 0,
doc_table_size_mb: 0,
sortable_values_size_mb: 0,
key_table_size_mb: 0,
records_per_doc_avg: NaN,
bytes_per_record_avg: NaN,
cleaning: 0,
offsets_per_term_avg: NaN,
offset_bits_per_record_avg: NaN,
geoshapes_sz_mb: 0,
hash_indexing_failures: 0,
indexing: 0,
percent_indexed: 1,
number_of_uses: 1,
tag_overhead_sz_mb: 0,
text_overhead_sz_mb: 0,
total_index_memory_sz_mb: 0,
total_indexing_time: 0,
gc_stats: {
bytes_collected: 0,
total_ms_run: 0,
total_cycles: 0,
average_cycle_time_ms: NaN,
last_run_time_ms: 0,
gc_numeric_trees_missed: 0,
gc_blocks_denied: 0
},
cursor_stats: {
global_idle: 0,
global_total: 0,
index_capacity: 128,
index_total: 0
},
}
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClientIfVersionWithinRange([[7, 4, 2], [7, 4, 2]], 'client.ft.info', async client => {
await client.ft.create('index', {
field: SCHEMA_FIELD_TYPE.TEXT
});
const ret = await client.ft.info('index');
// effectively testing that stopwords_list is not in ret
assert.deepEqual(
ret,
{
index_name: 'index',
index_options: [],
index_definition: Object.create(null, {
default_score: {
value: '1',
configurable: true,
enumerable: true
},
key_type: {
value: 'HASH',
configurable: true,
enumerable: true
},
prefixes: {
value: [''],
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
}
})],
num_docs: 0,
max_doc_id: 0,
num_terms: 0,
num_records: 0,
inverted_sz_mb: 0,
vector_index_sz_mb: 0,
total_inverted_index_blocks: 0,
offset_vectors_sz_mb: 0,
doc_table_size_mb: 0,
sortable_values_size_mb: 0,
key_table_size_mb: 0,
records_per_doc_avg: NaN,
bytes_per_record_avg: NaN,
cleaning: 0,
offsets_per_term_avg: NaN,
offset_bits_per_record_avg: NaN,
geoshapes_sz_mb: 0,
hash_indexing_failures: 0,
indexing: 0,
percent_indexed: 1,
number_of_uses: 1,
tag_overhead_sz_mb: 0,
text_overhead_sz_mb: 0,
total_index_memory_sz_mb: 0,
total_indexing_time: 0,
gc_stats: {
bytes_collected: 0,
total_ms_run: 0,
total_cycles: 0,
average_cycle_time_ms: NaN,
last_run_time_ms: 0,
gc_numeric_trees_missed: 0,
gc_blocks_denied: 0
},
cursor_stats: {
global_idle: 0,
global_total: 0,
index_capacity: 128,
index_total: 0
},
}
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClientIfVersionWithinRange([[7, 2, 0], [7, 2, 0]], 'client.ft.info', async client => {
await client.ft.create('index', {
field: SCHEMA_FIELD_TYPE.TEXT
});
const ret = await client.ft.info('index');
// effectively testing that stopwords_list is not in ret
assert.deepEqual(
ret,
{
index_name: 'index',
index_options: [],
index_definition: Object.create(null, {
default_score: {
value: '1',
configurable: true,
enumerable: true
},
key_type: {
value: 'HASH',
configurable: true,
enumerable: true
},
prefixes: {
value: [''],
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
}
})],
num_docs: "0",
max_doc_id: "0",
num_terms: "0",
num_records: "0",
inverted_sz_mb: 0,
vector_index_sz_mb: 0,
total_inverted_index_blocks: "0",
offset_vectors_sz_mb: 0,
doc_table_size_mb: 0,
sortable_values_size_mb: 0,
key_table_size_mb: 0,
records_per_doc_avg: NaN,
bytes_per_record_avg: NaN,
cleaning: 0,
offsets_per_term_avg: NaN,
offset_bits_per_record_avg: NaN,
geoshapes_sz_mb: 0,
hash_indexing_failures: "0",
indexing: "0",
percent_indexed: 1,
number_of_uses: 1,
tag_overhead_sz_mb: 0,
text_overhead_sz_mb: 0,
total_index_memory_sz_mb: 0,
total_indexing_time: 0,
gc_stats: {
bytes_collected: 0,
total_ms_run: 0,
total_cycles: 0,
average_cycle_time_ms: NaN,
last_run_time_ms: 0,
gc_numeric_trees_missed: 0,
gc_blocks_denied: 0
},
cursor_stats: {
global_idle: 0,
global_total: 0,
index_capacity: 128,
index_total: 0
},
}
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -7,43 +7,106 @@ import { parseArgs } from '@redis/client/lib/commands/generic-transformers';
import { DEFAULT_DIALECT } from '../dialect/default';
describe('PROFILE AGGREGATE', () => {
describe('transformArguments', () => {
it('without options', () => {
assert.deepEqual(
parseArgs(PROFILE_AGGREGATE, 'index', 'query'),
['FT.PROFILE', 'index', 'AGGREGATE', 'QUERY', 'query', 'DIALECT', DEFAULT_DIALECT]
);
});
it('with options', () => {
assert.deepEqual(
parseArgs(PROFILE_AGGREGATE, 'index', 'query', {
LIMITED: true,
VERBATIM: true,
STEPS: [{
type: FT_AGGREGATE_STEPS.SORTBY,
BY: '@by'
}]
}),
['FT.PROFILE', 'index', 'AGGREGATE', 'LIMITED', 'QUERY', 'query',
'VERBATIM', 'SORTBY', '1', '@by', 'DIALECT', DEFAULT_DIALECT]
);
});
describe('transformArguments', () => {
it('without options', () => {
assert.deepEqual(
parseArgs(PROFILE_AGGREGATE, 'index', 'query'),
['FT.PROFILE', 'index', 'AGGREGATE', 'QUERY', 'query', 'DIALECT', DEFAULT_DIALECT]
);
});
testUtils.testWithClient('client.ft.search', async client => {
await Promise.all([
client.ft.create('index', {
field: SCHEMA_FIELD_TYPE.NUMERIC
}),
client.hSet('1', 'field', '1'),
client.hSet('2', 'field', '2')
]);
const res = await client.ft.profileAggregate('index', '*');
assert.deepEqual('None', res.profile.warning);
assert.ok(typeof res.profile.iteratorsProfile.counter === 'number');
assert.ok(typeof res.profile.parsingTime === 'string');
assert.ok(res.results.total == 1);
}, GLOBAL.SERVERS.OPEN);
it('with options', () => {
assert.deepEqual(
parseArgs(PROFILE_AGGREGATE, 'index', 'query', {
LIMITED: true,
VERBATIM: true,
STEPS: [{
type: FT_AGGREGATE_STEPS.SORTBY,
BY: '@by'
}]
}),
['FT.PROFILE', 'index', 'AGGREGATE', 'LIMITED', 'QUERY', 'query',
'VERBATIM', 'SORTBY', '1', '@by', 'DIALECT', DEFAULT_DIALECT]
);
});
});
testUtils.testWithClientIfVersionWithinRange([[8], 'LATEST'], 'client.ft.search', async client => {
await Promise.all([
client.ft.create('index', {
field: SCHEMA_FIELD_TYPE.NUMERIC
}),
client.hSet('1', 'field', '1'),
client.hSet('2', 'field', '2')
]);
const normalizeObject = obj => JSON.parse(JSON.stringify(obj));
const res = await client.ft.profileAggregate('index', '*');
const normalizedRes = normalizeObject(res);
assert.equal(normalizedRes.results.total, 1);
assert.ok(normalizedRes.profile[0] === 'Shards');
assert.ok(Array.isArray(normalizedRes.profile[1]));
assert.ok(normalizedRes.profile[2] === 'Coordinator');
assert.ok(Array.isArray(normalizedRes.profile[3]));
const shardProfile = normalizedRes.profile[1][0];
assert.ok(shardProfile.includes('Total profile time'));
assert.ok(shardProfile.includes('Parsing time'));
assert.ok(shardProfile.includes('Pipeline creation time'));
assert.ok(shardProfile.includes('Warning'));
assert.ok(shardProfile.includes('Iterators profile'));
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClientIfVersionWithinRange([[7, 2, 0], [7, 4, 0]], 'client.ft.search', async client => {
await Promise.all([
client.ft.create('index', {
field: SCHEMA_FIELD_TYPE.NUMERIC
}),
client.hSet('1', 'field', '1'),
client.hSet('2', 'field', '2')
]);
const normalizeObject = obj => JSON.parse(JSON.stringify(obj));
const res = await client.ft.profileAggregate('index', '*');
const normalizedRes = normalizeObject(res);
assert.equal(normalizedRes.results.total, 1);
assert.ok(Array.isArray(normalizedRes.profile));
assert.equal(normalizedRes.profile[0][0], 'Total profile time');
assert.equal(normalizedRes.profile[1][0], 'Parsing time');
assert.equal(normalizedRes.profile[2][0], 'Pipeline creation time');
assert.equal(normalizedRes.profile[3][0], 'Warning');
assert.equal(normalizedRes.profile[4][0], 'Iterators profile');
assert.equal(normalizedRes.profile[5][0], 'Result processors profile');
const iteratorsProfile = normalizedRes.profile[4][1];
assert.equal(iteratorsProfile[0], 'Type');
assert.equal(iteratorsProfile[1], 'WILDCARD');
assert.equal(iteratorsProfile[2], 'Time');
assert.equal(iteratorsProfile[4], 'Counter');
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClientIfVersionWithinRange([[8], 'LATEST'], '[RESP3] client.ft.search', async client => {
await Promise.all([
client.ft.create('index', {
field: SCHEMA_FIELD_TYPE.NUMERIC
}),
client.hSet('1', 'field', '1'),
client.hSet('2', 'field', '2')
]);
const normalizeObject = obj => JSON.parse(JSON.stringify(obj));
const res = await client.ft.profileAggregate('index', '*');
const normalizedRes = normalizeObject(res);
assert.equal(normalizedRes.Results.total_results, 1);
assert.ok(normalizedRes.Profile.Shards);
}, GLOBAL.SERVERS.OPEN_3)
});

View File

@@ -1,37 +1,35 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { Command, ReplyUnion } from "@redis/client/dist/lib/RESP/types";
import AGGREGATE, { AggregateRawReply, FtAggregateOptions, parseAggregateOptions } from "./AGGREGATE";
import { ProfileOptions, ProfileRawReply, ProfileReply, transformProfile } from "./PROFILE_SEARCH";
import { Command, ReplyUnion, UnwrapReply } from '@redis/client/dist/lib/RESP/types';
import AGGREGATE, { AggregateRawReply, FtAggregateOptions, parseAggregateOptions } from './AGGREGATE';
import { ProfileOptions, ProfileRawReplyResp2, ProfileReplyResp2, } from './PROFILE_SEARCH';
export default {
NOT_KEYED_COMMAND: true,
IS_READ_ONLY: true,
parseCommand(
parser: CommandParser,
index: string,
query: string,
options?: ProfileOptions & FtAggregateOptions
) {
parser.push('FT.PROFILE', index, 'AGGREGATE');
if (options?.LIMITED) {
parser.push('LIMITED');
IS_READ_ONLY: true,
parseCommand(
parser: CommandParser,
index: string,
query: string,
options?: ProfileOptions & FtAggregateOptions
) {
parser.push('FT.PROFILE', index, 'AGGREGATE');
if (options?.LIMITED) {
parser.push('LIMITED');
}
parser.push('QUERY', query);
parseAggregateOptions(parser, options)
},
transformReply: {
2: (reply: UnwrapReply<ProfileRawReplyResp2<AggregateRawReply>>): ProfileReplyResp2 => {
return {
results: AGGREGATE.transformReply[2](reply[0]),
profile: reply[1]
}
parser.push('QUERY', query);
parseAggregateOptions(parser, options)
},
transformReply: {
2: (reply: ProfileAggeregateRawReply): ProfileReply => {
return {
results: AGGREGATE.transformReply[2](reply[0]),
profile: transformProfile(reply[1])
}
},
3: undefined as unknown as () => ReplyUnion
},
unstableResp3: true
} as const satisfies Command;
type ProfileAggeregateRawReply = ProfileRawReply<AggregateRawReply>;
3: (reply: ReplyUnion): ReplyUnion => reply
},
unstableResp3: true
} as const satisfies Command;

View File

@@ -6,39 +6,90 @@ import { parseArgs } from '@redis/client/lib/commands/generic-transformers';
import { DEFAULT_DIALECT } from '../dialect/default';
describe('PROFILE SEARCH', () => {
describe('transformArguments', () => {
it('without options', () => {
assert.deepEqual(
parseArgs(PROFILE_SEARCH, 'index', 'query'),
['FT.PROFILE', 'index', 'SEARCH', 'QUERY', 'query', 'DIALECT', DEFAULT_DIALECT]
);
});
it('with options', () => {
assert.deepEqual(
parseArgs(PROFILE_SEARCH, 'index', 'query', {
LIMITED: true,
VERBATIM: true,
INKEYS: 'key'
}),
['FT.PROFILE', 'index', 'SEARCH', 'LIMITED', 'QUERY', 'query',
'VERBATIM', 'INKEYS', '1', 'key', 'DIALECT', DEFAULT_DIALECT]
);
});
describe('transformArguments', () => {
it('without options', () => {
assert.deepEqual(
parseArgs(PROFILE_SEARCH, 'index', 'query'),
['FT.PROFILE', 'index', 'SEARCH', 'QUERY', 'query', 'DIALECT', DEFAULT_DIALECT]
);
});
testUtils.testWithClient('client.ft.search', async client => {
await Promise.all([
client.ft.create('index', {
field: SCHEMA_FIELD_TYPE.NUMERIC
}),
client.hSet('1', 'field', '1')
]);
const res = await client.ft.profileSearch('index', '*');
assert.strictEqual('None', res.profile.warning);
assert.ok(typeof res.profile.iteratorsProfile.counter === 'number');
assert.ok(typeof res.profile.parsingTime === 'string');
assert.ok(res.results.total == 1);
}, GLOBAL.SERVERS.OPEN);
it('with options', () => {
assert.deepEqual(
parseArgs(PROFILE_SEARCH, 'index', 'query', {
LIMITED: true,
VERBATIM: true,
INKEYS: 'key'
}),
['FT.PROFILE', 'index', 'SEARCH', 'LIMITED', 'QUERY', 'query',
'VERBATIM', 'INKEYS', '1', 'key', 'DIALECT', DEFAULT_DIALECT]
);
});
});
testUtils.testWithClientIfVersionWithinRange([[8], 'LATEST'], 'client.ft.search', async client => {
await Promise.all([
client.ft.create('index', {
field: SCHEMA_FIELD_TYPE.NUMERIC
}),
client.hSet('1', 'field', '1')
]);
const normalizeObject = obj => JSON.parse(JSON.stringify(obj));
const res = await client.ft.profileSearch('index', '*');
const normalizedRes = normalizeObject(res);
assert.equal(normalizedRes.results.total, 1);
assert.ok(normalizedRes.profile[0] === 'Shards');
assert.ok(Array.isArray(normalizedRes.profile[1]));
assert.ok(normalizedRes.profile[2] === 'Coordinator');
assert.ok(Array.isArray(normalizedRes.profile[3]));
const shardProfile = normalizedRes.profile[1][0];
assert.ok(shardProfile.includes('Total profile time'));
assert.ok(shardProfile.includes('Parsing time'));
assert.ok(shardProfile.includes('Pipeline creation time'));
assert.ok(shardProfile.includes('Warning'));
assert.ok(shardProfile.includes('Iterators profile'));
;
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClientIfVersionWithinRange([[7, 2, 0], [7, 4, 0]], 'client.ft.search', async client => {
await Promise.all([
client.ft.create('index', {
field: SCHEMA_FIELD_TYPE.NUMERIC
}),
client.hSet('1', 'field', '1')
]);
const normalizeObject = obj => JSON.parse(JSON.stringify(obj));
const res = await client.ft.profileSearch('index', '*');
const normalizedRes = normalizeObject(res);
assert.equal(normalizedRes.results.total, 1);
assert.ok(Array.isArray(normalizedRes.profile));
assert.equal(normalizedRes.profile[0][0], 'Total profile time');
assert.equal(normalizedRes.profile[1][0], 'Parsing time');
assert.equal(normalizedRes.profile[2][0], 'Pipeline creation time');
assert.equal(normalizedRes.profile[3][0], 'Warning');
assert.equal(normalizedRes.profile[4][0], 'Iterators profile');
assert.equal(normalizedRes.profile[5][0], 'Result processors profile');
const iteratorsProfile = normalizedRes.profile[4][1];
assert.equal(iteratorsProfile[0], 'Type');
assert.equal(iteratorsProfile[1], 'WILDCARD');
assert.equal(iteratorsProfile[2], 'Time');
assert.equal(iteratorsProfile[4], 'Counter');
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,23 +1,19 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { Command, RedisArgument, ReplyUnion } from "@redis/client/dist/lib/RESP/types";
import { AggregateReply } from "./AGGREGATE";
import SEARCH, { FtSearchOptions, SearchRawReply, SearchReply, parseSearchOptions } from "./SEARCH";
import { ArrayReply, Command, RedisArgument, ReplyUnion, TuplesReply, UnwrapReply } from '@redis/client/dist/lib/RESP/types';
import { AggregateReply } from './AGGREGATE';
import SEARCH, { FtSearchOptions, SearchRawReply, SearchReply, parseSearchOptions } from './SEARCH';
export type ProfileRawReply<T> = [
results: T,
profile: [
_: string,
TotalProfileTime: string,
_: string,
ParsingTime: string,
_: string,
PipelineCreationTime: string,
_: string,
IteratorsProfile: Array<any>
]
];
export type ProfileRawReplyResp2<T> = TuplesReply<[
T,
ArrayReply<ReplyUnion>
]>;
type ProfileSearchRawReply = ProfileRawReply<SearchRawReply>;
type ProfileSearchResponseResp2 = ProfileRawReplyResp2<SearchRawReply>;
export interface ProfileReplyResp2 {
results: SearchReply | AggregateReply;
profile: ReplyUnion;
}
export interface ProfileOptions {
LIMITED?: true;
@@ -43,108 +39,13 @@ export default {
parseSearchOptions(parser, options);
},
transformReply: {
2: (reply: ProfileSearchRawReply, withoutDocuments: boolean): ProfileReply => {
2: (reply: UnwrapReply<ProfileSearchResponseResp2>): ProfileReplyResp2 => {
return {
results: SEARCH.transformReply[2](reply[0]),
profile: transformProfile(reply[1])
}
profile: reply[1]
};
},
3: undefined as unknown as () => ReplyUnion
3: (reply: ReplyUnion): ReplyUnion => reply
},
unstableResp3: true
} as const satisfies Command;
export interface ProfileReply {
results: SearchReply | AggregateReply;
profile: ProfileData;
}
interface ChildIterator {
type?: string,
counter?: number,
term?: string,
size?: number,
time?: string,
childIterators?: Array<ChildIterator>
}
interface IteratorsProfile {
type?: string,
counter?: number,
queryType?: string,
time?: string,
childIterators?: Array<ChildIterator>
}
interface ProfileData {
totalProfileTime: string,
parsingTime: string,
pipelineCreationTime: string,
warning: string,
iteratorsProfile: IteratorsProfile
}
export function transformProfile(reply: Array<any>): ProfileData{
return {
totalProfileTime: reply[0][1],
parsingTime: reply[1][1],
pipelineCreationTime: reply[2][1],
warning: reply[3][1] ? reply[3][1] : 'None',
iteratorsProfile: transformIterators(reply[4][1])
};
}
function transformIterators(IteratorsProfile: Array<any>): IteratorsProfile {
var res: IteratorsProfile = {};
for (let i = 0; i < IteratorsProfile.length; i += 2) {
const value = IteratorsProfile[i+1];
switch (IteratorsProfile[i]) {
case 'Type':
res.type = value;
break;
case 'Counter':
res.counter = value;
break;
case 'Time':
res.time = value;
break;
case 'Query type':
res.queryType = value;
break;
case 'Child iterators':
res.childIterators = value.map(transformChildIterators);
break;
}
}
return res;
}
function transformChildIterators(IteratorsProfile: Array<any>): ChildIterator {
var res: ChildIterator = {};
for (let i = 1; i < IteratorsProfile.length; i += 2) {
const value = IteratorsProfile[i+1];
switch (IteratorsProfile[i]) {
case 'Type':
res.type = value;
break;
case 'Counter':
res.counter = value;
break;
case 'Time':
res.time = value;
break;
case 'Size':
res.size = value;
break;
case 'Term':
res.term = value;
break;
case 'Child iterators':
res.childIterators = value.map(transformChildIterators);
break;
}
}
return res;
}

View File

@@ -28,12 +28,22 @@ describe('FT.SUGGET', () => {
});
describe('client.ft.sugGet', () => {
testUtils.testWithClient('null', async client => {
assert.equal(
testUtils.testWithClientIfVersionWithinRange([[8], 'LATEST'], 'null', async client => {
assert.deepStrictEqual(
await client.ft.sugGet('key', 'prefix'),
[]
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClientIfVersionWithinRange([[6, 2, 0], [7, 4, 0]], 'null', async client => {
assert.deepStrictEqual(
await client.ft.sugGet('key', 'prefix'),
null
);
}, GLOBAL.SERVERS.OPEN);
}, GLOBAL.SERVERS.OPEN)
testUtils.testWithClient('with suggestions', async client => {
const [, reply] = await Promise.all([

View File

@@ -11,14 +11,21 @@ describe('FT.SUGGET WITHPAYLOADS', () => {
);
});
describe('client.ft.sugGetWithPayloads', () => {
testUtils.testWithClient('null', async client => {
assert.equal(
await client.ft.sugGetWithPayloads('key', 'prefix'),
null
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClientIfVersionWithinRange([[8], 'LATEST'], 'null', async client => {
assert.deepStrictEqual(
await client.ft.sugGetWithPayloads('key', 'prefix'),
[]
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClientIfVersionWithinRange([[6], [7, 4, 0]], 'null', async client => {
assert.deepStrictEqual(
await client.ft.sugGetWithPayloads('key', 'prefix'),
null
);
}, GLOBAL.SERVERS.OPEN);
describe('with suggestions', () => {
testUtils.testWithClient('with suggestions', async client => {
const [, reply] = await Promise.all([
client.ft.sugAdd('key', 'string', 1, {

View File

@@ -12,14 +12,15 @@ describe('FT.SUGGET WITHSCORES', () => {
});
describe('client.ft.sugGetWithScores', () => {
testUtils.testWithClient('null', async client => {
assert.equal(
testUtils.testWithClientIfVersionWithinRange([[8],'LATEST'], 'null', async client => {
assert.deepStrictEqual(
await client.ft.sugGetWithScores('key', 'prefix'),
null
[]
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClient('with suggestions', async client => {
testUtils.testWithClientIfVersionWithinRange([[8],'LATEST'],'with suggestions', async client => {
const [, reply] = await Promise.all([
client.ft.sugAdd('key', 'string', 1),
client.ft.sugGetWithScores('key', 's')

View File

@@ -12,14 +12,14 @@ describe('FT.SUGGET WITHSCORES WITHPAYLOADS', () => {
});
describe('client.ft.sugGetWithScoresWithPayloads', () => {
testUtils.testWithClient('null', async client => {
assert.equal(
testUtils.testWithClientIfVersionWithinRange([[8], 'LATEST'], 'null', async client => {
assert.deepStrictEqual(
await client.ft.sugGetWithScoresWithPayloads('key', 'prefix'),
null
[]
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClient('with suggestions', async client => {
testUtils.testWithClientIfVersionWithinRange([[8], 'LATEST'], 'with suggestions', async client => {
const [, reply] = await Promise.all([
client.ft.sugAdd('key', 'string', 1, {
PAYLOAD: 'payload'

View File

@@ -48,9 +48,21 @@ export default {
aliasDel: ALIASDEL,
ALIASUPDATE,
aliasUpdate: ALIASUPDATE,
/**
* @deprecated Redis >=8 uses the standard CONFIG command
*/
CONFIG_GET,
/**
* @deprecated Redis >=8 uses the standard CONFIG command
*/
configGet: CONFIG_GET,
/**
* @deprecated Redis >=8 uses the standard CONFIG command
*/
CONFIG_SET,
/**
* @deprecated Redis >=8 uses the standard CONFIG command
*/
configSet: CONFIG_SET,
CREATE,
create: CREATE,