You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kyuubi.apache.org by ya...@apache.org on 2023/05/29 04:09:55 UTC

[kyuubi-service-rpc] 01/02: initial commit

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

yao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/kyuubi-service-rpc.git

commit a4fe7ff681e97e7dae5fd9b7f4c6348b1ea88304
Author: Kent Yao <ya...@apache.org>
AuthorDate: Mon May 29 12:06:42 2023 +0800

    initial commit
---
 .gitignore                                         |  12 +
 .idea/.gitignore                                   |   8 +
 .idea/codeStyles/Project.xml                       |  53 +++
 .idea/codeStyles/codeStyleConfig.xml               |   5 +
 .idea/copyright/apache.xml                         |   6 +
 .idea/encodings.xml                                |  15 +
 .idea/jpa-buddy.xml                                |   6 +
 .idea/misc.xml                                     |  17 +
 .idea/protoeditor.xml                              |  47 ++
 .idea/rSettings.xml                                |   6 +
 .idea/sbt.xml                                      |   6 +
 .idea/scopes/apache.xml                            |   3 +
 .idea/thriftCompiler.xml                           |   6 +
 .idea/vcs.xml                                      |   6 +
 README.md                                          |   6 +
 health-proto/pom.xml                               |  80 ++++
 .../org/apache/kyuubi/grpc/health/health.proto     |  66 +++
 jdbc-grpc-client/pom.xml                           |  73 +++
 .../java/org/apache/kyuubi/grpc/GrpcUtils.java     |   5 +
 .../apache/kyuubi/grpc/client/JdbcGrpcClient.java  |  81 ++++
 .../grpc/client/SimpleBlockingJdbcClient.java      | 394 +++++++++++++++++
 .../org/apache/kyuubi/grpc/DummyJdbcService.java   |  40 ++
 .../apache/kyuubi/grpc/TestConnectionService.java  | 492 +++++++++++++++++++++
 .../SimpleBlockingJdbcServiceClientTest.java       | 261 +++++++++++
 jdbc-proto/README.md                               |  26 ++
 jdbc-proto/pom.xml                                 |  68 +++
 .../org/apache/kyuubi/grpc/common/errors.proto     |  43 ++
 .../org/apache/kyuubi/grpc/jdbc/connection.proto   | 300 +++++++++++++
 .../org/apache/kyuubi/grpc/jdbc/request.proto      | 345 +++++++++++++++
 .../org/apache/kyuubi/grpc/jdbc/response.proto     |  46 ++
 .../org/apache/kyuubi/grpc/jdbc/schema.proto       | 103 +++++
 .../org/apache/kyuubi/grpc/jdbc/service.proto      |  54 +++
 pom.xml                                            | 136 ++++++
 33 files changed, 2815 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f4bef8b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+.idea/
