apache/cassandra · error · InvalidRequestException

Range deletions must specify a complete partition key in…

Error message

Range deletions must specify a complete partition key in the underlying table 

What it means

Range tombstones in an UPDATE against the virtual table are translated back into partitions of the underlying remote table. A range deletion whose clustering bounds cover fewer than the full partition key of the underlying table cannot be mapped to an exact partition key, so it is rejected with InvalidRequestException.

Solutions

  1. Delete by exact, full primary key of the virtual table (equality on all primary key columns).
  2. Use a partition-level delete if you intend to remove an entire underlying partition.
  3. Rewrite the deletion as explicit per-row DELETEs for each key to remove.
  4. Avoid range predicates in DELETE on this virtual table.

Example fix

// before
DELETE FROM vt WHERE clustering_col < 10;   // range tombstone
// after
DELETE FROM vt WHERE pk = ... AND clustering_col = 5;  // exact row delete
Defensive patterns

Strategy: validation

Validate before calling

// Ensure deletes are exact-key
if (deleteStatement.hasRangePredicate())
    throw new IllegalArgumentException("Range deletes unsupported; delete by full primary key");

Try / catch

try { session.execute(delete); }
catch (InvalidRequestException e) {
    if (e.getMessage().contains("complete partition key"))
        // convert to per-row exact deletes
}

Prevention

When it happens

Trigger: DELETE ... WHERE clustering-col range / USING TTL batch that produces a range tombstone whose deletedSlice().start() or end() has fewer components than the underlying table's partition key count (pkCount).

Common situations: DELETE with a range predicate (e.g. clustering_col < X) on the virtual table where the underlying table's partition key is derived from remote clustering columns; accidental multi-row range deletes.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10). Data as JSON: /api/errors/6dbe997aad21574e. Report an issue: GitHub.

Appendix: source

Thrown at src/java/org/apache/cassandra/db/virtual/RemoteToLocalVirtualTable.java:530

        {
            int ckCount = local.clusteringColumns().size();
            pkBuffer = pkCount == 1 ? null : new ByteBuffer[pkCount];
            ckBuffer = new ByteBuffer[ckCount];
        }

        PartitionUpdate.Builder builder = null;
        ArrayDeque<Promise<Void>> results = new ArrayDeque<>();

        if (deletionInfo.hasRanges())
        {
            Iterator<RangeTombstone> iterator = deletionInfo.rangeIterator(false);
            while (iterator.hasNext())
            {
                RangeTombstone rt = iterator.next();
                ClusteringBound start = rt.deletedSlice().start();
                ClusteringBound end = rt.deletedSlice().end();
                if (start.size() < pkCount || end.size() < pkCount)
                    throw new InvalidRequestException("Range deletions must specify a complete partition key in the underlying table " + metadata);

                for (int i = 0 ; i < pkCount ; ++i)
                {
                    if (0 != start.accessor().compare(start.get(i), end.get(i), end.accessor()))
                        throw new InvalidRequestException("Range deletions must specify a single partition key in the underlying table " + metadata);
                }

                DecoratedKey key = remoteClusteringToLocalPartitionKey(local, start, pkCount, pkBuffer);
                builder = maybeRolloverAndWait(key, builder, results, endpoint);
                if (start.size() == pkCount && end.size() == pkCount)
                {
                    builder.addPartitionDeletion(rt.deletionTime());
                }
                else
                {
                    start = ClusteringBound.create(start.kind(), Clustering.make(remoteClusteringToLocalClustering(start.clustering(), pkCount, ckBuffer)));
                    end = ClusteringBound.create(end.kind(), Clustering.make(remoteClusteringToLocalClustering(end.clustering(), pkCount, ckBuffer)));
                    builder.add(new RangeTombstone(Slice.make(start, end), rt.deletionTime()));

View on GitHub (pinned to 88fd0f6a0e)