diff --git a/cql3/statements/batch_statement.cc b/cql3/statements/batch_statement.cc index 386dced1e8..7db5cf17bc 100644 --- a/cql3/statements/batch_statement.cc +++ b/cql3/statements/batch_statement.cc @@ -388,7 +388,7 @@ future> batch_statement::exe return proxy.cas(schema, request, request->read_command(proxy), request->key(), {read_timeout, qs.get_permit(), qs.get_client_state(), qs.get_trace_state()}, cl_for_paxos, cl_for_learn, batch_timeout, cas_timeout).then([this, request] (bool is_applied) { - return modification_statement::build_cas_result_set(_metadata, _columns_of_cas_result_set, is_applied, request->rows()); + return request->build_cas_result_set(_metadata, _columns_of_cas_result_set, is_applied); }); } diff --git a/cql3/statements/cas_request.cc b/cql3/statements/cas_request.cc index f3d9519963..55b21b33da 100644 --- a/cql3/statements/cas_request.cc +++ b/cql3/statements/cas_request.cc @@ -42,6 +42,9 @@ #include "modification_statement.hh" #include "cas_request.hh" #include +#include "cql3/result_set.hh" +#include "transport/messages/result_message.hh" +#include "types/map.hh" namespace cql3::statements { @@ -120,60 +123,16 @@ lw_shared_ptr cas_request::read_command(service::storage_pr } bool cas_request::applies_to() const { - - const partition_key& pkey = _key.front().start()->value().key().value(); - const clustering_key empty_ckey = clustering_key::make_empty(); - bool applies = true; - bool is_cas_result_set_empty = true; - bool has_static_column_conditions = false; for (const cas_row_update& op: _updates) { - if (op.statement.has_conditions() == false) { + if (!op.statement.has_conditions()) { continue; } - if (op.statement.has_static_column_conditions()) { - has_static_column_conditions = true; - } - // If a statement has only static columns conditions, we must ignore its clustering columns - // restriction when choosing a row to check the conditions, i.e. choose any partition row, - // because any of them must have static columns and that's all we need to know if the - // statement applies. For example, the following update must successfully apply (effectively - // turn into INSERT), because, although the table doesn't have any regular rows matching the - // statement clustering column restriction, the static row matches the statement condition: - // CREATE TABLE t(p int, c int, s int static, v int, PRIMARY KEY(p, c)); - // INSERT INTO t(p, s) VALUES(1, 1); - // UPDATE t SET v=1 WHERE p=1 AND c=1 IF s=1; - // Another case when we pass an empty clustering key prefix is apparently when the table - // doesn't have any clustering key columns and the clustering key range is empty (open - // ended on both sides). - const auto& ckey = !op.statement.has_only_static_column_conditions() && op.ranges.front().start() ? - op.ranges.front().start()->value() : empty_ckey; - const auto* row = _rows.find_row(pkey, ckey); - if (row) { - row->is_in_cas_result_set = true; - is_cas_result_set_empty = false; - } - if (!applies) { - // No need to check this condition as we have already failed a previous one. - // Continuing the loop just to set is_in_cas_result_set flag for all involved - // statements, which is necessary to build the CAS result set. - continue; - } - applies = op.statement.applies_to(row, op.options); - } - if (has_static_column_conditions && is_cas_result_set_empty) { - // If none of the fetched rows matches clustering key restrictions and hence none of them is - // included into the CAS result set, but there is a static column condition in the CAS batch, - // we must still include the static row into the result set. Consider the following example: - // CREATE TABLE t(p int, c int, s int static, v int, PRIMARY KEY(p, c)); - // INSERT INTO t(p, s) VALUES(1, 1); - // DELETE v FROM t WHERE p=1 AND c=1 IF v=1 AND s=1; - // In this case the conditional DELETE must return [applied=False, v=null, s=1]. - const auto* row = _rows.find_row(pkey, empty_ckey); - if (row) { - row->is_in_cas_result_set = true; + // No need to check subsequent conditions as we have already failed the current one. + if (!op.statement.applies_to(find_old_row(op), op.options)) { + return false; } } - return applies; + return true; } std::optional cas_request::apply(foreign_ptr> qr, @@ -186,4 +145,91 @@ std::optional cas_request::apply(foreign_ptrvalue().key().value(); + // If a statement has only static columns conditions, we must ignore its clustering columns + // restriction when choosing a row to check the conditions, i.e. choose any partition row, + // because any of them must have static columns and that's all we need to know if the + // statement applies. For example, the following update must successfully apply (effectively + // turn into INSERT), because, although the table doesn't have any regular rows matching the + // statement clustering column restriction, the static row matches the statement condition: + // CREATE TABLE t(p int, c int, s int static, v int, PRIMARY KEY(p, c)); + // INSERT INTO t(p, s) VALUES(1, 1); + // UPDATE t SET v=1 WHERE p=1 AND c=1 IF s=1; + // Another case when we pass an empty clustering key prefix is apparently when the table + // doesn't have any clustering key columns and the clustering key range is empty (open + // ended on both sides). + const clustering_key& ckey = !op.statement.has_only_static_column_conditions() && op.ranges.front().start() ? + op.ranges.front().start()->value() : empty_ckey; + return _rows.find_row(pkey, ckey); +} + +seastar::shared_ptr +cas_request::build_cas_result_set(seastar::shared_ptr metadata, + const column_set& columns, + bool is_applied) const { + const partition_key& pkey = _key.front().start()->value().key().value(); + const clustering_key empty_ckey = clustering_key::make_empty(); + auto result_set = std::make_unique(metadata); + + for (const cas_row_update& op: _updates) { + // Construct the result set row + std::vector rs_row; + rs_row.reserve(metadata->value_count()); + rs_row.emplace_back(boolean_type->decompose(is_applied)); + // Get old row from prefetched data for the row update + const auto* old_row = find_old_row(op); + if (!old_row) { + if (!op.statement.has_static_column_conditions()) { + // In case there is no old row, leave all other columns null + // so that we can infer whether the update attempts to insert a + // non-existing row. + rs_row.resize(metadata->value_count()); + result_set->add_row(std::move(rs_row)); + continue; + } + // If none of the fetched rows matches clustering key restrictions, + // but there is a static column condition in the CAS batch, + // we must still include the static row into the result set. Consider the following example: + // CREATE TABLE t(p int, c int, s int static, v int, PRIMARY KEY(p, c)); + // INSERT INTO t(p, s) VALUES(1, 1); + // DELETE v FROM t WHERE p=1 AND c=1 IF v=1 AND s=1; + // In this case the conditional DELETE must return [applied=False, v=null, s=1]. + old_row = _rows.find_row(pkey, empty_ckey); + if (!old_row) { + // In case there is no old row, leave all other columns null + // so that we can infer whether the update attempts to insert a + // non-existing row. + rs_row.resize(metadata->value_count()); + result_set->add_row(std::move(rs_row)); + continue; + } + } + // Fill in the cells from prefetch data (old row) into the result set row + for (ordinal_column_id id = columns.find_first(); id != column_set::npos; id = columns.find_next(id)) { + const auto it = old_row->cells.find(id); + if (it == old_row->cells.end()) { + rs_row.emplace_back(bytes_opt{}); + continue; + } + const data_value& cell = it->second; + const abstract_type& cell_type = *cell.type(); + const abstract_type& column_type = *_rows.schema->column_at(id).type; + + if (column_type.is_listlike() && cell_type.is_map()) { + // List/sets are fetched as maps, but need to be stored as sets. + const listlike_collection_type_impl& list_type = static_cast(column_type); + const map_type_impl& map_type = static_cast(cell_type); + rs_row.emplace_back(list_type.serialize_map(map_type, cell)); + } else { + rs_row.emplace_back(cell_type.decompose(cell)); + } + } + result_set->add_row(std::move(rs_row)); + } + cql3::result result(std::move(result_set)); + return seastar::make_shared(std::move(result)); +} + } // end of namespace "cql3::statements" diff --git a/cql3/statements/cas_request.hh b/cql3/statements/cas_request.hh index 5f254b6d20..6bf696276a 100644 --- a/cql3/statements/cas_request.hh +++ b/cql3/statements/cas_request.hh @@ -98,9 +98,21 @@ public: virtual std::optional apply(foreign_ptr> qr, const query::partition_slice& slice, api::timestamp_type ts) override; + /// Build a result set with prefetched rows, but return only + /// the columns required by CAS. + /// + /// Each cas_row_update provides a row in the result set. + /// Rows are ordered the same way as the individual statements appear + /// in case of batch statement. + seastar::shared_ptr + build_cas_result_set(seastar::shared_ptr metadata, + const column_set& mask, bool is_applied) const; + private: bool applies_to() const; std::optional apply_updates(api::timestamp_type t) const; + /// Find a row in prefetch_data which matches primary key identifying a given `cas_row_update` + const update_parameters::prefetch_data::row* find_old_row(const cas_row_update& op) const; }; } // end of namespace "cql3::statements" diff --git a/cql3/statements/modification_statement.cc b/cql3/statements/modification_statement.cc index bdcc13f0fb..75faf83237 100644 --- a/cql3/statements/modification_statement.cc +++ b/cql3/statements/modification_statement.cc @@ -355,57 +355,10 @@ modification_statement::execute_with_condition(service::storage_proxy& proxy, se return proxy.cas(s, request, request->read_command(proxy), request->key(), {read_timeout, qs.get_permit(), qs.get_client_state(), qs.get_trace_state()}, cl_for_paxos, cl_for_learn, statement_timeout, cas_timeout).then([this, request] (bool is_applied) { - return build_cas_result_set(_metadata, _columns_of_cas_result_set, is_applied, request->rows()); + return request->build_cas_result_set(_metadata, _columns_of_cas_result_set, is_applied); }); } -seastar::shared_ptr -modification_statement::build_cas_result_set(seastar::shared_ptr metadata, - const column_set& columns, - bool is_applied, - const update_parameters::prefetch_data& rows) { - - auto result_set = std::make_unique(metadata); - for (const auto& it : rows.rows) { - const update_parameters::prefetch_data::row& cell_map = it.second; - if (!cell_map.is_in_cas_result_set) { - continue; - } - std::vector row; - row.reserve(metadata->value_count()); - row.emplace_back(boolean_type->decompose(is_applied)); - for (ordinal_column_id id = columns.find_first(); id != column_set::npos; id = columns.find_next(id)) { - const auto it = cell_map.cells.find(id); - if (it == cell_map.cells.end()) { - row.emplace_back(bytes_opt{}); - } else { - const data_value& cell = it->second; - const abstract_type& cell_type = *cell.type(); - const abstract_type& column_type = *rows.schema->column_at(id).type; - - if (column_type.is_listlike() && cell_type.is_map()) { - // List/sets are fetched as maps, but need to be stored as sets. - const listlike_collection_type_impl& list_type = static_cast(column_type); - const map_type_impl& map_type = static_cast(cell_type); - row.emplace_back(list_type.serialize_map(map_type, cell)); - } else { - row.emplace_back(cell_type.decompose(cell)); - } - } - } - result_set->add_row(std::move(row)); - } - if (result_set->empty()) { - // Is the case when, e.g., IF EXISTS or IF NOT EXISTS finds no row. - std::vector row; - row.emplace_back(boolean_type->decompose(is_applied)); - row.resize(metadata->value_count()); - result_set->add_row(std::move(row)); - } - cql3::result result(std::move(result_set)); - return seastar::make_shared(std::move(result)); -} - void modification_statement::build_cas_result_set_metadata() { std::vector> columns; diff --git a/cql3/statements/modification_statement.hh b/cql3/statements/modification_statement.hh index 435e9ce863..dfa6924336 100644 --- a/cql3/statements/modification_statement.hh +++ b/cql3/statements/modification_statement.hh @@ -207,13 +207,7 @@ public: // CAS statement returns a result set. Prepare result set metadata // so that get_result_metadata() returns a meaningful value. void build_cas_result_set_metadata(); - // Build a result set with prefetched rows, but return only - // the columns required by CAS. Static since reused by BATCH - // CAS. - static seastar::shared_ptr - build_cas_result_set(seastar::shared_ptr metadata, - const column_set& mask, bool is_applied, - const update_parameters::prefetch_data& rows); + public: virtual dht::partition_range_vector build_partition_keys(const query_options& options, const json_cache_opt& json_cache) const; virtual query::clustering_row_ranges create_clustering_ranges(const query_options& options, const json_cache_opt& json_cache) const; diff --git a/cql3/update_parameters.hh b/cql3/update_parameters.hh index 9417e1055b..c669b11664 100644 --- a/cql3/update_parameters.hh +++ b/cql3/update_parameters.hh @@ -96,10 +96,6 @@ public: struct row { // Order CAS columns by ordinal column id. std::map cells; - // Set if the statement is used for checking conditions of a CAS request. - // Only those statements that have this flag set should be included into - // the CAS result set. - mutable bool is_in_cas_result_set = false; // Return true if this row has at least one static column set. bool has_static_columns(const schema& schema) const { if (!schema.has_static_columns()) { diff --git a/test/cql/cassandra_cql_test.result b/test/cql/cassandra_cql_test.result index 369fb077ab..0656bce66d 100644 --- a/test/cql/cassandra_cql_test.result +++ b/test/cql/cassandra_cql_test.result @@ -1105,6 +1105,9 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "true" + }, { "[applied]" : "true" } @@ -1130,6 +1133,9 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "true" + }, { "[applied]" : "true" } @@ -1532,6 +1538,11 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "true", + "a" : "2", + "b" : "2" + }, { "[applied]" : "true", "a" : "2", @@ -1560,6 +1571,11 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "true", + "a" : "4", + "b" : "4" + }, { "[applied]" : "true", "a" : "4", @@ -1588,6 +1604,11 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "true", + "a" : "5", + "b" : "5" + }, { "[applied]" : "true", "a" : "5", @@ -1616,6 +1637,9 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "true" + }, { "[applied]" : "true" } @@ -1642,6 +1666,11 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "false", + "a" : "3", + "b" : "3" + }, { "[applied]" : "false", "a" : "3", @@ -1656,6 +1685,11 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "false", + "a" : "3", + "b" : "3" + }, { "[applied]" : "false", "a" : "3", @@ -1670,6 +1704,11 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "false", + "a" : "3", + "b" : "3" + }, { "[applied]" : "false", "a" : "3", @@ -1684,6 +1723,11 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "false", + "a" : "3", + "b" : "3" + }, { "[applied]" : "false", "a" : "3", @@ -1698,6 +1742,11 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "false", + "a" : "3", + "b" : "3" + }, { "[applied]" : "false", "a" : "3", @@ -1724,6 +1773,11 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "false", + "a" : "6", + "b" : "6" + }, { "[applied]" : "false", "a" : "6", @@ -1970,6 +2024,9 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "true" + }, { "[applied]" : "true" } @@ -1995,6 +2052,9 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "false" + }, { "[applied]" : "false" } @@ -2011,6 +2071,9 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "false" + }, { "[applied]" : "false" } @@ -2027,6 +2090,9 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "false" + }, { "[applied]" : "false" } @@ -2043,6 +2109,9 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "false" + }, { "[applied]" : "false" } @@ -2059,6 +2128,9 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "false" + }, { "[applied]" : "false" } @@ -2077,6 +2149,9 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "false" + }, { "[applied]" : "false" } @@ -2093,6 +2168,9 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "true" + }, { "[applied]" : "true" } @@ -2117,6 +2195,9 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "true" + }, { "[applied]" : "true" } @@ -2141,6 +2222,9 @@ APPLY BATCH; { "rows" : [ + { + "[applied]" : "true" + }, { "[applied]" : "true" } diff --git a/test/cql/lwt_batch_test.cql b/test/cql/lwt_batch_test.cql index 88f06260a8..f077513936 100644 --- a/test/cql/lwt_batch_test.cql +++ b/test/cql/lwt_batch_test.cql @@ -221,4 +221,46 @@ begin batch insert into lwt (key, ck, cv) values (1, 0, {'b', 'c'}) apply batch; select * from lwt; -drop table lwt; \ No newline at end of file +drop table lwt; + +-- +-- A test case for Issue #7113 +-- Return one row per each LWT statement +-- in a batch, in statement order. +-- +CREATE TABLE IF NOT EXISTS gh7113 ( + part int, + key int, + lwt_trivial int, + int1 int, + int2 int, + PRIMARY KEY (part, key) +); + +BEGIN BATCH + UPDATE gh7113 SET int1 = 6 WHERE part = 0 AND key = 4 IF lwt_trivial = null +APPLY BATCH; + +BEGIN BATCH + UPDATE gh7113 SET int2 = 0, int1 = 0 WHERE part = 0 AND key = 0 IF lwt_trivial = null + UPDATE gh7113 SET int2 = 1, int1 = 6 WHERE part = 0 AND key = 7 IF lwt_trivial = null +APPLY BATCH; + +BEGIN BATCH + UPDATE gh7113 SET int2 = 0, int1 = 2 WHERE part = 0 AND key = 9 IF lwt_trivial = null + UPDATE gh7113 SET int1 = 7 WHERE part = 0 AND key = 0 IF lwt_trivial = null +APPLY BATCH; + + +BEGIN BATCH +UPDATE gh7113 SET int1 = 6, int2 = 7 WHERE part = 0 AND key = 1 IF lwt_trivial = null +UPDATE gh7113 SET int2 = 4 WHERE part = 0 AND key = 0 IF lwt_trivial = null +UPDATE gh7113 SET int2 = 2 WHERE part = 0 AND key = 3 IF lwt_trivial = null +APPLY BATCH; + +BEGIN BATCH + UPDATE gh7113 SET int2 = 1 WHERE part = 0 AND key = 4 IF lwt_trivial = null + UPDATE gh7113 SET int2 = 1 WHERE part = 0 AND key = 0 IF lwt_trivial = null + UPDATE gh7113 SET int1 = 4, int2 = 8 WHERE part = 0 AND key = 9 IF lwt_trivial = null + UPDATE gh7113 SET int1 = 0, int2 = 9 WHERE part = 0 AND key = 0 IF lwt_trivial = null +APPLY BATCH; diff --git a/test/cql/lwt_batch_test.result b/test/cql/lwt_batch_test.result index f909ed7713..edd6949784 100644 --- a/test/cql/lwt_batch_test.result +++ b/test/cql/lwt_batch_test.result @@ -68,6 +68,9 @@ apply batch; "[applied]" : "false", "a" : "1", "b" : "1" + }, + { + "[applied]" : "false" } ] } @@ -83,6 +86,9 @@ apply batch; "[applied]" : "false", "a" : "1", "b" : "1" + }, + { + "[applied]" : "false" } ] } @@ -127,6 +133,11 @@ apply batch; { "rows" : [ + { + "[applied]" : "false", + "a" : "1", + "b" : "1" + }, { "[applied]" : "false", "a" : "1", @@ -204,6 +215,9 @@ apply batch; { "rows" : [ + { + "[applied]" : "true" + }, { "[applied]" : "true" } @@ -245,6 +259,9 @@ apply batch; { "rows" : [ + { + "[applied]" : "true" + }, { "[applied]" : "true" } @@ -279,6 +296,9 @@ apply batch; "b" : "1", "c" : "[\"1\"]", "d" : "[\"1\", \"2\"]" + }, + { + "[applied]" : "true" } ] } @@ -310,6 +330,9 @@ apply batch; "a" : "1", "b" : "1", "c" : "[\"1\", \"3\"]" + }, + { + "[applied]" : "true" } ] } @@ -450,6 +473,13 @@ apply batch; { "rows" : [ + { + "[applied]" : "true", + "a" : "1", + "b" : "1", + "c" : "1", + "d" : "1" + }, { "[applied]" : "true", "a" : "1", @@ -543,6 +573,12 @@ apply batch; "c" : "1", "i" : "1", "p" : "1" + }, + { + "[applied]" : "false", + "c" : "2", + "i" : "2", + "p" : "1" } ] } @@ -559,6 +595,12 @@ apply batch; "c" : "1", "i" : "1", "p" : "1" + }, + { + "[applied]" : "true", + "c" : "2", + "i" : "2", + "p" : "1" } ] } @@ -572,6 +614,13 @@ apply batch; [ { "[applied]" : "false" + }, + { + "[applied]" : "false", + "c" : "2", + "i" : "2", + "l" : "[1, 3, 4]", + "p" : "1" } ] } @@ -589,6 +638,13 @@ apply batch; "i" : "2", "l" : "[1, 2]", "p" : "1" + }, + { + "[applied]" : "true", + "c" : "2", + "i" : "2", + "l" : "[1, 3, 4]", + "p" : "1" } ] } @@ -633,6 +689,9 @@ apply batch; { "rows" : [ + { + "[applied]" : "true" + }, { "[applied]" : "true", "p" : "1", @@ -690,6 +749,12 @@ apply batch; { "rows" : [ + { + "[applied]" : "true", + "ck" : "0", + "cv" : "[\"a\", \"b\"]", + "key" : "1" + }, { "[applied]" : "true", "ck" : "0", @@ -730,6 +795,12 @@ apply batch; { "rows" : [ + { + "[applied]" : "true", + "ck" : "0", + "cv" : "[\"a\", \"b\"]", + "key" : "1" + }, { "[applied]" : "true", "ck" : "0", @@ -753,3 +824,121 @@ drop table lwt; { "status" : "ok" } + +-- +-- A test case for Issue #7113 +-- Return one row per each LWT statement +-- in a batch, in statement order. +-- +CREATE TABLE IF NOT EXISTS gh7113 ( + part int, + key int, + lwt_trivial int, + int1 int, + int2 int, + PRIMARY KEY (part, key) +); +{ + "status" : "ok" +} + +BEGIN BATCH + UPDATE gh7113 SET int1 = 6 WHERE part = 0 AND key = 4 IF lwt_trivial = null +APPLY BATCH; +{ + "rows" : + [ + { + "[applied]" : "true" + } + ] +} + +BEGIN BATCH + UPDATE gh7113 SET int2 = 0, int1 = 0 WHERE part = 0 AND key = 0 IF lwt_trivial = null + UPDATE gh7113 SET int2 = 1, int1 = 6 WHERE part = 0 AND key = 7 IF lwt_trivial = null +APPLY BATCH; +{ + "rows" : + [ + { + "[applied]" : "true" + }, + { + "[applied]" : "true" + } + ] +} + +BEGIN BATCH + UPDATE gh7113 SET int2 = 0, int1 = 2 WHERE part = 0 AND key = 9 IF lwt_trivial = null + UPDATE gh7113 SET int1 = 7 WHERE part = 0 AND key = 0 IF lwt_trivial = null +APPLY BATCH; +{ + "rows" : + [ + { + "[applied]" : "true" + }, + { + "[applied]" : "true", + "key" : "0", + "part" : "0" + } + ] +} + + +BEGIN BATCH +UPDATE gh7113 SET int1 = 6, int2 = 7 WHERE part = 0 AND key = 1 IF lwt_trivial = null +UPDATE gh7113 SET int2 = 4 WHERE part = 0 AND key = 0 IF lwt_trivial = null +UPDATE gh7113 SET int2 = 2 WHERE part = 0 AND key = 3 IF lwt_trivial = null +APPLY BATCH; +{ + "rows" : + [ + { + "[applied]" : "true" + }, + { + "[applied]" : "true", + "key" : "0", + "part" : "0" + }, + { + "[applied]" : "true" + } + ] +} + +BEGIN BATCH + UPDATE gh7113 SET int2 = 1 WHERE part = 0 AND key = 4 IF lwt_trivial = null + UPDATE gh7113 SET int2 = 1 WHERE part = 0 AND key = 0 IF lwt_trivial = null + UPDATE gh7113 SET int1 = 4, int2 = 8 WHERE part = 0 AND key = 9 IF lwt_trivial = null + UPDATE gh7113 SET int1 = 0, int2 = 9 WHERE part = 0 AND key = 0 IF lwt_trivial = null +APPLY BATCH; +{ + "rows" : + [ + { + "[applied]" : "true", + "key" : "4", + "part" : "0" + }, + { + "[applied]" : "true", + "key" : "0", + "part" : "0" + }, + { + "[applied]" : "true", + "key" : "9", + "part" : "0" + }, + { + "[applied]" : "true", + "key" : "0", + "part" : "0" + } + ] +}