This is an automated email from the ASF dual-hosted git repository.

SYaoJun pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-graphar.git


The following commit(s) were added to refs/heads/main by this push:
     new 923f5953 feat(java): complete GraphAr metadata API parity (#956)
923f5953 is described below

commit 923f5953993d39600d86311548f1d7dae53f03c1
Author: alex <[email protected]>
AuthorDate: Wed Aug 19 02:34:18 2026 +0300

    feat(java): complete GraphAr metadata API parity (#956)
    
    * feat(java): complete immutable metadata API parity
    
    Expose the indexed lookups and immutable property-group removal operations 
that match the C++ GraphInfo metadata surface.\n\nConstraint: Product-fork 
slice only; Phase 3 io-api remains owned by the parallel session.
    
    * feat(java): preserve GraphAr metadata in pure-Java SDK
    
    * fix(java): make metadata removal value based
---
 .../java/org/apache/graphar/info/EdgeInfo.java     |  21 ++++
 .../java/org/apache/graphar/info/GraphInfo.java    | 107 +++++++++++++++++++--
 .../java/org/apache/graphar/info/Property.java     |  26 ++++-
 .../org/apache/graphar/info/PropertyGroup.java     |  43 ++++++++-
 .../java/org/apache/graphar/info/VertexInfo.java   |  19 ++++
 .../graphar/info/loader/BaseGraphInfoLoader.java   |  12 ++-
 .../org/apache/graphar/info/type/DataType.java     | 103 +++++++++++++++-----
 .../org/apache/graphar/info/type/FileType.java     |   7 +-
 .../apache/graphar/info/yaml/ExtraInfoYaml.java    |  51 ++++++++++
 .../org/apache/graphar/info/yaml/GraphYaml.java    |  29 ++++++
 .../java/org/apache/graphar/info/EdgeInfoTest.java |  30 ++++++
 .../apache/graphar/info/GraphInfoLoaderTest.java   |  23 +++++
 .../apache/graphar/info/GraphInfoMetadataTest.java |  55 +++++++++++
 .../apache/graphar/info/GraphInfoSaverTest.java    |   9 +-
 .../org/apache/graphar/info/GraphInfoTest.java     |   8 ++
 .../graphar/info/MultiFormatGraphInfoTest.java     |   9 +-
 .../org/apache/graphar/info/PropertyGroupTest.java |  27 +++++-
 .../java/org/apache/graphar/info/PropertyTest.java |  20 ++--
 .../org/apache/graphar/info/TestDataFactory.java   |   5 +-
 .../java/org/apache/graphar/info/TestUtil.java     |  10 ++
 .../apache/graphar/info/TestVerificationUtils.java |   3 +
 .../org/apache/graphar/info/VertexInfoTest.java    |  32 ++++++
 .../org/apache/graphar/info/type/DataTypeTest.java |  51 ++++++++++
 23 files changed, 641 insertions(+), 59 deletions(-)

diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/EdgeInfo.java 
b/maven-projects/info/src/main/java/org/apache/graphar/info/EdgeInfo.java
index 5e546779..1d0a804a 100644
--- a/maven-projects/info/src/main/java/org/apache/graphar/info/EdgeInfo.java
+++ b/maven-projects/info/src/main/java/org/apache/graphar/info/EdgeInfo.java
@@ -423,6 +423,23 @@ public class EdgeInfo {
                                         newPropertyGroups));
     }
 
+    public Optional<EdgeInfo> removePropertyGroupAsNew(PropertyGroup 
propertyGroup) {
+        return propertyGroups
+                .removePropertyGroupAsNew(propertyGroup)
+                .map(
+                        newPropertyGroups ->
+                                new EdgeInfo(
+                                        edgeTriplet,
+                                        chunkSize,
+                                        srcChunkSize,
+                                        dstChunkSize,
+                                        directed,
+                                        baseUri,
+                                        version,
+                                        adjacentLists,
+                                        newPropertyGroups));
+    }
+
     public boolean hasAdjListType(AdjListType adjListType) {
         return adjacentLists.containsKey(adjListType);
     }
@@ -463,6 +480,10 @@ public class EdgeInfo {
         return propertyGroups.getPropertyGroup(property);
     }
 
