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/08 03:21:00 UTC

(phoenix) 01/17: PHOENIX-7998 Phase 1: Vector data type and SQL grammar

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 4c65327b0868ca82af15c9a803fefbc3da30e9ee
Author: Andrew Purtell <ap...@apache.org>
AuthorDate: Wed Sep 16 23:23:12 2026 -0700

    PHOENIX-7998 Phase 1: Vector data type and SQL grammar
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <no...@anthropic.com>
---
 phoenix-core-client/src/main/antlr3/PhoenixSQL.g   |  44 +-
 .../apache/phoenix/exception/SQLExceptionCode.java |   1 +
 .../org/apache/phoenix/jdbc/PhoenixResultSet.java  |   9 +-
 .../java/org/apache/phoenix/parse/ColumnDef.java   |  40 +-
 .../org/apache/phoenix/schema/ValueSchema.java     |  14 +-
 .../phoenix/schema/types/PArrayDataType.java       |  17 +
 .../org/apache/phoenix/schema/types/PDataType.java |   6 +
 .../phoenix/schema/types/PDataTypeFactory.java     |  16 +
 .../phoenix/schema/types/PVectorDataType.java      | 351 ++++++++++++++
 .../apache/phoenix/schema/types/PVectorDouble.java | 133 ++++++
 .../apache/phoenix/schema/types/PVectorFloat.java  | 133 ++++++
 .../java/org/apache/phoenix/util/SchemaUtil.java   |   8 +-
 .../org/apache/phoenix/end2end/VectorColumnIT.java | 441 +++++++++++++++++
 .../phoenix/expression/CoerceExpressionTest.java   |   2 +
 .../phoenix/parse/VectorColumnParseTest.java       | 222 +++++++++
 .../apache/phoenix/schema/types/PDataTypeTest.java |  30 +-
 .../phoenix/schema/types/VectorDataTypeTest.java   | 529 +++++++++++++++++++++
 17 files changed, 1957 insertions(+), 39 deletions(-)

diff --git a/phoenix-core-client/src/main/antlr3/PhoenixSQL.g b/phoenix-core-client/src/main/antlr3/PhoenixSQL.g
index 768eb60249..af0f0e5d9d 100644
--- a/phoenix-core-client/src/main/antlr3/PhoenixSQL.g
+++ b/phoenix-core-client/src/main/antlr3/PhoenixSQL.g
@@ -224,6 +224,7 @@ import org.apache.phoenix.schema.PTable.IndexType;
 import org.apache.phoenix.schema.PTable.CDCChangeScope;
 import org.apache.phoenix.schema.stats.StatisticsCollectionScope;
 import org.apache.phoenix.schema.types.PDataType;
+import org.apache.phoenix.schema.types.PDataTypeFactory;
 import org.apache.phoenix.schema.types.PDate;
 import org.apache.phoenix.schema.types.PTime;
 import org.apache.phoenix.schema.types.PTimestamp;
@@ -885,16 +886,35 @@ indexes returns [List<NamedNode> ret]
     :  v = index_name {$ret.add(v);}  (COMMA v = index_name {$ret.add(v);} )*
 ;
 
+// Column type specification with optional length and scale, supporting parameterized
+// VECTOR(<component type>, <dimension>) definitions.
+column_type returns [String name, Integer maxLength, Integer scale]
+    :   dt=identifier (LPAREN (ct=identifier COMMA)? l=NUMBER (COMMA s=NUMBER)? RPAREN)?
+        {
+            $name = dt;
+            $maxLength = l == null ? null : Integer.parseInt( l.getText() );
+            $scale = s == null ? null : Integer.parseInt( s.getText() );
+            if (ct != null) {
+                PDataType vectorType = "VECTOR".equalsIgnoreCase(dt) && s == null
+                    ? PDataTypeFactory.getInstance().typeForVector(ct) : null;
+                if (vectorType == null) {
+                    throw new ParseException("Unsupported type: " + dt + "(" + ct + ", ...)");
+                }
+                $name = vectorType.getSqlTypeName();
+            }
+        }
+    ;
+
 column_def returns [ColumnDef ret]
