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

CRZbulabula pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 0c25e53281f Implemented the count database in table model (#17705)
0c25e53281f is described below

commit 0c25e53281f54c360bb33264b7acbc06fcf32bdc
Author: Caideyipi <[email protected]>
AuthorDate: Tue May 26 14:25:59 2026 +0800

    Implemented the count database in table model (#17705)
---
 .../relational/it/schema/IoTDBDatabaseIT.java      |  27 ++++++
 .../iotdb/db/queryengine/plan/Coordinator.java     |   2 +
 .../execution/config/TableConfigTaskVisitor.java   |  11 +++
 .../config/executor/ClusterConfigTaskExecutor.java |  24 ++++-
 .../config/executor/IConfigTaskExecutor.java       |   8 +-
 .../config/metadata/relational/CountDBTask.java    |  82 ++++++++++++++++
 .../config/metadata/relational/ShowDBTask.java     |  22 ++---
 .../plan/relational/sql/ast/AstVisitor.java        |   4 +
 .../plan/relational/sql/ast/CountDB.java           |  77 +++++++++++++++
 .../plan/relational/sql/parser/AstBuilder.java     |   7 ++
 .../relational/sql/util/DataNodeSqlFormatter.java  |   9 +-
 .../metadata/relational/CountDBTaskTest.java       | 107 +++++++++++++++++++++
 .../sql/parser/CountDBStatementTest.java           |  65 +++++++++++++
 .../db/relational/grammar/sql/RelationalSql.g4     |   5 +
 14 files changed, 434 insertions(+), 16 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBDatabaseIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBDatabaseIT.java
index 46d27d997e9..4eab11ca3c7 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBDatabaseIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBDatabaseIT.java
@@ -816,6 +816,12 @@ public class IoTDBDatabaseIT {
     try (final Connection connection =
             EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
         final Statement statement = connection.createStatement()) {
+      TestUtils.assertResultSetEqual(
+          statement.executeQuery("count databases"), "count,", 
Collections.singleton("3,"));
+      TestUtils.assertResultSetEqual(
+          statement.executeQuery("select count(*) from 
information_schema.databases"),
+          "_col0,",
+          Collections.singleton("3,"));
       statement.execute("drop database test");
     }
 
@@ -826,6 +832,23 @@ public class IoTDBDatabaseIT {
     }
   }
 
+  @Test
+  public void testCountDatabases() throws SQLException {
+    try (final Connection connection =
+            EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+        final Statement statement = connection.createStatement()) {
+      statement.execute("create database db1");
+      statement.execute("create database db2");
+
+      TestUtils.assertResultSetEqual(
+          statement.executeQuery("count databases"), "count,", 
Collections.singleton("3,"));
+      TestUtils.assertResultSetEqual(
+          statement.executeQuery("select count(*) from 
information_schema.databases"),
+          "_col0,",
+          Collections.singleton("3,"));
+    }
+  }
+
   @Test
   public void testDBAuth() throws SQLException {
     try (final Connection adminCon = 
EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
@@ -839,6 +862,8 @@ public class IoTDBDatabaseIT {
     try (final Connection userCon =
             EnvFactory.getEnv().getConnection("test", "password123456", 
BaseEnv.TABLE_SQL_DIALECT);
         final Statement userStmt = userCon.createStatement()) {
+      TestUtils.assertResultSetEqual(
+          userStmt.executeQuery("count databases"), "count,", 
Collections.singleton("1,"));
       TestUtils.assertResultSetEqual(
           userStmt.executeQuery("show databases"),
           
"Database,TTL(ms),SchemaReplicationFactor,DataReplicationFactor,TimePartitionInterval,",
@@ -870,6 +895,8 @@ public class IoTDBDatabaseIT {
     try (final Connection userCon =
             EnvFactory.getEnv().getConnection("test", "password123456", 
BaseEnv.TABLE_SQL_DIALECT);
         final Statement userStmt = userCon.createStatement()) {
+      TestUtils.assertResultSetEqual(
+          userStmt.executeQuery("count databases"), "count,", 
Collections.singleton("2,"));
       try (final ResultSet resultSet = userStmt.executeQuery("SHOW 
DATABASES")) {
         final ResultSetMetaData metaData = resultSet.getMetaData();
         assertEquals(showDBColumnHeaders.size(), metaData.getColumnCount());
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/Coordinator.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/Coordinator.java
index d2e76b218b1..bd1512aa428 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/Coordinator.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/Coordinator.java
@@ -78,6 +78,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AddColumn;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AlterColumnDataType;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AlterDB;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ClearCache;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CountDB;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CreateDB;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CreateExternalService;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CreateFunction;
@@ -610,6 +611,7 @@ public class Coordinator {
     queryContext.setTimeOut(timeOut);
     queryContext.setStartTime(startTime);
     if (statement instanceof DropDB
+        || statement instanceof CountDB
         || statement instanceof ShowDB
         || statement instanceof CreateDB
         || statement instanceof AlterDB
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java
index e07bc920313..827590e86a0 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java
@@ -99,6 +99,7 @@ import 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational
 import 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.AlterTableRenameTableTask;
 import 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.AlterTableSetPropertiesTask;
 import 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.ClearCacheTask;
+import 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.CountDBTask;
 import 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.CreateDBTask;
 import 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.CreateTableTask;
 import 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.CreateTableViewTask;
@@ -156,6 +157,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AlterPipe;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AstVisitor;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ClearCache;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ColumnDefinition;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CountDB;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CreateDB;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CreateExternalService;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CreateFunction;
@@ -424,6 +426,15 @@ public class TableConfigTaskVisitor implements 
AstVisitor<IConfigTask, MPPQueryC
             canShowDB(accessControl, context.getSession().getUserName(), 
databaseName, context));
   }
 
+  @Override
+  public IConfigTask visitCountDB(final CountDB node, final MPPQueryContext 
context) {
+    context.setQueryType(QueryType.READ);
+    return new CountDBTask(
+        node,
+        databaseName ->
+            canShowDB(accessControl, context.getSession().getUserName(), 
databaseName, context));
+  }
+
   public static boolean canShowDB(
       final AccessControl accessControl,
       final String userName,
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/ClusterConfigTaskExecutor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/ClusterConfigTaskExecutor.java
index 3f632c9a987..15a538b98b4 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/ClusterConfigTaskExecutor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/ClusterConfigTaskExecutor.java
@@ -229,6 +229,7 @@ import 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.region.Ext
 import 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.region.MigrateRegionTask;
 import 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.region.ReconstructRegionTask;
 import 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.region.RemoveRegionTask;
+import 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.CountDBTask;
 import 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.DeleteDeviceTask;
 import 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.DescribeTableDetailsTask;
 import 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.DescribeTableTask;
@@ -255,6 +256,7 @@ import 
org.apache.iotdb.db.queryengine.plan.execution.config.sys.subscription.Sh
 import org.apache.iotdb.db.queryengine.plan.expression.Expression;
 import 
org.apache.iotdb.db.queryengine.plan.expression.visitor.TransformToViewExpressionVisitor;
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.view.AlterLogicalViewNode;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CountDB;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.DeleteDevice;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.DropDB;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ShowCluster;
@@ -4121,7 +4123,7 @@ public class ClusterConfigTaskExecutor implements 
IConfigTaskExecutor {
 
   @Override
   public SettableFuture<ConfigTaskResult> showDatabases(
-      final ShowDB showDB, final Predicate<String> canSeenDB) {
+      final ShowDB showDB, final Predicate<String> canSeeDB) {
     final SettableFuture<ConfigTaskResult> future = SettableFuture.create();
     // Construct request using statement
     final List<String> databasePathPattern = Arrays.asList(ALL_RESULT_NODES);
@@ -4133,7 +4135,25 @@ public class ClusterConfigTaskExecutor implements 
IConfigTaskExecutor {
               .setIsTableModel(true);
       final TShowDatabaseResp resp = client.showDatabase(req);
       // build TSBlock
-      ShowDBTask.buildTSBlock(resp.getDatabaseInfoMap(), future, 
showDB.isDetails(), canSeenDB);
+      ShowDBTask.buildTSBlock(resp.getDatabaseInfoMap(), future, 
showDB.isDetails(), canSeeDB);
+    } catch (final IOException | ClientManagerException | TException e) {
+      future.setException(e);
+    }
+    return future;
+  }
+
+  @Override
+  public SettableFuture<ConfigTaskResult> countDatabases(
+      final CountDB countDB, final Predicate<String> canSeeDB) {
+    final SettableFuture<ConfigTaskResult> future = SettableFuture.create();
+    final List<String> databasePathPattern = Arrays.asList(ALL_RESULT_NODES);
+    try (final ConfigNodeClient client =
+        
CONFIG_NODE_CLIENT_MANAGER.borrowClient(ConfigNodeInfo.CONFIG_REGION_ID)) {
+      final TGetDatabaseReq req =
+          new TGetDatabaseReq(databasePathPattern, ALL_MATCH_SCOPE.serialize())
+              .setIsTableModel(true);
+      final TShowDatabaseResp resp = client.showDatabase(req);
+      CountDBTask.buildTSBlock(resp.getDatabaseInfoMap(), future, canSeeDB);
     } catch (final IOException | ClientManagerException | TException e) {
       future.setException(e);
     }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/IConfigTaskExecutor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/IConfigTaskExecutor.java
index b4b928ba0b6..b45209aeffe 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/IConfigTaskExecutor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/IConfigTaskExecutor.java
@@ -41,6 +41,7 @@ import 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.region.Mig
 import 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.region.ReconstructRegionTask;
 import 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.region.RemoveRegionTask;
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.view.AlterLogicalViewNode;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CountDB;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.DeleteDevice;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.DropDB;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ShowCluster;
@@ -331,7 +332,10 @@ public interface IConfigTaskExecutor {
   // =============================== table syntax 
=========================================
 
   SettableFuture<ConfigTaskResult> showDatabases(
-      final ShowDB showDB, final Predicate<String> canSeenDB);
+      final ShowDB showDB, final Predicate<String> canSeeDB);
+
+  SettableFuture<ConfigTaskResult> countDatabases(
+      final CountDB countDB, final Predicate<String> canSeeDB);
 
   SettableFuture<ConfigTaskResult> showCluster(ShowCluster showCluster);
 
@@ -355,7 +359,7 @@ public interface IConfigTaskExecutor {
       final Boolean isShowCreateView);
 
   SettableFuture<ConfigTaskResult> showTables(
-      final String database, final Predicate<String> canSeenDB, final boolean 
isDetails);
+      final String database, final Predicate<String> checkCanShowTable, final 
boolean isDetails);
 
   TFetchTableResp fetchTables(final Map<String, Set<String>> fetchTableMap);
 
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/relational/CountDBTask.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/relational/CountDBTask.java
new file mode 100644
index 00000000000..d5605feac18
--- /dev/null
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/relational/CountDBTask.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational;
+
+import org.apache.iotdb.commons.conf.IoTDBConstant;
+import org.apache.iotdb.commons.schema.column.ColumnHeader;
+import org.apache.iotdb.db.queryengine.common.header.DatasetHeader;
+import org.apache.iotdb.db.queryengine.plan.execution.config.ConfigTaskResult;
+import org.apache.iotdb.db.queryengine.plan.execution.config.IConfigTask;
+import 
org.apache.iotdb.db.queryengine.plan.execution.config.executor.IConfigTaskExecutor;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CountDB;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.read.common.block.TsBlockBuilder;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.function.Predicate;
+
+import static 
org.apache.iotdb.commons.schema.table.InformationSchema.INFORMATION_DATABASE;
+
+public class CountDBTask implements IConfigTask {
+
+  private final CountDB node;
+  private final Predicate<String> canSeeDB;
+
+  public CountDBTask(final CountDB node, final Predicate<String> canSeeDB) {
+    this.node = node;
+    this.canSeeDB = canSeeDB;
+  }
+
+  @Override
+  public ListenableFuture<ConfigTaskResult> execute(final IConfigTaskExecutor 
configTaskExecutor)
+      throws InterruptedException {
+    return configTaskExecutor.countDatabases(node, canSeeDB);
+  }
+
+  public static void buildTSBlock(
+      final Map<String, ?> databaseInfoMap,
+      final SettableFuture<ConfigTaskResult> future,
+      final Predicate<String> canSeeDB) {
+    // information_schema is synthesized in table model rather than returned 
from ConfigNode.
+    final long databaseCount =
+        databaseInfoMap.keySet().stream()
+                .filter(databaseName -> 
!INFORMATION_DATABASE.equals(databaseName))
+                .filter(canSeeDB::test)
+                .count()
+            + (canSeeDB.test(INFORMATION_DATABASE) ? 1 : 0);
+
+    final TsBlockBuilder builder = new 
TsBlockBuilder(Collections.singletonList(TSDataType.INT32));
+    builder.getTimeColumnBuilder().writeLong(0L);
+    builder.getColumnBuilder(0).writeInt((int) databaseCount);
+    builder.declarePosition();
+
+    final DatasetHeader datasetHeader =
+        new DatasetHeader(
+            Collections.singletonList(
+                new ColumnHeader(IoTDBConstant.COLUMN_COUNT, 
TSDataType.INT32)),
+            true);
+    future.set(new ConfigTaskResult(TSStatusCode.SUCCESS_STATUS, 
builder.build(), datasetHeader));
+  }
+}
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/relational/ShowDBTask.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/relational/ShowDBTask.java
index f5ab58d3ead..ba8ed54541a 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/relational/ShowDBTask.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/relational/ShowDBTask.java
@@ -51,35 +51,35 @@ public class ShowDBTask implements IConfigTask {
   private final ShowDB node;
 
   // judge whether the specific database can be seen, dbName should be without 
`root.` prefix
-  private final Predicate<String> canSeenDB;
+  private final Predicate<String> canSeeDB;
 
-  public ShowDBTask(final ShowDB node, final Predicate<String> canSeenDB) {
+  public ShowDBTask(final ShowDB node, final Predicate<String> canSeeDB) {
     this.node = node;
-    this.canSeenDB = canSeenDB;
+    this.canSeeDB = canSeeDB;
   }
 
   @Override
   public ListenableFuture<ConfigTaskResult> execute(final IConfigTaskExecutor 
configTaskExecutor)
       throws InterruptedException {
-    return configTaskExecutor.showDatabases(node, canSeenDB);
+    return configTaskExecutor.showDatabases(node, canSeeDB);
   }
 
   public static void buildTSBlock(
       final Map<String, TDatabaseInfo> databaseInfoMap,
       final SettableFuture<ConfigTaskResult> future,
       final boolean isDetails,
-      final Predicate<String> canSeenDB) {
+      final Predicate<String> canSeeDB) {
     if (isDetails) {
-      buildTSBlockForDetails(databaseInfoMap, future, canSeenDB);
+      buildTSBlockForDetails(databaseInfoMap, future, canSeeDB);
     } else {
-      buildTSBlockForNonDetails(databaseInfoMap, future, canSeenDB);
+      buildTSBlockForNonDetails(databaseInfoMap, future, canSeeDB);
     }
   }
 
   private static void buildTSBlockForNonDetails(
       final Map<String, TDatabaseInfo> databaseInfoMap,
       final SettableFuture<ConfigTaskResult> future,
-      final Predicate<String> canSeenDB) {
+      final Predicate<String> canSeeDB) {
     final List<TSDataType> outputDataTypes =
         ColumnHeaderConstant.showDBColumnHeaders.stream()
             .map(ColumnHeader::getColumnType)
@@ -93,7 +93,7 @@ public class ShowDBTask implements IConfigTask {
             .sorted(Map.Entry.comparingByKey())
             .collect(Collectors.toList())) {
       final String dbName = entry.getKey();
-      if (Boolean.FALSE.equals(canSeenDB.test(dbName))) {
+      if (Boolean.FALSE.equals(canSeeDB.test(dbName))) {
         continue;
       }
       if (dbName.equals(INFORMATION_DATABASE)) {
@@ -127,7 +127,7 @@ public class ShowDBTask implements IConfigTask {
   private static void buildTSBlockForDetails(
       final Map<String, TDatabaseInfo> databaseMap,
       final SettableFuture<ConfigTaskResult> future,
-      final Predicate<String> canSeenDB) {
+      final Predicate<String> canSeeDB) {
     final List<TSDataType> outputDataTypes =
         ColumnHeaderConstant.showDBDetailsColumnHeaders.stream()
             .map(ColumnHeader::getColumnType)
@@ -141,7 +141,7 @@ public class ShowDBTask implements IConfigTask {
             .sorted(Map.Entry.comparingByKey())
             .collect(Collectors.toList())) {
       final String dbName = entry.getKey();
-      if (!canSeenDB.test(dbName)) {
+      if (!canSeeDB.test(dbName)) {
         continue;
       }
       if (dbName.equals(INFORMATION_DATABASE)) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/AstVisitor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/AstVisitor.java
index 2e236e116e8..7c311fa7f62 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/AstVisitor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/AstVisitor.java
@@ -79,6 +79,10 @@ public interface AstVisitor<R, C> extends 
CommonQueryAstVisitor<R, C> {
     return visitStatement(node, context);
   }
 
+  default R visitCountDB(final CountDB node, final C context) {
+    return visitStatement(node, context);
+  }
+
   default R visitCreateTable(final CreateTable node, final C context) {
     return visitStatement(node, context);
   }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/CountDB.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/CountDB.java
new file mode 100644
index 00000000000..49f113421b2
--- /dev/null
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/CountDB.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.queryengine.plan.relational.sql.ast;
+
+import 
org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.AstMemoryEstimationHelper;
+import 
org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.IAstVisitor;
+import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Node;
+import 
org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.NodeLocation;
+import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Statement;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.tsfile.utils.RamUsageEstimator;
+
+import java.util.List;
+
+import static java.util.Objects.requireNonNull;
+
+public class CountDB extends Statement {
+
+  private static final long INSTANCE_SIZE = 
RamUsageEstimator.shallowSizeOfInstance(CountDB.class);
+
+  public CountDB(final NodeLocation location) {
+    super(requireNonNull(location, "location is null"));
+  }
+
+  @Override
+  public <R, C> R accept(final IAstVisitor<R, C> visitor, final C context) {
+    return ((AstVisitor<R, C>) visitor).visitCountDB(this, context);
+  }
+
+  @Override
+  public List<Node> getChildren() {
+    return ImmutableList.of();
+  }
+
+  @Override
+  public int hashCode() {
+    return getClass().hashCode();
+  }
+
+  @Override
+  public boolean equals(final Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    return (obj != null) && (getClass() == obj.getClass());
+  }
+
+  @Override
+  public String toString() {
+    return "COUNT DATABASES";
+  }
+
+  @Override
+  public long ramBytesUsed() {
+    long size = INSTANCE_SIZE;
+    size += 
AstMemoryEstimationHelper.getEstimatedSizeOfNodeLocation(getLocationInternal());
+    return size;
+  }
+}
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
index 768a05689e8..25d68eba6be 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
@@ -162,6 +162,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AsofJoinOn;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ClearCache;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ColumnDefinition;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CopyTo;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CountDB;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CountDevice;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CountStatement;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CreateDB;
@@ -406,6 +407,12 @@ public class AstBuilder extends 
RelationalSqlBaseVisitor<Node> {
     return new ShowDB(getLocation(ctx), Objects.nonNull(ctx.DETAILS()));
   }
 
+  @Override
+  public Node visitCountDatabasesStatement(
+      final RelationalSqlParser.CountDatabasesStatementContext ctx) {
+    return new CountDB(getLocation(ctx));
+  }
+
   @Override
   public Node visitCreateDbStatement(final 
RelationalSqlParser.CreateDbStatementContext ctx) {
     List<Property> properties = ImmutableList.of();
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/DataNodeSqlFormatter.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/DataNodeSqlFormatter.java
index 4797d43cf21..b806fd72592 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/DataNodeSqlFormatter.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/DataNodeSqlFormatter.java
@@ -32,6 +32,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AlterPipe;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AstVisitor;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ColumnDefinition;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CopyTo;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CountDB;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CreateDB;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CreateFunction;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CreatePipe;
@@ -134,7 +135,13 @@ public final class DataNodeSqlFormatter extends 
CommonQuerySqlFormatter
 
   @Override
   public Void visitShowDB(ShowDB node, Integer indent) {
-    builder.append("SHOW DATABASE");
+    builder.append("SHOW DATABASES");
+    return null;
+  }
+
+  @Override
+  public Void visitCountDB(CountDB node, Integer indent) {
+    builder.append("COUNT DATABASES");
     return null;
   }
 
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/relational/CountDBTaskTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/relational/CountDBTaskTest.java
new file mode 100644
index 00000000000..b29c11c2a91
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/relational/CountDBTaskTest.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational;
+
+import org.apache.iotdb.commons.conf.IoTDBConstant;
+import 
org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.NodeLocation;
+import org.apache.iotdb.db.queryengine.plan.execution.config.ConfigTaskResult;
+import 
org.apache.iotdb.db.queryengine.plan.execution.config.executor.IConfigTaskExecutor;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CountDB;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import com.google.common.util.concurrent.SettableFuture;
+import org.apache.tsfile.read.common.block.TsBlock;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Predicate;
+
+import static 
org.apache.iotdb.commons.schema.table.InformationSchema.INFORMATION_DATABASE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class CountDBTaskTest {
+
+  @Test
+  public void testBuildTSBlockCountsVisibleDatabases() throws Exception {
+    final Map<String, Object> databaseInfoMap = new HashMap<>();
+    databaseInfoMap.put("db1", new Object());
+    databaseInfoMap.put("db2", new Object());
+
+    final SettableFuture<ConfigTaskResult> future = SettableFuture.create();
+    CountDBTask.buildTSBlock(databaseInfoMap, future, databaseName -> 
!"db2".equals(databaseName));
+
+    final ConfigTaskResult result = future.get();
+    final TsBlock resultSet = result.getResultSet();
+
+    assertEquals(TSStatusCode.SUCCESS_STATUS, result.getStatusCode());
+    assertEquals(
+        Collections.singletonList(IoTDBConstant.COLUMN_COUNT),
+        result.getResultSetHeader().getRespColumns());
+    assertEquals(1, resultSet.getPositionCount());
+    assertEquals(2, resultSet.getColumn(0).getInt(0));
+  }
+
+  @Test
+  public void testBuildTSBlockCanHideInformationSchema() throws Exception {
+    final Map<String, Object> databaseInfoMap = new HashMap<>();
+    databaseInfoMap.put("db1", new Object());
+    databaseInfoMap.put("db2", new Object());
+
+    final SettableFuture<ConfigTaskResult> future = SettableFuture.create();
+    CountDBTask.buildTSBlock(databaseInfoMap, future, databaseName -> 
"db1".equals(databaseName));
+
+    final ConfigTaskResult result = future.get();
+    assertEquals(1, result.getResultSet().getColumn(0).getInt(0));
+  }
+
+  @Test
+  public void testBuildTSBlockDoesNotDoubleCountInformationSchema() throws 
Exception {
+    final Map<String, Object> databaseInfoMap = new HashMap<>();
+    databaseInfoMap.put("db1", new Object());
+    databaseInfoMap.put(INFORMATION_DATABASE, new Object());
+
+    final SettableFuture<ConfigTaskResult> future = SettableFuture.create();
+    CountDBTask.buildTSBlock(databaseInfoMap, future, databaseName -> true);
+
+    final ConfigTaskResult result = future.get();
+    assertEquals(2, result.getResultSet().getColumn(0).getInt(0));
+  }
+
+  @Test
+  public void testExecuteDelegatesToExecutor() throws Exception {
+    final CountDB node = new CountDB(new NodeLocation(1, 1));
+    final Predicate<String> canSeenDB = databaseName -> true;
+    final CountDBTask task = new CountDBTask(node, canSeenDB);
+    final IConfigTaskExecutor executor = 
Mockito.mock(IConfigTaskExecutor.class);
+    final SettableFuture<ConfigTaskResult> future = SettableFuture.create();
+
+    when(executor.countDatabases(same(node), 
same(canSeenDB))).thenReturn(future);
+
+    assertSame(future, task.execute(executor));
+    verify(executor).countDatabases(same(node), same(canSeenDB));
+  }
+}
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/CountDBStatementTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/CountDBStatementTest.java
new file mode 100644
index 00000000000..fa36bcd1bf8
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/CountDBStatementTest.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.queryengine.plan.relational.sql.parser;
+
+import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Statement;
+import 
org.apache.iotdb.commons.queryengine.plan.relational.sql.parser.ParsingException;
+import org.apache.iotdb.db.protocol.session.IClientSession;
+import org.apache.iotdb.db.protocol.session.InternalClientSession;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CountDB;
+import 
org.apache.iotdb.db.queryengine.plan.relational.sql.util.DataNodeSqlFormatter;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.time.ZoneId;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+public class CountDBStatementTest {
+
+  private SqlParser sqlParser;
+  private IClientSession clientSession;
+
+  @Before
+  public void setUp() {
+    sqlParser = new SqlParser();
+    clientSession = new InternalClientSession("testClient");
+  }
+
+  @Test
+  public void testCountDatabaseStatementRejected() {
+    assertThrows(
+        ParsingException.class,
+        () -> sqlParser.createStatement("count database", 
ZoneId.systemDefault(), clientSession));
+  }
+
+  @Test
+  public void testCountDatabasesStatement() {
+    final Statement statement =
+        sqlParser.createStatement("count databases", ZoneId.systemDefault(), 
clientSession);
+
+    assertTrue(statement instanceof CountDB);
+    assertEquals("COUNT DATABASES", statement.toString());
+    assertEquals("COUNT DATABASES", 
DataNodeSqlFormatter.formatDataNodeSql(statement));
+  }
+}
diff --git 
a/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
 
b/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
index ca766464686..f984e825fc7 100644
--- 
a/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
+++ 
b/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
@@ -49,6 +49,7 @@ statement
     // Database Statement
     | useDatabaseStatement
     | showDatabasesStatement
+    | countDatabasesStatement
     | createDbStatement
     | alterDbStatement
     | dropDbStatement
@@ -203,6 +204,10 @@ showDatabasesStatement
     : SHOW DATABASES (DETAILS)?
     ;
 
+countDatabasesStatement
+    : COUNT DATABASES
+    ;
+
 createDbStatement
     : CREATE DATABASE (IF NOT EXISTS)? database=identifier (WITH properties)?
     ;


Reply via email to