diff --git a/examples/java/src/main/java/glide/examples/GlideFtExample.java b/examples/java/src/main/java/glide/examples/GlideFtExample.java new file mode 100644 index 0000000000..98d61849c3 --- /dev/null +++ b/examples/java/src/main/java/glide/examples/GlideFtExample.java @@ -0,0 +1,228 @@ +/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.examples; + +import static glide.api.logging.Logger.Level.ERROR; +import static glide.api.logging.Logger.Level.INFO; +import static glide.api.logging.Logger.Level.WARN; +import static glide.api.logging.Logger.log; +import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleMultiNodeRoute.ALL_NODES; + +import glide.api.GlideClusterClient; +import glide.api.commands.servermodules.FT; +import glide.api.logging.Logger; +import glide.api.models.ClusterValue; +import glide.api.models.commands.InfoOptions.Section; +import glide.api.models.configuration.GlideClusterClientConfiguration; +import glide.api.models.configuration.NodeAddress; +import glide.api.models.exceptions.ClosingException; +import glide.api.models.exceptions.ConnectionException; +import glide.api.models.exceptions.TimeoutException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class GlideFtExample { + + /** + * Creates and returns a GlideClusterClient instance. + * + *

This function initializes a GlideClusterClient with the provided list of nodes. + * The list may contain the address of one or more cluster nodes, and the client will + * automatically discover all nodes in the cluster. + * + * @return A GlideClusterClient connected to the discovered nodes. + * @throws CancellationException if the operation is cancelled. + * @throws ExecutionException if the client fails due to execution errors. + * @throws InterruptedException if the operation is interrupted. + */ + public static GlideClusterClient createClient(List nodeList) + throws CancellationException, ExecutionException, InterruptedException { + // Check `GlideClusterClientConfiguration` for additional options. + GlideClusterClientConfiguration config = + GlideClusterClientConfiguration.builder() + .addresses(nodeList) + // Enable this field if the servers are configured with TLS. + // .useTLS(true); + .build(); + + GlideClusterClient client = GlideClusterClient.createClient(config).get(); + return client; + } + + /** + * Executes the main logic of the application, performing basic operations such as FT.CREATE and + * FT.SEARCH using the provided GlideClusterClient. + * + * @param client An instance of GlideClusterClient. + * @throws ExecutionException if an execution error occurs during operations. + * @throws InterruptedException if the operation is interrupted. + */ + public static void appLogic(GlideClusterClient client) + throws ExecutionException, InterruptedException { + + String prefix = "{" + UUID.randomUUID() + "}:"; + String index = prefix + "index"; + + CompletableFuture createResponse = + FT.create( + client, + index, + new FieldInfo[] { + new FieldInfo("vec", "VEC", VectorFieldHnsw.builder(DistanceMetric.L2, 2).build()) + }, + FTCreateOptions.builder() + .dataType(DataType.HASH) + .prefixes(new String[] {prefix}) + .build()); // "OK" + + CompletableFuture hsetResponse = + client.hset( + gs(prefix + 0), + Map.of( + gs("vec"), + gs( + new byte[] { + (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0 + }))); // response is 1L which represents the number of fields that were added. + + hsetResponse = + client.hset( + gs(prefix + 1), + Map.of( + gs("vec"), + gs( + new byte[] { + (byte) 0, + (byte) 0, + (byte) 0, + (byte) 0, + (byte) 0, + (byte) 0, + (byte) 0x80, + (byte) 0xBF + }))); // response is 1L which represents the number of fields that were added. + Thread.sleep(DATA_PROCESSING_TIMEOUT); // let server digest the data and update + + // These are the optional arguments used for the FT.search command + var options = + FTSearchOptions.builder() + .params( + Map.of( + gs("query_vec"), + gs( + new byte[] { + (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, + (byte) 0 + }))) + .build(); + String query = "*=>[KNN 2 @VEC $query_vec]"; // This is the text query to search for + CompletableFuture searchResponse = FT.search(client, index, query, options); + + // When you call .get() on searchResponse, the result will be an Object[] as shown in the commented assert test below. + // assertArrayEquals( + // new Object[] { + // 2L, + // Map.of( + // gs(prefix + 0), + // Map.of(gs("__VEC_score"), gs("0"), gs("vec"), gs("\0\0\0\0\0\0\0\0")), + // gs(prefix + 1), + // Map.of( + // gs("__VEC_score"), + // gs("1"), + // gs("vec"), + // gs( + // new byte[] { + // (byte) 0, + // (byte) 0, + // (byte) 0, + // (byte) 0, + // (byte) 0, + // (byte) 0, + // (byte) 0x80, + // (byte) 0xBF + // }))) + // }, + // searchResponse.get()); + + System.out.println("Create response: " + createResponse.get()); + System.out.println("Hset response: " + hsetResponse.get()); + System.out.println("Search response: " + searchResponse.get()); + + // Send INFO REPLICATION with routing option to all nodes + ClusterValue infoResponse = + client.info(new Section[] {Section.REPLICATION}, ALL_NODES).get(); + log( + INFO, + "app", + "INFO REPLICATION responses from all nodes are " + infoResponse.getMultiValue()); + } + + /** + * Executes the application logic with exception handling. + * + * @throws ExecutionException if an execution error occurs during operations. + */ + private static void execAppLogic() throws ExecutionException { + + // Define list of nodes + List nodeList = + Collections.singletonList(NodeAddress.builder().host("localhost").port(6379).build()); + + while (true) { + try (GlideClusterClient client = createClient(nodeList)) { + appLogic(client); + return; + } catch (CancellationException e) { + log(ERROR, "glide", "Request cancelled: " + e.getMessage()); + throw e; + } catch (InterruptedException e) { + log(ERROR, "glide", "Client interrupted: " + e.getMessage()); + Thread.currentThread().interrupt(); // Restore interrupt status + throw new CancellationException("Client was interrupted."); + } catch (ExecutionException e) { + // All Glide errors will be handled as ExecutionException + if (e.getCause() instanceof ClosingException) { + // If the error message contains "NOAUTH", raise the exception + // because it indicates a critical authentication issue. + if (e.getMessage().contains("NOAUTH")) { + log(ERROR, "glide", "Authentication error encountered: " + e.getMessage()); + throw e; + } else { + log(WARN, "glide", "Client has closed and needs to be re-created: " + e.getMessage()); + } + } else if (e.getCause() instanceof ConnectionException) { + // The client wasn't able to reestablish the connection within the given retries + log(ERROR, "glide", "Connection error encountered: " + e.getMessage()); + throw e; + } else if (e.getCause() instanceof TimeoutException) { + // A request timed out. You may choose to retry the execution based on your application's + // logic + log(ERROR, "glide", "Timeout encountered: " + e.getMessage()); + throw e; + } else { + log(ERROR, "glide", "Execution error encountered: " + e.getCause()); + throw e; + } + } + } + } + + /** + * The entry point of the cluster example. This method sets up the logger configuration and + * executes the main application logic. + * + * @param args Command-line arguments passed to the application. + * @throws ExecutionException if an error occurs during execution of the application logic. + */ + public static void main(String[] args) throws ExecutionException { + // In this example, we will utilize the client's logger for all log messages + Logger.setLoggerConfig(INFO); + // Optional - set the logger to write to a file + // Logger.setLoggerConfig(Logger.Level.INFO, file) + execAppLogic(); + } +} diff --git a/examples/java/src/main/java/glide/examples/GlideJsonExample.java b/examples/java/src/main/java/glide/examples/GlideJsonExample.java new file mode 100644 index 0000000000..d5060d3fc1 --- /dev/null +++ b/examples/java/src/main/java/glide/examples/GlideJsonExample.java @@ -0,0 +1,146 @@ +/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.examples; + +import static glide.api.logging.Logger.Level.ERROR; +import static glide.api.logging.Logger.Level.INFO; +import static glide.api.logging.Logger.Level.WARN; +import static glide.api.logging.Logger.log; +import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleMultiNodeRoute.ALL_NODES; + +import glide.api.GlideClusterClient; +import glide.api.commands.servermodules.Json; +import glide.api.logging.Logger; +import glide.api.models.ClusterValue; +import glide.api.models.commands.InfoOptions.Section; +import glide.api.models.configuration.GlideClusterClientConfiguration; +import glide.api.models.configuration.NodeAddress; +import glide.api.models.exceptions.ClosingException; +import glide.api.models.exceptions.ConnectionException; +import glide.api.models.exceptions.TimeoutException; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class GlideJsonExample { + + /** + * Creates and returns a GlideClusterClient instance. + * + *

This function initializes a GlideClusterClient with the provided list of nodes. + * The list may contain the address of one or more cluster nodes, and the client will + * automatically discover all nodes in the cluster. + * + * @return A GlideClusterClient connected to the discovered nodes. + * @throws CancellationException if the operation is cancelled. + * @throws ExecutionException if the client fails due to execution errors. + * @throws InterruptedException if the operation is interrupted. + */ + public static GlideClusterClient createClient(List nodeList) + throws CancellationException, ExecutionException, InterruptedException { + // Check `GlideClusterClientConfiguration` for additional options. + GlideClusterClientConfiguration config = + GlideClusterClientConfiguration.builder() + .addresses(nodeList) + // Enable this field if the servers are configured with TLS. + // .useTLS(true); + .build(); + + GlideClusterClient client = GlideClusterClient.createClient(config).get(); + return client; + } + + /** + * Executes the main logic of the application, performing basic operations such as JSON.SET and + * JSON.GET using the provided GlideClusterClient. + * + * @param client An instance of GlideClusterClient. + * @throws ExecutionException if an execution error occurs during operations. + * @throws InterruptedException if the operation is interrupted. + */ + public static void appLogic(GlideClusterClient client) + throws ExecutionException, InterruptedException { + + CompletableFuture setResponse = Json.set(client, "key", "$", "{\"a\": 1.0,\"b\": 2}"); + System.out.println("The set response is " + setResponse.get()); // The response should be "OK" + + CompletableFuture getResponse = Json.get(client, "key", new String[] {"$.a", "$.b"}); + System.out.println( + "The get response is " + + getResponse.get()); // The response should be "{\"$.a\":[1.0],\"$.b\":[2]}" + + // Send INFO REPLICATION with routing option to all nodes + ClusterValue infoResponse = + client.info(new Section[] {Section.REPLICATION}, ALL_NODES).get(); + log( + INFO, + "app", + "INFO REPLICATION responses from all nodes are " + infoResponse.getMultiValue()); + } + + /** + * Executes the application logic with exception handling. + * + * @throws ExecutionException if an execution error occurs during operations. + */ + private static void execAppLogic() throws ExecutionException { + + // Define list of nodes + List nodeList = + Collections.singletonList(NodeAddress.builder().host("localhost").port(6379).build()); + + while (true) { + try (GlideClusterClient client = createClient(nodeList)) { + appLogic(client); + return; + } catch (CancellationException e) { + log(ERROR, "glide", "Request cancelled: " + e.getMessage()); + throw e; + } catch (InterruptedException e) { + log(ERROR, "glide", "Client interrupted: " + e.getMessage()); + Thread.currentThread().interrupt(); // Restore interrupt status + throw new CancellationException("Client was interrupted."); + } catch (ExecutionException e) { + // All Glide errors will be handled as ExecutionException + if (e.getCause() instanceof ClosingException) { + // If the error message contains "NOAUTH", raise the exception + // because it indicates a critical authentication issue. + if (e.getMessage().contains("NOAUTH")) { + log(ERROR, "glide", "Authentication error encountered: " + e.getMessage()); + throw e; + } else { + log(WARN, "glide", "Client has closed and needs to be re-created: " + e.getMessage()); + } + } else if (e.getCause() instanceof ConnectionException) { + // The client wasn't able to reestablish the connection within the given retries + log(ERROR, "glide", "Connection error encountered: " + e.getMessage()); + throw e; + } else if (e.getCause() instanceof TimeoutException) { + // A request timed out. You may choose to retry the execution based on your application's + // logic + log(ERROR, "glide", "Timeout encountered: " + e.getMessage()); + throw e; + } else { + log(ERROR, "glide", "Execution error encountered: " + e.getCause()); + throw e; + } + } + } + } + + /** + * The entry point of the cluster example. This method sets up the logger configuration and + * executes the main application logic. + * + * @param args Command-line arguments passed to the application. + * @throws ExecutionException if an error occurs during execution of the application logic. + */ + public static void main(String[] args) throws ExecutionException { + // In this example, we will utilize the client's logger for all log messages + Logger.setLoggerConfig(INFO); + // Optional - set the logger to write to a file + // Logger.setLoggerConfig(Logger.Level.INFO, file) + execAppLogic(); + } +} diff --git a/examples/python/ft_example.py b/examples/python/ft_example.py new file mode 100644 index 0000000000..1a54eac031 --- /dev/null +++ b/examples/python/ft_example.py @@ -0,0 +1,155 @@ +import asyncio +from typing import List, Tuple + +from glide import ( + ClosingError, + ConnectionError, + GlideClient, + GlideClientConfiguration, + Logger, + LogLevel, + NodeAddress, + RequestError, + TimeoutError +) + +from python.glade.async_commands.server_modules import ft +from glide.async_commands.server_modules.ft_options.ft_search_options import ( + FtSearchOptions, +) + +async def create_client( + nodes_list: List[Tuple[str, int]] = [("localhost", 6379)] +) -> GlideClient: + """ + Creates and returns a GlideClient instance. + + This function initializes a GlideClient with the provided list of nodes. + The nodes_list may contain either only primary node or a mix of primary + and replica nodes. The GlideClient use these nodes to connect to + the Standalone setup servers. + + Args: + nodes_list (List[Tuple[str, int]]): A list of tuples where each tuple + contains a host (str) and port (int). Defaults to [("localhost", 6379)]. + + Returns: + GlideClient: An instance of GlideClient connected to the specified nodes. + """ + addresses = [] + for host, port in nodes_list: + addresses.append(NodeAddress(host, port)) + + # Check `GlideClientConfiguration` for additional options. + config = GlideClientConfiguration( + addresses, + # Enable this field if the servers are configured with TLS. + # use_tls=True + ) + return await GlideClient.create(config) + + +async def app_logic(client: GlideClusterClient): + """ + Executes the main logic of the application, performing basic operations + such as FT.CREATE and FT.SEARCH using the provided GlideClient. + + Args: + client (GlideClusterClient): An instance of GlideClient. + """ + # Create a vector + index = prefix + str(uuid.uuid4()) + create_response = await ft.create(client, index, + schema=[ + NumericField("$.a", "a"), + NumericField("$.b", "b"), + ], + options=FtCreateOptions(DataType.JSON), + ) + Logger.log(LogLevel.INFO, "app", f"Create response is = {create_response!r}") # 'OK' + + # Create a json key. + assert ( + await GlideJson.set(glide_client, json_key1, "$", json.dumps(json_value1)) + == OK + ) + assert ( + await GlideJson.set(glide_client, json_key2, "$", json.dumps(json_value2)) + == OK + ) + + time.sleep(self.sleep_wait_time) + + # Search for the vector + search_response = await ft.search(glide_client, index, "*", options=ft_search_options) + ft_search_options = FtSearchOptions( + return_fields=[ + ReturnField(field_identifier="a", alias="a_new"), + ReturnField(field_identifier="b", alias="b_new"), + ] + ) + + Logger.log(LogLevel.INFO, "app", f"Search response is = {search_response!r}") + + +async def exec_app_logic(): + """ + Executes the application logic with exception handling. + """ + while True: + try: + client = await create_client() + return await app_logic(client) + except asyncio.CancelledError: + raise + except ClosingError as e: + # If the error message contains "NOAUTH", raise the exception + # because it indicates a critical authentication issue. + if "NOAUTH" in str(e): + Logger.log( + LogLevel.ERROR, + "glide", + f"Authentication error encountered: {e}", + ) + raise e + Logger.log( + LogLevel.WARN, + "glide", + f"Client has closed and needs to be re-created: {e}", + ) + except TimeoutError as e: + # A request timed out. You may choose to retry the execution based on your application's logic + Logger.log(LogLevel.ERROR, "glide", f"TimeoutError encountered: {e}") + raise e + except ConnectionError as e: + # The client wasn't able to reestablish the connection within the given retries + Logger.log(LogLevel.ERROR, "glide", f"ConnectionError encountered: {e}") + raise e + except RequestError as e: + # Other error reported during a request, such as a server response error + Logger.log(LogLevel.ERROR, "glide", f"RequestError encountered: {e}") + raise e + except Exception as e: + Logger.log(LogLevel.ERROR, "glide", f"Unexpected error: {e}") + raise e + finally: + try: + await client.close() + except Exception as e: + Logger.log( + LogLevel.WARN, + "glide", + f"Encountered an error while closing the client: {e}", + ) + + +def main(): + # In this example, we will utilize the client's logger for all log messages + Logger.set_logger_config(LogLevel.INFO) + # Optional - set the logger to write to a file + # Logger.set_logger_config(LogLevel.INFO, file) + asyncio.run(exec_app_logic()) + + +if __name__ == "__main__": + main() diff --git a/examples/python/json_example.py b/examples/python/json_example.py new file mode 100644 index 0000000000..c46de4f25a --- /dev/null +++ b/examples/python/json_example.py @@ -0,0 +1,128 @@ +import asyncio +from typing import List, Tuple + +from glide import ( + ClosingError, + ConnectionError, + GlideClient, + GlideClientConfiguration, + Logger, + LogLevel, + NodeAddress, + RequestError, + TimeoutError +) + +from python.glade.async_commands.server_modules import glide_json + + +async def create_client( + nodes_list: List[Tuple[str, int]] = [("localhost", 6379)] +) -> GlideClient: + """ + Creates and returns a GlideClient instance. + + This function initializes a GlideClient with the provided list of nodes. + The nodes_list may contain either only primary node or a mix of primary + and replica nodes. The GlideClient use these nodes to connect to + the Standalone setup servers. + + Args: + nodes_list (List[Tuple[str, int]]): A list of tuples where each tuple + contains a host (str) and port (int). Defaults to [("localhost", 6379)]. + + Returns: + GlideClient: An instance of GlideClient connected to the specified nodes. + """ + addresses = [] + for host, port in nodes_list: + addresses.append(NodeAddress(host, port)) + + # Check `GlideClientConfiguration` for additional options. + config = GlideClientConfiguration( + addresses, + # Enable this field if the servers are configured with TLS. + # use_tls=True + ) + return await GlideClient.create(config) + + +async def app_logic(client: TGlideClient): + """ + Executes the main logic of the application, performing basic operations + such as JSON.SET and JSON.GET using the provided GlideClient. + + Args: + client (TGlideClient): An instance of GlideClient. + """ + + json_value = {"a": 1.0, "b": 2} + # Send SET and GET + set_response = await json.set(client, "key", "$") + Logger.log(LogLevel.INFO, "app", f"Set response is = {set_response!r}") + + get_response = await json.get(client, "key", "$") + Logger.log(LogLevel.INFO, "app", f"Get response is = {get_response.decode()!r}") + + +async def exec_app_logic(): + """ + Executes the application logic with exception handling. + """ + while True: + try: + client = await create_client() + return await app_logic(client) + except asyncio.CancelledError: + raise + except ClosingError as e: + # If the error message contains "NOAUTH", raise the exception + # because it indicates a critical authentication issue. + if "NOAUTH" in str(e): + Logger.log( + LogLevel.ERROR, + "glide", + f"Authentication error encountered: {e}", + ) + raise e + Logger.log( + LogLevel.WARN, + "glide", + f"Client has closed and needs to be re-created: {e}", + ) + except TimeoutError as e: + # A request timed out. You may choose to retry the execution based on your application's logic + Logger.log(LogLevel.ERROR, "glide", f"TimeoutError encountered: {e}") + raise e + except ConnectionError as e: + # The client wasn't able to reestablish the connection within the given retries + Logger.log(LogLevel.ERROR, "glide", f"ConnectionError encountered: {e}") + raise e + except RequestError as e: + # Other error reported during a request, such as a server response error + Logger.log(LogLevel.ERROR, "glide", f"RequestError encountered: {e}") + raise e + except Exception as e: + Logger.log(LogLevel.ERROR, "glide", f"Unexpected error: {e}") + raise e + finally: + try: + await client.close() + except Exception as e: + Logger.log( + LogLevel.WARN, + "glide", + f"Encountered an error while closing the client: {e}", + ) + + +def main(): + # In this example, we will utilize the client's logger for all log messages + Logger.set_logger_config(LogLevel.INFO) + # Optional - set the logger to write to a file + # Logger.set_logger_config(LogLevel.INFO, file) + asyncio.run(exec_app_logic()) + + +if __name__ == "__main__": + main()