From 424a2575c4153ec40a43fa5b934f72a2afd9cd14 Mon Sep 17 00:00:00 2001 From: Erik Merkle Date: Mon, 1 Apr 2019 14:24:21 -0500 Subject: [PATCH] JAVA-2210: Add ability to set TTL for modification queries --- changelog/README.md | 1 + manual/query_builder/insert/README.md | 29 ++- manual/query_builder/update/README.md | 33 +++- query-builder/revapi.json | 20 +++ .../api/querybuilder/insert/Insert.java | 28 +++ .../api/querybuilder/update/UpdateStart.java | 28 +++ .../querybuilder/insert/DefaultInsert.java | 96 +++++++++- .../querybuilder/update/DefaultUpdate.java | 65 ++++++- .../insert/RegularInsertTest.java | 65 +++++++ .../update/UpdateTimestampTest.java | 56 ------ .../querybuilder/update/UpdateUsingTest.java | 169 ++++++++++++++++++ 11 files changed, 518 insertions(+), 72 deletions(-) delete mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateTimestampTest.java create mode 100644 query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateUsingTest.java diff --git a/changelog/README.md b/changelog/README.md index 18c6aa4372d..0c2e00149d4 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -4,6 +4,7 @@ ### 4.0.1 (in progress) +- [bug] JAVA-2210: Add ability to set TTL for modification queries - [improvement] JAVA-2212: Add truncate to QueryBuilder - [improvement] JAVA-2211: Upgrade Jersey examples to fix security issue sid-3606 - [bug] JAVA-2193: Fix flaky tests in ExecutionInfoWarningsIT diff --git a/manual/query_builder/insert/README.md b/manual/query_builder/insert/README.md index 2fcd7117332..0d1ceb75cdd 100644 --- a/manual/query_builder/insert/README.md +++ b/manual/query_builder/insert/README.md @@ -87,4 +87,31 @@ insertInto("user").json(bindMarker()).usingTimestamp(bindMarker()) If you call the method multiple times, the last value will be used. -[QueryBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilder.html +### Time To Live (TTL) + +You can generate a USING TTL clause that will cause column values to be deleted (marked with a +tombstone) after the specified time (in seconds) has expired. This can be done with a literal: + +```java +insertInto("user").value("a", bindMarker()).usingTtl(60) +// INSERT INTO user (a) VALUES (?) USING TTL 60 +``` + +Or a bind marker: + +```java +insertInto("user").value("a", bindMarker()).usingTtl(bindMarker()) +// INSERT INTO user (a) VALUES (?) USING TTL ? +``` + +If you call the method multiple times, the last value will be used. + +The TTL value applies only to the inserted data, not the entire column. Any subsequent updates to +the column resets the TTL. + +Setting the value to 0 will result in removing the TTL for the inserted data in Cassandra when the query +is executed. This is distinctly different than setting the value to null. Passing a null value to +this method will only remove the USING TTL clause from the query, which will not alter the TTL (if +one is set) in Cassandra. + +[QueryBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/QueryBuilder.html \ No newline at end of file diff --git a/manual/query_builder/update/README.md b/manual/query_builder/update/README.md index bc05f89157e..59dec2f9cfc 100644 --- a/manual/query_builder/update/README.md +++ b/manual/query_builder/update/README.md @@ -32,6 +32,35 @@ update("user").usingTimestamp(bindMarker()); If you call the method multiple times, the last value will be used. +### Time To Live (TTL) + +You can generate a USING TTL clause that will cause column values to be deleted (marked with a +tombstone) after the specified time (in seconds) has expired. This can be done with a literal: + +```java +update("user").usingTtl(60).setColumn("v", bindMarker()).whereColumn("k").isEqualTo(bindMarker()); +// UPDATE user USING TTL 60 SET v=? WHERE k=? +``` + +Or a bind marker: + +```java +update("user").usingTtl(bindMarker()).setColumn("v", bindMarker()).whereColumn("k").isEqualTo(bindMarker()); +// UPDATE user USING TTL ? SET v=? WHERE k=? +``` + +You can clear a previously set TTL by setting the value to 0: + +```java +update("user").usingTtl(0).setColumn("v", bindMarker()).whereColumn("k").isEqualTo(bindMarker()); +// UPDATE user USING TTL 0 SET v=? WHERE k=? +``` + +Setting the value to 0 will result in removing the TTL from the column in Cassandra when the query +is executed. This is distinctly different than setting the value to null. Passing a null value to +this method will only remove the USING TTL clause from the query, which will not alter the TTL (if +one is set) in Cassandra. + ### Assignments An assignment is an operation that appears after the SET keyword. You need at least one for a valid @@ -222,5 +251,5 @@ update("foo") Conditions are a common feature used by UPDATE and DELETE, so they have a [dedicated page](../condition) in this manual. -[QueryBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/QueryBuilder.html -[Assignment]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/query-builder/update/Assignment.html +[QueryBuilder]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/QueryBuilder.html +[Assignment]: http://docs.datastax.com/en/drivers/java/4.0/com/datastax/oss/driver/api/querybuilder/update/Assignment.html diff --git a/query-builder/revapi.json b/query-builder/revapi.json index 818c85d0d1b..0cf4e85f90d 100644 --- a/query-builder/revapi.json +++ b/query-builder/revapi.json @@ -2752,6 +2752,26 @@ "new": "method SelfT com.datastax.oss.driver.api.querybuilder.relation.OngoingWhereClause>>::whereRaw(java.lang.String) @ com.datastax.oss.driver.api.querybuilder.update.UpdateWithAssignments", "annotation": "@edu.umd.cs.findbugs.annotations.CheckReturnValue", "justification": "JAVA-2161: Annotate mutating methods with @CheckReturnValue" + }, + { + "code": "java.method.addedToInterface", + "new": "method com.datastax.oss.driver.api.querybuilder.insert.Insert com.datastax.oss.driver.api.querybuilder.insert.Insert::usingTtl(com.datastax.oss.driver.api.querybuilder.BindMarker)", + "justification": "JAVA-2210: Add ability to set TTL for modification queries" + }, + { + "code": "java.method.addedToInterface", + "new": "method com.datastax.oss.driver.api.querybuilder.insert.Insert com.datastax.oss.driver.api.querybuilder.insert.Insert::usingTtl(int)", + "justification": "JAVA-2210: Add ability to set TTL for modification queries" + }, + { + "code": "java.method.addedToInterface", + "new": "method com.datastax.oss.driver.api.querybuilder.update.UpdateStart com.datastax.oss.driver.api.querybuilder.update.UpdateStart::usingTtl(com.datastax.oss.driver.api.querybuilder.BindMarker)", + "justification": "JAVA-2210: Add ability to set TTL for modification queries" + }, + { + "code": "java.method.addedToInterface", + "new": "method com.datastax.oss.driver.api.querybuilder.update.UpdateStart com.datastax.oss.driver.api.querybuilder.update.UpdateStart::usingTtl(int)", + "justification": "JAVA-2210: Add ability to set TTL for modification queries" } ] } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/Insert.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/Insert.java index 039965aba5d..929797c9d1b 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/Insert.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/insert/Insert.java @@ -44,4 +44,32 @@ public interface Insert extends BuildableQuery { */ @NonNull Insert usingTimestamp(@Nullable BindMarker bindMarker); + + /** + * Adds a {@code USING TTL} clause to this statement with a literal value. Setting a value of + * {@code null} will remove the {@code USING TTL} clause on this statement. Setting a value of + * {@code 0} will insert the data with no TTL when the statement is executed, overriding any Table + * TTL that might exist. + * + *

