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

hanahmily pushed a commit to branch sync-api
in repository 
https://gitbox.apache.org/repos/asf/skywalking-banyandb-java-client.git

commit 647d0211d1379e701bee73d4824dc34730a28ca8
Author: Gao Hongtao <hanahm...@gmail.com>
AuthorDate: Wed Mar 19 08:55:03 2025 +0800

    Bump up API
    
    Signed-off-by: Gao Hongtao <hanahm...@gmail.com>
---
 .mvn/wrapper/MavenWrapperDownloader.java           | 117 ------------------
 .mvn/wrapper/maven-wrapper.properties              |   3 -
 CHANGES.md                                         |   1 +
 README.md                                          | 134 ++++++++++++++-------
 .../banyandb/v1/client/BanyanDBClient.java         |  73 +++++++++++
 .../banyandb/v1/client/PropertyStore.java          |   2 +-
 .../client/metadata/PropertyMetadataRegistry.java  |  92 ++++++++++++++
 src/main/proto/banyandb/v1/banyandb-common.proto   |  30 ++++-
 src/main/proto/banyandb/v1/banyandb-database.proto | 116 +++++++++++++++---
 src/main/proto/banyandb/v1/banyandb-property.proto |  28 ++---
 .../banyandb/v1/client/BanyanDBClientTestCI.java   |   2 +-
 .../v1/client/ITBanyanDBPropertyTests.java         |  45 ++++---
 .../v1/client/ITGroupMetadataRegistryTest.java     |  81 +++++++++++++
 .../v1/client/ITMeasureMetadataRegistryTest.java   |  17 ++-
 .../v1/client/ITPropertyMetadataRegistryTest.java  | 114 ++++++++++++++++++
 15 files changed, 642 insertions(+), 213 deletions(-)

diff --git a/.mvn/wrapper/MavenWrapperDownloader.java 
b/.mvn/wrapper/MavenWrapperDownloader.java
deleted file mode 100644
index 187216f..0000000
--- a/.mvn/wrapper/MavenWrapperDownloader.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2007-present the original author or authors.
- *
- * Licensed 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.
- */
-import java.net.*;
-import java.io.*;
-import java.nio.channels.*;
-import java.util.Properties;
-
-public class MavenWrapperDownloader {
-
-    private static final String WRAPPER_VERSION = "0.5.5";
-    /**
-     * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' 
is provided.
-     */
-    private static final String DEFAULT_DOWNLOAD_URL = 
"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/";
-        + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
-
-    /**
-     * Path to the maven-wrapper.properties file, which might contain a 
downloadUrl property to
-     * use instead of the default one.
-     */
-    private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
-        ".mvn/wrapper/maven-wrapper.properties";
-
-    /**
-     * Path where the maven-wrapper.jar will be saved to.
-     */
-    private static final String MAVEN_WRAPPER_JAR_PATH =
-        ".mvn/wrapper/maven-wrapper.jar";
-
-    /**
-     * Name of the property which should be used to override the default 
download url for the wrapper.
-     */
-    private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
-
-    public static void main(String args[]) {
-        System.out.println("- Downloader started");
-        File baseDirectory = new File(args[0]);
-        System.out.println("- Using base directory: " + 
baseDirectory.getAbsolutePath());
-
-        // If the maven-wrapper.properties exists, read it and check if it 
contains a custom
-        // wrapperUrl parameter.
-        File mavenWrapperPropertyFile = new File(baseDirectory, 
MAVEN_WRAPPER_PROPERTIES_PATH);
-        String url = DEFAULT_DOWNLOAD_URL;
-        if(mavenWrapperPropertyFile.exists()) {
-            FileInputStream mavenWrapperPropertyFileInputStream = null;
-            try {
-                mavenWrapperPropertyFileInputStream = new 
FileInputStream(mavenWrapperPropertyFile);
-                Properties mavenWrapperProperties = new Properties();
-                
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
-                url = 
mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
-            } catch (IOException e) {
-                System.out.println("- ERROR loading '" + 
MAVEN_WRAPPER_PROPERTIES_PATH + "'");
-            } finally {
-                try {
-                    if(mavenWrapperPropertyFileInputStream != null) {
-                        mavenWrapperPropertyFileInputStream.close();
-                    }
-                } catch (IOException e) {
-                    // Ignore ...
-                }
-            }
-        }
-        System.out.println("- Downloading from: " + url);
-
-        File outputFile = new File(baseDirectory.getAbsolutePath(), 
MAVEN_WRAPPER_JAR_PATH);
-        if(!outputFile.getParentFile().exists()) {
-            if(!outputFile.getParentFile().mkdirs()) {
-                System.out.println(
-                    "- ERROR creating output directory '" + 
outputFile.getParentFile().getAbsolutePath() + "'");
-            }
-        }
-        System.out.println("- Downloading to: " + 
outputFile.getAbsolutePath());
-        try {
-            downloadFileFromURL(url, outputFile);
-            System.out.println("Done");
-            System.exit(0);
-        } catch (Throwable e) {
-            System.out.println("- Error downloading");
-            e.printStackTrace();
-            System.exit(1);
-        }
-    }
-
-    private static void downloadFileFromURL(String urlString, File 
destination) throws Exception {
-        if (System.getenv("MVNW_USERNAME") != null && 
System.getenv("MVNW_PASSWORD") != null) {
-            String username = System.getenv("MVNW_USERNAME");
-            char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
-            Authenticator.setDefault(new Authenticator() {
-                @Override
-                protected PasswordAuthentication getPasswordAuthentication() {
-                    return new PasswordAuthentication(username, password);
-                }
-            });
-        }
-        URL website = new URL(urlString);
-        ReadableByteChannel rbc;
-        rbc = Channels.newChannel(website.openStream());
-        FileOutputStream fos = new FileOutputStream(destination);
-        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
-        fos.close();
-        rbc.close();
-    }
-
-}
diff --git a/.mvn/wrapper/maven-wrapper.properties 
b/.mvn/wrapper/maven-wrapper.properties
deleted file mode 100644
index 54ab0bc..0000000
--- a/.mvn/wrapper/maven-wrapper.properties
+++ /dev/null
@@ -1,3 +0,0 @@
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.1/apache-maven-3.6.1-bin.zip
-wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar
-
diff --git a/CHANGES.md b/CHANGES.md
index 8543da4..7f652d3 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -11,6 +11,7 @@ Release Notes.
 * Bump up the API to support the new property.
 * Bump up the API to adopt the status field which is changed to the string 
