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();

Reply via email to