You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@phoenix.apache.org by ap...@apache.org on 2026/10/09 05:10:13 UTC

(phoenix) 14/16: PHOENIX-7998 Phase 14: Compatibility and version validation

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 <ap...@apache.org>
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) <no...@anthropic.com>
---
 .../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();