Skip to content

Commit c3c9b4a

Browse files
committed
Add BYPASS CACHE and USING TIMEOUT capabilities to mapper
Now @select, @insert and @update annotations used in DAOs can specify (usingTimeout = "durationString") in order to use Scylla CQL extension timeouts. Fixes #168
1 parent e561a2d commit c3c9b4a

File tree

8 files changed

+218
-7
lines changed

8 files changed

+218
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package com.datastax.oss.driver.mapper;
2+
3+
import static org.junit.Assert.assertThrows;
4+
import static org.junit.Assert.assertTrue;
5+
6+
import com.datastax.oss.driver.api.core.CqlIdentifier;
7+
import com.datastax.oss.driver.api.core.CqlSession;
8+
import com.datastax.oss.driver.api.core.PagingIterable;
9+
import com.datastax.oss.driver.api.core.cql.ResultSet;
10+
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
11+
import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException;
12+
import com.datastax.oss.driver.api.mapper.annotations.Dao;
13+
import com.datastax.oss.driver.api.mapper.annotations.DaoFactory;
14+
import com.datastax.oss.driver.api.mapper.annotations.DaoKeyspace;
15+
import com.datastax.oss.driver.api.mapper.annotations.Entity;
16+
import com.datastax.oss.driver.api.mapper.annotations.Insert;
17+
import com.datastax.oss.driver.api.mapper.annotations.Mapper;
18+
import com.datastax.oss.driver.api.mapper.annotations.PartitionKey;
19+
import com.datastax.oss.driver.api.mapper.annotations.Select;
20+
import com.datastax.oss.driver.api.mapper.annotations.Update;
21+
import com.datastax.oss.driver.api.testinfra.CassandraSkip;
22+
import com.datastax.oss.driver.api.testinfra.ScyllaRequirement;
23+
import com.datastax.oss.driver.api.testinfra.ccm.CcmRule;
24+
import com.datastax.oss.driver.api.testinfra.session.SessionRule;
25+
import com.datastax.oss.driver.categories.ParallelizableTests;
26+
import org.junit.BeforeClass;
27+
import org.junit.ClassRule;
28+
import org.junit.Test;
29+
import org.junit.experimental.categories.Category;
30+
import org.junit.rules.RuleChain;
31+
import org.junit.rules.TestRule;
32+
33+
@Category(ParallelizableTests.class)
34+
@CassandraSkip
35+
@ScyllaRequirement(minOSS = "3.1.0", minEnterprise = "2020.1.0")
36+
public class UsingTimeoutIT {
37+
private static final CcmRule CCM_RULE = CcmRule.getInstance();
38+
private static final SessionRule<CqlSession> SESSION_RULE = SessionRule.builder(CCM_RULE).build();
39+
40+
@ClassRule
41+
public static final TestRule CHAIN = RuleChain.outerRule(CCM_RULE).around(SESSION_RULE);
42+
43+
private static TimeoutTestEntityDao dao;
44+
private static TimeoutTestMapper mapper;
45+
46+
@BeforeClass
47+
public static void setup() {
48+
CqlSession session = SESSION_RULE.session();
49+
50+
session.execute(
51+
SimpleStatement.newInstance(
52+
"CREATE TABLE timeout_test_entity(id int, col int, PRIMARY KEY (id))")
53+
.setExecutionProfile(SESSION_RULE.slowProfile()));
54+
55+
mapper = new UsingTimeoutIT_TimeoutTestMapperBuilder(session).build();
56+
dao = mapper.timeoutTestEntityDao(SESSION_RULE.keyspace());
57+
}
58+
59+
@Test
60+
public void ensure_use_of_timeout() {
61+
Exception exInsert =
62+
assertThrows(InvalidQueryException.class, () -> dao.save(new TimeoutTestEntity(1, 1)));
63+
Exception exSelect = assertThrows(InvalidQueryException.class, () -> dao.findById(1));
64+
Exception exUpdate =
65+
assertThrows(InvalidQueryException.class, () -> dao.update(new TimeoutTestEntity(1, 2)));
66+
assertTrue(exInsert.getMessage().contains("Timeout values must be non-negative"));
67+
assertTrue(exSelect.getMessage().contains("Timeout values must be non-negative"));
68+
assertTrue(exUpdate.getMessage().contains("Timeout values must be non-negative"));
69+
}
70+
71+
@Entity
72+
public static class TimeoutTestEntity {
73+
@PartitionKey private Integer id;
74+
private Integer col;
75+
76+
public TimeoutTestEntity() {}
77+
78+
public TimeoutTestEntity(Integer id, Integer col) {
79+
this.id = id;
80+
this.col = col;
81+
}
82+
83+
public Integer getId() {
84+
return id;
85+
}
86+
87+
public void setId(Integer id) {
88+
this.id = id;
89+
}
90+
91+
public Integer getCol() {
92+
return col;
93+
}
94+
95+
public void setCol(Integer col) {
96+
this.col = col;
97+
}
98+
}
99+
100+
@Dao
101+
public interface TimeoutTestEntityDao {
102+
@Select(usingTimeout = "-5s")
103+
PagingIterable<TimeoutTestEntity> findById(Integer id);
104+
105+
@Insert(usingTimeout = "-5s")
106+
ResultSet save(TimeoutTestEntity timeoutTestEntity);
107+
108+
@Update(usingTimeout = "-5s")
109+
ResultSet update(TimeoutTestEntity timeoutTestEntity);
110+
}
111+
112+
@Mapper
113+
public interface TimeoutTestMapper {
114+
@DaoFactory
115+
TimeoutTestEntityDao timeoutTestEntityDao(@DaoKeyspace CqlIdentifier keyspace);
116+
}
117+
}

