From 41d7ee20fe530a81428595c26647f8927810e57f Mon Sep 17 00:00:00 2001
From: jeremyhi <jiachun_fjc@163.com>
Date: Thu, 21 Dec 2023 16:48:05 +0800
Subject: [PATCH] feat: bind dbname to client

---
 .../src/main/java/io/greptime/QuickStart.java |  9 +--
 .../src/main/java/io/greptime/PojoMapper.java | 19 +----
 .../main/java/io/greptime/WriteClient.java    | 19 +++--
 .../java/io/greptime/models/Database.java     | 31 -------
 .../java/io/greptime/models/TableName.java    | 80 -------------------
 .../java/io/greptime/models/TableRows.java    | 14 ++--
 .../io/greptime/models/TableRowsHelper.java   | 29 +++----
 .../java/io/greptime/models/TableSchema.java  | 16 ++--
 .../main/java/io/greptime/models/WriteOk.java | 14 +---
 .../io/greptime/options/GreptimeOptions.java  | 45 ++++++++---
 .../io/greptime/options/WriteOptions.java     | 34 +++++---
 .../test/java/io/greptime/PojoMapperTest.java |  9 +--
 .../src/test/java/io/greptime/TestUtil.java   |  3 +-
 .../java/io/greptime/WriteClientTest.java     |  5 +-
 .../java/io/greptime/models/ResultTest.java   | 24 +++---
 .../io/greptime/models/TableRowsTest.java     |  4 +-
 16 files changed, 112 insertions(+), 243 deletions(-)
 delete mode 100644 ingester-protocol/src/main/java/io/greptime/models/Database.java
 delete mode 100644 ingester-protocol/src/main/java/io/greptime/models/TableName.java

diff --git a/ingester-example/src/main/java/io/greptime/QuickStart.java b/ingester-example/src/main/java/io/greptime/QuickStart.java
index 350aa54..996057f 100644
--- a/ingester-example/src/main/java/io/greptime/QuickStart.java
+++ b/ingester-example/src/main/java/io/greptime/QuickStart.java
@@ -17,7 +17,6 @@
 
 import io.greptime.models.Column;
 import io.greptime.models.DataType;
-import io.greptime.models.Database;
 import io.greptime.models.Err;
 import io.greptime.models.Metric;
 import io.greptime.models.Result;
