Skip to content

Commit 0f0da29

Browse files
fromanatorAlexandre Dutra
authored and
Alexandre Dutra
committed
Add BoundStatement ReturnType as a return type for Create, Update, and Delete operations
Motivation: In the 3.x driver the object-mapper had Mapper.saveQuery(entity) and Mapper.deleteQuery(id) which returned a BoundStatement that you could later execute. This was useful for creating a list of BoundStatement that would be added to a LOGGED Batch statement for atomically saving data that is saved across several denormalized tables (to support different query patterns on the same data). While the current code sort of enables that with the @SetEntity, it however lacks a lot of the features that the other DAO methods have, for instance setting a TTL, custom IF and WHERE clauses that are are supported by the @insert, @update, and @delete methods. And not all of these features may be supportable on this more generic implemenation. Modifcations: Added a new DaoReturnType.BOUND_STATEMENT that is practically identical to to the existing DaoReturnType. Since it just returns the BoundStatment instead of executing it. Result: For @insert, @update, @delete, and @query DAO methods BoundStatement is now a new supported return type. This is an additive change to the API of the Mapper so it is non-breaking.
1 parent 76b0cdd commit 0f0da29

File tree

23 files changed

+314
-15
lines changed

23 files changed

+314
-15
lines changed

changelog/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### 4.4.0 (in progress)
66

7+
- [new feature] JAVA-2532: Add BoundStatement ReturnType for insert, update, and delete DAO methods
78
- [improvement] JAVA-2107: Add XML formatting plugin
89
- [bug] JAVA-2527: Allow AllNodesFailedException to accept more than one error per node
910
- [improvement] JAVA-2546: Abort schema refresh if a query fails

integration-tests/src/test/java/com/datastax/oss/driver/mapper/DeleteIT.java

+144
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.datastax.oss.driver.api.core.CqlSession;
2222
import com.datastax.oss.driver.api.core.PagingIterable;
2323
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
24+
import com.datastax.oss.driver.api.core.cql.BoundStatement;
2425
import com.datastax.oss.driver.api.core.cql.ResultSet;
2526
import com.datastax.oss.driver.api.core.cql.Row;
2627
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
@@ -174,6 +175,21 @@ public void should_delete_with_condition() {
174175
assertThat(dao.findById(id)).isNull();
175176
}
176177