type due to the compatibility issue.
 * Bump up the API to support getting the API version.
+* Bump up the API to support the lifecycle management.
 
 0.7.0
 ------------------
diff --git a/README.md b/README.md
index 402e187..0b07cdf 100644
--- a/README.md
+++ b/README.md
@@ -71,6 +71,62 @@ client.define(g);
 
 Then we may define a stream with customized configurations.
 
+#### Define a how-warm-cold Group
+
+Here illustrates how to use the lifecycle stages feature for hot-warm-cold 
data architecture:
+
+```java
+// build a group sw_record for Stream with hot-warm-cold lifecycle stages
+Group g = 
Group.newBuilder().setMetadata(Metadata.newBuilder().setName("sw_record"))
+            .setCatalog(Catalog.CATALOG_STREAM)
+            .setResourceOpts(ResourceOpts.newBuilder()
+                // Hot configuration
+                .setShardNum(3)
+                // Default segment interval (will be overridden by stages if 
defined)
+                .setSegmentInterval(
+                    IntervalRule.newBuilder()
+                        .setUnit(IntervalRule.Unit.UNIT_DAY)
+                        .setNum(1))
+                // Default TTL (will be overridden by stages if defined)
+                .setTtl(
+                    IntervalRule.newBuilder()
+                        .setUnit(IntervalRule.Unit.UNIT_DAY)
+                        .setNum(3))
+                // Define lifecycle stages (hot → warm → cold)
+                .addStages(LifecycleStage.newBuilder()
+                    .setName("warm")
+                    .setShardNum(2) // Fewer shards
+                    .setSegmentInterval(IntervalRule.newBuilder()
+                        .setUnit(IntervalRule.Unit.UNIT_DAY)
+                        .setNum(1))
+                    .setTtl(IntervalRule.newBuilder()
+                        .setUnit(IntervalRule.Unit.UNIT_DAY)
+                        .setNum(7)) // Keep in warm for 7 days
+                    .setNodeSelector("hdd-nodes") // Store on cheaper HDD nodes
+                    .build())
+                .addStages(LifecycleStage.newBuilder()
+                    .setName("cold")
+                    .setShardNum(1) // Minimal shards for archived data
+                    .setSegmentInterval(IntervalRule.newBuilder()
+                        .setUnit(IntervalRule.Unit.UNIT_DAY)
+                        .setNum(7)) // Larger segments for cold data
+                    .setTtl(IntervalRule.newBuilder()
+                        .setUnit(IntervalRule.Unit.UNIT_DAY)
+                        .setNum(30)) // Keep in cold for 30 more days
+                    .setNodeSelector("archive-nodes") // Store on archive nodes
+                    .setClose(true) // Close segments that are no longer live
+                    .build()))
+            .build();
+client.define(g);
+```
+
+This configuration creates a hot-warm-cold architecture where:
+- Hot stage: Data is stored on fast SSD nodes with many shards for 1 day, 
optimized for high query performance
+- Warm stage: Data moves to HDD nodes with fewer shards for 7 days, balanced 
between performance and cost
+- Cold stage: Data finally moves to archive nodes with minimal shards for 30 
days, optimized for storage efficiency
+
+Data automatically flows through these stages according to the defined TTLs. 
The total retention of data is 38 days (1+7+30).
+
 #### Define a Stream
 ```java
 // build a stream trace with above group
@@ -469,68 +525,64 @@ MeasureWrite measureWrite = 
client.createMeasureWrite("sw_metric", "service_cpm_
 CompletableFuture<Void> f = measureBulkWriteProcessor.add(measureWrite);
 f.get(10, TimeUnit.SECONDS);
 ```
+# Property APIs
 
-## Property APIs
-
-Property APIs are used to store key-value pairs.
-
-### Apply(Create/Update)
-
-`apply` will always succeed whenever the property exists or not.
-The old value will be overwritten if already existed, otherwise a new value 
will be set.
+Before using properties, you need to define a property schema:
 
 ```java
-Property property = Property.create("default", "sw", "ui_template")
-    .addTag(TagAndValue.newStringTag("name", "hello"))
-    .addTag(TagAndValue.newStringTag("state", "successd"))
-    .build();
-this.client.apply(property); //created:true tagsNum:2
+// Define property schema
+BanyandbDatabase.Property propertyDef = 
+   BanyandbDatabase.Property.newBuilder()
+        .setMetadata(Metadata.newBuilder()
+            .setGroup("default")
+            .setName("ui_template"))
+        .setTagType(TagType.TAG_TYPE_STRING)
+        .build();
+
+client.define(propertyDef);
 ```
 
-The operation supports updating partial tags.
+After defining the schema, you can apply (create/update) properties:
 
 ```java
-Property property = Property.create("default", "sw", "ui_template")
-    .addTag(TagAndValue.newStringTag("state", "failed"))
+// Apply a property (create or update)
+Property property = Property.newBuilder()
+    .setMetadata(Metadata.newBuilder()
+        .setGroup("default")
+        .setName("ui_template"))
+    .setId("dashboard-1")
+    .setTagValue(BanyandbModel.TagValue.newBuilder()
+        .setStr("template-data-json"))
     .build();
-this.client.apply(property); //created:false tagsNum:1
-```
-
-### Query
 
-Property can be queried via `Client.findProperty`,
-
-```java
- BanyandbProperty.QueryResponse resp = 
client.query(BanyandbProperty.QueryRequest.newBuilder()
-        .addGroups("default")
-        .setContainer("sw")
-        .addIds("ui_template")
-        .build());
+ApplyResponse response = client.apply(property);
 ```
 
-The query operation could filter tags,
+You can also apply with a specific strategy:
 
 ```java
-BanyandbProperty.QueryResponse resp = 
client.query(BanyandbProperty.QueryRequest.newBuilder()
-        .addGroups("default")
-        .setContainer("sw")
-        .addIds("ui_template")
-        .addTagProjection("state")
-        .build());
+// Apply with merge strategy
+ApplyResponse response = client.apply(property, Strategy.STRATEGY_MERGE);
 ```
 
-### Delete
-
-Property can be deleted by calling `Client.deleteProperty`,
+Query properties:
 
 ```java
-this.client.deleteProperty("default", "sw", "ui_template"); //deleted:true 
tagsNum:2
+// Query properties
+BanyandbProperty.QueryRequest queryRequest = 
BanyandbProperty.QueryRequest.newBuilder()
+    .setMetadata(Metadata.newBuilder()
+        .setGroup("default")
+        .setName("ui_template"))
+    .build();
+
+BanyandbProperty.QueryResponse queryResponse = client.query(queryRequest);
 ```
 
