| .. Licensed to the Apache Software Foundation (ASF) under one |
| .. or more contributor license agreements. See the NOTICE file |
| .. distributed with this work for additional information |
| .. regarding copyright ownership. The ASF licenses this file |
| .. to you under the Apache License, Version 2.0 (the |
| .. "License"); 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. |
| |
| .. highlight:: cql |
| |
| .. _data-manipulation: |
| |
| Data Manipulation |
| ----------------- |
| |
| This section describes the statements supported by CQL to insert, update, delete and query data. |
| |
| .. _select-statement: |
| |
| SELECT |
| ^^^^^^ |
| |
| Querying data from data is done using a ``SELECT`` statement: |
| |
| .. productionlist:: |
| select_statement: SELECT [ JSON | DISTINCT ] ( `select_clause` | '*' ) |
| : FROM `table_name` |
| : [ WHERE `where_clause` ] |
| : [ GROUP BY `group_by_clause` ] |
| : [ ORDER BY `ordering_clause` ] |
| : [ PER PARTITION LIMIT (`integer` | `bind_marker`) ] |
| : [ LIMIT (`integer` | `bind_marker`) ] |
| : [ ALLOW FILTERING ] |
| select_clause: `selector` [ AS `identifier` ] ( ',' `selector` [ AS `identifier` ] ) |
| selector: `column_name` |
| : | `term` |
| : | CAST '(' `selector` AS `cql_type` ')' |
| : | `function_name` '(' [ `selector` ( ',' `selector` )* ] ')' |
| : | COUNT '(' '*' ')' |
| where_clause: `relation` ( AND `relation` )* |
| relation: `column_name` `operator` `term` |
| : '(' `column_name` ( ',' `column_name` )* ')' `operator` `tuple_literal` |
| : TOKEN '(' `column_name` ( ',' `column_name` )* ')' `operator` `term` |
| operator: '=' | '<' | '>' | '<=' | '>=' | '!=' | IN | CONTAINS | CONTAINS KEY |
| group_by_clause: `column_name` ( ',' `column_name` )* |
| ordering_clause: `column_name` [ ASC | DESC ] ( ',' `column_name` [ ASC | DESC ] )* |
| |
| For instance:: |
| |
| SELECT name, occupation FROM users WHERE userid IN (199, 200, 207); |
| SELECT JSON name, occupation FROM users WHERE userid = 199; |
| SELECT name AS user_name, occupation AS user_occupation FROM users; |
| |
| SELECT time, value |
| FROM events |
| WHERE event_type = 'myEvent' |
| AND time > '2011-02-03' |
| AND time <= '2012-01-01' |
| |
| SELECT COUNT (*) AS user_count FROM users; |
| |
| The ``SELECT`` statements reads one or more columns for one or more rows in a table. It returns a result-set of the rows |
| matching the request, where each row contains the values for the selection corresponding to the query. Additionally, |
| :ref:`functions <cql-functions>` including :ref:`aggregation <aggregate-functions>` ones can be applied to the result. |
| |
| A ``SELECT`` statement contains at least a :ref:`selection clause <selection-clause>` and the name of the table on which |
| the selection is on (note that CQL does **not** joins or sub-queries and thus a select statement only apply to a single |
| table). In most case, a select will also have a :ref:`where clause <where-clause>` and it can optionally have additional |
| clauses to :ref:`order <ordering-clause>` or :ref:`limit <limit-clause>` the results. Lastly, :ref:`queries that require |
| filtering <allow-filtering>` can be allowed if the ``ALLOW FILTERING`` flag is provided. |
| |
| .. _selection-clause: |
| |
| Selection clause |
| ~~~~~~~~~~~~~~~~ |
| |
| The :token:`select_clause` determines which columns needs to be queried and returned in the result-set, as well as any |
| transformation to apply to this result before returning. It consists of a comma-separated list of *selectors* or, |
| alternatively, of the wildcard character (``*``) to select all the columns defined in the table. |
| |
| Selectors |
| ````````` |
| |
| A :token:`selector` can be one of: |
| |
| - A column name of the table selected, to retrieve the values for that column. |
| - A term, which is usually used nested inside other selectors like functions (if a term is selected directly, then the |
| corresponding column of the result-set will simply have the value of this term for every row returned). |
| - A casting, which allows to convert a nested selector to a (compatible) type. |
| - A function call, where the arguments are selector themselves. See the section on :ref:`functions <cql-functions>` for |
| more details. |
| - The special call ``COUNT(*)`` to the :ref:`COUNT function <count-function>`, which counts all non-null results. |
| |
| Aliases |
| ``````` |
| |
| Every *top-level* selector can also be aliased (using `AS`). If so, the name of the corresponding column in the result |
| set will be that of the alias. For instance:: |
| |
| // Without alias |
| SELECT intAsBlob(4) FROM t; |
| |
| // intAsBlob(4) |
| // -------------- |
| // 0x00000004 |
| |
| // With alias |
| SELECT intAsBlob(4) AS four FROM t; |
| |
| // four |
| // ------------ |
| // 0x00000004 |
| |
| .. note:: Currently, aliases aren't recognized anywhere else in the statement where they are used (not in the ``WHERE`` |
| clause, not in the ``ORDER BY`` clause, ...). You must use the orignal column name instead. |
| |
| |
| ``WRITETIME`` and ``TTL`` function |
| ``````````````````````````````````` |
| |
| Selection supports two special functions (that aren't allowed anywhere else): ``WRITETIME`` and ``TTL``. Both function |
| take only one argument and that argument *must* be a column name (so for instance ``TTL(3)`` is invalid). |
| |
| Those functions allow to retrieve meta-information that are stored internally for each column, namely: |
| |
| - the timestamp of the value of the column for ``WRITETIME``. |
| - the remaining time to live (in seconds) for the value of the column if it set to expire (and ``null`` otherwise). |
| |
| .. _where-clause: |
| |
| The ``WHERE`` clause |
| ~~~~~~~~~~~~~~~~~~~~ |
| |
| The ``WHERE`` clause specifies which rows must be queried. It is composed of relations on the columns that are part of |
| the ``PRIMARY KEY`` and/or have a `secondary index <#createIndexStmt>`__ defined on them. |
| |
| Not all relations are allowed in a query. For instance, non-equal relations (where ``IN`` is considered as an equal |
| relation) on a partition key are not supported (but see the use of the ``TOKEN`` method below to do non-equal queries on |
| the partition key). Moreover, for a given partition key, the clustering columns induce an ordering of rows and relations |
| on them is restricted to the relations that allow to select a **contiguous** (for the ordering) set of rows. For |
| instance, given:: |
| |
| CREATE TABLE posts ( |
| userid text, |
| blog_title text, |
| posted_at timestamp, |
| entry_title text, |
| content text, |
| category int, |
| PRIMARY KEY (userid, blog_title, posted_at) |
| ) |
| |
| The following query is allowed:: |
| |
| SELECT entry_title, content FROM posts |
| WHERE userid = 'john doe' |
| AND blog_title='John''s Blog' |
| AND posted_at >= '2012-01-01' AND posted_at < '2012-01-31' |
| |
| But the following one is not, as it does not select a contiguous set of rows (and we suppose no secondary indexes are |
| set):: |
| |
| // Needs a blog_title to be set to select ranges of posted_at |
| SELECT entry_title, content FROM posts |
| WHERE userid = 'john doe' |
| AND posted_at >= '2012-01-01' AND posted_at < '2012-01-31' |
| |
| When specifying relations, the ``TOKEN`` function can be used on the ``PARTITION KEY`` column to query. In that case, |
| rows will be selected based on the token of their ``PARTITION_KEY`` rather than on the value. Note that the token of a |
| key depends on the partitioner in use, and that in particular the RandomPartitioner won't yield a meaningful order. Also |
| note that ordering partitioners always order token values by bytes (so even if the partition key is of type int, |
| ``token(-1) > token(0)`` in particular). Example:: |
| |
| SELECT * FROM posts |
| WHERE token(userid) > token('tom') AND token(userid) < token('bob') |
| |
| Moreover, the ``IN`` relation is only allowed on the last column of the partition key and on the last column of the full |
| primary key. |
| |
| It is also possible to “group” ``CLUSTERING COLUMNS`` together in a relation using the tuple notation. For instance:: |
| |
| SELECT * FROM posts |
| WHERE userid = 'john doe' |
| AND (blog_title, posted_at) > ('John''s Blog', '2012-01-01') |
| |
| will request all rows that sorts after the one having “John's Blog” as ``blog_tile`` and '2012-01-01' for ``posted_at`` |
| in the clustering order. In particular, rows having a ``post_at <= '2012-01-01'`` will be returned as long as their |
| ``blog_title > 'John''s Blog'``, which would not be the case for:: |
| |
| SELECT * FROM posts |
| WHERE userid = 'john doe' |
| AND blog_title > 'John''s Blog' |
| AND posted_at > '2012-01-01' |
| |
| The tuple notation may also be used for ``IN`` clauses on clustering columns:: |
| |
| SELECT * FROM posts |
| WHERE userid = 'john doe' |
| AND (blog_title, posted_at) IN (('John''s Blog', '2012-01-01'), ('Extreme Chess', '2014-06-01')) |
| |
| The ``CONTAINS`` operator may only be used on collection columns (lists, sets, and maps). In the case of maps, |
| ``CONTAINS`` applies to the map values. The ``CONTAINS KEY`` operator may only be used on map columns and applies to the |
| map keys. |
| |
| .. _group-by-clause: |
| |
| Grouping results |
| ~~~~~~~~~~~~~~~~ |
| |
| The ``GROUP BY`` option allows to condense into a single row all selected rows that share the same values for a set |
| of columns. |
| |
| Using the ``GROUP BY`` option, it is only possible to group rows at the partition key level or at a clustering column |
| level. By consequence, the ``GROUP BY`` option only accept as arguments primary key column names in the primary key |
| order. If a primary key column is restricted by an equality restriction it is not required to be present in the |
| ``GROUP BY`` clause. |
| |
| Aggregate functions will produce a separate value for each group. If no ``GROUP BY`` clause is specified, |
| aggregates functions will produce a single value for all the rows. |
| |
| If a column is selected without an aggregate function, in a statement with a ``GROUP BY``, the first value encounter |
| in each group will be returned. |
| |
| .. _ordering-clause: |
| |
| Ordering results |
| ~~~~~~~~~~~~~~~~ |
| |
| The ``ORDER BY`` clause allows to select the order of the returned results. It takes as argument a list of column names |
| along with the order for the column (``ASC`` for ascendant and ``DESC`` for descendant, omitting the order being |
| equivalent to ``ASC``). Currently the possible orderings are limited by the :ref:`clustering order <clustering-order>` |
| defined on the table: |
| |
| - if the table has been defined without any specific ``CLUSTERING ORDER``, then then allowed orderings are the order |
| induced by the clustering columns and the reverse of that one. |
| - otherwise, the orderings allowed are the order of the ``CLUSTERING ORDER`` option and the reversed one. |
| |
| .. _limit-clause: |
| |
| Limiting results |
| ~~~~~~~~~~~~~~~~ |
| |
| The ``LIMIT`` option to a ``SELECT`` statement limits the number of rows returned by a query, while the ``PER PARTITION |
| LIMIT`` option limits the number of rows returned for a given partition by the query. Note that both type of limit can |
| used in the same statement. |
| |
| .. _allow-filtering: |
| |
| Allowing filtering |
| ~~~~~~~~~~~~~~~~~~ |
| |
| By default, CQL only allows select queries that don't involve “filtering” server side, i.e. queries where we know that |
| all (live) record read will be returned (maybe partly) in the result set. The reasoning is that those “non filtering” |
| queries have predictable performance in the sense that they will execute in a time that is proportional to the amount of |
| data **returned** by the query (which can be controlled through ``LIMIT``). |
| |
| The ``ALLOW FILTERING`` option allows to explicitly allow (some) queries that require filtering. Please note that a |
| query using ``ALLOW FILTERING`` may thus have unpredictable performance (for the definition above), i.e. even a query |
| that selects a handful of records **may** exhibit performance that depends on the total amount of data stored in the |
| cluster. |
| |
| For instance, considering the following table holding user profiles with their year of birth (with a secondary index on |
| it) and country of residence:: |
| |
| CREATE TABLE users ( |
| username text PRIMARY KEY, |
| firstname text, |
| lastname text, |
| birth_year int, |
| country text |
| ) |
| |
| CREATE INDEX ON users(birth_year); |
| |
| Then the following queries are valid:: |
| |
| SELECT * FROM users; |
| SELECT * FROM users WHERE birth_year = 1981; |
| |
| because in both case, Cassandra guarantees that these queries performance will be proportional to the amount of data |
| returned. In particular, if no users are born in 1981, then the second query performance will not depend of the number |
| of user profile stored in the database (not directly at least: due to secondary index implementation consideration, this |
| query may still depend on the number of node in the cluster, which indirectly depends on the amount of data stored. |
| Nevertheless, the number of nodes will always be multiple number of magnitude lower than the number of user profile |
| stored). Of course, both query may return very large result set in practice, but the amount of data returned can always |
| be controlled by adding a ``LIMIT``. |
| |
| However, the following query will be rejected:: |
| |
| SELECT * FROM users WHERE birth_year = 1981 AND country = 'FR'; |
| |
| because Cassandra cannot guarantee that it won't have to scan large amount of data even if the result to those query is |
| small. Typically, it will scan all the index entries for users born in 1981 even if only a handful are actually from |
| France. However, if you “know what you are doing”, you can force the execution of this query by using ``ALLOW |
| FILTERING`` and so the following query is valid:: |
| |
| SELECT * FROM users WHERE birth_year = 1981 AND country = 'FR' ALLOW FILTERING; |
| |
| .. _insert-statement: |
| |
| INSERT |
| ^^^^^^ |
| |
| Inserting data for a row is done using an ``INSERT`` statement: |
| |
| .. productionlist:: |
| insert_statement: INSERT INTO `table_name` ( `names_values` | `json_clause` ) |
| : [ IF NOT EXISTS ] |
| : [ USING `update_parameter` ( AND `update_parameter` )* ] |
| names_values: `names` VALUES `tuple_literal` |
| json_clause: JSON `string` [ DEFAULT ( NULL | UNSET ) ] |
| names: '(' `column_name` ( ',' `column_name` )* ')' |
| |
| For instance:: |
| |
| INSERT INTO NerdMovies (movie, director, main_actor, year) |
| VALUES ('Serenity', 'Joss Whedon', 'Nathan Fillion', 2005) |
| USING TTL 86400; |
| |
| INSERT INTO NerdMovies JSON '{"movie": "Serenity", |
| "director": "Joss Whedon", |
| "year": 2005}'; |
| |
| The ``INSERT`` statement writes one or more columns for a given row in a table. Note that since a row is identified by |
| its ``PRIMARY KEY``, at least the columns composing it must be specified. The list of columns to insert to must be |
| supplied when using the ``VALUES`` syntax. When using the ``JSON`` syntax, they are optional. See the |
| section on :ref:`JSON support <cql-json>` for more detail. |
| |
| Note that unlike in SQL, ``INSERT`` does not check the prior existence of the row by default: the row is created if none |
| existed before, and updated otherwise. Furthermore, there is no mean to know which of creation or update happened. |
| |
| It is however possible to use the ``IF NOT EXISTS`` condition to only insert if the row does not exist prior to the |
| insertion. But please note that using ``IF NOT EXISTS`` will incur a non negligible performance cost (internally, Paxos |
| will be used) so this should be used sparingly. |
| |
| All updates for an ``INSERT`` are applied atomically and in isolation. |
| |
| Please refer to the :ref:`UPDATE <update-parameters>` section for informations on the :token:`update_parameter`. |
| |
| Also note that ``INSERT`` does not support counters, while ``UPDATE`` does. |
| |
| .. _update-statement: |
| |
| UPDATE |
| ^^^^^^ |
| |
| Updating a row is done using an ``UPDATE`` statement: |
| |
| .. productionlist:: |
| update_statement: UPDATE `table_name` |
| : [ USING `update_parameter` ( AND `update_parameter` )* ] |
| : SET `assignment` ( ',' `assignment` )* |
| : WHERE `where_clause` |
| : [ IF ( EXISTS | `condition` ( AND `condition` )*) ] |
| update_parameter: ( TIMESTAMP | TTL ) ( `integer` | `bind_marker` ) |
| assignment: `simple_selection` '=' `term` |
| :| `column_name` '=' `column_name` ( '+' | '-' ) `term` |
| :| `column_name` '=' `list_literal` '+' `column_name` |
| simple_selection: `column_name` |
| :| `column_name` '[' `term` ']' |
| :| `column_name` '.' `field_name |
| condition: `simple_selection` `operator` `term` |
| |
| For instance:: |
| |
| UPDATE NerdMovies USING TTL 400 |
| SET director = 'Joss Whedon', |
| main_actor = 'Nathan Fillion', |
| year = 2005 |
| WHERE movie = 'Serenity'; |
| |
| UPDATE UserActions |
| SET total = total + 2 |
| WHERE user = B70DE1D0-9908-4AE3-BE34-5573E5B09F14 |
| AND action = 'click'; |
| |
| The ``UPDATE`` statement writes one or more columns for a given row in a table. The :token:`where_clause` is used to |
| select the row to update and must include all columns composing the ``PRIMARY KEY``. Non primary key columns are then |
| set using the ``SET`` keyword. |
| |
| Note that unlike in SQL, ``UPDATE`` does not check the prior existence of the row by default (except through ``IF``, see |
| below): the row is created if none existed before, and updated otherwise. Furthermore, there are no means to know |
| whether a creation or update occurred. |
| |
| It is however possible to use the conditions on some columns through ``IF``, in which case the row will not be updated |
| unless the conditions are met. But, please note that using ``IF`` conditions will incur a non-negligible performance |
| cost (internally, Paxos will be used) so this should be used sparingly. |
| |
| In an ``UPDATE`` statement, all updates within the same partition key are applied atomically and in isolation. |
| |
| Regarding the :token:`assignment`: |
| |
| - ``c = c + 3`` is used to increment/decrement counters. The column name after the '=' sign **must** be the same than |
| the one before the '=' sign. Note that increment/decrement is only allowed on counters, and are the *only* update |
| operations allowed on counters. See the section on :ref:`counters <counters>` for details. |
| - ``id = id + <some-collection>`` and ``id[value1] = value2`` are for collections, see the :ref:`relevant section |
| <collections>` for details. |
| - ``id.field = 3`` is for setting the value of a field on a non-frozen user-defined types. see the :ref:`relevant section |
| <udts>` for details. |
| |
| .. _update-parameters: |
| |
| Update parameters |
| ~~~~~~~~~~~~~~~~~ |
| |
| The ``UPDATE``, ``INSERT`` (and ``DELETE`` and ``BATCH`` for the ``TIMESTAMP``) statements support the following |
| parameters: |
| |
| - ``TIMESTAMP``: sets the timestamp for the operation. If not specified, the coordinator will use the current time (in |
| microseconds) at the start of statement execution as the timestamp. This is usually a suitable default. |
| - ``TTL``: specifies an optional Time To Live (in seconds) for the inserted values. If set, the inserted values are |
| automatically removed from the database after the specified time. Note that the TTL concerns the inserted values, not |
| the columns themselves. This means that any subsequent update of the column will also reset the TTL (to whatever TTL |
| is specified in that update). By default, values never expire. A TTL of 0 is equivalent to no TTL. If the table has a |
| default_time_to_live, a TTL of 0 will remove the TTL for the inserted or updated values. A TTL of ``null`` is equivalent |
| to inserting with a TTL of 0. |
| |
| .. _delete_statement: |
| |
| DELETE |
| ^^^^^^ |
| |
| Deleting rows or parts of rows uses the ``DELETE`` statement: |
| |
| .. productionlist:: |
| delete_statement: DELETE [ `simple_selection` ( ',' `simple_selection` ) ] |
| : FROM `table_name` |
| : [ USING `update_parameter` ( AND `update_parameter` )* ] |
| : WHERE `where_clause` |
| : [ IF ( EXISTS | `condition` ( AND `condition` )*) ] |
| |
| For instance:: |
| |
| DELETE FROM NerdMovies USING TIMESTAMP 1240003134 |
| WHERE movie = 'Serenity'; |
| |
| DELETE phone FROM Users |
| WHERE userid IN (C73DE1D3-AF08-40F3-B124-3FF3E5109F22, B70DE1D0-9908-4AE3-BE34-5573E5B09F14); |
| |
| The ``DELETE`` statement deletes columns and rows. If column names are provided directly after the ``DELETE`` keyword, |
| only those columns are deleted from the row indicated by the ``WHERE`` clause. Otherwise, whole rows are removed. |
| |
| The ``WHERE`` clause specifies which rows are to be deleted. Multiple rows may be deleted with one statement by using an |
| ``IN`` operator. A range of rows may be deleted using an inequality operator (such as ``>=``). |
| |
| ``DELETE`` supports the ``TIMESTAMP`` option with the same semantics as in :ref:`updates <update-parameters>`. |
| |
| In a ``DELETE`` statement, all deletions within the same partition key are applied atomically and in isolation. |
| |
| A ``DELETE`` operation can be conditional through the use of an ``IF`` clause, similar to ``UPDATE`` and ``INSERT`` |
| statements. However, as with ``INSERT`` and ``UPDATE`` statements, this will incur a non-negligible performance cost |
| (internally, Paxos will be used) and so should be used sparingly. |
| |
| .. _batch_statement: |
| |
| BATCH |
| ^^^^^ |
| |
| Multiple ``INSERT``, ``UPDATE`` and ``DELETE`` can be executed in a single statement by grouping them through a |
| ``BATCH`` statement: |
| |
| .. productionlist:: |
| batch_statement: BEGIN [ UNLOGGED | COUNTER ] BATCH |
| : [ USING `update_parameter` ( AND `update_parameter` )* ] |
| : `modification_statement` ( ';' `modification_statement` )* |
| : APPLY BATCH |
| modification_statement: `insert_statement` | `update_statement` | `delete_statement` |
| |
| For instance:: |
| |
| BEGIN BATCH |
| INSERT INTO users (userid, password, name) VALUES ('user2', 'ch@ngem3b', 'second user'); |
| UPDATE users SET password = 'ps22dhds' WHERE userid = 'user3'; |
| INSERT INTO users (userid, password) VALUES ('user4', 'ch@ngem3c'); |
| DELETE name FROM users WHERE userid = 'user1'; |
| APPLY BATCH; |
| |
| The ``BATCH`` statement group multiple modification statements (insertions/updates and deletions) into a single |
| statement. It serves several purposes: |
| |
| - It saves network round-trips between the client and the server (and sometimes between the server coordinator and the |
| replicas) when batching multiple updates. |
| - All updates in a ``BATCH`` belonging to a given partition key are performed in isolation. |
| - By default, all operations in the batch are performed as *logged*, to ensure all mutations eventually complete (or |
| none will). See the notes on :ref:`UNLOGGED batches <unlogged-batches>` for more details. |
| |
| Note that: |
| |
| - ``BATCH`` statements may only contain ``UPDATE``, ``INSERT`` and ``DELETE`` statements (not other batches for instance). |
| - Batches are *not* a full analogue for SQL transactions. |
| - If a timestamp is not specified for each operation, then all operations will be applied with the same timestamp |
| (either one generated automatically, or the timestamp provided at the batch level). Due to Cassandra's conflict |
| resolution procedure in the case of `timestamp ties <http://wiki.apache.org/cassandra/FAQ#clocktie>`__, operations may |
| be applied in an order that is different from the order they are listed in the ``BATCH`` statement. To force a |
| particular operation ordering, you must specify per-operation timestamps. |
| - A LOGGED batch to a single partition will be converted to an UNLOGGED batch as an optimization. |
| |
| .. _unlogged-batches: |
| |
| ``UNLOGGED`` batches |
| ~~~~~~~~~~~~~~~~~~~~ |
| |
| By default, Cassandra uses a batch log to ensure all operations in a batch eventually complete or none will (note |
| however that operations are only isolated within a single partition). |
| |
| There is a performance penalty for batch atomicity when a batch spans multiple partitions. If you do not want to incur |
| this penalty, you can tell Cassandra to skip the batchlog with the ``UNLOGGED`` option. If the ``UNLOGGED`` option is |
| used, a failed batch might leave the patch only partly applied. |
| |
| ``COUNTER`` batches |
| ~~~~~~~~~~~~~~~~~~~ |
| |
| Use the ``COUNTER`` option for batched counter updates. Unlike other |
| updates in Cassandra, counter updates are not idempotent. |