You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-06 02:15:48 +03:00
use dockers for tests, use npm workspaces, add rejson & redisearch modules, fix some bugs
This commit is contained in:
482
packages/search/lib/commands/AGGREGATE.spec.ts
Normal file
482
packages/search/lib/commands/AGGREGATE.spec.ts
Normal file
@@ -0,0 +1,482 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { AggregateGroupByReducers, AggregateSteps, transformArguments } from './AGGREGATE';
|
||||
import { SchemaFieldTypes } from './CREATE';
|
||||
|
||||
describe('AGGREGATE', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*'),
|
||||
['FT.AGGREGATE', 'index', '*']
|
||||
);
|
||||
});
|
||||
|
||||
it('with VERBATIM', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', { VERBATIM: true }),
|
||||
['FT.AGGREGATE', 'index', '*', 'VERBATIM']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with LOAD', () => {
|
||||
describe('single', () => {
|
||||
describe('without alias', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', { LOAD: '@property' }),
|
||||
['FT.AGGREGATE', 'index', '*', 'LOAD', '1', '@property']
|
||||
);
|
||||
});
|
||||
|
||||
it('{ identifier: string }', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
LOAD: {
|
||||
identifier: '@property'
|
||||
}
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'LOAD', '1', '@property']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with alias', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
LOAD: {
|
||||
identifier: '@property',
|
||||
AS: 'alias'
|
||||
}
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'LOAD', '3', '@property', 'AS', 'alias']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('multiple', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', { LOAD: ['@1', '@2'] }),
|
||||
['FT.AGGREGATE', 'index', '*', 'LOAD', '2', '@1', '@2']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with STEPS', () => {
|
||||
describe('GROUPBY', () => {
|
||||
describe('COUNT', () => {
|
||||
describe('without properties', () => {
|
||||
it('without alias', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.GROUPBY,
|
||||
REDUCE: {
|
||||
type: AggregateGroupByReducers.COUNT
|
||||
}
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'GROUPBY', '0', 'REDUCE', 'COUNT', '0']
|
||||
);
|
||||
});
|
||||
|
||||
it('with alias', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.GROUPBY,
|
||||
REDUCE: {
|
||||
type: AggregateGroupByReducers.COUNT,
|
||||
AS: 'count'
|
||||
}
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'GROUPBY', '0', 'REDUCE', 'COUNT', '0', 'AS', 'count']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with properties', () => {
|
||||
it('single', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.GROUPBY,
|
||||
properties: '@property',
|
||||
REDUCE: {
|
||||
type: AggregateGroupByReducers.COUNT
|
||||
}
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'GROUPBY', '1', '@property', 'REDUCE', 'COUNT', '0']
|
||||
);
|
||||
});
|
||||
|
||||
it('multiple', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.GROUPBY,
|
||||
properties: ['@1', '@2'],
|
||||
REDUCE: {
|
||||
type: AggregateGroupByReducers.COUNT
|
||||
}
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'GROUPBY', '2', '@1', '@2', 'REDUCE', 'COUNT', '0']
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('COUNT_DISTINCT', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.GROUPBY,
|
||||
REDUCE: {
|
||||
type: AggregateGroupByReducers.COUNT_DISTINCT,
|
||||
property: '@property'
|
||||
}
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'GROUPBY', '0', 'REDUCE', 'COUNT_DISTINCT', '1', '@property']
|
||||
);
|
||||
});
|
||||
|
||||
it('COUNT_DISTINCTISH', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.GROUPBY,
|
||||
REDUCE: {
|
||||
type: AggregateGroupByReducers.COUNT_DISTINCTISH,
|
||||
property: '@property'
|
||||
}
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'GROUPBY', '0', 'REDUCE', 'COUNT_DISTINCTISH', '1', '@property']
|
||||
);
|
||||
});
|
||||
|
||||
it('SUM', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.GROUPBY,
|
||||
REDUCE: {
|
||||
type: AggregateGroupByReducers.SUM,
|
||||
property: '@property'
|
||||
}
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'GROUPBY', '0', 'REDUCE', 'SUM', '1', '@property']
|
||||
);
|
||||
});
|
||||
|
||||
it('MIN', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.GROUPBY,
|
||||
REDUCE: {
|
||||
type: AggregateGroupByReducers.MIN,
|
||||
property: '@property'
|
||||
}
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'GROUPBY', '0', 'REDUCE', 'MIN', '1', '@property']
|
||||
);
|
||||
});
|
||||
|
||||
it('MAX', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.GROUPBY,
|
||||
REDUCE: {
|
||||
type: AggregateGroupByReducers.MAX,
|
||||
property: '@property'
|
||||
}
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'GROUPBY', '0', 'REDUCE', 'MAX', '1', '@property']
|
||||
);
|
||||
});
|
||||
|
||||
it('AVG', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.GROUPBY,
|
||||
REDUCE: {
|
||||
type: AggregateGroupByReducers.AVG,
|
||||
property: '@property'
|
||||
}
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'GROUPBY', '0', 'REDUCE', 'AVG', '1', '@property']
|
||||
);
|
||||
});
|
||||
|
||||
it('STDDEV', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.GROUPBY,
|
||||
REDUCE: {
|
||||
type: AggregateGroupByReducers.STDDEV,
|
||||
property: '@property'
|
||||
}
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'GROUPBY', '0', 'REDUCE', 'STDDEV', '1', '@property']
|
||||
);
|
||||
});
|
||||
|
||||
it('QUANTILE', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.GROUPBY,
|
||||
REDUCE: {
|
||||
type: AggregateGroupByReducers.QUANTILE,
|
||||
property: '@property',
|
||||
quantile: 0.5
|
||||
}
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'GROUPBY', '0', 'REDUCE', 'QUANTILE', '2', '@property', '0.5']
|
||||
);
|
||||
});
|
||||
|
||||
it('TO_LIST', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.GROUPBY,
|
||||
REDUCE: {
|
||||
type: AggregateGroupByReducers.TO_LIST,
|
||||
property: '@property'
|
||||
}
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'GROUPBY', '0', 'REDUCE', 'TOLIST', '1', '@property']
|
||||
);
|
||||
});
|
||||
|
||||
describe('FIRST_VALUE', () => {
|
||||
it('simple', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.GROUPBY,
|
||||
REDUCE: {
|
||||
type: AggregateGroupByReducers.FIRST_VALUE,
|
||||
property: '@property'
|
||||
}
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'GROUPBY', '0', 'REDUCE', 'FIRST_VALUE', '1', '@property']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with BY', () => {
|
||||
describe('without direction', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.GROUPBY,
|
||||
REDUCE: {
|
||||
type: AggregateGroupByReducers.FIRST_VALUE,
|
||||
property: '@property',
|
||||
BY: '@by'
|
||||
}
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'GROUPBY', '0', 'REDUCE', 'FIRST_VALUE', '3', '@property', 'BY', '@by']
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
it('{ property: string }', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.GROUPBY,
|
||||
REDUCE: {
|
||||
type: AggregateGroupByReducers.FIRST_VALUE,
|
||||
property: '@property',
|
||||
BY: {
|
||||
property: '@by'
|
||||
}
|
||||
}
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'GROUPBY', '0', 'REDUCE', 'FIRST_VALUE', '3', '@property', 'BY', '@by']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with direction', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.GROUPBY,
|
||||
REDUCE: {
|
||||
type: AggregateGroupByReducers.FIRST_VALUE,
|
||||
property: '@property',
|
||||
BY: {
|
||||
property: '@by',
|
||||
direction: 'ASC'
|
||||
}
|
||||
}
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'GROUPBY', '0', 'REDUCE', 'FIRST_VALUE', '4', '@property', 'BY', '@by', 'ASC']
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('RANDOM_SAMPLE', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.GROUPBY,
|
||||
REDUCE: {
|
||||
type: AggregateGroupByReducers.RANDOM_SAMPLE,
|
||||
property: '@property',
|
||||
sampleSize: 1
|
||||
}
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'GROUPBY', '0', 'REDUCE', 'RANDOM_SAMPLE', '2', '@property', '1']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SORTBY', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.SORTBY,
|
||||
BY: '@by'
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'SORTBY', '1', '@by']
|
||||
);
|
||||
});
|
||||
|
||||
it('Array', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.SORTBY,
|
||||
BY: ['@1', '@2']
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'SORTBY', '2', '@1', '@2']
|
||||
);
|
||||
});
|
||||
|
||||
it('with MAX', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.SORTBY,
|
||||
BY: '@by',
|
||||
MAX: 1
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'SORTBY', '1', '@by', 'MAX', '1']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('APPLY', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.APPLY,
|
||||
expression: '@field + 1',
|
||||
AS: 'as'
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'APPLY', '@field + 1', 'AS', 'as']
|
||||
);
|
||||
});
|
||||
|
||||
describe('LIMIT', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.LIMIT,
|
||||
from: 0,
|
||||
size: 1
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'LIMIT', '0', '1']
|
||||
);
|
||||
});
|
||||
|
||||
describe('FILTER', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.FILTER,
|
||||
expression: '@field != ""'
|
||||
}]
|
||||
}),
|
||||
['FT.AGGREGATE', 'index', '*', 'FILTER', '@field != ""']
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.aggregate', async client => {
|
||||
await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: SchemaFieldTypes.NUMERIC
|
||||
}),
|
||||
client.hSet('1', 'field', '1'),
|
||||
client.hSet('2', 'field', '2')
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.aggregate('index', '*', {
|
||||
STEPS: [{
|
||||
type: AggregateSteps.GROUPBY,
|
||||
REDUCE: [{
|
||||
type: AggregateGroupByReducers.SUM,
|
||||
property: '@field',
|
||||
AS: 'sum'
|
||||
}, {
|
||||
type: AggregateGroupByReducers.AVG,
|
||||
property: '@field',
|
||||
AS: 'avg'
|
||||
}]
|
||||
}]
|
||||
}),
|
||||
{
|
||||
total: 1,
|
||||
results: [
|
||||
Object.create(null, {
|
||||
sum: {
|
||||
value: '3',
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
},
|
||||
avg: {
|
||||
value: '1.5',
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
283
packages/search/lib/commands/AGGREGATE.ts
Normal file
283
packages/search/lib/commands/AGGREGATE.ts
Normal file
@@ -0,0 +1,283 @@
|
||||
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||
import { pushVerdictArgument, transformReplyTuples, TuplesObject } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
import { PropertyName, pushArgumentsWithLength, pushSortByArguments, SortByOptions } from '.';
|
||||
|
||||
export enum AggregateSteps {
|
||||
GROUPBY = 'GROUPBY',
|
||||
SORTBY = 'SORTBY',
|
||||
APPLY = 'APPLY',
|
||||
LIMIT = 'LIMIT',
|
||||
FILTER = 'FILTER'
|
||||
}
|
||||
|
||||
interface AggregateStep<T extends AggregateSteps> {
|
||||
type: T;
|
||||
}
|
||||
|
||||
export enum AggregateGroupByReducers {
|
||||
COUNT = 'COUNT',
|
||||
COUNT_DISTINCT = 'COUNT_DISTINCT',
|
||||
COUNT_DISTINCTISH = 'COUNT_DISTINCTISH',
|
||||
SUM = 'SUM',
|
||||
MIN = 'MIN',
|
||||
MAX = 'MAX',
|
||||
AVG = 'AVG',
|
||||
STDDEV = 'STDDEV',
|
||||
QUANTILE = 'QUANTILE',
|
||||
TOLIST = 'TOLIST',
|
||||
TO_LIST = 'TOLIST',
|
||||
FIRST_VALUE = 'FIRST_VALUE',
|
||||
RANDOM_SAMPLE = 'RANDOM_SAMPLE'
|
||||
}
|
||||
|
||||
interface GroupByReducer<T extends AggregateGroupByReducers> {
|
||||
type: T;
|
||||
AS?: string;
|
||||
}
|
||||
|
||||
type CountReducer = GroupByReducer<AggregateGroupByReducers.COUNT>;
|
||||
|
||||
interface CountDistinctReducer extends GroupByReducer<AggregateGroupByReducers.COUNT_DISTINCT> {
|
||||
property: PropertyName;
|
||||
}
|
||||
|
||||
interface CountDistinctishReducer extends GroupByReducer<AggregateGroupByReducers.COUNT_DISTINCTISH> {
|
||||
property: PropertyName;
|
||||
}
|
||||
|
||||
interface SumReducer extends GroupByReducer<AggregateGroupByReducers.SUM> {
|
||||
property: PropertyName;
|
||||
}
|
||||
|
||||
interface MinReducer extends GroupByReducer<AggregateGroupByReducers.MIN> {
|
||||
property: PropertyName;
|
||||
}
|
||||
|
||||
interface MaxReducer extends GroupByReducer<AggregateGroupByReducers.MAX> {
|
||||
property: PropertyName;
|
||||
}
|
||||
|
||||
interface AvgReducer extends GroupByReducer<AggregateGroupByReducers.AVG> {
|
||||
property: PropertyName;
|
||||
}
|
||||
|
||||
interface StdDevReducer extends GroupByReducer<AggregateGroupByReducers.STDDEV> {
|
||||
property: PropertyName;
|
||||
}
|
||||
|
||||
interface QuantileReducer extends GroupByReducer<AggregateGroupByReducers.QUANTILE> {
|
||||
property: PropertyName;
|
||||
quantile: number;
|
||||
}
|
||||
|
||||
interface ToListReducer extends GroupByReducer<AggregateGroupByReducers.TOLIST> {
|
||||
property: PropertyName;
|
||||
}
|
||||
|
||||
interface FirstValueReducer extends GroupByReducer<AggregateGroupByReducers.FIRST_VALUE> {
|
||||
property: PropertyName;
|
||||
BY?: PropertyName | {
|
||||
property: PropertyName;
|
||||
direction?: 'ASC' | 'DESC';
|
||||
};
|
||||
}
|
||||
|
||||
interface RandomSampleReducer extends GroupByReducer<AggregateGroupByReducers.RANDOM_SAMPLE> {
|
||||
property: PropertyName;
|
||||
sampleSize: number;
|
||||
}
|
||||
|
||||
type GroupByReducers = CountReducer | CountDistinctReducer | CountDistinctishReducer | SumReducer | MinReducer | MaxReducer | AvgReducer | StdDevReducer | QuantileReducer | ToListReducer | FirstValueReducer | RandomSampleReducer;
|
||||
|
||||
interface GroupByStep extends AggregateStep<AggregateSteps.GROUPBY> {
|
||||
properties?: PropertyName | Array<PropertyName>;
|
||||
REDUCE: GroupByReducers | Array<GroupByReducers>;
|
||||
}
|
||||
|
||||
interface SortStep extends AggregateStep<AggregateSteps.SORTBY> {
|
||||
BY: SortByOptions | Array<SortByOptions>;
|
||||
MAX?: number;
|
||||
}
|
||||
|
||||
interface ApplyStep extends AggregateStep<AggregateSteps.APPLY> {
|
||||
expression: string;
|
||||
AS: string;
|
||||
}
|
||||
|
||||
interface LimitStep extends AggregateStep<AggregateSteps.LIMIT> {
|
||||
from: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
interface FilterStep extends AggregateStep<AggregateSteps.FILTER> {
|
||||
expression: string;
|
||||
}
|
||||
|
||||
type LoadField = PropertyName | {
|
||||
identifier: PropertyName;
|
||||
AS?: string;
|
||||
}
|
||||
|
||||
interface AggregateOptions {
|
||||
VERBATIM?: true;
|
||||
LOAD?: LoadField | Array<LoadField>;
|
||||
STEPS?: Array<GroupByStep | SortStep | ApplyStep | LimitStep | FilterStep>;
|
||||
}
|
||||
|
||||
export function transformArguments(index: string, query: string, options?: AggregateOptions): RedisCommandArguments {
|
||||
const args = ['FT.AGGREGATE', index, query];
|
||||
|
||||
if (options?.VERBATIM) {
|
||||
args.push('VERBATIM');
|
||||
}
|
||||
|
||||
if (options?.LOAD) {
|
||||
args.push('LOAD');
|
||||
pushArgumentsWithLength(args, () => {
|
||||
if (Array.isArray(options.LOAD)) {
|
||||
for (const load of options.LOAD) {
|
||||
pushLoadField(args, load);
|
||||
}
|
||||
} else {
|
||||
pushLoadField(args, options.LOAD!);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (options?.STEPS) {
|
||||
for (const step of options.STEPS) {
|
||||
switch (step.type) {
|
||||
case AggregateSteps.GROUPBY:
|
||||
args.push('GROUPBY');
|
||||
if (!step.properties) {
|
||||
args.push('0');
|
||||
} else {
|
||||
pushVerdictArgument(args, step.properties);
|
||||
}
|
||||
|
||||
if (Array.isArray(step.REDUCE)) {
|
||||
for (const reducer of step.REDUCE) {
|
||||
pushGroupByReducer(args, reducer);
|
||||
}
|
||||
} else {
|
||||
pushGroupByReducer(args, step.REDUCE);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case AggregateSteps.SORTBY:
|
||||
pushSortByArguments(args, 'SORTBY', step.BY);
|
||||
|
||||
if (step.MAX) {
|
||||
args.push('MAX', step.MAX.toString());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case AggregateSteps.APPLY:
|
||||
args.push('APPLY', step.expression, 'AS', step.AS);
|
||||
break;
|
||||
|
||||
case AggregateSteps.LIMIT:
|
||||
args.push('LIMIT', step.from.toString(), step.size.toString());
|
||||
break;
|
||||
|
||||
case AggregateSteps.FILTER:
|
||||
args.push('FILTER', step.expression);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
function pushLoadField(args: RedisCommandArguments, toLoad: LoadField): void {
|
||||
if (typeof toLoad === 'string') {
|
||||
args.push(toLoad);
|
||||
} else {
|
||||
args.push(toLoad.identifier);
|
||||
|
||||
if (toLoad.AS) {
|
||||
args.push('AS', toLoad.AS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pushGroupByReducer(args: RedisCommandArguments, reducer: GroupByReducers): void {
|
||||
args.push('REDUCE', reducer.type);
|
||||
|
||||
switch (reducer.type) {
|
||||
case AggregateGroupByReducers.COUNT:
|
||||
args.push('0');
|
||||
break;
|
||||
|
||||
case AggregateGroupByReducers.COUNT_DISTINCT:
|
||||
case AggregateGroupByReducers.COUNT_DISTINCTISH:
|
||||
case AggregateGroupByReducers.SUM:
|
||||
case AggregateGroupByReducers.MIN:
|
||||
case AggregateGroupByReducers.MAX:
|
||||
case AggregateGroupByReducers.AVG:
|
||||
case AggregateGroupByReducers.STDDEV:
|
||||
case AggregateGroupByReducers.TOLIST:
|
||||
args.push('1', reducer.property);
|
||||
break;
|
||||
|
||||
case AggregateGroupByReducers.QUANTILE:
|
||||
args.push('2', reducer.property, reducer.quantile.toString());
|
||||
break;
|
||||
|
||||
case AggregateGroupByReducers.FIRST_VALUE: {
|
||||
pushArgumentsWithLength(args, () => {
|
||||
args.push(reducer.property);
|
||||
|
||||
if (reducer.BY) {
|
||||
args.push('BY');
|
||||
if (typeof reducer.BY === 'string') {
|
||||
args.push(reducer.BY);
|
||||
} else {
|
||||
args.push(reducer.BY.property);
|
||||
|
||||
if (reducer.BY.direction) {
|
||||
args.push(reducer.BY.direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case AggregateGroupByReducers.RANDOM_SAMPLE:
|
||||
args.push('2', reducer.property, reducer.sampleSize.toString());
|
||||
break;
|
||||
}
|
||||
|
||||
if (reducer.AS) {
|
||||
args.push('AS', reducer.AS);
|
||||
}
|
||||
}
|
||||
|
||||
type AggregateRawReply = [
|
||||
total: number,
|
||||
...results: Array<Array<string>>
|
||||
];
|
||||
|
||||
interface AggregateReply {
|
||||
total: number;
|
||||
results: Array<TuplesObject>;
|
||||
}
|
||||
|
||||
export function transformReply(rawReply: AggregateRawReply): AggregateReply {
|
||||
const results: Array<TuplesObject> = [];
|
||||
for (let i = 1; i < rawReply.length; i++) {
|
||||
results.push(
|
||||
transformReplyTuples(rawReply[i] as Array<string>)
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
total: rawReply[0],
|
||||
results
|
||||
};
|
||||
}
|
11
packages/search/lib/commands/ALIASADD.spec.ts
Normal file
11
packages/search/lib/commands/ALIASADD.spec.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { transformArguments } from './ALIASADD';
|
||||
|
||||
describe('ALIASADD', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('alias', 'index'),
|
||||
['FT.ALIASADD', 'alias', 'index']
|
||||
);
|
||||
});
|
||||
});
|
5
packages/search/lib/commands/ALIASADD.ts
Normal file
5
packages/search/lib/commands/ALIASADD.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export function transformArguments(name: string, index: string): Array<string> {
|
||||
return ['FT.ALIASADD', name, index];
|
||||
}
|
||||
|
||||
export declare function transformReply(): 'OK';
|
11
packages/search/lib/commands/ALIASDEL.spec.ts
Normal file
11
packages/search/lib/commands/ALIASDEL.spec.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { transformArguments } from './ALIASDEL';
|
||||
|
||||
describe('ALIASDEL', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('alias', 'index'),
|
||||
['FT.ALIASDEL', 'alias', 'index']
|
||||
);
|
||||
});
|
||||
});
|
5
packages/search/lib/commands/ALIASDEL.ts
Normal file
5
packages/search/lib/commands/ALIASDEL.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export function transformArguments(name: string, index: string): Array<string> {
|
||||
return ['FT.ALIASDEL', name, index];
|
||||
}
|
||||
|
||||
export declare function transformReply(): 'OK';
|
11
packages/search/lib/commands/ALIASUPDATE.spec.ts
Normal file
11
packages/search/lib/commands/ALIASUPDATE.spec.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { transformArguments } from './ALIASUPDATE';
|
||||
|
||||
describe('ALIASUPDATE', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('alias', 'index'),
|
||||
['FT.ALIASUPDATE', 'alias', 'index']
|
||||
);
|
||||
});
|
||||
});
|
5
packages/search/lib/commands/ALIASUPDATE.ts
Normal file
5
packages/search/lib/commands/ALIASUPDATE.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export function transformArguments(name: string, index: string): Array<string> {
|
||||
return ['FT.ALIASUPDATE', name, index];
|
||||
}
|
||||
|
||||
export declare function transformReply(): 'OK';
|
25
packages/search/lib/commands/CONFIG_GET.spec.ts
Normal file
25
packages/search/lib/commands/CONFIG_GET.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './CONFIG_GET';
|
||||
|
||||
describe('CONFIG GET', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('TIMEOUT'),
|
||||
['FT.CONFIG', 'GET', 'TIMEOUT']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.configGet', async client => {
|
||||
assert.deepEqual(
|
||||
await client.ft.configGet('TIMEOUT'),
|
||||
Object.create(null, {
|
||||
TIMEOUT: {
|
||||
value: '500',
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
}
|
||||
})
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
16
packages/search/lib/commands/CONFIG_GET.ts
Normal file
16
packages/search/lib/commands/CONFIG_GET.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export function transformArguments(option: string) {
|
||||
return ['FT.CONFIG', 'GET', option];
|
||||
}
|
||||
|
||||
interface ConfigGetReply {
|
||||
[option: string]: string | null;
|
||||
}
|
||||
|
||||
export function transformReply(rawReply: Array<[string, string | null]>): ConfigGetReply {
|
||||
const transformedReply: ConfigGetReply = Object.create(null);
|
||||
for (const [key, value] of rawReply) {
|
||||
transformedReply[key] = value;
|
||||
}
|
||||
|
||||
return transformedReply;
|
||||
}
|
12
packages/search/lib/commands/CONFIG_SET.spec.ts
Normal file
12
packages/search/lib/commands/CONFIG_SET.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './CONFIG_SET';
|
||||
|
||||
describe('CONFIG SET', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('TIMEOUT', '500'),
|
||||
['FT.CONFIG', 'SET', 'TIMEOUT', '500']
|
||||
);
|
||||
});
|
||||
});
|
5
packages/search/lib/commands/CONFIG_SET.ts
Normal file
5
packages/search/lib/commands/CONFIG_SET.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export function transformArguments(option: string, value: string): Array<string> {
|
||||
return ['FT.CONFIG', 'SET', option, value];
|
||||
}
|
||||
|
||||
export declare function transformReply(): 'OK';
|
347
packages/search/lib/commands/CREATE.spec.ts
Normal file
347
packages/search/lib/commands/CREATE.spec.ts
Normal file
@@ -0,0 +1,347 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { RedisSearchLanguages, SchemaFieldTypes, SchemaTextFieldPhonetics, transformArguments } from './CREATE';
|
||||
|
||||
describe('CREATE', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('simple', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}),
|
||||
['FT.CREATE', 'index', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with fields', () => {
|
||||
describe('TEXT', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: SchemaFieldTypes.TEXT
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT']
|
||||
);
|
||||
});
|
||||
|
||||
it('with NOSTEM', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
NOSTEM: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'NOSTEM']
|
||||
);
|
||||
});
|
||||
|
||||
it('with WEIGHT', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
WEIGHT: 1
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'WEIGHT', '1']
|
||||
);
|
||||
});
|
||||
|
||||
it('with PHONETIC', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
PHONETIC: SchemaTextFieldPhonetics.DM_EN
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'PHONETIC', SchemaTextFieldPhonetics.DM_EN]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('NUMERIC', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: SchemaFieldTypes.NUMERIC
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'NUMERIC']
|
||||
);
|
||||
});
|
||||
|
||||
it('GEO', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: SchemaFieldTypes.GEO
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'GEO']
|
||||
);
|
||||
});
|
||||
|
||||
describe('TAG', () => {
|
||||
describe('without options', () => {
|
||||
it('SchemaFieldTypes.TAG', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: SchemaFieldTypes.TAG
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TAG']
|
||||
);
|
||||
});
|
||||
|
||||
it('{ type: SchemaFieldTypes.TAG }', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TAG
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TAG']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with SEPERATOR', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TAG,
|
||||
SEPERATOR: 'seperator'
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TAG', 'SEPERATOR', 'seperator']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with generic options', () => {
|
||||
it('with AS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
AS: 'as'
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'AS', 'as', 'TEXT']
|
||||
);
|
||||
});
|
||||
|
||||
it('with CASESENSITIVE', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
CASESENSITIVE: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'CASESENSITIVE']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with SORTABLE', () => {
|
||||
it('true', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
SORTABLE: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'SORTABLE']
|
||||
);
|
||||
});
|
||||
|
||||
it('UNF', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
SORTABLE: 'UNF'
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'SORTABLE', 'UNF']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with NOINDEX', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {
|
||||
field: {
|
||||
type: SchemaFieldTypes.TEXT,
|
||||
NOINDEX: true
|
||||
}
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCHEMA', 'field', 'TEXT', 'NOINDEX']
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('with ON', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
ON: 'HASH'
|
||||
}),
|
||||
['FT.CREATE', 'index', 'ON', 'HASH', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with PREFIX', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
PREFIX: 'prefix'
|
||||
}),
|
||||
['FT.CREATE', 'index', 'PREFIX', '1', 'prefix', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('Array', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
PREFIX: ['1', '2']
|
||||
}),
|
||||
['FT.CREATE', 'index', 'PREFIX', '2', '1', '2', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with FILTER', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
FILTER: '@field != ""'
|
||||
}),
|
||||
['FT.CREATE', 'index', 'FILTER', '@field != ""', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with LANGUAGE', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
LANGUAGE: RedisSearchLanguages.ARABIC
|
||||
}),
|
||||
['FT.CREATE', 'index', 'LANGUAGE', RedisSearchLanguages.ARABIC, 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with LANGUAGE_FIELD', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
LANGUAGE_FIELD: '@field'
|
||||
}),
|
||||
['FT.CREATE', 'index', 'LANGUAGE_FIELD', '@field', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with SCORE', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
SCORE: 1
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCORE', '1', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with SCORE_FIELD', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
SCORE_FIELD: '@field'
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SCORE_FIELD', '@field', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with MAXTEXTFIELDS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
MAXTEXTFIELDS: true
|
||||
}),
|
||||
['FT.CREATE', 'index', 'MAXTEXTFIELDS', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with TEMPORARY', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
TEMPORARY: 1
|
||||
}),
|
||||
['FT.CREATE', 'index', 'TEMPORARY', '1', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with NOOFFSETS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
NOOFFSETS: true
|
||||
}),
|
||||
['FT.CREATE', 'index', 'NOOFFSETS', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with NOHL', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
NOHL: true
|
||||
}),
|
||||
['FT.CREATE', 'index', 'NOHL', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with NOFIELDS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
NOFIELDS: true
|
||||
}),
|
||||
['FT.CREATE', 'index', 'NOFIELDS', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with NOFREQS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
NOFREQS: true
|
||||
}),
|
||||
['FT.CREATE', 'index', 'NOFREQS', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('with SKIPINITIALSCAN', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
SKIPINITIALSCAN: true
|
||||
}),
|
||||
['FT.CREATE', 'index', 'SKIPINITIALSCAN', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with STOPWORDS', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
STOPWORDS: 'stopword'
|
||||
}),
|
||||
['FT.CREATE', 'index', 'STOPWORDS', '1', 'stopword', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
|
||||
it('Array', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', {}, {
|
||||
STOPWORDS: ['1', '2']
|
||||
}),
|
||||
['FT.CREATE', 'index', 'STOPWORDS', '2', '1', '2', 'SCHEMA']
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.create', async client => {
|
||||
assert.equal(
|
||||
await client.ft.create('index', {
|
||||
field: SchemaFieldTypes.TEXT // TODO: shouldn't be mandatory
|
||||
}),
|
||||
'OK'
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
222
packages/search/lib/commands/CREATE.ts
Normal file
222
packages/search/lib/commands/CREATE.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
import { pushOptionalVerdictArgument } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
import { PropertyName } from '.';
|
||||
|
||||
export enum SchemaFieldTypes {
|
||||
TEXT = 'TEXT',
|
||||
NUMERIC = 'NUMERIC',
|
||||
GEO = 'GEO',
|
||||
TAG = 'TAG',
|
||||
}
|
||||
|
||||
type CreateSchemaField<T extends SchemaFieldTypes, E = Record<string, never>> = T | ({
|
||||
type: T;
|
||||
AS?: string;
|
||||
CASESENSITIVE?: true;
|
||||
SORTABLE?: true | 'UNF';
|
||||
NOINDEX?: true;
|
||||
} & E);
|
||||
|
||||
export enum SchemaTextFieldPhonetics {
|
||||
DM_EN = 'dm:en',
|
||||
DM_FR = 'dm:fr',
|
||||
FM_PT = 'dm:pt',
|
||||
DM_ES = 'dm:es'
|
||||
}
|
||||
|
||||
type CreateSchemaTextField = CreateSchemaField<SchemaFieldTypes.TEXT, {
|
||||
NOSTEM?: true;
|
||||
WEIGHT?: number;
|
||||
PHONETIC?: SchemaTextFieldPhonetics;
|
||||
}>;
|
||||
|
||||
type CreateSchemaNumericField = CreateSchemaField<SchemaFieldTypes.NUMERIC>;
|
||||
|
||||
type CreateSchemaGeoField = CreateSchemaField<SchemaFieldTypes.GEO>;
|
||||
|
||||
type CreateSchemaTagField = CreateSchemaField<SchemaFieldTypes.TAG, {
|
||||
SEPERATOR?: string;
|
||||
}>;
|
||||
|
||||
interface CreateSchema {
|
||||
[field: string]:
|
||||
CreateSchemaTextField |
|
||||
CreateSchemaNumericField |
|
||||
CreateSchemaGeoField |
|
||||
CreateSchemaTagField
|
||||
}
|
||||
|
||||
export enum RedisSearchLanguages {
|
||||
ARABIC = 'Arabic',
|
||||
BASQUE = 'Basque',
|
||||
CATALANA = 'Catalan',
|
||||
DANISH = 'Danish',
|
||||
DUTCH = 'Dutch',
|
||||
ENGLISH = 'English',
|
||||
FINNISH = 'Finnish',
|
||||
FRENCH = 'French',
|
||||
GERMAN = 'German',
|
||||
GREEK = 'Greek',
|
||||
HUNGARIAN = 'Hungarian',
|
||||
INDONESAIN = 'Indonesian',
|
||||
IRISH = 'Irish',
|
||||
ITALIAN = 'Italian',
|
||||
LITHUANIAN = 'Lithuanian',
|
||||
NEPALI = 'Nepali',
|
||||
NORWEIGAN = 'Norwegian',
|
||||
PORTUGUESE = 'Portuguese',
|
||||
ROMANIAN = 'Romanian',
|
||||
RUSSIAN = 'Russian',
|
||||
SPANISH = 'Spanish',
|
||||
SWEDISH = 'Swedish',
|
||||
TAMIL = 'Tamil',
|
||||
TURKISH = 'Turkish',
|
||||
CHINESE = 'Chinese'
|
||||
}
|
||||
|
||||
interface CreateOptions {
|
||||
ON?: 'HASH' | 'JSON';
|
||||
PREFIX?: string | Array<string>;
|
||||
FILTER?: string;
|
||||
LANGUAGE?: RedisSearchLanguages;
|
||||
LANGUAGE_FIELD?: PropertyName;
|
||||
SCORE?: number;
|
||||
SCORE_FIELD?: PropertyName;
|
||||
// PAYLOAD_FIELD?: string;
|
||||
MAXTEXTFIELDS?: true;
|
||||
TEMPORARY?: number;
|
||||
NOOFFSETS?: true;
|
||||
NOHL?: true;
|
||||
NOFIELDS?: true;
|
||||
NOFREQS?: true;
|
||||
SKIPINITIALSCAN?: true;
|
||||
STOPWORDS?: string | Array<string>;
|
||||
}
|
||||
|
||||
export function transformArguments(index: string, schema: CreateSchema, options?: CreateOptions): Array<string> {
|
||||
const args = ['FT.CREATE', index];
|
||||
|
||||
if (options?.ON) {
|
||||
args.push('ON', options.ON);
|
||||
}
|
||||
|
||||
pushOptionalVerdictArgument(args, 'PREFIX', options?.PREFIX);
|
||||
|
||||
if (options?.FILTER) {
|
||||
args.push('FILTER', options.FILTER);
|
||||
}
|
||||
|
||||
if (options?.LANGUAGE) {
|
||||
args.push('LANGUAGE', options.LANGUAGE);
|
||||
}
|
||||
|
||||
if (options?.LANGUAGE_FIELD) {
|
||||
args.push('LANGUAGE_FIELD', options.LANGUAGE_FIELD);
|
||||
}
|
||||
|
||||
if (options?.SCORE) {
|
||||
args.push('SCORE', options.SCORE.toString());
|
||||
}
|
||||
|
||||
if (options?.SCORE_FIELD) {
|
||||
args.push('SCORE_FIELD', options.SCORE_FIELD);
|
||||
}
|
||||
|
||||
// if (options?.PAYLOAD_FIELD) {
|
||||
// args.push('PAYLOAD_FIELD', options.PAYLOAD_FIELD);
|
||||
// }
|
||||
|
||||
if (options?.MAXTEXTFIELDS) {
|
||||
args.push('MAXTEXTFIELDS');
|
||||
}
|
||||
|
||||
if (options?.TEMPORARY) {
|
||||
args.push('TEMPORARY', options.TEMPORARY.toString());
|
||||
}
|
||||
|
||||
if (options?.NOOFFSETS) {
|
||||
args.push('NOOFFSETS');
|
||||
}
|
||||
|
||||
if (options?.NOHL) {
|
||||
args.push('NOHL');
|
||||
}
|
||||
|
||||
if (options?.NOFIELDS) {
|
||||
args.push('NOFIELDS');
|
||||
}
|
||||
|
||||
if (options?.NOFREQS) {
|
||||
args.push('NOFREQS');
|
||||
}
|
||||
|
||||
if (options?.SKIPINITIALSCAN) {
|
||||
args.push('SKIPINITIALSCAN');
|
||||
}
|
||||
|
||||
pushOptionalVerdictArgument(args, 'STOPWORDS', options?.STOPWORDS);
|
||||
|
||||
args.push('SCHEMA');
|
||||
|
||||
for (const [field, fieldOptions] of Object.entries(schema)) {
|
||||
args.push(field);
|
||||
|
||||
if (typeof fieldOptions === 'string') {
|
||||
args.push(fieldOptions);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fieldOptions.AS) {
|
||||
args.push('AS', fieldOptions.AS);
|
||||
}
|
||||
|
||||
args.push(fieldOptions.type);
|
||||
|
||||
switch (fieldOptions.type) {
|
||||
case 'TEXT':
|
||||
if (fieldOptions.NOSTEM) {
|
||||
args.push('NOSTEM');
|
||||
}
|
||||
|
||||
if (fieldOptions.WEIGHT) {
|
||||
args.push('WEIGHT', fieldOptions.WEIGHT.toString());
|
||||
}
|
||||
|
||||
if (fieldOptions.PHONETIC) {
|
||||
args.push('PHONETIC', fieldOptions.PHONETIC);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// case 'NUMERIC':
|
||||
// case 'GEO':
|
||||
// break;
|
||||
|
||||
case 'TAG':
|
||||
if (fieldOptions.SEPERATOR) {
|
||||
args.push('SEPERATOR', fieldOptions.SEPERATOR);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (fieldOptions.CASESENSITIVE) {
|
||||
args.push('CASESENSITIVE');
|
||||
}
|
||||
|
||||
if (fieldOptions.SORTABLE) {
|
||||
args.push('SORTABLE');
|
||||
|
||||
if (fieldOptions.SORTABLE === 'UNF') {
|
||||
args.push('UNF');
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldOptions.NOINDEX) {
|
||||
args.push('NOINDEX');
|
||||
}
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export declare function transformReply(): 'OK';
|
28
packages/search/lib/commands/DICTADD.spec.ts
Normal file
28
packages/search/lib/commands/DICTADD.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './DICTADD';
|
||||
|
||||
describe('DICTADD', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('dictionary', 'term'),
|
||||
['FT.DICTADD', 'dictionary', 'term']
|
||||
);
|
||||
});
|
||||
|
||||
it('Array', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('dictionary', ['1', '2']),
|
||||
['FT.DICTADD', 'dictionary', '1', '2']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.dictAdd', async client => {
|
||||
assert.equal(
|
||||
await client.ft.dictAdd('dictionary', 'term'),
|
||||
1
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
8
packages/search/lib/commands/DICTADD.ts
Normal file
8
packages/search/lib/commands/DICTADD.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||
import { pushVerdictArguments } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
|
||||
export function transformArguments(dictionary: string, term: string | Array<string>): RedisCommandArguments {
|
||||
return pushVerdictArguments(['FT.DICTADD', dictionary], term);
|
||||
}
|
||||
|
||||
export declare function transformReply(): number;
|
28
packages/search/lib/commands/DICTDEL.spec.ts
Normal file
28
packages/search/lib/commands/DICTDEL.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './DICTDEL';
|
||||
|
||||
describe('DICTDEL', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('dictionary', 'term'),
|
||||
['FT.DICTDEL', 'dictionary', 'term']
|
||||
);
|
||||
});
|
||||
|
||||
it('Array', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('dictionary', ['1', '2']),
|
||||
['FT.DICTDEL', 'dictionary', '1', '2']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.dictDel', async client => {
|
||||
assert.equal(
|
||||
await client.ft.dictDel('dictionary', 'term'),
|
||||
0
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
8
packages/search/lib/commands/DICTDEL.ts
Normal file
8
packages/search/lib/commands/DICTDEL.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||
import { pushVerdictArguments } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
|
||||
export function transformArguments(dictionary: string, term: string | Array<string>): RedisCommandArguments {
|
||||
return pushVerdictArguments(['FT.DICTDEL', dictionary], term);
|
||||
}
|
||||
|
||||
export declare function transformReply(): number;
|
21
packages/search/lib/commands/DICTDUMP.spec.ts
Normal file
21
packages/search/lib/commands/DICTDUMP.spec.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './DICTDUMP';
|
||||
|
||||
describe('DICTDUMP', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('dictionary'),
|
||||
['FT.DICTDUMP', 'dictionary']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.dictDump', async client => {
|
||||
await client.ft.dictAdd('dictionary', 'string')
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.dictDump('dictionary'),
|
||||
['string']
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
5
packages/search/lib/commands/DICTDUMP.ts
Normal file
5
packages/search/lib/commands/DICTDUMP.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export function transformArguments(dictionary: string): Array<string> {
|
||||
return ['FT.DICTDUMP', dictionary];
|
||||
}
|
||||
|
||||
export declare function transformReply(): Array<string>;
|
33
packages/search/lib/commands/DROPINDEX.spec.ts
Normal file
33
packages/search/lib/commands/DROPINDEX.spec.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { SchemaFieldTypes } from './CREATE';
|
||||
import { transformArguments } from './DROPINDEX';
|
||||
|
||||
describe('DROPINDEX', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index'),
|
||||
['FT.DROPINDEX', 'index']
|
||||
);
|
||||
});
|
||||
|
||||
it('with DD', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', { DD: true }),
|
||||
['FT.DROPINDEX', 'index', 'DD']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.dropIndex', async client => {
|
||||
await client.ft.create('index', {
|
||||
field: SchemaFieldTypes.TEXT // TODO: shouldn't be mandatory
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
await client.ft.dropIndex('index'),
|
||||
'OK'
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
15
packages/search/lib/commands/DROPINDEX.ts
Normal file
15
packages/search/lib/commands/DROPINDEX.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
interface DropIndexOptions {
|
||||
DD?: true;
|
||||
}
|
||||
|
||||
export function transformArguments(index: string, options?: DropIndexOptions): Array<string> {
|
||||
const args = ['FT.DROPINDEX', index];
|
||||
|
||||
if (options?.DD) {
|
||||
args.push('DD');
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export declare function transformReply(): 'OK';
|
11
packages/search/lib/commands/EXPLAIN.spec.ts
Normal file
11
packages/search/lib/commands/EXPLAIN.spec.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { transformArguments } from './EXPLAIN';
|
||||
|
||||
describe('EXPLAIN', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*'),
|
||||
['FT.EXPLAIN', 'index', '*']
|
||||
);
|
||||
});
|
||||
});
|
7
packages/search/lib/commands/EXPLAIN.ts
Normal file
7
packages/search/lib/commands/EXPLAIN.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const IS_READ_ONLY = true;
|
||||
|
||||
export function transformArguments(index: string, query: string): Array<string> {
|
||||
return ['FT.EXPLAIN', index, query];
|
||||
}
|
||||
|
||||
export declare function transformReply(): string;
|
11
packages/search/lib/commands/EXPLAINCLI.spec.ts
Normal file
11
packages/search/lib/commands/EXPLAINCLI.spec.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { transformArguments } from './EXPLAINCLI';
|
||||
|
||||
describe('EXPLAINCLI', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '*'),
|
||||
['FT.EXPLAINCLI', 'index', '*']
|
||||
);
|
||||
});
|
||||
});
|
7
packages/search/lib/commands/EXPLAINCLI.ts
Normal file
7
packages/search/lib/commands/EXPLAINCLI.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const IS_READ_ONLY = true;
|
||||
|
||||
export function transformArguments(index: string, query: string): Array<string> {
|
||||
return ['FT.EXPLAINCLI', index, query];
|
||||
}
|
||||
|
||||
export declare function transformReply(): Array<string>;
|
65
packages/search/lib/commands/INFO.spec.ts
Normal file
65
packages/search/lib/commands/INFO.spec.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { SchemaFieldTypes } from './CREATE';
|
||||
import { transformArguments } from './INFO';
|
||||
|
||||
describe('INFO', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index'),
|
||||
['FT.INFO', 'index']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.info', async client => {
|
||||
await client.ft.create('index', {}, {
|
||||
ON: 'HASH' // TODO: shouldn't be mandatory
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.info('index'),
|
||||
{
|
||||
indexName: 'index',
|
||||
indexOptions: [],
|
||||
indexDefinition: {
|
||||
defaultScore: '1',
|
||||
keyType: 'HASH',
|
||||
prefixes: ['']
|
||||
},
|
||||
attributes: [],
|
||||
numDocs: '0',
|
||||
maxDocId: '0',
|
||||
numTerms: '0',
|
||||
numRecords: '0',
|
||||
invertedSzMb: '0',
|
||||
totalInvertedIndexBlocks: '0',
|
||||
offsetVectorsSzMb: '0',
|
||||
docTableSizeMb: '0',
|
||||
sortableValuesSizeMb: '0',
|
||||
keyTableSizeMb: '0',
|
||||
recordsPerDocAvg: '-nan',
|
||||
bytesPerRecordAvg: '-nan',
|
||||
offsetsPerTermAvg: '-nan',
|
||||
offsetBitsPerRecordAvg: '-nan',
|
||||
hashIndexingFailures: '0',
|
||||
indexing: '0',
|
||||
percentIndexed: '1',
|
||||
gcStats: {
|
||||
bytesCollected: '0',
|
||||
totalMsRun: '0',
|
||||
totalCycles: '0',
|
||||
averageCycleTimeMs: '-nan',
|
||||
lastRunTimeMs: '0',
|
||||
gcNumericTreesMissed: '0',
|
||||
gcBlocksDenied: '0'
|
||||
},
|
||||
cursorStats: {
|
||||
globalIdle: 0,
|
||||
globalTotal: 0,
|
||||
indexCapacity: 128,
|
||||
idnexTotal: 0
|
||||
}
|
||||
}
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
171
packages/search/lib/commands/INFO.ts
Normal file
171
packages/search/lib/commands/INFO.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
export function transformArguments(index: string): Array<string> {
|
||||
return ['FT.INFO', index];
|
||||
}
|
||||
|
||||
type InfoRawReply = [
|
||||
_: string,
|
||||
indexName: string,
|
||||
_: string,
|
||||
indexOptions: Array<string>,
|
||||
_: string,
|
||||
indexDefinition: [
|
||||
_: string,
|
||||
keyType: string,
|
||||
_: string,
|
||||
prefixes: Array<string>,
|
||||
_: string,
|
||||
defaultScore: string
|
||||
],
|
||||
_: string,
|
||||
attributes: Array<Array<string>>,
|
||||
_: string,
|
||||
numDocs: string,
|
||||
_: string,
|
||||
maxDocId: string,
|
||||
_: string,
|
||||
numTerms: string,
|
||||
_: string,
|
||||
numRecords: string,
|
||||
_: 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,
|
||||
cursorStats: [
|
||||
_: string,
|
||||
globalIdle: number,
|
||||
_: string,
|
||||
globalTotal: number,
|
||||
_: string,
|
||||
indexCapacity: number,
|
||||
_: string,
|
||||
idnexTotal: number
|
||||
]
|
||||
];
|
||||
|
||||
interface InfoReply {
|
||||
indexName: string;
|
||||
indexOptions: Array<string>;
|
||||
indexDefinition: {
|
||||
keyType: string;
|
||||
prefixes: Array<string>;
|
||||
defaultScore: string;
|
||||
};
|
||||
attributes: Array<Array<string>>;
|
||||
numDocs: string;
|
||||
maxDocId: string;
|
||||
numTerms: string;
|
||||
numRecords: string;
|
||||
invertedSzMb: string;
|
||||
totalInvertedIndexBlocks: string;
|
||||
offsetVectorsSzMb: string;
|
||||
docTableSizeMb: string;
|
||||
sortableValuesSizeMb: string;
|
||||
keyTableSizeMb: string;
|
||||
recordsPerDocAvg: string;
|
||||
bytesPerRecordAvg: string;
|
||||
offsetsPerTermAvg: string;
|
||||
offsetBitsPerRecordAvg: string;
|
||||
hashIndexingFailures: string;
|
||||
indexing: string;
|
||||
percentIndexed: string;
|
||||
gcStats: {
|
||||
bytesCollected: string;
|
||||
totalMsRun: string;
|
||||
totalCycles: string;
|
||||
averageCycleTimeMs: string;
|
||||
lastRunTimeMs: string;
|
||||
gcNumericTreesMissed: string;
|
||||
gcBlocksDenied: string;
|
||||
};
|
||||
cursorStats: {
|
||||
globalIdle: number;
|
||||
globalTotal: number;
|
||||
indexCapacity: number;
|
||||
idnexTotal: number;
|
||||
};
|
||||
}
|
||||
|
||||
export function transformReply(rawReply: InfoRawReply): InfoReply {
|
||||
return {
|
||||
indexName: rawReply[1],
|
||||
indexOptions: rawReply[3],
|
||||
indexDefinition: {
|
||||
keyType: rawReply[5][1],
|
||||
prefixes: rawReply[5][3],
|
||||
defaultScore: rawReply[5][5]
|
||||
},
|
||||
attributes: rawReply[7],
|
||||
numDocs: rawReply[9],
|
||||
maxDocId: rawReply[11],
|
||||
numTerms: rawReply[13],
|
||||
numRecords: rawReply[15],
|
||||
invertedSzMb: rawReply[17],
|
||||
totalInvertedIndexBlocks: rawReply[19],
|
||||
offsetVectorsSzMb: rawReply[21],
|
||||
docTableSizeMb: rawReply[23],
|
||||
sortableValuesSizeMb: rawReply[25],
|
||||
keyTableSizeMb: rawReply[27],
|
||||
recordsPerDocAvg: rawReply[29],
|
||||
bytesPerRecordAvg: rawReply[31],
|
||||
offsetsPerTermAvg: rawReply[33],
|
||||
offsetBitsPerRecordAvg: rawReply[35],
|
||||
hashIndexingFailures: rawReply[37],
|
||||
indexing: rawReply[39],
|
||||
percentIndexed: rawReply[41],
|
||||
gcStats: {
|
||||
bytesCollected: rawReply[43][1],
|
||||
totalMsRun: rawReply[43][3],
|
||||
totalCycles: rawReply[43][5],
|
||||
averageCycleTimeMs: rawReply[43][7],
|
||||
lastRunTimeMs: rawReply[43][9],
|
||||
gcNumericTreesMissed: rawReply[43][11],
|
||||
gcBlocksDenied: rawReply[43][13]
|
||||
},
|
||||
cursorStats: {
|
||||
globalIdle: rawReply[45][1],
|
||||
globalTotal: rawReply[45][3],
|
||||
indexCapacity: rawReply[45][5],
|
||||
idnexTotal: rawReply[45][7]
|
||||
}
|
||||
};
|
||||
}
|
26
packages/search/lib/commands/PROFILE.ts
Normal file
26
packages/search/lib/commands/PROFILE.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export const IS_READ_ONLY = true;
|
||||
|
||||
interface ProfileOptions {
|
||||
LIMITED?: true;
|
||||
}
|
||||
|
||||
export function transformArguments(
|
||||
index: string,
|
||||
type: 'SEARCH' | 'AGGREGATE',
|
||||
query: string,
|
||||
options?: ProfileOptions
|
||||
): Array<string> {
|
||||
const args = ['FT.PROFILE', index, type];
|
||||
|
||||
if (options?.LIMITED) {
|
||||
args.push('LIMITED');
|
||||
}
|
||||
|
||||
args.push('QUERY', query);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export function transformReply() {
|
||||
|
||||
}
|
243
packages/search/lib/commands/SEARCH.spec.ts
Normal file
243
packages/search/lib/commands/SEARCH.spec.ts
Normal file
@@ -0,0 +1,243 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { RedisSearchLanguages, SchemaFieldTypes } from './CREATE';
|
||||
import { transformArguments } from './SEARCH';
|
||||
|
||||
describe('SEARCH', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query'),
|
||||
['FT.SEARCH', 'index', 'query']
|
||||
);
|
||||
});
|
||||
|
||||
it('with VERBATIM', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { VERBATIM: true }),
|
||||
['FT.SEARCH', 'index', 'query', 'VERBATIM']
|
||||
);
|
||||
});
|
||||
|
||||
it('with NOSTOPWORDS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { NOSTOPWORDS: true }),
|
||||
['FT.SEARCH', 'index', 'query', 'NOSTOPWORDS']
|
||||
);
|
||||
});
|
||||
|
||||
it('with INKEYS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { INKEYS: 'key' }),
|
||||
['FT.SEARCH', 'index', 'query', 'INKEYS', '1', 'key']
|
||||
);
|
||||
});
|
||||
|
||||
it('with INFIELDS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { INFIELDS: 'field' }),
|
||||
['FT.SEARCH', 'index', 'query', 'INFIELDS', '1', 'field']
|
||||
);
|
||||
});
|
||||
|
||||
it('with RETURN', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { RETURN: 'return' }),
|
||||
['FT.SEARCH', 'index', 'query', 'RETURN', '1', 'return']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with SUMMARIZE', () => {
|
||||
it('true', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { SUMMARIZE: true }),
|
||||
['FT.SEARCH', 'index', 'query', 'SUMMARIZE']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with FIELDS', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
SUMMARIZE: {
|
||||
FIELDS: ['@field']
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'SUMMARIZE', 'FIELDS', '1', '@field']
|
||||
);
|
||||
});
|
||||
|
||||
it('Array', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
SUMMARIZE: {
|
||||
FIELDS: ['@1', '@2']
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'SUMMARIZE', 'FIELDS', '2', '@1', '@2']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with FRAGS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
SUMMARIZE: {
|
||||
FRAGS: 1
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'SUMMARIZE', 'FRAGS', '1']
|
||||
);
|
||||
});
|
||||
|
||||
it('with LEN', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
SUMMARIZE: {
|
||||
LEN: 1
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'SUMMARIZE', 'LEN', '1']
|
||||
);
|
||||
});
|
||||
|
||||
it('with SEPARATOR', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
SUMMARIZE: {
|
||||
SEPARATOR: 'separator'
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'SUMMARIZE', 'SEPARATOR', 'separator']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with HIGHLIGHT', () => {
|
||||
it('true', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { HIGHLIGHT: true }),
|
||||
['FT.SEARCH', 'index', 'query', 'HIGHLIGHT']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with FIELDS', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
HIGHLIGHT: {
|
||||
FIELDS: ['@field']
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'HIGHLIGHT', 'FIELDS', '1', '@field']
|
||||
);
|
||||
});
|
||||
|
||||
it('Array', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
HIGHLIGHT: {
|
||||
FIELDS: ['@1', '@2']
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'HIGHLIGHT', 'FIELDS', '2', '@1', '@2']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with TAGS', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
HIGHLIGHT: {
|
||||
TAGS: {
|
||||
open: 'open',
|
||||
close: 'close'
|
||||
}
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'HIGHLIGHT', 'TAGS', 'open', 'close']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('with SLOP', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { SLOP: 1 }),
|
||||
['FT.SEARCH', 'index', 'query', 'SLOP', '1']
|
||||
);
|
||||
});
|
||||
|
||||
it('with INORDER', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { INORDER: true }),
|
||||
['FT.SEARCH', 'index', 'query', 'INORDER']
|
||||
);
|
||||
});
|
||||
|
||||
it('with LANGUAGE', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { LANGUAGE: RedisSearchLanguages.ARABIC }),
|
||||
['FT.SEARCH', 'index', 'query', 'LANGUAGE', RedisSearchLanguages.ARABIC]
|
||||
);
|
||||
});
|
||||
|
||||
it('with EXPANDER', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { EXPANDER: 'expender' }),
|
||||
['FT.SEARCH', 'index', 'query', 'EXPANDER', 'expender']
|
||||
);
|
||||
});
|
||||
|
||||
it('with SCORER', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { SCORER: 'scorer' }),
|
||||
['FT.SEARCH', 'index', 'query', 'SCORER', 'scorer']
|
||||
);
|
||||
});
|
||||
|
||||
it('with MSORTBY', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { MSORTBY: '@by' }),
|
||||
['FT.SEARCH', 'index', 'query', 'MSORTBY', '1', '@by']
|
||||
);
|
||||
});
|
||||
|
||||
it('with LIMIT', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
LIMIT: {
|
||||
from: 0,
|
||||
size: 1
|
||||
}
|
||||
}),
|
||||
['FT.SEARCH', 'index', 'query', 'LIMIT', '0', '1']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.search', async client => {
|
||||
await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: SchemaFieldTypes.NUMERIC
|
||||
}),
|
||||
client.hSet('1', 'field', '1')
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.search('index', '*'),
|
||||
{
|
||||
total: 1,
|
||||
documents: [{
|
||||
id: '1',
|
||||
value: Object.create(null, {
|
||||
field: {
|
||||
value: '1',
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
}
|
||||
})
|
||||
}]
|
||||
}
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
202
packages/search/lib/commands/SEARCH.ts
Normal file
202
packages/search/lib/commands/SEARCH.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||
import { pushOptionalVerdictArgument, pushVerdictArgument, transformReplyTuples } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
import { type } from 'os';
|
||||
import { PropertyName, pushSortByArguments, SortByOptions } from '.';
|
||||
import { RedisSearchLanguages } from './CREATE';
|
||||
|
||||
export const FIRST_KEY_INDEX = 1;
|
||||
|
||||
export const IS_READ_ONLY = true;
|
||||
|
||||
interface SearchOptions {
|
||||
// NOCONTENT?: true; TODO
|
||||
VERBATIM?: true;
|
||||
NOSTOPWORDS?: true;
|
||||
// WITHSCORES?: true;
|
||||
// WITHPAYLOADS?: true;
|
||||
WITHSORTKEYS?: true;
|
||||
// FILTER?: {
|
||||
// field: string;
|
||||
// min: number | string;
|
||||
// max: number | string;
|
||||
// };
|
||||
// GEOFILTER?: {
|
||||
// field: string;
|
||||
// lon: number;
|
||||
// lat: number;
|
||||
// radius: number;
|
||||
// unit: 'm' | 'km' | 'mi' | 'ft';
|
||||
// };
|
||||
INKEYS?: string | Array<string>;
|
||||
INFIELDS?: string | Array<string>;
|
||||
RETURN?: string | Array<string>;
|
||||
SUMMARIZE?: true | {
|
||||
FIELDS?: PropertyName | Array<PropertyName>;
|
||||
FRAGS?: number;
|
||||
LEN?: number;
|
||||
SEPARATOR?: string;
|
||||
};
|
||||
HIGHLIGHT?: true | {
|
||||
FIELDS?: PropertyName | Array<PropertyName>;
|
||||
TAGS?: {
|
||||
open: string;
|
||||
close: string;
|
||||
}
|
||||
};
|
||||
SLOP?: number;
|
||||
INORDER?: true;
|
||||
LANGUAGE?: RedisSearchLanguages;
|
||||
EXPANDER?: string;
|
||||
SCORER?: string;
|
||||
// EXPLAINSCORE?: true; // TODO: WITHSCORES
|
||||
// PAYLOAD?: ;
|
||||
// SORTBY?: SortByOptions;
|
||||
MSORTBY?: SortByOptions | Array<SortByOptions>;
|
||||
LIMIT?: {
|
||||
from: number | string;
|
||||
size: number | string;
|
||||
};
|
||||
}
|
||||
|
||||
export function transformArguments(
|
||||
index: string,
|
||||
query: string,
|
||||
options?: SearchOptions
|
||||
): RedisCommandArguments {
|
||||
const args: RedisCommandArguments = ['FT.SEARCH', index, query];
|
||||
|
||||
if (options?.VERBATIM) {
|
||||
args.push('VERBATIM');
|
||||
}
|
||||
|
||||
if (options?.NOSTOPWORDS) {
|
||||
args.push('NOSTOPWORDS');
|
||||
}
|
||||
|
||||
// if (options?.WITHSCORES) {
|
||||
// args.push('WITHSCORES');
|
||||
// }
|
||||
|
||||
// if (options?.WITHPAYLOADS) {
|
||||
// args.push('WITHPAYLOADS');
|
||||
// }
|
||||
|
||||
pushOptionalVerdictArgument(args, 'INKEYS', options?.INKEYS);
|
||||
pushOptionalVerdictArgument(args, 'INFIELDS', options?.INFIELDS);
|
||||
pushOptionalVerdictArgument(args, 'RETURN', options?.RETURN);
|
||||
|
||||
if (options?.SUMMARIZE) {
|
||||
args.push('SUMMARIZE');
|
||||
|
||||
if (typeof options.SUMMARIZE === 'object') {
|
||||
if (options.SUMMARIZE.FIELDS) {
|
||||
args.push('FIELDS');
|
||||
pushVerdictArgument(args, options.SUMMARIZE.FIELDS);
|
||||
}
|
||||
|
||||
if (options.SUMMARIZE.FRAGS) {
|
||||
args.push('FRAGS', options.SUMMARIZE.FRAGS.toString());
|
||||
}
|
||||
|
||||
if (options.SUMMARIZE.LEN) {
|
||||
args.push('LEN', options.SUMMARIZE.LEN.toString());
|
||||
}
|
||||
|
||||
if (options.SUMMARIZE.SEPARATOR) {
|
||||
args.push('SEPARATOR', options.SUMMARIZE.SEPARATOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options?.HIGHLIGHT) {
|
||||
args.push('HIGHLIGHT');
|
||||
|
||||
if (typeof options.HIGHLIGHT === 'object') {
|
||||
if (options.HIGHLIGHT.FIELDS) {
|
||||
args.push('FIELDS');
|
||||
pushVerdictArgument(args, options.HIGHLIGHT.FIELDS);
|
||||
}
|
||||
|
||||
if (options.HIGHLIGHT.TAGS) {
|
||||
args.push('TAGS', options.HIGHLIGHT.TAGS.open, options.HIGHLIGHT.TAGS.close);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options?.SLOP) {
|
||||
args.push('SLOP', options.SLOP.toString());
|
||||
}
|
||||
|
||||
if (options?.INORDER) {
|
||||
args.push('INORDER');
|
||||
}
|
||||
|
||||
if (options?.LANGUAGE) {
|
||||
args.push('LANGUAGE', options.LANGUAGE);
|
||||
}
|
||||
|
||||
if (options?.EXPANDER) {
|
||||
args.push('EXPANDER', options.EXPANDER);
|
||||
}
|
||||
|
||||
if (options?.SCORER) {
|
||||
args.push('SCORER', options.SCORER);
|
||||
}
|
||||
|
||||
// if (options?.EXPLAINSCORE) {
|
||||
// args.push('EXPLAINSCORE');
|
||||
// }
|
||||
|
||||
// if (options?.PAYLOAD) {
|
||||
// args.push('PAYLOAD', options.PAYLOAD);
|
||||
// }
|
||||
|
||||
// if (options?.SORTBY) {
|
||||
// args.push('SORTBY');
|
||||
// pushSortByArguments(args, options.SORTBY);
|
||||
// }
|
||||
|
||||
if (options?.MSORTBY) {
|
||||
pushSortByArguments(args, 'MSORTBY', options.MSORTBY);
|
||||
}
|
||||
|
||||
if (options?.LIMIT) {
|
||||
args.push(
|
||||
'LIMIT',
|
||||
options.LIMIT.from.toString(),
|
||||
options.LIMIT.size.toString()
|
||||
);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
interface SearchDocumentValue {
|
||||
[key: string]: string | number | null | Array<SearchDocumentValue> | SearchDocumentValue;
|
||||
}
|
||||
|
||||
interface SearchReply {
|
||||
total: number;
|
||||
documents: Array<{
|
||||
id: string;
|
||||
value: SearchDocumentValue;
|
||||
}>;
|
||||
}
|
||||
|
||||
export function transformReply(reply: Array<any>): SearchReply {
|
||||
const documents = [];
|
||||
for (let i = 1; i < reply.length; i += 2) {
|
||||
const tuples = reply[i + 1];
|
||||
documents.push({
|
||||
id: reply[i],
|
||||
value: tuples.length === 2 && tuples[0] === '$' ?
|
||||
JSON.parse(tuples[1]) :
|
||||
transformReplyTuples(tuples)
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
total: reply[0],
|
||||
documents
|
||||
};
|
||||
}
|
71
packages/search/lib/commands/SPELLCHECK.spec.ts
Normal file
71
packages/search/lib/commands/SPELLCHECK.spec.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { SchemaFieldTypes } from './CREATE';
|
||||
import { transformArguments } from './SPELLCHECK';
|
||||
|
||||
describe('SPELLCHECK', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query'),
|
||||
['FT.SPELLCHECK', 'index', 'query']
|
||||
);
|
||||
});
|
||||
|
||||
it('with DISTANCE', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', { DISTANCE: 2 }),
|
||||
['FT.SPELLCHECK', 'index', 'query', 'DISTANCE', '2']
|
||||
);
|
||||
});
|
||||
|
||||
describe('with TERMS', () => {
|
||||
it('single', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
TERMS: {
|
||||
mode: 'INCLUDE',
|
||||
dictionary: 'dictionary'
|
||||
}
|
||||
}),
|
||||
['FT.SPELLCHECK', 'index', 'query', 'TERMS', 'INCLUDE', 'dictionary']
|
||||
);
|
||||
});
|
||||
|
||||
it('multiple', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'query', {
|
||||
TERMS: [{
|
||||
mode: 'INCLUDE',
|
||||
dictionary: 'include'
|
||||
}, {
|
||||
mode: 'EXCLUDE',
|
||||
dictionary: 'exclude'
|
||||
}]
|
||||
}),
|
||||
['FT.SPELLCHECK', 'index', 'query', 'TERMS', 'INCLUDE', 'include', 'TERMS', 'EXCLUDE', 'exclude']
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.spellCheck', async client => {
|
||||
await Promise.all([
|
||||
client.ft.create('index', {
|
||||
field: SchemaFieldTypes.TEXT
|
||||
}),
|
||||
client.hSet('key', 'field', 'query')
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.spellCheck('index', 'quer'),
|
||||
[{
|
||||
term: 'quer',
|
||||
suggestions: [{
|
||||
score: 1,
|
||||
suggestion: 'query'
|
||||
}]
|
||||
}]
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
57
packages/search/lib/commands/SPELLCHECK.ts
Normal file
57
packages/search/lib/commands/SPELLCHECK.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
interface SpellCheckTerms {
|
||||
mode: 'INCLUDE' | 'EXCLUDE';
|
||||
dictionary: string;
|
||||
}
|
||||
|
||||
interface SpellCheckOptions {
|
||||
DISTANCE?: number;
|
||||
TERMS?: SpellCheckTerms | Array<SpellCheckTerms>;
|
||||
}
|
||||
|
||||
export function transformArguments(index: string, query: string, options?: SpellCheckOptions): Array<string> {
|
||||
const args = ['FT.SPELLCHECK', index, query];
|
||||
|
||||
if (options?.DISTANCE) {
|
||||
args.push('DISTANCE', options.DISTANCE.toString());
|
||||
}
|
||||
|
||||
if (options?.TERMS) {
|
||||
if (Array.isArray(options.TERMS)) {
|
||||
for (const term of options.TERMS) {
|
||||
pushTerms(args, term);
|
||||
}
|
||||
} else {
|
||||
pushTerms(args, options.TERMS);
|
||||
}
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
function pushTerms(args: Array<string>, { mode, dictionary }: SpellCheckTerms): void {
|
||||
args.push('TERMS', mode, dictionary);
|
||||
}
|
||||
|
||||
type SpellCheckRawReply = Array<[
|
||||
_: string,
|
||||
term: string,
|
||||
suggestions: Array<[score: string, suggestion: string]>
|
||||
]>;
|
||||
|
||||
type SpellCheckReply = Array<{
|
||||
term: string,
|
||||
suggestions: Array<{
|
||||
score: number,
|
||||
suggestion: string
|
||||
}>
|
||||
}>;
|
||||
|
||||
export function transformReply(rawReply: SpellCheckRawReply): SpellCheckReply {
|
||||
return rawReply.map(([, term, suggestions]) => ({
|
||||
term,
|
||||
suggestions: suggestions.map(([score, suggestion]) => ({
|
||||
score: Number(score),
|
||||
suggestion
|
||||
}))
|
||||
}));
|
||||
}
|
35
packages/search/lib/commands/SUGADD.spec.ts
Normal file
35
packages/search/lib/commands/SUGADD.spec.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './SUGADD';
|
||||
|
||||
describe('SUGADD', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key', 'string', 1),
|
||||
['FT.SUGADD', 'key', 'string', '1']
|
||||
);
|
||||
});
|
||||
|
||||
it('with INCR', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key', 'string', 1, { INCR: true }),
|
||||
['FT.SUGADD', 'key', 'string', '1', 'INCR']
|
||||
);
|
||||
});
|
||||
|
||||
it('with PAYLOAD', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key', 'string', 1, { PAYLOAD: 'payload' }),
|
||||
['FT.SUGADD', 'key', 'string', '1', 'PAYLOAD', 'payload']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.sugAdd', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugAdd('key', 'string', 1),
|
||||
1
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
20
packages/search/lib/commands/SUGADD.ts
Normal file
20
packages/search/lib/commands/SUGADD.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
interface SugAddOptions {
|
||||
INCR?: true;
|
||||
PAYLOAD?: string;
|
||||
}
|
||||
|
||||
export function transformArguments(key: string, string: string, score: number, options?: SugAddOptions): Array<string> {
|
||||
const args = ['FT.SUGADD', key, string, score.toString()];
|
||||
|
||||
if (options?.INCR) {
|
||||
args.push('INCR');
|
||||
}
|
||||
|
||||
if (options?.PAYLOAD) {
|
||||
args.push('PAYLOAD', options.PAYLOAD);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export declare function transformReply(): number;
|
19
packages/search/lib/commands/SUGDEL.spec.ts
Normal file
19
packages/search/lib/commands/SUGDEL.spec.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './SUGDEL';
|
||||
|
||||
describe('SUGDEL', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key', 'string'),
|
||||
['FT.SUGDEL', 'key', 'string']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.sugDel', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugDel('key', 'string'),
|
||||
false
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
5
packages/search/lib/commands/SUGDEL.ts
Normal file
5
packages/search/lib/commands/SUGDEL.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export function transformArguments(key: string, string: string): Array<string> {
|
||||
return ['FT.SUGDEL', key, string];
|
||||
}
|
||||
|
||||
export { transformReplyBoolean as transformReply } from '@redis/client/dist/lib/commands/generic-transformers';
|
46
packages/search/lib/commands/SUGGET.spec.ts
Normal file
46
packages/search/lib/commands/SUGGET.spec.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './SUGGET';
|
||||
|
||||
describe('SUGGET', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('without options', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key', 'prefix'),
|
||||
['FT.SUGGET', 'key', 'prefix']
|
||||
);
|
||||
});
|
||||
|
||||
it('with FUZZY', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key', 'prefix', { FUZZY: true }),
|
||||
['FT.SUGGET', 'key', 'prefix', 'FUZZY']
|
||||
);
|
||||
});
|
||||
|
||||
it('with MAX', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key', 'prefix', { MAX: 10 }),
|
||||
['FT.SUGGET', 'key', 'prefix', 'MAX', '10']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('client.ft.sugGet', () => {
|
||||
testUtils.testWithClient('null', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugGet('key', 'prefix'),
|
||||
null
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
|
||||
testUtils.testWithClient('with suggestions', async client => {
|
||||
await client.ft.sugAdd('key', 'string', 1);
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.sugGet('key', 'string'),
|
||||
['string']
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
});
|
22
packages/search/lib/commands/SUGGET.ts
Normal file
22
packages/search/lib/commands/SUGGET.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export const IS_READ_ONLY = true;
|
||||
|
||||
export interface SugGetOptions {
|
||||
FUZZY?: true;
|
||||
MAX?: number;
|
||||
}
|
||||
|
||||
export function transformArguments(key: string, prefix: string, options?: SugGetOptions): Array<string> {
|
||||
const args = ['FT.SUGGET', key, prefix];
|
||||
|
||||
if (options?.FUZZY) {
|
||||
args.push('FUZZY');
|
||||
}
|
||||
|
||||
if (options?.MAX) {
|
||||
args.push('MAX', options.MAX.toString());
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export declare function transformReply(): null | Array<string>;
|
33
packages/search/lib/commands/SUGGET_WITHPAYLOADS.spec.ts
Normal file
33
packages/search/lib/commands/SUGGET_WITHPAYLOADS.spec.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './SUGGET_WITHPAYLOADS';
|
||||
|
||||
describe('SUGGET WITHPAYLOADS', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key', 'prefix'),
|
||||
['FT.SUGGET', 'key', 'prefix', 'WITHPAYLOADS']
|
||||
);
|
||||
});
|
||||
|
||||
describe('client.ft.sugGetWithPayloads', () => {
|
||||
testUtils.testWithClient('null', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugGetWithPayloads('key', 'prefix'),
|
||||
null
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
|
||||
testUtils.testWithClient('with suggestions', async client => {
|
||||
await client.ft.sugAdd('key', 'string', 1, { PAYLOAD: 'payload' });
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.sugGetWithPayloads('key', 'string'),
|
||||
[{
|
||||
suggestion: 'string',
|
||||
payload: 'payload'
|
||||
}]
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
});
|
29
packages/search/lib/commands/SUGGET_WITHPAYLOADS.ts
Normal file
29
packages/search/lib/commands/SUGGET_WITHPAYLOADS.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { SugGetOptions, transformArguments as transformSugGetArguments } from './SUGGET';
|
||||
|
||||
export { IS_READ_ONLY } from './SUGGET';
|
||||
|
||||
export function transformArguments(key: string, prefix: string, options?: SugGetOptions): Array<string> {
|
||||
return [
|
||||
...transformSugGetArguments(key, prefix, options),
|
||||
'WITHPAYLOADS'
|
||||
];
|
||||
}
|
||||
|
||||
export interface SuggestionWithPayload {
|
||||
suggestion: string;
|
||||
payload: string | null;
|
||||
}
|
||||
|
||||
export function transformReply(rawReply: Array<string | null> | null): Array<SuggestionWithPayload> | null {
|
||||
if (rawReply === null) return null;
|
||||
|
||||
const transformedReply = [];
|
||||
for (let i = 0; i < rawReply.length; i += 2) {
|
||||
transformedReply.push({
|
||||
suggestion: rawReply[i]!,
|
||||
payload: rawReply[i + 1]
|
||||
});
|
||||
}
|
||||
|
||||
return transformedReply;
|
||||
}
|
33
packages/search/lib/commands/SUGGET_WITHSCORES.spec.ts
Normal file
33
packages/search/lib/commands/SUGGET_WITHSCORES.spec.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './SUGGET_WITHSCORES';
|
||||
|
||||
describe('SUGGET WITHSCORES', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key', 'prefix'),
|
||||
['FT.SUGGET', 'key', 'prefix', 'WITHSCORES']
|
||||
);
|
||||
});
|
||||
|
||||
describe('client.ft.sugGetWithScores', () => {
|
||||
testUtils.testWithClient('null', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugGetWithScores('key', 'prefix'),
|
||||
null
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
|
||||
testUtils.testWithClient('with suggestions', async client => {
|
||||
await client.ft.sugAdd('key', 'string', 1);
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.sugGetWithScores('key', 'string'),
|
||||
[{
|
||||
suggestion: 'string',
|
||||
score: 2147483648
|
||||
}]
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
});
|
29
packages/search/lib/commands/SUGGET_WITHSCORES.ts
Normal file
29
packages/search/lib/commands/SUGGET_WITHSCORES.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { SugGetOptions, transformArguments as transformSugGetArguments } from './SUGGET';
|
||||
|
||||
export { IS_READ_ONLY } from './SUGGET';
|
||||
|
||||
export function transformArguments(key: string, prefix: string, options?: SugGetOptions): Array<string> {
|
||||
return [
|
||||
...transformSugGetArguments(key, prefix, options),
|
||||
'WITHSCORES'
|
||||
];
|
||||
}
|
||||
|
||||
export interface SuggestionWithScores {
|
||||
suggestion: string;
|
||||
score: number;
|
||||
}
|
||||
|
||||
export function transformReply(rawReply: Array<string> | null): Array<SuggestionWithScores> | null {
|
||||
if (rawReply === null) return null;
|
||||
|
||||
const transformedReply = [];
|
||||
for (let i = 0; i < rawReply.length; i += 2) {
|
||||
transformedReply.push({
|
||||
suggestion: rawReply[i],
|
||||
score: Number(rawReply[i + 1])
|
||||
});
|
||||
}
|
||||
|
||||
return transformedReply;
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './SUGGET_WITHSCORES_WITHPAYLOADS';
|
||||
|
||||
describe('SUGGET WITHSCORES WITHPAYLOADS', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key', 'prefix'),
|
||||
['FT.SUGGET', 'key', 'prefix', 'WITHSCORES', 'WITHPAYLOADS']
|
||||
);
|
||||
});
|
||||
|
||||
describe('client.ft.sugGetWithScoresWithPayloads', () => {
|
||||
testUtils.testWithClient('null', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugGetWithScoresWithPayloads('key', 'prefix'),
|
||||
null
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
|
||||
testUtils.testWithClient('with suggestions', async client => {
|
||||
await client.ft.sugAdd('key', 'string', 1, { PAYLOAD: 'payload' });
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.sugGetWithScoresWithPayloads('key', 'string'),
|
||||
[{
|
||||
suggestion: 'string',
|
||||
score: 2147483648,
|
||||
payload: 'payload'
|
||||
}]
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
||||
});
|
@@ -0,0 +1,30 @@
|
||||
import { SugGetOptions, transformArguments as transformSugGetArguments } from './SUGGET';
|
||||
import { SuggestionWithPayload } from './SUGGET_WITHPAYLOADS';
|
||||
import { SuggestionWithScores } from './SUGGET_WITHSCORES';
|
||||
|
||||
export { IS_READ_ONLY } from './SUGGET';
|
||||
|
||||
export function transformArguments(key: string, prefix: string, options?: SugGetOptions): Array<string> {
|
||||
return [
|
||||
...transformSugGetArguments(key, prefix, options),
|
||||
'WITHSCORES',
|
||||
'WITHPAYLOADS'
|
||||
];
|
||||
}
|
||||
|
||||
type SuggestionWithScoresAndPayloads = SuggestionWithScores & SuggestionWithPayload;
|
||||
|
||||
export function transformReply(rawReply: Array<string | null> | null): Array<SuggestionWithScoresAndPayloads> | null {
|
||||
if (rawReply === null) return null;
|
||||
|
||||
const transformedReply = [];
|
||||
for (let i = 0; i < rawReply.length; i += 3) {
|
||||
transformedReply.push({
|
||||
suggestion: rawReply[i]!,
|
||||
score: Number(rawReply[i + 1]!),
|
||||
payload: rawReply[i + 2]
|
||||
});
|
||||
}
|
||||
|
||||
return transformedReply;
|
||||
}
|
19
packages/search/lib/commands/SUGLEN.spec.ts
Normal file
19
packages/search/lib/commands/SUGLEN.spec.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './SUGLEN';
|
||||
|
||||
describe('SUGLEN', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('key'),
|
||||
['FT.SUGLEN', 'key']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.sugLen', async client => {
|
||||
assert.equal(
|
||||
await client.ft.sugLen('key'),
|
||||
0
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
7
packages/search/lib/commands/SUGLEN.ts
Normal file
7
packages/search/lib/commands/SUGLEN.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const IS_READ_ONLY = true;
|
||||
|
||||
export function transformArguments(key: string): Array<string> {
|
||||
return ['FT.SUGLEN', key];
|
||||
}
|
||||
|
||||
export declare function transformReply(): number;
|
23
packages/search/lib/commands/SYNDUMP.spec.ts
Normal file
23
packages/search/lib/commands/SYNDUMP.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './SYNDUMP';
|
||||
|
||||
describe('SYNDUMP', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index'),
|
||||
['FT.SYNDUMP', 'index']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.synDump', async client => {
|
||||
await client.ft.create('index', {}, {
|
||||
ON: 'HASH' // TODO: shouldn't be mandatory
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.synDump('index'),
|
||||
[]
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
5
packages/search/lib/commands/SYNDUMP.ts
Normal file
5
packages/search/lib/commands/SYNDUMP.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export function transformArguments(index: string): Array<string> {
|
||||
return ['FT.SYNDUMP', index];
|
||||
}
|
||||
|
||||
export declare function transformReply(): Array<string>;
|
39
packages/search/lib/commands/SYNUPDATE.spec.ts
Normal file
39
packages/search/lib/commands/SYNUPDATE.spec.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './SYNUPDATE';
|
||||
|
||||
describe('SYNUPDATE', () => {
|
||||
describe('transformArguments', () => {
|
||||
it('single term', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'groupId', 'term'),
|
||||
['FT.SYNUPDATE', 'index', 'groupId', 'term']
|
||||
);
|
||||
});
|
||||
|
||||
it('multiple terms', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'groupId', ['1', '2']),
|
||||
['FT.SYNUPDATE', 'index', 'groupId', '1', '2']
|
||||
);
|
||||
});
|
||||
|
||||
it('with SKIPINITIALSCAN', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', 'groupId', 'term', { SKIPINITIALSCAN: true }),
|
||||
['FT.SYNUPDATE', 'index', 'groupId', 'SKIPINITIALSCAN', 'term']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.synUpdate', async client => {
|
||||
await client.ft.create('index', {}, {
|
||||
ON: 'HASH' // TODO: shouldn't be mandatory
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
await client.ft.synUpdate('index', 'groupId', 'term'),
|
||||
'OK'
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
23
packages/search/lib/commands/SYNUPDATE.ts
Normal file
23
packages/search/lib/commands/SYNUPDATE.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { pushVerdictArguments } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||
|
||||
interface SynUpdateOptions {
|
||||
SKIPINITIALSCAN?: true;
|
||||
}
|
||||
|
||||
export function transformArguments(
|
||||
index: string,
|
||||
groupId: string,
|
||||
terms: string | Array<string>,
|
||||
options?: SynUpdateOptions
|
||||
): RedisCommandArguments {
|
||||
const args = ['FT.SYNUPDATE', index, groupId];
|
||||
|
||||
if (options?.SKIPINITIALSCAN) {
|
||||
args.push('SKIPINITIALSCAN');
|
||||
}
|
||||
|
||||
return pushVerdictArguments(args, terms);
|
||||
}
|
||||
|
||||
export declare function transformReply(): 'OK';
|
24
packages/search/lib/commands/TAGVALS.spec.ts
Normal file
24
packages/search/lib/commands/TAGVALS.spec.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { SchemaFieldTypes } from './CREATE';
|
||||
import { transformArguments } from './TAGVALS';
|
||||
|
||||
describe('TAGVALS', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('index', '@field'),
|
||||
['FT.TAGVALS', 'index', '@field']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft.tagVals', async client => {
|
||||
await client.ft.create('index', {
|
||||
field: SchemaFieldTypes.TAG
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
await client.ft.tagVals('index', 'field'),
|
||||
[]
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
5
packages/search/lib/commands/TAGVALS.ts
Normal file
5
packages/search/lib/commands/TAGVALS.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export function transformArguments(index: string, fieldName: string): Array<string> {
|
||||
return ['FT.TAGVALS', index, fieldName];
|
||||
}
|
||||
|
||||
export declare function transformReply(): Array<string>;
|
19
packages/search/lib/commands/_LIST.spec.ts
Normal file
19
packages/search/lib/commands/_LIST.spec.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './_LIST';
|
||||
|
||||
describe('_LIST', () => {
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments(),
|
||||
['FT._LIST']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.ft._list', async client => {
|
||||
assert.deepEqual(
|
||||
await client.ft._list(),
|
||||
[]
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
5
packages/search/lib/commands/_LIST.ts
Normal file
5
packages/search/lib/commands/_LIST.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export function transformArguments(): Array<string> {
|
||||
return ['FT._LIST'];
|
||||
}
|
||||
|
||||
export declare function transformReply(): Array<string>;
|
46
packages/search/lib/commands/index.spec.ts
Normal file
46
packages/search/lib/commands/index.spec.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { pushArgumentsWithLength, pushSortByArguments } from '.';
|
||||
|
||||
describe('pushSortByArguments', () => {
|
||||
describe('single', () => {
|
||||
it('string', () => {
|
||||
assert.deepEqual(
|
||||
pushSortByArguments([], 'SORTBT', '@property'),
|
||||
['SORTBT', '1', '@property']
|
||||
);
|
||||
});
|
||||
|
||||
it('.BY', () => {
|
||||
assert.deepEqual(
|
||||
pushSortByArguments([], 'SORTBT', { BY: '@property' }),
|
||||
['SORTBT', '1', '@property']
|
||||
);
|
||||
});
|
||||
|
||||
it('with DIRECTION', () => {
|
||||
assert.deepEqual(
|
||||
pushSortByArguments([], 'SORTBY', {
|
||||
BY: '@property',
|
||||
DIRECTION: 'ASC'
|
||||
}),
|
||||
['SORTBY', '2', '@property', 'ASC']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('multiple', () => {
|
||||
assert.deepEqual(
|
||||
pushSortByArguments([], 'SORTBY', ['@1', '@2']),
|
||||
['SORTBY', '2', '@1', '@2']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('pushArgumentsWithLength', () => {
|
||||
assert.deepEqual(
|
||||
pushArgumentsWithLength(['a'], args => {
|
||||
args.push('b', 'c');
|
||||
}),
|
||||
['a', '2', 'b', 'c']
|
||||
);
|
||||
});
|
133
packages/search/lib/commands/index.ts
Normal file
133
packages/search/lib/commands/index.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import * as _LIST from './_LIST';
|
||||
import * as AGGREGATE from './AGGREGATE';
|
||||
import * as ALIASADD from './ALIASADD';
|
||||
import * as ALIASDEL from './ALIASDEL';
|
||||
import * as ALIASUPDATE from './ALIASUPDATE';
|
||||
import * as CONFIG_GET from './CONFIG_GET';
|
||||
import * as CONFIG_SET from './CONFIG_SET';
|
||||
import * as CREATE from './CREATE';
|
||||
import * as DICTADD from './DICTADD';
|
||||
import * as DICTDEL from './DICTDEL';
|
||||
import * as DICTDUMP from './DICTDUMP';
|
||||
import * as DROPINDEX from './DROPINDEX';
|
||||
import * as EXPLAIN from './EXPLAIN';
|
||||
import * as EXPLAINCLI from './EXPLAINCLI';
|
||||
import * as INFO from './INFO';
|
||||
// import * as PROFILE from './PROFILE';
|
||||
import * as SEARCH from './SEARCH';
|
||||
import * as SPELLCHECK from './SPELLCHECK';
|
||||
import * as SUGADD from './SUGADD';
|
||||
import * as SUGDEL from './SUGDEL';
|
||||
import * as SUGGET_WITHPAYLOADS from './SUGGET_WITHPAYLOADS';
|
||||
import * as SUGGET_WITHSCORES_WITHPAYLOADS from './SUGGET_WITHSCORES_WITHPAYLOADS';
|
||||
import * as SUGGET_WITHSCORES from './SUGGET_WITHSCORES';
|
||||
import * as SUGGET from './SUGGET';
|
||||
import * as SUGLEN from './SUGLEN';
|
||||
import * as SYNDUMP from './SYNDUMP';
|
||||
import * as SYNUPDATE from './SYNUPDATE';
|
||||
import * as TAGVALS from './TAGVALS';
|
||||
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||
|
||||
export default {
|
||||
_LIST,
|
||||
_list: _LIST,
|
||||
AGGREGATE,
|
||||
aggregate: AGGREGATE,
|
||||
ALIASADD,
|
||||
aliasAdd: ALIASADD,
|
||||
ALIASDEL,
|
||||
aliasDel: ALIASDEL,
|
||||
ALIASUPDATE,
|
||||
aliasUpdate: ALIASUPDATE,
|
||||
CONFIG_GET,
|
||||
configGet: CONFIG_GET,
|
||||
CONFIG_SET,
|
||||
configSet: CONFIG_SET,
|
||||
CREATE,
|
||||
create: CREATE,
|
||||
DICTADD,
|
||||
dictAdd: DICTADD,
|
||||
DICTDEL,
|
||||
dictDel: DICTDEL,
|
||||
DICTDUMP,
|
||||
dictDump: DICTDUMP,
|
||||
DROPINDEX,
|
||||
dropIndex: DROPINDEX,
|
||||
EXPLAIN,
|
||||
explain: EXPLAIN,
|
||||
EXPLAINCLI,
|
||||
explainCli: EXPLAINCLI,
|
||||
INFO,
|
||||
info: INFO,
|
||||
// PROFILE,
|
||||
// profile: PROFILE,
|
||||
SEARCH,
|
||||
search: SEARCH,
|
||||
SPELLCHECK,
|
||||
spellCheck: SPELLCHECK,
|
||||
SUGADD,
|
||||
sugAdd: SUGADD,
|
||||
SUGDEL,
|
||||
sugDel: SUGDEL,
|
||||
SUGGET_WITHPAYLOADS,
|
||||
sugGetWithPayloads: SUGGET_WITHPAYLOADS,
|
||||
SUGGET_WITHSCORES_WITHPAYLOADS,
|
||||
sugGetWithScoresWithPayloads: SUGGET_WITHSCORES_WITHPAYLOADS,
|
||||
SUGGET_WITHSCORES,
|
||||
sugGetWithScores: SUGGET_WITHSCORES,
|
||||
SUGGET,
|
||||
sugGet: SUGGET,
|
||||
SUGLEN,
|
||||
sugLen: SUGLEN,
|
||||
SYNDUMP,
|
||||
synDump: SYNDUMP,
|
||||
SYNUPDATE,
|
||||
synUpdate: SYNUPDATE,
|
||||
TAGVALS,
|
||||
tagVals: TAGVALS
|
||||
};
|
||||
|
||||
export type PropertyName = `${'@' | '$.'}${string}`;
|
||||
|
||||
export type SortByOptions = PropertyName | {
|
||||
BY: PropertyName;
|
||||
DIRECTION?: 'ASC' | 'DESC';
|
||||
};
|
||||
|
||||
function pushSortByProperty(args: RedisCommandArguments, sortBy: SortByOptions): void {
|
||||
if (typeof sortBy === 'string') {
|
||||
args.push(sortBy);
|
||||
} else {
|
||||
args.push(sortBy.BY);
|
||||
|
||||
if (sortBy.DIRECTION) {
|
||||
args.push(sortBy.DIRECTION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function pushSortByArguments(args: RedisCommandArguments, name: string, sortBy: SortByOptions | Array<SortByOptions>): RedisCommandArguments {
|
||||
const lengthBefore = args.push(
|
||||
name,
|
||||
'' // will be overwritten
|
||||
);
|
||||
|
||||
if (Array.isArray(sortBy)) {
|
||||
for (const field of sortBy) {
|
||||
pushSortByProperty(args, field);
|
||||
}
|
||||
} else {
|
||||
pushSortByProperty(args, sortBy);
|
||||
}
|
||||
|
||||
args[lengthBefore - 1] = (args.length - lengthBefore).toString();
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export function pushArgumentsWithLength(args: RedisCommandArguments, fn: (args: RedisCommandArguments) => void): RedisCommandArguments {
|
||||
const lengthIndex = args.push('') - 1;
|
||||
fn(args);
|
||||
args[lengthIndex] = (args.length - lengthIndex - 1).toString();
|
||||
return args;
|
||||
}
|
Reference in New Issue
Block a user