Skip to content

Commit 5c7d8c1

Browse files
author
Alexandre Dutra
committed
JAVA-1446: Support 'DEFAULT UNSET' in Query Builder JSON Insert
1 parent 28fe028 commit 5c7d8c1

5 files changed

Lines changed: 122 additions & 20 deletions

File tree

changelog/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- [documentation] JAVA-1463: Revisit speculative execution docs.
1313
- [documentation] JAVA-1466: Revisit timestamp docs.
1414
- [documentation] JAVA-1445: Clarify how nodes are penalized in LatencyAwarePolicy docs.
15+
- [improvement] JAVA-1446: Support 'DEFAULT UNSET' in Query Builder JSON Insert.
1516

1617

1718
### 3.2.0

driver-core/src/main/java/com/datastax/driver/core/querybuilder/BuiltStatement.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.util.ArrayList;
2424
import java.util.List;
2525
import java.util.Map;
26-
import java.util.regex.Pattern;
2726

2827
/**
2928
* Common ancestor to statements generated with the {@link QueryBuilder}.
@@ -76,8 +75,6 @@
7675
*/
7776
public abstract class BuiltStatement extends RegularStatement {
7877

79-
private static final Pattern lowercaseAlphanumeric = Pattern.compile("[a-z][a-z0-9_]*");
80-
8178
private final List<ColumnMetadata> partitionKey;
8279
private final List<Object> routingKeyValues;
8380
final String keyspace;

driver-core/src/main/java/com/datastax/driver/core/querybuilder/Insert.java

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,21 @@
2727
import static com.google.common.base.Preconditions.checkState;
2828

2929
/**
30-
* A built INSERT statement.
30+
* A built {@code INSERT} statement.
3131
*/
3232
public class Insert extends BuiltStatement {
3333

34+
private enum JsonDefault {
35+
NULL, UNSET
36+
}
37+
3438
private final String table;
3539
private final List<Object> names = new ArrayList<Object>();
3640
private final List<Object> values = new ArrayList<Object>();
3741
private final Options usings;
3842
private boolean ifNotExists;
3943
private Object json;
44+
private JsonDefault jsonDefault;
4045

4146
Insert(String keyspace, String table) {
4247
this(keyspace, table, null, null);
@@ -71,6 +76,10 @@ StringBuilder buildQueryString(List<Object> variables, CodecRegistry codecRegist
7176
if (json != null) {
7277
builder.append("JSON ");
7378
Utils.appendValue(json, codecRegistry, builder, variables);
79+
if (jsonDefault == JsonDefault.UNSET)
80+
builder.append(" DEFAULT UNSET");
81+
else if (jsonDefault == JsonDefault.NULL)
82+
builder.append(" DEFAULT NULL");
7483
} else {
7584
builder.append("(");
7685
Utils.joinAndAppendNames(builder, codecRegistry, names);
@@ -90,17 +99,17 @@ StringBuilder buildQueryString(List<Object> variables, CodecRegistry codecRegist
9099
}
91100

92101
/**
93-
* Adds a column/value pair to the values inserted by this INSERT statement.
102+
* Adds a column/value pair to the values inserted by this {@code INSERT} statement.
94103
*
95104
* @param name the name of the column to insert/update.
96105
* @param value the value to insert/update for {@code name}.
97-
* @return this INSERT statement.
106+
* @return this {@code INSERT} statement.
98107
* @throws IllegalStateException if this method is called and the {@link #json(Object)}
99108
* method has been called before, because it's not possible
100109
* to mix {@code INSERT JSON} syntax with regular {@code INSERT} syntax.
101110
*/
102111
public Insert value(String name, Object value) {
103-
checkState(json == null, "Cannot mix INSERT JSON syntax with regular INSERT syntax");
112+
checkState(json == null && jsonDefault == null, "Cannot mix INSERT JSON syntax with regular INSERT syntax");
104113
names.add(name);
105114
values.add(value);
106115
checkForBindMarkers(value);
@@ -143,7 +152,7 @@ public Insert values(String[] names, Object[] values) {
143152
public Insert values(List<String> names, List<Object> values) {
144153
if (names.size() != values.size())
145154
throw new IllegalArgumentException(String.format("Got %d names but %d values", names.size(), values.size()));
146-
checkState(json == null, "Cannot mix INSERT JSON syntax with regular INSERT syntax");
155+
checkState(json == null && jsonDefault == null, "Cannot mix INSERT JSON syntax with regular INSERT syntax");
147156
this.names.addAll(names);
148157
this.values.addAll(values);
149158
for (int i = 0; i < names.size(); i++) {
@@ -219,28 +228,60 @@ public Insert json(Object json) {
219228
}
220229

221230
/**
222-
* Adds a new options for this INSERT statement.
231+
* Appends a {@code DEFAULT UNSET} clause to this {@code INSERT INTO ... JSON} statement.
232+
* <p/>
233+
* Support for {@code DEFAULT UNSET} has been introduced in Cassandra 3.10.
234+
*
235+
* @return this {@code INSERT} statement.
236+
* @throws IllegalStateException if this method is called and any of the {@code value} or {@code values}
237+
* methods have been called before, because it's not possible
238+
* to mix {@code INSERT JSON} syntax with regular {@code INSERT} syntax.
239+
*/
240+
public Insert defaultUnset() {
241+
checkState(values.isEmpty() && names.isEmpty(), "Cannot mix INSERT JSON syntax with regular INSERT syntax");
242+
this.jsonDefault = JsonDefault.UNSET;
243+
return this;
244+
}
245+
246+
/**
247+
* Appends a {@code DEFAULT NULL} clause to this {@code INSERT INTO ... JSON} statement.
248+
* <p/>
249+
* Support for {@code DEFAULT NULL} has been introduced in Cassandra 3.10.
250+
*
251+
* @return this {@code INSERT} statement.
252+
* @throws IllegalStateException if this method is called and any of the {@code value} or {@code values}
253+
* methods have been called before, because it's not possible
254+
* to mix {@code INSERT JSON} syntax with regular {@code INSERT} syntax.
255+
*/
256+
public Insert defaultNull() {
257+
checkState(values.isEmpty() && names.isEmpty(), "Cannot mix INSERT JSON syntax with regular INSERT syntax");
258+
this.jsonDefault = JsonDefault.NULL;
259+
return this;
260+
}
261+
262+
/**
263+
* Adds a new options for this {@code INSERT} statement.
223264
*
224265
* @param using the option to add.
225-
* @return the options of this INSERT statement.
266+
* @return the options of this {@code INSERT} statement.
226267
*/
227268
public Options using(Using using) {
228269
return usings.and(using);
229270
}
230271

231272
/**
232-
* Returns the options for this INSERT statement.
273+
* Returns the options for this {@code INSERT} statement.
233274
* <p/>
234275
* Chain this with {@link Options#and(Using)} to add options.
235276
*
236-
* @return the options of this INSERT statement.
277+
* @return the options of this {@code INSERT} statement.
237278
*/
238279
public Options using() {
239280
return usings;
240281
}
241282

242283
/**
243-
* Sets the 'IF NOT EXISTS' option for this INSERT statement.
284+
* Sets the 'IF NOT EXISTS' option for this {@code INSERT} statement.
244285
* <p/>
245286
* An insert with that option will not succeed unless the row does not
246287
* exist at the time the insertion is executed. The existence check and
@@ -254,7 +295,7 @@ public Options using() {
254295
* This will configure the statement as non-idempotent, see {@link com.datastax.driver.core.Statement#isIdempotent()}
255296
* for more information.
256297
*
257-
* @return this INSERT statement.
298+
* @return this {@code INSERT} statement.
258299
*/
259300
public Insert ifNotExists() {
260301
this.setNonIdempotentOps();
@@ -263,7 +304,7 @@ public Insert ifNotExists() {
263304
}
264305

265306
/**
266-
* The options of an INSERT statement.
307+
* The options of an {@code INSERT} statement.
267308
*/
268309
public static class Options extends BuiltStatement.ForwardingStatement<Insert> {
269310

@@ -276,7 +317,7 @@ public static class Options extends BuiltStatement.ForwardingStatement<Insert> {
276317
/**
277318
* Adds the provided option.
278319
*
279-
* @param using an INSERT option.
320+
* @param using an {@code INSERT} option.
280321
* @return this {@code Options} object.
281322
*/
282323
public Options and(Using using) {
@@ -286,24 +327,24 @@ public Options and(Using using) {
286327
}
287328

288329
/**
289-
* Adds a column/value pair to the values inserted by this INSERT statement.
330+
* Adds a column/value pair to the values inserted by this {@code INSERT} statement.
290331
*
291332
* @param name the name of the column to insert/update.
292333
* @param value the value to insert/update for {@code name}.
293-
* @return the INSERT statement those options are part of.
334+
* @return the {@code INSERT} statement those options are part of.
294335
*/
295336
public Insert value(String name, Object value) {
296337
return statement.value(name, value);
297338
}
298339

299340
/**
300-
* Adds multiple column/value pairs to the values inserted by this INSERT statement.
341+
* Adds multiple column/value pairs to the values inserted by this {@code INSERT} statement.
301342
*
302343
* @param names a list of column names to insert/update.
303344
* @param values a list of values to insert/update. The {@code i}th
304345
* value in {@code values} will be inserted for the {@code i}th column
305346
* in {@code names}.
306-
* @return the INSERT statement those options are part of.
347+
* @return the {@code INSERT} statement those options are part of.
307348
* @throws IllegalArgumentException if {@code names.length != values.length}.
308349
*/
309350
public Insert values(String[] names, Object[] values) {

driver-core/src/test/java/com/datastax/driver/core/querybuilder/QueryBuilderExecutionTest.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,4 +390,61 @@ public void should_support_per_partition_limit() throws Exception {
390390
row(2, 3, 3));
391391
}
392392

393+
/**
394+
* Validates that {@link QueryBuilder} can construct an INSERT INTO ... JSON query using the 'DEFAULT UNSET/NULL' clause.
395+
*
396+
* @test_category queries:builder
397+
* @jira_ticket JAVA-1446
398+
* @since 3.3.0
399+
*/
400+
@CassandraVersion(value = "3.10", description = "Support for DEFAULT UNSET/NULL was added to C* 3.10 (CASSANDRA-11424)")
401+
@Test(groups = "short")
402+
public void should_support_insert_json_with_default_unset_and_default_null() throws Throwable {
403+
404+
String table = TestUtils.generateIdentifier("table");
405+
execute(
406+
String.format("CREATE TABLE %s (k int primary key, v1 int, v2 int)", table),
407+
String.format("INSERT INTO %s JSON '{\"k\": 0, \"v1\": 0, \"v2\": 0}'", table)
408+
);
409+
410+
// leave v1 unset
411+
session().execute(session().prepare(insertInto(table).json(bindMarker()).defaultUnset()).bind("{\"k\": 0, \"v2\": 2}"));
412+
assertThat(session().execute(select().from(table))).containsExactly(
413+
row(0, 0, 2)
414+
);
415+
416+
// explicit specification DEFAULT NULL
417+
session().execute(session().prepare(insertInto(table).json(bindMarker()).defaultNull()).bind("{\"k\": 0, \"v2\": 2}"));
418+
assertThat(session().execute(select().from(table))).containsExactly(
419+
row(0, null, 2)
420+
);
421+
422+
// implicitly setting v2 to null
423+
session().execute(session().prepare(insertInto(table).json(bindMarker()).defaultNull()).bind("{\"k\": 0}"));
424+
assertThat(session().execute(select().from(table))).containsExactly(
425+
row(0, null, null)
426+
);
427+
428+
// mix setting null explicitly with default unset:
429+
// set values for all fields
430+
session().execute(session().prepare(insertInto(table).json(bindMarker())).bind("{\"k\": 1, \"v1\": 1, \"v2\": 1}"));
431+
// explicitly set v1 to null while leaving v2 unset which retains its value
432+
session().execute(session().prepare(insertInto(table).json(bindMarker()).defaultUnset()).bind("{\"k\": 1, \"v1\": null}"));
433+
assertThat(session().execute(select().from(table).where(eq("k", 1)))).containsExactly(
434+
row(1, null, 1)
435+
);
436+
437+
// test string literal instead of bind marker
438+
session().execute(insertInto(table).json("{\"k\": 2, \"v1\": 2, \"v2\": 2}"));
439+
// explicitly set v1 to null while leaving v2 unset which retains its value
440+
session().execute(insertInto(table).json("{\"k\": 2, \"v1\": null}").defaultUnset());
441+
assertThat(session().execute(select().from(table).where(eq("k", 2)))).containsExactly(
442+
row(2, null, 2)
443+
);
444+
session().execute(insertInto(table).json("{\"k\": 2}").defaultNull());
445+
assertThat(session().execute(select().from(table).where(eq("k", 2)))).containsExactly(
446+
row(2, null, null)
447+
);
448+
}
449+
393450
}

driver-core/src/test/java/com/datastax/driver/core/querybuilder/QueryBuilderTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,12 @@ public void should_handle_insert_json() throws Exception {
11091109
assertThat(
11101110
insertInto("users").json(bindMarker("json")).toString())
11111111
.isEqualTo("INSERT INTO users JSON :json;");
1112+
assertThat(
1113+
insertInto("example").json("{\"id\": 0, \"tupleval\": [1, \"abc\"], \"numbers\": [1, 2, 3], \"letters\": [\"a\", \"b\", \"c\"]}").defaultNull().toString())
1114+
.isEqualTo("INSERT INTO example JSON '{\"id\": 0, \"tupleval\": [1, \"abc\"], \"numbers\": [1, 2, 3], \"letters\": [\"a\", \"b\", \"c\"]}' DEFAULT NULL;");
1115+
assertThat(
1116+
insertInto("example").json("{\"id\": 0, \"tupleval\": [1, \"abc\"], \"numbers\": [1, 2, 3], \"letters\": [\"a\", \"b\", \"c\"]}").defaultUnset().toString())
1117+
.isEqualTo("INSERT INTO example JSON '{\"id\": 0, \"tupleval\": [1, \"abc\"], \"numbers\": [1, 2, 3], \"letters\": [\"a\", \"b\", \"c\"]}' DEFAULT UNSET;");
11121118
}
11131119

11141120
@Test(groups = "unit")

0 commit comments

Comments
 (0)