jerryshao commented on code in PR #12547:
URL: https://github.com/apache/gravitino/pull/12547#discussion_r3841838795
##########
trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorManager.java:
##########
@@ -212,67 +330,170 @@ public GravitinoMetalake retrieveMetalake(String
metalakeName) {
}
private void loadCatalogs(GravitinoMetalake metalake) {
- List<String> catalogNames;
+ String metalakeName = metalake.name();
+ String[] allCatalogNames;
try {
- catalogNames =
- Arrays.stream(metalake.listCatalogs())
- .filter(id -> !skipCatalog(getTrinoCatalogName(metalake.name(),
id)))
- .collect(Collectors.toList());
+ allCatalogNames = metalake.listCatalogs();
} catch (Exception e) {
- LOG.error("Failed to list catalogs in metalake {}.", metalake.name(), e);
+ // Keep the existing catalog states untouched, a transient listing
failure must not turn
+ // healthy catalogs into failed ones. The load status system table
reports the cause.
+ recordMetalakeError(metalakeName, e);
return;
}
+ metalakeErrors.remove(metalakeName);
+
+ // The Trino names of every catalog the Gravitino server currently
reports, including the
+ // catalogs that are intentionally not registered.
+ Set<String> presentTrinoNames = new HashSet<>();
+ List<String> catalogNames = new ArrayList<>();
+ for (String catalogName : allCatalogNames) {
+ String trinoCatalogName = getTrinoCatalogName(metalakeName, catalogName);
+ presentTrinoNames.add(trinoCatalogName);
+ if (skipCatalog(trinoCatalogName)) {
+ recordCatalogState(
+ CatalogRegistrationState.skipped(
+ metalakeName,
+ catalogName,
+ trinoCatalogName,
+ "Matched gravitino.trino.skip-catalog-patterns"),
+ null);
+ continue;
+ }
+ catalogNames.add(catalogName);
+ }
- LOG.debug("Load metalake {}'s catalogs. catalogs: {}.", metalake.name(),
catalogNames);
+ LOG.debug("Load metalake {}'s catalogs. catalogs: {}.", metalakeName,
catalogNames);
// Delete those catalogs that have been deleted in Gravitino server
- Set<String> catalogNameStrings =
- catalogNames.stream()
- .map(id -> getTrinoCatalogName(metalake.name(), id))
- .collect(Collectors.toSet());
+ Set<String> catalogNameStrings = new HashSet<>();
+ for (String catalogName : catalogNames) {
+ catalogNameStrings.add(getTrinoCatalogName(metalakeName, catalogName));
+ }
for (Map.Entry<String, CatalogConnectorContext> entry :
catalogConnectors.entrySet()) {
if (!catalogNameStrings.contains(entry.getKey())
&&
// Skip the catalog doesn't belong to this metalake.
- entry.getValue().getMetalake().name().equals(metalake.name())) {
+ entry.getValue().getMetalake().name().equals(metalakeName)) {
try {
unloadCatalog(entry.getValue().getCatalog());
} catch (Exception e) {
- LOG.error("Failed to remove catalog {}.", entry.getKey(), e);
+ // The catalog is gone from Gravitino but is still registered in
Trino. Record it, or
+ // the pruning below would drop the row and the table would report
nothing at all about
+ // a catalog that still shows up in SHOW CATALOGS.
+ GravitinoCatalog catalog = entry.getValue().getCatalog();
+ recordCatalogState(
+ CatalogRegistrationState.failed(
+ metalakeName,
+ catalog.getName(),
+ entry.getKey(),
+ catalog.getProvider(),
+ "The catalog was deleted in Gravitino but could not be
unregistered from Trino: "
+ + toErrorMessage(e)),
+ e);
}
}
}
+ // Drop the states of catalogs that no longer exist in the Gravitino
server, including the
+ // states of catalogs that never had a connector. A catalog whose
connector could not be
+ // removed from Trino is kept, so that its failure stays visible for as
long as it is real.
+ catalogStates
+ .values()
+ .removeIf(
+ state ->
+ state.getMetalake().equals(metalakeName)
+ && !presentTrinoNames.contains(state.getTrinoCatalogName())
+ &&
!catalogConnectors.containsKey(state.getTrinoCatalogName()));
+
// Load new catalogs belows to the metalake.
- catalogNames.stream()
- .forEach(
- (String catalogName) -> {
- try {
- Catalog catalog = metalake.loadCatalog(catalogName);
- GravitinoCatalog gravitinoCatalog = new
GravitinoCatalog(metalake.name(), catalog);
- if
(catalogConnectors.containsKey(getTrinoCatalogName(gravitinoCatalog))) {
- // Reload catalogs that have been updated in Gravitino
server.
- reloadCatalog(gravitinoCatalog);
- } else {
- if (catalog.type() == Catalog.Type.RELATIONAL
- && catalogConnectorFactory
- .getSupportedCatalogProviders()
- .contains(gravitinoCatalog.getProvider())) {
- loadCatalog(gravitinoCatalog);
- }
- }
- } catch (UnsupportedOperationException e) {
- LOG.warn(
- "Unsupported catalog type for catalog {} in metalake {}:
{}",
- catalogName,
- metalake.name(),
- e.getMessage());
- } catch (Exception e) {
- LOG.error(
- "Failed to load metalake {}'s catalog {}.",
metalake.name(), catalogName, e);
- }
+ for (String catalogName : catalogNames) {
+ String trinoCatalogName = getTrinoCatalogName(metalakeName, catalogName);
+ // Tracked outside the try so that a failure can still report the
provider it knows about.
+ String provider = null;
+ try {
+ Catalog catalog = metalake.loadCatalog(catalogName);
Review Comment:
**[Confirmed bug]** An `UnsupportedOperationException` thrown by
`metalake.loadCatalog()` for a catalog type the client library doesn't
recognize is now recorded as `FAILED` (with escalating `failure_count` and an
ERROR log) instead of being handled as `UNSUPPORTED`.
`DTOConverters.toCatalog` (clients/client-java) throws
`UnsupportedOperationException("Unsupported catalog type: " + catalog.type())`
for a catalog type it doesn't map. The old code caught this specifically and
logged a WARN with no persisted state. This new loop wraps
`metalake.loadCatalog(catalogName)` inside the same try whose only catch is the
generic `catch (Exception e)` below, which calls
`recordCatalogState(CatalogRegistrationState.failed(...))`. Because the
type/provider checks run only *after* `loadCatalog()` succeeds, this catalog is
permanently misreported as FAILED (not UNSUPPORTED) in
`gravitino.system.catalog_status`, defeating the PR's own goal of letting users
self-diagnose via that table.
##########
trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/system/table/GravitinoSystemTableCatalog.java:
##########
@@ -54,23 +56,29 @@ public class GravitinoSystemTableCatalog extends
GravitinoSystemTable {
ColumnMetadata.builder().setName("properties").setType(VARCHAR).build()));
private final CatalogConnectorManager catalogConnectorManager;
+ private final String metalake;
/**
* Constructs a new GravitinoSystemTableCatalog.
*
* @param catalogConnectorManager the manager for catalog connectors
+ * @param metalake the metalake to report on
*/
- public GravitinoSystemTableCatalog(CatalogConnectorManager
catalogConnectorManager) {
+ public GravitinoSystemTableCatalog(
+ CatalogConnectorManager catalogConnectorManager, String metalake) {
this.catalogConnectorManager = catalogConnectorManager;
+ this.metalake = metalake;
}
@Override
public Page loadPageData() {
List<GravitinoCatalog> gravitinoCatalogs = new ArrayList<>();
// retrieve catalogs form the Gravitino server with the configuration
metalakes,
// the catalogConnectorManager does not manager catalogs in worker nodes
- catalogConnectorManager
- .getUsedMetalakes()
+ // Only the metalake this connector is configured with: the manager is
shared by every entry
+ // catalog in this Trino.
+ catalogConnectorManager.getUsedMetalakes().stream()
+ .filter(metalake::equals)
.forEach(
(metalakeName) -> {
GravitinoMetalake metalake =
catalogConnectorManager.getMetalake(metalakeName);
Review Comment:
**[Confirmed bug, a few lines below]** `loadPageData()` calls
`catalogConnectorManager.skipCatalog(catalog.name())` (unchanged line just
below this hunk) with the bare Gravitino catalog name, while the load loop
(`CatalogConnectorManager.loadCatalogs`) matches skip patterns against the
Trino-qualified name via `skipCatalog(trinoCatalogName)`.
In multi-metalake mode, `getTrinoCatalogName` returns a quoted
`"metalake.catalog"` form. If `gravitino.trino.skip-catalog-patterns` is
written against that qualified form (e.g. `prod\..*`), the load loop correctly
skips/unregisters matching catalogs (and now reports them SKIPPED in
catalog_status), but this table still evaluates `skipCatalog()` against the
bare catalog name, which never matches a metalake-qualified pattern — so
`gravitino.system.catalog` keeps listing a catalog that is actually SKIPPED and
absent from `SHOW CATALOGS`, misleading a user into thinking it is usable.
##########
trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorManager.java:
##########
@@ -310,6 +531,7 @@ private void unloadCatalog(GravitinoCatalog catalog) {
String catalogFullName = getTrinoCatalogName(catalog);
catalogRegister.unregisterCatalog(catalogFullName);
catalogConnectors.remove(catalogFullName);
+ catalogStates.remove(catalogFullName);
Review Comment:
**[Confirmed bug]** A catalog that newly starts matching
`gravitino.trino.skip-catalog-patterns` has its just-recorded `SKIPPED` state
deleted in the very same load cycle by this method, which unconditionally
removes the `catalogStates` entry.
When a previously-REGISTERED catalog starts matching a skip pattern: (1) the
load loop calls `recordCatalogState(skipped(...))` for it; (2) since it's now
excluded from `catalogNames`, the unload loop sees it still in
`catalogConnectors` and calls `unloadCatalog(...)`; (3) `unloadCatalog()`
unregisters it from Trino correctly but also does
`catalogStates.remove(catalogFullName)` here, wiping the SKIPPED row just
written in step 1. For that one refresh cycle,
`gravitino.system.catalog_status` has no row at all for the catalog,
contradicting the PR's own documentation claim that catalog_status "covers
every catalog the connector considered, including the ones catalog filters
out." It reappears correctly on the next tick.
##########
trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/system/table/GravitinoSystemTableLoadStatus.java:
##########
@@ -0,0 +1,131 @@
+/*
+ * 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.gravitino.trino.connector.system.table;
+
+import static io.trino.spi.type.BigintType.BIGINT;
+import static io.trino.spi.type.BooleanType.BOOLEAN;
+import static io.trino.spi.type.VarcharType.VARCHAR;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.trino.spi.Page;
+import io.trino.spi.block.BlockBuilder;
+import io.trino.spi.connector.ColumnMetadata;
+import io.trino.spi.connector.ConnectorTableMetadata;
+import io.trino.spi.connector.SchemaTableName;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import org.apache.gravitino.trino.connector.catalog.CatalogConnectorManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An implementation of the load status system table.
+ *
+ * <p>It reports the health of the loop that registers Apache Gravitino
catalogs into Trino. A
+ * failure that prevents the loop from listing catalogs at all, such as an
unreachable Gravitino
+ * server, has no catalog to attach itself to and is only visible here.
+ */
+public class GravitinoSystemTableLoadStatus extends GravitinoSystemTable {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(GravitinoSystemTableLoadStatus.class);
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+ /** The name of the load status system table. */
+ public static final SchemaTableName TABLE_NAME =
+ new SchemaTableName(SYSTEM_TABLE_SCHEMA_NAME, "load_status");
+
+ private static final ConnectorTableMetadata TABLE_METADATA =
+ new ConnectorTableMetadata(
+ TABLE_NAME,
+ List.of(
+
ColumnMetadata.builder().setName("trino_started").setType(BOOLEAN).build(),
+
ColumnMetadata.builder().setName("last_attempt_time").setType(VARCHAR).build(),
+
ColumnMetadata.builder().setName("last_success_time").setType(VARCHAR).build(),
+
ColumnMetadata.builder().setName("consecutive_failures").setType(BIGINT).build(),
+
ColumnMetadata.builder().setName("last_error").setType(VARCHAR).build(),
+
ColumnMetadata.builder().setName("metalake_errors").setType(VARCHAR).build()));
+
+ private final CatalogConnectorManager catalogConnectorManager;
+ private final String metalake;
+
+ /**
+ * Constructs a new GravitinoSystemTableLoadStatus.
+ *
+ * @param catalogConnectorManager the manager for catalog connectors
+ * @param metalake the metalake to report errors for
+ */
+ public GravitinoSystemTableLoadStatus(
+ CatalogConnectorManager catalogConnectorManager, String metalake) {
+ this.catalogConnectorManager = catalogConnectorManager;
+ this.metalake = metalake;
+ }
+
+ @Override
+ public Page loadPageData() {
+ BlockBuilder trinoStartedColumnBuilder = BOOLEAN.createBlockBuilder(null,
1);
+ BlockBuilder lastAttemptTimeColumnBuilder =
VARCHAR.createBlockBuilder(null, 1);
+ BlockBuilder lastSuccessTimeColumnBuilder =
VARCHAR.createBlockBuilder(null, 1);
+ BlockBuilder consecutiveFailuresColumnBuilder =
BIGINT.createBlockBuilder(null, 1);
+ BlockBuilder lastErrorColumnBuilder = VARCHAR.createBlockBuilder(null, 1);
+ BlockBuilder metalakeErrorsColumnBuilder =
VARCHAR.createBlockBuilder(null, 1);
+
+ BOOLEAN.writeBoolean(trinoStartedColumnBuilder,
catalogConnectorManager.isTrinoStarted());
Review Comment:
**[Plausible]** `loadPageData()` makes six independent, unsynchronized reads
of `CatalogConnectorManager` (`isTrinoStarted`, `getLastLoadAttemptTimeMs`,
`getLastSuccessfulLoadTimeMs`, `getConsecutiveLoadFailures`,
`getLastLoadError`, `getMetalakeErrors`) that the load loop updates as a group,
with no atomic snapshot.
The background load loop writes these separate fields sequentially over the
course of one cycle. A query thread landing mid-cycle can read
`trino_started=true` (already updated) together with a stale `last_error` from
the previous failed cycle and a `consecutive_load_failures` count not yet
reset, producing a self-contradictory `load_status` row. Notably,
`GravitinoSystemTableCatalogStatus` explicitly takes a single snapshot via
`getCatalogRegistrationStates()` for exactly this reason, but that discipline
wasn't applied here.
##########
trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoConnectorFactory.java:
##########
@@ -52,8 +53,6 @@ public class GravitinoConnectorFactory implements
ConnectorFactory {
public static final String DEFAULT_CONNECTOR_NAME = "gravitino";
@SuppressWarnings("UnusedVariable")
Review Comment:
**[Confirmed]** `@SuppressWarnings("UnusedVariable")` is left on this field
after the field it originally annotated (the deleted
`gravitinoSystemTableFactory` field) was removed by this diff;
`catalogConnectorManager` is actively assigned and read and is not unused.
This stale suppression now silences an unused-variable warning that would
never fire for this field, and misleads a reviewer into thinking the field is
intentionally write-only. Should simply be deleted.
##########
trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorManager.java:
##########
@@ -186,14 +216,102 @@ private void loadMetalake() {
LOG.debug("Load metalake: {}", usedMetalake);
loadCatalogs(metalake);
} catch (Exception e) {
- LOG.error("Load Metalake {} failed.", usedMetalake, e);
+ recordMetalakeError(usedMetalake, e);
}
}
- } catch (Exception e) {
- LOG.error("Error when loading metalake", e);
+
+ pruneMissingMetalakes(usedMetalakes);
+
+ if (metalakeErrors.isEmpty()) {
+ lastSuccessfulLoadTimeMs = System.currentTimeMillis();
+ recordLoadSuccess();
+ } else {
+ // Some metalake failed. The loop reaching its last line is not a
health signal, so do not
+ // advance the success time or clear the error, or load_status would
report a healthy loop
+ // while no catalog is being registered at all.
+ recordLoadFailure(
+ String.format(
+ "%d of %d metalakes failed to load: %s",
+ metalakeErrors.size(), usedMetalakes.size(), new
TreeMap<>(metalakeErrors)),
+ null);
+ }
+ } catch (Throwable t) {
+ // Catch Throwable, not Exception: scheduleWithFixedDelay silently
cancels the task forever
+ // the first time the runnable throws, and loading a Trino connector
plugin can raise
+ // NoClassDefFoundError. A dead loop must not look like a healthy one.
+ recordLoadFailure(toErrorMessage(t), t);
}
}
+ private void pruneMissingMetalakes(Set<String> usedMetalakes) {
+ // A metalake that was deleted, or that dropped out of the configuration,
leaves its catalog
+ // rows behind. Without this they keep reporting REGISTERED for catalogs
that no longer exist.
+ catalogStates.values().removeIf(state ->
!usedMetalakes.contains(state.getMetalake()));
+ metalakeErrors.keySet().removeIf(metalakeName ->
!usedMetalakes.contains(metalakeName));
+ }
+
+ private void recordLoadSuccess() {
+ if (lastLoadError != null) {
+ LOG.info("The Gravitino catalog load loop recovered.");
+ }
+ lastLoadError = null;
+ consecutiveLoadFailures.set(0);
+ }
+
+ private void recordLoadFailure(String message, Throwable cause) {
+ boolean changed = !Objects.equals(lastLoadError, message);
+ lastLoadError = message;
+ consecutiveLoadFailures.incrementAndGet();
+ if (!changed) {
+ LOG.debug("Failed to load catalogs from the Gravitino server: {}",
message, cause);
+ } else if (trinoStarted) {
+ LOG.error("Failed to load catalogs from the Gravitino server: {}",
message, cause);
+ } else {
+ // Trino not being up yet is the normal state during startup, not an
error.
+ LOG.info("{}", message);
+ }
+ }
+
+ private void recordMetalakeError(String metalakeName, Throwable cause) {
+ String message = toErrorMessage(cause);
+ String previous = metalakeErrors.put(metalakeName, message);
+ if (!Objects.equals(previous, message)) {
+ LOG.error("Load metalake {} failed: {}", metalakeName, message, cause);
+ } else {
+ LOG.debug("Load metalake {} failed: {}", metalakeName, message, cause);
+ }
+ }
+
+ private static String toErrorMessage(Throwable e) {
Review Comment:
**[Plausible simplification]** `toErrorMessage(Throwable)` hand-rolls a
bounded (`MAX_CAUSE_DEPTH=32`) root-cause walk with ad hoc cycle detection,
duplicating Guava's `Throwables.getRootCause()`, which is already used
elsewhere in the codebase and has proper cycle detection.
A cause chain longer than 32 links (unlikely but possible with deeply
wrapped exceptions) is silently truncated at an arbitrary depth instead of
reaching the true root, producing a less useful error message in
catalog_status/load_status than Guava's well-tested implementation would.
Replacing this loop with `Throwables.getRootCause(e)` removes the hand-rolled
code and its arbitrary bound.
##########
trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorManager.java:
##########
@@ -382,6 +604,100 @@ public String getTrinoCatalogName(GravitinoCatalog
catalog) {
return getTrinoCatalogName(catalog.getMetalake(), catalog.getName());
}
+ /**
+ * Retrieves a snapshot of the registration state of every Gravitino catalog
seen by the load
+ * loop.
+ *
+ * @return the registration states
+ */
+ public List<CatalogRegistrationState> getCatalogRegistrationStates() {
Review Comment:
**[Plausible efficiency]** `getCatalogRegistrationStates()` does
`List.copyOf(catalogStates.values())` over ALL metalakes' catalog states on
every call, but its only caller
(`GravitinoSystemTableCatalogStatus.loadPageData`) immediately discards
everything outside the current connector's own metalake.
On every query against `gravitino.system.catalog_status`, a Trino cluster
with many entry catalogs/metalakes and many catalogs per metalake copies the
entire cluster-wide catalog-state collection just to keep the subset belonging
to one metalake — cost scales with total cluster-wide catalog count rather than
the querying connector's own. Filtering while streaming
`catalogStates.values()` directly, or exposing a metalake-scoped accessor,
would avoid materializing the full collection per query.
##########
trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/system/GravitinoSystemConnector.java:
##########
@@ -183,14 +217,33 @@ public SchemaTableName getTableName() {
return tableName;
}
+ // The system table data lives on the coordinator only: the catalog load
loop runs there, and
+ // the registration state it records is never replicated to workers. Set
once by the
+ // coordinator's GravitinoConnectorFactory.create(), which necessarily
runs before any query
+ // can reach the scheduler, and read by the scheduler through
isRemotelyAccessible() and
+ // getAddresses() below. On a real worker JVM it is never set and the
split keeps the previous
+ // remotely accessible behaviour; in a single JVM test runner the static
is shared with the
+ // coordinator, which is harmless because only the coordinator's scheduler
reads it.
+ private static volatile HostAddress coordinatorAddress;
Review Comment:
**[Plausible / CLAUDE.md violation]** This new static field
`coordinatorAddress` is declared after the instance field `tableName`, the
constructor, and the `getTableName()` method in the `Split` class, violating
the project's Class Member Ordering rule (static constants → static fields →
instance fields → constructors → methods).
This newly added static field sits after an instance field, a constructor,
and a method instead of being grouped with the class's other fields before the
constructor — a direct, quotable violation of the stated ordering rule in
CLAUDE.md.
##########
trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogRegistrationState.java:
##########
@@ -0,0 +1,299 @@
+/*
+ * 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.gravitino.trino.connector.catalog;
+
+import javax.annotation.Nullable;
+import org.apache.gravitino.trino.connector.metadata.GravitinoCatalog;
+
+/**
+ * The registration state of a single Apache Gravitino catalog in Trino.
+ *
+ * <p>Instances are immutable and are always replaced as a whole, so a reader
never observes a
+ * partially updated state.
+ */
+public final class CatalogRegistrationState {
+
+ /** The registration status of a catalog. */
+ public enum Status {
+ /** The catalog was registered in Trino with a CREATE CATALOG statement. */
+ REGISTERED,
+ /** The last registration attempt failed. */
+ FAILED,
+ /** The catalog matches `gravitino.trino.skip-catalog-patterns` and is not
registered. */
+ SKIPPED,
+ /** The catalog type or provider is not supported by the connector. */
+ UNSUPPORTED
+ }
+
+ private final String metalake;
+ private final String catalogName;
+ private final String trinoCatalogName;
+ private final String provider;
+ private final Status status;
+ private final String lastError;
+ private final long lastAttemptTimeMs;
+ private final long lastSuccessTimeMs;
+ private final long failureCount;
+
+ private CatalogRegistrationState(
+ String metalake,
+ String catalogName,
+ String trinoCatalogName,
+ String provider,
+ Status status,
+ String lastError,
+ long lastAttemptTimeMs,
+ long lastSuccessTimeMs,
+ long failureCount) {
+ this.metalake = metalake;
+ this.catalogName = catalogName;
+ this.trinoCatalogName = trinoCatalogName;
+ this.provider = provider;
+ this.status = status;
+ this.lastError = lastError;
+ this.lastAttemptTimeMs = lastAttemptTimeMs;
+ this.lastSuccessTimeMs = lastSuccessTimeMs;
+ this.failureCount = failureCount;
+ }
+
+ /**
+ * Creates a state for a catalog that was registered in Trino successfully.
+ *
+ * @param catalog the Gravitino catalog
+ * @param trinoCatalogName the name the catalog is registered under in Trino
+ * @return the registration state
+ */
+ public static CatalogRegistrationState succeeded(
Review Comment:
**[Plausible simplification]** The four static factories (`succeeded`,
`failed`, `skipped`, `unsupported`) are ~90% identical, each calling the same
9-arg private constructor and differing only in the `Status` literal and a
couple of field values.
Adding a 10th field to this class requires editing the same field list in
all four factories plus `withHistoryOf`'s reconstruction, with no compiler
assistance if one call site is missed since the diffs all look alike — a
copy-paste-and-forget-one-field bug is easy to introduce and hard to catch in
review. A shared private helper taking `(metalake, catalogName,
trinoCatalogName, provider, status, reason)` would collapse three of the four
factories to one-line callers.
##########
trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/system/table/GravitinoSystemTableCatalogStatus.java:
##########
@@ -0,0 +1,125 @@
+/*
+ * 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.gravitino.trino.connector.system.table;
+
+import static io.trino.spi.type.BigintType.BIGINT;
+import static io.trino.spi.type.VarcharType.VARCHAR;
+
+import io.trino.spi.Page;
+import io.trino.spi.block.BlockBuilder;
+import io.trino.spi.connector.ColumnMetadata;
+import io.trino.spi.connector.ConnectorTableMetadata;
+import io.trino.spi.connector.SchemaTableName;
+import java.util.List;
+import org.apache.gravitino.trino.connector.catalog.CatalogConnectorManager;
+import org.apache.gravitino.trino.connector.catalog.CatalogRegistrationState;
+
+/**
+ * An implementation of the catalog status system table.
+ *
+ * <p>It reports why every Apache Gravitino catalog is or is not registered in
Trino, so that a
+ * catalog missing from SHOW CATALOGS can be diagnosed without reading the
coordinator log.
+ */
+public class GravitinoSystemTableCatalogStatus extends GravitinoSystemTable {
+
+ /** The name of the catalog status system table. */
+ public static final SchemaTableName TABLE_NAME =
+ new SchemaTableName(SYSTEM_TABLE_SCHEMA_NAME, "catalog_status");
+
+ private static final ConnectorTableMetadata TABLE_METADATA =
+ new ConnectorTableMetadata(
+ TABLE_NAME,
+ List.of(
+
ColumnMetadata.builder().setName("metalake").setType(VARCHAR).build(),
+
ColumnMetadata.builder().setName("catalog_name").setType(VARCHAR).build(),
+
ColumnMetadata.builder().setName("trino_catalog_name").setType(VARCHAR).build(),
+
ColumnMetadata.builder().setName("provider").setType(VARCHAR).build(),
+
ColumnMetadata.builder().setName("status").setType(VARCHAR).build(),
+
ColumnMetadata.builder().setName("last_error").setType(VARCHAR).build(),
+
ColumnMetadata.builder().setName("last_attempt_time").setType(VARCHAR).build(),
+
ColumnMetadata.builder().setName("last_success_time").setType(VARCHAR).build(),
+
ColumnMetadata.builder().setName("failure_count").setType(BIGINT).build()));
+
+ private final CatalogConnectorManager catalogConnectorManager;
+ private final String metalake;
+
+ /**
+ * Constructs a new GravitinoSystemTableCatalogStatus.
+ *
+ * @param catalogConnectorManager the manager for catalog connectors
+ * @param metalake the metalake to report on
+ */
+ public GravitinoSystemTableCatalogStatus(
+ CatalogConnectorManager catalogConnectorManager, String metalake) {
+ this.catalogConnectorManager = catalogConnectorManager;
+ this.metalake = metalake;
+ }
+
+ @Override
+ public Page loadPageData() {
+ // Take a snapshot first, the load loop writes these states concurrently
and the column
+ // builders must all end up with the same number of positions.
+ // The load loop is shared by every entry catalog in this Trino, so report
only the metalake
+ // this connector is configured with.
+ List<CatalogRegistrationState> states =
+ catalogConnectorManager.getCatalogRegistrationStates().stream()
+ .filter(state -> state.getMetalake().equals(metalake))
Review Comment:
**[Plausible]** This metalake scoping filter compares the raw
`gravitino.metalake` connector-config string via `.equals()` against canonical
metalake names collected by the load loop, with no normalization of case or
whitespace.
`GravitinoConnectorFactory.create()` passes `config.getMetalake()` (the
literal config value) into `GravitinoSystemTableFactory`, used unmodified in
the `.filter(state -> state.getMetalake().equals(metalake))` /
`.filter(metalake::equals)` checks across all three per-metalake tables. If the
configured value differs even slightly (trailing whitespace, case) from the
canonical name recorded in `CatalogRegistrationState`/`getUsedMetalakes()`,
every row is silently filtered out — `gravitino.system.catalog`,
`catalog_status`, and `load_status.metalake_errors` all report empty/zero with
no error, even while catalogs are actively failing to load.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]