+    public PropertyGroup getPropertyGroupByIndex(int index) {
+        return propertyGroups.getPropertyGroupByIndex(index);
+    }
+
     public URI getPropertyGroupUri(PropertyGroup propertyGroup, AdjListType 
adjListType) {
         checkPropertyGroupExist(propertyGroup);
         return resolvePath(getAdjacentListBaseUri(adjListType), 
propertyGroup.getPrefix());
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/GraphInfo.java 
b/maven-projects/info/src/main/java/org/apache/graphar/info/GraphInfo.java
index 894f22ea..1249de39 100644
--- a/maven-projects/info/src/main/java/org/apache/graphar/info/GraphInfo.java
+++ b/maven-projects/info/src/main/java/org/apache/graphar/info/GraphInfo.java
@@ -22,7 +22,9 @@ package org.apache.graphar.info;
 import java.io.Writer;
 import java.net.URI;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -40,6 +42,8 @@ public class GraphInfo {
     private final Map<String, VertexInfo> vertexType2VertexInfo;
     private final Map<String, EdgeInfo> edgeConcat2EdgeInfo;
     private final VersionInfo version;
+    private final List<String> labels;
+    private final Map<String, String> extraInfo;
     private final Map<String, URI> types2StoreUri;
 
     public GraphInfo(
@@ -48,12 +52,25 @@ public class GraphInfo {
             Map<URI, EdgeInfo> edgeInfos,
             URI uri,
             String version) {
+        this(name, vertexInfos, edgeInfos, uri, version, List.of(), Map.of());
+    }
+
+    public GraphInfo(
+            String name,
+            Map<URI, VertexInfo> vertexInfos,
+            Map<URI, EdgeInfo> edgeInfos,
+            URI uri,
+            String version,
+            List<String> labels,
+            Map<String, String> extraInfo) {
         this(
                 name,
                 new ArrayList<>(vertexInfos.values()),
                 new ArrayList<>(edgeInfos.values()),
                 uri,
-                version);
+                version,
+                labels,
+                extraInfo);
         vertexInfos.forEach((key, value) -> types2StoreUri.put(value.getType() 
+ ".vertex", key));
         edgeInfos.forEach((key, value) -> types2StoreUri.put(value.getConcat() 
+ ".edge", key));
     }
@@ -64,7 +81,25 @@ public class GraphInfo {
             List<EdgeInfo> edgeInfos,
             String prefix,
             String version) {
-        this(name, vertexInfos, edgeInfos, prefix == null ? null : 
URI.create(prefix), version);
+        this(name, vertexInfos, edgeInfos, prefix, version, List.of(), 
Map.of());
+    }
+
+    public GraphInfo(
+            String name,
+            List<VertexInfo> vertexInfos,
+            List<EdgeInfo> edgeInfos,
+            String prefix,
+            String version,
+            List<String> labels,
+            Map<String, String> extraInfo) {
+        this(
+                name,
+                vertexInfos,
+                edgeInfos,
+                prefix == null ? null : URI.create(prefix),
+                version,
+                labels,
+                extraInfo);
     }
 
     public GraphInfo(
@@ -73,11 +108,27 @@ public class GraphInfo {
             List<EdgeInfo> edgeInfos,
             URI baseUri,
             String version) {
+        this(name, vertexInfos, edgeInfos, baseUri, version, List.of(), 
Map.of());
+    }
+
+    public GraphInfo(
+            String name,
+            List<VertexInfo> vertexInfos,
+            List<EdgeInfo> edgeInfos,
+            URI baseUri,
+            String version,
+            List<String> labels,
+            Map<String, String> extraInfo) {
         this.name = name;
         this.vertexInfos = List.copyOf(vertexInfos);
         this.edgeInfos = List.copyOf(edgeInfos);
         this.baseUri = baseUri;
         this.version = VersionParser.getVersion(version);
+        this.labels = labels == null ? List.of() : List.copyOf(labels);
+        this.extraInfo =
+                extraInfo == null
+                        ? Map.of()
+                        : Collections.unmodifiableMap(new 
LinkedHashMap<>(extraInfo));
         this.vertexType2VertexInfo =
                 vertexInfos.stream()
                         .collect(
@@ -98,7 +149,9 @@ public class GraphInfo {
             URI baseUri,
             String version,
             Map<String, VertexInfo> vertexType2VertexInfo,
-            Map<String, EdgeInfo> edgeConcat2EdgeInfo) {
+            Map<String, EdgeInfo> edgeConcat2EdgeInfo,
+            List<String> labels,
+            Map<String, String> extraInfo) {
         this(
                 name,
                 vertexInfos,
@@ -106,7 +159,9 @@ public class GraphInfo {
                 baseUri,
                 VersionParser.getVersion(version),
                 vertexType2VertexInfo,
-                edgeConcat2EdgeInfo);
+                edgeConcat2EdgeInfo,
+                labels,
+                extraInfo);
     }
 
     private GraphInfo(
@@ -116,12 +171,16 @@ public class GraphInfo {
             URI baseUri,
             VersionInfo version,
             Map<String, VertexInfo> vertexType2VertexInfo,
-            Map<String, EdgeInfo> edgeConcat2EdgeInfo) {
+            Map<String, EdgeInfo> edgeConcat2EdgeInfo,
+            List<String> labels,
+            Map<String, String> extraInfo) {
         this.name = name;
         this.vertexInfos = vertexInfos;
         this.edgeInfos = edgeInfos;
         this.baseUri = baseUri;
         this.version = version;
+        this.labels = labels;
+        this.extraInfo = extraInfo;
         this.vertexType2VertexInfo = vertexType2VertexInfo;
         this.edgeConcat2EdgeInfo = edgeConcat2EdgeInfo;
         this.types2StoreUri = new HashMap<>();
@@ -176,7 +235,9 @@ public class GraphInfo {
                         baseUri,
                         version,
                         newVertexType2VertexInfo,
-                        edgeConcat2EdgeInfo));
+                        edgeConcat2EdgeInfo,
+                        labels,
+                        extraInfo));
     }
 
     public Optional<GraphInfo> removeVertex(VertexInfo vertexInfo) {
@@ -204,7 +265,9 @@ public class GraphInfo {
                         baseUri,
                         version,
                         newVertexInfoMap,
-                        edgeConcat2EdgeInfo));
+                        edgeConcat2EdgeInfo,
+                        labels,
+                        extraInfo));
     }
 
     public Optional<GraphInfo> addEdgeAsNew(EdgeInfo edgeInfo) {
@@ -230,7 +293,9 @@ public class GraphInfo {
                         baseUri,
                         version,
                         vertexType2VertexInfo,
-                        newEdgeConcat2EdgeInfo));
+                        newEdgeConcat2EdgeInfo,
+                        labels,
+                        extraInfo));
     }
 
     public Optional<GraphInfo> removeEdge(EdgeInfo edgeInfo) {
@@ -262,7 +327,9 @@ public class GraphInfo {
                         baseUri,
                         version,
                         vertexType2VertexInfo,
-                        newEdgeConcat2EdgeInfo));
+                        newEdgeConcat2EdgeInfo,
+                        labels,
+                        extraInfo));
     }
 
     public boolean hasVertexInfo(String type) {
@@ -283,6 +350,20 @@ public class GraphInfo {
         return edgeConcat2EdgeInfo.get(EdgeInfo.concat(srcType, edgeType, 
dstType));
     }
 
+    public VertexInfo getVertexInfoByIndex(int index) {
+        if (index < 0 || index >= vertexInfos.size()) {
+            throw new IllegalArgumentException("Vertex info index " + index + 
" is out of range");
+        }
+        return vertexInfos.get(index);
+    }
+
+    public EdgeInfo getEdgeInfoByIndex(int index) {
+        if (index < 0 || index >= edgeInfos.size()) {
+            throw new IllegalArgumentException("Edge info index " + index + " 
is out of range");
+        }
+        return edgeInfos.get(index);
+    }
+
     public int getVertexInfoNum() {
         return vertexInfos.size();
     }
@@ -315,6 +396,14 @@ public class GraphInfo {
         return version;
     }
 
+    public List<String> getLabels() {
+        return labels;
+    }
+
+    public Map<String, String> getExtraInfo() {
+        return extraInfo;
+    }
+
     public void setStoreUri(VertexInfo vertexInfo, URI storeUri) {
         this.types2StoreUri.put(vertexInfo.getType() + ".vertex", storeUri);
     }
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/Property.java 
b/maven-projects/info/src/main/java/org/apache/graphar/info/Property.java
index 2d63d29f..a9c859ec 100644
--- a/maven-projects/info/src/main/java/org/apache/graphar/info/Property.java
+++ b/maven-projects/info/src/main/java/org/apache/graphar/info/Property.java
@@ -19,6 +19,7 @@
 
 package org.apache.graphar.info;
 
+import java.util.Objects;
 import org.apache.graphar.info.type.Cardinality;
 import org.apache.graphar.info.type.DataType;
 import org.apache.graphar.info.yaml.PropertyYaml;
@@ -44,14 +45,14 @@ public class Property {
         this.dataType = dataType;
         this.cardinality = cardinality;
         this.primary = primary;
-        this.nullable = nullable;
+        this.nullable = primary ? false : nullable;
     }
 
     public Property(PropertyYaml yamlParser) {
         this.name = yamlParser.getName();
         this.dataType = DataType.fromString(yamlParser.getData_type());
         this.primary = yamlParser.getIs_primary();
-        this.nullable = yamlParser.getIs_nullable();
+        this.nullable = primary ? false : yamlParser.getIs_nullable();
         Cardinality cardinality = Cardinality.SINGLE;
         if (yamlParser.getCardinality() != null && 
!yamlParser.getCardinality().isEmpty()) {
             cardinality = Cardinality.fromString(yamlParser.getCardinality());
@@ -78,4 +79,25 @@ public class Property {
     public Cardinality getCardinality() {
         return cardinality;
     }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (!(other instanceof Property)) {
+            return false;
+        }
+        Property property = (Property) other;
+        return primary == property.primary
+                && nullable == property.nullable
+                && Objects.equals(name, property.name)
+                && Objects.equals(dataType, property.dataType)
+                && Objects.equals(cardinality, property.cardinality);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(name, dataType, cardinality, primary, nullable);
+    }
 }
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/PropertyGroup.java 
b/maven-projects/info/src/main/java/org/apache/graphar/info/PropertyGroup.java
index 2aceacea..4ce23d1e 100644
--- 
a/maven-projects/info/src/main/java/org/apache/graphar/info/PropertyGroup.java
+++ 
b/maven-projects/info/src/main/java/org/apache/graphar/info/PropertyGroup.java
@@ -24,6 +24,7 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -135,7 +136,7 @@ public class PropertyGroup implements Iterable<Property> {
             propertyNameSet.put(propertyName, true);
 
             // TODO: support list type in csv file
-            if (property.getDataType() == DataType.LIST && fileType == 
FileType.CSV) {
+            if (property.getDataType().isList() && fileType == FileType.CSV) {
                 return false;
             }
             if (property.getCardinality() != Cardinality.SINGLE && fileType == 
FileType.CSV) {
@@ -145,6 +146,25 @@ public class PropertyGroup implements Iterable<Property> {
 
         return true;
     }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (!(other instanceof PropertyGroup)) {
+            return false;
+        }
+        PropertyGroup propertyGroup = (PropertyGroup) other;
+        return propertyList.equals(propertyGroup.propertyList)
+                && fileType == propertyGroup.fileType
+                && Objects.equals(baseUri, propertyGroup.baseUri);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(propertyList, fileType, baseUri);
+    }
 }
 
 class PropertyGroups {
@@ -208,6 +228,19 @@ class PropertyGroups {
                         newProperties));
     }
 
+    Optional<PropertyGroups> removePropertyGroupAsNew(PropertyGroup 
propertyGroup) {
+        if (propertyGroup == null || !hasPropertyGroup(propertyGroup)) {
+            return Optional.empty();
+        }
+        return Optional.of(
+                new PropertyGroups(
+                        propertyGroupList.stream()
+                                .filter(
+                                        existingPropertyGroup ->
+                                                
!existingPropertyGroup.equals(propertyGroup))
+                                .collect(Collectors.toUnmodifiableList())));
+    }
+
     boolean hasProperty(String propertyName) {
         return properties.containsKey(propertyName);
     }
@@ -249,6 +282,14 @@ class PropertyGroups {
         return propertyGroupMap.get(propertyName);
     }
 
+    PropertyGroup getPropertyGroupByIndex(int index) {
+        if (index < 0 || index >= propertyGroupList.size()) {
+            throw new IllegalArgumentException(
+                    "Property group index " + index + " is out of range");
+        }
+        return propertyGroupList.get(index);
+    }
+
     private void checkPropertyExist(String propertyName) {
         if (null == propertyName) {
             throw new IllegalArgumentException("Property name is null");
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/VertexInfo.java 
b/maven-projects/info/src/main/java/org/apache/graphar/info/VertexInfo.java
index c6f4603d..ee1dc642 100644
--- a/maven-projects/info/src/main/java/org/apache/graphar/info/VertexInfo.java
+++ b/maven-projects/info/src/main/java/org/apache/graphar/info/VertexInfo.java
@@ -236,6 +236,21 @@ public class VertexInfo {
                                         version));
     }
 
+    public Optional<VertexInfo> removePropertyGroupAsNew(PropertyGroup 
propertyGroup) {
+        return propertyGroups
+                .removePropertyGroupAsNew(propertyGroup)
+                .map(PropertyGroups::getPropertyGroupList)
+                .map(
+                        newPropertyGroups ->
+                                new VertexInfo(
+                                        type,
+                                        chunkSize,
+                                        newPropertyGroups,
+                                        labels,
+                                        baseUri,
+                                        version));
+    }
+
     public int getPropertyGroupNum() {
         return propertyGroups.getPropertyGroupNum();
     }
@@ -268,6 +283,10 @@ public class VertexInfo {
         return propertyGroups.getPropertyGroup(property);
     }
 
+    public PropertyGroup getPropertyGroupByIndex(int index) {
+        return propertyGroups.getPropertyGroupByIndex(index);
+    }
+
     public URI getPropertyGroupUri(PropertyGroup propertyGroup) {
         checkPropertyGroupExist(propertyGroup);
         return getBaseUri().resolve(propertyGroup.getBaseUri());
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/loader/BaseGraphInfoLoader.java
 
b/maven-projects/info/src/main/java/org/apache/graphar/info/loader/BaseGraphInfoLoader.java
index e5767b94..a83411b8 100644
--- 
a/maven-projects/info/src/main/java/org/apache/graphar/info/loader/BaseGraphInfoLoader.java
+++ 
b/maven-projects/info/src/main/java/org/apache/graphar/info/loader/BaseGraphInfoLoader.java
@@ -66,7 +66,17 @@ public abstract class BaseGraphInfoLoader implements 
GraphInfoLoader {
                 vertexInfos,
                 edgeInfos,
                 defaultBaseUri,
-                graphYaml.getVersion());
+                graphYaml.getVersion(),
+                graphYaml.getLabels() == null ? java.util.List.of() : 
graphYaml.getLabels(),
+                graphYaml.getExtra_info() == null
+                        ? Map.of()
+                        : graphYaml.getExtra_info().stream()
+                                .collect(
+                                        Collectors.toMap(
+                                                extraInfo -> 
extraInfo.getKey(),
+                                                extraInfo -> 
extraInfo.getValue(),
+                                                (left, right) -> right,
+                                                
java.util.LinkedHashMap::new)));
     }
 
     protected VertexInfo buildVertexInfoFromVertexYaml(VertexYaml vertexYaml) {
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/type/DataType.java 
b/maven-projects/info/src/main/java/org/apache/graphar/info/type/DataType.java
index d5329338..a552edba 100644
--- 
a/maven-projects/info/src/main/java/org/apache/graphar/info/type/DataType.java
+++ 
b/maven-projects/info/src/main/java/org/apache/graphar/info/type/DataType.java
@@ -19,36 +19,62 @@
 
 package org.apache.graphar.info.type;
 
-public enum DataType {
-    /** Boolean */
-    BOOL,
+import java.util.Objects;
 
-    /** Signed 32-bit integer */
-    INT32,
+/** A GraphAr logical property type. */
+public final class DataType {
+    /** Boolean. */
+    public static final DataType BOOL = new DataType("bool", null);
 
-    /** Signed 64-bit integer */
-    INT64,
+    /** Signed 32-bit integer. */
+    public static final DataType INT32 = new DataType("int32", null);
 
-    /** 4-byte floating point value */
-    FLOAT,
+    /** Signed 64-bit integer. */
+    public static final DataType INT64 = new DataType("int64", null);
 
-    /** 8-byte floating point value */
-    DOUBLE,
+    /** 4-byte floating point value. */
+    public static final DataType FLOAT = new DataType("float", null);
 
-    /** UTF8 variable-length string */
-    STRING,
+    /** 8-byte floating point value. */
+    public static final DataType DOUBLE = new DataType("double", null);
 
-    /** List of same type */
-    LIST,
+    /** UTF8 variable-length string. */
+    public static final DataType STRING = new DataType("string", null);
 
-    /** Date value */
-    DATE,
+    /** Date value. */
+    public static final DataType DATE = new DataType("date", null);
 
-    /** Timestamp value */
-    TIMESTAMP;
+    /** Timestamp value. */
+    public static final DataType TIMESTAMP = new DataType("timestamp", null);
 
-    public static DataType fromString(String s) {
-        switch (s) {
+    private final String typeName;
+    private final DataType valueType;
+
+    private DataType(String typeName, DataType valueType) {
+        this.typeName = typeName;
+        this.valueType = valueType;
+    }
+
+    /**
+     * Creates a GraphAr list type. The GraphAr v1 metadata format supports 
lists of the five
+     * physical element types that can be represented independently in storage 
metadata.
+     */
+    public static DataType listOf(DataType valueType) {
+        if (valueType != INT32
+                && valueType != INT64
+                && valueType != FLOAT
+                && valueType != DOUBLE
+                && valueType != STRING) {
+            throw new IllegalArgumentException("Unsupported GraphAr list value 
type: " + valueType);
+        }
+        return new DataType("list", valueType);
+    }
+
+    public static DataType fromString(String typeName) {
+        if (typeName == null) {
+            throw new IllegalArgumentException("Data type must not be null");
+        }
+        switch (typeName) {
             case "bool":
                 return BOOL;
             case "int32":
@@ -61,19 +87,46 @@ public enum DataType {
                 return DOUBLE;
             case "string":
                 return STRING;
-            case "list":
-                return LIST;
             case "date":
                 return DATE;
             case "timestamp":
                 return TIMESTAMP;
             default:
-                throw new IllegalArgumentException("Unknown data type: " + s);
+                if (typeName.startsWith("list<") && typeName.endsWith(">")) {
+                    return listOf(fromString(typeName.substring(5, 
typeName.length() - 1)));
+                }
+                throw new IllegalArgumentException("Unknown data type: " + 
typeName);
         }
     }
 
+    public boolean isList() {
+        return valueType != null;
+    }
+
+    /** Returns the element type for a list, or {@code null} for a scalar 
type. */
+    public DataType getValueType() {
+        return valueType;
+    }
+
     @Override
     public String toString() {
-        return name().toLowerCase();
+        return isList() ? "list<" + valueType + ">" : typeName;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (!(other instanceof DataType)) {
+            return false;
+        }
+        DataType that = (DataType) other;
+        return typeName.equals(that.typeName) && Objects.equals(valueType, 
that.valueType);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(typeName, valueType);
     }
 }
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/type/FileType.java 
b/maven-projects/info/src/main/java/org/apache/graphar/info/type/FileType.java
index 09e5bb19..6cf162e8 100644
--- 
a/maven-projects/info/src/main/java/org/apache/graphar/info/type/FileType.java
+++ 
b/maven-projects/info/src/main/java/org/apache/graphar/info/type/FileType.java
@@ -22,7 +22,8 @@ package org.apache.graphar.info.type;
 public enum FileType {
     CSV,
     PARQUET,
-    ORC;
+    ORC,
+    JSON;
 
     public String toString() {
         switch (this) {
@@ -32,6 +33,8 @@ public enum FileType {
                 return "parquet";
             case ORC:
                 return "orc";
+            case JSON:
+                return "json";
             default:
                 throw new IllegalArgumentException("Unknown file type: " + 
this);
         }
@@ -48,6 +51,8 @@ public enum FileType {
                 return PARQUET;
             case "orc":
                 return ORC;
+            case "json":
+                return JSON;
             default:
                 throw new IllegalArgumentException("Unknown file type: " + 
fileType);
         }
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/yaml/ExtraInfoYaml.java
 
b/maven-projects/info/src/main/java/org/apache/graphar/info/yaml/ExtraInfoYaml.java
new file mode 100644
index 00000000..ea477e0b
--- /dev/null
+++ 
b/maven-projects/info/src/main/java/org/apache/graphar/info/yaml/ExtraInfoYaml.java
@@ -0,0 +1,51 @@
+/*
+ * 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.graphar.info.yaml;
+
+/** A single GraphAr graph-level metadata key/value pair. */
+public class ExtraInfoYaml {
+    private String key;
+    private String value;
+
+    public ExtraInfoYaml() {
+        this("", "");
+    }
+
+    public ExtraInfoYaml(String key, String value) {
+        this.key = key;
+        this.value = value;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+}
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/yaml/GraphYaml.java 
b/maven-projects/info/src/main/java/org/apache/graphar/info/yaml/GraphYaml.java
index f5353967..bc24f792 100644
--- 
a/maven-projects/info/src/main/java/org/apache/graphar/info/yaml/GraphYaml.java
+++ 
b/maven-projects/info/src/main/java/org/apache/graphar/info/yaml/GraphYaml.java
@@ -37,6 +37,8 @@ public class GraphYaml {
     private String prefix;
     private List<String> vertices;
     private List<String> edges;
+    private List<String> labels;
+    private List<ExtraInfoYaml> extra_info;
     private String version;
     private static final DumperOptions dumperOption;
     private static Representer representer;
@@ -65,6 +67,7 @@ public class GraphYaml {
                     }
                 };
         representer.addClassTag(GraphYaml.class, Tag.MAP);
+        representer.addClassTag(ExtraInfoYaml.class, Tag.MAP);
         representer.addClassTag(VertexYaml.class, Tag.MAP);
         representer.addClassTag(EdgeYaml.class, Tag.MAP);
     }
@@ -74,6 +77,8 @@ public class GraphYaml {
         this.prefix = "";
         this.vertices = new ArrayList<>();
         this.edges = new ArrayList<>();
+        this.labels = null;
+        this.extra_info = null;
         this.version = "";
     }
 
@@ -113,6 +118,14 @@ public class GraphYaml {
                                     return storeUri.toString();
                                 })
                         .collect(Collectors.toList());
+        this.labels =
+                graphInfo.getLabels().isEmpty() ? null : new 
ArrayList<>(graphInfo.getLabels());
+        this.extra_info =
+                graphInfo.getExtraInfo().isEmpty()
+                        ? null
+                        : graphInfo.getExtraInfo().entrySet().stream()
+                                .map(entry -> new 
ExtraInfoYaml(entry.getKey(), entry.getValue()))
+                                .collect(Collectors.toList());
         this.version =
                 Optional.of(graphInfo)
                         .map(GraphInfo::getVersion)
@@ -160,6 +173,22 @@ public class GraphYaml {
         this.edges = edges;
     }
 
+    public List<String> getLabels() {
+        return labels;
+    }
+
+    public void setLabels(List<String> labels) {
+        this.labels = labels;
+    }
+
+    public List<ExtraInfoYaml> getExtra_info() {
+        return extra_info;
+    }
+
+    public void setExtra_info(List<ExtraInfoYaml> extra_info) {
+        this.extra_info = extra_info;
+    }
+
     public String getVersion() {
         return version;
     }
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/EdgeInfoTest.java 
b/maven-projects/info/src/test/java/org/apache/graphar/info/EdgeInfoTest.java
index da64cf01..458ec71e 100644
--- 
a/maven-projects/info/src/test/java/org/apache/graphar/info/EdgeInfoTest.java
+++ 
b/maven-projects/info/src/test/java/org/apache/graphar/info/EdgeInfoTest.java
@@ -22,6 +22,7 @@ package org.apache.graphar.info;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 import org.apache.graphar.info.type.AdjListType;
 import org.apache.graphar.info.type.FileType;
 import org.junit.Assert;
@@ -169,6 +170,35 @@ public class EdgeInfoTest {
         Assert.assertEquals(2, edgeInfo.getPropertyGroups().size());
     }
 
+    @Test
+    public void propertyGroupLookupAndRemovalTest() {
+        EdgeInfo edgeInfo =
+                createBaseEdgeInfoBuilder()
+                        .dstType("person")
+                        .adjacentLists(List.of(TestUtil.orderedBySource))
+                        .propertyGroups(new 
PropertyGroups(List.of(TestUtil.pg1, TestUtil.pg2)))
+                        .build();
+
+        Assert.assertEquals(TestUtil.pg1, edgeInfo.getPropertyGroupByIndex(0));
+        Assert.assertEquals(TestUtil.pg2, edgeInfo.getPropertyGroupByIndex(1));
+        Assert.assertThrows(
+                IllegalArgumentException.class, () -> 
edgeInfo.getPropertyGroupByIndex(-1));
+        Assert.assertThrows(
+                IllegalArgumentException.class, () -> 
edgeInfo.getPropertyGroupByIndex(2));
+        
Assert.assertTrue(edgeInfo.removePropertyGroupAsNew(TestUtil.pg3).isEmpty());
+
+        PropertyGroup equalFirstGroup =
+                new PropertyGroup(
+                        TestUtil.pg1.getPropertyList(),
+                        TestUtil.pg1.getFileType(),
+                        TestUtil.pg1.getBaseUri());
+        Optional<EdgeInfo> withoutFirstGroup = 
edgeInfo.removePropertyGroupAsNew(equalFirstGroup);
+        Assert.assertTrue(withoutFirstGroup.isPresent());
+        Assert.assertEquals(2, edgeInfo.getPropertyGroupNum());
+        Assert.assertEquals(1, withoutFirstGroup.get().getPropertyGroupNum());
+        Assert.assertEquals(TestUtil.pg2, 
withoutFirstGroup.get().getPropertyGroupByIndex(0));
+    }
+
     @Test
     public void testIsValidated() {
         // Test valid edge info
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoLoaderTest.java
 
b/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoLoaderTest.java
index 94187c42..3d9f08d4 100644
--- 
a/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoLoaderTest.java
+++ 
b/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoLoaderTest.java
@@ -25,6 +25,8 @@ import org.apache.graphar.info.loader.GraphInfoLoader;
 import 
org.apache.graphar.info.loader.impl.LocalFileSystemReaderGraphInfoLoader;
 import 
org.apache.graphar.info.loader.impl.LocalFileSystemStreamGraphInfoLoader;
 import 
org.apache.graphar.info.loader.impl.LocalFileSystemStringGraphInfoLoader;
+import org.apache.graphar.info.type.AdjListType;
+import org.apache.graphar.info.type.FileType;
 import org.junit.AfterClass;
 import org.junit.Assert;
 import org.junit.BeforeClass;
@@ -64,6 +66,27 @@ public class GraphInfoLoaderTest {
         testGraphInfo(graphInfo);
     }
 
+    @Test
+    public void testJsonMetadataFixture() throws IOException {
+        GraphInfoLoader loader = new LocalFileSystemStreamGraphInfoLoader();
+        GraphInfo graphInfo = 
loader.loadGraphInfo(TestUtil.getJsonLdbcSampleGraphURI());
+
+        Assert.assertEquals("LdbcSample", graphInfo.getName());
+        Assert.assertEquals(
+                FileType.JSON,
+                
graphInfo.getVertexInfos().get(0).getPropertyGroups().get(0).getFileType());
+        Assert.assertEquals(
+                FileType.JSON,
+                graphInfo
+                        .getEdgeInfos()
+                        .get(0)
+                        .getAdjacentList(AdjListType.ordered_by_source)
+                        .getFileType());
+        Assert.assertEquals(
+                FileType.JSON,
+                
graphInfo.getEdgeInfos().get(0).getPropertyGroups().get(0).getFileType());
+    }
+
     private void testGraphInfo(GraphInfo graphInfo) {
         Assert.assertNotNull(graphInfo);
         Assert.assertNotNull(graphInfo.getEdgeInfos());
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoMetadataTest.java
 
b/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoMetadataTest.java
new file mode 100644
index 00000000..7ef97a27
--- /dev/null
+++ 
b/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoMetadataTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.graphar.info;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import org.apache.graphar.info.yaml.GraphYaml;
+import org.junit.Assert;
+import org.junit.Test;
+import org.yaml.snakeyaml.LoaderOptions;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.constructor.Constructor;
+
+public class GraphInfoMetadataTest {
+
+    @Test
+    public void testLabelsAndExtraInfoRoundTrip() {
+        GraphInfo graphInfo =
+                new GraphInfo(
+                        "metadata_graph",
+                        List.of(),
+                        List.of(),
+                        URI.create("file:///tmp/metadata_graph/"),
+                        "gar/v1",
+                        List.of("production", "ldbc"),
+                        Map.of("category", "test graph"));
+
+        String dumped = graphInfo.dump();
+        Yaml yaml = new Yaml(new Constructor(GraphYaml.class, new 
LoaderOptions()));
+        GraphYaml graphYaml = yaml.load(dumped);
+
+        Assert.assertEquals(List.of("production", "ldbc"), 
graphYaml.getLabels());
+        Assert.assertEquals(1, graphYaml.getExtra_info().size());
+        Assert.assertEquals("category", 
graphYaml.getExtra_info().get(0).getKey());
+        Assert.assertEquals("test graph", 
graphYaml.getExtra_info().get(0).getValue());
+    }
+}
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoSaverTest.java
 
b/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoSaverTest.java
index 0c37f96e..111c4a96 100644
--- 
a/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoSaverTest.java
+++ 
b/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoSaverTest.java
@@ -23,6 +23,7 @@ import java.net.URI;
 import java.nio.file.FileSystems;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 import 
org.apache.graphar.info.loader.impl.LocalFileSystemStringGraphInfoLoader;
 import org.apache.graphar.info.saver.GraphInfoSaver;
 import org.apache.graphar.info.saver.impl.LocalFileSystemYamlGraphSaver;
@@ -85,7 +86,13 @@ public class GraphInfoSaverTest extends BaseFileSystemTest {
                         vertexInfos,
                         edgeInfos,
                         graphYaml.getPrefix(),
-                        graphYaml.getVersion());
+                        graphYaml.getVersion(),
+                        graphYaml.getLabels(),
+                        graphYaml.getExtra_info().stream()
+                                .collect(
+                                        Collectors.toMap(
+                                                extraInfo -> 
extraInfo.getKey(),
+                                                extraInfo -> 
extraInfo.getValue())));
         Assert.assertTrue(TestVerificationUtils.equalsGraphInfo(testGraphInfo, 
graphInfoFromYaml));
     }
 
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoTest.java 
b/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoTest.java
index fe5bcbce..57359327 100644
--- 
a/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoTest.java
+++ 
b/maven-projects/info/src/test/java/org/apache/graphar/info/GraphInfoTest.java
@@ -99,6 +99,11 @@ public class GraphInfoTest {
         Assert.assertEquals(1, graphInfo.getVertexInfos().size());
         Assert.assertEquals(1, graphInfo.getVertexInfoNum());
         Assert.assertEquals(personVertexInfo, 
graphInfo.getVertexInfo("person"));
+        Assert.assertEquals(personVertexInfo, 
graphInfo.getVertexInfoByIndex(0));
+        Assert.assertThrows(
+                IllegalArgumentException.class, () -> 
graphInfo.getVertexInfoByIndex(-1));
+        Assert.assertThrows(
+                IllegalArgumentException.class, () -> 
graphInfo.getVertexInfoByIndex(1));
         IllegalArgumentException illegalArgumentException =
                 Assert.assertThrows(
                         IllegalArgumentException.class, () -> 
graphInfo.getVertexInfo("not_exist"));
@@ -106,6 +111,9 @@ public class GraphInfoTest {
                 "Vertex type not_exist not exist in graph ldbc_sample",
                 illegalArgumentException.getMessage());
         Assert.assertEquals(knowsEdgeInfo, graphInfo.getEdgeInfo("person", 
"knows", "person"));
+        Assert.assertEquals(knowsEdgeInfo, graphInfo.getEdgeInfoByIndex(0));
+        Assert.assertThrows(IllegalArgumentException.class, () -> 
graphInfo.getEdgeInfoByIndex(-1));
+        Assert.assertThrows(IllegalArgumentException.class, () -> 
graphInfo.getEdgeInfoByIndex(1));
         illegalArgumentException =
                 Assert.assertThrows(
                         IllegalArgumentException.class,
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/MultiFormatGraphInfoTest.java
 
b/maven-projects/info/src/test/java/org/apache/graphar/info/MultiFormatGraphInfoTest.java
index 1a4d819d..6cbab23a 100644
--- 
a/maven-projects/info/src/test/java/org/apache/graphar/info/MultiFormatGraphInfoTest.java
+++ 
b/maven-projects/info/src/test/java/org/apache/graphar/info/MultiFormatGraphInfoTest.java
@@ -158,7 +158,7 @@ public class MultiFormatGraphInfoTest {
         Property floatProp = new Property("score", DataType.FLOAT, false, 
true);
         Property doubleProp = new Property("precision", DataType.DOUBLE, 
false, true);
         Property stringProp = new Property("name", DataType.STRING, false, 
true);
-        Property listProp = new Property("tags", DataType.LIST, false, true);
+        Property listProp = new Property("tags", 
DataType.listOf(DataType.STRING), false, true);
 
         List<Property> mixedProps =
                 Arrays.asList(
@@ -179,7 +179,8 @@ public class MultiFormatGraphInfoTest {
         Assert.assertEquals(DataType.FLOAT, 
pg.getPropertyMap().get("score").getDataType());
         Assert.assertEquals(DataType.DOUBLE, 
pg.getPropertyMap().get("precision").getDataType());
         Assert.assertEquals(DataType.STRING, 
pg.getPropertyMap().get("name").getDataType());
-        Assert.assertEquals(DataType.LIST, 
pg.getPropertyMap().get("tags").getDataType());
+        Assert.assertEquals(
+                DataType.listOf(DataType.STRING), 
pg.getPropertyMap().get("tags").getDataType());
     }
 
     @Test
@@ -272,13 +273,13 @@ public class MultiFormatGraphInfoTest {
         Assert.assertEquals("verylongpropertynamethatgoesonyesitdoes", 
longNameProp.getName());
         Assert.assertEquals("prop-with_special.chars", 
specialCharsProp.getName());
 
-        // Test all combinations of primary/nullable flags
+        // Primary properties are normalized to non-nullable by GraphAr.
         Property primaryNullable = new Property("test1", DataType.INT32, true, 
true);
         Property primaryNotNullable = new Property("test2", DataType.INT32, 
true, false);
         Property notPrimaryNullable = new Property("test3", DataType.INT32, 
false, true);
         Property notPrimaryNotNullable = new Property("test4", DataType.INT32, 
false, false);
 
-        Assert.assertTrue(primaryNullable.isPrimary() && 
primaryNullable.isNullable());
+        Assert.assertTrue(primaryNullable.isPrimary() && 
!primaryNullable.isNullable());
         Assert.assertTrue(primaryNotNullable.isPrimary() && 
!primaryNotNullable.isNullable());
         Assert.assertTrue(!notPrimaryNullable.isPrimary() && 
notPrimaryNullable.isNullable());
         Assert.assertTrue(
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/PropertyGroupTest.java
 
b/maven-projects/info/src/test/java/org/apache/graphar/info/PropertyGroupTest.java
index 48346577..2057ecf8 100644
--- 
a/maven-projects/info/src/test/java/org/apache/graphar/info/PropertyGroupTest.java
+++ 
b/maven-projects/info/src/test/java/org/apache/graphar/info/PropertyGroupTest.java
@@ -61,6 +61,19 @@ public class PropertyGroupTest {
         Assert.assertEquals(Arrays.asList(idProperty, nameProperty), 
basicGroup.getPropertyList());
     }
 
+    @Test
+    public void testPropertyGroupValueEquality() {
+        Property equivalentId = new Property("id", DataType.INT64, true, 
false);
+        Property equivalentName = new Property("name", DataType.STRING, false, 
true);
+        PropertyGroup equivalentGroup =
+                new PropertyGroup(List.of(equivalentId, equivalentName), 
FileType.CSV, "test/");
+
+        Assert.assertEquals(idProperty, equivalentId);
+        Assert.assertEquals(idProperty.hashCode(), equivalentId.hashCode());
+        Assert.assertEquals(basicGroup, equivalentGroup);
+        Assert.assertEquals(basicGroup.hashCode(), equivalentGroup.hashCode());
+    }
+
     @Test
     public void testPropertyGroupWithAllFileTypes() {
         List<Property> properties = Arrays.asList(testProperty);
@@ -76,6 +89,10 @@ public class PropertyGroupTest {
         PropertyGroup orcGroup =
                 TestDataFactory.createPropertyGroup(properties, FileType.ORC, 
"orc/");
         Assert.assertEquals(FileType.ORC, orcGroup.getFileType());
+
+        PropertyGroup jsonGroup =
+                TestDataFactory.createPropertyGroup(properties, FileType.JSON, 
"json/");
+        Assert.assertEquals(FileType.JSON, jsonGroup.getFileType());
     }
 
     @Test
@@ -249,7 +266,9 @@ public class PropertyGroupTest {
 
     @Test
     public void testPropertyGroupWithComplexDataTypes() {
-        Property listProp = TestDataFactory.createProperty("items", 
DataType.LIST, false, true);
+        Property listProp =
+                TestDataFactory.createProperty(
+                        "items", DataType.listOf(DataType.STRING), false, 
true);
         Property boolProp = TestDataFactory.createProperty("flag", 
DataType.BOOL, false, false);
         Property doubleProp = TestDataFactory.createProperty("score", 
DataType.DOUBLE, false, true);
         List<Property> properties = Arrays.asList(listProp, boolProp, 
doubleProp);
@@ -258,7 +277,8 @@ public class PropertyGroupTest {
                 TestDataFactory.createPropertyGroup(properties, 
FileType.PARQUET, "complex/");
 
         Assert.assertEquals(3, pg.size());
-        Assert.assertEquals(DataType.LIST, 
pg.getPropertyMap().get("items").getDataType());
+        Assert.assertEquals(
+                DataType.listOf(DataType.STRING), 
pg.getPropertyMap().get("items").getDataType());
         Assert.assertEquals(DataType.BOOL, 
pg.getPropertyMap().get("flag").getDataType());
         Assert.assertEquals(DataType.DOUBLE, 
pg.getPropertyMap().get("score").getDataType());
     }
@@ -309,7 +329,8 @@ public class PropertyGroupTest {
 
         // Test invalid property group with CSV file type and LIST data type
         Property listProperty =
-                TestDataFactory.createProperty("listProp", DataType.LIST, 
false, true);
+                TestDataFactory.createProperty(
+                        "listProp", DataType.listOf(DataType.STRING), false, 
true);
         PropertyGroup csvWithListGroup =
                 new PropertyGroup(Arrays.asList(listProperty), FileType.CSV, 
"test/");
         Assert.assertFalse(csvWithListGroup.isValidated());
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/PropertyTest.java 
b/maven-projects/info/src/test/java/org/apache/graphar/info/PropertyTest.java
index 27f28bab..562b0263 100644
--- 
a/maven-projects/info/src/test/java/org/apache/graphar/info/PropertyTest.java
+++ 
b/maven-projects/info/src/test/java/org/apache/graphar/info/PropertyTest.java
@@ -89,8 +89,10 @@ public class PropertyTest {
         Assert.assertEquals(DataType.STRING, stringProp.getDataType());
         TestVerificationUtils.verifyProperty(stringProp, "text", false, true);
 
-        Property listProp = TestDataFactory.createProperty("items", 
DataType.LIST, false, true);
-        Assert.assertEquals(DataType.LIST, listProp.getDataType());
+        Property listProp =
+                TestDataFactory.createProperty(
+                        "items", DataType.listOf(DataType.STRING), false, 
true);
+        Assert.assertEquals(DataType.listOf(DataType.STRING), 
listProp.getDataType());
         TestVerificationUtils.verifyProperty(listProp, "items", false, true);
 
         // Newly added data types
@@ -111,10 +113,10 @@ public class PropertyTest {
         Property primaryNonNull = TestDataFactory.createProperty("id", 
DataType.INT64, true, false);
         TestVerificationUtils.verifyProperty(primaryNonNull, "id", true, 
false);
 
-        // Primary and nullable (unusual but valid)
+        // GraphAr primary properties are never nullable.
         Property primaryNull =
                 TestDataFactory.createProperty("optionalId", DataType.INT64, 
true, true);
-        TestVerificationUtils.verifyProperty(primaryNull, "optionalId", true, 
true);
+        TestVerificationUtils.verifyProperty(primaryNull, "optionalId", true, 
false);
 
         // Non-primary and non-nullable (required field)
         Property requiredField =
@@ -154,13 +156,9 @@ public class PropertyTest {
         Property prop2 = TestDataFactory.createProperty("id", DataType.INT32, 
true, false);
         Property prop3 = TestDataFactory.createProperty("name", 
DataType.STRING, false, true);
 
-        // Note: Property class doesn't override equals(), so this tests 
object identity
-        Assert.assertNotEquals(prop1, prop2); // Different objects
-        Assert.assertNotEquals(prop1, prop3); // Different properties
-
-        // Same object reference
-        Property sameRef = prop1;
-        Assert.assertEquals(prop1, sameRef);
+        Assert.assertEquals(prop1, prop2);
+        Assert.assertEquals(prop1.hashCode(), prop2.hashCode());
+        Assert.assertNotEquals(prop1, prop3);
     }
 
     @Test
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/TestDataFactory.java
 
b/maven-projects/info/src/test/java/org/apache/graphar/info/TestDataFactory.java
index 66b14e13..d04a7d53 100644
--- 
a/maven-projects/info/src/test/java/org/apache/graphar/info/TestDataFactory.java
+++ 
b/maven-projects/info/src/test/java/org/apache/graphar/info/TestDataFactory.java
@@ -20,6 +20,7 @@
 package org.apache.graphar.info;
 
 import java.util.List;
+import java.util.Map;
 import org.apache.graphar.info.type.AdjListType;
 import org.apache.graphar.info.type.Cardinality;
 import org.apache.graphar.info.type.DataType;
@@ -43,7 +44,9 @@ public class TestDataFactory {
                 List.of(personVertex),
                 List.of(knowsEdge),
                 "file:///test_path",
-                "gar/v1");
+                "gar/v1",
+                List.of("ldbc", "sample"),
+                Map.of("category", "test graph"));
     }
 
     /** Creates a person vertex info for testing. */
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/TestUtil.java 
b/maven-projects/info/src/test/java/org/apache/graphar/info/TestUtil.java
index 8a1aec6e..e1b4f575 100644
--- a/maven-projects/info/src/test/java/org/apache/graphar/info/TestUtil.java
+++ b/maven-projects/info/src/test/java/org/apache/graphar/info/TestUtil.java
@@ -41,6 +41,8 @@ public class TestUtil {
             "/ldbc_sample/csv/ldbc_sample.graph.yml";
     private static final String PARQUET_LDBC_SAMPLE_GRAPH_PATH =
             "/ldbc_sample/parquet/ldbc_sample.graph.yml";
+    private static final String JSON_LDBC_SAMPLE_GRAPH_PATH =
+            "/ldbc_sample/json/LdbcSample.graph.yml";
     private static final String LDBC_GRAPH_PATH = 
"/ldbc/parquet/ldbc.graph.yml";
 
     public static String getTestData() {
@@ -68,6 +70,14 @@ public class TestUtil {
         return URI.create(getParquetLdbcSampleGraphPath());
     }
 
+    public static String getJsonLdbcSampleGraphPath() {
+        return getTestData() + "/" + JSON_LDBC_SAMPLE_GRAPH_PATH;
+    }
+
+    public static URI getJsonLdbcSampleGraphURI() {
+        return URI.create(getJsonLdbcSampleGraphPath());
+    }
+
     public static String getLdbcGraphPath() {
         return getTestData() + "/" + LDBC_GRAPH_PATH;
     }
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/TestVerificationUtils.java
 
b/maven-projects/info/src/test/java/org/apache/graphar/info/TestVerificationUtils.java
index b9687787..43ecb1c7 100644
--- 
a/maven-projects/info/src/test/java/org/apache/graphar/info/TestVerificationUtils.java
+++ 
b/maven-projects/info/src/test/java/org/apache/graphar/info/TestVerificationUtils.java
@@ -164,6 +164,9 @@ public class TestVerificationUtils {
                 "GraphInfo version mismatch",
                 expected.getVersion().toString(),
                 actual.getVersion().toString());
+        Assert.assertEquals("GraphInfo labels mismatch", expected.getLabels(), 
actual.getLabels());
+        Assert.assertEquals(
+                "GraphInfo extra info mismatch", expected.getExtraInfo(), 
actual.getExtraInfo());
         Assert.assertTrue(
                 "VertexInfo list mismatch",
                 equalsVertexInfoList(expected.getVertexInfos(), 
actual.getVertexInfos()));
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/VertexInfoTest.java 
b/maven-projects/info/src/test/java/org/apache/graphar/info/VertexInfoTest.java
index b4624415..3c6f77e7 100644
--- 
a/maven-projects/info/src/test/java/org/apache/graphar/info/VertexInfoTest.java
+++ 
b/maven-projects/info/src/test/java/org/apache/graphar/info/VertexInfoTest.java
@@ -23,6 +23,7 @@ import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Optional;
 import org.apache.graphar.info.type.DataType;
 import org.apache.graphar.info.type.FileType;
 import org.junit.Assert;
@@ -81,6 +82,37 @@ public class VertexInfoTest {
         Assert.assertEquals(1, v.getPropertyGroups().size());
     }
 
+    @Test
+    public void propertyGroupLookupAndRemovalTest() {
+        VertexInfo vertexInfo =
+                new VertexInfo(
+                        "person",
+                        100,
+                        List.of(TestUtil.pg1, TestUtil.pg2),
+                        "vertex/person/",
+                        "gar/v1");
+
+        Assert.assertEquals(TestUtil.pg1, 
vertexInfo.getPropertyGroupByIndex(0));
+        Assert.assertEquals(TestUtil.pg2, 
vertexInfo.getPropertyGroupByIndex(1));
+        Assert.assertThrows(
+                IllegalArgumentException.class, () -> 
vertexInfo.getPropertyGroupByIndex(-1));
+        Assert.assertThrows(
+                IllegalArgumentException.class, () -> 
vertexInfo.getPropertyGroupByIndex(2));
+        
Assert.assertTrue(vertexInfo.removePropertyGroupAsNew(TestUtil.pg3).isEmpty());
+
+        PropertyGroup equalFirstGroup =
+                new PropertyGroup(
+                        TestUtil.pg1.getPropertyList(),
+                        TestUtil.pg1.getFileType(),
+                        TestUtil.pg1.getBaseUri());
+        Optional<VertexInfo> withoutFirstGroup =
+                vertexInfo.removePropertyGroupAsNew(equalFirstGroup);
+        Assert.assertTrue(withoutFirstGroup.isPresent());
+        Assert.assertEquals(2, vertexInfo.getPropertyGroupNum());
+        Assert.assertEquals(1, withoutFirstGroup.get().getPropertyGroupNum());
+        Assert.assertEquals(TestUtil.pg2, 
withoutFirstGroup.get().getPropertyGroupByIndex(0));
+    }
+
     @Test
     public void invalidChunkSizeTest() {
         VertexInfo.VertexInfoBuilder invalidChunkSizeBuilder = 
defaultBuilder.chunkSize(-1);
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/type/DataTypeTest.java
 
b/maven-projects/info/src/test/java/org/apache/graphar/info/type/DataTypeTest.java
new file mode 100644
index 00000000..ccd3c907
--- /dev/null
+++ 
b/maven-projects/info/src/test/java/org/apache/graphar/info/type/DataTypeTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.graphar.info.type;
+
+import org.apache.graphar.info.Property;
+import org.apache.graphar.info.yaml.PropertyYaml;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class DataTypeTest {
+
+    @Test
+    public void testListTypeRoundTrip() {
+        DataType listType = DataType.listOf(DataType.INT64);
+
+        Assert.assertEquals("list<int64>", listType.toString());
+        Assert.assertTrue(listType.isList());
+        Assert.assertEquals(DataType.INT64, listType.getValueType());
+        Assert.assertEquals(listType, DataType.fromString("list<int64>"));
+
+        Property property = new Property("ids", listType, false, true);
+        Property roundTripped = new Property(new PropertyYaml(property));
+        Assert.assertEquals(listType, roundTripped.getDataType());
+    }
+
+    @Test
+    public void testUnsupportedListTypesFailFast() {
+        Assert.assertThrows(IllegalArgumentException.class, () -> 
DataType.fromString("list"));
+        Assert.assertThrows(
+                IllegalArgumentException.class, () -> 
DataType.fromString("list<bool>"));
+        Assert.assertThrows(
+                IllegalArgumentException.class, () -> 
DataType.fromString("list<list<int32>>"));
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to