-    :   c=column_name dt=identifier (LPAREN l=NUMBER (COMMA s=NUMBER)? RPAREN)? ar=ARRAY? (lsq=LSQUARE (a=NUMBER)? RSQUARE)? (nn=NOT? n=NULL)? (DEFAULT df=expression)? ((pk=PRIMARY KEY (order=ASC|order=DESC)? rr=ROW_TIMESTAMP?)|(ENCODED_QUALIFIER eq=NUMBER))?
+    :   c=column_name t=column_type ar=ARRAY? (lsq=LSQUARE (a=NUMBER)? RSQUARE)? (nn=NOT? n=NULL)? (DEFAULT df=expression)? ((pk=PRIMARY KEY (order=ASC|order=DESC)? rr=ROW_TIMESTAMP?)|(ENCODED_QUALIFIER eq=NUMBER))?
         { $ret = factory.columnDef(
             c,
-            dt,
+            $t.name,
             ar != null || lsq != null,
             a == null ? null :  Integer.parseInt( a.getText() ),
             nn!=null ? Boolean.FALSE : n!=null ? Boolean.TRUE : null,
-            l == null ? null : Integer.parseInt( l.getText() ),
-            s == null ? null : Integer.parseInt( s.getText() ),
+            $t.maxLength,
+            $t.scale,
             pk != null, 
             order == null ? SortOrder.getDefault() : SortOrder.fromDDLValue(order.getText()),
             df == null ? null : df.toString(),
@@ -908,20 +928,20 @@ dyn_column_defs returns [List<ColumnDef> ret]
 ;
 
 dyn_column_def returns [ColumnDef ret]
-    :   c=column_name dt=identifier (LPAREN l=NUMBER (COMMA s=NUMBER)? RPAREN)? ar=ARRAY? (lsq=LSQUARE (a=NUMBER)? RSQUARE)?
-        {$ret = factory.columnDef(c, dt, ar != null || lsq != null, a == null ? null :  Integer.parseInt( a.getText() ), Boolean.TRUE,
-            l == null ? null : Integer.parseInt( l.getText() ),
-            s == null ? null : Integer.parseInt( s.getText() ),
+    :   c=column_name t=column_type ar=ARRAY? (lsq=LSQUARE (a=NUMBER)? RSQUARE)?
+        {$ret = factory.columnDef(c, $t.name, ar != null || lsq != null, a == null ? null :  Integer.parseInt( a.getText() ), Boolean.TRUE,
+            $t.maxLength,
+            $t.scale,
             false, 
             SortOrder.getDefault(),
             false); }
     ;
 
 dyn_column_name_or_def returns [ColumnDef ret]
-    :   c=column_name (dt=identifier (LPAREN l=NUMBER (COMMA s=NUMBER)? RPAREN)? ar=ARRAY? (lsq=LSQUARE (a=NUMBER)? RSQUARE)? )? 
-        {$ret = factory.columnDef(c, dt, ar != null || lsq != null, a == null ? null :  Integer.parseInt( a.getText() ), Boolean.TRUE,
-            l == null ? null : Integer.parseInt( l.getText() ),
-            s == null ? null : Integer.parseInt( s.getText() ),
+    :   c=column_name (t=column_type ar=ARRAY? (lsq=LSQUARE (a=NUMBER)? RSQUARE)? )? 
+        {$ret = factory.columnDef(c, $t.name, ar != null || lsq != null, a == null ? null :  Integer.parseInt( a.getText() ), Boolean.TRUE,
+            $t.maxLength,
+            $t.scale,
             false, 
             SortOrder.getDefault(),
             false); }
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java b/phoenix-core-client/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
index 3eddf2278f..c4b33a7649 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
@@ -88,6 +88,7 @@ public enum SQLExceptionCode {
   DATA_EXCEEDS_MAX_CAPACITY(206, "22003", "The data exceeds the max capacity for the data type."),
   MISSING_MAX_LENGTH(207, "22004", "Max length must be specified for type."),
   NONPOSITIVE_MAX_LENGTH(208, "22006", "Max length must have a positive length for type."),
+  VECTOR_DIMENSION_EXCEEDED(210, "22003", "Vector dimension exceeds the configured maximum."),
   DECIMAL_PRECISION_OUT_OF_RANGE(209, "22003",
     "Decimal precision outside of range. Should be within 1 and " + PDataType.MAX_PRECISION + "."),
   SERVER_ARITHMETIC_ERROR(212, "22012", "Arithmetic error on server."),
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java b/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java
index 6b8ae717de..5e058268eb 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java
@@ -263,8 +263,10 @@ public class PhoenixResultSet implements PhoenixMonitoredResultSet, SQLCloseable
     // Get the value using the expected type instead of trying to coerce to VARCHAR.
     // We can't coerce using our formatter because we don't have enough context in PDataType.
     ColumnProjector projector = getRowProjector().getColumnProjector(columnIndex - 1);
-    Array value =
-      (Array) projector.getValue(currentRow, projector.getExpression().getDataType(), ptr);
+    PDataType type = projector.getExpression().getDataType();
+    Array value = (Array) (type != null && type.isVectorType()
+      ? projector.getValue(currentRow, type, ptr, java.sql.Array.class)
+      : projector.getValue(currentRow, type, ptr));
     wasNull = (value == null);
     return value;
   }
@@ -707,6 +709,9 @@ public class PhoenixResultSet implements PhoenixMonitoredResultSet, SQLCloseable
     // This provides a simple way of getting a reasonable string representation
     // for types like DATE and TIME
     Format formatter = statement.getFormatter(type);
+    if (type != null && type.isVectorType()) {
+      return type.toStringLiteral(value, formatter);
+    }
     return formatter == null ? value.toString() : formatter.format(value);
   }
 
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/ColumnDef.java b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/ColumnDef.java
index 1bfe53b3b9..fde787f383 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/ColumnDef.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/ColumnDef.java
@@ -33,6 +33,7 @@ import org.apache.phoenix.schema.SortOrder;
 import org.apache.phoenix.schema.types.PDataType;
 import org.apache.phoenix.schema.types.PDecimal;
 import org.apache.phoenix.schema.types.PVarbinary;
+import org.apache.phoenix.schema.types.PVectorDataType;
 import org.apache.phoenix.util.ExpressionUtil;
 import org.apache.phoenix.util.SchemaUtil;
 
@@ -130,6 +131,23 @@ public class ColumnDef {
           // ignored. All decimal are stored with as much decimal points as possible.
           scale = scale == null ? PDataType.DEFAULT_SCALE : scale > maxLength ? maxLength : scale;
         }
+      } else if (baseType != null && baseType.isVectorType()) {
+        if (maxLength == null) {
+          throw new SQLExceptionInfo.Builder(SQLExceptionCode.MISSING_MAX_LENGTH)
+            .setColumnName(columnDefName.getColumnName()).build().buildException();
+        }
+        if (maxLength < 1) {
+          throw new SQLExceptionInfo.Builder(SQLExceptionCode.NONPOSITIVE_MAX_LENGTH)
+            .setColumnName(columnDefName.getColumnName()).build().buildException();
+        }
+        // Enforce maximum supported vector dimension
+        if (maxLength > PVectorDataType.MAX_VECTOR_DIMENSION) {
+          throw new SQLExceptionInfo.Builder(SQLExceptionCode.VECTOR_DIMENSION_EXCEEDED)
+            .setColumnName(columnDefName.getColumnName()).setMessage("Vector dimension " + maxLength
+              + " exceeds maximum of " + PVectorDataType.MAX_VECTOR_DIMENSION)
+            .build().buildException();
+        }
+        scale = null;
       } else {
         if (maxLength != null && maxLength < 1) {
           throw new SQLExceptionInfo.Builder(SQLExceptionCode.NONPOSITIVE_MAX_LENGTH)
@@ -244,15 +262,21 @@ public class ColumnDef {
   public String toString() {
     StringBuilder buf = new StringBuilder(columnDefName.getColumnNode().toString());
     buf.append(' ');
-    buf.append(dataType.getSqlTypeName());
-    if (maxLength != null) {
-      buf.append('(');
-      buf.append(maxLength);
-      if (scale != null) {
-        buf.append(',');
-        buf.append(scale); // has both max length and scale. For ex- decimal(10,2)
+    if (dataType != null && dataType.isVectorType()) {
+      // Format as VECTOR(<component_type>, <dimension>)
+      String typeName = dataType.getSqlTypeName();
+      buf.append(typeName, 0, typeName.length() - 1).append(", ").append(maxLength).append(')');
+    } else {
+      buf.append(dataType.getSqlTypeName());
+      if (maxLength != null) {
+        buf.append('(');
+        buf.append(maxLength);
+        if (scale != null) {
+          buf.append(',');
+          buf.append(scale); // has both max length and scale. For ex- decimal(10,2)
+        }
+        buf.append(')');
       }
-      buf.append(')');
     }
     if (isArray) {
       buf.append(' ');
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/ValueSchema.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/ValueSchema.java
index 0947c4e609..6a64ac46ee 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/ValueSchema.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/ValueSchema.java
@@ -26,6 +26,7 @@ import java.util.List;
 import org.apache.hadoop.io.Writable;
 import org.apache.hadoop.io.WritableUtils;
 import org.apache.phoenix.schema.types.PDataType;
+import org.apache.phoenix.schema.types.PVectorDataType;
 import org.apache.phoenix.util.SizedUtil;
 
 import org.apache.phoenix.thirdparty.com.google.common.base.Preconditions;
@@ -192,7 +193,10 @@ public abstract class ValueSchema implements Writable {
       this.isNullable = isNullable;
       if (this.type != null && this.type.isFixedWidth() && this.type.getByteSize() == null) {
         if (datum.getMaxLength() != null) {
-          this.byteSize = datum.getMaxLength();
+          // Vector maxLength represents dimension count rather than byte size.
+          this.byteSize = this.type.isVectorType()
+            ? this.type.estimateByteSizeFromLength(datum.getMaxLength())
+            : datum.getMaxLength();
         }
       }
     }
@@ -235,7 +239,13 @@ public abstract class ValueSchema implements Writable {
 
     @Override
     public Integer getMaxLength() {
-      return type.isFixedWidth() ? byteSize : null;
+      if (!type.isFixedWidth()) {
+        return null;
+      }
+      // Derive dimension count from total byte size for vector types.
+      return type.isVectorType()
+        ? byteSize / ((PVectorDataType) type).getElementByteSize()
+        : byteSize;
     }
 
     @Override
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PArrayDataType.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PArrayDataType.java
index 20dd8a6ff7..c93d658374 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PArrayDataType.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PArrayDataType.java
@@ -309,12 +309,29 @@ public abstract class PArrayDataType<T> extends PDataType<T> {
     throw new UnsupportedOperationException();
   }
 
+  /** Returns true if the given array type has numeric elements. */
+  private static boolean isNumericArray(PDataType arrayType) {
+    PDataType elemType = arrayBaseType(arrayType);
+    return elemType.isCoercibleTo(PDouble.INSTANCE) || elemType.isCoercibleTo(PDecimal.INSTANCE);
+  }
+
+  @Override
+  public boolean isCastableTo(PDataType targetType) {
+    if (targetType != null && targetType.isVectorType()) {
+      return isNumericArray(this);
+    }
+    return super.isCastableTo(targetType);
+  }
+
   @Override
   public boolean isCoercibleTo(PDataType targetType, Object value) {
     return targetType.isCoercibleTo(targetType, value);
   }
 
   public boolean isCoercibleTo(PDataType targetType, PDataType expectedTargetType) {
+    if (targetType.isVectorType()) {
+      return isNumericArray(expectedTargetType);
+    }
     if (!targetType.isArrayType()) {
       return false;
     } else {
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PDataType.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PDataType.java
index cf1bcb93fb..91c902b82a 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PDataType.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PDataType.java
@@ -165,6 +165,10 @@ public abstract class PDataType<T> implements DataType<T>, Comparable<PDataType<
     return false;
   }
 
+  public boolean isVectorType() {
+    return false;
+  }
+
   public final int compareTo(byte[] lhs, int lhsOffset, int lhsLength, SortOrder lhsSortOrder,
     byte[] rhs, int rhsOffset, int rhsLength, SortOrder rhsSortOrder, PDataType rhsType) {
     Preconditions.checkNotNull(lhsSortOrder);
@@ -564,6 +568,8 @@ public abstract class PDataType<T> implements DataType<T>, Comparable<PDataType<
   public final static Integer DOUBLE_PRECISION = 15;
 
   public static final int ARRAY_TYPE_BASE = 3000;
+  public static final int VECTOR_FLOAT_TYPE = 4001;
+  public static final int VECTOR_DOUBLE_TYPE = 4002;
   public static final int JSON_TYPE = 5000;
   public static final int BSON_TYPE = 7000;
   public static final int VARBINARY_ENCODED_TYPE = 9000;
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PDataTypeFactory.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PDataTypeFactory.java
index 648426bd90..34c85b2bb9 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PDataTypeFactory.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PDataTypeFactory.java
@@ -126,6 +126,8 @@ public class PDataTypeFactory {
     types.add(PJson.INSTANCE);
     types.add(PBson.INSTANCE);
     types.add(PVarbinaryEncoded.INSTANCE);
+    types.add(PVectorFloat.INSTANCE);
+    types.add(PVectorDouble.INSTANCE);
 
     classToInstance = new HashMap<>(types.size());
     for (PDataType t : types) {
@@ -165,4 +167,18 @@ public class PDataTypeFactory {
       return javaClassToInstance.get(clazz);
     }
   }
+
+  /**
+   * Returns the vector data type corresponding to the given component type name, or null if
+   * unsupported.
+   */
+  public PDataType typeForVector(String componentType) {
+    String normalized = componentType.trim().toUpperCase();
+    if (PFloat.INSTANCE.getSqlTypeName().equals(normalized)) {
+      return PVectorFloat.INSTANCE;
+    } else if (PDouble.INSTANCE.getSqlTypeName().equals(normalized)) {
+      return PVectorDouble.INSTANCE;
+    }
+    return null;
+  }
 }
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVectorDataType.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVectorDataType.java
new file mode 100644
index 0000000000..73a428f5fe
--- /dev/null
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVectorDataType.java
@@ -0,0 +1,351 @@
+/*
+ * 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.schema.types;
+
+import java.lang.reflect.Array;
+import java.sql.SQLException;
+import java.text.Format;
+import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.schema.ConstraintViolationException;
+import org.apache.phoenix.schema.SortOrder;
+import org.apache.phoenix.util.ByteUtil;
+
+/**
+ * Base class for fixed-dimension vector types {@code VECTOR(FLOAT, N)} and
+ * {@code VECTOR(DOUBLE, N)}.
+ * <p>
+ * Vectors are serialized as dense, contiguous IEEE 754 big-endian values without header metadata or
+ * null masks. Vector dimension is stored in column max length. Vectors do not support primary key
+ * usage or ordering comparisons.
+ * @param <T> the primitive array type of the vector, {@code float[]} or {@code double[]}
+ */
+public abstract class PVectorDataType<T> extends PDataType<T> {
+
+  /** Maximum supported vector dimension for column definitions. */
+  public static final int MAX_VECTOR_DIMENSION = 65536;
+
+  protected PVectorDataType(String sqlTypeName, int sqlType, Class<T> clazz, int ordinal) {
+    super(sqlTypeName, sqlType, clazz, null, ordinal);
+  }
+
+  /** Returns the width in bytes of one vector element. */
+  public abstract int getElementByteSize();
+
+  /** Returns the scalar type of one vector element. */
+  public abstract PDataType getElementType();
+
+  /** Returns the number of elements in a vector of this type. */
+  protected abstract int length(T vector);
+
+  /** Allocates a vector of this type with the given dimension. */
+  protected abstract T allocate(int dimension);
+
+  /** Sets one element of a vector of this type from a numeric value. */
+  protected abstract void set(T vector, int index, Number value);
+
+  /** Returns one element of a vector of this type, boxed. */
+  protected abstract Number get(T vector, int index);
+
+  /** Parses one element from its string representation. */
+  protected abstract Number parseElement(String s);
+
+  /** Packs a vector into a byte buffer as contiguous big-endian IEEE 754 values. */
+  protected abstract void pack(T vector, byte[] buf, int offset);
+
+  /** Unpacks a vector from a packed byte range written with the given sort order. */
+  protected abstract T unpack(byte[] buf, int offset, int length, SortOrder sortOrder);
+
+  @Override
+  public boolean isVectorType() {
+    return true;
+  }
+
+  @Override
+  public boolean isFixedWidth() {
+    return true;
+  }
+
+  @Override
+  public boolean canBePrimaryKey() {
+    return false;
+  }
+
+  @Override
+  public boolean isComparisonSupported() {
+    return false;
+  }
+
+  @Override
+  public Integer getByteSize() {
+    return null;
+  }
+
+  @Override
+  public int estimateByteSize(Object o) {
+    Integer dim = getMaxLength(o);
+    return dim == null ? 0 : dim * getElementByteSize();
+  }
+
+  @Override
+  public Integer estimateByteSizeFromLength(Integer length) {
+    return length == null ? null : length * getElementByteSize();
+  }
+
+  @Override
+  public Integer getMaxLength(Object o) {
+    if (o == null) {
+      return null;
+    }
+    if (o instanceof PhoenixArray) {
+      return ((PhoenixArray) o).getDimensions();
+    }
+    if (o instanceof java.sql.Array) {
+      o = getArray((java.sql.Array) o);
+    }
+    return o != null && o.getClass().isArray() ? Array.getLength(o) : null;
+  }
+
+  @Override
+  public int compareTo(Object lhs, Object rhs, PDataType rhsType) {
+    if (lhs == rhs) {
+      return 0;
+    }
+    if (lhs == null) {
+      return -1;
+    }
+    if (rhs == null) {
+      return 1;
+    }
+    return Bytes.compareTo(toBytes(lhs), rhsType.toBytes(rhs));
+  }
+
+  @Override
+  public byte[] toBytes(Object object) {
+    if (object == null) {
+      return ByteUtil.EMPTY_BYTE_ARRAY;
+    }
+    T vector = toVector(object);
+    byte[] bytes = new byte[length(vector) * getElementByteSize()];
+    pack(vector, bytes, 0);
+    return bytes;
+  }
+
+  @Override
+  public int toBytes(Object object, byte[] bytes, int offset) {
+    if (object == null) {
+      return 0;
+    }
+    T vector = toVector(object);
+    pack(vector, bytes, offset);
+    return length(vector) * getElementByteSize();
+  }
+
+  @Override
+  public byte[] toBytes(Object object, SortOrder sortOrder) {
+    byte[] bytes = toBytes(object);
+    if (sortOrder == SortOrder.DESC) {
+      SortOrder.invert(bytes, 0, bytes, 0, bytes.length);
+    }
+    return bytes;
+  }
+
+  @Override
+  public T toObject(byte[] bytes, int offset, int length, PDataType actualType, SortOrder sortOrder,
+    Integer maxLength, Integer scale) {
+    if (bytes == null || length == 0) {
+      return null;
+    }
+    return unpack(bytes, offset, length, sortOrder == null ? SortOrder.getDefault() : sortOrder);
+  }
+
+  @Override
+  public Object toObject(byte[] bytes, int offset, int length, PDataType actualType,
+    SortOrder sortOrder, Integer maxLength, Integer scale, Class jdbcType) throws SQLException {
+    if (getJavaClass().isAssignableFrom(jdbcType) || Object.class.equals(jdbcType)) {
+      return toObject(bytes, offset, length, actualType, sortOrder, maxLength, scale);
+    }
+    boolean boxed = jdbcType.isArray() && !jdbcType.getComponentType().isPrimitive();
+    if (boxed || java.sql.Array.class.isAssignableFrom(jdbcType)) {
+      T raw = toObject(bytes, offset, length, actualType, sortOrder, maxLength, scale);
+      if (raw == null) {
+        return null;
+      }
+      Object[] elements =
+        (Object[]) Array.newInstance(getElementType().getJavaClass(), length(raw));
+      for (int i = 0; i < elements.length; i++) {
+        elements[i] = get(raw, i);
+      }
+      return boxed ? elements : PArrayDataType.instantiatePhoenixArray(getElementType(), elements);
+    }
+    throw newMismatchException(actualType, jdbcType);
+  }
+
+  @Override
+  public Object toObject(String value) {
+    if (value == null) {
+      return null;
+    }
+    String str = value.trim();
+    if (str.isEmpty()) {
+      return null;
+    }
+    if (str.startsWith("[") && str.endsWith("]")) {
+      str = str.substring(1, str.length() - 1).trim();
+    }
+    if (str.isEmpty()) {
+      return allocate(0);
+    }
+    String[] parts = str.split(",");
+    T vector = allocate(parts.length);
+    for (int i = 0; i < parts.length; i++) {
+      set(vector, i, parseElement(parts[i].trim()));
+    }
+    return vector;
+  }
+
+  /**
+   * Coerces an array or collection into a typed vector. Supports numeric arrays (primitive or
+   * boxed), Phoenix arrays, and JDBC arrays. Null elements are prohibited.
+   */
+  @Override
+  public Object toObject(Object object, PDataType actualType) {
+    if (object == null) {
+      return null;
+    }
+    if (getJavaClass().isInstance(object)) {
+      return object;
+    }
+    if (object instanceof java.sql.Array) {
+      object = getArray((java.sql.Array) object);
+    }
+    if (object.getClass().isArray()) {
+      int dim = Array.getLength(object);
+      T vector = allocate(dim);
+      for (int i = 0; i < dim; i++) {
+        Object e = Array.get(object, i);
+        if (e == null) {
+          throw new ConstraintViolationException("Vector elements must not be NULL");
+        }
+        if (!(e instanceof Number)) {
+          return throwConstraintViolationException(actualType, this);
+        }
+        set(vector, i, (Number) e);
+      }
+      return vector;
+    }
+    if (object instanceof String) {
+      return toObject((String) object);
+    }
+    if (object instanceof byte[]) {
+      byte[] b = (byte[]) object;
+      return toObject(b, 0, b.length);
+    }
+    return throwConstraintViolationException(actualType, this);
+  }
+
+  @Override
+  public boolean isCastableTo(PDataType targetType) {
+    return this.equals(targetType) || targetType.isArrayType()
+      || targetType.equals(PVarchar.INSTANCE) || super.isCastableTo(targetType);
+  }
+
+  @Override
+  public boolean isCoercibleTo(PDataType targetType, Object value) {
+    return value == null || isCoercibleTo(targetType);
+  }
+
+  @Override
+  public boolean isBytesComparableWith(PDataType otherType) {
+    return this.equals(otherType);
+  }
+
+  @Override
+  public boolean isSizeCompatible(ImmutableBytesWritable ptr, Object value, PDataType srcType,
+    SortOrder sortOrder, Integer maxLength, Integer scale, Integer desiredMaxLength,
+    Integer desiredScale) {
+    if (desiredMaxLength == null) {
+      return true;
+    }
+    Integer actualDimension = getMaxLength(value);
+    if (actualDimension != null) {
+      checkDimension(desiredMaxLength, actualDimension);
+    }
+    if (ptr != null && ptr.getLength() != 0 && srcType == this) {
+      checkDimension(desiredMaxLength, ptr.getLength() / getElementByteSize());
+    }
+    return true;
+  }
+
+  @Override
+  public void coerceBytes(ImmutableBytesWritable ptr, Object o, PDataType actualType,
+    Integer actualMaxLength, Integer actualScale, SortOrder actualModifier,
+    Integer desiredMaxLength, Integer desiredScale, SortOrder expectedModifier) {
+    super.coerceBytes(ptr, o, actualType, actualMaxLength, actualScale, actualModifier,
+      desiredMaxLength, desiredScale, expectedModifier);
+    if (ptr.getLength() > 0 && desiredMaxLength != null) {
+      checkDimension(desiredMaxLength, ptr.getLength() / getElementByteSize());
+    }
+  }
+
+  @Override
+  public Object getSampleValue(Integer maxLength, Integer arrayLength) {
+    int dim = maxLength == null ? (arrayLength == null ? 3 : arrayLength) : maxLength;
+    T sample = allocate(dim);
+    for (int i = 0; i < dim; i++) {
+      set(sample, i, RANDOM.get().nextFloat());
+    }
+    return sample;
+  }
+
+  @Override
+  public String toStringLiteral(Object o, Format formatter) {
+    if (o == null) {
+      return String.valueOf((Object) null);
+    }
+    T vector = toVector(o);
+    StringBuilder buf = new StringBuilder("[");
+    for (int i = 0; i < length(vector); i++) {
+      if (i > 0) {
+        buf.append(", ");
+      }
+      buf.append(get(vector, i));
+    }
+    return buf.append(']').toString();
+  }
+
+  @SuppressWarnings("unchecked")
+  private T toVector(Object object) {
+    return (T) toObject(object, this);
+  }
+
+  private static void checkDimension(int expected, int actual) {
+    if (actual != expected) {
+      throw new ConstraintViolationException(
+        "Vector dimension mismatch: expected " + expected + ", but got " + actual);
+    }
+  }
+
+  private static Object getArray(java.sql.Array array) {
+    try {
+      return array.getArray();
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVectorDouble.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVectorDouble.java
new file mode 100644
index 0000000000..b9c59ddea7
--- /dev/null
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVectorDouble.java
@@ -0,0 +1,133 @@
+/*
+ * 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.schema.types;
+
+import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.schema.SortOrder;
+
+/**
+ * Data type representing fixed-dimension double-precision floating point vectors,
+ * {@code VECTOR(DOUBLE, N)}. Stored as packed big-endian IEEE 754 values.
+ */
+public class PVectorDouble extends PVectorDataType<double[]> {
+
+  public static final PVectorDouble INSTANCE = new PVectorDouble();
+
+  private PVectorDouble() {
+    super("VECTOR(DOUBLE)", PDataType.VECTOR_DOUBLE_TYPE, double[].class, 52);
+  }
+
+  /** Reads one element by index from a packed ascending-order vector buffer. */
+  public static double readElement(byte[] buf, int offset, int index) {
+    return Bytes.toDouble(buf, offset + index * Bytes.SIZEOF_DOUBLE);
+  }
+
+  /** Reads one element by index from a packed vector buffer with the given sort order. */
+  public static double readElement(byte[] buf, int offset, int index, SortOrder sortOrder) {
+    long b = Bytes.toLong(buf, offset + index * Bytes.SIZEOF_DOUBLE);
+    if (sortOrder == SortOrder.DESC) {
+      b ^= 0xFFFFFFFFFFFFFFFFL;
+    }
+    return Double.longBitsToDouble(b);
+  }
+
+  /** Reads one element by index from a packed ascending-order vector pointer. */
+  public static double readElement(ImmutableBytesWritable ptr, int index) {
+    return readElement(ptr.get(), ptr.getOffset(), index);
+  }
+
+  /** Decodes all elements from a packed ascending-order vector byte range. */
+  public static double[] readElements(byte[] buf, int offset, int length) {
+    return readElements(buf, offset, length, SortOrder.ASC);
+  }
+
+  /** Decodes all elements from a packed vector byte range written with the given sort order. */
+  public static double[] readElements(byte[] buf, int offset, int length, SortOrder sortOrder) {
+    double[] out = new double[length / Bytes.SIZEOF_DOUBLE];
+    for (int i = 0; i < out.length; i++) {
+      out[i] = readElement(buf, offset, i, sortOrder);
+    }
+    return out;
+  }
+
+  /**
+   * Writes a vector into a byte buffer as packed contiguous big-endian IEEE 754 values.
+   * @throws IllegalArgumentException if the buffer is too small to contain the vector
+   */
+  public static void writeElements(double[] vector, byte[] buf, int offset) {
+    if (buf.length < offset + (long) vector.length * Bytes.SIZEOF_DOUBLE) {
+      throw new IllegalArgumentException("Buffer too small: need "
+        + (offset + vector.length * Bytes.SIZEOF_DOUBLE) + " bytes, have " + buf.length);
+    }
+    for (int i = 0; i < vector.length; i++) {
+      Bytes.putDouble(buf, offset + i * Bytes.SIZEOF_DOUBLE, vector[i]);
+    }
+  }
+
+  @Override
+  public int getElementByteSize() {
+    return Bytes.SIZEOF_DOUBLE;
+  }
+
+  @Override
+  public PDataType getElementType() {
+    return PDouble.INSTANCE;
+  }
+
+  @Override
+  protected int length(double[] vector) {
+    return vector.length;
+  }
+
+  @Override
+  protected double[] allocate(int dimension) {
+    return new double[dimension];
+  }
+
+  @Override
+  protected void set(double[] vector, int index, Number value) {
+    vector[index] = value.doubleValue();
+  }
+
+  @Override
+  protected Number get(double[] vector, int index) {
+    return vector[index];
+  }
+
+  @Override
+  protected Number parseElement(String s) {
+    return Double.valueOf(s);
+  }
+
+  @Override
+  protected void pack(double[] vector, byte[] buf, int offset) {
+    writeElements(vector, buf, offset);
+  }
+
+  @Override
+  protected double[] unpack(byte[] buf, int offset, int length, SortOrder sortOrder) {
+    return readElements(buf, offset, length, sortOrder);
+  }
+
+  @Override
+  public boolean isCoercibleTo(PDataType targetType) {
+    return this.equals(targetType) || targetType.equals(PDoubleArray.INSTANCE)
+      || super.isCoercibleTo(targetType);
+  }
+}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVectorFloat.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVectorFloat.java
new file mode 100644
index 0000000000..619a719ae2
--- /dev/null
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVectorFloat.java
@@ -0,0 +1,133 @@
+/*
+ * 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.schema.types;
+
+import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.schema.SortOrder;
+
+/**
+ * Data type representing fixed-dimension single-precision floating point vectors,
+ * {@code VECTOR(FLOAT, N)}. Stored as packed big-endian IEEE 754 values.
+ */
+public class PVectorFloat extends PVectorDataType<float[]> {
+
+  public static final PVectorFloat INSTANCE = new PVectorFloat();
+
+  private PVectorFloat() {
+    super("VECTOR(FLOAT)", PDataType.VECTOR_FLOAT_TYPE, float[].class, 51);
+  }
+
+  /** Reads one element by index from a packed ascending-order vector buffer. */
+  public static float readElement(byte[] buf, int offset, int index) {
+    return Bytes.toFloat(buf, offset + index * Bytes.SIZEOF_FLOAT);
+  }
+
+  /** Reads one element by index from a packed vector buffer with the given sort order. */
+  public static float readElement(byte[] buf, int offset, int index, SortOrder sortOrder) {
+    int b = Bytes.toInt(buf, offset + index * Bytes.SIZEOF_FLOAT);
+    if (sortOrder == SortOrder.DESC) {
+      b ^= 0xFFFFFFFF;
+    }
+    return Float.intBitsToFloat(b);
+  }
+
+  /** Reads one element by index from a packed ascending-order vector pointer. */
+  public static float readElement(ImmutableBytesWritable ptr, int index) {
+    return readElement(ptr.get(), ptr.getOffset(), index);
+  }
+
+  /** Decodes all elements from a packed ascending-order vector byte range. */
+  public static float[] readElements(byte[] buf, int offset, int length) {
+    return readElements(buf, offset, length, SortOrder.ASC);
+  }
+
+  /** Decodes all elements from a packed vector byte range written with the given sort order. */
+  public static float[] readElements(byte[] buf, int offset, int length, SortOrder sortOrder) {
+    float[] out = new float[length / Bytes.SIZEOF_FLOAT];
+    for (int i = 0; i < out.length; i++) {
+      out[i] = readElement(buf, offset, i, sortOrder);
+    }
+    return out;
+  }
+
+  /**
+   * Writes a vector into a byte buffer as packed contiguous big-endian IEEE 754 values.
+   * @throws IllegalArgumentException if the buffer is too small to contain the vector
+   */
+  public static void writeElements(float[] vector, byte[] buf, int offset) {
+    if (buf.length < offset + (long) vector.length * Bytes.SIZEOF_FLOAT) {
+      throw new IllegalArgumentException("Buffer too small: need "
+        + (offset + vector.length * Bytes.SIZEOF_FLOAT) + " bytes, have " + buf.length);
+    }
+    for (int i = 0; i < vector.length; i++) {
+      Bytes.putFloat(buf, offset + i * Bytes.SIZEOF_FLOAT, vector[i]);
+    }
+  }
+
+  @Override
+  public int getElementByteSize() {
+    return Bytes.SIZEOF_FLOAT;
+  }
+
+  @Override
+  public PDataType getElementType() {
+    return PFloat.INSTANCE;
+  }
+
+  @Override
+  protected int length(float[] vector) {
+    return vector.length;
+  }
+
+  @Override
+  protected float[] allocate(int dimension) {
+    return new float[dimension];
+  }
+
+  @Override
+  protected void set(float[] vector, int index, Number value) {
+    vector[index] = value.floatValue();
+  }
+
+  @Override
+  protected Number get(float[] vector, int index) {
+    return vector[index];
+  }
+
+  @Override
+  protected Number parseElement(String s) {
+    return Float.valueOf(s);
+  }
+
+  @Override
+  protected void pack(float[] vector, byte[] buf, int offset) {
+    writeElements(vector, buf, offset);
+  }
+
+  @Override
+  protected float[] unpack(byte[] buf, int offset, int length, SortOrder sortOrder) {
+    return readElements(buf, offset, length, sortOrder);
+  }
+
+  @Override
+  public boolean isCoercibleTo(PDataType targetType) {
+    return this.equals(targetType) || targetType.equals(PFloatArray.INSTANCE)
+      || super.isCoercibleTo(targetType);
+  }
+}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/util/SchemaUtil.java b/phoenix-core-client/src/main/java/org/apache/phoenix/util/SchemaUtil.java
index 81057f305f..1cce4a4afe 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/util/SchemaUtil.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/util/SchemaUtil.java
@@ -905,7 +905,13 @@ public class SchemaUtil {
   public static int getFixedByteSize(PDatum e) {
     assert (e.getDataType().isFixedWidth());
     Integer maxLength = e.getMaxLength();
-    return maxLength == null ? e.getDataType().getByteSize() : maxLength;
+    if (maxLength == null) {
+      return e.getDataType().getByteSize();
+    }
+    // Vector maxLength represents dimension count rather than byte size.
+    return e.getDataType().isVectorType()
+      ? e.getDataType().estimateByteSizeFromLength(maxLength)
+      : maxLength;
   }
 
   public static short getMaxKeySeq(PTable table) {
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/VectorColumnIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/VectorColumnIT.java
new file mode 100644
index 0000000000..95be94b39c
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/VectorColumnIT.java
@@ -0,0 +1,441 @@
+/*
+ * 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.end2end;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.sql.Array;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import org.apache.phoenix.exception.SQLExceptionCode;
+import org.apache.phoenix.jdbc.PhoenixConnection;
+import org.apache.phoenix.schema.PColumn;
+import org.apache.phoenix.schema.PTable;
+import org.apache.phoenix.schema.PTableKey;
+import org.apache.phoenix.schema.types.PVectorDouble;
+import org.apache.phoenix.schema.types.PVectorFloat;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+@Category(ParallelStatsDisabledTest.class)
+public class VectorColumnIT extends ParallelStatsDisabledIT {
+
+  @Test
+  public void testVectorDimensionPersistenceRoundTrip() throws Exception {
+    String tableName = generateUniqueName();
+    try (Connection conn = DriverManager.getConnection(getUrl())) {
+      conn.createStatement()
+        .execute("CREATE TABLE " + tableName + " (pk INTEGER PRIMARY KEY, v VECTOR(FLOAT, 128))");
+      PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class);
+      PTable pTable = pconn.getTable(new PTableKey(null, tableName));
+      assertNotNull(pTable);
+      PColumn pColumn = pTable.getColumnForColumnName("V");
+      assertNotNull(pColumn);
+      assertEquals(Integer.valueOf(128), pColumn.getMaxLength());
+      assertEquals(PVectorFloat.INSTANCE, pColumn.getDataType());
+
+      // Verify uncached metadata resolution from system catalog
+      try (Connection conn2 = DriverManager.getConnection(getUrl())) {
+        PhoenixConnection pconn2 = conn2.unwrap(PhoenixConnection.class);
+        PTable pTableFresh = pconn2.getTableNoCache(tableName);
+        assertNotNull(pTableFresh);
+        PColumn pColumnFresh = pTableFresh.getColumnForColumnName("V");
+        assertNotNull(pColumnFresh);
+        assertEquals(Integer.valueOf(128), pColumnFresh.getMaxLength());
+        assertEquals(PVectorFloat.INSTANCE, pColumnFresh.getDataType());
+      }
+
+      // Recreate table with dimension 256
+      conn.createStatement().execute("DROP TABLE " + tableName);
+      conn.createStatement()
+        .execute("CREATE TABLE " + tableName + " (pk INTEGER PRIMARY KEY, v VECTOR(FLOAT, 256))");
+      pTable = pconn.getTable(new PTableKey(null, tableName));
+      assertNotNull(pTable);
+      pColumn = pTable.getColumnForColumnName("V");
+      assertNotNull(pColumn);
+      assertEquals(Integer.valueOf(256), pColumn.getMaxLength());
+      assertEquals(PVectorFloat.INSTANCE, pColumn.getDataType());
+
+      // Verify updated dimension on uncached connection
+      try (Connection conn3 = DriverManager.getConnection(getUrl())) {
+        PhoenixConnection pconn3 = conn3.unwrap(PhoenixConnection.class);
+        PTable pTableFresh256 = pconn3.getTableNoCache(tableName);
+        assertNotNull(pTableFresh256);
+        PColumn pColumnFresh256 = pTableFresh256.getColumnForColumnName("V");
+        assertNotNull(pColumnFresh256);
+        assertEquals(Integer.valueOf(256), pColumnFresh256.getMaxLength());
+        assertEquals(PVectorFloat.INSTANCE, pColumnFresh256.getDataType());
+      }
+    }
+  }
+
+  @Test
+  public void testJdbcMetadataReporting() throws Exception {
+    String tableName = generateUniqueName();
+    try (Connection conn = DriverManager.getConnection(getUrl())) {
+      conn.createStatement()
+        .execute("CREATE TABLE " + tableName + " (pk INTEGER PRIMARY KEY, v VECTOR(FLOAT, 128))");
+
+      DatabaseMetaData dbmd = conn.getMetaData();
+      try (ResultSet rs = dbmd.getColumns(null, "", tableName, "V")) {
+        assertTrue(rs.next());
+        assertEquals("V", rs.getString("COLUMN_NAME"));
+        assertEquals(PVectorFloat.INSTANCE.getSqlType(), rs.getInt("DATA_TYPE"));
+        assertEquals("VECTOR(FLOAT)", rs.getString("TYPE_NAME"));
+        assertEquals(128, rs.getInt("COLUMN_SIZE"));
+      }
+
+      try (ResultSet rs = conn.createStatement().executeQuery("SELECT v FROM " + tableName)) {
+        ResultSetMetaData rsmd = rs.getMetaData();
+        String colTypeName = rsmd.getColumnTypeName(1);
+        assertNotNull(colTypeName);
+        assertTrue("Column type name should contain VECTOR: " + colTypeName,
+          colTypeName.contains("VECTOR"));
+      }
+    }
+  }
+
+  @Test
+  public void testVectorDoubleDimensionPersistence() throws Exception {
+    String tableName = generateUniqueName();
+    try (Connection conn = DriverManager.getConnection(getUrl())) {
+      conn.createStatement()
+        .execute("CREATE TABLE " + tableName + " (pk INTEGER PRIMARY KEY, v VECTOR(DOUBLE, 64))");
+      PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class);
+      PTable pTable = pconn.getTable(new PTableKey(null, tableName));
+      assertNotNull(pTable);
+      PColumn pColumn = pTable.getColumnForColumnName("V");
+      assertNotNull(pColumn);
+      assertEquals(Integer.valueOf(64), pColumn.getMaxLength());
+      assertEquals(PVectorDouble.INSTANCE, pColumn.getDataType());
+
+      DatabaseMetaData dbmd = conn.getMetaData();
+      try (ResultSet rs = dbmd.getColumns(null, "", tableName, "V")) {
+        assertTrue(rs.next());
+        assertEquals("VECTOR(DOUBLE)", rs.getString("TYPE_NAME"));
+        assertEquals(64, rs.getInt("COLUMN_SIZE"));
+        assertEquals(PVectorDouble.INSTANCE.getSqlType(), rs.getInt("DATA_TYPE"));
+      }
+    }
+  }
+
+  @Test
+  public void testMultipleVectorColumnsPersistence() throws Exception {
+    String tableName = generateUniqueName();
+    try (Connection conn = DriverManager.getConnection(getUrl())) {
+      conn.createStatement().execute("CREATE TABLE " + tableName
+        + " (pk INTEGER PRIMARY KEY, vf VECTOR(FLOAT, 512), vd VECTOR(DOUBLE, 1536))");
+      PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class);
+      PTable pTable = pconn.getTable(new PTableKey(null, tableName));
+      assertNotNull(pTable);
+
+      PColumn colF = pTable.getColumnForColumnName("VF");
+      assertNotNull(colF);
+      assertEquals(Integer.valueOf(512), colF.getMaxLength());
+      assertEquals(PVectorFloat.INSTANCE, colF.getDataType());
+
+      PColumn colD = pTable.getColumnForColumnName("VD");
+      assertNotNull(colD);
+      assertEquals(Integer.valueOf(1536), colD.getMaxLength());
+      assertEquals(PVectorDouble.INSTANCE, colD.getDataType());
+    }
+  }
+
+  @Test
+  public void testJdbcArrayBindingRoundTrip() throws Exception {
+    String tableName = generateUniqueName();
+    try (Connection conn = DriverManager.getConnection(getUrl())) {
+      conn.createStatement()
+        .execute("CREATE TABLE " + tableName + " (pk INTEGER PRIMARY KEY, v VECTOR(FLOAT, 3))");
+
+      String upsertSql = "UPSERT INTO " + tableName + " VALUES (?, ?)";
+      try (PreparedStatement ps = conn.prepareStatement(upsertSql)) {
+        ps.setInt(1, 1);
+        Array array = conn.createArrayOf("FLOAT", new Float[] { 1.0f, 2.0f, 3.0f });
+        ps.setArray(2, array);
+        ps.executeUpdate();
+      }
+      conn.commit();
+
+      try (ResultSet rs =
+        conn.createStatement().executeQuery("SELECT v FROM " + tableName + " WHERE pk = 1")) {
+        assertTrue(rs.next());
+        float[] retrieved = (float[]) rs.getObject(1);
+        assertNotNull(retrieved);
+        assertArrayEquals(new float[] { 1.0f, 2.0f, 3.0f }, retrieved, 0.00001f);
+
+        Array retrievedArray = rs.getArray(1);
+        assertNotNull(retrievedArray);
+        Object arrayObj = retrievedArray.getArray();
+        if (arrayObj instanceof Float[]) {
+          assertArrayEquals(new Float[] { 1.0f, 2.0f, 3.0f }, (Float[]) arrayObj);
+        } else if (arrayObj instanceof float[]) {
+          assertArrayEquals(new float[] { 1.0f, 2.0f, 3.0f }, (float[]) arrayObj, 0.00001f);
+        }
+
+        String str = rs.getString(1);
+        assertNotNull(str);
+        assertTrue("Expected string representation containing numbers: " + str,
+          str.contains("1.0") && str.contains("2.0") && str.contains("3.0"));
+      }
+    }
+  }
+
+  @Test
+  public void testDimensionMismatchRejection() throws Exception {
+    String tableName = generateUniqueName();
+    try (Connection conn = DriverManager.getConnection(getUrl())) {
+      conn.createStatement()
+        .execute("CREATE TABLE " + tableName + " (pk INTEGER PRIMARY KEY, v VECTOR(FLOAT, 3))");
+
+      String upsertSql = "UPSERT INTO " + tableName + " VALUES (?, ?)";
+      try (PreparedStatement ps = conn.prepareStatement(upsertSql)) {
+        ps.setInt(1, 1);
+        Array array = conn.createArrayOf("FLOAT", new Float[] { 1.0f, 2.0f, 3.0f, 4.0f });
+        ps.setArray(2, array);
+        try {
+          ps.executeUpdate();
+          fail("Should have thrown exception on dimension mismatch");
+        } catch (SQLException e) {
+          assertEquals(SQLExceptionCode.CONSTRAINT_VIOLATION.getErrorCode(), e.getErrorCode());
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testArrayConstructorSyntax() throws Exception {
+    String tableName = generateUniqueName();
+    try (Connection conn = DriverManager.getConnection(getUrl())) {
+      conn.createStatement()
+        .execute("CREATE TABLE " + tableName + " (pk INTEGER PRIMARY KEY, v VECTOR(FLOAT, 3))");
+
+      conn.createStatement()
+        .execute("UPSERT INTO " + tableName + " VALUES (1, ARRAY[1.0, 2.0, 3.0])");
+      conn.commit();
+
+      try (ResultSet rs =
+        conn.createStatement().executeQuery("SELECT v FROM " + tableName + " WHERE pk = 1")) {
+        assertTrue(rs.next());
+        float[] retrieved = (float[]) rs.getObject(1);
+        assertNotNull(retrieved);
+        assertArrayEquals(new float[] { 1.0f, 2.0f, 3.0f }, retrieved, 0.00001f);
+      }
+    }
+  }
+
+  @Test
+  public void testJsonStringLiteralBinding() throws Exception {
+    String tableName = generateUniqueName();
+    try (Connection conn = DriverManager.getConnection(getUrl())) {
+      conn.createStatement()
+        .execute("CREATE TABLE " + tableName + " (pk INTEGER PRIMARY KEY, v VECTOR(FLOAT, 3))");
+
+      conn.createStatement().execute("UPSERT INTO " + tableName + " VALUES (1, '[4.0, 5.0, 6.0]')");
+      conn.commit();
+
+      try (ResultSet rs =
+        conn.createStatement().executeQuery("SELECT v FROM " + tableName + " WHERE pk = 1")) {
+        assertTrue(rs.next());
+        float[] retrieved = (float[]) rs.getObject(1);
+        assertNotNull(retrieved);
+        assertArrayEquals(new float[] { 4.0f, 5.0f, 6.0f }, retrieved, 0.00001f);
+      }
+    }
+  }
+
+  @Test
+  public void testVectorDoubleArrayBindingRoundTrip() throws Exception {
+    String tableName = generateUniqueName();
+    try (Connection conn = DriverManager.getConnection(getUrl())) {
+      conn.createStatement()
+        .execute("CREATE TABLE " + tableName + " (pk INTEGER PRIMARY KEY, v VECTOR(DOUBLE, 3))");
+
+      String upsertSql = "UPSERT INTO " + tableName + " VALUES (?, ?)";
+      try (PreparedStatement ps = conn.prepareStatement(upsertSql)) {
+        ps.setInt(1, 1);
+        Array array = conn.createArrayOf("DOUBLE", new Double[] { 1.1, 2.2, 3.3 });
+        ps.setArray(2, array);
+        ps.executeUpdate();
+      }
+      conn.commit();
+
+      try (ResultSet rs =
+        conn.createStatement().executeQuery("SELECT v FROM " + tableName + " WHERE pk = 1")) {
+        assertTrue(rs.next());
+        double[] retrieved = (double[]) rs.getObject(1);
+        assertNotNull(retrieved);
+        assertArrayEquals(new double[] { 1.1, 2.2, 3.3 }, retrieved, 0.000001);
+      }
+    }
+  }
+
+  @Test
+  public void testAlterTableAddVectorColumn() throws Exception {
+    String tableName = generateUniqueName();
+    try (Connection conn = DriverManager.getConnection(getUrl())) {
+      conn.createStatement()
+        .execute("CREATE TABLE " + tableName + " (pk INTEGER PRIMARY KEY, name VARCHAR)");
+      conn.createStatement().execute("ALTER TABLE " + tableName + " ADD v VECTOR(FLOAT, 256)");
+
+      PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class);
+      PTable pTable = pconn.getTable(new PTableKey(null, tableName));
+      PColumn pColumn = pTable.getColumnForColumnName("V");
+      assertNotNull(pColumn);
+      assertEquals(PVectorFloat.INSTANCE, pColumn.getDataType());
+      assertEquals(Integer.valueOf(256), pColumn.getMaxLength());
+
+      DatabaseMetaData dbmd = conn.getMetaData();
+      try (ResultSet rs = dbmd.getColumns(null, "", tableName, "V")) {
+        assertTrue(rs.next());
+        assertEquals("VECTOR(FLOAT)", rs.getString("TYPE_NAME"));
+        assertEquals(256, rs.getInt("COLUMN_SIZE"));
+      }
+
+      String upsertSql = "UPSERT INTO " + tableName + " VALUES (?, ?, ?)";
+      try (PreparedStatement ps = conn.prepareStatement(upsertSql)) {
+        ps.setInt(1, 1);
+        ps.setString(2, "test");
+        Array array = conn.createArrayOf("FLOAT", new Float[] { 1.0f, 2.0f, 3.0f });
+        Float[] vec = new Float[256];
+        for (int i = 0; i < 256; i++) {
+          vec[i] = (float) i;
+        }
+        ps.setArray(3, conn.createArrayOf("FLOAT", vec));
+        ps.executeUpdate();
+      }
+      conn.commit();
+
+      try (ResultSet rs =
+        conn.createStatement().executeQuery("SELECT v FROM " + tableName + " WHERE pk = 1")) {
+        assertTrue(rs.next());
+        float[] retrieved = (float[]) rs.getObject(1);
+        assertNotNull(retrieved);
+        assertEquals(256, retrieved.length);
+        assertEquals(0.0f, retrieved[0], 0.00001f);
+        assertEquals(255.0f, retrieved[255], 0.00001f);
+      }
+    }
+  }
+
+  @Test
+  public void testAlterTableAddVectorDoubleColumn() throws Exception {
+    String tableName = generateUniqueName();
+    try (Connection conn = DriverManager.getConnection(getUrl())) {
+      conn.createStatement()
+        .execute("CREATE TABLE " + tableName + " (pk INTEGER PRIMARY KEY, name VARCHAR)");
+
+      conn.createStatement().execute("ALTER TABLE " + tableName + " ADD v VECTOR(DOUBLE, 64)");
+
+      PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class);
+      PTable pTable = pconn.getTable(new PTableKey(null, tableName));
+      PColumn pColumn = pTable.getColumnForColumnName("V");
+      assertNotNull(pColumn);
+      assertEquals(PVectorDouble.INSTANCE, pColumn.getDataType());
+      assertEquals(Integer.valueOf(64), pColumn.getMaxLength());
+    }
+  }
+
+  @Test
+  public void testVectorColumnAsPrimaryKeyRejected() throws Exception {
+    String tableName = generateUniqueName();
+    try (Connection conn = DriverManager.getConnection(getUrl())) {
+      try {
+        conn.createStatement()
+          .execute("CREATE TABLE " + tableName + " (v VECTOR(FLOAT, 128) PRIMARY KEY)");
+        fail("Should have rejected vector column as primary key");
+      } catch (SQLException e) {
+        assertEquals(SQLExceptionCode.INVALID_PRIMARY_KEY_CONSTRAINT.getErrorCode(),
+          e.getErrorCode());
+      }
+    }
+  }
+
+  @Test
+  public void testIntegerArrayConstructorAndCrossWidthBinds() throws Exception {
+    String tableName = generateUniqueName();
+    try (Connection conn = DriverManager.getConnection(getUrl())) {
+      conn.createStatement().execute("CREATE TABLE " + tableName
+        + " (pk INTEGER PRIMARY KEY, vf VECTOR(FLOAT, 3), vd VECTOR(DOUBLE, 3))");
+      // Integer array literal coercion
+      conn.createStatement()
+        .execute("UPSERT INTO " + tableName + " VALUES (1, ARRAY[1, 0, -2], ARRAY[3, 4, 5])");
+      // Cross-precision numeric array coercion
+      try (PreparedStatement ps =
+        conn.prepareStatement("UPSERT INTO " + tableName + " VALUES (?, ?, ?)")) {
+        ps.setInt(1, 2);
+        ps.setArray(2, conn.createArrayOf("DOUBLE", new Double[] { 1.5, 2.5, 3.5 }));
+        ps.setArray(3, conn.createArrayOf("FLOAT", new Float[] { 4.5f, 5.5f, 6.5f }));
+        ps.executeUpdate();
+      }
+      conn.commit();
+
+      try (ResultSet rs =
+        conn.createStatement().executeQuery("SELECT vf, vd FROM " + tableName + " ORDER BY pk")) {
+        assertTrue(rs.next());
+        assertArrayEquals(new float[] { 1f, 0f, -2f }, (float[]) rs.getObject(1), 0f);
+        assertArrayEquals(new double[] { 3, 4, 5 }, (double[]) rs.getObject(2), 0);
+        assertTrue(rs.next());
+        assertArrayEquals(new float[] { 1.5f, 2.5f, 3.5f }, (float[]) rs.getObject(1), 0f);
+        assertArrayEquals(new double[] { 4.5, 5.5, 6.5 }, (double[]) rs.getObject(2), 0);
+      }
+    }
+  }
+
+  @Test
+  public void testNullElementRejected() throws Exception {
+    String tableName = generateUniqueName();
+    try (Connection conn = DriverManager.getConnection(getUrl())) {
+      conn.createStatement()
+        .execute("CREATE TABLE " + tableName + " (pk INTEGER PRIMARY KEY, v VECTOR(FLOAT, 3))");
+      // Verify null elements in literal arrays trigger constraint violation
+      try {
+        conn.createStatement()
+          .execute("UPSERT INTO " + tableName + " VALUES (1, ARRAY[1.0, NULL, 3.0])");
+        fail("A NULL vector element must be rejected, not stored as zero");
+      } catch (SQLException e) {
+        assertTrue(e.getMessage(), e.getMessage().contains("must not be NULL"));
+      }
+    }
+  }
+
+  @Test
+  public void testComparisonRejected() throws Exception {
+    String tableName = generateUniqueName();
+    try (Connection conn = DriverManager.getConnection(getUrl())) {
+      conn.createStatement()
+        .execute("CREATE TABLE " + tableName + " (pk INTEGER PRIMARY KEY, v VECTOR(FLOAT, 3))");
+      try {
+        conn.createStatement()
+          .executeQuery("SELECT pk FROM " + tableName + " WHERE v = ARRAY[1.0, 2.0, 3.0]");
+        fail("Vectors do not support comparison");
+      } catch (SQLException e) {
+        assertEquals(SQLExceptionCode.COMPARISON_UNSUPPORTED.getErrorCode(), e.getErrorCode());
+      }
+    }
+  }
+}
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/expression/CoerceExpressionTest.java b/phoenix-core/src/test/java/org/apache/phoenix/expression/CoerceExpressionTest.java
index a6ab6fa588..71ae9b595c 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/expression/CoerceExpressionTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/expression/CoerceExpressionTest.java
@@ -59,6 +59,8 @@ public class CoerceExpressionTest {
     map.put(Date.class, new Date(0));
     map.put(Boolean.class, Boolean.TRUE);
     map.put(byte[].class, new byte[] { -128, 0, 0, 1 });
+    map.put(float[].class, new float[] { 1.00f, 2.00f });
+    map.put(double[].class, new double[] { 1.00d, 2.00d });
   }
 
   @Test
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/parse/VectorColumnParseTest.java b/phoenix-core/src/test/java/org/apache/phoenix/parse/VectorColumnParseTest.java
new file mode 100644
index 0000000000..795d7b2d0b
--- /dev/null
+++ b/phoenix-core/src/test/java/org/apache/phoenix/parse/VectorColumnParseTest.java
@@ -0,0 +1,222 @@
+/*
+ * 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.parse;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.sql.SQLException;
+import java.util.List;
+import org.apache.phoenix.exception.SQLExceptionCode;
+import org.apache.phoenix.schema.SortOrder;
+import org.apache.phoenix.schema.types.PVectorDouble;
+import org.apache.phoenix.schema.types.PVectorFloat;
+import org.junit.Test;
+
+/** Tests for parsing VECTOR column definitions in SQL statements. */
+public class VectorColumnParseTest {
+
+  @Test
+  public void testParseVectorFloatColumn() throws Exception {
+    String ddl = "CREATE TABLE t (pk INTEGER PRIMARY KEY, v VECTOR(FLOAT, 128))";
+    SQLParser parser = new SQLParser(ddl);
+    BindableStatement stmt = parser.parseStatement();
+    assertTrue("Expected CreateTableStatement", stmt instanceof CreateTableStatement);
+    CreateTableStatement createStmt = (CreateTableStatement) stmt;
+
+    List<ColumnDef> colDefs = createStmt.getColumnDefs();
+    assertEquals(2, colDefs.size());
+
+    ColumnDef vectorCol = colDefs.get(1);
+    assertEquals("V", vectorCol.getColumnDefName().getColumnName());
+    assertEquals(PVectorFloat.INSTANCE, vectorCol.getDataType());
+    assertEquals(Integer.valueOf(128), vectorCol.getMaxLength());
+  }
+
+  @Test
+  public void testParseVectorDoubleColumn() throws Exception {
+    String ddl = "CREATE TABLE t (pk INTEGER PRIMARY KEY, v VECTOR(DOUBLE, 64))";
+    SQLParser parser = new SQLParser(ddl);
+    BindableStatement stmt = parser.parseStatement();
+    assertTrue("Expected CreateTableStatement", stmt instanceof CreateTableStatement);
+    CreateTableStatement createStmt = (CreateTableStatement) stmt;
+
+    List<ColumnDef> colDefs = createStmt.getColumnDefs();
+    assertEquals(2, colDefs.size());
+
+    ColumnDef vectorCol = colDefs.get(1);
+    assertEquals("V", vectorCol.getColumnDefName().getColumnName());
+    assertEquals(PVectorDouble.INSTANCE, vectorCol.getDataType());
+    assertEquals(Integer.valueOf(64), vectorCol.getMaxLength());
+  }
+
+  @Test
+  public void testParseVectorColumnWithNotNull() throws Exception {
+    String ddl = "CREATE TABLE t (pk INTEGER PRIMARY KEY, v VECTOR(FLOAT, 256) NOT NULL)";
+    SQLParser parser = new SQLParser(ddl);
+    CreateTableStatement createStmt = (CreateTableStatement) parser.parseStatement();
+
+    ColumnDef vectorCol = createStmt.getColumnDefs().get(1);
+    assertEquals(PVectorFloat.INSTANCE, vectorCol.getDataType());
+    assertEquals(Integer.valueOf(256), vectorCol.getMaxLength());
+    assertFalse("Column should be NOT NULL", vectorCol.isNull());
+  }
+
+  @Test
+  public void testZeroDimensionRejection() throws Exception {
+    String ddl = "CREATE TABLE t (pk INTEGER PRIMARY KEY, v VECTOR(FLOAT, 0))";
+    try {
+      SQLParser parser = new SQLParser(ddl);
+      parser.parseStatement();
+      fail("Should have rejected zero dimension vector column definition");
+    } catch (SQLException e) {
+      assertEquals("Expected NONPOSITIVE_MAX_LENGTH error code",
+        SQLExceptionCode.NONPOSITIVE_MAX_LENGTH.getErrorCode(), e.getErrorCode());
+    }
+  }
+
+  @Test
+  public void testMissingDimensionRejection() throws Exception {
+    String ddl = "CREATE TABLE t (pk INTEGER PRIMARY KEY, v VECTOR(FLOAT))";
+    try {
+      SQLParser parser = new SQLParser(ddl);
+      parser.parseStatement();
+      fail("Should have rejected missing dimension vector column definition");
+    } catch (SQLException e) {
+      // Expected parse error due to missing dimension
+      assertNotNull("Expected parse exception", e.getMessage());
+    }
+  }
+
+  @Test
+  public void testNegativeDimensionRejection() throws Exception {
+    String ddl = "CREATE TABLE t (pk INTEGER PRIMARY KEY, v VECTOR(FLOAT, -5))";
+    try {
+      SQLParser parser = new SQLParser(ddl);
+      parser.parseStatement();
+      fail("Should have rejected negative dimension vector column definition");
+    } catch (SQLException e) {
+      assertNotNull("Expected parse exception for negative dimension", e.getMessage());
+    }
+  }
+
+  @Test
+  public void testInvalidComponentTypeRejection() throws Exception {
+    String ddl = "CREATE TABLE t (pk INTEGER PRIMARY KEY, v VECTOR(INT, 128))";
+    try {
+      SQLParser parser = new SQLParser(ddl);
+      parser.parseStatement();
+      fail("Should have rejected unsupported component type in vector column definition");
+    } catch (SQLException e) {
+      assertNotNull("Expected parse exception for invalid component type", e.getMessage());
+    }
+  }
+
+  @Test
+  public void testDynamicVectorColumn() throws Exception {
+    String sql = "SELECT * FROM t(v VECTOR(FLOAT, 128))";
+    SQLParser parser = new SQLParser(sql);
+    SelectStatement select = (SelectStatement) parser.parseStatement();
+    NamedTableNode tableNode = (NamedTableNode) select.getFrom();
+    List<ColumnDef> dynCols = tableNode.getDynamicColumns();
+    assertEquals(1, dynCols.size());
+    ColumnDef dynCol = dynCols.get(0);
+    assertEquals("V", dynCol.getColumnDefName().getColumnName());
+    assertEquals(PVectorFloat.INSTANCE, dynCol.getDataType());
+    assertEquals(Integer.valueOf(128), dynCol.getMaxLength());
+  }
+
+  @Test
+  public void testColumnDefConstructorValidation() {
+    ColumnName colName = new ColumnName("V");
+    try {
+      new ColumnDef(colName, PVectorFloat.INSTANCE.getSqlTypeName(), null, null, null, false,
+        SortOrder.getDefault(), null, null, false);
+      fail("Should reject null dimension for vector type");
+    } catch (ParseException e) {
+      assertTrue("Cause must be SQLException", e.getCause() instanceof SQLException);
+      assertEquals(SQLExceptionCode.MISSING_MAX_LENGTH.getErrorCode(),
+        ((SQLException) e.getCause()).getErrorCode());
+    }
+
+    try {
+      new ColumnDef(colName, PVectorFloat.INSTANCE.getSqlTypeName(), null, 0, null, false,
+        SortOrder.getDefault(), null, null, false);
+      fail("Should reject zero dimension for vector type");
+    } catch (ParseException e) {
+      assertTrue("Cause must be SQLException", e.getCause() instanceof SQLException);
+      assertEquals(SQLExceptionCode.NONPOSITIVE_MAX_LENGTH.getErrorCode(),
+        ((SQLException) e.getCause()).getErrorCode());
+    }
+
+    try {
+      new ColumnDef(colName, PVectorDouble.INSTANCE.getSqlTypeName(), null, -1, null, false,
+        SortOrder.getDefault(), null, null, false);
+      fail("Should reject negative dimension for vector type");
+    } catch (ParseException e) {
+      assertTrue("Cause must be SQLException", e.getCause() instanceof SQLException);
+      assertEquals(SQLExceptionCode.NONPOSITIVE_MAX_LENGTH.getErrorCode(),
+        ((SQLException) e.getCause()).getErrorCode());
+    }
+  }
+
+  @Test
+  public void testColumnDefToString() {
+    ColumnDef floatVec = new ColumnDef(new ColumnName("V"), PVectorFloat.INSTANCE.getSqlTypeName(),
+      null, 128, null, false, SortOrder.getDefault(), null, null, false);
+    assertEquals("V VECTOR(FLOAT, 128)", floatVec.toString());
+
+    ColumnDef doubleVec =
+      new ColumnDef(new ColumnName("V"), PVectorDouble.INSTANCE.getSqlTypeName(), null, 64, null,
+        false, SortOrder.getDefault(), null, null, false);
+    assertEquals("V VECTOR(DOUBLE, 64)", doubleVec.toString());
+  }
+
+  @Test
+  public void testVectorIsNotReserved() throws Exception {
+    // Verify non-reserved status of VECTOR identifier in table, column, family, and alias positions
+    CreateTableStatement create = (CreateTableStatement) new SQLParser(
+      "CREATE TABLE vector (pk INTEGER PRIMARY KEY, vector VARBINARY, f.vector INTEGER)")
+        .parseStatement();
+    assertEquals("VECTOR", create.getTableName().getTableName());
+    assertEquals("VECTOR", create.getColumnDefs().get(1).getColumnDefName().getColumnName());
+    assertEquals("VARBINARY", create.getColumnDefs().get(1).getDataType().getSqlTypeName());
+    assertEquals("VECTOR", create.getColumnDefs().get(2).getColumnDefName().getColumnName());
+
+    SelectStatement select = (SelectStatement) new SQLParser(
+      "SELECT vector, v AS vector FROM vector WHERE vector IS NOT NULL ORDER BY vector")
+        .parseStatement();
+    assertEquals("VECTOR", select.getSelect().get(1).getAlias());
+
+    // Column named VECTOR with vector data type
+    create = (CreateTableStatement) new SQLParser(
+      "CREATE TABLE t (pk INTEGER PRIMARY KEY, vector VECTOR(FLOAT, 3))").parseStatement();
+    assertEquals("VECTOR", create.getColumnDefs().get(1).getColumnDefName().getColumnName());
+    assertEquals(PVectorFloat.INSTANCE, create.getColumnDefs().get(1).getDataType());
+    assertEquals(Integer.valueOf(3), create.getColumnDefs().get(1).getMaxLength());
+
+    // Dynamic column named VECTOR with non-vector type
+    select = (SelectStatement) new SQLParser("SELECT * FROM t(vector VARCHAR)").parseStatement();
+    ColumnDef dynCol = ((NamedTableNode) select.getFrom()).getDynamicColumns().get(0);
+    assertEquals("VECTOR", dynCol.getColumnDefName().getColumnName());
+    assertEquals("VARCHAR", dynCol.getDataType().getSqlTypeName());
+  }
+}
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java b/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java
index 40009dd8fc..b2ab665305 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java
@@ -1779,7 +1779,7 @@ public class PDataTypeTest {
       }
     }
     assertEquals("{BIGINT=[BIGINT, BINARY, DECIMAL, DOUBLE, VARBINARY], "
-      + "BIGINT ARRAY=[BIGINT ARRAY, BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, VARBINARY ARRAY], "
+      + "BIGINT ARRAY=[BIGINT ARRAY, BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, VARBINARY ARRAY, VECTOR(DOUBLE), VECTOR(FLOAT)], "
       + "BINARY=[BINARY, VARBINARY, VARBINARY_ENCODED], "
       + "BINARY ARRAY=[BINARY ARRAY, VARBINARY ARRAY], "
       + "BOOLEAN=[BINARY, BOOLEAN, VARBINARY, VARBINARY_ENCODED], "
@@ -1789,45 +1789,47 @@ public class PDataTypeTest {
       + "DATE=[BINARY, DATE, TIME, TIMESTAMP, VARBINARY], "
       + "DATE ARRAY=[BINARY ARRAY, DATE ARRAY, TIME ARRAY, TIMESTAMP ARRAY, VARBINARY ARRAY], "
       + "DECIMAL=[DECIMAL, VARBINARY, VARBINARY_ENCODED], "
-      + "DECIMAL ARRAY=[DECIMAL ARRAY, VARBINARY ARRAY], "
+      + "DECIMAL ARRAY=[DECIMAL ARRAY, VARBINARY ARRAY, VECTOR(DOUBLE), VECTOR(FLOAT)], "
       + "DOUBLE=[BINARY, DECIMAL, DOUBLE, VARBINARY], "
-      + "DOUBLE ARRAY=[BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, VARBINARY ARRAY], "
+      + "DOUBLE ARRAY=[BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, VARBINARY ARRAY, VECTOR(DOUBLE), VECTOR(FLOAT)], "
       + "FLOAT=[BINARY, DECIMAL, DOUBLE, FLOAT, VARBINARY], "
-      + "FLOAT ARRAY=[BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, FLOAT ARRAY, VARBINARY ARRAY], "
+      + "FLOAT ARRAY=[BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, FLOAT ARRAY, VARBINARY ARRAY, VECTOR(DOUBLE), VECTOR(FLOAT)], "
       + "INTEGER=[BIGINT, BINARY, DECIMAL, DOUBLE, FLOAT, INTEGER, VARBINARY], "
-      + "INTEGER ARRAY=[BIGINT ARRAY, BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, FLOAT ARRAY, INTEGER ARRAY, VARBINARY ARRAY], "
+      + "INTEGER ARRAY=[BIGINT ARRAY, BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, FLOAT ARRAY, INTEGER ARRAY, VARBINARY ARRAY, VECTOR(DOUBLE), VECTOR(FLOAT)], "
       + "JSON=[BINARY, JSON, VARBINARY], "
       + "SMALLINT=[BIGINT, BINARY, DECIMAL, DOUBLE, FLOAT, INTEGER, SMALLINT, VARBINARY], "
-      + "SMALLINT ARRAY=[BIGINT ARRAY, BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, FLOAT ARRAY, INTEGER ARRAY, SMALLINT ARRAY, VARBINARY ARRAY], "
+      + "SMALLINT ARRAY=[BIGINT ARRAY, BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, FLOAT ARRAY, INTEGER ARRAY, SMALLINT ARRAY, VARBINARY ARRAY, VECTOR(DOUBLE), VECTOR(FLOAT)], "
       + "TIME=[BINARY, DATE, TIME, TIMESTAMP, VARBINARY], "
       + "TIME ARRAY=[BINARY ARRAY, DATE ARRAY, TIME ARRAY, TIMESTAMP ARRAY, VARBINARY ARRAY], "
       + "TIMESTAMP=[BINARY, TIMESTAMP, VARBINARY], "
       + "TIMESTAMP ARRAY=[BINARY ARRAY, TIMESTAMP ARRAY, VARBINARY ARRAY], "
       + "TINYINT=[BIGINT, BINARY, DECIMAL, DOUBLE, FLOAT, INTEGER, SMALLINT, TINYINT, VARBINARY], "
-      + "TINYINT ARRAY=[BIGINT ARRAY, BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, FLOAT ARRAY, INTEGER ARRAY, SMALLINT ARRAY, TINYINT ARRAY, VARBINARY ARRAY], "
+      + "TINYINT ARRAY=[BIGINT ARRAY, BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, FLOAT ARRAY, INTEGER ARRAY, SMALLINT ARRAY, TINYINT ARRAY, VARBINARY ARRAY, VECTOR(DOUBLE), VECTOR(FLOAT)], "
       + "UNSIGNED_DATE=[BINARY, DATE, TIME, TIMESTAMP, UNSIGNED_DATE, UNSIGNED_TIME, UNSIGNED_TIMESTAMP, VARBINARY], "
       + "UNSIGNED_DATE ARRAY=[BINARY ARRAY, DATE ARRAY, TIME ARRAY, TIMESTAMP ARRAY, UNSIGNED_DATE ARRAY, UNSIGNED_TIME ARRAY, UNSIGNED_TIMESTAMP ARRAY, VARBINARY ARRAY], "
       + "UNSIGNED_DOUBLE=[BINARY, DECIMAL, DOUBLE, UNSIGNED_DOUBLE, VARBINARY], "
-      + "UNSIGNED_DOUBLE ARRAY=[BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, UNSIGNED_DOUBLE ARRAY, VARBINARY ARRAY], "
+      + "UNSIGNED_DOUBLE ARRAY=[BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, UNSIGNED_DOUBLE ARRAY, VARBINARY ARRAY, VECTOR(DOUBLE), VECTOR(FLOAT)], "
       + "UNSIGNED_FLOAT=[BINARY, DECIMAL, DOUBLE, FLOAT, UNSIGNED_DOUBLE, UNSIGNED_FLOAT, VARBINARY], "
-      + "UNSIGNED_FLOAT ARRAY=[BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, FLOAT ARRAY, UNSIGNED_DOUBLE ARRAY, UNSIGNED_FLOAT ARRAY, VARBINARY ARRAY], "
+      + "UNSIGNED_FLOAT ARRAY=[BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, FLOAT ARRAY, UNSIGNED_DOUBLE ARRAY, UNSIGNED_FLOAT ARRAY, VARBINARY ARRAY, VECTOR(DOUBLE), VECTOR(FLOAT)], "
       + "UNSIGNED_INT=[BIGINT, BINARY, DECIMAL, DOUBLE, FLOAT, INTEGER, UNSIGNED_DOUBLE, UNSIGNED_FLOAT, UNSIGNED_INT, UNSIGNED_LONG, VARBINARY], "
-      + "UNSIGNED_INT ARRAY=[BIGINT ARRAY, BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, FLOAT ARRAY, INTEGER ARRAY, UNSIGNED_DOUBLE ARRAY, UNSIGNED_FLOAT ARRAY, UNSIGNED_INT ARRAY, UNSIGNED_LONG ARRAY, VARBINARY ARRAY], "
+      + "UNSIGNED_INT ARRAY=[BIGINT ARRAY, BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, FLOAT ARRAY, INTEGER ARRAY, UNSIGNED_DOUBLE ARRAY, UNSIGNED_FLOAT ARRAY, UNSIGNED_INT ARRAY, UNSIGNED_LONG ARRAY, VARBINARY ARRAY, VECTOR(DOUBLE), VECTOR(FLOAT)], "
       + "UNSIGNED_LONG=[BIGINT, BINARY, DECIMAL, DOUBLE, UNSIGNED_DOUBLE, UNSIGNED_LONG, VARBINARY], "
-      + "UNSIGNED_LONG ARRAY=[BIGINT ARRAY, BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, UNSIGNED_DOUBLE ARRAY, UNSIGNED_LONG ARRAY, VARBINARY ARRAY], "
+      + "UNSIGNED_LONG ARRAY=[BIGINT ARRAY, BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, UNSIGNED_DOUBLE ARRAY, UNSIGNED_LONG ARRAY, VARBINARY ARRAY, VECTOR(DOUBLE), VECTOR(FLOAT)], "
       + "UNSIGNED_SMALLINT=[BIGINT, BINARY, DECIMAL, DOUBLE, FLOAT, INTEGER, SMALLINT, UNSIGNED_DOUBLE, UNSIGNED_FLOAT, UNSIGNED_INT, UNSIGNED_LONG, UNSIGNED_SMALLINT, VARBINARY], "
-      + "UNSIGNED_SMALLINT ARRAY=[BIGINT ARRAY, BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, FLOAT ARRAY, INTEGER ARRAY, SMALLINT ARRAY, UNSIGNED_DOUBLE ARRAY, UNSIGNED_FLOAT ARRAY, UNSIGNED_INT ARRAY, UNSIGNED_LONG ARRAY, UNSIGNED_SMALLINT ARRAY, VARBINARY ARRAY], "
+      + "UNSIGNED_SMALLINT ARRAY=[BIGINT ARRAY, BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, FLOAT ARRAY, INTEGER ARRAY, SMALLINT ARRAY, UNSIGNED_DOUBLE ARRAY, UNSIGNED_FLOAT ARRAY, UNSIGNED_INT ARRAY, UNSIGNED_LONG ARRAY, UNSIGNED_SMALLINT ARRAY, VARBINARY ARRAY, VECTOR(DOUBLE), VECTOR(FLOAT)], "
       + "UNSIGNED_TIME=[BINARY, DATE, TIME, TIMESTAMP, UNSIGNED_DATE, UNSIGNED_TIME, UNSIGNED_TIMESTAMP, VARBINARY], "
       + "UNSIGNED_TIME ARRAY=[BINARY ARRAY, DATE ARRAY, TIME ARRAY, TIMESTAMP ARRAY, UNSIGNED_DATE ARRAY, UNSIGNED_TIME ARRAY, UNSIGNED_TIMESTAMP ARRAY, VARBINARY ARRAY], "
       + "UNSIGNED_TIMESTAMP=[BINARY, DATE, TIME, TIMESTAMP, UNSIGNED_DATE, UNSIGNED_TIME, UNSIGNED_TIMESTAMP, VARBINARY], "
       + "UNSIGNED_TIMESTAMP ARRAY=[BINARY ARRAY, DATE ARRAY, TIME ARRAY, TIMESTAMP ARRAY, UNSIGNED_DATE ARRAY, UNSIGNED_TIME ARRAY, UNSIGNED_TIMESTAMP ARRAY, VARBINARY ARRAY], "
       + "UNSIGNED_TINYINT=[BIGINT, BINARY, DECIMAL, DOUBLE, FLOAT, INTEGER, SMALLINT, TINYINT, UNSIGNED_DOUBLE, UNSIGNED_FLOAT, UNSIGNED_INT, UNSIGNED_LONG, UNSIGNED_SMALLINT, UNSIGNED_TINYINT, VARBINARY], "
-      + "UNSIGNED_TINYINT ARRAY=[BIGINT ARRAY, BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, FLOAT ARRAY, INTEGER ARRAY, SMALLINT ARRAY, TINYINT ARRAY, UNSIGNED_DOUBLE ARRAY, UNSIGNED_FLOAT ARRAY, UNSIGNED_INT ARRAY, UNSIGNED_LONG ARRAY, UNSIGNED_SMALLINT ARRAY, UNSIGNED_TINYINT ARRAY, VARBINARY ARRAY], "
+      + "UNSIGNED_TINYINT ARRAY=[BIGINT ARRAY, BINARY ARRAY, DECIMAL ARRAY, DOUBLE ARRAY, FLOAT ARRAY, INTEGER ARRAY, SMALLINT ARRAY, TINYINT ARRAY, UNSIGNED_DOUBLE ARRAY, UNSIGNED_FLOAT ARRAY, UNSIGNED_INT ARRAY, UNSIGNED_LONG ARRAY, UNSIGNED_SMALLINT ARRAY, UNSIGNED_TINYINT ARRAY, VARBINARY ARRAY, VECTOR(DOUBLE), VECTOR(FLOAT)], "
       + "VARBINARY=[BINARY, VARBINARY, VARBINARY_ENCODED], "
       + "VARBINARY ARRAY=[BINARY ARRAY, VARBINARY ARRAY], "
       + "VARBINARY_ENCODED=[BINARY, VARBINARY, VARBINARY_ENCODED], "
       + "VARCHAR=[BINARY, BSON, CHAR, VARBINARY, VARBINARY_ENCODED, VARCHAR], "
-      + "VARCHAR ARRAY=[BINARY ARRAY, CHAR ARRAY, VARBINARY ARRAY, VARCHAR ARRAY]}",
+      + "VARCHAR ARRAY=[BINARY ARRAY, CHAR ARRAY, VARBINARY ARRAY, VARCHAR ARRAY], "
+      + "VECTOR(DOUBLE)=[DOUBLE ARRAY, VARBINARY, VARBINARY_ENCODED, VECTOR(DOUBLE)], "
+      + "VECTOR(FLOAT)=[FLOAT ARRAY, VARBINARY, VARBINARY_ENCODED, VECTOR(FLOAT)]}",
       coercibleToMap.toString());
   }
 
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
new file mode 100644
index 0000000000..9ed8b4d29d
--- /dev/null
+++ b/phoenix-core/src/test/java/org/apache/phoenix/schema/types/VectorDataTypeTest.java
@@ -0,0 +1,529 @@
+/*
+ * 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.schema.types;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.coprocessor.generated.PTableProtos;
+import org.apache.phoenix.schema.ConstraintViolationException;
+import org.apache.phoenix.schema.PColumn;
+import org.apache.phoenix.schema.PColumnImpl;
+import org.apache.phoenix.schema.PNameFactory;
+import org.apache.phoenix.schema.SortOrder;
+import org.junit.Test;
+
+/** Tests for vector data types, type factory resolution, column metadata, and serialization. */
+public class VectorDataTypeTest {
+
+  @Test
+  public void testFactoryTypeForSqlType() {
+    PDataTypeFactory factory = PDataTypeFactory.getInstance();
+    assertSame(PVectorFloat.INSTANCE, PDataType.fromTypeId(PDataType.VECTOR_FLOAT_TYPE));
+    assertSame(PVectorDouble.INSTANCE, PDataType.fromTypeId(PDataType.VECTOR_DOUBLE_TYPE));
+    assertEquals(4001, PVectorFloat.INSTANCE.getSqlType());
+    assertEquals(4002, PVectorDouble.INSTANCE.getSqlType());
+  }
+
+  @Test
+  public void testFactoryTypeEnumeration() {
+    PDataTypeFactory factory = PDataTypeFactory.getInstance();
+    assertTrue(factory.getTypes().contains(PVectorFloat.INSTANCE));
+    assertTrue(factory.getTypes().contains(PVectorDouble.INSTANCE));
+  }
+
+  @Test
+  public void testSqlTypeNameResolution() {
+    PDataTypeFactory factory = PDataTypeFactory.getInstance();
+    assertSame(PVectorFloat.INSTANCE, PDataType.fromSqlTypeName("VECTOR(FLOAT)"));
+    assertSame(PVectorFloat.INSTANCE, PDataType.fromSqlTypeName("vector(float)"));
+    assertSame(PVectorDouble.INSTANCE, PDataType.fromSqlTypeName("VECTOR(DOUBLE)"));
+    assertSame(PVectorFloat.INSTANCE, factory.typeForVector("float"));
+    assertSame(PVectorDouble.INSTANCE, factory.typeForVector("DOUBLE"));
+    assertNull(factory.typeForVector("INTEGER"));
+  }
+
+  @Test
+  public void testVectorFloatProperties() {
+    PVectorFloat type = PVectorFloat.INSTANCE;
+    assertTrue(type.isVectorType());
+    assertTrue(type.isFixedWidth());
+    assertNull(type.getByteSize());
+    assertFalse(type.isComparisonSupported());
+
+    assertEquals(Integer.valueOf(12), type.estimateByteSizeFromLength(3));
+    assertTrue(type.isCoercibleTo(PFloatArray.INSTANCE));
+    assertFalse(type.isCoercibleTo(PVarchar.INSTANCE));
+    assertTrue(type.isCastableTo(PFloatArray.INSTANCE));
+    assertTrue(type.isCastableTo(PVarchar.INSTANCE));
+  }
+
+  @Test
+  public void testVectorFloatSerializationRoundTrip() {
+    PVectorFloat type = PVectorFloat.INSTANCE;
+    float[] original = new float[] { 1.0f, -2.5f, 3.14159f };
+    byte[] bytes = type.toBytes(original);
+    assertEquals(original.length * Bytes.SIZEOF_FLOAT, bytes.length);
+
+    float[] deserialized = (float[]) type.toObject(bytes);
+    assertArrayEquals(original, deserialized, 0.00001f);
+
+    byte[] bytesDesc = type.toBytes(original, SortOrder.DESC);
+    float[] deserializedDesc = (float[]) type.toObject(bytesDesc, 0, bytesDesc.length, type,
+      SortOrder.DESC, original.length, null);
+    assertArrayEquals(original, deserializedDesc, 0.00001f);
+  }
+
+  @Test
+  public void testVectorFloatCompareTo() {
+    PVectorFloat type = PVectorFloat.INSTANCE;
+    float[] a = new float[] { 1.0f, 2.0f, 3.0f };
+    float[] b = new float[] { 1.0f, 2.0f, 3.0f };
+    float[] c = new float[] { 1.0f, 2.0f, 4.0f };
+    float[] d = new float[] { 1.0f, 2.0f };
+
+    assertEquals(0, type.compareTo(a, b, type));
+    assertTrue(type.compareTo(a, c, type) < 0);
+    assertTrue(type.compareTo(c, a, type) > 0);
+    assertTrue(type.compareTo(a, d, type) > 0);
+    assertTrue(type.compareTo(d, a, type) < 0);
+  }
+
+  @Test
+  public void testVectorFloatCoerceBytes() {
+    PVectorFloat type = PVectorFloat.INSTANCE;
+    float[] original = new float[] { 1.0f, 2.0f, 3.0f };
+    byte[] ascBytes = type.toBytes(original, SortOrder.ASC);
+    ImmutableBytesWritable ptr = new ImmutableBytesWritable(ascBytes);
+
+    type.coerceBytes(ptr, null, type, 3, null, SortOrder.ASC, 3, null, SortOrder.DESC);
+    float[] deserialized = (float[]) type.toObject(ptr.get(), ptr.getOffset(), ptr.getLength(),
+      type, SortOrder.DESC, 3, null);
+    assertArrayEquals(original, deserialized, 0.00001f);
+
+    try {
+      type.coerceBytes(ptr, null, type, 3, null, SortOrder.DESC, 4, null, SortOrder.ASC);
+      fail("Should have thrown ConstraintViolationException on dimension mismatch");
+    } catch (ConstraintViolationException expected) {
+    }
+  }
+
+  @Test
+  public void testVectorFloatIsSizeCompatible() {
+    PVectorFloat type = PVectorFloat.INSTANCE;
+    float[] vector = new float[] { 1.0f, 2.0f, 3.0f };
+    byte[] bytes = type.toBytes(vector);
+    ImmutableBytesWritable ptr = new ImmutableBytesWritable(bytes);
+
+    assertTrue(type.isSizeCompatible(ptr, vector, type, SortOrder.ASC, 3, null, 3, null));
+    try {
+      type.isSizeCompatible(ptr, vector, type, SortOrder.ASC, 3, null, 4, null);
+      fail("Should have thrown ConstraintViolationException on dimension mismatch");
+    } catch (ConstraintViolationException expected) {
+    }
+  }
+
+  @Test
+  public void testVectorFloatStringConversion() {
+    PVectorFloat type = PVectorFloat.INSTANCE;
+    float[] vector = new float[] { 1.0f, 2.5f, -3.0f };
+    assertEquals("[1.0, 2.5, -3.0]", type.toStringLiteral(vector, null));
+
+    float[] parsed1 = (float[]) type.toObject("[1.0, 2.5, -3.0]");
+    assertArrayEquals(vector, parsed1, 0.00001f);
+
+    float[] parsed2 = (float[]) type.toObject("1.0, 2.5, -3.0");
+    assertArrayEquals(vector, parsed2, 0.00001f);
+
+    float[] parsedEmpty = (float[]) type.toObject("[]");
+    assertEquals(0, parsedEmpty.length);
+  }
+
+  @Test
+  public void testVectorDoubleProperties() {
+    PVectorDouble type = PVectorDouble.INSTANCE;
+    assertTrue(type.isVectorType());
+    assertTrue(type.isFixedWidth());
+    assertNull(type.getByteSize());
+    assertEquals(Integer.valueOf(24), type.estimateByteSizeFromLength(3));
+    assertFalse(type.isComparisonSupported());
+
+    assertEquals(Integer.valueOf(24), type.estimateByteSizeFromLength(3));
+    assertTrue(type.isCoercibleTo(PDoubleArray.INSTANCE));
+    assertFalse(type.isCoercibleTo(PVarchar.INSTANCE));
+    assertTrue(type.isCastableTo(PDoubleArray.INSTANCE));
+    assertTrue(type.isCastableTo(PVarchar.INSTANCE));
+  }
+
+  @Test
+  public void testCanBePrimaryKeyReturnsFalse() {
+    assertFalse(PVectorFloat.INSTANCE.canBePrimaryKey());
+    assertFalse(PVectorDouble.INSTANCE.canBePrimaryKey());
+  }
+
+  @Test
+  public void testVectorDoubleSerializationRoundTrip() {
+    PVectorDouble type = PVectorDouble.INSTANCE;
+    double[] original = new double[] { 1.0, -2.5, 3.141592653589793 };
+    byte[] bytes = type.toBytes(original);
+    assertEquals(original.length * Bytes.SIZEOF_DOUBLE, bytes.length);
+
+    double[] deserialized = (double[]) type.toObject(bytes);
+    assertArrayEquals(original, deserialized, 0.0000001);
+
+    byte[] bytesDesc = type.toBytes(original, SortOrder.DESC);
+    double[] deserializedDesc = (double[]) type.toObject(bytesDesc, 0, bytesDesc.length, type,
+      SortOrder.DESC, original.length, null);
+    assertArrayEquals(original, deserializedDesc, 0.0000001);
+  }
+
+  @Test
+  public void testVectorDoubleCompareTo() {
+    PVectorDouble type = PVectorDouble.INSTANCE;
+    double[] a = new double[] { 1.0, 2.0, 3.0 };
+    double[] b = new double[] { 1.0, 2.0, 3.0 };
+    double[] c = new double[] { 1.0, 2.0, 4.0 };
+    double[] d = new double[] { 1.0, 2.0 };
+
+    assertEquals(0, type.compareTo(a, b, type));
+    assertTrue(type.compareTo(a, c, type) < 0);
+    assertTrue(type.compareTo(c, a, type) > 0);
+    assertTrue(type.compareTo(a, d, type) > 0);
+    assertTrue(type.compareTo(d, a, type) < 0);
+  }
+
+  @Test
+  public void testVectorDoubleCoerceBytes() {
+    PVectorDouble type = PVectorDouble.INSTANCE;
+    double[] original = new double[] { 1.0, 2.0, 3.0 };
+    byte[] ascBytes = type.toBytes(original, SortOrder.ASC);
+    ImmutableBytesWritable ptr = new ImmutableBytesWritable(ascBytes);
+
+    type.coerceBytes(ptr, null, type, 3, null, SortOrder.ASC, 3, null, SortOrder.DESC);
+    double[] deserialized = (double[]) type.toObject(ptr.get(), ptr.getOffset(), ptr.getLength(),
+      type, SortOrder.DESC, 3, null);
+    assertArrayEquals(original, deserialized, 0.0000001);
+
+    try {
+      type.coerceBytes(ptr, null, type, 3, null, SortOrder.DESC, 4, null, SortOrder.ASC);
+      fail("Should have thrown ConstraintViolationException on dimension mismatch");
+    } catch (ConstraintViolationException expected) {
+    }
+  }
+
+  @Test
+  public void testVectorDoubleIsSizeCompatible() {
+    PVectorDouble type = PVectorDouble.INSTANCE;
+    double[] vector = new double[] { 1.0, 2.0, 3.0 };
+    byte[] bytes = type.toBytes(vector);
+    ImmutableBytesWritable ptr = new ImmutableBytesWritable(bytes);
+
+    assertTrue(type.isSizeCompatible(ptr, vector, type, SortOrder.ASC, 3, null, 3, null));
+    try {
+      type.isSizeCompatible(ptr, vector, type, SortOrder.ASC, 3, null, 4, null);
+      fail("Should have thrown ConstraintViolationException on dimension mismatch");
+    } catch (ConstraintViolationException expected) {
+    }
+  }
+
+  @Test
+  public void testVectorDoubleStringConversion() {
+    PVectorDouble type = PVectorDouble.INSTANCE;
+    double[] vector = new double[] { 1.0, 2.5, -3.0 };
+    assertEquals("[1.0, 2.5, -3.0]", type.toStringLiteral(vector, null));
+
+    double[] parsed1 = (double[]) type.toObject("[1.0, 2.5, -3.0]");
+    assertArrayEquals(vector, parsed1, 0.0000001);
+
+    double[] parsed2 = (double[]) type.toObject("1.0, 2.5, -3.0");
+    assertArrayEquals(vector, parsed2, 0.0000001);
+
+    double[] parsedEmpty = (double[]) type.toObject("[]");
+    assertEquals(0, parsedEmpty.length);
+  }
+
+  @Test
+  public void testVectorFloatProtobufRoundTrip() {
+    PColumn column =
+      new PColumnImpl(PNameFactory.newName("V"), PNameFactory.newName("0"), PVectorFloat.INSTANCE,
+        128, null, true, 1, SortOrder.ASC, null, null, false, null, false, false, null, 0L);
+
+    assertEquals(PNameFactory.newName("V"), column.getName());
+    assertEquals(PNameFactory.newName("0"), column.getFamilyName());
+    assertEquals(PVectorFloat.INSTANCE, column.getDataType());
+    assertEquals(Integer.valueOf(128), column.getMaxLength());
+    assertEquals(1, column.getPosition());
+    assertTrue(column.isNullable());
+
+    PTableProtos.PColumn proto = PColumnImpl.toProto(column);
+    assertNotNull(proto);
+    assertEquals("VECTOR(FLOAT)", proto.getDataType());
+    assertEquals(128, proto.getMaxLength());
+
+    PColumn deserialized = PColumnImpl.createFromProto(proto);
+    assertNotNull(deserialized);
+    assertEquals(column.getName(), deserialized.getName());
+    assertEquals(column.getFamilyName(), deserialized.getFamilyName());
+    assertEquals(PVectorFloat.INSTANCE, deserialized.getDataType());
+    assertEquals(Integer.valueOf(128), deserialized.getMaxLength());
+    assertEquals(column.getPosition(), deserialized.getPosition());
+    assertEquals(column.isNullable(), deserialized.isNullable());
+  }
+
+  @Test
+  public void testVectorDoubleProtobufRoundTrip() {
+    PColumn column =
+      new PColumnImpl(PNameFactory.newName("VD"), PNameFactory.newName("0"), PVectorDouble.INSTANCE,
+        256, null, false, 2, SortOrder.ASC, null, null, false, null, false, false, null, 0L);
+
+    assertEquals(PNameFactory.newName("VD"), column.getName());
+    assertEquals(PNameFactory.newName("0"), column.getFamilyName());
+    assertEquals(PVectorDouble.INSTANCE, column.getDataType());
+    assertEquals(Integer.valueOf(256), column.getMaxLength());
+    assertEquals(2, column.getPosition());
+    assertFalse(column.isNullable());
+
+    PTableProtos.PColumn proto = PColumnImpl.toProto(column);
+    assertNotNull(proto);
+    assertEquals("VECTOR(DOUBLE)", proto.getDataType());
+    assertEquals(256, proto.getMaxLength());
+
+    PColumn deserialized = PColumnImpl.createFromProto(proto);
+    assertNotNull(deserialized);
+    assertEquals(column.getName(), deserialized.getName());
+    assertEquals(column.getFamilyName(), deserialized.getFamilyName());
+    assertEquals(PVectorDouble.INSTANCE, deserialized.getDataType());
+    assertEquals(Integer.valueOf(256), deserialized.getMaxLength());
+    assertEquals(column.getPosition(), deserialized.getPosition());
+    assertEquals(column.isNullable(), deserialized.isNullable());
+  }
+
+  @Test
+  public void testFloatNativeEncodingRoundTrip() {
+    float[] original = new float[] { 1.0f, -2.5f, Float.NaN, Float.POSITIVE_INFINITY, 0.0f };
+    byte[] encoded = PVectorFloat.INSTANCE.toBytes(original);
+    assertEquals(original.length * Bytes.SIZEOF_FLOAT, encoded.length);
+
+    for (int i = 0; i < original.length; i++) {
+      int rawBits = Bytes.toInt(encoded, i * Bytes.SIZEOF_FLOAT);
+      assertEquals("Element " + i + " bit pattern mismatch", Float.floatToRawIntBits(original[i]),
+        rawBits);
+    }
+
+    float[] decoded = (float[]) PVectorFloat.INSTANCE.toObject(encoded, 0, encoded.length,
+      PVectorFloat.INSTANCE, SortOrder.ASC, null, null);
+    assertArrayEquals(original, decoded, 0.0f);
+  }
+
+  @Test
+  public void testDoubleNativeEncodingRoundTrip() {
+    double[] original = new double[] { 1.0, -2.5, Double.NaN, Double.NEGATIVE_INFINITY, 0.0 };
+    byte[] encoded = PVectorDouble.INSTANCE.toBytes(original);
+    assertEquals(original.length * Bytes.SIZEOF_DOUBLE, encoded.length);
+
+    for (int i = 0; i < original.length; i++) {
+      long rawBits = Bytes.toLong(encoded, i * Bytes.SIZEOF_DOUBLE);
+      assertEquals("Element " + i + " bit pattern mismatch",
+        Double.doubleToRawLongBits(original[i]), rawBits);
+    }
+
+    double[] decoded = (double[]) PVectorDouble.INSTANCE.toObject(encoded, 0, encoded.length,
+      PVectorDouble.INSTANCE, SortOrder.ASC, null, null);
+    assertArrayEquals(original, decoded, 0.0);
+  }
+
+  @Test
+  public void testFloatReadElementASC() {
+    float[] vector = new float[] { 3.14f, -1.0f, 0.0f, Float.MAX_VALUE };
+    byte[] bytes = PVectorFloat.INSTANCE.toBytes(vector);
+
+    for (int i = 0; i < vector.length; i++) {
+      float read = PVectorFloat.readElement(bytes, 0, i);
+      assertEquals("readElement[" + i + "]", vector[i], read, 0.0f);
+    }
+  }
+
+  @Test
+  public void testFloatReadElementDESC() {
+    float[] vector = new float[] { 1.0f, 2.0f, 3.0f };
+    byte[] bytes = PVectorFloat.INSTANCE.toBytes(vector, SortOrder.DESC);
+
+    for (int i = 0; i < vector.length; i++) {
+      float read = PVectorFloat.readElement(bytes, 0, i, SortOrder.DESC);
+      assertEquals("readElement DESC[" + i + "]", vector[i], read, 1e-5f);
+    }
+  }
+
+  @Test
+  public void testDoubleReadElementASC() {
+    double[] vector = new double[] { Math.PI, -Math.E, 0.0, Double.MAX_VALUE };
+    byte[] bytes = PVectorDouble.INSTANCE.toBytes(vector);
+
+    for (int i = 0; i < vector.length; i++) {
+      double read = PVectorDouble.readElement(bytes, 0, i);
+      assertEquals("readElement[" + i + "]", vector[i], read, 0.0);
+    }
+  }
+
+  @Test
+  public void testFloatWriteElementsAndReadElements() {
+    float[] vector = new float[] { 10.0f, 20.0f, 30.0f, 40.0f };
+    byte[] buf = new byte[vector.length * Bytes.SIZEOF_FLOAT + 4]; // Offset padding
+    int offset = 4;
+    PVectorFloat.writeElements(vector, buf, offset);
+
+    float[] decoded = PVectorFloat.readElements(buf, offset, vector.length * Bytes.SIZEOF_FLOAT);
+    assertArrayEquals(vector, decoded, 0.0f);
+  }
+
+  @Test
+  public void testDoubleWriteElementsAndReadElements() {
+    double[] vector = new double[] { 100.0, 200.0, 300.0 };
+    byte[] buf = new byte[vector.length * Bytes.SIZEOF_DOUBLE + 8];
+    int offset = 8;
+    PVectorDouble.writeElements(vector, buf, offset);
+
+    double[] decoded = PVectorDouble.readElements(buf, offset, vector.length * Bytes.SIZEOF_DOUBLE);
+    assertArrayEquals(vector, decoded, 0.0);
+  }
+
+  @Test
+  public void testFloatReadElementsDescRoundTrip() {
+    float[] original = new float[] { 1.0f, 2.0f, 3.0f };
+    byte[] descBytes = PVectorFloat.INSTANCE.toBytes(original, SortOrder.DESC);
+
+    float[] decoded = PVectorFloat.readElements(descBytes, 0, descBytes.length, SortOrder.DESC);
+    assertArrayEquals(original, decoded, 1e-5f);
+  }
+
+  @Test
+  public void testDoubleReadElementsDescRoundTrip() {
+    double[] original = new double[] { 1.0, 2.0, 3.0 };
+    byte[] descBytes = PVectorDouble.INSTANCE.toBytes(original, SortOrder.DESC);
+
+    double[] decoded = PVectorDouble.readElements(descBytes, 0, descBytes.length, SortOrder.DESC);
+    assertArrayEquals(original, decoded, 1e-7);
+  }
+
+  @Test
+  public void testZeroCopyL2DistancePattern() {
+    // Direct buffer Euclidean distance calculation
+    float[] a = { 3.0f, 0.0f };
+    float[] b = { 0.0f, 4.0f };
+
+    byte[] bytesA = PVectorFloat.INSTANCE.toBytes(a);
+    byte[] bytesB = PVectorFloat.INSTANCE.toBytes(b);
+    int dim = bytesA.length / Bytes.SIZEOF_FLOAT;
+
+    double sumSq = 0.0;
+    for (int i = 0; i < dim; i++) {
+      float ai = PVectorFloat.readElement(bytesA, 0, i);
+      float bi = PVectorFloat.readElement(bytesB, 0, i);
+      double d = ai - bi;
+      sumSq += d * d;
+    }
+    assertEquals(5.0, Math.sqrt(sumSq), 1e-6);
+  }
+
+  @Test
+  public void testZeroCopyL2DistancePatternDouble() {
+    // Direct buffer Euclidean distance calculation for double vectors
+    double[] a = { 3.0, 0.0 };
+    double[] b = { 0.0, 4.0 };
+
+    byte[] bytesA = PVectorDouble.INSTANCE.toBytes(a);
+    byte[] bytesB = PVectorDouble.INSTANCE.toBytes(b);
+    int dim = bytesA.length / Bytes.SIZEOF_DOUBLE;
+
+    double sumSq = 0.0;
+    for (int i = 0; i < dim; i++) {
+      double ai = PVectorDouble.readElement(bytesA, 0, i);
+      double bi = PVectorDouble.readElement(bytesB, 0, i);
+      double d = ai - bi;
+      sumSq += d * d;
+    }
+    assertEquals(5.0, Math.sqrt(sumSq), 1e-9);
+  }
+
+  @Test
+  public void testWriteElementsBufferTooSmall() {
+    float[] vector = new float[] { 1.0f, 2.0f, 3.0f };
+    byte[] tooSmall = new byte[8];
+    try {
+      PVectorFloat.writeElements(vector, tooSmall, 0);
+      fail("Should throw on buffer too small");
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+
+  @Test
+  public void testDoubleWriteElementsBufferTooSmall() {
+    double[] vector = new double[] { 1.0, 2.0 };
+    byte[] tooSmall = new byte[8];
+    try {
+      PVectorDouble.writeElements(vector, tooSmall, 0);
+      fail("Should throw on buffer too small");
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+
+  @Test
+  public void testNumericArraysOfAnyElementTypeConvert() throws Exception {
+    // Numeric array coercion across element types and widths
+    PhoenixArray ints =
+      PArrayDataType.instantiatePhoenixArray(PInteger.INSTANCE, new Integer[] { 1, 0, -2 });
+    assertArrayEquals(new float[] { 1f, 0f, -2f },
+      (float[]) PVectorFloat.INSTANCE.toObject(ints, PIntegerArray.INSTANCE), 0f);
+    assertArrayEquals(new double[] { 1, 0, -2 },
+      (double[]) PVectorDouble.INSTANCE.toObject(ints, PIntegerArray.INSTANCE), 0);
+    assertArrayEquals(new float[] { 1.5f, 2.5f },
+      (float[]) PVectorFloat.INSTANCE.toObject(new double[] { 1.5, 2.5 }, PDoubleArray.INSTANCE),
+      0f);
+    assertArrayEquals(new double[] { 1.5, 2.5 },
+      (double[]) PVectorDouble.INSTANCE.toObject(new float[] { 1.5f, 2.5f }, PFloatArray.INSTANCE),
+      0);
+    assertArrayEquals(new float[] { 1f, 2f },
+      (float[]) PVectorFloat.INSTANCE.toObject(new Long[] { 1L, 2L }, PLongArray.INSTANCE), 0f);
+    assertEquals(Integer.valueOf(3), PVectorFloat.INSTANCE.getMaxLength(ints));
+    assertTrue(PIntegerArray.INSTANCE.isCoercibleTo(PVectorFloat.INSTANCE));
+    assertTrue(PDecimalArray.INSTANCE.isCoercibleTo(PVectorDouble.INSTANCE));
+    assertFalse(PVarcharArray.INSTANCE.isCoercibleTo(PVectorFloat.INSTANCE));
+  }
+
+  @Test
+  public void testNullElementsRejected() {
+    try {
+      PVectorFloat.INSTANCE.toObject(new Float[] { 1f, null, 3f }, PFloatArray.INSTANCE);
+      fail("A NULL element must not be stored as zero");
+    } catch (ConstraintViolationException expected) {
+    }
+    try {
+      PVectorDouble.INSTANCE.toObject(new Object[] { 1.0, null }, PDoubleArray.INSTANCE);
+      fail("A NULL element must not be stored as zero");
+    } catch (ConstraintViolationException expected) {
+    }
+  }
+}