diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/AnnOrderingClause.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/AnnOrderingClause.java new file mode 100644 index 00000000000..920934b7e89 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/AnnOrderingClause.java @@ -0,0 +1,45 @@ +/* + * 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.api.querybuilder.select; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.data.CqlVector; +import com.datastax.oss.driver.api.querybuilder.QueryBuilder; +import edu.umd.cs.findbugs.annotations.NonNull; + +public class AnnOrderingClause extends OrderingClause { + + private final CqlIdentifier identifier; + private final CqlVector vector; + + AnnOrderingClause(CqlIdentifier identifier, CqlVector vector) { + + this.identifier = identifier; + this.vector = vector; + } + + public static AnnOrderingClause create(CqlIdentifier identifier, CqlVector vector) { + return new AnnOrderingClause(identifier, vector); + } + + @Override + public void appendTo(@NonNull StringBuilder builder) { + builder.append(" ORDER BY ").append(this.identifier.asCql(true)).append(" ANN OF "); + QueryBuilder.literal(this.vector).appendTo(builder); + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/ColumnsOrderingClause.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/ColumnsOrderingClause.java new file mode 100644 index 00000000000..7a7bab30079 --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/ColumnsOrderingClause.java @@ -0,0 +1,64 @@ +/* + * 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.api.querybuilder.select; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder; +import com.datastax.oss.driver.internal.querybuilder.ImmutableCollections; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Map; + +public class ColumnsOrderingClause extends OrderingClause { + + private final ImmutableMap orderings; + + ColumnsOrderingClause(ImmutableMap orderings) { + + this.orderings = orderings; + } + + public static ColumnsOrderingClause create() { + return new ColumnsOrderingClause(ImmutableMap.of()); + } + + public ColumnsOrderingClause add( + @NonNull CqlIdentifier identifier, @NonNull ClusteringOrder order) { + return new ColumnsOrderingClause( + ImmutableCollections.append(this.orderings, identifier, order)); + } + + public ColumnsOrderingClause add(@NonNull Map orderMap) { + return new ColumnsOrderingClause(ImmutableCollections.concat(this.orderings, orderMap)); + } + + @Override + public void appendTo(@NonNull StringBuilder builder) { + + boolean first = true; + for (Map.Entry entry : orderings.entrySet()) { + if (first) { + builder.append(" ORDER BY "); + first = false; + } else { + builder.append(","); + } + builder.append(entry.getKey().asCql(true)).append(" ").append(entry.getValue().name()); + } + } +} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/OrderingClause.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/OrderingClause.java new file mode 100644 index 00000000000..f7ea0b1ed7f --- /dev/null +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/OrderingClause.java @@ -0,0 +1,22 @@ +/* + * 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.api.querybuilder.select; + +import com.datastax.oss.driver.api.querybuilder.CqlSnippet; + +public abstract class OrderingClause implements CqlSnippet {} diff --git a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultSelect.java b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultSelect.java index 5daf252a9eb..e4b98ef6f53 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultSelect.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/internal/querybuilder/select/DefaultSelect.java @@ -23,8 +23,10 @@ import com.datastax.oss.driver.api.core.data.CqlVector; import com.datastax.oss.driver.api.core.metadata.schema.ClusteringOrder; import com.datastax.oss.driver.api.querybuilder.BindMarker; -import com.datastax.oss.driver.api.querybuilder.QueryBuilder; import com.datastax.oss.driver.api.querybuilder.relation.Relation; +import com.datastax.oss.driver.api.querybuilder.select.AnnOrderingClause; +import com.datastax.oss.driver.api.querybuilder.select.ColumnsOrderingClause; +import com.datastax.oss.driver.api.querybuilder.select.OrderingClause; import com.datastax.oss.driver.api.querybuilder.select.Select; import com.datastax.oss.driver.api.querybuilder.select.SelectFrom; import com.datastax.oss.driver.api.querybuilder.select.Selector; @@ -32,10 +34,10 @@ 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 com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Map; +import java.util.Optional; import net.jcip.annotations.Immutable; @Immutable @@ -50,8 +52,7 @@ public class DefaultSelect implements SelectFrom, Select { private final ImmutableList selectors; private final ImmutableList relations; private final ImmutableList groupByClauses; - private final ImmutableMap orderings; - private final Ann ann; + private final Optional orderingClause; private final Object limit; private final Object perPartitionLimit; private final boolean allowsFiltering; @@ -65,8 +66,7 @@ public DefaultSelect(@Nullable CqlIdentifier keyspace, @NonNull CqlIdentifier ta ImmutableList.of(), ImmutableList.of(), ImmutableList.of(), - ImmutableMap.of(), - null, + Optional.empty(), null, null, false); @@ -78,8 +78,6 @@ public DefaultSelect(@Nullable CqlIdentifier keyspace, @NonNull CqlIdentifier ta * @param selectors if it contains {@link AllSelector#INSTANCE}, that must be the only element. * This isn't re-checked because methods that call this constructor internally already do it, * make sure you do it yourself. - * @param ann Approximate nearest neighbor. ANN ordering does not support secondary ordering or - * ASC order. */ public DefaultSelect( @Nullable CqlIdentifier keyspace, @@ -89,21 +87,17 @@ public DefaultSelect( @NonNull ImmutableList selectors, @NonNull ImmutableList relations, @NonNull ImmutableList groupByClauses, - @NonNull ImmutableMap orderings, - @Nullable Ann ann, + @NonNull Optional orderingClause, @Nullable Object limit, @Nullable Object perPartitionLimit, boolean allowsFiltering) { this.groupByClauses = groupByClauses; - this.orderings = orderings; + this.orderingClause = orderingClause; Preconditions.checkArgument( limit == null || (limit instanceof Integer && (Integer) limit > 0) || limit instanceof BindMarker, "limit must be a strictly positive integer or a bind marker"); - Preconditions.checkArgument( - orderings.isEmpty() || ann == null, "ANN ordering does not support secondary ordering"); - this.ann = ann; this.keyspace = keyspace; this.table = table; this.isJson = isJson; @@ -126,8 +120,7 @@ public SelectFrom json() { selectors, relations, groupByClauses, - orderings, - ann, + orderingClause, limit, perPartitionLimit, allowsFiltering); @@ -144,8 +137,7 @@ public SelectFrom distinct() { selectors, relations, groupByClauses, - orderings, - ann, + orderingClause, limit, perPartitionLimit, allowsFiltering); @@ -204,8 +196,7 @@ public Select withSelectors(@NonNull ImmutableList newSelectors) { newSelectors, relations, groupByClauses, - orderings, - ann, + orderingClause, limit, perPartitionLimit, allowsFiltering); @@ -233,8 +224,7 @@ public Select withRelations(@NonNull ImmutableList newRelations) { selectors, newRelations, groupByClauses, - orderings, - ann, + orderingClause, limit, perPartitionLimit, allowsFiltering); @@ -262,39 +252,54 @@ public Select withGroupByClauses(@NonNull ImmutableList newGroupByClau selectors, relations, newGroupByClauses, - orderings, - ann, + orderingClause, limit, perPartitionLimit, allowsFiltering); } + /** + * Retrieve the current {@link OrderingClause} as a {@link ColumnsOrderingClause} if it exists and + * is an instance of this class, Otherwise create a new one. + * + * @return the current OrderingClause if it's a ColumnsOrderingClause or a new one otherwise + */ + private ColumnsOrderingClause getColumnOrderingClause() { + return (ColumnsOrderingClause) + orderingClause + .map( + (oc) -> (oc instanceof ColumnsOrderingClause) ? oc : ColumnsOrderingClause.create()) + .orElseGet(() -> ColumnsOrderingClause.create()); + } + @NonNull @Override public Select orderBy(@NonNull CqlIdentifier columnId, @NonNull ClusteringOrder order) { - return withOrderings(ImmutableCollections.append(orderings, columnId, order)); + ColumnsOrderingClause coc = getColumnOrderingClause(); + return withOrderingClause(coc.add(columnId, order)); } @NonNull @Override public Select orderByAnnOf(@NonNull String columnName, @NonNull CqlVector ann) { - return withAnn(new Ann(CqlIdentifier.fromCql(columnName), ann)); + return withOrderingClause(AnnOrderingClause.create(CqlIdentifier.fromCql(columnName), ann)); } @NonNull @Override public Select orderByAnnOf(@NonNull CqlIdentifier columnId, @NonNull CqlVector ann) { - return withAnn(new Ann(columnId, ann)); + return withOrderingClause(AnnOrderingClause.create(columnId, ann)); } @NonNull @Override public Select orderByIds(@NonNull Map newOrderings) { - return withOrderings(ImmutableCollections.concat(orderings, newOrderings)); + ColumnsOrderingClause coc = getColumnOrderingClause(); + return withOrderingClause(coc.add(newOrderings)); } @NonNull - public Select withOrderings(@NonNull ImmutableMap newOrderings) { + public Select withOrderingClause(@NonNull OrderingClause newOrderingClause) { return new DefaultSelect( keyspace, table, @@ -303,25 +308,7 @@ public Select withOrderings(@NonNull ImmutableMap entry : orderings.entrySet()) { - if (first) { - builder.append(" ORDER BY "); - first = false; - } else { - builder.append(","); - } - builder.append(entry.getKey().asCql(true)).append(" ").append(entry.getValue().name()); - } - } + orderingClause.ifPresent(c -> c.appendTo(builder)); if (limit != null) { builder.append(" LIMIT "); @@ -545,8 +513,8 @@ public ImmutableList getGroupByClauses() { } @NonNull - public ImmutableMap getOrderings() { - return orderings; + public Optional getOrderingClause() { + return orderingClause; } @Nullable @@ -554,11 +522,6 @@ public Object getLimit() { return limit; } - @Nullable - public Ann getAnn() { - return ann; - } - @Nullable public Object getPerPartitionLimit() { return perPartitionLimit; @@ -572,14 +535,4 @@ public boolean allowsFiltering() { public String toString() { return asCql(); } - - public static class Ann { - private final CqlVector vector; - private final CqlIdentifier columnId; - - private Ann(CqlIdentifier columnId, CqlVector vector) { - this.vector = vector; - this.columnId = columnId; - } - } } diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectOrderingTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectOrderingTest.java index a9c618e9559..04831bb0f8d 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectOrderingTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/select/SelectOrderingTest.java @@ -86,12 +86,25 @@ public void should_generate_ann_clause() { .hasCql("SELECT * FROM foo WHERE k=1 ORDER BY c1 ANN OF [0.1, 0.2, 0.3]"); } - @Test(expected = IllegalArgumentException.class) - public void should_fail_when_provided_ann_with_other_orderings() { - selectFrom("foo") - .all() - .where(Relation.column("k").isEqualTo(literal(1))) - .orderBy("c1", ASC) - .orderByAnnOf("c2", CqlVector.newInstance(0.1, 0.2, 0.3)); + @Test + public void should_replace_columns_ordering_with_ann() { + assertThat( + selectFrom("foo") + .all() + .where(Relation.column("k").isEqualTo(literal(1))) + .orderBy("c1", ASC) + .orderByAnnOf("c2", CqlVector.newInstance(0.1, 0.2, 0.3))) + .hasCql("SELECT * FROM foo WHERE k=1 ORDER BY c2 ANN OF [0.1, 0.2, 0.3]"); + } + + @Test + public void should_replace_ann_ordering_with_columns() { + assertThat( + selectFrom("foo") + .all() + .where(Relation.column("k").isEqualTo(literal(1))) + .orderByAnnOf("c1", CqlVector.newInstance(0.1, 0.2, 0.3)) + .orderBy("c2", ASC)) + .hasCql("SELECT * FROM foo WHERE k=1 ORDER BY c2 ASC"); } }