Queries
The Mapper generates the CQL queries and adapts the results into objects according to the defined mappings.
Note that throughout the Mapper documentation the killrvideo schema 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 theResult
that is an iterable of objects. -
get()
: Gets one document matching the provided filter ornull
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 theResult
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
const video = await videoMapper.get({ videoId });
Get an iterable of objects
Get all videos posted by a user
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
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
const q = cassandra.mapping.q;
Get objects using multiple conditions on the same field
Get videos from a user between two dates.
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
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.
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) 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
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.
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.
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) 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
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.
await videoMapper.update(video, { fields: ['videoId', 'name', 'description'] });
Update an object using a conditional statement
Update a video when the existing name contains a certain value.
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) 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
await videoMapper.remove({ videoId });
Delete an object using a conditional statement
Delete a video when the existing name contains a certain value.
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 and 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.
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.
Use mapWithQuery()
method to create your own ModelMapper
execution method.
Usage example
// 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.
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 allows you to define the execution options once and reuse them
across different execution invocations.
As stated in the Execution Profiles documentation, you should define the profiles when
creating the Client
instance.
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
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 to understand how tables and columns are mapped into object and properties.