From 01e66e7c8ffc0ad59e08bc9e6ce860efe12b88b7 Mon Sep 17 00:00:00 2001 From: Avital Fine <79420960+AvitalFineRedis@users.noreply.github.com> Date: Mon, 13 Dec 2021 16:28:04 +0100 Subject: [PATCH] Search commands (#1778) * ft.alter * ft.profile --- .../search/lib/commands/AGGREGATE.spec.ts | 2 +- packages/search/lib/commands/AGGREGATE.ts | 26 +- packages/search/lib/commands/ALTER.spec.ts | 37 ++ packages/search/lib/commands/ALTER.ts | 10 + packages/search/lib/commands/CREATE.spec.ts | 4 +- packages/search/lib/commands/CREATE.ts | 107 +----- .../search/lib/commands/DROPINDEX.spec.ts | 2 +- packages/search/lib/commands/INFO.spec.ts | 1 - packages/search/lib/commands/PROFILE.ts | 26 -- .../lib/commands/PROFILE_AGGREGATE.spec.ts | 46 +++ .../search/lib/commands/PROFILE_AGGREGATE.ts | 29 ++ .../lib/commands/PROFILE_SEARCH.spec.ts | 41 ++ .../search/lib/commands/PROFILE_SEARCH.ts | 29 ++ packages/search/lib/commands/SEARCH.spec.ts | 3 +- packages/search/lib/commands/SEARCH.ts | 125 +----- .../search/lib/commands/SPELLCHECK.spec.ts | 2 +- packages/search/lib/commands/TAGVALS.spec.ts | 2 +- packages/search/lib/commands/index.ts | 359 +++++++++++++++++- packages/search/lib/index.ts | 2 +- 19 files changed, 581 insertions(+), 272 deletions(-) create mode 100644 packages/search/lib/commands/ALTER.spec.ts create mode 100644 packages/search/lib/commands/ALTER.ts delete mode 100644 packages/search/lib/commands/PROFILE.ts create mode 100644 packages/search/lib/commands/PROFILE_AGGREGATE.spec.ts create mode 100644 packages/search/lib/commands/PROFILE_AGGREGATE.ts create mode 100644 packages/search/lib/commands/PROFILE_SEARCH.spec.ts create mode 100644 packages/search/lib/commands/PROFILE_SEARCH.ts diff --git a/packages/search/lib/commands/AGGREGATE.spec.ts b/packages/search/lib/commands/AGGREGATE.spec.ts index 2a6647c97a..24684d447d 100644 --- a/packages/search/lib/commands/AGGREGATE.spec.ts +++ b/packages/search/lib/commands/AGGREGATE.spec.ts @@ -1,7 +1,7 @@ import { strict as assert } from 'assert'; import testUtils, { GLOBAL } from '../test-utils'; import { AggregateGroupByReducers, AggregateSteps, transformArguments } from './AGGREGATE'; -import { SchemaFieldTypes } from './CREATE'; +import { SchemaFieldTypes } from '.'; describe('AGGREGATE', () => { describe('transformArguments', () => { diff --git a/packages/search/lib/commands/AGGREGATE.ts b/packages/search/lib/commands/AGGREGATE.ts index 2546618890..7160cee1d4 100644 --- a/packages/search/lib/commands/AGGREGATE.ts +++ b/packages/search/lib/commands/AGGREGATE.ts @@ -1,6 +1,6 @@ import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands'; import { pushVerdictArgument, transformReplyTuples, TuplesObject } from '@node-redis/client/dist/lib/commands/generic-transformers'; -import { PropertyName, pushArgumentsWithLength, pushSortByArguments, SortByProperty } from '.'; +import { AggregateReply, PropertyName, pushArgumentsWithLength, pushSortByArguments, SortByProperty } from '.'; export enum AggregateSteps { GROUPBY = 'GROUPBY', @@ -118,14 +118,27 @@ type LoadField = PropertyName | { AS?: string; } -interface AggregateOptions { +export interface AggregateOptions { VERBATIM?: true; LOAD?: LoadField | Array; STEPS?: Array; } -export function transformArguments(index: string, query: string, options?: AggregateOptions): RedisCommandArguments { +export function transformArguments( + index: string, + query: string, + options?: AggregateOptions +): RedisCommandArguments { + const args = ['FT.AGGREGATE', index, query]; + pushAggregatehOptions(args, options); + return args; +} + +export function pushAggregatehOptions( + args: RedisCommandArguments, + options?: AggregateOptions +): RedisCommandArguments { if (options?.VERBATIM) { args.push('VERBATIM'); @@ -258,16 +271,11 @@ function pushGroupByReducer(args: RedisCommandArguments, reducer: GroupByReducer } } -type AggregateRawReply = [ +export type AggregateRawReply = [ total: number, ...results: Array> ]; -interface AggregateReply { - total: number; - results: Array; -} - export function transformReply(rawReply: AggregateRawReply): AggregateReply { const results: Array = []; for (let i = 1; i < rawReply.length; i++) { diff --git a/packages/search/lib/commands/ALTER.spec.ts b/packages/search/lib/commands/ALTER.spec.ts new file mode 100644 index 0000000000..e9724757ad --- /dev/null +++ b/packages/search/lib/commands/ALTER.spec.ts @@ -0,0 +1,37 @@ +import { strict as assert } from 'assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import { transformArguments } from './ALTER'; +import { SchemaFieldTypes } from '.'; + +describe('ALTER', () => { + describe('transformArguments', () => { + it('with NOINDEX', () => { + assert.deepEqual( + transformArguments('index', { + field: { + type: SchemaFieldTypes.TEXT, + NOINDEX: true, + SORTABLE: 'UNF', + AS: 'text' + } + }), + ['FT.ALTER', 'index', 'SCHEMA', 'ADD', 'field', 'AS', 'text', 'TEXT', 'SORTABLE', 'UNF', 'NOINDEX'] + ); + }); + }); + + testUtils.testWithClient('client.ft.create', async client => { + await Promise.all([ + client.ft.create('index', { + title: SchemaFieldTypes.TEXT + }), + ]); + + assert.equal( + await client.ft.alter('index', { + body: SchemaFieldTypes.TEXT + }), + 'OK' + ); + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/search/lib/commands/ALTER.ts b/packages/search/lib/commands/ALTER.ts new file mode 100644 index 0000000000..8e74c64376 --- /dev/null +++ b/packages/search/lib/commands/ALTER.ts @@ -0,0 +1,10 @@ +import { CreateSchema, pushSchema } from '.'; + +export function transformArguments(index: string, schema: CreateSchema): Array { + const args = ['FT.ALTER', index, 'SCHEMA', 'ADD']; + pushSchema(args, schema); + + return args; +} + +export declare function transformReply(): 'OK'; diff --git a/packages/search/lib/commands/CREATE.spec.ts b/packages/search/lib/commands/CREATE.spec.ts index 5bdf4c93a4..115487b7e8 100644 --- a/packages/search/lib/commands/CREATE.spec.ts +++ b/packages/search/lib/commands/CREATE.spec.ts @@ -1,7 +1,7 @@ import { strict as assert } from 'assert'; import testUtils, { GLOBAL } from '../test-utils'; -import { SchemaFieldTypes, SchemaTextFieldPhonetics, transformArguments } from './CREATE'; -import { RedisSearchLanguages } from '.'; +import { transformArguments } from './CREATE'; +import { SchemaFieldTypes, SchemaTextFieldPhonetics, RedisSearchLanguages } from '.'; describe('CREATE', () => { describe('transformArguments', () => { diff --git a/packages/search/lib/commands/CREATE.ts b/packages/search/lib/commands/CREATE.ts index 1a5e45a4a8..b2f49a7f0e 100644 --- a/packages/search/lib/commands/CREATE.ts +++ b/packages/search/lib/commands/CREATE.ts @@ -1,49 +1,5 @@ import { pushOptionalVerdictArgument } from '@node-redis/client/dist/lib/commands/generic-transformers'; -import { RedisSearchLanguages, PropertyName } from '.'; - -export enum SchemaFieldTypes { - TEXT = 'TEXT', - NUMERIC = 'NUMERIC', - GEO = 'GEO', - TAG = 'TAG' -} - -type CreateSchemaField> = T | ({ - type: T; - AS?: string; - 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; - -type CreateSchemaNumericField = CreateSchemaField; - -type CreateSchemaGeoField = CreateSchemaField; - -type CreateSchemaTagField = CreateSchemaField; - -interface CreateSchema { - [field: string]: - CreateSchemaTextField | - CreateSchemaNumericField | - CreateSchemaGeoField | - CreateSchemaTagField -} +import { RedisSearchLanguages, PropertyName, CreateSchema, pushSchema } from '.'; interface CreateOptions { ON?: 'HASH' | 'JSON'; @@ -126,67 +82,8 @@ export function transformArguments(index: string, schema: CreateSchema, options? } 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); - } - - if (fieldOptions.CASESENSITIVE) { - args.push('CASESENSITIVE'); - } - - break; - } - - if (fieldOptions.SORTABLE) { - args.push('SORTABLE'); - - if (fieldOptions.SORTABLE === 'UNF') { - args.push('UNF'); - } - } - - if (fieldOptions.NOINDEX) { - args.push('NOINDEX'); - } - } + pushSchema(args, schema); return args; } diff --git a/packages/search/lib/commands/DROPINDEX.spec.ts b/packages/search/lib/commands/DROPINDEX.spec.ts index 751e274ba6..b1cb7c9313 100644 --- a/packages/search/lib/commands/DROPINDEX.spec.ts +++ b/packages/search/lib/commands/DROPINDEX.spec.ts @@ -1,6 +1,6 @@ import { strict as assert } from 'assert'; import testUtils, { GLOBAL } from '../test-utils'; -import { SchemaFieldTypes } from './CREATE'; +import { SchemaFieldTypes } from '.'; import { transformArguments } from './DROPINDEX'; describe('DROPINDEX', () => { diff --git a/packages/search/lib/commands/INFO.spec.ts b/packages/search/lib/commands/INFO.spec.ts index fa50a4b0cd..805d4c820c 100644 --- a/packages/search/lib/commands/INFO.spec.ts +++ b/packages/search/lib/commands/INFO.spec.ts @@ -1,6 +1,5 @@ import { strict as assert } from 'assert'; import testUtils, { GLOBAL } from '../test-utils'; -import { SchemaFieldTypes } from './CREATE'; import { transformArguments } from './INFO'; describe('INFO', () => { diff --git a/packages/search/lib/commands/PROFILE.ts b/packages/search/lib/commands/PROFILE.ts deleted file mode 100644 index e315ea5230..0000000000 --- a/packages/search/lib/commands/PROFILE.ts +++ /dev/null @@ -1,26 +0,0 @@ -export const IS_READ_ONLY = true; - -interface ProfileOptions { - LIMITED?: true; -} - -export function transformArguments( - index: string, - type: 'SEARCH' | 'AGGREGATE', - query: string, - options?: ProfileOptions -): Array { - const args = ['FT.PROFILE', index, type]; - - if (options?.LIMITED) { - args.push('LIMITED'); - } - - args.push('QUERY', query); - - return args; -} - -export function transformReply() { - -} diff --git a/packages/search/lib/commands/PROFILE_AGGREGATE.spec.ts b/packages/search/lib/commands/PROFILE_AGGREGATE.spec.ts new file mode 100644 index 0000000000..c3d6f990ff --- /dev/null +++ b/packages/search/lib/commands/PROFILE_AGGREGATE.spec.ts @@ -0,0 +1,46 @@ +import { strict as assert } from 'assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import { SchemaFieldTypes } from '.'; +import { transformArguments } from './PROFILE_AGGREGATE'; +import { AggregateSteps } from './AGGREGATE'; + +describe('PROFILE AGGREGATE', () => { + describe('transformArguments', () => { + it('without options', () => { + assert.deepEqual( + transformArguments('index', 'query'), + ['FT.PROFILE', 'index', 'AGGREGATE', 'QUERY', 'query'] + ); + }); + + it('with options', () => { + assert.deepEqual( + transformArguments('index', 'query', { + LIMITED: true, + VERBATIM: true, + STEPS: [{ + type: AggregateSteps.SORTBY, + BY: '@by' + }] + }), + ['FT.PROFILE', 'index', 'AGGREGATE', 'LIMITED', 'QUERY', 'query', + 'VERBATIM', 'SORTBY', '1', '@by'] + ); + }); + }); + + testUtils.testWithClient('client.ft.search', async client => { + await Promise.all([ + client.ft.create('index', { + field: SchemaFieldTypes.NUMERIC + }), + client.hSet('1', 'field', '1'), + client.hSet('2', 'field', '2') + ]); + + const res = await client.ft.profileAggregate('index', '*'); + assert.ok(typeof res.profile.iteratorsProfile.counter === 'number'); + assert.ok(typeof res.profile.parsingTime === 'string'); + assert.ok(res.results.total == 1); + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/search/lib/commands/PROFILE_AGGREGATE.ts b/packages/search/lib/commands/PROFILE_AGGREGATE.ts new file mode 100644 index 0000000000..b28e06ade9 --- /dev/null +++ b/packages/search/lib/commands/PROFILE_AGGREGATE.ts @@ -0,0 +1,29 @@ +import { pushAggregatehOptions, AggregateOptions, transformReply as transformAggregateReply, AggregateRawReply } from './AGGREGATE'; +import { ProfileOptions, ProfileRawReply, ProfileReply, transformProfile } from '.'; + +export const IS_READ_ONLY = true; + +export function transformArguments( + index: string, + query: string, + options?: ProfileOptions & AggregateOptions +): Array { + const args = ['FT.PROFILE', index, 'AGGREGATE']; + + if (options?.LIMITED) { + args.push('LIMITED'); + } + + args.push('QUERY', query); + pushAggregatehOptions(args, options) + return args; +} + +type ProfileAggeregateRawReply = ProfileRawReply; + +export function transformReply(reply: ProfileAggeregateRawReply): ProfileReply { + return { + results: transformAggregateReply(reply[0]), + profile: transformProfile(reply[1]) + }; +} diff --git a/packages/search/lib/commands/PROFILE_SEARCH.spec.ts b/packages/search/lib/commands/PROFILE_SEARCH.spec.ts new file mode 100644 index 0000000000..6d7c5adda1 --- /dev/null +++ b/packages/search/lib/commands/PROFILE_SEARCH.spec.ts @@ -0,0 +1,41 @@ +import { strict as assert } from 'assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import { SchemaFieldTypes } from '.'; +import { transformArguments } from './PROFILE_SEARCH'; + +describe('PROFILE SEARCH', () => { + describe('transformArguments', () => { + it('without options', () => { + assert.deepEqual( + transformArguments('index', 'query'), + ['FT.PROFILE', 'index', 'SEARCH', 'QUERY', 'query'] + ); + }); + + it('with options', () => { + assert.deepEqual( + transformArguments('index', 'query', { + LIMITED: true, + VERBATIM: true, + INKEYS: 'key' + }), + ['FT.PROFILE', 'index', 'SEARCH', 'LIMITED', 'QUERY', 'query', + 'VERBATIM', 'INKEYS', '1', 'key'] + ); + }); + }); + + testUtils.testWithClient('client.ft.search', async client => { + await Promise.all([ + client.ft.create('index', { + field: SchemaFieldTypes.NUMERIC + }), + client.hSet('1', 'field', '1') + ]); + + const res = await client.ft.profileSearch('index', '*'); + assert.ok(typeof res.profile.iteratorsProfile.counter === 'number'); + assert.ok(typeof res.profile.parsingTime === 'string'); + assert.ok(res.results.total == 1); + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/search/lib/commands/PROFILE_SEARCH.ts b/packages/search/lib/commands/PROFILE_SEARCH.ts new file mode 100644 index 0000000000..bdb67ec387 --- /dev/null +++ b/packages/search/lib/commands/PROFILE_SEARCH.ts @@ -0,0 +1,29 @@ +import { SearchOptions, SearchRawReply, transformReply as transformSearchReply } from './SEARCH'; +import { pushSearchOptions, ProfileOptions, ProfileRawReply, ProfileReply, transformProfile } from '.'; + +export const IS_READ_ONLY = true; + +export function transformArguments( + index: string, + query: string, + options?: ProfileOptions & SearchOptions +): Array { + const args = ['FT.PROFILE', index, 'SEARCH']; + + if (options?.LIMITED) { + args.push('LIMITED'); + } + + args.push('QUERY', query); + pushSearchOptions(args, options) + return args; +} + +type ProfileSearchRawReply = ProfileRawReply; + +export function transformReply(reply: ProfileSearchRawReply): ProfileReply { + return { + results: transformSearchReply(reply[0]), + profile: transformProfile(reply[1]) + }; +} diff --git a/packages/search/lib/commands/SEARCH.spec.ts b/packages/search/lib/commands/SEARCH.spec.ts index a4d75dd895..c2f2c295c8 100644 --- a/packages/search/lib/commands/SEARCH.spec.ts +++ b/packages/search/lib/commands/SEARCH.spec.ts @@ -1,7 +1,6 @@ import { strict as assert } from 'assert'; -import { RedisSearchLanguages } from '.'; +import { RedisSearchLanguages, SchemaFieldTypes } from '.'; import testUtils, { GLOBAL } from '../test-utils'; -import { SchemaFieldTypes } from './CREATE'; import { transformArguments } from './SEARCH'; describe('SEARCH', () => { diff --git a/packages/search/lib/commands/SEARCH.ts b/packages/search/lib/commands/SEARCH.ts index 34d255e5b2..c289f6fc27 100644 --- a/packages/search/lib/commands/SEARCH.ts +++ b/packages/search/lib/commands/SEARCH.ts @@ -1,12 +1,12 @@ import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands'; -import { pushOptionalVerdictArgument, pushVerdictArgument, transformReplyTuples } from '@node-redis/client/dist/lib/commands/generic-transformers'; -import { RedisSearchLanguages, PropertyName, pushSortByProperty, SortByProperty } from '.'; +import { transformReplyTuples } from '@node-redis/client/dist/lib/commands/generic-transformers'; +import { pushSearchOptions, RedisSearchLanguages, PropertyName, SortByProperty, SearchReply } from '.'; export const FIRST_KEY_INDEX = 1; export const IS_READ_ONLY = true; -interface SearchOptions { +export interface SearchOptions { // NOCONTENT?: true; TODO VERBATIM?: true; NOSTOPWORDS?: true; @@ -62,126 +62,13 @@ export function transformArguments( 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'); - pushSortByProperty(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() - ); - } - + pushSearchOptions(args, options); return args; } -interface SearchDocumentValue { - [key: string]: string | number | null | Array | SearchDocumentValue; -} +export type SearchRawReply = Array; -interface SearchReply { - total: number; - documents: Array<{ - id: string; - value: SearchDocumentValue; - }>; -} - -export function transformReply(reply: Array): SearchReply { +export function transformReply(reply: SearchRawReply): SearchReply { const documents = []; for (let i = 1; i < reply.length; i += 2) { const tuples = reply[i + 1]; diff --git a/packages/search/lib/commands/SPELLCHECK.spec.ts b/packages/search/lib/commands/SPELLCHECK.spec.ts index bacbe118b3..fe74f5910f 100644 --- a/packages/search/lib/commands/SPELLCHECK.spec.ts +++ b/packages/search/lib/commands/SPELLCHECK.spec.ts @@ -1,6 +1,6 @@ import { strict as assert } from 'assert'; import testUtils, { GLOBAL } from '../test-utils'; -import { SchemaFieldTypes } from './CREATE'; +import { SchemaFieldTypes } from '.'; import { transformArguments } from './SPELLCHECK'; describe('SPELLCHECK', () => { diff --git a/packages/search/lib/commands/TAGVALS.spec.ts b/packages/search/lib/commands/TAGVALS.spec.ts index 1f90939bb0..d59bfcfe3e 100644 --- a/packages/search/lib/commands/TAGVALS.spec.ts +++ b/packages/search/lib/commands/TAGVALS.spec.ts @@ -1,6 +1,6 @@ import { strict as assert } from 'assert'; import testUtils, { GLOBAL } from '../test-utils'; -import { SchemaFieldTypes } from './CREATE'; +import { SchemaFieldTypes } from '.'; import { transformArguments } from './TAGVALS'; describe('TAGVALS', () => { diff --git a/packages/search/lib/commands/index.ts b/packages/search/lib/commands/index.ts index d7b2b79343..f148adc08f 100644 --- a/packages/search/lib/commands/index.ts +++ b/packages/search/lib/commands/index.ts @@ -1,4 +1,5 @@ import * as _LIST from './_LIST'; +import * as ALTER from './ALTER'; import * as AGGREGATE from './AGGREGATE'; import * as ALIASADD from './ALIASADD'; import * as ALIASDEL from './ALIASDEL'; @@ -13,7 +14,8 @@ 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 PROFILESEARCH from './PROFILE_SEARCH'; +import * as PROFILEAGGREGATE from './PROFILE_AGGREGATE'; import * as SEARCH from './SEARCH'; import * as SPELLCHECK from './SPELLCHECK'; import * as SUGADD from './SUGADD'; @@ -27,10 +29,15 @@ import * as SYNDUMP from './SYNDUMP'; import * as SYNUPDATE from './SYNUPDATE'; import * as TAGVALS from './TAGVALS'; import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands'; +import { pushOptionalVerdictArgument, pushVerdictArgument, TuplesObject } from '@node-redis/client/dist/lib/commands/generic-transformers'; +import internal = require('stream'); +import { SearchOptions } from './SEARCH'; export default { _LIST, _list: _LIST, + ALTER, + alter: ALTER, AGGREGATE, aggregate: AGGREGATE, ALIASADD, @@ -59,8 +66,10 @@ export default { explainCli: EXPLAINCLI, INFO, info: INFO, - // PROFILE, - // profile: PROFILE, + PROFILESEARCH, + profileSearch: PROFILESEARCH, + PROFILEAGGREGATE, + profileAggregate: PROFILEAGGREGATE, SEARCH, search: SEARCH, SPELLCHECK, @@ -159,3 +168,347 @@ export function pushArgumentsWithLength(args: RedisCommandArguments, fn: (args: args[lengthIndex] = (args.length - lengthIndex - 1).toString(); return args; } + +export enum SchemaFieldTypes { + TEXT = 'TEXT', + NUMERIC = 'NUMERIC', + GEO = 'GEO', + TAG = 'TAG' +} + +type CreateSchemaField> = T | ({ + type: T; + AS?: string; + 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; + +type CreateSchemaNumericField = CreateSchemaField; + +type CreateSchemaGeoField = CreateSchemaField; + +type CreateSchemaTagField = CreateSchemaField; + +export interface CreateSchema { + [field: string]: + CreateSchemaTextField | + CreateSchemaNumericField | + CreateSchemaGeoField | + CreateSchemaTagField +} + +export function pushSchema(args: RedisCommandArguments, schema: CreateSchema) { + 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); + } + + if (fieldOptions.CASESENSITIVE) { + args.push('CASESENSITIVE'); + } + + break; + } + + if (fieldOptions.SORTABLE) { + args.push('SORTABLE'); + + if (fieldOptions.SORTABLE === 'UNF') { + args.push('UNF'); + } + } + + if (fieldOptions.NOINDEX) { + args.push('NOINDEX'); + } + } +} + +export function pushSearchOptions( + args: RedisCommandArguments, + options?: SearchOptions +): RedisCommandArguments { + + 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'); + pushSortByProperty(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; +} + +export interface SearchReply { + total: number; + documents: Array<{ + id: string; + value: SearchDocumentValue; + }>; +} + +export interface AggregateReply { + total: number; + results: Array; +} + +export interface ProfileOptions { + LIMITED?: true; +} + +export type ProfileRawReply = [ + results: T, + profile: [ + _: string, + TotalProfileTime: string, + _: string, + ParsingTime: string, + _: string, + PipelineCreationTime: string, + _: string, + IteratorsProfile: Array + ] +]; + +export interface ProfileReply { + results: SearchReply | AggregateReply, + profile: ProfileData +} + +interface ChildIterator { + type?: string, + counter?: number, + term?: string, + size?: number, + time?: string, + childIterators?: Array +} + +interface IteratorsProfile { + type?: string, + counter?: number, + queryType?: string, + time?: string, + childIterators?: Array +} + +interface ProfileData { + totalProfileTime: string, + parsingTime: string, + pipelineCreationTime: string, + iteratorsProfile: IteratorsProfile +} + +export function transformProfile(reply: Array): ProfileData{ + return { + totalProfileTime: reply[0][1], + parsingTime: reply[1][1], + pipelineCreationTime: reply[2][1], + iteratorsProfile: transformIterators(reply[3][1]) + }; +} + +function transformIterators(IteratorsProfile: Array): IteratorsProfile { + var res: IteratorsProfile = {}; + for (let i = 0; i < IteratorsProfile.length; i += 2) { + const value = IteratorsProfile[i+1]; + switch (IteratorsProfile[i]) { + case 'Type': + res.type = value; + break; + case 'Counter': + res.counter = value; + break; + case 'Time': + res.time = value; + break; + case 'Query type': + res.queryType = value; + break; + case 'Child iterators': + res.childIterators = value.map(transformChildIterators); + break; + } + } + + return res; +} + +function transformChildIterators(IteratorsProfile: Array): ChildIterator { + var res: ChildIterator = {}; + for (let i = 1; i < IteratorsProfile.length; i += 2) { + const value = IteratorsProfile[i+1]; + switch (IteratorsProfile[i]) { + case 'Type': + res.type = value; + break; + case 'Counter': + res.counter = value; + break; + case 'Time': + res.time = value; + break; + case 'Size': + res.size = value; + break; + case 'Term': + res.term = value; + break; + case 'Child iterators': + res.childIterators = value.map(transformChildIterators); + break; + } + } + + return res; +} diff --git a/packages/search/lib/index.ts b/packages/search/lib/index.ts index 9e0be7b169..9480c5a7a7 100644 --- a/packages/search/lib/index.ts +++ b/packages/search/lib/index.ts @@ -1,4 +1,4 @@ export { default } from './commands'; -export { SchemaFieldTypes, SchemaTextFieldPhonetics } from './commands/CREATE'; +export { SchemaFieldTypes, SchemaTextFieldPhonetics } from './commands'; export { AggregateSteps, AggregateGroupByReducers } from './commands/AGGREGATE';