This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch PHOENIX-7998 in repository https://gitbox.apache.org/repos/asf/phoenix.git
commit deae510427e6ede49028e9f016262c83d9fcf000 Author: Andrew Purtell <[email protected]> AuthorDate: Tue Oct 6 00:32:54 2026 -0700 PHOENIX-7998 Phase 14: Compatibility and version validation Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> --- .../java/org/apache/phoenix/schema/PTable.java | 3 +- .../expression/ExpressionTypeOrdinalTest.java | 99 ++++++++++++++++++++++ .../apache/phoenix/schema/VectorIndexTypeTest.java | 2 + .../phoenix/schema/types/VectorDataTypeTest.java | 18 ++++ 4 files changed, 121 insertions(+), 1 deletion(-) diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/PTable.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/PTable.java index e89fa4affe..ea2b781733 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/PTable.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/PTable.java @@ -137,7 +137,8 @@ public interface PTable extends PMetaDataEntity { public static IndexType fromSerializedValue(byte serializedValue) { if (serializedValue < 1 || serializedValue > IndexType.values().length) { - throw new IllegalArgumentException("Invalid IndexType " + serializedValue); + throw new IllegalArgumentException("Unsupported IndexType " + serializedValue + + ": the index was created by a newer Phoenix version. Upgrade this client to read it."); } return IndexType.values()[serializedValue - 1]; } diff --git a/phoenix-core/src/test/java/org/apache/phoenix/expression/ExpressionTypeOrdinalTest.java b/phoenix-core/src/test/java/org/apache/phoenix/expression/ExpressionTypeOrdinalTest.java new file mode 100644 index 0000000000..9df7940000 --- /dev/null +++ b/phoenix-core/src/test/java/org/apache/phoenix/expression/ExpressionTypeOrdinalTest.java @@ -0,0 +1,99 @@ +/* + * 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 org.apache.phoenix.expression; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * Makes sure that the ordinals of {@link ExpressionType} entries do not change. Clients and servers + * of different versions serialize expressions by ordinal. Thus, add new entries only at the end of + * the enum, and do not move or remove a released entry. + */ +public class ExpressionTypeOrdinalTest { + + /** The entries that existed before vector support, in their released order. */ + private static final String[] RELEASED = { "ReverseFunction", "RowKey", "KeyValue", + "LiteralValue", "RoundFunction", "FloorFunction", "CeilFunction", "RoundDateExpression", + "FloorDateExpression", "CeilDateExpression", "RoundTimestampExpression", + "CeilTimestampExpression", "RoundDecimalExpression", "FloorDecimalExpression", + "CeilDecimalExpression", "TruncFunction", "ToDateFunction", "ToCharFunction", + "ToNumberFunction", "CoerceFunction", "SubstrFunction", "AndExpression", "OrExpression", + "ComparisonExpression", "CountAggregateFunction", "SumAggregateFunction", + "MinAggregateFunction", "MaxAggregateFunction", "StringBasedLikeExpression", "NotExpression", + "CaseExpression", "InListExpression", "IsNullExpression", "LongSubtractExpression", + "DateSubtractExpression", "DecimalSubtractExpression", "LongAddExpression", + "DecimalAddExpression", "DateAddExpression", "LongMultiplyExpression", + "DecimalMultiplyExpression", "LongDivideExpression", "DecimalDivideExpression", + "CoalesceFunction", "StringBasedRegexpReplaceFunction", "SQLTypeNameFunction", + "StringBasedRegexpSubstrFunction", "StringConcatExpression", "LengthFunction", "LTrimFunction", + "RTrimFunction", "UpperFunction", "LowerFunction", "TrimFunction", + "DistinctCountAggregateFunction", "PercentileContAggregateFunction", + "PercentRankAggregateFunction", "StddevPopFunction", "StddevSampFunction", + "PercentileDiscAggregateFunction", "DoubleAddExpression", "DoubleSubtractExpression", + "DoubleMultiplyExpression", "DoubleDivideExpression", "RowValueConstructorExpression", + "MD5Function", "SQLTableTypeFunction", "IndexStateName", "InvertFunction", + "ProjectedColumnExpression", "TimestampAddExpression", "TimestampSubtractExpression", + "ArrayIndexFunction", "ArrayLengthFunction", "ArrayConstructorExpression", + "SQLViewTypeFunction", "ExternalSqlTypeIdFunction", "ConvertTimezoneFunction", "DecodeFunction", + "TimezoneOffsetFunction", "EncodeFunction", "LpadFunction", "NthValueFunction", + "FirstValueFunction", "LastValueFunction", "ArrayAnyComparisonExpression", + "ArrayAllComparisonExpression", "InlineArrayElemRefExpression", "SQLIndexTypeFunction", + "ModulusExpression", "DistinctValueAggregateFunction", "StringBasedRegexpSplitFunction", + "RandomFunction", "ToTimeFunction", "ToTimestampFunction", "ByteBasedLikeExpression", + "ByteBasedRegexpReplaceFunction", "ByteBasedRegexpSubstrFunction", + "ByteBasedRegexpSplitFunction", "LikeExpression", "RegexpReplaceFunction", + "RegexpSubstrFunction", "RegexpSplitFunction", "SignFunction", "YearFunction", "MonthFunction", + "SecondFunction", "WeekFunction", "HourFunction", "NowFunction", "InstrFunction", + "MinuteFunction", "DayOfMonthFunction", "ArrayAppendFunction", "UDFExpression", + "ArrayPrependFunction", "SqrtFunction", "AbsFunction", "CbrtFunction", "LnFunction", + "LogFunction", "ExpFunction", "PowerFunction", "ArrayConcatFunction", "ArrayFillFunction", + "ArrayToStringFunction", "StringToArrayFunction", "GetByteFunction", "SetByteFunction", + "GetBitFunction", "SetBitFunction", "OctetLengthFunction", "RoundWeekExpression", + "RoundMonthExpression", "RoundYearExpression", "FloorWeekExpression", "FloorMonthExpression", + "FloorYearExpression", "CeilWeekExpression", "CeilMonthExpression", "CeilYearExpression", + "DayOfWeekFunction", "DayOfYearFunction", "DefaultValueExpression", "ArrayColumnExpression", + "FirstValuesFunction", "LastValuesFunction", "DistinctCountHyperLogLogAggregateFunction", + "CollationKeyFunction", "ArrayRemoveFunction", "TransactionProviderNameFunction", + "MathPIFunction", "SinFunction", "CosFunction", "TanFunction", "RowKeyBytesStringFunction", + "PhoenixRowTimestampFunction", "JsonValueFunction", "JsonQueryFunction", "JsonExistsFunction", + "JsonModifyFunction", "BsonConditionExpressionFunction", "BsonUpdateExpressionFunction", + "BsonValueFunction", "BsonValueTypeFunction", "PartitionIdFunction", "DecodeBinaryFunction", + "EncodeBinaryFunction", "DecodeViewIdFunction", "SubBinaryFunction", "ScanStartKeyFunction", + "ScanEndKeyFunction", "TotalSegmentsFunction", "RowSizeFunction", "RawRowSizeFunction", + "RegexpLikeFunction", "ByteBasedRegexpLikeFunction", "StringBasedRegexpLikeFunction", }; + + @Test + public void testReleasedOrdinalsAreUnchanged() { + for (int i = 0; i < RELEASED.length; i++) { + assertEquals("ordinal of " + RELEASED[i], i, ExpressionType.valueOf(RELEASED[i]).ordinal()); + } + } + + @Test + public void testVectorExpressionsAreAppended() { + String[] vectorTypes = { "L2DistanceFunction", "L2DistanceSquaredFunction", + "CosineDistanceFunction", "InnerProductDistanceFunction", "BsonVectorValueFunction" }; + for (String name : vectorTypes) { + assertTrue(name + " must follow every released entry", + ExpressionType.valueOf(name).ordinal() >= RELEASED.length); + } + } +} diff --git a/phoenix-core/src/test/java/org/apache/phoenix/schema/VectorIndexTypeTest.java b/phoenix-core/src/test/java/org/apache/phoenix/schema/VectorIndexTypeTest.java index 79abe73d50..235b1834ed 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/schema/VectorIndexTypeTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/schema/VectorIndexTypeTest.java @@ -67,6 +67,8 @@ public class VectorIndexTypeTest { fail("Expected IllegalArgumentException for unknown serialized value 99"); } catch (IllegalArgumentException e) { assertTrue("Exception message should mention invalid value", e.getMessage().contains("99")); + assertTrue("Exception message should direct an upgrade", + e.getMessage().toLowerCase().contains("upgrade")); } } diff --git a/phoenix-core/src/test/java/org/apache/phoenix/schema/types/VectorDataTypeTest.java b/phoenix-core/src/test/java/org/apache/phoenix/schema/types/VectorDataTypeTest.java index e1cd9ed490..b32aea37c9 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/schema/types/VectorDataTypeTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/schema/types/VectorDataTypeTest.java @@ -55,6 +55,24 @@ public class VectorDataTypeTest { assertTrue(factory.getTypes().contains(PVectorDouble.INSTANCE)); } + /** + * The SQL type ID and the serialization ordinal identify a type across versions. Thus, the values + * of each vector type must not change, and no other registered type can use them. + */ + @Test + public void testTypeIdentifiersAreUniqueAndStable() { + assertEquals(51, PVectorFloat.INSTANCE.ordinal()); + assertEquals(52, PVectorDouble.INSTANCE.ordinal()); + java.util.Set<Integer> sqlTypes = new java.util.HashSet<>(); + java.util.Set<Integer> ordinals = new java.util.HashSet<>(); + for (PDataType<?> type : PDataTypeFactory.getInstance().getTypes()) { + assertTrue("duplicate sql type " + type.getSqlType() + " for " + type, + sqlTypes.add(type.getSqlType())); + assertTrue("duplicate ordinal " + type.ordinal() + " for " + type, + ordinals.add(type.ordinal())); + } + } + @Test public void testSqlTypeNameResolution() { PDataTypeFactory factory = PDataTypeFactory.getInstance();
