This is an automated email from the ASF dual-hosted git repository. adelapena pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/cassandra.git
The following commit(s) were added to refs/heads/trunk by this push: new c95e5a5567 Improve vector value validation errors c95e5a5567 is described below commit c95e5a556709cf131ba074680585106559ff9511 Author: Andrés de la Peña <a.penya.gar...@gmail.com> AuthorDate: Thu Jul 6 13:01:55 2023 +0100 Improve vector value validation errors patch by Andrés de la Peña; reviewed by Ekaterina Dimitrova and Maxwell Guo for CASSANDRA-18652 --- CHANGES.txt | 1 + .../apache/cassandra/db/marshal/VectorType.java | 15 +++- .../cql3/validation/operations/CQLVectorTest.java | 90 ++++++++++++++++++++++ 3 files changed, 103 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index bcfdcfee58..e3c591378b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 5.0 + * Improve vector value validation errors (CASSANDRA-18652) * Upgrade Guava to 32.0.1 (CASSANDRA-18645) * Add duration and count of partition keys to sstablemetadata (CASSANDRA-18639) * Remove deprecated compaction_tombstone_warning_threshold and compaction_large_partition_warning_threshold from yaml (CASSANDRA-18626) diff --git a/src/java/org/apache/cassandra/db/marshal/VectorType.java b/src/java/org/apache/cassandra/db/marshal/VectorType.java index a184be6bb6..631d990c6c 100644 --- a/src/java/org/apache/cassandra/db/marshal/VectorType.java +++ b/src/java/org/apache/cassandra/db/marshal/VectorType.java @@ -352,10 +352,11 @@ public final class VectorType<T> extends AbstractType<List<T>> } } - private static <V> void checkConsumedFully(V buffer, ValueAccessor<V> accessor, int offset) + private <V> void checkConsumedFully(V buffer, ValueAccessor<V> accessor, int offset) { - if (!accessor.isEmptyFromOffset(buffer, offset)) - throw new MarshalException("Unexpected extraneous bytes after vector value"); + int remaining = accessor.sizeFromOffset(buffer, offset); + if (remaining > 0) + throw new MarshalException("Unexpected " + remaining + " extraneous bytes after " + asCQL3Type() + " value"); } public abstract class VectorSerializer extends TypeSerializer<List<T>> @@ -494,6 +495,11 @@ public final class VectorType<T> extends AbstractType<List<T>> return; int offset = 0; int elementSize = elementType.valueLengthIfFixed(); + + int expectedSize = elementSize * dimension; + if (accessor.size(input) < expectedSize) + throw new MarshalException("Not enough bytes to read a " + asCQL3Type()); + for (int i = 0; i < dimension; i++) { V bb = accessor.slice(input, offset, elementSize); @@ -628,6 +634,9 @@ public final class VectorType<T> extends AbstractType<List<T>> int offset = 0; for (int i = 0; i < dimension; i++) { + if (offset >= accessor.size(input)) + throw new MarshalException("Not enough bytes to read a " + asCQL3Type()); + V bb = readValue(input, accessor, offset); offset += sizeOf(bb, accessor); elementSerializer.validate(bb, accessor); diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/CQLVectorTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/CQLVectorTest.java index c1025659ca..ab39feb952 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/operations/CQLVectorTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/operations/CQLVectorTest.java @@ -18,6 +18,7 @@ package org.apache.cassandra.cql3.validation.operations; +import java.math.BigInteger; import java.nio.ByteBuffer; import org.junit.Assert; @@ -139,6 +140,95 @@ public class CQLVectorTest extends CQLTester.InMemory test.run(); } + @Test + public void invalidNumberOfDimensionsFixedWidth() throws Throwable + { + createTable("CREATE TABLE %s (pk int primary key, value vector<int, 2>)"); + + // fewer values than expected, with literals and bind markers + assertInvalidThrowMessage("Invalid vector literal for value of type vector<int, 2>; expected 2 elements, but given 1", + InvalidRequestException.class, + "INSERT INTO %s (pk, value) VALUES (0, [1])"); + assertInvalidThrowMessage("Not enough bytes to read a vector<int, 2>", + InvalidRequestException.class, + "INSERT INTO %s (pk, value) VALUES (0, ?)", vector(1)); + + // more values than expected, with literals and bind markers + assertInvalidThrowMessage("Invalid vector literal for value of type vector<int, 2>; expected 2 elements, but given 3", + InvalidRequestException.class, + "INSERT INTO %s (pk, value) VALUES (0, [1, 2, 3])"); + assertInvalidThrowMessage("Unexpected 4 extraneous bytes after vector<int, 2> value", + InvalidRequestException.class, + "INSERT INTO %s (pk, value) VALUES (0, ?)", vector(1, 2, 3)); + } + + @Test + public void invalidNumberOfDimensionsVariableWidth() throws Throwable + { + createTable("CREATE TABLE %s (pk int primary key, value vector<text, 2>)"); + + // fewer values than expected, with literals and bind markers + assertInvalidThrowMessage("Invalid vector literal for value of type vector<text, 2>; expected 2 elements, but given 1", + InvalidRequestException.class, + "INSERT INTO %s (pk, value) VALUES (0, ['a'])"); + assertInvalidThrowMessage("Not enough bytes to read a vector<text, 2>", + InvalidRequestException.class, + "INSERT INTO %s (pk, value) VALUES (0, ?)", vector("a")); + + // more values than expected, with literals and bind markers + assertInvalidThrowMessage("Invalid vector literal for value of type vector<text, 2>; expected 2 elements, but given 3", + InvalidRequestException.class, + "INSERT INTO %s (pk, value) VALUES (0, ['a', 'b', 'c'])"); + assertInvalidThrowMessage("Unexpected 2 extraneous bytes after vector<text, 2> value", + InvalidRequestException.class, + "INSERT INTO %s (pk, value) VALUES (0, ?)", vector("a", "b", "c")); + } + + @Test + public void invalidElementTypeFixedWidth() throws Throwable + { + createTable("CREATE TABLE %s (pk int primary key, value vector<int, 2>)"); + + // fixed-length bigint instead of int, with literals and bind markers + assertInvalidThrowMessage("Invalid vector literal for value: value (bigint)1 is not of type int", + InvalidRequestException.class, + "INSERT INTO %s (pk, value) VALUES (0, [(bigint) 1, (bigint) 2])"); + assertInvalidThrowMessage("Unexpected 8 extraneous bytes after vector<int, 2> value", + InvalidRequestException.class, + "INSERT INTO %s (pk, value) VALUES (0, ?)", vector(1L, Long.MAX_VALUE)); + + // variable-length text instead of int, with literals and bind markers + assertInvalidThrowMessage("Invalid vector literal for value: value 'a' is not of type int", + InvalidRequestException.class, + "INSERT INTO %s (pk, value) VALUES (0, ['a', 'b'])"); + assertInvalidThrowMessage("Not enough bytes to read a vector<int, 2>", + InvalidRequestException.class, + "INSERT INTO %s (pk, value) VALUES (0, ?)", vector("a", "b")); + } + + @Test + public void invalidElementTypeVariableWidth() throws Throwable + { + createTable("CREATE TABLE %s (pk int primary key, value vector<text, 2>)"); + + // fixed-length int instead of text, with literals and bind markers + assertInvalidThrowMessage("Invalid vector literal for value: value 1 is not of type text", + InvalidRequestException.class, + "INSERT INTO %s (pk, value) VALUES (0, [1, 2])"); + assertInvalidThrowMessage("Unexpected 6 extraneous bytes after vector<text, 2> value", + InvalidRequestException.class, + "INSERT INTO %s (pk, value) VALUES (0, ?)", vector(1, 2)); + + // variable-length varint instead of text, with literals and bind markers + assertInvalidThrowMessage("Invalid vector literal for value: value (varint)1 is not of type text", + InvalidRequestException.class, + "INSERT INTO %s (pk, value) VALUES (0, [(varint) 1, (varint) 2])"); + assertInvalidThrowMessage("String didn't validate.", + InvalidRequestException.class, + "INSERT INTO %s (pk, value) VALUES (0, ?)", + vector(BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE), BigInteger.ONE)); + } + @Test public void update() { --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org For additional commands, e-mail: commits-h...@cassandra.apache.org