-The delete operation could remove specific tags instead of the whole property.
+Delete a property:
 
 ```java
-this.client.deleteProperty("default", "sw", "ui_template", "state"); 
//deleted:true tagsNum:1
+// Delete a property
+DeleteResponse deleteResponse = client.deleteProperty("default", 
"ui_template", "dashboard-1");
 ```
 
 # Compiling project
diff --git 
a/src/main/java/org/apache/skywalking/banyandb/v1/client/BanyanDBClient.java 
b/src/main/java/org/apache/skywalking/banyandb/v1/client/BanyanDBClient.java
index 912a14f..cdea7bb 100644
--- a/src/main/java/org/apache/skywalking/banyandb/v1/client/BanyanDBClient.java
+++ b/src/main/java/org/apache/skywalking/banyandb/v1/client/BanyanDBClient.java
@@ -62,6 +62,7 @@ import 
org.apache.skywalking.banyandb.v1.client.metadata.IndexRuleBindingMetadat
 import 
org.apache.skywalking.banyandb.v1.client.metadata.IndexRuleMetadataRegistry;
 import 
org.apache.skywalking.banyandb.v1.client.metadata.MeasureMetadataRegistry;
 import org.apache.skywalking.banyandb.v1.client.metadata.MetadataCache;
+import 
org.apache.skywalking.banyandb.v1.client.metadata.PropertyMetadataRegistry;
 import org.apache.skywalking.banyandb.v1.client.metadata.ResourceExist;
 import 
org.apache.skywalking.banyandb.v1.client.metadata.StreamMetadataRegistry;
 import 
org.apache.skywalking.banyandb.v1.client.metadata.TopNAggregationMetadataRegistry;
@@ -812,6 +813,64 @@ public class BanyanDBClient implements Closeable {
         return registry.list(group);
     }
 