178+
@Test
179+
public void should_delete_with_condition_statement() {
180+
UUID id = FLAMETHROWER.getId();
181+
assertThat(dao.findById(id)).isNotNull();
182+
183+
BoundStatement bs = dao.deleteIfDescriptionMatchesStatement(id, "foo");
184+
ResultSet rs = SESSION_RULE.session().execute(bs);
185+
assertThat(rs.wasApplied()).isFalse();
186+
assertThat(rs.one().getString("description")).isEqualTo(FLAMETHROWER.getDescription());
187+
188+
rs = dao.deleteIfDescriptionMatches(id, FLAMETHROWER.getDescription());
189+
assertThat(rs.wasApplied()).isTrue();
190+
assertThat(dao.findById(id)).isNull();
191+
}
192+
177193
@Test
178194
public void should_delete_with_condition_asynchronously() {
179195
UUID id = FLAMETHROWER.getId();
@@ -198,6 +214,13 @@ public void should_delete_by_partition_key() {
198214
assertThat(saleDao.all().all()).containsOnly(FLAMETHROWER_SALE_5, MP3_DOWNLOAD_SALE_1);
199215
}
200216

217+
@Test
218+
public void should_delete_by_partition_key_statement() {
219+
// should delete FLAMETHROWER_SALE_[1-4]
220+
SESSION_RULE.session().execute(saleDao.deleteByIdForDayStatement(FLAMETHROWER.getId(), DATE_1));
221+
assertThat(saleDao.all().all()).containsOnly(FLAMETHROWER_SALE_5, MP3_DOWNLOAD_SALE_1);
222+
}
223+
201224
@Test
202225
public void should_delete_by_partition_key_and_partial_clustering() {
203226
// should delete FLAMETHROWER_SALE_{1,3,4]
@@ -206,6 +229,16 @@ public void should_delete_by_partition_key_and_partial_clustering() {
206229
.containsOnly(FLAMETHROWER_SALE_2, FLAMETHROWER_SALE_5, MP3_DOWNLOAD_SALE_1);
207230
}
208231

232+
@Test
233+
public void should_delete_by_partition_key_and_partial_clustering_statement() {
234+
// should delete FLAMETHROWER_SALE_{1,3,4]
235+
SESSION_RULE
236+
.session()
237+
.execute(saleDao.deleteByIdForCustomerStatement(FLAMETHROWER.getId(), DATE_1, 1));
238+
assertThat(saleDao.all().all())
239+
.containsOnly(FLAMETHROWER_SALE_2, FLAMETHROWER_SALE_5, MP3_DOWNLOAD_SALE_1);
240+
}
241+
209242
@Test
210243
public void should_delete_by_primary_key_sales() {
211244
// should delete FLAMETHROWER_SALE_2
@@ -220,6 +253,23 @@ public void should_delete_by_primary_key_sales() {
220253
MP3_DOWNLOAD_SALE_1);
221254
}
222255

256+
@Test
257+
public void should_delete_by_primary_key_sales_statement() {
258+
// should delete FLAMETHROWER_SALE_2
259+
SESSION_RULE
260+
.session()
261+
.execute(
262+
saleDao.deleteByIdForCustomerAtTimeStatement(
263+
FLAMETHROWER.getId(), DATE_1, 2, FLAMETHROWER_SALE_2.getTs()));
264+
assertThat(saleDao.all().all())
265+
.containsOnly(
266+
FLAMETHROWER_SALE_1,
267+
FLAMETHROWER_SALE_3,
268+
FLAMETHROWER_SALE_4,
269+
FLAMETHROWER_SALE_5,
270+
MP3_DOWNLOAD_SALE_1);
271+
}
272+
223273
@Test
224274
public void should_delete_if_price_matches() {
225275
ResultSet result =
@@ -238,6 +288,26 @@ public void should_delete_if_price_matches() {
238288
assertThat(result.wasApplied()).isTrue();
239289
}
240290

291+
@Test
292+
public void should_delete_if_price_matchesStatement() {
293+
BoundStatement bs =
294+
saleDao.deleteIfPriceMatchesStatement(
295+
FLAMETHROWER.getId(), DATE_1, 2, FLAMETHROWER_SALE_2.getTs(), 250.0);
296+
ResultSet result = SESSION_RULE.session().execute(bs);
297+
298+
assertThat(result.wasApplied()).isFalse();
299+
Row row = result.one();
300+
assertThat(row).isNotNull();
301+
assertThat(row.getDouble("price")).isEqualTo(500.0);
302+
303+
bs =
304+
saleDao.deleteIfPriceMatchesStatement(
305+
FLAMETHROWER.getId(), DATE_1, 2, FLAMETHROWER_SALE_2.getTs(), 500.0);
306+
result = SESSION_RULE.session().execute(bs);
307+
308+
assertThat(result.wasApplied()).isTrue();
309+
}
310+
241311
@Test
242312
public void should_delete_if_exists_sales() {
243313
assertThat(saleDao.deleteIfExists(FLAMETHROWER.getId(), DATE_1, 2, FLAMETHROWER_SALE_2.getTs()))
@@ -262,6 +332,24 @@ public void should_delete_within_time_range() {
262332
FLAMETHROWER_SALE_2, FLAMETHROWER_SALE_4, FLAMETHROWER_SALE_5, MP3_DOWNLOAD_SALE_1);
263333
}
264334

335+
@Test
336+
public void should_delete_within_time_range_statement() {
337+
// should delete FLAMETHROWER_SALE_{1,3}, but not 4 because range ends before
338+
SESSION_RULE
339+
.session()
340+
.execute(
341+
saleDao.deleteInTimeRangeStatement(
342+
FLAMETHROWER.getId(),
343+
DATE_1,
344+
1,
345+
FLAMETHROWER_SALE_1.getTs(),
346+
Uuids.startOf(Uuids.unixTimestamp(FLAMETHROWER_SALE_4.getTs()) - 1000)));
347+
348+
assertThat(saleDao.all().all())
349+
.containsOnly(
350+
FLAMETHROWER_SALE_2, FLAMETHROWER_SALE_4, FLAMETHROWER_SALE_5, MP3_DOWNLOAD_SALE_1);
351+
}
352+
265353
@Test
266354
public void should_delete_if_price_matches_custom_where() {
267355
ResultSet result =
@@ -280,6 +368,26 @@ public void should_delete_if_price_matches_custom_where() {
280368
assertThat(result.wasApplied()).isTrue();
281369
}
282370

371+
@Test
372+
public void should_delete_if_price_matches_custom_where_statement() {
373+
BoundStatement bs =
374+
saleDao.deleteCustomWhereCustomIfStatement(
375+
2, FLAMETHROWER.getId(), DATE_1, FLAMETHROWER_SALE_2.getTs(), 250.0);
376+
ResultSet result = SESSION_RULE.session().execute(bs);
377+
378+
assertThat(result.wasApplied()).isFalse();
379+
Row row = result.one();
380+
assertThat(row).isNotNull();
381+
assertThat(row.getDouble("price")).isEqualTo(500.0);
382+
383+
bs =
384+
saleDao.deleteCustomWhereCustomIfStatement(
385+
2, FLAMETHROWER.getId(), DATE_1, FLAMETHROWER_SALE_2.getTs(), 500.0);
386+
result = SESSION_RULE.session().execute(bs);
387+
388+
assertThat(result.wasApplied()).isTrue();
389+
}
390+
283391
@Mapper
284392
public interface InventoryMapper {
285393
@DaoFactory
@@ -305,6 +413,9 @@ public interface ProductDao {
305413
@Delete(entityClass = Product.class, customIfClause = "description = :expectedDescription")
306414
ResultSet deleteIfDescriptionMatches(UUID productId, String expectedDescription);
307415

416+
@Delete(entityClass = Product.class, customIfClause = "description = :expectedDescription")
417+
BoundStatement deleteIfDescriptionMatchesStatement(UUID productId, String expectedDescription);
418+
308419
@Delete
309420
CompletionStage<Void> deleteAsync(Product product);
310421

@@ -335,25 +446,50 @@ public interface ProductSaleDao {
335446
@Delete(entityClass = ProductSale.class)
336447
ResultSet deleteByIdForDay(UUID id, String day);
337448

449+
// delete all rows in partition
450+
@Delete(entityClass = ProductSale.class)
451+
BoundStatement deleteByIdForDayStatement(UUID id, String day);
452+
338453
// delete by partition key and partial clustering key
339454
@Delete(entityClass = ProductSale.class)
340455
ResultSet deleteByIdForCustomer(UUID id, String day, int customerId);
341456

457+
// delete by partition key and partial clustering key
458+
@Delete(entityClass = ProductSale.class)
459+
BoundStatement deleteByIdForCustomerStatement(UUID id, String day, int customerId);
460+
342461
// delete row (full primary key)
343462
@Delete(entityClass = ProductSale.class)
344463
ResultSet deleteByIdForCustomerAtTime(UUID id, String day, int customerId, UUID ts);
345464

465+
// delete row (full primary key)
466+
@Delete(entityClass = ProductSale.class)
467+
BoundStatement deleteByIdForCustomerAtTimeStatement(
468+
UUID id, String day, int customerId, UUID ts);
469+
346470
@Delete(entityClass = ProductSale.class, customIfClause = "price = :expectedPrice")
347471
ResultSet deleteIfPriceMatches(
348472
UUID id, String day, int customerId, UUID ts, double expectedPrice);
349473

474+
@Delete(entityClass = ProductSale.class, customIfClause = "price = :expectedPrice")
475+
BoundStatement deleteIfPriceMatchesStatement(
476+
UUID id, String day, int customerId, UUID ts, double expectedPrice);
477+
350478
@Delete(
351479
entityClass = ProductSale.class,
352480
customWhereClause =
353481
"id = :id and day = :day and customer_id = :customerId and ts >= :startTs and ts < "
354482
+ ":endTs")
355483
ResultSet deleteInTimeRange(UUID id, String day, int customerId, UUID startTs, UUID endTs);
356484

485+
@Delete(
486+
entityClass = ProductSale.class,
487+
customWhereClause =
488+
"id = :id and day = :day and customer_id = :customerId and ts >= :startTs and ts < "
489+
+ ":endTs")
490+
BoundStatement deleteInTimeRangeStatement(
491+
UUID id, String day, int customerId, UUID startTs, UUID endTs);
492+
357493
// transpose order of parameters so doesn't match primary key to ensure that works.
358494
@Delete(
359495
entityClass = ProductSale.class,
@@ -362,6 +498,14 @@ ResultSet deleteIfPriceMatches(
362498
ResultSet deleteCustomWhereCustomIf(
363499
int customerId, UUID id, String day, UUID ts, double expectedPrice);
364500

501+
// transpose order of parameters so doesn't match primary key to ensure that works.
502+
@Delete(
503+
entityClass = ProductSale.class,
504+
customWhereClause = "id = :id and day = :day and customer_id = :customerId and ts = :ts",
505+
customIfClause = "price = :expectedPrice")
506+
BoundStatement deleteCustomWhereCustomIfStatement(
507+
int customerId, UUID id, String day, UUID ts, double expectedPrice);
508+
365509
@Delete(entityClass = ProductSale.class, ifExists = true)
366510
boolean deleteIfExists(UUID id, String day, int customerId, UUID ts);
367511

integration-tests/src/test/java/com/datastax/oss/driver/mapper/InsertIT.java

+13
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.datastax.oss.driver.api.core.CqlIdentifier;
2121
import com.datastax.oss.driver.api.core.CqlSession;
2222
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
23+
import com.datastax.oss.driver.api.core.cql.BoundStatement;
2324
import com.datastax.oss.driver.api.core.cql.ResultSet;
2425
import com.datastax.oss.driver.api.core.cql.Row;
2526
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
@@ -98,6 +99,15 @@ public void should_insert_entity_returning_result_set() {
9899
assertThat(dao.findById(FLAMETHROWER.getId())).isEqualTo(FLAMETHROWER);
99100
}
100101

102+
@Test
103+
public void should_return_bound_statement_to_execute() {
104+
assertThat(dao.findById(FLAMETHROWER.getId())).isNull();
105+
BoundStatement bs = dao.saveReturningBoundStatement(FLAMETHROWER);
106+
ResultSet rs = SESSION_RULE.session().execute(bs);
107+
assertThat(rs.getAvailableWithoutFetching()).isZero();
108+
assertThat(dao.findById(FLAMETHROWER.getId())).isEqualTo(FLAMETHROWER);
109+
}
110+
101111
@Test
102112
public void should_insert_entity_asynchronously() {
103113
assertThat(dao.findById(FLAMETHROWER.getId())).isNull();
@@ -289,6 +299,9 @@ public interface ProductDao {
289299
@Insert
290300
ResultSet saveReturningResultSet(Product product);
291301

302+
@Insert
303+
BoundStatement saveReturningBoundStatement(Product product);
304+
292305
@Insert(timestamp = ":timestamp")
293306
void saveWithBoundTimestamp(Product product, long timestamp);
294307

integration-tests/src/test/java/com/datastax/oss/driver/mapper/UpdateCustomIfClauseIT.java

+36
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.datastax.oss.driver.api.core.CqlIdentifier;
2121
import com.datastax.oss.driver.api.core.CqlSession;
2222
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
23+
import com.datastax.oss.driver.api.core.cql.BoundStatement;
2324
import com.datastax.oss.driver.api.core.cql.ResultSet;
2425
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
2526
import com.datastax.oss.driver.api.mapper.annotations.CqlName;
@@ -90,6 +91,22 @@ public void should_update_entity_if_condition_is_met() {
9091
assertThat(dao.updateIfLength(otherProduct, 10).wasApplied()).isEqualTo(true);
9192
}
9293

94+
@Test
95+
public void should_update_entity_if_condition_is_met_statement() {
96+
dao.update(
97+
new Product(FLAMETHROWER.getId(), "Description for length 10", new Dimensions(10, 1, 1)));
98+
assertThat(dao.findById(FLAMETHROWER.getId())).isNotNull();
99+
100+
Product otherProduct =
101+
new Product(FLAMETHROWER.getId(), "Other description", new Dimensions(1, 1, 1));
102+
assertThat(
103+
SESSION_RULE
104+
.session()
105+
.execute(dao.updateIfLengthStatement(otherProduct, 10))
106+
.wasApplied())
107+
.isEqualTo(true);
108+
}
109+
93110
@Test
94111
public void should_not_update_entity_if_condition_is_not_met() {
95112
dao.update(
@@ -101,6 +118,22 @@ public void should_not_update_entity_if_condition_is_not_met() {
101118
assertThat(dao.updateIfLength(otherProduct, 20).wasApplied()).isEqualTo(false);
102119
}
103120

121+
@Test
122+
public void should_not_update_entity_if_condition_is_not_met_statement() {
123+
dao.update(
124+
new Product(FLAMETHROWER.getId(), "Description for length 10", new Dimensions(10, 1, 1)));
125+
assertThat(dao.findById(FLAMETHROWER.getId())).isNotNull();
126+
127+
Product otherProduct =
128+
new Product(FLAMETHROWER.getId(), "Other description", new Dimensions(1, 1, 1));
129+
assertThat(
130+
SESSION_RULE
131+
.session()
132+
.execute(dao.updateIfLengthStatement(otherProduct, 20))
133+
.wasApplied())
134+
.isEqualTo(false);
135+
}
136+
104137
@Test
105138
public void should_async_update_entity_if_condition_is_met() {
106139
dao.update(
@@ -144,6 +177,9 @@ public interface ProductDao {
144177
@Update(customIfClause = "dimensions.length = :length")
145178
ResultSet updateIfLength(Product product, int length);
146179

180+
@Update(customIfClause = "dimensions.length = :length")
181+
BoundStatement updateIfLengthStatement(Product product, int length);
182+
147183
@Update(customIfClause = "dimensions.length = :\"Length\"")
148184
CompletableFuture<AsyncResultSet> updateIfLengthAsync(
149185
Product product, @CqlName("\"Length\"") int length);

0 commit comments

Comments
 (0)