diff --git a/.github/workflows/ci-astra-dev-new.yml b/.github/workflows/ci-astra-dev-new.yml index 19c065ae..53bafe18 100644 --- a/.github/workflows/ci-astra-dev-new.yml +++ b/.github/workflows/ci-astra-dev-new.yml @@ -68,7 +68,7 @@ jobs: mvn test -Dtest=com.datastax.astra.test.integration.dev.*Test run_vectorize_tests: - needs: setup + needs: BUILD runs-on: ubuntu-latest strategy: matrix: diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/DataAPIClient.java b/astra-db-java/src/main/java/com/datastax/astra/client/DataAPIClient.java index 3fe8e536..fd31a61c 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/DataAPIClient.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/DataAPIClient.java @@ -21,67 +21,58 @@ */ import com.datastax.astra.client.admin.AstraDBAdmin; -import com.datastax.astra.client.admin.AstraDBDatabaseAdmin; -import com.datastax.astra.client.admin.DatabaseAdmin; +import com.datastax.astra.client.admin.AdminOptions; import com.datastax.astra.client.core.options.DataAPIClientOptions; import com.datastax.astra.client.databases.Database; +import com.datastax.astra.client.databases.DatabaseOptions; import com.datastax.astra.internal.api.AstraApiEndpoint; import com.datastax.astra.internal.utils.Assert; import java.util.UUID; -import static com.datastax.astra.client.admin.AstraDBAdmin.DEFAULT_KEYSPACE; import static com.datastax.astra.client.exception.InvalidEnvironmentException.throwErrorRestrictedAstra; /** - * Serves as the primary entry point to the Data API client, offering streamlined access to the functionalities - * provided by the Data API, whether deployed within Astra environments or on-premise DataStax Enterprise installations. - *

- * This client aims to simplify interactions with the Data API through a user-friendly, high-level API design. It - * supports fluent API patterns, builder mechanisms for complex configurations, and employs idiomatic method naming - * conventions to enhance readability and ease of use. The design philosophy of this client closely mirrors that of - * the established MongoDB API, providing a familiar experience to developers accustomed to MongoDB's client interface. + * Serves as the primary entry point to the Data API client, providing an intuitive and streamlined interface + * for interacting with the Data API. The client is compatible with both Astra environments and on-premise + * DataStax Enterprise installations, offering flexibility across deployment scenarios. + * + *

The {@code DataAPIClient} is designed to simplify database interactions by providing: + *

+ * This design philosophy facilitates quick onboarding and ease of use while enabling advanced customizations when needed. *

- *

- * Through this client, users can perform a wide range of operations, from basic data manipulation in databases and - * collections to more advanced administrative tasks. Administrative capabilities, such as database creation and - * keyspace management, are available to users with the appropriate administrative privileges. + * + *

Core Features: + *

*

* *

Example usage:

*
  * {@code
- * // Initialize the client with default settings
- * DataAPIClient client = new DataAPIClient("yourAuthTokenHere");
- *
- * // Initialize the client with custom HTTP configuration
- * DataAPIClient clientWithCustomConfig = new DataAPIClient("yourAuthTokenHere", DataAPIOptions.builder()
- *                 .withHttpRequestTimeout(1000) // Request timeout in milliseconds
- *                 .withHttpConnectTimeout(10) // Connection timeout in milliseconds
- *                 .withHttpVersion(HttpClient.Version.HTTP_2) // HTTP protocol version
- *                 .withDestination("ASTRA") // Target destination, e.g., Astra
- *                 .build());
+ * DataAPIClientOptions options = new DataAPIClientOptions()
+ *         .destination(DataAPIDestination.DSE) // Set the destination
+ *         .httpClientOptions(new HttpClientOptions()
+ *                 .httpRedirect(HttpClient.Redirect.NORMAL) // Configure HTTP redirects
+ *                 .httpProxy(new HttpProxy("localhost", 8080)) // Set up an HTTP proxy
+ *                 .httpRetries(1, Duration.ofSeconds(10))) // Configure retries
+ *         .timeoutOptions(new TimeoutOptions()
+ *                 .requestTimeoutMillis(1000)) // Set request timeout
+ *         .enableFeatureFlagTables() // Enable feature flag for tables
+ *         .addDatabaseAdditionalHeader(HEADER_FEATURE_FLAG_TABLES, "true"); // Add custom headers
+ * DataAPIClient client = new DataAPIClient("token", options);
  * }
  * 
- * - * This documentation highlights the ease of starting with the DataAPIClient, whether opting for a quick setup with - * default configurations or a more tailored approach via detailed HTTP client settings. The examples demonstrate - * both the straightforward initialization process and the method to apply fine-grained configurations for developers' - * specific needs. */ public class DataAPIClient { - /** parameters names. */ - private static final String ARG_KEYSPACE = "keyspace"; - /** parameters names. */ - private static final String ARG_OPTIONS = "options"; - /** parameters names. */ - private static final String ARG_TOKEN = "token"; - /** parameters names. */ - private static final String ARG_DATABASE_ID = "databaseId"; - /** parameters names. */ - private static final String ARG_REGION = "region"; - /** * The authentication token used as credentials in HTTP requests, specifically as the Authorization bearer token. * This token is crucial for accessing and interacting with Astra environments, where it plays a role in determining @@ -130,9 +121,7 @@ public class DataAPIClient { *

Example usage:

*
      * {@code
-     * String myAuthToken = "AstraCS:...";
-     * DataAPIClient client = new DataAPIClient(myAuthToken);
-     * // Now the client is ready to make authenticated requests with default settings
+     * DataAPIClient client = new DataAPIClient("token");
      * }
      * 
* @@ -140,7 +129,7 @@ public class DataAPIClient { * by the server, typically starting with "AstraCS:.." for Astra environments. */ public DataAPIClient(String token) { - this(token, DataAPIClientOptions.builder().build()); + this(token, new DataAPIClientOptions()); } /** @@ -161,15 +150,17 @@ public DataAPIClient(String token) { *

Example usage:

*
      * {@code
-     * String myAuthToken = "AstraCS:...";
-     * DataAPIOptions myOptions = DataAPIOptions.builder()
-     *      .withHttpRequestTimeout(1000)
-     *      .withHttpConnectTimeout(500)
-     *      .withHttpVersion(HttpClient.Version.HTTP_2)
-     *      .build();
-     *
-     * DataAPIClient myClient = new DataAPIClient(myAuthToken, myOptions);
-     * // The client is now ready to perform actions with custom configurations.
+     * DataAPIClientOptions options = new DataAPIClientOptions()
+     *         .destination(DataAPIDestination.DSE) // Set the destination
+     *         .httpClientOptions(new HttpClientOptions()
+     *                 .httpRedirect(HttpClient.Redirect.NORMAL) // Configure HTTP redirects
+     *                 .httpProxy(new HttpProxy("localhost", 8080)) // Set up an HTTP proxy
+     *                 .httpRetries(1, Duration.ofSeconds(10))) // Configure retries
+     *         .timeoutOptions(new TimeoutOptions()
+     *                 .requestTimeoutMillis(1000)) // Set request timeout
+     *         .enableFeatureFlagTables() // Enable feature flag for tables
+     *         .addDatabaseAdditionalHeader(HEADER_FEATURE_FLAG_TABLES, "true"); // Add custom headers
+     * DataAPIClient client = new DataAPIClient("token", options);
      * }
      * 
* @@ -180,82 +171,112 @@ public DataAPIClient(String token) { * @throws IllegalArgumentException if the token is empty or null, or if the options are null. */ public DataAPIClient(String token, DataAPIClientOptions options) { - Assert.hasLength(token, ARG_TOKEN); - Assert.notNull(options, ARG_OPTIONS); + Assert.hasLength(token, "token"); + Assert.notNull(options, "options"); this.token = token; this.options = options; } - // -------------------------------------------------- - // --- Access AstraDBAdmin --- - // -------------------------------------------------- - /** - * Retrieves an administration client specific to Astra deployments. This client is intended for performing - * administrative tasks such as creating databases. It requires the use of a token with sufficient privileges. - *

- * To use this method effectively, the provided authentication token must be associated with a user having - * elevated privileges, such as a Database Administrator or Organization Administrator. This ensures that - * the client has the necessary permissions to execute administrative operations within the Astra environment. - *

- *

- * The administration client provides a programmatic interface for managing various aspects of the Astra - * deployment, enabling tasks such as database creation, user management, and configuration adjustments - * without the need for direct interaction with the Astra UI. - *

+ * Constructs a new instance of the {@link DataAPIClient} without a default token. The token will instead need to + * be specified when calling `.getDatabase()` or `.getAdmin()`. Prefer this method when using a db-scoped token + * instead of a more universal token. * *

Example usage:

*
      * {@code
-     * DataAPIClient apiClient = new DataAPIClient("AstraCS:your_admin_token_here");
-     * AstraDBAdmin adminClient = apiClient.getAdmin();
-     * // Use adminClient to perform administrative operations, e.g., create a database
+     * DataAPIClientOptions options = new DataAPIClientOptions()
+     *                 .timeoutOptions(new TimeoutOptions()
+     *                     .connectTimeoutMillis(1000)
+     *                     .requestTimeoutMillis(1000))
+     *                 .httpClientOptions(new HttpClientOptions()
+     *                     .httpVersion(HttpClient.Version.HTTP_2)
+     *                 );
+     * DataAPIClient myClient = new DataAPIClient(myOptions);
+     * // The client is now ready to perform actions with custom configurations.
      * }
      * 
* - * @return An instance of {@link AstraDBAdmin} configured with the current authentication token, ready for - * administrative operations. - * @throws SecurityException if the current token does not have the necessary administrative privileges. + * @param options - The default options to use when spawning new instances of {@link Database} or {@link AstraDBAdmin}. */ - public AstraDBAdmin getAdmin() { - return getAdmin(this.token); + public DataAPIClient(DataAPIClientOptions options) { + Assert.notNull(options, "options"); + this.token = null; + this.options = options; } + // -------------------------------------------------- + // --- Access AstraDBAdmin --- + // -------------------------------------------------- + /** - * Retrieves an administration client capable of performing CRUD operations on databases, requiring a token with - * advanced privileges. This method is designed for scenarios where administrative access is necessary beyond the - * default token capabilities associated with the {@code DataAPIClient}. - *

- * The provided {@code superUserToken} should be granted sufficient privileges to perform administrative operations, - * such as creating, updating, and deleting databases. This typically involves tokens associated with roles like - * Database Administrator or Organization Administrator within the Astra environment. - *

- *

- * Utilizing this method allows for direct access to the Astra database's administrative functionalities, enabling - * comprehensive management capabilities through the returned {@link AstraDBAdmin} client. This includes but is not - * limited to database creation, modification, and deletion. + * Retrieves an administration client specifically designed for Astra deployments. This client is used for + * performing administrative tasks such as database creation, user management, and configuration adjustments. + * It provides a programmatic interface for managing Astra resources securely and efficiently. + * + *

This method has three variants, allowing for flexibility in token usage: + *

*

* + *

To perform administrative tasks, the token must belong to a user with sufficient privileges (e.g., Database + * Administrator or Organization Administrator). If these conditions are not met, a {@code SecurityException} is thrown.

+ * *

Example usage:

*
      * {@code
-     * String superUserToken = "AstraCS:super_user_token_here";
-     * DataAPIClient apiClient = new DataAPIClient(superUserToken);
-     * AstraDBAdmin adminClient = apiClient.getAdmin(superUserToken);
-     * // Now you can use adminClient for administrative operations like creating a database
+     * // Example 1: Using the default token provided at client initialization
+     * DataAPIClient apiClient = new DataAPIClient("AstraCS:your_admin_token_here");
+     * AstraDBAdmin adminClient = apiClient.getAdmin();
+     * adminClient.createDatabase("new_database", "keyspace_name");
+     *
+     * // Example 2: Using a custom super token for administrative operations
+     * AstraDBAdmin adminClientWithSuperToken = apiClient.getAdmin("AstraCS:your_super_admin_token_here");
+     * adminClientWithSuperToken.createDatabase("another_database", "another_keyspace");
+     *
+     * // Example 3: Using advanced options for fine-grained control
+     * AdminOptions options = new AdminOptions("AstraCS:custom_token", new DataAPIClientOptions().logRequests());
+     * AstraDBAdmin advancedAdminClient = apiClient.getAdmin(options);
+     * advancedAdminClient.createDatabase("custom_database", "custom_keyspace");
      * }
      * 
* - * @param superUserToken A token with elevated privileges, enabling administrative actions within the Astra - * environment. This token must be authorized to perform operations such as creating and managing databases. - * @return An instance of {@link AstraDBAdmin}, configured for administrative tasks with the provided user token. - * @throws SecurityException if the provided {@code superUserToken} lacks the necessary privileges for administrative operations. + * @return An instance of {@link AstraDBAdmin} configured with the appropriate authentication token and options, + * ready for administrative operations. */ - public AstraDBAdmin getAdmin(String superUserToken) { + public AstraDBAdmin getAdmin(AdminOptions adminOptions) { if (!options.isAstra()) { throwErrorRestrictedAstra("getAdmin()", options.getDestination()); } - return new AstraDBAdmin(superUserToken, options); + return new AstraDBAdmin(adminOptions); + } + + /** + * Retrieves an administration client using the default authentication token provided during + * {@code DataAPIClient} initialization. + * + * @return An instance of {@link AstraDBAdmin} configured with the default token. + * @throws SecurityException if the token does not have the necessary privileges or the operation is not in an Astra environment. + * @see #getAdmin(AdminOptions) + */ + public AstraDBAdmin getAdmin() { + return getAdmin(new AdminOptions(token, options)); + } + + /** + * Retrieves an administration client using the default authentication token provided during + * {@code DataAPIClient} initialization. + * + * @param superToken The custom token to use for administrative operations. + * @return An instance of {@link AstraDBAdmin} configured with the default token. + * @see #getAdmin(AdminOptions) + */ + public AstraDBAdmin getAdmin(String superToken) { + return getAdmin(new AdminOptions(superToken, options)); } // -------------------------------------------------- @@ -263,188 +284,147 @@ public AstraDBAdmin getAdmin(String superUserToken) { // -------------------------------------------------- /** - * Retrieves a client for a specific database, enabling interactions with the Data API. This method allows for - * operations such as querying, updating, and managing data within the specified database and keyspace. - *

- * The {@code databaseId} parameter is a unique identifier for the target database. This ID ensures that operations - * performed through the returned client are executed against the correct database instance within the Astra - * environment or an on-premise DataStax Enterprise setup. - *

- *

- * The {@code keyspace} parameter specifies the keyspace (often synonymous with "keyspace" in Cassandra terminology) - * within the database to which the client will have access. Keyspaces are used to organize and isolate data within - * the database, providing a layer of abstraction and security. + * Retrieves a database client configured to interact with the Data API. This client enables direct communication + * with the specified Data API endpoint, supporting a wide range of data manipulation operations such as querying, + * inserting, updating, and deleting data. + * + *

The {@code getDatabase} method has multiple variants to cater to different usage scenarios: + *

*

* + *

By providing flexibility in how connections are established and configured, these methods simplify the process + * of interacting with Cassandra databases through the Data API. They are suitable for various deployment scenarios, + * including Astra cloud services and on-premise installations.

+ * *

Example usage:

*
      * {@code
-     * UUID databaseId = UUID.fromString("your-database-uuid-here");
-     * String keyspace = "your_keyspace_here";
+     * // Example 1: Connect using a direct API endpoint
+     * String apiEndpoint = "https://-.apps.astra.datastax.com";
      * DataAPIClient apiClient = new DataAPIClient("yourAuthTokenHere");
-     * Database databaseClient = apiClient.getDatabase(databaseId, keyspace);
-     * // Use databaseClient to perform data operations within the specified keyspace
+     * Database databaseClient = apiClient.getDatabase(apiEndpoint);
+     *
+     * // Example 2: Connect using a database ID (with automatic endpoint resolution)
+     * UUID databaseId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
+     * Database databaseClientById = apiClient.getDatabase(databaseId);
+     *
+     * // Example 3: Customize options while connecting
+     * DatabaseOptions options = new DatabaseOptions("yourAuthTokenHere", new DataAPIClientOptions().logRequests());
+     * Database customDatabaseClient = apiClient.getDatabase(apiEndpoint, options);
      * }
      * 
* - * @param databaseId The unique identifier of the database to interact with. - * @param keyspace The keyspace within the specified database to which operations will be scoped. - * @return A {@link Database} client configured to interact with the specified database and keyspace, allowing for - * data manipulation and query operations. + * @return A {@link Database} client tailored for interaction with the Data API, configured according to the + * provided parameters. + * + * @throws IllegalArgumentException If the provided parameters are invalid or insufficient for resolving the endpoint. */ - public Database getDatabase(UUID databaseId, String keyspace) { - Assert.notNull(databaseId, ARG_DATABASE_ID); - Assert.hasLength(keyspace, ARG_KEYSPACE); - if (!options.isAstra()) { - throwErrorRestrictedAstra("getDatabase(id,keyspace)", options.getDestination()); - } - return new Database(new AstraApiEndpoint(databaseId, - getAdmin().getDatabaseInfo(databaseId).getRegion(), - options.getAstraEnvironment()).getApiEndPoint(), - this.token, keyspace, options); + public Database getDatabase(String apiEndpoint, DatabaseOptions dbOptions) { + return new Database(apiEndpoint, dbOptions); } /** - * Retrieves a client for a specific database, enabling interactions with the Data API. This method allows for - * operations such as querying, updating, and managing data within the specified database and keyspace. - * - * @param databaseId The unique identifier of the database to interact with. - * @param keyspace The keyspace associated to this database - * @param region The cloud provider region where the database is deployed. + * Retrieves a database client configured to connect to the Data API using the specified API endpoint. + *

+ * Uses default {@link DatabaseOptions} for configuration. + *

* - * @return - * A {@link Database} client configured to interact with the specified database and keyspace, allowing for - * data manipulation and query operations. + * @param apiEndpoint The URL of the Data API endpoint to connect to. + * @return A {@link Database} client configured with default options for the specified endpoint. + * @see #getDatabase(String, DatabaseOptions) */ - public Database getDatabase(UUID databaseId, String keyspace, String region) { - Assert.notNull(databaseId, ARG_DATABASE_ID); - Assert.hasLength(keyspace, ARG_KEYSPACE); - Assert.hasLength(region, ARG_REGION); - if (!options.isAstra()) { - throwErrorRestrictedAstra("getDatabase(dbId,keyspace,region)", options.getDestination()); - } - return new Database(new AstraApiEndpoint(databaseId, region, - options.getAstraEnvironment()).getApiEndPoint(), - this.token, keyspace, options); + public Database getDatabase(String apiEndpoint) { + return getDatabase(apiEndpoint, new DatabaseOptions(token, options)); } /** - * Retrieves a client for interacting with a specified database using its unique identifier. This client facilitates - * direct communication with the Data API, enabling a range of operations such as querying, inserting, updating, and - * deleting data within the database. This streamlined method provides access to the default keyspace or keyspace. + * Retrieves a database client configured to connect to the Data API using a database identifier. *

- * The {@code databaseId} serves as a unique identifier for the database within the Astra environment or an on-premise - * DataStax Enterprise setup, ensuring that all operations through the client are correctly routed to the intended - * database instance. + * Automatically resolves the API endpoint based on the database ID and uses default {@link DatabaseOptions}. *

* - *

Example usage:

- *
-     * {@code
-     * UUID databaseId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
-     * DataAPIClient apiClient = new DataAPIClient("yourAuthTokenHere");
-     * Database databaseClient = apiClient.getDatabase(databaseId);
-     * // Perform data operations using the databaseClient
-     * }
-     * 
- * - * @param databaseId The unique identifier of the database for which to retrieve the client. - * @return A {@link Database} client that provides the ability to perform operations on the specified database. + * @param databaseId The unique identifier of the database. + * @return A {@link Database} client configured with default options for the resolved endpoint. + * @see #getDatabase(UUID, DatabaseOptions) */ public Database getDatabase(UUID databaseId) { - return getDatabase(databaseId, DEFAULT_KEYSPACE); + return getDatabase(lookupEndpoint(databaseId, null), new DatabaseOptions(token, options)); } /** - * Creates and returns a database client configured to interact with the Data API at the specified API endpoint - * and within the given keyspace. This method facilitates operations such as querying, inserting, updating, and - * deleting data by directly communicating with the Data API, allowing for a wide range of data manipulation - * tasks in a specified keyspace. + * Retrieves a database client configured to connect to the Data API using a database identifier, + * with custom {@link DatabaseOptions}. *

- * The {@code apiEndpoint} parameter should be the URL of the API endpoint where the Data API is hosted. This - * is typically a fully qualified URL that points to the Astra service or an on-premise DataStax Enterprise - * deployment hosting the Data API. - *

- *

- * The {@code keyspace} parameter specifies the keyspace (or keyspace in Cassandra terms) within the database - * that the client will interact with. Keyspaces help organize data within the database and provide a way to - * isolate and manage access to data. + * Automatically resolves the API endpoint based on the database ID. *

* - *

Example usage:

- *
-     * {@code
-     * String apiEndpoint = "https://example-astra-data-api.com";
-     * String keyspace = "my_keyspace";
-     * DataAPIClient apiClient = new DataAPIClient("yourAuthTokenHere");
-     * Database databaseClient = apiClient.getDatabase(apiEndpoint, keyspace);
-     * // Now you can use the databaseClient to perform operations within "my_keyspace"
-     * }
-     * 
- * - * @param apiEndpoint The URL of the Data API endpoint. This specifies the location of the API the client will connect to. - * @param keyspace The keyspace within the database that the client will access and perform operations in. - * @return A {@link Database} client configured for the specified API endpoint and keyspace, enabling data manipulation - * and query operations within the targeted keyspace. + * @param databaseId The unique identifier of the database. + * @param dbOptions The options to configure the database client. + * @return A {@link Database} client configured for the resolved endpoint and custom options. + * @see #getDatabase(UUID) */ - public Database getDatabase(String apiEndpoint, String keyspace) { - return new Database(apiEndpoint, token, keyspace, options); + public Database getDatabase(UUID databaseId, DatabaseOptions dbOptions) { + return getDatabase(databaseId, null, dbOptions); } /** - * Retrieves a client for interacting with a specified database using its unique identifier. + * Retrieves a database client configured to connect to the Data API using a database identifier, + * a specified region, and custom {@link DatabaseOptions}. * - * @param apiEndpoint - * apiEndpoint for the database. - * @return - * an instance of Database admin + * @param databaseId The unique identifier of the database. + * @param region The region where the database is deployed (optional). + * @param dbOptions The options to configure the database client. + * @return A {@link Database} client configured for the specified database ID, region, and options. + * @see #getDatabase(UUID, DatabaseOptions) */ - public DatabaseAdmin getDatabaseAdmin(String apiEndpoint) { - return getDatabase(apiEndpoint).getDatabaseAdmin(); + public Database getDatabase(UUID databaseId, String region, DatabaseOptions dbOptions) { + return getDatabase(lookupEndpoint(databaseId, region), dbOptions); } + // -------------------------------------------------- + // --- Getters --- + // -------------------------------------------------- + /** - * Retrieves a client for interacting with a specified database using its unique identifier. + * Gets token * - * @param databaseId - * database identifier - * @return - * the database admin if exists + * @return value of token */ - public AstraDBDatabaseAdmin getDatabaseAdmin(UUID databaseId) { - return (AstraDBDatabaseAdmin) getDatabase(databaseId).getDatabaseAdmin(); + public String getToken() { + return token; } /** - * Retrieves a database client configured to interact with the Data API at the specified API endpoint. This method - * enables direct communication with the Data API, facilitating a range of data manipulation operations such as querying, - * inserting, updating, and deleting data. The client accesses the default keyspace or keyspace for operations, unless - * otherwise specified through additional configuration. - *

- * The {@code apiEndpoint} parameter should be the URL of the Data API endpoint you wish to connect to. This URL - * points to the location where the Data API is hosted, which could be an Astra cloud service or an on-premise DataStax - * Enterprise instance. - *

- *

- * Utilizing this method simplifies the process of connecting to the Data API by focusing on essential configuration, - * making it particularly useful for scenarios where detailed keyspace management is handled separately or not required. - *

+ * Gets options * - *

Example usage:

- *
-     * {@code
-     * String apiEndpoint = "https://-.apps.astra.datastax.com";
-     * DataAPIClient apiClient = new DataAPIClient("yourAuthTokenHere");
-     * Database databaseClient = apiClient.getDatabase(apiEndpoint);
-     * // The databaseClient is now ready to perform data operations at the specified API endpoint
-     * }
-     * 
- * - * @param apiEndpoint The URL of the Data API endpoint to connect to, specifying the API's location. - * @return A {@link Database} client tailored for interaction with the Data API at the provided API endpoint, - * ready for executing data manipulation tasks. + * @return value of options */ - public Database getDatabase(String apiEndpoint) { - return getDatabase(apiEndpoint, DEFAULT_KEYSPACE); + public DataAPIClientOptions getOptions() { + return options; } + + /** + * Compute the endpoint for the database based on its ID and region. + * @param databaseId + * database ID (mandotory) + * @param region + * region (optional), if not provided the default region of the database is used. + * @return + * the endpoint for the database + */ + private String lookupEndpoint(UUID databaseId, String region) { + Assert.notNull(databaseId, "databaseId"); + String dbRegion = region; + if (dbRegion == null) { + dbRegion = getAdmin().getDatabaseInfo(databaseId).getRegion(); + } + return new AstraApiEndpoint(databaseId, dbRegion, options.getAstraEnvironment()).getApiEndPoint(); + } + } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/DataAPIClients.java b/astra-db-java/src/main/java/com/datastax/astra/client/DataAPIClients.java index 5765f0db..95d3e5ad 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/DataAPIClients.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/DataAPIClients.java @@ -20,13 +20,11 @@ * #L% */ -import com.datastax.astra.client.admin.DataAPIDatabaseAdmin; import com.datastax.astra.client.core.auth.UsernamePasswordTokenProvider; import com.datastax.astra.client.core.options.DataAPIClientOptions; import com.datastax.astra.client.databases.Database; -import com.datastax.astra.internal.command.LoggingCommandObserver; -import static com.datastax.astra.client.admin.AstraDBAdmin.DEFAULT_KEYSPACE; +import static com.datastax.astra.client.core.options.DataAPIClientOptions.DEFAULT_KEYSPACE; /** * Provides utility methods for initializing and configuring clients to interact with the Data API. This class @@ -42,10 +40,10 @@ *
  * {@code
  * // Get you the client for a local deployment of Data API
- * DataAPIClient devClient = DataAPIClients.localClient();
+ * DataAPIClient devClient = DataAPIClients.local();
  *
  * // Get you the database for a local deployment of Data API
- * DataAPIClient devClient = DataAPIClients.localDatabase();
+ * DataAPIClient devClient = DataAPIClients.astraDev("token");
  *
  * // Default target environment Astra Production
  * DataAPIClient devClient = DataAPIClients.astra("token");
@@ -66,76 +64,122 @@ public class DataAPIClients {
     private DataAPIClients() {}
 
     /**
-     * Creates and configures a {@link DataAPIClient} for interaction with a local instance of Stargate, a
-     * data gateway for working with Apache Cassandra®. This method is specifically designed for scenarios
-     * where the application is intended to communicate with a Stargate instance running locally, facilitating
-     * development and testing workflows by providing easy access to local database resources.
+     * Creates and configures a {@link DataAPIClient} for interaction with a local instance of DataAPI,
+     * a data gateway that facilitates working with Apache Cassandra®. This method is tailored for
+     * development and testing workflows, enabling simplified and efficient access to local database
+     * resources without the need for extensive configuration.
      *
-     * @return A fully configured {@link DataAPIClient} ready for interacting with the local Stargate instance, equipped
-     *         with the necessary authentication token and targeting options for Cassandra. This client abstracts away
-     *         the complexities of direct database communication, providing a simplified interface for data operations.
+     * 

The returned {@link DataAPIClient} is preconfigured with: + *

+ * + * @return A fully configured {@link DataAPIClient} ready for interacting with the local DataAPI instance. + * This client provides a streamlined interface for executing data operations, abstracting away + * the complexity of direct database interactions. + * + *

Example usage:

+ *
+     * {@code
+     * DataAPIClient client = DataAPIClients.local();
+     * }
+     * 
*/ - public static DataAPIClient createForLocal() { + public static DataAPIClient clientCassandra() { + return clientCassandra( + UsernamePasswordTokenProvider.DEFAULT_USERNAME, + UsernamePasswordTokenProvider.DEFAULT_CREDENTIALS); + } + + public static DataAPIClient clientCassandra(String username, String password) { + return new DataAPIClient( + new UsernamePasswordTokenProvider(username, password).getToken(), + new DataAPIClientOptions() + .destination(DataAPIDestination.CASSANDRA) + .enableFeatureFlagTables() + .logRequests()); + } + + public static DataAPIClient clientHCD() { + return clientHCD( + UsernamePasswordTokenProvider.DEFAULT_USERNAME, + UsernamePasswordTokenProvider.DEFAULT_CREDENTIALS); + } + + public static DataAPIClient clientHCD(String username, String password) { return new DataAPIClient( - new UsernamePasswordTokenProvider().getToken(), - DataAPIClientOptions.builder() - .withDestination(DataAPIDestination.CASSANDRA) + new UsernamePasswordTokenProvider(username, password).getToken(), + new DataAPIClientOptions() + .destination(DataAPIDestination.HCD) .enableFeatureFlagTables() - .logRequests() - .withObserver(new LoggingCommandObserver(DataAPIClient.class)) - .build()); + .logRequests()); } /** * Creates and configures a {@link Database} client specifically designed for interaction with a local instance - * of Stargate. This method streamlines the process of setting up a client for local database interactions, - * encapsulating both the creation of a {@link DataAPIClient} and its integration within a {@link Database} - * abstraction. This setup is ideal for local development and testing, providing a straightforward path to - * interact with Cassandra through Stargate with minimal setup. + * of the Data API and Cassandra. This method simplifies the setup process by combining the creation of a {@link DataAPIClient} + * with the integration of a {@link Database} abstraction. It is tailored for local development and testing, + * enabling seamless interaction with Apache Cassandra® through Stargate with minimal configuration. * - * @return A {@link Database} client ready for use with a local Stargate instance, fully configured for immediate - * interaction with the database. This client enables developers to focus on their application logic rather - * than the intricacies of database connectivity and command execution. + *

Upon creation, this method ensures that a default keyspace is available in the local Stargate instance + * by automatically invoking {@link com.datastax.astra.client.admin.DatabaseAdmin#createKeyspace(String)}. This guarantees that developers + * have a ready-to-use environment for executing database operations during their development or testing workflows. + * + *

The returned {@link Database} client is preconfigured with: + *

+ * This setup allows developers to focus on application logic rather than database configuration or connectivity. + * + * @return A {@link Database} client configured for use with a local Stargate instance, including a default + * keyspace for immediate interaction. This client abstracts database connectivity and administrative tasks, + * streamlining development workflows. + * + *

Example usage:

+ *
+     * {@code
+     * Database db = localDbWithDefaultKeyspace();
+     * }
+     * 
*/ - public static Database defaultLocalDatabase() { - Database db = createForLocal().getDatabase(DEFAULT_ENDPOINT_LOCAL, DEFAULT_KEYSPACE); - DataAPIDatabaseAdmin dbAdmin = (DataAPIDatabaseAdmin) db.getDatabaseAdmin(); - dbAdmin.createKeyspace(DEFAULT_KEYSPACE); + public static Database localDbWithDefaultKeyspace() { + Database db = clientCassandra().getDatabase(DEFAULT_ENDPOINT_LOCAL); + db.getDatabaseAdmin().createKeyspace(DEFAULT_KEYSPACE); return db; } /** - * Creates a {@link DataAPIClient} configured for interaction with Astra, DataStax's cloud-native database - * as a service. This method streamlines the client setup by requiring only an authentication token, handling - * the other configuration details internally to ensure compatibility with Astra's API and endpoints. + * Creates a {@link DataAPIClient} configured for interacting with Astra in a development environment. This + * method simplifies the setup of a client specifically tailored for development purposes, where you might + * need different configurations or less stringent security measures compared to a production environment. + * The client is configured to target Astra's development environment, ensuring that operations do not + * affect production data. * - *

By specifying the destination as Astra in the {@link DataAPIClientOptions}, this method ensures that the - * client is properly configured to communicate with Astra's infrastructure, leveraging the provided token - * for authentication. This approach enables developers to quickly establish a connection to Astra for - * database operations without manually setting up connection parameters and authentication details.

- ** - * @param token The authentication token required for accessing Astra. This token should be treated - * securely and not exposed in public code repositories or unsecured locations. - * @return A {@link DataAPIClient} instance ready for use with Astra, fully configured with the provided - * authentication token and set to target Astra as its destination. + * @param token The authentication token required for accessing Astra's development environment. This token + * should have the necessary permissions for development activities and be protected accordingly. + * @return A {@link DataAPIClient} instance ready for development activities with Astra, configured with the + * provided authentication token and targeting Astra's development environment. * *

Example usage:

*
      * {@code
-     * DataAPIClient astraClient = DataAPIClients.astra("my_astra_auth_token");
-     * // Use astraClient for database operations
+     * DataAPIClient devClient = DataAPIClients.astraDev("your_astra_dev_token");
+     * // Utilize devClient for development database operations
      * }
      * 
*/ - public static DataAPIClient create(String token) { - return new DataAPIClient(token, DataAPIClientOptions - .builder() - .withDestination(DataAPIDestination.ASTRA) - .build()); + public static DataAPIClient astraDev(String token) { + return new DataAPIClient(token, new DataAPIClientOptions() + .destination(DataAPIDestination.ASTRA_DEV) + .logRequests()); } /** - * Creates a {@link DataAPIClient} configured for interacting with Astra in a development environment. This + * Creates a {@link DataAPIClient} configured for interacting with Astra in a production environment. This * method simplifies the setup of a client specifically tailored for development purposes, where you might * need different configurations or less stringent security measures compared to a production environment. * The client is configured to target Astra's development environment, ensuring that operations do not @@ -149,46 +193,52 @@ public static DataAPIClient create(String token) { *

Example usage:

*
      * {@code
-     * DataAPIClient devClient = DataAPIClients.astraDev("your_astra_dev_token");
+     * DataAPIClient devClient = DataAPIClients.astra("your_astra_dev_token");
      * // Utilize devClient for development database operations
      * }
      * 
*/ - public static DataAPIClient createForAstraDev(String token) { - return new DataAPIClient(token, DataAPIClientOptions - .builder() - .withDestination(DataAPIDestination.ASTRA_DEV) - .withObserver(new LoggingCommandObserver(DataAPIClient.class)) - .build()); + public static DataAPIClient astra(String token) { + return new DataAPIClient(token, new DataAPIClientOptions() + .destination(DataAPIDestination.ASTRA) + .logRequests()); } + + /** * Creates a {@link DataAPIClient} specifically configured for interacting with Astra in a test environment. - * This setup is ideal for testing scenarios, where isolation from development and production environments - * is critical to ensure the integrity and stability of test results. By directing the client to Astra's - * test environment, it facilitates safe, isolated testing of database interactions without risking the - * alteration of development or production data. + * This method is designed for testing scenarios, providing an isolated environment to safely execute + * database operations without impacting development or production data. + * + *

The returned {@link DataAPIClient} is preconfigured to: + *

+ * + * This setup ensures that all database interactions are restricted to Astra's test environment, + * preserving the integrity of other environments while facilitating thorough testing. * - * @param token The authentication token required for accessing Astra's test environment. Ensure that this - * token is designated for testing purposes to prevent unintended access to or effects on - * non-test data and resources. - * @return A {@link DataAPIClient} instance specifically for use in testing scenarios with Astra, equipped - * with the necessary authentication token and configured to target the test environment. + * @param token The authentication token required for accessing Astra's test environment. It is important + * to use a token that is explicitly designated for testing purposes to avoid unintended + * access to production or development resources. + * @return A {@link DataAPIClient} instance configured for testing with Astra, equipped with the provided + * authentication token and targeting the test environment. * *

Example usage:

*
      * {@code
      * DataAPIClient testClient = DataAPIClients.astraTest("your_astra_test_token");
-     * // Execute test database operations with testClient
+     * testClient.execute(query -> query.cql("SELECT * FROM test_table").execute());
      * }
      * 
*/ - public static DataAPIClient createForAstraTest(String token) { - return new DataAPIClient(token, DataAPIClientOptions - .builder() - .withDestination(DataAPIDestination.ASTRA_TEST) - .withObserver(new LoggingCommandObserver(DataAPIClient.class)) - .build()); + public static DataAPIClient astraTest(String token) { + return new DataAPIClient(token, new DataAPIClientOptions() + .destination(DataAPIDestination.ASTRA_TEST) + .logRequests()); } } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/admin/AdminOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/admin/AdminOptions.java new file mode 100644 index 00000000..69a50f99 --- /dev/null +++ b/astra-db-java/src/main/java/com/datastax/astra/client/admin/AdminOptions.java @@ -0,0 +1,46 @@ +package com.datastax.astra.client.admin; + +/*- + * #%L + * Data API Java Client + * -- + * Copyright (C) 2024 DataStax + * -- + * Licensed under the Apache License, Version 2.0 + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.datastax.astra.client.core.commands.BaseOptions; +import com.datastax.astra.client.core.commands.CommandType; +import com.datastax.astra.client.core.options.DataAPIClientOptions; +import com.datastax.astra.internal.serdes.DataAPISerializer; +import com.datastax.astra.internal.serdes.DatabaseSerializer; +import lombok.Setter; +import lombok.experimental.Accessors; + +@Setter +@Accessors(fluent = true, chain = true) +public class AdminOptions extends BaseOptions { + + /** Serializer for the Collections. */ + private static final DataAPISerializer DEFAULT_SERIALIZER = new DatabaseSerializer(); + + public AdminOptions() { + this(null, null); + } + + public AdminOptions(String token, DataAPIClientOptions options) { + super(token, CommandType.DATABASE_ADMIN, DEFAULT_SERIALIZER, options); + } + +} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/admin/AstraDBAdmin.java b/astra-db-java/src/main/java/com/datastax/astra/client/admin/AstraDBAdmin.java index feb9b625..411a0a47 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/admin/AstraDBAdmin.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/admin/AstraDBAdmin.java @@ -22,6 +22,7 @@ import com.datastax.astra.client.core.options.DataAPIClientOptions; import com.datastax.astra.client.databases.DatabaseInfo; +import com.datastax.astra.client.databases.DatabaseOptions; import com.datastax.astra.internal.api.AstraApiEndpoint; import com.datastax.astra.internal.command.LoggingCommandObserver; import com.datastax.astra.internal.utils.Assert; @@ -47,6 +48,7 @@ import java.util.UUID; import java.util.stream.Collectors; +import static com.datastax.astra.client.core.options.DataAPIClientOptions.DEFAULT_KEYSPACE; import static com.datastax.astra.client.exception.InvalidEnvironmentException.throwErrorRestrictedAstra; import static com.datastax.astra.internal.utils.AnsiUtils.green; import static com.dtsx.astra.sdk.utils.Utils.readEnvVariable; @@ -66,20 +68,11 @@ public class AstraDBAdmin { /** Default region. (free-tier) */ public static final String FREE_TIER_CLOUD_REGION = "us-east1"; - /** Header name used to hold the Astra Token. */ - public static final String TOKEN_HEADER_PARAM = "X-Token"; - - /** Default keyspace (same created by the ui). */ - public static final String DEFAULT_KEYSPACE = "default_keyspace"; - /** Client for Astra Devops Api. */ final AstraDBOpsClient devopsDbClient; /** Options to personalized http client other client options. */ - final DataAPIClientOptions dataAPIClientOptions; - - /** Astra Token (credentials). */ - final String token; + final AdminOptions adminOptions; /** Side Http Client (use only to resume a db). */ final HttpClient httpClient; @@ -100,31 +93,30 @@ public class AstraDBAdmin { /** * Initialization with an authentication token and target environment, Use this constructor for testing purpose. - * - * @param token - * authentication token + * @param options * options for client */ - public AstraDBAdmin(String token, DataAPIClientOptions options) { - Assert.hasLength(token, "token"); + public AstraDBAdmin(AdminOptions options) { Assert.notNull(options, "options"); - this.token = token; - this.dataAPIClientOptions = options; - if (options.getObservers() != null) { + this.adminOptions = options; + DataAPIClientOptions dataAPIClientOptions = options.getDataAPIClientOptions(); + if (dataAPIClientOptions.getObservers() != null) { Map devopsObservers = new HashMap<>(); - if (options.getObservers().containsKey(LoggingCommandObserver.class.getSimpleName())) { + if (dataAPIClientOptions.getObservers().containsKey(LoggingCommandObserver.class.getSimpleName())) { devopsObservers.put("logging", new LoggingRequestObserver(AstraDBAdmin.class)); } - this.devopsDbClient = new AstraDBOpsClient(token, options.getAstraEnvironment(), devopsObservers); + this.devopsDbClient = new AstraDBOpsClient(options.getToken(), + dataAPIClientOptions.getAstraEnvironment(), devopsObservers); } else { - this.devopsDbClient = new AstraDBOpsClient(token, options.getAstraEnvironment()); + this.devopsDbClient = new AstraDBOpsClient(options.getToken(), + dataAPIClientOptions.getAstraEnvironment()); } // Local Agent for Resume HttpClient.Builder httpClientBuilder = HttpClient.newBuilder(); - httpClientBuilder.version(options.getHttpClientOptions().getHttpVersion()); - httpClientBuilder.connectTimeout(Duration.ofMillis(options.getTimeoutOptions().connectTimeoutMillis())); + httpClientBuilder.version(dataAPIClientOptions.getHttpClientOptions().getHttpVersion()); + httpClientBuilder.connectTimeout(Duration.ofMillis(dataAPIClientOptions.getTimeoutOptions().getConnectTimeoutMillis())); this.httpClient = httpClientBuilder.build(); } @@ -327,25 +319,42 @@ public DatabaseInfo getDatabaseInfo(@NonNull UUID id) { * * @param databaseId * database identifier - * @param keyspace + * @param options * target keyspace name * @return * database client */ - public com.datastax.astra.client.databases.Database getDatabase(UUID databaseId, String keyspace) { + public com.datastax.astra.client.databases.Database getDatabase(UUID databaseId, DatabaseOptions dbOptions) { Assert.notNull(databaseId, "databaseId"); - Assert.hasLength(keyspace, "keyspace"); - if (!dataAPIClientOptions.isAstra()) { - throwErrorRestrictedAstra("getDatabase(id, keyspace)", dataAPIClientOptions.getDestination()); + if (!adminOptions.getDataAPIClientOptions().isAstra()) { + throwErrorRestrictedAstra("getDatabase(id, keyspace)", adminOptions.getDataAPIClientOptions().getDestination()); } String databaseRegion = devopsDbClient .findById(databaseId.toString()) .map(db -> db.getInfo().getRegion()) .orElseThrow(() -> new DatabaseNotFoundException(databaseId.toString())); - return new com.datastax.astra.client.databases.Database( - new AstraApiEndpoint(databaseId, databaseRegion, dataAPIClientOptions.getAstraEnvironment()).getApiEndPoint(), - token, keyspace, dataAPIClientOptions) { - }; + + AstraApiEndpoint astraApiEndpoint = new AstraApiEndpoint(databaseId, + databaseRegion, adminOptions.getDataAPIClientOptions().getAstraEnvironment()); + + return new com.datastax.astra.client.databases.Database(astraApiEndpoint.getApiEndPoint(), dbOptions); + } + + /** + * Access the database functions. + * + * @param databaseId + * database identifier + * @param keyspace + * target keyspace name + * @return + * database client + */ + public com.datastax.astra.client.databases.Database getDatabase(UUID databaseId, String keyspace) { + return getDatabase(databaseId, new DatabaseOptions( + this.adminOptions.getToken(), + this.adminOptions.getDataAPIClientOptions()) + .keyspace(keyspace)); } /** @@ -370,7 +379,9 @@ public com.datastax.astra.client.databases.Database getDatabase(UUID databaseId) */ public AstraDBDatabaseAdmin getDatabaseAdmin(UUID databaseId) { Assert.notNull(databaseId, "databaseId"); - return new AstraDBDatabaseAdmin(token, databaseId, dataAPIClientOptions); + return new AstraDBDatabaseAdmin( + adminOptions.getToken(), databaseId, + adminOptions.getDataAPIClientOptions()); } /** diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/admin/AstraDBDatabaseAdmin.java b/astra-db-java/src/main/java/com/datastax/astra/client/admin/AstraDBDatabaseAdmin.java index c73dc68e..8178db86 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/admin/AstraDBDatabaseAdmin.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/admin/AstraDBDatabaseAdmin.java @@ -21,10 +21,14 @@ */ import com.datastax.astra.client.DataAPIDestination; -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; +import com.datastax.astra.client.core.commands.CommandType; import com.datastax.astra.client.core.options.DataAPIClientOptions; import com.datastax.astra.client.core.results.FindEmbeddingProvidersResult; +import com.datastax.astra.client.databases.DatabaseOptions; import com.datastax.astra.internal.api.AstraApiEndpoint; +import com.datastax.astra.internal.command.AbstractCommandRunner; +import com.datastax.astra.internal.utils.Assert; import com.dtsx.astra.sdk.db.AstraDBOpsClient; import com.dtsx.astra.sdk.db.domain.Database; import com.dtsx.astra.sdk.db.exception.DatabaseNotFoundException; @@ -34,29 +38,21 @@ import java.util.Set; import java.util.UUID; -import static com.datastax.astra.client.admin.AstraDBAdmin.DEFAULT_KEYSPACE; - /** * Implementation of the DatabaseAdmin interface for Astra. To create the namespace the devops APi is leverage. To use this class a higher token permission is required. */ @Slf4j -public class AstraDBDatabaseAdmin implements DatabaseAdmin { - - /** Token used for the credentials. */ - final String token; +public class AstraDBDatabaseAdmin extends AbstractCommandRunner implements DatabaseAdmin { /** Token used for the credentials. */ final UUID databaseId; - /** Data Api Options. */ - final DataAPIClientOptions dataAPIClientOptions; - /** Client for Astra Devops Api. */ final AstraDBOpsClient devopsDbClient; /** Database if initialized from the DB. */ - protected com.datastax.astra.client.databases.Database db; + final com.datastax.astra.client.databases.Database db; /** * Initialize a database admin from token and database id. @@ -64,12 +60,24 @@ public class AstraDBDatabaseAdmin implements DatabaseAdmin { * @param db * target database */ - public AstraDBDatabaseAdmin(com.datastax.astra.client.databases.Database db) { - this.databaseId = UUID.fromString(db.getDbApiEndpoint().substring(8, 44)); - this.dataAPIClientOptions = db.getOptions(); - this.token = db.getToken(); - this.db = db; - this.devopsDbClient = new AstraDBOpsClient(token, dataAPIClientOptions.getAstraEnvironment()); + public AstraDBDatabaseAdmin(com.datastax.astra.client.databases.Database db, AdminOptions adminOptions) { + Assert.notNull(db, "database"); + this.db = db; + this.databaseId = db.getId(); // UUID.fromString(db.getApiEndpoint().substring(8, 44)); + this.options = adminOptions; + if (adminOptions == null) { + this.options = new AdminOptions(); + } + // Using database options if no extra parameters provided + if (this.options.getToken() == null) { + this.options.token(db.getOptions().getToken()); + } + if (this.options.getDataAPIClientOptions() == null) { + this.options.dataAPIClientOptions(db.getOptions().getDataAPIClientOptions()); + } + this.devopsDbClient = new AstraDBOpsClient( + options.getToken(), + options.getDataAPIClientOptions().getAstraEnvironment()); } /** @@ -79,15 +87,16 @@ public AstraDBDatabaseAdmin(com.datastax.astra.client.databases.Database db) { * token value * @param databaseId * database identifier - * @param options + * @param clientOptions * options used to initialize the http client */ - public AstraDBDatabaseAdmin(String token, UUID databaseId, DataAPIClientOptions options) { - this.token = token; + public AstraDBDatabaseAdmin(String token, UUID databaseId, DataAPIClientOptions clientOptions) { this.databaseId = databaseId; - this.dataAPIClientOptions = options; - this.devopsDbClient = new AstraDBOpsClient(token, options.getAstraEnvironment()); - this.db = new com.datastax.astra.client.databases.Database(getApiEndpoint(), token, DEFAULT_KEYSPACE, options); + this.options = new AdminOptions(token, clientOptions); + this.devopsDbClient = new AstraDBOpsClient(token, options.getDataAPIClientOptions().getAstraEnvironment()); + + this.db = new com.datastax.astra.client.databases.Database(getApiEndpoint(), + new DatabaseOptions(token, options.getDataAPIClientOptions())); } /** @@ -134,9 +143,9 @@ private static AstraEnvironment getEnvironment(DataAPIDestination destination) { * @return * the endpoint as an url. */ - private String getApiEndpoint() { + public String getApiEndpoint() { return new AstraApiEndpoint(databaseId, - getDatabaseInformations().getInfo().getRegion(), dataAPIClientOptions.getAstraEnvironment()) + getDatabaseInformations().getInfo().getRegion(), options.getDataAPIClientOptions().getAstraEnvironment()) .getApiEndPoint(); } @@ -162,7 +171,7 @@ public com.datastax.astra.client.databases.Database getDatabase(String keyspace) * client to interact with database DML. */ public com.datastax.astra.client.databases.Database getDatabase(String keyspace, String tokenUser) { - return new com.datastax.astra.client.databases.Database(getApiEndpoint(), tokenUser, keyspace, db.getOptions()); + return new com.datastax.astra.client.databases.Database(getApiEndpoint(), db.getOptions()); } @Override @@ -177,8 +186,7 @@ public Set listKeyspaceNames() { @Override public FindEmbeddingProvidersResult findEmbeddingProviders() { log.debug("findEmbeddingProviders"); - DataAPIDatabaseAdmin admin = - new DataAPIDatabaseAdmin(getApiEndpoint() + "/" + db.getOptions().getApiVersion(), token, db.getOptions()); + DataAPIDatabaseAdmin admin = new DataAPIDatabaseAdmin(db, this.options); return new FindEmbeddingProvidersResult(admin.findEmbeddingProviders().getEmbeddingProviders()); } @@ -199,9 +207,9 @@ public void dropKeyspace(String keyspace) { } @Override - public void dropKeyspace(String namespace, CommandOptions options) { + public void dropKeyspace(String keyspace, BaseOptions options) { log.warn("CommandOptions are not supported for dropKeyspace in Astra MODE"); - dropKeyspace(namespace); + dropKeyspace(keyspace); } /** {@inheritDoc} */ diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/admin/DataAPIDatabaseAdmin.java b/astra-db-java/src/main/java/com/datastax/astra/client/admin/DataAPIDatabaseAdmin.java index 822abbeb..c7b33c60 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/admin/DataAPIDatabaseAdmin.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/admin/DataAPIDatabaseAdmin.java @@ -20,17 +20,15 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandType; -import com.datastax.astra.client.core.options.DataAPIClientOptions; -import com.datastax.astra.client.databases.Database; +import com.datastax.astra.client.core.commands.BaseOptions; import com.datastax.astra.client.core.commands.Command; -import com.datastax.astra.client.core.commands.CommandOptions; -import com.datastax.astra.client.core.vectorize.EmbeddingProvider; +import com.datastax.astra.client.core.commands.CommandType; import com.datastax.astra.client.core.results.FindEmbeddingProvidersResult; +import com.datastax.astra.client.core.vectorize.EmbeddingProvider; +import com.datastax.astra.client.databases.Database; import com.datastax.astra.client.keyspaces.KeyspaceOptions; import com.datastax.astra.internal.api.DataAPIResponse; import com.datastax.astra.internal.command.AbstractCommandRunner; -import com.datastax.astra.internal.command.CommandObserver; import com.datastax.astra.internal.serdes.DataAPISerializer; import com.datastax.astra.internal.serdes.collections.DocumentSerializer; import com.datastax.astra.internal.utils.Assert; @@ -40,7 +38,6 @@ import java.util.Set; import java.util.stream.Collectors; -import static com.datastax.astra.client.admin.AstraDBAdmin.DEFAULT_KEYSPACE; import static com.datastax.astra.internal.utils.AnsiUtils.green; import static com.datastax.astra.internal.utils.Assert.hasLength; import static com.datastax.astra.internal.utils.Assert.notNull; @@ -50,7 +47,7 @@ */ @Slf4j @Getter -public class DataAPIDatabaseAdmin extends AbstractCommandRunner implements DatabaseAdmin { +public class DataAPIDatabaseAdmin extends AbstractCommandRunner implements DatabaseAdmin { /** parameters names. */ private static final String ARG_KEYSPACE = "keyspaceName"; @@ -64,28 +61,19 @@ public class DataAPIDatabaseAdmin extends AbstractCommandRunner implements Datab /** * Initialize a database admin from token and database id. * - * @param token - * token value - * @param apiEndpoint - * api endpoint. + * @param db + * current database used to initialized the admin * @param options * list of options for the admin */ - public DataAPIDatabaseAdmin(String apiEndpoint, String token, DataAPIClientOptions options) { - this(new Database(apiEndpoint, token, DEFAULT_KEYSPACE, options)); - } - - /** - * Initialize a database admin from token and database id. - * - * @param db - * current database instance - */ - public DataAPIDatabaseAdmin(Database db) { - this.db = db; - this.commandOptions = new CommandOptions<>(db.getOptions()); - this.commandOptions.token(db.getToken()); - this.commandOptions.commandType(CommandType.KEYSPACE_ADMIN); + public DataAPIDatabaseAdmin(Database db, AdminOptions options) { + super(db.getRootEndpoint(), options); + this.db = db; + this.options.commandType(CommandType.KEYSPACE_ADMIN); + String apiVersion = options.getDataAPIClientOptions().getApiVersion(); + if (!db.getRootEndpoint().endsWith(apiVersion)) { + this.apiEndpoint += "/" + apiVersion; + } } // ------------------------------------------ @@ -125,10 +113,9 @@ public Database getDatabase(String keyspace) { /** {@inheritDoc} */ @Override public Database getDatabase(String keyspace, String userToken) { - Assert.hasLength(keyspace, ARG_KEYSPACE); - Assert.hasLength(userToken, "userToken"); - db = new Database(db.getDbApiEndpoint(), userToken, keyspace, db.getOptions()); - return db; + return new Database(db.getRootEndpoint(), db.getOptions().clone() + .token(userToken) + .keyspace(keyspace)); } /** {@inheritDoc} */ @@ -161,7 +148,7 @@ public void createKeyspace(String keyspace, KeyspaceOptions options) { } @Override - public void dropKeyspace(String keyspace, CommandOptions options) { + public void dropKeyspace(String keyspace, BaseOptions options) { hasLength(keyspace, ARG_KEYSPACE); Command dropNamespace = Command .create("dropKeyspace") @@ -170,28 +157,4 @@ public void dropKeyspace(String keyspace, CommandOptions options) { log.info("Keyspace '" + green("{}") + "' has been deleted", keyspace); } - /** {@inheritDoc} */ - @Override - protected String getApiEndpoint() { - return db.getDbApiEndpoint() + "/v1"; - } - - /** {@inheritDoc} */ - @Override - protected DataAPISerializer getSerializer() { - return SERIALIZER; - } - - /** - * Register a listener to execute commands on the collection. Please now use {@link CommandOptions}. - * - * @param logger - * name for the logger - * @param commandObserver - * class for the logger - */ - public void registerListener(String logger, CommandObserver commandObserver) { - this.commandOptions.registerObserver(logger, commandObserver); - } - } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/admin/DatabaseAdmin.java b/astra-db-java/src/main/java/com/datastax/astra/client/admin/DatabaseAdmin.java index 78a358c5..b0affcc7 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/admin/DatabaseAdmin.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/admin/DatabaseAdmin.java @@ -20,11 +20,12 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import com.datastax.astra.client.databases.Database; import com.datastax.astra.client.core.commands.CommandRunner; import com.datastax.astra.client.core.vectorize.EmbeddingProvider; import com.datastax.astra.client.core.results.FindEmbeddingProvidersResult; +import com.datastax.astra.internal.utils.Assert; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -188,10 +189,10 @@ default CompletableFuture> listKeyspacesNamesAsync() { * // Assume 'client' is an instance of your data API client * String keyspace = "targetKeyspace"; * - * // Drop the namespace + * // Drop the keyspace * client.dropKeyspace(keyspace); * - * // The namespace 'targetKeyspace' is now deleted, along with all its contained data + * // The keyspace 'targetKeyspace' is now deleted, along with all its contained data * } *
* @@ -199,15 +200,16 @@ default CompletableFuture> listKeyspacesNamesAsync() { * keyspace does not exist, the method call will not interrupt the flow of the application, thereby allowing * for flexible and error-tolerant code design. * - * @param namespace The name of the keyspace to be dropped. This parameter specifies the target keyspace + * @param keyspace The name of the keyspace to be dropped. This parameter specifies the target keyspace * that should be deleted. The operation will proceed silently and without error even if the * keyspace does not exist, ensuring consistent behavior. */ - default void dropKeyspace(String namespace) { - dropKeyspace(namespace, null); + default void dropKeyspace(String keyspace) { + Assert.hasLength(keyspace, "keyspace"); + dropKeyspace(keyspace, null); } - void dropKeyspace(String namespace, CommandOptions options); + void dropKeyspace(String keyspace, BaseOptions options); /** * Asynchronously drops (deletes) the specified keyspace from the database. This operation is idempotent, meaning diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/collections/Collection.java b/astra-db-java/src/main/java/com/datastax/astra/client/collections/Collection.java index ea747440..5f4d38d8 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/collections/Collection.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/collections/Collection.java @@ -43,9 +43,8 @@ import com.datastax.astra.client.collections.results.CollectionInsertOneResult; import com.datastax.astra.client.collections.results.CollectionUpdateResult; import com.datastax.astra.client.collections.results.FindOneAndReplaceResult; +import com.datastax.astra.client.core.commands.BaseOptions; import com.datastax.astra.client.core.commands.Command; -import com.datastax.astra.client.core.commands.CommandOptions; -import com.datastax.astra.client.core.options.DataAPIClientOptions; import com.datastax.astra.client.core.paging.CollectionCursor; import com.datastax.astra.client.core.paging.CollectionDistinctIterable; import com.datastax.astra.client.core.paging.FindIterable; @@ -62,7 +61,6 @@ import com.datastax.astra.internal.api.DataAPIResponse; import com.datastax.astra.internal.api.DataAPIStatus; import com.datastax.astra.internal.command.AbstractCommandRunner; -import com.datastax.astra.internal.command.CommandObserver; import com.datastax.astra.internal.serdes.DataAPISerializer; import com.datastax.astra.internal.serdes.collections.DocumentSerializer; import com.datastax.astra.internal.utils.Assert; @@ -87,7 +85,8 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; -import static com.datastax.astra.client.core.commands.CommandType.DATA; +import static com.datastax.astra.client.core.options.DataAPIClientOptions.MAX_CHUNK_SIZE; +import static com.datastax.astra.client.core.options.DataAPIClientOptions.MAX_COUNT; import static com.datastax.astra.client.core.types.DataAPIKeywords.SORT_VECTOR; import static com.datastax.astra.client.exception.DataAPIException.ERROR_CODE_INTERRUPTED; import static com.datastax.astra.client.exception.DataAPIException.ERROR_CODE_TIMEOUT; @@ -130,56 +129,33 @@ * Java bean to unmarshall documents for collection. */ @Slf4j -public class Collection extends AbstractCommandRunner { +public class Collection extends AbstractCommandRunner { /** parameters names. */ protected static final String ARG_OPTIONS = "options"; /** parameters names. */ - protected static final String ARG_FILTER = "filter"; - /** parameters names. */ - protected static final String ARG_DATABASE = "database"; - /** parameters names. */ - protected static final String ARG_CLAZZ = "working class 'clazz'"; - /** parameters names. */ - protected static final String ARG_COLLECTION_NAME = "collectionName"; - /** parameters names. */ protected static final String ARG_UPDATE = "update"; /** parameters names. */ protected static final String DOCUMENT = "document"; - // Json Outputs - - /** Serializer for the Collections. */ - private static final DocumentSerializer SERIALIZER = new DocumentSerializer(); + /** Default collection serializer. */ + public static final DataAPISerializer DEFAULT_COLLECTION_SERIALIZER = new DocumentSerializer(); /** Collection identifier. */ @Getter private final String collectionName; - /** Working class representing documents of the collection. The default value is {@link Document}. */ - @Getter - protected final Class documentClass; - /** Parent Database reference. */ @Getter private final Database database; - /** Get global Settings for the client. */ @Getter - private final DataAPIClientOptions dataAPIClientOptions; - - /** Api Endpoint for the Database, if using an astra environment it will contain the database id and the database region. */ - private final String apiEndpoint; + private final Class documentClass; /** * Keep Collection options in -memory to avoid multiple calls to the API. */ - private CollectionOptions options; - - /** - * Check if options has been fetched - */ - private boolean optionChecked = false; + private CollectionDefinition collectionDefinition; /** * Constructs an instance of a collection within the specified database. This constructor @@ -194,11 +170,7 @@ public class Collection extends AbstractCommandRunner { * @param collectionName A {@code String} that uniquely identifies the collection within the * database. This name is used to route operations to the correct * collection and should adhere to the database's naming conventions. - * @param clazz The {@code Class} object that represents the model for documents within - * this collection. This class is used for serialization and deserialization of - * documents to and from the database. It ensures type safety and facilitates - * the mapping of database documents to Java objects. - * @param commandOptions the options to apply to the command operation. If left blank the default collection + * @param collectionOptions the options to apply to the command operation. If left blank the default collection * *

Example usage:

*
@@ -212,18 +184,15 @@ public class Collection extends AbstractCommandRunner {
      * }
      * 
*/ - public Collection(Database db, String collectionName, CommandOptions commandOptions, Class clazz) { - notNull(db, ARG_DATABASE); - notNull(clazz, ARG_CLAZZ); - hasLength(collectionName, ARG_COLLECTION_NAME); - this.collectionName = collectionName; + public Collection(Database db, String collectionName, CollectionOptions collectionOptions, Class documentClass) { + super(db.getApiEndpoint() + "/" + collectionName, collectionOptions); + hasLength(collectionName, "collection name"); + notNull(documentClass, "documentClass"); + notNull(collectionOptions, "collection options"); this.database = db; - this.dataAPIClientOptions = db.getOptions(); - this.documentClass = clazz; - this.commandOptions = commandOptions; - // Defaulting to data in case of a Collection - this.commandOptions.commandType(DATA); - this.apiEndpoint = db.getApiEndpoint() + "/" + collectionName; + this.collectionName = collectionName; + this.documentClass = documentClass; + this.options.serializer(new DocumentSerializer()); } // ---------------------------- @@ -251,7 +220,7 @@ public Collection(Database db, String collectionName, CommandOptions commandO * */ public String getKeyspaceName() { - return getDatabase().getKeyspaceName(); + return getDatabase().getKeyspace(); } /** @@ -284,57 +253,16 @@ public String getKeyspaceName() { * and identity within the database. */ public CollectionDefinition getDefinition() { - return database - .listCollections() - .filter(col -> col.getName().equals(collectionName)) - .findFirst() - .orElseThrow(() -> new DataAPIException("[COLLECTION_NOT_EXIST] - Collection does not exist, " + - "collection name: '" + collectionName + "'", "COLLECTION_NOT_EXIST", null)); - } - - /** - * Retrieves the configuration options for the collection, including vector and indexing settings. - * These options specify how the collection should be created and managed, potentially affecting - * performance, search capabilities, and data organization. - *

Example usage:

- *
-     * {@code
-     * // Given a collection
-     * DataApiCollection collection;
-     * // Access its Options
-     * CollectionOptions options = collection.getOptions();
-     * if (null != c.getVector()) {
-     *   System.out.println(c.getVector().getDimension());
-     *   System.out.println(c.getVector().getMetric());
-     * }
-     * if (null != c.getIndexing()) {
-     *   System.out.println(c.getIndexing().getAllow());
-     *   System.out.println(c.getIndexing().getDeny());
-     * }
-     * }
-     * 
- * - * @return An instance of {@link CollectionOptions} containing the collection's configuration settings, - * such as vector and indexing options. Returns {@code null} if no options are set or applicable. - */ - public CollectionOptions getOptions() { - if (!optionChecked) { - options = Optional.ofNullable(getDefinition().getOptions()).orElse(new CollectionOptions()); - optionChecked = true; + if (collectionDefinition == null) { + collectionDefinition = database + .listCollections().stream() + .filter(col -> col.getName().equals(collectionName)) + .findFirst() + .map(CollectionDescriptor::getOptions) + .orElseThrow(() -> new DataAPIException("[COLLECTION_NOT_EXIST] - Collection does not exist, " + + "collection name: '" + collectionName + "'", "COLLECTION_NOT_EXIST", null)); } - return options; - } - - /** - * Retrieves the name of the collection. This name serves as a unique identifier within the database and is - * used to reference the collection in database operations such as queries, updates, and deletions. The collection - * name is defined at the time of collection creation and is immutable. - * - * @return A {@code String} representing the name of the collection. This is the same name that was specified - * when the collection was created or initialized. - */ - public String getName() { - return collectionName; + return collectionDefinition; } // -------------------------- @@ -454,7 +382,11 @@ public final CollectionInsertOneResult insertOne(T document) { */ public final CollectionInsertOneResult insertOne(T document, CollectionInsertOneOptions collectionInsertOneOptions) { Assert.notNull(document, DOCUMENT); - return internalInsertOne(SERIALIZER.convertValue(document, Document.class), collectionInsertOneOptions); + DataAPISerializer serializer = getSerializer(); + if (collectionInsertOneOptions != null && collectionInsertOneOptions.getSerializer() != null) { + serializer = collectionInsertOneOptions.getSerializer(); + } + return internalInsertOne(serializer.convertValue(document, Document.class), collectionInsertOneOptions); } /** @@ -545,16 +477,14 @@ private Object unmarshallDocumentId(Object id) { if (mapId.containsKey(DataAPIKeywords.UUID.getKeyword())) { // defaultId with UUID UUID uid = UUID.fromString((String) mapId.get(DataAPIKeywords.UUID.getKeyword())); - if (getOptions() != null && getOptions().getDefaultId() != null) { - CollectionIdTypes defaultIdType = CollectionIdTypes.fromValue(getOptions().getDefaultId().getType()); - switch(defaultIdType) { - case UUIDV6: - return new UUIDv6(uid); - case UUIDV7: - return new UUIDv7(uid); - default: - return uid; - } + + if (getDefinition().getDefaultId() != null) { + CollectionDefaultIdTypes defaultIdType = getDefinition().getDefaultId().getType(); + return switch (defaultIdType) { + case UUIDV6 -> new UUIDv6(uid); + case UUIDV7 -> new UUIDv7(uid); + default -> uid; + }; } throw new IllegalStateException("Returned is is a UUID, but no defaultId is set in the collection definition."); } @@ -628,16 +558,16 @@ private Object unmarshallDocumentId(Object id) { public CollectionInsertManyResult insertMany(List documents, CollectionInsertManyOptions options) { Assert.isTrue(documents != null && !documents.isEmpty(), "documents list cannot be null or empty"); Assert.notNull(options, "insertMany options cannot be null"); - if (options.concurrency() > 1 && options.ordered()) { + if (options.getConcurrency() > 1 && options.isOrdered()) { throw new IllegalArgumentException("Cannot run ordered insert_many concurrently."); } - if (options.chunkSize() > dataAPIClientOptions.getMaxRecordsInInsert()) { - throw new IllegalArgumentException("Cannot insert more than " + dataAPIClientOptions.getMaxRecordsInInsert() + " at a time."); + if (options.getChunkSize() > MAX_CHUNK_SIZE) { + throw new IllegalArgumentException("Cannot insert more than " + MAX_CHUNK_SIZE + " at a time."); } long start = System.currentTimeMillis(); - ExecutorService executor = Executors.newFixedThreadPool(options.concurrency()); + ExecutorService executor = Executors.newFixedThreadPool(options.getConcurrency()); List> futures = new ArrayList<>(); - for (int i = 0; i < documents.size(); i += options.chunkSize()) { + for (int i = 0; i < documents.size(); i += options.getChunkSize()) { futures.add(executor.submit(getInsertManyResultCallable(documents, options, i))); } executor.shutdown(); @@ -651,11 +581,9 @@ public CollectionInsertManyResult insertMany(List documents, Collec finalResult.getDocumentResponses().addAll(res.getDocumentResponses()); } // Set a default timeouts for the overall operation - long totalTimeout = this.commandOptions - .getTimeoutOptions() - .getDataOperationTimeoutMillis(); - if (options.getTimeoutOptions() != null) { - totalTimeout = options.getTimeoutOptions().dataOperationTimeoutMillis(); + long totalTimeout = this.options.getTimeout(); + if (options.getDataAPIClientOptions() != null) { + totalTimeout = options.getTimeout(); } if (executor.awaitTermination(totalTimeout, TimeUnit.MILLISECONDS)) { log.debug(magenta(".[total insertMany.responseTime]") + "=" + yellow("{}") + " millis.", @@ -840,15 +768,15 @@ public CompletableFuture insertManyAsync(List getInsertManyResultCallable(List documents, CollectionInsertManyOptions collectionInsertManyOptions, int start) { - int end = Math.min(start + collectionInsertManyOptions.chunkSize(), documents.size()); + int end = Math.min(start + collectionInsertManyOptions.getChunkSize(), documents.size()); return () -> { log.debug("Insert block (" + cyan("size={}") + ") in collection {}", end - start, green(getCollectionName())); Command insertMany = new Command("insertMany") .withDocuments(documents.subList(start, end)) .withOptions(new Document() - .append(INPUT_ORDERED, collectionInsertManyOptions.ordered()) - .append(INPUT_RETURN_DOCUMENT_RESPONSES, collectionInsertManyOptions.returnDocumentResponses())); + .append(INPUT_ORDERED, collectionInsertManyOptions.isOrdered()) + .append(INPUT_RETURN_DOCUMENT_RESPONSES, collectionInsertManyOptions.isReturnDocumentResponses())); DataAPIStatus status = runCommand(insertMany, collectionInsertManyOptions).getStatus(); CollectionInsertManyResult result = new CollectionInsertManyResult(); @@ -1308,8 +1236,8 @@ public long estimatedDocumentCount(EstimatedCountDocumentsOptions options) { public int countDocuments(Filter filter, int upperBound, CountDocumentsOptions options) throws TooManyDocumentsToCountException { // Argument Validation - if (upperBound<1 || upperBound> dataAPIClientOptions.getMaxCount()) { - throw new IllegalArgumentException("UpperBound limit should be in between 1 and " + dataAPIClientOptions.getMaxCount()); + if (upperBound < 1 || upperBound > MAX_COUNT) { + throw new IllegalArgumentException("UpperBound limit should be in between 1 and " + MAX_COUNT); } // Build command Command command = new Command("countDocuments").withFilter(filter); @@ -1354,7 +1282,6 @@ public int countDocuments(Filter filter, int upperBound) * the query filter to apply the delete operation * @return * the result of the remove one operation - * */ public CollectionDeleteResult deleteOne(Filter filter) { return deleteOne(filter, new CollectionDeleteOneOptions()); @@ -1446,7 +1373,7 @@ public CollectionDeleteResult deleteAll() { * @return {@code true} if the collection exists within the namespace, {@code false} otherwise. */ public boolean exists() { - return getDatabase().collectionExists(getName()); + return getDatabase().collectionExists(getCollectionName()); } /** @@ -1558,7 +1485,7 @@ public CollectionUpdateResult replaceOne(Filter filter, T replacement, Collectio result.setMatchedCount(res.getMatchedCount()); result.setModifiedCount(res.getModifiedCount()); if (res.getDocument() != null) { - Document doc = SERIALIZER.convertValue(res.getDocument(), Document.class); + Document doc = getSerializer().convertValue(res.getDocument(), Document.class); if (doc.getId(Object.class) != null) { result.setUpsertedId(doc.getId(Object.class)); } @@ -1574,7 +1501,7 @@ public CollectionUpdateResult replaceOne(Filter filter, T replacement, Collectio * @return * command result */ - private FindOneAndReplaceResult executeFindOneAndReplace(Command cmd, CommandOptions options) { + private FindOneAndReplaceResult executeFindOneAndReplace(Command cmd, BaseOptions options) { // Run Command DataAPIResponse apiResponse = runCommand(cmd, options); // Parse Command Result @@ -1831,38 +1758,5 @@ public Optional findOneAndDelete(Filter filter, CollectionFindOneAndDeleteOpt return Optional.empty(); } - /** - * Register a listener to execute commands on the collection. Please now use {@link CommandOptions}. - * - * @param logger - * name for the logger - * @param commandObserver - * class for the logger - */ - public void registerListener(String logger, CommandObserver commandObserver) { - this.commandOptions.registerObserver(logger, commandObserver); - } - - /** - * Register a listener to execute commands on the collection. Please now use {@link CommandOptions}. - * - * @param name - * name for the observer - */ - public void deleteListener(String name) { - this.commandOptions.unregisterObserver(name); - } - - /** {@inheritDoc} */ - @Override - protected DataAPISerializer getSerializer() { - return SERIALIZER; - } - - /** {@inheritDoc} */ - @Override - protected String getApiEndpoint() { - return apiEndpoint; - } } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/collections/CollectionIdTypes.java b/astra-db-java/src/main/java/com/datastax/astra/client/collections/CollectionDefaultIdTypes.java similarity index 89% rename from astra-db-java/src/main/java/com/datastax/astra/client/collections/CollectionIdTypes.java rename to astra-db-java/src/main/java/com/datastax/astra/client/collections/CollectionDefaultIdTypes.java index f04361a8..d880c074 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/collections/CollectionIdTypes.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/collections/CollectionDefaultIdTypes.java @@ -26,7 +26,7 @@ * List of possible types for the collection 'defaultId'. */ @Getter -public enum CollectionIdTypes { +public enum CollectionDefaultIdTypes { /** * Represent a BSON ObjectId. @@ -56,7 +56,7 @@ public enum CollectionIdTypes { * @param value * value to the types */ - CollectionIdTypes(String value) { + CollectionDefaultIdTypes(String value) { this.value = value; } @@ -67,8 +67,8 @@ public enum CollectionIdTypes { * @return The corresponding CollectionIdTypes enum constant. * @throws IllegalArgumentException if the value does not correspond to any CollectionIdTypes. */ - public static CollectionIdTypes fromValue(String value) { - for (CollectionIdTypes type : values()) { + public static CollectionDefaultIdTypes fromValue(String value) { + for (CollectionDefaultIdTypes type : values()) { if (type.getValue().equals(value)) { return type; } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/collections/CollectionDefinition.java b/astra-db-java/src/main/java/com/datastax/astra/client/collections/CollectionDefinition.java index 8140278c..af425063 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/collections/CollectionDefinition.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/collections/CollectionDefinition.java @@ -9,9 +9,9 @@ * Licensed under the Apache License, Version 2.0 * You may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,36 +20,271 @@ * #L% */ -import com.datastax.astra.internal.serdes.collections.DocumentSerializer; +import com.datastax.astra.client.core.vector.SimilarityMetric; +import com.datastax.astra.client.core.vector.VectorOptions; +import com.datastax.astra.client.core.vectorize.VectorServiceOptions; +import lombok.Data; import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.NonNull; import lombok.Setter; +import lombok.experimental.Accessors; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; /** - * Represents the Collection definition with its name and metadata. + * Set of options to define and initialize a collection. */ -@Getter @Setter +@Setter +@NoArgsConstructor +@Accessors(fluent = true, chain = true) public class CollectionDefinition { /** - * Name of the collection. + * The 'defaultId' to allow working with different types of identifiers. + */ + private DefaultIdOptions defaultId; + + /** + * Vector options. + */ + private VectorOptions vector; + + /** + * Indexing options + */ + private IndexingOptions indexing; + + /** + * Subclass representing the indexing options. + */ + @Setter + public static class DefaultIdOptions { + + /** + * Type for the default id. + */ + private CollectionDefaultIdTypes type; + + /** + * Default constructor. + */ + public DefaultIdOptions() { + // marshalled by jackson + } + + /** + * Default constructor. + * + * @param type type for the default id + */ + public DefaultIdOptions(CollectionDefaultIdTypes type) { + this.type = type; + } + + /** + * Gets type + * + * @return value of type + */ + public CollectionDefaultIdTypes getType() { + return type; + } + } + + /** + * Subclass representing the indexing options. + */ + @Setter + @NoArgsConstructor + public static class IndexingOptions { + + /** + * If not empty will index everything but those properties. + */ + List deny; + + /** + * If not empty will index just those properties. + */ + List allow; + + /** + * Gets deny + * + * @return value of deny + */ + public List getDeny() { + return deny; + } + + /** + * Gets allow + * + * @return value of allow + */ + public List getAllow() { + return allow; + } + } + + /*** Access the vector options. + * + * @return + * vector options */ - private String name; + public VectorOptions getVector() { + return vector; + } /** - * Options for the collection. + * Access the indexing options. + * + * @return indexing options */ - private CollectionOptions options; + public IndexingOptions getIndexing() { + return indexing; + } /** - * Default constructor. + * Gets defaultId + * + * @return value of defaultId */ - public CollectionDefinition() { - // left blank, serialization with jackson + public DefaultIdOptions getDefaultId() { + return defaultId; } - /** {@inheritDoc} */ - @Override - public String toString() { - return new DocumentSerializer().marshall(this); + /** + * Builder pattern. + * + * @param type + * default id type + * @return self reference + */ + public CollectionDefinition defaultId(CollectionDefaultIdTypes type) { + this.defaultId = new DefaultIdOptions(type); + return this; } + + /** + * Builder pattern. + * + * @param size size + * @return self reference + */ + public CollectionDefinition vectorDimension(int size) { + getVector().dimension(size); + return this; + } + + /** + * Builder pattern. + * + * @param metric similarity metric + * @return self reference + */ + public CollectionDefinition vectorSimilarity(@NonNull SimilarityMetric metric) { + getVector().metric(metric.getValue()); + return this; + } + + /** + * Builder pattern. + * + * @param properties size + * @return self reference + */ + public CollectionDefinition indexingDeny(@NonNull String... properties) { + if (getIndexing() == null) { + indexing = new IndexingOptions(); + } + if (getIndexing().getAllow() != null) { + throw new IllegalStateException("'indexing.deny' and 'indexing.allow' are mutually exclusive"); + } + getIndexing().deny(Arrays.asList(properties)); + return this; + } + + /** + * Builder pattern. + * + * @param properties size + * @return self reference + */ + public CollectionDefinition indexingAllow(String... properties) { + if (getIndexing() == null) { + indexing = new IndexingOptions(); + } + if (getIndexing().getDeny() != null) { + throw new IllegalStateException("'indexing.deny' and 'indexing.allow' are mutually exclusive"); + } + getIndexing().allow(Arrays.asList(properties)); + return this; + } + + /** + * Builder pattern. + * + * @param dimension dimension + * @param function function + * @return self reference + */ + public CollectionDefinition vector(int dimension, @NonNull SimilarityMetric function) { + if (getVector() == null) { + vector = new VectorOptions(); + } + return vectorSimilarity(function).vectorDimension(dimension); + } + + /** + * Enable Vectorization within the collection. + * + * @param provider provider Name (LLM) + * @param modeName mode name + * @return self reference + */ + public CollectionDefinition vectorize(String provider, String modeName) { + return vectorize(provider, modeName, null); + } + + /** + * Enable Vectorization within the collection. + * + * @param provider provider Name (LLM) + * @param modeName mode name + * @param sharedSecretKey name of the key in the system + * @return self reference + */ + public CollectionDefinition vectorize(String provider, String modeName, String sharedSecretKey) { + VectorServiceOptions embeddingService = new VectorServiceOptions(); + embeddingService.provider(provider).modelName(modeName); + if (sharedSecretKey != null) { + // --> Since 1.3.1 the suffix is not needed anymore + //embeddingService.setAuthentication(Map.of("providerKey", keyName + ".providerKey")); + embeddingService.authentication(Map.of("providerKey", sharedSecretKey)); + // <--- Since 1.3.1 the suffix is not needed anymore + } + getVector().service(embeddingService); + return this; + } + + /** + * Enable Vectorization within the collection. + * + * @param provider provider Name (LLM) + * @param modeName mode name + * @param parameters expected parameters for vectorize + * @param sharedSecretKey name of the key in the system + * @return self reference + */ + public CollectionDefinition vectorize(String provider, String modeName, String sharedSecretKey, Map parameters) { + vectorize(provider, modeName, sharedSecretKey); + getVector().getService().parameters(parameters); + return this; + } + + } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/collections/CollectionDescriptor.java b/astra-db-java/src/main/java/com/datastax/astra/client/collections/CollectionDescriptor.java new file mode 100644 index 00000000..717d109c --- /dev/null +++ b/astra-db-java/src/main/java/com/datastax/astra/client/collections/CollectionDescriptor.java @@ -0,0 +1,55 @@ +package com.datastax.astra.client.collections; + +/*- + * #%L + * Data API Java Client + * -- + * Copyright (C) 2024 DataStax + * -- + * Licensed under the Apache License, Version 2.0 + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.datastax.astra.internal.serdes.collections.DocumentSerializer; +import lombok.Getter; +import lombok.Setter; + +/** + * Represents the Collection definition with its name and metadata. + */ +@Getter @Setter +public class CollectionDescriptor { + + /** + * Name of the collection. + */ + private String name; + + /** + * Options for the collection. + */ + private CollectionDefinition options; + + /** + * Default constructor. + */ + public CollectionDescriptor() { + // left blank, serialization with jackson + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return new DocumentSerializer().marshall(this); + } +} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/collections/CollectionOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/collections/CollectionOptions.java index bea0c67f..93f7d544 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/collections/CollectionOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/collections/CollectionOptions.java @@ -20,309 +20,44 @@ * #L% */ -import com.datastax.astra.client.core.vector.SimilarityMetric; -import com.datastax.astra.client.core.vector.VectorOptions; -import com.datastax.astra.client.core.vectorize.VectorServiceOptions; -import lombok.Getter; -import lombok.NonNull; +import com.datastax.astra.client.core.commands.BaseOptions; +import com.datastax.astra.client.core.commands.CommandType; +import com.datastax.astra.client.core.options.DataAPIClientOptions; +import com.datastax.astra.internal.serdes.collections.DocumentSerializer; +import com.datastax.astra.internal.utils.Assert; +import lombok.NoArgsConstructor; import lombok.Setter; +import lombok.experimental.Accessors; -import java.util.Arrays; -import java.util.List; -import java.util.Map; +import static com.datastax.astra.client.collections.Collection.DEFAULT_COLLECTION_SERIALIZER; +import static com.datastax.astra.client.core.commands.CommandType.COLLECTION_ADMIN; /** - * Set of options to define and initialize a collection. + * The options to use for the data API client. */ -@Getter @Setter -public class CollectionOptions { +@Accessors(fluent = true, chain = true) +public class CollectionOptions extends BaseOptions { /** - * The 'defaultId' to allow working with different types of identifiers. - */ - private DefaultIdOptions defaultId; - - /** - * Vector options. - */ - private VectorOptions vector; - - /** - * Indexing options - */ - private IndexingOptions indexing; - - /** - * Default constructor. + * Default constructor nor overriding token nor options + * but stilling setting the default timeouts and serializer + * for collection. */ public CollectionOptions() { - // left blank on purpose, built with builder - } - - /** - * Subclass representing the indexing options. - */ - @Getter @Setter - public static class DefaultIdOptions { - - /** Type for the default id. */ - private String type; - - /** - * Default constructor. - */ - public DefaultIdOptions() { - // marshalled by jackson - } - - /** - * Default constructor. - * - * @param type - * type for the default id - */ - public DefaultIdOptions(String type) { - this.type = type; - } - } - - /** - * Subclass representing the indexing options. - */ - @Getter @Setter - public static class IndexingOptions { - - /** - * If not empty will index everything but those properties. - */ - private List deny; - - /** - * If not empty will index just those properties. - */ - private List allow; - - /** - * Default constructor. - */ - public IndexingOptions() { - // left blank, serialization with jackson - } + this(null, null); } - /** - * Gets a builder. + * Constructor with options and not token override. * - * @return a builder - */ - public static CollectionOptionsBuilder builder() { - return new CollectionOptionsBuilder(); - } - - /** - * Builder for {@link CollectionDefinition}. + * @param token + * the token to use for the database + * @param options + * data API client options */ - public static class CollectionOptionsBuilder { - - /** - * Options for Vector - */ - VectorOptions vector; - - /** - * Options for Indexing - */ - IndexingOptions indexing; - - /** - * Options for Default Id - */ - String defaultId; - - /** - * Access the vector options. - * - * @return - * vector options - */ - private VectorOptions getVector() { - if (vector == null) { - vector = new VectorOptions(); - } - return vector; - } - - /** - * Access the indexing options. - * - * @return - * indexing options - */ - private IndexingOptions getIndexing() { - if (indexing == null) { - indexing = new IndexingOptions(); - } - return indexing; - } - - /** - * Default constructor. - */ - public CollectionOptionsBuilder() { - // left blank, builder pattern - } - - /** - * Builder Pattern with the Identifiers. - * - * @param idType - * type of ids - * @return - * self reference - */ - public CollectionOptionsBuilder defaultIdType(CollectionIdTypes idType) { - this.defaultId = idType.getValue(); - return this; - } - - /** - * Builder pattern. - * - * @param size size - * @return self reference - */ - public CollectionOptionsBuilder vectorDimension(int size) { - getVector().setDimension(size); - return this; - } - - /** - * Builder pattern. - * - * @param function function - * @return self reference - */ - public CollectionOptionsBuilder vectorSimilarity(@NonNull SimilarityMetric function) { - getVector().setMetric(function.getValue()); - return this; - } - - /** - * Builder pattern. - * - * @param properties size - * @return self reference - */ - public CollectionOptionsBuilder indexingDeny(@NonNull String... properties) { - if (getIndexing().getAllow() != null) { - throw new IllegalStateException("'indexing.deny' and 'indexing.allow' are mutually exclusive"); - } - getIndexing().setDeny(Arrays.asList(properties)); - return this; - } - - /** - * Builder pattern. - * - * @param properties size - * @return self reference - */ - public CollectionOptionsBuilder indexingAllow(String... properties) { - if (getIndexing().getDeny() != null) { - throw new IllegalStateException("'indexing.deny' and 'indexing.allow' are mutually exclusive"); - } - getIndexing().setAllow(Arrays.asList(properties)); - return this; - } - - /** - * Builder pattern. - * - * @param dimension dimension - * @param function function - * @return self reference - */ - public CollectionOptionsBuilder vector(int dimension, @NonNull SimilarityMetric function) { - vectorSimilarity(function); - vectorDimension(dimension); - return this; - } - - /** - * Enable Vectorization within the collection. - * - * @param provider - * provider Name (LLM) - * @param modeName - * mode name - * @return - * self reference - */ - public CollectionOptionsBuilder vectorize(String provider, String modeName) { - return vectorize(provider, modeName, null); - } - - /** - * Enable Vectorization within the collection. - * - * @param provider - * provider Name (LLM) - * @param modeName - * mode name - * @param sharedSecretKey - * name of the key in the system - * @return - * self reference - */ - public CollectionOptionsBuilder vectorize(String provider, String modeName, String sharedSecretKey) { - VectorServiceOptions embeddingService = new VectorServiceOptions(); - embeddingService.provider(provider).modelName(modeName); - if (sharedSecretKey != null) { - // --> Since 1.3.1 the suffix is not needed anymore - //embeddingService.setAuthentication(Map.of("providerKey", keyName + ".providerKey")); - embeddingService.authentication(Map.of("providerKey", sharedSecretKey)); - // <--- Since 1.3.1 the suffix is not needed anymore - } - getVector().setService(embeddingService); - return this; - } - - /** - * Enable Vectorization within the collection. - * - * @param provider - * provider Name (LLM) - * @param modeName - * mode name - * @param parameters - * expected parameters for vectorize - * @param sharedSecretKey - * name of the key in the system - * @return - * self reference - */ - public CollectionOptionsBuilder vectorize(String provider, String modeName, String sharedSecretKey, Map parameters) { - vectorize(provider, modeName, sharedSecretKey); - getVector().getService().parameters(parameters); - return this; - } - - /** - * Build the output. - * - * @return collection definition - */ - public CollectionOptions build() { - CollectionOptions req = new CollectionOptions(); - req.vector = this.vector; - req.indexing = this.indexing; - if (defaultId != null) { - req.defaultId = new DefaultIdOptions(defaultId); - } - return req; - } + public CollectionOptions(String token, DataAPIClientOptions options) { + super(token, COLLECTION_ADMIN, DEFAULT_COLLECTION_SERIALIZER, options); } } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/collections/exceptions/TooManyDocumentsToCountException.java b/astra-db-java/src/main/java/com/datastax/astra/client/collections/exceptions/TooManyDocumentsToCountException.java index b65a04a8..6e3a3cda 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/collections/exceptions/TooManyDocumentsToCountException.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/collections/exceptions/TooManyDocumentsToCountException.java @@ -33,7 +33,7 @@ public class TooManyDocumentsToCountException extends DataAPIException { * Default constructor. */ public TooManyDocumentsToCountException() { - super(ClientErrorCodes.HTTP, "Document count exceeds '" + DataAPIClientOptions.DEFAULT_MAX_COUNT + ", the maximum allowed by the server"); + super(ClientErrorCodes.HTTP, "Document count exceeds '" + DataAPIClientOptions.MAX_COUNT + ", the maximum allowed by the server"); } /** diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionDeleteManyOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionDeleteManyOptions.java index 24666c2b..fe53d8bf 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionDeleteManyOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionDeleteManyOptions.java @@ -20,7 +20,7 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -32,4 +32,4 @@ @Getter @Setter @NoArgsConstructor @Accessors(fluent = true, chain = true) -public class CollectionDeleteManyOptions extends CommandOptions {} +public class CollectionDeleteManyOptions extends BaseOptions {} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionDeleteOneOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionDeleteOneOptions.java index 35aaf745..d687444d 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionDeleteOneOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionDeleteOneOptions.java @@ -20,7 +20,7 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import com.datastax.astra.client.core.query.Sort; import lombok.NoArgsConstructor; @@ -28,7 +28,7 @@ * Options to delete One document. */ @NoArgsConstructor -public class CollectionDeleteOneOptions extends CommandOptions { +public class CollectionDeleteOneOptions extends BaseOptions { /** Sort List. */ Sort[] sort; diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionFindOneAndDeleteOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionFindOneAndDeleteOptions.java index fd50cd41..46d40b19 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionFindOneAndDeleteOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionFindOneAndDeleteOptions.java @@ -20,19 +20,16 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import com.datastax.astra.client.core.query.Projection; import com.datastax.astra.client.core.query.Sort; -import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.experimental.Accessors; /** * Options to find one and delete. */ @NoArgsConstructor -public class CollectionFindOneAndDeleteOptions extends CommandOptions { +public class CollectionFindOneAndDeleteOptions extends BaseOptions { /** Order by. */ Sort[] sort; diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionFindOneAndReplaceOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionFindOneAndReplaceOptions.java index 4928b119..2043cf0f 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionFindOneAndReplaceOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionFindOneAndReplaceOptions.java @@ -21,7 +21,7 @@ */ import com.datastax.astra.client.collections.documents.ReturnDocument; -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import com.datastax.astra.client.core.query.Projection; import com.datastax.astra.client.core.query.Sort; import lombok.Getter; @@ -35,7 +35,7 @@ @Getter @Setter @NoArgsConstructor @Accessors(fluent = true, chain = true) -public class CollectionFindOneAndReplaceOptions extends CommandOptions { +public class CollectionFindOneAndReplaceOptions extends BaseOptions { /** * Option to order the result. diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionFindOneAndUpdateOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionFindOneAndUpdateOptions.java index 4170b25d..7c135bc1 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionFindOneAndUpdateOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionFindOneAndUpdateOptions.java @@ -21,7 +21,7 @@ */ import com.datastax.astra.client.collections.documents.ReturnDocument; -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import com.datastax.astra.client.core.query.Projection; import com.datastax.astra.client.core.query.Sort; import lombok.Getter; @@ -36,7 +36,7 @@ @Getter @Setter @NoArgsConstructor @Accessors(fluent = true, chain = true) -public class CollectionFindOneAndUpdateOptions extends CommandOptions { +public class CollectionFindOneAndUpdateOptions extends BaseOptions { /** * Order by. diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionFindOneOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionFindOneOptions.java index 74567ca4..b16acc2c 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionFindOneOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionFindOneOptions.java @@ -20,7 +20,7 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import com.datastax.astra.client.core.query.Projection; import com.datastax.astra.client.core.query.Sort; import lombok.Getter; @@ -34,7 +34,7 @@ @Getter @Setter @NoArgsConstructor @Accessors(fluent = true, chain = true) -public class CollectionFindOneOptions extends CommandOptions { +public class CollectionFindOneOptions extends BaseOptions { /** * Order by. diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionFindOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionFindOptions.java index e127f5a4..b0ce7a14 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionFindOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionFindOptions.java @@ -20,7 +20,7 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import com.datastax.astra.client.core.query.Projection; import com.datastax.astra.client.core.query.Sort; import lombok.Getter; @@ -34,7 +34,7 @@ @Getter @Setter @NoArgsConstructor @Accessors(fluent = true, chain = true) -public class CollectionFindOptions extends CommandOptions { +public class CollectionFindOptions extends BaseOptions { /** * Order by. diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionInsertManyOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionInsertManyOptions.java index 57e31d94..f7ad0041 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionInsertManyOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionInsertManyOptions.java @@ -21,8 +21,8 @@ */ import com.datastax.astra.client.core.options.DataAPIClientOptions; -import com.datastax.astra.client.core.commands.CommandOptions; -import lombok.Getter; +import com.datastax.astra.client.core.commands.BaseOptions; +import com.datastax.astra.client.core.options.TimeoutOptions; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; @@ -30,10 +30,10 @@ /** * Options for InsertMany */ -@Getter @Setter +@Setter @NoArgsConstructor @Accessors(fluent = true, chain = true) -public class CollectionInsertManyOptions extends CommandOptions { +public class CollectionInsertManyOptions extends BaseOptions { /** * If the flag is set to true the command is failing on first error @@ -58,6 +58,61 @@ public class CollectionInsertManyOptions extends CommandOptions { +public class CollectionInsertOneOptions extends BaseOptions { } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionReplaceOneOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionReplaceOneOptions.java index ee9a6aa5..6f3da7c8 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionReplaceOneOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionReplaceOneOptions.java @@ -20,7 +20,7 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -32,7 +32,7 @@ @Getter @Setter @NoArgsConstructor @Accessors(fluent = true, chain = true) -public class CollectionReplaceOneOptions extends CommandOptions { +public class CollectionReplaceOneOptions extends BaseOptions { /** If upsert is selected. */ Boolean upsert; diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionUpdateManyOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionUpdateManyOptions.java index fc90f1b1..28ce030d 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionUpdateManyOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CollectionUpdateManyOptions.java @@ -20,7 +20,7 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -32,7 +32,7 @@ @Getter @Setter @NoArgsConstructor @Accessors(fluent = true, chain = true) -public class CollectionUpdateManyOptions extends CommandOptions { +public class CollectionUpdateManyOptions extends BaseOptions { /** * if upsert is selected diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CountDocumentsOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CountDocumentsOptions.java index 16eebdfc..5df03e2b 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CountDocumentsOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/CountDocumentsOptions.java @@ -20,7 +20,7 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -32,4 +32,4 @@ @Getter @Setter @NoArgsConstructor @Accessors(fluent = true, chain = true) -public class CountDocumentsOptions extends CommandOptions {} +public class CountDocumentsOptions extends BaseOptions {} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/EstimatedCountDocumentsOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/EstimatedCountDocumentsOptions.java index 3d001895..a6a3953d 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/EstimatedCountDocumentsOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/EstimatedCountDocumentsOptions.java @@ -20,7 +20,7 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -32,4 +32,4 @@ @Getter @Setter @NoArgsConstructor @Accessors(fluent = true, chain = true) -public class EstimatedCountDocumentsOptions extends CommandOptions {} +public class EstimatedCountDocumentsOptions extends BaseOptions {} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/UpdateOneOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/UpdateOneOptions.java index 5ee47f48..db8b0718 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/UpdateOneOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/collections/options/UpdateOneOptions.java @@ -20,7 +20,7 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import com.datastax.astra.client.core.query.Sort; import lombok.Getter; import lombok.NoArgsConstructor; @@ -34,7 +34,7 @@ @Getter @Setter @NoArgsConstructor @Accessors(fluent = true, chain = true) -public class UpdateOneOptions extends CommandOptions { +public class UpdateOneOptions extends BaseOptions { /** * if upsert is selected diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/core/commands/BaseOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/core/commands/BaseOptions.java new file mode 100644 index 00000000..4edb9823 --- /dev/null +++ b/astra-db-java/src/main/java/com/datastax/astra/client/core/commands/BaseOptions.java @@ -0,0 +1,455 @@ +package com.datastax.astra.client.core.commands; + +/*- + * #%L + * Data API Java Client + * -- + * Copyright (C) 2024 DataStax + * -- + * Licensed under the Apache License, Version 2.0 + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.datastax.astra.client.core.auth.EmbeddingHeadersProvider; +import com.datastax.astra.client.core.http.HttpClientOptions; +import com.datastax.astra.client.core.options.DataAPIClientOptions; +import com.datastax.astra.client.core.options.TimeoutOptions; +import com.datastax.astra.internal.command.CommandObserver; +import com.datastax.astra.internal.serdes.DataAPISerializer; +import com.datastax.astra.internal.utils.Assert; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.NoArgsConstructor; + +import java.time.Duration; +import java.util.Map; + +/** + * Options that will be provided to all commands for this collection. + * + * @param + * the sub-class implementing the command options + */ +@NoArgsConstructor +public class BaseOptions> implements Cloneable { + + /** + * Token used + */ + protected DataAPIClientOptions dataAPIClientOptions; + + /** + * Serializer for the command. + */ + protected DataAPISerializer serializer; + + /** + * Token to use for authentication. + */ + protected String token; + + /** + * The command type will drive the timeout in used. + */ + protected CommandType commandType = CommandType.GENERAL_METHOD; + + // -------------------------------------------- + // ----- Setters (Fluent) ----- + // -------------------------------------------- + + /** + * Provide the token. + * + * @param token + * authentication token + * @return + * service key + */ + @SuppressWarnings("unchecked") + public T token(String token) { + this.token = token; + return (T) this; + } + + /** + * Provide the token. + * + * @param serializer + * serializer + * @return + * service key + */ + @SuppressWarnings("unchecked") + public T serializer(DataAPISerializer serializer) { + this.serializer = serializer; + return (T) this; + } + + /** + * Provide the command type. The nature of the command will determine the timeout. + * + * @param commandType + * command Type + * @return + * service key + */ + public T commandType(CommandType commandType) { + this.commandType = commandType; + return (T) this; + } + + /** + * Provide a fluent setter for the data API Client. + * + * @param options + * command Type + * @return + * service key + */ + public T dataAPIClientOptions(DataAPIClientOptions options) { + this.dataAPIClientOptions = options; + return (T) this; + } + + /** + * Provide the command type. The nature of the command will determine the timeout. + * + * @param timeoutMillis + * timeout for the request + * @return + * service key + */ + public T timeout(long timeoutMillis) { + return timeout(timeoutMillis, getCommandType()); + } + + /** + * Provide the command type. The nature of the command will determine the timeout. + * + * @param duration + * timeout for the request + * @return + * service key + */ + public T timeout(Duration duration) { + Assert.notNull(duration, "duration"); + return timeout(duration.toMillis(), getCommandType()); + } + + // -------------------------------------------- + // ----- Setters ----- + // -------------------------------------------- + + /** + * Provide the embedding service API key. + * + * @param embeddingAuthProvider + * authentication provider + * @return + * service key + */ + @SuppressWarnings("unchecked") + public T embeddingAuthProvider(EmbeddingHeadersProvider embeddingAuthProvider) { + Assert.notNull(embeddingAuthProvider, "embeddingAuthProvider"); + getDataAPIClientOptions().embeddingAuthProvider(embeddingAuthProvider); + return (T) this; + } + + /** + * Provide the token. + * + * @param params + * additional headers + * @return + * service key + */ + @SuppressWarnings("unchecked") + public T databaseAdditionalHeaders(Map params) { + if (params!=null && !params.isEmpty()) { + getDataAPIClientOptions().databaseAdditionalHeaders(params); + } + return (T) this; + } + + /** + * Provide the token. + * + * @param params + * additional headers + * @return + * service key + */ + @SuppressWarnings("unchecked") + public T adminAdditionalHeaders(Map params) { + if (params!=null && !params.isEmpty()) { + getDataAPIClientOptions().adminAdditionalHeaders(params); + } + return (T) this; + } + + /** + * Provide the token. + * + * @param options + * options to initialize the http client + * @return + * service key + */ + @SuppressWarnings("unchecked") + public T httpClientOptions(HttpClientOptions options) { + getDataAPIClientOptions().httpClientOptions(options); + return (T) this; + } + + /** + * Provide the embedding service API key. + * + * @param timeoutOptions + * options of timeouts + * @return + * service key + */ + @SuppressWarnings("unchecked") + public T timeoutOptions(TimeoutOptions timeoutOptions) { + Assert.notNull(timeoutOptions, "timeoutOptions"); + getDataAPIClientOptions().timeoutOptions(timeoutOptions); + return (T) this; + } + + /** + * Allow to register a listener for the command. + * @param name + * name of the observer + * @param observer + * observer to register + * @return + * instance of the command options + */ + @SuppressWarnings("unchecked") + public T registerObserver(String name, CommandObserver observer) { + Assert.hasLength(name, "name"); + Assert.notNull(observer, "observer"); + getDataAPIClientOptions().addObserver(name, observer); + return (T) this; + } + + /** + * Remove a listener from the command. + * + * @param name + * name of the observer + * @return + * instance of the command options + */ + @SuppressWarnings("unchecked") + public T unregisterObserver(String name) { + getDataAPIClientOptions().getObservers().remove(name); + return (T) this; + } + + /** + * Request timeout based on the command type. + * + * @param timeoutOptions + * options of timeouts + * @param type + * command type + * @return + * timeout + */ + public long getTimeout(TimeoutOptions timeoutOptions, CommandType type) { + return switch (type) { + case DATABASE_ADMIN -> timeoutOptions.getDatabaseAdminTimeoutMillis(); + case KEYSPACE_ADMIN -> timeoutOptions.getKeyspaceAdminTimeoutMillis(); + case TABLE_ADMIN -> timeoutOptions.getTableAdminTimeoutMillis(); + case COLLECTION_ADMIN -> timeoutOptions.getCollectionAdminTimeoutMillis(); + default -> timeoutOptions.getGeneralMethodTimeoutMillis(); + }; + } + + /** + * Request timeout based on the command type. + * + * @param timeoutOptions + * options of timeouts + * @param type + * command type + * @return + * timeout + */ + public long getRequestTimeout(TimeoutOptions timeoutOptions, CommandType type) { + return switch (type) { + case DATABASE_ADMIN -> timeoutOptions.getDatabaseAdminTimeoutMillis(); + case KEYSPACE_ADMIN -> timeoutOptions.getKeyspaceAdminTimeoutMillis(); + case TABLE_ADMIN -> timeoutOptions.getTableAdminTimeoutMillis(); + case COLLECTION_ADMIN -> timeoutOptions.getCollectionAdminTimeoutMillis(); + default -> timeoutOptions.getRequestTimeoutMillis(); + }; + } + + /** + * Provide the command type. The nature of the command will determine the timeout. + * + * @param timeoutMillis + * timeout for the request + * @return + * service key + */ + public T timeout(long timeoutMillis, CommandType commandType) { + if (getDataAPIClientOptions().getTimeoutOptions() == null) { + getDataAPIClientOptions().timeoutOptions(new TimeoutOptions()); + } + TimeoutOptions timeoutOptions = getDataAPIClientOptions().getTimeoutOptions(); + switch (commandType) { + case DATABASE_ADMIN -> timeoutOptions.databaseAdminTimeoutMillis(timeoutMillis); + case KEYSPACE_ADMIN -> timeoutOptions.keyspaceAdminTimeoutMillis(timeoutMillis); + case TABLE_ADMIN -> timeoutOptions.tableAdminTimeoutMillis(timeoutMillis); + case COLLECTION_ADMIN -> timeoutOptions.collectionAdminTimeoutMillis(timeoutMillis); + default -> timeoutOptions.generalMethodTimeoutMillis(timeoutMillis); + }; + return (T) this; + } + + // -------------------------------------------- + // ----- Getters ----- + // -------------------------------------------- + + /** + * Gets dataAPIClientOptions + * + * @return value of dataAPIClientOptions + */ + @JsonIgnore + public DataAPIClientOptions getDataAPIClientOptions() { + if (this.dataAPIClientOptions == null) { + this.dataAPIClientOptions = new DataAPIClientOptions(); + } + return dataAPIClientOptions; + } + + /** + * Return the HTTP Request Timeout based on the command type + * + * @return value of token + */ + @JsonIgnore + public long getTimeout() { + if (getDataAPIClientOptions() != null && getDataAPIClientOptions().getTimeoutOptions() != null) { + return getTimeout(getDataAPIClientOptions().getTimeoutOptions(), getCommandType()); + } + return -1; + } + + /** + * Return the HTTP Request Timeout based on the command type + * + * @return value of token + */ + @JsonIgnore + public long getRequestTimeout() { + if (getDataAPIClientOptions() != null && getDataAPIClientOptions().getTimeoutOptions() != null) { + return getRequestTimeout(getDataAPIClientOptions().getTimeoutOptions(), getCommandType()); + } + return -1; + } + + /** + * Gets token + * + * @return value of token + */ + @JsonIgnore + public String getToken() { + return token; + } + + /** + * Gets commandType + * + * @return value of commandType + */ + @JsonIgnore + public CommandType getCommandType() { + return commandType; + } + + /** + * Gets serializer + * + * @return value of serializer + */ + @JsonIgnore + public DataAPISerializer getSerializer() { + return serializer; + } + + // -------------------------------------------- + // ----- Java Core ----- + // -------------------------------------------- + + /** + * Return the HTTP Request Timeout based on the command type. + * + * @param token + * authentication token + * @param type + * command type + * @param options + * data api options + */ + public BaseOptions(String token, CommandType type, DataAPIClientOptions options) { + this.token = token; + this.commandType = type; + if (options != null) { + this.dataAPIClientOptions = options.clone(); + } + } + + /** + * Return the HTTP Request Timeout based on the command type. + * + * @param token + * authentication token + * @param type + * command type + * @param options + * data api options + */ + public BaseOptions(String token, CommandType type, DataAPISerializer serializer, DataAPIClientOptions options) { + this.token = token; + this.commandType = type; + this.serializer = serializer; + if (options != null) { + this.dataAPIClientOptions = options.clone(); + } + } + + @Override + public String toString() { + return getSerializer().marshall(this); + } + + @Override + @SuppressWarnings("unchecked") + public T clone() { + try { + BaseOptions cloned = (BaseOptions) super.clone(); + cloned.token = token; + cloned.commandType = commandType; + cloned.dataAPIClientOptions = dataAPIClientOptions.clone(); + return (T) cloned; + } catch (CloneNotSupportedException e) { + throw new AssertionError("Cloning not supported", e); + } + } + +} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/core/commands/CommandOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/core/commands/CommandOptions.java deleted file mode 100644 index fae268d2..00000000 --- a/astra-db-java/src/main/java/com/datastax/astra/client/core/commands/CommandOptions.java +++ /dev/null @@ -1,343 +0,0 @@ -package com.datastax.astra.client.core.commands; - -/*- - * #%L - * Data API Java Client - * -- - * Copyright (C) 2024 DataStax - * -- - * Licensed under the Apache License, Version 2.0 - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import com.datastax.astra.client.core.auth.EmbeddingHeadersProvider; -import com.datastax.astra.client.core.http.HttpClientOptions; -import com.datastax.astra.client.core.options.DataAPIClientOptions; -import com.datastax.astra.client.core.options.TimeoutOptions; -import com.datastax.astra.internal.command.CommandObserver; -import lombok.Getter; - -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; - -import static com.datastax.astra.client.core.options.DataAPIClientOptions.HEADER_FEATURE_FLAG_TABLES; - -/** - * Options that will be provided to all commands for this collection. - * - * @param - * the sub-class implementing the command options - */ -@Getter -public class CommandOptions> implements Cloneable { - - /** - * Default command type is DATA - */ - protected CommandType commandType = CommandType.DATA; - - /** - * List of observers to notify. - */ - protected Map observers = new LinkedHashMap<>(); - - /** - * Token to use for authentication. - */ - protected String token; - - /** - * Will be used to create a client - */ - protected HttpClientOptions httpClientOptions; - - /** - * Will be used to create a client - */ - protected TimeoutOptions timeoutOptions; - - /** - * Embedding auth provider - */ - protected EmbeddingHeadersProvider embeddingAuthProvider; - - /** Add headers to db calls. */ - protected Map databaseAdditionalHeaders = new HashMap<>(); - - /** Add headers to admin calls. */ - protected Map adminAdditionalHeaders = new HashMap<>(); - - /** - * Provide the token. - * - * @param token - * authentication token - * @return - * service key - */ - @SuppressWarnings("unchecked") - public T token(String token) { - this.token = token; - return (T) this; - } - - /** - * Provide the token. - * - * @param params - * additional headers - * @return - * service key - */ - @SuppressWarnings("unchecked") - public T databaseAdditionalHeaders(Map params) { - this.adminAdditionalHeaders = params; - return (T) this; - } - - /** - * Provide the token. - * - * @param params - * additional headers - * @return - * service key - */ - @SuppressWarnings("unchecked") - public T adminAdditionalHeaders(Map params) { - this.adminAdditionalHeaders = params; - return (T) this; - } - - /** - * Provide the token. - * - * @param options - * options to initialize the http client - * @return - * service key - */ - @SuppressWarnings("unchecked") - public T httpClientOptions(HttpClientOptions options) { - this.httpClientOptions = options; - return (T) this; - } - - /** - * Provide the embedding service API key. - * - * @param embeddingAuthProvider - * authentication provider - * @return - * service key - */ - @SuppressWarnings("unchecked") - public T embeddingAuthProvider(EmbeddingHeadersProvider embeddingAuthProvider) { - this.embeddingAuthProvider = embeddingAuthProvider; - return (T) this; - } - - /** - * Provide the command type if default is not DATA. - * - * @param commandType - * command Type - * @return - * service key - */ - public T commandType(CommandType commandType) { - this.commandType = commandType; - return (T) this; - } - - /** - * Provide the embedding service API key. - * - * @param timeoutOptions - * options of timeouts - * @return - * service key - */ - @SuppressWarnings("unchecked") - public T timeoutOptions(TimeoutOptions timeoutOptions) { - this.timeoutOptions = timeoutOptions; - return (T) this; - } - - /** - * Allow to register a listener for the command. - * @param name - * name of the observer - * @param observer - * observer to register - * @return - * instance of the command options - */ - @SuppressWarnings("unchecked") - public T registerObserver(String name, CommandObserver observer) { - if (observer != null) { - observers.put(name, observer); - } - return (T) this; - } - - /** - * Register an observer with its className. - * - * @param observer - * command observer - * @return - * instance of the command options - */ - public T registerObserver(CommandObserver observer) { - return registerObserver(observer.getClass().getSimpleName(), observer); - } - - /** - * Remove a listener from the command. - * - * @param name - * name of the observer - * @return - * instance of the command options - */ - @SuppressWarnings("unchecked") - public T unregisterObserver(String name) { - observers.remove(name); - return (T) this; - } - - /** - * Remove an observer by its class. - * - * @param observer - * observer to remove - * @return - * instance of the command options - */ - public T unregisterObserver(Class observer) { - return unregisterObserver(observer.getSimpleName()); - } - - /** - * Return the HTTP Request Timeout based on the command type - * - * @return value of token - */ - public long selectRequestTimeout() { - return selectRequestTimeout(commandType, timeoutOptions); - } - - /** - * Return the HTTP Request Timeout based on the command type - * - * @return value of token - */ - public static long selectRequestTimeout(CommandType type, TimeoutOptions timeoutOptions) { - return switch (type) { - case DATABASE_ADMIN -> timeoutOptions.databaseAdminTimeoutMillis(); - case KEYSPACE_ADMIN -> timeoutOptions.keyspaceAdminTimeoutMillis(); - case SCHEMA -> timeoutOptions.schemaOperationTimeoutMillis(); - default -> timeoutOptions.requestTimeoutMillis(); - }; - } - - /** - * Add a header to the db calls. - * - * @param key - * key - * @param value - * value - * @return - * self reference - */ - @SuppressWarnings("unchecked") - public T addDatabaseAdditionalHeader(String key, String value) { - databaseAdditionalHeaders.put(key, value); - return (T) this; - } - - public T enableFeatureFlagTables() { - return addDatabaseAdditionalHeader(HEADER_FEATURE_FLAG_TABLES, "true"); - } - - public T disableFeatureFlagTables() { - return addDatabaseAdditionalHeader(HEADER_FEATURE_FLAG_TABLES, null); - } - - /** - * Add a header to the admin calls. - * - * @param key - * key - * @param value - * value - * @return - * self reference - */ - @SuppressWarnings("unchecked") - public T addAdminAdditionalHeader(String key, String value) { - adminAdditionalHeaders.put(key, value); - return (T) this; - } - - /** - * Default Constructor. - */ - public CommandOptions() { - // left blank, jackson serialization - } - - /** - * Default Constructor. - */ - public CommandOptions(DataAPIClientOptions options) { - if (options != null) { - this.embeddingAuthProvider = options.getEmbeddingAuthProvider(); - this.adminAdditionalHeaders = options.getAdminAdditionalHeaders(); - this.databaseAdditionalHeaders = options.getDatabaseAdditionalHeaders(); - this.httpClientOptions = options.getHttpClientOptions(); - this.timeoutOptions = options.getTimeoutOptions(); - for(Map.Entry entry : options.getObservers().entrySet()) { - // Avoid observers Duplication - if (!getObservers().containsKey(entry.getKey())) { - registerObserver(entry.getKey(), entry.getValue()); - } - } - } - } - - @SuppressWarnings("unchecked") - public T clone() { - try { - CommandOptions cloned = (CommandOptions) super.clone(); - - // Deep copy of mutable fields - cloned.observers = new LinkedHashMap<>(this.observers); - cloned.databaseAdditionalHeaders = new HashMap<>(this.databaseAdditionalHeaders); - cloned.adminAdditionalHeaders = new HashMap<>(this.adminAdditionalHeaders); - - // Copying other objects (assuming they have proper copy constructors or are immutable) - cloned.httpClientOptions = this.httpClientOptions != null ? this.httpClientOptions.clone() : null; - cloned.timeoutOptions = this.timeoutOptions != null ? this.timeoutOptions.clone() : null; - cloned.embeddingAuthProvider = this.embeddingAuthProvider != null ? this.embeddingAuthProvider.copy() : null; - - return (T) cloned; - } catch (CloneNotSupportedException e) { - throw new AssertionError("Cloning not supported", e); - } - } - -} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/core/commands/CommandRunner.java b/astra-db-java/src/main/java/com/datastax/astra/client/core/commands/CommandRunner.java index f94bb816..b421c417 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/core/commands/CommandRunner.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/core/commands/CommandRunner.java @@ -43,7 +43,7 @@ public interface CommandRunner { * result as a document map */ default DataAPIResponse runCommand(Command command) { - return runCommand(command, (CommandOptions) null); + return runCommand(command, (BaseOptions) null); } /** @@ -58,7 +58,7 @@ default DataAPIResponse runCommand(Command command) { * @return * result as a document map */ - DataAPIResponse runCommand(Command command, CommandOptions options) + DataAPIResponse runCommand(Command command, BaseOptions options) throws DataAPIResponseException; /** @@ -93,6 +93,6 @@ default T runCommand(Command command, Class documentClass) { * @param * document type to use */ - T runCommand(Command command, CommandOptions options, Class documentClass) + T runCommand(Command command, BaseOptions options, Class documentClass) throws DataAPIResponseException; } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/core/commands/CommandType.java b/astra-db-java/src/main/java/com/datastax/astra/client/core/commands/CommandType.java index cdf46212..cfbc8854 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/core/commands/CommandType.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/core/commands/CommandType.java @@ -29,12 +29,17 @@ public enum CommandType { /** * Data operation (find*, insert*, update*, delete*) */ - DATA, + GENERAL_METHOD, /** - * Schema operation (create*, drop*) + * Table schema operation (create*, drop*) */ - SCHEMA, + TABLE_ADMIN, + + /** + * Collection Schema operation (create*, drop*) + */ + COLLECTION_ADMIN, /** * Database admin operation (create, delete, list) diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/core/http/Caller.java b/astra-db-java/src/main/java/com/datastax/astra/client/core/http/Caller.java index dc064f97..0969f241 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/core/http/Caller.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/core/http/Caller.java @@ -23,11 +23,16 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; /** * Caller information. */ -@Data @NoArgsConstructor @AllArgsConstructor +@Setter +@Accessors(fluent = true, chain = true) +@NoArgsConstructor +@AllArgsConstructor public class Caller { /** caller name. */ @@ -35,4 +40,22 @@ public class Caller { /** caller version. */ String version; + + /** + * Gets name + * + * @return value of name + */ + public String getName() { + return name; + } + + /** + * Gets version + * + * @return value of version + */ + public String getVersion() { + return version; + } } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/core/http/HttpClientOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/core/http/HttpClientOptions.java index 139afa63..38a5dcde 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/core/http/HttpClientOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/core/http/HttpClientOptions.java @@ -22,7 +22,8 @@ import com.datastax.astra.client.core.options.DataAPIClientOptions; import com.datastax.astra.internal.utils.Assert; -import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; import java.net.http.HttpClient; import java.time.Duration; @@ -32,7 +33,8 @@ /** * Options to set up http Client. */ -@Getter +@Setter +@Accessors(fluent = true, chain = true) public class HttpClientOptions implements Cloneable { // -------------------------------------------- @@ -59,6 +61,15 @@ public void addCaller(Caller caller) { callers.add(caller); } + /** + * Gets callers + * + * @return value of callers + */ + public List getCallers() { + return callers; + } + // -------------------------------------------- // ----------------- RETRIES ----------------- // -------------------------------------------- @@ -79,18 +90,28 @@ public void addCaller(Caller caller) { */ Duration retryDelay = Duration.ofMillis(DEFAULT_RETRY_DELAY_MILLIS); + public HttpClientOptions httpRetries(int i, Duration duration) { + this.retryCount = i; + this.retryDelay = duration; + return this; + } + /** - * Add a caller. + * Gets retryCount * - * @param count - * retry count - * @param delay - * retry delay + * @return value of retryCount */ - public HttpClientOptions withRetries(int count, Duration delay) { - this.retryCount = count; - this.retryDelay = delay; - return this; + public int getRetryCount() { + return retryCount; + } + + /** + * Gets retryDelay + * + * @return value of retryDelay + */ + public Duration getRetryDelay() { + return retryDelay; } // -------------------------------------------- @@ -110,48 +131,33 @@ public HttpClientOptions withRetries(int count, Duration delay) { /** * The http client could work through a proxy. */ - HttpProxy proxy; - + HttpProxy httpProxy; /** - * Provide a custom redirect policy. + * Gets httpVersion * - * @param redirect - * redirect policy - * @return - * self reference + * @return value of httpVersion */ - public HttpClientOptions withHttpRedirect(HttpClient.Redirect redirect) { - Assert.notNull(redirect, "redirect"); - this.httpRedirect = redirect; - return this; + public HttpClient.Version getHttpVersion() { + return httpVersion; } /** - * Provide a custom http version. + * Gets httpRedirect * - * @param version - * http version - * @return - * self reference + * @return value of httpRedirect */ - public HttpClientOptions withHttpVersion(HttpClient.Version version) { - Assert.notNull(version, "version"); - this.httpVersion = version; - return this; + public HttpClient.Redirect getHttpRedirect() { + return httpRedirect; } /** - * Provide a proxy for HTTP connection. + * Gets proxy * - * @param proxy - * http proxy - * @return - * self reference + * @return value of proxy */ - public HttpClientOptions withHttpProxy(HttpProxy proxy) { - this.proxy = proxy; - return this; + public HttpProxy getHttpProxy() { + return httpProxy; } // -------------------------------------------- @@ -172,7 +178,7 @@ public HttpClientOptions clone() { // Deep copy of mutable fields cloned.callers = new ArrayList<>(this.callers); cloned.retryDelay = this.retryDelay != null ? Duration.ofMillis(this.retryDelay.toMillis()) : null; - cloned.proxy = this.proxy != null ? this.proxy.clone() : null; + cloned.httpProxy = this.httpProxy != null ? this.httpProxy.clone() : null; return cloned; } catch (CloneNotSupportedException e) { @@ -180,4 +186,5 @@ public HttpClientOptions clone() { } } + } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/core/http/HttpProxy.java b/astra-db-java/src/main/java/com/datastax/astra/client/core/http/HttpProxy.java index e3f95962..de22c434 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/core/http/HttpProxy.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/core/http/HttpProxy.java @@ -20,14 +20,19 @@ * #L% */ +import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; +import lombok.experimental.Accessors; /** * Subclass to represent an http proxy. */ -@Getter @Setter +@NoArgsConstructor +@AllArgsConstructor +@Accessors(fluent = true, chain = true) public class HttpProxy implements Cloneable { /** hostname of the proxy. */ @@ -36,21 +41,26 @@ public class HttpProxy implements Cloneable { /** port of the proxy. */ int port; + @Override + public HttpProxy clone() { + return new HttpProxy(this.hostname, this.port); + } + /** - * Default constructor. + * Gets hostname * - * @param hostname - * host name - * @param port - * roxy port + * @return value of hostname */ - public HttpProxy(String hostname, int port) { - this.hostname = hostname; - this.port = port; + public String getHostname() { + return hostname; } - @Override - public HttpProxy clone() { - return new HttpProxy(this.hostname, this.port); + /** + * Gets port + * + * @return value of port + */ + public int getPort() { + return port; } } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/core/options/DataAPIClientOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/core/options/DataAPIClientOptions.java index 1a606843..56461e2a 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/core/options/DataAPIClientOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/core/options/DataAPIClientOptions.java @@ -9,9 +9,9 @@ * Licensed under the Apache License, Version 2.0 * You may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -26,16 +26,16 @@ import com.datastax.astra.client.core.auth.EmbeddingHeadersProvider; import com.datastax.astra.client.core.http.Caller; import com.datastax.astra.client.core.http.HttpClientOptions; -import com.datastax.astra.client.core.http.HttpProxy; import com.datastax.astra.internal.command.CommandObserver; import com.datastax.astra.internal.command.LoggingCommandObserver; import com.datastax.astra.internal.serdes.DatabaseSerializer; +import com.dtsx.astra.sdk.utils.Assert; import com.dtsx.astra.sdk.utils.AstraEnvironment; -import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; -import java.net.http.HttpClient; -import java.time.Duration; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; @@ -47,112 +47,138 @@ /** * Options to set up the client for DataApiClient. */ -@Getter @Slf4j -public class DataAPIClientOptions { +@Setter +@Accessors(fluent = true, chain = true) +public class DataAPIClientOptions implements Cloneable { - /** Feature Flag Tables. */ - public static final String HEADER_FEATURE_FLAG_TABLES = "Feature-Flag-tables"; - - /** path for json api. */ - public static final String DEFAULT_VERSION = "v1"; - - /** Number of documents for a count. */ - public static final int DEFAULT_MAX_COUNT = 1000; - - /** Maximum number of documents in a page. */ - public static final int DEFAULT_MAX_PAGE_SIZE = 20; + // -------------------------------------------------- + // --- Defaults --- + // -------------------------------------------------- - /** Maximum number of documents when you insert. */ - public static final int DEFAULT_MAX_CHUNK_SIZE = 100; + /** + * The default keyspace to use for working with your databases. + *

+ * This value is the default in Astra but you can create your own keyspaces in your database. + * Ensure the keyspace matches your database configuration to avoid runtime issues. + *

+ */ + public static final String DEFAULT_KEYSPACE = "default_keyspace"; - /** Encode the vector as binary. */ - public static boolean encodeDurationAsISO8601 = true; + /** + * API version and path in the URL in the format '/api/${version}'. + *

+ * This constant can be used to construct endpoint URLs for making API calls. + * Modify this value if the backend version changes. + *

+ */ + public static final String DEFAULT_VERSION = "v1"; - /** Encode the vector as binary. */ - public static boolean encodeDataApiVectorsAsBase64 = true; + /** + * The maximum number of documents allowed for processing before throwing an exception. + *

+ * This is a safeguard to prevent excessive memory or computational load when processing + * large batches of data. Applications should adhere to this limit for better stability. + *

+ */ + public static final int MAX_COUNT = 1000; - /** When operating a count operation, the maximum number of documents that can be returned. */ - final int maxCount; + /** + * The maximum number of documents to insert in a single batch operation. + *

+ * This is used to optimize bulk insertion performance while ensuring the operation + * doesn't exceed database constraints or memory limits. + *

+ */ + public static final int MAX_CHUNK_SIZE = 50; - /** The maximum number of documents that can be returned in a single page. */ - final int maxPageSize; + // -------------------------------------------------- + // --- More Global Constants --- + // -------------------------------------------------- - /** The maximum number of documents that can be inserted in a single operation. */ - final int maxRecordsInInsert; + /** + * Feature Flag Tables. + */ + public static final String HEADER_FEATURE_FLAG_TABLES = "Feature-Flag-tables"; - /** Set the API version like 'v1' */ - final String apiVersion; + // -------------------------------------------------- + // --- Fields --- + // -------------------------------------------------- - /** Set Timeouts for the different operations */ - final TimeoutOptions timeoutOptions; + /** + * Set the API version like 'v1' + */ + private String apiVersion = DEFAULT_VERSION; - /** Group options and parameters for http client. */ - final HttpClientOptions httpClientOptions; + /** + * Encode the destination like Astra or local installation. + */ + private DataAPIDestination destination = DataAPIDestination.ASTRA; - /** Encode the destination like Astra or local installation. */ - final DataAPIDestination destination; + /** + * Http Client Options + */ + private HttpClientOptions httpClientOptions = new HttpClientOptions(); - /** Embedding auth provider. */ - final EmbeddingHeadersProvider embeddingAuthProvider; + /** + * Timeout options. + */ + private TimeoutOptions timeoutOptions = new TimeoutOptions(); - /** Add headers to db calls. */ - final Map databaseAdditionalHeaders; + /** + * Options for serialization and deserialization. + *

+ * This static field is shared across the application to ensure consistency in + * how objects are serialized and deserialized. The Jackson serializer, or any + * similar serialization framework, will leverage the options defined in this instance. + *

+ *

+ * Use this field to configure global serialization/deserialization behavior, such as + * custom serializers, choose vector and durations encodings. + *

+ */ + private static SerdesOptions serdesOptions = new SerdesOptions(); - /** Add headers to admin calls. */ - final Map adminAdditionalHeaders; + /** + * The embedding service API key can be provided at top level. + */ + private EmbeddingHeadersProvider embeddingAuthProvider; - /** Observers for the commands. */ - final Map observers; + /** + * Add headers to admin calls. + */ + private Map databaseAdditionalHeaders = new HashMap<>(); /** - * Initializer for the builder. - * - * @return - * a new instance of builder + * Add headers to admin calls. */ - public static DataAPIClientOptionsBuilder builder() { - return new DataAPIClientOptionsBuilder(); - } + private Map adminAdditionalHeaders = new HashMap<>(); /** - * Hidden constructor with the builder to build immutable class. - * - * @param builder - * current builder - */ - private DataAPIClientOptions(DataAPIClientOptionsBuilder builder) { - this.apiVersion = builder.apiVersion; - this.destination = builder.destination; - this.maxCount = builder.maxCount; - this.maxPageSize = builder.maxPageSize; - this.maxRecordsInInsert = builder.maxDocumentsInInsert; - this.embeddingAuthProvider = builder.embeddingAuthProvider; - this.httpClientOptions = builder.httpClientOptions; - this.observers = builder.observers; - this.databaseAdditionalHeaders = builder.databaseAdditionalHeaders; - this.adminAdditionalHeaders = builder.adminAdditionalHeaders; - this.timeoutOptions = builder.timeoutOptions; - } + * Observers for the commands (logging or metrics - emitter pattern). + */ + private Map observers = new TreeMap<>(); + + // -------------------------------------------------- + // --- Accessors --- + // -------------------------------------------------- /** * Check if the deploying is Astra * - * @return - * true if the destination is Astra + * @return true if the destination is Astra */ public boolean isAstra() { return getDestination() == ASTRA || - getDestination() == ASTRA_DEV || - getDestination() == ASTRA_TEST; + getDestination() == ASTRA_DEV || + getDestination() == ASTRA_TEST; } /** * Find the Astra Environment from the destination provided in the initial Optional. It will help * shaping the Api endpoint to spawn sub database objects. * - * @return - * astra environment if found + * @return astra environment if found */ public AstraEnvironment getAstraEnvironment() { if (getDestination() != null) { @@ -169,384 +195,215 @@ public AstraEnvironment getAstraEnvironment() { } /** - * Disabling the encoding of the vectors as base64. + * Gets apiVersion + * + * @return value of apiVersion */ - public static void disableEncodeDataApiVectorsAsBase64() { - encodeDataApiVectorsAsBase64 = false; + public String getApiVersion() { + return apiVersion; } /** - * Disabling the encoding of the vectors as base64. + * Gets destination + * + * @return value of destination */ - public static void disableEncodeDurationAsISO8601() { - encodeDurationAsISO8601 = false; + public DataAPIDestination getDestination() { + return destination; } - @Override - public String toString() { - return new DatabaseSerializer().marshall(this); + /** + * Gets embeddingAuthProvider + * + * @return value of embeddingAuthProvider + */ + public EmbeddingHeadersProvider getEmbeddingAuthProvider() { + return embeddingAuthProvider; } /** - * Builder for the DataAPIClientOptions. + * Gets databaseAdditionalHeaders + * + * @return value of databaseAdditionalHeaders */ - public static class DataAPIClientOptionsBuilder { - - /** Caller name in User agent. */ - private String apiVersion = DEFAULT_VERSION; - - /** When operating a count operation, the maximum number of documents that can be returned. */ - private int maxCount = DEFAULT_MAX_COUNT; - - /** The maximum number of documents that can be returned in a single page. */ - private int maxPageSize = DEFAULT_MAX_PAGE_SIZE; - - /** The maximum number of documents that can be inserted in a single operation. */ - private int maxDocumentsInInsert = DEFAULT_MAX_CHUNK_SIZE; - - /** Encode the destination like Astra or local installation. */ - private DataAPIDestination destination = DataAPIDestination.ASTRA; - - /** The embedding service API key can be provided at top level. */ - private EmbeddingHeadersProvider embeddingAuthProvider; - - /** Add headers to admin calls. */ - final Map databaseAdditionalHeaders = new HashMap<>(); - - /** Add headers to admin calls. */ - final Map adminAdditionalHeaders = new HashMap<>(); - - /** Observers for the commands. */ - private final Map observers = new TreeMap<>(); - - /** client options. */ - private HttpClientOptions httpClientOptions = new HttpClientOptions(); - - /** timeout options. */ - public TimeoutOptions timeoutOptions = new TimeoutOptions(); - - /** - * Default constructor. - */ - public DataAPIClientOptionsBuilder() { - } - - /** - * Builder pattern, update api version. - * - * @param apiVersion - * api version - * @return - * self reference - */ - public DataAPIClientOptionsBuilder withApiVersion(String apiVersion) { - this.apiVersion = apiVersion; - return this; - } - - /** - * Builder pattern, update api version. - * - * @param timeoutOptions - * timeoutOptions - * @return - * self reference - */ - public DataAPIClientOptionsBuilder withTimeoutOptions(TimeoutOptions timeoutOptions) { - this.timeoutOptions = timeoutOptions; - return this; - } + public Map getDatabaseAdditionalHeaders() { + return databaseAdditionalHeaders; + } - /** - * Sets the maximum number of documents that can be returned by the count function. - * - *

- * If not explicitly set, the default value is defined by {@code MAX_DOCUMENTS_COUNT}, - * which is 1000 documents. - *

- * - * @param maxDocumentCount the maximum number of documents that can be returned by the count function. - * Must be a positive number. - * @return a reference to this builder, allowing for method chaining. - * - * Example usage: - *
-         * {@code
-         * DataAPIClientOptions
-         *   .builder()
-         *   .withMaxDocumentCount(2000); // Sets the maximum number of documents to 2000.
-         * }
- */ - public DataAPIClientOptionsBuilder withMaxCount(int maxDocumentCount) { - if (maxDocumentCount <= 0) { - throw new IllegalArgumentException("Max document count must be a positive number"); - } - if (maxDocumentCount > DEFAULT_MAX_COUNT) { - log.warn("Setting the maximum document count to a value greater than the default value of {} may impact performance.", DEFAULT_MAX_COUNT); - } - this.maxCount = maxDocumentCount; - return this; - } + /** + * Gets adminAdditionalHeaders + * + * @return value of adminAdditionalHeaders + */ + public Map getAdminAdditionalHeaders() { + return adminAdditionalHeaders; + } - /** - * Sets the maximum number of documents that can be returned in a single page. - *

- * If not explicitly set, the default value is defined by {@code MAX_PAGE_SIZE}, - * which is 20 documents. - *

- * - * @param maxPageSize the maximum number of documents that can be returned in a single page. - * Must be a positive number. - * @return a reference to this builder, allowing for method chaining. - * - * Example usage: - *
-         * {@code
-         * DataAPIClientOptions
-         *   .builder()
-         *   .withMaxPageSize(50); // Sets the maximum page size to 50 documents.
-         * }
- */ - public DataAPIClientOptionsBuilder withMaxPageSize(int maxPageSize) { - if (maxPageSize <= 0) { - throw new IllegalArgumentException("Max page size must be a positive number"); - } - if (maxPageSize > DEFAULT_MAX_PAGE_SIZE) { - log.warn("Setting the maximum page size to a value greater than the " + - "default value of {} may impact performance or result in error at server level", DEFAULT_MAX_PAGE_SIZE); - } - this.maxPageSize = maxPageSize; - return this; - } + /** + * Gets observers + * + * @return value of observers + */ + public Map getObservers() { + return observers; + } - /** - * Sets the maximum number of documents that can be inserted in a single operation. - *

- * If not explicitly set, the default value is defined by {@code MAX_DOCUMENTS_IN_INSERT}, - * which is 20 documents. - *

- * - * @param maxDocumentsInInsert the maximum number of documents that can be inserted in a single operation. - * Must be a positive number. - * @return a reference to this builder, allowing for method chaining. - * - * Example usage: - *
-         * {@code
-         * DataAPIClientOptions
-         *   .builder()
-         *   .withMaxDocumentsInInsert(50); // Sets the maximum number of documents to insert to 50.
-         * }
- */ - public DataAPIClientOptionsBuilder withMaxDocumentsInInsert(int maxDocumentsInInsert) { - if (maxDocumentsInInsert <= 0) { - throw new IllegalArgumentException("Max documents in insert must be a positive number"); - } - if (maxDocumentsInInsert > DEFAULT_MAX_CHUNK_SIZE) { - log.warn("Setting the maximum number of documents in insert to a value greater than the " + - "default value of {} may impact performance or result in error at server level", DEFAULT_MAX_CHUNK_SIZE); - } - this.maxDocumentsInInsert = maxDocumentsInInsert; - return this; - } + /** + * Gets httpClientOptions + * + * @return value of httpClientOptions + */ + public HttpClientOptions getHttpClientOptions() { + return httpClientOptions; + } - /** - * Allow to register a listener for the command. - * @param name - * name of the observer - * @param observer - * observer to register - * @return - * instance of the command options - */ - public DataAPIClientOptionsBuilder withObserver(String name, CommandObserver observer) { - observers.put(name, observer); - return this; - } + /** + * Gets timeoutOptions + * + * @return value of timeoutOptions + */ + public TimeoutOptions getTimeoutOptions() { + return timeoutOptions; + } - /** - * Register an observer with its className. - * - * @param observer - * command observer - * @return - * instance of the command options - */ - public DataAPIClientOptionsBuilder withObserver(CommandObserver observer) { - return withObserver(observer.getClass().getSimpleName(), observer); - } + /** + * Gets serdesOptions + * + * @return value of serdesOptions + */ + public static SerdesOptions getSerdesOptions() { + return serdesOptions; + } - /** - * Help to enable loggin requests. - * - * @return current reference - * - */ - public DataAPIClientOptionsBuilder logRequests() { - return withObserver(new LoggingCommandObserver(DataAPIClient.class)); - } + /** + * Register an observer with its className. + * + * @param observer command observer + * @return instance of the command options + */ + public DataAPIClientOptions addObserver(String name, CommandObserver observer) { + this.observers.put(name, observer); + return this; + } - /** - * Builder pattern, update http connection Timeout - * - * @param destination - * data api destination - * @return - * self reference - */ - public DataAPIClientOptionsBuilder withDestination(DataAPIDestination destination) { - this.destination = destination; - return this; - } + /** + * Register an observer with its className. + * + * @param observer command observer + * @return instance of the command options + */ + public DataAPIClientOptions addObserver(CommandObserver observer) { + return addObserver(observer.getClass().getSimpleName(), observer); + } - // -------------------------------------------- - // ----------------- HEADERS ----------------- - // -------------------------------------------- - - /** - * Builder pattern, update http connection Timeout - * - * @param embeddingAPIKey - * embedding API Key - * @return - * self reference - */ - public DataAPIClientOptionsBuilder withEmbeddingAPIKey(String embeddingAPIKey) { - return withEmbeddingAuthProvider(new EmbeddingAPIKeyHeaderProvider(embeddingAPIKey)); - } + /** + * Help to enable loggin requests. + * + * @return current reference + */ + public DataAPIClientOptions logRequests() { + return addObserver(new LoggingCommandObserver(DataAPIClient.class)); + } - /** - * Builder pattern, update authentication provider for vectorize. - * - * @param embeddingAuthProvider - * embedding authentication provider - * @return - * self reference - */ - public DataAPIClientOptionsBuilder withEmbeddingAuthProvider(EmbeddingHeadersProvider embeddingAuthProvider) { - this.embeddingAuthProvider = embeddingAuthProvider; - return this; - } + // -------------------------------------------------- + // --- Http Headers --- + // -------------------------------------------------- - /** - * Builder pattern, update caller information. - *o - * @param name - * caller name in the user agent - * @param version - * caller version in the user agent - * @return - * self reference - */ - public DataAPIClientOptionsBuilder addCaller(String name, String version) { - httpClientOptions.addCaller(new Caller(name, version)); - return this; - } + /** + * Builder pattern, update http connection Timeout + * + * @param embeddingAPIKey embedding API Key + * @return self reference + */ + public DataAPIClientOptions embeddingAPIKey(String embeddingAPIKey) { + return embeddingAuthProvider(new EmbeddingAPIKeyHeaderProvider(embeddingAPIKey)); + } - /** - * Add a header to the db calls. - * - * @param key - * key - * @param value - * value - * @return - * self reference - */ - public DataAPIClientOptionsBuilder addDatabaseAdditionalHeader(String key, String value) { - databaseAdditionalHeaders.put(key, value); - return this; - } + /** + * Builder pattern, update caller information. + * o + * + * @param name caller name in the user agent + * @param version caller version in the user agent + * @return self reference + */ + public DataAPIClientOptions addCaller(String name, String version) { + this.httpClientOptions.addCaller(new Caller(name, version)); + return this; + } - public DataAPIClientOptionsBuilder enableFeatureFlagTables() { - return addDatabaseAdditionalHeader(HEADER_FEATURE_FLAG_TABLES, "true"); - } + /** + * Add a header to the db calls. + * + * @param key key + * @param value value + * @return self reference + */ + public DataAPIClientOptions addDatabaseAdditionalHeader(String key, String value) { + this.databaseAdditionalHeaders.put(key, value); + return this; + } - public DataAPIClientOptionsBuilder disableFeatureFlagTables() { - return addDatabaseAdditionalHeader(HEADER_FEATURE_FLAG_TABLES, null); - } + /** + * Add a header to the admin calls. + * + * @param key key + * @param value value + * @return self reference + */ + public DataAPIClientOptions addAdminAdditionalHeader(String key, String value) { + adminAdditionalHeaders.put(key, value); + return this; + } - /** - * Add a header to the admin calls. - * - * @param key - * key - * @param value - * value - * @return - * self reference - */ - public DataAPIClientOptionsBuilder addAdminAdditionalHeader(String key, String value) { - adminAdditionalHeaders.put(key, value); - return this; - } + public DataAPIClientOptions enableFeatureFlagTables() { + return addDatabaseAdditionalHeader(HEADER_FEATURE_FLAG_TABLES, "true"); + } - public DataAPIClientOptionsBuilder withHttpRetries(int count, Duration delay) { - httpClientOptions.withRetries(count, delay); - return this; - } + public DataAPIClientOptions disableFeatureFlagTables() { + return addDatabaseAdditionalHeader(HEADER_FEATURE_FLAG_TABLES, null); + } - /** - * Builder pattern, update http redirect - * - * @param redirect - * http redirect - * @return - * self reference - */ - public DataAPIClientOptionsBuilder withHttpRedirect(HttpClient.Redirect redirect) { - httpClientOptions.withHttpRedirect(redirect); - return this; - } + // -------------------------------------------- + // ---------- JAVA DEFAULTS ----------------- + // -------------------------------------------- - /** - * Builder pattern, update http redirect - * - * @param httpClientOptions - * all options for http client - * @return - * self reference - */ - public DataAPIClientOptionsBuilder withHttpClientOptions(HttpClientOptions httpClientOptions) { - this.httpClientOptions = httpClientOptions; - return this; - } + @Override + public String toString() { + return new DatabaseSerializer().marshall(this); + } - /** - * Builder pattern, update http version - * - * @param version - * http version - * @return - * self reference - */ - public DataAPIClientOptionsBuilder withHttpVersion(HttpClient.Version version) { - httpClientOptions.withHttpVersion(version); - return this; - } + /** + * A default Client Options + */ + public DataAPIClientOptions() { + // defaulting values + } - /** - * Builder pattern, update http httpProxy - * - * @param httpProxy - * http proxy - * @return - * self reference - */ - public DataAPIClientOptionsBuilder withHttpProxy(HttpProxy httpProxy) { - httpClientOptions.withHttpProxy(httpProxy); - return this; - } + public DataAPIClientOptions(DataAPIClientOptions options) { + Assert.notNull(options, "Options"); + this.apiVersion = options.apiVersion; + this.destination = options.destination; + this.embeddingAuthProvider = options.embeddingAuthProvider; + // Deep Copy + this.databaseAdditionalHeaders = options.databaseAdditionalHeaders != null ? + new HashMap<>(options.databaseAdditionalHeaders) : null; + this.adminAdditionalHeaders = options.adminAdditionalHeaders != null ? + new HashMap<>(options.adminAdditionalHeaders) : null; + this.observers = options.observers != null ? + new HashMap<>(options.observers) : null; + // Clones + this.httpClientOptions = options.httpClientOptions != null ? + options.httpClientOptions.clone() : null; + this.timeoutOptions = options.timeoutOptions != null ? + options.timeoutOptions.clone() : null; + this.serdesOptions = options.serdesOptions != null ? + options.serdesOptions.clone() : null; + } - /** - * Build the options. - * - * @return - * options - */ - public DataAPIClientOptions build() { - return new DataAPIClientOptions(this); - } + @Override + public DataAPIClientOptions clone() { + return new DataAPIClientOptions(this); } } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/core/options/SerdesOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/core/options/SerdesOptions.java new file mode 100644 index 00000000..3ec37c9a --- /dev/null +++ b/astra-db-java/src/main/java/com/datastax/astra/client/core/options/SerdesOptions.java @@ -0,0 +1,71 @@ +package com.datastax.astra.client.core.options; + +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; + +/*- + * #%L + * Data API Java Client + * -- + * Copyright (C) 2024 DataStax + * -- + * Licensed under the Apache License, Version 2.0 + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +@Setter +@NoArgsConstructor +@Accessors(fluent = true, chain = true) +public class SerdesOptions implements Cloneable { + + /** Encode the vector as binary. */ + boolean encodeDurationAsISO8601 = true; + + /** Encode the vector as binary. */ + boolean encodeDataApiVectorsAsBase64 = true; + + /** + * Gets encodeDurationAsISO8601 + * + * @return value of encodeDurationAsISO8601 + */ + public boolean isEncodeDurationAsISO8601() { + return encodeDurationAsISO8601; + } + + /** + * Gets encodeDataApiVectorsAsBase64 + * + * @return value of encodeDataApiVectorsAsBase64 + */ + public boolean isEncodeDataApiVectorsAsBase64() { + return encodeDataApiVectorsAsBase64; + } + + public SerdesOptions disableEncodeDataApiVectorsAsBase64() { + return encodeDataApiVectorsAsBase64(false); + } + + public SerdesOptions disableEncodeDurationAsISO8601() { + return encodeDurationAsISO8601(false); + } + + @Override + public SerdesOptions clone() { + try { + return (SerdesOptions) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError("Cloning not supported", e); + } + } +} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/core/options/TimeoutOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/core/options/TimeoutOptions.java index a92a5dd3..2986dbe3 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/core/options/TimeoutOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/core/options/TimeoutOptions.java @@ -20,27 +20,28 @@ * #L% */ -import lombok.Builder; -import lombok.Data; -import lombok.Getter; +import com.datastax.astra.internal.utils.Assert; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; +import java.time.Duration; + /** * This class is used to define the timeout options for the client. */ -@Getter @Setter +@Setter @NoArgsConstructor @Accessors(fluent = true, chain = true) public class TimeoutOptions implements Cloneable { public static final long DEFAULT_CONNECT_TIMEOUT_MILLIS = 10000L; - public static final long DEFAULT_REQUEST_TIMEOUT_MILLIS = 30000L; - public static final long DEFAULT_DATA_OPERATION_TIMEOUT_MILLIS = 30000L; - public static final long DEFAULT_SCHEMA_OPERATION_TIMEOUT_MILLIS = 45000L; + public static final long DEFAULT_REQUEST_TIMEOUT_MILLIS = 10000L; + public static final long DEFAULT_GENERAL_METHOD_TIMEOUT_MILLIS = 30000L; + public static final long DEFAULT_COLLECTION_ADMIN_TIMEOUT_MILLIS = 60000L; + public static final long DEFAULT_TABLE_ADMIN_TIMEOUT_MILLIS = 30000L; public static final long DEFAULT_DATABASE_ADMIN_TIMEOUT_MILLIS = 600000L; - public static final long DEFAULT_KEYSPACE_ADMIN_TIMEOUT_MILLIS = 20000L; + public static final long DEFAULT_KEYSPACE_ADMIN_TIMEOUT_MILLIS = 30000L; /** * Lower level request timeout (http request) @@ -55,12 +56,7 @@ public class TimeoutOptions implements Cloneable { /** * Data operation timeout (find*, insert*, update*, delete*) */ - long dataOperationTimeoutMillis = DEFAULT_DATA_OPERATION_TIMEOUT_MILLIS; - - /** - * Schema operation timeout (create*, alter*, drop*) - */ - long schemaOperationTimeoutMillis = DEFAULT_SCHEMA_OPERATION_TIMEOUT_MILLIS; + long generalMethodTimeoutMillis = DEFAULT_GENERAL_METHOD_TIMEOUT_MILLIS; /** * Database admin timeout (create, delete, list) @@ -72,6 +68,16 @@ public class TimeoutOptions implements Cloneable { */ long keyspaceAdminTimeoutMillis = DEFAULT_KEYSPACE_ADMIN_TIMEOUT_MILLIS; + /** + * Schema operation timeout (create*, alter*, drop*) + */ + long collectionAdminTimeoutMillis = DEFAULT_COLLECTION_ADMIN_TIMEOUT_MILLIS; + + /** + * Schema operation timeout (create*, alter*, drop*) + */ + long tableAdminTimeoutMillis = DEFAULT_TABLE_ADMIN_TIMEOUT_MILLIS; + @Override public TimeoutOptions clone() { try { @@ -81,6 +87,48 @@ public TimeoutOptions clone() { } } + public TimeoutOptions connectTimeout(Duration timeout) { + Assert.notNull(timeout, "Timeout"); + this.connectTimeoutMillis = timeout.toMillis(); + return this; + } + + public TimeoutOptions requestTimeout(Duration timeout) { + Assert.notNull(timeout, "Timeout"); + this.requestTimeoutMillis = timeout.toMillis(); + return this; + } + + public TimeoutOptions generalMethodTimeout(Duration timeout) { + Assert.notNull(timeout, "Timeout"); + this.generalMethodTimeoutMillis = timeout.toMillis(); + return this; + } + + public TimeoutOptions databaseAdminTimeout(Duration timeout) { + Assert.notNull(timeout, "Timeout"); + this.databaseAdminTimeoutMillis = timeout.toMillis(); + return this; + } + + public TimeoutOptions keyspaceAdminTimeout(Duration timeout) { + Assert.notNull(timeout, "Timeout"); + this.keyspaceAdminTimeoutMillis = timeout.toMillis(); + return this; + } + + public TimeoutOptions collectionAdminTimeout(Duration timeout) { + Assert.notNull(timeout, "Timeout"); + this.collectionAdminTimeoutMillis = timeout.toMillis(); + return this; + } + + public TimeoutOptions tableAdminTimeout(Duration timeout) { + Assert.notNull(timeout, "Timeout"); + this.tableAdminTimeoutMillis = timeout.toMillis(); + return this; + } + /** * Gets connectTimeoutMillis * @@ -104,8 +152,8 @@ public long getRequestTimeoutMillis() { * * @return value of dataOperationTimeoutMillis */ - public long getDataOperationTimeoutMillis() { - return dataOperationTimeoutMillis; + public long getGeneralMethodTimeoutMillis() { + return generalMethodTimeoutMillis; } /** @@ -113,8 +161,8 @@ public long getDataOperationTimeoutMillis() { * * @return value of schemaOperationTimeoutMillis */ - public long getSchemaOperationTimeoutMillis() { - return schemaOperationTimeoutMillis; + public long getTableAdminTimeoutMillis() { + return tableAdminTimeoutMillis; } /** @@ -134,4 +182,13 @@ public long getDatabaseAdminTimeoutMillis() { public long getKeyspaceAdminTimeoutMillis() { return keyspaceAdminTimeoutMillis; } + + /** + * Gets collectionAdminTimeoutMillis + * + * @return value of collectionAdminTimeoutMillis + */ + public long getCollectionAdminTimeoutMillis() { + return collectionAdminTimeoutMillis; + } } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/core/query/Sort.java b/astra-db-java/src/main/java/com/datastax/astra/client/core/query/Sort.java index 80196cda..1fd0b3b3 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/core/query/Sort.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/core/query/Sort.java @@ -122,7 +122,7 @@ public static Sort vector(float[] embeddings) { * sort instance. */ public static Sort vectorize(String vectorize) { - return new Sort(DataAPIKeywords.VECTOR.getKeyword(), null, vectorize, null); + return new Sort(DataAPIKeywords.VECTORIZE.getKeyword(), null, vectorize, null); } } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/core/vector/VectorOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/core/vector/VectorOptions.java index 95144e34..100dc337 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/core/vector/VectorOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/core/vector/VectorOptions.java @@ -24,11 +24,15 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; /** * Subclass representing the vector options. */ -@Data @NoArgsConstructor +@Setter +@NoArgsConstructor +@Accessors(fluent = true, chain = true) public class VectorOptions { /** @@ -56,5 +60,32 @@ public class VectorOptions { public SimilarityMetric getSimilarityMetric() { return SimilarityMetric.fromValue(metric); } + + /** + * Gets dimension + * + * @return value of dimension + */ + public Integer getDimension() { + return dimension; + } + + /** + * Gets metric + * + * @return value of metric + */ + public String getMetric() { + return metric; + } + + /** + * Gets service + * + * @return value of service + */ + public VectorServiceOptions getService() { + return service; + } } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/databases/Database.java b/astra-db-java/src/main/java/com/datastax/astra/client/databases/Database.java index 7e6d8895..c8833b79 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/databases/Database.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/databases/Database.java @@ -20,524 +20,1109 @@ * #L% */ +import com.datastax.astra.client.admin.AdminOptions; import com.datastax.astra.client.admin.AstraDBAdmin; import com.datastax.astra.client.admin.AstraDBDatabaseAdmin; import com.datastax.astra.client.admin.DataAPIDatabaseAdmin; import com.datastax.astra.client.admin.DatabaseAdmin; import com.datastax.astra.client.collections.Collection; import com.datastax.astra.client.collections.CollectionDefinition; +import com.datastax.astra.client.collections.CollectionDescriptor; import com.datastax.astra.client.collections.CollectionOptions; import com.datastax.astra.client.collections.documents.Document; +import com.datastax.astra.client.core.commands.BaseOptions; import com.datastax.astra.client.core.commands.Command; -import com.datastax.astra.client.core.commands.CommandOptions; -import com.datastax.astra.client.core.options.DataAPIClientOptions; -import com.datastax.astra.client.core.vector.SimilarityMetric; +import com.datastax.astra.client.databases.options.CreateCollectionOptions; +import com.datastax.astra.client.databases.options.DropCollectionOptions; +import com.datastax.astra.client.databases.options.ListCollectionOptions; +import com.datastax.astra.client.databases.options.ListIndexesOptions; +import com.datastax.astra.client.databases.options.ListTablesOptions; import com.datastax.astra.client.exception.InvalidConfigurationException; import com.datastax.astra.client.tables.Table; import com.datastax.astra.client.tables.TableDefinition; import com.datastax.astra.client.tables.TableDescriptor; +import com.datastax.astra.client.tables.TableOptions; import com.datastax.astra.client.tables.ddl.CreateTableOptions; import com.datastax.astra.client.tables.ddl.DropTableIndexOptions; import com.datastax.astra.client.tables.ddl.DropTableOptions; -import com.datastax.astra.client.tables.index.IndexDescriptor; +import com.datastax.astra.client.tables.index.TableIndexDefinition; +import com.datastax.astra.client.tables.index.TableIndexDescriptor; import com.datastax.astra.client.tables.mapping.EntityTable; import com.datastax.astra.client.tables.row.Row; import com.datastax.astra.internal.api.AstraApiEndpoint; import com.datastax.astra.internal.command.AbstractCommandRunner; import com.datastax.astra.internal.command.CommandObserver; -import com.datastax.astra.internal.serdes.DataAPISerializer; -import com.datastax.astra.internal.serdes.DatabaseSerializer; +import com.datastax.astra.internal.utils.Assert; import com.dtsx.astra.sdk.utils.Utils; import lombok.Getter; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; -import java.util.stream.Stream; +import java.util.List; +import java.util.UUID; -import static com.datastax.astra.client.core.commands.CommandType.SCHEMA; -import static com.datastax.astra.client.exception.InvalidEnvironmentException.throwErrorRestrictedAstra; import static com.datastax.astra.client.tables.mapping.EntityBeanDefinition.createTableCommand; -import static com.datastax.astra.internal.utils.AnsiUtils.green; import static com.datastax.astra.internal.utils.Assert.hasLength; import static com.datastax.astra.internal.utils.Assert.notNull; /** - * A Data API database. This is the entry-point object for doing database-level - * DML, such as creating/deleting collections, and for obtaining Collection - * objects themselves. This class has a synchronous interface. - *

- * A Database comes with an "API Endpoint", which implies a Database object - * instance reaches a specific region (relevant point in case of multi-region - * databases). - *

+ * Represents a Data API database, providing the primary entry point for database-level operations + * and interactions. This class enables Data Manipulation Language (DML) operations such as + * creating and deleting collections, as well as obtaining {@link Collection} objects for further + * operations on specific collections. It also provides access to operations for managing tables. + * + *

This class provides a synchronous interface, designed for straightforward and immediate + * execution of database commands. It is intended for use in scenarios where blocking calls are + * acceptable or desirable, such as in traditional server-side applications or command-line tools.

+ * + *

Each {@code Database} instance is associated with an "API Endpoint," which defines the specific + * region it connects to. This is particularly important for multi-region databases, where each + * instance of {@code Database} ensures connectivity to a specific regional endpoint for optimal + * performance and consistency.

+ * + *

Key Features:

+ *
    + *
  • Direct access to database-level operations, including collection management.
  • + *
  • Region-specific connectivity for multi-region database configurations.
  • + *
  • Built on {@link AbstractCommandRunner}, providing consistent command execution semantics.
  • + *
+ * + *

Example usage:

+ *
+ * {@code
+ * // Initialize the database object with endpoint and options
+ * DataAPIClient client = new DataAPIClient("token");
+ * client.getDatabase("https://-.apps.astra.datastax.com");
+ *
+ * // Perform database-level operations
+ * database.createCollection("myCollection");
+ * Collection collection = database.getCollection("myCollection");
+ * collection.insert(new Document("field1", "value1"));
+ * }
+ * 
+ * + * @see Collection + * @see AbstractCommandRunner */ -@Slf4j -public class Database extends AbstractCommandRunner { +@Getter +public class Database extends AbstractCommandRunner { - /** Serializer for the Collections. */ - private static final DatabaseSerializer SERIALIZER = new DatabaseSerializer(); - - /** Token to be used with the Database. */ - @Getter - private final String token; + /** + * This core endpoint could be used for admin operations. + */ + private final String rootEndpoint; - /** Api Endpoint for the API. */ - @Getter - private final String dbApiEndpoint; + /** + * Database information cached if (getInfo()) is called + */ + private DatabaseInfo cachedDbInfo; - /** Options to set up the client. */ - @Getter - private final DataAPIClientOptions options; + /** + * Initializes a {@link Database} instance with the specified API endpoint and connection options. + * This constructor configures the database client to interact with the Data API at the provided + * root endpoint, setting up necessary parameters and constructing the API endpoint based on + * the deployment environment and options. + * + *

The API endpoint is automatically adjusted for Astra deployments (e.g., {@code ASTRA}, + * {@code ASTRA_TEST}, {@code ASTRA_DEV}), appending the required JSON API path if the root + * endpoint ends with {@code ".com"}. For local or on-premise deployments, no adjustments are made + * to the root endpoint.

+ * + *

The constructed API endpoint includes:

+ *
    + *
  • The specified {@code apiVersion}, retrieved from {@link com.datastax.astra.client.core.options.DataAPIClientOptions}.
  • + *
  • The {@code keyspace}, defined in the provided {@link DatabaseOptions}.
  • + *
+ * + * @param rootEndpoint + * The root API endpoint for connecting to the database. This is the base URL that determines + * the target deployment environment (e.g., Astra or local). + * @param options + * The {@link DatabaseOptions} containing all attributes required to connect to the database, + * including authentication details, keyspace configuration, and client options. + */ + public Database(String rootEndpoint, DatabaseOptions options) { + super(rootEndpoint, options); + this.rootEndpoint = rootEndpoint; + StringBuilder dbApiEndPointBuilder = new StringBuilder(rootEndpoint); + switch(options.getDataAPIClientOptions().getDestination()) { + case ASTRA: + case ASTRA_TEST: + case ASTRA_DEV: + if (rootEndpoint.endsWith(".com")) { + dbApiEndPointBuilder.append("/api/json"); + } + break; + default: + // left blank as local deployments does not require any change + break; + } + this.apiEndpoint = dbApiEndPointBuilder + .append("/") + .append(options.getDataAPIClientOptions().getApiVersion()) + .append("/") + .append(options.getKeyspace()) + .toString(); + } - /** Current Keyspace information.*/ - @Getter - private String keyspaceName; + // ------------------------------------------ + // ---- Core Features ---- + // ------------------------------------------ /** - * This core endpoint could be used for admin operations. + * Retrieves the name of the currently selected keyspace. + * + * @return The name of the keyspace currently in use by this {@code Database} instance. */ - private final String databaseAdminEndpoint; + public String getKeyspace() { + return options.getKeyspace(); + } /** - * Initialization with endpoint and apikey. + * Retrieves the region of the database if it is deployed in Astra. This method ensures that + * the database is an Astra deployment before returning the region. If the database is not deployed + * in Astra, an assertion error is raised. * - * @param token - * api token - * @param apiEndpoint - * api endpoint + * @return The region where the Astra database is deployed. + * @throws IllegalStateException if the database is not deployed in Astra. */ - public Database(String apiEndpoint, String token) { - this(apiEndpoint, token, AstraDBAdmin.DEFAULT_KEYSPACE, DataAPIClientOptions.builder().build()); + public String getRegion() { + assertIsAstra(); + return AstraApiEndpoint.parse(getApiEndpoint()).getDatabaseRegion(); } /** - * Initialization with endpoint and apikey. + * Retrieves the unique database identifier (UUID) of the database if it is deployed in Astra. + * This method ensures that the database is an Astra deployment before returning the identifier. + * If the database is not deployed in Astra, an assertion error is raised. * - * @param token - * api token - * @param apiEndpoint - * api endpoint - * @param keyspace - * keyspace + * @return The unique identifier (UUID) of the Astra database. + * @throws IllegalStateException if the database is not deployed in Astra. */ - public Database(String apiEndpoint, String token, String keyspace) { - this(apiEndpoint, token, keyspace, DataAPIClientOptions.builder().build()); + public UUID getId() { + assertIsAstra(); + return AstraApiEndpoint.parse(getApiEndpoint()).getDatabaseId(); } /** - * Initialization with endpoint and apikey. + * Retrieves information about the current database, including metadata and configuration details. * - * @param apiEndpoint - * api endpoint - * @param token - * api token - * @param keyspace - * keyspace - * @param options - * setup of the clients with options + *

This method interacts with the devops API to fetch database information. To optimize + * performance, the database information is cached after the first retrieval. Subsequent calls to this + * method return the cached {@link DatabaseInfo} object unless the cache is invalidated externally.

+ * + *

Example usage:

+ *
+     * {@code
+     * DatabaseInfo info = database.getInfo();
+     * System.out.println("Database Name: " + info.getName());
+     * System.out.println("Database Version: " + info.getVersion());
+     * }
+     * 
+ * + * @return A {@link DatabaseInfo} object containing details about the current database. + * @throws IllegalStateException if the database information cannot be retrieved or if the + * database is not properly configured for administration operations. */ - public Database(String apiEndpoint, String token, String keyspace, DataAPIClientOptions options) { - hasLength(apiEndpoint, "endpoint"); - hasLength(token, "token"); - hasLength(keyspace, "keyspace"); - notNull(options, "options"); - this.keyspaceName = keyspace; - this.token = token; - this.options = options; - this.dbApiEndpoint = apiEndpoint; - - // Command Options inherit from DataAPIOptions - this.commandOptions = new CommandOptions<>(options); - this.commandOptions.token(token); - this.commandOptions.commandType(SCHEMA); - this.databaseAdminEndpoint = apiEndpoint.endsWith(options.getApiVersion()) ? - apiEndpoint : - apiEndpoint + "/" + options.getApiVersion(); + public DatabaseInfo getInfo() { + if (cachedDbInfo == null) { + cachedDbInfo = getAdmin().getDatabaseInfo(getId()); + } + return cachedDbInfo; } - // ------------------------------------------ - // ---- Mutate Keyspace ---- - // ------------------------------------------ + /** + * Retrieves the name of the current database. + * + *

This method provides a convenient way to access the database name from the {@link DatabaseInfo} + * object returned by {@link #getInfo()}. It encapsulates the process of fetching and extracting + * the database name.

+ * + *

Example usage:

+ *
+     * {@code
+     * String dbName = database.getName();
+     * System.out.println("Database Name: " + dbName);
+     * }
+     * 
+ * + * @return The name of the current database as a {@link String}. + * @throws IllegalStateException if the database information cannot be retrieved or is unavailable. + */ + public String getName() { + return getInfo().getName(); + } /** - * This mutates the keyspace to be used. + * Sets the active keyspace for the database. + * This method allows switching the current keyspace context used for database operations. * * @param keyspace - * current keyspace + * The name of the keyspace to set as the current keyspace. + * This must not be null or empty. * @return - * the database + * The database instance with the specified keyspace set as active, + * allowing for method chaining. + * @throws IllegalArgumentException + * If the provided keyspace is null or empty. + * + *

Example usage:

+ *
+     * {@code
+     * Database database = new Database();
+     * database.useKeyspace("my_keyspace");
+     * }
+     * 
*/ public Database useKeyspace(String keyspace) { - this.keyspaceName = keyspace; + Assert.hasLength(keyspace, "keyspace"); + this.options.keyspace(keyspace); return this; } // ------------------------------------------ - // ---- Access Region ---- + // ---- Astra Admin ---- // ------------------------------------------ /** - * Get the region of the database if deployed in Astra. + * Retrieves an administration client for Astra deployments using detailed administrative options. + *

+ * This method provides fine-grained control over the client configuration by allowing explicit + * specification of both the token and additional options. + *

* - * @return - * the region + * @param adminOptions The {@link AdminOptions} object containing authentication and configuration details. + * @return An {@link AstraDBAdmin} instance configured with the provided administrative options. + * @throws IllegalStateException if the database is not deployed in Astra. */ - public String getRegion() { - if (!options.isAstra()) { - throwErrorRestrictedAstra("getRegion", options.getDestination()); - } - return AstraApiEndpoint.parse(getApiEndpoint()).getDatabaseRegion(); + public AstraDBAdmin getAdmin(AdminOptions adminOptions) { + assertIsAstra(); + return new AstraDBAdmin(adminOptions); + } + + /** + * Retrieves an administration client specifically for Astra deployments using the default authentication token. + *

+ * This client allows execution of administrative tasks such as creating databases and managing Astra configurations. + *

+ * + * @return An {@link AstraDBAdmin} instance configured with the default token for administrative operations. + * @throws IllegalStateException if the database is not deployed in Astra. + */ + public AstraDBAdmin getAdmin() { + return getAdmin(options.getToken()); + } + + /** + * Retrieves an administration client specifically for Astra deployments using a provided super-user token. + *

+ * This method allows overriding the default token with a custom super-user token for enhanced privileges. + *

+ * + * @param superUserToken A token with elevated privileges for administrative operations. + * @return An {@link AstraDBAdmin} instance configured with the provided token. + * @throws IllegalStateException if the database is not deployed in Astra. + */ + public AstraDBAdmin getAdmin(String superUserToken) { + return getAdmin(new AdminOptions(superUserToken, options.getDataAPIClientOptions())); } // ------------------------------------------ - // ---- Access Database Admin ---- + // ---- Database Admin --- // ------------------------------------------ /** - * Access a database Admin client from the database - * @return - * database admin + * Retrieves a database administration client using detailed administrative options. + *

+ * Depending on the deployment type (Astra or non-Astra), this method returns an appropriate implementation of + * {@link DatabaseAdmin}, either {@link AstraDBDatabaseAdmin} or {@link DataAPIDatabaseAdmin}. The provided + * {@link AdminOptions} object determines the authentication and configuration used for the client. + *

+ * + *

Key behaviors: + *

    + *
  • If no {@code adminOptions} are provided, a default configuration is derived from the current options.
  • + *
  • If the deployment is Astra, an {@link AstraDBDatabaseAdmin} instance is returned.
  • + *
  • For non-Astra deployments, a {@link DataAPIDatabaseAdmin} instance is returned.
  • + *
+ *

+ * + * @param adminOptions The {@link AdminOptions} object containing authentication and configuration details. + * @return A {@link DatabaseAdmin} instance tailored for the current deployment type and configured with the provided options. + */ + public DatabaseAdmin getDatabaseAdmin(AdminOptions adminOptions) { + if (adminOptions == null) { + adminOptions = new AdminOptions(options.getToken(), options.getDataAPIClientOptions().clone()); + } else if (adminOptions.getDataAPIClientOptions() == null) { + adminOptions.dataAPIClientOptions(options.getDataAPIClientOptions().clone()); + }else if (adminOptions.getToken() == null) { + adminOptions.token(options.getToken()); + } + // Pick the right admin client + if (options.getDataAPIClientOptions().isAstra()) { + return new AstraDBDatabaseAdmin(this, adminOptions); + } + return new DataAPIDatabaseAdmin(this, adminOptions); + } + + /** + * Retrieves a database administration client using the default authentication token. + *

+ * The client enables management of database-level configurations, such as keyspaces, collections, + * and other database settings. + *

+ * + * @return A {@link DatabaseAdmin} instance configured with the default token for database-level operations. */ public DatabaseAdmin getDatabaseAdmin() { - return new DataAPIDatabaseAdmin(this); + return getDatabaseAdmin(options.getToken()); } /** - * Gets the name of the database. + * Retrieves a database administration client using a provided super-user token. + *

+ * This method allows overriding the default token with a custom super-user token for privileged database + * management operations. + *

* - * @param superUserToken - * provide a token with a super-user role - * @return the database name + * @param superUserToken A token with elevated privileges for database administration tasks. + * @return A {@link DatabaseAdmin} instance configured with the provided token. */ public DatabaseAdmin getDatabaseAdmin(String superUserToken) { - if (options.isAstra()) { - AstraApiEndpoint endpoint = AstraApiEndpoint.parse(getApiEndpoint()); - return new AstraDBDatabaseAdmin(superUserToken, endpoint.getDatabaseId(), options); - } - return new DataAPIDatabaseAdmin(databaseAdminEndpoint, token, options); + return getDatabaseAdmin(new AdminOptions(superUserToken, options.getDataAPIClientOptions())); } // ------------------------------------------ - // ---- Collection CRUD ---- + // ---- List Collections ---- // ------------------------------------------ /** - * Gets the names of all the collections in this database. + * Retrieves the names of all the collections present in this database. + *

+ * This method provides a list of collection names, allowing developers to explore or iterate over the + * available collections in the database. It is particularly useful for dynamic scenarios where the + * collections within a database might not be predetermined or when you need to inspect the database's + * current state programmatically. + *

* - * @return - * a stream containing all the names of all the collections in this database + *

Example usage:

+ *
+     * {@code
+     * Database database = new DataAPIClient("token").getDatabase("endpoint);
+     * List collectionNames = database.listCollectionNames();
+     * collectionNames.forEach(System.out::println);
+     * }
+     * 
+ * + * @return A {@link List} containing the names of all collections in this database. + * @throws com.datastax.astra.client.exception.DataAPIException if an error occurs while retrieving the collection names. */ - public Stream listCollectionNames() { - Command findCollections = Command.create("findCollections"); - return runCommand(findCollections, this.commandOptions) - .getStatusKeyAsList("collections", String.class) - .stream(); + public List listCollectionNames() { + return listCollectionNames(null); } /** - * Finds all the collections in this database. + * Retrieves the names of all the collections present in this database, with the ability to customize + * the listing behavior using the specified {@link ListCollectionOptions}. + *

+ * This method provides a list of collection names, allowing developers to explore or iterate over + * the collections in the database. The behavior of this operation can be tailored by providing + * {@link ListCollectionOptions}, enabling filtering or additional configuration as needed. + *

* - * @return - * list of collection definitions + *

Parameters:

+ *
    + *
  • {@code listCollectionOptions} - The options to customize the collection listing operation, + * such as filtering criteria or additional query parameters.
  • + *
+ * + *

Example usage:

+ *
+     * {@code
+     * // Create list collection options
+     * ListCollectionOptions options = new ListCollectionOptions()
+     *    .timeout(Duration.ofMillis(1000));
+     *
+     * // Retrieve collection names based on options
+     * Database database = new DataAPIClient("token").getDatabase("endpoint);
+     * List collectionNames = database.listCollectionNames(options);
+     *
+     * // Print the collection names
+     * collectionNames.forEach(System.out::println);
+     * }
+     * 
+ * + * @param listCollectionOptions The {@link ListCollectionOptions} to customize the collection listing behavior. + * @return A {@link List} containing the names of all collections in this database, filtered or modified + * according to the provided options. + */ + public List listCollectionNames(ListCollectionOptions listCollectionOptions) { + return runCommand(Command.create("findCollections"), listCollectionOptions) + .getStatusKeyAsStringStream("collections") + .toList(); + } + + /** + * Retrieves all collections in this database along with their definitions. + *

+ * This method returns a list of {@link CollectionDescriptor} objects, providing detailed metadata + * about each collection, such as its name, schema, or other relevant attributes. It acts as a + * convenient entry point for obtaining all collection definitions without any filtering or additional options. + *

+ * + *

Example usage:

+ *
+     * {@code
+     * Database database = new DataAPIClient("token").getDatabase("endpoint);
+     * List collections = database.listCollections();
+     * }
+     * 
+ * + * @return A {@link List} of {@link CollectionDescriptor} objects representing all collections in this database. + */ + public List listCollections() { + return listCollections(null); + } + + /** + * Retrieves all collections in this database along with their definitions, customized by the + * specified {@link ListCollectionOptions}. + *

+ * This method allows for more fine-grained control over the collection retrieval process, enabling + * options such as filtering, limiting the number of results, or specifying additional query parameters. + * The returned list includes {@link CollectionDescriptor} objects, which provide detailed metadata + * for each collection that matches the provided options. + *

+ * + *

Parameters:

+ *
    + *
  • {@code listCollectionOptions} - The {@link ListCollectionOptions} to customize the listing behavior, + * such as filtering criteria or additional query parameters. If {@code null}, all collections are returned.
  • + *
+ * + *

Example usage:

+ *
+     * {@code
+     * // Create options for listing collections with a specific prefix
+     * ListCollectionOptions options = new ListCollectionOptions()
+     *    .timeout(Duration.ofMillis(1000));
+     *
+     * // Retrieve matching collections
+     * Database database = new DataAPIClient("token").getDatabase("endpoint);
+     * List collections = database.listCollections(options);
+     * }
+     * 
+ * + * @param listCollectionOptions The {@link ListCollectionOptions} to customize the collection retrieval process. + * If {@code null}, no filtering or additional options are applied. + * @return A {@link List} of {@link CollectionDescriptor} objects representing the collections that match the criteria. */ - public Stream listCollections() { - Command findCollections = Command - .create("findCollections") + public List listCollections(ListCollectionOptions listCollectionOptions) { + Command findCollections = Command.create("findCollections") .withOptions(new Document().append("explain", true)); - return runCommand(findCollections, this.commandOptions) - .getStatusKeyAsList("collections", CollectionDefinition.class) - .stream(); + return runCommand(findCollections, listCollectionOptions) + .getStatusKeyAsList("collections", CollectionDescriptor.class); } /** - * Evaluate if a collection exists. + * Checks if a specified collection exists in this database. + *

+ * This method evaluates whether a collection with the given name is present in the database. + * It is useful for verifying the existence of a collection before performing operations such + * as querying, inserting, or updating data. + *

* - * @param collection - * collections name. - * @return - * if collections exists + *

Example usage:

+ *
+     * {@code
+     * Database database = new DataAPIClient("token").getDatabase("endpoint");
+     * boolean exists = database.collectionExists("my_collection");
+     * if (exists) {
+     *     System.out.println("Collection exists!");
+     * } else {
+     *     System.out.println("Collection does not exist.");
+     * }
+     * }
+     * 
+ * + * @param collectionName The name of the collection to check. + * @return {@code true} if the collection exists, {@code false} otherwise. + * @throws IllegalArgumentException if the collection name is {@code null} or empty. */ - public boolean collectionExists(String collection) { - return listCollectionNames().anyMatch(collection::equals); + public boolean collectionExists(String collectionName) { + Assert.hasLength(collectionName, "collectionName"); + return listCollectionNames().contains(collectionName); } + // ------------------------------------------ + // ---- Get Collection ---- + // ------------------------------------------ + /** - * Gets a collection. + * Retrieves a {@link Collection} object for the specified collection name. + *

+ * This method provides a convenient way to obtain a {@link Collection} instance for a specific + * collection in the database. The returned object allows for further operations on the collection, + * such as querying, inserting, or updating documents. + *

* - * @param collectionName - * the name of the collection to return - * @return - * the collection - * @throws IllegalArgumentException - * if collectionName is invalid + *

Parameters:

+ *
    + *
  • {@code collectionName} - The name of the collection to retrieve. This must not be null or empty.
  • + *
+ * + *

Example usage:

+ *
+     * {@code
+     * Database database = new DataAPIClient("token").getDatabase("endpoint");
+     * Collection collection = database.getCollection("my_collection");
+     * }
+     * 
+ * + * @param collectionName The name of the collection to retrieve. + * @return A {@link Collection} object representing the specified collection. + * @throws IllegalArgumentException if the collection name is {@code null} or empty. */ public Collection getCollection(String collectionName) { return getCollection(collectionName, Document.class); } /** - * Gets a collection, with a specific default document class. + * Retrieves a {@link Collection} object for the specified collection name, with the ability to + * customize the collection behavior using the specified {@link CollectionOptions}. + *

+ * This method provides a way to obtain a {@link Collection} instance for a specific collection in + * the database, with additional options for configuring the collection's behavior. The returned object + * allows for further operations on the collection, such as querying, inserting, or updating documents. + *

* - * @param collectionName - * the name of the collection to return - * @param documentClass - * the default class to cast any documents returned from the database into. - * @param - * the type of the class to use instead of {@code Document}. - * @return - * the collection + *

Parameters:

+ *
    + *
  • {@code collectionName} - The name of the collection to retrieve. This must not be null or empty.
  • + *
  • {@code collectionOptions} - The {@link CollectionOptions} to customize the collection behavior, + * such as setting a custom serializer or specifying additional options. If {@code null}, default options are used.
  • + *
+ * + *

Example usage:

+ *
+     * {@code
+     * // Create custom collection options
+     * CollectionOptions options = new CollectionOptions()
+     *    .serializer(new MyCustomSerializer());
+     *
+     * // Retrieve the collection with custom options
+     * Database database = new DataAPIClient("token").getDatabase("endpoint");
+     * Collection collection = database.getCollection("my_collection", options);
+     * }
+     * 
+ * + * @param collectionName The name of the collection to retrieve. + * @return A {@link Collection} object representing the specified collection, configured with the provided options. + * @throws IllegalArgumentException if the collection name is {@code null} or empty. */ - public Collection getCollection(String collectionName, @NonNull Class documentClass) { - return getCollection(collectionName, this.commandOptions, documentClass); + public Collection getCollection(String collectionName, Class documentClass) { + return getCollection(collectionName, new CollectionOptions( + options.getToken(), + options.getDataAPIClientOptions()), documentClass); } /** - * Gets a collection, with a specific default document class. + * Retrieves a {@link Collection} object for the specified collection name with the ability to specify custom options. + *

+ * This method provides a flexible way to obtain a {@link Collection} instance by allowing + * the caller to specify {@link CollectionOptions} to customize the behavior of the collection. + *

* - * @param collectionName - * the name of the collection to return - * @param documentClass - * the default class to cast any documents returned from the database into. - * @param commandOptions - * options to use when using this collection - * @param - * the type of the class to use instead of {@code Document}. - * @return - * the collection + *

Parameters:

+ *
    + *
  • {@code collectionName} - The name of the collection to retrieve. This must not be null or empty.
  • + *
  • {@code collectionOptions} - A {@link CollectionOptions} object that specifies custom + * behaviors for the collection. If {@code null}, default options will be used.
  • + *
+ * + *

Example usage:

+ *
+     * {@code
+     *
+     * CollectionOptions options = new CollectionOptions()
+     *  .timeout(Duration.ofMillis(1000))
+     *  .dataAPIClientOptions(new DataAPIClientOptions())
+     *  .embeddingAuthProvider(new EmbeddingAPIKeyHeaderProvider("api-key"));
+     *
+     * Database database = new DataAPIClient("token").getDatabase("endpoint");
+     * Collection collection = database.getCollection("my_collection", options);
+     * }
+     * 
+ * + * @param collectionName The name of the collection to retrieve. + * @param collectionOptions The {@link CollectionOptions} to customize the collection behavior. + * @return A {@link Collection} object representing the specified collection. + * @throws IllegalArgumentException if {@code collectionName} is {@code null} or empty. */ - public Collection getCollection(String collectionName, CommandOptions commandOptions, @NonNull Class documentClass) { - hasLength(collectionName, "collectionName"); - notNull(documentClass, "documentClass"); - return new Collection<>(this, collectionName, commandOptions, documentClass); + public Collection getCollection(String collectionName, CollectionOptions collectionOptions) { + return getCollection(collectionName, collectionOptions, Document.class); } /** - * Create a new collection with the given name. + * Retrieves a {@link Collection} object for the specified collection name with custom options and document type. + *

+ * This method provides the most flexible way to obtain a {@link Collection} instance, allowing + * clients to specify custom options and the type of documents in the collection. + *

* - * @param collectionName - * the name for the new collection to create - * @return - * the instance of collection + *

Parameters:

+ *
    + *
  • {@code collectionName} - The name of the collection to retrieve. This must not be null or empty.
  • + *
  • {@code options} - The {@link CollectionOptions} to customize the collection behavior. Must not be null.
  • + *
  • {@code documentClass} - The {@link Class} type of the documents stored in the collection. + * This enables type safety when working with the collection's documents. Must not be null.
  • + *
+ * + *

Example usage:

+ *
+     * {@code
+     * CollectionOptions options = new CollectionOptions()
+     *  .timeout(Duration.ofMillis(1000))
+     *  .dataAPIClientOptions(new DataAPIClientOptions())
+     *  .embeddingAuthProvider(new EmbeddingAPIKeyHeaderProvider("api-key"));
+     * Collection collection = database.getCollection("my_collection", options, MyDocument.class);
+     * }
+     * 
+ * + * @param collectionName The name of the collection to retrieve. + * @param options The {@link CollectionOptions} for customizing the collection behavior. + * @param documentClass The class type of the documents in the collection. + * @return A {@link Collection} object representing the specified collection. + * @throws IllegalArgumentException if {@code collectionName}, {@code options}, or {@code documentClass} is {@code null}. */ - public Collection createCollection(String collectionName) { - return createCollection(collectionName, null, commandOptions, Document.class); + public Collection getCollection(String collectionName, CollectionOptions options, Class documentClass) { + hasLength(collectionName, "collectionName"); + notNull(options, "options"); + notNull(documentClass, "documentClass"); + return new Collection<>(this, collectionName, options, documentClass); } + // ------------------------------------------ + // ---- Create Collection ---- + // ------------------------------------------ + /** - * Create a default new collection for vector. - * @param collectionName - * collection name - * @param dimension - * vector dimension - * @param metric - * vector metric - * @return - * the instance of collection + * Creates a new collection in the database. + * + * @param The type of the documents stored in the collection. + * @param collectionName The name of the collection to be created. + * @param collectionDefinition An optional {@link CollectionDefinition} object defining the schema and other properties of the collection. + * @param collectionOptions The {@link CollectionOptions} that include token, client configuration, and serializer for the collection. + * @param createCollectionOptions Additional options for creating the collection, such as timeouts or retry policies. + * @param documentClass The class of the documents stored in the collection. + * @return The created collection as a {@link Collection} of the specified document type. + * + * @throws IllegalArgumentException If any required argument is null or invalid. + * + *

Example usage:

+ *
+     * {@code
+     * Collection collection = db.createCollection(
+     *     "myCollection",
+     *     new CollectionDefinition(),
+     *     new CollectionOptions(token, dataAPIClientOptions),
+     *     new CreateCollectionOptions(),
+     *     MyDocument.class
+     * );
+     * }
+     * 
*/ - public Collection createCollection(String collectionName, int dimension, SimilarityMetric metric) { - return createCollection(collectionName, dimension, metric, Document.class); + public Collection createCollection(String collectionName, + CollectionDefinition collectionDefinition, + Class documentClass, + CreateCollectionOptions createCollectionOptions, + CollectionOptions collectionOptions) { + + hasLength(collectionName, "collectionName"); + notNull(collectionOptions, "collectionOptions"); + notNull(documentClass, "documentClass"); + notNull(collectionOptions.getSerializer(), "serializer"); + + Command createCollectionCommand = Command + .create("createCollection") + .append("name", collectionName); + if (collectionDefinition != null) { + createCollectionCommand.withOptions(collectionOptions + .getSerializer() + .convertValue(collectionDefinition, Document.class)); + } + runCommand(createCollectionCommand, createCollectionOptions); + return getCollection(collectionName, collectionOptions, documentClass); } /** - * Create a default new collection for vector. - * @param collectionName - * collection name - * @param dimension - * vector dimension - * @param metric - * vector metric - * @param documentClass - * class of document to return - * @param - * working class for the document - * @return - * the instance of collection + * Creates a new collection with the default document type {@link Document}. + * + * @param collectionName The name of the collection to be created. + * @return The created collection as a {@link Collection} of {@link Document}. + * + *

Example usage:

+ *
+     * {@code
+     * Collection collection = db.createCollection("myDefaultCollection");
+     * }
+     * 
*/ - public Collection createCollection(String collectionName, int dimension, SimilarityMetric metric, Class documentClass) { - return createCollection(collectionName, CollectionOptions.builder() - .vectorDimension(dimension) - .vectorSimilarity(metric) - .build(), this.commandOptions, documentClass); + public Collection createCollection(String collectionName) { + return createCollection(collectionName, Document.class); } /** - * Create a new collection with the given name. + * Creates a new collection with the specified document class. * - * @param collectionName - * the name for the new collection to create - * @param documentClass - * class of document to return - * @param - * working class for the document - * @return the collection + * @param The type of the documents stored in the collection. + * @param collectionName The name of the collection to be created. + * @param documentClass The class of the documents stored in the collection. + * @return The created collection as a {@link Collection} of the specified document type. + * + *

Example usage:

+ *
+     * {@code
+     * Collection collection = db.createCollection("myTypedCollection", MyDocument.class);
+     * }
+     * 
*/ public Collection createCollection(String collectionName, Class documentClass) { - return createCollection(collectionName, null, this.commandOptions, documentClass); + return createCollection(collectionName, + // no CollectionDefinition as simple + null, + documentClass, + // No create collection options + null, + new CollectionOptions(options.getToken(), options.getDataAPIClientOptions()) + ); } /** - * Create a new collection with the given name. + * Creates a new collection with a specified definition and the default document type {@link Document}. + * + * @param name The name of the collection to be created. + * @param def The {@link CollectionDefinition} specifying the schema and other properties of the collection. + * @return The created collection as a {@link Collection} of {@link Document}. * - * @param collectionName - * the name for the new collection to create - * @param collectionOptions - * various options for creating the collection - * @return the collection + *

Example usage:

+ *
+     * {@code
+     * Collection collection = createCollection("myDefinedCollection", new CollectionDefinition());
+     * }
+     * 
*/ - public Collection createCollection(String collectionName, CollectionOptions collectionOptions) { - return createCollection(collectionName, collectionOptions, this.commandOptions, Document.class); + public Collection createCollection(String name, CollectionDefinition def) { + return createCollection(name, def, Document.class); } /** - * Create a new collection with the given name. + * Creates a new collection with a specified definition and the specified document class. * - * @param collectionName - * collection name - * @param collectionOptions - * collection options - * @param documentClass - * document class - * @return - * the collection created - * @param - * working object for the document + * @param The type of the documents stored in the collection. + * @param name The name of the collection to be created. + * @param def The {@link CollectionDefinition} specifying the schema and other properties of the collection. + * @param documentClass The class of the documents stored in the collection. + * @return The created collection as a {@link Collection} of the specified document type. + * + *

Example usage:

+ *
+     * {@code
+     * Collection collection = createCollection("myDefinedCollection",
+     *  new CollectionDefinition(), MyDocument.class);
+     * }
+     * 
*/ - public Collection createCollection(String collectionName, CollectionOptions collectionOptions, Class documentClass) { - return createCollection(collectionName, collectionOptions, this.commandOptions, documentClass); + public Collection createCollection(String name, CollectionDefinition def, Class documentClass) { + return createCollection(name, + def, + documentClass, + null, + new CollectionOptions(options.getToken(), options.getDataAPIClientOptions())); } /** - * Create a new collection with the given name. + * Creates a new collection with a specified definition, options, and the default document type {@link Document}. + * + * @param collectionName The name of the collection to be created. + * @param collectionDefinition The {@link CollectionDefinition} specifying the schema and other properties of the collection. + * @param collectionOptions The {@link CollectionOptions} that include token, client configuration, and serializer for the collection. + * @param createCollectionOptions Additional options for creating the collection, such as timeouts or retry policies. + * @return The created collection as a {@link Collection} of {@link Document}. * - * @param collectionName - * the name for the new collection to create - * @param collectionOptions - * various options for creating the collection - * @param pCommandOptions - * options to use when using this collection - * @return the collection + *

Example usage:

+ *
+     * {@code
+     * Collection collection = createCollection(
+     *     "myComplexCollection",
+     *     new CollectionDefinition(),
+     *     new CollectionOptions(token, dataAPIClientOptions),
+     *     new CreateCollectionOptions()
+     * );
+     * }
+     * 
*/ - public Collection createCollection(String collectionName, CollectionOptions collectionOptions, CommandOptions pCommandOptions) { - return createCollection(collectionName, collectionOptions, pCommandOptions, Document.class); + public Collection createCollection(String collectionName, + CollectionDefinition collectionDefinition, + CreateCollectionOptions createCollectionOptions, + CollectionOptions collectionOptions + ) { + return createCollection( + collectionName, + collectionDefinition, + Document.class, + createCollectionOptions, + collectionOptions); } + // ------------------------------------------ + // ---- Drop Collection ---- + // ------------------------------------------ + /** - * Create a new collection with the selected options + * Deletes a collection from the database. + * + * @param collectionName The name of the collection to be deleted. Must not be null or empty. + * @param dropCollectionOptions Additional options for dropping the collection, such as timeout or retry policies. * - * @param collectionName - * the name for the new collection to create - * @param collectionOptions - * various options for creating the collection - * @param documentClass - * the default class to cast any documents returned from the database into. - * @param pCommandOptions - * options to use when using this collection - * @param - * working class for the document - * @return the collection + *

Example usage:

+ *
+     * {@code
+     * db.dropCollection("myCollection", new DropCollectionOptions().timeout(Duration.ofMillis(1000)));
+     * }
+     * 
*/ - public Collection createCollection(String collectionName, CollectionOptions collectionOptions, CommandOptions pCommandOptions - , Class documentClass) { - hasLength(collectionName, "collectionName"); - notNull(documentClass, "documentClass"); - Command createCollection = Command - .create("createCollection") - .append("name", collectionName); - if (collectionOptions != null) { - createCollection.withOptions(SERIALIZER.convertValue(collectionOptions, Document.class)); - } - runCommand(createCollection, pCommandOptions); - log.info("Collection '" + green("{}") + "' has been created", collectionName); - return getCollection(collectionName, commandOptions, documentClass); + public void dropCollection(String collectionName, DropCollectionOptions dropCollectionOptions) { + runCommand(Command + .create("deleteCollection") + .append("name", collectionName), dropCollectionOptions); } /** - * Delete a collection. + * Deletes a collection from the database with default options. + * + * @param collectionName The name of the collection to be deleted. Must not be null or empty. * - * @param collectionName - * collection name + *

Example usage:

+ *
+     * {@code
+     * dropCollection("myCollection");
+     * }
+     * 
*/ public void dropCollection(String collectionName) { - runCommand(Command - .create("deleteCollection") - .append("name", collectionName), this.commandOptions); - log.info("Collection '" + green("{}") + "' has been deleted", collectionName); + dropCollection(collectionName, null); } // ------------------------------------------ - // ------- TABLES CRUD ----- + // ------- List tables ----- // ------------------------------------------ /** - * Gets the names of all the tables in this database. + * Retrieves the names of all tables in the database with default options. * - * @return - * a stream containing all the names of all the collections in this database + * @return A list of all table names in the database. + * + *

Example usage:

+ *
+     * {@code
+     * List tableNames = listTableNames();
+     * }
+     * 
*/ - public Stream listTableNames() { - return runCommand(Command.create("listTables")) - .getStatusKeyAsList("tables", String.class) - .stream(); + public List listTableNames() { + return listTableNames(null); } /** - * Finds all the tables in this database. + * Retrieves the names of all tables in the database. * - * @return - * list of table definitions + * @param listTablesOptions Options for filtering or configuring the table listing operation. + * @return A list of all table names in the database. + * + *

Example usage:

+ *
+     * {@code
+     * ListTablesOptions options = new ListTablesOptions();
+     * List tableNames = listTableNames(options);
+     * }
+     * 
+ */ + public List listTableNames(ListTablesOptions listTablesOptions) { + return runCommand(Command.create("listTables"), listTablesOptions) + .getStatusKeyAsStringStream("tables") + .toList(); + } + + /** + * Retrieves the details of all tables in the database with default options. + * + * @return A list of {@link TableDescriptor} objects representing all tables in the database. + * + *

Example usage:

+ *
+     * {@code
+     * List tables = listTables();
+     * }
+     * 
+ */ + public List listTables() { + return listTables(null); + } + + /** + * Retrieves the details of all tables in the database. + * + * @param listTableOptions Options for filtering or configuring the table listing operation. + * @return A list of {@link TableDescriptor} objects representing all tables in the database. + * + *

Example usage:

+ *
+     * {@code
+     * ListTablesOptions options = new ListTablesOptions();
+     * List tables = listTables(options);
+     * }
+     * 
*/ - public Stream listTables() { + public List listTables(ListTablesOptions listTableOptions) { Command findTables = Command .create("listTables") .withOptions(new Document().append("explain", true)); - return runCommand(findTables) - .getStatusKeyAsList("tables", TableDescriptor.class) - .stream(); + return runCommand(findTables, listTableOptions) + .getStatusKeyAsList("tables", TableDescriptor.class); } /** - * Evaluate if a collection exists. + * Checks if a table exists in the database by its name. * - * @param tableName - * table name. - * @return - * if collections exists + * @param tableName The name of the table to check. Must not be null or empty. + * @return {@code true} if the table exists, {@code false} otherwise. + * + * @throws IllegalArgumentException if {@code tableName} is null or empty. + * + *

Example usage:

+ *
+     * {@code
+     * boolean exists = tableExists("myTable");
+     * if (exists) {
+     *     System.out.println("The table exists.");
+     * } else {
+     *     System.out.println("The table does not exist.");
+     * }
+     * }
+     * 
*/ public boolean tableExists(String tableName) { - return listTableNames().anyMatch(tableName::equals); + Assert.hasLength(tableName, "tableName"); + return listTableNames().contains(tableName); } + // ------------------------------------------ + // ---- Get Table ---- + // ------------------------------------------ + /** - * Gets a collection. + * Retrieves a table representation for the specified table name, table options, and row class type. + * This is the primary method to obtain a typed table instance. * - * @param tableName - * the name of the table to return - * @return - * the collection - * @throws IllegalArgumentException - * if collectionName is invalid + * @param the type of the row objects + * @param tableName the name of the table (must not be null or empty) + * @param tableOptions options used to configure the table (e.g., connection options) + * @param rowClass the class representing the type of rows in the table (must not be null) + * @return a {@code Table} instance for the specified configuration + * + *

Example usage:

+ *
+     * {@code
+     * Table table = db.getTable("my_table", new TableOptions(...), MyRowType.class);
+     * }
+     * 
+ */ + public Table getTable(String tableName, TableOptions tableOptions, Class rowClass) { + hasLength(tableName, "tableName"); + notNull(rowClass, "rowClass"); + return new Table<>(this, tableName, tableOptions, rowClass); + } + + /** + * Retrieves a table representation for the specified table name with default {@code TableOptions}. + * + * @param tableName the name of the table (must not be null or empty) + * @return a {@code Table} instance representing a generic table with {@code Row} type rows + * @throws IllegalArgumentException if {@code tableName} is null or empty + * + *

Example usage:

+ *
+     * {@code
+     * Table table = db.getTable("my_table");
+     * }
+     * 
*/ public Table getTable(String tableName) { - return getTable(tableName, this.commandOptions, Row.class); + return getTable(tableName, Row.class); } /** - * Gets a table, with a specific default document class. + * Retrieves a table representation for the specified table name and row class type with default {@code TableOptions}. * - * @param tableName - * the name of the collection to return - * @param rowClass - * the default class to cast any row returned from the database into. - * @param - * the type of the class to use instead of {@code Document}. - * @return - * the collection + * @param the type of the row objects + * @param tableName the name of the table (must not be null or empty) + * @param rowClass the class representing the type of rows in the table (must not be null) + * @return a {@code Table} instance for the specified configuration + * @throws IllegalArgumentException if {@code tableName} is null or empty + * @throws NullPointerException if {@code rowClass} is null + * + *

Example usage:

+ *
+     * {@code
+     * Table table = myFramework.getTable("my_table", MyRowType.class);
+     * }
+     * 
*/ - public Table getTable(String tableName, @NonNull Class rowClass) { - return getTable(tableName, this.commandOptions, rowClass); + public Table getTable(String tableName, Class rowClass) { + return getTable(tableName, new TableOptions( + options.getToken(), + options.getDataAPIClientOptions()), rowClass); } - public Table getTable(@NonNull Class rowClass) { + /** + * Retrieves a table representation for the specified table name and {@code TableOptions}, defaulting to {@code Row} type rows. + * + * @param tableName the name of the table (must not be null or empty) + * @param tableOptions options used to configure the table (e.g., connection options) + * @return a {@code Table} instance representing a generic table with {@code Row} type rows + * @throws IllegalArgumentException if {@code tableName} is null or empty + * @throws NullPointerException if {@code tableOptions} is null + * + *

Example usage:

+ *
+     * {@code
+     * Table table = myFramework.getTable("my_table", new TableOptions(...));
+     * }
+     * 
+ */ + public Table getTable(String tableName, TableOptions tableOptions) { + return getTable(tableName, tableOptions, Row.class); + } + + /** + * Retrieves a table representation for a row class annotated with {@link EntityTable}. + * The table name is inferred from the {@code value} attribute of the {@code EntityTable} annotation. + * + * @param the type of the row objects + * @param rowClass the class representing the type of rows in the table (must be annotated with {@link EntityTable}) + * @return a {@code Table} instance for the inferred table name and row type + * @throws InvalidConfigurationException if the provided class is not annotated with {@link EntityTable} + * + *

Example usage:

+ *
+     * {@code
+     * @EntityTable("my_table")
+     * public class MyRowType { ... }
+     *
+     * Table table = myFramework.getTable(MyRowType.class);
+     * }
+     * 
+ */ + public Table getTable(Class rowClass) { EntityTable ann = rowClass.getAnnotation(EntityTable.class); if (ann == null) { InvalidConfigurationException.throwErrorMissingAnnotation( @@ -545,150 +1130,179 @@ public Table getTable(@NonNull Class rowClass) { rowClass.getName(), "getTable(rowClass)"); } - return getTable(ann.value(), this.commandOptions, rowClass); + return getTable(ann.value(), rowClass); } + // ------------------------------------- + // ---- Create Table ---- + // ------------------------------------- + /** - * Gets a table with a specific default document class. + * Creates a table in the system with the specified parameters. * - * @param tableName - * the name of the table to - * @param rowClass - * the default class to cast any row returned from the database into. - * @param commandOptions - * options to use when using this table - * @param - * the type of the class to use instead of {@code Row}. - * @return - * the table + * @param the type of the row objects that the table will hold + * @param tableName the name of the table to be created; must not be null or empty + * @param tableDefinition the schema definition of the table; must not be null + * @param creatTableOptions additional options for creating the table; optional, can be null + * @param rowClass the class representing the row type; must not be null + * @param tableOptions runtime options for interacting with the table; must not be null + * @return the created table object + * @throws IllegalArgumentException if any mandatory argument is null or invalid + * + *

Example usage:

+ *
+     * {@code
+     * TableDefinition tableDefinition = new TableDefinition()
+     *  .addColumnText("match_id")
+     *  .addColumnInt("round")
+     *  .addColumnVector("m_vector", new ColumnDefinitionVector().dimension(3).metric(COSINE))
+     *  .addColumn("score", ColumnTypes.INT)
+     *  .addColumn("when", ColumnTypes.TIMESTAMP)
+     *  .addColumn("winner", ColumnTypes.TEXT)
+     *  .addColumnSet("fighters", ColumnTypes.UUID)
+     *  .addPartitionBy("match_id")
+     *  .addPartitionSort(Sort.ascending("round"));
+     *
+     * // Optional
+     * CreateTableOptions createTableOptions =
+     *      new CreateTableOptions().timeout(Duration.ofMillis(1000));
+     *
+     * // Optional to override spawn options
+     * TableOptions tableOptions =
+     *      new TableOptions().timeout(Duration.ofMillis(1000));
+     *
+     * Table tableSimple2 = db.createTable("TABLE_SIMPLE", tableDefinition,
+     *  Row.class, createTableOptions,  tableOptions);
+     * }
+     * 
*/ - public Table getTable(String tableName, CommandOptions commandOptions, @NonNull Class rowClass) { + public Table createTable(String tableName, + TableDefinition tableDefinition, + Class rowClass, + CreateTableOptions creatTableOptions, + TableOptions tableOptions) { hasLength(tableName, "tableName"); + notNull(tableDefinition, "tableDefinition"); notNull(rowClass, "rowClass"); - return new Table<>(this, tableName, commandOptions, rowClass); + Command createTable = Command + .create("createTable") + .append("name", tableName) + .append("definition", tableDefinition); + if (creatTableOptions != null) { + createTable.append("options", creatTableOptions); + } + runCommand(createTable, tableOptions); + return getTable(tableName, tableOptions, rowClass); } /** - * Create a new table with the given description. + * Creates a table using default options and runtime configurations. * - * @param tableName - * the name of the table to create - * @param tableDefinition - * table definition + * @param the type of the row objects that the table will hold + * @param tableName the name of the table to be created; must not be null or empty + * @param tableDefinition the schema definition of the table; must not be null + * @param rowClass the class representing the row type; must not be null + * @return the created table object */ - public Table createTable(String tableName, TableDefinition tableDefinition) { - return createTable(tableName, tableDefinition, null, this.commandOptions, Row.class); + public Table createTable(String tableName, TableDefinition tableDefinition, Class rowClass) { + return createTable(tableName, tableDefinition, rowClass, new CreateTableOptions(), + new TableOptions(this.options.getToken(), this.options.getDataAPIClientOptions())); } /** - * Create a new table with the given description. + * Creates a table with a default row type of {@code Row}. * - * @param tableName - * the name for the new table to create - * @param tableDefinition - * table definition - * @param options - * collection options + * @param tableName the name of the table to be created; must not be null or empty + * @param tableDefinition the schema definition of the table; must not be null + * @return the created table object with rows of type {@code Row} */ - public Table createTable(String tableName, TableDefinition tableDefinition, CreateTableOptions options) { - return createTable(tableName, tableDefinition, options, commandOptions, Row.class); + public Table createTable(String tableName, TableDefinition tableDefinition) { + return createTable(tableName, tableDefinition, Row.class); } /** - * Create a new table with the given description. + * Creates a table using the specified row class and runtime configurations. * - * @param tableName - * the table name - * @param tableDefinition - * table definition - * @param options - * collection options - * @param documentClass - * document class - * @return - * the collection created - * @param - * working object for the document + * @param the type of the row objects that the table will hold + * @param tableName the name of the table to be created; must not be null or empty + * @param rowClass the class representing the row type; must not be null + * @param tableDefinition the schema definition of the table; must not be null + * @param createTableOptions additional options for creating the table; optional, can be null + * @return the created table object */ - public Table createTable(String tableName, TableDefinition tableDefinition, CreateTableOptions options, Class documentClass) { - return createTable(tableName, tableDefinition, options, commandOptions, documentClass); + public Table createTable(String tableName, TableDefinition tableDefinition, Class rowClass, CreateTableOptions createTableOptions) { + return createTable(tableName, tableDefinition, rowClass, createTableOptions, + new TableOptions(this.options.getToken(), this.options.getDataAPIClientOptions())); } /** - * Create a new collection with the given name. + * Creates a table using the specified row class and runtime configurations. * - * @param tableName - * the definition for the new table to create - * @param tableDefinition - * the definition for the new table to create - * @param tableOptions - * various options for creating the table - * @param commandOptions - * options to use when using this collection - * @return the collection + * @param tableName the name of the table to be created; must not be null or empty + * @param createTableOptions additional options for creating the table; optional, can be null + * @param tableDefinition the schema definition of the table; must not be null + * @return the created table object */ - public Table createTable(String tableName, TableDefinition tableDefinition, CreateTableOptions tableOptions, CommandOptions commandOptions) { - return createTable(tableName, tableDefinition, tableOptions, commandOptions, Row.class); + public Table createTable(String tableName, TableDefinition tableDefinition, CreateTableOptions createTableOptions) { + return createTable(tableName, tableDefinition, Row.class, createTableOptions); } + /** + * Creates a table using default options and the inferred table name from the row class. + * + * @param the type of the row objects that the table will hold + * @param rowClass the class representing the row type; must not be null + * @return the created table object + */ public Table createTable(Class rowClass) { - return createTable(getTableName(rowClass), rowClass); - } - - public Table createTable(@NonNull Class rowClass, CreateTableOptions tableOptions) { - return createTable(getTableName(rowClass), rowClass, tableOptions); + return createTable(rowClass, new CreateTableOptions()); } - public Table createTable(String tableName, @NonNull Class rowClass) { - return createTable(tableName, rowClass, null); + /** + * Creates a table using default options and the inferred table name from the row class. + * + * @param the type of the row objects that the table will hold + * @param rowClass the class representing the row type; must not be null + * @param createTableOptions additional options for creating the table; optional, can be null + * @return the created table object + */ + public Table createTable(Class rowClass, CreateTableOptions createTableOptions) { + return createTable(getTableName(rowClass), rowClass, createTableOptions, new TableOptions()); } - public Table createTable(String tableName, @NonNull Class rowClass, CreateTableOptions tableOptions) { + /** + * Creates a table using default options and runtime configurations. + * + * @param the type of the row objects that the table will hold + * @param rowClass the class representing the row type; must not be null + * @param tableName the name of the table to be created; must not be null or empty + * @param tableOptions runtime options for interacting with the table; must not be null + * @param createTableOptions additional options for creating the table; optional, can be null + * @return the created table object + */ + public Table createTable(String tableName, + Class rowClass, + CreateTableOptions createTableOptions, + TableOptions tableOptions) { hasLength(tableName, "tableName"); notNull(rowClass, "rowClass"); + // FIX ME INVESTIGATING TO CREATE A TABLE DEFINITION OBJECT Command createTable = new Command("createTable", createTableCommand(tableName, rowClass)); - if (tableOptions != null) { - createTable.append("options", tableOptions); + if (createTableOptions != null) { + createTable.append("options", createTableOptions); } - runCommand(createTable, commandOptions); - log.info("Table '" + green("{}") + "' has been created", tableName); - return getTable(tableName, commandOptions, rowClass); + runCommand(createTable, createTableOptions); + return getTable(tableName, tableOptions, rowClass); } /** - * Create a new table with the selected options + * Creates a table using default options and runtime configurations. * - * @param tableName - * the definition for the new table to create - * @param tableDefinition - * the definition for the new table to create - * @param tableOptions - * various options for creating the table - * @param rowClass - * the default class to cast any row returned from the database into. - * @param commandOptions - * options to use when using this collection - * @param - * working class for the document - * @return the collection - */ - public Table createTable(String tableName, TableDefinition tableDefinition, CreateTableOptions tableOptions, CommandOptions commandOptions, Class rowClass) { - hasLength(tableName, "tableName"); - notNull(tableDefinition, "tableDefinition"); - notNull(rowClass, "rowClass"); - Command createTable = Command - .create("createTable") - .append("name", tableName) - .append("definition", tableDefinition); - if (tableOptions != null) { - createTable.append("options", tableOptions); - } - runCommand(createTable, commandOptions); - log.info("Table '" + green("{}") + "' has been created", tableName); - return getTable(tableName, commandOptions, rowClass); - } - - private String getTableName(Class rowClass) { + * @param the type of the row objects that the table will hold + * @param rowClass the class representing the row type; must not be null + * @return the created table object + */ + public String getTableName(Class rowClass) { notNull(rowClass, "rowClass"); EntityTable ann = rowClass.getAnnotation(EntityTable.class); if (ann == null) { @@ -700,21 +1314,48 @@ private String getTableName(Class rowClass) { return ann.value(); } + // ------------------------------------- + // ---- Drop Table ---- + // ------------------------------------- + /** - * Delete a collection. + * Deletes a collection (table) from the database. + * This method delegates to {@link #dropTable(String, DropTableOptions)} + * with default options. * * @param tableName - * table name + * the name of the table to be deleted; must not be null or empty. + * @throws IllegalArgumentException + * if {@code tableName} is null or empty. + * + *

Example usage:

+ *
+     * {@code
+     * database.dropTable("exampleTable");
+     * }
+     * 
*/ public void dropTable(String tableName) { dropTable(tableName, null); } /** - * Delete a collection. + * Deletes a collection (table) from the database with specific options. * * @param tableName - * table name + * the name of the table to be deleted; must not be null or empty. + * @param dropTableOptions + * the options to configure the table deletion operation; can be null. + * @throws IllegalArgumentException + * if {@code tableName} is null or empty. + * + *

Example usage:

+ *
+     * {@code
+     * DropTableOptions options = new DropTableOptions();
+     * database.dropTable("exampleTable", options);
+     * }
+     * 
*/ public void dropTable(String tableName, DropTableOptions dropTableOptions) { hasLength(tableName, "tableName"); @@ -724,41 +1365,12 @@ public void dropTable(String tableName, DropTableOptions dropTableOptions) { if (dropTableOptions != null) { dropTableCmd.withOptions(dropTableOptions); } - runCommand(dropTableCmd, commandOptions); - log.info("Table '" + green("{}") + "' has been deleted", tableName); + runCommand(dropTableCmd, dropTableOptions); } - // ------------------------------------------ - // ---- Indexes CRUD --- + // ---- Drop Indexes --- // ------------------------------------------ - /** - * Gets the names of indices in the selected keyspace. - * - * @return - * a stream containing all the names of all the collections in this database - */ - public Stream listIndexesNames() { - return runCommand(Command.create("listIndexes")) - .getStatusKeyAsList("indexes", String.class) - .stream(); - } - - /** - * Finds all the indices in the selected keyspace. - * - * @return - * list of table definitions - */ - public Stream listIndexes() { - Command findTables = Command - .create("listIndexes") - .withOptions(new Document().append("explain", true)); - return runCommand(findTables) - .getStatusKeyAsList("indexes", IndexDescriptor.class) - .stream(); - } - /** * Delete an index by name. * @@ -778,69 +1390,23 @@ public void dropTableIndex(String indexName) { * flag to drop index */ public void dropTableIndex(String indexName, DropTableIndexOptions dropIndexOptions) { - Command dropIndexCommand = Command.create("dropIndex").append("name", indexName); + Command dropIndexCommand = Command + .create("dropIndex") + .append("name", indexName); if (dropIndexOptions != null) { dropIndexCommand.withOptions(dropIndexOptions); } - runCommand(dropIndexCommand, commandOptions); - log.info("Index '" + green("{}") + "' has been dropped", indexName); + runCommand(dropIndexCommand, dropIndexOptions); } // ------------------------------------------ // ---- Generation Information ---- // ------------------------------------------ - /** {@inheritDoc} */ - @Override - protected DataAPISerializer getSerializer() { - return SERIALIZER; - } - /** {@inheritDoc} */ @Override public String getApiEndpoint() { - StringBuilder dbApiEndPointBuilder = new StringBuilder(dbApiEndpoint); - // Adding /api/json if needed for Astra. - switch(options.getDestination()) { - case ASTRA: - case ASTRA_TEST: - case ASTRA_DEV: - if (dbApiEndpoint.endsWith(".com")) { - dbApiEndPointBuilder.append("/api/json"); - } - break; - default: - // left blank as local deployments does not require any change - break; - } - return dbApiEndPointBuilder - .append("/") - .append(options.getApiVersion()) - .append("/") - .append(keyspaceName) - .toString(); - } - - /** - * Register a listener to execute commands on the collection. Please now use {@link CommandOptions}. - * - * @param logger - * name for the logger - * @param commandObserver - * class for the logger - */ - public void registerListener(String logger, CommandObserver commandObserver) { - this.commandOptions.registerObserver(logger, commandObserver); - } - - /** - * Register a listener to execute commands on the collection. Please now use {@link CommandOptions}. - * - * @param name - * name for the observer - */ - public void deleteListener(String name) { - this.commandOptions.unregisterObserver(name); + return this.apiEndpoint; } } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/databases/DatabaseInfo.java b/astra-db-java/src/main/java/com/datastax/astra/client/databases/DatabaseInfo.java index 636e45b6..60b813b5 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/databases/DatabaseInfo.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/databases/DatabaseInfo.java @@ -24,6 +24,7 @@ import com.dtsx.astra.sdk.db.domain.CloudProviderType; import com.dtsx.astra.sdk.db.domain.Database; import com.dtsx.astra.sdk.db.domain.Datacenter; +import com.dtsx.astra.sdk.utils.AstraEnvironment; import lombok.Getter; import lombok.Setter; @@ -44,25 +45,19 @@ public class DatabaseInfo { private String name; /** cloud provider. */ - private CloudProviderType cloud; + private CloudProviderType cloudProvider; - /** Main Region for the database. */ - private String region; - - /** Default namespace for the database. */ - @Deprecated - private String namespace; + /** Astra Environment. */ + private AstraEnvironment environment; /** Default keyspace for the database. */ - @Deprecated private String keyspace; - /** List of Namespace for the db. */ - @Deprecated - private String namespaceList; - private String keyspaceList; + /** Main Region for the database. */ + private String region; + /** List of regions where the database is deployed. */ private Set regionList; @@ -82,18 +77,20 @@ public class DatabaseInfo { public DatabaseInfo(Database db) { Assert.notNull(db, "Database"); this.rawDevopsResponse = db; - this.id = UUID.fromString(db.getId()); - this.name = db.getInfo().getName(); - this.keyspace = db.getInfo().getKeyspace(); - this.region = db.getInfo().getRegion(); - this.cloud = db.getInfo().getCloudProvider(); + this.id = UUID.fromString(db.getId()); + this.name = db.getInfo().getName(); + this.keyspace = db.getInfo().getKeyspace(); + this.region = db.getInfo().getRegion(); + this.cloudProvider = db.getInfo().getCloudProvider(); + + // FIX ME + //this.status; + //this.environment = db.getInfo().g + this.creationTime = db.getCreationTime(); - this.regionList = db.getInfo().getDatacenters().stream() + this.regionList = db.getInfo().getDatacenters().stream() .map(Datacenter::getRegion).collect(Collectors.toSet()); this.keyspaceList = String.join(",", db.getInfo().getKeyspaces()); - - this.namespace = keyspace; - this.namespaceList = keyspaceList; } } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/databases/DatabaseOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/databases/DatabaseOptions.java new file mode 100644 index 00000000..d89707fe --- /dev/null +++ b/astra-db-java/src/main/java/com/datastax/astra/client/databases/DatabaseOptions.java @@ -0,0 +1,110 @@ +package com.datastax.astra.client.databases; + +/*- + * #%L + * Data API Java Client + * -- + * Copyright (C) 2024 DataStax + * -- + * Licensed under the Apache License, Version 2.0 + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.datastax.astra.client.core.commands.BaseOptions; +import com.datastax.astra.client.core.commands.CommandType; +import com.datastax.astra.client.core.options.DataAPIClientOptions; +import com.datastax.astra.internal.serdes.DataAPISerializer; +import com.datastax.astra.internal.serdes.DatabaseSerializer; +import com.datastax.astra.internal.utils.Assert; +import lombok.Setter; +import lombok.experimental.Accessors; + +/** + * Represents the configuration options required to connect to a database. This class encapsulates + * various settings, such as authentication details, API version, and keyspace configuration, + * enabling flexible and customized database connections. + * + *

If not explicitly provided, default options will be used. This ensures ease of use for + * developers while still allowing fine-grained control over the connection configuration when needed.

+ * + *

This class is annotated with {@code @Setter} and {@code @Accessors(fluent = true, chain = true)}, + * enabling a fluent, chainable API for setting properties.

+ * + *

Key Features:

+ *
    + *
  • Fluent setters for convenient configuration of options.
  • + *
  • Support for default configurations when options are not specified.
  • + *
  • Encapsulation of essential parameters such as keyspace, API version, and authentication token.
  • + *
+ * + *

Example usage:

+ *
+ * {@code
+ * // Create a DatabaseOptions object with custom settings
+ * DatabaseOptions options = new DatabaseOptions()
+ *         .keyspace("my_keyspace")
+ *         .authToken("my_auth_token")
+ *         .apiVersion("v2")
+ *         .logRequests(true);
+ *
+ * // Use the options when initializing a Database instance
+ * Database database = new Database("https://my-endpoint.com", options);
+ * }
+ * 
+ */ +@Setter +@Accessors(fluent = true, chain = true) +public class DatabaseOptions extends BaseOptions implements Cloneable { + + /** Serializer for the Collections. */ + private static final DataAPISerializer DEFAULT_SERIALIZER = new DatabaseSerializer(); + + /** + * The keyspace to use for the database. + */ + String keyspace = DataAPIClientOptions.DEFAULT_KEYSPACE; + + /** + * Default constructor. + */ + public DatabaseOptions() { + this(null, null); + } + + /** + * Constructor with options and not token override. + * + * @param options + * data API client options + */ + public DatabaseOptions(String token, DataAPIClientOptions options) { + super(token, CommandType.KEYSPACE_ADMIN, DEFAULT_SERIALIZER, options); + } + + /** + * Gets keyspace + * + * @return value of keyspace + */ + public String getKeyspace() { + return keyspace; + } + + @Override + public DatabaseOptions clone() { + // Cloning options, token, and serializer + DatabaseOptions cloned = (DatabaseOptions) super.clone(); + cloned.keyspace = keyspace; + return cloned; + } +} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/databases/options/CreateCollectionOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/databases/options/CreateCollectionOptions.java new file mode 100644 index 00000000..9582ece9 --- /dev/null +++ b/astra-db-java/src/main/java/com/datastax/astra/client/databases/options/CreateCollectionOptions.java @@ -0,0 +1,32 @@ +package com.datastax.astra.client.databases.options; + +/*- + * #%L + * Data API Java Client + * -- + * Copyright (C) 2024 DataStax + * -- + * Licensed under the Apache License, Version 2.0 + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.datastax.astra.client.core.commands.BaseOptions; + +import static com.datastax.astra.client.core.commands.CommandType.COLLECTION_ADMIN; + +public class CreateCollectionOptions extends BaseOptions { + + public CreateCollectionOptions() { + super(null, COLLECTION_ADMIN, null); + } +} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/databases/options/DropCollectionOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/databases/options/DropCollectionOptions.java new file mode 100644 index 00000000..1f0cd7e2 --- /dev/null +++ b/astra-db-java/src/main/java/com/datastax/astra/client/databases/options/DropCollectionOptions.java @@ -0,0 +1,33 @@ +package com.datastax.astra.client.databases.options; + +/*- + * #%L + * Data API Java Client + * -- + * Copyright (C) 2024 DataStax + * -- + * Licensed under the Apache License, Version 2.0 + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.datastax.astra.client.core.commands.BaseOptions; + +import static com.datastax.astra.client.collections.Collection.DEFAULT_COLLECTION_SERIALIZER; +import static com.datastax.astra.client.core.commands.CommandType.COLLECTION_ADMIN; + +public class DropCollectionOptions extends BaseOptions { + + public DropCollectionOptions() { + super(null, COLLECTION_ADMIN, DEFAULT_COLLECTION_SERIALIZER, null); + } +} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/databases/options/ListCollectionOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/databases/options/ListCollectionOptions.java new file mode 100644 index 00000000..8bdf26b8 --- /dev/null +++ b/astra-db-java/src/main/java/com/datastax/astra/client/databases/options/ListCollectionOptions.java @@ -0,0 +1,34 @@ +package com.datastax.astra.client.databases.options; + +/*- + * #%L + * Data API Java Client + * -- + * Copyright (C) 2024 DataStax + * -- + * Licensed under the Apache License, Version 2.0 + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.datastax.astra.client.core.commands.BaseOptions; +import com.datastax.astra.internal.serdes.collections.DocumentSerializer; +import com.datastax.astra.internal.serdes.tables.RowSerializer; + +import static com.datastax.astra.client.core.commands.CommandType.COLLECTION_ADMIN; + +public class ListCollectionOptions extends BaseOptions { + + public ListCollectionOptions() { + super(null, COLLECTION_ADMIN, new DocumentSerializer(), null); + } +} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/databases/options/ListIndexesOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/databases/options/ListIndexesOptions.java new file mode 100644 index 00000000..fc0edd93 --- /dev/null +++ b/astra-db-java/src/main/java/com/datastax/astra/client/databases/options/ListIndexesOptions.java @@ -0,0 +1,33 @@ +package com.datastax.astra.client.databases.options; + +/*- + * #%L + * Data API Java Client + * -- + * Copyright (C) 2024 DataStax + * -- + * Licensed under the Apache License, Version 2.0 + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.datastax.astra.client.core.commands.BaseOptions; +import com.datastax.astra.internal.serdes.tables.RowSerializer; + +import static com.datastax.astra.client.core.commands.CommandType.TABLE_ADMIN; + +public class ListIndexesOptions extends BaseOptions { + + public ListIndexesOptions() { + super(null, TABLE_ADMIN, new RowSerializer(), null); + } +} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/databases/options/ListTablesOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/databases/options/ListTablesOptions.java new file mode 100644 index 00000000..ba4b0d76 --- /dev/null +++ b/astra-db-java/src/main/java/com/datastax/astra/client/databases/options/ListTablesOptions.java @@ -0,0 +1,34 @@ +package com.datastax.astra.client.databases.options; + +/*- + * #%L + * Data API Java Client + * -- + * Copyright (C) 2024 DataStax + * -- + * Licensed under the Apache License, Version 2.0 + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.datastax.astra.client.core.commands.BaseOptions; +import com.datastax.astra.internal.serdes.tables.RowSerializer; + +import static com.datastax.astra.client.core.commands.CommandType.COLLECTION_ADMIN; +import static com.datastax.astra.client.core.commands.CommandType.TABLE_ADMIN; + +public class ListTablesOptions extends BaseOptions { + + public ListTablesOptions() { + super(null, TABLE_ADMIN, new RowSerializer(), null); + } +} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/exception/DataAPIErrorDescriptor.java b/astra-db-java/src/main/java/com/datastax/astra/client/exception/DataAPIErrorDescriptor.java index 7e832d30..d22c8b59 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/exception/DataAPIErrorDescriptor.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/exception/DataAPIErrorDescriptor.java @@ -20,6 +20,8 @@ * #L% */ +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Getter; import lombok.Setter; @@ -30,6 +32,7 @@ */ @Getter @Setter +@JsonIgnoreProperties({"stackTrace", "suppressed", "cause", "localizedMessage"}) public class DataAPIErrorDescriptor extends RuntimeException { /** @@ -77,6 +80,7 @@ public DataAPIErrorDescriptor() { * @return A concatenated string representing the full error message, which may include the exception class name, * the error code in parentheses, and the detailed error message. Each element is included only if it is not {@code null}. */ + @JsonIgnore public String getErrorMessage() { StringBuilder sb = new StringBuilder(); if (exceptionClass != null) { @@ -90,4 +94,11 @@ public String getErrorMessage() { } return sb.toString().trim(); } + + @Override + @JsonIgnore + public String getLocalizedMessage() { + return super.getLocalizedMessage(); + } + } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/Table.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/Table.java index 0bec2789..03b46b6c 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/Table.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/Table.java @@ -20,17 +20,20 @@ * #L% */ +import com.datastax.astra.client.collections.CollectionDefinition; import com.datastax.astra.client.collections.options.CollectionFindOptions; import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.core.commands.Command; -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import com.datastax.astra.client.core.commands.CommandType; import com.datastax.astra.client.core.options.DataAPIClientOptions; import com.datastax.astra.client.core.paging.TableCursor; import com.datastax.astra.client.core.paging.Page; import com.datastax.astra.client.core.query.Filter; import com.datastax.astra.client.databases.Database; +import com.datastax.astra.client.databases.options.ListIndexesOptions; import com.datastax.astra.client.exception.DataAPIException; +import com.datastax.astra.client.tables.index.TableIndexDescriptor; import com.datastax.astra.client.tables.options.CountRowsOptions; import com.datastax.astra.client.tables.options.EstimatedCountRowsOptions; import com.datastax.astra.client.tables.options.TableDeleteManyOptions; @@ -47,8 +50,8 @@ import com.datastax.astra.client.tables.ddl.CreateIndexOptions; import com.datastax.astra.client.tables.ddl.CreateVectorIndexOptions; import com.datastax.astra.client.tables.exceptions.TooManyRowsToCountException; -import com.datastax.astra.client.tables.index.IndexDefinition; -import com.datastax.astra.client.tables.index.VectorIndexDefinition; +import com.datastax.astra.client.tables.index.TableIndexDefinition; +import com.datastax.astra.client.tables.index.TableVectorIndexDefinition; import com.datastax.astra.client.tables.mapping.EntityBeanDefinition; import com.datastax.astra.client.tables.mapping.EntityTable; import com.datastax.astra.client.tables.results.TableUpdateResult; @@ -80,6 +83,8 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import static com.datastax.astra.client.core.options.DataAPIClientOptions.MAX_CHUNK_SIZE; +import static com.datastax.astra.client.core.options.DataAPIClientOptions.MAX_COUNT; import static com.datastax.astra.client.core.types.DataAPIKeywords.SORT_VECTOR; import static com.datastax.astra.client.exception.DataAPIException.ERROR_CODE_INTERRUPTED; import static com.datastax.astra.client.exception.DataAPIException.ERROR_CODE_TIMEOUT; @@ -94,18 +99,10 @@ * Execute commands against tables */ @Slf4j -public class Table extends AbstractCommandRunner { +public class Table extends AbstractCommandRunner { - /** parameters names. */ - private static final String ARG_TABLE_NAME = "tableName"; - - /** parameters names. */ - private static final String ROW = "row"; - - /** Serializer for the table. */ - public static final RowSerializer SERIALIZER = new RowSerializer(); - - // -- Json Outputs + /** Avoid duplicating for each operation if not override. */ + public static final DataAPISerializer DEFAULT_TABLE_SERIALIZER = new RowSerializer(); /** table identifier. */ @Getter @@ -119,12 +116,10 @@ public class Table extends AbstractCommandRunner { @Getter private final Database database; - /** Get global Settings for the client. */ - @Getter - private final DataAPIClientOptions dataAPIClientOptions; - - /** Api Endpoint for the Database, if using an astra environment it will contain the database id and the database region. */ - private final String apiEndpoint; + /** + * Collection definition loaded once. + */ + private CollectionDefinition collectionDefinition; /** * Constructs an instance of a table within the specified database. This constructor @@ -139,11 +134,11 @@ public class Table extends AbstractCommandRunner { * @param tableName A {@code String} that uniquely identifies the table within the * database. This name is used to route operations to the correct * table and should adhere to the database's naming conventions. - * @param clazz The {@code Class} object that represents the model for rows within + * @param rowClass The {@code Class} object that represents the model for rows within * this table. This class is used for serialization and deserialization of * rows to and from the database. It ensures type safety and facilitates * the mapping of database rows to Java objects. - * @param commandOptions the options to apply to the command operation. If left blank the default table + * @param tableOptions the options to apply to the command operation. If left blank the default table * *

Example usage:

*
@@ -157,18 +152,15 @@ public class Table  extends AbstractCommandRunner {
      * }
      * 
*/ - public Table(Database db, String tableName, CommandOptions commandOptions, Class clazz) { - notNull(db, ARG_DATABASE); - notNull(clazz, ARG_CLAZZ); - hasLength(tableName, ARG_TABLE_NAME); - this.tableName = tableName; - this.database = db; - this.dataAPIClientOptions = db.getOptions(); - this.rowClass = clazz; - this.commandOptions = commandOptions; - // Defaulting command types to DATA - this.commandOptions.commandType(CommandType.DATA); - this.apiEndpoint = db.getApiEndpoint() + "/" + tableName; + public Table(Database db, String tableName, TableOptions tableOptions, Class rowClass) { + super(db.getApiEndpoint() + "/" + tableName, tableOptions); + hasLength(tableName, "collection name"); + notNull(rowClass, "rowClass"); + notNull(tableOptions, "table options"); + this.tableName = tableName; + this.database = db; + this.rowClass = rowClass; + this.options.serializer(DEFAULT_TABLE_SERIALIZER); } // ---------------------------- @@ -196,7 +188,7 @@ public Table(Database db, String tableName, CommandOptions commandOptions, Cl * */ public String getKeyspaceName() { - return getDatabase().getKeyspaceName(); + return getDatabase().getKeyspace(); } /** @@ -229,7 +221,7 @@ public String getKeyspaceName() { */ public TableDefinition getDefinition() { return database - .listTables() + .listTables().stream() .filter(col -> col.getName().equals(tableName)) .findFirst() .map(TableDescriptor::getDefinition) @@ -249,18 +241,6 @@ public String getName() { return tableName; } - /** {@inheritDoc} */ - @Override - protected DataAPISerializer getSerializer() { - return SERIALIZER; - } - - /** {@inheritDoc} */ - @Override - protected String getApiEndpoint() { - return apiEndpoint; - } - // -------------------------- // --- alterTable ---- // -------------------------- @@ -276,44 +256,18 @@ public final void alter(AlterTableOperation operation, AlterTableOptions options if (options != null) { alterTable.append("options", options); } - runCommand(alterTable, commandOptions); + runCommand(alterTable, this.options); } public final Table alter(AlterTableOperation operation, AlterTableOptions options, Class clazz) { alter(operation, options); - return new Table<>(database, tableName, commandOptions, clazz); + return new Table<>(database, tableName, this.options, clazz); } // -------------------------- // --- createIndex ---- // -------------------------- - /** - * Create a new index with the given description. - * - * @param idxName - * name of the index - * @param idxDefinition - * definition of the index - */ - public void createIndex(String idxName, IndexDefinition idxDefinition) { - createIndex(idxName, idxDefinition, null, commandOptions); - } - - /** - * Create a new index with the given description. - * - * @param idxName - * name of the index - * @param idxDefinition - * definition of the index - * @param options - * index options - */ - public void createIndex(String idxName, IndexDefinition idxDefinition, CreateIndexOptions options) { - createIndex(idxName, idxDefinition, options, commandOptions); - } - /** * Create a new index with the given description. * @@ -323,10 +277,8 @@ public void createIndex(String idxName, IndexDefinition idxDefinition, CreateInd * definition of the index * @param idxOptions * index options - * @param cmd - * override the default command options */ - public void createIndex(String idxName, IndexDefinition idxDefinition, CreateIndexOptions idxOptions, CommandOptions cmd) { + public void createIndex(String idxName, TableIndexDefinition idxDefinition, CreateIndexOptions idxOptions) { hasLength(idxName, "indexName"); notNull(idxDefinition, "idxDefinition"); Command createIndexCommand = Command @@ -336,7 +288,7 @@ public void createIndex(String idxName, IndexDefinition idxDefinition, CreateInd if (idxOptions != null) { createIndexCommand.append("options", idxOptions); } - runCommand(createIndexCommand, commandOptions); + runCommand(createIndexCommand, idxOptions); log.info("Index '" + green("{}") + "' has been created", idxName); } @@ -352,8 +304,8 @@ public void createIndex(String idxName, IndexDefinition idxDefinition, CreateInd * @param idxDefinition * definition of the index */ - public void createVectorIndex(String idxName, VectorIndexDefinition idxDefinition) { - createVectorIndex(idxName, idxDefinition, null, commandOptions); + public void createVectorIndex(String idxName, TableVectorIndexDefinition idxDefinition) { + createVectorIndex(idxName, idxDefinition, null, options); } /** @@ -366,8 +318,8 @@ public void createVectorIndex(String idxName, VectorIndexDefinition idxDefinitio * @param options * index options */ - public void createVectorIndex(String idxName, VectorIndexDefinition idxDefinition, CreateVectorIndexOptions options) { - createVectorIndex(idxName, idxDefinition, options, commandOptions); + public void createVectorIndex(String idxName, TableVectorIndexDefinition idxDefinition, CreateVectorIndexOptions options) { + createVectorIndex(idxName, idxDefinition, options, this.options); } /** @@ -382,7 +334,7 @@ public void createVectorIndex(String idxName, VectorIndexDefinition idxDefinitio * @param cmd * override the default command options */ - public void createVectorIndex(String idxName, VectorIndexDefinition idxDefinition, CreateVectorIndexOptions idxOptions, CommandOptions cmd) { + public void createVectorIndex(String idxName, TableVectorIndexDefinition idxDefinition, CreateVectorIndexOptions idxOptions, BaseOptions cmd) { hasLength(idxName, "indexName"); notNull(idxDefinition, "idxDefinition"); Command createIndexCommand = Command @@ -392,7 +344,7 @@ public void createVectorIndex(String idxName, VectorIndexDefinition idxDefinitio if (idxOptions != null) { createIndexCommand.append("options", idxOptions); } - runCommand(createIndexCommand, commandOptions); + runCommand(createIndexCommand, options); log.info("Vector Index '" + green("{}") + "' has been created",idxName); } @@ -401,13 +353,16 @@ public void createVectorIndex(String idxName, VectorIndexDefinition idxDefinitio // -------------------------- public final TableInsertOneResult insertOne(T row) { - return insertOneDelegate(mapAsRow(row), (TableInsertOneOptions) null); + return insertOneDelegate(mapAsRow(row), null); } public final TableInsertOneResult insertOne(T row, TableInsertOneOptions insertOneOptions) { - notNull(row, ROW); - Command insertOne = Command.create("insertOne").withDocument(row); - return runCommand(insertOne, insertOneOptions).getStatus(TableInsertOneResult.class); + notNull(row, "row"); + Command insertOne = Command + .create("insertOne") + .withDocument(row); + TableInsertManyResult result = runCommand(insertOne, insertOneOptions).getStatus(TableInsertManyResult.class); + return new TableInsertOneResult(result.getInsertedIds().get(0), result.getPrimaryKeySchema()); } public final CompletableFuture insertOneAsync(T row) { @@ -419,7 +374,7 @@ public final CompletableFuture insertOneAsync(T row, Table } private TableInsertOneResult insertOneDelegate(Row row, TableInsertOneOptions insertOneOptions) { - notNull(row, ROW); + notNull(row, "row"); Command insertOne = Command .create("insertOne") .withDocument(row); @@ -435,20 +390,20 @@ public TableInsertManyResult insertMany(List rows) { return insertMany(rows, new TableInsertManyOptions()); } - public TableInsertManyResult insertMany(List rows, TableInsertManyOptions options) { + public TableInsertManyResult insertMany(List rows, TableInsertManyOptions insertManyOptions) { Assert.isTrue(rows != null && !rows.isEmpty(), "rows list cannot be null or empty"); - Assert.notNull(options, "insertMany options cannot be null"); - if (options.concurrency() > 1 && options.ordered()) { + Assert.notNull(insertManyOptions, "insertMany options cannot be null"); + if (insertManyOptions.concurrency() > 1 && insertManyOptions.ordered()) { throw new IllegalArgumentException("Cannot run ordered insert_many concurrently."); } - if (options.chunkSize() > dataAPIClientOptions.getMaxRecordsInInsert()) { - throw new IllegalArgumentException("Cannot insert more than " + dataAPIClientOptions.getMaxRecordsInInsert() + " at a time."); + if (insertManyOptions.chunkSize() > MAX_CHUNK_SIZE) { + throw new IllegalArgumentException("Cannot insert more than " + MAX_CHUNK_SIZE + " at a time."); } long start = System.currentTimeMillis(); - ExecutorService executor = Executors.newFixedThreadPool(options.concurrency()); + ExecutorService executor = Executors.newFixedThreadPool(insertManyOptions.concurrency()); List> futures = new ArrayList<>(); - for (int i = 0; i < rows.size(); i += options.chunkSize()) { - futures.add(executor.submit(getInsertManyResultCallable(rows, options, i))); + for (int i = 0; i < rows.size(); i += insertManyOptions.chunkSize()) { + futures.add(executor.submit(getInsertManyResultCallable(rows, insertManyOptions, i))); } executor.shutdown(); @@ -470,11 +425,10 @@ public TableInsertManyResult insertMany(List rows, TableInsertManyO } } - long totalTimeout = this.commandOptions - .getTimeoutOptions() - .getDataOperationTimeoutMillis(); - if (options.getTimeoutOptions() != null) { - totalTimeout = options.getTimeoutOptions().dataOperationTimeoutMillis(); + long totalTimeout = this.options.getTimeout(); + if (options.getDataAPIClientOptions() != null + && options.getDataAPIClientOptions().getTimeoutOptions() != null) { + totalTimeout = options.getTimeout(); } if (executor.awaitTermination(totalTimeout, TimeUnit.MILLISECONDS)) { log.debug(magenta(".[total insertMany.responseTime]") + "=" + yellow("{}") + " millis.", @@ -501,6 +455,12 @@ public CompletableFuture insertManyAsync(List insertMany(rows)); } + public TableInsertManyOptions insertManyOptions() { + TableInsertManyOptions options = new TableInsertManyOptions(); + options.dataAPIClientOptions(this.options.getDataAPIClientOptions().clone()); + return options; + } + /** * Execute a 1 for 1 call to the Data API. * @@ -922,8 +882,8 @@ public int countRows(int upperBound) throws TooManyRowsToCountException { public int countRows(Filter filter, int upperBound, CountRowsOptions options) throws TooManyRowsToCountException { // Argument Validation - if (upperBound<1 || upperBound> dataAPIClientOptions.getMaxCount()) { - throw new IllegalArgumentException("UpperBound limit should be in between 1 and " + dataAPIClientOptions.getMaxCount()); + if (upperBound < 1 || upperBound > MAX_COUNT) { + throw new IllegalArgumentException("UpperBound limit should be in between 1 and " + MAX_COUNT); } // Build command Command command = new Command("countDocuments").withFilter(filter); @@ -990,16 +950,81 @@ public Row mapAsRow(T input) { return row; } else { // Defaults mapping as a Row - return SERIALIZER.convertValue(input, Row.class); + return getSerializer().convertValue(input, Row.class); } } + // ------------------------------------------ + // ---- List Indexes --- + // ------------------------------------------ + + /** + * Retrieves the names of all indices in the keyspace with default options. + * + * @return A list of all indices names in the database. + * + *

Example usage:

+ *
+     * {@code
+     * List indicesNames = listIndexesNames();
+     * }
+     * 
+ */ + public List listIndexesNames() { + return listIndexesNames(null); + } + + /** + * Retrieves the names of all indices in the keyspace with default options. + * + * @param listIndexesOptions Options for filtering or configuring the indices listing operation. + * @return A list of all indices names in the database. + * + *

Example usage:

+ *
+     * {@code
+     * ListIndexesOptions options = new ListIndexesOptions();
+     * List indicesNames = listIndexesNames(options);
+     * }
+     * 
+ */ + public List listIndexesNames(ListIndexesOptions listIndexesOptions) { + return runCommand(Command.create("listIndexes"), listIndexesOptions) + .getStatusKeyAsList("indexes", String.class); + } + + /** + * Finds all the indices in the selected keyspace. + * + * @return + * list of table definitions + */ + public List listIndexes() { + return listIndexes(null); + } + + /** + * Finds all the indices in the selected keyspace. + * + * @return + * list of table definitions + */ + public List listIndexes(ListIndexesOptions listIndexesOptions) { + Command findTables = Command + .create("listIndexes") + .withOptions(new Document().append("explain", true)); + return runCommand(findTables, listIndexesOptions) + .getStatusKeyAsList("indexes", TableIndexDescriptor.class) + .stream().map(TableIndexDescriptor::getDefinition) + .toList(); + } + // -------------------------- // --- Listeners ---- // -------------------------- /** - * Register a listener to execute commands on the table. Please now use {@link CommandOptions}. + * Register a listener to execute commands on the table. Please now use {@link BaseOptions}. * * @param logger * name for the logger @@ -1007,17 +1032,7 @@ public Row mapAsRow(T input) { * class for the logger */ public void registerListener(String logger, CommandObserver commandObserver) { - this.commandOptions.registerObserver(logger, commandObserver); - } - - /** - * Register a listener to execute commands on the table. Please now use {@link CommandOptions}. - * - * @param name - * name for the observer - */ - public void deleteListener(String name) { - this.commandOptions.unregisterObserver(name); + this.options.registerObserver(logger, commandObserver); } } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/TableDefinition.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/TableDefinition.java index 8032992a..e13970d7 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/TableDefinition.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/TableDefinition.java @@ -59,6 +59,9 @@ public TableDefinition addColumnText(String name) { public TableDefinition addColumnInt(String name) { return addColumn(name, ColumnTypes.INT); } + public TableDefinition addColumnTimestamp(String name) { + return addColumn(name, ColumnTypes.TIMESTAMP); + } public TableDefinition addColumnBoolean(String name) { return addColumn(name, ColumnTypes.BOOLEAN); @@ -84,7 +87,23 @@ public TableDefinition addColumnVector(String name, ColumnDefinitionVector colDe return this; } - public TableDefinition withPartitionKey(String... partitionKeys) { + public TableDefinition addPartitionBy(String partitionKey) { + primaryKey.getPartitionBy().add(partitionKey); + return this; + } + + public TableDefinition addPartitionSort(Sort column) { + Assert.notNull(column, "Column"); + Assert.notNull(column.getOrder(), "column order"); + Assert.hasLength(column.getField(), "column name"); + if (primaryKey.getPartitionSort() == null) { + primaryKey.setPartitionSort(new LinkedHashMap<>()); + } + primaryKey.getPartitionSort().put(column.getField(), column.getOrder().getCode()); + return this; + } + + public TableDefinition partitionKey(String... partitionKeys) { if (partitionKeys != null) { primaryKey.getPartitionBy().clear(); Arrays.asList(partitionKeys).forEach(pk -> { @@ -97,7 +116,15 @@ public TableDefinition withPartitionKey(String... partitionKeys) { return this; } - public TableDefinition withClusteringColumns(Sort... clusteringColumns) { + public TableDefinition partitionSort(Sort... clusteringColumns) { + return clusteringColumns(clusteringColumns); + } + + public TableDefinition addClusteringColumn(Sort clusteringColumn) { + return addPartitionSort(clusteringColumn); + } + + public TableDefinition clusteringColumns(Sort... clusteringColumns) { if (clusteringColumns != null) { primaryKey.setPartitionSort(new LinkedHashMap<>()); Arrays.asList(clusteringColumns).forEach(cc -> { diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/TableOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/TableOptions.java new file mode 100644 index 00000000..1fb77716 --- /dev/null +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/TableOptions.java @@ -0,0 +1,59 @@ +package com.datastax.astra.client.tables; + +/*- + * #%L + * Data API Java Client + * -- + * Copyright (C) 2024 DataStax + * -- + * Licensed under the Apache License, Version 2.0 + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.datastax.astra.client.core.commands.BaseOptions; +import com.datastax.astra.client.core.commands.CommandType; +import com.datastax.astra.client.core.options.DataAPIClientOptions; +import com.datastax.astra.internal.serdes.collections.DocumentSerializer; +import com.datastax.astra.internal.serdes.tables.RowSerializer; +import com.datastax.astra.internal.utils.Assert; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; + +import static com.datastax.astra.client.core.commands.CommandType.TABLE_ADMIN; +import static com.datastax.astra.client.tables.Table.DEFAULT_TABLE_SERIALIZER; + +/** + * The options to use for the data API client. + */ +@Setter +@Accessors(fluent = true, chain = true) +public class TableOptions extends BaseOptions { + + public TableOptions() { + this(null, null); + } + + /** + * Constructor with options and not token override. + * + * @param token + * the token to use for the database + * @param options + * data API client options + */ + public TableOptions(String token, DataAPIClientOptions options) { + super(token, TABLE_ADMIN, DEFAULT_TABLE_SERIALIZER, options); + } + +} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/columns/ColumnTypeMapper.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/columns/ColumnTypeMapper.java new file mode 100644 index 00000000..93dff0c2 --- /dev/null +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/columns/ColumnTypeMapper.java @@ -0,0 +1,68 @@ +package com.datastax.astra.client.tables.columns; + +import com.datastax.astra.client.core.vector.DataAPIVector; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.InetAddress; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** + * Maps Java types to Cassandra column types. + */ +public class ColumnTypeMapper { + + private static final Map, ColumnTypes> typeMapping = new HashMap<>(); + + static { + typeMapping.put(Integer.class, ColumnTypes.INT); + typeMapping.put(int.class, ColumnTypes.INT); + typeMapping.put(Long.class, ColumnTypes.BIGINT); + typeMapping.put(long.class, ColumnTypes.BIGINT); + + typeMapping.put(Double.class, ColumnTypes.DOUBLE); + typeMapping.put(double.class, ColumnTypes.DOUBLE); + + typeMapping.put(Float.class, ColumnTypes.FLOAT); + typeMapping.put(float.class, ColumnTypes.FLOAT); + typeMapping.put(Boolean.class, ColumnTypes.BOOLEAN); + typeMapping.put(boolean.class, ColumnTypes.BOOLEAN); + typeMapping.put(Byte.class, ColumnTypes.TINYINT); + typeMapping.put(byte.class, ColumnTypes.TINYINT); + typeMapping.put(Short.class, ColumnTypes.SMALLINT); + typeMapping.put(short.class, ColumnTypes.SMALLINT); + + // Commonly used Java types + typeMapping.put(String.class, ColumnTypes.TEXT); + typeMapping.put(UUID.class, ColumnTypes.UUID); + typeMapping.put(BigDecimal.class, ColumnTypes.DECIMAL); + typeMapping.put(BigInteger.class, ColumnTypes.VARINT); + typeMapping.put(InetAddress.class, ColumnTypes.INET); + + // Date and time types + typeMapping.put(Instant.class, ColumnTypes.TIMESTAMP); + typeMapping.put(LocalDate.class, ColumnTypes.DATE); + typeMapping.put(LocalTime.class, ColumnTypes.TIME); + + // Collection types + typeMapping.put(List.class, ColumnTypes.LIST); + typeMapping.put(Set.class, ColumnTypes.SET); + typeMapping.put(Map.class, ColumnTypes.MAP); + typeMapping.put(DataAPIVector.class, ColumnTypes.VECTOR); + + // Unsupported or undefined + typeMapping.put(Object.class, ColumnTypes.UNSUPPORTED); + } + + public static ColumnTypes getColumnType(Class clazz) { + return typeMapping.getOrDefault(clazz, ColumnTypes.UNSUPPORTED); + } + +} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/AlterTableOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/AlterTableOptions.java index fff99b61..fd16fda3 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/AlterTableOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/AlterTableOptions.java @@ -20,17 +20,19 @@ * #L% */ +import com.datastax.astra.client.core.commands.BaseOptions; +import com.datastax.astra.client.core.commands.CommandType; import lombok.Data; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; -@Getter +import static com.datastax.astra.client.tables.Table.DEFAULT_TABLE_SERIALIZER; + @Setter -@NoArgsConstructor @Accessors(fluent = true, chain = true) -public class AlterTableOptions { +public class AlterTableOptions extends BaseOptions { /** Improve syntax. */ public static final AlterTableOptions IF_EXISTS = new AlterTableOptions().ifExists(true); @@ -40,6 +42,10 @@ public class AlterTableOptions { */ boolean ifExists = true; + public AlterTableOptions() { + super(null, CommandType.TABLE_ADMIN, DEFAULT_TABLE_SERIALIZER, null); + } + /** * Accessor for serialization. * diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/CreateIndexOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/CreateIndexOptions.java index 2bfb849c..ca6f8a3a 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/CreateIndexOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/CreateIndexOptions.java @@ -20,21 +20,21 @@ * #L% */ -import lombok.AllArgsConstructor; -import lombok.Data; +import com.datastax.astra.client.core.commands.BaseOptions; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; +import static com.datastax.astra.client.core.commands.CommandType.TABLE_ADMIN; +import static com.datastax.astra.client.tables.Table.DEFAULT_TABLE_SERIALIZER; + /** * Set of options used when creating a table */ -@Getter @Setter -@NoArgsConstructor @Accessors(fluent = true, chain = true) -public class CreateIndexOptions { +public class CreateIndexOptions extends BaseOptions { /** Improve syntax. */ public static final CreateIndexOptions IF_NOT_EXISTS = new CreateIndexOptions().ifNotExists(true); @@ -44,6 +44,10 @@ public class CreateIndexOptions { */ boolean ifNotExists = true; + public CreateIndexOptions() { + super(null, TABLE_ADMIN, DEFAULT_TABLE_SERIALIZER, null); + } + /** * Accessor for serialization. * diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/CreateTableOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/CreateTableOptions.java index cb08b5a8..a676b9a6 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/CreateTableOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/CreateTableOptions.java @@ -20,20 +20,24 @@ * #L% */ +import com.datastax.astra.client.core.commands.BaseOptions; +import com.datastax.astra.client.core.commands.CommandType; +import com.datastax.astra.client.core.options.DataAPIClientOptions; +import com.datastax.astra.internal.serdes.DataAPISerializer; import lombok.Data; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; +import static com.datastax.astra.client.tables.Table.DEFAULT_TABLE_SERIALIZER; + /** * Set of options used when creating a table */ -@Getter @Setter -@NoArgsConstructor @Accessors(fluent = true, chain = true) -public class CreateTableOptions { +public class CreateTableOptions extends BaseOptions { /** Improve syntax. */ public static final CreateTableOptions IF_NOT_EXISTS = new CreateTableOptions().ifNotExists(true); @@ -43,6 +47,10 @@ public class CreateTableOptions { */ boolean ifNotExists = true; + public CreateTableOptions() { + super(null, CommandType.TABLE_ADMIN, DEFAULT_TABLE_SERIALIZER, null); + } + /** * Accessor for serialization. * diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/CreateVectorIndexOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/CreateVectorIndexOptions.java index c4653f3f..db2f8099 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/CreateVectorIndexOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/CreateVectorIndexOptions.java @@ -20,19 +20,22 @@ * #L% */ +import com.datastax.astra.client.core.commands.BaseOptions; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; +import static com.datastax.astra.client.core.commands.CommandType.TABLE_ADMIN; +import static com.datastax.astra.client.tables.Table.DEFAULT_TABLE_SERIALIZER; + /** * Set of options used when creating a table */ @Getter @Setter -@NoArgsConstructor @Accessors(fluent = true, chain = true) -public class CreateVectorIndexOptions { +public class CreateVectorIndexOptions extends BaseOptions { /** Improve syntax. */ public static final CreateVectorIndexOptions IF_NOT_EXISTS = new CreateVectorIndexOptions().ifNotExists(true); @@ -42,6 +45,10 @@ public class CreateVectorIndexOptions { */ boolean ifNotExists = true; + public CreateVectorIndexOptions() { + super(null, TABLE_ADMIN, DEFAULT_TABLE_SERIALIZER, null); + } + /** * Accessor for serialization. * diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/DropTableIndexOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/DropTableIndexOptions.java index 0b5731a7..a49ad164 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/DropTableIndexOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/DropTableIndexOptions.java @@ -20,17 +20,19 @@ * #L% */ +import com.datastax.astra.client.core.commands.BaseOptions; import lombok.Data; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; -@Getter +import static com.datastax.astra.client.core.commands.CommandType.TABLE_ADMIN; +import static com.datastax.astra.client.tables.Table.DEFAULT_TABLE_SERIALIZER; + @Setter -@NoArgsConstructor @Accessors(fluent = true, chain = true) -public class DropTableIndexOptions { +public class DropTableIndexOptions extends BaseOptions { /** Improve syntax. */ public static final DropTableIndexOptions IF_EXISTS = new DropTableIndexOptions().ifExists(true); @@ -40,6 +42,10 @@ public class DropTableIndexOptions { */ boolean ifExists = true; + public DropTableIndexOptions() { + super(null, TABLE_ADMIN, DEFAULT_TABLE_SERIALIZER, null); + } + /** * Accessor for serialization. * diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/DropTableOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/DropTableOptions.java index 7bf6f7e6..d2173bc9 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/DropTableOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/ddl/DropTableOptions.java @@ -20,17 +20,20 @@ * #L% */ +import com.datastax.astra.client.core.commands.BaseOptions; +import com.datastax.astra.internal.serdes.tables.RowSerializer; import lombok.Data; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; -@Getter +import static com.datastax.astra.client.core.commands.CommandType.TABLE_ADMIN; +import static com.datastax.astra.client.tables.Table.DEFAULT_TABLE_SERIALIZER; + @Setter -@NoArgsConstructor @Accessors(fluent = true, chain = true) -public class DropTableOptions { +public class DropTableOptions extends BaseOptions { /** Improve syntax. */ public static final DropTableOptions IF_EXISTS = new DropTableOptions().ifExists(true); @@ -40,6 +43,10 @@ public class DropTableOptions { */ boolean ifExists = true; + public DropTableOptions() { + super(null, TABLE_ADMIN, DEFAULT_TABLE_SERIALIZER, null); + } + /** * Accessor for serialization. * @@ -50,4 +57,6 @@ public boolean isIfExists() { return ifExists; } + + } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/exceptions/TooManyRowsToCountException.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/exceptions/TooManyRowsToCountException.java index 9f9796ef..2e8e740e 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/exceptions/TooManyRowsToCountException.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/exceptions/TooManyRowsToCountException.java @@ -33,7 +33,7 @@ public class TooManyRowsToCountException extends DataAPIException { * Default constructor. */ public TooManyRowsToCountException() { - super(ClientErrorCodes.HTTP,"Rows count exceeds '" + DataAPIClientOptions.DEFAULT_MAX_COUNT + ", the maximum allowed by the server"); + super(ClientErrorCodes.HTTP,"Rows count exceeds '" + DataAPIClientOptions.MAX_COUNT + ", the maximum allowed by the server"); } /** diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableBaseIndexDefinition.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableBaseIndexDefinition.java new file mode 100644 index 00000000..374e3dae --- /dev/null +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableBaseIndexDefinition.java @@ -0,0 +1,35 @@ +package com.datastax.astra.client.tables.index; + +/*- + * #%L + * Data API Java Client + * -- + * Copyright (C) 2024 DataStax + * -- + * Licensed under the Apache License, Version 2.0 + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public abstract class TableBaseIndexDefinition { + + protected String column; + + protected TableIndexDefinitionApiSupport apiSupport; + +} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/VectorIndexDefinition.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableIndexDefinition.java similarity index 76% rename from astra-db-java/src/main/java/com/datastax/astra/client/tables/index/VectorIndexDefinition.java rename to astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableIndexDefinition.java index aa9e4b04..e4bc60a0 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/VectorIndexDefinition.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableIndexDefinition.java @@ -21,24 +21,23 @@ */ import lombok.Data; +import lombok.Getter; import lombok.NoArgsConstructor; /** * Index Definitions. */ -@Data @NoArgsConstructor -public class VectorIndexDefinition { +@Getter +public class TableIndexDefinition extends TableBaseIndexDefinition { - String column; + TableIndexDefinitionOptions options; - VectorIndexDefinitionOptions options; - - public VectorIndexDefinition column(String column) { + public TableIndexDefinition column(String column) { this.column = column; return this; } - public VectorIndexDefinition options(VectorIndexDefinitionOptions options) { + public TableIndexDefinition options(TableIndexDefinitionOptions options) { this.options = options; return this; } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableIndexDefinitionApiSupport.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableIndexDefinitionApiSupport.java new file mode 100644 index 00000000..8b7f6e70 --- /dev/null +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableIndexDefinitionApiSupport.java @@ -0,0 +1,36 @@ +package com.datastax.astra.client.tables.index; + +/*- + * #%L + * Data API Java Client + * -- + * Copyright (C) 2024 DataStax + * -- + * Licensed under the Apache License, Version 2.0 + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data @NoArgsConstructor +public class TableIndexDefinitionApiSupport { + + private boolean createTable; + + private boolean insert; + + private boolean read; + + private String cqlDefinition; +} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/IndexDefinitionOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableIndexDefinitionOptions.java similarity index 81% rename from astra-db-java/src/main/java/com/datastax/astra/client/tables/index/IndexDefinitionOptions.java rename to astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableIndexDefinitionOptions.java index 7e29de75..e56591c1 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/IndexDefinitionOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableIndexDefinitionOptions.java @@ -28,7 +28,7 @@ * Using booleans as those flag could be null. */ @Data @NoArgsConstructor -public class IndexDefinitionOptions { +public class TableIndexDefinitionOptions { Boolean ascii; @@ -36,17 +36,17 @@ public class IndexDefinitionOptions { Boolean caseSensitive; - public IndexDefinitionOptions ascii(boolean ascii) { + public TableIndexDefinitionOptions ascii(boolean ascii) { this.ascii = ascii; return this; } - public IndexDefinitionOptions normalize(boolean normalize) { + public TableIndexDefinitionOptions normalize(boolean normalize) { this.normalize = normalize; return this; } - public IndexDefinitionOptions caseSensitive(boolean caseSensitive) { + public TableIndexDefinitionOptions caseSensitive(boolean caseSensitive) { this.caseSensitive = caseSensitive; return this; } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/IndexDescriptor.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableIndexDescriptor.java similarity index 82% rename from astra-db-java/src/main/java/com/datastax/astra/client/tables/index/IndexDescriptor.java rename to astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableIndexDescriptor.java index 25832647..9315ff4f 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/IndexDescriptor.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableIndexDescriptor.java @@ -26,7 +26,7 @@ @Getter @Setter -public class IndexDescriptor { +public class TableIndexDescriptor { /** * Name of the table. @@ -36,29 +36,29 @@ public class IndexDescriptor { /** * Options for the table. */ - private IndexDefinition definition; + private TableIndexDefinition definition; /** * Default constructor. */ - public IndexDescriptor() { + public TableIndexDescriptor() { // left blank, serialization with jackson } /** * Default constructor. */ - public IndexDescriptor(String name) { + public TableIndexDescriptor(String name) { // left blank, serialization with jackson this.name = name; } - public IndexDescriptor name(String name) { + public TableIndexDescriptor name(String name) { this.name = name; return this; } - public IndexDescriptor definition(IndexDefinition def) { + public TableIndexDescriptor definition(TableIndexDefinition def) { this.definition = def; return this; } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/IndexDefinition.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableVectorIndexDefinition.java similarity index 73% rename from astra-db-java/src/main/java/com/datastax/astra/client/tables/index/IndexDefinition.java rename to astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableVectorIndexDefinition.java index bf1cd555..1152ccff 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/IndexDefinition.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableVectorIndexDefinition.java @@ -21,24 +21,24 @@ */ import lombok.Data; +import lombok.Getter; import lombok.NoArgsConstructor; /** * Index Definitions. */ -@Data @NoArgsConstructor -public class IndexDefinition { +@Getter +@NoArgsConstructor +public class TableVectorIndexDefinition extends TableBaseIndexDefinition { - String column; + TableVectorIndexDefinitionOptions options; - IndexDefinitionOptions options; - - public IndexDefinition column(String column) { + public TableVectorIndexDefinition column(String column) { this.column = column; return this; } - public IndexDefinition options(IndexDefinitionOptions options) { + public TableVectorIndexDefinition options(TableVectorIndexDefinitionOptions options) { this.options = options; return this; } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/VectorIndexDefinitionOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableVectorIndexDefinitionOptions.java similarity index 84% rename from astra-db-java/src/main/java/com/datastax/astra/client/tables/index/VectorIndexDefinitionOptions.java rename to astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableVectorIndexDefinitionOptions.java index e7afe56b..4826d983 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/VectorIndexDefinitionOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableVectorIndexDefinitionOptions.java @@ -29,18 +29,18 @@ * Using booleans as those flag could be null. */ @Data @NoArgsConstructor -public class VectorIndexDefinitionOptions { +public class TableVectorIndexDefinitionOptions { String metric; String sourceModel; - public VectorIndexDefinitionOptions metric(SimilarityMetric metric) { + public TableVectorIndexDefinitionOptions metric(SimilarityMetric metric) { this.metric = metric.getValue(); return this; } - public VectorIndexDefinitionOptions sourceModel(String sourceModel) { + public TableVectorIndexDefinitionOptions sourceModel(String sourceModel) { this.sourceModel = sourceModel; return this; } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/VectorIndexDescriptor.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableVectorIndexDescriptor.java similarity index 80% rename from astra-db-java/src/main/java/com/datastax/astra/client/tables/index/VectorIndexDescriptor.java rename to astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableVectorIndexDescriptor.java index 0b6ef03f..8d172704 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/VectorIndexDescriptor.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/index/TableVectorIndexDescriptor.java @@ -26,7 +26,7 @@ @Getter @Setter -public class VectorIndexDescriptor { +public class TableVectorIndexDescriptor { /** * Name of the table. @@ -36,29 +36,29 @@ public class VectorIndexDescriptor { /** * Options for the table. */ - private VectorIndexDefinition definition; + private TableVectorIndexDefinition definition; /** * Default constructor. */ - public VectorIndexDescriptor() { + public TableVectorIndexDescriptor() { // left blank, serialization with jackson } /** * Default constructor. */ - public VectorIndexDescriptor(String name) { + public TableVectorIndexDescriptor(String name) { // left blank, serialization with jackson this.name = name; } - public VectorIndexDescriptor name(String name) { + public TableVectorIndexDescriptor name(String name) { this.name = name; return this; } - public VectorIndexDescriptor definition(VectorIndexDefinition def) { + public TableVectorIndexDescriptor definition(TableVectorIndexDefinition def) { this.definition = def; return this; } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/mapping/Column.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/mapping/Column.java index 9b5b7626..2f697aa2 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/mapping/Column.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/mapping/Column.java @@ -35,7 +35,7 @@ /** * Column Name, if not provided the field name will be used */ - String value(); + String name(); /** * Column Type, if not provided the field type will be used diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/mapping/EntityBeanDefinition.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/mapping/EntityBeanDefinition.java index 47b8856d..f4d1b25f 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/mapping/EntityBeanDefinition.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/mapping/EntityBeanDefinition.java @@ -21,8 +21,11 @@ */ import com.datastax.astra.client.collections.documents.Document; +import com.datastax.astra.client.core.vector.SimilarityMetric; +import com.datastax.astra.client.tables.columns.ColumnTypeMapper; import com.datastax.astra.client.tables.columns.ColumnTypes; import com.dtsx.astra.sdk.utils.Utils; +import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.introspect.AnnotatedField; @@ -30,17 +33,21 @@ import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; import com.fasterxml.jackson.databind.type.TypeFactory; import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; /** * Bean introspector will get information of a Bean to populate fields * directly from the output. */ +@Slf4j @Data public class EntityBeanDefinition { @@ -72,7 +79,7 @@ public EntityBeanDefinition(Class clazz) { // Table Name EntityTable tableAnn = clazz.getAnnotation(EntityTable.class); if (tableAnn == null) { - throw new IllegalArgumentException("Please annotate your bean with @Table(name=\"table_name\")"); + throw new IllegalArgumentException("Invalid class: It should be annotated with @Table(name=\"table_name\")"); } this.name = tableAnn.value(); @@ -87,7 +94,23 @@ public EntityBeanDefinition(Class clazz) { EntityFieldDefinition field = new EntityFieldDefinition(); field.setName(property.getName()); field.setType(property.getPrimaryType().getRawClass()); - + if (Map.class.isAssignableFrom(field.getType())) { + JavaType keyType = property.getPrimaryType().getBindings().getBoundType(0); + JavaType valueType = property.getPrimaryType().getBindings().getBoundType(1); + if (keyType != null) { + field.setGenericKeyType(keyType.getRawClass()); // Get the key's raw class + } + if (valueType != null) { + field.setGenericValueType(valueType.getRawClass()); // Get the value's raw class + } + } + // Handle List or Set types + if (List.class.isAssignableFrom(field.getType()) || Set.class.isAssignableFrom(field.getType())) { + JavaType elementType = property.getPrimaryType().getBindings().getBoundType(0); + if (elementType != null) { + field.setGenericValueType(elementType.getRawClass()); // Get the element's raw class + } + } AnnotatedMethod getter = property.getGetter(); field.setGetter((getter != null) ? getter.getAnnotated() : null); AnnotatedMethod setter = property.getSetter(); @@ -96,8 +119,8 @@ public EntityBeanDefinition(Class clazz) { AnnotatedField annfield = property.getField(); Column column = annfield.getAnnotated().getAnnotation(Column.class); if (column != null) { - if (Utils.hasLength(column.value())) { - field.setColumnName(column.value()); + if (Utils.hasLength(column.name())) { + field.setColumnName(column.name()); } if (column.type() != ColumnTypes.UNDEFINED) { field.setColumnType(column.type()); @@ -110,25 +133,15 @@ public EntityBeanDefinition(Class clazz) { } field.setDimension(column.dimension()); field.setMetric(column.metric()); - // Control for consistency ib between parameters of @Column - if (field.getDimension() > 0 - && field.getColumnType() != ColumnTypes.VECTOR) { - throw new IllegalArgumentException("Field " + field.getName() + " provides a dimension must be of type VECTOR"); - } - if (field.getValueType() != null - && field.getColumnType() != ColumnTypes.LIST - && field.getColumnType() != ColumnTypes.SET - && field.getColumnType() != ColumnTypes.MAP) { - throw new IllegalArgumentException("Field " + field.getName() + " provides a valueType must be of type LIST, SET or MAP"); - } - } - if (field.getKeyType() != null && field.getColumnType() != ColumnTypes.MAP) { - throw new IllegalArgumentException("Field " + field.getName() + " provides a keyType must be of type MAP"); + } else { + log.warn("Field {} is not annotated with @Column", field.getName()); } + PartitionBy partitionBy = annfield.getAnnotated().getAnnotation(PartitionBy.class); if (partitionBy != null) { field.setPartitionByPosition(partitionBy.value()); } + PartitionSort partitionSort = annfield.getAnnotated().getAnnotation(PartitionSort.class); if (partitionSort != null) { field.setPartitionSortPosition(partitionSort.position()); @@ -139,33 +152,43 @@ public EntityBeanDefinition(Class clazz) { } /** - * Build the partition Key based on annotated fields. + * Build the partition Key based on annotated fields with @PartitionBy with position. * * @return - * list of partition keys + * An ordered list of partition keys (partition key) */ public List getPartitionBy() { return getFields().values().stream() .filter(e -> e.getPartitionByPosition() != null) - .sorted((f1, f2) -> f1.getPartitionByPosition().compareTo(f2.getPartitionByPosition())) - .map(EntityFieldDefinition::getColumnName) + .sorted(Comparator.comparing(EntityFieldDefinition::getPartitionByPosition)) + .map(efd -> { + if (Utils.hasLength(efd.getColumnName())) { + return efd.getColumnName(); + } + return efd.getName(); + }) .collect(Collectors.toList()); } /** - * Build the clustering Key based on annotated fields. + * Build the partition sort based on annotated fields @PartitionSort with position and order. * * @return - * list of clustering keys + * an ordered map representing the partition sort (clustering columns) */ public Map getPartitionSort() { List fields = getFields().values().stream() .filter(e -> e.getPartitionSortPosition() != null) - .sorted((f1, f2) -> f1.getPartitionSortPosition().compareTo(f2.getPartitionSortPosition())) + .sorted(Comparator.comparing(EntityFieldDefinition::getPartitionSortPosition)) .toList(); + // Order is preserved in LinkedHashMap Map cc = new LinkedHashMap<>(); for (EntityFieldDefinition field : fields) { - cc.put(field.getColumnName(), field.getPartitionSortOrder().getCode()); + if (Utils.hasLength(field.getColumnName())) { + cc.put(field.getColumnName(), field.getPartitionSortOrder().getCode()); + } else { + cc.put(field.getName(), field.getPartitionSortOrder().getCode()); + } } return cc; } @@ -183,16 +206,66 @@ public static Document createTableCommand(String tableName, Class clazz) { Document columns = new Document(); bean.getFields().forEach((name, field) -> { Document column = new Document(); - if (field.getColumnType() == null) { - throw new IllegalArgumentException("Missing column type in annotation @Column for field '" + field.getName() + "'"); + ColumnTypes colType = field.getColumnType(); + // No types has been provided, trying to map from Java types + if (colType == null) { + colType = ColumnTypeMapper.getColumnType(field.getType()); + if (colType == ColumnTypes.UNSUPPORTED) { + throw new IllegalArgumentException("Unsupported type '" + field.getType().getName() + "' for field '" + field.getName() + "'"); + } } - column.append("type", field.getColumnType().getValue()); + column.append("type", colType.getValue()); - // -- FIXME SUPPORT MAP, SET, LIST, VECTOR - columns.append(field.getColumnName(), column); + // Vector: Dimension and Metric + if (colType == ColumnTypes.VECTOR) { + if (field.getDimension() == null) { + throw new IllegalArgumentException("Missing attribute 'dimension' in annotation '@Column' for field '" + field.getName() + "'"); + } + column.append("dimension", field.getDimension()); + + SimilarityMetric metric = SimilarityMetric.COSINE; + if (field.getMetric() != null) { + metric = field.getMetric(); + } + column.append("metric", metric.getValue()); + } + + // KeyType with MAPS + if (colType == ColumnTypes.MAP) { + ColumnTypes keyType = field.getKeyType(); + if (keyType == null) { + keyType = ColumnTypeMapper.getColumnType(field.getGenericKeyType()); + if (keyType == ColumnTypes.UNSUPPORTED) { + throw new IllegalArgumentException("Unsupported type '" + field.getType().getName() + "' for key in field '" + field.getName() + "'"); + } + } + column.append("keyType", keyType.getValue()); + } + + // ValueType with MAPS, LISTS and SETS + if (colType == ColumnTypes.MAP || + colType == ColumnTypes.LIST || + colType == ColumnTypes.SET) { + ColumnTypes valueType = field.getValueType(); + if (valueType == null) { + valueType = ColumnTypeMapper.getColumnType(field.getGenericValueType()); + if (valueType == ColumnTypes.UNSUPPORTED) { + throw new IllegalArgumentException("Unsupported type '" + field.getType().getName() + "' for value in field '" + field.getName() + "'"); + } + } + column.append("valueType", valueType.getValue()); + } + + // Column Name, using the field Name if nothing provided + String nameColumn = field.getColumnName(); + if (!Utils.hasLength(nameColumn)) { + nameColumn = field.getName(); + } + columns.append(nameColumn, column); }); definition.append("columns", columns); + // Primary Key Document primaryKey = new Document(); primaryKey.append("partitionBy", bean.getPartitionBy()); if (!bean.getPartitionSort().isEmpty()) { diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/mapping/EntityFieldDefinition.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/mapping/EntityFieldDefinition.java index d5170696..ff72b856 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/mapping/EntityFieldDefinition.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/mapping/EntityFieldDefinition.java @@ -26,16 +26,18 @@ import lombok.Data; import java.lang.reflect.Method; +import java.lang.reflect.Type; @Data public class EntityFieldDefinition { // --- Java Types -- - private String name; private Class type; private Method getter; private Method setter; + private Class genericValueType; + private Class genericKeyType; // --- Table Hints -- diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/CountRowsOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/CountRowsOptions.java index 9ee21369..c40707e4 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/CountRowsOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/CountRowsOptions.java @@ -20,7 +20,7 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -32,4 +32,4 @@ @Getter @Setter @NoArgsConstructor @Accessors(fluent = true, chain = true) -public class CountRowsOptions extends CommandOptions {} +public class CountRowsOptions extends BaseOptions {} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/CreateIndexOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/CreateIndexOptions.java new file mode 100644 index 00000000..3a27d180 --- /dev/null +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/CreateIndexOptions.java @@ -0,0 +1,24 @@ +package com.datastax.astra.client.tables.options; + +/*- + * #%L + * Data API Java Client + * -- + * Copyright (C) 2024 DataStax + * -- + * Licensed under the Apache License, Version 2.0 + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +public class CreateIndexOptions { +} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/EstimatedCountRowsOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/EstimatedCountRowsOptions.java index 3c0a9d00..8bc36b7a 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/EstimatedCountRowsOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/EstimatedCountRowsOptions.java @@ -20,7 +20,7 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -32,4 +32,4 @@ @Getter @Setter @NoArgsConstructor @Accessors(fluent = true, chain = true) -public class EstimatedCountRowsOptions extends CommandOptions {} +public class EstimatedCountRowsOptions extends BaseOptions {} diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableDeleteManyOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableDeleteManyOptions.java index 9018d670..493bbc42 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableDeleteManyOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableDeleteManyOptions.java @@ -20,7 +20,7 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -32,5 +32,5 @@ @Getter @Setter @NoArgsConstructor @Accessors(fluent = true, chain = true) -public class TableDeleteManyOptions extends CommandOptions { +public class TableDeleteManyOptions extends BaseOptions { } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableDeleteOneOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableDeleteOneOptions.java index db49ea95..56e378dc 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableDeleteOneOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableDeleteOneOptions.java @@ -20,7 +20,7 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import com.datastax.astra.client.core.query.Sort; import lombok.Getter; import lombok.NoArgsConstructor; @@ -33,7 +33,7 @@ @Getter @Setter @NoArgsConstructor @Accessors(fluent = true, chain = true) -public class TableDeleteOneOptions extends CommandOptions { +public class TableDeleteOneOptions extends BaseOptions { /** * Order by. diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableFindOneOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableFindOneOptions.java index 495d3b20..26d26bc6 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableFindOneOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableFindOneOptions.java @@ -20,7 +20,7 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import com.datastax.astra.client.core.query.Projection; import com.datastax.astra.client.core.query.Sort; import lombok.Getter; @@ -34,7 +34,7 @@ @Getter @Setter @NoArgsConstructor @Accessors(fluent = true, chain = true) -public class TableFindOneOptions extends CommandOptions { +public class TableFindOneOptions extends BaseOptions { /** * Order by. diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableFindOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableFindOptions.java index c6bd8f3a..077dfdf9 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableFindOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableFindOptions.java @@ -20,7 +20,7 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import com.datastax.astra.client.core.query.Projection; import com.datastax.astra.client.core.query.Sort; import lombok.Getter; @@ -34,7 +34,7 @@ @Getter @Setter @NoArgsConstructor @Accessors(fluent = true, chain = true) -public class TableFindOptions extends CommandOptions { +public class TableFindOptions extends BaseOptions { /** * Projections diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableInsertManyOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableInsertManyOptions.java index fc4c8b94..94d97416 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableInsertManyOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableInsertManyOptions.java @@ -21,7 +21,7 @@ */ import com.datastax.astra.client.core.options.DataAPIClientOptions; -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -33,7 +33,7 @@ @Getter @Setter @NoArgsConstructor @Accessors(fluent = true, chain = true) -public class TableInsertManyOptions extends CommandOptions { +public class TableInsertManyOptions extends BaseOptions { /** * If the flag is set to true the command is failing on first error @@ -58,6 +58,6 @@ public class TableInsertManyOptions extends CommandOptions { +public class TableInsertOneOptions extends BaseOptions { } diff --git a/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableUpdateOneOptions.java b/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableUpdateOneOptions.java index 40571532..1d6d0439 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableUpdateOneOptions.java +++ b/astra-db-java/src/main/java/com/datastax/astra/client/tables/options/TableUpdateOneOptions.java @@ -20,7 +20,7 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import com.datastax.astra.client.core.query.Sort; import lombok.Getter; import lombok.NoArgsConstructor; @@ -34,7 +34,7 @@ @Getter @Setter @NoArgsConstructor @Accessors(fluent = true, chain = true) -public class TableUpdateOneOptions extends CommandOptions { +public class TableUpdateOneOptions extends BaseOptions { /** * if upsert is selected diff --git a/astra-db-java/src/main/java/com/datastax/astra/internal/api/DataAPIResponse.java b/astra-db-java/src/main/java/com/datastax/astra/internal/api/DataAPIResponse.java index 6fd6d431..59c7408b 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/internal/api/DataAPIResponse.java +++ b/astra-db-java/src/main/java/com/datastax/astra/internal/api/DataAPIResponse.java @@ -21,7 +21,9 @@ */ import com.datastax.astra.client.collections.documents.Document; +import com.datastax.astra.client.core.commands.Command; import com.datastax.astra.client.exception.DataAPIErrorDescriptor; +import com.datastax.astra.client.exception.UnexpectedDataAPIResponseException; import com.datastax.astra.internal.serdes.DataAPISerializer; import com.datastax.astra.internal.utils.Assert; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -89,7 +91,9 @@ public DataAPIResponse() { */ @SuppressWarnings("unchecked") public Stream getStatusKeyAsStringStream(@NonNull String key) { - Assert.isTrue(status.containsKey(key), "Key not found in status map"); + if (!status.containsKey(key)) { + throw new UnexpectedDataAPIResponseException(new Command(), this, "Key '" + key + "' has not been found in response."); + } return ((ArrayList) status.get(key)).stream(); } diff --git a/astra-db-java/src/main/java/com/datastax/astra/internal/command/AbstractCommandRunner.java b/astra-db-java/src/main/java/com/datastax/astra/internal/command/AbstractCommandRunner.java index 448b5e35..cfa5d10e 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/internal/command/AbstractCommandRunner.java +++ b/astra-db-java/src/main/java/com/datastax/astra/internal/command/AbstractCommandRunner.java @@ -20,15 +20,21 @@ * #L% */ +import com.datastax.astra.client.core.commands.BaseOptions; import com.datastax.astra.client.core.commands.Command; -import com.datastax.astra.client.core.commands.CommandOptions; import com.datastax.astra.client.core.commands.CommandRunner; +import com.datastax.astra.client.core.http.HttpClientOptions; +import com.datastax.astra.client.core.options.DataAPIClientOptions; +import com.datastax.astra.client.core.options.TimeoutOptions; import com.datastax.astra.client.exception.DataAPIResponseException; import com.datastax.astra.internal.api.ApiResponseHttp; import com.datastax.astra.internal.api.DataAPIResponse; import com.datastax.astra.internal.http.RetryHttpClient; import com.datastax.astra.internal.serdes.DataAPISerializer; +import com.datastax.astra.internal.serdes.DatabaseSerializer; +import com.datastax.astra.internal.utils.Assert; import com.datastax.astra.internal.utils.CompletableFutures; +import com.dtsx.astra.sdk.db.domain.Database; import com.evanlennick.retry4j.Status; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -46,6 +52,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; +import static com.datastax.astra.client.exception.InvalidEnvironmentException.throwErrorRestrictedAstra; import static com.datastax.astra.internal.http.RetryHttpClient.CONTENT_TYPE_JSON; import static com.datastax.astra.internal.http.RetryHttpClient.HEADER_ACCEPT; import static com.datastax.astra.internal.http.RetryHttpClient.HEADER_AUTHORIZATION; @@ -59,7 +66,7 @@ */ @Slf4j @Getter -public abstract class AbstractCommandRunner implements CommandRunner { +public abstract class AbstractCommandRunner> implements CommandRunner { // --- Arguments --- @@ -109,10 +116,13 @@ public abstract class AbstractCommandRunner implements CommandRunner { /** Http client reused when properties not override. */ protected RetryHttpClient httpClient; + /** Api Endpoint for the API. */ + protected String apiEndpoint; + /** * Default command options when not override */ - protected CommandOptions commandOptions; + protected OPTIONS options; /** * Default constructor. @@ -120,64 +130,105 @@ public abstract class AbstractCommandRunner implements CommandRunner { protected AbstractCommandRunner() { } - /** - * Drive serialization depending on context (Table, Collection, Database, Devops..) - * - * @return - * serializer for the need. - */ - protected abstract DataAPISerializer getSerializer(); + public AbstractCommandRunner(String apiEndpoint, OPTIONS options) { + Assert.hasLength(apiEndpoint, "apiEndpoint"); + Assert.notNull(options, "options"); + this.apiEndpoint = apiEndpoint; + this.options = options; + } /** {@inheritDoc} */ @Override - public DataAPIResponse runCommand(Command command, CommandOptions spec) { + public DataAPIResponse runCommand(Command command, BaseOptions overridingOptions) { + + // Initializing options with the Collection/Table/Database level options + DataAPIClientOptions options = this.options.getDataAPIClientOptions(); + + // ================== + // === HTTPCLIENT === + // ================== - // === HTTP CLIENT === if (httpClient == null) { - httpClient = new RetryHttpClient(this.commandOptions); + httpClient = new RetryHttpClient(options.getHttpClientOptions(), options.getTimeoutOptions()); } RetryHttpClient requestHttpClient = httpClient; - if (spec!= null && - (spec.getHttpClientOptions() != null || spec.getTimeoutOptions() != null)) { - // We need it to initialize the client - if (spec.getTimeoutOptions() == null) { - spec.timeoutOptions(this.commandOptions.getTimeoutOptions().clone()); - } - if (spec.getHttpClientOptions() == null) { - spec.httpClientOptions(this.commandOptions.getHttpClientOptions().clone()); + + // Should we override the client to use a different one + if (overridingOptions != null && overridingOptions.getDataAPIClientOptions() != null) { + DataAPIClientOptions overClientOptions = overridingOptions.getDataAPIClientOptions(); + HttpClientOptions overHttpClientOptions = overClientOptions.getHttpClientOptions(); + TimeoutOptions overTimeoutOptions = overClientOptions.getTimeoutOptions(); + // User provided specific parameters for the client + if (overHttpClientOptions != null || overTimeoutOptions != null) { + requestHttpClient = new RetryHttpClient( + overHttpClientOptions != null ? overHttpClientOptions : options.getHttpClientOptions(), + overTimeoutOptions != null ? overTimeoutOptions : options.getTimeoutOptions()); } - requestHttpClient = new RetryHttpClient(spec); } + // ================== // === OBSERVERS === - List observers = new ArrayList<>(commandOptions.getObservers().values()); - if (spec != null && spec.getObservers() != null) { - for (Map.Entry observer : spec.getObservers().entrySet()) { - if (!commandOptions.getObservers().containsKey(observer.getKey())) { + // ================== + + List observers = new ArrayList<>(options.getObservers().values()); + if (overridingOptions != null + && overridingOptions.getDataAPIClientOptions() != null + && overridingOptions.getDataAPIClientOptions().getObservers() != null) { + // Specialization has been found + for (Map.Entry observer : overridingOptions + .getDataAPIClientOptions() + .getObservers() + .entrySet()) { + // Add only if not already present + if (!options.getObservers().containsKey(observer.getKey())) { observers.add(observer.getValue()); } } } - // === TOKEN === - String token = commandOptions.getToken(); - if (spec != null && spec.getToken() != null) { - token = spec.getToken(); + // ================== + // === TOKEN === + // ================== + + String token = this.options.getToken(); + if (overridingOptions != null && overridingOptions.getToken() != null) { + token = overridingOptions.getToken(); + } + + // ======================= + // === SERIALIZER === + // ======================= + + DataAPISerializer serializer = this.options.getSerializer(); + if (overridingOptions != null && overridingOptions.getSerializer() != null) { + serializer = overridingOptions.getSerializer(); + } + + // ======================= + // === Timeouts === + // ======================= + + long requestTimeout = this.options.getRequestTimeout(); + if (overridingOptions != null + && overridingOptions.getDataAPIClientOptions() != null + && overridingOptions.getDataAPIClientOptions().getTimeoutOptions() != null) { + requestTimeout = overridingOptions.getRequestTimeout(); } // Initializing the Execution infos (could be pushed to 3rd parties) ExecutionInfos.DataApiExecutionInfoBuilder executionInfo = ExecutionInfos.builder() .withCommand(command) - .withCommandOptions(this.commandOptions) - .withOverrideCommandOptions(spec); + .withCommandOptions(this.options) + .withOverrideCommandOptions(overridingOptions); try { + // (Custom) Serialization different for Tables and Documents - String jsonCommand = getSerializer().marshall(command); + String jsonCommand = serializer.marshall(command); // Build the request - HttpRequest.Builder builder= HttpRequest.newBuilder() + HttpRequest.Builder builder = HttpRequest.newBuilder() .uri(new URI(getApiEndpoint())) .header(HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON) .header(HEADER_ACCEPT, CONTENT_TYPE_JSON) @@ -185,44 +236,39 @@ public DataAPIResponse runCommand(Command command, CommandOptions spec) { .header(HEADER_REQUESTED_WITH, httpClient.getUserAgentHeader()) .header(HEADER_TOKEN, token) .header(HEADER_AUTHORIZATION, "Bearer " + token) - .method("POST", HttpRequest.BodyPublishers.ofString(jsonCommand)); - - // === Request TIMEOUT === - long requestTimeout = commandOptions.selectRequestTimeout(); - if (spec!= null && spec.getTimeoutOptions() != null) { - requestTimeout = CommandOptions.selectRequestTimeout( - commandOptions.getCommandType(), - spec.getTimeoutOptions()); - } + .method("POST", HttpRequest.BodyPublishers.ofString(jsonCommand)) + .timeout(Duration.ofSeconds(requestTimeout / 1000)); - builder.timeout(Duration.ofSeconds(requestTimeout / 1000)); + // ======================= + // === HEADERS === + // ======================= - // === Embedding KEY === - if (commandOptions.getEmbeddingAuthProvider() != null) { - commandOptions.getEmbeddingAuthProvider().getHeaders().forEach(builder::header); + if (options.getEmbeddingAuthProvider() != null) { + options.getEmbeddingAuthProvider().getHeaders().forEach(builder::header); } - if (spec!= null && spec.getEmbeddingAuthProvider() != null) { - spec.getEmbeddingAuthProvider().getHeaders().forEach(builder::header); + if (options.getDatabaseAdditionalHeaders() != null) { + options.getDatabaseAdditionalHeaders().forEach(builder::header); } - - // === Extra Headers (Database) ==== - if (commandOptions.getDatabaseAdditionalHeaders() != null) { - commandOptions.getDatabaseAdditionalHeaders().forEach(builder::header); - } - if (spec!= null && spec.getDatabaseAdditionalHeaders() != null) { - spec.getDatabaseAdditionalHeaders().forEach(builder::header); + if (options.getAdminAdditionalHeaders() != null) { + options.getAdminAdditionalHeaders().forEach(builder::header); } - // === Extra Headers (Admin) ==== - if (commandOptions.getAdminAdditionalHeaders() != null) { - commandOptions.getAdminAdditionalHeaders().forEach(builder::header); - } - if (spec!= null && spec.getAdminAdditionalHeaders() != null) { - spec.getAdminAdditionalHeaders().forEach(builder::header); + if (overridingOptions!= null && overridingOptions.getDataAPIClientOptions() != null) { + DataAPIClientOptions overClientOptions = overridingOptions.getDataAPIClientOptions(); + if (overClientOptions.getEmbeddingAuthProvider() != null) { + overClientOptions.getEmbeddingAuthProvider().getHeaders().forEach(builder::header); + } + if (overClientOptions.getDatabaseAdditionalHeaders() != null) { + overClientOptions.getDatabaseAdditionalHeaders().forEach(builder::header); + } + if (overClientOptions.getAdminAdditionalHeaders() != null) { + overClientOptions.getAdminAdditionalHeaders().forEach(builder::header); + } } + HttpRequest request = builder.build(); - executionInfo.withSerializer(getSerializer()); + executionInfo.withSerializer(serializer); executionInfo.withRequestHeaders(request.headers().map()); executionInfo.withRequestUrl(getApiEndpoint()); @@ -230,11 +276,11 @@ public DataAPIResponse runCommand(Command command, CommandOptions spec) { ApiResponseHttp httpRes = requestHttpClient.parseHttpResponse(status.getResult()); executionInfo.withHttpResponse(httpRes); - DataAPIResponse apiResponse = getSerializer() + DataAPIResponse apiResponse = serializer .unMarshallBean(httpRes.getBody(), DataAPIResponse.class); - apiResponse.setSerializer(getSerializer()); + apiResponse.setSerializer(serializer); if (apiResponse.getStatus() != null) { - apiResponse.getStatus().setSerializer(getSerializer()); + apiResponse.getStatus().setSerializer(serializer); } executionInfo.withApiResponse(apiResponse); @@ -246,10 +292,10 @@ public DataAPIResponse runCommand(Command command, CommandOptions spec) { if (apiResponse.getStatus()!= null && apiResponse.getStatus().getWarnings() != null) { try { apiResponse.getStatus().getWarnings().stream() - .map(getSerializer()::marshall).forEach(log::warn); + .map(this.options.getSerializer()::marshall).forEach(log::warn); } catch(Exception e) { apiResponse.getStatusKeyAsList("warnings", Object.class) - .forEach(error -> log.warn(getSerializer().marshall(error))); + .forEach(error -> log.warn(this.options.getSerializer().marshall(error))); } } return apiResponse; @@ -263,7 +309,7 @@ public DataAPIResponse runCommand(Command command, CommandOptions spec) { /** {@inheritDoc} */ @Override - public T runCommand(Command command, CommandOptions options, Class documentClass) { + public DOC runCommand(Command command, BaseOptions options, Class documentClass) { return unmarshall(runCommand(command, options), documentClass); } @@ -284,6 +330,16 @@ private void notifyASync(Consumer lambda, List } } + protected void assertIsAstra() { + if (!options.getDataAPIClientOptions().isAstra()) { + throwErrorRestrictedAstra("getRegion", options.getDataAPIClientOptions().getDestination()); + } + } + + protected DataAPISerializer getSerializer() { + return this.options.getSerializer(); + } + /** * Document Mapping. * @@ -293,10 +349,10 @@ private void notifyASync(Consumer lambda, List * document class * @return * document - * @param + * @param * document type */ - protected T unmarshall(DataAPIResponse api, Class documentClass) { + protected DOC unmarshall(DataAPIResponse api, Class documentClass) { String payload; if (api.getData() != null) { if (api.getData().getDocument() != null) { @@ -313,12 +369,21 @@ protected T unmarshall(DataAPIResponse api, Class documentClass) { } /** - * The subclass should provide the endpoint, url to post request. + * Gets apiEndpoint * - * @return - * url on which to post the request + * @return value of apiEndpoint */ - protected abstract String getApiEndpoint(); + public String getApiEndpoint() { + return apiEndpoint; + } + /** + * Gets commandOptions + * + * @return value of commandOptions + */ + public OPTIONS getOptions() { + return options; + } } diff --git a/astra-db-java/src/main/java/com/datastax/astra/internal/command/CommandObserver.java b/astra-db-java/src/main/java/com/datastax/astra/internal/command/CommandObserver.java index dce699a4..660bed88 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/internal/command/CommandObserver.java +++ b/astra-db-java/src/main/java/com/datastax/astra/internal/command/CommandObserver.java @@ -41,7 +41,7 @@ *
  • Triggering additional processes or workflows based on the success or failure of a command.
  • * */ -public interface CommandObserver { +public interface CommandObserver extends Cloneable { /** * Invoked when a command is executed, providing an opportunity for registered observers to perform diff --git a/astra-db-java/src/main/java/com/datastax/astra/internal/command/ExecutionInfos.java b/astra-db-java/src/main/java/com/datastax/astra/internal/command/ExecutionInfos.java index 56acfe59..a531ab6a 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/internal/command/ExecutionInfos.java +++ b/astra-db-java/src/main/java/com/datastax/astra/internal/command/ExecutionInfos.java @@ -20,7 +20,7 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import com.datastax.astra.internal.api.DataAPIResponse; import com.datastax.astra.internal.api.ApiResponseHttp; import com.datastax.astra.client.core.commands.Command; @@ -51,13 +51,13 @@ public class ExecutionInfos implements Serializable { * The original command request that was executed. This field provides access to the details of the * command that triggered the execution, allowing observers to understand what operation was performed. */ - private final CommandOptions commandOptions; + private final BaseOptions baseOptions; /** * The original command request that was executed. This field provides access to the details of the * command that triggered the execution, allowing observers to understand what operation was performed. */ - private final CommandOptions overridingCommandOptions; + private final BaseOptions overridingBaseOptions; /** * A map containing the HTTP headers from the request. @@ -120,8 +120,8 @@ private ExecutionInfos(DataApiExecutionInfoBuilder builder) { this.executionDate = builder.executionDate; this.requestUrl = builder.requestUrl; this.serializer = builder.serializer; - this.commandOptions = builder.commandOptions; - this.overridingCommandOptions = builder.specialOptions; + this.baseOptions = builder.baseOptions; + this.overridingBaseOptions = builder.specialOptions; } /** @@ -139,8 +139,8 @@ public static DataApiExecutionInfoBuilder builder() { */ public static class DataApiExecutionInfoBuilder { private Command command; - private CommandOptions commandOptions; - private CommandOptions specialOptions; + private BaseOptions baseOptions; + private BaseOptions specialOptions; private DataAPIResponse response; private long executionTime; private int responseHttpCode; @@ -173,26 +173,26 @@ public DataApiExecutionInfoBuilder withCommand(Command command) { /** * Populate after http call. * - * @param commandOptions + * @param baseOptions * current command options * @return * current reference */ - public DataApiExecutionInfoBuilder withCommandOptions(CommandOptions commandOptions) { - this.commandOptions = commandOptions; + public DataApiExecutionInfoBuilder withCommandOptions(BaseOptions baseOptions) { + this.baseOptions = baseOptions; return this; } /** * Populate after http call. * - * @param commandOptions + * @param baseOptions * special commands * @return * current reference */ - public DataApiExecutionInfoBuilder withOverrideCommandOptions(CommandOptions commandOptions) { - this.specialOptions = commandOptions; + public DataApiExecutionInfoBuilder withOverrideCommandOptions(BaseOptions baseOptions) { + this.specialOptions = baseOptions; return this; } diff --git a/astra-db-java/src/main/java/com/datastax/astra/internal/http/RetryHttpClient.java b/astra-db-java/src/main/java/com/datastax/astra/internal/http/RetryHttpClient.java index 6526336a..98b32fbd 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/internal/http/RetryHttpClient.java +++ b/astra-db-java/src/main/java/com/datastax/astra/internal/http/RetryHttpClient.java @@ -20,7 +20,6 @@ * #L% */ -import com.datastax.astra.client.core.commands.CommandOptions; import com.datastax.astra.client.core.http.Caller; import com.datastax.astra.client.core.http.HttpClientOptions; import com.datastax.astra.client.core.options.TimeoutOptions; @@ -36,8 +35,6 @@ import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.ProxySelector; -import java.net.URI; -import java.net.URISyntaxException; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; @@ -90,23 +87,6 @@ public class RetryHttpClient { /** Default retry configuration. */ protected final RetryConfig retryConfig; - /** - * Default constructor. - */ - public RetryHttpClient() { - this(new HttpClientOptions(), new TimeoutOptions()); - } - - /** - * Initialize the instance with all items - * - * @param commandOptions - * configuration of the HTTP CLIENT. - */ - public RetryHttpClient(CommandOptions commandOptions) { - this(commandOptions.getHttpClientOptions(), commandOptions.getTimeoutOptions()); - } - /** * Initialize the instance with all items * @@ -116,17 +96,17 @@ public RetryHttpClient(CommandOptions commandOptions) { * timeout options * */ - protected RetryHttpClient(HttpClientOptions httpClientOptions, TimeoutOptions timeoutOptions) { + public RetryHttpClient(HttpClientOptions httpClientOptions, TimeoutOptions timeoutOptions) { this.httpClientOptions = httpClientOptions; this.timeoutOptions = timeoutOptions; HttpClient.Builder httpClientBuilder = HttpClient.newBuilder(); httpClientBuilder.version(httpClientOptions.getHttpVersion()); httpClientBuilder.followRedirects(httpClientOptions.getHttpRedirect()); - httpClientBuilder.connectTimeout(Duration.ofMillis(timeoutOptions.connectTimeoutMillis())); - if (httpClientOptions.getProxy() != null) { + httpClientBuilder.connectTimeout(Duration.ofMillis(timeoutOptions.getConnectTimeoutMillis())); + if (httpClientOptions.getHttpProxy() != null) { httpClientBuilder.proxy(ProxySelector.of(new InetSocketAddress( - httpClientOptions.getProxy().getHostname(), - httpClientOptions.getProxy().getPort()))); + httpClientOptions.getHttpProxy().getHostname(), + httpClientOptions.getHttpProxy().getPort()))); } httpClient = httpClientBuilder.build(); @@ -154,64 +134,6 @@ public String getUserAgentHeader() { return sj.toString(); } - // ------------------------------------------- - // ---------- Working with HTTP -------------- - // ------------------------------------------- - - /** - * Helper to build the HTTP request. - * - * @param url - * target url - * @param token - * authentication token - * @param body - * request body - * @return - * http request - */ - public ApiResponseHttp post(String url, String token, String body) { - return executeHttp("POST", url, token, body, CONTENT_TYPE_JSON); - } - - /** - * Help to build the HTTP request. - * - * @param method - * http method name - * @param url - * http url - * @param token - * token for authorization header - * @param body - * body of the request - * @param contentType - * content type for the request - * @return - * the http request. - */ - public HttpRequest builtHttpRequest(final String method, - final String url, - final String token, - String body, - String contentType) { - try { - return HttpRequest.newBuilder() - .uri(new URI(url)) - .header(HEADER_CONTENT_TYPE, contentType) - .header(HEADER_ACCEPT, CONTENT_TYPE_JSON) - .header(HEADER_USER_AGENT, getUserAgentHeader()) - .header(HEADER_REQUESTED_WITH, getUserAgentHeader()) - .header(HEADER_TOKEN, token) - .header(HEADER_AUTHORIZATION, "Bearer " + token) - .timeout(Duration.ofMillis(timeoutOptions.requestTimeoutMillis())) - .method(method, HttpRequest.BodyPublishers.ofString(body)) - .build(); - } catch(URISyntaxException e) { - throw new IllegalArgumentException("Invalid URL '" + url + "'", e); - } - } - /** * Parse HTTP response as a ApiResponseHttp. * @@ -236,32 +158,6 @@ public ApiResponseHttp parseHttpResponse(HttpResponse response) { return res; } - /** - * Main Method executing HTTP Request. - * - * @param method - * http method - * @param url - * url - * @param token - * authentication token - * @param contentType - * request content type - * @param body - * request body - * @return - * basic request - */ - public ApiResponseHttp executeHttp(final String method, - final String url, - final String token, - String body, - String contentType) { - HttpRequest httpRequest = builtHttpRequest(method, url, token, body, contentType); - Status> status = executeHttpRequest(httpRequest); - return parseHttpResponse(status.getResult()); - } - /** * Implementing retries. * diff --git a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/DatabaseSerializer.java b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/DatabaseSerializer.java index c9a56509..7efc0ae4 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/DatabaseSerializer.java +++ b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/DatabaseSerializer.java @@ -36,7 +36,16 @@ * #L% */ +import com.datastax.astra.client.collections.CollectionDefaultIdTypes; +import com.datastax.astra.client.core.vector.DataAPIVector; +import com.datastax.astra.client.core.vector.SimilarityMetric; import com.datastax.astra.client.tables.columns.ColumnTypes; +import com.datastax.astra.internal.serdes.collections.CollectionDefaultIdTypeDeserializer; +import com.datastax.astra.internal.serdes.collections.CollectionDefaultIdTypeSerializer; +import com.datastax.astra.internal.serdes.shared.DataAPIVectorDeserializer; +import com.datastax.astra.internal.serdes.shared.DataAPIVectorSerializer; +import com.datastax.astra.internal.serdes.shared.SimilarityMetricDeserializer; +import com.datastax.astra.internal.serdes.shared.SimilarityMetricSerializer; import com.datastax.astra.internal.serdes.tables.ColumnTypeDeserializer; import com.datastax.astra.internal.serdes.tables.ColumnTypeSerializer; import com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -95,6 +104,15 @@ public ObjectMapper getMapper() { SimpleModule module = new SimpleModule(); module.addSerializer(ColumnTypes.class, new ColumnTypeSerializer()); module.addDeserializer(ColumnTypes.class, new ColumnTypeDeserializer()); + // DefaultId + module.addSerializer(CollectionDefaultIdTypes.class, new CollectionDefaultIdTypeSerializer()); + module.addDeserializer(CollectionDefaultIdTypes.class, new CollectionDefaultIdTypeDeserializer()); + // Similarity Metric + module.addSerializer(SimilarityMetric.class, new SimilarityMetricSerializer()); + module.addDeserializer(SimilarityMetric.class, new SimilarityMetricDeserializer()); + // DataAPIVector + module.addSerializer(DataAPIVector.class, new DataAPIVectorSerializer()); + module.addDeserializer(DataAPIVector.class, new DataAPIVectorDeserializer()); objectMapper.registerModule(module); } return objectMapper; diff --git a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/collections/CollectionDefaultIdTypeDeserializer.java b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/collections/CollectionDefaultIdTypeDeserializer.java new file mode 100644 index 00000000..8fce2e72 --- /dev/null +++ b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/collections/CollectionDefaultIdTypeDeserializer.java @@ -0,0 +1,52 @@ +package com.datastax.astra.internal.serdes.collections; + +/*- + * #%L + * Data API Java Client + * -- + * Copyright (C) 2024 DataStax + * -- + * Licensed under the Apache License, Version 2.0 + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.datastax.astra.client.collections.CollectionDefaultIdTypes; +import com.datastax.astra.client.core.vector.SimilarityMetric; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; + +/** + * Custom deserializer for EJson Date type. + */ +public class CollectionDefaultIdTypeDeserializer extends JsonDeserializer { + + /** + * Default constructor. + */ + public CollectionDefaultIdTypeDeserializer() { + // left blank, will be populated by jackson + } + + /** {@inheritDoc} */ + @Override + public CollectionDefaultIdTypes deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException { + JsonNode node = jp.getCodec().readTree(jp); + return CollectionDefaultIdTypes.fromValue(node.asText()); + } + +} diff --git a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/collections/CollectionDefaultIdTypeSerializer.java b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/collections/CollectionDefaultIdTypeSerializer.java new file mode 100644 index 00000000..65dd87a0 --- /dev/null +++ b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/collections/CollectionDefaultIdTypeSerializer.java @@ -0,0 +1,53 @@ +package com.datastax.astra.internal.serdes.collections; + +/*- + * #%L + * Data API Java Client + * -- + * Copyright (C) 2024 DataStax + * -- + * Licensed under the Apache License, Version 2.0 + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.datastax.astra.client.collections.CollectionDefaultIdTypes; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import java.io.IOException; + +public class CollectionDefaultIdTypeSerializer extends StdSerializer { + /** + * Default constructor. + */ + public CollectionDefaultIdTypeSerializer() { + this(null); + } + + /** + * Constructor with type + * @param t + * type + */ + public CollectionDefaultIdTypeSerializer(Class t) { + super(t); + } + + /** {@inheritDoc} */ + @Override + public void serialize(CollectionDefaultIdTypes defaultId, JsonGenerator gen, SerializerProvider provider) + throws IOException { + gen.writeString(defaultId.getValue()); + } +} diff --git a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/collections/DocumentSerializer.java b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/collections/DocumentSerializer.java index 2f9c51e0..08b1c196 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/collections/DocumentSerializer.java +++ b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/collections/DocumentSerializer.java @@ -36,16 +36,17 @@ * #L% */ +import com.datastax.astra.client.collections.CollectionDefaultIdTypes; import com.datastax.astra.client.core.types.ObjectId; import com.datastax.astra.client.core.types.UUIDv6; import com.datastax.astra.client.core.types.UUIDv7; import com.datastax.astra.client.core.vector.DataAPIVector; import com.datastax.astra.client.core.vector.SimilarityMetric; import com.datastax.astra.internal.serdes.DataAPISerializer; -import com.datastax.astra.internal.serdes.DataAPIVectorDeserializer; -import com.datastax.astra.internal.serdes.DataAPIVectorSerializer; -import com.datastax.astra.internal.serdes.tables.SimilarityMetricDeserializer; -import com.datastax.astra.internal.serdes.tables.SimilarityMetricSerializer; +import com.datastax.astra.internal.serdes.shared.DataAPIVectorDeserializer; +import com.datastax.astra.internal.serdes.shared.DataAPIVectorSerializer; +import com.datastax.astra.internal.serdes.shared.SimilarityMetricDeserializer; +import com.datastax.astra.internal.serdes.shared.SimilarityMetricSerializer; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.StreamReadFeature; @@ -125,6 +126,9 @@ public ObjectMapper getMapper() { // ObjectId module.addSerializer(ObjectId.class, new ObjectIdSerializer()); module.addDeserializer(ObjectId.class, new ObjectIdDeserializer()); + // DefaultId + module.addSerializer(CollectionDefaultIdTypes.class, new CollectionDefaultIdTypeSerializer()); + module.addDeserializer(CollectionDefaultIdTypes.class, new CollectionDefaultIdTypeDeserializer()); // Similarity Metric module.addSerializer(SimilarityMetric.class, new SimilarityMetricSerializer()); module.addDeserializer(SimilarityMetric.class, new SimilarityMetricDeserializer()); diff --git a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/DataAPIVectorDeserializer.java b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/shared/DataAPIVectorDeserializer.java similarity index 98% rename from astra-db-java/src/main/java/com/datastax/astra/internal/serdes/DataAPIVectorDeserializer.java rename to astra-db-java/src/main/java/com/datastax/astra/internal/serdes/shared/DataAPIVectorDeserializer.java index 268ff1d4..6048798a 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/DataAPIVectorDeserializer.java +++ b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/shared/DataAPIVectorDeserializer.java @@ -1,4 +1,4 @@ -package com.datastax.astra.internal.serdes; +package com.datastax.astra.internal.serdes.shared; /*- * #%L diff --git a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/DataAPIVectorSerializer.java b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/shared/DataAPIVectorSerializer.java similarity index 94% rename from astra-db-java/src/main/java/com/datastax/astra/internal/serdes/DataAPIVectorSerializer.java rename to astra-db-java/src/main/java/com/datastax/astra/internal/serdes/shared/DataAPIVectorSerializer.java index 492619a5..f7c8aa71 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/DataAPIVectorSerializer.java +++ b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/shared/DataAPIVectorSerializer.java @@ -1,4 +1,4 @@ -package com.datastax.astra.internal.serdes; +package com.datastax.astra.internal.serdes.shared; /*- * #%L @@ -41,7 +41,7 @@ public DataAPIVectorSerializer() { public void serialize(DataAPIVector dataApiVector, JsonGenerator gen, SerializerProvider provider) throws IOException { if (dataApiVector == null) { gen.writeNull(); - } else if (DataAPIClientOptions.encodeDataApiVectorsAsBase64) { + } else if (DataAPIClientOptions.getSerdesOptions().isEncodeDataApiVectorsAsBase64()) { // Binary ENCODING final int vectorLen = dataApiVector.getEmbeddings().length; final byte[] b = new byte[vectorLen << 2]; diff --git a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/SimilarityMetricDeserializer.java b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/shared/SimilarityMetricDeserializer.java similarity index 93% rename from astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/SimilarityMetricDeserializer.java rename to astra-db-java/src/main/java/com/datastax/astra/internal/serdes/shared/SimilarityMetricDeserializer.java index 67828723..6e55ab6a 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/SimilarityMetricDeserializer.java +++ b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/shared/SimilarityMetricDeserializer.java @@ -1,4 +1,4 @@ -package com.datastax.astra.internal.serdes.tables; +package com.datastax.astra.internal.serdes.shared; /*- * #%L @@ -21,7 +21,6 @@ */ import com.datastax.astra.client.core.vector.SimilarityMetric; -import com.datastax.astra.client.tables.columns.ColumnTypes; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; diff --git a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/SimilarityMetricSerializer.java b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/shared/SimilarityMetricSerializer.java similarity index 93% rename from astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/SimilarityMetricSerializer.java rename to astra-db-java/src/main/java/com/datastax/astra/internal/serdes/shared/SimilarityMetricSerializer.java index 6d62b615..0a3e8ca6 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/SimilarityMetricSerializer.java +++ b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/shared/SimilarityMetricSerializer.java @@ -1,4 +1,4 @@ -package com.datastax.astra.internal.serdes.tables; +package com.datastax.astra.internal.serdes.shared; /*- * #%L @@ -21,7 +21,6 @@ */ import com.datastax.astra.client.core.vector.SimilarityMetric; -import com.datastax.astra.client.tables.columns.ColumnTypes; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.std.StdSerializer; diff --git a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/DurationDeserializer.java b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/DurationDeserializer.java index 9688fb8c..e09b9baa 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/DurationDeserializer.java +++ b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/DurationDeserializer.java @@ -9,9 +9,9 @@ * Licensed under the Apache License, Version 2.0 * You may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,6 +20,7 @@ * #L% */ +import com.datastax.astra.client.core.options.DataAPIClientOptions; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; @@ -30,8 +31,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import static com.datastax.astra.client.core.options.DataAPIClientOptions.encodeDurationAsISO8601; - public class DurationDeserializer extends JsonDeserializer { private static final Pattern DURATION_PATTERN = Pattern.compile("(\\d+)([a-zA-Zµ]+)"); @@ -44,7 +43,7 @@ public Duration deserialize(JsonParser p, DeserializationContext ctxt) throws IO return null; } - if (encodeDurationAsISO8601) { + if (DataAPIClientOptions.getSerdesOptions().isEncodeDurationAsISO8601()) { return Duration.parse(text); } diff --git a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/DurationSerializer.java b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/DurationSerializer.java index 9f1b8c79..2b765bbb 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/DurationSerializer.java +++ b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/DurationSerializer.java @@ -20,6 +20,7 @@ * #L% */ +import com.datastax.astra.client.core.options.DataAPIClientOptions; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.std.StdSerializer; @@ -27,8 +28,6 @@ import java.io.IOException; import java.time.Duration; -import static com.datastax.astra.client.core.options.DataAPIClientOptions.encodeDurationAsISO8601; - /** * Serialize a date as compact or ISO8601 format. */ @@ -45,7 +44,7 @@ public void serialize(Duration duration, JsonGenerator gen, SerializerProvider s return; } - if (encodeDurationAsISO8601) { + if (DataAPIClientOptions.getSerdesOptions().isEncodeDurationAsISO8601()) { gen.writeString(duration.toString()); return; } diff --git a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/RowSerializer.java b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/RowSerializer.java index c988ed48..44c9b83c 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/RowSerializer.java +++ b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/RowSerializer.java @@ -41,8 +41,10 @@ import com.datastax.astra.client.tables.TableDuration; import com.datastax.astra.client.tables.columns.ColumnTypes; import com.datastax.astra.internal.serdes.DataAPISerializer; -import com.datastax.astra.internal.serdes.DataAPIVectorDeserializer; -import com.datastax.astra.internal.serdes.DataAPIVectorSerializer; +import com.datastax.astra.internal.serdes.shared.DataAPIVectorDeserializer; +import com.datastax.astra.internal.serdes.shared.DataAPIVectorSerializer; +import com.datastax.astra.internal.serdes.shared.SimilarityMetricDeserializer; +import com.datastax.astra.internal.serdes.shared.SimilarityMetricSerializer; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.StreamReadFeature; @@ -78,15 +80,9 @@ public class RowSerializer implements DataAPISerializer { * Default constructor */ public RowSerializer() { - // left blank, hiding constructor for utility class } - /** - * Building the data api specific object mapper. - * - * @return - * object mapper. - */ + @Override public ObjectMapper getMapper() { if (objectMapper == null) { JsonFactory jsonFactory = JsonFactory.builder() @@ -148,7 +144,4 @@ public ObjectMapper getMapper() { } return objectMapper; } - - - } diff --git a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/TableDurationSerializer.java b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/TableDurationSerializer.java index ac2128fa..0c1a5245 100644 --- a/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/TableDurationSerializer.java +++ b/astra-db-java/src/main/java/com/datastax/astra/internal/serdes/tables/TableDurationSerializer.java @@ -20,16 +20,14 @@ * #L% */ +import com.datastax.astra.client.core.options.DataAPIClientOptions; import com.datastax.astra.client.tables.TableDuration; - import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import java.io.IOException; -import static com.datastax.astra.client.core.options.DataAPIClientOptions.encodeDurationAsISO8601; - public class TableDurationSerializer extends JsonSerializer { @Override @@ -41,7 +39,7 @@ public void serialize(TableDuration value, JsonGenerator gen, SerializerProvider } // Flag iso 8601 is enabled - if (encodeDurationAsISO8601) { + if (DataAPIClientOptions.getSerdesOptions().isEncodeDurationAsISO8601()) { gen.writeString(value.toISO8601()); return; } diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/integration/AbstractCollectionITTest.java b/astra-db-java/src/test/java/com/datastax/astra/test/integration/AbstractCollectionITTest.java index fe30711e..d3aecd1c 100644 --- a/astra-db-java/src/test/java/com/datastax/astra/test/integration/AbstractCollectionITTest.java +++ b/astra-db-java/src/test/java/com/datastax/astra/test/integration/AbstractCollectionITTest.java @@ -1,7 +1,7 @@ package com.datastax.astra.test.integration; import com.datastax.astra.client.collections.Collection; -import com.datastax.astra.client.collections.CollectionOptions; +import com.datastax.astra.client.collections.CollectionDefinition; import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.collections.documents.Update; import com.datastax.astra.client.collections.exceptions.TooManyDocumentsToCountException; @@ -45,6 +45,7 @@ import java.util.stream.IntStream; import static com.datastax.astra.client.collections.documents.ReturnDocument.AFTER; +import static com.datastax.astra.client.core.options.DataAPIClientOptions.MAX_COUNT; import static com.datastax.astra.client.core.query.Filters.eq; import static com.datastax.astra.client.core.query.Filters.gt; import static com.datastax.astra.client.core.query.Projection.include; @@ -88,26 +89,21 @@ protected Collection getCollectionSimple() { protected Collection getCollectionVector() { if (collectionVector == null) { - collectionVector = getDatabase().createCollection(COLLECTION_VECTOR, - CollectionOptions - .builder() - .vectorDimension(14) - .vectorSimilarity(SimilarityMetric.COSINE) - .build(), ProductString.class); + collectionVector = getDatabase().createCollection(COLLECTION_VECTOR, new CollectionDefinition() + .vector(14, SimilarityMetric.COSINE) + , ProductString.class); } - return collectionVector; } @Test @Order(1) protected void shouldPopulateGeneralInformation() { - assertThat(getCollectionSimple().getOptions()).isNotNull(); - assertThat(getCollectionSimple().getName()).isNotNull(); + assertThat(getCollectionSimple().getDefinition()).isNotNull(); + assertThat(getCollectionSimple().getCollectionName()).isNotNull(); assertThat(getCollectionSimple().getDocumentClass()).isNotExactlyInstanceOf(Document.class); assertThat(getCollectionSimple().getKeyspaceName()).isNotNull(); - assertThat(getCollectionVector().getOptions()).isNotNull(); - assertThat(getCollectionVector().getName()).isNotNull(); + assertThat(getCollectionVector().getCollectionName()).isNotNull(); assertThat(getCollectionVector().getDocumentClass()).isNotExactlyInstanceOf(Document.class); assertThat(getCollectionVector().getKeyspaceName()).isNotNull(); } @@ -245,7 +241,7 @@ protected void testCountDocument() throws TooManyDocumentsToCountException { // Add a filter assertThat(getCollectionSimple() - .countDocuments(gt("indice", 3), getCollectionSimple().getDataAPIClientOptions().getMaxCount()) ) + .countDocuments(gt("indice", 3), MAX_COUNT)) .isEqualTo(6); // Filter + limit @@ -259,7 +255,7 @@ protected void testCountDocument() throws TooManyDocumentsToCountException { // More than 1000 items assertThatThrownBy(() -> getCollectionSimple() - .countDocuments(getCollectionSimple().getDataAPIClientOptions().getMaxCount())) + .countDocuments(MAX_COUNT)) .isInstanceOf(TooManyDocumentsToCountException.class) .hasMessageContaining("server"); } @@ -324,7 +320,7 @@ public void testInsertManyWithPagingDistributed() throws TooManyDocumentsToCount getCollectionSimple().insertMany(docList, new CollectionInsertManyOptions() .concurrency(5) .timeoutOptions(new TimeoutOptions() - .dataOperationTimeoutMillis(100000)) // I need 100s. + .generalMethodTimeoutMillis(100000)) // I need 100s. ); assertThat(getCollectionSimple().countDocuments(100)).isEqualTo(55); } diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/integration/AbstractDataAPITest.java b/astra-db-java/src/test/java/com/datastax/astra/test/integration/AbstractDataAPITest.java index f10665e2..2e9f7bfd 100644 --- a/astra-db-java/src/test/java/com/datastax/astra/test/integration/AbstractDataAPITest.java +++ b/astra-db-java/src/test/java/com/datastax/astra/test/integration/AbstractDataAPITest.java @@ -157,13 +157,13 @@ public static class ProductObjectUUID extends Product {} protected DataAPIClient getDataApiClient(AstraEnvironment env) { switch (env) { case DEV: return DataAPIClients - .createForAstraDev(Utils.readEnvVariable(ASTRA_DB_APPLICATION_TOKEN_DEV) + .astraDev(Utils.readEnvVariable(ASTRA_DB_APPLICATION_TOKEN_DEV) .orElseThrow(() -> new IllegalStateException(ASTRA_DB_APPLICATION_TOKEN_DEV + " env var is missing"))); case PROD: return DataAPIClients - .create(Utils.readEnvVariable(ASTRA_DB_APPLICATION_TOKEN) + .astra(Utils.readEnvVariable(ASTRA_DB_APPLICATION_TOKEN) .orElseThrow(() -> new IllegalStateException(ASTRA_DB_APPLICATION_TOKEN + " env var is missing"))); case TEST: return DataAPIClients - .createForAstraTest(Utils.readEnvVariable(ASTRA_DB_APPLICATION_TOKEN_TEST) + .astraTest(Utils.readEnvVariable(ASTRA_DB_APPLICATION_TOKEN_TEST) .orElseThrow(() -> new IllegalStateException(ASTRA_DB_APPLICATION_TOKEN_TEST + " env var is missing"))); default: throw new IllegalArgumentException("Invalid Environment"); diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/integration/AbstractDatabaseAdminITTest.java b/astra-db-java/src/test/java/com/datastax/astra/test/integration/AbstractDatabaseAdminITTest.java index 02961c92..6c036266 100644 --- a/astra-db-java/src/test/java/com/datastax/astra/test/integration/AbstractDatabaseAdminITTest.java +++ b/astra-db-java/src/test/java/com/datastax/astra/test/integration/AbstractDatabaseAdminITTest.java @@ -80,24 +80,24 @@ void shouldKeyNamespaceDefault() throws InterruptedException { // Then assertThat(getDatabaseAdmin().keyspaceExists("nsx")).isTrue(); assertThat(getDatabaseAdmin().getDatabase("nsx") - .getKeyspaceName()).isEqualTo("nsx"); + .getKeyspace()).isEqualTo("nsx"); if (!getDatabaseAdmin().keyspaceExists("nsx2")) { getDatabaseAdmin().createKeyspace("nsx2", true); while (!getDatabaseAdmin().keyspaceExists("nsx2")) { Thread.sleep(1000); } - assertThat(getDatabaseAdmin().getDatabase().getKeyspaceName()).isEqualTo("nsx2"); + assertThat(getDatabaseAdmin().getDatabase().getKeyspace()).isEqualTo("nsx2"); } // Surface final DatabaseAdmin dbAdmin2 = getDatabaseAdmin(); assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> dbAdmin2.createKeyspace(null)) - .withMessage("Parameter 'keyspace' should be null nor empty"); + .withMessage("Parameter 'keyspaceName' should be null nor empty"); assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> dbAdmin2.createKeyspace("")) - .withMessage("Parameter 'keyspace' should be null nor empty"); + .withMessage("Parameter 'keyspaceName' should be null nor empty"); } @Test diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/integration/AbstractDatabaseTest.java b/astra-db-java/src/test/java/com/datastax/astra/test/integration/AbstractDatabaseTest.java index 293d827d..d1077c3d 100644 --- a/astra-db-java/src/test/java/com/datastax/astra/test/integration/AbstractDatabaseTest.java +++ b/astra-db-java/src/test/java/com/datastax/astra/test/integration/AbstractDatabaseTest.java @@ -1,18 +1,30 @@ package com.datastax.astra.test.integration; -import com.datastax.astra.client.collections.CollectionIdTypes; +import com.datastax.astra.client.collections.Collection; +import com.datastax.astra.client.collections.CollectionDefaultIdTypes; +import com.datastax.astra.client.collections.CollectionDefinition; +import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.collections.results.CollectionInsertManyResult; import com.datastax.astra.client.collections.results.CollectionInsertOneResult; +import com.datastax.astra.client.core.auth.EmbeddingAPIKeyHeaderProvider; +import com.datastax.astra.client.core.commands.Command; import com.datastax.astra.client.core.types.ObjectId; import com.datastax.astra.client.core.types.UUIDv6; import com.datastax.astra.client.core.types.UUIDv7; -import com.datastax.astra.client.collections.Collection; -import com.datastax.astra.client.exception.DataAPIException; -import com.datastax.astra.client.collections.CollectionOptions; -import com.datastax.astra.client.core.commands.Command; -import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.core.vector.SimilarityMetric; +import com.datastax.astra.client.databases.options.ListTablesOptions; +import com.datastax.astra.client.exception.DataAPIException; +import com.datastax.astra.client.tables.Table; +import com.datastax.astra.client.tables.TableDefinition; +import com.datastax.astra.client.tables.TableDescriptor; +import com.datastax.astra.client.tables.TableOptions; +import com.datastax.astra.client.tables.columns.ColumnDefinitionVector; +import com.datastax.astra.client.tables.columns.ColumnTypes; +import com.datastax.astra.client.tables.ddl.CreateTableOptions; +import com.datastax.astra.client.tables.row.Row; import com.datastax.astra.internal.api.DataAPIResponse; +import com.datastax.astra.test.model.TableEntityGameWithAnnotation; +import com.datastax.astra.test.model.TableEntityGameWithAnnotationAllHints; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Data; @@ -23,15 +35,19 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; +import java.time.Duration; import java.util.List; import java.util.Optional; import java.util.UUID; -import java.util.stream.Collectors; -import static com.datastax.astra.client.collections.CollectionIdTypes.OBJECT_ID; -import static com.datastax.astra.client.collections.CollectionIdTypes.UUIDV6; -import static com.datastax.astra.client.collections.CollectionIdTypes.UUIDV7; +import static com.datastax.astra.client.collections.CollectionDefaultIdTypes.OBJECT_ID; +import static com.datastax.astra.client.collections.CollectionDefaultIdTypes.UUIDV6; +import static com.datastax.astra.client.collections.CollectionDefaultIdTypes.UUIDV7; import static com.datastax.astra.client.core.query.Filters.eq; +import static com.datastax.astra.client.core.query.Sort.ascending; +import static com.datastax.astra.client.core.vector.SimilarityMetric.COSINE; +import static com.datastax.astra.client.tables.ddl.DropTableOptions.IF_EXISTS; +import static java.time.Duration.ofSeconds; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -46,47 +62,52 @@ public abstract class AbstractDatabaseTest extends AbstractDataAPITest { // --------- Collections -------------- // ------------------------------------ + public void cleanupCollections() { + // Removing a few collections to test mores elements + getDatabase().listCollectionNames().forEach(c -> { + System.out.println("Dropping collection ..." + c); + getDatabase().dropCollection(c); + }); + } + @Test @Order(1) public void shouldCreateCollectionSimple() { + cleanupCollections(); // When getDatabase().createCollection(COLLECTION_SIMPLE); assertThat(getDatabase().collectionExists(COLLECTION_SIMPLE)).isTrue(); // When Collection collection_simple = getDatabase().getCollection(COLLECTION_SIMPLE); assertThat(collection_simple).isNotNull(); - assertThat(collection_simple.getName()).isEqualTo(COLLECTION_SIMPLE); + assertThat(collection_simple.getCollectionName()).isEqualTo(COLLECTION_SIMPLE); Collection c1 = getDatabase().createCollection(COLLECTION_SIMPLE, Document.class); assertThat(c1).isNotNull(); - assertThat(c1.getName()).isEqualTo(COLLECTION_SIMPLE); + assertThat(c1.getCollectionName()).isEqualTo(COLLECTION_SIMPLE); } @Test @Order(2) public void shouldCreateCollectionsVector() { Collection collectionVector = getDatabase().createCollection(COLLECTION_VECTOR, - CollectionOptions.builder() - .vectorDimension(14) - .vectorSimilarity(SimilarityMetric.COSINE) - .build()); + new CollectionDefinition().vector(14, SimilarityMetric.COSINE)); + assertThat(collectionVector).isNotNull(); - assertThat(collectionVector.getName()).isEqualTo(COLLECTION_VECTOR); + assertThat(collectionVector.getCollectionName()).isEqualTo(COLLECTION_VECTOR); - CollectionOptions options = collectionVector.getOptions(); - assertThat(options.getVector()).isNotNull(); - assertThat(options.getVector().getDimension()).isEqualTo(14); + CollectionDefinition colDefinition = collectionVector.getDefinition(); + assertThat(colDefinition.getVector()).isNotNull(); + assertThat(colDefinition.getVector().getDimension()).isEqualTo(14); } @Test @Order(3) public void shouldCreateCollectionsAllows() { Collection collectionAllow = getDatabase().createCollection(COLLECTION_ALLOW, - CollectionOptions.builder() - .indexingAllow("a", "b", "c") - .build()); + new CollectionDefinition().indexingAllow("a", "b", "c")); assertThat(collectionAllow).isNotNull(); - CollectionOptions options = collectionAllow.getOptions(); + CollectionDefinition options = collectionAllow.getDefinition(); assertThat(options.getIndexing()).isNotNull(); assertThat(options.getIndexing().getAllow()).isNotNull(); } @@ -95,11 +116,9 @@ public void shouldCreateCollectionsAllows() { @Order(4) public void shouldCreateCollectionsDeny() { Collection collectionDeny = getDatabase().createCollection(COLLECTION_DENY, - CollectionOptions.builder() - .indexingDeny("a", "b", "c") - .build()); + new CollectionDefinition().indexingDeny("a", "b", "c")); assertThat(collectionDeny).isNotNull(); - CollectionOptions options = collectionDeny.getOptions(); + CollectionDefinition options = collectionDeny.getDefinition(); assertThat(options.getIndexing()).isNotNull(); assertThat(options.getIndexing().getDeny()).isNotNull(); } @@ -108,7 +127,7 @@ public void shouldCreateCollectionsDeny() { @Order(5) public void shouldListCollections() { shouldCreateCollectionSimple(); - assertThat(getDatabase().listCollectionNames().collect(Collectors.toList())).isNotNull(); + assertThat(getDatabase().listCollectionNames()).isNotNull(); } @Test @@ -128,9 +147,7 @@ public void shouldDropCollectionAllow() { public void shouldDropCollectionsDeny() { // Given Collection collectionDeny = getDatabase().createCollection(COLLECTION_DENY, - CollectionOptions.builder() - .indexingDeny("a", "b", "c") - .build()); + new CollectionDefinition().indexingDeny("a", "b", "c")); assertThat(getDatabase().collectionExists(COLLECTION_DENY)).isTrue(); // When collectionDeny.drop(); @@ -165,10 +182,10 @@ public void shouldErrorGetIfCollectionDoesNotExists() { Collection collection = getDatabase().getCollection("invalid"); assertThat(collection).isNotNull(); assertThat(getDatabase().collectionExists("invalid")).isFalse(); - assertThatThrownBy(collection::getOptions) + assertThatThrownBy(collection::getDefinition) .isInstanceOf(DataAPIException.class) .hasMessageContaining("COLLECTION_NOT_EXIST"); - assertThatThrownBy(collection::getOptions) + assertThatThrownBy(collection::getDefinition) .isInstanceOf(DataAPIException.class) .hasMessageContaining("COLLECTION_NOT_EXIST") .extracting("errorCode") // Extract the errorCode attribute @@ -213,9 +230,7 @@ static class ProductObjectId extends Product { public void shouldCollectionWorkWithUUIDs() { // When Collection collectionUUID = getDatabase() - .createCollection(COLLECTION_UUID, CollectionOptions.builder() - .defaultIdType(CollectionIdTypes.UUID) - .build()); + .createCollection(COLLECTION_UUID, new CollectionDefinition().defaultId(CollectionDefaultIdTypes.UUID)); collectionUUID.deleteAll(); UUID uid = UUID.fromString("00000000-0000-0000-0000-000000000000"); @@ -238,16 +253,12 @@ public void shouldCollectionWorkWithUUIDs() { resultsList.getInsertedIds().forEach(id -> assertThat(id).isInstanceOf(UUID.class)); } - - @Test @Order(11) public void shouldCollectionWorkWithObjectIds() { // When - Collection collectionUUID = getDatabase() - .createCollection(COLLECTION_OBJECTID, CollectionOptions.builder() - .defaultIdType(OBJECT_ID) - .build()); + Collection collectionUUID = getDatabase().createCollection(COLLECTION_OBJECTID, + new CollectionDefinition().defaultId(OBJECT_ID)); collectionUUID.deleteAll(); // Insert One @@ -275,29 +286,27 @@ public void shouldCollectionWorkWithObjectIds() { product.setName("name"); product.setCode(UUID.randomUUID()); product.setPrice(0d); - Collection collectionObjectId = getDatabase() - .createCollection(COLLECTION_OBJECTID, CollectionOptions.builder() - .defaultIdType(OBJECT_ID) - .build(), ProductObjectId.class); + Collection collectionObjectId = getDatabase().createCollection(COLLECTION_OBJECTID, + new CollectionDefinition().defaultId(OBJECT_ID), ProductObjectId.class); collectionObjectId.deleteAll(); collectionObjectId.insertOne(product); Optional productObjectId = collectionObjectId.findOne(eq(product.getId())); assertThat(productObjectId).isPresent(); } + @Test @Order(12) public void shouldCollectionWorkWithUUIDv6() { // When - Collection collectionUUID = getDatabase() - .createCollection(COLLECTION_UUID_V6, CollectionOptions.builder() - .defaultIdType(UUIDV6) - .build()); + Collection collectionUUID = getDatabase().createCollection(COLLECTION_UUID_V6, + new CollectionDefinition().defaultId(UUIDV6)); collectionUUID.deleteAll(); // Insert One UUIDv6 id1 = new UUIDv6(); - CollectionInsertOneResult res = collectionUUID.insertOne(new Document().id(id1).append("sample", UUID.randomUUID())); + CollectionInsertOneResult res = collectionUUID + .insertOne(new Document().id(id1).append("sample", UUID.randomUUID())); assertThat(res).isNotNull(); assertThat(res.getInsertedId()).isInstanceOf(UUIDv6.class); Optional doc = collectionUUID.findOne(eq(id1)); @@ -318,11 +327,11 @@ public void shouldCollectionWorkWithUUIDv6() { @Test @Order(13) public void shouldCollectionWorkWithUUIDv7() { + cleanupCollections(); // When Collection collectionUUID = getDatabase() - .createCollection(COLLECTION_UUID_V7, CollectionOptions.builder() - .defaultIdType(UUIDV7) - .build()); + .createCollection(COLLECTION_UUID_V7, new CollectionDefinition() + .defaultId(UUIDV7)); collectionUUID.deleteAll(); // Insert One @@ -345,4 +354,74 @@ public void shouldCollectionWorkWithUUIDv7() { resultsList.getInsertedIds().forEach(id -> assertThat(id).isInstanceOf(UUIDv7.class)); } + // === TABLES === + + @Test + @Order(14) + public void shouldCreateTables() { + // Definition of the table in fluent style + TableDefinition tableDefinition = new TableDefinition() + .addColumnText("match_id") + .addColumnInt("round") + .addColumnVector("m_vector", new ColumnDefinitionVector().dimension(3).metric(COSINE)) + .addColumn("score", ColumnTypes.INT) + .addColumn("when", ColumnTypes.TIMESTAMP) + .addColumn("winner", ColumnTypes.TEXT) + .addColumnSet("fighters", ColumnTypes.UUID) + .addPartitionBy("match_id") + .addPartitionSort(ascending("round")); + assertThat(tableDefinition).isNotNull(); + + // Minimal creation + Table table1 = getDatabase().createTable("game1", tableDefinition); + assertThat(table1).isNotNull(); + assertThat(getDatabase().tableExists("game1")).isTrue(); + + // Creation with a fully annotated bean + String tableXName = getDatabase().getTableName(TableEntityGameWithAnnotation.class); + Table tableX = getDatabase().createTable( + TableEntityGameWithAnnotation.class, + CreateTableOptions.IF_NOT_EXISTS); + assertThat(getDatabase().tableExists(tableXName)).isTrue(); + + // Minimal creation + String tableYName = getDatabase().getTableName(TableEntityGameWithAnnotationAllHints.class); + Table tableY = getDatabase().createTable( + TableEntityGameWithAnnotationAllHints.class); + assertThat(getDatabase().tableExists(tableYName)).isTrue(); + + // -- options -- + + // One can add options to setup the creation with finer grained: + CreateTableOptions createTableOptions = new CreateTableOptions() + .ifNotExists(true) + .timeout(ofSeconds(5)); + // Table table3 = db.createTable("game3", tableDefinition, createTableOptions); + + // One can can tuned the table object returned by the function + TableOptions tableOptions = new TableOptions() + .embeddingAuthProvider(new EmbeddingAPIKeyHeaderProvider("api-key")) + .timeout(ofSeconds(5)); + + // Change the Type of objects in use instead of default Row + Table table4 = getDatabase().createTable("game4", tableDefinition,Row.class, createTableOptions, tableOptions); + } + + + @Test + @Order(15) + public void shouldListTables() { + ListTablesOptions options = new ListTablesOptions().timeout(Duration.ofMillis(1000)); + List tableNames = getDatabase().listTableNames(); + assertThat(tableNames).isNotEmpty(); + assertThat(tableNames).contains("game1"); + + assertThat(getDatabase().listTableNames(options)).isNotEmpty(); + List tables = getDatabase().listTables(); + assertThat().isNotEmpty(); + assertThat(getDatabase().listTables(options)).isNotEmpty(); + + } + + } diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/integration/AbstractVectorizeITTest.java b/astra-db-java/src/test/java/com/datastax/astra/test/integration/AbstractVectorizeITTest.java index e1e8cb18..61491743 100644 --- a/astra-db-java/src/test/java/com/datastax/astra/test/integration/AbstractVectorizeITTest.java +++ b/astra-db-java/src/test/java/com/datastax/astra/test/integration/AbstractVectorizeITTest.java @@ -1,6 +1,7 @@ package com.datastax.astra.test.integration; import com.datastax.astra.client.collections.Collection; +import com.datastax.astra.client.collections.CollectionDefinition; import com.datastax.astra.client.collections.CollectionOptions; import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.collections.options.CollectionFindOneOptions; @@ -9,13 +10,14 @@ import com.datastax.astra.client.collections.results.CollectionInsertManyResult; import com.datastax.astra.client.core.auth.EmbeddingAPIKeyHeaderProvider; import com.datastax.astra.client.core.auth.EmbeddingHeadersProvider; -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.commands.BaseOptions; import com.datastax.astra.client.core.paging.FindIterable; import com.datastax.astra.client.core.query.Projection; import com.datastax.astra.client.core.query.Sort; import com.datastax.astra.client.core.types.DataAPIKeywords; import com.datastax.astra.client.core.vector.SimilarityMetric; import com.datastax.astra.client.core.vectorize.EmbeddingProvider; +import com.datastax.astra.client.databases.options.CreateCollectionOptions; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.TestMethodOrder; @@ -85,7 +87,7 @@ public void testEmbeddingProvider(String key, EmbeddingProvider provider) { try { log.info("Testing model {}", model); Collection collection = createCollectionHeader(key, model, apiKey, params); - log.info("Collection created {}", collection.getName()); + log.info("Collection created {}", collection.getCollectionName()); testCollection(collection, new EmbeddingAPIKeyHeaderProvider(apiKey)); collection.drop(); @@ -103,7 +105,7 @@ private void testEmbeddingProviderSharedKey(String key, EmbeddingProvider provid try { log.info("Testing model {}", model); Collection collection = createCollectionSharedSecret(key, model, keyName, params); - log.info("Collection created {}", collection.getName()); + log.info("Collection created {}", collection.getCollectionName()); testCollectionSharedKey(collection); collection.drop(); @@ -180,15 +182,21 @@ protected void testCollection(Collection collection, EmbeddingHeadersP // =================================================================================== protected Collection createCollectionHeader(String provider, EmbeddingProvider.Model model, String apiKey, Map parameters) { - CollectionOptions.CollectionOptionsBuilder builder = CollectionOptions.builder(); - builder.vectorSimilarity(SimilarityMetric.COSINE); + String collectionName = getCollectionNameFromModel(model.getName()); + + CollectionDefinition colDef = new CollectionDefinition(); + colDef.vectorSimilarity(SimilarityMetric.COSINE); if (model.getVectorDimension() != null) { - builder.vectorDimension(model.getVectorDimension()); + colDef.vectorDimension(model.getVectorDimension()); } - builder.vectorize(provider, model.getName(), null, parameters); - return getDatabase().createCollection( - getCollectionNameFromModel(model.getName()), builder.build(), - new CommandOptions<>().embeddingAuthProvider(new EmbeddingAPIKeyHeaderProvider(apiKey))); + colDef.vectorize(provider, model.getName(), null, parameters); + + CreateCollectionOptions ccOptions = new CreateCollectionOptions(); + + CollectionOptions colOptions = new CollectionOptions(); + colOptions.embeddingAuthProvider(new EmbeddingAPIKeyHeaderProvider(apiKey)); + + return getDatabase().createCollection(collectionName, colDef, ccOptions, colOptions); } private String getCollectionNameFromModel(String modelName) { @@ -204,21 +212,19 @@ private String getCollectionNameFromModel(String modelName) { } private Collection createCollectionSharedSecret(String provider, EmbeddingProvider.Model model, String keyName, Map parameters) { - CollectionOptions.CollectionOptionsBuilder builder = CollectionOptions.builder(); - builder.vectorSimilarity(SimilarityMetric.COSINE); + CollectionDefinition cd = new CollectionDefinition().vectorSimilarity(SimilarityMetric.COSINE); if (model.getVectorDimension() != null) { - builder.vectorDimension(model.getVectorDimension()); + cd.vectorDimension(model.getVectorDimension()); } - builder.vectorize(provider, model.getName(), keyName, parameters); - return getDatabase().createCollection( - getCollectionNameFromModel(model.getName()), - builder.build(), new CommandOptions<>()); + cd.vectorize(provider, model.getName(), keyName, parameters); + String collectionName = getCollectionNameFromModel(model.getName()); + return getDatabase().createCollection(collectionName, cd, Document.class); } protected void testEmbeddingModelSharedSecret(String provider, EmbeddingProvider.Model model, String keyName, Map parameters) { log.info("Testing model {}", model); Collection collection = createCollectionSharedSecret(provider, model, keyName, parameters); - log.info("Collection created {}", collection.getName()); + log.info("Collection created {}", collection.getCollectionName()); testCollection(collection, null); } @@ -267,7 +273,7 @@ public void testEmbeddingProvider(String key, EmbeddingProvider provider, String try { log.info("Testing model {}", model); Collection collection = createCollectionHeader(key, model, targetApiKey, params); - log.info("Collection created {}", collection.getName()); + log.info("Collection created {}", collection.getCollectionName()); testCollection(collection, new EmbeddingAPIKeyHeaderProvider(targetApiKey)); collection.drop(); diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/integration/dev/AstraDevCollectionITTest.java b/astra-db-java/src/test/java/com/datastax/astra/test/integration/dev/AstraDevCollectionITTest.java index 1169d424..c7ffdc44 100644 --- a/astra-db-java/src/test/java/com/datastax/astra/test/integration/dev/AstraDevCollectionITTest.java +++ b/astra-db-java/src/test/java/com/datastax/astra/test/integration/dev/AstraDevCollectionITTest.java @@ -10,9 +10,9 @@ * AstraEnvironment.DEV, CloudProviderType.GCP, "europe-west4" * AstraEnvironment.DEV, CloudProviderType.GCP, "us-central1" */ -@EnabledIfEnvironmentVariable(named = "ASTRA_DB_APPLICATION_TOKEN_DEV", matches = "Astra.*") -@EnabledIfEnvironmentVariable(named = "ASTRA_CLOUD_PROVIDER_DEV", matches = ".*") -@EnabledIfEnvironmentVariable(named = "ASTRA_CLOUD_REGION_DEV", matches = ".*") +//@EnabledIfEnvironmentVariable(named = "ASTRA_DB_APPLICATION_TOKEN_DEV", matches = "Astra.*") +//@EnabledIfEnvironmentVariable(named = "ASTRA_CLOUD_PROVIDER_DEV", matches = ".*") +//@EnabledIfEnvironmentVariable(named = "ASTRA_CLOUD_REGION_DEV", matches = ".*") class AstraDevCollectionITTest extends AbstractCollectionITTest { @Override @@ -22,11 +22,17 @@ public AstraEnvironment getAstraEnvironment() { @Override public CloudProviderType getCloudProvider() { + if (System.getenv("ASTRA_CLOUD_PROVIDER_DEV") == null) { + return CloudProviderType.GCP; + } return CloudProviderType.valueOf(System.getenv("ASTRA_CLOUD_PROVIDER_DEV")); } @Override public String getRegion() { + if (System.getenv("ASTRA_CLOUD_REGION_DEV") == null) { + return "europe-west4"; + } return System.getenv("ASTRA_CLOUD_REGION_DEV"); } diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/integration/dev_vectorize/AstraDevVectorizeAwsBedRockITTest.java b/astra-db-java/src/test/java/com/datastax/astra/test/integration/dev_vectorize/AstraDevVectorizeAwsBedRockITTest.java index d94dafa6..3c22794f 100644 --- a/astra-db-java/src/test/java/com/datastax/astra/test/integration/dev_vectorize/AstraDevVectorizeAwsBedRockITTest.java +++ b/astra-db-java/src/test/java/com/datastax/astra/test/integration/dev_vectorize/AstraDevVectorizeAwsBedRockITTest.java @@ -1,7 +1,7 @@ package com.datastax.astra.test.integration.dev_vectorize; import com.datastax.astra.client.collections.Collection; -import com.datastax.astra.client.collections.CollectionOptions; +import com.datastax.astra.client.collections.CollectionDefinition; import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.collections.options.CollectionFindOneOptions; import com.datastax.astra.client.collections.options.CollectionInsertManyOptions; @@ -69,11 +69,9 @@ public void shouldTestAwsBedRock() { assertThat(result.getEmbeddingProviders()).containsKey(providerName); // Create collection for AWS Bedrock - Collection collection = getDatabase().createCollection(collectionName, CollectionOptions - .builder() + Collection collection = getDatabase().createCollection(collectionName, new CollectionDefinition() .vectorize(providerName, providerModel,null, - Map.of("region", System.getenv("BEDROCK_REGION"))) - .build());; + Map.of("region", System.getenv("BEDROCK_REGION"))));; assertThat(getDatabase().collectionExists(collectionName)).isTrue(); // Insertion With Vectorize List entries = List.of( diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/integration/dev_vectorize/AstraDevVectorizeHuggingFaceDedicatedITTest.java b/astra-db-java/src/test/java/com/datastax/astra/test/integration/dev_vectorize/AstraDevVectorizeHuggingFaceDedicatedITTest.java index 81944388..cec08021 100644 --- a/astra-db-java/src/test/java/com/datastax/astra/test/integration/dev_vectorize/AstraDevVectorizeHuggingFaceDedicatedITTest.java +++ b/astra-db-java/src/test/java/com/datastax/astra/test/integration/dev_vectorize/AstraDevVectorizeHuggingFaceDedicatedITTest.java @@ -1,10 +1,9 @@ package com.datastax.astra.test.integration.dev_vectorize; import com.datastax.astra.client.collections.Collection; -import com.datastax.astra.client.core.auth.EmbeddingAPIKeyHeaderProvider; -import com.datastax.astra.client.collections.CollectionOptions; -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.collections.CollectionDefinition; import com.datastax.astra.client.collections.documents.Document; +import com.datastax.astra.client.core.auth.EmbeddingAPIKeyHeaderProvider; import com.datastax.astra.client.core.results.FindEmbeddingProvidersResult; import com.datastax.astra.test.integration.AbstractVectorizeITTest; import com.dtsx.astra.sdk.db.domain.CloudProviderType; @@ -49,17 +48,15 @@ public void testHuggingFaceDedicated() { assertThat(result.getEmbeddingProviders().get(providerName)).isNotNull(); // Create Collection - CollectionOptions.CollectionOptionsBuilder builder = CollectionOptions.builder(); - builder.vectorDimension(Integer.valueOf(System.getenv("HUGGINGFACEDED_DIMENSION"))); + CollectionDefinition cd = new CollectionDefinition() + .vectorDimension(Integer.valueOf(System.getenv("HUGGINGFACEDED_DIMENSION"))); Map params = new HashMap<>(); params.put("endpointName", System.getenv("HUGGINGFACEDED_ENDPOINTNAME")); params.put("regionName", System.getenv("HUGGINGFACEDED_REGIONNAME")); params.put("cloudName", System.getenv("HUGGINGFACEDED_CLOUDNAME")); - builder.vectorize(providerName, null, null, params); - Collection collection = getDatabase() - .createCollection(collectionName, builder.build(), new CommandOptions<>()); - assertThat(getDatabase() - .collectionExists(collectionName)).isTrue(); + cd.vectorize(providerName, null, null, params); + Collection collection = getDatabase().createCollection(collectionName, cd); + assertThat(getDatabase().collectionExists(collectionName)).isTrue(); // Test Collection testCollection(collection, new EmbeddingAPIKeyHeaderProvider( System.getenv("EMBEDDING_API_KEY"))); diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/integration/local/LocalCollectionITTest.java b/astra-db-java/src/test/java/com/datastax/astra/test/integration/local/LocalCollectionITTest.java index 11128186..151aaabb 100644 --- a/astra-db-java/src/test/java/com/datastax/astra/test/integration/local/LocalCollectionITTest.java +++ b/astra-db-java/src/test/java/com/datastax/astra/test/integration/local/LocalCollectionITTest.java @@ -15,6 +15,7 @@ import com.datastax.astra.client.core.query.Filter; import com.datastax.astra.client.core.query.Sort; import com.datastax.astra.client.databases.Database; +import com.datastax.astra.client.databases.options.ListCollectionOptions; import com.datastax.astra.client.exception.DataAPIResponseException; import com.datastax.astra.test.integration.AbstractCollectionITTest; import com.dtsx.astra.sdk.db.domain.CloudProviderType; @@ -56,7 +57,7 @@ class LocalCollectionITTest extends AbstractCollectionITTest { @Override protected Database getDatabase() { if (database == null) { - database = DataAPIClients.defaultLocalDatabase(); + database = DataAPIClients.localDbWithDefaultKeyspace(); } return database; } @@ -118,14 +119,15 @@ void shouldTestInsertMany() { @Test void shouldInsertManyWithDuplicatesOrder() { getDatabase().dropCollection(COLLECTION_SIMPLE); - Collection collectionSimple = getDatabase() - .createCollection(COLLECTION_SIMPLE, Document.class); + Collection collectionSimple = getDatabase().createCollection(COLLECTION_SIMPLE); collectionSimple.deleteAll(); List players = new ArrayList<>(FRENCH_SOCCER_TEAM.subList(0, 5)); // duplicate players.add(FRENCH_SOCCER_TEAM.get(4)); players.addAll(FRENCH_SOCCER_TEAM.subList(5,7)); + getDatabase().listCollectionNames(new ListCollectionOptions().timeout(1000)); + try { CollectionInsertManyResult res = collectionSimple.insertMany(players, new CollectionInsertManyOptions().ordered(true)); @@ -229,7 +231,7 @@ void shouldDoSemanticSearch() { getCollectionVector(); // Made at environment level for Serializers - DataAPIClientOptions.disableEncodeDataApiVectorsAsBase64(); + DataAPIClientOptions.getSerdesOptions().disableEncodeDataApiVectorsAsBase64(); Collection collectionVectorRaw = getDatabase().getCollection(COLLECTION_VECTOR); collectionVectorRaw.deleteAll(); diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/integration/local/LocalDatabaseAdminITTest.java b/astra-db-java/src/test/java/com/datastax/astra/test/integration/local/LocalDatabaseAdminITTest.java index b91d2e28..5715ee6f 100644 --- a/astra-db-java/src/test/java/com/datastax/astra/test/integration/local/LocalDatabaseAdminITTest.java +++ b/astra-db-java/src/test/java/com/datastax/astra/test/integration/local/LocalDatabaseAdminITTest.java @@ -30,7 +30,7 @@ class LocalDatabaseAdminITTest extends AbstractDatabaseAdminITTest { @Override protected Database getDatabase() { if (database == null) { - database = DataAPIClients.defaultLocalDatabase(); + database = DataAPIClients.localDbWithDefaultKeyspace(); } return database; } @@ -53,7 +53,7 @@ void shouldCreateKeyspaceSimpleStrategy() { } @Test - void shouldCreateNamespaceNetworkStrategy() { + void shouldCreateKeyspaceNetworkStrategy() { // Given DataAPIDatabaseAdmin dbAdmin = (DataAPIDatabaseAdmin) getDatabaseAdmin(); // When @@ -75,9 +75,9 @@ void shouldCreateNamespaceNetworkStrategy() { // non-passing case assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> dbAdmin.dropKeyspace(null)) - .withMessage("Parameter 'namespaceName' should be null nor empty"); + .withMessage("Parameter 'keyspace' should be null nor empty"); assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> dbAdmin.dropKeyspace("")) - .withMessage("Parameter 'namespaceName' should be null nor empty"); + .withMessage("Parameter 'keyspace' should be null nor empty"); } } diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/integration/local/LocalDatabaseITTest.java b/astra-db-java/src/test/java/com/datastax/astra/test/integration/local/LocalDatabaseITTest.java index e12e491c..e7fd21c4 100644 --- a/astra-db-java/src/test/java/com/datastax/astra/test/integration/local/LocalDatabaseITTest.java +++ b/astra-db-java/src/test/java/com/datastax/astra/test/integration/local/LocalDatabaseITTest.java @@ -3,21 +3,23 @@ import com.datastax.astra.client.DataAPIClient; import com.datastax.astra.client.DataAPIClients; import com.datastax.astra.client.DataAPIDestination; +import com.datastax.astra.client.admin.AdminOptions; +import com.datastax.astra.client.core.http.HttpClientOptions; import com.datastax.astra.client.core.options.DataAPIClientOptions; import com.datastax.astra.client.databases.Database; import com.datastax.astra.client.core.auth.UsernamePasswordTokenProvider; -import com.datastax.astra.client.exception.DataAPIHttpException; +import com.datastax.astra.client.exception.DataAPIException; import com.datastax.astra.client.exception.DataAPIResponseException; import com.datastax.astra.client.core.commands.Command; import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.core.http.HttpProxy; +import com.datastax.astra.client.exception.UnexpectedDataAPIResponseException; import com.datastax.astra.test.integration.AbstractDatabaseTest; import com.dtsx.astra.sdk.db.domain.CloudProviderType; import com.dtsx.astra.sdk.utils.AstraEnvironment; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import java.io.IOException; import java.util.Set; @@ -29,7 +31,7 @@ /** * Integration tests against a Local Instance of Stargate. */ -@EnabledIfEnvironmentVariable(named = "ENABLED_TEST_DATA_API_LOCAL", matches = "true") +//@EnabledIfEnvironmentVariable(named = "ENABLED_TEST_DATA_API_LOCAL", matches = "true") class LocalDatabaseITTest extends AbstractDatabaseTest { @Override @@ -41,7 +43,7 @@ class LocalDatabaseITTest extends AbstractDatabaseTest { @Override protected Database getDatabase() { if (database == null) { - database = DataAPIClients.defaultLocalDatabase(); + database = DataAPIClients.localDbWithDefaultKeyspace(); } return database; } @@ -51,22 +53,14 @@ void shouldGetATokenFromAuthenticationEndpoint() { assertThat(new UsernamePasswordTokenProvider().getToken()).isNotNull(); } - @Test - void shouldThrowAuthenticationCode() { - UsernamePasswordTokenProvider tokenProvider = new UsernamePasswordTokenProvider("invalid", "invalid"); - assertThatThrownBy(tokenProvider::getToken).isInstanceOf(DataAPIHttpException.class); - } - @Test void shouldRunInvalidCommand() { try { //getDatabase().registerListener("demo", new MockCommandObserver()); getDatabase().runCommand(new Command("invalid", new Document())); //getDatabase().deleteListener("demo"); - } catch(DataAPIResponseException dat) { - assertThat(dat.getMessage()).contains("No \"invalid\" command found "); - assertThat(dat.getApiErrors()).isNotEmpty(); - assertThat(dat.getCommandsList()).isNotEmpty(); + } catch(DataAPIException dat) { + assertThat(dat.getMessage()).contains("COMMAND_UNKNOWN");; } } @@ -79,7 +73,7 @@ void shouldInitializeHttpClientWithProxy() throws IOException { .addHeader("Content-Type", "application/json; charset=utf-8") .setBody("{\n" + " \"status\": {\n" + - " \"namespaces\": [\n" + + " \"keyspaces\": [\n" + " \"mock1\",\n" + " \"mock2\"\n" + " ]\n" + @@ -90,15 +84,16 @@ void shouldInitializeHttpClientWithProxy() throws IOException { // Start the server mockWebServer.start(); - DataAPIClient otherCallerClient = new DataAPIClient( - new UsernamePasswordTokenProvider().getToken(), - DataAPIClientOptions.builder() - .withDestination(DataAPIDestination.CASSANDRA) - .withHttpProxy(new HttpProxy(mockWebServer.getHostName(), mockWebServer.getPort())) - .build()); + DataAPIClient otherCallerClient = DataAPIClients.clientCassandra(); Set names = otherCallerClient - .getDatabase(DEFAULT_ENDPOINT_LOCAL, DEFAULT_NAMESPACE) - .getDatabaseAdmin() + .getDatabase(DEFAULT_ENDPOINT_LOCAL) + // Moving to admin I add a HTTP PROXY + .getDatabaseAdmin(new AdminOptions() + .dataAPIClientOptions(new DataAPIClientOptions() + .httpClientOptions(new HttpClientOptions() + .httpProxy(new HttpProxy(mockWebServer.getHostName(), mockWebServer.getPort())) + ) + .logRequests())) .listKeyspaceNames(); assertThat(names).isNotNull(); @@ -110,12 +105,11 @@ void shouldInitializeHttpClientWithProxy() throws IOException { void shouldInitializeHttpClientWithCallerAndProxy() { DataAPIClient otherCallerClient = new DataAPIClient( new UsernamePasswordTokenProvider().getToken(), - DataAPIClientOptions.builder() - .withDestination(DataAPIDestination.CASSANDRA) - .addCaller("Cedrick", "1.0") - .build()); + new DataAPIClientOptions() + .destination(DataAPIDestination.CASSANDRA) + .addCaller("Cedrick", "1.0")); assertThat(otherCallerClient - .getDatabase(DEFAULT_ENDPOINT_LOCAL, DEFAULT_NAMESPACE) + .getDatabase(DEFAULT_ENDPOINT_LOCAL) .listCollectionNames()).isNotNull(); } diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/integration/local/LocalTableITTest.java b/astra-db-java/src/test/java/com/datastax/astra/test/integration/local/LocalTableITTest.java index 5fcf50ba..225351dc 100644 --- a/astra-db-java/src/test/java/com/datastax/astra/test/integration/local/LocalTableITTest.java +++ b/astra-db-java/src/test/java/com/datastax/astra/test/integration/local/LocalTableITTest.java @@ -1,8 +1,12 @@ package com.datastax.astra.test.integration.local; +import com.datastax.astra.client.DataAPIClient; import com.datastax.astra.client.DataAPIClients; -import com.datastax.astra.client.collections.results.CollectionUpdateResult; +import com.datastax.astra.client.DataAPIDestination; +import com.datastax.astra.client.core.auth.EmbeddingAPIKeyHeaderProvider; +import com.datastax.astra.client.core.http.HttpClientOptions; import com.datastax.astra.client.core.options.DataAPIClientOptions; +import com.datastax.astra.client.core.options.TimeoutOptions; import com.datastax.astra.client.core.query.Filter; import com.datastax.astra.client.core.query.Filters; import com.datastax.astra.client.core.query.Projection; @@ -10,6 +14,7 @@ import com.datastax.astra.client.core.vector.DataAPIVector; import com.datastax.astra.client.core.vectorize.VectorServiceOptions; import com.datastax.astra.client.databases.Database; +import com.datastax.astra.client.databases.DatabaseOptions; import com.datastax.astra.client.tables.Table; import com.datastax.astra.client.tables.TableDefinition; import com.datastax.astra.client.tables.TableDuration; @@ -23,10 +28,10 @@ import com.datastax.astra.client.tables.ddl.CreateTableOptions; import com.datastax.astra.client.tables.ddl.CreateVectorIndexOptions; import com.datastax.astra.client.tables.ddl.DropTableIndexOptions; -import com.datastax.astra.client.tables.index.IndexDefinition; -import com.datastax.astra.client.tables.index.IndexDefinitionOptions; -import com.datastax.astra.client.tables.index.VectorIndexDefinition; -import com.datastax.astra.client.tables.index.VectorIndexDefinitionOptions; +import com.datastax.astra.client.tables.index.TableIndexDefinition; +import com.datastax.astra.client.tables.index.TableIndexDefinitionOptions; +import com.datastax.astra.client.tables.index.TableVectorIndexDefinition; +import com.datastax.astra.client.tables.index.TableVectorIndexDefinitionOptions; import com.datastax.astra.client.tables.options.TableFindOneOptions; import com.datastax.astra.client.tables.options.TableFindOptions; import com.datastax.astra.client.tables.options.TableInsertManyOptions; @@ -35,6 +40,7 @@ import com.datastax.astra.client.tables.results.TableUpdateResult; import com.datastax.astra.client.tables.row.Row; import com.datastax.astra.client.tables.row.TableUpdate; +import com.datastax.astra.internal.serdes.tables.RowSerializer; import com.datastax.astra.test.integration.AbstractTableITTest; import com.datastax.astra.test.model.TableCompositeAnnotatedRow; import com.datastax.astra.test.model.TableCompositeRow; @@ -49,6 +55,7 @@ import java.math.BigInteger; import java.net.InetAddress; import java.net.UnknownHostException; +import java.net.http.HttpClient; import java.time.Duration; import java.time.Instant; import java.time.LocalDate; @@ -60,7 +67,6 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; import static com.datastax.astra.client.core.query.Sort.ascending; import static com.datastax.astra.client.core.query.Sort.descending; @@ -94,19 +100,21 @@ public class LocalTableITTest extends AbstractTableITTest { @Override protected Database getDatabase() { if (database == null) { - database = DataAPIClients.defaultLocalDatabase(); + database = DataAPIClients.localDbWithDefaultKeyspace(); } return database; } @Test @Order(1) - public void shouldInitiateDatabase() { + public void shouldInitiateDatabase() throws Exception { Database db = getDatabase(); db.dropTableIndex(INDEX_COUNTRY, DropTableIndexOptions.IF_EXISTS); db.dropTableIndex(INDEX_ALL_RETURNS_PTEXT, DropTableIndexOptions.IF_EXISTS); db.dropTableIndex(INDEX_ALL_RETURNS_VECTOR, DropTableIndexOptions.IF_EXISTS); + System.out.println("ok"); + System.out.println(new RowSerializer().marshall(db.getOptions())); db.dropTable(TABLE_SIMPLE, IF_EXISTS); db.dropTable(TABLE_COMPOSITE, IF_EXISTS); db.dropTable(TABLE_ALL_RETURNS, IF_EXISTS); @@ -129,19 +137,27 @@ public void shouldCreateTableSimple() { .addColumnText("name") .addColumnText("country") .addColumnBoolean("human") - .withPartitionKey("email")); + .partitionKey("email")); assertThat(getDatabase().tableExists(TABLE_SIMPLE)).isTrue(); // Create Index Simple - tableSimple.createIndex(INDEX_COUNTRY, new IndexDefinition() + tableSimple.createIndex(INDEX_COUNTRY, new TableIndexDefinition() .column("country") - .options(new IndexDefinitionOptions() + .options(new TableIndexDefinitionOptions() .ascii(true) .caseSensitive(true) .normalize(true)), CreateIndexOptions.IF_NOT_EXISTS); } + @Test + public void listIndex() { + for (TableIndexDefinition tid : getDatabase().getTable(TABLE_SIMPLE).listIndexes()) { + System.out.println(tid.getColumn()); + System.out.println(tid.getOptions()); + } + } + @Test @Order(3) public void shouldCreateTableComposite() { @@ -149,7 +165,7 @@ public void shouldCreateTableComposite() { .addColumnText("id") .addColumnInt("age") .addColumnText("name") - .withPartitionKey("id", "name")); + .partitionKey("id", "name")); assertThat(getDatabase().tableExists(TABLE_COMPOSITE)).isTrue(); } @@ -188,21 +204,21 @@ public void shouldCreateTableAllReturns() { .addColumn("p_double_minf", ColumnTypes.DOUBLE) .addColumn("p_double_pinf", ColumnTypes.DOUBLE) .addColumn("p_float_nan", ColumnTypes.FLOAT) - .withPartitionKey("p_ascii", "p_bigint") - .withClusteringColumns(ascending("p_int"), descending("p_boolean")), + .partitionKey("p_ascii", "p_bigint") + .clusteringColumns(ascending("p_int"), descending("p_boolean")), new CreateTableOptions().ifNotExists(true)); assertThat(getDatabase().tableExists(TABLE_ALL_RETURNS)).isTrue(); tableAllReturns .createVectorIndex(INDEX_ALL_RETURNS_VECTOR, - new VectorIndexDefinition() + new TableVectorIndexDefinition() .column("p_vector") - .options(new VectorIndexDefinitionOptions().metric(COSINE)), + .options(new TableVectorIndexDefinitionOptions().metric(COSINE)), new CreateVectorIndexOptions().ifNotExists(true)); - tableAllReturns.createIndex(INDEX_ALL_RETURNS_PTEXT, new IndexDefinition() + tableAllReturns.createIndex(INDEX_ALL_RETURNS_PTEXT, new TableIndexDefinition() .column("p_text") - .options(new IndexDefinitionOptions() + .options(new TableIndexDefinitionOptions() .ascii(true) .caseSensitive(true) .normalize(true)), @@ -221,8 +237,8 @@ public void shouldCreateTableAllCassio() { .addColumnMap("metadata_s", ColumnTypes.TEXT, ColumnTypes.TEXT) .addColumnVector("vector", new ColumnDefinitionVector() .dimension(1536).metric(COSINE)) - .withPartitionKey("partition_id") - .withClusteringColumns(Sort.descending("row_id"))); + .partitionKey("partition_id") + .clusteringColumns(Sort.descending("row_id"))); assertThat(getDatabase().tableExists("table_cassio")).isTrue(); } @@ -239,8 +255,7 @@ public void shouldListTables() { @Order(7) public void shouldListTableNames() { assertThat(getDatabase() - .listTableNames() - .collect(Collectors.toList())) + .listTableNames()) .isNotNull(); } @@ -348,7 +363,7 @@ public void shouldInsertOneAllReturns() throws UnknownHostException { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSS"); LocalTime localTime = LocalTime.parse(timeString, formatter); - DataAPIClientOptions.disableEncodeDataApiVectorsAsBase64(); + DataAPIClientOptions.getSerdesOptions().disableEncodeDataApiVectorsAsBase64(); Row row = new Row() .addAscii("p_ascii", "abc") @@ -395,54 +410,17 @@ public void shouldFindOneAllReturns() throws UnknownHostException { .orElseThrow(() -> new IllegalArgumentException("Row not found")); assertThat(row.getText("p_ascii")).isEqualTo( "abc"); assertThat(row.getBigInt("p_bigint")).isEqualTo( 10002L); - - //Row row2 = tableAllReturns.findOne( - // and(eq( "p_ascii", "abc"), - // eq("p_bigint", 10002L) - // )).orElseThrow(() -> new IllegalArgumentException("Row not found")); - - //assertThat(row2.getText("p_ascii")).isEqualTo( "abc"); - //assertThat(row2.getBigInt("p_bigint")).isEqualTo( 10002L); - /* - .addAscii("p_ascii", "abc") - .addBigInt("p_bigint", 10002L) - .addInt("p_int", 987) - .addBoolean("p_boolean", false) - .addText("p_text", "Ålesund") - .addText("p_text_omitted", null) - .addDouble("p_double_pinf", Double.MAX_VALUE) - .addDouble("p_double_minf", Double.NEGATIVE_INFINITY) - .addBlob("p_blob", "blob".getBytes()) - .addSmallInt("p_smallint", (short) 200) - .addVarInt("p_varint",444) - .addTinyInt("p_tinyint",(short) 17) - .addDuration("p_duration", Duration.ofHours(12).plusMinutes(48)) - .addInet("p_inet", InetAddress.getByAddress(new byte[]{12, 34, 56, 78})) - .addDouble("p_double", 987.6543d) - .addFloat("p_float", 66.55f) - .addFloat("p_float_nan", Float.NaN) - .addTimeStamp("p_timestamp", Instant.now()) - .addTime("p_time", localTime) - .addUUID("p_uuid", java.util.UUID.fromString("9c5b94b1-35ad-49bb-b118-8e8fc24abf80")) - .addDate("p_date", LocalDate.of(2015,5,3)) - .addDecimal("p_decimal", new BigDecimal("123.45")) - .addVector("p_vector", DataAPIVector.of(.1f, 0.2f, 0.3f)) - .addList("p_list_int", List.of(4, 17, 34)) - .addSet("p_set_int", Set.of(9, 81)); - - //.addTableDuration("p_duration", TableDuration.of( - // Period.ofDays(3), - // Duration.ofHours(12).plusMinutes(48))); - */ } @Test + @Order(18) public void shouldCreateTableFromBeanDefinition() { getDatabase().createTable(TableCompositeAnnotatedRow.class, IF_NOT_EXISTS); assertThat(getDatabase().tableExists("table_composite_pk_annotated")).isTrue(); } @Test + @Order(19) public void shouldAlterAddColumns() { Table t = getDatabase().getTable(TABLE_SIMPLE); // Add Column (simple) @@ -459,8 +437,7 @@ public void shouldAlterAddColumns() { t.alter(new AlterTableAddVectorize().columns( Map.of("vv", new VectorServiceOptions() .modelName("mistral-embed") - .provider("mistral")))) - ; + .provider("mistral")))); // Drop Vectorize t.alter(new AlterTableDropVectorize("vv")); @@ -472,6 +449,7 @@ public void shouldAlterAddColumns() { } @Test + @Order(20) public void should_insert_many() { Table table = getDatabase().getTable(TABLE_COMPOSITE); @@ -482,6 +460,7 @@ public void should_insert_many() { TableInsertManyResult res = table.insertMany( List.of(row1, row2, row3), new TableInsertManyOptions() .ordered(false) + .timeout(10000L) .returnDocumentResponses(true)); System.out.println(res.getInsertedIds()); System.out.println(res.getPrimaryKeySchema()); @@ -521,8 +500,8 @@ public void should_work_with_cursors() { .addColumnText("country") .addColumnText("city") .addColumnInt("population") - .withPartitionKey("country") - .withClusteringColumns(Sort.ascending("city")), IF_NOT_EXISTS); + .partitionKey("country") + .clusteringColumns(Sort.ascending("city")), IF_NOT_EXISTS); tableCities.deleteAll(); List rowsFrance = new ArrayList<>(); @@ -682,6 +661,9 @@ public void should_updateOne() { Table table = getDatabase().getTable(TABLE_COMPOSITE); table.deleteAll(); + TableInsertManyOptions options = new TableInsertManyOptions() + .returnDocumentResponses(true); + Row row = new Row() .addInt("age", 42) .addText("name", "Cedrick") @@ -691,12 +673,34 @@ public void should_updateOne() { assertThat(table.findOne(johnFilter)).isPresent(); // Update the document - TableUpdateResult u1 = table.updateOne(johnFilter, TableUpdate.create() - .set("name", "new")); + TableUpdateResult birthday = table.updateOne(johnFilter, TableUpdate.create() + .set("age", 43)); //.updateMul(Map.of("price", 1.1d))); - Assertions.assertThat(u1.getMatchedCount()).isEqualTo(1); - Assertions.assertThat(u1.getModifiedCount()).isEqualTo(1); + Assertions.assertThat(birthday.getMatchedCount()).isEqualTo(1); + Assertions.assertThat(birthday.getModifiedCount()).isEqualTo(1); } + @Test + public void revampingOptions() { + DataAPIClientOptions options = new DataAPIClientOptions() + .destination(DataAPIDestination.ASTRA) + .embeddingAuthProvider(new EmbeddingAPIKeyHeaderProvider("myKey")) + .addCaller("myCaller", "ssss") + .httpClientOptions(new HttpClientOptions() + .httpVersion(HttpClient.Version.HTTP_2) + .httpRedirect(HttpClient.Redirect.NORMAL)) + .timeoutOptions(new TimeoutOptions() + .requestTimeoutMillis(1000)); + + DataAPIClient client = new DataAPIClient("token", options); + + Database database1 = client.getDatabase("endpoint"); + + Database database2 = client.getDatabase("endpoints", + new DatabaseOptions("token" , options).keyspace("otherKeyspace")); + + database2.getOptions().getDataAPIClientOptions(); + Table table = database1.getTable("table"); + } } diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/model/TableCompositeAnnotatedRow.java b/astra-db-java/src/test/java/com/datastax/astra/test/model/TableCompositeAnnotatedRow.java index 6537dbb6..5f6b39d0 100644 --- a/astra-db-java/src/test/java/com/datastax/astra/test/model/TableCompositeAnnotatedRow.java +++ b/astra-db-java/src/test/java/com/datastax/astra/test/model/TableCompositeAnnotatedRow.java @@ -17,14 +17,14 @@ public class TableCompositeAnnotatedRow { @PartitionBy(0) - @Column(value="id", type=TEXT) + @Column(name ="id", type=TEXT) private String idx; @PartitionBy(1) - @Column(value="name", type=TEXT) + @Column(name ="name", type=TEXT) private String namex; - @Column(value="age", type=INT) + @Column(name ="age", type=INT) private int agex; } diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/model/TableEntityGame.java b/astra-db-java/src/test/java/com/datastax/astra/test/model/TableEntityGame.java new file mode 100644 index 00000000..afd6278f --- /dev/null +++ b/astra-db-java/src/test/java/com/datastax/astra/test/model/TableEntityGame.java @@ -0,0 +1,31 @@ +package com.datastax.astra.test.model; + +import com.datastax.astra.client.core.vector.DataAPIVector; +import com.datastax.astra.client.tables.mapping.Column; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; +import java.util.Set; + +@Data +@NoArgsConstructor +public class TableEntityGame { + + @Column(name ="match_id") + private String matchId; + + private Integer round; + + private Integer score; + + private Instant when; + + private String winner; + + private Set fighters; + + @Column(name ="m_vector") + private DataAPIVector vector; + +} diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/model/TableEntityGameWithAnnotation.java b/astra-db-java/src/test/java/com/datastax/astra/test/model/TableEntityGameWithAnnotation.java new file mode 100644 index 00000000..ce0fd020 --- /dev/null +++ b/astra-db-java/src/test/java/com/datastax/astra/test/model/TableEntityGameWithAnnotation.java @@ -0,0 +1,42 @@ +package com.datastax.astra.test.model; + +import com.datastax.astra.client.core.query.SortOrder; +import com.datastax.astra.client.core.vector.DataAPIVector; +import com.datastax.astra.client.core.vector.SimilarityMetric; +import com.datastax.astra.client.tables.mapping.Column; +import com.datastax.astra.client.tables.mapping.EntityTable; +import com.datastax.astra.client.tables.mapping.PartitionBy; +import com.datastax.astra.client.tables.mapping.PartitionSort; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; +import java.util.Set; +import java.util.UUID; + +@Data +@EntityTable("game_ann2") +@NoArgsConstructor +@AllArgsConstructor +public class TableEntityGameWithAnnotation { + + @PartitionBy(0) + @Column(name ="match_id") + private String matchId; + + @PartitionSort(position = 0, order = SortOrder.ASCENDING) + private Integer round; + + private Integer score; + + private Instant when; + + private String winner; + + private Set fighters; + + @Column(name ="m_vector", dimension = 3, metric = SimilarityMetric.COSINE) + private DataAPIVector vector; + +} diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/model/TableEntityGameWithAnnotationAllHints.java b/astra-db-java/src/test/java/com/datastax/astra/test/model/TableEntityGameWithAnnotationAllHints.java new file mode 100644 index 00000000..6b604bf2 --- /dev/null +++ b/astra-db-java/src/test/java/com/datastax/astra/test/model/TableEntityGameWithAnnotationAllHints.java @@ -0,0 +1,53 @@ +package com.datastax.astra.test.model; + +import com.datastax.astra.client.core.query.SortOrder; +import com.datastax.astra.client.core.vector.DataAPIVector; +import com.datastax.astra.client.core.vector.SimilarityMetric; +import com.datastax.astra.client.tables.mapping.Column; +import com.datastax.astra.client.tables.mapping.EntityTable; +import com.datastax.astra.client.tables.mapping.PartitionBy; +import com.datastax.astra.client.tables.mapping.PartitionSort; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; +import java.util.Set; + +import static com.datastax.astra.client.tables.columns.ColumnTypes.INT; +import static com.datastax.astra.client.tables.columns.ColumnTypes.SET; +import static com.datastax.astra.client.tables.columns.ColumnTypes.TEXT; +import static com.datastax.astra.client.tables.columns.ColumnTypes.TIMESTAMP; +import static com.datastax.astra.client.tables.columns.ColumnTypes.UUID; +import static com.datastax.astra.client.tables.columns.ColumnTypes.VECTOR; + +@Data +@EntityTable("game_ann1") +@NoArgsConstructor +@AllArgsConstructor +public class TableEntityGameWithAnnotationAllHints { + + @PartitionBy(0) + @Column(name ="match_id", type=TEXT ) + private String matchId; + + @PartitionSort(position = 0, order= SortOrder.ASCENDING) + @Column(name ="round", type=INT) + private Integer round; + + @Column(name ="score", type=INT) + private Integer score; + + @Column(name ="when", type=TIMESTAMP) + private Instant when; + + @Column(name ="winner", type=TEXT) + private String winner; + + @Column(name ="fighters", type=SET, valueType = UUID) + private Set fighters; + + @Column(name ="m_vector", type=VECTOR, dimension = 3, metric = SimilarityMetric.COSINE) + private DataAPIVector vector; + +} diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/model/TableSimpleAnnotatedRow.java b/astra-db-java/src/test/java/com/datastax/astra/test/model/TableSimpleAnnotatedRow.java index 71998d70..87ba6812 100644 --- a/astra-db-java/src/test/java/com/datastax/astra/test/model/TableSimpleAnnotatedRow.java +++ b/astra-db-java/src/test/java/com/datastax/astra/test/model/TableSimpleAnnotatedRow.java @@ -19,20 +19,20 @@ public class TableSimpleAnnotatedRow { @PartitionBy(0) - @Column(value="email", type= TEXT) + @Column(name ="email", type= TEXT) private String email; @PartitionSort(position = 0, order = ASCENDING) - @Column(value="age", type = INT) + @Column(name ="age", type = INT) private Integer age; - @Column(value="country", type = TEXT) + @Column(name ="country", type = TEXT) private String country; - @Column(value="name", type = TEXT) + @Column(name ="name", type = TEXT) private String name; - @Column(value="human", type = BOOLEAN) + @Column(name ="human", type = BOOLEAN) private Boolean human; } diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/unit/DataApiOptionsTest.java b/astra-db-java/src/test/java/com/datastax/astra/test/unit/DataApiOptionsTest.java index e91891d9..a251f522 100644 --- a/astra-db-java/src/test/java/com/datastax/astra/test/unit/DataApiOptionsTest.java +++ b/astra-db-java/src/test/java/com/datastax/astra/test/unit/DataApiOptionsTest.java @@ -1,7 +1,9 @@ package com.datastax.astra.test.unit; +import com.datastax.astra.client.DataAPIClient; import com.datastax.astra.client.DataAPIDestination; import com.datastax.astra.client.collections.documents.ReturnDocument; +import com.datastax.astra.client.core.http.HttpClientOptions; import com.datastax.astra.client.core.options.DataAPIClientOptions; import com.datastax.astra.client.collections.options.CollectionDeleteOneOptions; import com.datastax.astra.client.collections.results.CollectionDeleteResult; @@ -21,12 +23,13 @@ import com.datastax.astra.client.collections.documents.Updates; import com.datastax.astra.client.core.vector.VectorOptions; import com.datastax.astra.client.core.vectorize.VectorServiceOptions; -import com.datastax.astra.client.collections.CollectionIdTypes; -import com.datastax.astra.client.collections.CollectionOptions; +import com.datastax.astra.client.collections.CollectionDefaultIdTypes; +import com.datastax.astra.client.collections.CollectionDefinition; import com.datastax.astra.client.core.http.HttpProxy; import com.datastax.astra.client.core.query.Filter; import com.datastax.astra.client.core.query.FilterOperator; import com.datastax.astra.client.core.query.Projection; +import com.datastax.astra.client.databases.DatabaseOptions; import com.datastax.astra.internal.serdes.collections.DocumentSerializer; import org.junit.jupiter.api.Test; @@ -36,7 +39,7 @@ import java.util.Map; import static com.datastax.astra.client.core.options.DataAPIClientOptions.HEADER_FEATURE_FLAG_TABLES; -import static com.datastax.astra.client.core.options.TimeoutOptions.DEFAULT_DATA_OPERATION_TIMEOUT_MILLIS; +import static com.datastax.astra.client.core.options.TimeoutOptions.DEFAULT_GENERAL_METHOD_TIMEOUT_MILLIS; import static com.datastax.astra.client.core.options.TimeoutOptions.DEFAULT_REQUEST_TIMEOUT_MILLIS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -45,17 +48,31 @@ class DataApiOptionsTest { @Test void shouldPopulateOptions() { - DataAPIClientOptions options = DataAPIClientOptions.builder() - .withHttpProxy(new HttpProxy("localhost", 8080)) - .withApiVersion("v1") - .withHttpRedirect(HttpClient.Redirect.NORMAL) - .withHttpRetries(5, Duration.ofMillis(1000)) - .withDestination(DataAPIDestination.DSE) + DataAPIClientOptions options = new DataAPIClientOptions() + // Setup defaults based on destinations + .destination(DataAPIDestination.DSE) + // Overriding HTTP + .httpClientOptions(new HttpClientOptions() + .httpRedirect(HttpClient.Redirect.NORMAL) + .httpProxy(new HttpProxy("localhost", 8080)) + .httpRetries(1, Duration.ofSeconds(10))) + // Overriding Timeouts + .timeoutOptions(new TimeoutOptions() + .requestTimeoutMillis(1000)) + // Headers .enableFeatureFlagTables() - // equivalent to: - .addDatabaseAdditionalHeader(HEADER_FEATURE_FLAG_TABLES, "true") - .build(); - assertThat(options.getHttpClientOptions().getProxy().getHostname()).isEqualTo("localhost"); + .addDatabaseAdditionalHeader(HEADER_FEATURE_FLAG_TABLES, "true"); + DataAPIClient client = new DataAPIClient("token", options); + client.getDatabase("https://-.apps.astra.datastax.com"); + assertThat(options.getHttpClientOptions().getHttpProxy().getHostname()).isEqualTo("localhost"); + + DatabaseOptions optionss = new DatabaseOptions() + .keyspace("sample") + .token("another") + .timeout(Duration.ofSeconds(10)); + + + } @Test @@ -64,7 +81,7 @@ void shouldInitializeInsertManyOptions() { assertThat(new CollectionInsertManyOptions(). timeoutOptions(new TimeoutOptions() .requestTimeoutMillis(DEFAULT_REQUEST_TIMEOUT_MILLIS) - .dataOperationTimeoutMillis(DEFAULT_DATA_OPERATION_TIMEOUT_MILLIS))) + .generalMethodTimeoutMillis(DEFAULT_GENERAL_METHOD_TIMEOUT_MILLIS))) .isNotNull(); assertThat(new CollectionInsertManyOptions().ordered(true)).isNotNull(); assertThat(new CollectionInsertManyOptions().concurrency(2)).isNotNull(); @@ -72,7 +89,7 @@ void shouldInitializeInsertManyOptions() { @Test void shouldFailParsingCollectionIdTypes() { - assertThatThrownBy(() -> CollectionIdTypes.fromValue("invalid")) + assertThatThrownBy(() -> CollectionDefaultIdTypes.fromValue("invalid")) .isInstanceOf(IllegalArgumentException.class); } @@ -180,7 +197,7 @@ void shouldTestInsertOneResult() { @Test void shouldTestCollectionOptions() { - CollectionOptions c = new CollectionOptions(); + CollectionDefinition c = new CollectionDefinition(); VectorOptions v = new VectorOptions(); @@ -195,8 +212,8 @@ void shouldTestCollectionOptions() { p1.defaultValue("OK"); s.parameters(Map.of("ok", p1)); - v.setService(s); - c.setVector(v); + v.service(s); + c.vector(v); System.out.println(new DocumentSerializer().marshall(c)); assertThat(new DocumentSerializer().marshall(c)).isNotNull(); } @@ -262,22 +279,4 @@ void shouldTestFindOptions() { assertThat(new CollectionFindOptions().limit(10)).isNotNull(); assertThat(new CollectionFindOptions().skip(10)).isNotNull(); } - - @Test - void shouldTOverrideMaximumLimits() { - DataAPIClientOptions options = DataAPIClientOptions.builder() - .withMaxDocumentsInInsert(100) - .build(); - - Projection p1 = new Projection("field1", true); - Projection p2 = new Projection("field2", true); - CollectionFindOptions options1 = new CollectionFindOptions().projection(p1,p2); - CollectionFindOptions options2 = new CollectionFindOptions().projection(Projection.include("field1", "field2")); - - CollectionInsertManyOptions collectionInsertManyOptions = new CollectionInsertManyOptions().chunkSize(100); - //DataAPIClient client = new DataAPIClient("token", options); - - } - - } diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/unit/DocumentSerializationTest.java b/astra-db-java/src/test/java/com/datastax/astra/test/unit/DocumentSerializationTest.java index fc985f4f..0298de7e 100644 --- a/astra-db-java/src/test/java/com/datastax/astra/test/unit/DocumentSerializationTest.java +++ b/astra-db-java/src/test/java/com/datastax/astra/test/unit/DocumentSerializationTest.java @@ -1,6 +1,6 @@ package com.datastax.astra.test.unit; -import com.datastax.astra.client.collections.CollectionOptions; +import com.datastax.astra.client.collections.CollectionDefinition; import com.datastax.astra.client.core.commands.Command; import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.core.vector.SimilarityMetric; @@ -25,10 +25,8 @@ void shouldSerializeAsJson() { void shouldSerializeCommand() { Command ccc = Command.create("createCollection") .append("name", "demo") - .withOptions(CollectionOptions.builder() - .vectorDimension(14) - .vectorSimilarity(SimilarityMetric.COSINE) - .build()); + .withOptions(new CollectionDefinition() + .vector(14, SimilarityMetric.COSINE)); assertThat(new DocumentSerializer().marshall(ccc)).contains(SimilarityMetric.COSINE.getValue()); } diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/integration/local/QuickStartTablesLocal.java b/astra-db-java/src/test/java/com/datastax/astra/test/unit/QuickStartTablesLocal.java similarity index 82% rename from astra-db-java/src/test/java/com/datastax/astra/test/integration/local/QuickStartTablesLocal.java rename to astra-db-java/src/test/java/com/datastax/astra/test/unit/QuickStartTablesLocal.java index 1e4d5ea5..e8252268 100644 --- a/astra-db-java/src/test/java/com/datastax/astra/test/integration/local/QuickStartTablesLocal.java +++ b/astra-db-java/src/test/java/com/datastax/astra/test/unit/QuickStartTablesLocal.java @@ -1,4 +1,4 @@ -package com.datastax.astra.test.integration.local; +package com.datastax.astra.test.unit; import com.datastax.astra.client.DataAPIClients; import com.datastax.astra.client.core.query.Filter; @@ -17,7 +17,7 @@ import java.util.Optional; -import static com.datastax.astra.client.admin.AstraDBAdmin.DEFAULT_KEYSPACE; +import static com.datastax.astra.client.core.options.DataAPIClientOptions.DEFAULT_KEYSPACE; import static com.datastax.astra.client.tables.columns.ColumnTypes.INT; import static com.datastax.astra.client.tables.columns.ColumnTypes.TEXT; import static com.datastax.astra.client.tables.ddl.CreateTableOptions.IF_NOT_EXISTS; @@ -28,16 +28,16 @@ public class QuickStartTablesLocal { @Test public void should_quickstart_tables_default() { // Connect to local and create default_keyspace - Database localDb = DataAPIClients.defaultLocalDatabase(); - assertThat(localDb.getKeyspaceName()).isEqualTo(DEFAULT_KEYSPACE); + Database localDb = DataAPIClients.localDbWithDefaultKeyspace(); + assertThat(localDb.getKeyspace()).isEqualTo(DEFAULT_KEYSPACE); // Create table (explicit definition) TableDefinition tableDefinition = new TableDefinition() .addColumnText("id") .addColumnInt("age") .addColumnText("name") - .withPartitionKey("id"); - Table tablePersonDefault = localDb.createTable("person_default", tableDefinition, IF_NOT_EXISTS, Row.class); + .partitionKey("id"); + Table tablePersonDefault = localDb.createTable("person_default", tableDefinition, IF_NOT_EXISTS); assertThat(localDb.tableExists("person_default")).isTrue(); // Inserting data @@ -63,22 +63,22 @@ public void should_quickstart_tables_default() { public static class Person { @PartitionBy(0) - @Column(value="id", type=TEXT) + @Column(name ="id", type=TEXT) private String id; @PartitionBy(1) - @Column(value="name", type=TEXT) + @Column(name ="name", type=TEXT) private String name; - @Column(value="age", type=INT) + @Column(name ="age", type=INT) private int age; } @Test public void should_quickstart_tables_om() { // Connect to local and create default_keyspace - Database localDb = DataAPIClients.defaultLocalDatabase(); - assertThat(localDb.getKeyspaceName()).isEqualTo(DEFAULT_KEYSPACE); + Database localDb = DataAPIClients.localDbWithDefaultKeyspace(); + assertThat(localDb.getKeyspace()).isEqualTo(DEFAULT_KEYSPACE); // Create table (introspecting bean) Table tablePerson = localDb.createTable(Person.class, IF_NOT_EXISTS); diff --git a/astra-db-java/src/test/java/com/datastax/astra/test/unit/WorkWithOptions.java b/astra-db-java/src/test/java/com/datastax/astra/test/unit/WorkWithOptions.java new file mode 100644 index 00000000..0c424b86 --- /dev/null +++ b/astra-db-java/src/test/java/com/datastax/astra/test/unit/WorkWithOptions.java @@ -0,0 +1,71 @@ +package com.datastax.astra.test.unit; + +import com.datastax.astra.client.DataAPIClient; +import com.datastax.astra.client.collections.CollectionOptions; +import com.datastax.astra.client.core.auth.EmbeddingAPIKeyHeaderProvider; +import com.datastax.astra.client.core.commands.CommandType; +import com.datastax.astra.client.core.options.DataAPIClientOptions; +import com.datastax.astra.client.core.query.Sort; +import com.datastax.astra.client.databases.Database; +import com.datastax.astra.client.databases.options.CreateCollectionOptions; +import com.datastax.astra.client.databases.options.DropCollectionOptions; +import com.datastax.astra.client.databases.options.ListCollectionOptions; +import com.datastax.astra.client.tables.Table; +import com.datastax.astra.client.tables.TableDefinition; +import com.datastax.astra.client.tables.TableOptions; +import com.datastax.astra.client.tables.columns.ColumnDefinitionVector; +import com.datastax.astra.client.tables.columns.ColumnTypes; +import com.datastax.astra.client.tables.ddl.CreateTableOptions; +import com.datastax.astra.client.tables.row.Row; +import org.junit.jupiter.api.Test; + +import java.time.Duration; + +import static com.datastax.astra.client.core.vector.SimilarityMetric.COSINE; + +public class WorkWithOptions { + + @Test + public void shouldListCollection() { + + new ListCollectionOptions() + .timeout(Duration.ofMillis(1000)); + + new CollectionOptions() + .timeout(Duration.ofMillis(1000)) + .dataAPIClientOptions(new DataAPIClientOptions()) + .embeddingAuthProvider(new EmbeddingAPIKeyHeaderProvider("api-key")); + + new CreateCollectionOptions() + .commandType(CommandType.COLLECTION_ADMIN) + .timeout(Duration.ofMillis(1000)); + + new DropCollectionOptions() + .timeout(Duration.ofMillis(1000)); + + Database db = new DataAPIClient("token").getDatabase("db-endpoint"); + + TableDefinition tableDefinition = new TableDefinition() + .addColumnText("match_id") + .addColumnInt("round") + .addColumnVector("m_vector", new ColumnDefinitionVector().dimension(3).metric(COSINE)) + .addColumn("score", ColumnTypes.INT) + .addColumn("when", ColumnTypes.TIMESTAMP) + .addColumn("winner", ColumnTypes.TEXT) + .addColumnSet("fighters", ColumnTypes.UUID) + .addPartitionBy("match_id") + .addPartitionSort(Sort.ascending("round")); + + // Optional + CreateTableOptions createTableOptions = + new CreateTableOptions().timeout(Duration.ofMillis(1000)); + + // Optional to override spawn options + TableOptions tableOptions = + new TableOptions().timeout(Duration.ofMillis(1000)); + + Table tableSimple = db.createTable("TABLE_SIMPLE", tableDefinition); + + } + +} diff --git a/cassio-cql/src/test/resources/documentation.asciidoc b/cassio-cql/src/test/resources/documentation.asciidoc index 1499be63..9d19eb3f 100644 --- a/cassio-cql/src/test/resources/documentation.asciidoc +++ b/cassio-cql/src/test/resources/documentation.asciidoc @@ -284,7 +284,7 @@ Parameters: | The similarity metric to use for the vectors in the collection. | `def` -| `CollectionDefinition` +| `CollectionDescriptor` | The definition of the collection to create. |=== @@ -1035,7 +1035,7 @@ Parameters: | The dimension for the vector in the collection. | `def` -| `CollectionDefinition` +| `CollectionDescriptor` | The definition of the collection to create. | `bean` diff --git a/examples/src/main/java/QuickStartHCD.java b/examples/src/main/java/QuickStartHCD.java index f712ce2a..afd583de 100644 --- a/examples/src/main/java/QuickStartHCD.java +++ b/examples/src/main/java/QuickStartHCD.java @@ -1,22 +1,24 @@ -import com.datastax.astra.client.collections.Collection; import com.datastax.astra.client.DataAPIClient; +import com.datastax.astra.client.admin.DataAPIDatabaseAdmin; +import com.datastax.astra.client.collections.Collection; +import com.datastax.astra.client.collections.CollectionDefinition; import com.datastax.astra.client.collections.CollectionOptions; import com.datastax.astra.client.collections.documents.Document; +import com.datastax.astra.client.collections.options.CollectionFindOneOptions; import com.datastax.astra.client.core.auth.EmbeddingAPIKeyHeaderProvider; -import com.datastax.astra.client.core.commands.CommandOptions; +import com.datastax.astra.client.core.auth.UsernamePasswordTokenProvider; +import com.datastax.astra.client.core.options.DataAPIClientOptions; import com.datastax.astra.client.core.query.Sort; -import com.datastax.astra.client.databases.Database; -import com.datastax.astra.client.admin.DataAPIDatabaseAdmin; -import com.datastax.astra.client.collections.options.CollectionFindOneOptions; import com.datastax.astra.client.core.vector.SimilarityMetric; -import com.datastax.astra.client.core.auth.UsernamePasswordTokenProvider; +import com.datastax.astra.client.databases.Database; +import com.datastax.astra.client.databases.DatabaseOptions; +import com.datastax.astra.client.databases.options.CreateCollectionOptions; import com.datastax.astra.client.keyspaces.KeyspaceOptions; import java.util.Optional; import static com.datastax.astra.client.DataAPIClients.DEFAULT_ENDPOINT_LOCAL; import static com.datastax.astra.client.DataAPIDestination.HCD; -import static com.datastax.astra.client.core.options.DataAPIClientOptions.builder; import static com.datastax.astra.client.core.query.Filters.eq; public class QuickStartHCD { @@ -41,7 +43,7 @@ public static void main(String[] args) { System.out.println("1/7 - Creating Token: " + token); // Initialize the client - DataAPIClient client = new DataAPIClient(token, builder().withDestination(HCD).build()); + DataAPIClient client = new DataAPIClient(token, new DataAPIClientOptions().destination(HCD)); System.out.println("2/7 - Connected to Data API"); // Create a default keyspace @@ -51,16 +53,19 @@ public static void main(String[] args) { .createKeyspace(keyspaceName, KeyspaceOptions.simpleStrategy(1)); System.out.println("3/7 - Keyspace '" + keyspaceName + "'created "); - Database db = client.getDatabase(dataApiUrl, keyspaceName); + DatabaseOptions options = new DatabaseOptions().keyspace(keyspaceName); + Database db = client.getDatabase(dataApiUrl, options); System.out.println("4/7 - Connected to Database"); // Create a collection with Vector embeddings OPEN AI - Collection collectionLyrics = db.createCollection(collectionName, CollectionOptions.builder() + CollectionDefinition cd = new CollectionDefinition() .vectorSimilarity(SimilarityMetric.COSINE) .vectorDimension(openAiEmbeddingDimension) - .vectorize(openAiProvider, openAiModel) - .build(), - new CommandOptions<>().embeddingAuthProvider(new EmbeddingAPIKeyHeaderProvider(openAiKey))); + .vectorize(openAiProvider, openAiModel); + CreateCollectionOptions createCollectionOptions = new CreateCollectionOptions() + .embeddingAuthProvider(new EmbeddingAPIKeyHeaderProvider(openAiKey)); + Collection collectionLyrics = db.createCollection(collectionName, cd, Document.class, + createCollectionOptions, new CollectionOptions()); System.out.println("5/7 - Collection created with OpenAI embeddings"); // Insert some documents diff --git a/examples/src/main/java/QuickStartLocal.java b/examples/src/main/java/QuickStartLocal.java index 1320c395..5663d685 100644 --- a/examples/src/main/java/QuickStartLocal.java +++ b/examples/src/main/java/QuickStartLocal.java @@ -1,14 +1,16 @@ import com.datastax.astra.client.DataAPIClient; import com.datastax.astra.client.collections.Collection; +import com.datastax.astra.client.collections.CollectionDefinition; import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.collections.options.CollectionFindOptions; import com.datastax.astra.client.core.auth.UsernamePasswordTokenProvider; +import com.datastax.astra.client.core.options.DataAPIClientOptions; import com.datastax.astra.client.core.paging.FindIterable; import com.datastax.astra.client.core.query.Filter; import com.datastax.astra.client.databases.Database; +import com.datastax.astra.client.databases.DatabaseOptions; import static com.datastax.astra.client.DataAPIDestination.CASSANDRA; -import static com.datastax.astra.client.core.options.DataAPIClientOptions.builder; import static com.datastax.astra.client.core.query.Sort.vector; import static com.datastax.astra.client.core.vector.SimilarityMetric.COSINE; @@ -17,19 +19,26 @@ public class QuickStartLocal { public static void main(String[] args) { // Create a Token - String token = new UsernamePasswordTokenProvider("cassandra", "cassandra").getTokenAsString(); + String token = new UsernamePasswordTokenProvider("cassandra", "cassandra") + .getTokenAsString(); System.out.println("Token: " + token); // Initialize the client - DataAPIClient client = new DataAPIClient(token, builder().withDestination(CASSANDRA).build()); + DataAPIClientOptions dataApiOptions = new DataAPIClientOptions().destination(CASSANDRA); + DataAPIClient client = new DataAPIClient(token, dataApiOptions); System.out.println("Connected to Data API"); - Database db = client.getDatabase("http://localhost:8181", "default_keyspace"); + // Initialize the database + DatabaseOptions dbOptions = new DatabaseOptions().keyspace("default_keyspace"); + Database db = client.getDatabase("http://localhost:8181", dbOptions); System.out.println("Connected to Database"); // Create a collection. The default similarity metric is cosine. - Collection collection = db.createCollection("vector_test", 5, COSINE); - System.out.println("Created a Collection"); + CollectionDefinition collectionDefinition = new CollectionDefinition() + .vectorDimension(5) + .vectorSimilarity(COSINE); + Collection collection = db.createCollection("vector_test", collectionDefinition); + System.out.println("Collection created"); collection.insertMany( new Document("1") diff --git a/examples/src/main/java/QuickStartTraining.java b/examples/src/main/java/QuickStartTraining.java index 69564437..190c8dca 100644 --- a/examples/src/main/java/QuickStartTraining.java +++ b/examples/src/main/java/QuickStartTraining.java @@ -1,12 +1,12 @@ -import com.datastax.astra.client.collections.Collection; import com.datastax.astra.client.DataAPIClient; -import com.datastax.astra.client.collections.CollectionOptions; +import com.datastax.astra.client.collections.Collection; +import com.datastax.astra.client.collections.CollectionDefinition; import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.collections.options.CollectionFindOptions; import com.datastax.astra.client.core.options.DataAPIClientOptions; +import com.datastax.astra.client.core.paging.FindIterable; import com.datastax.astra.client.core.query.Filter; import com.datastax.astra.client.databases.Database; -import com.datastax.astra.client.core.paging.FindIterable; import static com.datastax.astra.client.core.query.Sort.vector; import static com.datastax.astra.client.core.vector.SimilarityMetric.COSINE; @@ -17,25 +17,23 @@ public static void main(String[] args) { String astraToken = System.getenv("ASTRA_DB_APPLICATION_TOKEN"); String astraApiEndpoint = System.getenv("ASTRA_DB_API_ENDPOINT"); - DataAPIClient client = new DataAPIClient(astraToken, DataAPIClientOptions.builder() - .withEmbeddingAPIKey("sfdsfdsfd") - .build()); + DataAPIClient client = new DataAPIClient(astraToken, new DataAPIClientOptions() + .embeddingAPIKey("sfdsfdsfd")); System.out.println("Connected to AstraDB"); - Database db = client.getDatabase(astraApiEndpoint, "default_keyspace"); + Database db = client.getDatabase(astraApiEndpoint); System.out.println("Connected to Database."); // Create a collection. The default similarity metric is cosine. - Collection collection = db - .createCollection("vector2", 5, COSINE); + CollectionDefinition collectionDefinition = new CollectionDefinition() + .vectorDimension(5) + .vectorSimilarity(COSINE); + Collection collection = db.createCollection("vector2", collectionDefinition); - db.createCollection("sdfdsf", CollectionOptions.builder() - .vectorize("openai", "eeneee") - .build()); + db.createCollection("sdfdsf", new CollectionDefinition() + .vectorize("openai", "eeneee")); System.out.println("Created a collection"); - DataAPIClientOptions.builder().withEmbeddingAPIKey("EMBEDDING_API_KEY"); - collection.insertMany( new Document("1") .append("text", "ChatGPT integrated sneakers that talk to you") diff --git a/examples/src/main/java/Quickstart.java b/examples/src/main/java/Quickstart.java index b6ad7105..7fb0fdac 100644 --- a/examples/src/main/java/Quickstart.java +++ b/examples/src/main/java/Quickstart.java @@ -1,5 +1,6 @@ import com.datastax.astra.client.collections.Collection; import com.datastax.astra.client.DataAPIClient; +import com.datastax.astra.client.collections.CollectionDefinition; import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.core.options.DataAPIClientOptions; import com.datastax.astra.client.core.query.Filter; @@ -17,25 +18,27 @@ public static void main(String[] args) { String astraToken = System.getenv("ASTRA_DB_APPLICATION_TOKEN"); String astraApiEndpoint = System.getenv("ASTRA_DB_API_ENDPOINT"); - // Initialize the client. The keyspace parameter is optional if you use - // "default_keyspace". - DataAPIClient client = new DataAPIClient(astraToken, DataAPIClientOptions.builder().build()); + // Initialize the client. + DataAPIClient client = new DataAPIClient(astraToken); System.out.println("Connected to AstraDB"); - Database db = client.getDatabase(astraApiEndpoint, "default_keyspace"); + // Initialize the database. + Database db = client.getDatabase(astraApiEndpoint); System.out.println("Connected to Database."); // end::init[] // tag::collection[] // Create a collection. The default similarity metric is cosine. - Collection collection = db - .createCollection("vector_test", 5, COSINE); + CollectionDefinition cd = new CollectionDefinition() + .vectorDimension(5) + .vectorSimilarity(COSINE); + Collection col = db.createCollection("vector_test", cd); System.out.println("Created a collection"); // end::collection[] // tag::data[] // Insert documents into the collection - collection.insertMany( + col.insertMany( new Document("1") .append("text", "ChatGPT integrated sneakers that talk to you") .vector(new float[]{0.1f, 0.15f, 0.3f, 0.12f, 0.05f}), @@ -54,13 +57,13 @@ public static void main(String[] args) { CollectionFindOptions options = new CollectionFindOptions() .sort(vector(new float[]{0.15f, 0.1f, 0.1f, 0.35f, 0.55f})) .limit(10); - FindIterable resultsSet = collection.find(filter,options); + FindIterable resultsSet = col.find(filter,options); resultsSet.forEach(System.out::println); // end::search[] // tag::cleanup[] // Delete the collection - collection.drop(); + col.drop(); System.out.println("Deleted the collection"); // end::cleanup[] diff --git a/examples/src/main/java/com/datastax/astra/client/Connecting.java b/examples/src/main/java/com/datastax/astra/client/Connecting.java index 411498c5..51314f6d 100644 --- a/examples/src/main/java/com/datastax/astra/client/Connecting.java +++ b/examples/src/main/java/com/datastax/astra/client/Connecting.java @@ -2,6 +2,7 @@ import com.datastax.astra.client.core.options.DataAPIClientOptions; import com.datastax.astra.client.databases.Database; +import com.datastax.astra.client.databases.DatabaseOptions; import java.util.UUID; @@ -11,20 +12,19 @@ public static void main(String[] args) { DataAPIClient client = new DataAPIClient("TOKEN"); // Overriding the default options - DataAPIClient client1 = new DataAPIClient("TOKEN", DataAPIClientOptions - .builder() - .build()); + DataAPIClient client1 = new DataAPIClient("TOKEN", new DataAPIClientOptions()); // Access the Database from its endpoint Database db1 = client1.getDatabase("*API_ENDPOINT*"); - Database db2 = client1.getDatabase("*API_ENDPOINT*", "*KEYSPACE*"); + Database db2 = client1.getDatabase("*API_ENDPOINT*", new DatabaseOptions().keyspace("*KEYSPACE*")); // Access the Database from its endpoint UUID databaseId = UUID.fromString("f5abf92f-ff66-48a0-bbc2-d240bc25dc1f"); Database db3 = client.getDatabase(databaseId); - Database db4 = client.getDatabase(databaseId, "*KEYSPACE*"); - Database db5 = client.getDatabase(databaseId, "*KEYSPACE*", "us-east-2"); - + Database db4 = client.getDatabase(databaseId, + new DatabaseOptions().keyspace("*KEYSPACE*")); + Database db5 = client.getDatabase(databaseId, "us-east-2", + new DatabaseOptions().keyspace("*KEYSPACE*")); db5.useKeyspace("yet_another"); } diff --git a/examples/src/main/java/com/datastax/astra/client/DataApiClientDemo.java b/examples/src/main/java/com/datastax/astra/client/DataApiClientDemo.java index c26eb629..5ff0cb2f 100644 --- a/examples/src/main/java/com/datastax/astra/client/DataApiClientDemo.java +++ b/examples/src/main/java/com/datastax/astra/client/DataApiClientDemo.java @@ -2,8 +2,10 @@ import com.datastax.astra.client.admin.AstraDBAdmin; import com.datastax.astra.client.collections.Collection; +import com.datastax.astra.client.collections.CollectionDefinition; import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.databases.Database; +import com.datastax.astra.client.databases.DatabaseOptions; import java.util.List; import java.util.UUID; @@ -14,10 +16,12 @@ public class DataApiClientDemo { public static void main(String[] args) { DataAPIClient client = new DataAPIClient("TOKEN"); Database database0 = client.getDatabase("API_ENDPOINT"); - Collection collection0 = database0.createCollection("movies", 2, COSINE); + Collection collection0 = database0.createCollection("movies", + new CollectionDefinition().vector(2, COSINE)); collection0.insertOne(new Document().append("title", "The Title").vector(new float[]{1.0f, 1.0f})); Database database1 = client.getDatabase(UUID.fromString("01234567-...")); - Database database2 = client.getDatabase(UUID.fromString("01234567-..."), "*KEYSPACE*", "us-east1"); + Database database2 = client.getDatabase(UUID.fromString("01234567-..."), "us-east1", + new DatabaseOptions().keyspace("*KEYSPACE*")); AstraDBAdmin admin1 = client.getAdmin(); AstraDBAdmin admin2 = client.getAdmin("more_powerful_token_override"); List databases = admin1.listDatabaseNames(); diff --git a/examples/src/main/java/com/datastax/astra/client/GettingStarted.java b/examples/src/main/java/com/datastax/astra/client/GettingStarted.java index 1c892f1e..03c8719f 100644 --- a/examples/src/main/java/com/datastax/astra/client/GettingStarted.java +++ b/examples/src/main/java/com/datastax/astra/client/GettingStarted.java @@ -1,6 +1,7 @@ package com.datastax.astra.client; import com.datastax.astra.client.collections.Collection; +import com.datastax.astra.client.collections.CollectionDefinition; import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.collections.options.CollectionFindOptions; import com.datastax.astra.client.core.paging.FindIterable; @@ -21,7 +22,8 @@ public static void main(String[] args) { Database db = client.getDatabase("http://db-region.apps.astra.datastax.com"); // Create collection with vector support - Collection col = db.createCollection("demo", 2, EUCLIDEAN); + CollectionDefinition def = new CollectionDefinition().vector(2, EUCLIDEAN); + Collection col = db.createCollection("demo", def); // Insert records col.insertMany(List.of( diff --git a/examples/src/main/java/com/datastax/astra/client/collections/ClearCollection.java b/examples/src/main/java/com/datastax/astra/client/collections/ClearCollection.java index 39aff082..9a502a0a 100644 --- a/examples/src/main/java/com/datastax/astra/client/collections/ClearCollection.java +++ b/examples/src/main/java/com/datastax/astra/client/collections/ClearCollection.java @@ -1,12 +1,14 @@ package com.datastax.astra.client.collections; +import com.datastax.astra.client.DataAPIClient; import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.databases.Database; public class ClearCollection { public static void main(String[] args) { // Connect to running dn - Database db = new Database("API_ENDPOINT", "TOKEN"); + Database db = new DataAPIClient("TOKEN") + .getDatabase("API_ENDPOINT"); // Accessing the collection Collection collection = db.getCollection("collection_simple"); diff --git a/examples/src/main/java/com/datastax/astra/client/collections/InsertMany.java b/examples/src/main/java/com/datastax/astra/client/collections/InsertMany.java index 8ffc26f5..4c132334 100644 --- a/examples/src/main/java/com/datastax/astra/client/collections/InsertMany.java +++ b/examples/src/main/java/com/datastax/astra/client/collections/InsertMany.java @@ -43,7 +43,7 @@ public static void main(String[] args) { .concurrency(1) // parallel processing .ordered(false) // allows parallel processing .timeoutOptions(new TimeoutOptions() - .dataOperationTimeoutMillis(50000) + .generalMethodTimeoutMillis(50000) .requestTimeoutMillis(2000)); // timeout in millis CollectionInsertManyResult res2 = collectionProduct.insertMany( diff --git a/examples/src/main/java/com/datastax/astra/client/collections/vectorize/QuickStartOpenAI.java b/examples/src/main/java/com/datastax/astra/client/collections/vectorize/QuickStartOpenAI.java index 133fe584..c2479ffc 100644 --- a/examples/src/main/java/com/datastax/astra/client/collections/vectorize/QuickStartOpenAI.java +++ b/examples/src/main/java/com/datastax/astra/client/collections/vectorize/QuickStartOpenAI.java @@ -2,7 +2,7 @@ import com.datastax.astra.client.collections.Collection; import com.datastax.astra.client.DataAPIClient; -import com.datastax.astra.client.collections.CollectionOptions; +import com.datastax.astra.client.collections.CollectionDefinition; import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.core.options.DataAPIClientOptions; import com.datastax.astra.client.core.query.Sort; @@ -11,6 +11,7 @@ import com.datastax.astra.client.collections.options.CollectionFindOneOptions; import com.datastax.astra.client.core.vector.SimilarityMetric; import com.datastax.astra.client.core.auth.UsernamePasswordTokenProvider; +import com.datastax.astra.client.databases.DatabaseOptions; import com.datastax.astra.internal.command.LoggingCommandObserver; import lombok.extern.slf4j.Slf4j; @@ -18,7 +19,7 @@ import static com.datastax.astra.client.DataAPIClients.DEFAULT_ENDPOINT_LOCAL; import static com.datastax.astra.client.DataAPIDestination.CASSANDRA; -import static com.datastax.astra.client.admin.AstraDBAdmin.DEFAULT_KEYSPACE;; +import static com.datastax.astra.client.core.options.DataAPIClientOptions.DEFAULT_KEYSPACE; /** * This demo want to illustrate how to use the java client in GenAI Context @@ -35,15 +36,14 @@ public static void main(String[] args) { String dataAPICassandraToken = new UsernamePasswordTokenProvider("cassandra", "cassandra").getToken(); // Create the Client, option is provided at top level and will be available - DataAPIClient localDataAPI = new DataAPIClient(dataAPICassandraToken, DataAPIClientOptions.builder() - .withDestination(CASSANDRA) - .withEmbeddingAPIKey(embeddingApiKey) - .withObserver(new LoggingCommandObserver(DataAPIClient.class)) - .build()); + DataAPIClient localDataAPI = new DataAPIClient(dataAPICassandraToken, new DataAPIClientOptions() + .destination(CASSANDRA) + .embeddingAPIKey(embeddingApiKey) + .logRequests()); // Access to the database Database localDb = localDataAPI - .getDatabase(DEFAULT_ENDPOINT_LOCAL, DEFAULT_KEYSPACE); + .getDatabase(DEFAULT_ENDPOINT_LOCAL, new DatabaseOptions().keyspace("ks1")); // Create a Namespace if Needed localDb.getDatabaseAdmin().createKeyspace(DEFAULT_KEYSPACE); @@ -52,12 +52,11 @@ public static void main(String[] args) { Collection collection = localDb.createCollection( embeddingModel.name().toLowerCase(), // Create collection with a Service in vectorize - CollectionOptions.builder() + new CollectionDefinition() .indexingAllow() .vectorDimension(embeddingModel.getDimension()) .vectorSimilarity(SimilarityMetric.COSINE) - .vectorize(embeddingModel.getProvider(), embeddingModel.getName()) - .build()); + .vectorize(embeddingModel.getProvider(), embeddingModel.getName())); // Insert documents collection.deleteAll(); diff --git a/examples/src/main/java/com/datastax/astra/client/database/CreateCollection.java b/examples/src/main/java/com/datastax/astra/client/database/CreateCollection.java index 32a63426..ef54f32b 100644 --- a/examples/src/main/java/com/datastax/astra/client/database/CreateCollection.java +++ b/examples/src/main/java/com/datastax/astra/client/database/CreateCollection.java @@ -1,70 +1,64 @@ package com.datastax.astra.client.database; +import com.datastax.astra.client.DataAPIClients; import com.datastax.astra.client.collections.Collection; -import com.datastax.astra.client.collections.CollectionIdTypes; -import com.datastax.astra.client.collections.CollectionOptions; +import com.datastax.astra.client.collections.CollectionDefaultIdTypes; +import com.datastax.astra.client.collections.CollectionDefinition; import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.core.vector.SimilarityMetric; import com.datastax.astra.client.databases.Database; public class CreateCollection { public static void main(String[] args) { - - Database db = new Database( - System.getenv("ASTRA_DB_API_ENDPOINT"), - System.getenv("ASTRA_DB_APPLICATION_TOKEN")); + /* Using Astra + String astraToken = System.getenv("ASTRA_DB_APPLICATION_TOKEN"); + String astraApiEndpoint = System.getenv("ASTRA_DB_API_ENDPOINT"); + Database db = new DataAPIClient(astraToken).getDatabase(System.getenv(astraApiEndpoint)); + */ + // Using Local DB + Database db = DataAPIClients.localDbWithDefaultKeyspace(); // Create a non-vector collection Collection simple1 = db.createCollection("col"); // Default Id Collection - Collection defaultId = db.createCollection("defaultId", CollectionOptions - .builder() - .defaultIdType(CollectionIdTypes.OBJECT_ID) - .build()); + Collection defaultId = db.createCollection("defaultId", new CollectionDefinition() + .defaultId(CollectionDefaultIdTypes.OBJECT_ID)); // -- Indexing - Collection indexingDeny = db.createCollection("indexing1", CollectionOptions - .builder() - .indexingDeny("blob") - .build()); + Collection indexingDeny = db.createCollection("indexing1", + new CollectionDefinition().indexingDeny("blob")); // Create a collection with indexing (allow) - cannot use allow and denay at the same time - Collection indexingAllow = db.createCollection("allow1", CollectionOptions - .builder() - .indexingAllow("metadata") - .build()); + Collection indexingAllow = db.createCollection("allow1", + new CollectionDefinition().indexingAllow("metadata")); // Vector - Collection vector1 = db.createCollection("vector1", 14, SimilarityMetric.DOT_PRODUCT); + CollectionDefinition cd = new CollectionDefinition() + .vectorDimension(5) + .vectorSimilarity(SimilarityMetric.COSINE); + Collection vector1 = db.createCollection("vector1", cd); // Create a vector collection - Collection vector2 = db.createCollection("vector2", CollectionOptions - .builder() + Collection vector2 = db.createCollection("vector2", new CollectionDefinition() .vectorDimension(1536) - .vectorSimilarity(SimilarityMetric.EUCLIDEAN) - .build()); + .vectorSimilarity(SimilarityMetric.EUCLIDEAN)); // Create a collection for the db Collection collection_vectorize_header = db.createCollection( "collection_vectorize_header", // Create collection with a Service in vectorize (No API KEY) - CollectionOptions.builder() + new CollectionDefinition() .vectorDimension(1536) .vectorSimilarity(SimilarityMetric.DOT_PRODUCT) - .vectorize("openai", "text-embedding-ada-002") - .build()); + .vectorize("openai", "text-embedding-ada-002")); // Create a collection for the db Collection collection_vectorize_shared_key = db.createCollection( "collection_vectorize_shared_key", // Create collection with a Service in vectorize (No API KEY) - CollectionOptions.builder() + new CollectionDefinition() .vectorDimension(1536) .vectorSimilarity(SimilarityMetric.DOT_PRODUCT) - .vectorize("openai", "text-embedding-ada-002", "OPENAI_API_KEY" ) - .build()); - - - + .vectorize("openai", "text-embedding-ada-002", "OPENAI_API_KEY" )); } } diff --git a/examples/src/main/java/com/datastax/astra/client/database/CreateTable.java b/examples/src/main/java/com/datastax/astra/client/database/CreateTable.java new file mode 100644 index 00000000..e45a0a4a --- /dev/null +++ b/examples/src/main/java/com/datastax/astra/client/database/CreateTable.java @@ -0,0 +1,61 @@ +package com.datastax.astra.client.database; + +import com.datastax.astra.client.DataAPIClients; +import com.datastax.astra.client.core.auth.EmbeddingAPIKeyHeaderProvider; +import com.datastax.astra.client.databases.Database; +import com.datastax.astra.client.tables.Game; +import com.datastax.astra.client.tables.Table; +import com.datastax.astra.client.tables.TableDefinition; +import com.datastax.astra.client.tables.TableOptions; +import com.datastax.astra.client.tables.columns.ColumnDefinitionVector; +import com.datastax.astra.client.tables.columns.ColumnTypes; +import com.datastax.astra.client.tables.ddl.CreateTableOptions; +import com.datastax.astra.client.tables.row.Row; + +import static com.datastax.astra.client.core.query.Sort.ascending; +import static com.datastax.astra.client.core.vector.SimilarityMetric.COSINE; +import static java.time.Duration.ofSeconds; + +public class CreateTable { + + public static void main(String[] args) { + + // Database astraDb = new DataAPIClient(token).getDatabase(endpoint); + Database db = DataAPIClients.localDbWithDefaultKeyspace(); + + // Definition of the table in fluent style + TableDefinition tableDefinition = new TableDefinition() + .addColumnText("match_id") + .addColumnInt("round") + .addColumnVector("m_vector", new ColumnDefinitionVector().dimension(3).metric(COSINE)) + .addColumn("score", ColumnTypes.INT) + .addColumn("when", ColumnTypes.TIMESTAMP) + .addColumn("winner", ColumnTypes.TEXT) + .addColumnSet("fighters", ColumnTypes.UUID) + .addPartitionBy("match_id") + .addPartitionSort(ascending("round")); + + // Minimal creation + Table table1 = db.createTable("game1", tableDefinition); + + // Minimal Creation with a Bean + Table table2 = db.createTable("game2", tableDefinition, Game.class); + + // -- options -- + + // One can add options to setup the creation with finer grained: + CreateTableOptions createTableOptions = new CreateTableOptions() + .ifNotExists(true) + .timeout(ofSeconds(5)); + Table table3 = db.createTable("game3", tableDefinition, createTableOptions); + + // One can can tuned the table object returned by the function + TableOptions tableOptions = new TableOptions() + .embeddingAuthProvider(new EmbeddingAPIKeyHeaderProvider("api-key")) + .timeout(ofSeconds(5)); + + // Change the Type of objects in use instead of default Row + Table table4 = db.createTable("game4", tableDefinition,Row.class, + createTableOptions, tableOptions); + } +} diff --git a/examples/src/main/java/com/datastax/astra/client/database/CreateTableOM.java b/examples/src/main/java/com/datastax/astra/client/database/CreateTableOM.java new file mode 100644 index 00000000..1e4cf5f9 --- /dev/null +++ b/examples/src/main/java/com/datastax/astra/client/database/CreateTableOM.java @@ -0,0 +1,25 @@ +package com.datastax.astra.client.database; + +import com.datastax.astra.client.DataAPIClients; +import com.datastax.astra.client.databases.Database; +import com.datastax.astra.client.tables.GameWithAnnotation; +import com.datastax.astra.client.tables.GameWithAnnotationAllHints; +import com.datastax.astra.client.tables.Table; + +import static com.datastax.astra.client.tables.ddl.DropTableOptions.IF_EXISTS; + +public class CreateTableOM { + + public static void main(String[] args) { + Database db = DataAPIClients.localDbWithDefaultKeyspace(); + + db.dropTable(db.getTableName(GameWithAnnotationAllHints.class), IF_EXISTS); + db.dropTable(db.getTableName(GameWithAnnotation.class), IF_EXISTS); + + // Creation with a fully annotated bean + Table table1 = db.createTable(GameWithAnnotationAllHints.class); + + // Minimal creation + Table table2 = db.createTable(GameWithAnnotation.class); + } +} diff --git a/examples/src/main/java/com/datastax/astra/client/database/DropCollection.java b/examples/src/main/java/com/datastax/astra/client/database/DropCollection.java index d2a2fa43..83ad487a 100644 --- a/examples/src/main/java/com/datastax/astra/client/database/DropCollection.java +++ b/examples/src/main/java/com/datastax/astra/client/database/DropCollection.java @@ -1,12 +1,16 @@ package com.datastax.astra.client.database; +import com.datastax.astra.client.DataAPIClient; import com.datastax.astra.client.databases.Database; +import com.datastax.astra.client.databases.options.DropCollectionOptions; public class DropCollection { public static void main(String[] args) { - Database db = new Database("API_ENDPOINT", "TOKEN"); + Database db = new DataAPIClient("TOKEN") + .getDatabase("API_ENDPOINT"); // Delete an existing collection - db.dropCollection("collection_vector2"); + DropCollectionOptions options = new DropCollectionOptions(); + db.dropCollection("collection_vector2", options); } } diff --git a/examples/src/main/java/com/datastax/astra/client/database/FindCollection.java b/examples/src/main/java/com/datastax/astra/client/database/FindCollection.java index f72b5876..008ce772 100644 --- a/examples/src/main/java/com/datastax/astra/client/database/FindCollection.java +++ b/examples/src/main/java/com/datastax/astra/client/database/FindCollection.java @@ -1,19 +1,21 @@ package com.datastax.astra.client.database; +import com.datastax.astra.client.DataAPIClient; import com.datastax.astra.client.collections.Collection; -import com.datastax.astra.client.collections.CollectionOptions; +import com.datastax.astra.client.collections.CollectionDefinition; import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.databases.Database; public class FindCollection { public static void main(String[] args) { - Database db = new Database("TOKEN", "API_ENDPOINT"); + Database db = new DataAPIClient("TOKEN") + .getDatabase("API_ENDPOINT"); // Find a collection Collection collection = db.getCollection("collection_vector1"); // Gather collection information - CollectionOptions options = collection.getOptions(); + CollectionDefinition options = collection.getDefinition(); // Check if a collection exists boolean collectionExists = db.getCollection("collection_vector2").exists(); diff --git a/examples/src/main/java/com/datastax/astra/client/database/InitializeDatabase.java b/examples/src/main/java/com/datastax/astra/client/database/InitializeDatabase.java index 13f66668..fa909b19 100644 --- a/examples/src/main/java/com/datastax/astra/client/database/InitializeDatabase.java +++ b/examples/src/main/java/com/datastax/astra/client/database/InitializeDatabase.java @@ -1,19 +1,37 @@ package com.datastax.astra.client.database; +import com.datastax.astra.client.DataAPIClient; +import com.datastax.astra.client.core.http.HttpClientOptions; import com.datastax.astra.client.core.options.DataAPIClientOptions; +import com.datastax.astra.client.core.options.TimeoutOptions; import com.datastax.astra.client.databases.Database; +import com.datastax.astra.client.databases.DatabaseOptions; + +import java.net.http.HttpClient; +import java.time.Duration; public class InitializeDatabase { public static void main(String[] args) { // Default initialization - Database db = new Database("API_ENDPOINT", "TOKEN"); + Database db = new DataAPIClient("TOKEN") + .getDatabase("API_ENDPOINT"); // Initialize with a non-default namespace. - Database db2 = new Database("API_ENDPOINT", "TOKEN", "KEYSPACE"); + Database db2 = new DataAPIClient("TOKEN") + .getDatabase("API_ENDPOINT", new DatabaseOptions().keyspace("KEYSPACE")); // non-default namespace + options // 'Options' allows fined-grained configuration. - DataAPIClientOptions options = DataAPIClientOptions.builder().build(); - Database db3 = new Database("API_ENDPOINT", "TOKEN", "KEYSPACE", options); + DataAPIClientOptions options = new DataAPIClientOptions() + .httpClientOptions(new HttpClientOptions() + .httpVersion(HttpClient.Version.HTTP_2) + .httpRetries(1, Duration.ofMillis(100)) + ) + .timeoutOptions(new TimeoutOptions() + .connectTimeout(Duration.ofMillis(100)) + .generalMethodTimeout(Duration.ofSeconds(5)) + ); + Database db3 = new DataAPIClient("TOKEN", options) + .getDatabase("API_ENDPOINT"); } } diff --git a/examples/src/main/java/com/datastax/astra/client/database/ListCollections.java b/examples/src/main/java/com/datastax/astra/client/database/ListCollections.java index b988a161..67d3d0f9 100644 --- a/examples/src/main/java/com/datastax/astra/client/database/ListCollections.java +++ b/examples/src/main/java/com/datastax/astra/client/database/ListCollections.java @@ -1,19 +1,22 @@ package com.datastax.astra.client.database; -import com.datastax.astra.client.collections.CollectionDefinition; +import com.datastax.astra.client.DataAPIClient; +import com.datastax.astra.client.collections.CollectionDescriptor; import com.datastax.astra.client.databases.Database; +import java.util.List; import java.util.stream.Stream; public class ListCollections { public static void main(String[] args) { - Database db = new Database("TOKEN", "API_ENDPOINT"); + Database db = new DataAPIClient("TOKEN") + .getDatabase("API_ENDPOINT"); // Get collection Names - Stream collectionNames = db.listCollectionNames(); + List collectionNames = db.listCollectionNames(); // Get Collection information (with options) - Stream collections = db.listCollections(); - collections.map(CollectionDefinition::getOptions).forEach(System.out::println); + List collections = db.listCollections(); + collections.stream().map(CollectionDescriptor::getOptions).forEach(System.out::println); } } diff --git a/examples/src/main/java/com/datastax/astra/client/database_admin/CreateKeyspace.java b/examples/src/main/java/com/datastax/astra/client/database_admin/CreateKeyspace.java index 9851b32b..651bea24 100644 --- a/examples/src/main/java/com/datastax/astra/client/database_admin/CreateKeyspace.java +++ b/examples/src/main/java/com/datastax/astra/client/database_admin/CreateKeyspace.java @@ -1,11 +1,12 @@ package com.datastax.astra.client.database_admin; +import com.datastax.astra.client.DataAPIClient; import com.datastax.astra.client.databases.Database; public class CreateKeyspace { public static void main(String[] args) { // Default initialization - Database db = new Database("API_ENDPOINT", "TOKEN"); + Database db = new DataAPIClient("TOKEN").getDatabase("API_ENDPOINT"); // Create a new keyspace db.getDatabaseAdmin().createKeyspace(""); diff --git a/examples/src/main/java/com/datastax/astra/client/database_admin/DropKeyspace.java b/examples/src/main/java/com/datastax/astra/client/database_admin/DropKeyspace.java index 33f1a343..f3ed7376 100644 --- a/examples/src/main/java/com/datastax/astra/client/database_admin/DropKeyspace.java +++ b/examples/src/main/java/com/datastax/astra/client/database_admin/DropKeyspace.java @@ -1,13 +1,14 @@ package com.datastax.astra.client.database_admin; +import com.datastax.astra.client.DataAPIClient; import com.datastax.astra.client.databases.Database; public class DropKeyspace { public static void main(String[] args) { // Default initialization - Database db = new Database("API_ENDPOINT", "TOKEN"); - + Database db = new DataAPIClient("TOKEN") + .getDatabase("API_ENDPOINT"); // Drop a Namespace db.getDatabaseAdmin().dropKeyspace(""); } diff --git a/examples/src/main/java/com/datastax/astra/client/database_admin/FindEmbeddingProviders.java b/examples/src/main/java/com/datastax/astra/client/database_admin/FindEmbeddingProviders.java index 910eb23a..00fbaaca 100644 --- a/examples/src/main/java/com/datastax/astra/client/database_admin/FindEmbeddingProviders.java +++ b/examples/src/main/java/com/datastax/astra/client/database_admin/FindEmbeddingProviders.java @@ -1,15 +1,17 @@ package com.datastax.astra.client.database_admin; -import com.datastax.astra.client.databases.Database; +import com.datastax.astra.client.DataAPIClient; import com.datastax.astra.client.admin.DatabaseAdmin; -import com.datastax.astra.client.core.vectorize.EmbeddingProvider; import com.datastax.astra.client.core.results.FindEmbeddingProvidersResult; +import com.datastax.astra.client.core.vectorize.EmbeddingProvider; +import com.datastax.astra.client.databases.Database; import java.util.Map; public class FindEmbeddingProviders { public static void main(String[] args) { - Database db = new Database("API_ENDPOINT","TOKEN"); + Database db = new DataAPIClient("TOKEN") + .getDatabase("API_ENDPOINT"); DatabaseAdmin dbAdmin = db.getDatabaseAdmin(); // was actually a new object not the initial diff --git a/examples/src/main/java/com/datastax/astra/client/tables/Game.java b/examples/src/main/java/com/datastax/astra/client/tables/Game.java new file mode 100644 index 00000000..47472659 --- /dev/null +++ b/examples/src/main/java/com/datastax/astra/client/tables/Game.java @@ -0,0 +1,31 @@ +package com.datastax.astra.client.tables; + +import com.datastax.astra.client.core.vector.DataAPIVector; +import com.datastax.astra.client.tables.mapping.Column; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; +import java.util.Set; + +@Data +@NoArgsConstructor +public class Game { + + @Column(name ="match_id") + private String matchId; + + private Integer round; + + private Integer score; + + private Instant when; + + private String winner; + + private Set fighters; + + @Column(name ="m_vector") + private DataAPIVector vector; + +} diff --git a/examples/src/main/java/com/datastax/astra/client/tables/GameWithAnnotation.java b/examples/src/main/java/com/datastax/astra/client/tables/GameWithAnnotation.java new file mode 100644 index 00000000..b948b919 --- /dev/null +++ b/examples/src/main/java/com/datastax/astra/client/tables/GameWithAnnotation.java @@ -0,0 +1,49 @@ +package com.datastax.astra.client.tables; + +import com.datastax.astra.client.core.query.SortOrder; +import com.datastax.astra.client.core.vector.DataAPIVector; +import com.datastax.astra.client.core.vector.SimilarityMetric; +import com.datastax.astra.client.tables.mapping.Column; +import com.datastax.astra.client.tables.mapping.EntityTable; +import com.datastax.astra.client.tables.mapping.PartitionBy; +import com.datastax.astra.client.tables.mapping.PartitionSort; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; +import java.util.Set; +import java.util.UUID; + +import static com.datastax.astra.client.tables.columns.ColumnTypes.INT; +import static com.datastax.astra.client.tables.columns.ColumnTypes.SET; +import static com.datastax.astra.client.tables.columns.ColumnTypes.TEXT; +import static com.datastax.astra.client.tables.columns.ColumnTypes.TIMESTAMP; +import static com.datastax.astra.client.tables.columns.ColumnTypes.UUID; +import static com.datastax.astra.client.tables.columns.ColumnTypes.VECTOR; + +@Data +@EntityTable("game_ann2") +@NoArgsConstructor +@AllArgsConstructor +public class GameWithAnnotation { + + @PartitionBy(0) + @Column(name ="match_id") + private String matchId; + + @PartitionSort(position = 0, order = SortOrder.ASCENDING) + private Integer round; + + private Integer score; + + private Instant when; + + private String winner; + + private Set fighters; + + @Column(name ="m_vector", dimension = 3, metric = SimilarityMetric.COSINE) + private DataAPIVector vector; + +} diff --git a/examples/src/main/java/com/datastax/astra/client/tables/GameWithAnnotationAllHints.java b/examples/src/main/java/com/datastax/astra/client/tables/GameWithAnnotationAllHints.java new file mode 100644 index 00000000..74025946 --- /dev/null +++ b/examples/src/main/java/com/datastax/astra/client/tables/GameWithAnnotationAllHints.java @@ -0,0 +1,53 @@ +package com.datastax.astra.client.tables; + +import com.datastax.astra.client.core.query.SortOrder; +import com.datastax.astra.client.core.vector.DataAPIVector; +import com.datastax.astra.client.core.vector.SimilarityMetric; +import com.datastax.astra.client.tables.mapping.Column; +import com.datastax.astra.client.tables.mapping.EntityTable; +import com.datastax.astra.client.tables.mapping.PartitionBy; +import com.datastax.astra.client.tables.mapping.PartitionSort; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; +import java.util.Set; + +import static com.datastax.astra.client.tables.columns.ColumnTypes.INT; +import static com.datastax.astra.client.tables.columns.ColumnTypes.SET; +import static com.datastax.astra.client.tables.columns.ColumnTypes.TEXT; +import static com.datastax.astra.client.tables.columns.ColumnTypes.TIMESTAMP; +import static com.datastax.astra.client.tables.columns.ColumnTypes.UUID; +import static com.datastax.astra.client.tables.columns.ColumnTypes.VECTOR; + +@Data +@EntityTable("game_ann1") +@NoArgsConstructor +@AllArgsConstructor +public class GameWithAnnotationAllHints { + + @PartitionBy(0) + @Column(name ="match_id", type=TEXT ) + private String matchId; + + @PartitionSort(position = 0, order= SortOrder.ASCENDING) + @Column(name ="round", type=INT) + private Integer round; + + @Column(name ="score", type=INT) + private Integer score; + + @Column(name ="when", type=TIMESTAMP) + private Instant when; + + @Column(name ="winner", type=TEXT) + private String winner; + + @Column(name ="fighters", type=SET, valueType = UUID) + private Set fighters; + + @Column(name ="m_vector", type=VECTOR, dimension = 3, metric = SimilarityMetric.COSINE) + private DataAPIVector vector; + +} diff --git a/examples/src/main/java/com/datastax/astra/genai/QuickStartAzureOpenAI.java b/examples/src/main/java/com/datastax/astra/genai/QuickStartAzureOpenAI.java index 2722dfaf..58ceda71 100644 --- a/examples/src/main/java/com/datastax/astra/genai/QuickStartAzureOpenAI.java +++ b/examples/src/main/java/com/datastax/astra/genai/QuickStartAzureOpenAI.java @@ -2,8 +2,8 @@ import com.datastax.astra.client.DataAPIClient; import com.datastax.astra.client.collections.Collection; -import com.datastax.astra.client.collections.CollectionIdTypes; -import com.datastax.astra.client.collections.CollectionOptions; +import com.datastax.astra.client.collections.CollectionDefaultIdTypes; +import com.datastax.astra.client.collections.CollectionDefinition; import com.datastax.astra.client.collections.results.CollectionInsertManyResult; import com.datastax.astra.client.core.paging.FindIterable; import com.datastax.astra.client.collections.options.CollectionFindOptions; @@ -48,14 +48,13 @@ public static void main(String[] args) { Map params = new HashMap<>(); params.put("resourceName", RESOURCE_NAME); params.put("deploymentId", DEPLOYMENT_ID); - CollectionOptions.CollectionOptionsBuilder builder = CollectionOptions - .builder() + CollectionDefinition collectionDefinition = new CollectionDefinition() .vectorSimilarity(SimilarityMetric.COSINE) .vectorDimension(1536) - .defaultIdType(CollectionIdTypes.UUID) + .defaultId(CollectionDefaultIdTypes.UUID) .vectorize("azureOpenAI","text-embedding-ada-002", API_KEY_NAME,params); Collection collection = db - .createCollection("vectorize_test", builder.build()); + .createCollection("vectorize_test", collectionDefinition); collection.deleteAll(); CollectionInsertManyResult insertResult = collection.insertMany( diff --git a/examples/src/main/java/com/datastax/astra/genai/QuickStartHuggingFaceDedicated.java b/examples/src/main/java/com/datastax/astra/genai/QuickStartHuggingFaceDedicated.java index dd779157..1ede084e 100644 --- a/examples/src/main/java/com/datastax/astra/genai/QuickStartHuggingFaceDedicated.java +++ b/examples/src/main/java/com/datastax/astra/genai/QuickStartHuggingFaceDedicated.java @@ -2,12 +2,12 @@ import com.datastax.astra.client.DataAPIClient; import com.datastax.astra.client.collections.Collection; -import com.datastax.astra.client.collections.CollectionIdTypes; -import com.datastax.astra.client.collections.CollectionOptions; +import com.datastax.astra.client.collections.CollectionDefaultIdTypes; +import com.datastax.astra.client.collections.CollectionDefinition; +import com.datastax.astra.client.collections.documents.Document; +import com.datastax.astra.client.collections.options.CollectionFindOptions; import com.datastax.astra.client.collections.results.CollectionInsertManyResult; import com.datastax.astra.client.core.paging.FindIterable; -import com.datastax.astra.client.collections.options.CollectionFindOptions; -import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.core.query.Sort; import com.datastax.astra.client.core.vector.SimilarityMetric; import com.datastax.astra.client.databases.Database; @@ -48,14 +48,13 @@ public static void main(String[] args) { params.put("endpointName", ENDPOINT_NAME); params.put("regionName",REGION_NAME); params.put("cloudName", CLOUD_NAME); - CollectionOptions.CollectionOptionsBuilder builder = CollectionOptions - .builder() + CollectionDefinition collectionDefinition = new CollectionDefinition() .vectorSimilarity(SimilarityMetric.COSINE) .vectorDimension(MODEL_DIMENSIONS) - .defaultIdType(CollectionIdTypes.UUID) + .defaultId(CollectionDefaultIdTypes.UUID) .vectorize("huggingfaceDedicated","endpoint-defined-model", API_KEY_NAME,params); Collection collection = db - .createCollection("vectorize_test_hf_dedicate", builder.build()); + .createCollection("vectorize_test_hf_dedicate", collectionDefinition); collection.deleteAll(); CollectionInsertManyResult insertResult = collection.insertMany( diff --git a/examples/src/main/java/com/datastax/astra/genai/QuickStartNvidia.java b/examples/src/main/java/com/datastax/astra/genai/QuickStartNvidia.java index 8e2927f9..e6e7c9ed 100644 --- a/examples/src/main/java/com/datastax/astra/genai/QuickStartNvidia.java +++ b/examples/src/main/java/com/datastax/astra/genai/QuickStartNvidia.java @@ -2,12 +2,12 @@ import com.datastax.astra.client.DataAPIClient; import com.datastax.astra.client.collections.Collection; -import com.datastax.astra.client.collections.CollectionIdTypes; -import com.datastax.astra.client.collections.CollectionOptions; +import com.datastax.astra.client.collections.CollectionDefaultIdTypes; +import com.datastax.astra.client.collections.CollectionDefinition; +import com.datastax.astra.client.collections.documents.Document; +import com.datastax.astra.client.collections.options.CollectionFindOptions; import com.datastax.astra.client.collections.results.CollectionInsertManyResult; import com.datastax.astra.client.core.paging.FindIterable; -import com.datastax.astra.client.collections.options.CollectionFindOptions; -import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.core.query.Sort; import com.datastax.astra.client.core.vector.SimilarityMetric; import com.datastax.astra.client.databases.Database; @@ -36,13 +36,12 @@ public static void main(String[] args) { Database db = new DataAPIClient(ASTRA_DB_TOKEN).getDatabase(ASTRA_DB_URL); // 1/ Create a collection programmatically (if needed) - CollectionOptions.CollectionOptionsBuilder builder = CollectionOptions - .builder() + CollectionDefinition collectionDefinition = new CollectionDefinition() .vectorSimilarity(SimilarityMetric.COSINE) - .defaultIdType(CollectionIdTypes.UUID) + .defaultId(CollectionDefaultIdTypes.UUID) .vectorize("nvidia","NV-Embed-QA"); Collection collection = db - .createCollection("vectorize_test", builder.build()); + .createCollection("vectorize_test", collectionDefinition); collection.deleteAll(); CollectionInsertManyResult insertResult = collection.insertMany( diff --git a/examples/src/main/java/com/datastax/astra/genai/QuickStartOpenAI.java b/examples/src/main/java/com/datastax/astra/genai/QuickStartOpenAI.java index c7f45086..eb813381 100644 --- a/examples/src/main/java/com/datastax/astra/genai/QuickStartOpenAI.java +++ b/examples/src/main/java/com/datastax/astra/genai/QuickStartOpenAI.java @@ -2,12 +2,12 @@ import com.datastax.astra.client.DataAPIClient; import com.datastax.astra.client.collections.Collection; -import com.datastax.astra.client.collections.CollectionIdTypes; -import com.datastax.astra.client.collections.CollectionOptions; +import com.datastax.astra.client.collections.CollectionDefaultIdTypes; +import com.datastax.astra.client.collections.CollectionDefinition; +import com.datastax.astra.client.collections.documents.Document; +import com.datastax.astra.client.collections.options.CollectionFindOptions; import com.datastax.astra.client.collections.results.CollectionInsertManyResult; import com.datastax.astra.client.core.paging.FindIterable; -import com.datastax.astra.client.collections.options.CollectionFindOptions; -import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.core.query.Sort; import com.datastax.astra.client.core.vector.SimilarityMetric; import com.datastax.astra.client.databases.Database; @@ -46,13 +46,11 @@ public static void main(String[] args) { Map params = new HashMap<>(); params.put("organizationId", ORGANIZATION_ID); params.put("projectId", PROJECT_ID); - CollectionOptions.CollectionOptionsBuilder builder = CollectionOptions - .builder() + CollectionDefinition collectionDefinition = new CollectionDefinition() .vectorSimilarity(SimilarityMetric.COSINE) - .defaultIdType(CollectionIdTypes.UUID) + .defaultId(CollectionDefaultIdTypes.UUID) .vectorize("openai","text-embedding-ada-002", API_KEY_NAME,params); - Collection collection = db - .createCollection("vectorize_test", builder.build()); + Collection collection = db.createCollection("vectorize_test", collectionDefinition); collection.deleteAll(); CollectionInsertManyResult insertResult = collection.insertMany( diff --git a/langchain4j-astradb/src/main/java/com/datastax/astra/langchain4j/store/memory/AstraDbChatMemoryStore.java b/langchain4j-astradb/src/main/java/com/datastax/astra/langchain4j/store/memory/AstraDbChatMemoryStore.java index 9c987b79..ccdad58d 100644 --- a/langchain4j-astradb/src/main/java/com/datastax/astra/langchain4j/store/memory/AstraDbChatMemoryStore.java +++ b/langchain4j-astradb/src/main/java/com/datastax/astra/langchain4j/store/memory/AstraDbChatMemoryStore.java @@ -21,7 +21,7 @@ */ import com.datastax.astra.client.collections.Collection; -import com.datastax.astra.client.collections.CollectionOptions; +import com.datastax.astra.client.collections.CollectionDefinition; import com.datastax.astra.client.collections.options.CollectionFindOptions; import com.datastax.astra.client.databases.Database; import dev.langchain4j.data.message.ChatMessage; @@ -78,9 +78,9 @@ public AstraDbChatMemoryStore(Collection collection) { * client for existing active database */ public AstraDbChatMemoryStore(Database database) { - this(database.createCollection(DEFAULT_COLLECTION_NAME, CollectionOptions - .builder().indexingDeny(PROP_MESSAGE) // i do not need index on the message - .build(), AstraDbChatMessage.class)); + this(database.createCollection(DEFAULT_COLLECTION_NAME, + new CollectionDefinition().indexingDeny(PROP_MESSAGE), + AstraDbChatMessage.class)); } /** @@ -88,11 +88,8 @@ public AstraDbChatMemoryStore(Database database) { */ public void create() { if (!chatMemoryCollection.exists()) { - astraDatabase.createCollection( - chatMemoryCollection.getCollectionName(),CollectionOptions - .builder() - .indexingDeny(PROP_MESSAGE) - .build()); + astraDatabase.createCollection(chatMemoryCollection.getCollectionName(), + new CollectionDefinition().indexingDeny(PROP_MESSAGE)); } } diff --git a/langchain4j-astradb/src/test/java/dev/langchain4j/store/embedding/astradb/GettingStartedGuideVectorizedTestIT.java b/langchain4j-astradb/src/test/java/dev/langchain4j/store/embedding/astradb/GettingStartedGuideVectorizedTestIT.java index 2947c2b1..32485808 100644 --- a/langchain4j-astradb/src/test/java/dev/langchain4j/store/embedding/astradb/GettingStartedGuideVectorizedTestIT.java +++ b/langchain4j-astradb/src/test/java/dev/langchain4j/store/embedding/astradb/GettingStartedGuideVectorizedTestIT.java @@ -1,6 +1,6 @@ package dev.langchain4j.store.embedding.astradb; -import com.datastax.astra.client.collections.CollectionOptions; +import com.datastax.astra.client.collections.CollectionDefinition; import com.datastax.astra.client.databases.Database; import com.datastax.astra.client.core.vector.SimilarityMetric; import com.datastax.astra.langchain4j.Assistant; @@ -60,7 +60,7 @@ public static void initStoreForTests() { * the need of a embedding model. It is done at database level for you. */ embeddingStoreVectorizeNVidia = new AstraDbEmbeddingStore( - astraDatabase.createCollection("store_with_nvidia", CollectionOptions + astraDatabase.createCollection("store_with_nvidia", CollectionDefinition .builder() .vector(1024, SimilarityMetric.COSINE) .vectorize("nvidia", "NV-Embed-QA") diff --git a/tools/src/test/java/com/datastax/astra/samples/CsvLoaderWiki.java b/tools/src/test/java/com/datastax/astra/samples/CsvLoaderWiki.java index d736c47a..91e875bf 100644 --- a/tools/src/test/java/com/datastax/astra/samples/CsvLoaderWiki.java +++ b/tools/src/test/java/com/datastax/astra/samples/CsvLoaderWiki.java @@ -3,7 +3,7 @@ import com.datastax.astra.client.collections.Collection; import com.datastax.astra.client.DataAPIClient; import com.datastax.astra.client.databases.Database; -import com.datastax.astra.client.collections.CollectionOptions; +import com.datastax.astra.client.collections.CollectionDefinition; import com.datastax.astra.client.collections.documents.Document; import com.datastax.astra.client.core.vector.SimilarityMetric; import com.datastax.astra.tool.loader.csv.CsvLoader; @@ -28,7 +28,7 @@ public static void main(String[] args) throws Exception { Collection wiki = wikiDataDb.createCollection( "wiki", // Create collection with a Service in vectorize - CollectionOptions.builder() + CollectionDefinition.builder() .vectorDimension(768) // found from the CSV .vectorSimilarity(SimilarityMetric.COSINE) .build()); pFad - Phonifier reborn

    Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

    Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


    Alternative Proxies:

    Alternative Proxy

    pFad Proxy

    pFad v3 Proxy

    pFad v4 Proxy