This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new bc5f8cd7c80 [fix](fe) Skip HMS database events that
include/exclude_database_list filters out (#70165)
bc5f8cd7c80 is described below
commit bc5f8cd7c800245b925faefef0a7c01467fef6a9
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Thu Nov 5 07:08:02 2026 +0000
[fix](fe) Skip HMS database events that include/exclude_database_list
filters out (#70165)
### What problem does this PR solve?
Issue Number: None
Related PR: #67417, #68196
Problem Summary:
**In short.** This ports from branch-4.x #67417 and #68196 the check
that keeps a metastore event from adding a database that
`include_database_list` or `exclude_database_list` keeps out of an HMS
catalog. On master, a CREATE DATABASE event for such a database puts it
into the catalog's database list on every FE that applies the event:
SHOW DATABASES lists it and queries can reach it until the list is
reloaded.
**Background.** A catalog lists its databases through
`ExternalCatalog#getFilteredDatabaseNames`, which drops a remote
database that `exclude_database_list` names or, when
`include_database_list` is set, one that it does not name. On an HMS
catalog with `hive.enable_hms_events_incremental_sync`,
`MetastoreEventSyncDriver` applies the metastore's events on every FE. A
CREATE DATABASE event, and the new name of an ALTER DATABASE rename,
reach `PluginDrivenExternalCatalog#registerDatabaseFromEvent`, which
adds the database to the cached database names. branch-4.1 and
branch-4.2 have checked the two lists for these events since #67417.
**The problem, and what it cost.** `registerDatabaseFromEvent` adds the
database without the check. A database that the catalog is configured to
hide then shows in SHOW DATABASES, `USE` and queries reach it
(privileges still apply), and table events register tables in it. It
goes away only when the cached names are reloaded: after
`external_cache_refresh_time_minutes` (10 by default), on a lookup of a
name that is not cached, on REFRESH CATALOG, or after a restart.
**How this PR fixes it.** The check moves out of
`getFilteredDatabaseNames` into `ExternalCatalog#isDatabaseListed`,
which the listing and `registerDatabaseFromEvent` share; an event for a
database the lists keep out returns before it touches any cache. Like
the listing, the check compares the remote name exactly. Not ported from
branch-4.x: skipping the table, partition and refresh events of such
databases, a performance item (on master they find no database and
return, after one reload of the names), and comparing names ignoring
case when `lower_case_database_names` is not 0, which would change the
listing as well.
**Results.**
| Event, with the database names cached | Before | After |
|---|---|---|
| CREATE DATABASE `archived`, `exclude_database_list = archived` |
`archived` listed | not listed |
| CREATE DATABASE `other`, `include_database_list = sales,sales2` |
`other` listed | not listed |
| CREATE DATABASE `sales2`, allowed by either list | listed | listed |
Original author: @924060929
Co-authored-by: 924060929 <[email protected]>
---
.../apache/doris/datasource/ExternalCatalog.java | 36 ++++---
.../plugin/PluginDrivenExternalCatalog.java | 5 +
...PluginDrivenExternalCatalogEventFilterTest.java | 118 +++++++++++++++++++++
3 files changed, 147 insertions(+), 12 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalCatalog.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalCatalog.java
index 7b64a46d62d..fa39a5e0594 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalCatalog.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalCatalog.java
@@ -601,18 +601,9 @@ public abstract class ExternalCatalog
Map<String, Boolean> excludeDatabaseMap = getExcludeDatabaseMap();
List<Pair<String, String>> remoteToLocalPairs = Lists.newArrayList();
- allDatabases = allDatabases.stream().filter(dbName -> {
- if (!dbName.equals(InfoSchemaDb.DATABASE_NAME) &&
!dbName.equals(MysqlDb.DATABASE_NAME)) {
- // Exclude database map take effect with higher priority over
include database map
- if (!excludeDatabaseMap.isEmpty() &&
excludeDatabaseMap.containsKey(dbName)) {
- return false;
- }
- if (!includeDatabaseMap.isEmpty() &&
!includeDatabaseMap.containsKey(dbName)) {
- return false;
- }
- }
- return true;
- }).collect(Collectors.toList());
+ allDatabases = allDatabases.stream()
+ .filter(dbName -> isDatabaseListed(dbName, includeDatabaseMap,
excludeDatabaseMap))
+ .collect(Collectors.toList());
for (String remoteDbName : allDatabases) {
remoteToLocalPairs.add(Pair.of(
@@ -1415,6 +1406,27 @@ public abstract class ExternalCatalog
throw new NotImplementedException("registerDatabaseFromEvent not
implemented");
}
+ /**
+ * Whether the database list of this catalog keeps the remote database:
information_schema and mysql always, the
+ * others as include_database_list and exclude_database_list allow. A
database that an event announces is added
+ * only when it is.
+ */
+ protected boolean isDatabaseListed(String remoteDbName) {
+ return isDatabaseListed(remoteDbName, getIncludeDatabaseMap(),
getExcludeDatabaseMap());
+ }
+
+ private static boolean isDatabaseListed(String remoteDbName, Map<String,
Boolean> includeDatabaseMap,
+ Map<String, Boolean> excludeDatabaseMap) {
+ if (remoteDbName.equals(InfoSchemaDb.DATABASE_NAME) ||
remoteDbName.equals(MysqlDb.DATABASE_NAME)) {
+ return true;
+ }
+ // Exclude database map take effect with higher priority over include
database map
+ if (!excludeDatabaseMap.isEmpty() &&
excludeDatabaseMap.containsKey(remoteDbName)) {
+ return false;
+ }
+ return includeDatabaseMap.isEmpty() ||
includeDatabaseMap.containsKey(remoteDbName);
+ }
+
protected Map<String, Boolean> getIncludeDatabaseMap() {
return getSpecifiedDatabaseMap(ExternalCatalog.INCLUDE_DATABASE_LIST);
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/plugin/PluginDrivenExternalCatalog.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/plugin/PluginDrivenExternalCatalog.java
index d58087e1d43..95628d1eca6 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/plugin/PluginDrivenExternalCatalog.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/plugin/PluginDrivenExternalCatalog.java
@@ -692,6 +692,11 @@ public class PluginDrivenExternalCatalog extends
ExternalCatalog {
*/
@Override
public void registerDatabaseFromEvent(String remoteDbName, String
localDbName) {
+ // The database list leaves out what include_database_list and
exclude_database_list keep out; an event must
+ // not add it to the list either.
+ if (!isDatabaseListed(remoteDbName)) {
+ return;
+ }
long dbId = Util.genIdByName(getName(), localDbName);
try {
ExternalDatabase<? extends ExternalTable> db =
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/datasource/plugin/PluginDrivenExternalCatalogEventFilterTest.java
b/fe/fe-core/src/test/java/org/apache/doris/datasource/plugin/PluginDrivenExternalCatalogEventFilterTest.java
new file mode 100644
index 00000000000..c9114f6ca7e
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/datasource/plugin/PluginDrivenExternalCatalogEventFilterTest.java
@@ -0,0 +1,118 @@
+// 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.doris.datasource.plugin;
+
+import org.apache.doris.catalog.Env;
+import org.apache.doris.connector.spi.Connector;
+import org.apache.doris.datasource.ExternalCatalog;
+import org.apache.doris.datasource.ExternalDatabase;
+import org.apache.doris.datasource.ExternalMetaCacheMgr;
+import org.apache.doris.datasource.ExternalTable;
+import org.apache.doris.datasource.log.InitCatalogLog;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.MoreExecutors;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A database that include_database_list or exclude_database_list keeps out of
a catalog's database list must not
+ * get into it through a metastore event (a CREATE DATABASE, or an ALTER
DATABASE that renames to it) either.
+ */
+public class PluginDrivenExternalCatalogEventFilterTest {
+
+ @Test
+ public void testEventDoesNotAddExcludedDatabase() {
+ assertEventKeepsOut(ExternalCatalog.EXCLUDE_DATABASE_LIST, "archived",
"archived", "sales2");
+ }
+
+ @Test
+ public void testEventDoesNotAddDatabaseOutsideIncludeList() {
+ assertEventKeepsOut(ExternalCatalog.INCLUDE_DATABASE_LIST,
"sales,sales2", "other", "sales2");
+ }
+
+ // With the database names cached, an event for keptOut leaves them as
they are, and one for allowed adds it.
+ private static void assertEventKeepsOut(String filterKey, String
filterValue, String keptOut, String allowed) {
+ Env env = Mockito.mock(Env.class);
+ ExternalMetaCacheMgr cacheMgr =
Mockito.mock(ExternalMetaCacheMgr.class);
+ Mockito.when(env.getExtMetaCacheMgr()).thenReturn(cacheMgr);
+
Mockito.when(cacheMgr.commonRefreshExecutor()).thenReturn(MoreExecutors.newDirectExecutorService());
+ try (MockedStatic<Env> mockedEnv = Mockito.mockStatic(Env.class)) {
+ mockedEnv.when(Env::getCurrentEnv).thenReturn(env);
+ FilteredCatalog catalog = new FilteredCatalog(filterKey,
filterValue);
+ catalog.setInitializedForTest(true);
+ Assertions.assertFalse(catalog.getDbNames().contains(keptOut));
+
+ catalog.registerDatabaseFromEvent(keptOut, keptOut);
+ Assertions.assertFalse(catalog.getDbNames().contains(keptOut));
+ Assertions.assertFalse(catalog.builtDatabases.contains(keptOut));
+
+ catalog.registerDatabaseFromEvent(allowed, allowed);
+ Assertions.assertTrue(catalog.getDbNames().contains(allowed));
+ }
+ }
+
+ private static class FilteredCatalog extends PluginDrivenExternalCatalog {
+ private final List<String> builtDatabases = new ArrayList<>();
+
+ FilteredCatalog(String filterKey, String filterValue) {
+ super(1L, "event_filter_catalog", null, properties(filterKey,
filterValue), "",
+ Mockito.mock(Connector.class));
+ }
+
+ @Override
+ protected void initLocalObjectsImpl() {
+ }
+
+ @Override
+ protected List<String> listDatabaseNames() {
+ return ImmutableList.of("sales", "archived", "other");
+ }
+
+ @Override
+ public String fromRemoteDatabaseName(String remoteDatabaseName) {
+ return remoteDatabaseName;
+ }
+
+ @Override
+ protected ExternalDatabase<? extends ExternalTable> buildDbForInit(
+ String remoteDbName, String localDbName, long dbId,
+ InitCatalogLog.Type logType, boolean checkExists) {
+ builtDatabases.add(localDbName);
+ ExternalDatabase<? extends ExternalTable> db =
Mockito.mock(ExternalDatabase.class);
+ Mockito.when(db.getId()).thenReturn(dbId);
+ Mockito.when(db.getRemoteName()).thenReturn(remoteDbName);
+ Mockito.when(db.getFullName()).thenReturn(localDbName);
+ return db;
+ }
+
+ private static Map<String, String> properties(String filterKey, String
filterValue) {
+ Map<String, String> properties = new HashMap<>();
+ properties.put("type", "hms");
+ properties.put(filterKey, filterValue);
+ return properties;
+ }
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]