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.
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.
const video = await videoMapper.get({ videoId });
Get all videos posted by a user
const result = await videoMapper.find({ userId });
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 videos from a user between two dates.
const result = await videoMapper.find({ userId, addedDate: q.and(q.gte(beginDate), q.lt(endDate)) });
Get only name and description of the videos
const result = await videoMapper.find({ userId }, { fields: ['name', 'description' ]});
Get all videos posted by a user sorted by added date in descending order.
const result = await videoMapper.find({ userId }, { orderBy: { 'addedDate': 'desc' }});
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.
Insert a video
await videoMapper.insert({ videoId, name, addedDate, userId, description });
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 a video when there isn't a video with the same id.
await videoMapper.insert({ videoId, name, description }, { ifNotExists: true });
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.
Update a video
await videoMapper.update({ videoId, name, addedDate, userId, description });
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 a video when the existing name contains a certain value.
await videoMapper.update({ videoId, name, description }, { when: { name: 'original name' } });
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.
Delete a video
await videoMapper.remove({ videoId });
Delete a video when the existing name contains a certain value.
await videoMapper.remove({ videoId }, { when: { name: 'original name' } });
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.
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);
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.
// 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.
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.
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.