+    /**
+     * Define a new property.
+     * 
+     * @param property the property to be stored in the BanyanBD
+     * @throws BanyanDBException if the property is invalid
+     */
+    public void 
define(org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property 
property) throws BanyanDBException {
+        PropertyMetadataRegistry registry = new 
PropertyMetadataRegistry(checkNotNull(this.channel));
+        registry.create(property);
+    }
+
+    /**
+     * Update the property.
+     *
+     * @param property the property to be stored in the BanyanBD
+     * @throws BanyanDBException if the property is invalid
+     */
+    public void 
update(org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property 
property) throws BanyanDBException {
+        PropertyMetadataRegistry registry = new 
PropertyMetadataRegistry(checkNotNull(this.channel));
+        registry.update(property);
+    }
+
+    /**
+     * Find the property with given group and name
+     *
+     * @param group group of the metadata
+     * @param name  name of the metadata
+     * @return the property found in BanyanDB. Otherwise, null is returned.
+     */
+    public 
org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property 
findPropertyDefinition(String group, String name) throws BanyanDBException {
+        PropertyMetadataRegistry registry = new 
PropertyMetadataRegistry(checkNotNull(this.channel));
+        return registry.get(group, name);
+    }
+
+    /**
+     * Find the properties with given group
+     *
+     * @param group group of the metadata
+     * @return the properties found in BanyanDB
+     */
+    public 
List<org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property> 
findPropertiesDefinition(String group) throws BanyanDBException {
+        PropertyMetadataRegistry registry = new 
PropertyMetadataRegistry(checkNotNull(this.channel));
+        return registry.list(group);
+    }
+
+    /**
+     * Delete the property
+     *
+     * @param group group of the metadata
+     * @param name  name of the metadata
+     * @param id    identity of the property
+     * @return if this property has been deleted
+     */
+    public boolean deletePropertyDefinition(String group, String name) throws 
BanyanDBException {
+        PropertyMetadataRegistry registry = new 
PropertyMetadataRegistry(checkNotNull(this.channel));
+        return registry.delete(group, name);
+    }
+
     /**
      * Apply(Create or update) the property with {@link 
BanyandbProperty.ApplyRequest.Strategy#STRATEGY_MERGE}
      *
@@ -1046,6 +1105,20 @@ public class BanyanDBClient implements Closeable {
         return new 
TopNAggregationMetadataRegistry(checkNotNull(this.channel)).exist(group, name);
     }
 
+    /**
+     * Check if the given property exists.
+     *
+     * @param group group of the property
+     * @param name name of the property
+     * @return ResourceExist which indicates whether group and property exist
+     */
+    public ResourceExist existProperty(String group, String name) throws 
BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
+
+        return new 
PropertyMetadataRegistry(checkNotNull(this.channel)).exist(group, name);
+    }
+
     /**
      * Update the stream metadata cache from the server
      * @param group the group of the stream
diff --git 
a/src/main/java/org/apache/skywalking/banyandb/v1/client/PropertyStore.java 
b/src/main/java/org/apache/skywalking/banyandb/v1/client/PropertyStore.java
index 8e2fbe3..638ee8c 100644
--- a/src/main/java/org/apache/skywalking/banyandb/v1/client/PropertyStore.java
+++ b/src/main/java/org/apache/skywalking/banyandb/v1/client/PropertyStore.java
@@ -54,7 +54,7 @@ public class PropertyStore {
         return HandleExceptionsWith.callAndTranslateApiException(() ->
                 this.stub.delete(BanyandbProperty.DeleteRequest.newBuilder()
                         .setGroup(group)
-                        .setContainer(name)
+                        .setName(name)
                         .setId(id)
                         .build()));
     }
diff --git 
a/src/main/java/org/apache/skywalking/banyandb/v1/client/metadata/PropertyMetadataRegistry.java
 
b/src/main/java/org/apache/skywalking/banyandb/v1/client/metadata/PropertyMetadataRegistry.java
new file mode 100644
index 0000000..efbb4bc
--- /dev/null
+++ 
b/src/main/java/org/apache/skywalking/banyandb/v1/client/metadata/PropertyMetadataRegistry.java
@@ -0,0 +1,92 @@
+/*
+ * 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.skywalking.banyandb.v1.client.metadata;
+
+import io.grpc.Channel;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property;
+import org.apache.skywalking.banyandb.database.v1.PropertyRegistryServiceGrpc;
+import org.apache.skywalking.banyandb.v1.client.grpc.MetadataClient;
+import 
org.apache.skywalking.banyandb.v1.client.grpc.exception.BanyanDBException;
+
+import java.util.List;
+
+public class PropertyMetadataRegistry extends 
MetadataClient<PropertyRegistryServiceGrpc.PropertyRegistryServiceBlockingStub,
+        BanyandbDatabase.Property> {
+
+    public PropertyMetadataRegistry(Channel channel) {
+        super(PropertyRegistryServiceGrpc.newBlockingStub(channel));
+    }
+
+    @Override
+    public long create(final Property payload) throws BanyanDBException {
+        BanyandbDatabase.PropertyRegistryServiceCreateResponse resp = 
execute(() ->
+                
stub.create(BanyandbDatabase.PropertyRegistryServiceCreateRequest.newBuilder()
+                        .setProperty(payload)
+                        .build()));
+        return resp.getModRevision();
+    }
+
+    @Override
+    public void update(final Property payload) throws BanyanDBException {
+        execute(() ->
+                
stub.update(BanyandbDatabase.PropertyRegistryServiceUpdateRequest.newBuilder()
+                        .setProperty(payload)
+                        .build()));
+    }
+
+    @Override
+    public boolean delete(final String group, final String name) throws 
BanyanDBException {
+        BanyandbDatabase.PropertyRegistryServiceDeleteResponse resp = 
execute(() ->
+                
stub.delete(BanyandbDatabase.PropertyRegistryServiceDeleteRequest.newBuilder()
+                        
.setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+        return resp != null && resp.getDeleted();
+    }
+
+    @Override
+    public Property get(final String group, final String name) throws 
BanyanDBException {
+        BanyandbDatabase.PropertyRegistryServiceGetResponse resp = execute(() 
->
+                
stub.get(BanyandbDatabase.PropertyRegistryServiceGetRequest.newBuilder()
+                        
.setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+
+        return resp.getProperty();
+    }
+
+    @Override
+    public ResourceExist exist(String group, String name) throws 
BanyanDBException {
+        BanyandbDatabase.PropertyRegistryServiceExistResponse resp = 
execute(() ->
+                
stub.exist(BanyandbDatabase.PropertyRegistryServiceExistRequest.newBuilder()
+                        
.setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+        return ResourceExist.create(resp.getHasGroup(), resp.getHasProperty());
+    }
+
+    @Override
+    public List<Property> list(final String group) throws BanyanDBException {
+        BanyandbDatabase.PropertyRegistryServiceListResponse resp = execute(() 
->
+                
stub.list(BanyandbDatabase.PropertyRegistryServiceListRequest.newBuilder()
+                        .setGroup(group)
+                        .build()));
+
+        return resp.getPropertiesList();
+    }
+}
diff --git a/src/main/proto/banyandb/v1/banyandb-common.proto 
b/src/main/proto/banyandb/v1/banyandb-common.proto
index 75aa950..979bf6c 100644
--- a/src/main/proto/banyandb/v1/banyandb-common.proto
+++ b/src/main/proto/banyandb/v1/banyandb-common.proto
@@ -58,6 +58,31 @@ message IntervalRule {
   uint32 num = 2 [(validate.rules).uint32.gt = 0];
 }
 
+message LifecycleStage {
+  // The stage name (e.g., "warm", "cold").
+  // This should be a non-empty string.
+  string name = 1 [(validate.rules).string.min_len = 1];
+
+  // Number of shards allocated for this stage.
+  // Must be greater than zero.
+  uint32 shard_num = 2 [(validate.rules).uint32.gt = 0];
+
+  // Defines the interval for data segmentation in this stage.
+  // This is a required field and uses the IntervalRule structure.
+  IntervalRule segment_interval = 3 [(validate.rules).message.required = true];
+
+  // Specifies the time-to-live for data in this stage before moving to the 
next.
+  // This is also a required field using the IntervalRule structure.
+  IntervalRule ttl = 4 [(validate.rules).message.required = true];
+
+  // Node selector specifying target nodes for this stage.
+  // Optional; if provided, it must be a non-empty string.
+  string node_selector = 5 [(validate.rules).string.min_len = 1];
+
+  // Indicates whether segments that are no longer live should be closed.
+  bool close = 6;
+}
+
 message ResourceOpts {
   // shard_num is the number of shards
   uint32 shard_num = 1 [(validate.rules).uint32.gt = 0];
@@ -65,6 +90,10 @@ message ResourceOpts {
   IntervalRule segment_interval = 2;
   // ttl indicates time to live, how long the data will be cached
   IntervalRule ttl = 3;
+  // stages defines the ordered lifecycle stages. Data progresses through 
these stages sequentially.
+  repeated LifecycleStage stages = 4;
+  // default_node_selector is the default node selector for queries if 
node_selector is not specified
+  string default_node_selector = 5;
 }
 
 // Group is an internal object for Group management
@@ -79,7 +108,6 @@ message Group {
   google.protobuf.Timestamp updated_at = 4;
 }
 
-
 // Trace is the top level message of a trace.
 message Trace {
   // trace_id is the unique identifier of the trace.
diff --git a/src/main/proto/banyandb/v1/banyandb-database.proto 
b/src/main/proto/banyandb/v1/banyandb-database.proto
index 2426849..01ce14d 100644
--- a/src/main/proto/banyandb/v1/banyandb-database.proto
+++ b/src/main/proto/banyandb/v1/banyandb-database.proto
@@ -94,8 +94,6 @@ message FieldSpec {
   EncodingMethod encoding_method = 3 [(validate.rules).enum.defined_only = 
true];
   // compression_method indicates how to compress data during writing
   CompressionMethod compression_method = 4 [(validate.rules).enum.defined_only 
= true];
-  // aggregate_function indicates how to aggregate data
-  model.v1.MeasureAggregate aggregate_function = 5;
 }
 
 // Measure intends to store data point
@@ -118,13 +116,6 @@ message Measure {
   bool index_mode = 7;
 }
 
-message MeasureAggregateFunction {
-  // type indicates the type of function argument
-  FieldType type = 1 [(validate.rules).enum.defined_only = true];
-  // aggregate_function indicates specific function for measure data
-  model.v1.MeasureAggregate aggregate_function = 2;
-}
-
 // TopNAggregation generates offline TopN statistics for a measure's TopN 
approximation
 message TopNAggregation {
   // metadata is the identity of an aggregation
@@ -211,6 +202,15 @@ message IndexRuleBinding {
   google.protobuf.Timestamp updated_at = 6;
 }
 
+// Property stores the user defined data
+message Property {
+  // metadata is the identity of a property
+  common.v1.Metadata metadata = 1 [(validate.rules).message.required = true];
+  // tag stores the content of a property
+  repeated TagSpec tags = 2 [(validate.rules).repeated.min_items = 1];
+  // updated_at indicates when the property is updated
+  google.protobuf.Timestamp updated_at = 6;
+}
 
 message StreamRegistryServiceCreateRequest {
   banyandb.database.v1.Stream stream = 1;
@@ -263,11 +263,15 @@ message StreamRegistryServiceListResponse {
 
 service StreamRegistryService {
   rpc Create(StreamRegistryServiceCreateRequest) returns 
(StreamRegistryServiceCreateResponse);
+
   rpc Update(StreamRegistryServiceUpdateRequest) returns 
(StreamRegistryServiceUpdateResponse);
+
   rpc Delete(StreamRegistryServiceDeleteRequest) returns 
(StreamRegistryServiceDeleteResponse);
+
   rpc Get(StreamRegistryServiceGetRequest) returns 
(StreamRegistryServiceGetResponse);
+
   rpc List(StreamRegistryServiceListRequest) returns 
(StreamRegistryServiceListResponse);
-  // Exist doesn't expose an HTTP endpoint. Please use HEAD method to touch 
Get instead
+
   rpc Exist(StreamRegistryServiceExistRequest) returns 
(StreamRegistryServiceExistResponse);
 }
 
@@ -275,15 +279,13 @@ message IndexRuleBindingRegistryServiceCreateRequest {
   banyandb.database.v1.IndexRuleBinding index_rule_binding = 1;
 }
 
-message IndexRuleBindingRegistryServiceCreateResponse {
-}
+message IndexRuleBindingRegistryServiceCreateResponse {}
 
 message IndexRuleBindingRegistryServiceUpdateRequest {
   banyandb.database.v1.IndexRuleBinding index_rule_binding = 1;
 }
 
-message IndexRuleBindingRegistryServiceUpdateResponse {
-}
+message IndexRuleBindingRegistryServiceUpdateResponse {}
 
 message IndexRuleBindingRegistryServiceDeleteRequest {
   banyandb.common.v1.Metadata metadata = 1;
@@ -556,3 +558,89 @@ service TopNAggregationRegistryService {
   rpc List(TopNAggregationRegistryServiceListRequest) returns 
(TopNAggregationRegistryServiceListResponse);
   rpc Exist(TopNAggregationRegistryServiceExistRequest) returns 
(TopNAggregationRegistryServiceExistResponse);
 }
+
+
+message SnapshotRequest {
+  message Group {
+    common.v1.Catalog catalog = 1;
+    string group = 2;
+  }
+  repeated Group groups = 1;
+}
+
+message Snapshot {
+  common.v1.Catalog catalog = 1;
+  string name = 2;
+  string error = 3;
+}
+
+message SnapshotResponse {
+  repeated Snapshot snapshots = 1;
+}
+
+service SnapshotService {
+  rpc Snapshot(SnapshotRequest) returns (SnapshotResponse);
+}
+
+message PropertyRegistryServiceCreateRequest {
+  banyandb.database.v1.Property property = 1;
+}
+
+message PropertyRegistryServiceCreateResponse {
+  int64 mod_revision = 1;
+}
+
+message PropertyRegistryServiceUpdateRequest {
+  banyandb.database.v1.Property property = 1;
+}
+
+message PropertyRegistryServiceUpdateResponse {
+  int64 mod_revision = 1;
+}
+
+message PropertyRegistryServiceDeleteRequest {
+  banyandb.common.v1.Metadata metadata = 1;
+}
+
+message PropertyRegistryServiceDeleteResponse {
+  bool deleted = 1;
+}
+
+message PropertyRegistryServiceGetRequest {
+  banyandb.common.v1.Metadata metadata = 1;
+}
+
+message PropertyRegistryServiceGetResponse {
+  banyandb.database.v1.Property property = 1;
+}
+
+message PropertyRegistryServiceListRequest {
+  string group = 1;
+}
+
+message PropertyRegistryServiceListResponse {
+  repeated banyandb.database.v1.Property properties = 1;
+}
+
+message PropertyRegistryServiceExistRequest {
+  banyandb.common.v1.Metadata metadata = 1;
+}
+
+message PropertyRegistryServiceExistResponse {
+  bool has_group = 1;
+  bool has_property = 2;
+}
+
+service PropertyRegistryService {
+  rpc Create(PropertyRegistryServiceCreateRequest) returns 
(PropertyRegistryServiceCreateResponse);
+
+  rpc Update(PropertyRegistryServiceUpdateRequest) returns 
(PropertyRegistryServiceUpdateResponse);
+
+  rpc Delete(PropertyRegistryServiceDeleteRequest) returns 
(PropertyRegistryServiceDeleteResponse);
+
+  rpc Get(PropertyRegistryServiceGetRequest) returns 
(PropertyRegistryServiceGetResponse);
+
+  rpc List(PropertyRegistryServiceListRequest) returns 
(PropertyRegistryServiceListResponse);
+
+  rpc Exist(PropertyRegistryServiceExistRequest) returns 
(PropertyRegistryServiceExistResponse);
+}
diff --git a/src/main/proto/banyandb/v1/banyandb-property.proto 
b/src/main/proto/banyandb/v1/banyandb-property.proto
index 357a82c..2f00370 100644
--- a/src/main/proto/banyandb/v1/banyandb-property.proto
+++ b/src/main/proto/banyandb/v1/banyandb-property.proto
@@ -26,22 +26,16 @@ import "validate/validate.proto";
 import "banyandb/v1/banyandb-common.proto";
 import "banyandb/v1/banyandb-model.proto";
 
-// Metadata is for multi-tenant use
-message Metadata {
-  // container is created when it receives the first property
-  common.v1.Metadata container = 1 [(validate.rules).message.required = true];
-  // id identifies a property
-  string id = 2 [(validate.rules).string.min_len = 1];
-}
-
 // Property stores the user defined data
 message Property {
   // metadata is the identity of a property
-  Metadata metadata = 1 [(validate.rules).message.required = true];
+  common.v1.Metadata metadata = 1 [(validate.rules).message.required = true];
+  // id is the identity of a property
+  string id = 2 [(validate.rules).string.min_len = 1];
   // tag stores the content of a property
-  repeated model.v1.Tag tags = 2 [(validate.rules).repeated.min_items = 1];
+  repeated model.v1.Tag tags = 3 [(validate.rules).repeated.min_items = 1];
   // updated_at indicates when the property is updated
-  google.protobuf.Timestamp updated_at = 3;
+  google.protobuf.Timestamp updated_at = 4;
 }
 
 message ApplyRequest {
@@ -65,9 +59,9 @@ message ApplyResponse {
 message DeleteRequest {
   // groups indicate where the data points are stored.
   string group = 1 [(validate.rules).string.min_len = 1];
-  // container is created when it receives the first property
-  string container = 2 [(validate.rules).string.min_len = 1];
-  // id is the identity of properties
+  // name is the identity of a property.
+  string name = 2 [(validate.rules).string.min_len = 1];
+  // id is the identity of item in the property.
   string id = 3;
 }
 
@@ -79,8 +73,8 @@ message DeleteResponse {
 message QueryRequest {
   // groups indicate where the data points are stored.
   repeated string groups = 1 [(validate.rules).repeated.min_items = 1];
-  // container is created when it receives the first property
-  string container = 2;
+  // name is created when it receives the first property
+  string name = 2;
   // ids is the identities of properties
   repeated string ids = 3;
   // criteria is used to filter properties based on tags
@@ -90,6 +84,8 @@ message QueryRequest {
   uint32 limit = 6;
   // trace is used to enable trace for the query
   bool trace = 7;
+  // node_selector is used to select the node to query
+  string node_selector = 8;
 }
 
 // QueryResponse is the response for a query to the Query module.
diff --git 
a/src/test/java/org/apache/skywalking/banyandb/v1/client/BanyanDBClientTestCI.java
 
b/src/test/java/org/apache/skywalking/banyandb/v1/client/BanyanDBClientTestCI.java
index a73a404..ca826a3 100644
--- 
a/src/test/java/org/apache/skywalking/banyandb/v1/client/BanyanDBClientTestCI.java
+++ 
b/src/test/java/org/apache/skywalking/banyandb/v1/client/BanyanDBClientTestCI.java
@@ -31,7 +31,7 @@ import java.io.IOException;
 public class BanyanDBClientTestCI {
     private static final String REGISTRY = "ghcr.io";
     private static final String IMAGE_NAME = "apache/skywalking-banyandb";
-    private static final String TAG = 
"362d68ed79c532e6d61dd6674cce38090caa0da7";
+    private static final String TAG = 
"53b3be42d162e2f4ef0c667dc30f25e42ff17d70";
 
     private static final String IMAGE = REGISTRY + "/" + IMAGE_NAME + ":" + 
TAG;
 
diff --git 
a/src/test/java/org/apache/skywalking/banyandb/v1/client/ITBanyanDBPropertyTests.java
 
b/src/test/java/org/apache/skywalking/banyandb/v1/client/ITBanyanDBPropertyTests.java
index 828284a..af9b9aa 100644
--- 
a/src/test/java/org/apache/skywalking/banyandb/v1/client/ITBanyanDBPropertyTests.java
+++ 
b/src/test/java/org/apache/skywalking/banyandb/v1/client/ITBanyanDBPropertyTests.java
@@ -33,6 +33,8 @@ import 
org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
 import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Group;
 import org.apache.skywalking.banyandb.property.v1.BanyandbProperty.Property;
 import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Metadata;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagSpec;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagType;
 
 import static org.awaitility.Awaitility.await;
 
@@ -49,6 +51,21 @@ public class ITBanyanDBPropertyTests extends 
BanyanDBClientTestCI {
                     .build();
         client.define(expectedGroup);
         Assert.assertNotNull(expectedGroup);
+        org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property 
expectedProperty =
+            
org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property.newBuilder()
+                    .setMetadata(
+                        BanyandbCommon.Metadata.newBuilder()
+                                .setGroup("default")
+                                .setName("sw")
+                                .build())
+                     .addTags(
+                        TagSpec.newBuilder()
+                                .setName("name")
+                                .setType(
+                                        TagType.TAG_TYPE_STRING))
+                    .build();
+        client.define(expectedProperty);
+        Assert.assertNotNull(expectedProperty);
     }
 
     @After
@@ -66,7 +83,7 @@ public class ITBanyanDBPropertyTests extends 
BanyanDBClientTestCI {
         await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
             BanyandbProperty.QueryResponse resp = 
client.query(BanyandbProperty.QueryRequest.newBuilder()
                             .addGroups("default")
-                            .setContainer("sw")
+                            .setName("sw")
                             .addIds("ui_template")
                     .build());
             Assert.assertEquals(1, resp.getPropertiesCount());
@@ -86,7 +103,7 @@ public class ITBanyanDBPropertyTests extends 
BanyanDBClientTestCI {
         await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
             BanyandbProperty.QueryResponse resp = 
client.query(BanyandbProperty.QueryRequest.newBuilder()
                     .addGroups("default")
-                    .setContainer("sw")
+                    .setName("sw")
                     .addIds("ui_template")
                     .build());
             Assert.assertEquals(1, resp.getPropertiesCount());
@@ -98,7 +115,7 @@ public class ITBanyanDBPropertyTests extends 
BanyanDBClientTestCI {
         Assert.assertTrue(this.client.deleteProperty("default", "sw", 
"ui_template").getDeleted());
         BanyandbProperty.QueryResponse resp = 
client.query(BanyandbProperty.QueryRequest.newBuilder()
                 .addGroups("default")
-                .setContainer("sw")
+                .setName("sw")
                 .addIds("ui_template")
                 .build());
         Assert.assertEquals(0, resp.getPropertiesCount());
@@ -119,7 +136,7 @@ public class ITBanyanDBPropertyTests extends 
BanyanDBClientTestCI {
         await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
             BanyandbProperty.QueryResponse resp = 
client.query(BanyandbProperty.QueryRequest.newBuilder()
                     .addGroups("default")
-                    .setContainer("sw")
+                    .setName("sw")
                     .addIds("ui_template")
                     .build());
             Assert.assertEquals(1, resp.getPropertiesCount());
@@ -143,14 +160,14 @@ public class ITBanyanDBPropertyTests extends 
BanyanDBClientTestCI {
         await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
             BanyandbProperty.QueryResponse resp = 
client.query(BanyandbProperty.QueryRequest.newBuilder()
                     .addGroups("default")
-                    .setContainer("sw")
+                    .setName("sw")
                     .build());
             Assert.assertEquals(2, resp.getPropertiesCount());
         });
         await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
             BanyandbProperty.QueryResponse resp = 
client.query(BanyandbProperty.QueryRequest.newBuilder()
                     .addGroups("default")
-                    .setContainer("sw")
+                    .setName("sw")
                     .addIds("id1")
                     .addIds("id2")
                     .build());
@@ -159,7 +176,7 @@ public class ITBanyanDBPropertyTests extends 
BanyanDBClientTestCI {
         await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
             BanyandbProperty.QueryResponse resp = 
client.query(BanyandbProperty.QueryRequest.newBuilder()
                     .addGroups("default")
-                    .setContainer("sw")
+                    .setName("sw")
                     .addIds("id2")
                     .build());
             Assert.assertEquals(1, resp.getPropertiesCount());
@@ -169,14 +186,12 @@ public class ITBanyanDBPropertyTests extends 
BanyanDBClientTestCI {
     private BanyandbProperty.Property buildProperty(String group, String name, 
String id) {
         BanyandbProperty.Property.Builder builder = 
BanyandbProperty.Property.newBuilder()
                                                                              
.setMetadata(
-                                                                               
  BanyandbProperty.Metadata.newBuilder()
-                                                                               
                           .setContainer(
-                                                                               
                               BanyandbCommon.Metadata.newBuilder()
-                                                                               
                                                      .setGroup(
-                                                                               
                                                          group)
-                                                                               
                                                      .setName(
-                                                                               
                                                          name))
-                                                                               
                           .setId(id));
+                                                                               
         BanyandbCommon.Metadata.newBuilder()
+                                                                               
                                 .setGroup(
+                                                                               
                                     group)
+                                                                               
                                 .setName(
+                                                                               
                                     name).build())
+                                                                               
     .setId(id);
         return builder.build();
     }
 }
diff --git 
a/src/test/java/org/apache/skywalking/banyandb/v1/client/ITGroupMetadataRegistryTest.java
 
b/src/test/java/org/apache/skywalking/banyandb/v1/client/ITGroupMetadataRegistryTest.java
index dfe5a18..c9387d3 100644
--- 
a/src/test/java/org/apache/skywalking/banyandb/v1/client/ITGroupMetadataRegistryTest.java
+++ 
b/src/test/java/org/apache/skywalking/banyandb/v1/client/ITGroupMetadataRegistryTest.java
@@ -25,6 +25,7 @@ import 
org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Catalog;
 import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.IntervalRule;
 import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.ResourceOpts;
 import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Metadata;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.LifecycleStage;
 import 
org.apache.skywalking.banyandb.v1.client.grpc.exception.BanyanDBException;
 import org.junit.After;
 import org.junit.Assert;
@@ -96,6 +97,86 @@ public class ITGroupMetadataRegistryTest extends 
BanyanDBClientTestCI {
         Assert.assertNull(client.findGroup("sw_metric"));
     }
 
+    @Test
+    public void testGroupRegistry_hotWarmCold() throws BanyanDBException {
+        Group g = 
Group.newBuilder().setMetadata(Metadata.newBuilder().setName("sw_record"))
+            .setCatalog(Catalog.CATALOG_STREAM)
+            .setResourceOpts(ResourceOpts.newBuilder()
+                .setShardNum(3)
+                .setSegmentInterval(
+                    IntervalRule.newBuilder()
+                        .setUnit(IntervalRule.Unit.UNIT_DAY)
+                        .setNum(1))
+                .setTtl(
+                    IntervalRule.newBuilder()
+                        .setUnit(IntervalRule.Unit.UNIT_DAY)
+                        .setNum(3))
+                .addStages(LifecycleStage.newBuilder()
+                    .setName("warm")
+                    .setShardNum(2)
+                    .setSegmentInterval(IntervalRule.newBuilder()
+                        .setUnit(IntervalRule.Unit.UNIT_DAY)
+                        .setNum(1))
+                    .setTtl(IntervalRule.newBuilder()
+                        .setUnit(IntervalRule.Unit.UNIT_DAY)
+                        .setNum(7))
+                    .setNodeSelector("hdd-nodes")
+                    .build())
+                .addStages(LifecycleStage.newBuilder()
+                    .setName("cold")
+                    .setShardNum(1)
+                    .setSegmentInterval(IntervalRule.newBuilder()
+                        .setUnit(IntervalRule.Unit.UNIT_DAY)
+                        .setNum(7))
+                    .setTtl(IntervalRule.newBuilder()
+                        .setUnit(IntervalRule.Unit.UNIT_DAY)
+                        .setNum(30))
+                    .setNodeSelector("archive-nodes")
+                    .setClose(true)
+                    .build()))
+            .build();
+        this.client.define(g);
+        Group actualGroup = client.findGroup("sw_record");
+        Assert.assertNotNull(actualGroup);
+        Assert.assertNotNull(actualGroup.getUpdatedAt());
+
+        // Verify group exists
+        Assert.assertNotNull(actualGroup);
+
+        // Verify basic metadata
+        Assert.assertEquals("sw_record", actualGroup.getMetadata().getName());
+        Assert.assertEquals(Catalog.CATALOG_STREAM, actualGroup.getCatalog());
+
+        // Verify resource options
+        ResourceOpts actualOpts = actualGroup.getResourceOpts();
+        Assert.assertEquals(3, actualOpts.getShardNum());
+        Assert.assertEquals(IntervalRule.Unit.UNIT_DAY, 
actualOpts.getSegmentInterval().getUnit());
+        Assert.assertEquals(1, actualOpts.getSegmentInterval().getNum());
+        Assert.assertEquals(IntervalRule.Unit.UNIT_DAY, 
actualOpts.getTtl().getUnit());
+        Assert.assertEquals(3, actualOpts.getTtl().getNum());
+
+        // Verify stages (should have 2 stages: warm and cold)
+        Assert.assertEquals(2, actualOpts.getStagesCount());
+
+        // Verify warm stage
+        LifecycleStage warmStage = actualOpts.getStages(0);
+        Assert.assertEquals("warm", warmStage.getName());
+        Assert.assertEquals(2, warmStage.getShardNum());
+        Assert.assertEquals(1, warmStage.getSegmentInterval().getNum());
+        Assert.assertEquals(IntervalRule.Unit.UNIT_DAY, 
warmStage.getSegmentInterval().getUnit());
+        Assert.assertEquals(7, warmStage.getTtl().getNum());
+        Assert.assertEquals("hdd-nodes", warmStage.getNodeSelector());
+
+        // Verify cold stage
+        LifecycleStage coldStage = actualOpts.getStages(1);
+        Assert.assertEquals("cold", coldStage.getName());
+        Assert.assertEquals(1, coldStage.getShardNum());
+        Assert.assertEquals(7, coldStage.getSegmentInterval().getNum());
+        Assert.assertEquals(30, coldStage.getTtl().getNum());
+        Assert.assertEquals("archive-nodes", coldStage.getNodeSelector());
+        Assert.assertTrue(coldStage.getClose());
+    }
+
     private Group buildGroup() {
         return 
Group.newBuilder().setMetadata(Metadata.newBuilder().setName("sw_metric"))
                     .setCatalog(Catalog.CATALOG_MEASURE)
diff --git 
a/src/test/java/org/apache/skywalking/banyandb/v1/client/ITMeasureMetadataRegistryTest.java
 
b/src/test/java/org/apache/skywalking/banyandb/v1/client/ITMeasureMetadataRegistryTest.java
index ebe75ed..36fba76 100644
--- 
a/src/test/java/org/apache/skywalking/banyandb/v1/client/ITMeasureMetadataRegistryTest.java
+++ 
b/src/test/java/org/apache/skywalking/banyandb/v1/client/ITMeasureMetadataRegistryTest.java
@@ -69,10 +69,19 @@ public class ITMeasureMetadataRegistryTest extends 
BanyanDBClientTestCI {
         this.client.define(expectedMeasure);
         List<Measure> actualMeasures = client.findMeasures("sw_metric");
         Assert.assertNotNull(actualMeasures);
-        Assert.assertEquals(1, actualMeasures.size());
-        Measure actualMeasure = actualMeasures.get(0);
-        actualMeasure = 
actualMeasure.toBuilder().clearUpdatedAt().setMetadata(actualMeasure.getMetadata().toBuilder().clearModRevision().clearCreateRevision()).build();
-        Assert.assertEquals(expectedMeasure, actualMeasure);
+        // _topn_result is a system measure, so there should be 2 measures
+        Assert.assertEquals(2, actualMeasures.size());
+        boolean found = false;
+        for (Measure actualMeasure : actualMeasures) {
+            if 
(actualMeasure.getMetadata().getName().equals("service_cpm_minute")) {
+                found = true;
+                actualMeasure = 
actualMeasure.toBuilder().clearUpdatedAt().setMetadata(actualMeasure.getMetadata().toBuilder().clearModRevision().clearCreateRevision()).build();
+                Assert.assertEquals(expectedMeasure, actualMeasure);
+            }
+        }
+        if (!found) {
+            Assert.fail("service_cpm_minute not found");
+        }
     }
 
     @Test
diff --git 
a/src/test/java/org/apache/skywalking/banyandb/v1/client/ITPropertyMetadataRegistryTest.java
 
b/src/test/java/org/apache/skywalking/banyandb/v1/client/ITPropertyMetadataRegistryTest.java
new file mode 100644
index 0000000..ecfda2e
--- /dev/null
+++ 
b/src/test/java/org/apache/skywalking/banyandb/v1/client/ITPropertyMetadataRegistryTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.skywalking.banyandb.v1.client;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Metadata;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagSpec;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagType;
+import 
org.apache.skywalking.banyandb.v1.client.grpc.exception.BanyanDBException;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ITPropertyMetadataRegistryTest extends BanyanDBClientTestCI {
+    @Before
+    public void setUp() throws IOException, BanyanDBException, 
InterruptedException {
+        super.setUpConnection();
+        BanyandbCommon.Group expectedGroup = buildPropertyGroup();
+        client.define(expectedGroup);
+        Assert.assertNotNull(expectedGroup);
+    }
+
+    @After
+    public void tearDown() throws IOException {
+        this.closeClient();
+    }
+
+    @Test
+    public void testPropertyRegistry_createAndGet() throws BanyanDBException {
+        Property expectedProperty = buildProperty();
+        this.client.define(expectedProperty);
+        Property actualProperty = client.findPropertyDefinition("sw_config", 
"ui_template");
+        Assert.assertNotNull(actualProperty);
+        Assert.assertNotNull(actualProperty.getUpdatedAt());
+        actualProperty = 
actualProperty.toBuilder().clearUpdatedAt().setMetadata(actualProperty.getMetadata().toBuilder().clearModRevision().clearCreateRevision()).build();
+        Assert.assertEquals(expectedProperty, actualProperty);
+    }
+
+    @Test
+    public void testPropertyRegistry_createAndList() throws BanyanDBException {
+        Property expectedProperty = buildProperty();
+        this.client.define(expectedProperty);
+        List<Property> actualProperties = 
client.findPropertiesDefinition("sw_config");
+        Assert.assertNotNull(actualProperties);
+        Assert.assertEquals(1, actualProperties.size());
+        Property actualProperty = actualProperties.get(0);
+        actualProperty = 
actualProperty.toBuilder().clearUpdatedAt().setMetadata(actualProperty.getMetadata().toBuilder().clearModRevision().clearCreateRevision()).build();
+        Assert.assertEquals(expectedProperty, actualProperty);
+    }
+
+    @Test
+    public void testPropertyRegistry_createAndDelete() throws 
BanyanDBException {
+        Property expectedProperty = buildProperty();
+        this.client.define(expectedProperty);
+        boolean deleted = this.client.deletePropertyDefinition(
+            expectedProperty.getMetadata().getGroup(), 
expectedProperty.getMetadata().getName());
+        Assert.assertTrue(deleted);
+        Assert.assertThrows(
+                
org.apache.skywalking.banyandb.v1.client.grpc.exception.NotFoundException.class,
+                () -> client.findPropertyDefinition(
+                    expectedProperty.getMetadata().getGroup(), 
expectedProperty.getMetadata().getName())
+            );
+    }
+
+    private Property buildProperty() {
+        Property.Builder builder = Property.newBuilder()
+                                         .setMetadata(Metadata.newBuilder()
+                                                              
.setGroup("sw_config")
+                                                              
.setName("ui_template"))
+                                         .addTags(
+                                                TagSpec.newBuilder()
+                                                        .setName("type")
+                                                        .setType(
+                                                                
TagType.TAG_TYPE_STRING))
+                                                        .addTags(
+                                                        TagSpec.newBuilder()
+                                                                
.setName("service")
+                                                                .setType(
+                                                                        
TagType.TAG_TYPE_STRING));
+        return builder.build();
+    }
+
+    private BanyandbCommon.Group buildPropertyGroup() {
+        return BanyandbCommon.Group.newBuilder()
+                .setMetadata(BanyandbCommon.Metadata.newBuilder()
+                        .setName("sw_config")
+                        .build())
+                .setCatalog(BanyandbCommon.Catalog.CATALOG_PROPERTY)
+                .setResourceOpts(BanyandbCommon.ResourceOpts.newBuilder()
+                        .setShardNum(2))
+                .build();
+    }
+}

Reply via email to