@@ -39,7 +38,7 @@ public class QuickStart {
 
     public static void main(String[] args) throws Exception {
         String endpoint = "127.0.0.1:4001";
-        GreptimeOptions opts = GreptimeOptions.newBuilder(endpoint) //
+        GreptimeOptions opts = GreptimeOptions.newBuilder("public", endpoint) //
                 .writeMaxRetries(1) //
                 .routeTableRefreshPeriodSeconds(-1) //
                 .build();
@@ -60,8 +59,7 @@ public static void main(String[] args) throws Exception {
         runInsertWithStream(greptimeDB, now);
     }
 
-    @Database(name = "public")
-    @Metric(name = "monitor")
+    @Metric(name = "monitor1")
     static class Monitor {
         @Column(name = "host", tag = true, dataType = DataType.String)
         String host;
@@ -75,8 +73,7 @@ static class Monitor {
         BigDecimal decimalValue;
     }
 
-    @Database(name = "public")
-    @Metric(name = "monitor_cpu")
+    @Metric(name = "monitor_cpu1")
     static class MonitorCpu {
         @Column(name = "host", tag = true, dataType = DataType.String)
         String host;
diff --git a/ingester-protocol/src/main/java/io/greptime/PojoMapper.java b/ingester-protocol/src/main/java/io/greptime/PojoMapper.java
index d814b8d..0fa037b 100644
--- a/ingester-protocol/src/main/java/io/greptime/PojoMapper.java
+++ b/ingester-protocol/src/main/java/io/greptime/PojoMapper.java
@@ -19,10 +19,8 @@
 import io.greptime.errors.PojoException;
 import io.greptime.models.Column;
 import io.greptime.models.DataType;
-import io.greptime.models.Database;
 import io.greptime.models.Metric;
 import io.greptime.models.SemanticType;
-import io.greptime.models.TableName;
 import io.greptime.models.TableRows;
 import io.greptime.models.TableSchema;
 import java.lang.reflect.Field;
@@ -57,11 +55,8 @@ public <M> TableRows toTableRows(List<M> pojos) {
 
         Map<String, Field> fieldMap = getAndCacheMetricClass(metricType);
 
-        String database = getDatabase(metricType);
         String metricName = getMetricName(metricType);
 
-        TableName tableName = TableName.with(database, metricName);
-
         String[] columnNames = new String[fieldMap.size()];
         DataType[] dataTypes = new DataType[fieldMap.size()];
         SemanticType[] semanticTypes = new SemanticType[fieldMap.size()];
@@ -85,7 +80,7 @@ public <M> TableRows toTableRows(List<M> pojos) {
             i++;
         }
 
-        TableSchema schema = TableSchema.newBuilder(tableName) //
+        TableSchema schema = TableSchema.newBuilder(metricName) //
                 .columnNames(columnNames) //
                 .semanticTypes(semanticTypes) //
                 .dataTypes(dataTypes) //
@@ -113,18 +108,6 @@ public <M> TableRows toTableRows(List<M> pojos) {
         return tableRows;
     }
 
-    private <M> String getDatabase(Class<M> metricType) {
-        Database databaseAnnotation = metricType.getAnnotation(Database.class);
-        if (databaseAnnotation != null) {
-            return databaseAnnotation.name();
-        }
-
-        String err =
-                String.format("Unable to determine Database for '%s'." + " Does it have a @Database annotation?",
-                        metricType);
-        throw new PojoException(err);
-    }
-
     private String getMetricName(Class<?> metricType) {
         // From @Metirc annotation
         Metric metricAnnotation = metricType.getAnnotation(Metric.class);
diff --git a/ingester-protocol/src/main/java/io/greptime/WriteClient.java b/ingester-protocol/src/main/java/io/greptime/WriteClient.java
index 6c0b096..253a5f1 100644
--- a/ingester-protocol/src/main/java/io/greptime/WriteClient.java
+++ b/ingester-protocol/src/main/java/io/greptime/WriteClient.java
@@ -31,9 +31,9 @@
 import io.greptime.errors.StreamException;
 import io.greptime.limit.LimitedPolicy;
 import io.greptime.limit.WriteLimiter;
+import io.greptime.models.AuthInfo;
 import io.greptime.models.Err;
 import io.greptime.models.Result;
-import io.greptime.models.TableName;
 import io.greptime.models.WriteOk;
 import io.greptime.models.TableRows;
 import io.greptime.models.TableRowsHelper;
@@ -48,7 +48,6 @@
 import java.util.Collection;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
-import java.util.stream.Collectors;
 
 /**
  * Default Write API impl.
@@ -161,10 +160,9 @@ private CompletableFuture<Result<WriteOk, Err>> write0(Collection<TableRows> row
     }
 
     private CompletableFuture<Result<WriteOk, Err>> writeTo(Endpoint endpoint, Collection<TableRows> rows, WriteOp writeOp,  Context ctx, int retries) {
-        Collection<TableName> tableNames = rows.stream() //
-                .map(TableRows::tableName) //
-                .collect(Collectors.toList());
-        Database.GreptimeRequest req = TableRowsHelper.toGreptimeRequest(tableNames, rows, writeOp, this.opts.getAuthInfo());
+        String database = this.opts.getDatabase();
+        AuthInfo authInfo = this.opts.getAuthInfo();
+        Database.GreptimeRequest req = TableRowsHelper.toGreptimeRequest(rows, writeOp, database, authInfo);
         ctx.with("retries", retries);
 
         CompletableFuture<Database.GreptimeResponse> future = this.routerClient.invoke(endpoint, req, ctx);
@@ -175,7 +173,7 @@ private CompletableFuture<Result<WriteOk, Err>> writeTo(Endpoint endpoint, Colle
             int statusCode = status.getStatusCode();
             if (Status.isSuccess(statusCode)) {
                 int affectedRows = resp.getAffectedRows().getValue();
-                return WriteOk.ok(affectedRows, 0, tableNames).mapToResult();
+                return WriteOk.ok(affectedRows, 0).mapToResult();
             } else {
                 return Err.writeErr(statusCode, new ServerException(status.getErrMsg()), endpoint, rows).mapToResult();
             }
@@ -190,7 +188,7 @@ private Observer<WriteTable> streamWriteTo(Endpoint endpoint, Context ctx, Obser
                             @Override
                             public void onNext(Database.GreptimeResponse resp) {
                                 int affectedRows = resp.getAffectedRows().getValue();
-                                Result<WriteOk, Err> ret = WriteOk.ok(affectedRows, 0, null).mapToResult();
+                                Result<WriteOk, Err> ret = WriteOk.ok(affectedRows, 0).mapToResult();
                                 if (ret.isOk()) {
                                     respObserver.onNext(ret.getOk());
                                 } else {
@@ -215,8 +213,9 @@ public void onCompleted() {
             public void onNext(WriteTable writeTable) {
                 TableRows rows = writeTable.getRows();
                 WriteOp writeOp = writeTable.getWriteOp();
-                Database.GreptimeRequest req =
-                        TableRowsHelper.toGreptimeRequest(rows, writeOp, WriteClient.this.opts.getAuthInfo());
+                String database = WriteClient.this.opts.getDatabase();
+                AuthInfo authInfo = WriteClient.this.opts.getAuthInfo();
+                Database.GreptimeRequest req = TableRowsHelper.toGreptimeRequest(rows, writeOp, database, authInfo);
                 rpcObserver.onNext(req);
             }
 
diff --git a/ingester-protocol/src/main/java/io/greptime/models/Database.java b/ingester-protocol/src/main/java/io/greptime/models/Database.java
deleted file mode 100644
index fe23d8f..0000000
--- a/ingester-protocol/src/main/java/io/greptime/models/Database.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2023 Greptime Team
- *
- * 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.
- */
-package io.greptime.models;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- *
- * @author jiachun.fjc
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.TYPE)
-public @interface Database {
-    String name();
-}
diff --git a/ingester-protocol/src/main/java/io/greptime/models/TableName.java b/ingester-protocol/src/main/java/io/greptime/models/TableName.java
deleted file mode 100644
index cf4e5c5..0000000
--- a/ingester-protocol/src/main/java/io/greptime/models/TableName.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2023 Greptime Team
- *
- * 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.
- */
-package io.greptime.models;
-
-import io.greptime.common.util.Ensures;
-import io.greptime.common.util.Strings;
-import java.util.Objects;
-
-/**
- * Table name, contains database name and table name.
- *
- * @author jiachun.fjc
- */
-public class TableName {
-    private String databaseName;
-    private String tableName;
-
-    public static TableName with(String databaseName, String tableName) {
-        Ensures.ensure(Strings.isNotBlank(tableName), "blank `tableName`");
-        TableName tn = new TableName();
-        tn.setDatabaseName(databaseName);
-        tn.setTableName(tableName);
-        return tn;
-    }
-
-    public String getDatabaseName() {
-        return databaseName;
-    }
-
-    public void setDatabaseName(String databaseName) {
-        this.databaseName = databaseName;
-    }
-
-    public String getTableName() {
-        return tableName;
-    }
-
-    public void setTableName(String tableName) {
-        this.tableName = tableName;
-    }
-
-    @Override
-    public String toString() {
-        return "TableName{" + //
-                "databaseName='" + databaseName + '\'' + //
-                ", tableName='" + tableName + '\'' + //
-                '}';
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        TableName tableName1 = (TableName) o;
-        return Objects.equals(getDatabaseName(), tableName1.getDatabaseName())
-                && Objects.equals(getTableName(), tableName1.getTableName());
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(getDatabaseName(), getTableName());
-    }
-}
diff --git a/ingester-protocol/src/main/java/io/greptime/models/TableRows.java b/ingester-protocol/src/main/java/io/greptime/models/TableRows.java
index 08a63a9..b696370 100644
--- a/ingester-protocol/src/main/java/io/greptime/models/TableRows.java
+++ b/ingester-protocol/src/main/java/io/greptime/models/TableRows.java
@@ -34,7 +34,7 @@ public interface TableRows {
     /**
      * The table name to write.
      */
-    TableName tableName();
+    String tableName();
 
     /**
      * The rows count to write.
@@ -89,7 +89,7 @@ public Builder(TableSchema tableSchema) {
         }
 
         public TableRows build() {
-            TableName tableName = this.tableSchema.getTableName();
+            String tableName = this.tableSchema.getTableName();
             List<String> columnNames = this.tableSchema.getColumnNames();
             List<Common.SemanticType> semanticTypes = this.tableSchema.getSemanticTypes();
             List<Common.ColumnDataType> dataTypes = this.tableSchema.getDataTypes();
@@ -110,7 +110,7 @@ public TableRows build() {
             return buildRow(tableName, columnCount, columnNames, semanticTypes, dataTypes, dataTypeExtensions);
         }
 
-        private static TableRows buildRow(TableName tableName, //
+        private static TableRows buildRow(String tableName, //
                                           int columnCount, //
                                           List<String> columnNames, //
                                           List<Common.SemanticType> semanticTypes, //
@@ -134,13 +134,13 @@ private static TableRows buildRow(TableName tableName, //
 
     class RowBasedTableRows implements TableRows, Into<RowData.Rows> {
 
-        private TableName tableName;
+        private String tableName;
 
         private List<RowData.ColumnSchema> columnSchemas;
         private final List<RowData.Row> rows = new ArrayList<>();
 
         @Override
-        public TableName tableName() {
+        public String tableName() {
             return tableName;
         }
 
@@ -172,7 +172,7 @@ public TableRows insert(Object... values) {
         @Override
         public Database.RowInsertRequest intoRowInsertRequest() {
             return Database.RowInsertRequest.newBuilder() //
-                    .setTableName(this.tableName.getTableName()) //
+                    .setTableName(this.tableName) //
                     .setRows(into()) //
                     .build();
         }
@@ -180,7 +180,7 @@ public Database.RowInsertRequest intoRowInsertRequest() {
         @Override
         public Database.RowDeleteRequest intoRowDeleteRequest() {
             return Database.RowDeleteRequest.newBuilder() //
-                    .setTableName(this.tableName.getTableName()) //
+                    .setTableName(this.tableName) //
                     .setRows(into()) //
                     .build();
         }
diff --git a/ingester-protocol/src/main/java/io/greptime/models/TableRowsHelper.java b/ingester-protocol/src/main/java/io/greptime/models/TableRowsHelper.java
index d5b7196..16a50a8 100644
--- a/ingester-protocol/src/main/java/io/greptime/models/TableRowsHelper.java
+++ b/ingester-protocol/src/main/java/io/greptime/models/TableRowsHelper.java
@@ -26,36 +26,25 @@
  */
 public class TableRowsHelper {
 
-    public static Database.GreptimeRequest toGreptimeRequest(TableRows rows, WriteOp writeOp, AuthInfo authInfo) {
-        return toGreptimeRequest(Collections.singleton(rows.tableName()), Collections.singleton(rows), writeOp,
-                authInfo);
+    public static Database.GreptimeRequest toGreptimeRequest(TableRows rows, //
+            WriteOp writeOp, //
+            String database, //
+            AuthInfo authInfo) {
+        return toGreptimeRequest(Collections.singleton(rows), writeOp, database, authInfo);
     }
 
-    public static Database.GreptimeRequest toGreptimeRequest(Collection<TableName> tableNames, //
-            Collection<TableRows> rows, //
+    public static Database.GreptimeRequest toGreptimeRequest(Collection<TableRows> rows, //
             WriteOp writeOp, //
+            String database, //
             AuthInfo authInfo) {
-        String dbName = null;
-        for (TableName t : tableNames) {
-            if (dbName == null) {
-                dbName = t.getDatabaseName();
-            } else if (!dbName.equals(t.getDatabaseName())) {
-                String errMsg =
-                        String.format("Write to multiple databases is not supported: %s, %s", dbName,
-                                t.getDatabaseName());
-                throw new IllegalArgumentException(errMsg);
-            }
-        }
-
         Common.RequestHeader.Builder headerBuilder = Common.RequestHeader.newBuilder();
-        if (dbName != null) {
-            headerBuilder.setDbname(dbName);
+        if (database != null) {
+            headerBuilder.setDbname(database);
         }
         if (authInfo != null) {
             headerBuilder.setAuthorization(authInfo.into());
         }
 
-
         switch (writeOp) {
             case Insert:
                 Database.RowInsertRequests.Builder insertBuilder = Database.RowInsertRequests.newBuilder();
diff --git a/ingester-protocol/src/main/java/io/greptime/models/TableSchema.java b/ingester-protocol/src/main/java/io/greptime/models/TableSchema.java
index aba2e24..fb895e9 100644
--- a/ingester-protocol/src/main/java/io/greptime/models/TableSchema.java
+++ b/ingester-protocol/src/main/java/io/greptime/models/TableSchema.java
@@ -30,9 +30,9 @@
  */
 public class TableSchema {
 
-    private static final Map<TableName, TableSchema> TABLE_SCHEMA_CACHE = new ConcurrentHashMap<>();
+    private static final Map<String, TableSchema> TABLE_SCHEMA_CACHE = new ConcurrentHashMap<>();
 
-    private TableName tableName;
+    private String tableName;
     private List<String> columnNames;
     private List<Common.SemanticType> semanticTypes;
     private List<Common.ColumnDataType> dataTypes;
@@ -40,7 +40,7 @@ public class TableSchema {
 
     private TableSchema() {}
 
-    public TableName getTableName() {
+    public String getTableName() {
         return tableName;
     }
 
@@ -60,11 +60,11 @@ public List<Common.ColumnDataTypeExtension> getDataTypeExtensions() {
         return dataTypeExtensions;
     }
 
-    public static TableSchema findSchema(TableName tableName) {
+    public static TableSchema findSchema(String tableName) {
         return TABLE_SCHEMA_CACHE.get(tableName);
     }
 
-    public static TableSchema removeSchema(TableName tableName) {
+    public static TableSchema removeSchema(String tableName) {
         return TABLE_SCHEMA_CACHE.remove(tableName);
     }
 
@@ -72,18 +72,18 @@ public static void clearAllSchemas() {
         TABLE_SCHEMA_CACHE.clear();
     }
 
-    public static Builder newBuilder(TableName tableName) {
+    public static Builder newBuilder(String tableName) {
         return new Builder(tableName);
     }
 
     public static class Builder {
-        private final TableName tableName;
+        private final String tableName;
         private List<String> columnNames;
         private List<Common.SemanticType> semanticTypes;
         private List<Common.ColumnDataType> dataTypes;
         private List<Common.ColumnDataTypeExtension> dataTypeExtensions;
 
-        public Builder(TableName tableName) {
+        public Builder(String tableName) {
             this.tableName = tableName;
         }
 
diff --git a/ingester-protocol/src/main/java/io/greptime/models/WriteOk.java b/ingester-protocol/src/main/java/io/greptime/models/WriteOk.java
index 13e51dd..b9b9924 100644
--- a/ingester-protocol/src/main/java/io/greptime/models/WriteOk.java
+++ b/ingester-protocol/src/main/java/io/greptime/models/WriteOk.java
@@ -26,7 +26,6 @@ public class WriteOk {
 
     private int success;
     private int failure;
-    private Collection<TableName> tableNames;
 
     /**
      * Returns the number of successful writes.
@@ -42,13 +41,6 @@ public int getFailure() {
         return failure;
     }
 
-    /**
-     * Returns the table names.
-     */
-    public Collection<TableName> getTableNames() {
-        return tableNames;
-    }
-
     /**
      * Map the {@link WriteOk} to {@link Result}.
      */
@@ -61,7 +53,6 @@ public String toString() {
         return "WriteOk{" + //
                 "success=" + success + //
                 ", failure=" + failure + //
-                ", tableNames=" + tableNames + //
                 '}';
     }
 
@@ -69,17 +60,16 @@ public String toString() {
      * Returns an empty {@link WriteOk}.
      */
     public static WriteOk emptyOk() {
-        return ok(0, 0, null);
+        return ok(0, 0);
     }
 
     /**
      * Creates a new {@link WriteOk} from the given value.
      */
-    public static WriteOk ok(int success, int failure, Collection<TableName> tableNames) {
+    public static WriteOk ok(int success, int failure) {
         WriteOk ok = new WriteOk();
         ok.success = success;
         ok.failure = failure;
-        ok.tableNames = tableNames;
         return ok;
     }
 }
diff --git a/ingester-protocol/src/main/java/io/greptime/options/GreptimeOptions.java b/ingester-protocol/src/main/java/io/greptime/options/GreptimeOptions.java
index 18f8187..872f6a3 100644
--- a/ingester-protocol/src/main/java/io/greptime/options/GreptimeOptions.java
+++ b/ingester-protocol/src/main/java/io/greptime/options/GreptimeOptions.java
@@ -38,6 +38,7 @@ public class GreptimeOptions implements Copiable<GreptimeOptions> {
     private RpcOptions rpcOptions;
     private RouterOptions routerOptions;
     private WriteOptions writeOptions;
+    private String database;
     private AuthInfo authInfo;
 
     public List<Endpoint> getEndpoints() {
@@ -80,6 +81,14 @@ public void setWriteOptions(WriteOptions writeOptions) {
         this.writeOptions = writeOptions;
     }
 
+    public String getDatabase() {
+        return database;
+    }
+
+    public void setDatabase(String database) {
+        this.database = database;
+    }
+
     public AuthInfo getAuthInfo() {
         return authInfo;
     }
@@ -93,6 +102,7 @@ public GreptimeOptions copy() {
         GreptimeOptions opts = new GreptimeOptions();
         opts.endpoints = new ArrayList<>(this.endpoints);
         opts.asyncPool = this.asyncPool;
+        opts.database = this.database;
         opts.authInfo = this.authInfo;
         if (this.rpcOptions != null) {
             opts.rpcOptions = this.rpcOptions.copy();
@@ -114,6 +124,7 @@ public String toString() {
                 ", rpcOptions=" + rpcOptions + //
                 ", routerOptions=" + routerOptions + //
                 ", writeOptions=" + writeOptions + //
+                ", database='" + database + '\'' + //
                 ", authInfo=" + authInfo + //
                 '}';
     }
@@ -128,19 +139,20 @@ public static GreptimeOptions checkSelf(GreptimeOptions opts) {
         return opts;
     }
 
-    public static Builder newBuilder(List<Endpoint> endpoints) {
-        return new Builder(endpoints);
+    public static Builder newBuilder(String database, List<Endpoint> endpoints) {
+        return new Builder(database, endpoints);
     }
 
-    public static Builder newBuilder(Endpoint... endpoints) {
-        return new Builder(Arrays.stream(endpoints).collect(Collectors.toList()));
+    public static Builder newBuilder(String database, Endpoint... endpoints) {
+        return new Builder(database, Arrays.stream(endpoints).collect(Collectors.toList()));
     }
 
-    public static Builder newBuilder(String... endpoints) {
-        return new Builder(Arrays.stream(endpoints).map(Endpoint::parse).collect(Collectors.toList()));
+    public static Builder newBuilder(String database, String... endpoints) {
+        return new Builder(database, Arrays.stream(endpoints).map(Endpoint::parse).collect(Collectors.toList()));
     }
 
     public static final class Builder {
+        private final String database;
         private final List<Endpoint> endpoints = new ArrayList<>();
 
         // Asynchronous thread pool, which is used to handle various asynchronous tasks in the SDK.
@@ -158,7 +170,8 @@ public static final class Builder {
         // Authentication information
         private AuthInfo authInfo;
 
-        public Builder(List<Endpoint> endpoints) {
+        public Builder(String database, List<Endpoint> endpoints) {
+            this.database = database;
             this.endpoints.addAll(endpoints);
         }
 
@@ -245,7 +258,7 @@ public Builder routeTableRefreshPeriodSeconds(long routeTableRefreshPeriodSecond
         }
 
         /**
-         * Set authentication information.
+         * Sets authentication information.
          *
          * @param authInfo the authentication information
          * @return this builder
@@ -265,22 +278,30 @@ public GreptimeOptions build() {
             opts.setEndpoints(this.endpoints);
             opts.setAsyncPool(this.asyncPool);
             opts.setRpcOptions(this.rpcOptions);
+            opts.setDatabase(this.database);
             opts.setAuthInfo(this.authInfo);
+            opts.setRouterOptions(createRouterOptions());
+            opts.setWriteOptions(createWriteOptions());
+            return GreptimeOptions.checkSelf(opts);
+        }
 
+        private RouterOptions createRouterOptions() {
             RouterOptions routerOpts = new RouterOptions();
             routerOpts.setEndpoints(this.endpoints);
             routerOpts.setRefreshPeriodSeconds(this.routeTableRefreshPeriodSeconds);
-            opts.setRouterOptions(routerOpts);
+            return routerOpts;
+        }
 
+        private WriteOptions createWriteOptions() {
             WriteOptions writeOpts = new WriteOptions();
+            writeOpts.setDatabase(this.database);
+            writeOpts.setAuthInfo(this.authInfo);
             writeOpts.setAsyncPool(this.asyncPool);
             writeOpts.setMaxRetries(this.writeMaxRetries);
             writeOpts.setMaxInFlightWriteRows(this.maxInFlightWriteRows);
             writeOpts.setLimitedPolicy(this.writeLimitedPolicy);
             writeOpts.setDefaultStreamMaxWritePointsPerSecond(this.defaultStreamMaxWritePointsPerSecond);
-            opts.setWriteOptions(writeOpts);
-
-            return GreptimeOptions.checkSelf(opts);
+            return writeOpts;
         }
     }
 }
diff --git a/ingester-protocol/src/main/java/io/greptime/options/WriteOptions.java b/ingester-protocol/src/main/java/io/greptime/options/WriteOptions.java
index 56a56c8..5624dbb 100644
--- a/ingester-protocol/src/main/java/io/greptime/options/WriteOptions.java
+++ b/ingester-protocol/src/main/java/io/greptime/options/WriteOptions.java
@@ -27,17 +27,33 @@
  * @author jiachun.fjc
  */
 public class WriteOptions implements Copiable<WriteOptions> {
+    private String database;
+    private AuthInfo authInfo;
     private RouterClient routerClient;
     private Executor asyncPool;
     private int maxRetries = 1;
-    private AuthInfo authInfo;
-
     // Write flow limit: maximum number of data rows in-flight.
     private int maxInFlightWriteRows = 65536;
     private LimitedPolicy limitedPolicy = LimitedPolicy.defaultWriteLimitedPolicy();
     // Default rate limit for stream writer
     private int defaultStreamMaxWritePointsPerSecond = 10 * 65536;
 
+    public String getDatabase() {
+        return database;
+    }
+
+    public void setDatabase(String database) {
+        this.database = database;
+    }
+
+    public AuthInfo getAuthInfo() {
+        return authInfo;
+    }
+
+    public void setAuthInfo(AuthInfo authInfo) {
+        this.authInfo = authInfo;
+    }
+
     public RouterClient getRouterClient() {
         return routerClient;
     }
@@ -86,37 +102,31 @@ public void setDefaultStreamMaxWritePointsPerSecond(int defaultStreamMaxWritePoi
         this.defaultStreamMaxWritePointsPerSecond = defaultStreamMaxWritePointsPerSecond;
     }
 
-    public AuthInfo getAuthInfo() {
-        return this.authInfo;
-    }
-
-    public void setAuthInfo(AuthInfo authInfo) {
-        this.authInfo = authInfo;
-    }
-
     @Override
     public WriteOptions copy() {
         WriteOptions opts = new WriteOptions();
+        opts.database = this.database;
+        opts.authInfo = this.authInfo;
         opts.routerClient = this.routerClient;
         opts.asyncPool = this.asyncPool;
         opts.maxRetries = this.maxRetries;
         opts.maxInFlightWriteRows = this.maxInFlightWriteRows;
         opts.limitedPolicy = this.limitedPolicy;
         opts.defaultStreamMaxWritePointsPerSecond = this.defaultStreamMaxWritePointsPerSecond;
-        opts.authInfo = this.authInfo;
         return opts;
     }
 
     @Override
     public String toString() {
         return "WriteOptions{" + //
+                "database='" + database + '\'' + //
+                ", authInfo=" + authInfo + //
                 ", routerClient=" + routerClient + //
                 ", asyncPool=" + asyncPool + //
                 ", maxRetries=" + maxRetries + //
                 ", maxInFlightWriteRows=" + maxInFlightWriteRows + //
                 ", limitedPolicy=" + limitedPolicy + //
                 ", defaultStreamMaxWritePointsPerSecond=" + defaultStreamMaxWritePointsPerSecond + //
-                ", authInfo=" + authInfo + //
                 '}';
     }
 }
diff --git a/ingester-protocol/src/test/java/io/greptime/PojoMapperTest.java b/ingester-protocol/src/test/java/io/greptime/PojoMapperTest.java
index e3e7a9e..3eeca9e 100644
--- a/ingester-protocol/src/test/java/io/greptime/PojoMapperTest.java
+++ b/ingester-protocol/src/test/java/io/greptime/PojoMapperTest.java
@@ -17,7 +17,6 @@
 
 import io.greptime.models.Column;
 import io.greptime.models.DataType;
-import io.greptime.models.Database;
 import io.greptime.models.Metric;
 import io.greptime.models.TableRows;
 import org.junit.Assert;
@@ -39,8 +38,7 @@ public void testToTableRows() {
             pojos1.add(pojo1);
         }
         TableRows tp1 = new PojoMapper(65536).toTableRows(pojos1);
-        Assert.assertEquals("public", tp1.tableName().getDatabaseName());
-        Assert.assertEquals("pojo1", tp1.tableName().getTableName());
+        Assert.assertEquals("pojo1", tp1.tableName());
         Assert.assertEquals(50, tp1.pointCount());
 
 
@@ -50,8 +48,7 @@ public void testToTableRows() {
             pojos2.add(pojo2);
         }
         TableRows tp2 = new PojoMapper(65536).toTableRows(pojos2);
-        Assert.assertEquals("public", tp2.tableName().getDatabaseName());
-        Assert.assertEquals("pojo2", tp2.tableName().getTableName());
+        Assert.assertEquals("pojo2", tp2.tableName());
         Assert.assertEquals(30, tp2.pointCount());
     }
 
@@ -76,7 +73,6 @@ static Pojo2Test createNewPojo2Test() {
 }
 
 
-@Database(name = "public")
 @Metric(name = "pojo1")
 class Pojo1Test {
     @Column(name = "a", dataType = DataType.String, tag = true)
@@ -92,7 +88,6 @@ class Pojo1Test {
 }
 
 
-@Database(name = "public")
 @Metric(name = "pojo2")
 class Pojo2Test {
     @Column(name = "pojo2", dataType = DataType.String, tag = true)
diff --git a/ingester-protocol/src/test/java/io/greptime/TestUtil.java b/ingester-protocol/src/test/java/io/greptime/TestUtil.java
index 3240529..374e763 100644
--- a/ingester-protocol/src/test/java/io/greptime/TestUtil.java
+++ b/ingester-protocol/src/test/java/io/greptime/TestUtil.java
@@ -17,7 +17,6 @@
 
 import io.greptime.models.DataType;
 import io.greptime.models.SemanticType;
-import io.greptime.models.TableName;
 import io.greptime.models.TableRows;
 import io.greptime.models.TableSchema;
 import java.util.Collection;
@@ -30,7 +29,7 @@ public class TestUtil {
 
     public static Collection<TableRows> testTableRows(String tableName, int rowCount) {
         TableSchema tableSchema =
-                TableSchema.newBuilder(TableName.with("public", tableName))
+                TableSchema.newBuilder(tableName)
                         .semanticTypes(SemanticType.Tag, SemanticType.Timestamp, SemanticType.Field)
                         .dataTypes(DataType.String, DataType.TimestampMillisecond, DataType.Float64) //
                         .columnNames("host", "ts", "cpu") //
diff --git a/ingester-protocol/src/test/java/io/greptime/WriteClientTest.java b/ingester-protocol/src/test/java/io/greptime/WriteClientTest.java
index c00d65c..00ce84e 100644
--- a/ingester-protocol/src/test/java/io/greptime/WriteClientTest.java
+++ b/ingester-protocol/src/test/java/io/greptime/WriteClientTest.java
@@ -20,7 +20,6 @@
 import io.greptime.models.Err;
 import io.greptime.models.Result;
 import io.greptime.models.SemanticType;
-import io.greptime.models.TableName;
 import io.greptime.models.TableRows;
 import io.greptime.models.TableSchema;
 import io.greptime.models.WriteOk;
@@ -35,9 +34,7 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.runners.MockitoJUnitRunner;
-import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ForkJoinPool;
 import static io.greptime.models.DataType.Binary;
@@ -100,7 +97,7 @@ public void testWriteSuccess() throws ExecutionException, InterruptedException {
                         Float32, Float64, Bool, Binary, Date, DateTime, TimestampSecond, TimestampMillisecond,
                         TimestampNanosecond};
 
-        TableSchema schema = TableSchema.newBuilder(TableName.with("", "test_table")) //
+        TableSchema schema = TableSchema.newBuilder("test_table") //
                 .columnNames(columnNames) //
                 .semanticTypes(semanticTypes) //
                 .dataTypes(dataTypes) //
diff --git a/ingester-protocol/src/test/java/io/greptime/models/ResultTest.java b/ingester-protocol/src/test/java/io/greptime/models/ResultTest.java
index 578fb78..8e39eca 100644
--- a/ingester-protocol/src/test/java/io/greptime/models/ResultTest.java
+++ b/ingester-protocol/src/test/java/io/greptime/models/ResultTest.java
@@ -25,7 +25,7 @@ public class ResultTest {
 
     @Test
     public void testMap() {
-        final Result<WriteOk, Err> r1 = Result.ok(WriteOk.ok(2, 0, null));
+        final Result<WriteOk, Err> r1 = Result.ok(WriteOk.ok(2, 0));
         final Result<Integer, Err> r2 = r1.map(WriteOk::getSuccess);
         Assert.assertEquals(2, r2.getOk().intValue());
 
@@ -36,7 +36,7 @@ public void testMap() {
 
     @Test
     public void testMapOr() {
-        final Result<WriteOk, Err> r1 = Result.ok(WriteOk.ok(2, 0, null));
+        final Result<WriteOk, Err> r1 = Result.ok(WriteOk.ok(2, 0));
         final Integer r2 = r1.mapOr(-1, WriteOk::getSuccess);
         Assert.assertEquals(2, r2.intValue());
 
@@ -47,7 +47,7 @@ public void testMapOr() {
 
     @Test
     public void testMapOrElse() {
-        final Result<WriteOk, Err> r1 = Result.ok(WriteOk.ok(2, 0, null));
+        final Result<WriteOk, Err> r1 = Result.ok(WriteOk.ok(2, 0));
         final Integer r2 = r1.mapOrElse(err -> -1, WriteOk::getSuccess);
         Assert.assertEquals(2, r2.intValue());
 
@@ -58,7 +58,7 @@ public void testMapOrElse() {
 
     @Test
     public void testMapErr() {
-        final Result<WriteOk, Err> r1 = Result.ok(WriteOk.ok(2, 0, null));
+        final Result<WriteOk, Err> r1 = Result.ok(WriteOk.ok(2, 0));
         final Result<WriteOk, Throwable> r2 = r1.mapErr(Err::getError);
         Assert.assertEquals(2, r2.getOk().getSuccess());
 
@@ -70,16 +70,16 @@ public void testMapErr() {
 
     @Test
     public void testAndThen() {
-        final Result<WriteOk, Err> r1 = Result.ok(WriteOk.ok(2, 0, null));
+        final Result<WriteOk, Err> r1 = Result.ok(WriteOk.ok(2, 0));
         final Result<WriteOk, Err> r2 = r1.andThen(writeOk -> {
-            WriteOk newOne = WriteOk.ok(writeOk.getSuccess() + 1, 0, null);
+            WriteOk newOne = WriteOk.ok(writeOk.getSuccess() + 1, 0);
             return newOne.mapToResult();
         });
         Assert.assertEquals(3, r2.getOk().getSuccess());
 
         final Result<WriteOk, Err> r3 = Result.err(Err.writeErr(400, null, null, null));
         final Result<WriteOk, Err> r4 = r3.andThen(writeOk -> {
-            WriteOk newOne = WriteOk.ok(writeOk.getSuccess() + 1, 0, null);
+            WriteOk newOne = WriteOk.ok(writeOk.getSuccess() + 1, 0);
             return newOne.mapToResult();
         });
         Assert.assertFalse(r4.isOk());
@@ -87,18 +87,18 @@ public void testAndThen() {
 
     @Test
     public void testOrElse() {
-        final Result<WriteOk, Err> r1 = Result.ok(WriteOk.ok(2, 0, null));
-        final Result<WriteOk, String> r2 = r1.orElse(err -> Result.ok(WriteOk.ok(0, 0, null)));
+        final Result<WriteOk, Err> r1 = Result.ok(WriteOk.ok(2, 0));
+        final Result<WriteOk, String> r2 = r1.orElse(err -> Result.ok(WriteOk.ok(0, 0)));
         Assert.assertEquals(2, r2.getOk().getSuccess());
 
         final Result<WriteOk, Err> r3 = Result.err(Err.writeErr(400, null, null, null));
-        final Result<WriteOk, String> r4 = r3.orElse(err -> Result.ok(WriteOk.ok(0, 0, null)));
+        final Result<WriteOk, String> r4 = r3.orElse(err -> Result.ok(WriteOk.ok(0, 0)));
         Assert.assertEquals(0, r4.getOk().getSuccess());
     }
 
     @Test
     public void testUnwrapOr() {
-        final Result<WriteOk, Err> r1 = Result.ok(WriteOk.ok(2, 0, null));
+        final Result<WriteOk, Err> r1 = Result.ok(WriteOk.ok(2, 0));
         final WriteOk r2 = r1.unwrapOr(WriteOk.emptyOk());
         Assert.assertEquals(2, r2.getSuccess());
 
@@ -109,7 +109,7 @@ public void testUnwrapOr() {
 
     @Test
     public void testUnwrapOrElse() {
-        final Result<WriteOk, Err> r1 = Result.ok(WriteOk.ok(2, 0, null));
+        final Result<WriteOk, Err> r1 = Result.ok(WriteOk.ok(2, 0));
         final WriteOk r2 = r1.unwrapOrElse(err -> WriteOk.emptyOk());
         Assert.assertEquals(2, r2.getSuccess());
 
diff --git a/ingester-protocol/src/test/java/io/greptime/models/TableRowsTest.java b/ingester-protocol/src/test/java/io/greptime/models/TableRowsTest.java
index ce22c18..026af0a 100644
--- a/ingester-protocol/src/test/java/io/greptime/models/TableRowsTest.java
+++ b/ingester-protocol/src/test/java/io/greptime/models/TableRowsTest.java
@@ -26,7 +26,7 @@ public class TableRowsTest {
 
     @Test
     public void testWriteRowsNonNull() {
-        TableSchema schema = TableSchema.newBuilder(TableName.with("", "test_table")) //
+        TableSchema schema = TableSchema.newBuilder("test_table") //
                 .columnNames("col1", "col2", "col3") //
                 .semanticTypes(SemanticType.Tag, SemanticType.Tag, SemanticType.Field) //
                 .dataTypes(DataType.String, DataType.String, DataType.Int32) //
@@ -46,7 +46,7 @@ public void testWriteRowsNonNull() {
 
     @Test
     public void testWriteRowsSomeNull() {
-        TableSchema schema = TableSchema.newBuilder(TableName.with("", "test_table")) //
+        TableSchema schema = TableSchema.newBuilder("test_table") //
                 .columnNames("col1", "col2", "col3") //
                 .semanticTypes(SemanticType.Tag, SemanticType.Tag, SemanticType.Field) //
                 .dataTypes(DataType.String, DataType.String, DataType.Int32) //