This is an automated email from the ASF dual-hosted git repository.

davidzollo 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 970cadb1a4 [Feature][Connector-V2] Add optional Markdown RAG metadata 
for file source (#10844)
970cadb1a4 is described below

commit 970cadb1a4b27f0347a3b1e65427bea8a68281c3
Author: yzeng1618 <[email protected]>
AuthorDate: Wed May 13 11:33:49 2026 +0800

    [Feature][Connector-V2] Add optional Markdown RAG metadata for file source 
(#10844)
    
    Co-authored-by: zengyi <[email protected]>
---
 docs/en/connectors/source/CosFile.md               |   9 +
 docs/en/connectors/source/FtpFile.md               |   9 +
 docs/en/connectors/source/HdfsFile.md              |   9 +
 docs/en/connectors/source/LocalFile.md             |   9 +
 docs/en/connectors/source/ObsFile.md               |   9 +
 docs/en/connectors/source/OssFile.md               |   9 +
 docs/en/connectors/source/OssJindoFile.md          |   9 +
 docs/en/connectors/source/S3File.md                |   9 +
 docs/en/connectors/source/SftpFile.md              |   9 +
 docs/zh/connectors/source/CosFile.md               |   9 +
 docs/zh/connectors/source/FtpFile.md               |   9 +
 docs/zh/connectors/source/HdfsFile.md              |   9 +
 docs/zh/connectors/source/LocalFile.md             |   9 +
 docs/zh/connectors/source/ObsFile.md               |  28 +++
 docs/zh/connectors/source/OssFile.md               |   9 +
 docs/zh/connectors/source/OssJindoFile.md          |  29 +++
 docs/zh/connectors/source/S3File.md                |   9 +
 docs/zh/connectors/source/SftpFile.md              |   9 +
 .../file/config/BaseFileSourceConfig.java          |  14 +-
 .../file/config/FileBaseSourceOptions.java         |   8 +
 .../seatunnel/file/source/BaseFileSource.java      |  15 +-
 .../file/source/reader/MarkdownReadStrategy.java   | 203 ++++++++++++++++----
 .../seatunnel/file/source/BaseFileSourceTest.java  | 206 +++++++++++++++++++++
 .../source/reader/MarkdownReadStrategyTest.java    | 164 +++++++++++++++-
 .../file/cos/source/CosFileSourceFactory.java      |   4 +
 .../file/ftp/source/FtpFileSourceFactory.java      |   4 +
 .../file/hdfs/source/HdfsFileSourceFactory.java    |   4 +
 .../oss/jindo/source/OssFileSourceFactory.java     |   4 +
 .../file/local/source/LocalFileSourceFactory.java  |   4 +
 .../seatunnel/file/local/LocalFileFactoryTest.java |  10 +
 .../seatunnel/file/local/LocalFileSourceTest.java  |  89 +++++++++
 .../file/obs/source/ObsFileSourceFactory.java      |   4 +
 .../file/oss/source/OssFileSourceFactory.java      |   4 +
 .../file/s3/source/S3FileSourceFactory.java        |   4 +
 .../file/sftp/source/SftpFileSourceFactory.java    |   4 +
 .../e2e/connector/file/local/LocalFileIT.java      |  12 ++
 ...local_file_markdown_rag_metadata_to_assert.conf | 143 ++++++++++++++
 37 files changed, 1053 insertions(+), 48 deletions(-)

diff --git a/docs/en/connectors/source/CosFile.md 
b/docs/en/connectors/source/CosFile.md
index b81254e02d..14097b4390 100644
--- a/docs/en/connectors/source/CosFile.md
+++ b/docs/en/connectors/source/CosFile.md
@@ -195,6 +195,15 @@ Each element is converted to a row with the following 
schema:
 - `parent_id`: ID of the parent element
 - `child_ids`: Comma-separated list of child element IDs
 
+When `markdown_rag_metadata_enabled` is set to `true`, SeaTunnel appends the 
following RAG metadata fields after `child_ids`:
+- `source_uri`: Source file path or URI
+- `document_id`: Stable document identifier derived from `source_uri`
+- `chunk_id`: Stable chunk identifier derived from document identity, chunk 
order, and content hash
+- `chunk_index`: One-based chunk order in the parsed document
+- `content_hash`: SHA-256 hash of the emitted `text` value
+
+The option defaults to `false`, so the original Markdown schema is unchanged 
unless you enable it.
+
 Note: Markdown format only supports reading, not writing.
 
 ### bucket [string]
diff --git a/docs/en/connectors/source/FtpFile.md 
b/docs/en/connectors/source/FtpFile.md
index a59aa86cc8..d38c18b666 100644
--- a/docs/en/connectors/source/FtpFile.md
+++ b/docs/en/connectors/source/FtpFile.md
@@ -281,6 +281,15 @@ Each element is converted to a row with the following 
schema:
 - `parent_id`: ID of the parent element
 - `child_ids`: Comma-separated list of child element IDs
 
+When `markdown_rag_metadata_enabled` is set to `true`, SeaTunnel appends the 
following RAG metadata fields after `child_ids`:
+- `source_uri`: Source file path or URI
+- `document_id`: Stable document identifier derived from `source_uri`
+- `chunk_id`: Stable chunk identifier derived from document identity, chunk 
order, and content hash
+- `chunk_index`: One-based chunk order in the parsed document
+- `content_hash`: SHA-256 hash of the emitted `text` value
+
+The option defaults to `false`, so the original Markdown schema is unchanged 
unless you enable it.
+
 Note: Markdown format only supports reading, not writing.
 
 ### connection_mode [string]
diff --git a/docs/en/connectors/source/HdfsFile.md 
b/docs/en/connectors/source/HdfsFile.md
index 5d0fe84de9..9a4835af34 100644
--- a/docs/en/connectors/source/HdfsFile.md
+++ b/docs/en/connectors/source/HdfsFile.md
@@ -114,6 +114,15 @@ Each element is converted to a row with the following 
schema:
 - `parent_id`: ID of the parent element
 - `child_ids`: Comma-separated list of child element IDs
 
+When `markdown_rag_metadata_enabled` is set to `true`, SeaTunnel appends the 
following RAG metadata fields after `child_ids`:
+- `source_uri`: Source file path or URI
+- `document_id`: Stable document identifier derived from `source_uri`
+- `chunk_id`: Stable chunk identifier derived from document identity, chunk 
order, and content hash
+- `chunk_index`: One-based chunk order in the parsed document
+- `content_hash`: SHA-256 hash of the emitted `text` value
+
+The option defaults to `false`, so the original Markdown schema is unchanged 
unless you enable it.
+
 Note: Markdown format only supports reading, not writing.
 
 ### delimiter/field_delimiter [string]
diff --git a/docs/en/connectors/source/LocalFile.md 
b/docs/en/connectors/source/LocalFile.md
index 46aba942d6..33e4085509 100644
--- a/docs/en/connectors/source/LocalFile.md
+++ b/docs/en/connectors/source/LocalFile.md
@@ -201,6 +201,15 @@ Each element is converted to a row with the following 
schema:
 - `parent_id`: ID of the parent element
 - `child_ids`: Comma-separated list of child element IDs
 
+When `markdown_rag_metadata_enabled` is set to `true`, SeaTunnel appends the 
following RAG metadata fields after `child_ids`:
+- `source_uri`: Source file path or URI
+- `document_id`: Stable document identifier derived from `source_uri`
+- `chunk_id`: Stable chunk identifier derived from document identity, chunk 
order, and content hash
+- `chunk_index`: One-based chunk order in the parsed document
+- `content_hash`: SHA-256 hash of the emitted `text` value
+
+The option defaults to `false`, so the original Markdown schema is unchanged 
unless you enable it.
+
 Note: Markdown format only supports reading, not writing.
 
 ### read_columns [list]
diff --git a/docs/en/connectors/source/ObsFile.md 
b/docs/en/connectors/source/ObsFile.md
index 573d618843..824c63f07c 100644
--- a/docs/en/connectors/source/ObsFile.md
+++ b/docs/en/connectors/source/ObsFile.md
@@ -237,6 +237,15 @@ schema {
 > - `parent_id`: ID of the parent element
 > - `child_ids`: Comma-separated list of child element IDs
 >
+> When `markdown_rag_metadata_enabled` is set to `true`, SeaTunnel appends the 
following RAG metadata fields after `child_ids`:
+> - `source_uri`: Source file path or URI
+> - `document_id`: Stable document identifier derived from `source_uri`
+> - `chunk_id`: Stable chunk identifier derived from document identity, chunk 
order, and content hash
+> - `chunk_index`: One-based chunk order in the parsed document
+> - `content_hash`: SHA-256 hash of the emitted `text` value
+>
+> The option defaults to `false`, so the original Markdown schema is unchanged 
unless you enable it.
+>
 > Note: Markdown format only supports reading, not writing.
 
 #### <span id="schema"> schema  </span>
diff --git a/docs/en/connectors/source/OssFile.md 
b/docs/en/connectors/source/OssFile.md
index 90ed790adc..2ed32e9ca9 100644
--- a/docs/en/connectors/source/OssFile.md
+++ b/docs/en/connectors/source/OssFile.md
@@ -236,6 +236,15 @@ Each element is converted to a row with the following 
schema:
 - `parent_id`: ID of the parent element
 - `child_ids`: Comma-separated list of child element IDs
 
+When `markdown_rag_metadata_enabled` is set to `true`, SeaTunnel appends the 
following RAG metadata fields after `child_ids`:
+- `source_uri`: Source file path or URI
+- `document_id`: Stable document identifier derived from `source_uri`
+- `chunk_id`: Stable chunk identifier derived from document identity, chunk 
order, and content hash
+- `chunk_index`: One-based chunk order in the parsed document
+- `content_hash`: SHA-256 hash of the emitted `text` value
+
+The option defaults to `false`, so the original Markdown schema is unchanged 
unless you enable it.
+
 Note: Markdown format only supports reading, not writing.
 
 ### compress_codec [string]
diff --git a/docs/en/connectors/source/OssJindoFile.md 
b/docs/en/connectors/source/OssJindoFile.md
index f9e3f8c86b..e3b9867f23 100644
--- a/docs/en/connectors/source/OssJindoFile.md
+++ b/docs/en/connectors/source/OssJindoFile.md
@@ -197,6 +197,15 @@ Each element is converted to a row with the following 
schema:
 - `parent_id`: ID of the parent element
 - `child_ids`: Comma-separated list of child element IDs
 
+When `markdown_rag_metadata_enabled` is set to `true`, SeaTunnel appends the 
following RAG metadata fields after `child_ids`:
+- `source_uri`: Source file path or URI
+- `document_id`: Stable document identifier derived from `source_uri`
+- `chunk_id`: Stable chunk identifier derived from document identity, chunk 
order, and content hash
+- `chunk_index`: One-based chunk order in the parsed document
+- `content_hash`: SHA-256 hash of the emitted `text` value
+
+The option defaults to `false`, so the original Markdown schema is unchanged 
unless you enable it.
+
 Note: Markdown format only supports reading, not writing.
 
 ### bucket [string]
diff --git a/docs/en/connectors/source/S3File.md 
b/docs/en/connectors/source/S3File.md
index 239886a80d..61275f37c1 100644
--- a/docs/en/connectors/source/S3File.md
+++ b/docs/en/connectors/source/S3File.md
@@ -245,6 +245,15 @@ Each element is converted to a row with the following 
schema:
 - `parent_id`: ID of the parent element
 - `child_ids`: Comma-separated list of child element IDs
 
+When `markdown_rag_metadata_enabled` is set to `true`, SeaTunnel appends the 
following RAG metadata fields after `child_ids`:
+- `source_uri`: Source file path or URI
+- `document_id`: Stable document identifier derived from `source_uri`
+- `chunk_id`: Stable chunk identifier derived from document identity, chunk 
order, and content hash
+- `chunk_index`: One-based chunk order in the parsed document
+- `content_hash`: SHA-256 hash of the emitted `text` value
+
+The option defaults to `false`, so the original Markdown schema is unchanged 
unless you enable it.
+
 Note: Markdown format only supports reading, not writing.
 
 ### delimiter/field_delimiter [string]
diff --git a/docs/en/connectors/source/SftpFile.md 
b/docs/en/connectors/source/SftpFile.md
index 272ea7cbe5..b819cb3ce5 100644
--- a/docs/en/connectors/source/SftpFile.md
+++ b/docs/en/connectors/source/SftpFile.md
@@ -257,6 +257,15 @@ Each element is converted to a row with the following 
schema:
 - `parent_id`: ID of the parent element
 - `child_ids`: Comma-separated list of child element IDs
 
+When `markdown_rag_metadata_enabled` is set to `true`, SeaTunnel appends the 
following RAG metadata fields after `child_ids`:
+- `source_uri`: Source file path or URI
+- `document_id`: Stable document identifier derived from `source_uri`
+- `chunk_id`: Stable chunk identifier derived from document identity, chunk 
order, and content hash
+- `chunk_index`: One-based chunk order in the parsed document
+- `content_hash`: SHA-256 hash of the emitted `text` value
+
+The option defaults to `false`, so the original Markdown schema is unchanged 
unless you enable it.
+
 Note: Markdown format only supports reading, not writing.
 
 ### compress_codec [string]
diff --git a/docs/zh/connectors/source/CosFile.md 
b/docs/zh/connectors/source/CosFile.md
index 1066a0ad5d..5a2e8943bc 100644
--- a/docs/zh/connectors/source/CosFile.md
+++ b/docs/zh/connectors/source/CosFile.md
@@ -192,6 +192,15 @@ markdown 解析器提取各种元素,包括标题、段落、列表、代码
 - `parent_id`:父元素的 ID
 - `child_ids`:子元素 ID 的逗号分隔列表
 
+当 `markdown_rag_metadata_enabled` 设置为 `true` 时,SeaTunnel 会在 `child_ids` 之后追加以下 
RAG 元数据字段:
+- `source_uri`:源文件路径或 URI
+- `document_id`:由 `source_uri` 派生的稳定文档标识符
+- `chunk_id`:由文档标识、chunk 顺序和内容哈希派生的稳定 chunk 标识符
+- `chunk_index`:解析后文档中的一基 chunk 顺序
+- `content_hash`:已输出 `text` 值的 SHA-256 哈希
+
+该选项默认值为 `false`,因此只有显式启用后才会改变原始 Markdown schema。
+
 注意:Markdown 格式仅支持读取,不支持写入。
 根据此要求,您需要确保源端和目标端使用“二进制”格式进行文件同步同时。您可以在下面的示例中找到具体用法。
 
diff --git a/docs/zh/connectors/source/FtpFile.md 
b/docs/zh/connectors/source/FtpFile.md
index a015ec91ad..64666a8c85 100644
--- a/docs/zh/connectors/source/FtpFile.md
+++ b/docs/zh/connectors/source/FtpFile.md
@@ -258,6 +258,15 @@ markdown 解析器提取各种元素,包括标题、段落、列表、代码
 - `parent_id`:父元素的 ID
 - `child_ids`:子元素 ID 的逗号分隔列表
 
+当 `markdown_rag_metadata_enabled` 设置为 `true` 时,SeaTunnel 会在 `child_ids` 之后追加以下 
RAG 元数据字段:
+- `source_uri`:源文件路径或 URI
+- `document_id`:由 `source_uri` 派生的稳定文档标识符
+- `chunk_id`:由文档标识、chunk 顺序和内容哈希派生的稳定 chunk 标识符
+- `chunk_index`:解析后文档中的一基 chunk 顺序
+- `content_hash`:已输出 `text` 值的 SHA-256 哈希
+
+该选项默认值为 `false`,因此只有显式启用后才会改变原始 Markdown schema。
+
 注意:Markdown 格式仅支持读取,不支持写入。
 
 ### connection_mode [string]
diff --git a/docs/zh/connectors/source/HdfsFile.md 
b/docs/zh/connectors/source/HdfsFile.md
index 0a376cd92d..c2c649ffc9 100644
--- a/docs/zh/connectors/source/HdfsFile.md
+++ b/docs/zh/connectors/source/HdfsFile.md
@@ -114,6 +114,15 @@ markdown 解析器提取各种元素,包括标题、段落、列表、代码
 - `parent_id`:父元素的 ID
 - `child_ids`:子元素 ID 的逗号分隔列表
 
+当 `markdown_rag_metadata_enabled` 设置为 `true` 时,SeaTunnel 会在 `child_ids` 之后追加以下 
RAG 元数据字段:
+- `source_uri`:源文件路径或 URI
+- `document_id`:由 `source_uri` 派生的稳定文档标识符
+- `chunk_id`:由文档标识、chunk 顺序和内容哈希派生的稳定 chunk 标识符
+- `chunk_index`:解析后文档中的一基 chunk 顺序
+- `content_hash`:已输出 `text` 值的 SHA-256 哈希
+
+该选项默认值为 `false`,因此只有显式启用后才会改变原始 Markdown schema。
+
 注意:Markdown 格式仅支持读取,不支持写入。
 
 ### delimiter/field_delimiter [string]
diff --git a/docs/zh/connectors/source/LocalFile.md 
b/docs/zh/connectors/source/LocalFile.md
index e39ffea331..14f1b074be 100644
--- a/docs/zh/connectors/source/LocalFile.md
+++ b/docs/zh/connectors/source/LocalFile.md
@@ -201,6 +201,15 @@ markdown 解析器提取各种元素,包括标题、段落、列表、代码
 - `parent_id`:父元素的 ID
 - `child_ids`:子元素 ID 的逗号分隔列表
 
+当 `markdown_rag_metadata_enabled` 设置为 `true` 时,SeaTunnel 会在 `child_ids` 之后追加以下 
RAG 元数据字段:
+- `source_uri`:源文件路径或 URI
+- `document_id`:由 `source_uri` 派生的稳定文档标识符
+- `chunk_id`:由文档标识、chunk 顺序和内容哈希派生的稳定 chunk 标识符
+- `chunk_index`:解析后文档中的一基 chunk 顺序
+- `content_hash`:已输出 `text` 值的 SHA-256 哈希
+
+该选项默认值为 `false`,因此只有显式启用后才会改变原始 Markdown schema。
+
 注意:Markdown 格式仅支持读取,不支持写入。
 
 ### read_columns [list]
diff --git a/docs/zh/connectors/source/ObsFile.md 
b/docs/zh/connectors/source/ObsFile.md
index ab8d6becfd..6349284912 100644
--- a/docs/zh/connectors/source/ObsFile.md
+++ b/docs/zh/connectors/source/ObsFile.md
@@ -81,6 +81,34 @@ import ChangeLog from '../changelog/connector-file-obs.md';
 | quote_char                | string  | 否  | "                   | 用于包裹 CSV 
字段的单字符,可保证包含逗号、换行符或引号的字段被正确解析。 |
 | escape_char               | string  | 否  | -                   | 用于在 CSV 
字段内转义引号或其他特殊字符,使其不会结束字段。        |
 
+### file_format_type [string]
+
+文件类型,支持以下文件类型:
+
+`text` `csv` `parquet` `orc` `json` `excel` `markdown`
+
+如果您将文件类型指定为 `markdown`,SeaTunnel 可以解析 markdown 文件并提取结构化数据。
+markdown 解析器提取各种元素,包括标题、段落、列表、代码块、表格等。
+每个元素都转换为具有以下架构的行:
+- `element_id`:元素的唯一标识符
+- `element_type`:元素类型(Heading、Paragraph、ListItem 等)
+- `heading_level`:标题级别(1-6,非标题元素为 null)
+- `text`:元素的文本内容
+- `page_number`:页码(默认:1)
+- `position_index`:文档中的位置索引
+- `parent_id`:父元素的 ID
+- `child_ids`:子元素 ID 的逗号分隔列表
+
+当 `markdown_rag_metadata_enabled` 设置为 `true` 时,SeaTunnel 会在 `child_ids` 之后追加以下 
RAG 元数据字段:
+- `source_uri`:源文件路径或 URI
+- `document_id`:由 `source_uri` 派生的稳定文档标识符
+- `chunk_id`:由文档标识、chunk 顺序和内容哈希派生的稳定 chunk 标识符
+- `chunk_index`:解析后文档中的一基 chunk 顺序
+- `content_hash`:已输出 `text` 值的 SHA-256 哈希
+
+该选项默认值为 `false`,因此只有显式启用后才会改变原始 Markdown schema。
+
+注意:Markdown 格式仅支持读取,不支持写入。
 
 ## 变更日志
 
diff --git a/docs/zh/connectors/source/OssFile.md 
b/docs/zh/connectors/source/OssFile.md
index 45990aaf9f..aa583cb0e5 100644
--- a/docs/zh/connectors/source/OssFile.md
+++ b/docs/zh/connectors/source/OssFile.md
@@ -262,6 +262,15 @@ markdown 解析器提取各种元素,包括标题、段落、列表、代码
 - `parent_id`:父元素的 ID
 - `child_ids`:子元素 ID 的逗号分隔列表
 
+当 `markdown_rag_metadata_enabled` 设置为 `true` 时,SeaTunnel 会在 `child_ids` 之后追加以下 
RAG 元数据字段:
+- `source_uri`:源文件路径或 URI
+- `document_id`:由 `source_uri` 派生的稳定文档标识符
+- `chunk_id`:由文档标识、chunk 顺序和内容哈希派生的稳定 chunk 标识符
+- `chunk_index`:解析后文档中的一基 chunk 顺序
+- `content_hash`:已输出 `text` 值的 SHA-256 哈希
+
+该选项默认值为 `false`,因此只有显式启用后才会改变原始 Markdown schema。
+
 注意:Markdown 格式仅支持读取,不支持写入。
 
 ### quote_char [string]
diff --git a/docs/zh/connectors/source/OssJindoFile.md 
b/docs/zh/connectors/source/OssJindoFile.md
index cbd5e25d18..096488d268 100644
--- a/docs/zh/connectors/source/OssJindoFile.md
+++ b/docs/zh/connectors/source/OssJindoFile.md
@@ -81,6 +81,35 @@ import ChangeLog from 
'../changelog/connector-file-oss-jindo.md';
 | quote_char                | string  | 否  | "                           | 
用于包裹 CSV 字段的单字符,可保证包含逗号、换行符或引号的字段被正确解析。                                       |
 | escape_char               | string  | 否  | -                           | 用于在 
CSV 字段内转义引号或其他特殊字符,使其不会结束字段。                                              |
 
+### file_format_type [string]
+
+文件类型,支持以下文件类型:
+
+`text` `csv` `parquet` `orc` `json` `excel` `xml` `binary` `markdown`
+
+如果您将文件类型指定为 `markdown`,SeaTunnel 可以解析 markdown 文件并提取结构化数据。
+markdown 解析器提取各种元素,包括标题、段落、列表、代码块、表格等。
+每个元素都转换为具有以下架构的行:
+- `element_id`:元素的唯一标识符
+- `element_type`:元素类型(Heading、Paragraph、ListItem 等)
+- `heading_level`:标题级别(1-6,非标题元素为 null)
+- `text`:元素的文本内容
+- `page_number`:页码(默认:1)
+- `position_index`:文档中的位置索引
+- `parent_id`:父元素的 ID
+- `child_ids`:子元素 ID 的逗号分隔列表
+
+当 `markdown_rag_metadata_enabled` 设置为 `true` 时,SeaTunnel 会在 `child_ids` 之后追加以下 
RAG 元数据字段:
+- `source_uri`:源文件路径或 URI
+- `document_id`:由 `source_uri` 派生的稳定文档标识符
+- `chunk_id`:由文档标识、chunk 顺序和内容哈希派生的稳定 chunk 标识符
+- `chunk_index`:解析后文档中的一基 chunk 顺序
+- `content_hash`:已输出 `text` 值的 SHA-256 哈希
+
+该选项默认值为 `false`,因此只有显式启用后才会改变原始 Markdown schema。
+
+注意:Markdown 格式仅支持读取,不支持写入。
+
 ## 变更日志
 
 <ChangeLog />
diff --git a/docs/zh/connectors/source/S3File.md 
b/docs/zh/connectors/source/S3File.md
index 5b1dd33eb5..6fae586557 100644
--- a/docs/zh/connectors/source/S3File.md
+++ b/docs/zh/connectors/source/S3File.md
@@ -383,6 +383,15 @@ markdown 解析器提取各种元素,包括标题、段落、列表、代码
 - `parent_id`:父元素的 ID
 - `child_ids`:子元素 ID 的逗号分隔列表
 
+当 `markdown_rag_metadata_enabled` 设置为 `true` 时,SeaTunnel 会在 `child_ids` 之后追加以下 
RAG 元数据字段:
+- `source_uri`:源文件路径或 URI
+- `document_id`:由 `source_uri` 派生的稳定文档标识符
+- `chunk_id`:由文档标识、chunk 顺序和内容哈希派生的稳定 chunk 标识符
+- `chunk_index`:解析后文档中的一基 chunk 顺序
+- `content_hash`:已输出 `text` 值的 SHA-256 哈希
+
+该选项默认值为 `false`,因此只有显式启用后才会改变原始 Markdown schema。
+
 注意:Markdown 格式仅支持读取,不支持写入。
 
 ### schema [config]
diff --git a/docs/zh/connectors/source/SftpFile.md 
b/docs/zh/connectors/source/SftpFile.md
index 7b3b13c1d9..9112f06d34 100644
--- a/docs/zh/connectors/source/SftpFile.md
+++ b/docs/zh/connectors/source/SftpFile.md
@@ -255,6 +255,15 @@ markdown 解析器提取各种元素,包括标题、段落、列表、代码
 - `parent_id`:父元素的 ID
 - `child_ids`:子元素 ID 的逗号分隔列表
 
+当 `markdown_rag_metadata_enabled` 设置为 `true` 时,SeaTunnel 会在 `child_ids` 之后追加以下 
RAG 元数据字段:
+- `source_uri`:源文件路径或 URI
+- `document_id`:由 `source_uri` 派生的稳定文档标识符
+- `chunk_id`:由文档标识、chunk 顺序和内容哈希派生的稳定 chunk 标识符
+- `chunk_index`:解析后文档中的一基 chunk 顺序
+- `content_hash`:已输出 `text` 值的 SHA-256 哈希
+
+该选项默认值为 `false`,因此只有显式启用后才会改变原始 Markdown schema。
+
 注意:Markdown 格式仅支持读取,不支持写入。
 在此要求下,您需要确保源和接收器同时使用`binary`格式进行文件同步。
 
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/BaseFileSourceConfig.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/BaseFileSourceConfig.java
index 5b2223d605..604cf90db3 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/BaseFileSourceConfig.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/BaseFileSourceConfig.java
@@ -91,10 +91,8 @@ public abstract class BaseFileSourceConfig implements 
Serializable {
         if (CollectionUtils.isEmpty(filePaths)) {
             // When there are no files (including sync_mode=update filtered 
all files), choose a
             // compatible schema so that downstream can initialize correctly.
-            if (fileFormat == FileFormat.BINARY) {
-                String rootPath = 
readonlyConfig.get(FileBaseSourceOptions.FILE_PATH);
-                return newCatalogTable(
-                        catalogTable, 
readStrategy.getSeaTunnelRowTypeInfo(rootPath));
+            if (fileFormat == FileFormat.BINARY || fileFormat == 
FileFormat.MARKDOWN) {
+                return newCatalogTable(catalogTable, 
getSchemaForEmptyFilePath(readonlyConfig));
             }
             return catalogTable;
         }
@@ -114,6 +112,9 @@ public abstract class BaseFileSourceConfig implements 
Serializable {
                         
readStrategy.getSeaTunnelRowTypeInfoWithUserConfigRowType(
                                 filePaths.get(0),
                                 configSchema ? 
catalogTable.getSeaTunnelRowType() : null));
+            case MARKDOWN:
+                return newCatalogTable(
+                        catalogTable, 
readStrategy.getSeaTunnelRowTypeInfo(filePaths.get(0)));
             default:
                 throw new FileConnectorException(
                         FileConnectorErrorCode.FORMAT_NOT_SUPPORT,
@@ -121,6 +122,11 @@ public abstract class BaseFileSourceConfig implements 
Serializable {
         }
     }
 
+    private SeaTunnelRowType getSchemaForEmptyFilePath(ReadonlyConfig 
readonlyConfig) {
+        String rootPath = readonlyConfig.get(FileBaseSourceOptions.FILE_PATH);
+        return readStrategy.getSeaTunnelRowTypeInfo(rootPath);
+    }
+
     private CatalogTable newCatalogTable(
             CatalogTable catalogTable, SeaTunnelRowType seaTunnelRowType) {
         TableSchema tableSchema = catalogTable.getTableSchema();
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 c98d82b989..62590e5aa5 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
@@ -108,6 +108,14 @@ public class FileBaseSourceOptions extends FileBaseOptions 
{
                     .noDefaultValue()
                     .withDescription("The columns list that the user want to 
read");
 
+    public static final Option<Boolean> MARKDOWN_RAG_METADATA_ENABLED =
+            Options.key("markdown_rag_metadata_enabled")
+                    .booleanType()
+                    .defaultValue(false)
+                    .withDescription(
+                            "Whether to append RAG-oriented metadata columns 
when reading markdown files. "
+                                    + "Only valid when file_format_type is 
markdown.");
+
     public static final Option<ExcelEngine> EXCEL_ENGINE =
             Options.key("excel_engine")
                     .enumType(ExcelEngine.class)
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/BaseFileSource.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/BaseFileSource.java
index f482b36310..ecbacb8e98 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/BaseFileSource.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/BaseFileSource.java
@@ -106,9 +106,8 @@ public abstract class BaseFileSource
                             "SeaTunnel does not supported this file format");
             }
         } else {
-            if (filePaths.isEmpty() && fileFormat != FileFormat.BINARY) {
-                // When the directory is empty, distribute default behavior 
schema
-                userDefinedCatalogTable = 
CatalogTableUtil.buildSimpleTextTable();
+            if (filePaths.isEmpty()) {
+                userDefinedCatalogTable = buildCatalogTableForEmptyPath(path, 
fileFormat);
             } else {
                 try {
                     SeaTunnelRowType rowType =
@@ -126,6 +125,16 @@ public abstract class BaseFileSource
         this.catalogTable = userDefinedCatalogTable;
     }
 
+    private CatalogTable buildCatalogTableForEmptyPath(String path, FileFormat 
fileFormat) {
+        if (fileFormat != FileFormat.BINARY && fileFormat != 
FileFormat.MARKDOWN) {
+            // Preserve the legacy simple-text fallback for formats that still 
infer schema from
+            // the first concrete file.
+            return CatalogTableUtil.buildSimpleTextTable();
+        }
+        SeaTunnelRowType rowType = readStrategy.getSeaTunnelRowTypeInfo(path);
+        return CatalogTableUtil.getCatalogTable("default", rowType);
+    }
+
     protected abstract HadoopConf initHadoopConf();
 
     @Override
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/MarkdownReadStrategy.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/MarkdownReadStrategy.java
index 675fe5c69c..fcb1f5e95b 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/MarkdownReadStrategy.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/MarkdownReadStrategy.java
@@ -17,12 +17,18 @@
 
 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.type.BasicType;
+import org.apache.seatunnel.api.table.type.SeaTunnelDataType;
 import org.apache.seatunnel.api.table.type.SeaTunnelRow;
 import org.apache.seatunnel.api.table.type.SeaTunnelRowType;
+import 
org.apache.seatunnel.connectors.seatunnel.file.config.FileBaseSourceOptions;
 import 
org.apache.seatunnel.connectors.seatunnel.file.exception.FileConnectorException;
 
+import org.apache.commons.io.IOUtils;
+
 import com.vladsch.flexmark.ast.BlockQuote;
 import com.vladsch.flexmark.ast.BulletList;
 import com.vladsch.flexmark.ast.Code;
@@ -42,8 +48,12 @@ import com.vladsch.flexmark.util.ast.Node;
 import lombok.extern.slf4j.Slf4j;
 
 import java.io.IOException;
-import java.nio.file.Files;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Paths;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
@@ -55,6 +65,42 @@ public class MarkdownReadStrategy extends 
AbstractReadStrategy {
 
     private static final int DEFAULT_PAGE_NUMBER = 1;
     private static final int DEFAULT_POSITION = 1;
+    private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();
+    private static final String[] DEFAULT_FIELD_NAMES = {
+        "element_id",
+        "element_type",
+        "heading_level",
+        "text",
+        "page_number",
+        "position_index",
+        "parent_id",
+        "child_ids"
+    };
+    private static final SeaTunnelDataType[] DEFAULT_FIELD_TYPES = {
+        BasicType.STRING_TYPE,
+        BasicType.STRING_TYPE,
+        BasicType.INT_TYPE,
+        BasicType.STRING_TYPE,
+        BasicType.INT_TYPE,
+        BasicType.INT_TYPE,
+        BasicType.STRING_TYPE,
+        BasicType.STRING_TYPE
+    };
+    /** Stable metadata fields appended for downstream RAG/document indexing 
pipelines. */
+    private static final String[] RAG_METADATA_FIELD_NAMES = {
+        "source_uri", "document_id", "chunk_id", "chunk_index", "content_hash"
+    };
+
+    private static final SeaTunnelDataType[] RAG_METADATA_FIELD_TYPES = {
+        BasicType.STRING_TYPE,
+        BasicType.STRING_TYPE,
+        BasicType.STRING_TYPE,
+        BasicType.INT_TYPE,
+        BasicType.STRING_TYPE
+    };
+
+    private boolean markdownRagMetadataEnabled =
+            FileBaseSourceOptions.MARKDOWN_RAG_METADATA_ENABLED.defaultValue();
 
     private static class NodeInfo {
         String elementId;
@@ -72,16 +118,26 @@ public class MarkdownReadStrategy extends 
AbstractReadStrategy {
     @Override
     public void read(String path, String tableId, Collector<SeaTunnelRow> 
output)
             throws IOException, FileConnectorException {
-        String markdown = new String(Files.readAllBytes(Paths.get(path)));
+        String markdown;
+        try (InputStream inputStream = 
hadoopFileSystemProxy.getInputStream(path)) {
+            markdown = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
+        }
         Parser parser = Parser.builder().build();
         Node document = parser.parse(markdown);
+        String sourceUri = normalizeSourceUri(path);
 
         Map<Node, NodeInfo> nodeInfoMap = new IdentityHashMap<>();
         Map<String, Integer> typeCounters = new HashMap<>();
         List<SeaTunnelRow> rows = new ArrayList<>();
 
         assignIdsAndCollectTree(document, null, nodeInfoMap, DEFAULT_POSITION, 
typeCounters);
-        generateRows(document, rows, nodeInfoMap, DEFAULT_PAGE_NUMBER);
+        generateRows(
+                document,
+                rows,
+                nodeInfoMap,
+                DEFAULT_PAGE_NUMBER,
+                sourceUri,
+                buildDocumentId(sourceUri));
 
         for (SeaTunnelRow row : rows) {
             output.collect(row);
@@ -118,7 +174,12 @@ public class MarkdownReadStrategy extends 
AbstractReadStrategy {
     }
 
     private void generateRows(
-            Node node, List<SeaTunnelRow> rows, Map<Node, NodeInfo> 
nodeInfoMap, int pageNumber) {
+            Node node,
+            List<SeaTunnelRow> rows,
+            Map<Node, NodeInfo> nodeInfoMap,
+            int pageNumber,
+            String sourceUri,
+            String documentId) {
         if (isEligibleForRow(node)) {
             NodeInfo nodeInfo = nodeInfoMap.get(node);
             String elementType = node.getClass().getSimpleName();
@@ -129,20 +190,22 @@ public class MarkdownReadStrategy extends 
AbstractReadStrategy {
                 headingLevel = ((Heading) node).getLevel();
             }
 
-            rows.add(
-                    new SeaTunnelRow(
-                            new Object[] {
-                                nodeInfo.elementId,
-                                elementType,
-                                headingLevel,
-                                text,
-                                pageNumber,
-                                nodeInfo.positionIndex,
-                                nodeInfo.parentId,
-                                nodeInfo.childIds.isEmpty()
-                                        ? null
-                                        : String.join(",", nodeInfo.childIds)
-                            }));
+            Object[] fields =
+                    new Object[] {
+                        nodeInfo.elementId,
+                        elementType,
+                        headingLevel,
+                        text,
+                        pageNumber,
+                        nodeInfo.positionIndex,
+                        nodeInfo.parentId,
+                        nodeInfo.childIds.isEmpty() ? null : String.join(",", 
nodeInfo.childIds)
+                    };
+            if (markdownRagMetadataEnabled) {
+                fields = appendRagMetadata(fields, sourceUri, documentId, 
rows.size() + 1, text);
+            }
+
+            rows.add(new SeaTunnelRow(fields));
             log.debug(
                     "Added row: element_id={} type={} heading_level={} text={} 
parent_id={} child_ids={}",
                     nodeInfo.elementId,
@@ -154,7 +217,7 @@ public class MarkdownReadStrategy extends 
AbstractReadStrategy {
         }
 
         for (Node child = node.getFirstChild(); child != null; child = 
child.getNext()) {
-            generateRows(child, rows, nodeInfoMap, pageNumber);
+            generateRows(child, rows, nodeInfoMap, pageNumber, sourceUri, 
documentId);
         }
     }
 
@@ -254,26 +317,86 @@ public class MarkdownReadStrategy extends 
AbstractReadStrategy {
 
     @Override
     public SeaTunnelRowType getSeaTunnelRowTypeInfo(String path) throws 
FileConnectorException {
-        return new SeaTunnelRowType(
-                new String[] {
-                    "element_id",
-                    "element_type",
-                    "heading_level",
-                    "text",
-                    "page_number",
-                    "position_index",
-                    "parent_id",
-                    "child_ids"
-                },
-                new org.apache.seatunnel.api.table.type.SeaTunnelDataType[] {
-                    BasicType.STRING_TYPE,
-                    BasicType.STRING_TYPE,
-                    BasicType.INT_TYPE,
-                    BasicType.STRING_TYPE,
-                    BasicType.INT_TYPE,
-                    BasicType.INT_TYPE,
-                    BasicType.STRING_TYPE,
-                    BasicType.STRING_TYPE
-                });
+        if (markdownRagMetadataEnabled) {
+            return new SeaTunnelRowType(
+                    concat(DEFAULT_FIELD_NAMES, RAG_METADATA_FIELD_NAMES),
+                    concat(DEFAULT_FIELD_TYPES, RAG_METADATA_FIELD_TYPES));
+        }
+        return new SeaTunnelRowType(DEFAULT_FIELD_NAMES, DEFAULT_FIELD_TYPES);
+    }
+
+    @Override
+    public void setPluginConfig(Config pluginConfig) {
+        super.setPluginConfig(pluginConfig);
+        if 
(pluginConfig.hasPath(FileBaseSourceOptions.MARKDOWN_RAG_METADATA_ENABLED.key()))
 {
+            markdownRagMetadataEnabled =
+                    pluginConfig.getBoolean(
+                            
FileBaseSourceOptions.MARKDOWN_RAG_METADATA_ENABLED.key());
+        }
+    }
+
+    private Object[] appendRagMetadata(
+            Object[] fields, String sourceUri, String documentId, int 
chunkIndex, String text) {
+        String contentHash = sha256Hex(text == null ? "" : text);
+        // Keep chunk ids stable across re-reads of the same logical document 
while still changing
+        // when the chunk content changes.
+        String chunkId = "chunk_" + sha256Hex(documentId + ":" + chunkIndex + 
":" + contentHash);
+        Object[] enriched = new Object[fields.length + 
RAG_METADATA_FIELD_NAMES.length];
+        System.arraycopy(fields, 0, enriched, 0, fields.length);
+        enriched[fields.length] = sourceUri;
+        enriched[fields.length + 1] = documentId;
+        enriched[fields.length + 2] = chunkId;
+        enriched[fields.length + 3] = chunkIndex;
+        enriched[fields.length + 4] = contentHash;
+        return enriched;
+    }
+
+    private static String buildDocumentId(String sourceUri) {
+        // Document ids stay anchored to the normalized source location so 
every chunk from the same
+        // file shares one stable parent id.
+        return "doc_" + sha256Hex(sourceUri);
+    }
+
+    private static String normalizeSourceUri(String sourceUri) {
+        // Normalize local file URIs to the path form emitted by existing 
local-file reads so the
+        // metadata contract stays stable between "file:/..." and plain local 
paths.
+        if (!sourceUri.startsWith("file:")) {
+            return sourceUri;
+        }
+        try {
+            return Paths.get(URI.create(sourceUri)).toString();
+        } catch (IllegalArgumentException e) {
+            return sourceUri;
+        }
+    }
+
+    private static String sha256Hex(String value) {
+        try {
+            MessageDigest digest = MessageDigest.getInstance("SHA-256");
+            byte[] bytes = 
digest.digest(value.getBytes(StandardCharsets.UTF_8));
+            char[] chars = new char[bytes.length * 2];
+            for (int i = 0; i < bytes.length; i++) {
+                int unsigned = bytes[i] & 0xFF;
+                chars[i * 2] = HEX_CHARS[unsigned >>> 4];
+                chars[i * 2 + 1] = HEX_CHARS[unsigned & 0x0F];
+            }
+            return new String(chars);
+        } catch (NoSuchAlgorithmException e) {
+            throw new IllegalStateException("SHA-256 is not available", e);
+        }
+    }
+
+    private static String[] concat(String[] left, String[] right) {
+        String[] result = new String[left.length + right.length];
+        System.arraycopy(left, 0, result, 0, left.length);
+        System.arraycopy(right, 0, result, left.length, right.length);
+        return result;
+    }
+
+    private static SeaTunnelDataType[] concat(SeaTunnelDataType[] left, 
SeaTunnelDataType[] right) {
+        SeaTunnelDataType[] result = new SeaTunnelDataType[left.length + 
right.length];
+        System.arraycopy(left, 0, result, 0, left.length);
+        System.arraycopy(right, 0, result, left.length, right.length);
+        return result;
     }
 }
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/source/BaseFileSourceTest.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/source/BaseFileSourceTest.java
new file mode 100644
index 0000000000..e7ede52914
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/source/BaseFileSourceTest.java
@@ -0,0 +1,206 @@
+/*
+ * 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.seatunnel.connectors.seatunnel.file.source;
+
+import org.apache.seatunnel.api.configuration.ReadonlyConfig;
+import org.apache.seatunnel.api.table.catalog.CatalogTable;
+import 
org.apache.seatunnel.connectors.seatunnel.file.config.FileBaseSourceOptions;
+import org.apache.seatunnel.connectors.seatunnel.file.config.HadoopConf;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FSDataInputStream;
+import org.apache.hadoop.fs.FSDataOutputStream;
+import org.apache.hadoop.fs.FileStatus;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.permission.FsPermission;
+import org.apache.hadoop.util.Progressable;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+class BaseFileSourceTest {
+
+    private static final String EMPTY_FILE_SYSTEM_URI = "empty:///";
+    private static final String EMPTY_PATH = "empty:///empty-dir";
+    private static final String[] MARKDOWN_FIELD_NAMES = {
+        "element_id",
+        "element_type",
+        "heading_level",
+        "text",
+        "page_number",
+        "position_index",
+        "parent_id",
+        "child_ids"
+    };
+    private static final String[] MARKDOWN_RAG_METADATA_FIELD_NAMES = {
+        "source_uri", "document_id", "chunk_id", "chunk_index", "content_hash"
+    };
+
+    @Test
+    void testMarkdownSourceDiscoversSchemaFromEmptyDirectory() {
+        BaseFileSource source = new 
TestFileSource(createMarkdownConfig(false));
+
+        CatalogTable catalogTable = source.getProducedCatalogTables().get(0);
+
+        Assertions.assertArrayEquals(
+                MARKDOWN_FIELD_NAMES, 
catalogTable.getSeaTunnelRowType().getFieldNames());
+    }
+
+    @Test
+    void testMarkdownSourceDiscoversRagMetadataSchemaFromEmptyDirectory() {
+        BaseFileSource source = new TestFileSource(createMarkdownConfig(true));
+
+        CatalogTable catalogTable = source.getProducedCatalogTables().get(0);
+
+        Assertions.assertArrayEquals(
+                concat(MARKDOWN_FIELD_NAMES, 
MARKDOWN_RAG_METADATA_FIELD_NAMES),
+                catalogTable.getSeaTunnelRowType().getFieldNames());
+    }
+
+    private ReadonlyConfig createMarkdownConfig(boolean ragMetadataEnabled) {
+        Map<String, Object> map = new HashMap<>();
+        map.put(FileBaseSourceOptions.FILE_PATH.key(), EMPTY_PATH);
+        map.put(FileBaseSourceOptions.FILE_FORMAT_TYPE.key(), "markdown");
+        map.put(FileBaseSourceOptions.MARKDOWN_RAG_METADATA_ENABLED.key(), 
ragMetadataEnabled);
+        return ReadonlyConfig.fromMap(map);
+    }
+
+    private static String[] concat(String[] left, String[] right) {
+        String[] result = new String[left.length + right.length];
+        System.arraycopy(left, 0, result, 0, left.length);
+        System.arraycopy(right, 0, result, left.length, right.length);
+        return result;
+    }
+
+    private static class TestFileSource extends BaseFileSource {
+
+        private TestFileSource(ReadonlyConfig pluginConfig) {
+            super(pluginConfig);
+        }
+
+        @Override
+        protected HadoopConf initHadoopConf() {
+            return new EmptyConf(EMPTY_FILE_SYSTEM_URI);
+        }
+
+        @Override
+        public String getPluginName() {
+            return "TestFileSource";
+        }
+    }
+
+    private static class EmptyConf extends HadoopConf {
+
+        private static final String SCHEMA = "empty";
+
+        private EmptyConf(String hdfsNameKey) {
+            super(hdfsNameKey);
+        }
+
+        @Override
+        public String getFsHdfsImpl() {
+            return EmptyFileSystem.class.getName();
+        }
+
+        @Override
+        public String getSchema() {
+            return SCHEMA;
+        }
+    }
+
+    public static class EmptyFileSystem extends FileSystem {
+
+        private URI uri;
+        private Path workingDirectory;
+
+        @Override
+        public void initialize(URI name, Configuration conf) throws 
IOException {
+            super.initialize(name, conf);
+            this.uri = name;
+            this.workingDirectory = new Path("/");
+        }
+
+        @Override
+        public URI getUri() {
+            return uri;
+        }
+
+        @Override
+        public FSDataInputStream open(Path path, int bufferSize) {
+            throw new UnsupportedOperationException("open is not needed for 
this test");
+        }
+
+        @Override
+        public FSDataOutputStream create(
+                Path path,
+                FsPermission permission,
+                boolean overwrite,
+                int bufferSize,
+                short replication,
+                long blockSize,
+                Progressable progress) {
+            throw new UnsupportedOperationException("create is not needed for 
this test");
+        }
+
+        @Override
+        public FSDataOutputStream append(Path path, int bufferSize, 
Progressable progress) {
+            throw new UnsupportedOperationException("append is not needed for 
this test");
+        }
+
+        @Override
+        public boolean rename(Path source, Path target) {
+            throw new UnsupportedOperationException("rename is not needed for 
this test");
+        }
+
+        @Override
+        public boolean delete(Path path, boolean recursive) {
+            throw new UnsupportedOperationException("delete is not needed for 
this test");
+        }
+
+        @Override
+        public FileStatus[] listStatus(Path path) {
+            return new FileStatus[0];
+        }
+
+        @Override
+        public void setWorkingDirectory(Path newDirectory) {
+            this.workingDirectory = newDirectory;
+        }
+
+        @Override
+        public Path getWorkingDirectory() {
+            return workingDirectory;
+        }
+
+        @Override
+        public boolean mkdirs(Path path, FsPermission permission) {
+            return true;
+        }
+
+        @Override
+        public FileStatus getFileStatus(Path path) {
+            return new FileStatus(0, true, 1, 0, 0, path.makeQualified(uri, 
workingDirectory));
+        }
+    }
+}
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/MarkdownReadStrategyTest.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/MarkdownReadStrategyTest.java
index bdd7189cb7..9c37687be5 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/MarkdownReadStrategyTest.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/MarkdownReadStrategyTest.java
@@ -17,23 +17,55 @@
 
 package org.apache.seatunnel.connectors.seatunnel.file.source.reader;
 
+import org.apache.seatunnel.shade.com.typesafe.config.ConfigFactory;
+
+import org.apache.seatunnel.api.table.type.SeaTunnelRowType;
+import org.apache.seatunnel.connectors.seatunnel.file.config.HadoopConf;
+
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 
 import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Arrays;
+
+import static 
org.apache.hadoop.fs.CommonConfigurationKeysPublic.FS_DEFAULT_NAME_DEFAULT;
 
 class MarkdownReadStrategyTest {
 
+    private static final String[] DEFAULT_FIELD_NAMES = {
+        "element_id",
+        "element_type",
+        "heading_level",
+        "text",
+        "page_number",
+        "position_index",
+        "parent_id",
+        "child_ids"
+    };
+    private static final String[] RAG_FIELD_NAMES = {
+        "source_uri", "document_id", "chunk_id", "chunk_index", "content_hash"
+    };
+
+    @TempDir private Path tempDir;
+
     @Test
     public void testReadMarkdown() throws Exception {
         URL resource = this.getClass().getResource("/test.md");
         String path = Paths.get(resource.toURI()).toString();
-        AbstractReadStrategy markdownReadStrategy = new MarkdownReadStrategy();
+        AbstractReadStrategy markdownReadStrategy = 
createMarkdownReadStrategy();
+        SeaTunnelRowType rowType = 
markdownReadStrategy.getSeaTunnelRowTypeInfo(path);
         TempCollector tempCollector = new TempCollector();
         markdownReadStrategy.read(path, "", tempCollector);
 
+        Assertions.assertArrayEquals(DEFAULT_FIELD_NAMES, 
rowType.getFieldNames());
         Assertions.assertEquals(75, tempCollector.getRows().size());
+        Assertions.assertEquals(
+                DEFAULT_FIELD_NAMES.length, 
tempCollector.getRows().get(0).getArity());
 
         Assertions.assertEquals("Heading_1", 
tempCollector.getRows().get(0).getField(0));
         Assertions.assertEquals("Heading", 
tempCollector.getRows().get(0).getField(1));
@@ -77,4 +109,134 @@ class MarkdownReadStrategyTest {
         Assertions.assertEquals("OrderedList_1", 
tempCollector.getRows().get(4).getField(6));
         Assertions.assertNull(tempCollector.getRows().get(4).getField(7));
     }
+
+    @Test
+    public void testReadMarkdownWithFileUri() throws Exception {
+        Path markdownFile = tempDir.resolve("doc.md");
+        Files.write(markdownFile, Arrays.asList("# Title"), 
StandardCharsets.UTF_8);
+
+        AbstractReadStrategy markdownReadStrategy = 
createMarkdownReadStrategy();
+        TempCollector tempCollector = new TempCollector();
+        markdownReadStrategy.read(markdownFile.toUri().toString(), "", 
tempCollector);
+
+        Assertions.assertEquals(1, tempCollector.getRows().size());
+        Assertions.assertEquals("Title", 
tempCollector.getRows().get(0).getField(3));
+    }
+
+    @Test
+    public void testReadMarkdownWithRagMetadata() throws Exception {
+        URL resource = this.getClass().getResource("/test.md");
+        String path = Paths.get(resource.toURI()).toString();
+        AbstractReadStrategy markdownReadStrategy = 
createRagMetadataMarkdownReadStrategy();
+        SeaTunnelRowType rowType = 
markdownReadStrategy.getSeaTunnelRowTypeInfo(path);
+        TempCollector firstCollector = new TempCollector();
+        markdownReadStrategy.read(path, "", firstCollector);
+
+        Assertions.assertArrayEquals(
+                concat(DEFAULT_FIELD_NAMES, RAG_FIELD_NAMES), 
rowType.getFieldNames());
+        Assertions.assertEquals(75, firstCollector.getRows().size());
+        Assertions.assertEquals(13, 
firstCollector.getRows().get(0).getArity());
+        Assertions.assertEquals(path, 
firstCollector.getRows().get(0).getField(8));
+        Assertions.assertTrue(
+                
String.valueOf(firstCollector.getRows().get(0).getField(9)).startsWith("doc_"));
+        Assertions.assertTrue(
+                
String.valueOf(firstCollector.getRows().get(0).getField(10)).startsWith("chunk_"));
+        Assertions.assertEquals(1, 
firstCollector.getRows().get(0).getField(11));
+        Assertions.assertEquals(
+                64, 
String.valueOf(firstCollector.getRows().get(0).getField(12)).length());
+
+        AbstractReadStrategy secondReadStrategy = 
createRagMetadataMarkdownReadStrategy();
+        TempCollector secondCollector = new TempCollector();
+        secondReadStrategy.read(path, "", secondCollector);
+
+        for (int fieldIndex = 8; fieldIndex < 13; fieldIndex++) {
+            Assertions.assertEquals(
+                    firstCollector.getRows().get(0).getField(fieldIndex),
+                    secondCollector.getRows().get(0).getField(fieldIndex));
+        }
+    }
+
+    @Test
+    public void testReadMarkdownWithRagMetadataNormalizesFileUri() throws 
Exception {
+        Path markdownFile = tempDir.resolve("doc.md");
+        Files.write(markdownFile, Arrays.asList("# Title"), 
StandardCharsets.UTF_8);
+
+        AbstractReadStrategy markdownReadStrategy = 
createRagMetadataMarkdownReadStrategy();
+        TempCollector tempCollector = new TempCollector();
+        markdownReadStrategy.read(markdownFile.toUri().toString(), "", 
tempCollector);
+
+        AbstractReadStrategy expectedReadStrategy = 
createRagMetadataMarkdownReadStrategy();
+        TempCollector expectedCollector = new TempCollector();
+        expectedReadStrategy.read(markdownFile.toString(), "", 
expectedCollector);
+
+        Assertions.assertEquals(
+                markdownFile.toString(), 
tempCollector.getRows().get(0).getField(8));
+        for (int fieldIndex = 8; fieldIndex < 11; fieldIndex++) {
+            Assertions.assertEquals(
+                    expectedCollector.getRows().get(0).getField(fieldIndex),
+                    tempCollector.getRows().get(0).getField(fieldIndex));
+        }
+    }
+
+    @Test
+    public void testRagMetadataContentHashChangesWithText() throws Exception {
+        Path markdownFile = tempDir.resolve("doc.md");
+        Files.write(markdownFile, Arrays.asList("# First Title"), 
StandardCharsets.UTF_8);
+
+        AbstractReadStrategy firstReadStrategy = 
createRagMetadataMarkdownReadStrategy();
+        TempCollector firstCollector = new TempCollector();
+        firstReadStrategy.read(markdownFile.toString(), "", firstCollector);
+        Object firstDocumentId = firstCollector.getRows().get(0).getField(9);
+        Object firstChunkIndex = firstCollector.getRows().get(0).getField(11);
+        Object firstContentHash = firstCollector.getRows().get(0).getField(12);
+
+        Files.write(markdownFile, Arrays.asList("# Second Title"), 
StandardCharsets.UTF_8);
+
+        AbstractReadStrategy secondReadStrategy = 
createRagMetadataMarkdownReadStrategy();
+        TempCollector secondCollector = new TempCollector();
+        secondReadStrategy.read(markdownFile.toString(), "", secondCollector);
+
+        Assertions.assertEquals(firstDocumentId, 
secondCollector.getRows().get(0).getField(9));
+        Assertions.assertEquals(firstChunkIndex, 
secondCollector.getRows().get(0).getField(11));
+        Assertions.assertNotEquals(firstContentHash, 
secondCollector.getRows().get(0).getField(12));
+    }
+
+    private static AbstractReadStrategy createMarkdownReadStrategy() {
+        AbstractReadStrategy markdownReadStrategy = new MarkdownReadStrategy();
+        markdownReadStrategy.init(new LocalConf(FS_DEFAULT_NAME_DEFAULT));
+        return markdownReadStrategy;
+    }
+
+    private static AbstractReadStrategy 
createRagMetadataMarkdownReadStrategy() {
+        AbstractReadStrategy markdownReadStrategy = 
createMarkdownReadStrategy();
+        markdownReadStrategy.setPluginConfig(
+                ConfigFactory.parseString("markdown_rag_metadata_enabled = 
true"));
+        return markdownReadStrategy;
+    }
+
+    private static String[] concat(String[] left, String[] right) {
+        String[] result = new String[left.length + right.length];
+        System.arraycopy(left, 0, result, 0, left.length);
+        System.arraycopy(right, 0, result, left.length, right.length);
+        return result;
+    }
+
+    public static class LocalConf extends HadoopConf {
+        private static final String HDFS_IMPL = 
"org.apache.hadoop.fs.LocalFileSystem";
+        private static final String SCHEMA = "file";
+
+        public LocalConf(String hdfsNameKey) {
+            super(hdfsNameKey);
+        }
+
+        @Override
+        public String getFsHdfsImpl() {
+            return HDFS_IMPL;
+        }
+
+        @Override
+        public String getSchema() {
+            return SCHEMA;
+        }
+    }
 }
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 3d069ae954..68704b920e 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
@@ -95,6 +95,10 @@ public class CosFileSourceFactory implements 
TableSourceFactory {
                 .optional(FileBaseSourceOptions.NULL_FORMAT)
                 .optional(FileBaseSourceOptions.FILENAME_EXTENSION)
                 .optional(FileBaseSourceOptions.READ_COLUMNS)
+                .conditional(
+                        FileBaseSourceOptions.FILE_FORMAT_TYPE,
+                        FileFormat.MARKDOWN,
+                        FileBaseSourceOptions.MARKDOWN_RAG_METADATA_ENABLED)
                 .optional(FileBaseSourceOptions.QUOTE_CHAR)
                 .optional(FileBaseSourceOptions.ESCAPE_CHAR)
                 .build();
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 2ad2088e95..79499cbf47 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
@@ -100,6 +100,10 @@ public class FtpFileSourceFactory implements 
TableSourceFactory {
                 .optional(FileBaseSourceOptions.NULL_FORMAT)
                 .optional(FileBaseSourceOptions.FILENAME_EXTENSION)
                 .optional(FileBaseSourceOptions.READ_COLUMNS)
+                .conditional(
+                        FileBaseSourceOptions.FILE_FORMAT_TYPE,
+                        FileFormat.MARKDOWN,
+                        FileBaseSourceOptions.MARKDOWN_RAG_METADATA_ENABLED)
                 .optional(FtpFileSourceOptions.FTP_REMOTE_VERIFICATION_ENABLED)
                 .optional(FtpFileSourceOptions.FTP_CONTROL_ENCODING)
                 .optional(FileBaseSourceOptions.QUOTE_CHAR)
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 a08fa2c790..de9a088b7e 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
@@ -108,6 +108,10 @@ public class HdfsFileSourceFactory implements 
TableSourceFactory {
                 .optional(HdfsFileSourceOptions.NULL_FORMAT)
                 .optional(HdfsFileSourceOptions.FILENAME_EXTENSION)
                 .optional(HdfsFileSourceOptions.READ_COLUMNS)
+                .conditional(
+                        HdfsFileSourceOptions.FILE_FORMAT_TYPE,
+                        FileFormat.MARKDOWN,
+                        HdfsFileSourceOptions.MARKDOWN_RAG_METADATA_ENABLED)
                 .optional(
                         HdfsFileSourceOptions.DISCOVERY_MODE,
                         HdfsFileSourceOptions.SCAN_INTERVAL,
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 ecbcd908c6..d07ab8fffe 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
@@ -90,6 +90,10 @@ public class OssFileSourceFactory implements 
TableSourceFactory {
                 .optional(FileBaseSourceOptions.NULL_FORMAT)
                 .optional(FileBaseSourceOptions.FILENAME_EXTENSION)
                 .optional(FileBaseSourceOptions.READ_COLUMNS)
+                .conditional(
+                        FileBaseSourceOptions.FILE_FORMAT_TYPE,
+                        FileFormat.MARKDOWN,
+                        FileBaseSourceOptions.MARKDOWN_RAG_METADATA_ENABLED)
                 .optional(FileBaseSourceOptions.QUOTE_CHAR)
                 .optional(FileBaseSourceOptions.ESCAPE_CHAR)
                 .build();
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 972b569f95..5cd72b6513 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
@@ -109,6 +109,10 @@ public class LocalFileSourceFactory implements 
TableSourceFactory {
                 .optional(FileBaseSourceOptions.NULL_FORMAT)
                 .optional(FileBaseSourceOptions.FILENAME_EXTENSION)
                 .optional(FileBaseSourceOptions.READ_COLUMNS)
+                .conditional(
+                        FileBaseSourceOptions.FILE_FORMAT_TYPE,
+                        FileFormat.MARKDOWN,
+                        FileBaseSourceOptions.MARKDOWN_RAG_METADATA_ENABLED)
                 .optional(FileBaseSourceOptions.QUOTE_CHAR)
                 .optional(FileBaseSourceOptions.ESCAPE_CHAR)
                 .optional(ConnectorCommonOptions.METALAKE_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 5c3a97968c..1fbbe1a12b 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
@@ -49,6 +49,16 @@ class LocalFileFactoryTest {
                 
optionRule.getOptionalOptions().contains(FileBaseSourceOptions.SCAN_INTERVAL));
         Assertions.assertTrue(
                 
optionRule.getOptionalOptions().contains(FileBaseSourceOptions.START_MODE));
+        Assertions.assertTrue(
+                optionRule.getRequiredOptions().stream()
+                        
.filter(RequiredOption.ConditionalRequiredOptions.class::isInstance)
+                        
.map(RequiredOption.ConditionalRequiredOptions.class::cast)
+                        .anyMatch(
+                                required ->
+                                        required.getOptions()
+                                                .contains(
+                                                        FileBaseSourceOptions
+                                                                
.MARKDOWN_RAG_METADATA_ENABLED)));
 
         Expression expectExpression =
                 Expression.of(FileBaseSourceOptions.SYNC_MODE, 
FileSyncMode.UPDATE);
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-local/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/local/LocalFileSourceTest.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-local/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/local/LocalFileSourceTest.java
index a64af3b81a..2a2629638e 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-local/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/local/LocalFileSourceTest.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-local/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/local/LocalFileSourceTest.java
@@ -17,11 +17,14 @@
 package org.apache.seatunnel.connectors.seatunnel.file.local;
 
 import org.apache.seatunnel.api.configuration.ReadonlyConfig;
+import org.apache.seatunnel.api.table.catalog.CatalogTable;
+import org.apache.seatunnel.api.table.catalog.CatalogTableUtil;
 import 
org.apache.seatunnel.connectors.seatunnel.file.config.ArchiveCompressFormat;
 import org.apache.seatunnel.connectors.seatunnel.file.config.CompressFormat;
 import 
org.apache.seatunnel.connectors.seatunnel.file.config.FileBaseSourceOptions;
 import org.apache.seatunnel.connectors.seatunnel.file.config.FileFormat;
 import 
org.apache.seatunnel.connectors.seatunnel.file.local.config.LocalFileHadoopConf;
+import 
org.apache.seatunnel.connectors.seatunnel.file.local.source.config.LocalFileSourceConfig;
 import 
org.apache.seatunnel.connectors.seatunnel.file.source.split.AccordingToSplitSizeSplitStrategy;
 import 
org.apache.seatunnel.connectors.seatunnel.file.source.split.DefaultFileSplitStrategy;
 import 
org.apache.seatunnel.connectors.seatunnel.file.source.split.FileSplitStrategy;
@@ -30,13 +33,35 @@ import 
org.apache.seatunnel.connectors.seatunnel.file.source.split.ParquetFileSp
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 
 import java.io.Closeable;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
 public class LocalFileSourceTest {
 
+    private static final String[] MARKDOWN_FIELD_NAMES = {
+        "element_id",
+        "element_type",
+        "heading_level",
+        "text",
+        "page_number",
+        "position_index",
+        "parent_id",
+        "child_ids"
+    };
+    private static final String[] MARKDOWN_RAG_METADATA_FIELD_NAMES = {
+        "source_uri", "document_id", "chunk_id", "chunk_index", "content_hash"
+    };
+
+    @TempDir private Path tempDir;
+
     @Test
     void testInitFileSplitStrategy() {
         // test orc
@@ -116,6 +141,63 @@ public class LocalFileSourceTest {
         closeQuietly(fileSplitStrategy);
     }
 
+    @Test
+    void testMarkdownSourceDiscoversRagMetadataSchema() throws IOException {
+        Path markdownFile = tempDir.resolve("doc.md");
+        Files.write(markdownFile, Collections.singletonList("# Title"), 
StandardCharsets.UTF_8);
+
+        Map<String, Object> map = new HashMap<>();
+        map.put(FileBaseSourceOptions.FILE_PATH.key(), 
markdownFile.toUri().toString());
+        map.put(FileBaseSourceOptions.FILE_FORMAT_TYPE.key(), "markdown");
+        map.put(FileBaseSourceOptions.MARKDOWN_RAG_METADATA_ENABLED.key(), 
true);
+
+        CatalogTable catalogTable =
+                new LocalFileSourceConfig(
+                                ReadonlyConfig.fromMap(map),
+                                CatalogTableUtil.buildSimpleTextTable())
+                        .getCatalogTable();
+
+        Assertions.assertArrayEquals(
+                concat(MARKDOWN_FIELD_NAMES, 
MARKDOWN_RAG_METADATA_FIELD_NAMES),
+                catalogTable.getSeaTunnelRowType().getFieldNames());
+    }
+
+    @Test
+    void testContinuousMarkdownSourceDiscoversSchemaWithoutFiles() {
+        Map<String, Object> map = new HashMap<>();
+        map.put(FileBaseSourceOptions.FILE_PATH.key(), tempDir.toString());
+        map.put(FileBaseSourceOptions.FILE_FORMAT_TYPE.key(), "markdown");
+        map.put(FileBaseSourceOptions.DISCOVERY_MODE.key(), "continuous");
+
+        CatalogTable catalogTable =
+                new LocalFileSourceConfig(
+                                ReadonlyConfig.fromMap(map),
+                                CatalogTableUtil.buildSimpleTextTable())
+                        .getCatalogTable();
+
+        Assertions.assertArrayEquals(
+                MARKDOWN_FIELD_NAMES, 
catalogTable.getSeaTunnelRowType().getFieldNames());
+    }
+
+    @Test
+    void testContinuousMarkdownSourceDiscoversRagMetadataSchemaWithoutFiles() {
+        Map<String, Object> map = new HashMap<>();
+        map.put(FileBaseSourceOptions.FILE_PATH.key(), tempDir.toString());
+        map.put(FileBaseSourceOptions.FILE_FORMAT_TYPE.key(), "markdown");
+        map.put(FileBaseSourceOptions.DISCOVERY_MODE.key(), "continuous");
+        map.put(FileBaseSourceOptions.MARKDOWN_RAG_METADATA_ENABLED.key(), 
true);
+
+        CatalogTable catalogTable =
+                new LocalFileSourceConfig(
+                                ReadonlyConfig.fromMap(map),
+                                CatalogTableUtil.buildSimpleTextTable())
+                        .getCatalogTable();
+
+        Assertions.assertArrayEquals(
+                concat(MARKDOWN_FIELD_NAMES, 
MARKDOWN_RAG_METADATA_FIELD_NAMES),
+                catalogTable.getSeaTunnelRowType().getFieldNames());
+    }
+
     private void closeQuietly(FileSplitStrategy strategy) {
         try {
             if (strategy instanceof Closeable) {
@@ -129,4 +211,11 @@ public class LocalFileSourceTest {
             // ignore
         }
     }
+
+    private static String[] concat(String[] left, String[] right) {
+        String[] result = new String[left.length + right.length];
+        System.arraycopy(left, 0, result, 0, left.length);
+        System.arraycopy(right, 0, result, left.length, right.length);
+        return result;
+    }
 }
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 98897d2577..1f97737952 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
@@ -79,6 +79,10 @@ public class ObsFileSourceFactory implements 
TableSourceFactory {
                 .optional(FileBaseSourceOptions.NULL_FORMAT)
                 .optional(FileBaseSourceOptions.FILENAME_EXTENSION)
                 .optional(FileBaseSourceOptions.READ_COLUMNS)
+                .conditional(
+                        FileBaseSourceOptions.FILE_FORMAT_TYPE,
+                        FileFormat.MARKDOWN,
+                        FileBaseSourceOptions.MARKDOWN_RAG_METADATA_ENABLED)
                 .optional(FileBaseSourceOptions.QUOTE_CHAR)
                 .optional(FileBaseSourceOptions.ESCAPE_CHAR)
                 .build();
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 bf6e6701a5..b620f06289 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
@@ -99,6 +99,10 @@ public class OssFileSourceFactory implements 
TableSourceFactory {
                 .optional(FileBaseSourceOptions.NULL_FORMAT)
                 .optional(FileBaseSourceOptions.FILENAME_EXTENSION)
                 .optional(FileBaseSourceOptions.READ_COLUMNS)
+                .conditional(
+                        FileBaseSourceOptions.FILE_FORMAT_TYPE,
+                        FileFormat.MARKDOWN,
+                        FileBaseSourceOptions.MARKDOWN_RAG_METADATA_ENABLED)
                 .optional(FileBaseSourceOptions.QUOTE_CHAR)
                 .optional(FileBaseSourceOptions.ESCAPE_CHAR)
                 .optional(ConnectorCommonOptions.METALAKE_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 303ec74d39..f982fcce4a 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
@@ -115,6 +115,10 @@ public class S3FileSourceFactory implements 
TableSourceFactory {
                 .optional(FileBaseSourceOptions.NULL_FORMAT)
                 .optional(FileBaseSourceOptions.FILENAME_EXTENSION)
                 .optional(FileBaseSourceOptions.READ_COLUMNS)
+                .conditional(
+                        FileBaseSourceOptions.FILE_FORMAT_TYPE,
+                        FileFormat.MARKDOWN,
+                        FileBaseSourceOptions.MARKDOWN_RAG_METADATA_ENABLED)
                 .optional(FileBaseSourceOptions.QUOTE_CHAR)
                 .optional(FileBaseSourceOptions.ESCAPE_CHAR)
                 .optional(ConnectorCommonOptions.METALAKE_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 44d7782d99..16e22ae664 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
@@ -89,6 +89,10 @@ public class SftpFileSourceFactory implements 
TableSourceFactory {
                 .optional(FileBaseSourceOptions.NULL_FORMAT)
                 .optional(FileBaseSourceOptions.FILENAME_EXTENSION)
                 .optional(FileBaseSourceOptions.READ_COLUMNS)
+                .conditional(
+                        FileBaseSourceOptions.FILE_FORMAT_TYPE,
+                        FileFormat.MARKDOWN,
+                        FileBaseSourceOptions.MARKDOWN_RAG_METADATA_ENABLED)
                 .optional(FileBaseSourceOptions.QUOTE_CHAR)
                 .optional(FileBaseSourceOptions.ESCAPE_CHAR)
                 .optional(ConnectorCommonOptions.METALAKE_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 dc08fa7782..c0cc2d77c2 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
@@ -319,6 +319,11 @@ public class LocalFileIT extends TestSuiteBase {
                         "/seatunnel/read/e2e_null_format/e2e_null_format.txt",
                         container);
 
+                container.execInContainer(
+                        "sh",
+                        "-c",
+                        "mkdir -p /seatunnel/read/markdown && printf '# E2E 
Markdown RAG\\n' > /seatunnel/read/markdown/e2e.md");
+
                 container.execInContainer("mkdir", "-p", "/tmp/fake_empty");
             };
 
@@ -427,6 +432,13 @@ public class LocalFileIT extends TestSuiteBase {
         helper.execute("/excel/local_excel_xlsx_gz_to_assert.conf");
     }
 
+    @TestTemplate
+    public void testLocalFileReadMarkdownRagMetadata(TestContainer container)
+            throws IOException, InterruptedException {
+        TestHelper helper = new TestHelper(container);
+        
helper.execute("/markdown/local_file_markdown_rag_metadata_to_assert.conf");
+    }
+
     @TestTemplate
     @DisabledOnContainer(
             value = {},
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/resources/markdown/local_file_markdown_rag_metadata_to_assert.conf
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/resources/markdown/local_file_markdown_rag_metadata_to_assert.conf
new file mode 100644
index 0000000000..ef0a6d543c
--- /dev/null
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/resources/markdown/local_file_markdown_rag_metadata_to_assert.conf
@@ -0,0 +1,143 @@
+#
+# 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"
+  spark.app.name = "SeaTunnel"
+  spark.executor.instances = 2
+  spark.executor.cores = 1
+  spark.executor.memory = "1g"
+  spark.master = local
+}
+
+source {
+  LocalFile {
+    path = "/seatunnel/read/markdown/e2e.md"
+    file_format_type = "markdown"
+    markdown_rag_metadata_enabled = true
+  }
+}
+
+sink {
+  Assert {
+    rules {
+      row_rules = [
+        {
+          rule_type = MAX_ROW
+          rule_value = 1
+        },
+        {
+          rule_type = MIN_ROW
+          rule_value = 1
+        }
+      ],
+      field_rules = [
+        {
+          field_name = element_id
+          field_type = string
+          field_value = [
+            {
+              equals_to = "Heading_1"
+            }
+          ]
+        },
+        {
+          field_name = element_type
+          field_type = string
+          field_value = [
+            {
+              equals_to = "Heading"
+            }
+          ]
+        },
+        {
+          field_name = heading_level
+          field_type = int
+          field_value = [
+            {
+              equals_to = 1
+            }
+          ]
+        },
+        {
+          field_name = text
+          field_type = string
+          field_value = [
+            {
+              equals_to = "E2E Markdown RAG"
+            }
+          ]
+        },
+        {
+          field_name = parent_id
+          field_type = string
+          field_value = [
+            {
+              rule_type = NULL
+            }
+          ]
+        },
+        {
+          field_name = source_uri
+          field_type = string
+          field_value = [
+            {
+              equals_to = "/seatunnel/read/markdown/e2e.md"
+            }
+          ]
+        },
+        {
+          field_name = document_id
+          field_type = string
+          field_value = [
+            {
+              equals_to = 
"doc_f9e9a2324852c0fdff7ecea9013e95f3a2361f570b0d5226f2f671ae38d1d1c8"
+            }
+          ]
+        },
+        {
+          field_name = chunk_id
+          field_type = string
+          field_value = [
+            {
+              equals_to = 
"chunk_b48c799b9daccdb6a04d665be5892bc9efc84795f266f7ec27c5b4fd2f0a5af0"
+            }
+          ]
+        },
+        {
+          field_name = chunk_index
+          field_type = int
+          field_value = [
+            {
+              equals_to = 1
+            }
+          ]
+        },
+        {
+          field_name = content_hash
+          field_type = string
+          field_value = [
+            {
+              equals_to = 
"6fc6a22e9e5515086f32ea5400fcd490c92cbe0f3707febffb8f97b83abe2f46"
+            }
+          ]
+        }
+      ]
+    }
+  }
+}

Reply via email to