You've already forked node-redis
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:
@@ -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);
|
||||
|
||||
});
|
||||
|
@@ -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);
|
||||
});
|
||||
|
@@ -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)
|
||||
|
||||
});
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
||||
});
|
||||
|
@@ -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;
|
||||
}
|
@@ -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([
|
||||
|
@@ -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, {
|
||||
|
@@ -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')
|
||||
|
@@ -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'
|
||||
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user