If this method or {@link #usingTtl(BindMarker) } is called multiple times, the value from + * the last invocation is used. + * + * @param ttlInSeconds Time, in seconds, the inserted data should live before expiring. + */ + @NonNull + Insert usingTtl(int ttlInSeconds); + + /** + * Adds a {@code USING TTL} clause to this statement with a bind marker. Setting a value of {@code + * null} will remove the {@code USING TTL} clause on this statement. Binding a value of {@code 0} + * will insert the data with no TTL when the statement is executed, overriding any Table TTL that + * might exist. + * + *

If this method or {@link #usingTtl(int) } is called multiple times, the value from the last + * invocation is used. + * + * @param bindMarker A bind marker that is understood to be a value in seconds. + */ + @NonNull + Insert usingTtl(@Nullable BindMarker bindMarker); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/UpdateStart.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/UpdateStart.java index e0dd69167e8..89d93b8b7c3 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/UpdateStart.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/update/UpdateStart.java @@ -41,4 +41,32 @@ public interface UpdateStart extends OngoingAssignment { */ @NonNull UpdateStart usingTimestamp(@NonNull BindMarker bindMarker); + + /** + * Adds a {@code USING TTL} clause to this statement with a literal value. Setting a value of + * {@code null} will remove the {@code USING TTL} clause on this statement. Setting a value of + * {@code 0} will update the data and remove any TTL on the column when the statement is executed, + * overriding any TTL (table or column) that may exist in Cassandra. + * + *

If this method or {@link #usingTtl(BindMarker) } is called multiple times, the value from + * the last invocation is used. + * + * @param ttlInSeconds Time, in seconds, the inserted data should live before expiring. + */ + @NonNull + UpdateStart usingTtl(int ttlInSeconds); + + /** + * Adds a {@code USING TTL} clause to this statement with a bind marker. Setting a value of {@code + * null} will remove the {@code USING TTL} clause on this statement. Binding a value of {@code 0} + * will update the data and remove any TTL on the column when the statement is executed, + * overriding any TTL (table or column) that may exist in Cassandra. + * + *

If this method or {@link #usingTtl(int)} is called multiple times, the value from the last + * invocation is used. + * + * @param bindMarker A bind marker that is understood to be a value in seconds. + */ + @NonNull + UpdateStart usingTtl(@NonNull BindMarker bindMarker); } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java index 0e51c316dc5..e410765dd96 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/insert/DefaultInsert.java @@ -49,10 +49,11 @@ public enum MissingJsonBehavior { private final MissingJsonBehavior missingJsonBehavior; private final ImmutableMap assignments; private final Object timestamp; + private final Object ttlInSeconds; private final boolean ifNotExists; public DefaultInsert(@Nullable CqlIdentifier keyspace, @NonNull CqlIdentifier table) { - this(keyspace, table, null, null, ImmutableMap.of(), null, false); + this(keyspace, table, null, null, ImmutableMap.of(), null, null, false); } public DefaultInsert( @@ -62,17 +63,27 @@ public DefaultInsert( @Nullable MissingJsonBehavior missingJsonBehavior, @NonNull ImmutableMap assignments, @Nullable Object timestamp, + @Nullable Object ttlInSeconds, boolean ifNotExists) { // Note: the public API guarantees this, but check in case someone is calling the internal API // directly. Preconditions.checkArgument( json == null || assignments.isEmpty(), "JSON insert can't have regular assignments"); + Preconditions.checkArgument( + timestamp == null || timestamp instanceof Long || timestamp instanceof BindMarker, + "TIMESTAMP value must be a BindMarker or a Long"); + Preconditions.checkArgument( + ttlInSeconds == null + || ttlInSeconds instanceof Integer + || ttlInSeconds instanceof BindMarker, + "TTL value must be a BindMarker or an Integer"); this.keyspace = keyspace; this.table = table; this.json = json; this.missingJsonBehavior = missingJsonBehavior; this.assignments = assignments; this.timestamp = timestamp; + this.ttlInSeconds = ttlInSeconds; this.ifNotExists = ifNotExists; } @@ -86,6 +97,7 @@ public JsonInsert json(@NonNull String json) { missingJsonBehavior, ImmutableMap.of(), timestamp, + ttlInSeconds, ifNotExists); } @@ -93,7 +105,14 @@ public JsonInsert json(@NonNull String json) { @Override public JsonInsert json(@NonNull BindMarker json) { return new DefaultInsert( - keyspace, table, json, missingJsonBehavior, ImmutableMap.of(), timestamp, ifNotExists); + keyspace, + table, + json, + missingJsonBehavior, + ImmutableMap.of(), + timestamp, + ttlInSeconds, + ifNotExists); } @NonNull @@ -106,6 +125,7 @@ public JsonInsert json(@NonNull T value, @NonNull TypeCodec codec) { missingJsonBehavior, ImmutableMap.of(), timestamp, + ttlInSeconds, ifNotExists); } @@ -113,7 +133,14 @@ public JsonInsert json(@NonNull T value, @NonNull TypeCodec codec) { @Override public JsonInsert defaultNull() { return new DefaultInsert( - keyspace, table, json, MissingJsonBehavior.NULL, ImmutableMap.of(), timestamp, ifNotExists); + keyspace, + table, + json, + MissingJsonBehavior.NULL, + ImmutableMap.of(), + timestamp, + ttlInSeconds, + ifNotExists); } @NonNull @@ -126,6 +153,7 @@ public JsonInsert defaultUnset() { MissingJsonBehavior.UNSET, ImmutableMap.of(), timestamp, + ttlInSeconds, ifNotExists); } @@ -139,6 +167,7 @@ public RegularInsert value(@NonNull CqlIdentifier columnId, @NonNull Term value) null, ImmutableCollections.append(assignments, columnId, value), timestamp, + ttlInSeconds, ifNotExists); } @@ -146,21 +175,63 @@ public RegularInsert value(@NonNull CqlIdentifier columnId, @NonNull Term value) @Override public Insert ifNotExists() { return new DefaultInsert( - keyspace, table, json, missingJsonBehavior, assignments, timestamp, true); + keyspace, table, json, missingJsonBehavior, assignments, timestamp, ttlInSeconds, true); } @NonNull @Override public Insert usingTimestamp(long timestamp) { return new DefaultInsert( - keyspace, table, json, missingJsonBehavior, assignments, timestamp, ifNotExists); + keyspace, + table, + json, + missingJsonBehavior, + assignments, + timestamp, + ttlInSeconds, + ifNotExists); } @NonNull @Override public Insert usingTimestamp(@Nullable BindMarker timestamp) { return new DefaultInsert( - keyspace, table, json, missingJsonBehavior, assignments, timestamp, ifNotExists); + keyspace, + table, + json, + missingJsonBehavior, + assignments, + timestamp, + ttlInSeconds, + ifNotExists); + } + + @NonNull + @Override + public Insert usingTtl(int ttlInSeconds) { + return new DefaultInsert( + keyspace, + table, + json, + missingJsonBehavior, + assignments, + timestamp, + ttlInSeconds, + ifNotExists); + } + + @NonNull + @Override + public Insert usingTtl(@Nullable BindMarker ttlInSeconds) { + return new DefaultInsert( + keyspace, + table, + json, + missingJsonBehavior, + assignments, + timestamp, + ttlInSeconds, + ifNotExists); } @NonNull @@ -192,6 +263,14 @@ public String asCql() { builder.append(timestamp); } } + if (ttlInSeconds != null) { + builder.append((timestamp != null) ? " AND " : " USING ").append("TTL "); + if (ttlInSeconds instanceof BindMarker) { + ((BindMarker) ttlInSeconds).appendTo(builder); + } else { + builder.append(ttlInSeconds); + } + } return builder.toString(); } @@ -267,6 +346,11 @@ public Object getTimestamp() { return timestamp; } + @Nullable + public Object getTtlInSeconds() { + return ttlInSeconds; + } + public boolean isIfNotExists() { return ifNotExists; } diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultUpdate.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultUpdate.java index 5428f5b3b01..0e864f28e3b 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultUpdate.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/update/DefaultUpdate.java @@ -27,6 +27,7 @@ import com.datastax.oss.driver.api.querybuilder.update.UpdateWithAssignments; import com.datastax.oss.driver.internal.querybuilder.CqlHelper; import com.datastax.oss.driver.internal.querybuilder.ImmutableCollections; +import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -39,26 +40,46 @@ public class DefaultUpdate implements UpdateStart, UpdateWithAssignments, Update private final CqlIdentifier keyspace; private final CqlIdentifier table; private final Object timestamp; + private final Object ttlInSeconds; private final ImmutableList assignments; private final ImmutableList relations; private final boolean ifExists; private final ImmutableList conditions; public DefaultUpdate(@Nullable CqlIdentifier keyspace, @NonNull CqlIdentifier table) { - this(keyspace, table, null, ImmutableList.of(), ImmutableList.of(), false, ImmutableList.of()); + this( + keyspace, + table, + null, + null, + ImmutableList.of(), + ImmutableList.of(), + false, + ImmutableList.of()); } public DefaultUpdate( @Nullable CqlIdentifier keyspace, @NonNull CqlIdentifier table, @Nullable Object timestamp, + @Nullable Object ttlInSeconds, @NonNull ImmutableList assignments, @NonNull ImmutableList relations, boolean ifExists, @NonNull ImmutableList conditions) { + Preconditions.checkArgument( + timestamp == null || timestamp instanceof Long || timestamp instanceof BindMarker, + "TIMESTAMP value must be a BindMarker or a Long"); + Preconditions.checkArgument( + ttlInSeconds == null + || ttlInSeconds instanceof Integer + || ttlInSeconds instanceof BindMarker, + "TTL value must be a BindMarker or an Integer"); + this.keyspace = keyspace; this.table = table; this.timestamp = timestamp; + this.ttlInSeconds = ttlInSeconds; this.assignments = assignments; this.relations = relations; this.ifExists = ifExists; @@ -69,14 +90,28 @@ public DefaultUpdate( @Override public UpdateStart usingTimestamp(long newTimestamp) { return new DefaultUpdate( - keyspace, table, newTimestamp, assignments, relations, ifExists, conditions); + keyspace, table, newTimestamp, ttlInSeconds, assignments, relations, ifExists, conditions); } @NonNull @Override public UpdateStart usingTimestamp(@NonNull BindMarker newTimestamp) { return new DefaultUpdate( - keyspace, table, newTimestamp, assignments, relations, ifExists, conditions); + keyspace, table, newTimestamp, ttlInSeconds, assignments, relations, ifExists, conditions); + } + + @NonNull + @Override + public UpdateStart usingTtl(int ttlInSeconds) { + return new DefaultUpdate( + keyspace, table, timestamp, ttlInSeconds, assignments, relations, ifExists, conditions); + } + + @NonNull + @Override + public UpdateStart usingTtl(@NonNull BindMarker ttlInSeconds) { + return new DefaultUpdate( + keyspace, table, timestamp, ttlInSeconds, assignments, relations, ifExists, conditions); } @NonNull @@ -94,7 +129,7 @@ public UpdateWithAssignments set(@NonNull Iterable additionalAssignm @NonNull public UpdateWithAssignments withAssignments(@NonNull ImmutableList newAssignments) { return new DefaultUpdate( - keyspace, table, timestamp, newAssignments, relations, ifExists, conditions); + keyspace, table, timestamp, ttlInSeconds, newAssignments, relations, ifExists, conditions); } @NonNull @@ -112,13 +147,14 @@ public Update where(@NonNull Iterable additionalRelations) { @NonNull public Update withRelations(@NonNull ImmutableList newRelations) { return new DefaultUpdate( - keyspace, table, timestamp, assignments, newRelations, ifExists, conditions); + keyspace, table, timestamp, ttlInSeconds, assignments, newRelations, ifExists, conditions); } @NonNull @Override public Update ifExists() { - return new DefaultUpdate(keyspace, table, timestamp, assignments, relations, true, conditions); + return new DefaultUpdate( + keyspace, table, timestamp, ttlInSeconds, assignments, relations, true, conditions); } @NonNull @@ -136,7 +172,7 @@ public Update if_(@NonNull Iterable additionalConditions) { @NonNull public Update withConditions(@NonNull ImmutableList newConditions) { return new DefaultUpdate( - keyspace, table, timestamp, assignments, relations, false, newConditions); + keyspace, table, timestamp, ttlInSeconds, assignments, relations, false, newConditions); } @NonNull @@ -154,6 +190,16 @@ public String asCql() { } } + if (ttlInSeconds != null) { + // choose the correct keyword based on whether or not we have a timestamp + builder.append((timestamp != null) ? " AND " : " USING ").append("TTL "); + if (ttlInSeconds instanceof BindMarker) { + ((BindMarker) ttlInSeconds).appendTo(builder); + } else { + builder.append(ttlInSeconds); + } + } + CqlHelper.append(assignments, builder, " SET ", ", ", null); CqlHelper.append(relations, builder, " WHERE ", " AND ", null); @@ -227,6 +273,11 @@ public Object getTimestamp() { return timestamp; } + @Nullable + public Object getTtl() { + return ttlInSeconds; + } + @NonNull public ImmutableList getAssignments() { return assignments; diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/insert/RegularInsertTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/insert/RegularInsertTest.java index c445411b88c..1a41841dc9c 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/insert/RegularInsertTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/insert/RegularInsertTest.java @@ -20,10 +20,16 @@ import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.insertInto; import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; +import com.datastax.oss.driver.api.querybuilder.term.Term; +import com.datastax.oss.driver.internal.querybuilder.insert.DefaultInsert; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; public class RegularInsertTest { + @Rule public ExpectedException thrown = ExpectedException.none(); + @Test public void should_generate_column_assignments() { assertThat(insertInto("foo").value("a", literal(1)).value("b", literal(2))) @@ -74,4 +80,63 @@ public void should_generate_if_not_exists_and_timestamp_clauses() { assertThat(insertInto("foo").value("a", bindMarker()).ifNotExists().usingTimestamp(1)) .hasCql("INSERT INTO foo (a) VALUES (?) IF NOT EXISTS USING TIMESTAMP 1"); } + + @Test + public void should_generate_ttl_clause() { + assertThat(insertInto("foo").value("a", bindMarker()).usingTtl(10)) + .hasCql("INSERT INTO foo (a) VALUES (?) USING TTL 10"); + } + + @Test + public void should_use_last_ttl_if_called_multiple_times() { + assertThat(insertInto("foo").value("a", bindMarker()).usingTtl(10).usingTtl(20).usingTtl(30)) + .hasCql("INSERT INTO foo (a) VALUES (?) USING TTL 30"); + } + + @Test + public void should_generate_using_timestamp_and_ttl_clauses() { + assertThat(insertInto("foo").value("a", bindMarker()).usingTtl(10).usingTimestamp(30l)) + .hasCql("INSERT INTO foo (a) VALUES (?) USING TIMESTAMP 30 AND TTL 10"); + // order of TTL and TIMESTAMP method calls should not change the order of the generated clauses + assertThat(insertInto("foo").value("a", bindMarker()).usingTimestamp(30l).usingTtl(10)) + .hasCql("INSERT INTO foo (a) VALUES (?) USING TIMESTAMP 30 AND TTL 10"); + } + + @Test + public void should_throw_exception_with_invalid_ttl() { + DefaultInsert defaultInsert = + (DefaultInsert) insertInto("foo").value("a", bindMarker()).usingTtl(10); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("TTL value must be a BindMarker or an Integer"); + + new DefaultInsert( + defaultInsert.getKeyspace(), + defaultInsert.getTable(), + (Term) defaultInsert.getJson(), + defaultInsert.getMissingJsonBehavior(), + defaultInsert.getAssignments(), + defaultInsert.getTimestamp(), + new Object(), // invalid TTL object + defaultInsert.isIfNotExists()); + } + + @Test + public void should_throw_exception_with_invalid_timestamp() { + DefaultInsert defaultInsert = + (DefaultInsert) insertInto("foo").value("a", bindMarker()).usingTimestamp(1); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("TIMESTAMP value must be a BindMarker or a Long"); + + new DefaultInsert( + defaultInsert.getKeyspace(), + defaultInsert.getTable(), + (Term) defaultInsert.getJson(), + defaultInsert.getMissingJsonBehavior(), + defaultInsert.getAssignments(), + new Object(), // invalid timestamp object) + defaultInsert.getTtlInSeconds(), + defaultInsert.isIfNotExists()); + } } diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateTimestampTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateTimestampTest.java deleted file mode 100644 index 6ae165bfa19..00000000000 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateTimestampTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed 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. - */ -package com.datastax.oss.driver.api.querybuilder.update; - -import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; -import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.update; - -import org.junit.Test; - -public class UpdateTimestampTest { - - @Test - public void should_generate_using_timestamp_clause() { - assertThat( - update("foo") - .usingTimestamp(1) - .setColumn("v", bindMarker()) - .whereColumn("k") - .isEqualTo(bindMarker())) - .hasCql("UPDATE foo USING TIMESTAMP 1 SET v=? WHERE k=?"); - assertThat( - update("foo") - .usingTimestamp(bindMarker()) - .setColumn("v", bindMarker()) - .whereColumn("k") - .isEqualTo(bindMarker())) - .hasCql("UPDATE foo USING TIMESTAMP ? SET v=? WHERE k=?"); - } - - @Test - public void should_use_last_timestamp_if_called_multiple_times() { - assertThat( - update("foo") - .usingTimestamp(1) - .usingTimestamp(2) - .usingTimestamp(3) - .setColumn("v", bindMarker()) - .whereColumn("k") - .isEqualTo(bindMarker())) - .hasCql("UPDATE foo USING TIMESTAMP 3 SET v=? WHERE k=?"); - } -} diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateUsingTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateUsingTest.java new file mode 100644 index 00000000000..591a53f2200 --- /dev/null +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/update/UpdateUsingTest.java @@ -0,0 +1,169 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed 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. + */ +package com.datastax.oss.driver.api.querybuilder.update; + +import static com.datastax.oss.driver.api.querybuilder.Assertions.assertThat; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.update; + +import com.datastax.oss.driver.internal.querybuilder.update.DefaultUpdate; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class UpdateUsingTest { + + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test + public void should_generate_using_timestamp_clause() { + assertThat( + update("foo") + .usingTimestamp(1) + .setColumn("v", bindMarker()) + .whereColumn("k") + .isEqualTo(bindMarker())) + .hasCql("UPDATE foo USING TIMESTAMP 1 SET v=? WHERE k=?"); + assertThat( + update("foo") + .usingTimestamp(bindMarker()) + .setColumn("v", bindMarker()) + .whereColumn("k") + .isEqualTo(bindMarker())) + .hasCql("UPDATE foo USING TIMESTAMP ? SET v=? WHERE k=?"); + } + + @Test + public void should_use_last_timestamp_if_called_multiple_times() { + assertThat( + update("foo") + .usingTimestamp(1) + .usingTimestamp(2) + .usingTimestamp(3) + .setColumn("v", bindMarker()) + .whereColumn("k") + .isEqualTo(bindMarker())) + .hasCql("UPDATE foo USING TIMESTAMP 3 SET v=? WHERE k=?"); + } + + @Test + public void should_generate_using_ttl_clause() { + assertThat( + update("foo") + .usingTtl(10) + .setColumn("v", bindMarker()) + .whereColumn("k") + .isEqualTo(bindMarker())) + .hasCql("UPDATE foo USING TTL 10 SET v=? WHERE k=?"); + assertThat( + update("foo") + .usingTtl(bindMarker()) + .setColumn("v", bindMarker()) + .whereColumn("k") + .isEqualTo(bindMarker())) + .hasCql("UPDATE foo USING TTL ? SET v=? WHERE k=?"); + } + + @Test + public void should_use_last_ttl_if_called_multiple_times() { + assertThat( + update("foo") + .usingTtl(10) + .usingTtl(20) + .usingTtl(30) + .setColumn("v", bindMarker()) + .whereColumn("k") + .isEqualTo(bindMarker())) + .hasCql("UPDATE foo USING TTL 30 SET v=? WHERE k=?"); + } + + @Test + public void should_generate_using_ttl_and_timestamp_clauses() { + assertThat( + update("foo") + .usingTtl(10) + .usingTimestamp(1) + .setColumn("v", bindMarker()) + .whereColumn("k") + .isEqualTo(bindMarker())) + .hasCql("UPDATE foo USING TIMESTAMP 1 AND TTL 10 SET v=? WHERE k=?"); + // order of TTL and TIMESTAMP method calls should not change the order of the generated clauses + assertThat( + update("foo") + .usingTimestamp(1) + .usingTtl(10) + .setColumn("v", bindMarker()) + .whereColumn("k") + .isEqualTo(bindMarker())) + .hasCql("UPDATE foo USING TIMESTAMP 1 AND TTL 10 SET v=? WHERE k=?"); + assertThat( + update("foo") + .usingTtl(bindMarker()) + .usingTimestamp(1) + .setColumn("v", bindMarker()) + .whereColumn("k") + .isEqualTo(bindMarker())) + .hasCql("UPDATE foo USING TIMESTAMP 1 AND TTL ? SET v=? WHERE k=?"); + } + + @Test + public void should_throw_exception_with_invalid_ttl() { + DefaultUpdate defaultUpdate = + (DefaultUpdate) + update("foo") + .usingTtl(10) + .setColumn("v", bindMarker()) + .whereColumn("k") + .isEqualTo(bindMarker()); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("TTL value must be a BindMarker or an Integer"); + + new DefaultUpdate( + defaultUpdate.getKeyspace(), + defaultUpdate.getTable(), + defaultUpdate.getTimestamp(), + new Object(), // invalid TTL object + defaultUpdate.getAssignments(), + defaultUpdate.getRelations(), + defaultUpdate.isIfExists(), + defaultUpdate.getConditions()); + } + + @Test + public void should_throw_exception_with_invalid_timestamp() { + DefaultUpdate defaultUpdate = + (DefaultUpdate) + update("foo") + .usingTtl(10) + .setColumn("v", bindMarker()) + .whereColumn("k") + .isEqualTo(bindMarker()); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("TIMESTAMP value must be a BindMarker or a Long"); + + new DefaultUpdate( + defaultUpdate.getKeyspace(), + defaultUpdate.getTable(), + new Object(), // invalid timestamp object + defaultUpdate.getTtl(), + defaultUpdate.getAssignments(), + defaultUpdate.getRelations(), + defaultUpdate.isIfExists(), + defaultUpdate.getConditions()); + } +}