Skip to content

Commit

Permalink
Node: Add FT.SEARCH command (valkey-io#2551)
Browse files Browse the repository at this point in the history
* Node: add FT.SEARCH

Signed-off-by: Andrew Carbonetto <andrew.carbonetto@improving.com>

---------

Signed-off-by: Andrew Carbonetto <andrew.carbonetto@improving.com>
  • Loading branch information
acarbonetto authored Nov 1, 2024
1 parent 23689db commit 788b6c8
Show file tree
Hide file tree
Showing 6 changed files with 391 additions and 14 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
* Python: Add `JSON.ARRAPPEND` command ([#2382](https://github.com/valkey-io/valkey-glide/pull/2382))
* Python: Add `JSON.RESP` command ([#2451](https://github.com/valkey-io/valkey-glide/pull/2451))
* Node: Add `JSON.STRLEN` and `JSON.STRAPPEND` command ([#2537](https://github.com/valkey-io/valkey-glide/pull/2537))
* Node: Add `FT.SEARCH` ([#2551](https://github.com/valkey-io/valkey-glide/pull/2551))
* Python: Fix example ([#2556](https://github.com/valkey-io/valkey-glide/issues/2556))

#### Breaking Changes
Expand Down
4 changes: 4 additions & 0 deletions node/npm/glide/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ function initialize() {
VectorFieldAttributesFlat,
VectorFieldAttributesHnsw,
FtCreateOptions,
FtSearchOptions,
FtInfoReturnType,
FtSearchReturnType,
GlideRecord,
GlideString,
JsonGetOptions,
Expand Down Expand Up @@ -248,7 +250,9 @@ function initialize() {
VectorFieldAttributesFlat,
VectorFieldAttributesHnsw,
FtCreateOptions,
FtSearchOptions,
FtInfoReturnType,
FtSearchReturnType,
GlideRecord,
GlideJson,
GlideString,
Expand Down
144 changes: 134 additions & 10 deletions node/src/server-modules/GlideFt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,34 @@ import {
} from "../BaseClient";
import { GlideClient } from "../GlideClient";
import { GlideClusterClient } from "../GlideClusterClient";
import { Field, FtCreateOptions } from "./GlideFtOptions";
import { Field, FtCreateOptions, FtSearchOptions } from "./GlideFtOptions";

/** Data type of {@link GlideFt.info | info} command response. */
type FtInfoReturnType = Record<
/** Response type of {@link GlideFt.info | ft.info} command. */
export type FtInfoReturnType = Record<
string,
| GlideString
| number
| GlideString[]
| Record<string, GlideString | Record<string, GlideString | number>[]>
>;

/**
* Response type for the {@link GlideFt.search | ft.search} command.
*/
export type FtSearchReturnType = [
number,
GlideRecord<GlideRecord<GlideString>>,
];

/** Module for Vector Search commands. */
export class GlideFt {
/**
* Creates an index and initiates a backfill of that index.
*
* @param client The client to execute the command.
* @param indexName The index name for the index to be created.
* @param schema The fields of the index schema, specifying the fields and their types.
* @param options Optional arguments for the `FT.CREATE` command. See {@link FtCreateOptions}.
* @param client - The client to execute the command.
* @param indexName - The index name for the index to be created.
* @param schema - The fields of the index schema, specifying the fields and their types.
* @param options - (Optional) Options for the `FT.CREATE` command. See {@link FtCreateOptions}.
*
* @returns If the index is successfully created, returns "OK".
*
Expand Down Expand Up @@ -182,8 +190,8 @@ export class GlideFt {
/**
* Deletes an index and associated content. Indexed document keys are unaffected.
*
* @param client The client to execute the command.
* @param indexName The index name.
* @param client - The client to execute the command.
* @param indexName - The index name.
*
* @returns "OK"
*
Expand Down Expand Up @@ -269,6 +277,122 @@ export class GlideFt {
>
).then(convertGlideRecordToRecord);
}

/**
* Uses the provided query expression to locate keys within an index. Once located, the count
* and/or content of indexed fields within those keys can be returned.
*
* @param client - The client to execute the command.
* @param indexName - The index name to search into.
* @param query - The text query to search.
* @param options - (Optional) See {@link FtSearchOptions} and {@link DecoderOption}.
*
* @returns A two-element array, where the first element is the number of documents in the result set, and the
* second element has the format: `GlideRecord<GlideRecord<GlideString>>`:
* a mapping between document names and a map of their attributes.
*
* If `count` or `limit` with values `{offset: 0, count: 0}` is
* set, the command returns array with only one element: the number of documents.
*
* @example
* ```typescript
* //
* const vector = Buffer.alloc(24);
* const result = await GlideFt.search(client, "json_idx1", "*=>[KNN 2 @VEC $query_vec]", {params: [{key: "query_vec", value: vector}]});
* console.log(result); // Output:
* // [
* // 2,
* // [
* // {
* // key: "json:2",
* // value: [
* // {
* // key: "$",
* // value: '{"vec":[1.1,1.2,1.3,1.4,1.5,1.6]}',
* // },
* // {
* // key: "__VEC_score",
* // value: "11.1100006104",
* // },
* // ],
* // },
* // {
* // key: "json:0",
* // value: [
* // {
* // key: "$",
* // value: '{"vec":[1,2,3,4,5,6]}',
* // },
* // {
* // key: "__VEC_score",
* // value: "91",
* // },
* // ],
* // },
* // ],
* // ]
* ```
*/
static async search(
client: GlideClient | GlideClusterClient,
indexName: GlideString,
query: GlideString,
options?: FtSearchOptions & DecoderOption,
): Promise<FtSearchReturnType> {
const args: GlideString[] = ["FT.SEARCH", indexName, query];

if (options) {
// RETURN
if (options.returnFields) {
const returnFields: GlideString[] = [];
options.returnFields.forEach((returnField) =>
returnField.alias
? returnFields.push(
returnField.fieldIdentifier,
"AS",
returnField.alias,
)
: returnFields.push(returnField.fieldIdentifier),
);
args.push(
"RETURN",
returnFields.length.toString(),
...returnFields,
);
}

// TIMEOUT
if (options.timeout) {
args.push("TIMEOUT", options.timeout.toString());
}

// PARAMS
if (options.params) {
args.push("PARAMS", (options.params.length * 2).toString());
options.params.forEach((param) =>
args.push(param.key, param.value),
);
}

// LIMIT
if (options.limit) {
args.push(
"LIMIT",
options.limit.offset.toString(),
options.limit.count.toString(),
);
}

// COUNT
if (options.count) {
args.push("COUNT");
}
}

return _handleCustomCommand(client, args, options) as Promise<
[number, GlideRecord<GlideRecord<GlideString>>]
>;
}
}

/**
Expand All @@ -277,7 +401,7 @@ export class GlideFt {
async function _handleCustomCommand(
client: GlideClient | GlideClusterClient,
args: GlideString[],
decoderOption?: DecoderOption,
decoderOption: DecoderOption = {},
): Promise<GlideReturnType> {
return client instanceof GlideClient
? (client as GlideClient).customCommand(args, decoderOption)
Expand Down
45 changes: 44 additions & 1 deletion node/src/server-modules/GlideFtOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
*/

import { GlideString } from "../BaseClient";
import { GlideRecord, GlideString } from "../BaseClient";

interface BaseField {
/** The name of the field. */
Expand Down Expand Up @@ -118,3 +118,46 @@ export interface FtCreateOptions {
/** The prefix of the key to be indexed. */
prefixes?: GlideString[];
}

/**
* Represents the input options to be used in the FT.SEARCH command.
* All fields in this class are optional inputs for FT.SEARCH.
*/
export type FtSearchOptions = {
/**
* Add a field to be returned.
* @param fieldIdentifier field name to return.
* @param alias optional alias for the field name to return.
*/
returnFields?: { fieldIdentifier: GlideString; alias?: GlideString }[];

/** Query timeout in milliseconds. */
timeout?: number;

/**
* Query parameters, which could be referenced in the query by `$` sign, followed by
* the parameter name.
*/
params?: GlideRecord<GlideString>;
} & (
| {
/**
* Configure query pagination. By default only first 10 documents are returned.
*
* @param offset Zero-based offset.
* @param count Number of elements to return.
*/
limit?: { offset: number; count: number };
/** `limit` and `count` are mutually exclusive. */
count?: never;
}
| {
/**
* Once set, the query will return only the number of documents in the result set without actually
* returning them.
*/
count?: boolean;
/** `limit` and `count` are mutually exclusive. */
limit?: never;
}
);
Loading

0 comments on commit 788b6c8

Please sign in to comment.