This is an automated email from the ASF dual-hosted git repository.
zhanglistar pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gluten.git
The following commit(s) were added to refs/heads/main by this push:
new 948db6c03f [FLINK] Support ORC filesystem sink format (#12327)
948db6c03f is described below
commit 948db6c03f7ef50c92f40a330307f311b15d9f48
Author: zhanglistar <[email protected]>
AuthorDate: Tue Jul 28 09:18:58 2026 +0800
[FLINK] Support ORC filesystem sink format (#12327)
* [FLINK] Add Nexmark q10 ORC sink test
* [FLINK] Fix Nexmark ORC cleanup
---
gluten-flink/ut/pom.xml | 18 ++
.../table/runtime/stream/custom/NexmarkTest.java | 195 +++++++++++++++++++--
.../ut/src/test/resources/nexmark/q10_orc.sql | 23 +++
3 files changed, 217 insertions(+), 19 deletions(-)
diff --git a/gluten-flink/ut/pom.xml b/gluten-flink/ut/pom.xml
index c97c3838e1..3e3b9b5d8c 100644
--- a/gluten-flink/ut/pom.xml
+++ b/gluten-flink/ut/pom.xml
@@ -281,6 +281,24 @@
<version>${flink.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.flink</groupId>
+ <artifactId>flink-orc</artifactId>
+ <version>${flink.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.protobuf</groupId>
+ <artifactId>protobuf-java</artifactId>
+ <version>3.25.5</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-client</artifactId>
+ <version>2.7.2</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git
a/gluten-flink/ut/src/test/java/org/apache/gluten/table/runtime/stream/custom/NexmarkTest.java
b/gluten-flink/ut/src/test/java/org/apache/gluten/table/runtime/stream/custom/NexmarkTest.java
index 14b250480b..828e5c56bc 100644
---
a/gluten-flink/ut/src/test/java/org/apache/gluten/table/runtime/stream/custom/NexmarkTest.java
+++
b/gluten-flink/ut/src/test/java/org/apache/gluten/table/runtime/stream/custom/NexmarkTest.java
@@ -27,6 +27,12 @@ import
org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
import com.salesforce.kafka.test.junit5.SharedKafkaTestResource;
import com.salesforce.kafka.test.listeners.PlainListener;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hive.ql.exec.vector.VectorizedRowBatch;
+import org.apache.orc.OrcFile;
+import org.apache.orc.Reader;
+import org.apache.orc.RecordReader;
+import org.apache.orc.TypeDescription;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -42,6 +48,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -147,32 +154,40 @@ public class NexmarkTest {
@Test
void testAllNexmarkSourceQueries()
throws ExecutionException, InterruptedException, TimeoutException {
- setupNexmarkEnvironment(tEnv, "ddl_gen.sql", NEXMARK_VARIABLES);
- List<String> queryFiles = getQueries();
- assertThat(queryFiles).isNotEmpty();
- LOG.warn("Found {} Nexmark query files: {}", queryFiles.size(),
queryFiles);
-
- for (String queryFile : queryFiles) {
- LOG.warn("Executing nextmark query from file: {}", queryFile);
- executeQuery(tEnv, queryFile, false);
+ try {
+ setupNexmarkEnvironment(tEnv, "ddl_gen.sql", NEXMARK_VARIABLES);
+ List<String> queryFiles = getQueries();
+ assertThat(queryFiles).isNotEmpty();
+ LOG.warn("Found {} Nexmark query files: {}", queryFiles.size(),
queryFiles);
+
+ for (String queryFile : queryFiles) {
+ LOG.warn("Executing nextmark query from file: {}", queryFile);
+ executeQuery(tEnv, queryFile, false);
+ }
+ } finally {
+ clearEnvironment(tEnv);
}
- clearEnvironment(tEnv);
}
@Test
void testAllKafkaSourceQueries()
throws ExecutionException, InterruptedException, TimeoutException {
- kafkaInstance.getKafkaTestUtils().createTopic(topicName, 1, (short) 1);
- setupNexmarkEnvironment(tEnv, "ddl_kafka.sql", KAFKA_VARIABLES);
- List<String> queryFiles = getQueries();
- assertThat(queryFiles).isNotEmpty();
- LOG.warn("Found {} Nexmark query files: {}", queryFiles.size(),
queryFiles);
-
- for (String queryFile : queryFiles) {
- LOG.warn("Executing kafka query from file:{}", queryFile);
- executeQuery(tEnv, queryFile, true);
+ try {
+ kafkaInstance.getKafkaTestUtils().createTopic(topicName, 1, (short) 1);
+ setupNexmarkEnvironment(tEnv, "ddl_kafka.sql", KAFKA_VARIABLES);
+ List<String> queryFiles = getQueries();
+ assertThat(queryFiles).isNotEmpty();
+ LOG.warn("Found {} Nexmark query files: {}", queryFiles.size(),
queryFiles);
+
+ for (String queryFile : queryFiles) {
+ LOG.warn("Executing kafka query from file:{}", queryFile);
+ if (!"q10_orc.sql".equals(queryFile)) {
+ executeQuery(tEnv, queryFile, true);
+ }
+ }
+ } finally {
+ clearEnvironment(tEnv);
}
- clearEnvironment(tEnv);
}
private static void setupNexmarkEnvironment(
@@ -206,7 +221,10 @@ public class NexmarkTest {
String sql = String.format("drop table if exists %s", tableName);
tEnv.executeSql(sql);
}
+ tEnv.executeSql("drop table if exists nexmark_q10_orc");
for (String view : VIEWS) {
+ String dropTemporaryViewSql = String.format("drop temporary view if
exists %s", view);
+ tEnv.executeSql(dropTemporaryViewSql);
String sql = String.format("drop view if exists %s", view);
tEnv.executeSql(sql);
}
@@ -220,7 +238,15 @@ public class NexmarkTest {
private void executeQuery(StreamTableEnvironment tEnv, String queryFileName,
boolean kafkaSource)
throws ExecutionException, InterruptedException, TimeoutException {
+ if ("q10_orc.sql".equals(queryFileName) && !kafkaSource) {
+ executeQ10OrcBatchQuery();
+ return;
+ }
+
String queryContent = readSqlFromFile(NEXMARK_RESOURCE_DIR + "/" +
queryFileName);
+ if ("q10_orc.sql".equals(queryFileName)) {
+ cleanQ10OrcOutput();
+ }
String[] sqlStatements = queryContent.split(";");
assertThat(sqlStatements.length).isGreaterThanOrEqualTo(2);
@@ -242,11 +268,142 @@ public class NexmarkTest {
assertThat(checkJobRunningStatus(insertResult, 30000) == true);
} else {
waitForJobCompletion(insertResult, 30000);
+ if ("q10_orc.sql".equals(queryFileName)) {
+ verifyQ10OrcOutput();
+ }
}
}
assertTrue(sqlStatements[sqlStatements.length - 1].trim().isEmpty());
}
+ private void executeQ10OrcBatchQuery()
+ throws ExecutionException, InterruptedException, TimeoutException {
+ cleanQ10OrcOutput();
+ StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
+ env.setParallelism(1);
+
+ EnvironmentSettings settings =
EnvironmentSettings.newInstance().inBatchMode().build();
+ StreamTableEnvironment batchTEnv = StreamTableEnvironment.create(env,
settings);
+ try {
+ createQ10OrcBidView(batchTEnv);
+ String queryContent = readSqlFromFile(NEXMARK_RESOURCE_DIR +
"/q10_orc.sql");
+ String[] sqlStatements = queryContent.split(";");
+ assertThat(sqlStatements.length).isEqualTo(3);
+
+ TableResult createResult = batchTEnv.executeSql(sqlStatements[0].trim());
+ assertFalse(createResult.getJobClient().isPresent());
+
+ TableResult insertResult = batchTEnv.executeSql(sqlStatements[1].trim());
+ waitForJobCompletion(insertResult, 30000);
+ verifyQ10OrcOutput();
+ assertTrue(sqlStatements[2].trim().isEmpty());
+ } finally {
+ clearEnvironment(batchTEnv);
+ }
+ }
+
+ private static void createQ10OrcBidView(StreamTableEnvironment tEnv) {
+ tEnv.executeSql(
+ "CREATE TEMPORARY VIEW bid AS "
+ + "SELECT "
+ + "CAST(1 AS BIGINT) AS auction, "
+ + "CAST(2 AS BIGINT) AS bidder, "
+ + "CAST(100 AS BIGINT) AS price, "
+ + "CAST('channel' AS STRING) AS channel, "
+ + "CAST('url' AS STRING) AS url, "
+ + "TIMESTAMP '2026-07-21 07:30:00' AS `dateTime`, "
+ + "CAST('extra' AS STRING) AS extra");
+ }
+
+ private void cleanQ10OrcOutput() {
+ Path outputDir = Paths.get("/tmp/data/output/bid_orc");
+ if (!Files.exists(outputDir)) {
+ return;
+ }
+ try (java.util.stream.Stream<Path> files = Files.walk(outputDir)) {
+ files
+ .sorted(Comparator.reverseOrder())
+ .forEach(
+ path -> {
+ try {
+ Files.deleteIfExists(path);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to delete " + path, e);
+ }
+ });
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to clean Q10 ORC output directory",
e);
+ }
+ }
+
+ private void verifyQ10OrcOutput() throws InterruptedException {
+ Path outputDir = Paths.get("/tmp/data/output/bid_orc");
+ assertTrue("Q10 ORC output directory should exist",
Files.exists(outputDir));
+
+ List<Path> partFiles = waitForFinalQ10OrcPartFiles(outputDir);
+ long rowCount = 0L;
+ for (Path partFile : partFiles) {
+ try {
+ rowCount += readAndVerifyQ10OrcFile(partFile);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to read Q10 ORC output file " +
partFile, e);
+ }
+ }
+ assertThat(rowCount).isGreaterThan(0L);
+ }
+
+ private List<Path> waitForFinalQ10OrcPartFiles(Path outputDir) throws
InterruptedException {
+ long deadlineMillis = System.currentTimeMillis() + 30000L;
+ List<Path> regularFiles = List.of();
+ while (System.currentTimeMillis() < deadlineMillis) {
+ try (java.util.stream.Stream<Path> files = Files.walk(outputDir)) {
+ regularFiles =
files.filter(Files::isRegularFile).sorted().collect(Collectors.toList());
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to inspect Q10 ORC output", e);
+ }
+
+ boolean hasInProgress =
+ regularFiles.stream().anyMatch(path ->
path.toString().contains(".inprogress"));
+ List<Path> partFiles =
+ regularFiles.stream()
+ .filter(path ->
path.getFileName().toString().startsWith("part-"))
+ .collect(Collectors.toList());
+ if (!hasInProgress && !partFiles.isEmpty()) {
+ return partFiles;
+ }
+ Thread.sleep(1000L);
+ }
+
+ assertThat(regularFiles).allMatch(path ->
!path.toString().contains(".inprogress"));
+ List<Path> partFiles =
+ regularFiles.stream()
+ .filter(path -> path.getFileName().toString().startsWith("part-"))
+ .collect(Collectors.toList());
+ assertThat(partFiles).isNotEmpty();
+ return partFiles;
+ }
+
+ private long readAndVerifyQ10OrcFile(Path partFile) throws IOException {
+ Reader reader =
+ OrcFile.createReader(
+ new org.apache.hadoop.fs.Path(partFile.toUri()),
+ OrcFile.readerOptions(new Configuration()));
+ TypeDescription schema = reader.getSchema();
+
assertThat(schema.getCategory()).isEqualTo(TypeDescription.Category.STRUCT);
+ assertThat(schema.getFieldNames())
+ .containsExactly("auction", "bidder", "price", "dateTime", "extra");
+
+ long rowCount = 0L;
+ try (RecordReader rows = reader.rows()) {
+ VectorizedRowBatch batch = schema.createRowBatch();
+ while (rows.nextBatch(batch)) {
+ rowCount += batch.size;
+ }
+ }
+ assertThat(rowCount).isEqualTo(reader.getNumberOfRows());
+ return rowCount;
+ }
+
private void waitForJobCompletion(TableResult result, long timeoutMs)
throws InterruptedException, ExecutionException, TimeoutException {
assertTrue(result.getJobClient().isPresent());
diff --git a/gluten-flink/ut/src/test/resources/nexmark/q10_orc.sql
b/gluten-flink/ut/src/test/resources/nexmark/q10_orc.sql
new file mode 100644
index 0000000000..29df04b734
--- /dev/null
+++ b/gluten-flink/ut/src/test/resources/nexmark/q10_orc.sql
@@ -0,0 +1,23 @@
+CREATE TABLE nexmark_q10_orc (
+ auction BIGINT,
+ bidder BIGINT,
+ price BIGINT,
+ `dateTime` TIMESTAMP(3),
+ extra VARCHAR,
+ dt STRING,
+ hm STRING
+) PARTITIONED BY (dt, hm) WITH (
+ 'connector' = 'filesystem',
+ 'path' = 'file:///tmp/data/output/bid_orc/',
+ 'format' = 'orc',
+ 'sink.partition-commit.trigger' = 'process-time',
+ 'sink.partition-commit.delay' = '0s',
+ 'sink.partition-commit.policy.kind' = 'success-file',
+ 'partition.time-extractor.timestamp-pattern' = '$dt $hm:00',
+ 'sink.rolling-policy.rollover-interval' = '1s',
+ 'sink.rolling-policy.check-interval' = '1s'
+);
+
+INSERT INTO nexmark_q10_orc
+SELECT auction, bidder, price, `dateTime`, extra, DATE_FORMAT(`dateTime`,
'yyyy-MM-dd'), DATE_FORMAT(`dateTime`, 'HH:mm')
+FROM bid;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]