diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java
index 05ae398082..337e036610 100644
--- a/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java
+++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/TypeCodec.java
@@ -216,6 +216,17 @@ default boolean accepts(@NonNull DataType cqlType) {
@NonNull
String format(@Nullable JavaTypeT value);
+ /**
+ * Formats items from collection type as valid list of CQL literals.
+ *
+ *
Implementing this method is not strictly mandatory. Default implementation falls back to
+ * {@link #format(JavaTypeT)}. Method is used primarily for literal values in the query builder
+ * (see {@code DefaultLiteral#appendElementsTo(StringBuilder)}).
+ */
+ default String formatElements(@Nullable JavaTypeT value) {
+ return format(value);
+ }
+
/**
* Parse the given CQL literal into an instance of the Java type handled by this codec.
*
diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java
index c6482d4f4d..ebe7cfba6a 100644
--- a/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java
+++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java
@@ -40,6 +40,7 @@
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZonedDateTime;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -252,6 +253,11 @@ public final boolean isArray() {
return token.isArray();
}
+ /** Returns true if this type is a Java collection (e.g. list or set). */
+ public boolean isCollection() {
+ return Collection.class.isAssignableFrom(token.getRawType());
+ }
+
/** Returns true if this type is one of the nine primitive types (including {@code void}). */
public final boolean isPrimitive() {
return token.isPrimitive();
diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ListCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ListCodec.java
index d587bbd588..f26d38d071 100644
--- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ListCodec.java
+++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/ListCodec.java
@@ -142,6 +142,14 @@ public String format(@Nullable List value) {
return "NULL";
}
StringBuilder sb = new StringBuilder("[");
+ sb.append(formatElements(value));
+ sb.append("]");
+ return sb.toString();
+ }
+
+ @Override
+ public String formatElements(@Nullable List value) {
+ StringBuilder sb = new StringBuilder();
boolean first = true;
for (ElementT t : value) {
if (first) {
@@ -151,7 +159,6 @@ public String format(@Nullable List value) {
}
sb.append(elementCodec.format(t));
}
- sb.append("]");
return sb.toString();
}
diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SetCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SetCodec.java
index fc4c088751..fff9d557ce 100644
--- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SetCodec.java
+++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/SetCodec.java
@@ -143,6 +143,14 @@ public String format(@Nullable Set value) {
return "NULL";
}
StringBuilder sb = new StringBuilder("{");
+ sb.append(formatElements(value));
+ sb.append("}");
+ return sb.toString();
+ }
+
+ @Override
+ public String formatElements(@Nullable Set value) {
+ StringBuilder sb = new StringBuilder();
boolean first = true;
for (ElementT t : value) {
if (first) {
@@ -152,7 +160,6 @@ public String format(@Nullable Set value) {
}
sb.append(elementCodec.format(t));
}
- sb.append("}");
return sb.toString();
}
diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/CqlSnippet.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/CqlSnippet.java
index ba06391e62..4a9395a3cb 100644
--- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/CqlSnippet.java
+++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/CqlSnippet.java
@@ -22,4 +22,13 @@
/** An element in the query builder DSL, that will generate part of a CQL query. */
public interface CqlSnippet {
void appendTo(@NonNull StringBuilder builder);
+
+ /**
+ * Optional method used in collection types to append all elements to CQL query. List and set
+ * codecs typically enclose elements with '[]', '{}' characters. When we would like to append
+ * elements directly inside IN clause, mentioned behaviour generates incorrect CQL statement.
+ */
+ default void appendElementsTo(@NonNull StringBuilder builder) {
+ appendTo(builder);
+ }
}
diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/InRelationBuilder.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/InRelationBuilder.java
index d3fc8dce91..8038aa861d 100644
--- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/InRelationBuilder.java
+++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/relation/InRelationBuilder.java
@@ -18,8 +18,8 @@
package com.datastax.oss.driver.api.querybuilder.relation;
import com.datastax.oss.driver.api.querybuilder.BindMarker;
-import com.datastax.oss.driver.api.querybuilder.QueryBuilder;
import com.datastax.oss.driver.api.querybuilder.term.Term;
+import com.datastax.oss.driver.internal.querybuilder.term.UnwrappedCollectionTerm;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.util.Arrays;
@@ -41,7 +41,7 @@ default ResultT in(@NonNull BindMarker bindMarker) {
*/
@NonNull
default ResultT in(@NonNull Iterable alternatives) {
- return build(" IN ", QueryBuilder.tuple(alternatives));
+ return build(" IN ", new UnwrappedCollectionTerm(alternatives));
}
/** Var-arg equivalent of {@link #in(Iterable)} . */
diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/CqlHelper.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/CqlHelper.java
index 55a923e46a..a0c8688b8b 100644
--- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/CqlHelper.java
+++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/CqlHelper.java
@@ -71,6 +71,29 @@ public static void append(
}
}
+ public static void appendElements(
+ @NonNull Iterable extends CqlSnippet> snippets,
+ @NonNull StringBuilder builder,
+ @Nullable String prefix,
+ @NonNull String separator,
+ @Nullable String suffix) {
+ boolean first = true;
+ for (CqlSnippet snippet : snippets) {
+ if (first) {
+ if (prefix != null) {
+ builder.append(prefix);
+ }
+ first = false;
+ } else {
+ builder.append(separator);
+ }
+ snippet.appendElementsTo(builder);
+ }
+ if (!first && suffix != null) {
+ builder.append(suffix);
+ }
+ }
+
public static void qualify(
@Nullable CqlIdentifier keyspace,
@NonNull CqlIdentifier element,
diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/DefaultLiteral.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/DefaultLiteral.java
index 3d9349b553..c085e7b343 100644
--- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/DefaultLiteral.java
+++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/DefaultLiteral.java
@@ -58,6 +58,17 @@ public void appendTo(@NonNull StringBuilder builder) {
}
}
+ @Override
+ public void appendElementsTo(@NonNull StringBuilder builder) {
+ if (value != null) {
+ if (codec.getJavaType().isCollection()) {
+ builder.append(codec.formatElements(value));
+ } else {
+ builder.append(codec.format(value));
+ }
+ }
+ }
+
@Override
public boolean isIdempotent() {
return true;
diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/UnwrappedCollectionTerm.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/UnwrappedCollectionTerm.java
new file mode 100644
index 0000000000..5e0c6061c9
--- /dev/null
+++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/term/UnwrappedCollectionTerm.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.internal.querybuilder.term;
+
+import com.datastax.oss.driver.api.querybuilder.term.Term;
+import com.datastax.oss.driver.internal.querybuilder.CqlHelper;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import net.jcip.annotations.Immutable;
+
+@Immutable
+public class UnwrappedCollectionTerm implements Term {
+
+ private final Iterable extends Term> components;
+
+ public UnwrappedCollectionTerm(@NonNull Iterable extends Term> components) {
+ this.components = components;
+ }
+
+ @Override
+ public void appendTo(@NonNull StringBuilder builder) {
+ CqlHelper.appendElements(components, builder, "(", ",", ")");
+ }
+
+ @Override
+ public boolean isIdempotent() {
+ for (Term component : components) {
+ if (!component.isIdempotent()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @NonNull
+ public Iterable extends Term> getComponents() {
+ return components;
+ }
+}
diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/relation/RelationTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/relation/RelationTest.java
index 515a336f5f..15053204f9 100644
--- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/relation/RelationTest.java
+++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/relation/RelationTest.java
@@ -19,10 +19,15 @@
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.literal;
import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.raw;
import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom;
import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.tuple;
+import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
import org.junit.Test;
public class RelationTest {
@@ -47,6 +52,25 @@ public void should_generate_in_relation() {
.hasCql("SELECT * FROM foo WHERE k IN ?");
assertThat(selectFrom("foo").all().where(Relation.column("k").in(bindMarker(), bindMarker())))
.hasCql("SELECT * FROM foo WHERE k IN (?,?)");
+ assertThat(
+ selectFrom("foo")
+ .all()
+ .where(Relation.column("k").in(literal(Arrays.asList("vector", "data")))))
+ .hasCql("SELECT * FROM foo WHERE k IN ('vector','data')");
+ List uuids =
+ Arrays.asList(
+ UUID.fromString("d3f5c945-74bd-4a99-b6d7-aa73e54a5b75"),
+ UUID.fromString("464a834d-8fd8-4842-9fba-47bc599b8083"));
+ assertThat(selectFrom("foo").all().where(Relation.column("k").in(literal(uuids))))
+ .hasCql(
+ "SELECT * FROM foo WHERE k IN (d3f5c945-74bd-4a99-b6d7-aa73e54a5b75,464a834d-8fd8-4842-9fba-47bc599b8083)");
+ assertThat(
+ selectFrom("foo")
+ .all()
+ .where(Relation.column("k").in(literal(ImmutableSet.of(1, 3, 5, 7)))))
+ .hasCql("SELECT * FROM foo WHERE k IN (1,3,5,7)");
+ assertThat(selectFrom("foo").all().where(Relation.column("k").in(literal("atomic value"))))
+ .hasCql("SELECT * FROM foo WHERE k IN ('atomic value')");
}
@Test