Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions fe/fe-connector/fe-connector-hms-hive-shade/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -275,18 +275,26 @@ under the License.
<build>
<plugins>
<!-- Pure-shade module (no src/main): under a parallel reactor (build.sh's default -T 1C)
the package-phase shade goal can start before this module's main jar is created and
attached, failing with "Failed to create shaded artifact, project main artifact does
not exist". Pin maven-jar-plugin's default-jar to the package phase and force it to
always (re)create the jar, so the main artifact is guaranteed present before shade.
Mirrors fe-connector-paimon-hive-shade. -->
the shade goal can start before this module's main jar is created and attached,
failing with "Failed to create shaded artifact, project main artifact does not
exist". Pin maven-jar-plugin's default-jar ahead of shade in the SAME phase (within
one phase the declaration order here decides) and force it to always (re)create the
jar, so the main artifact is guaranteed present before shade.

That shared phase is process-classes, NOT package: this module carries no sources,
so its Hive metastore content exists only once shade has run. fe-connector-hms
compiles against that content, and run-fe-ut.sh builds with `mvn test`, which stops
before package — the reactor would then hand fe-connector-hms this module's (empty)
target/classes and every org.apache.hadoop.hive.* import would fail to resolve
(TeamCity FE UT 1009434). process-classes is the last phase still ahead of
test-compile. Mirrors fe-connector-paimon-hive-shade. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>default-jar</id>
<phase>package</phase>
<phase>process-classes</phase>
<goals>
<goal>jar</goal>
</goals>
Expand All @@ -304,7 +312,7 @@ under the License.
<goals>
<goal>shade</goal>
</goals>
<phase>package</phase>
<phase>process-classes</phase>
<configuration>
<!-- WHITELIST, not blacklist. The fat hive-catalog-shade used a short
exclude list and tolerated a huge tail of transitive junk (hadoop-yarn,
Expand Down
23 changes: 16 additions & 7 deletions fe/fe-connector/fe-connector-paimon-hive-shade/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -217,18 +217,27 @@ under the License.
<build>
<plugins>
<!-- Pure-shade module (no src/main): under a parallel reactor (build.sh's default -T 1C)
the package-phase shade goal can start before this module's main jar is created and
attached, failing with "Failed to create shaded artifact, project main artifact does
not exist". Pin maven-jar-plugin's default-jar to the package phase and force it to
always (re)create the jar, so the main artifact is guaranteed present before shade —
keeping parallel (-T) builds reliable without falling back to single-threaded. -->
the shade goal can start before this module's main jar is created and attached,
failing with "Failed to create shaded artifact, project main artifact does not
exist". Pin maven-jar-plugin's default-jar ahead of shade in the SAME phase (within
one phase the declaration order here decides) and force it to always (re)create the
jar, so the main artifact is guaranteed present before shade — keeping parallel (-T)
builds reliable without falling back to single-threaded.

That shared phase is process-classes, NOT package: this module carries no sources,
so its Hive metastore content exists only once shade has run. fe-connector-paimon
compiles against it (org.apache.hadoop.hive.conf.HiveConf), and run-fe-ut.sh builds
with `mvn test`, which stops before package — the reactor would then hand
fe-connector-paimon this module's (empty) target/classes and the HiveConf import
would fail to resolve. process-classes is the last phase still ahead of
test-compile. Mirrors fe-connector-hms-hive-shade. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>default-jar</id>
<phase>package</phase>
<phase>process-classes</phase>
<goals>
<goal>jar</goal>
</goals>
Expand All @@ -246,7 +255,7 @@ under the License.
<goals>
<goal>shade</goal>
</goals>
<phase>package</phase>
<phase>process-classes</phase>
<configuration>
<!-- Resolved at runtime from the plugin's own child-first lib (hadoop,
paimon-core/common/format) or the host (slf4j/log4j/guava/protobuf/
Expand Down
60 changes: 60 additions & 0 deletions fe/fe-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,66 @@ under the License.
<artifactId>fe-connector-spi</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Connector impl modules, TEST SCOPE ONLY. Production loads these from the
plugins/connector/ directory via ConnectorPluginManager; fe-core must never depend on a
connector at compile or runtime scope. Same arrangement as the fe-filesystem-* modules below.

They exist here for exactly one assertion that cannot be made without them:
Legacy413ProviderTypeContractTest pins that the catalog type string an upgraded 4.1.3 catalog
recovers from its persisted logType is a string a REAL SHIPPED provider answers to. Both halves
of that contract are string literals in different modules: rename
TrinoConnectorProvider.getType() to "trino" and every other test in this repository stays green
while every migrated trino catalog is permanently dead. -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>fe-connector-hive</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>fe-connector-iceberg</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>fe-connector-paimon</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>fe-connector-es</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>fe-connector-jdbc</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>fe-connector-maxcompute</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>fe-connector-trino</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<!-- hudi is the only provider with isStandaloneCatalogType()==false; it is here so the contract
test can pin that a sibling-only connector never becomes a creatable catalog type. -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>fe-connector-hudi</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<!-- fe-filesystem impl modules: runtime dependencies removed in Phase 4 P4.1.
Providers are now loaded from plugins/filesystem/ directory at startup
via FileSystemPluginManager + DirectoryPluginRuntimeManager. -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,26 @@ private static CatalogIf createCatalog(long catalogId, String name, String resou
// takes to make its type usable here. Returns null when nothing claims it — including for a
// sibling-only connector, whose type must never become a catalog (see ConnectorProvider
// .isStandaloneCatalogType).
Connector spiConnector = ConnectorFactory.createStandaloneCatalogConnector(
catalogType, props, new DefaultConnectorContext(name, catalogId));
Connector spiConnector;
try {
spiConnector = ConnectorFactory.createStandaloneCatalogConnector(
catalogType, props, new DefaultConnectorContext(name, catalogId));
} catch (RuntimeException | Error e) {
if (!isReplay) {
// Creating a catalog interactively must still fail loud: the user is waiting for the error.
throw e;
}
// On replay we must not propagate. The edit-log replay fallback turns any exception from a
// replayed operation into System.exit(-1), so a connector constructor that rejects a property an
// older FE stored without validating — or a half-installed plugin throwing NoClassDefFoundError —
// would keep the whole FE from starting instead of making one catalog unusable. Fall through to
// the degraded registration below; the failure resurfaces at first access as a query error.
// (The image path never had this problem: it builds the connector lazily.)
LOG.warn("Connector for catalog type '{}' failed to build while replaying catalog '{}'. "
+ "Registering it in degraded mode; accessing it will fail until the cause is fixed.",
catalogType, name, e);
spiConnector = null;
}
if (spiConnector != null) {
LOG.info("Created plugin-driven catalog '{}' via SPI connector for type '{}'",
name, catalogType);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// 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;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
* Access to the golden Doris 4.1.3 metadata fixtures under {@code src/test/resources/upgrade/413/}.
*
* <p>Those files were produced by real 4.1.3 bytecode, not written by hand; see the PROVENANCE.txt
* next to them for how to regenerate and for the five wire-format details that make hand-authored
* fixtures wrong.
*/
public final class Legacy413Fixtures {

/** The lastUpdateTime the generator stamps on every catalog, so the bytes are deterministic. */
public static final long FIXED_UPDATE_TIME = 1753000000000L;

private static final String ROOT = "/upgrade/413/";

private Legacy413Fixtures() {
}

/** The image's "datasource" module as a 4.1.3 FE would have written it. */
public static CatalogMgr loadCatalogMgr() throws IOException {
return loadCatalogMgr("datasource.module.bin");
}

/** Same, for the variant generated with {@code Config.deploy_mode="cloud"}. */
public static CatalogMgr loadCloudCatalogMgr() throws IOException {
return loadCatalogMgr("datasource.module.cloud.bin");
}

private static CatalogMgr loadCatalogMgr(String fileName) throws IOException {
try (InputStream in = open(fileName);
DataInputStream dis = new DataInputStream(in)) {
return CatalogMgr.read(dis);
}
}

/** Raw bytes of one edit-log entry, in JournalEntity wire form (opCode short + Text-framed JSON). */
public static byte[] journalEntry(String fileName) throws IOException {
try (InputStream in = open("editlog/" + fileName)) {
return readAll(in);
}
}

/** Names of every edit-log fixture with the given op code, e.g. {@code 320}. */
public static List<String> journalEntryNames(int opCode) {
List<String> names = new ArrayList<>();
for (String name : listEditLogFixtures()) {
if (name.startsWith("op" + opCode + "-")) {
names.add(name);
}
}
if (names.isEmpty()) {
throw new IllegalStateException("no edit-log fixture for op " + opCode
+ "; the resource index at " + ROOT + "editlog.index is stale");
}
return names;
}

private static List<String> listEditLogFixtures() {
// Resource directories are not enumerable from a jar, and surefire may well run these classes off one.
// The generator writes a flat index next to the fixtures precisely so this does not need directory
// listing; keeping it in one place means a fixture added without regenerating the index fails loudly
// in journalEntryNames() rather than silently reducing coverage.
try (InputStream in = open("editlog.index")) {
String text = new String(readAll(in), StandardCharsets.UTF_8);
List<String> names = new ArrayList<>();
for (String line : text.split("\n")) {
String trimmed = line.trim();
if (!trimmed.isEmpty() && !trimmed.startsWith("#")) {
names.add(trimmed);
}
}
return names;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

/**
* One of 4.1.3's own GSON label registries, read out of the generated {@code labels.*.txt}.
*
* @param which one of {@code ds}, {@code db}, {@code tbl}
* @return label to the fully-qualified name of the class 4.1.3 mapped it to, in file order
*/
public static Map<String, LegacyLabel> legacyLabels(String which) throws IOException {
Map<String, LegacyLabel> labels = new LinkedHashMap<>();
try (InputStream in = open("labels." + which + ".txt")) {
String text = new String(readAll(in), StandardCharsets.UTF_8);
for (String line : text.split("\n")) {
if (line.trim().isEmpty() || line.startsWith("#")) {
continue;
}
String[] parts = line.split("\t");
boolean isAbstract = parts.length > 2 && "ABSTRACT".equals(parts[2].trim());
labels.put(parts[0], new LegacyLabel(parts[0], parts[1].trim(), isAbstract));
}
}
return labels;
}

/** One entry of a 4.1.3 GSON label registry. */
public static final class LegacyLabel {
public final String label;
public final String legacyClassName;
/**
* Whether 4.1.3's class was abstract. GSON writes the runtime class's label, so an abstract
* class's label can never actually appear in an image -- it is registered but unreachable.
*/
public final boolean isAbstract;

LegacyLabel(String label, String legacyClassName, boolean isAbstract) {
this.label = label;
this.legacyClassName = legacyClassName;
this.isAbstract = isAbstract;
}

@Override
public String toString() {
return label + " -> " + legacyClassName + (isAbstract ? " (abstract)" : "");
}
}

private static InputStream open(String relative) throws IOException {
InputStream in = Legacy413Fixtures.class.getResourceAsStream(ROOT + relative);
if (in == null) {
throw new IOException("missing 4.1.3 fixture: " + ROOT + relative
+ " -- see src/test/resources/upgrade/413/PROVENANCE.txt");
}
return in;
}

private static byte[] readAll(InputStream in) throws IOException {
byte[] buffer = new byte[8192];
int size = 0;
int read;
while ((read = in.read(buffer, size, buffer.length - size)) > 0) {
size += read;
if (size == buffer.length) {
buffer = Arrays.copyOf(buffer, buffer.length * 2);
}
}
return Arrays.copyOf(buffer, size);
}
}
Loading
Loading