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 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 components; + + public UnwrappedCollectionTerm(@NonNull Iterable 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 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