Diff for: mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/dao/DaoInsertMethodGenerator.java

+1
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ private void generatePrepareRequest(
193193

194194
maybeAddTtl(annotation.ttl(), methodBuilder);
195195
maybeAddTimestamp(annotation.timestamp(), methodBuilder);
196+
maybeAddTimeout(annotation.usingTimeout(), methodBuilder);
196197

197198
methodBuilder.addCode(".build()$];\n");
198199
}

Diff for: mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/dao/DaoMethodGenerator.java

+40
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import com.datastax.oss.driver.api.core.CqlIdentifier;
2121
import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder;
22+
import com.datastax.oss.driver.api.core.data.CqlDuration;
2223
import com.datastax.oss.driver.api.mapper.annotations.CqlName;
2324
import com.datastax.oss.driver.api.mapper.annotations.Delete;
2425
import com.datastax.oss.driver.api.mapper.annotations.Increment;
@@ -103,6 +104,45 @@ protected void maybeAddTimestamp(String timestamp, MethodSpec.Builder methodBuil
103104
maybeAddSimpleClause(timestamp, Long::parseLong, "usingTimestamp", "timestamp", methodBuilder);
104105
}
105106

107+
protected void maybeAddTimeout(String timeout, MethodSpec.Builder methodBuilder) {
108+
if (!timeout.isEmpty()) {
109+
if (timeout.startsWith(":")) {
110+
String bindMarkerName = timeout.substring(1);
111+
try {
112+
CqlIdentifier.fromCql(bindMarkerName);
113+
} catch (IllegalArgumentException ignored) {
114+
context
115+
.getMessager()
116+
.warn(
117+
methodElement,
118+
"Invalid "
119+
+ "USING TIMEOUT"
120+
+ " value: "
121+
+ "'%s' is not a valid placeholder, the generated query will probably fail",
122+
timeout);
123+
}
124+
methodBuilder.addCode(
125+
".$L($T.bindMarker($S))", "usingTimeout", QueryBuilder.class, bindMarkerName);
126+
} else {
127+
try {
128+
CqlDuration unused = CqlDuration.from(timeout);
129+
} catch (Exception ex) {
130+
context
131+
.getMessager()
132+
.warn(
133+
methodElement,
134+
"Invalid "
135+
+ "USING TIMEOUT"
136+
+ " value: "
137+
+ "'%s' is not a bind marker name and can't be parsed as a CqlDuration "
138+
+ "either, the generated query will probably fail",
139+
timeout);
140+
}
141+
methodBuilder.addCode(".$L($T.$L($S))", "usingTimeout", CqlDuration.class, "from", timeout);
142+
}
143+
}
144+
}
145+
106146
protected void maybeAddSimpleClause(
107147
String annotationValue,
108148
Function<String, ? extends Number> numberParser,

Diff for: mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/dao/DaoSelectMethodGenerator.java

+4
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ private void generateSelectRequest(
239239
"perPartitionLimit",
240240
"perPartitionLimit",
241241
methodBuilder);
242+
maybeAddTimeout(annotation.usingTimeout(), methodBuilder);
242243
for (String orderingSpec : annotation.orderBy()) {
243244
addOrdering(orderingSpec, methodBuilder);
244245
}
@@ -248,6 +249,9 @@ private void generateSelectRequest(
248249
if (annotation.allowFiltering()) {
249250
methodBuilder.addCode(".allowFiltering()");
250251
}
252+
if (annotation.bypassCache()) {
253+
methodBuilder.addCode(".bypassCache()");
254+
}
251255
methodBuilder.addCode(".build();$]\n");
252256
}
253257

Diff for: mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/dao/DaoUpdateMethodGenerator.java

+1
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ private void generatePrepareRequest(
202202
methodBuilder, requestName, helperFieldName, annotation.customWhereClause());
203203
maybeAddTtl(annotation.ttl(), methodBuilder);
204204
maybeAddTimestamp(annotation.timestamp(), methodBuilder);
205+
maybeAddTimeout(annotation.usingTimeout(), methodBuilder);
205206
methodBuilder.addCode(")");
206207
maybeAddIfClause(methodBuilder, annotation);
207208

Diff for: mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Insert.java

+15
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,21 @@
150150
*/
151151
String ttl() default "";
152152

153+
/**
154+
* The timeout to use in the generated INSERT query. Equivalent to {@code USING TIMEOUT
155+
* <duration>} clause.
156+
*
157+
* <p>If this starts with ":", it is interpreted as a named placeholder (that must have a
158+
* corresponding parameter in the method signature). Otherwise, it must be a String representing a
159+
* valid CqlDuration.
160+
*
161+
* <p>If the placeholder name is invalid or the literal can't be parsed as a CqlDuration
162+
* (according to the rules of {@link
163+
* com.datastax.oss.driver.api.core.data.CqlDuration#from(String)}), the mapper will issue a
164+
* compile-time warning.
165+
*/
166+
String usingTimeout() default "";
167+
153168
/**
154169
* The timestamp to use in the generated INSERT query.
155170
*

Diff for: mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Select.java

+25-7
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,13 @@
6666
* </pre>
6767
*
6868
* The generated SELECT query can be further customized with {@link #limit()}, {@link
69-
* #perPartitionLimit()}, {@link #orderBy()}, {@link #groupBy()} and {@link #allowFiltering()}. Some
70-
* of these clauses can also contain placeholders whose values will be provided through additional
71-
* method parameters. Note that it is sometimes not possible to determine if a parameter is a
72-
* primary key component or a placeholder value; therefore the rule is that <b>if your method takes
73-
* a partial primary key, the first parameter that is not a primary key component must be explicitly
74-
* annotated with {@link CqlName}.</b> For example if the primary key is {@code ((day int, hour int,
75-
* minute int), ts timestamp)}:
69+
* #perPartitionLimit()}, {@link #orderBy()}, {@link #groupBy()}, {@link #allowFiltering()} and
70+
* {@link #bypassCache()}. Some of these clauses can also contain placeholders whose values will be
71+
* provided through additional method parameters. Note that it is sometimes not possible to
72+
* determine if a parameter is a primary key component or a placeholder value; therefore the rule is
73+
* that <b>if your method takes a partial primary key, the first parameter that is not a primary key
74+
* component must be explicitly annotated with {@link CqlName}.</b> For example if the primary key
75+
* is {@code ((day int, hour int, minute int), ts timestamp)}:
7676
*
7777
* <pre>
7878
* // Annotate 'l' so that it's not mistaken for the second PK component
@@ -201,4 +201,22 @@
201201

202202
/** Whether to add an ALLOW FILTERING clause to the SELECT query. */
203203
boolean allowFiltering() default false;
204+
205+
/** Whether to add BYPASS CACHE clause to the SELECT query. */
206+
boolean bypassCache() default false;
207+
208+
/**
209+
* The timeout to use in the generated SELECT query. Equivalent to {@code USING TIMEOUT
210+
* <duration>} clause.
211+
*
212+
* <p>If this starts with ":", it is interpreted as a named placeholder (that must have a
213+
* corresponding parameter in the method signature). Otherwise, it must be a String representing a
214+
* valid CqlDuration.
215+
*
216+
* <p>If the placeholder name is invalid or the literal can't be parsed as a CqlDuration
217+
* (according to the rules of {@link
218+
* com.datastax.oss.driver.api.core.data.CqlDuration#from(String)}), the mapper will issue a
219+
* compile-time warning.
220+
*/
221+
String usingTimeout() default "";
204222
}

Diff for: mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Update.java

+15
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,21 @@
206206
*/
207207
String timestamp() default "";
208208

209+
/**
210+
* The timeout to use in the generated UPDATE query. Equivalent to {@code USING TIMEOUT
211+
* <duration>} clause.
212+
*
213+
* <p>If this starts with ":", it is interpreted as a named placeholder (that must have a
214+
* corresponding parameter in the method signature). Otherwise, it must be a String representing a
215+
* valid CqlDuration.
216+
*
217+
* <p>If the placeholder name is invalid or the literal can't be parsed as a CqlDuration
218+
* (according to the rules of {@link
219+
* com.datastax.oss.driver.api.core.data.CqlDuration#from(String)}), the mapper will issue a
220+
* compile-time warning.
221+
*/
222+
String usingTimeout() default "";
223+
209224
/**
210225
* How to handle null entity properties during the update.
211226
*

0 commit comments

Comments
 (0)