Below is a list of APIs supported by Achilles Manager:
This API performs simple INSERT/DELETE operations on the entity. It exposes the following methods:
findById(...): find an entity providing the complete primary keyinsert(ENTITY instance): insert an instance of the entityinsertStatic(ENTITY instance): insert only static columns from the entityupdate(ENTITY instance): update Cassandra table with non null fields extracted from the entityupdateStatic(ENTITY instance): update Cassandra table with non null static fields extracted from the entitydelete(ENTITY instance): delete the entitydeleteById(...): delete the entity with the provided complete primary keydeleteByPartitionKeys(...): delete the entity with the provided complete partition key component(s)For each of those operations, you can specify runtime values for
The ResultSet async listener is just a function Function<ResultSet, ResultSet> to spy on the returned rawcom.datastax.driver.core.ResultSet object. You are not allowed to call methods that will consume this resultset
like one(), all(), iterator(), fetchMoreResults()
The Row async listener is a function Function<Row, Row> and lets you manipulate the rawcom.datastax.driver.core.ResultSet object. This time, it is possible to read values from the row because it is
already fetched
LightWeight Transaction result listener should implement the interface LWTResultListener
The DSL API lets you create type-safe SELECT, UPDATE and DELETE queries for your entity.
The Select DSL look like:
User user = manager
.dsl()
.select()
.id()
.firstname()
.lastname()
.fromBaseTable()
.where()
.userId().Eq(id)
.getOne();
The DSL exposes the following final methods:
ENTITY getOne(): get the first found entityCompletableFuture<ENTITY> getOneAsync(): get the first found entity asynchronouslyTuple2<ENTITY, ExecutionInfo> getOneWithStats(): get the first found entity with execution infoCompletableFuture<Tuple2<ENTITY, ExecutionInfo>> getOneAsyncWithStats(): get the first found entity with execution info asynchronously
List<ENTITY> getList(): get found entitiesCompletableFuture<List<ENTITY>> getListAsync(): get found entities asynchronouslyTuple2<List<ENTITY>, ExecutionInfo> getListWithStats(): get found entities with execution infoCompletableFuture<Tuple2<List<ENTITY>, ExecutionInfo>> getListAsyncWithStats(): get found entities with execution info asynchronously
Iterator<ENTITY> iterator(): return an iterator for found entities
TypedMap getTypedMap(): get the first found CQL row as an instance of TypedMapTuple2<TypedMap, ExecutionInfo> getTypedMapWithStatus(): get the first found CQL row with execution info as an instance of TypedMapCompletableFuture<TypedMap> getTypedMapAsync(): get the first found CQL row asynchronously as an instance of TypedMapCompletableFuture<Tuple2<TypedMap, ExecutionInfo>> getTypedMapAsyncWithStats(): get the first found CQL row with execution info asynchronously an instance of TypedMap
List<TypedMap> getTypedMaps(): get the found CQL rows as a list of TypedMapList<Tuple2<TypedMap, ExecutionInfo>> getTypedMapsWithStats(): get the found CQL rows with execution info as a list of TypedMapCompletableFuture<List<TypedMap>> getTypedMapsAsync(): get the found CQL rows asynchronously as a list of TypedMapCompletableFuture<Tuple2<List<TypedMap>, ExecutionInfo>> getTypedMapsAsyncWithStats(): get the found CQL rows with execution info asynchronously as a list of TypedMap
Iterator<TypedMap> typedMapIterator(): return an iterator for found CQL rows as TypedMap
It is not mandatory to provide values for the WHERE clause, you can call the methodwithout_WHERE_Clause() to retrieve values
Please note that as soon as you invoke a function call (with .function(), Achilles can only return the results as TypedMap and not as entities) because it cannot map the result of the function call into a field of your entity.
If you need to map the result of a function call into a field of your entity, use the @Computed annotation instead.
Similar to the CRUD API, you can define runtime values for consistency levels, retry policy, ...
The Update DSL look like:
manager
.dsl()
.update()
.fromBaseTable()
.firstname().Set("new firstname")
.where()
.userId().Eq(id)
.execute();
The DSL exposes the following final methods:
execute(): execute the updateCompletableFuture<Empty> executeAsync(): execute the update asynchronouslyExecutionInfo executeWithStats(): execute the update and return the execution infoCompletableFuture<ExecutionInfo>> executeAsyncWithStats(): execute the update and return the execution info asynchronouslySimilar to the CRUD API, you can define runtime values for consistency levels, retry policy, ...
The Delete DSL look like:
manager
.dsl()
.delete()
.firstname()
.lastname()
.fromBaseTable()
.where()
.userId().Eq(id)
.execute();
The DSL exposes the following final methods:
execute(): execute the deleteCompletableFuture<Empty> executeAsync(): execute the delete asynchronouslyExecutionInfo executeWithStats(): execute the delete and return the execution infoCompletableFuture<ExecutionInfo>> executeAsyncWithStats(): execute the delete and return the execution info asynchronouslySimilar to the CRUD API, you can define runtime values for consistency levels, retry policy, ...
Achilles does support Cassandra native secondary index, SASI or DSE Search.
Please annotate your fields with @Index, @SASI or @DSE_Search
For native secondary index, only equality ( = ) predicates are allowed. The API is quite simple:
@Table
public class User {
@PartitionKey
private UUID user_id;
...
@Index
@Column
private int age;
...
}
manager.
.indexed()
.select()
.allColumns_FromBaseTable()
.where()
.age().Eq(32)
....
You can restrict the search further by providing partition keys or clustering columns.
manager.
.indexed()
.select()
.allColumns_FromBaseTable()
.where()
.age().Eq(32)
.user_id().Eq(...)
For SASI , equality and range queries (<, <=, >=, >) are possible for all data types other than text/ascci. For text/ascii types, you can
also perform prefix/suffix/substring or full text search, provided you created the index with the appropriate options.
Example:
@Table
public class User {
@PartitionKey
private UUID user_id;
...
@SASI(indexMode = IndexMode.CONTAINS, analyzed = true, analyzerClass = Analyzer.NON_TOKENIZING_ANALYZER, normalization = Normalization.LOWERCASE)
@Column
private String name;
@SASI(indexMode = IndexMode.PREFIX, analyzed = false)
@Column
private String country:
@SASI
@Column
private int age;
...
}
manager.
.indexed()
.select()
.allColumns_FromBaseTable()
.where()
.name().Contains("John")
.age().Gte_And_Lte(25, 35)
.country().Eq("USA")
....
Again, similar to native secondary index, you can restrict the search by providing partition keys or/and clustering columns
The @SASI annotation exposes the following attributes:
| Attribute | Possible values | Description | Default value |
|---|---|---|---|
| indexMode | IndexMode.PREFIX, IndexMode.CONTAINS or IndexMode.SPARSE |
|
IndexMode.PREFIX |
| analyzed | true or false |
Indicates whether the data should be analyzed or not.
Setting 'analyzed' = true is only valid for text/ascii data types.
Setting 'analyzed' = true is mandatory if 'analyzerClass' is set to:
|
false |
| analyzerClass | Analyzer.NO_OP_ANALYZER, Analyzer.NON_TOKENIZING_ANALYZER or Analyzer.STANDARD_ANALYZER |
Defines the analyzer class. Available values are:
|
Analyzer.NO_OP_ANALYZER |
| maxCompactionFlushMemoryInMb | any integer | Maximum size of SASI data to keep in memory during compaction process. If there are more than 'maxCompactionFlushMemoryInMb' worth of index data, SASI will flush them on temporary files on disk before merging all the temp files into a single one. Of course it will add up to compaction duration. No free lunch, sorry | 1024 (e.g. 1Gb) |
| normalization | Normalization.NONE, Normalization.LOWERCASE or Normalization.UPPERCASE |
Defines the normalization to be applied to the input. Available values are:
|
Normalization.NONE |
| locale | any valid locale string | Defines the locale for tokenization. This attribute is only used when 'analyzerClass' == STANDARD_ANALYZER otherwise it is ignored | "en" |
| enableStemming | true or false | Enable stemming of input text. This attribute is only used when 'analyzerClass' == STANDARD_ANALYZER | false |
| skipStopWords | true or false | Enable stemming of input text. This attribute is only used when 'analyzerClass' == STANDARD_ANALYZER | false |
The following combinations are allowed for index options:
| Data type | Index Mode | Analyzer Class | Possible option values |
|---|---|---|---|
| Text or Ascii | PREFIX or CONTAINS | NoOpAnalyzer |
|
| Text or Ascii | PREFIX or CONTAINS | NonTokenizingAnalyzer |
|
| Text or Ascii | PREFIX or CONTAINS | StandardAnalyzer |
|
| Non Text | PREFIX OR SPARSE | NoOpAnalyzer |
|
DSE Search is only available for users having Datastax Enterprise edition. Please use the @DSE_Search
on the fields of your entity to let Achilles generate appropriate search methods.
Please note that this annotation is only valid for Cassandra versions:
Important: Achilles will NOT attempt to create the index for DSE Search even if doForceSchemaCreation() is set to true.
You should create the index in DSE yourself using dsetool create core .... (please refer to DSE documentation)
Nevertheless, Achilles will check the existence of DSE Search index at runtime and will complain if it cannot be found.
Also, please note that currently OR clause is not yet supported by Achilles. Please use ...where().rawSolrQuery(String rawSolrQuery) to search using OR clauses
Additionally, you need not map the solr_query in your Java bean. Just put the @DSE_Search annotation on the fields you want to search and Achilles will generate the appropriate DSL source code.
This annotation exposes the fullTextSearchEnabled attribute which is only useful on a text/ascii field/column.
If enabled, Achilles will generate:
StartWith(String prefix)EndWith(String suffix)Contains(String substring)methods in addition of the standard Eq(String term) and RawPredicate(String rawSolrPredicate) methods.
Example:
@Table
public class User {
@PartitionKey
private UUID user_id;
...
@DSE_Search(fullTextSearchEnabled = true)
@Column
private String name;
@DSE_Search
@Column
private String country:
@DSE_Search
@Column
private int age;
...
}
//Standard usage
manager.
.indexed()
.select()
.allColumns_FromBaseTable()
.where()
.name().Contains("John")
.age().Gte_And_Lte(25, 35)
.country().Eq("USA")
....
//Raw Predicate
manager.
.indexed()
.select()
.allColumns_FromBaseTable()
.where()
.name().RawPredicate("*Jo??y*")
....
//Raw Solr query with OR predicate
manager.
.indexed()
.select()
.allColumns_FromBaseTable()
.where()
.rawSolrQuery("(name:*John* OR login:jdoe*) AND age:[25 TO 35]")
....
The RAW queries API lets you inject any instance of:
com.datastax.driver.core.RegularStatementcom.datastax.driver.core.PreparedStatementcom.datastax.driver.core.BoundStatementand execute the query for you.
The Typed Query API execute the statement and map the returned com.datastax.driver.core.Row object(s) back to entity
instance. Thus this API can only be used for SELECT statements.
Statement statement = ...;
User instance = userManager
.raw()
.typedQueryForSelect(statement)
.getOne();
This API exposes the following final methods:
ENTITY getOne(): get the first found entityCompletableFuture<ENTITY> getOneAsync(): get the first found entity asynchronouslyTuple2<ENTITY, ExecutionInfo> getOneWithStats(): get the first found entity with execution infoCompletableFuture<Tuple2<ENTITY, ExecutionInfo>> getOneAsyncWithStats(): get the first found entity with execution info asynchronously
List<ENTITY> getList(): get found entitiesCompletableFuture<List<ENTITY>> getListAsync(): get found entities asynchronouslyTuple2<List<ENTITY>, ExecutionInfo> getListWithStats(): get found entities with execution infoCompletableFuture<Tuple2<List<ENTITY>, ExecutionInfo>> getListAsyncWithStats(): get found entities with execution info asynchronously
Iterator<ENTITY> iterator(): return an iterator for found entities
Similar to the CRUD API, you can define runtime values for consistency levels, retry policy, ...
The Native Query API execute the statement and map the returned com.datastax.driver.core.Row object(s) back to
instance(s) of TypedMap.
Statement statement = ...;
final TypedMap found = userManager
.raw()
.nativeQuery(statement)
.getOne();
This API exposes the following final methods:
TypedMap getOne(): get the first found rowCompletableFuture<TypedMap> getOneAsync(): get the first found row asynchronouslyTuple2<TypedMap, ExecutionInfo> getOneWithStats(): get the first found row with execution infoCompletableFuture<Tuple2<TypedMap, ExecutionInfo>> getOneAsyncWithStats(): get the first found row with execution info asynchronously
List<TypedMap> getList(): get found entitiesCompletableFuture<List<TypedMap>> getListAsync(): get found rows asynchronouslyTuple2<List<TypedMap>, ExecutionInfo> getListWithStats(): get found rows with execution infoCompletableFuture<Tuple2<List<TypedMap>, ExecutionInfo>> getListAsyncWithStats(): get found rows with execution info asynchronously
Iterator<ENTITY> iterator(): return an iterator for found rows
execute(): execute the statementCompletableFuture<Empty> executeAsync(): execute the statement asynchronouslyExecutionInfo executeWithStats(): execute the statement and return the execution infoCompletableFuture<ExecutionInfo>> executeAsyncWithStats(): execute the statement and return the execution info asynchronously
Similar to the CRUD API, you can define runtime values for consistency levels, retry policy, ...
In some multi-tenant environment, the keyspace/table name cannot be known ahead of time but only during runtime.
For this purpose, Achilles introduces a SchemaNameProvider interface to let people bind keyspace/table names
dynamically at runtime.
This provider can be used with CRUD API and DSL API:
final SchemaNameProvider dynamicProvider = ...;
userManager
.crud()
.withSchemaNameProvider(dynamicProvider)
...
.execute();
userManager
.dsl()
.select()
...
.from(dynamicProvider)
.where()
...
userManager
.dsl()
.update()
.from(dynamicProvider)
...
.where()
...
userManager
.dsl()
.delete()
...
.from(dynamicProvider)
...
.where()
...
The Manager instance also exposes the following methods to generate raw com.datastax.driver.core.BoundStatement
and bound values. They are available for all both CRUD API and DSL API
BoundStatement generateAndGetBoundStatement():
com.datastax.driver.core.BoundStatement instanceString getStatementAsString(): self-explanatoryList<Object> getBoundValues(): extract raw Java values from entity or from the given APIList<Object> getEncodedBoundValues(): similar as getBoundValues() but encode the values using the Codec System
Apart from the 3 API (CRUD, DSL and Query), the Manager class also exposes some utility methods:
public ENTITY mapFromRow(Row row): map a given instance of com.datastax.driver.core.Rowpublic Session getNativeSession(): return the native com.datastax.driver.core.Session object used by thisManagerpublic Cluster getNativeCluster(): return the native com.datastax.driver.core.Cluster object used by thisManager