This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git
The following commit(s) were added to refs/heads/dev by this push:
new 14bcba582d [Fix][Connector-File] Fix Excel column mapping and
read_columns projection (#12768)
14bcba582d is described below
commit 14bcba582d256c50609a7e4c3fb30ab078d1567c
Author: JeremyXin <[email protected]>
AuthorDate: Sun Oct 18 14:02:38 2026 +0000
[Fix][Connector-File] Fix Excel column mapping and read_columns projection
(#12768)
Co-authored-by: JeremyXin <--golbal>
---
docs/en/connectors/source/LocalFile.md | 9 +-
docs/zh/connectors/source/LocalFile.md | 9 +-
.../file/config/FileBaseSourceOptions.java | 8 ++
.../seatunnel/file/excel/ExcelReaderListener.java | 65 ++++++++-
.../file/source/reader/ExcelReadStrategy.java | 70 +++++++--
.../file/reader/ExcelReadStrategyTest.java | 157 +++++++++++++++++++++
.../file/bos/source/BosFileSourceFactory.java | 1 +
.../file/cos/source/CosFileSourceFactory.java | 1 +
.../file/ftp/source/FtpFileSourceFactory.java | 1 +
.../file/gcs/source/GcsFileSourceFactory.java | 1 +
.../file/hdfs/source/HdfsFileSourceFactory.java | 1 +
.../oss/jindo/source/OssFileSourceFactory.java | 1 +
.../file/local/source/LocalFileSourceFactory.java | 1 +
.../seatunnel/file/local/LocalFileFactoryTest.java | 4 +
.../file/obs/source/ObsFileSourceFactory.java | 1 +
.../file/oss/source/OssFileSourceFactory.java | 1 +
.../file/s3/source/S3FileSourceFactory.java | 1 +
.../file/sftp/source/SftpFileSourceFactory.java | 1 +
.../file/smb/source/SmbFileSourceFactory.java | 1 +
.../e2e/connector/file/local/LocalFileIT.java | 31 ++++
...xcel_header_projection_easyexcel_to_assert.conf | 61 ++++++++
...ocal_excel_header_projection_poi_to_assert.conf | 61 ++++++++
22 files changed, 468 insertions(+), 19 deletions(-)
diff --git a/docs/en/connectors/source/LocalFile.md
b/docs/en/connectors/source/LocalFile.md
index 413101e451..31885568e9 100644
--- a/docs/en/connectors/source/LocalFile.md
+++ b/docs/en/connectors/source/LocalFile.md
@@ -67,6 +67,7 @@ If you use SeaTunnel Engine, It automatically integrated the
hadoop jar when you
| schema | config | no | -
|
| sheet_name | string | no | -
|
| excel_engine | string | no | POI
|
+| excel_use_header_line | boolean | no | false
|
| poi_excel_max_file_size | long | no | 52428800
|
| xml_row_tag | string | no | -
|
| xml_use_attr_format | boolean | no | -
|
@@ -314,7 +315,7 @@ default `HH:mm:ss`
### skip_header_row_number [long]
-Skip the first few lines, but only for the txt and csv.
+Skip the first few lines for text, CSV, and Excel.
For example, set like following:
@@ -357,6 +358,12 @@ The default Excel reading engine is POI. POI keeps the
historical read behavior,
You can set `excel_engine = EasyExcel` to use streaming reads for large Excel
files.
+### excel_use_header_line [boolean]
+
+Only used when `file_format_type` is `excel`. The default is `false`, so Excel
columns are read by position even when `skip_header_row_number` is set.
+
+Set `excel_use_header_line = true` to use the first row as the header and map
schema fields by name. This skips the first row; when both options are set,
`excel_use_header_line` takes precedence over `skip_header_row_number`. Fields
missing from the header produce null values and a warning.
+
### poi_excel_max_file_size [long]
Only used when `file_format` is excel and `excel_engine` is POI.
diff --git a/docs/zh/connectors/source/LocalFile.md
b/docs/zh/connectors/source/LocalFile.md
index 396274b888..3519e80980 100644
--- a/docs/zh/connectors/source/LocalFile.md
+++ b/docs/zh/connectors/source/LocalFile.md
@@ -67,6 +67,7 @@ import ChangeLog from '../changelog/connector-file-local.md';
| schema | config | 否 | - |
| sheet_name | string | 否 | - |
| excel_engine | string | 否 | POI |
+| excel_use_header_line | boolean | 否 | false |
| poi_excel_max_file_size | long | 否 | 52428800 |
| xml_row_tag | string | 否 | - |
| xml_use_attr_format | boolean | 否 | - |
@@ -314,7 +315,7 @@ PDF 特有的解析行为如下:
### skip_header_row_number [long]
-跳过前几行,但仅适用于 txt 和 csv。
+跳过 text、CSV 或 Excel 文件的前几行。
例如,设置如下:
@@ -357,6 +358,12 @@ PDF 特有的解析行为如下:
如果需要读取大 Excel 文件,可以设置 `excel_engine = EasyExcel` 使用流式读取。
+### excel_use_header_line [boolean]
+
+仅用于 `file_format_type` 为 `excel` 的源。默认值为 `false`,因此即使设置了
`skip_header_row_number`,Excel 仍按列位置读取。
+
+设置 `excel_use_header_line = true` 后,第一行作为表头,按列名映射到 schema 字段,并跳过第一行。如果同时设置了
`skip_header_row_number`,则以 `excel_use_header_line` 为准。表头中缺少的字段将产生 null 值并记录警告。
+
### poi_excel_max_file_size [long]
仅在 `file_format` 为 excel 且 `excel_engine` 为 POI 时使用。
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/FileBaseSourceOptions.java
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/FileBaseSourceOptions.java
index edffd618d8..85e134bf9b 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/FileBaseSourceOptions.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/FileBaseSourceOptions.java
@@ -97,6 +97,14 @@ public class FileBaseSourceOptions extends FileBaseOptions {
.defaultValue(0L)
.withDescription("The number of rows to skip");
+ public static final Option<Boolean> EXCEL_USE_HEADER_LINE =
+ Options.key("excel_use_header_line")
+ .booleanType()
+ .defaultValue(false)
+ .withDescription(
+ "Use the first Excel row as column headers for
name-based mapping. "
+ + "Only used when file_format_type is
excel.");
+
public static final Option<List<String>> READ_PARTITIONS =
Options.key("read_partitions")
.listType()
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/excel/ExcelReaderListener.java
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/excel/ExcelReaderListener.java
index 297e667b65..5cb2ab51b6 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/excel/ExcelReaderListener.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/excel/ExcelReaderListener.java
@@ -19,6 +19,7 @@ package org.apache.seatunnel.connectors.seatunnel.file.excel;
import org.apache.seatunnel.shade.com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.seatunnel.shade.com.typesafe.config.Config;
+import org.apache.seatunnel.shade.org.apache.commons.lang3.StringUtils;
import org.apache.seatunnel.api.source.Collector;
import org.apache.seatunnel.api.table.type.SeaTunnelDataType;
@@ -52,6 +53,10 @@ public class ExcelReaderListener extends
AnalysisEventListener<Map<Integer, Obje
protected SeaTunnelRowType seaTunnelRowType;
private SeaTunnelDataType<?>[] fieldTypes;
+ private final int[] indexes;
+ private final boolean useHeaderLine;
+ // Maps output field indexes to Excel column indexes.
+ private final Map<Integer, Integer> headerColumns = new HashMap<>();
private ExcelCellUtils excelCellUtils;
@@ -62,20 +67,59 @@ public class ExcelReaderListener extends
AnalysisEventListener<Map<Integer, Obje
Collector<SeaTunnelRow> output,
ExcelCellUtils excelCellUtils,
SeaTunnelRowType seaTunnelRowType) {
+ this(tableId, output, excelCellUtils, seaTunnelRowType, null, true);
+ }
+
+ public ExcelReaderListener(
+ String tableId,
+ Collector<SeaTunnelRow> output,
+ ExcelCellUtils excelCellUtils,
+ SeaTunnelRowType seaTunnelRowType,
+ int[] indexes) {
+ this(tableId, output, excelCellUtils, seaTunnelRowType, indexes, true);
+ }
+
+ public ExcelReaderListener(
+ String tableId,
+ Collector<SeaTunnelRow> output,
+ ExcelCellUtils excelCellUtils,
+ SeaTunnelRowType seaTunnelRowType,
+ int[] indexes,
+ boolean useHeaderLine) {
this.tableId = tableId;
this.output = output;
this.excelCellUtils = excelCellUtils;
this.seaTunnelRowType = seaTunnelRowType;
+ this.indexes = indexes;
+ this.useHeaderLine = useHeaderLine;
fieldTypes = seaTunnelRowType.getFieldTypes();
}
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> headMap,
AnalysisContext context) {
- for (int i = 0; i < headMap.size(); i++) {
- String header = headMap.get(i).getStringValue();
- if (!"null".equals(header)) {
- customHeaders.put(i, header);
+ headerColumns.clear();
+ customHeaders.clear();
+ for (Map.Entry<Integer, ReadCellData<?>> entry : headMap.entrySet()) {
+ String header = entry.getValue().getStringValue();
+ if (StringUtils.isNotEmpty(header) && !"null".equals(header)) {
+ customHeaders.put(entry.getKey(), header);
+ // Schema fields may appear in a different order than the
Excel columns.
+ if (useHeaderLine) {
+ int fieldIndex = seaTunnelRowType.indexOf(header, false);
+ if (fieldIndex >= 0) {
+ headerColumns.put(fieldIndex, entry.getKey());
+ }
+ }
+ }
+ }
+ if (useHeaderLine) {
+ for (int i = 0; i < fieldTypes.length; i++) {
+ if (!headerColumns.containsKey(i)) {
+ log.warn(
+ "Excel header does not contain schema field '{}';
values will be null",
+ seaTunnelRowType.getFieldName(i));
+ }
}
}
}
@@ -87,11 +131,18 @@ public class ExcelReaderListener extends
AnalysisEventListener<Map<Integer, Obje
Map<Integer, Cell> cellMap = context.readRowHolder().getCellMap();
int i = 0;
for (; i < fieldTypes.length; i++) {
- if (cellMap.get(i) == null) {
+ Integer columnIndex;
+ if (!useHeaderLine) {
+ columnIndex = indexes == null ? i : indexes[i];
+ } else {
+ columnIndex = headerColumns.get(i);
+ }
+ Cell cell = columnIndex == null ? null : cellMap.get(columnIndex);
+ if (cell == null) {
seaTunnelRow.setField(i, null);
} else {
- Object cell = excelCellUtils.convert(data.get(i),
fieldTypes[i], cellMap.get(i));
- seaTunnelRow.setField(i, cell);
+ Object value = excelCellUtils.convert(data.get(columnIndex),
fieldTypes[i], cell);
+ seaTunnelRow.setField(i, value);
}
}
seaTunnelRow.setTableId(tableId);
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/ExcelReadStrategy.java
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/ExcelReadStrategy.java
index d63143f278..697f9be30a 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/ExcelReadStrategy.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/ExcelReadStrategy.java
@@ -17,6 +17,8 @@
package org.apache.seatunnel.connectors.seatunnel.file.source.reader;
+import org.apache.seatunnel.shade.com.typesafe.config.Config;
+
import org.apache.seatunnel.api.source.Collector;
import org.apache.seatunnel.api.table.catalog.CatalogTable;
import org.apache.seatunnel.api.table.type.SeaTunnelDataType;
@@ -42,6 +44,7 @@ import org.apache.poi.ss.usermodel.CellValue;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.FormulaEvaluator;
+import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.NumberToTextConverter;
@@ -56,6 +59,7 @@ import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.io.InputStream;
+import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.IntStream;
@@ -75,6 +79,8 @@ public class ExcelReadStrategy extends AbstractReadStrategy {
private int cellCount;
+ private boolean useHeaderLine;
+
@SneakyThrows
@Override
public void read(String path, String tableId, Collector<SeaTunnelRow>
output) {
@@ -183,7 +189,13 @@ public class ExcelReadStrategy extends
AbstractReadStrategy {
ExcelReaderBuilder read =
EasyExcel.read(
inputStream,
- new ExcelReaderListener(tableId, output,
excelCellUtils, seaTunnelRowType));
+ new ExcelReaderListener(
+ tableId,
+ output,
+ excelCellUtils,
+ seaTunnelRowType,
+ indexes,
+ useHeaderLine));
if (pluginConfig.hasPath(FileBaseSourceOptions.SHEET_NAME.key())) {
read.sheet(pluginConfig.getString(FileBaseSourceOptions.SHEET_NAME.key()))
.headRowNumber((int) skipHeaderNumber)
@@ -261,21 +273,46 @@ public class ExcelReadStrategy extends
AbstractReadStrategy {
CommonErrorCodeDeprecated.UNSUPPORTED_OPERATION,
"Skip the number of rows exceeds the maximum or minimum
limit of Sheet");
}
+ int[] cellIndexes = indexes == null ? IntStream.range(0,
cellCount).toArray() : indexes;
+ // Maps output field indexes to Excel column indexes.
+ // Header mapping is opt-in; skipping rows alone keeps positional
reads.
+ Map<Integer, Integer> headerColumns = new HashMap<>();
+ if (useHeaderLine) {
+ Row headerRow = sheet.getRow(startRow - 1);
+ if (headerRow != null) {
+ for (Cell headerCell : headerRow) {
+ String header = formatter.formatCellValue(headerCell,
formulaEvaluator);
+ int fieldIndex = seaTunnelRowType.indexOf(header, false);
+ if (fieldIndex >= 0) {
+ headerColumns.put(fieldIndex,
headerCell.getColumnIndex());
+ }
+ }
+ }
+ for (int i = 0; i < fieldTypes.length; i++) {
+ if (!headerColumns.containsKey(i)) {
+ log.warn(
+ "Excel file {} header does not contain schema
field '{}'; values will be null",
+ currentFileName,
+ seaTunnelRowType.getFieldName(i));
+ }
+ }
+ }
IntStream.range(startRow, lastRowNum + 1)
.mapToObj(sheet::getRow)
.filter(Objects::nonNull)
.forEach(
rowData -> {
- int[] cellIndexes =
- indexes == null
- ? IntStream.range(0,
cellCount).toArray()
- : indexes;
- int z = 0;
SeaTunnelRow seaTunnelRow = new
SeaTunnelRow(cellCount);
- for (int j : cellIndexes) {
- Cell cell = rowData.getCell(j);
+ for (int i = 0; i < fieldTypes.length; i++) {
+ Integer columnIndex;
+ columnIndex =
+ useHeaderLine
+ ? headerColumns.get(i)
+ :
Integer.valueOf(cellIndexes[i]);
+ Cell cell =
+ columnIndex == null ? null :
rowData.getCell(columnIndex);
seaTunnelRow.setField(
- z++,
+ i,
cell == null
? null
: excelCellUtils.convert(
@@ -284,7 +321,7 @@ public class ExcelReadStrategy extends AbstractReadStrategy
{
cell,
formulaEvaluator,
formatter),
- fieldTypes[z - 1],
+ fieldTypes[i],
null));
}
if (isMergePartition) {
@@ -383,6 +420,19 @@ public class ExcelReadStrategy extends
AbstractReadStrategy {
"User must defined schema for json file type");
}
+ /**
+ * Loads the Excel header option. When enabled, the first row is used for
column-name mapping
+ * and takes precedence over {@code skip_header_row_number}.
+ */
+ @Override
+ public void setPluginConfig(Config pluginConfig) {
+ super.setPluginConfig(pluginConfig);
+ useHeaderLine =
readonlyConfig.get(FileBaseSourceOptions.EXCEL_USE_HEADER_LINE);
+ if (useHeaderLine) {
+ skipHeaderNumber = 1L;
+ }
+ }
+
private Object getCellValue(
CellType cellType,
Cell cell,
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/reader/ExcelReadStrategyTest.java
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/reader/ExcelReadStrategyTest.java
index 3186f3421d..166bda2ab2 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/reader/ExcelReadStrategyTest.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/reader/ExcelReadStrategyTest.java
@@ -243,6 +243,163 @@ public class ExcelReadStrategyTest {
testLargeExcelRead("/excel/e2e.xlsx", "/excel/e2exls.conf", 5);
}
+ @Test
+ public void testEasyExcelReadColumnsUsesSourceColumnIndexes()
+ throws IOException, URISyntaxException {
+ URL excelFile =
+
ExcelReadStrategyTest.class.getResource("/excel/test_read_excel_date_string.xlsx");
+ URL conf =
+
ExcelReadStrategyTest.class.getResource("/excel/test_read_excel_data_string.conf");
+ Assertions.assertNotNull(excelFile);
+ Assertions.assertNotNull(conf);
+ Config pluginConfig =
+ ConfigFactory.parseString("read_columns = [\"c_string\",
\"c_int\"]")
+ .withFallback(ConfigFactory.parseFile(new
File(conf.toURI())));
+ ExcelReadStrategy readStrategy = new ExcelReadStrategy();
+ readStrategy.setPluginConfig(pluginConfig);
+ readStrategy.init(new LocalConf(FS_DEFAULT_NAME_DEFAULT));
+
readStrategy.setCatalogTable(CatalogTableUtil.buildWithConfig(pluginConfig));
+
+ TestCollector collector = new TestCollector();
+ readStrategy.read(Paths.get(excelFile.toURI()).toString(), "",
collector);
+
+ Assertions.assertEquals(1, collector.getRows().size());
+ SeaTunnelRow row = collector.getRows().get(0);
+ Assertions.assertEquals(2, row.getArity());
+ Assertions.assertEquals("Cosmos", row.getField(0));
+ Assertions.assertEquals(333, row.getField(1));
+ }
+
+ @Test
+ public void testReadColumnsMatchesExcelHeadersWithProjectedSchema()
+ throws IOException, URISyntaxException {
+ URL excelFile =
+
ExcelReadStrategyTest.class.getResource("/excel/test_read_excel_date_string.xlsx");
+ Assertions.assertNotNull(excelFile);
+ for (String engine : new String[] {"POI", "EasyExcel"}) {
+ for (String skipRows : new String[] {"", "skip_header_row_number =
2\n"}) {
+ Config pluginConfig =
+ ConfigFactory.parseString(
+ "file_format_type = excel\n"
+ + "excel_engine = "
+ + engine
+ + "\n"
+ + "excel_use_header_line = true\n"
+ + skipRows
+ + "schema { columns = [{name =
c_bytes, type = string}, "
+ + "{name = c_int, type = int}, "
+ + "{name = c_string, type = string}]
}\n"
+ + "read_columns = [c_string, c_int]");
+ ExcelReadStrategy readStrategy = new ExcelReadStrategy();
+ readStrategy.setPluginConfig(pluginConfig);
+ readStrategy.init(new LocalConf(FS_DEFAULT_NAME_DEFAULT));
+
readStrategy.setCatalogTable(CatalogTableUtil.buildWithConfig(pluginConfig));
+
+ TestCollector collector = new TestCollector();
+ readStrategy.read(Paths.get(excelFile.toURI()).toString(), "",
collector);
+
+ Assertions.assertEquals(1, collector.getRows().size(), engine);
+ SeaTunnelRow row = collector.getRows().get(0);
+ Assertions.assertEquals("Cosmos", row.getField(0), engine);
+ Assertions.assertEquals(333, row.getField(1), engine);
+ }
+ }
+ }
+
+ @Test
+ public void testExcelHeaderWithoutMatchingSchemaKeepsPositionalReading()
+ throws IOException, URISyntaxException {
+ URL excelFile =
+
ExcelReadStrategyTest.class.getResource("/excel/test_read_excel_date_string.xlsx");
+ Assertions.assertNotNull(excelFile);
+ for (String engine : new String[] {"POI", "EasyExcel"}) {
+ Config pluginConfig =
+ ConfigFactory.parseString(
+ "file_format_type = excel\n"
+ + "excel_engine = "
+ + engine
+ + "\n"
+ + "skip_header_row_number = 1\n"
+ + "schema { columns = [{name = first, type
= string}, "
+ + "{name = second, type = string}] }");
+ ExcelReadStrategy readStrategy = new ExcelReadStrategy();
+ readStrategy.setPluginConfig(pluginConfig);
+ readStrategy.init(new LocalConf(FS_DEFAULT_NAME_DEFAULT));
+
readStrategy.setCatalogTable(CatalogTableUtil.buildWithConfig(pluginConfig));
+
+ TestCollector collector = new TestCollector();
+ readStrategy.read(Paths.get(excelFile.toURI()).toString(), "",
collector);
+
+ Assertions.assertEquals(1, collector.getRows().size(), engine);
+ SeaTunnelRow row = collector.getRows().get(0);
+ Assertions.assertEquals("1", row.getField(0), engine);
+ Assertions.assertEquals("22", row.getField(1), engine);
+ }
+ }
+
+ @Test
+ public void testExcelPartialHeaderMatchWithoutOptInKeepsPositionalReading()
+ throws IOException, URISyntaxException {
+ URL excelFile =
+
ExcelReadStrategyTest.class.getResource("/excel/test_read_excel_date_string.xlsx");
+ Assertions.assertNotNull(excelFile);
+ for (String engine : new String[] {"POI", "EasyExcel"}) {
+ Config pluginConfig =
+ ConfigFactory.parseString(
+ "file_format_type = excel\n"
+ + "excel_engine = "
+ + engine
+ + "\n"
+ + "skip_header_row_number = 1\n"
+ + "schema { columns = [{name = c_bytes,
type = string}, "
+ + "{name = second, type = string}] }");
+ ExcelReadStrategy readStrategy = new ExcelReadStrategy();
+ readStrategy.setPluginConfig(pluginConfig);
+ readStrategy.init(new LocalConf(FS_DEFAULT_NAME_DEFAULT));
+
readStrategy.setCatalogTable(CatalogTableUtil.buildWithConfig(pluginConfig));
+
+ TestCollector collector = new TestCollector();
+ readStrategy.read(Paths.get(excelFile.toURI()).toString(), "",
collector);
+
+ Assertions.assertEquals(1, collector.getRows().size(), engine);
+ SeaTunnelRow row = collector.getRows().get(0);
+ Assertions.assertEquals("1", row.getField(0), engine);
+ Assertions.assertEquals("22", row.getField(1), engine);
+ }
+ }
+
+ @Test
+ public void testExcelHeaderMissingProjectedFieldReturnsNull()
+ throws IOException, URISyntaxException {
+ URL excelFile =
+
ExcelReadStrategyTest.class.getResource("/excel/test_read_excel_date_string.xlsx");
+ Assertions.assertNotNull(excelFile);
+ for (String engine : new String[] {"POI", "EasyExcel"}) {
+ Config pluginConfig =
+ ConfigFactory.parseString(
+ "file_format_type = excel\n"
+ + "excel_engine = "
+ + engine
+ + "\n"
+ + "excel_use_header_line = true\n"
+ + "schema { columns = [{name = c_string,
type = string}, "
+ + "{name = missing_field, type = string}]
}\n"
+ + "read_columns = [c_string,
missing_field]");
+ ExcelReadStrategy readStrategy = new ExcelReadStrategy();
+ readStrategy.setPluginConfig(pluginConfig);
+ readStrategy.init(new LocalConf(FS_DEFAULT_NAME_DEFAULT));
+
readStrategy.setCatalogTable(CatalogTableUtil.buildWithConfig(pluginConfig));
+
+ TestCollector collector = new TestCollector();
+ readStrategy.read(Paths.get(excelFile.toURI()).toString(), "",
collector);
+
+ Assertions.assertEquals(1, collector.getRows().size(), engine);
+ SeaTunnelRow row = collector.getRows().get(0);
+ Assertions.assertEquals("Cosmos", row.getField(0), engine);
+ Assertions.assertNull(row.getField(1), engine);
+ }
+ }
+
@Test
public void testEasyExcelIgnoresPoiFileSizeLimit() throws IOException,
URISyntaxException {
URL excelFile =
ExcelReadStrategyTest.class.getResource("/excel/e2e.xlsx");
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-bos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/bos/source/BosFileSourceFactory.java
b/seatunnel-connectors-v2/connector-file/connector-file-bos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/bos/source/BosFileSourceFactory.java
index d3e90a1719..06c4bd2f62 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-bos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/bos/source/BosFileSourceFactory.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-bos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/bos/source/BosFileSourceFactory.java
@@ -94,6 +94,7 @@ public class BosFileSourceFactory implements
TableSourceFactory {
.optional(
FileBaseSourceOptions.SHEET_NAME,
FileBaseSourceOptions.EXCEL_ENGINE,
+ FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
.conditional(
FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-cos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/cos/source/CosFileSourceFactory.java
b/seatunnel-connectors-v2/connector-file/connector-file-cos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/cos/source/CosFileSourceFactory.java
index fe6422e75c..e918631e03 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-cos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/cos/source/CosFileSourceFactory.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-cos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/cos/source/CosFileSourceFactory.java
@@ -94,6 +94,7 @@ public class CosFileSourceFactory implements
TableSourceFactory {
.optional(
FileBaseSourceOptions.SHEET_NAME,
FileBaseSourceOptions.EXCEL_ENGINE,
+ FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
.conditional(
FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/source/FtpFileSourceFactory.java
b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/source/FtpFileSourceFactory.java
index e965ffca14..2b2c0c26b1 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/source/FtpFileSourceFactory.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/source/FtpFileSourceFactory.java
@@ -104,6 +104,7 @@ public class FtpFileSourceFactory implements
TableSourceFactory {
.optional(
FileBaseSourceOptions.SHEET_NAME,
FileBaseSourceOptions.EXCEL_ENGINE,
+ FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
.conditional(
FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-gcs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/gcs/source/GcsFileSourceFactory.java
b/seatunnel-connectors-v2/connector-file/connector-file-gcs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/gcs/source/GcsFileSourceFactory.java
index 945cb2c03f..ad9ff53959 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-gcs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/gcs/source/GcsFileSourceFactory.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-gcs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/gcs/source/GcsFileSourceFactory.java
@@ -117,6 +117,7 @@ public class GcsFileSourceFactory implements
TableSourceFactory {
.optional(
FileBaseSourceOptions.SHEET_NAME,
FileBaseSourceOptions.EXCEL_ENGINE,
+ FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
.conditional(
FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-hadoop/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hdfs/source/HdfsFileSourceFactory.java
b/seatunnel-connectors-v2/connector-file/connector-file-hadoop/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hdfs/source/HdfsFileSourceFactory.java
index b90a7e8aeb..6ab6ace818 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-hadoop/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hdfs/source/HdfsFileSourceFactory.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-hadoop/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hdfs/source/HdfsFileSourceFactory.java
@@ -113,6 +113,7 @@ public class HdfsFileSourceFactory implements
TableSourceFactory {
.optional(
FileBaseSourceOptions.SHEET_NAME,
FileBaseSourceOptions.EXCEL_ENGINE,
+ FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
.conditional(
HdfsFileSourceOptions.FILE_FORMAT_TYPE,
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-jindo-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/jindo/source/OssFileSourceFactory.java
b/seatunnel-connectors-v2/connector-file/connector-file-jindo-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/jindo/source/OssFileSourceFactory.java
index f94430a48f..a66194550d 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-jindo-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/jindo/source/OssFileSourceFactory.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-jindo-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/jindo/source/OssFileSourceFactory.java
@@ -94,6 +94,7 @@ public class OssFileSourceFactory implements
TableSourceFactory {
.optional(
FileBaseSourceOptions.SHEET_NAME,
FileBaseSourceOptions.EXCEL_ENGINE,
+ FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
.conditional(
FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-local/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/local/source/LocalFileSourceFactory.java
b/seatunnel-connectors-v2/connector-file/connector-file-local/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/local/source/LocalFileSourceFactory.java
index b6ef0900ba..ce2f0f65a8 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-local/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/local/source/LocalFileSourceFactory.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-local/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/local/source/LocalFileSourceFactory.java
@@ -113,6 +113,7 @@ public class LocalFileSourceFactory implements
TableSourceFactory {
.optional(
FileBaseSourceOptions.SHEET_NAME,
FileBaseSourceOptions.EXCEL_ENGINE,
+ FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
.conditional(
FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-local/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/local/LocalFileFactoryTest.java
b/seatunnel-connectors-v2/connector-file/connector-file-local/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/local/LocalFileFactoryTest.java
index 20f32463c8..8ea1c03b49 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-local/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/local/LocalFileFactoryTest.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-local/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/local/LocalFileFactoryTest.java
@@ -66,6 +66,10 @@ class LocalFileFactoryTest {
optionRule.getOptionalOptions().contains(FileBaseSourceOptions.START_MODE));
Assertions.assertTrue(
optionRule.getOptionalOptions().contains(FileBaseSourceOptions.POST_SYNC_ACTION));
+ Assertions.assertTrue(
+ optionRule
+ .getOptionalOptions()
+
.contains(FileBaseSourceOptions.EXCEL_USE_HEADER_LINE));
Assertions.assertTrue(
optionRule.getOptionalOptions().contains(FileBaseSourceOptions.BACKUP_PATH));
Assertions.assertTrue(
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-obs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/obs/source/ObsFileSourceFactory.java
b/seatunnel-connectors-v2/connector-file/connector-file-obs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/obs/source/ObsFileSourceFactory.java
index 23876525b3..fb19d0a949 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-obs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/obs/source/ObsFileSourceFactory.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-obs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/obs/source/ObsFileSourceFactory.java
@@ -84,6 +84,7 @@ public class ObsFileSourceFactory implements
TableSourceFactory {
.optional(
FileBaseSourceOptions.SHEET_NAME,
FileBaseSourceOptions.EXCEL_ENGINE,
+ FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
.conditional(
FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/source/OssFileSourceFactory.java
b/seatunnel-connectors-v2/connector-file/connector-file-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/source/OssFileSourceFactory.java
index 634e0ce627..871093fed2 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/source/OssFileSourceFactory.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/source/OssFileSourceFactory.java
@@ -104,6 +104,7 @@ public class OssFileSourceFactory implements
TableSourceFactory {
.optional(
FileBaseSourceOptions.SHEET_NAME,
FileBaseSourceOptions.EXCEL_ENGINE,
+ FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
.conditional(
FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-s3/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/s3/source/S3FileSourceFactory.java
b/seatunnel-connectors-v2/connector-file/connector-file-s3/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/s3/source/S3FileSourceFactory.java
index 03cb29e75c..6e60f416ea 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-s3/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/s3/source/S3FileSourceFactory.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-s3/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/s3/source/S3FileSourceFactory.java
@@ -137,6 +137,7 @@ public class S3FileSourceFactory implements
TableSourceFactory, SupportSourceDry
.optional(
FileBaseSourceOptions.SHEET_NAME,
FileBaseSourceOptions.EXCEL_ENGINE,
+ FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
.conditional(
FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/source/SftpFileSourceFactory.java
b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/source/SftpFileSourceFactory.java
index 768c8f75ed..951d273504 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/source/SftpFileSourceFactory.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/source/SftpFileSourceFactory.java
@@ -96,6 +96,7 @@ public class SftpFileSourceFactory implements
TableSourceFactory {
.optional(
FileBaseSourceOptions.SHEET_NAME,
FileBaseSourceOptions.EXCEL_ENGINE,
+ FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
.conditional(
FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-smb/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/smb/source/SmbFileSourceFactory.java
b/seatunnel-connectors-v2/connector-file/connector-file-smb/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/smb/source/SmbFileSourceFactory.java
index c109039f83..c97d1599f6 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-smb/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/smb/source/SmbFileSourceFactory.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-smb/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/smb/source/SmbFileSourceFactory.java
@@ -97,6 +97,7 @@ public class SmbFileSourceFactory implements
TableSourceFactory {
.optional(
FileBaseSourceOptions.SHEET_NAME,
FileBaseSourceOptions.EXCEL_ENGINE,
+ FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
.conditional(
FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/java/org/apache/seatunnel/e2e/connector/file/local/LocalFileIT.java
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/java/org/apache/seatunnel/e2e/connector/file/local/LocalFileIT.java
index 80864b1ada..3eb325e074 100644
---
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/java/org/apache/seatunnel/e2e/connector/file/local/LocalFileIT.java
+++
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/java/org/apache/seatunnel/e2e/connector/file/local/LocalFileIT.java
@@ -38,6 +38,8 @@ import org.apache.seatunnel.e2e.common.util.JobIdGenerator;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Assertions;
@@ -45,6 +47,7 @@ import org.junit.jupiter.api.TestTemplate;
import org.testcontainers.containers.Container;
import org.testcontainers.containers.GenericContainer;
import
org.testcontainers.shaded.com.github.dockerjava.core.command.ExecStartResultCallback;
+import org.testcontainers.utility.MountableFile;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import io.airlift.compress.lzo.LzopCodec;
@@ -251,6 +254,18 @@ public class LocalFileIT extends TestSuiteBase {
"/seatunnel/read/excel/name=tyrantlucifer/hobby=coding/e2e.xls",
container);
+ Path headerProjectionFile =
+ Files.createTempFile("excel-header-projection-",
".xlsx");
+ try {
+ createExcelHeaderProjectionFile(headerProjectionFile);
+ // Temporary files can be owner-only; allow the container
process to read it.
+ container.copyFileToContainer(
+ MountableFile.forHostPath(headerProjectionFile,
0644),
+
"/seatunnel/read/excel_header_projection/e2e.xlsx");
+ } finally {
+ Files.deleteIfExists(headerProjectionFile);
+ }
+
Path xlsxZip =
convertToZipFile(
Lists.newArrayList(
@@ -360,6 +375,8 @@ public class LocalFileIT extends TestSuiteBase {
helper.execute("/excel/fake_to_local_excel.conf");
helper.execute("/excel/local_excel_to_assert.conf");
helper.execute("/excel/local_excel_projection_to_assert.conf");
+
helper.execute("/excel/local_excel_header_projection_poi_to_assert.conf");
+
helper.execute("/excel/local_excel_header_projection_easyexcel_to_assert.conf");
helper.execute("/excel/special_excel_to_assert.conf");
helper.execute("/excel/local_filter_excel_to_assert.conf");
helper.execute("/excel/local_filter_regex_excel_to_assert.conf");
@@ -971,6 +988,20 @@ public class LocalFileIT extends TestSuiteBase {
return path;
}
+ private void createExcelHeaderProjectionFile(Path file) throws IOException
{
+ try (XSSFWorkbook workbook = new XSSFWorkbook();
+ FileOutputStream output = new FileOutputStream(file.toFile()))
{
+ Sheet sheet = workbook.createSheet();
+ sheet.createRow(0).createCell(0).setCellValue("header-b");
+ sheet.getRow(0).createCell(1).setCellValue("header-a");
+ sheet.getRow(0).createCell(2).setCellValue("header-c");
+ sheet.createRow(1).createCell(0).setCellValue("value-b");
+ sheet.getRow(1).createCell(1).setCellValue("value-a");
+ sheet.getRow(1).createCell(2).setCellValue("value-c");
+ workbook.write(output);
+ }
+ }
+
public Path convertToZipFile(List<File> files, String name) throws
IOException {
if (files == null || files.isEmpty()) {
throw new IllegalArgumentException("File list is empty or
invalid");
diff --git
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/resources/excel/local_excel_header_projection_easyexcel_to_assert.conf
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/resources/excel/local_excel_header_projection_easyexcel_to_assert.conf
new file mode 100644
index 0000000000..77c57a073e
--- /dev/null
+++
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/resources/excel/local_excel_header_projection_easyexcel_to_assert.conf
@@ -0,0 +1,61 @@
+#
+# 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.
+#
+
+env {
+ parallelism = 1
+ job.mode = "BATCH"
+}
+
+source {
+ LocalFile {
+ path = "/seatunnel/read/excel_header_projection/e2e.xlsx"
+ file_format_type = excel
+ excel_engine = EasyExcel
+ excel_use_header_line = true
+ read_columns = ["header-a", "header-c"]
+ schema {
+ fields {
+ "header-a" = string
+ "header-b" = string
+ "header-c" = string
+ }
+ }
+ }
+}
+
+sink {
+ Assert {
+ rules {
+ row_rules = [
+ { rule_type = MIN_ROW, rule_value = 1 },
+ { rule_type = MAX_ROW, rule_value = 1 }
+ ]
+ field_rules = [
+ {
+ field_name = "header-a"
+ field_type = string
+ field_value = [{ rule_type = NOT_NULL, equals_to = "value-a" }]
+ },
+ {
+ field_name = "header-c"
+ field_type = string
+ field_value = [{ rule_type = NOT_NULL, equals_to = "value-c" }]
+ }
+ ]
+ }
+ }
+}
diff --git
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/resources/excel/local_excel_header_projection_poi_to_assert.conf
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/resources/excel/local_excel_header_projection_poi_to_assert.conf
new file mode 100644
index 0000000000..d01cabfeea
--- /dev/null
+++
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/resources/excel/local_excel_header_projection_poi_to_assert.conf
@@ -0,0 +1,61 @@
+#
+# 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.
+#
+
+env {
+ parallelism = 1
+ job.mode = "BATCH"
+}
+
+source {
+ LocalFile {
+ path = "/seatunnel/read/excel_header_projection/e2e.xlsx"
+ file_format_type = excel
+ excel_engine = POI
+ excel_use_header_line = true
+ read_columns = ["header-a", "header-c"]
+ schema {
+ fields {
+ "header-a" = string
+ "header-b" = string
+ "header-c" = string
+ }
+ }
+ }
+}
+
+sink {
+ Assert {
+ rules {
+ row_rules = [
+ { rule_type = MIN_ROW, rule_value = 1 },
+ { rule_type = MAX_ROW, rule_value = 1 }
+ ]
+ field_rules = [
+ {
+ field_name = "header-a"
+ field_type = string
+ field_value = [{ rule_type = NOT_NULL, equals_to = "value-a" }]
+ },
+ {
+ field_name = "header-c"
+ field_type = string
+ field_value = [{ rule_type = NOT_NULL, equals_to = "value-c" }]
+ }
+ ]
+ }
+ }
+}