+.idea/codeStyles/*
+.idea/**/
+*.iml
+*.ipr
+*.iws
+*.tgz
+*.log
+*.log.*
+*.
+target/
+
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..ea95349
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,53 @@
+<component name="ProjectCodeStyleConfiguration">
+  <code_scheme name="Project" version="173">
+    <JetCodeStyleSettings>
+      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
+    </JetCodeStyleSettings>
+    <ScalaCodeStyleSettings>
+      <option name="addFullQualifiedImports" value="false" />
+      <option name="importLayout">
+        <array>
+          <option value="java" />
+          <option value="_______ blank line _______" />
+          <option value="scala" />
+          <option value="_______ blank line _______" />
+          <option value="all other imports" />
+          <option value="_______ blank line _______" />
+          <option value="org.apache.kyuubi.*" />
+          <option value="_______ blank line _______" />
+        </array>
+      </option>
+      <option name="sortAsScalastyle" value="true" />
+      <option name="SPACE_AFTER_MODIFIERS_CONSTRUCTOR" value="true" />
+      <option name="SPACE_BEFORE_INFIX_METHOD_CALL_PARENTHESES" value="true" />
+      <option name="SPACE_BEFORE_INFIX_OPERATOR_LIKE_METHOD_CALL_PARENTHESES" value="false" />
+    </ScalaCodeStyleSettings>
+    <editorconfig>
+      <option name="ENABLED" value="false" />
+    </editorconfig>
+    <codeStyleSettings language="JAVA">
+      <indentOptions>
+        <option name="INDENT_SIZE" value="2" />
+        <option name="CONTINUATION_INDENT_SIZE" value="2" />
+        <option name="TAB_SIZE" value="2" />
+      </indentOptions>
+    </codeStyleSettings>
+    <codeStyleSettings language="Scala">
+      <option name="KEEP_LINE_BREAKS" value="false" />
+      <option name="KEEP_BLANK_LINES_IN_CODE" value="0" />
+      <option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
+      <option name="BLANK_LINES_AROUND_CLASS" value="0" />
+      <arrangement>
+        <groups>
+          <group>
+            <type>DEPENDENT_METHODS</type>
+            <order>DEPTH_FIRST</order>
+          </group>
+        </groups>
+      </arrangement>
+    </codeStyleSettings>
+    <codeStyleSettings language="kotlin">
+      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
+    </codeStyleSettings>
+  </code_scheme>
+</component>
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+  <state>
+    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+  </state>
+</component>
\ No newline at end of file
diff --git a/.idea/copyright/apache.xml b/.idea/copyright/apache.xml
new file mode 100644
index 0000000..2cff3d1
--- /dev/null
+++ b/.idea/copyright/apache.xml
@@ -0,0 +1,6 @@
+<component name="CopyrightManager">
+  <copyright>
+    <option name="notice" value="Licensed to the Apache Software Foundation (ASF) under one or more&#10;contributor license agreements.  See the NOTICE file distributed with&#10;this work for additional information regarding copyright ownership.&#10;The ASF licenses this file to You under the Apache License, Version 2.0&#10;(the &quot;License&quot;); you may not use this file except in compliance with&#10;the License.  You may obtain a copy of the License at&#10;&#10;   http://www.apache [...]
+    <option name="myName" value="apache" />
+  </copyright>
+</component>
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..5c552d0
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding">
+    <file url="file://$PROJECT_DIR$/health-proto/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jdbc-grpc-client/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jdbc-grpc-client/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jdbc-grpc-client/target/classes/generated-sources/protobuf/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jdbc-proto/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/kyuubi-health-proto/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/kyuubi-jdbc-proto/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/kyuubi-service-grpc/target/generated-sources/protobuf/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/jpa-buddy.xml b/.idea/jpa-buddy.xml
new file mode 100644
index 0000000..966d5f5
--- /dev/null
+++ b/.idea/jpa-buddy.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="JpaBuddyIdeaProjectConfig">
+    <option name="renamerInitialized" value="true" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..010c400
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="MavenProjectsManager">
+    <option name="originalFiles">
+      <list>
+        <option value="$PROJECT_DIR$/pom.xml" />
+      </list>
+    </option>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/out" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="jpab" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/protoeditor.xml b/.idea/protoeditor.xml
new file mode 100644
index 0000000..e300808
--- /dev/null
+++ b/.idea/protoeditor.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProtobufLanguageSettings">
+    <option name="autoConfigEnabled" value="false" />
+    <option name="importPathEntries">
+      <list>
+        <ImportPathEntry>
+          <option name="location" value="file://$PROJECT_DIR$/kyuubi-service-grpc/src/main/resources" />
+        </ImportPathEntry>
+        <ImportPathEntry>
+          <option name="location" value="file://$PROJECT_DIR$/kyuubi-service-grpc/src/test/java" />
+        </ImportPathEntry>
+        <ImportPathEntry>
+          <option name="location" value="file://$PROJECT_DIR$/kyuubi-service-grpc/target/generated-sources/annotations" />
+        </ImportPathEntry>
+        <ImportPathEntry>
+          <option name="location" value="file://$PROJECT_DIR$/kyuubi-service-grpc/target/generated-sources/protobuf/java" />
+        </ImportPathEntry>
+        <ImportPathEntry>
+          <option name="location" value="file://$PROJECT_DIR$/kyuubi-service-grpc/target/generated-sources/protobuf/java" />
+        </ImportPathEntry>
+        <ImportPathEntry>
+          <option name="location" value="file://$PROJECT_DIR$/kyuubi-service-grpc/src/main/java" />
+        </ImportPathEntry>
+        <ImportPathEntry>
+          <option name="location" value="file://$PROJECT_DIR$/kyuubi-service-grpc/target/generated-test-sources/test-annotations" />
+        </ImportPathEntry>
+        <ImportPathEntry>
+          <option name="location" value="file://$PROJECT_DIR$/kyuubi-service-grpc/target/generated-sources/protobuf/grpc-java" />
+        </ImportPathEntry>
+        <ImportPathEntry>
+          <option name="location" value="file://$PROJECT_DIR$/../Library/Caches/JetBrains/IntelliJIdea2022.3/protoeditor" />
+        </ImportPathEntry>
+        <ImportPathEntry>
+          <option name="location" value="file://$PROJECT_DIR$/kyuubi-service-grpc/src/main/protobuf" />
+        </ImportPathEntry>
+        <ImportPathEntry>
+          <option name="location" value="file://$PROJECT_DIR$/kyuubi-jdbc-proto/src/main/protobuf" />
+        </ImportPathEntry>
+        <ImportPathEntry>
+          <option name="location" value="file://$PROJECT_DIR$/jdbc-proto/src/main/protobuf" />
+        </ImportPathEntry>
+      </list>
+    </option>
+    <option name="descriptorPath" value="google/protobuf/descriptor.proto" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/rSettings.xml b/.idea/rSettings.xml
new file mode 100644
index 0000000..6d7112b
--- /dev/null
+++ b/.idea/rSettings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RSettings">
+    <option name="interpreterPath" value="/usr/local/bin/R" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/sbt.xml b/.idea/sbt.xml
new file mode 100644
index 0000000..2018743
--- /dev/null
+++ b/.idea/sbt.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ScalaSbtSettings">
+    <option name="customVMPath" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/scopes/apache.xml b/.idea/scopes/apache.xml
new file mode 100644
index 0000000..1cca035
--- /dev/null
+++ b/.idea/scopes/apache.xml
@@ -0,0 +1,3 @@
+<component name="DependencyValidationManager">
+  <scope name="apache" pattern="" />
+</component>
\ No newline at end of file
diff --git a/.idea/thriftCompiler.xml b/.idea/thriftCompiler.xml
new file mode 100644
index 0000000..7bc123c
--- /dev/null
+++ b/.idea/thriftCompiler.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ThriftCompiler">
+    <compilers />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f7b794f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,6 @@
+# Apache Kyuubi Service RPC
+
+## Introduction
+
+Apache Kyuubi Service RPC is an RPC layer based on [Grpc](https://grpc.io/),
+It is designed to provide a unified RPC interface for different services in Kyuubi project.
diff --git a/health-proto/pom.xml b/health-proto/pom.xml
new file mode 100644
index 0000000..fe57525
--- /dev/null
+++ b/health-proto/pom.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.kyuubi</groupId>
+        <artifactId>kyuubi-service-rpc</artifactId>
+        <version>0.1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>kyuubi-health-proto</artifactId>
+    <name>Kyuubi Health Service Protobuf Definition</name>
+    <packaging>jar</packaging>
+    <dependencies>
+        <dependency>
+            <groupId>com.google.protobuf</groupId>
+            <artifactId>protobuf-java</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-netty</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-protobuf</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-services</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-stub</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.xolstice.maven.plugins</groupId>
+                <artifactId>protobuf-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>add-protoc-gen</id>
+                        <goals>
+                            <goal>add-source</goal>
+                        </goals>
+                        <phase>generate-sources</phase>
+                        <configuration>
+                            <sources>
+                                <source>${project.build.outputDirectory}/generated-sources/protobuf</source>
+                            </sources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
\ No newline at end of file
diff --git a/health-proto/src/main/protobuf/org/apache/kyuubi/grpc/health/health.proto b/health-proto/src/main/protobuf/org/apache/kyuubi/grpc/health/health.proto
new file mode 100644
index 0000000..3bf48e0
--- /dev/null
+++ b/health-proto/src/main/protobuf/org/apache/kyuubi/grpc/health/health.proto
@@ -0,0 +1,66 @@
+// 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.
+
+// E.g. https://github.com/grpc/grpc/blob/master/doc/health-checking.md
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_package = "org.apache.kyuubi.grpc.health";
+
+/* Request the services list that support reporting health status
+ * at the server-side
+ */
+message GetServicesReq {
+}
+
+message GetServicesResp {
+  repeated string services = 1;
+}
+
+message HealthCheckReq {
+  repeated string services = 1;
+}
+
+enum HealthStatus {
+  UNKNOWN = 0; // unable to do health check
+  HEALTHY = 1;
+  UNHEALTHY = 2;
+  NOT_FOUND = 3;
+}
+
+message HealthCheckResponse {
+  map<string, HealthStatus> statuses = 1;
+}
+
+service Health {
+  /* Request the services list that support reporting health status
+   * at the server-side
+   */
+  rpc GetServices(GetServicesReq) returns(GetServicesResp);
+
+  /* Query the health statuses of all registered services from the server-side.
+   * and a deadline should be set on the rpc.
+   * The client can optionally chose the service names(ids) it wants to query
+   * for health statuses.
+   */
+  rpc Check(HealthCheckReq) returns (HealthCheckResponse);
+
+  /* Perform a streaming health-check. The server will immediately send back a message
+   * indicating the current health status. It will then subsequently send a new message
+   * whenever the service's serving status changes.
+   */
+  rpc Watch(HealthCheckReq) returns (stream HealthCheckResponse);
+}
diff --git a/jdbc-grpc-client/pom.xml b/jdbc-grpc-client/pom.xml
new file mode 100644
index 0000000..95b8d8b
--- /dev/null
+++ b/jdbc-grpc-client/pom.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.kyuubi</groupId>
+        <artifactId>kyuubi-service-rpc</artifactId>
+        <version>0.1.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>kyuubi-jdbc-grpc-client</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.kyuubi</groupId>
+            <artifactId>kyuubi-jdbc-proto</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.protobuf</groupId>
+            <artifactId>protobuf-java</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-netty</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-protobuf</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-services</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-stub</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.13.2</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.h2database</groupId>
+            <artifactId>h2</artifactId>
+            <version>2.1.214</version>
+        </dependency>
+    </dependencies>
+
+
+</project>
\ No newline at end of file
diff --git a/jdbc-grpc-client/src/main/java/org/apache/kyuubi/grpc/GrpcUtils.java b/jdbc-grpc-client/src/main/java/org/apache/kyuubi/grpc/GrpcUtils.java
new file mode 100644
index 0000000..3a72e5d
--- /dev/null
+++ b/jdbc-grpc-client/src/main/java/org/apache/kyuubi/grpc/GrpcUtils.java
@@ -0,0 +1,5 @@
+package org.apache.kyuubi.grpc;
+
+public class GrpcUtils {
+  final static Status OK = Status.newBuilder().setStatusCode(StatusCode.OK).setSqlState("00000").build();
+}
diff --git a/jdbc-grpc-client/src/main/java/org/apache/kyuubi/grpc/client/JdbcGrpcClient.java b/jdbc-grpc-client/src/main/java/org/apache/kyuubi/grpc/client/JdbcGrpcClient.java
new file mode 100644
index 0000000..e310057
--- /dev/null
+++ b/jdbc-grpc-client/src/main/java/org/apache/kyuubi/grpc/client/JdbcGrpcClient.java
@@ -0,0 +1,81 @@
+package org.apache.kyuubi.grpc.client;
+
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.kyuubi.grpc.jdbc.DirectStatusResp;
+import org.apache.kyuubi.grpc.jdbc.connection.*;
+
+public interface JdbcGrpcClient {
+  DirectStatusResp openConnection(Map<String, String> configs, Optional<String> connectionId);
+
+  DirectStatusResp closeConnection(String connectionId);
+
+  DirectStatusResp abortConnection(String connectionId);
+
+  DirectStatusResp setAutoCommit(String connectionId, boolean autoCommit);
+
+  GetAutoCommitResp getAutoCommit(String connectionId);
+
+  NativeSQLResp nativeSQL(String connectionId, String sql);
+
+  DirectStatusResp commit(String connectionId);
+
+  DirectStatusResp rollback(String connectionId);
+
+  DirectStatusResp rollback(String connectionId, int savepointId);
+
+  DirectStatusResp rollback(String connectionId, String savepointName);
+
+  DirectStatusResp rollback(String connectionId, int savepointId, String savepointName);
+
+  DirectStatusResp setReadOnly(String connectionId, boolean readOnly);
+
+  IsReadOnlyResp isReadOnly(String connectionId);
+
+  DirectStatusResp setCatalog(String connectionId, String catalog);
+
+  GetCatalogResp getCatalog(String connectionId);
+
+  DirectStatusResp setTransactionIsolation(String connectionId, int level);
+
+  GetTransactionIsolationResp getTransactionIsolation(String connectionId);
+
+  GetWarningsResp getWarnings(String connectionId);
+
+  DirectStatusResp clearWarnings(String connectionId);
+
+  DirectStatusResp setClientInfo(String connectionId, Map<String, String> info);
+
+  DirectStatusResp setClientInfo(String connectionId, String name, String value);
+
+  GetClientInfoResp getClientInfo(String connectionId);
+
+  DirectStatusResp setTypeMap(String connectionId, Map<String, String> map);
+
+  GetTypeMapResp getTypeMap(String connectionId);
+
+  DirectStatusResp setHoldability(String connectionId, int holdability);
+
+  GetHoldabilityResp getHoldability(String connectionId);
+
+  DirectStatusResp setSchema(String connectionId, String schema);
+
+  GetSchemaResp getSchema(String connectionId);
+
+  DirectStatusResp setNetworkTimeout(String connectionId, int milliseconds);
+
+  GetNetworkTimeoutResp getNetworkTimeout(String connectionId);
+
+  SetSavepointResp setSavepoint(String connectionId);
+
+  SetSavepointResp setSavepoint(String connectionId, String name);
+
+  DirectStatusResp releaseSavepoint(String connectionId, Savepoint savepoint);
+
+  DirectStatusResp setSchema(String connectionId, String schema, String catalog);
+
+  IsValidResp isValid(String connectionId, int timeout);
+
+  DirectStatusResp getCatalogs(String connectionId);
+}
diff --git a/jdbc-grpc-client/src/main/java/org/apache/kyuubi/grpc/client/SimpleBlockingJdbcClient.java b/jdbc-grpc-client/src/main/java/org/apache/kyuubi/grpc/client/SimpleBlockingJdbcClient.java
new file mode 100644
index 0000000..a132594
--- /dev/null
+++ b/jdbc-grpc-client/src/main/java/org/apache/kyuubi/grpc/client/SimpleBlockingJdbcClient.java
@@ -0,0 +1,394 @@
+package org.apache.kyuubi.grpc.client;
+
+import io.grpc.*;
+import org.apache.kyuubi.grpc.jdbc.*;
+import org.apache.kyuubi.grpc.jdbc.JdbcGrpc.JdbcBlockingStub;
+import org.apache.kyuubi.grpc.jdbc.connection.*;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+public class SimpleBlockingJdbcClient implements JdbcGrpcClient {
+  private final Logger logger = Logger.getLogger(this.getClass().getName());
+  private JdbcBlockingStub blockingStub = null;
+  private ConnectionGrpc.ConnectionBlockingStub connectionBlockingStub = null;
+
+  public SimpleBlockingJdbcClient(Channel channel) {
+    blockingStub = JdbcGrpc.newBlockingStub(channel);
+    connectionBlockingStub = ConnectionGrpc.newBlockingStub(channel);
+  }
+
+  public SimpleBlockingJdbcClient(ManagedChannelBuilder builder) {
+    this(builder.build());
+  }
+
+  public SimpleBlockingJdbcClient(String host, int port, ChannelCredentials creds) {
+    this(Grpc.newChannelBuilderForAddress(host, port, creds));
+  }
+
+  public SimpleBlockingJdbcClient(String host, int port) {
+    this(host, port, InsecureChannelCredentials.create());
+  }
+
+  public SimpleBlockingJdbcClient(int port) {
+    this("localhost", port);
+  }
+
+  @Override
+  public DirectStatusResp openConnection(
+    Map<String, String> configs,
+    Optional<String> connectionId) {
+    OpenConnectionReq.Builder builder = OpenConnectionReq.newBuilder();
+    if (connectionId.isPresent()) {
+      logger.info("Reconnecting to server with existing connection" + connectionId.get());
+      builder.setConnectionId(connectionId.get());
+    }
+    OpenConnectionReq req = builder
+      .putAllConfigs(configs)
+      .build();
+    return connectionBlockingStub.openConnection(req);
+  }
+
+  @Override
+  public DirectStatusResp closeConnection(String connectionId) {
+    CloseConnectionReq.Builder builder = CloseConnectionReq.newBuilder();
+    CloseConnectionReq req = builder
+      .setConnectionId(connectionId)
+      .build();
+    return connectionBlockingStub.closeConnection(req);
+  }
+
+  @Override
+  public DirectStatusResp abortConnection(String connectionId) {
+    AbortConnectionReq.Builder builder = AbortConnectionReq.newBuilder();
+    AbortConnectionReq req = builder
+      .setConnectionId(connectionId)
+      .build();
+    return connectionBlockingStub.abortConnection(req);
+  }
+
+  @Override
+  public DirectStatusResp setClientInfo(String connectionId, Map<String, String> info) {
+    SetClientInfoReq.Builder builder = SetClientInfoReq.newBuilder();
+    SetClientInfoReq req = builder
+      .setConnectionId(connectionId)
+      .putAllConfigs(info)
+      .build();
+    return connectionBlockingStub.setClientInfo(req);
+  }
+
+  @Override
+  public DirectStatusResp setClientInfo(String connectionId, String name, String value) {
+    SetClientInfoReq.Builder builder = SetClientInfoReq.newBuilder();
+    SetClientInfoReq req = builder
+      .setConnectionId(connectionId)
+      .putConfigs(name, value)
+      .build();
+    return connectionBlockingStub.setClientInfo(req);
+  }
+
+  @Override
+  public GetClientInfoResp getClientInfo(String connectionId) {
+    GetClientInfoReq.Builder builder = GetClientInfoReq.newBuilder();
+    GetClientInfoReq req = builder
+      .setConnectionId(connectionId)
+      .build();
+    return connectionBlockingStub.getClientInfo(req);
+  }
+
+  @Override
+  public DirectStatusResp setTypeMap(String connectionId, Map<String, String> map) {
+    SetTypeMapReq.Builder builder = SetTypeMapReq.newBuilder();
+    SetTypeMapReq req = builder
+      .setConnectionId(connectionId)
+      .putAllTypeToClass(map)
+      .build();
+    return connectionBlockingStub.setTypeMap(req);
+  }
+
+  @Override
+  public GetTypeMapResp getTypeMap(String connectionId) {
+    GetTypeMapReq.Builder builder = GetTypeMapReq.newBuilder();
+    GetTypeMapReq req = builder
+      .setConnectionId(connectionId)
+      .build();
+    return connectionBlockingStub.getTypeMap(req);
+  }
+
+  @Override
+  public DirectStatusResp setHoldability(String connectionId, int holdability) {
+    SetHoldabilityReq.Builder builder = SetHoldabilityReq.newBuilder();
+    SetHoldabilityReq req = builder
+      .setConnectionId(connectionId)
+      .setHoldability(holdability)
+      .build();
+    return connectionBlockingStub.setHoldability(req);
+  }
+
+  @Override
+  public GetHoldabilityResp getHoldability(String connectionId) {
+    GetHoldabilityReq.Builder builder = GetHoldabilityReq.newBuilder();
+    GetHoldabilityReq req = builder
+      .setConnectionId(connectionId)
+      .build();
+    return connectionBlockingStub.getHoldability(req);
+  }
+
+  @Override
+  public DirectStatusResp setSchema(String connectionId, String schema) {
+    SetSchemaReq.Builder builder = SetSchemaReq.newBuilder();
+    SetSchemaReq req = builder
+      .setConnectionId(connectionId)
+      .setSchema(schema)
+      .build();
+    return connectionBlockingStub.setSchema(req);
+  }
+
+  @Override
+  public GetSchemaResp getSchema(String connectionId) {
+    GetSchemaReq.Builder builder = GetSchemaReq.newBuilder();
+    GetSchemaReq req = builder
+      .setConnectionId(connectionId)
+      .build();
+    return connectionBlockingStub.getSchema(req);
+  }
+
+  @Override
+  public DirectStatusResp setNetworkTimeout(String connectionId, int milliseconds) {
+    SetNetworkTimeoutReq.Builder builder = SetNetworkTimeoutReq.newBuilder();
+    SetNetworkTimeoutReq req = builder
+      .setConnectionId(connectionId)
+      .setMilliseconds(milliseconds)
+      .build();
+    return connectionBlockingStub.setNetworkTimeout(req);
+  }
+
+  @Override
+  public GetNetworkTimeoutResp getNetworkTimeout(String connectionId) {
+    GetNetworkTimeoutReq.Builder builder = GetNetworkTimeoutReq.newBuilder();
+    GetNetworkTimeoutReq req = builder
+      .setConnectionId(connectionId)
+      .build();
+    return connectionBlockingStub.getNetworkTimeout(req);
+  }
+
+  @Override
+  public SetSavepointResp setSavepoint(String connectionId) {
+    SetSavepointReq.Builder builder = SetSavepointReq.newBuilder();
+    SetSavepointReq req = builder
+      .setConnectionId(connectionId)
+      .build();
+    return connectionBlockingStub.setSavepoint(req);
+  }
+
+  @Override
+  public SetSavepointResp setSavepoint(String connectionId, String name) {
+    SetSavepointReq.Builder builder = SetSavepointReq.newBuilder();
+    SetSavepointReq req = builder
+      .setConnectionId(connectionId)
+      .setSavepointName(name)
+      .build();
+    return connectionBlockingStub.setSavepoint(req);
+  }
+
+  @Override
+  public DirectStatusResp releaseSavepoint(String connectionId, Savepoint savepoint) {
+    ReleaseSavepointReq.Builder builder = ReleaseSavepointReq.newBuilder();
+    ReleaseSavepointReq req = builder
+      .setConnectionId(connectionId)
+      .setSavepoint(savepoint)
+      .build();
+    return connectionBlockingStub.releaseSavepoint(req);
+  }
+
+
+  @Override
+  public DirectStatusResp setSchema(String connectionId, String schema, String catalog) {
+    SetSchemaReq.Builder builder = SetSchemaReq.newBuilder();
+    SetSchemaReq req = builder
+      .setConnectionId(connectionId)
+      .setSchema(schema)
+      .build();
+    return connectionBlockingStub.setSchema(req);
+  }
+
+  @Override
+  public IsValidResp isValid(String connectionId, int timeout) {
+    IsValidReq.Builder builder = IsValidReq.newBuilder();
+    IsValidReq req = builder
+      .setConnectionId(connectionId)
+      .setTimeout(timeout)
+      .build();
+    return connectionBlockingStub.isValid(req);
+  }
+
+  @Override
+  public NativeSQLResp nativeSQL(String connectionId, String sql) {
+    NativeSQLReq.Builder builder = NativeSQLReq.newBuilder();
+    NativeSQLReq req = builder
+      .setConnectionId(connectionId)
+      .setSql(sql)
+      .build();
+    return connectionBlockingStub.nativeSQL(req);
+  }
+
+  @Override
+  public DirectStatusResp setAutoCommit(String connectionId, boolean autoCommit) {
+    SetAutoCommitReq.Builder builder = SetAutoCommitReq.newBuilder();
+    SetAutoCommitReq req = builder
+      .setConnectionId(connectionId)
+      .setAutoCommit(autoCommit)
+      .build();
+    return connectionBlockingStub.setAutoCommit(req);
+  }
+
+  @Override
+  public GetAutoCommitResp getAutoCommit(String connectionId) {
+    GetAutoCommitReq.Builder builder = GetAutoCommitReq.newBuilder();
+    GetAutoCommitReq req = builder
+      .setConnectionId(connectionId)
+      .build();
+    return connectionBlockingStub.getAutoCommit(req);
+  }
+
+  @Override
+  public DirectStatusResp commit(String connectionId) {
+    CommitReq.Builder builder = CommitReq.newBuilder();
+    CommitReq req = builder
+      .setConnectionId(connectionId)
+      .build();
+    return connectionBlockingStub.commit(req);
+  }
+
+  @Override
+  public DirectStatusResp rollback(String connectionId) {
+    RollbackReq.Builder builder = RollbackReq.newBuilder();
+    RollbackReq req = builder
+      .setConnectionId(connectionId)
+      .build();
+    return connectionBlockingStub.rollback(req);
+  }
+
+  @Override
+  public DirectStatusResp rollback(String connectionId, int savepointId) {
+    Savepoint sp = Savepoint.newBuilder()
+      .setSavepointId(savepointId)
+      .build();
+    RollbackReq req = RollbackReq.newBuilder()
+      .setConnectionId(connectionId)
+      .setSavepoint(sp)
+      .build();
+    return connectionBlockingStub.rollback(req);
+  }
+
+  @Override
+  public DirectStatusResp rollback(String connectionId, int savepointId, String savepointName) {
+    Savepoint sp = Savepoint.newBuilder()
+      .setSavepointId(savepointId)
+      .setSavepointName(savepointName)
+      .build();
+    RollbackReq req = RollbackReq.newBuilder()
+      .setConnectionId(connectionId)
+      .setSavepoint(sp)
+      .build();
+    return connectionBlockingStub.rollback(req);
+  }
+
+  @Override
+  public DirectStatusResp rollback(String connectionId, String savepointName) {
+    Savepoint sp = Savepoint.newBuilder()
+      .setSavepointName(savepointName)
+      .build();
+    RollbackReq req = RollbackReq.newBuilder()
+      .setConnectionId(connectionId)
+      .setSavepoint(sp)
+      .build();
+    return connectionBlockingStub.rollback(req);
+  }
+
+  @Override
+  public DirectStatusResp setReadOnly(String connectionId, boolean readOnly) {
+    SetReadOnlyReq.Builder builder = SetReadOnlyReq.newBuilder();
+    SetReadOnlyReq req = builder
+      .setConnectionId(connectionId)
+      .setReadOnly(readOnly)
+      .build();
+    return connectionBlockingStub.setReadOnly(req);
+  }
+
+  @Override
+  public IsReadOnlyResp isReadOnly(String connectionId) {
+    IsReadOnlyReq.Builder builder = IsReadOnlyReq.newBuilder();
+    IsReadOnlyReq req = builder
+      .setConnectionId(connectionId)
+      .build();
+    return connectionBlockingStub.isReadOnly(req);
+  }
+
+  @Override
+  public DirectStatusResp setCatalog(String connectionId, String catalog) {
+    SetCatalogReq.Builder builder = SetCatalogReq.newBuilder();
+    SetCatalogReq req = builder
+      .setConnectionId(connectionId)
+      .setCatalog(catalog)
+      .build();
+    return connectionBlockingStub.setCatalog(req);
+  }
+
+  @Override
+  public GetCatalogResp getCatalog(String connectionId) {
+    GetCatalogReq.Builder builder = GetCatalogReq.newBuilder();
+    GetCatalogReq req = builder
+      .setConnectionId(connectionId)
+      .build();
+    return connectionBlockingStub.getCatalog(req);
+  }
+
+
+  @Override
+  public DirectStatusResp setTransactionIsolation(String connectionId, int level) {
+    SetTransactionIsolationReq.Builder builder = SetTransactionIsolationReq.newBuilder();
+    SetTransactionIsolationReq req = builder
+      .setConnectionId(connectionId)
+      .setLevel(level)
+      .build();
+    return connectionBlockingStub.setTransactionIsolation(req);
+  }
+
+  @Override
+  public GetTransactionIsolationResp getTransactionIsolation(String connectionId) {
+    GetTransactionIsolationReq.Builder builder = GetTransactionIsolationReq.newBuilder();
+    GetTransactionIsolationReq req = builder
+      .setConnectionId(connectionId)
+      .build();
+    return connectionBlockingStub.getTransactionIsolation(req);
+  }
+
+  @Override
+  public DirectStatusResp clearWarnings(String connectionId) {
+    ClearWarningsReq.Builder builder = ClearWarningsReq.newBuilder();
+    ClearWarningsReq req = builder
+      .setConnectionId(connectionId)
+      .build();
+    return connectionBlockingStub.clearWarnings(req);
+  }
+
+  @Override
+  public GetWarningsResp getWarnings(String connectionId) {
+    GetWarningsReq.Builder builder = GetWarningsReq.newBuilder();
+    GetWarningsReq req = builder
+      .setConnectionId(connectionId)
+      .build();
+    return connectionBlockingStub.getWarnings(req);
+  }
+
+  @Override
+  public DirectStatusResp getCatalogs(String connectionId) {
+    GetCatalogsReq.Builder builder = GetCatalogsReq.newBuilder();
+    GetCatalogsReq req = builder
+      .setConnectionId(connectionId)
+      .build();
+    return blockingStub.getCatalogs(req);
+  }
+}
diff --git a/jdbc-grpc-client/src/test/java/org/apache/kyuubi/grpc/DummyJdbcService.java b/jdbc-grpc-client/src/test/java/org/apache/kyuubi/grpc/DummyJdbcService.java
new file mode 100644
index 0000000..6b30546
--- /dev/null
+++ b/jdbc-grpc-client/src/test/java/org/apache/kyuubi/grpc/DummyJdbcService.java
@@ -0,0 +1,40 @@
+package org.apache.kyuubi.grpc;
+
+import io.grpc.stub.StreamObserver;
+import org.apache.kyuubi.grpc.jdbc.DirectStatusResp;
+import org.apache.kyuubi.grpc.jdbc.GetCatalogsReq;
+import org.apache.kyuubi.grpc.jdbc.JdbcGrpc.JdbcImplBase;
+
+import java.io.IOException;
+
+import static org.apache.kyuubi.grpc.GrpcUtils.OK;
+
+public class DummyJdbcService extends JdbcImplBase {
+
+
+  public DummyJdbcService() throws IOException {
+  }
+
+
+
+  @Override
+  public void getCatalogs(GetCatalogsReq req, StreamObserver<DirectStatusResp> respOb) {
+    DirectStatusResp.Builder builder = DirectStatusResp.newBuilder();
+    try {
+      if (req.getConnectionId().isEmpty()) {
+        throw new IllegalArgumentException("Connection Id can not be empty");
+      } else {
+        builder.setIdentifier("apache kyuubi").setStatus(OK);
+      }
+    } catch (Exception e) {
+      Status status = Status.newBuilder()
+        .setStatusCode(StatusCode.ERROR)
+        .setSqlState("2E000")
+        .setErrorMessage("invalid connection id" + req.getConnectionId())
+        .build();
+      builder.setStatus(status);
+    }
+    respOb.onNext(builder.build());
+    respOb.onCompleted();
+  }
+}
diff --git a/jdbc-grpc-client/src/test/java/org/apache/kyuubi/grpc/TestConnectionService.java b/jdbc-grpc-client/src/test/java/org/apache/kyuubi/grpc/TestConnectionService.java
new file mode 100644
index 0000000..1a9617b
--- /dev/null
+++ b/jdbc-grpc-client/src/test/java/org/apache/kyuubi/grpc/TestConnectionService.java
@@ -0,0 +1,492 @@
+package org.apache.kyuubi.grpc;
+
+
+import io.grpc.stub.StreamObserver;
+import org.apache.kyuubi.grpc.jdbc.*;
+import org.apache.kyuubi.grpc.jdbc.connection.ConnectionGrpc;
+import org.apache.kyuubi.grpc.jdbc.connection.*;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+import static org.apache.kyuubi.grpc.GrpcUtils.OK;
+
+public class TestConnectionService extends ConnectionGrpc.ConnectionImplBase {
+
+  private final Path _tempDir = Files.createTempDirectory(getClass().getSimpleName());
+
+  public String defaultCatalogName = _tempDir.getFileName().toString().toUpperCase();
+  private final String jdbcUrl = "jdbc:h2:" + _tempDir + ";MODE=DB2;user=testUser;password=testPass";
+  private Connection _connection = null;
+
+  private final Executor executor = Executors.newSingleThreadExecutor();
+
+  private final Map<Savepoint, java.sql.Savepoint> savepoints = new HashMap<>();
+
+  public void setConnection(Connection _connection) {
+    this._connection = _connection;
+  }
+
+  public Connection getConnection() throws SQLException {
+    if (this._connection == null) {
+      Connection conn = DriverManager.getConnection(jdbcUrl);
+      setConnection(conn);
+      return conn;
+    } else {
+      return _connection;
+    }
+  }
+
+  public TestConnectionService() throws IOException {
+  }
+
+  private Status errorStatus(Exception e) {
+    return Status.newBuilder()
+      .setSqlState("38808")
+      .setErrorMessage(e.getMessage())
+      .setStatusCode(StatusCode.ERROR)
+      .build();
+  }
+
+  private DirectStatusResp error(Exception e) {
+    return DirectStatusResp
+      .newBuilder()
+      .setStatus(errorStatus(e))
+      .build();
+  }
+
+  private DirectStatusResp ok(String id) {
+    return DirectStatusResp
+      .newBuilder()
+      .setStatus(OK)
+      .setIdentifier(id)
+      .build();
+  }
+
+  @Override
+  public void openConnection(OpenConnectionReq req, StreamObserver<DirectStatusResp> respOb) {
+    DirectStatusResp.Builder builder = DirectStatusResp.newBuilder();
+    if (req.getConnectionId().isEmpty()) {
+      builder.setIdentifier("hello, kyuubi");
+    } else {
+      builder.setIdentifier("hello, " + req.getConnectionId());
+    }
+    try {
+      Properties properties = new Properties();
+      properties.putAll(req.getConfigsMap());
+      setConnection(
+        DriverManager.getConnection(jdbcUrl, properties));
+      builder.putExtraInfo("apache", "kyuubi");
+      builder.putExtraInfo("Kyuubi", "Serverless SQL on Lakehouse");
+      builder.setStatus(OK);
+      respOb.onNext(builder.build());
+    } catch (SQLException e) {
+      Status status = Status
+        .newBuilder()
+        .setStatusCode(StatusCode.ERROR)
+        .setErrorMessage(e.getMessage())
+        .setSqlState("0AS86")
+        .build();
+      builder.setStatus(status);
+      respOb.onNext(builder.build());
+    }
+
+    respOb.onCompleted();
+  }
+
+  @Override
+  public void closeConnection(CloseConnectionReq req, StreamObserver<DirectStatusResp> respOb) {
+    DirectStatusResp.Builder builder = DirectStatusResp.newBuilder();
+    try {
+      if (req.getConnectionId().isEmpty()) {
+        throw new IllegalArgumentException("ConnectionId cannot be empty for CloseConnection");
+      } else {
+        builder.setIdentifier(req.getConnectionId());
+      }
+      builder.setStatus(OK);
+    } catch (Exception e) {
+      Status status = Status.newBuilder()
+        .setStatusCode(StatusCode.ERROR)
+        .setSqlState("2E000")
+        .setErrorMessage("invalid connection id" + req.getConnectionId())
+        .build();
+      builder.setStatus(status);
+    }
+    respOb.onNext(builder.build());
+    respOb.onCompleted();
+  }
+
+  @Override
+  public void nativeSQL(NativeSQLReq request, StreamObserver<NativeSQLResp> responseObserver) {
+    NativeSQLResp.Builder builder = NativeSQLResp.newBuilder();
+    try {
+      String nativeSQL = getConnection().nativeSQL(request.getSql());
+      NativeSQLResp resp = builder
+        .setSql(nativeSQL)
+        .setStatus(OK)
+        .build();
+      responseObserver.onNext(resp);
+    } catch (Exception e) {
+      Status status = Status.newBuilder()
+        .setSqlState("38808")
+        .setErrorMessage(e.getMessage())
+        .setStatusCode(StatusCode.ERROR)
+        .build();
+      NativeSQLResp resp = builder.setStatus(status).build();
+      responseObserver.onNext(resp);
+    }
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void setAutoCommit(SetAutoCommitReq request, StreamObserver<DirectStatusResp> responseObserver) {
+    try {
+      getConnection().setAutoCommit(request.getAutoCommit());
+      responseObserver.onNext(ok(request.getConnectionId()));
+    } catch (SQLException e) {
+      responseObserver.onNext(error(e));
+    }
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void getAutoCommit(GetAutoCommitReq request, StreamObserver<GetAutoCommitResp> responseObserver) {
+    GetAutoCommitResp.Builder builder = GetAutoCommitResp.newBuilder();
+    try {
+      boolean autoCommit = getConnection().getAutoCommit();
+      GetAutoCommitResp resp = builder
+        .setStatus(OK)
+        .setAutoCommit(autoCommit)
+        .build();
+      responseObserver.onNext(resp);
+    } catch (SQLException e) {
+      responseObserver.onNext(builder.setStatus(errorStatus(e)).build());
+    }
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void commit(CommitReq request, StreamObserver<DirectStatusResp> responseObserver) {
+    try {
+      getConnection().commit();
+      responseObserver.onNext(ok(request.getConnectionId()));
+    } catch (SQLException e) {
+      responseObserver.onNext(error(e));
+    }
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void rollback(RollbackReq request, StreamObserver<DirectStatusResp> responseObserver) {
+    try {
+      Connection conn = getConnection();
+      Savepoint savepoint = request.getSavepoint();
+      if (savepoint == Savepoint.getDefaultInstance()) {
+        conn.rollback();
+      } else {
+        conn.rollback(savepoints.get(savepoint));
+      }
+      responseObserver.onNext(ok(request.getConnectionId()));
+    } catch (SQLException e) {
+      responseObserver.onNext(error(e));
+    }
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void setReadOnly(SetReadOnlyReq request, StreamObserver<DirectStatusResp> responseObserver) {
+    try {
+      getConnection().setReadOnly(request.getReadOnly());
+      responseObserver.onNext(ok(request.getConnectionId()));
+    } catch (SQLException e) {
+      responseObserver.onNext(error(e));
+    }
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void isReadOnly(IsReadOnlyReq request, StreamObserver<IsReadOnlyResp> responseObserver) {
+    IsReadOnlyResp.Builder builder = IsReadOnlyResp.newBuilder();
+    try {
+      IsReadOnlyResp resp = builder
+        .setStatus(OK)
+        .setReadOnly(getConnection().isReadOnly())
+        .build();
+      responseObserver.onNext(resp);
+    } catch (SQLException e) {
+      IsReadOnlyResp resp = builder
+        .setStatus(errorStatus(e))
+        .build();
+      responseObserver.onNext(resp);
+    }
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void setCatalog(SetCatalogReq request, StreamObserver<DirectStatusResp> responseObserver) {
+    try {
+      getConnection().setCatalog(request.getCatalog());
+      responseObserver.onNext(ok(request.getConnectionId()));
+    } catch (SQLException e) {
+      responseObserver.onNext(error(e));
+    }
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void getCatalog(GetCatalogReq request, StreamObserver<GetCatalogResp> responseObserver) {
+    GetCatalogResp.Builder builder = GetCatalogResp.newBuilder();
+    try {
+      String catalog = getConnection().getCatalog();
+      GetCatalogResp resp = builder
+        .setStatus(OK)
+        .setCatalog(catalog)
+        .build();
+      responseObserver.onNext(resp);
+    } catch (SQLException e) {
+      GetCatalogResp resp = GetCatalogResp.newBuilder()
+        .setStatus(errorStatus(e))
+        .build();
+      responseObserver.onNext(resp);
+    }
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void setTransactionIsolation(SetTransactionIsolationReq request, StreamObserver<DirectStatusResp> responseObserver) {
+    try {
+      getConnection().setTransactionIsolation(request.getLevel());
+      responseObserver.onNext(ok(request.getConnectionId()));
+    } catch (SQLException e) {
+      responseObserver.onNext(error(e));
+    }
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void getTransactionIsolation(GetTransactionIsolationReq request, StreamObserver<GetTransactionIsolationResp> responseObserver) {
+    GetTransactionIsolationResp.Builder builder = GetTransactionIsolationResp.newBuilder();
+    try {
+      int level = getConnection().getTransactionIsolation();
+      GetTransactionIsolationResp resp = builder
+        .setStatus(OK)
+        .setLevel(level)
+        .build();
+      responseObserver.onNext(resp);
+    } catch (SQLException e) {
+      GetTransactionIsolationResp resp = builder
+        .setStatus(errorStatus(e))
+        .build();
+      responseObserver.onNext(resp);
+    }
+    responseObserver.onCompleted();
+  }
+
+  public static SQLWarning buildWarning(java.sql.SQLWarning cur) {
+    SQLWarning.Builder builder = SQLWarning.newBuilder();
+    if (cur.getMessage() != null) {
+      builder.setReason(cur.getMessage());
+    }
+    if (cur.getSQLState() != null) {
+      builder.setSqlState(cur.getSQLState());
+    }
+    if (cur.getErrorCode() != 0) {
+      builder.setVendorCode(cur.getErrorCode());
+    }
+    if (cur.getNextWarning() != null) {
+      builder.setNextWarning(buildWarning(cur.getNextWarning()));
+    }
+    return builder.build();
+  }
+  @Override
+  public void getWarnings(GetWarningsReq request, StreamObserver<GetWarningsResp> responseObserver) {
+    GetWarningsResp.Builder builder = GetWarningsResp.newBuilder();
+    try {
+      java.sql.SQLWarning warnings = getConnection().getWarnings();
+
+      if (warnings != null) {
+        builder.setWarnings(buildWarning(getConnection().getWarnings()));
+      }
+      responseObserver.onNext(builder.setStatus(OK).build());
+    } catch (SQLException e) {
+      responseObserver.onNext(builder.setStatus(errorStatus(e)).build());
+    }
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void clearWarnings(ClearWarningsReq request, StreamObserver<DirectStatusResp> responseObserver) {
+    try {
+      getConnection().clearWarnings();
+      responseObserver.onNext(ok(request.getConnectionId()));
+    } catch (SQLException e) {
+      responseObserver.onNext(error(e));
+    }
+    responseObserver.onCompleted();
+  }
+
+
+  @Override
+  public void setTypeMap(SetTypeMapReq request, StreamObserver<DirectStatusResp> responseObserver) {
+    try {
+      Map<String, Class<?>> classMap = new HashMap<String, Class<?>>();
+      for (Map.Entry<String, String> entry : request.getTypeToClassMap().entrySet()) {
+        classMap.put(entry.getKey(), Class.forName(entry.getValue()));
+      }
+      getConnection().setTypeMap(classMap);
+      responseObserver.onNext(ok(request.getConnectionId()));
+    } catch (Exception e) {
+      responseObserver.onNext(error(e));
+    }
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void getTypeMap(GetTypeMapReq request, StreamObserver<GetTypeMapResp> responseObserver) {
+    GetTypeMapResp.Builder builder = GetTypeMapResp.newBuilder();
+    try {
+      Map<String, Class<?>> typeMap = getConnection().getTypeMap();
+      if (typeMap != null) {
+        Map<String, String> typeToClassMap = new HashMap<String, String>();
+        for (Map.Entry<String, Class<?>> entry : typeMap.entrySet()) {
+          typeToClassMap.put(entry.getKey(), entry.getValue().getName());
+        }
+        builder.putAllTypeToClass(typeToClassMap);
+      }
+      responseObserver.onNext(builder.setStatus(OK).build());
+    } catch (SQLException e) {
+      responseObserver.onNext(builder.setStatus(errorStatus(e)).build());
+    }
+    responseObserver.onCompleted();
+
+  }
+
+  @Override
+  public void setSchema(SetSchemaReq request, StreamObserver<DirectStatusResp> responseObserver) {
+    try {
+      getConnection().setSchema(request.getSchema());
+      responseObserver.onNext(ok(request.getConnectionId()));
+    } catch (SQLException e) {
+      responseObserver.onNext(error(e));
+    }
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void getSchema(GetSchemaReq request, StreamObserver<GetSchemaResp> responseObserver) {
+    GetSchemaResp.Builder builder = GetSchemaResp.newBuilder();
+    try {
+      String schema = getConnection().getSchema();
+      // schema can be null, can you verify this?
+      GetSchemaResp resp = builder
+        .setStatus(OK)
+        .setSchema(schema)
+        .build();
+      responseObserver.onNext(resp);
+    } catch (SQLException e) {
+      GetSchemaResp resp = builder
+        .setStatus(errorStatus(e))
+        .build();
+      responseObserver.onNext(resp);
+    }
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void setNetworkTimeout(SetNetworkTimeoutReq request, StreamObserver<DirectStatusResp> responseObserver) {
+     try {
+        getConnection().setNetworkTimeout(executor, request.getMilliseconds());
+        responseObserver.onNext(ok(request.getConnectionId()));
+      } catch (SQLException e) {
+        responseObserver.onNext(error(e));
+      }
+      responseObserver.onCompleted();
+  }
+
+  @Override
+  public void getNetworkTimeout(GetNetworkTimeoutReq request, StreamObserver<GetNetworkTimeoutResp> responseObserver) {
+    GetNetworkTimeoutResp.Builder builder = GetNetworkTimeoutResp.newBuilder();
+    try {
+      int timeout = getConnection().getNetworkTimeout();
+      GetNetworkTimeoutResp resp = builder
+        .setStatus(OK)
+        .setMilliseconds(timeout)
+        .build();
+      responseObserver.onNext(resp);
+    } catch (SQLException e) {
+      GetNetworkTimeoutResp resp = builder
+        .setStatus(errorStatus(e))
+        .build();
+      responseObserver.onNext(resp);
+    }
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void isValid(IsValidReq request, StreamObserver<IsValidResp> responseObserver) {
+    IsValidResp.Builder builder = IsValidResp.newBuilder();
+    try {
+      boolean valid = getConnection().isValid(request.getTimeout());
+      IsValidResp resp = builder
+        .setStatus(OK)
+        .setValid(valid)
+        .build();
+      responseObserver.onNext(resp);
+    } catch (SQLException e) {
+      IsValidResp resp = builder
+        .setStatus(errorStatus(e))
+        .build();
+      responseObserver.onNext(resp);
+    }
+  }
+
+  @Override
+  public void abortConnection(AbortConnectionReq request, StreamObserver<DirectStatusResp> responseObserver) {
+    try {
+      getConnection().abort(executor);
+      responseObserver.onNext(ok(request.getConnectionId()));
+    } catch (SQLException e) {
+      responseObserver.onNext(error(e));
+    }
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void setClientInfo(SetClientInfoReq request, StreamObserver<DirectStatusResp> responseObserver) {
+    try {
+      Properties properties = new Properties();
+      properties.putAll(request.getConfigsMap());
+      getConnection().setClientInfo(properties);
+      responseObserver.onNext(ok(request.getConnectionId()));
+    } catch (SQLException e) {
+      responseObserver.onNext(error(e));
+    }
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void getClientInfo(GetClientInfoReq request, StreamObserver<GetClientInfoResp> responseObserver) {
+    GetClientInfoResp.Builder builder = GetClientInfoResp.newBuilder();
+    try {
+      Properties properties = getConnection().getClientInfo();
+      // use a loop to put all properties into the builder
+      for (Map.Entry<Object, Object> entry : properties.entrySet()) {
+        builder.putConfigs(entry.getKey().toString(), entry.getValue().toString());
+      }
+      responseObserver.onNext(builder.setStatus(OK).build());
+    } catch (SQLException e) {
+      responseObserver.onNext(builder.setStatus(errorStatus(e)).build());
+    }
+    responseObserver.onCompleted();
+  }
+
+}
diff --git a/jdbc-grpc-client/src/test/java/org/apache/kyuubi/grpc/client/SimpleBlockingJdbcServiceClientTest.java b/jdbc-grpc-client/src/test/java/org/apache/kyuubi/grpc/client/SimpleBlockingJdbcServiceClientTest.java
new file mode 100644
index 0000000..098746a
--- /dev/null
+++ b/jdbc-grpc-client/src/test/java/org/apache/kyuubi/grpc/client/SimpleBlockingJdbcServiceClientTest.java
@@ -0,0 +1,261 @@
+package org.apache.kyuubi.grpc.client;
+
+import io.grpc.*;
+import org.apache.kyuubi.grpc.*;
+import org.apache.kyuubi.grpc.Status;
+import org.apache.kyuubi.grpc.jdbc.DirectStatusResp;
+import org.apache.kyuubi.grpc.jdbc.connection.*;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.*;
+
+public class SimpleBlockingJdbcServiceClientTest {
+
+  private final int port = 0;
+
+  private Server server = null;
+  private SimpleBlockingJdbcClient client = null;
+  DummyJdbcService dummyFrontendService = new DummyJdbcService();
+  TestConnectionService dummyConnectionService = new TestConnectionService();
+
+  public SimpleBlockingJdbcServiceClientTest() throws IOException {
+  }
+
+  @Before
+  public void setUp() throws IOException {
+    ServerCredentials serverCredentials = InsecureServerCredentials.create();
+    ServerBuilder<?> builder = Grpc.newServerBuilderForPort(port, serverCredentials)
+      .addService(dummyConnectionService)
+      .addService(dummyFrontendService);
+    server = builder.build();
+    server.start();
+    client = new SimpleBlockingJdbcClient(server.getPort());
+  }
+
+  @After
+  public void tearDown() {
+    if (server != null) {
+      try {
+        server.awaitTermination(5, TimeUnit.SECONDS);
+      } catch (InterruptedException e) {
+        // do nothing
+      }
+    }
+  }
+
+  @Test
+  public void testServer() {
+    assertNotEquals(port, server.getPort());
+    assertFalse(server.isTerminated());
+  }
+
+  @Test
+  public void testOpenConnection() {
+    Map<String, String> configs = new HashMap<>();
+    DirectStatusResp resp = client.openConnection(configs, Optional.empty());
+    Status status = resp.getStatus();
+    assertEquals("hello, kyuubi", resp.getIdentifier());
+    assertEquals(StatusCode.OK, status.getStatusCode());
+    assertEquals("00000", status.getSqlState());
+    assertEquals("Serverless SQL on Lakehouse", resp.getExtraInfoOrThrow("Kyuubi"));
+
+    configs.put("apache", "kyuubi");
+    configs.put("kent", "yao");
+    DirectStatusResp resp1 = client.openConnection(configs, Optional.of("20181117"));
+    Status status1 = resp1.getStatus();
+    assertEquals("hello, 20181117", resp1.getIdentifier());
+    assertEquals(StatusCode.OK, status1.getStatusCode());
+    assertEquals("kyuubi", resp1.getExtraInfoOrThrow("apache"));
+  }
+
+  @Test
+  public void testCloseConnection() {
+    DirectStatusResp resp1 = client.closeConnection("");
+    Status resp1Status = resp1.getStatus();
+    assertEquals(StatusCode.ERROR, resp1Status.getStatusCode());
+    assertEquals("2E000", resp1Status.getSqlState());
+    assertEquals("invalid connection id", resp1Status.getErrorMessage());
+    DirectStatusResp resp2 = client.closeConnection("apache kyuubi");
+    Status resp2Status = resp2.getStatus();
+    assertEquals(StatusCode.OK, resp2Status.getStatusCode());
+    assertEquals("00000", resp2Status.getSqlState());
+  }
+
+  @Test
+  public void testSetClientInfo() {
+    DirectStatusResp resp = client.setClientInfo("apache kyuubi", Collections.emptyMap());
+    Status status = resp.getStatus();
+    assertEquals(StatusCode.OK, status.getStatusCode());
+    assertEquals("00000", status.getSqlState());
+  }
+
+  @Test
+  public void testGetCatalogs() {
+    DirectStatusResp resp = client.getCatalogs("kyuubi");
+    Status status = resp.getStatus();
+    assertEquals(StatusCode.OK, status.getStatusCode());
+    assertEquals("00000", status.getSqlState());
+    DirectStatusResp resp1 = client.getCatalogs("");
+    assertEquals("", resp1.getIdentifier());
+    assertEquals(StatusCode.ERROR, resp1.getStatus().getStatusCode());
+  }
+
+  @Test
+  public void testNativeSQL() {
+    NativeSQLResp resp = client.nativeSQL("kyuubi", "SELECT {fn NOW()}");
+    Status status = resp.getStatus();
+    assertEquals(StatusCode.OK, status.getStatusCode());
+    String sql = resp.getSql();
+    assertFalse(sql.contains("fn"));
+  }
+
+
+  @Test
+  public void testSetAndGetAutoCommit() {
+    GetAutoCommitResp resp = client.getAutoCommit("kyuubi");
+    assertTrue(resp.getAutoCommit());
+    client.setAutoCommit("kyuubi", false);
+    GetAutoCommitResp resp1 = client.getAutoCommit("kyuubi");
+    assertFalse(resp1.getAutoCommit());
+  }
+
+  @Test
+  public void testCommitAndRollback() {
+    DirectStatusResp resp = client.commit("kyuubi");
+    assertEquals(StatusCode.OK, resp.getStatus().getStatusCode());
+    DirectStatusResp resp1 = client.rollback("kyuubi");
+    assertEquals(StatusCode.OK, resp1.getStatus().getStatusCode());
+    DirectStatusResp resp2 = client.rollback("kyuubi", "kyuubi_savepoint");
+    assertEquals(StatusCode.ERROR, resp2.getStatus().getStatusCode());
+  }
+
+  @Test
+  public void testSetReadOnly() {
+    DirectStatusResp resp = client.setReadOnly("kyuubi", true);
+    assertEquals(StatusCode.OK, resp.getStatus().getStatusCode());
+    DirectStatusResp resp1 = client.setReadOnly("kyuubi", false);
+    assertEquals(StatusCode.OK, resp1.getStatus().getStatusCode());
+  }
+
+  @Test
+  public void testIsReadOnly() {
+    IsReadOnlyResp resp = client.isReadOnly("kyuubi");
+    assertEquals(StatusCode.OK, resp.getStatus().getStatusCode());
+    boolean readOnly = resp.getReadOnly();
+    assertFalse(readOnly);
+  }
+
+  @Test
+  public void testSetAndGetCatalog() {
+    // H2 doesn't support switch catalog and the op will be ignored without any exception
+    DirectStatusResp resp = client.setCatalog("kyuubi", "kyuubi");
+    assertEquals(StatusCode.OK, resp.getStatus().getStatusCode());
+    GetCatalogResp resp1 = client.getCatalog("kyuubi");
+    assertEquals(StatusCode.OK, resp1.getStatus().getStatusCode());
+    String catalog = resp1.getCatalog();
+    assertEquals(dummyConnectionService.defaultCatalogName, catalog);
+  }
+
+  @Test
+  public void testSetAndGetTransactionIsolationLevel() {
+    // case 1: set/get transaction isolation level
+    DirectStatusResp resp = client.setTransactionIsolation("kyuubi", 1);
+    assertEquals(StatusCode.OK, resp.getStatus().getStatusCode());
+    // case 2: get transaction isolation level
+    GetTransactionIsolationResp resp1 = client.getTransactionIsolation("kyuubi");
+    assertEquals(StatusCode.OK, resp1.getStatus().getStatusCode());
+    int level = resp1.getLevel();
+    assertEquals(1, level);
+  }
+
+  @Test
+  public void testBuildSQLWarnings() {
+    java.sql.SQLWarning warning1 = new java.sql.SQLWarning("warning1");
+    warning1.setNextWarning(new java.sql.SQLWarning("warning2"));
+    SQLWarning warning = TestConnectionService.buildWarning(warning1);
+    assertEquals("warning1", warning.getReason());
+    assertEquals("warning2", warning.getNextWarning().getReason());
+  }
+
+  @Test
+  public void testGetAndClearWarnings() {
+    GetWarningsResp resp = client.getWarnings("kyuubi");
+    assertEquals(StatusCode.OK, resp.getStatus().getStatusCode());
+    SQLWarning warning = resp.getWarnings();
+    assertEquals(SQLWarning.getDefaultInstance(), warning);
+    DirectStatusResp resp1 = client.clearWarnings("kyuubi");
+    assertEquals(StatusCode.OK, resp1.getStatus().getStatusCode());
+  }
+
+  @Test
+  public void testGetAndSetSchema() {
+    // case 1: set/get schema
+    DirectStatusResp resp = client.setSchema("kyuubi", "kyuubi");
+    assertEquals(StatusCode.ERROR, resp.getStatus().getStatusCode());
+    assertTrue(resp.getStatus().getErrorMessage().contains("not found"));
+    DirectStatusResp resp0 = client.setSchema("kyuubi", "PUBLIC");
+    assertEquals(StatusCode.OK, resp0.getStatus().getStatusCode());
+    GetSchemaResp resp1 = client.getSchema("kyuubi");
+    assertEquals(StatusCode.OK, resp1.getStatus().getStatusCode());
+    String schema = resp1.getSchema();
+    assertEquals("PUBLIC", schema);
+  }
+
+  @Test
+  public void testGetAndSetNetworkTimeout() {
+    // case 1: set/get network timeout
+    DirectStatusResp resp = client.setNetworkTimeout("kyuubi", 1000);
+    assertEquals(StatusCode.OK, resp.getStatus().getStatusCode());
+    GetNetworkTimeoutResp resp1 = client.getNetworkTimeout("kyuubi");
+    assertEquals(StatusCode.OK, resp1.getStatus().getStatusCode());
+    int timeout = resp1.getMilliseconds();
+    assertEquals(0, timeout);
+  }
+
+  @Test
+  public void testGetAndSetClientInfo() {
+    HashMap<String, String> configs = new HashMap<>();
+    configs.put("key1", "value1");
+    DirectStatusResp resp = client.setClientInfo("kyuubi", configs);
+
+    assertEquals(StatusCode.ERROR, resp.getStatus().getStatusCode());
+    assertTrue(resp.getStatus().getErrorMessage().contains("not supported"));
+    configs.clear();
+    configs.put("ApplicationName", "value1");
+    DirectStatusResp resp0 = client.setClientInfo("kyuubi", configs);
+    assertEquals(StatusCode.OK, resp0.getStatus().getStatusCode());
+    GetClientInfoResp resp1 = client.getClientInfo("kyuubi");
+    assertEquals(StatusCode.OK, resp1.getStatus().getStatusCode());
+    Map<String, String> clientInfo = resp1.getConfigsMap();
+    configs.put("numServers", "0");
+    assertEquals(configs, clientInfo);
+  }
+
+  @Test
+  public void testGetAndSetClientInfoString() {
+    DirectStatusResp resp = client.setClientInfo("kyuubi", "key1", "value1");
+    assertEquals(StatusCode.ERROR, resp.getStatus().getStatusCode());
+    assertTrue(resp.getStatus().getErrorMessage().contains("not supported"));
+    DirectStatusResp resp0 = client.setClientInfo("kyuubi", "ApplicationName", "value1");
+    assertEquals(StatusCode.OK, resp0.getStatus().getStatusCode());
+    GetClientInfoResp resp1 = client.getClientInfo("kyuubi");
+    assertEquals(StatusCode.OK, resp1.getStatus().getStatusCode());
+    Map<String, String> clientInfo = resp1.getConfigsMap();
+    assertEquals("value1", clientInfo.get("ApplicationName"));
+  }
+
+  @Test
+  public void testAbortConnection() {
+    DirectStatusResp resp = client.abortConnection("kyuubi");
+    assertEquals(StatusCode.OK, resp.getStatus().getStatusCode());
+  }
+}
\ No newline at end of file
diff --git a/jdbc-proto/README.md b/jdbc-proto/README.md
new file mode 100644
index 0000000..a32ad9c
--- /dev/null
+++ b/jdbc-proto/README.md
@@ -0,0 +1,26 @@
+<!--
+ - 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.
+ -->
+
+# Kyuubi JDBC Proto Definition
+
+## Introduction
+
+A [Protocol Buffers](https://developers.google.com/protocol-buffers)
+(a.k.a., protobuf) definition for building Java Database Connectivity (JDBC)
+client and services.
+
+
diff --git a/jdbc-proto/pom.xml b/jdbc-proto/pom.xml
new file mode 100644
index 0000000..0d9483d
--- /dev/null
+++ b/jdbc-proto/pom.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.kyuubi</groupId>
+        <artifactId>kyuubi-service-rpc</artifactId>
+        <version>0.1.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>kyuubi-jdbc-proto</artifactId>
+    <name>Kyuubi JDBC Service Protobuf Definition</name>
+    <url>https://kyuubi.apache.org/</url>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.google.protobuf</groupId>
+            <artifactId>protobuf-java</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-netty</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-protobuf</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-services</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-stub</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.xolstice.maven.plugins</groupId>
+                <artifactId>protobuf-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>add-protoc-gen</id>
+                        <goals>
+                            <goal>add-source</goal>
+                        </goals>
+                        <phase>generate-sources</phase>
+                        <configuration>
+                            <sources>
+                                <source>${project.build.outputDirectory}/generated-sources/protobuf</source>
+                            </sources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/jdbc-proto/src/main/protobuf/org/apache/kyuubi/grpc/common/errors.proto b/jdbc-proto/src/main/protobuf/org/apache/kyuubi/grpc/common/errors.proto
new file mode 100644
index 0000000..def741e
--- /dev/null
+++ b/jdbc-proto/src/main/protobuf/org/apache/kyuubi/grpc/common/errors.proto
@@ -0,0 +1,43 @@
+// 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.
+
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_package = "org.apache.kyuubi.grpc";
+
+enum StatusCode {
+  OK = 0;
+  ERROR = 1;
+}
+
+message Status {
+  StatusCode status_code = 1;
+  string sql_state = 2;
+  // for legacy jdbc engines
+  uint32 error_code = 3;
+  string error_message = 4;
+}
+
+message SQLWarning {
+  // a description of the warning
+  string reason = 1;
+  // an XOPEN or SQL:2003 code identifying the warning
+  string sql_state = 2;
+  // a database vendor-specific warning code
+  uint32 vendor_code = 3;
+  SQLWarning next_warning = 4;
+}
\ No newline at end of file
diff --git a/jdbc-proto/src/main/protobuf/org/apache/kyuubi/grpc/jdbc/connection.proto b/jdbc-proto/src/main/protobuf/org/apache/kyuubi/grpc/jdbc/connection.proto
new file mode 100644
index 0000000..bcde549
--- /dev/null
+++ b/jdbc-proto/src/main/protobuf/org/apache/kyuubi/grpc/jdbc/connection.proto
@@ -0,0 +1,300 @@
+// 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.
+
+syntax = "proto3";
+
+import "org/apache/kyuubi/grpc/common/errors.proto";
+import "org/apache/kyuubi/grpc/jdbc/response.proto";
+
+option java_multiple_files = true;
+option java_package = "org.apache.kyuubi.grpc.jdbc.connection";
+
+/*
+ * APIs need for build a service component against java.sql.Connection class
+ * A connection (session) with a specific database. SQL statements are executed and results are
+ * returned within the context of a connection.
+ *
+ * APIs defined in separate sub-protocol:
+ *  - DatabaseMetaData API:
+ *    - getMetaData
+ *  - Statement API:
+ *    - createStatement
+ *    - prepareStatement
+ *    - prepareCall
+ *
+ * TODO: Other Undefined APIs:
+ *  1. createArrayOf
+ *  2. createBlob
+ *  3. createClob
+ *  4. createNClob
+ *  5. createSQLXML
+ *  6. createStruct
+ */
+service Connection {
+  rpc OpenConnection(OpenConnectionReq) returns (DirectStatusResp);
+  rpc CloseConnection(CloseConnectionReq) returns (DirectStatusResp);
+  rpc AbortConnection(AbortConnectionReq) returns (DirectStatusResp);
+  rpc NativeSQL(NativeSQLReq) returns (NativeSQLResp);
+  rpc SetAutoCommit(SetAutoCommitReq) returns (DirectStatusResp);
+  rpc GetAutoCommit(GetAutoCommitReq) returns (GetAutoCommitResp);
+  rpc Commit(CommitReq) returns (DirectStatusResp);
+  rpc Rollback(RollbackReq) returns (DirectStatusResp);
+  rpc SetReadOnly(SetReadOnlyReq) returns (DirectStatusResp);
+  rpc IsReadOnly(IsReadOnlyReq) returns (IsReadOnlyResp);
+  rpc SetCatalog(SetCatalogReq) returns (DirectStatusResp);
+  rpc GetCatalog(GetCatalogReq) returns (GetCatalogResp);
+  rpc SetTransactionIsolation(SetTransactionIsolationReq) returns (DirectStatusResp);
+  rpc GetTransactionIsolation(GetTransactionIsolationReq) returns (GetTransactionIsolationResp);
+  rpc GetWarnings(GetWarningsReq) returns (GetWarningsResp);
+  rpc ClearWarnings(ClearWarningsReq) returns (DirectStatusResp);
+  rpc SetTypeMap(SetTypeMapReq) returns (DirectStatusResp);
+  rpc GetTypeMap(GetTypeMapReq) returns (GetTypeMapResp);
+  rpc SetSchema(SetSchemaReq) returns (DirectStatusResp);
+  rpc GetSchema(GetSchemaReq) returns (GetSchemaResp);
+  rpc SetNetworkTimeout(SetNetworkTimeoutReq) returns (DirectStatusResp);
+  rpc GetNetworkTimeout(GetNetworkTimeoutReq) returns (GetNetworkTimeoutResp);
+  rpc SetHoldability(SetHoldabilityReq) returns (DirectStatusResp);
+  rpc GetHoldability(GetHoldabilityReq) returns (GetHoldabilityResp);
+  rpc SetSavepoint(SetSavepointReq) returns (SetSavepointResp);
+  rpc ReleaseSavepoint(ReleaseSavepointReq) returns (DirectStatusResp);
+  rpc IsValid(IsValidReq) returns (IsValidResp);
+  rpc SetClientInfo(SetClientInfoReq) returns (DirectStatusResp);
+  rpc GetClientInfo(GetClientInfoReq) returns (GetClientInfoResp);
+}
+
+message OpenConnectionReq {
+  string connection_id = 1;
+  map<string, string> configs = 2;
+}
+
+message CloseConnectionReq {
+  string connection_id = 1;
+  map<string, string> configs = 2;
+}
+
+message AbortConnectionReq {
+  string connection_id = 1;
+  map<string, string> configs = 2;
+}
+
+message NativeSQLReq {
+  string connection_id = 1;
+  string sql = 2;
+}
+
+message NativeSQLResp {
+  Status status = 1;
+  string sql = 2;
+}
+
+message SetAutoCommitReq {
+  string connection_id = 1;
+  bool autoCommit = 2;
+}
+
+message GetAutoCommitReq {
+  string connection_id = 1;
+}
+
+message GetAutoCommitResp {
+  Status status = 1;
+  bool autoCommit = 2;
+}
+
+message CommitReq {
+  string connection_id = 1;
+}
+
+message Savepoint {
+  uint32 savepoint_id = 1;
+  string savepoint_name = 2;
+}
+
+message RollbackReq {
+  string connection_id = 1;
+  Savepoint savepoint = 2;
+}
+
+message SetReadOnlyReq {
+  string connection_id = 1;
+  bool read_only = 2;
+}
+
+message IsReadOnlyReq {
+  string connection_id = 1;
+}
+
+message IsReadOnlyResp {
+  Status status = 1;
+  bool read_only = 2;
+}
+
+message SetClientInfoReq {
+  string connection_id = 1;
+  map<string, string> configs = 2;
+}
+
+message GetClientInfoReq {
+  string connection_id = 1;
+}
+
+message GetClientInfoResp {
+  Status status = 1;
+  map<string, string> configs = 2;
+}
+
+message SetCatalogReq {
+  string connection_id = 1;
+  string catalog = 2;
+}
+
+message GetCatalogReq {
+  string connection_id = 1;
+}
+
+message GetCatalogResp {
+  Status status = 1;
+  string catalog = 2;
+}
+
+/*
+ * Attempts to change the transaction isolation level for this Connection object to the one given.
+ * The constants defined in the interface Connection are the possible transaction isolation levels.
+ * @see java.sql.Connection
+ */
+message SetTransactionIsolationReq {
+  string connection_id = 1;
+  // The possible transaction isolation levels
+  // level one of the following <code>Connection</code> constants:
+  // <code>Connection.TRANSACTION_READ_UNCOMMITTED(1)</code>,
+  // <code>Connection.TRANSACTION_READ_COMMITTED(2)</code>,
+  // <code>Connection.TRANSACTION_REPEATABLE_READ(4)</code>, or
+  // <code>Connection.TRANSACTION_SERIALIZABLE(8)</code>.
+  uint32 level = 2;
+}
+
+message GetTransactionIsolationReq {
+  string connection_id = 1;
+}
+
+message GetTransactionIsolationResp {
+  Status status = 1;
+  uint32 level = 2;
+}
+
+message GetWarningsReq {
+  string connection_id = 1;
+}
+
+message GetWarningsResp {
+  Status status = 1;
+  SQLWarning warnings = 2;
+}
+
+
+message ClearWarningsReq {
+  string connection_id = 1;
+}
+
+message TypeClassMap {
+  string type_name = 1;
+  string class_name = 2;
+}
+
+message SetTypeMapReq {
+  string connection_id = 1;
+  map<string, string> type_to_class = 2;
+}
+
+message GetTypeMapReq {
+  string connection_id = 1;
+}
+
+message GetTypeMapResp {
+  Status status = 1;
+  map<string, string> type_to_class = 2;
+}
+
+message SetSchemaReq {
+  string connection_id = 1;
+  string schema = 2;
+}
+
+message GetSchemaReq {
+  string connection_id = 1;
+}
+
+message GetSchemaResp {
+  Status status = 1;
+  string schema = 2;
+}
+
+message SetHoldabilityReq {
+  string connection_id = 1;
+  // one of the following <code>ResultSet</code> constants:
+  // <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or
+  // <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>
+  uint32 holdability = 2;
+}
+
+message GetHoldabilityReq {
+  string connection_id = 1;
+}
+
+message GetHoldabilityResp {
+  Status status = 1;
+  uint32 holdability = 2;
+}
+
+message SetNetworkTimeoutReq {
+  string connection_id = 1;
+  // The new network timeout value in milliseconds
+  uint32 milliseconds = 2;
+}
+
+message GetNetworkTimeoutReq {
+  string connection_id = 1;
+}
+
+message GetNetworkTimeoutResp {
+  Status status = 1;
+  uint32 milliseconds = 2;
+}
+
+message ReleaseSavepointReq {
+  string connection_id = 1;
+  Savepoint savepoint = 2;
+}
+
+message SetSavepointReq {
+  string connection_id = 1;
+  string savepoint_name = 2;
+}
+
+message SetSavepointResp {
+  Status status = 1;
+  Savepoint savepoint = 2;
+}
+
+message IsValidReq {
+  string connection_id = 1;
+  uint32 timeout = 2;
+}
+
+message IsValidResp {
+  Status status = 1;
+  bool valid = 2;
+}
diff --git a/jdbc-proto/src/main/protobuf/org/apache/kyuubi/grpc/jdbc/request.proto b/jdbc-proto/src/main/protobuf/org/apache/kyuubi/grpc/jdbc/request.proto
new file mode 100644
index 0000000..73d56ed
--- /dev/null
+++ b/jdbc-proto/src/main/protobuf/org/apache/kyuubi/grpc/jdbc/request.proto
@@ -0,0 +1,345 @@
+// 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.
+
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_package = "org.apache.kyuubi.grpc.jdbc";
+
+message GetCatalogsReq {
+  string connection_id = 1;
+}
+
+message GetSchemasReq {
+  string connection_id = 1;
+  string catalog = 2;
+  string schemaPattern = 3;
+}
+
+message GetTablesReq {
+  string connection_id = 1;
+  string catalog = 2;
+  string schemaPattern = 3;
+  string tableNamePattern = 4;
+  repeated string tableTypes = 5;
+}
+
+message GetTableTypesReq {
+  string connection_id = 1;
+}
+
+message GetTablePrivilegesReq {
+  string connection_id = 1;
+  string catalog = 2;
+  string schemaPattern = 3;
+  string tableNamePattern = 4;
+}
+
+message GetColumnsReq {
+  string connection_id = 1;
+  string catalog = 2;
+  string schemaPattern = 3;
+  string tablePattern = 4;
+  string columnNamePattern = 5;
+}
+
+message GetColumnPrivilegesReq {
+  string connection_id = 1;
+  string catalog = 2;
+  string schema = 3;
+  string table = 4;
+  string columnNamePattern = 5;
+}
+
+message GetVersionColumnsReq {
+  string connection_id = 1;
+  string catalog = 2;
+  string schema = 3;
+  string table = 4;
+}
+
+message GetPseudoColumnsReq {
+  string connection_id = 1;
+  string catalog = 2;
+  string schemaPattern = 3;
+  string tableNamePattern = 4;
+  string columnNamePattern = 5;
+}
+
+message GetFunctionsReq {
+  string connection_id = 1;
+  string catalog = 2;
+  string schemaPattern = 3;
+  string functionNamePattern = 4;
+}
+
+message GetFunctionColumnsReq {
+  string connection_id = 1;
+  string catalog = 2;
+  string schemaPattern = 3;
+  string functionNamePattern = 4;
+  string columnNamePattern = 5;
+}
+
+message GetPrimaryKeysReq {
+  string connection_id = 1;
+  string catalog = 2;
+  string schema = 3;
+  string table = 4;
+}
+
+message GetImportedKeysReq {
+  string connection_id = 1;
+  string catalog = 2;
+  string schema = 3;
+  string table = 4;
+}
+
+message GetExportedKeysReq {
+  string connection_id = 1;
+  string catalog = 2;
+  string schema = 3;
+  string table = 4;
+}
+
+message GetCrossReferenceReq {
+  string connection_id = 1;
+  string primaryCatalog = 2;
+  string primarySchema = 3;
+  string primaryTable = 4;
+  string foreignCatalog = 5;
+  string foreignSchema = 6;
+  string foreignTable = 7;
+}
+
+message GetAttributesReq {
+  string connection_id = 1;
+  string catalog = 2;
+  string schemaPattern = 3;
+  string typeNamePattern = 4;
+  string attributeNamePattern = 5;
+}
+
+message GetBestRowIdentifierReq {
+  string connection_id = 1;
+  string catalog = 2;
+  string schema = 3;
+  string table = 4;
+  uint32 scope = 5;
+  bool nullable = 6;
+}
+
+message GetTypeInfoReq {
+  string connection_id = 1;
+}
+
+message GetIndexInfoReq {
+  string connection_id = 1;
+  string catalog = 2;
+  string schema = 3;
+  string table = 4;
+  bool unique = 5;
+  bool approximate = 6;
+}
+
+message GetUDTsReq {
+  string connection_id = 1;
+  string catalog = 2;
+  string schemaPattern = 3;
+  string typeNamePattern = 4;
+}
+
+message GetSuperTypesReq {
+  string connection_id = 1;
+  string catalog = 2;
+  string schemaPattern = 3;
+  string typeNamePattern = 4;
+}
+
+message GetSuperTablesReq {
+  string connection_id = 1;
+  string catalog = 2;
+  string schemaPattern = 3;
+  string tableNamePattern = 4;
+}
+
+
+message ExecuteSqlReq {
+  string connection_id = 1;
+  // any SQL statement
+  string sql = 2;
+  // java.sql.Statement.getQueryTimeout
+  // the current query timeout limit in seconds; zero means there is
+  // no limit.
+  uint32 queryTimeout = 3;
+  // java.sql.Statement.getMaxRows
+  // Retrieves the maximum number of rows that a ResultSet could have.
+  // If this limit is exceeded, the excess rows are silently dropped.
+  // zero means there is no limit
+  uint32 maxRows = 4;
+}
+
+message GetResultSetSchemaReq {
+  string operation_id = 1;
+}
+
+message GetNextColumnSet {
+  string operation_id = 1;
+  uint32 fetchSize = 2;
+}
+
+/* java.sql.DatabaseMetaData APIs except for those
+ * - Return java.sql.ResultSet
+ *   - java.sql.DatabaseMetaData.getImportedKeys
+ * - JDBC Driver Info
+ *   - java.sql.DatabaseMetaData.getDriverName
+ *   - java.sql.DatabaseMetaData.getDriverVersion
+ *   - java.sql.DatabaseMetaData.getDriverMajorVersion
+ *   - java.sql.DatabaseMetaData.getDriverMinorVersion
+ */
+enum DatabaseMetaData {
+  // java.sql.DatabaseMetaData.allProceduresAreCallable
+  ALL_PROCEDURES_ARE_CALLABLE = 0;
+  // java.sql.DatabaseMetaData.allTablesAreSelectable
+  ALL_TABLES_ARE_SELECTABLE = 1;
+  // java.sql.DatabaseMetaData.getURL
+  URL = 2;
+  // java.sql.DatabaseMetaData.getUserName
+  USERNAME = 3;
+  // java.sql.DatabaseMetaData.isReadOnly
+  IS_READ_ONLY = 4;
+  // java.sql.DatabaseMetaData.nullsAreSortedHigh
+  NULLS_ARE_SORTED_HIGH = 5;
+  // java.sql.DatabaseMetaData.nullsAreSortedLow
+  NULLS_ARE_SORTED_LOW = 6;
+  // java.sql.DatabaseMetaData.nullsAreSortedAtStart
+  NULLS_ARE_SORTED_AT_START = 7;
+  // java.sql.DatabaseMetaData.nullsAreSortedAtEnd
+  NULLS_ARE_SORTED_AT_END = 8;
+  // java.sql.DatabaseMetaData.getDatabaseProductName
+  DATABASE_PRODUCT_NAME = 9;
+  // java.sql.DatabaseMetaData.getDatabaseProductVersion
+  DATABASE_PRODUCT_VERSION = 10;
+  // java.sql.DatabaseMetaData.usesLocalFiles
+  USES_LOCAL_FILES = 11;
+  // java.sql.DatabaseMetaData.usesLocalFilePerTable
+  USES_LOCAL_FILE_PER_TABLE = 12;
+  // java.sql.DatabaseMetaData.supportsMixedCaseIdentifiers
+  SUPPORTS_MIXED_CASE_IDENTIFIERS = 13;
+  // java.sql.DatabaseMetaData.storesUpperCaseIdentifiers
+  STORES_UPPER_CASE_IDENTIFIERS = 14;
+  // java.sql.DatabaseMetaData.storesLowerCaseIdentifiers
+  STORES_LOWER_CASE_IDENTIFIERS = 15;
+  // java.sql.DatabaseMetaData.storesMixedCaseIdentifiers
+  STORES_MIXED_CASE_IDENTIFIERS = 16;
+  // java.sql.DatabaseMetaData.supportsMixedCaseQuotedIdentifiers
+  SUPPORTS_MIXED_CASE_QUOTED_IDENTIFIERS = 17;
+  // java.sql.DatabaseMetaData.storesUpperCaseQuotedIdentifiers
+  STORES_UPPER_CASE_QUOTED_IDENTIFIERS = 18;
+  // java.sql.DatabaseMetaData.storesLowerCaseQuotedIdentifiers
+  STORES_LOWER_CASE_QUOTED_IDENTIFIERS = 19;
+  // java.sql.DatabaseMetaData.storesMixedCaseQuotedIdentifiers
+  STORES_MIXED_CASE_QUOTED_IDENTIFIERS = 20;
+  // java.sql.DatabaseMetaData.getIdentifierQuoteString
+  IDENTIFIER_QUOTE_STRING = 21;
+  // java.sql.DatabaseMetaData.getSQLKeywords
+  SQL_KEYWORDS = 22;
+  // java.sql.DatabaseMetaData.getNumericFunctions
+  NUMERIC_FUNCTIONS = 23;
+  // java.sql.DatabaseMetaData.getStringFunctions
+  STRING_FUNCTIONS = 24;
+  // java.sql.DatabaseMetaData.getSystemFunctions
+  SYSTEM_FUNCTIONS = 25;
+  // java.sql.DatabaseMetaData.getTimeDateFunctions
+  TIME_DATE_FUNCTIONS = 26;
+  // java.sql.DatabaseMetaData.getSearchStringEscape
+  SEARCH_STRING_ESCAPE = 27;
+  // java.sql.DatabaseMetaData.getExtraNameCharacters
+  EXTRA_NAME_CHARACTERS = 28;
+  // java.sql.DatabaseMetaData.supportsAlterTableWithAddColumn
+  SUPPORTS_ALTER_TABLE_WITH_ADD_COLUMN = 29;
+  // java.sql.DatabaseMetaData.supportsAlterTableWithDropColumn
+  SUPPORTS_ALTER_TABLE_WITH_DROP_COLUMN = 30;
+  // java.sql.DatabaseMetaData.supportsColumnAliasing
+  SUPPORTS_COLUMN_ALIASING = 31;
+  // java.sql.DatabaseMetaData.nullPlusNonNullIsNull
+  NULL_PLUS_NON_NULL_IS_NULL = 32;
+  // java.sql.DatabaseMetaData.supportsConvert()
+  // java.sql.DatabaseMetaData.supportsConvert(int, int)
+  SUPPORTS_CONVERT = 33;
+  // java.sql.DatabaseMetaData.supportsTableCorrelationNames
+  SUPPORTS_TABLE_CORRELATION_NAMES = 34;
+  // java.sql.DatabaseMetaData.supportsDifferentTableCorrelationNames
+  SUPPORTS_DIFFERENT_TABLE_CORRELATION_NAMES = 35;
+  // java.sql.DatabaseMetaData.supportsExpressionsInOrderBy
+  SUPPORTS_EXPRESSIONS_IN_ORDER_BY = 36;
+  // java.sql.DatabaseMetaData.supportsOrderByUnrelated
+  SUPPORTS_ORDER_BY_UNRELATED = 37;
+  // java.sql.DatabaseMetaData.supportsGroupBy
+  SUPPORTS_GROUP_BY = 38;
+  // java.sql.DatabaseMetaData.supportsGroupByUnrelated
+  SUPPORTS_GROUP_BY_UNRELATED = 39;
+  // java.sql.DatabaseMetaData.supportsGroupByBeyondSelect
+  SUPPORTS_GROUP_BY_BEYOND_SELECT = 40;
+  // java.sql.DatabaseMetaData.supportsLikeEscapeClause
+  SUPPORTS_LIKE_ESCAPE_CLAUSE = 41;
+  // java.sql.DatabaseMetaData.supportsMultipleResultSets
+  SUPPORTS_MULTIPLE_RESULT_SETS = 42;
+  // java.sql.DatabaseMetaData.supportsMultipleTransactions
+  SUPPORTS_MULTIPLE_TRANSACTIONS = 43;
+  // java.sql.DatabaseMetaData.supportsNonNullableColumns
+  SUPPORTS_NON_NULLABLE_COLUMNS = 44;
+  // java.sql.DatabaseMetaData.supportsMinimumSQLGrammar
+  // java.sql.DatabaseMetaData.supportsCoreSQLGrammar
+  // java.sql.DatabaseMetaData.supportsExtendedSQLGrammar
+  // java.sql.DatabaseMetaData.supportsANSI92IntermediateSQL
+  // java.sql.DatabaseMetaData.supportsANSI92FullSQL
+  // java.sql.DatabaseMetaData.supportsIntegrityEnhancementFacility
+  // java.sql.DatabaseMetaData.supportsOuterJoins
+  // java.sql.DatabaseMetaData.supportsFullOuterJoins
+  // java.sql.DatabaseMetaData.supportsLimitedOuterJoins
+  // java.sql.DatabaseMetaData.getSchemaTerm
+  // java.sql.DatabaseMetaData.getProcedureTerm
+  // java.sql.DatabaseMetaData.getCatalogTerm
+
+}
+
+message NewEngineReq {
+  string connection_id = 1;
+  map<string, string> configs = 2;
+}
+
+message NewEngineResp {
+  string operation_id = 1;
+  map<string, string> configs = 2;
+}
+
+
+
+enum OperationState {
+  INITIALIZED = 0; // operation created
+  INITIALIZE_FAILED = 1; // operation failed to be created
+  SUBMITTED = 2; // operation submitted
+  SUBMIT_FAILED = 3; // operation failed to be submitted
+  RUNNING = 4; // operation running
+  RUNNING_COMPILED = 5;
+  RUNNING_FAILED = 6;
+  SUCCEEDED = 7;
+  CANCELED = 8;
+  CLOSED = 9;
+  TIMEOUT = 10;
+}
+
diff --git a/jdbc-proto/src/main/protobuf/org/apache/kyuubi/grpc/jdbc/response.proto b/jdbc-proto/src/main/protobuf/org/apache/kyuubi/grpc/jdbc/response.proto
new file mode 100644
index 0000000..c0c94bb
--- /dev/null
+++ b/jdbc-proto/src/main/protobuf/org/apache/kyuubi/grpc/jdbc/response.proto
@@ -0,0 +1,46 @@
+// 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.
+
+syntax = "proto3";
+
+import "google/protobuf/any.proto";
+import "org/apache/kyuubi/grpc/common/errors.proto";
+import "org/apache/kyuubi/grpc/jdbc/schema.proto";
+
+option java_multiple_files = true;
+option java_package = "org.apache.kyuubi.grpc.jdbc";
+
+message DirectStatusResp {
+  string identifier = 1;
+  Status status = 2;
+  map<string, string> extraInfo = 3;
+}
+
+message DirectValueResp {
+  Status status = 2;
+  google.protobuf.Any value = 1;
+}
+
+message GetResultSetSchemaResp {
+  Status status = 1;
+  ResultSetSchema schema = 2;
+}
+
+message GetNextColumnSetResp {
+  Status status = 1;
+  ColumnDataSet data = 2;
+}
+
diff --git a/jdbc-proto/src/main/protobuf/org/apache/kyuubi/grpc/jdbc/schema.proto b/jdbc-proto/src/main/protobuf/org/apache/kyuubi/grpc/jdbc/schema.proto
new file mode 100644
index 0000000..bb31dff
--- /dev/null
+++ b/jdbc-proto/src/main/protobuf/org/apache/kyuubi/grpc/jdbc/schema.proto
@@ -0,0 +1,103 @@
+// 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.
+
+syntax = "proto3";
+
+import "google/protobuf/any.proto";
+
+option java_multiple_files = true;
+option java_package = "org.apache.kyuubi.grpc.jdbc";
+
+enum TypeId {
+  BOOLEAN = 0;
+
+  TINYINT = 1;
+  SMALLINT = 2;
+  INT = 3;
+  LONG = 4;
+
+  FLOAT = 5;
+  DOUBLE = 6;
+  DECIMAL = 7;
+
+  DATE = 8;
+  TIME = 9;
+  TIMESTAMP = 10;
+  TIMESTAMP_WITH_TIMEZONE = 11;
+  TIMESTAMP_LOCAL_TIMEZONE = 12;
+  YEAR_MONTH_INTERVAL = 13;
+  DAY_TIME_INTERVAL = 14;
+
+  CHAR = 15;
+  VARCHAR = 16;
+  STRING = 17;
+  BINARY = 18;
+
+  ARRAY = 19;
+  MAP = 20;
+  STRUCT = 21;
+  UNION = 22;
+  USER_DEFINED_TYPE = 23;
+
+  NULL = 24;
+}
+
+message DataType {
+  TypeId type_id = 1;
+  string type_name = 2;
+  uint32 precision = 3;
+  uint32 scale = 4;
+  uint32 display_size = 5; // indicates the normal maximum width in characters
+  map<string, string> metadata = 6;
+  repeated ColumnInfo children = 7;
+}
+
+message ColumnInfo {
+  uint32 column_index = 1;
+  bool auto_increment = 2;
+  bool case_sensitive = 3;
+  bool searchable = 4;
+  bool currency = 5;
+  // 0 - no
+  // 1 - yes
+  // 2 - unknown
+  uint32 nullable = 6;
+  bool signed = 7;
+  // holds the suggested column title for this column, to be used in printing and displays.
+  string column_label = 9;
+  // holds the name of this column
+  string column_name = 10;
+  string schema_name = 11;
+  string table_name = 14;
+  string catalog_name = 15;
+  bool read_only = 16;
+  bool writable = 17;
+  bool definitely_writable = 18;
+  string class_name = 19;
+  DataType type = 20;
+}
+
+message ResultSetSchema {
+  repeated ColumnInfo columns = 1;
+}
+
+message ColumnData {
+  repeated google.protobuf.Any values = 1;
+}
+
+message ColumnDataSet {
+  repeated ColumnData columns = 1;
+}
\ No newline at end of file
diff --git a/jdbc-proto/src/main/protobuf/org/apache/kyuubi/grpc/jdbc/service.proto b/jdbc-proto/src/main/protobuf/org/apache/kyuubi/grpc/jdbc/service.proto
new file mode 100644
index 0000000..a2b17d1
--- /dev/null
+++ b/jdbc-proto/src/main/protobuf/org/apache/kyuubi/grpc/jdbc/service.proto
@@ -0,0 +1,54 @@
+// 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.
+
+syntax = "proto3";
+
+import "google/protobuf/any.proto";
+import "org/apache/kyuubi/grpc/common/errors.proto";
+import "org/apache/kyuubi/grpc/jdbc/request.proto";
+import "org/apache/kyuubi/grpc/jdbc/response.proto";
+
+option java_multiple_files = true;
+option java_package = "org.apache.kyuubi.grpc.jdbc";
+
+/* Frontend Endpoint for building JDBC-compatible service through Grpc.
+ */
+service Jdbc {
+  rpc GetCatalogs(GetCatalogsReq) returns (DirectStatusResp);
+  rpc GetSchemas(GetSchemasReq) returns (DirectStatusResp);
+  rpc GetTables(GetTablesReq) returns (DirectStatusResp);
+  rpc GetTableTypes(GetTableTypesReq) returns (DirectStatusResp);
+  rpc GetTablePrivileges(GetTablePrivilegesReq) returns (DirectStatusResp);
+  rpc GetColumns(GetColumnsReq) returns (DirectStatusResp);
+  rpc GetColumnPrivileges(GetColumnPrivilegesReq) returns (DirectStatusResp);
+  rpc GetVersionColumns(GetVersionColumnsReq) returns (DirectStatusResp);
+  rpc GetPseudoColumns(GetPseudoColumnsReq) returns (DirectStatusResp);
+  rpc GetFunctions(GetFunctionsReq) returns (DirectStatusResp);
+  rpc GetFunctionColumns(GetFunctionColumnsReq) returns (GetFunctionColumnsReq);
+  rpc GetPrimaryKeys(GetPrimaryKeysReq) returns (DirectStatusResp);
+  rpc GetImportedKeys(GetImportedKeysReq) returns (DirectStatusResp);
+  rpc GetExportedKeys(GetImportedKeysReq) returns (DirectStatusResp);
+  rpc GetCrossReference(GetCrossReferenceReq) returns (DirectStatusResp);
+  rpc GetAttributes(GetAttributesReq) returns (DirectStatusResp);
+  rpc GetBestRowIdentifier(GetBestRowIdentifierReq) returns (DirectStatusResp);
+  rpc GetTypeInfo(GetTypeInfoReq) returns (DirectStatusResp);
+  rpc GetIndexInfo(GetIndexInfoReq) returns (DirectStatusResp);
+  rpc GetUDTs(GetUDTsReq) returns (DirectStatusResp);
+  rpc GetSuperTypes(GetSuperTypesReq) returns (DirectStatusResp);
+  rpc ExecuteSql(ExecuteSqlReq) returns (DirectStatusResp);
+  rpc GetResultSetSchema(GetResultSetSchemaReq) returns (GetResultSetSchemaResp);
+  rpc GetNextResultSet(GetNextColumnSet) returns (GetNextColumnSetResp);
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..d2f3797
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache</groupId>
+        <artifactId>apache</artifactId>
+        <version>29</version>
+    </parent>
+
+    <groupId>org.apache.kyuubi</groupId>
+    <artifactId>kyuubi-service-rpc</artifactId>
+    <version>0.1.0-SNAPSHOT</version>
+    <packaging>pom</packaging>
+    <name>Kyuubi Service RPC Layer</name>
+
+    <modules>
+        <module>health-proto</module>
+        <module>jdbc-proto</module>
+        <module>jdbc-grpc-client</module>
+    </modules>
+
+    <properties>
+        <io.grpc.version>1.54.1</io.grpc.version>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <os-maven-plugin.version>1.7.1</os-maven-plugin.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <protobuf-java.version>3.22.3</protobuf-java.version>
+        <protobuf-maven-plugin.version>0.6.1</protobuf-maven-plugin.version>
+        <tomcat.annotations-api.version>6.0.53</tomcat.annotations-api.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>com.google.protobuf</groupId>
+                <artifactId>protobuf-java</artifactId>
+                <version>${protobuf-java.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.grpc</groupId>
+                <artifactId>grpc-netty</artifactId>
+                <version>${io.grpc.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.grpc</groupId>
+                <artifactId>grpc-protobuf</artifactId>
+                <version>${io.grpc.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.grpc</groupId>
+                <artifactId>grpc-services</artifactId>
+                <version>${io.grpc.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.grpc</groupId>
+                <artifactId>grpc-stub</artifactId>
+                <version>${io.grpc.version}</version>
+            </dependency>
+            <dependency> <!-- necessary for Java 9+ -->
+                <groupId>org.apache.tomcat</groupId>
+                <artifactId>annotations-api</artifactId>
+                <version>${tomcat.annotations-api.version}</version>
+                <scope>provided</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <extensions>
+            <extension>
+                <groupId>kr.motd.maven</groupId>
+                <artifactId>os-maven-plugin</artifactId>
+                <version>${os-maven-plugin.version}</version>
+            </extension>
+        </extensions>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.xolstice.maven.plugins</groupId>
+                    <artifactId>protobuf-maven-plugin</artifactId>
+                    <version>${protobuf-maven-plugin.version}</version>
+                    <configuration>
+                        <checkStaleness>true</checkStaleness>
+                        <pluginId>grpc-java</pluginId>
+                        <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.47.0:exe:${os.detected.classifier}</pluginArtifact>
+                        <protocArtifact>com.google.protobuf:protoc:3.21.1:exe:${os.detected.classifier}</protocArtifact>
+                        <protoSourceRoot>src/main/protobuf/</protoSourceRoot>
+                    </configuration>
+                    <executions>
+                        <execution>
+                            <goals>
+                                <goal>compile</goal>
+                                <goal>compile-custom</goal>
+                            </goals>
+                        </execution>
+                    </executions>
+                </plugin>
+                <plugin>
+                    <groupId>org.codehaus.mojo</groupId>
+                    <artifactId>build-helper-maven-plugin</artifactId>
+                    <version>3.3.0</version>
+                </plugin>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-surefire-plugin</artifactId>
+                    <version>3.0.0-M9</version>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>
\ No newline at end of file