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

xiaokang 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 24dbe1bb feat(java,info): support multi-property in yaml  (#780)
24dbe1bb is described below

commit 24dbe1bb0c89888e5482b9aafeb25b890fbea21c
Author: Xiaokang Yang <[email protected]>
AuthorDate: Mon Dec 8 20:20:00 2025 +0800

    feat(java,info): support multi-property in yaml  (#780)
    
    * add multi property
    
    * remove cardinality in edgeInfo
    
    * fix copilot comment
    
    * format code
    
    * try to fix
    
    * update
---
 .../java/org/apache/graphar/info/EdgeInfo.java     |  11 +
 .../java/org/apache/graphar/info/GraphInfo.java    |   9 +
 .../java/org/apache/graphar/info/Property.java     |  23 +-
 .../org/apache/graphar/info/PropertyGroup.java     |   9 +
 .../java/org/apache/graphar/info/VertexInfo.java   |  11 +
 .../graphar/info/saver/BaseGraphInfoSaver.java     |  13 +
 .../org/apache/graphar/info/type/Cardinality.java  |  60 ++++
 .../org/apache/graphar/info/yaml/PropertyYaml.java |  25 +-
 .../org/apache/graphar/info/yaml/VertexYaml.java   |  13 +
 .../org/apache/graphar/info/GraphInfoTest.java     |   7 +
 .../org/apache/graphar/info/MultiPropertyTest.java | 353 +++++++++++++++++++++
 .../org/apache/graphar/info/TestDataFactory.java   |  11 +
 .../java/org/apache/graphar/info/TestUtil.java     |   9 +
 .../apache/graphar/info/TestVerificationUtils.java |   4 +
 14 files changed, 551 insertions(+), 7 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 cd18c16d..91cb48c4 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
@@ -32,6 +32,7 @@ import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.graphar.info.type.AdjListType;
+import org.apache.graphar.info.type.Cardinality;
 import org.apache.graphar.info.type.DataType;
 import org.apache.graphar.info.yaml.EdgeYaml;
 import org.apache.graphar.info.yaml.GraphYaml;
@@ -496,12 +497,18 @@ public class EdgeInfo {
     }
 
     public void dump(Writer output) {
+        if (!isValidated()) {
+            throw new IllegalStateException("EdgeInfo is not valid and cannot 
be dumped.");
+        }
         Yaml yaml = new Yaml(GraphYaml.getRepresenter(), 
GraphYaml.getDumperOptions());
         EdgeYaml edgeYaml = new EdgeYaml(this);
         yaml.dump(edgeYaml, output);
     }
 
     public String dump() {
+        if (!isValidated()) {
+            throw new IllegalStateException("EdgeInfo is not valid and cannot 
be dumped.");
+        }
         Yaml yaml = new Yaml(GraphYaml.getRepresenter(), 
GraphYaml.getDumperOptions());
         EdgeYaml edgeYaml = new EdgeYaml(this);
         return yaml.dump(edgeYaml);
@@ -626,6 +633,10 @@ public class EdgeInfo {
             }
 
             for (Property p : pg.getPropertyList()) {
+                if (p.getCardinality() != Cardinality.SINGLE) {
+                    // edge property only supports single cardinality
+                    return false;
+                }
                 if (propertyNameSet.contains(p.getName())) {
                     return false;
                 }
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 e0ed2bfa..2e5661ee 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
@@ -128,18 +128,27 @@ public class GraphInfo {
     }
 
     public void dump(URI storeUri, Writer output) {
+        if (!isValidated()) {
+            throw new IllegalStateException("GraphInfo is not valid and cannot 
be dumped.");
+        }
         Yaml yaml = new Yaml(GraphYaml.getRepresenter(), 
GraphYaml.getDumperOptions());
         GraphYaml graphYaml = new GraphYaml(storeUri, this);
         yaml.dump(graphYaml, output);
     }
 
     public String dump(URI storeUri) {
+        if (!isValidated()) {
+            throw new IllegalStateException("GraphInfo is not valid and cannot 
be dumped.");
+        }
         Yaml yaml = new Yaml(GraphYaml.getRepresenter(), 
GraphYaml.getDumperOptions());
         GraphYaml graphYaml = new GraphYaml(storeUri, this);
         return yaml.dump(graphYaml);
     }
 
     public String dump() {
+        if (!isValidated()) {
+            throw new IllegalStateException("GraphInfo is not valid and cannot 
be dumped.");
+        }
         Yaml yaml = new Yaml(GraphYaml.getRepresenter(), 
GraphYaml.getDumperOptions());
         GraphYaml graphYaml = new GraphYaml(this);
         return yaml.dump(graphYaml);
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 1221ebc1..2d63d29f 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,27 +19,44 @@
 
 package org.apache.graphar.info;
 
+import org.apache.graphar.info.type.Cardinality;
 import org.apache.graphar.info.type.DataType;
 import org.apache.graphar.info.yaml.PropertyYaml;
 
 public class Property {
     private final String name;
     private final DataType dataType;
+    private final Cardinality cardinality;
     private final boolean primary;
     private final boolean nullable;
 
     public Property(String name, DataType dataType, boolean primary, boolean 
nullable) {
+        this(name, dataType, Cardinality.SINGLE, primary, nullable);
+    }
+
+    public Property(
+            String name,
+            DataType dataType,
+            Cardinality cardinality,
+            boolean primary,
+            boolean nullable) {
         this.name = name;
         this.dataType = dataType;
+        this.cardinality = cardinality;
         this.primary = primary;
         this.nullable = nullable;
     }
 
-    Property(PropertyYaml yamlParser) {
+    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();
+        Cardinality cardinality = Cardinality.SINGLE;
+        if (yamlParser.getCardinality() != null && 
!yamlParser.getCardinality().isEmpty()) {
+            cardinality = Cardinality.fromString(yamlParser.getCardinality());
+        }
+        this.cardinality = cardinality;
     }
 
     public String getName() {
@@ -57,4 +74,8 @@ public class Property {
     public boolean isNullable() {
         return nullable;
     }
+
+    public Cardinality getCardinality() {
+        return cardinality;
+    }
 }
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 ae1fdc4e..2aceacea 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
@@ -28,6 +28,7 @@ import java.util.Optional;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import org.apache.graphar.info.type.Cardinality;
 import org.apache.graphar.info.type.DataType;
 import org.apache.graphar.info.type.FileType;
 import org.apache.graphar.util.GeneralParams;
@@ -137,6 +138,9 @@ public class PropertyGroup implements Iterable<Property> {
             if (property.getDataType() == DataType.LIST && fileType == 
FileType.CSV) {
                 return false;
             }
+            if (property.getCardinality() != Cardinality.SINGLE && fileType == 
FileType.CSV) {
+                return false;
+            }
         }
 
         return true;
@@ -216,6 +220,11 @@ class PropertyGroups {
         return propertyGroupList.size();
     }
 
+    public Cardinality getCardinality(String propertyName) {
+        checkPropertyExist(propertyName);
+        return properties.get(propertyName).getCardinality();
+    }
+
     DataType getPropertyType(String propertyName) {
         checkPropertyExist(propertyName);
         return properties.get(propertyName).getDataType();
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 3a56ffb3..5821216f 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
@@ -26,6 +26,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import org.apache.graphar.info.type.Cardinality;
 import org.apache.graphar.info.type.DataType;
 import org.apache.graphar.info.yaml.GraphYaml;
 import org.apache.graphar.info.yaml.VertexYaml;
@@ -186,6 +187,10 @@ public class VertexInfo {
         return propertyGroups.getPropertyGroupNum();
     }
 
+    public Cardinality getCardinality(String propertyName) {
+        return propertyGroups.getCardinality(propertyName);
+    }
+
     public DataType getPropertyType(String propertyName) {
         return propertyGroups.getPropertyType(propertyName);
     }
@@ -225,12 +230,18 @@ public class VertexInfo {
     }
 
     public void dump(Writer output) {
+        if (!isValidated()) {
+            throw new IllegalStateException("VertexInfo is not valid and 
cannot be dumped.");
+        }
         Yaml yaml = new Yaml(GraphYaml.getRepresenter(), 
GraphYaml.getDumperOptions());
         VertexYaml vertexYaml = new VertexYaml(this);
         yaml.dump(vertexYaml, output);
     }
 
     public String dump() {
+        if (!isValidated()) {
+            throw new IllegalStateException("VertexInfo is not valid and 
cannot be dumped.");
+        }
         Yaml yaml = new Yaml(GraphYaml.getRepresenter(), 
GraphYaml.getDumperOptions());
         VertexYaml vertexYaml = new VertexYaml(this);
         return yaml.dump(vertexYaml);
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/saver/BaseGraphInfoSaver.java
 
b/maven-projects/info/src/main/java/org/apache/graphar/info/saver/BaseGraphInfoSaver.java
index a59bdcc7..680b877b 100644
--- 
a/maven-projects/info/src/main/java/org/apache/graphar/info/saver/BaseGraphInfoSaver.java
+++ 
b/maven-projects/info/src/main/java/org/apache/graphar/info/saver/BaseGraphInfoSaver.java
@@ -32,6 +32,10 @@ public abstract class BaseGraphInfoSaver implements 
GraphInfoSaver {
 
     @Override
     public void save(URI graphInfoUri, GraphInfo graphInfo) throws IOException 
{
+        // if graphInfoUri is a directory then save to 
${graphInfo.name}.graph.yml
+        if (graphInfoUri.getPath().endsWith("/")) {
+            graphInfoUri = URI.create(graphInfoUri.toString() + 
graphInfo.getName() + ".graph.yml");
+        }
         Writer writer = writeYaml(graphInfoUri);
         graphInfo.dump(graphInfoUri, writer);
         writer.close();
@@ -48,6 +52,11 @@ public abstract class BaseGraphInfoSaver implements 
GraphInfoSaver {
 
     @Override
     public void save(URI vertexInfoUri, VertexInfo vertexInfo) throws 
IOException {
+        // if vertexInfoUri is a directory then save to 
${vertexInfo.type}.vertex.yml
+        if (vertexInfoUri.getPath().endsWith("/")) {
+            vertexInfoUri =
+                    URI.create(vertexInfoUri.toString() + vertexInfo.getType() 
+ ".vertex.yml");
+        }
         Writer vertexWriter = writeYaml(vertexInfoUri);
         vertexInfo.dump(vertexWriter);
         vertexWriter.close();
@@ -55,6 +64,10 @@ public abstract class BaseGraphInfoSaver implements 
GraphInfoSaver {
 
     @Override
     public void save(URI edgeInfoUri, EdgeInfo edgeInfo) throws IOException {
+        // if edgeInfoUri is a directory then save to 
${edgeInfo.getConcat()}.edge.yml
+        if (edgeInfoUri.getPath().endsWith("/")) {
+            edgeInfoUri = URI.create(edgeInfoUri.toString() + 
edgeInfo.getConcat() + ".edge.yml");
+        }
         Writer edgeWriter = writeYaml(edgeInfoUri);
         edgeInfo.dump(edgeWriter);
         edgeWriter.close();
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/type/Cardinality.java
 
b/maven-projects/info/src/main/java/org/apache/graphar/info/type/Cardinality.java
new file mode 100644
index 00000000..b50a170a
--- /dev/null
+++ 
b/maven-projects/info/src/main/java/org/apache/graphar/info/type/Cardinality.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+/** Defines how multiple values are handled for a given property key. */
+public enum Cardinality {
+    /** Single value property */
+    SINGLE,
+    /** List of values property */
+    LIST,
+    /** Set of values property (no duplicates) */
+    SET;
+
+    @Override
+    public String toString() {
+        switch (this) {
+            case SINGLE:
+                return "single";
+            case LIST:
+                return "list";
+            case SET:
+                return "set";
+            default:
+                throw new IllegalArgumentException("Unknown cardinality: " + 
this);
+        }
+    }
+
+    public static Cardinality fromString(String cardinality) {
+        if (cardinality == null) {
+            return null;
+        }
+        switch (cardinality) {
+            case "single":
+                return SINGLE;
+            case "list":
+                return LIST;
+            case "set":
+                return SET;
+            default:
+                throw new IllegalArgumentException("Unknown cardinality: " + 
cardinality);
+        }
+    }
+}
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/yaml/PropertyYaml.java
 
b/maven-projects/info/src/main/java/org/apache/graphar/info/yaml/PropertyYaml.java
index ee98a339..aedd666f 100644
--- 
a/maven-projects/info/src/main/java/org/apache/graphar/info/yaml/PropertyYaml.java
+++ 
b/maven-projects/info/src/main/java/org/apache/graphar/info/yaml/PropertyYaml.java
@@ -21,19 +21,21 @@ package org.apache.graphar.info.yaml;
 
 import java.util.Optional;
 import org.apache.graphar.info.Property;
-import org.apache.graphar.info.type.DataType;
+import org.apache.graphar.info.type.Cardinality;
 
 public class PropertyYaml {
     private String name;
     private String data_type;
     private boolean is_primary;
     private Optional<Boolean> is_nullable;
+    private String cardinality;
 
     public PropertyYaml() {
         this.name = "";
         this.data_type = "";
         this.is_primary = false;
         this.is_nullable = Optional.empty();
+        this.cardinality = null;
     }
 
     public PropertyYaml(Property property) {
@@ -41,14 +43,14 @@ public class PropertyYaml {
         this.data_type = property.getDataType().toString();
         this.is_primary = property.isPrimary();
         this.is_nullable = Optional.of(property.isNullable());
+        this.cardinality =
+                property.getCardinality() == Cardinality.SINGLE
+                        ? null
+                        : property.getCardinality().toString();
     }
 
     Property toProperty() {
-        return new Property(
-                name,
-                DataType.fromString(data_type),
-                is_primary,
-                is_nullable.orElseGet(() -> !is_primary));
+        return new Property(this);
     }
 
     public String getName() {
@@ -82,4 +84,15 @@ public class PropertyYaml {
     public void setIs_nullable(boolean is_nullable) {
         this.is_nullable = Optional.of(is_nullable);
     }
+
+    public String getCardinality() {
+        if (cardinality == null) {
+            return null;
+        }
+        return Cardinality.SINGLE.toString().equals(cardinality) ? null : 
cardinality;
+    }
+
+    public void setCardinality(String cardinality) {
+        this.cardinality = cardinality;
+    }
 }
diff --git 
a/maven-projects/info/src/main/java/org/apache/graphar/info/yaml/VertexYaml.java
 
b/maven-projects/info/src/main/java/org/apache/graphar/info/yaml/VertexYaml.java
index 345e0369..42d5a3fc 100644
--- 
a/maven-projects/info/src/main/java/org/apache/graphar/info/yaml/VertexYaml.java
+++ 
b/maven-projects/info/src/main/java/org/apache/graphar/info/yaml/VertexYaml.java
@@ -30,6 +30,7 @@ public class VertexYaml {
     private String type;
     private long chunk_size;
     private List<PropertyGroupYaml> property_groups;
+    private List<String> labels;
     private String prefix;
     private String version;
 
@@ -37,6 +38,7 @@ public class VertexYaml {
         this.type = "";
         this.chunk_size = 0;
         this.property_groups = new ArrayList<>();
+        this.labels = null;
         this.prefix = "";
         this.version = "";
     }
@@ -80,6 +82,17 @@ public class VertexYaml {
         this.property_groups = property_groups;
     }
 
+    public List<String> getLabels() {
+        if (labels == null) {
+            return null;
+        }
+        return labels.isEmpty() ? null : labels;
+    }
+
+    public void setLabels(List<String> labels) {
+        this.labels = labels;
+    }
+
     public String getPrefix() {
         return prefix;
     }
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 b4ade718..77efdc6b 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
@@ -27,6 +27,7 @@ import java.util.List;
 import org.apache.graphar.info.loader.GraphInfoLoader;
 import 
org.apache.graphar.info.loader.impl.LocalFileSystemStreamGraphInfoLoader;
 import org.apache.graphar.info.type.AdjListType;
+import org.apache.graphar.info.type.Cardinality;
 import org.apache.graphar.info.type.DataType;
 import org.apache.graphar.info.type.FileType;
 import org.junit.AfterClass;
@@ -169,6 +170,8 @@ public class GraphInfoTest {
         Assert.assertEquals("id", idProperty.getName());
         Assert.assertEquals(DataType.INT64, idProperty.getDataType());
         Assert.assertEquals(DataType.INT64, 
personVertexInfo.getPropertyType("id"));
+        Assert.assertEquals(Cardinality.SINGLE, idProperty.getCardinality());
+        Assert.assertEquals(Cardinality.SINGLE, 
personVertexInfo.getCardinality("id"));
         Assert.assertTrue(idProperty.isPrimary());
         Assert.assertTrue(personVertexInfo.isPrimaryKey("id"));
         Assert.assertFalse(idProperty.isNullable());
@@ -196,18 +199,21 @@ public class GraphInfoTest {
         Assert.assertTrue(personVertexInfo.hasProperty("firstName"));
         Assert.assertEquals("firstName", firstNameProperty.getName());
         Assert.assertEquals(DataType.STRING, firstNameProperty.getDataType());
+        Assert.assertEquals(Cardinality.SINGLE, 
firstNameProperty.getCardinality());
         Assert.assertFalse(firstNameProperty.isPrimary());
         Assert.assertTrue(firstNameProperty.isNullable());
         Property lastNameProperty = 
firstName_lastName_gender.getPropertyList().get(1);
         Assert.assertTrue(personVertexInfo.hasProperty("lastName"));
         Assert.assertEquals("lastName", lastNameProperty.getName());
         Assert.assertEquals(DataType.STRING, lastNameProperty.getDataType());
+        Assert.assertEquals(Cardinality.SINGLE, 
lastNameProperty.getCardinality());
         Assert.assertFalse(lastNameProperty.isPrimary());
         Assert.assertTrue(lastNameProperty.isNullable());
         Property genderProperty = 
firstName_lastName_gender.getPropertyList().get(2);
         Assert.assertTrue(personVertexInfo.hasProperty("gender"));
         Assert.assertEquals("gender", genderProperty.getName());
         Assert.assertEquals(DataType.STRING, genderProperty.getDataType());
+        Assert.assertEquals(Cardinality.SINGLE, 
genderProperty.getCardinality());
         Assert.assertFalse(genderProperty.isPrimary());
         Assert.assertTrue(genderProperty.isNullable());
     }
@@ -351,6 +357,7 @@ public class GraphInfoTest {
         Assert.assertEquals("creationDate", property.getName());
         Assert.assertEquals(DataType.STRING, property.getDataType());
         Assert.assertEquals(DataType.STRING, 
knowsEdgeInfo.getPropertyType("creationDate"));
+        Assert.assertEquals(Cardinality.SINGLE, property.getCardinality());
         Assert.assertFalse(property.isPrimary());
         Assert.assertFalse(knowsEdgeInfo.isPrimaryKey("creationDate"));
         Assert.assertTrue(property.isNullable());
diff --git 
a/maven-projects/info/src/test/java/org/apache/graphar/info/MultiPropertyTest.java
 
b/maven-projects/info/src/test/java/org/apache/graphar/info/MultiPropertyTest.java
new file mode 100644
index 00000000..59a40e37
--- /dev/null
+++ 
b/maven-projects/info/src/test/java/org/apache/graphar/info/MultiPropertyTest.java
@@ -0,0 +1,353 @@
+/*
+ * 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.io.IOException;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.graphar.info.loader.GraphInfoLoader;
+import 
org.apache.graphar.info.loader.impl.LocalFileSystemStringGraphInfoLoader;
+import org.apache.graphar.info.saver.GraphInfoSaver;
+import org.apache.graphar.info.saver.impl.LocalFileSystemYamlGraphSaver;
+import org.apache.graphar.info.type.AdjListType;
+import org.apache.graphar.info.type.Cardinality;
+import org.apache.graphar.info.type.DataType;
+import org.apache.graphar.info.type.FileType;
+import org.apache.graphar.info.yaml.PropertyYaml;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MultiPropertyTest extends BaseFileSystemTest {
+
+    private String testSaveDirectory;
+    private GraphInfoLoader graphInfoLoader;
+    private GraphInfoSaver graphInfoSaver;
+    private Property singleProperty;
+    private Property listProperty;
+    private Property setProperty;
+
+    @Before
+    public void setUp() {
+        testSaveDirectory = 
createCleanTestDirectory("ldbc_multi_property_sample/");
+        graphInfoLoader = new LocalFileSystemStringGraphInfoLoader();
+        graphInfoSaver = new LocalFileSystemYamlGraphSaver();
+        singleProperty =
+                TestDataFactory.createProperty("single_email", 
DataType.STRING, false, true);
+        listProperty =
+                TestDataFactory.createProperty(
+                        "list_email", DataType.STRING, Cardinality.LIST, 
false, true);
+        setProperty =
+                TestDataFactory.createProperty(
+                        "set_email", DataType.STRING, Cardinality.SET, false, 
true);
+    }
+
+    @After
+    public void tearDown() {
+        // Test data will be preserved for debugging - cleanup happens before 
next test run
+        System.out.println("Test data saved in: " + testSaveDirectory);
+    }
+
+    @Test
+    public void testPropertyWithCardinality() {
+        // Test single cardinality property
+        Assert.assertEquals("single_email", singleProperty.getName());
+        Assert.assertEquals(DataType.STRING, singleProperty.getDataType());
+        Assert.assertFalse(singleProperty.isPrimary());
+        Assert.assertTrue(singleProperty.isNullable());
+        Assert.assertEquals(Cardinality.SINGLE, 
singleProperty.getCardinality());
+
+        // Test list cardinality property
+        Assert.assertEquals("list_email", listProperty.getName());
+        Assert.assertEquals(DataType.STRING, listProperty.getDataType());
+        Assert.assertFalse(listProperty.isPrimary());
+        Assert.assertTrue(listProperty.isNullable());
+        Assert.assertEquals(Cardinality.LIST, listProperty.getCardinality());
+
+        // Test set cardinality property
+        Assert.assertEquals("set_email", setProperty.getName());
+        Assert.assertEquals(DataType.STRING, setProperty.getDataType());
+        Assert.assertFalse(setProperty.isPrimary());
+        Assert.assertTrue(setProperty.isNullable());
+        Assert.assertEquals(Cardinality.SET, setProperty.getCardinality());
+    }
+
+    @Test
+    public void testPropertyGroupWithMultiProperties() {
+        // Create property group with different cardinality properties
+        PropertyGroup propertyGroup =
+                TestDataFactory.createPropertyGroup(
+                        Arrays.asList(singleProperty, listProperty, 
setProperty),
+                        FileType.PARQUET,
+                        "emails/");
+
+        Assert.assertEquals(3, propertyGroup.size());
+        Assert.assertEquals(FileType.PARQUET, propertyGroup.getFileType());
+        Assert.assertEquals("emails/", propertyGroup.getPrefix());
+
+        // Test iteration over properties
+        int count = 0;
+        for (Property property : propertyGroup) {
+            count++;
+            if ("single_email".equals(property.getName())) {
+                Assert.assertEquals(Cardinality.SINGLE, 
property.getCardinality());
+            } else if ("list_email".equals(property.getName())) {
+                Assert.assertEquals(Cardinality.LIST, 
property.getCardinality());
+            } else if ("set_email".equals(property.getName())) {
+                Assert.assertEquals(Cardinality.SET, 
property.getCardinality());
+            }
+        }
+        Assert.assertEquals(3, count);
+    }
+
+    @Test
+    public void testPropertyYamlWithCardinality() {
+        // Test converting Property to PropertyYaml and back
+        PropertyYaml singleYaml = new PropertyYaml(singleProperty);
+        PropertyYaml listYaml = new PropertyYaml(listProperty);
+        PropertyYaml setYaml = new PropertyYaml(setProperty);
+
+        // Check YAML representations
+        Assert.assertEquals("single_email", singleYaml.getName());
+        Assert.assertNull(singleYaml.getCardinality());
+
+        Assert.assertEquals("list_email", listYaml.getName());
+        Assert.assertEquals("list", listYaml.getCardinality());
+
+        Assert.assertEquals("set_email", setYaml.getName());
+        Assert.assertEquals("set", setYaml.getCardinality());
+
+        // Convert back to Property objects
+        Property singlePropFromYaml = new Property(singleYaml);
+        Property listPropFromYaml = new Property(listYaml);
+        Property setPropFromYaml = new Property(setYaml);
+
+        // Verify properties are correctly restored
+        Assert.assertEquals(singleProperty.getName(), 
singlePropFromYaml.getName());
+        Assert.assertEquals(singleProperty.getDataType(), 
singlePropFromYaml.getDataType());
+        Assert.assertEquals(singleProperty.getCardinality(), 
singlePropFromYaml.getCardinality());
+
+        Assert.assertEquals(listProperty.getName(), 
listPropFromYaml.getName());
+        Assert.assertEquals(listProperty.getDataType(), 
listPropFromYaml.getDataType());
+        Assert.assertEquals(listProperty.getCardinality(), 
listPropFromYaml.getCardinality());
+
+        Assert.assertEquals(setProperty.getName(), setPropFromYaml.getName());
+        Assert.assertEquals(setProperty.getDataType(), 
setPropFromYaml.getDataType());
+        Assert.assertEquals(setProperty.getCardinality(), 
setPropFromYaml.getCardinality());
+    }
+
+    @Test
+    public void testDefaultCardinality() {
+        // Test that properties created without specifying cardinality default 
to SINGLE
+        Property defaultProperty =
+                TestDataFactory.createProperty("default_prop", 
DataType.STRING, false, true);
+        Assert.assertEquals(Cardinality.SINGLE, 
defaultProperty.getCardinality());
+
+        // Test YAML conversion with default cardinality
+        PropertyYaml defaultYaml = new PropertyYaml(defaultProperty);
+        Assert.assertNull(defaultYaml.getCardinality());
+
+        Property defaultPropFromYaml = new Property(defaultYaml);
+        Assert.assertEquals(Cardinality.SINGLE, 
defaultPropFromYaml.getCardinality());
+    }
+
+    @Test
+    public void testInvalidCardinalityHandling() {
+        // Test handling of invalid cardinality in YAML
+        PropertyYaml invalidYaml = new PropertyYaml();
+        invalidYaml.setName("invalid_prop");
+        invalidYaml.setData_type("string");
+        invalidYaml.setCardinality("INVALID"); // This should throw an 
IllegalArgumentException
+        IllegalArgumentException illegalArgumentException =
+                Assert.assertThrows(
+                        IllegalArgumentException.class, () -> new 
Property(invalidYaml));
+        Assert.assertEquals("Unknown cardinality: INVALID", 
illegalArgumentException.getMessage());
+    }
+
+    @Test
+    public void testEdgeInfoWithMultiProperty() throws IOException {
+        Property weights =
+                TestDataFactory.createProperty(
+                        "weights", DataType.DOUBLE, Cardinality.LIST, false, 
true);
+        PropertyGroup weightsPg =
+                TestDataFactory.createPropertyGroup(
+                        Arrays.asList(weights), FileType.CSV, "weights/");
+        AdjacentList orderedBySource =
+                TestDataFactory.createAdjacentList(
+                        AdjListType.ordered_by_source, FileType.PARQUET, 
"ordered_by_source/");
+        EdgeInfo knowsEdgeInfo =
+                EdgeInfo.builder()
+                        .srcType("person")
+                        .edgeType("knows")
+                        .dstType("person")
+                        .chunkSize(1024)
+                        .srcChunkSize(100)
+                        .dstChunkSize(100)
+                        .directed(false)
+                        .prefix("edge/person_knows_person/")
+                        .version("gar/v1")
+                        .addAdjacentList(orderedBySource)
+                        .addPropertyGroups(List.of(weightsPg))
+                        .build();
+        Assert.assertFalse(knowsEdgeInfo.isValidated());
+    }
+
+    @Test
+    public void testListPropertyInCsv() {
+        Property emails =
+                TestDataFactory.createProperty(
+                        "emails", DataType.STRING, Cardinality.LIST, false, 
true);
+        PropertyGroup emailInCsv =
+                TestDataFactory.createPropertyGroup(List.of(emails), 
FileType.CSV, "emails_csv/");
+        VertexInfo personVertexInfo =
+                new VertexInfo("person", 1000, List.of(emailInCsv), 
"vertex/person/", "gar/v1");
+        Assert.assertFalse(personVertexInfo.isValidated());
+    }
+
+    @Test
+    public void testSetPropertyInCsv() {
+        Property emails =
+                TestDataFactory.createProperty(
+                        "emails", DataType.STRING, Cardinality.SET, false, 
true);
+        PropertyGroup emailInCsv =
+                TestDataFactory.createPropertyGroup(List.of(emails), 
FileType.CSV, "emails_csv/");
+        VertexInfo personVertexInfo =
+                new VertexInfo("person", 1000, List.of(emailInCsv), 
"vertex/person/", "gar/v1");
+        Assert.assertFalse(personVertexInfo.isValidated());
+    }
+
+    @Test
+    public void testGraphInfoWithMultiPropertyYamlLoadAndSave() throws 
IOException {
+        // Create a GraphInfo with multi-property support
+        Property id = TestDataFactory.createProperty("id", DataType.INT64, 
true, false);
+        Property name = TestDataFactory.createProperty("name", 
DataType.STRING, false, true);
+        Property emails =
+                TestDataFactory.createProperty(
+                        "emails", DataType.STRING, Cardinality.LIST, false, 
true);
+        Property tags =
+                TestDataFactory.createProperty(
+                        "tags", DataType.STRING, Cardinality.SET, false, true);
+        Property creationDate =
+                TestDataFactory.createProperty("creationDate", 
DataType.STRING, false, false);
+        PropertyGroup personIdGroup =
+                TestDataFactory.createPropertyGroup(List.of(id), 
FileType.PARQUET, "id/");
+        PropertyGroup personInfoGroup =
+                TestDataFactory.createPropertyGroup(
+                        Arrays.asList(name, emails, tags), FileType.PARQUET, 
"info/");
+
+        PropertyGroup edgePropertyGroup =
+                TestDataFactory.createPropertyGroup(
+                        List.of(creationDate), FileType.PARQUET, 
"properties/");
+
+        // Create adjacent lists
+        AdjacentList orderedBySource =
+                TestDataFactory.createAdjacentList(
+                        AdjListType.ordered_by_source, FileType.PARQUET, 
"ordered_by_source/");
+        AdjacentList unorderedByDest =
+                TestDataFactory.createAdjacentList(
+                        AdjListType.unordered_by_dest, FileType.PARQUET, 
"unordered_by_dest/");
+
+        VertexInfo personVertexInfo =
+                new VertexInfo(
+                        "person",
+                        1000,
+                        Arrays.asList(personIdGroup, personInfoGroup),
+                        "vertex/person/",
+                        "gar/v1");
+
+        EdgeInfo knowsEdgeInfo =
+                EdgeInfo.builder()
+                        .srcType("person")
+                        .edgeType("knows")
+                        .dstType("person")
+                        .chunkSize(1024)
+                        .srcChunkSize(100)
+                        .dstChunkSize(100)
+                        .directed(false)
+                        .prefix("edge/person_knows_person/")
+                        .version("gar/v1")
+                        .addAdjacentList(orderedBySource)
+                        .addAdjacentList(unorderedByDest)
+                        .addPropertyGroups(Arrays.asList(edgePropertyGroup))
+                        .build();
+
+        GraphInfo graphInfo =
+                new GraphInfo(
+                        "test_graph",
+                        Arrays.asList(personVertexInfo),
+                        Arrays.asList(knowsEdgeInfo),
+                        "/tmp/",
+                        "gar/v1");
+
+        // Check that the generated YAML files contain cardinality information
+        String graphYamlFilePath = testSaveDirectory + "/test_graph.graph.yml";
+
+        // Save GraphInfo to YAML files
+        graphInfoSaver.save(URI.create(testSaveDirectory), graphInfo);
+
+        // Load GraphInfo from YAML files
+        GraphInfo loadedGraphInfo = 
graphInfoLoader.loadGraphInfo(URI.create(graphYamlFilePath));
+        Assert.assertTrue(TestVerificationUtils.equalsGraphInfo(graphInfo, 
loadedGraphInfo));
+    }
+
+    @Test
+    public void testLoadFromTestData() throws IOException {
+        URI GRAPH_PATH_URI = TestUtil.getLdbcGraphURI();
+        GraphInfo graphInfo = graphInfoLoader.loadGraphInfo(GRAPH_PATH_URI);
+        VertexInfo personInfo = graphInfo.getVertexInfo("person");
+        Assert.assertEquals(Cardinality.SINGLE, 
personInfo.getCardinality("firstName"));
+        Assert.assertEquals(Cardinality.SINGLE, 
personInfo.getCardinality("lastName"));
+        Assert.assertEquals(Cardinality.SINGLE, 
personInfo.getCardinality("gender"));
+        Assert.assertEquals(Cardinality.SINGLE, 
personInfo.getCardinality("locationIP"));
+        Assert.assertEquals(Cardinality.SINGLE, 
personInfo.getCardinality("browserUsed"));
+        Assert.assertEquals(Cardinality.SINGLE, 
personInfo.getCardinality("id"));
+        Assert.assertEquals(Cardinality.LIST, 
personInfo.getCardinality("emails"));
+    }
+
+    @Test
+    public void testDumpCardinalities() throws IOException {
+        Property id = new Property("id", DataType.INT64, true, false);
+        PropertyGroup properties = new PropertyGroup(List.of(id), 
FileType.PARQUET, "id/");
+        VertexInfo vertexInfo =
+                new VertexInfo("person", 100, List.of(properties), 
"vertex/person/", "gar/v1");
+        String dump = vertexInfo.dump();
+        Assert.assertFalse(dump.contains("cardinality:"));
+
+        id = new Property("id", DataType.INT64, Cardinality.SINGLE, true, 
false);
+        properties = new PropertyGroup(List.of(id), FileType.PARQUET, "id/");
+        vertexInfo = new VertexInfo("person", 100, List.of(properties), 
"vertex/person/", "gar/v1");
+        dump = vertexInfo.dump();
+        Assert.assertFalse(dump.contains("cardinality:"));
+
+        id = new Property("id", DataType.INT64, Cardinality.LIST, true, false);
+        properties = new PropertyGroup(List.of(id), FileType.PARQUET, "id/");
+        vertexInfo = new VertexInfo("person", 100, List.of(properties), 
"vertex/person/", "gar/v1");
+        dump = vertexInfo.dump();
+        Assert.assertTrue(dump.contains("cardinality: list"));
+
+        id = new Property("id", DataType.INT64, Cardinality.SET, true, false);
+        properties = new PropertyGroup(List.of(id), FileType.PARQUET, "id/");
+        vertexInfo = new VertexInfo("person", 100, List.of(properties), 
"vertex/person/", "gar/v1");
+        dump = vertexInfo.dump();
+        Assert.assertTrue(dump.contains("cardinality: set"));
+    }
+}
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 b2943488..66b14e13 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
@@ -21,6 +21,7 @@ package org.apache.graphar.info;
 
 import java.util.List;
 import org.apache.graphar.info.type.AdjListType;
+import org.apache.graphar.info.type.Cardinality;
 import org.apache.graphar.info.type.DataType;
 import org.apache.graphar.info.type.FileType;
 
@@ -100,6 +101,16 @@ public class TestDataFactory {
         return new Property(name, dataType, isPrimary, isNullable);
     }
 
+    /** Creates a property with given parameters including cardinality. */
+    public static Property createProperty(
+            String name,
+            DataType dataType,
+            Cardinality cardinality,
+            boolean isPrimary,
+            boolean isNullable) {
+        return new Property(name, dataType, cardinality, isPrimary, 
isNullable);
+    }
+
     /** Creates a property group with given parameters. */
     public static PropertyGroup createPropertyGroup(
             List<Property> properties, FileType fileType, String prefix) {
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 880c6005..0b703343 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
@@ -38,6 +38,7 @@ public class TestUtil {
             System.getProperty("test.output.dir", "target/test-output") + "/";
 
     private static final String LDBC_SAMPLE_GRAPH_PATH = 
"/ldbc_sample/csv/ldbc_sample.graph.yml";
+    private static final String LDBC_GRAPH_PATH = 
"/ldbc/parquet/ldbc.graph.yml";
 
     public static String getTestData() {
         return GAR_TEST_DATA;
@@ -56,6 +57,14 @@ public class TestUtil {
         return URI.create(getLdbcSampleGraphPath());
     }
 
+    public static String getLdbcGraphPath() {
+        return getTestData() + "/" + LDBC_GRAPH_PATH;
+    }
+
+    public static URI getLdbcGraphURI() {
+        return URI.create(getLdbcGraphPath());
+    }
+
     public static VertexInfo buildVertexInfoFromYaml(VertexYaml vertexYaml) {
         return new VertexInfo(
                 vertexYaml.getType(),
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 fe09e6e6..ddf66067 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
@@ -389,6 +389,10 @@ public class TestVerificationUtils {
                 "Property nullable flag mismatch", expected.isNullable(), 
actual.isNullable());
         Assert.assertEquals(
                 "Property data type mismatch", expected.getDataType(), 
actual.getDataType());
+        Assert.assertEquals(
+                "Property cardinality mismatch",
+                expected.getCardinality(),
+                actual.getCardinality());
         return true;
     }
 


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


Reply via email to