blob: 90762767d28eb9e4966fe9be00667dfde2eff7fb [file] [view]
# Queries
The Mapper generates the CQL queries and adapts the results into objects according to the [defined
mappings](../defining-mappings).
*Note that throughout the Mapper documentation the [killrvideo schema][killrvideo] is used.*
## Retrieval
The are three methods in the `ModelMapper` that are used to retrieve objects from the database:
- `find()`: filters by one or more primary keys and returns the `Result` that is an iterable of objects.
- `get()`: Gets one document matching the provided filter or `null` when not found. Note that all partition and
clustering keys must be defined in order to use this method.
- `findAll()`: selects all the objects returns the `Result` that is an iterable of objects. This is only recommended
to be used for tables with a limited amount of results. Otherwise, breaking up the token ranges on the client side
would be better.
When a model is mapped to multiple tables or views, the mapper will select the table that matches the primary keys
and the fields provided.
Additionally, the retrieval methods support using relational operators, setting multiple conditions on the same
field, setting the order and defining the specific fields. This operator and clauses are translated and applied
on the server side, no client-side filtering is performed by the Mapper.
### Usage examples
#### Get a single object
```javascript
const video = await videoMapper.get({ videoId });
```
#### Get an iterable of objects
Get all videos posted by a user
```javascript
const result = await videoMapper.find({ userId });
```
#### Find objects matching a relational operator applied on a field
Get videos from a user since a specific date
```javascript
const result = await videoMapper.find({ userId, addedDate: q.gt(myDate) });
```
`q.gt()` represents the "greater than" operator (`>`), you can access all operators in `q` under the `mapping` module
```javascript
const q = cassandra.mapping.q;
```
#### Get objects using multiple conditions on the same field
Get videos from a user between two dates.
```javascript
const result = await videoMapper.find({ userId, addedDate: q.and(q.gte(beginDate), q.lt(endDate)) });
```
#### Get few selected fields of the objects
Get only name and description of the videos
```javascript
const result = await videoMapper.find({ userId }, { fields: ['name', 'description' ]});
```
#### Get objects with a specific order
Get all videos posted by a user sorted by added date in descending order.
```javascript
const result = await videoMapper.find({ userId }, { orderBy: { 'addedDate': 'desc' }});
```
## Insert
Use the `insert()` method on a `ModelMapper` instance to *upsert* a new object.
When a model is mapped to multiple tables, it will insert a row in each table when all the primary keys are specified
grouped in a logged batch (either all or none of the insert operations will succeed).
Additionally, `insert()` supports conditional clause for [lightweight transactions (CAS)][lwt] that allows to
insert only if the row doesn't exist. Please note that using IF conditions will incur a non-negligible performance
cost on the server-side so this should be used sparingly.
### Usage examples
#### Insert a single object into one or more tables
Insert a video
```javascript
await videoMapper.insert({ videoId, name, addedDate, userId, description });
```
#### Insert few selected fields of an object
Insert only the id, the name and the added date, regardless of the other properties specified in the object.
```javascript
await videoMapper.insert(video, { fields: ['videoId', 'name', 'description'] });
```
#### Insert an object if it doesn't exist
Insert a video when there isn't a video with the same id.
```javascript
await videoMapper.insert({ videoId, name, description }, { ifNotExists: true });
```
## Update
Use the `update()` method on a `ModelMapper` instance to *upsert* a new object.
When a model is mapped to multiple tables, it will update a row in each table when all the primary keys are specified
grouped in a logged batch (either all or none of the update operations will succeed).
Additionally, `update()` supports conditional clause for [lightweight transactions (CAS)][lwt] that allows to
specify the condition that has to be met for the update to occur. Please note that using IF conditions will incur a
non-negligible performance cost on the server-side so this should be used sparingly.
### Usage examples
#### Update a single object into one or more tables
Update a video
```javascript
await videoMapper.update({ videoId, name, addedDate, userId, description });
```
#### Update few selected fields of an object
Update only the name and the added date, regardless of the other properties specified in the object.
```javascript
await videoMapper.update(video, { fields: ['videoId', 'name', 'description'] });
```
#### Update an object using a [conditional statement][lwt]
Update a video when the existing name contains a certain value.
```javascript
await videoMapper.update({ videoId, name, description }, { when: { name: 'original name' } });
```
## Delete
Use the `remove()` method on a `ModelMapper` instance to delete an object.
When a model is mapped to multiple tables, it will delete the row on each table when all the primary keys are specified
grouped in a logged batch (either all or none of the delete operations will succeed).
Additionally, `remove()` supports conditional clause for [lightweight transactions (CAS)][lwt] that allows to
specify the condition that has to be met for the delete to occur. Please note that using IF conditions will incur a
non-negligible performance cost on the server-side so this should be used sparingly.
### Usage examples
#### Delete a single object into one or more tables
Delete a video
```javascript
await videoMapper.remove({ videoId });
```
#### Delete an object using a [conditional statement][lwt]
Delete a video when the existing name contains a certain value.
```javascript
await videoMapper.remove({ videoId }, { when: { name: 'original name' } });
```
## Group mutations in a batch
You can batch multiple operations for both single partition and multiple partitions when [atomicity][atomicity] and
[isolation][isolation] is a requirement for a group of changes.
You can use the field `batching` of a `ModelMapper` to create each item of a batch and the `batch()` method of the
`Mapper` to submit the request.
### Usage example
#### Update a group of objects
Update two videos from a user in a batch.
```javascript
const changes = [
videoMapper.batching.update({ userId, videoId1, name1, addedDate1 }),
videoMapper.batching.update({ userId, videoId2, name2, addedDate2 })
];
// Execute the batch
await mapper.batch(changes);
```
## Custom queries
The Mapper supports bypassing query generation, allowing you to specify the CQL query. It will execute the query and
map the results according to the [mapping configuration](../defining-mappings).
Use `mapWithQuery()` method to create your own `ModelMapper` execution method.
### Usage example
```javascript
// Write your own query using query markers for parameters
const query = 'SELECT COUNT(videoid) as video_count FROM user_videos WHERE userid = ? GROUP BY userid';
// Create a new ModelMapper method with your own query
// and a function to extract the parameters from an object
videoMapper.getCount = videoMapper.mapWithQuery(query, video => [ video.userId ]);
```
Once you created a new `ModelMapper` method, you can use it in your application.
```javascript
const result = await videoMapper.getCount({ userId });
console.log(result.first().videoCount);
```
The result will be an instance of `Result` with the columns mapped to the property name according to the
configuration, similar to other `ModelMapper` methods.
Note that you must use query markers to represent parameters in the query, you should avoid hard-coding the parameter
values in the query.
### Execution options
The last parameter of the `ModelMapper` execution methods is a string representing the [Execution
Profile](../../execution-profiles). Execution profiles allows you to define the execution options once and reuse them
across different execution invocations.
As stated in the [Execution Profiles documentation](../../execution-profiles), you should define the profiles when
creating the `Client` instance.
```javascript
const client = new Client({
contactPoints,
localDataCenter,
profiles: [
new ExecutionProfile('default', {
consistency: consistency.one,
readTimeout: 10000
}),
new ExecutionProfile('oltp-sample', {
consistency: consistency.localQuorum
})
]
});
```
Then, you can use those execution profile names when executing a query with the Mapper, you can look at the methods
signature for more info.
### Examples
```javascript
videoMapper.get({ videoid }, 'oltp-sample');
videoMapper.find({ userId }, 'oltp-sample');
// After the document info
videoMapper.find({ userId }, { fields: ['name', 'description'] }, 'oltp-sample');
videoMapper.update(video, 'oltp-sample');
videoMapper.insert(video, { ifNotExists: true }, 'oltp-sample');
// Use default execution profile
userMapper.get({ videoId });
// Use another execution profile
userMapper.get({ videoId }, 'another-execution-profile');
```
---
You can look at the [documentation on defining mappings](../defining-mappings) to understand how tables and columns
are mapped into object and properties.
[killrvideo]: https://github.com/pmcfadin/killrvideo-sample-schema
[lwt]: https://docs.datastax.com/en/cql/3.3/cql/cql_using/useInsertLWT.html
[atomicity]: https://en.wikipedia.org/wiki/Atomicity_(database_systems)
[isolation]: https://en.wikipedia.org/wiki/Isolation_(database_systems)