This is an automated email from the ASF dual-hosted git repository.

absurdfarce pushed a commit to branch 4.x
in repository https://gitbox.apache.org/repos/asf/cassandra-java-driver.git


The following commit(s) were added to refs/heads/4.x by this push:
     new af0a63a91 Re-work ordering clause support in query builder behind a 
single OrderingClause abstraction.  This allows for custom impls to define 
their own logic for managing "ORDER BY" clauses.
af0a63a91 is described below

commit af0a63a91a3dd1d3585070258e5c711fff50d77b
Author: absurdfarce <[email protected]>
AuthorDate: Mon Jul 21 16:11:32 2025 -0500

    Re-work ordering clause support in query builder behind a single 
OrderingClause abstraction.  This allows for
    custom impls to define their own logic for managing "ORDER BY" clauses.
    
    patch by Bret McGuire; reviewed by Bret McGuire and Lukasz Antoniak
    reference: https://github.com/apache/cassandra-java-driver/pull/2047
---
 .../api/querybuilder/select/AnnOrderingClause.java |  50 ++++++++
 .../querybuilder/select/ColumnsOrderingClause.java |  68 +++++++++++
 .../api/querybuilder/select/OrderingClause.java    |  27 +++++
 .../querybuilder/select/DefaultSelect.java         | 133 +++++++--------------
 .../querybuilder/select/SelectOrderingTest.java    |  27 +++--
 5 files changed, 208 insertions(+), 97 deletions(-)

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 000000000..a2226d88a
--- /dev/null
+++ 
b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/AnnOrderingClause.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+/**
+ * Concrete implementation of {@link OrderingClause} which supports ordering by
+ * the adjacent nearest-neighbor (ANN) calculation.  This usage is primarily 
used
+ * for vector calculations.
+ */
+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 000000000..b33a9d8e8
--- /dev/null
+++ 
b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/ColumnsOrderingClause.java
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+/**
+ * Concrete implementation of {@link OrderingClause} which supports ordering by
+ * specified columns.  This usages is the default ORDER BY syntax for Apache 
Cassandra.
+ */
+public class ColumnsOrderingClause extends OrderingClause {
+
+  private final ImmutableMap<CqlIdentifier, ClusteringOrder> orderings;
+
+  ColumnsOrderingClause(ImmutableMap<CqlIdentifier, ClusteringOrder> 
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<CqlIdentifier, 
ClusteringOrder> orderMap) {
+    return new 
ColumnsOrderingClause(ImmutableCollections.concat(this.orderings, orderMap));
+  }
+
+  @Override
+  public void appendTo(@NonNull StringBuilder builder) {
+
+    boolean first = true;
+    for (Map.Entry<CqlIdentifier, ClusteringOrder> 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 000000000..0318d115a
--- /dev/null
+++ 
b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/select/OrderingClause.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+/**
+ * Abstract representation of an ordering clause (i.e. ORDER BY) in a CQL 
statement.
+ * Alternate implementations may be provided if servers wind up implementing 
customized
+ * orderings.
+ */
+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 5daf252a9..e4b98ef6f 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.cql.SimpleStatementBuilder;
 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.CqlHelper;
 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<Selector> selectors;
   private final ImmutableList<Relation> relations;
   private final ImmutableList<Selector> groupByClauses;
-  private final ImmutableMap<CqlIdentifier, ClusteringOrder> orderings;
-  private final Ann ann;
+  private final Optional<OrderingClause> orderingClause;
   private final Object limit;
   private final Object perPartitionLimit;
   private final boolean allowsFiltering;
@@ -65,8 +66,7 @@ public class DefaultSelect implements SelectFrom, Select {
         ImmutableList.of(),
         ImmutableList.of(),
         ImmutableList.of(),
-        ImmutableMap.of(),
-        null,
+        Optional.empty(),
         null,
         null,
         false);
@@ -78,8 +78,6 @@ public class DefaultSelect implements SelectFrom, Select {
    * @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 class DefaultSelect implements SelectFrom, Select {
       @NonNull ImmutableList<Selector> selectors,
       @NonNull ImmutableList<Relation> relations,
       @NonNull ImmutableList<Selector> groupByClauses,
-      @NonNull ImmutableMap<CqlIdentifier, ClusteringOrder> orderings,
-      @Nullable Ann ann,
+      @NonNull Optional<OrderingClause> 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 class DefaultSelect implements SelectFrom, Select {
         selectors,
         relations,
         groupByClauses,
-        orderings,
-        ann,
+        orderingClause,
         limit,
         perPartitionLimit,
         allowsFiltering);
@@ -144,8 +137,7 @@ public class DefaultSelect implements SelectFrom, Select {
         selectors,
         relations,
         groupByClauses,
-        orderings,
-        ann,
+        orderingClause,
         limit,
         perPartitionLimit,
         allowsFiltering);
@@ -204,8 +196,7 @@ public class DefaultSelect implements SelectFrom, Select {
         newSelectors,
         relations,
         groupByClauses,
-        orderings,
-        ann,
+        orderingClause,
         limit,
         perPartitionLimit,
         allowsFiltering);
@@ -233,8 +224,7 @@ public class DefaultSelect implements SelectFrom, Select {
         selectors,
         newRelations,
         groupByClauses,
-        orderings,
-        ann,
+        orderingClause,
         limit,
         perPartitionLimit,
         allowsFiltering);
@@ -262,39 +252,54 @@ public class DefaultSelect implements SelectFrom, Select {
         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<CqlIdentifier, ClusteringOrder> 
newOrderings) {
-    return withOrderings(ImmutableCollections.concat(orderings, newOrderings));
+    ColumnsOrderingClause coc = getColumnOrderingClause();
+    return withOrderingClause(coc.add(newOrderings));
   }
 
   @NonNull
-  public Select withOrderings(@NonNull ImmutableMap<CqlIdentifier, 
ClusteringOrder> newOrderings) {
+  public Select withOrderingClause(@NonNull OrderingClause newOrderingClause) {
     return new DefaultSelect(
         keyspace,
         table,
@@ -303,25 +308,7 @@ public class DefaultSelect implements SelectFrom, Select {
         selectors,
         relations,
         groupByClauses,
-        newOrderings,
-        ann,
-        limit,
-        perPartitionLimit,
-        allowsFiltering);
-  }
-
-  @NonNull
-  Select withAnn(@NonNull Ann ann) {
-    return new DefaultSelect(
-        keyspace,
-        table,
-        isJson,
-        isDistinct,
-        selectors,
-        relations,
-        groupByClauses,
-        orderings,
-        ann,
+        Optional.of(newOrderingClause),
         limit,
         perPartitionLimit,
         allowsFiltering);
@@ -339,8 +326,7 @@ public class DefaultSelect implements SelectFrom, Select {
         selectors,
         relations,
         groupByClauses,
-        orderings,
-        ann,
+        orderingClause,
         limit,
         perPartitionLimit,
         allowsFiltering);
@@ -357,8 +343,7 @@ public class DefaultSelect implements SelectFrom, Select {
         selectors,
         relations,
         groupByClauses,
-        orderings,
-        ann,
+        orderingClause,
         bindMarker,
         perPartitionLimit,
         allowsFiltering);
@@ -377,8 +362,7 @@ public class DefaultSelect implements SelectFrom, Select {
         selectors,
         relations,
         groupByClauses,
-        orderings,
-        ann,
+        orderingClause,
         limit,
         perPartitionLimit,
         allowsFiltering);
@@ -395,8 +379,7 @@ public class DefaultSelect implements SelectFrom, Select {
         selectors,
         relations,
         groupByClauses,
-        orderings,
-        ann,
+        orderingClause,
         limit,
         bindMarker,
         allowsFiltering);
@@ -413,8 +396,7 @@ public class DefaultSelect implements SelectFrom, Select {
         selectors,
         relations,
         groupByClauses,
-        orderings,
-        ann,
+        orderingClause,
         limit,
         perPartitionLimit,
         true);
@@ -441,21 +423,7 @@ public class DefaultSelect implements SelectFrom, Select {
     CqlHelper.append(relations, builder, " WHERE ", " AND ", null);
     CqlHelper.append(groupByClauses, builder, " GROUP BY ", ",", null);
 
-    if (ann != null) {
-      builder.append(" ORDER BY 
").append(this.ann.columnId.asCql(true)).append(" ANN OF ");
-      QueryBuilder.literal(ann.vector).appendTo(builder);
-    } else {
-      boolean first = true;
-      for (Map.Entry<CqlIdentifier, ClusteringOrder> 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 class DefaultSelect implements SelectFrom, Select {
   }
 
   @NonNull
-  public ImmutableMap<CqlIdentifier, ClusteringOrder> getOrderings() {
-    return orderings;
+  public Optional<OrderingClause> getOrderingClause() {
+    return orderingClause;
   }
 
   @Nullable
@@ -554,11 +522,6 @@ public class DefaultSelect implements SelectFrom, Select {
     return limit;
   }
 
-  @Nullable
-  public Ann getAnn() {
-    return ann;
-  }
-
   @Nullable
   public Object getPerPartitionLimit() {
     return perPartitionLimit;
@@ -572,14 +535,4 @@ public class DefaultSelect implements SelectFrom, Select {
   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 a9c618e95..04831bb0f 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 class SelectOrderingTest {
         .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");
   }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to