morrySnow commented on code in PR #3997:
URL: https://github.com/apache/doris-website/pull/3997#discussion_r3602591894


##########
docs/sql-manual/sql-statements/data-governance/data-lineage.md:
##########
@@ -0,0 +1,336 @@
+---
+{
+    "title": "Data Lineage Technical Guide",
+    "sidebar_label": "Data Lineage",
+    "language": "en",
+    "description": "Collect table-level and column-level lineage from 
supported Doris DML statements through an external LineagePlugin.",
+    "keywords": ["Apache Doris", "data lineage", "data governance", 
"LineagePlugin", "column-level lineage"]
+}
+---
+
+<!-- Knowledge type: Feature overview + technical design + user guide -->
+<!-- Use cases: Data governance integration / impact analysis / compliance 
traceability -->
+
+Data Lineage extracts table-level and column-level dependencies from supported 
Doris DML statements and sends them to external governance systems through a 
`LineagePlugin`. Doris is the lineage producer. It does not retain events, 
expose a lineage query API, or provide lineage visualization.
+
+> This capability is available in the Dev version. It was first released in 
Apache Doris 4.0.6.
+
+## Capabilities and limitations
+
+The framework generates an event only after one of the following statements 
succeeds:
+
+| Supported statement | Event behavior |
+| --- | --- |
+| `INSERT INTO ... SELECT` | Generates one event after the Insert succeeds. |
+| `INSERT OVERWRITE ... SELECT` | Generates one event after the Overwrite 
succeeds. |
+| `CREATE TABLE AS SELECT` | Generates one event after the internal Insert 
succeeds. |
+
+The following operations do not generate an event: `SELECT`, `UPDATE`, 
`DELETE`, load jobs, `VALUES`-only inserts, and writes whose target is 
`__internal_schema`.
+
+:::caution Delivery guarantee
+

Review Comment:
   The SQL syntax used throughout this document (`INSERT OVERWRITE ... SELECT`) 
drops the `TABLE` keyword. The standard Doris SQL syntax is `INSERT OVERWRITE 
TABLE table_name ... SELECT ...`. The command class in the code is 
`InsertOverwriteTableCommand`. While this may be intended as shorthand in 
prose, the supported-statements table should use the precise syntax. The SVG 
diagrams (`lineage-architecture.svg`, `lineage-event-model.svg`) also use 
`INSERT OVERWRITE SELECT` without `TABLE`.



##########
community/developer-guide/data-lineage-plugin-development.md:
##########
@@ -0,0 +1,604 @@
+---
+title: Data Lineage Plugin Development
+language: en
+description: Build, package, deploy, and verify an Apache Doris data lineage 
plugin that logs lineage events or forwards them to an external governance 
system.
+keywords:
+    - Apache Doris
+    - data lineage
+    - LineagePlugin
+    - LineagePluginFactory
+    - ServiceLoader
+    - plugin development
+---
+
+<!--
+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.
+-->
+
+<!-- Knowledge type: Extension development -->
+<!-- Applicable scenario: Data governance integration / Kernel extension 
development -->
+
+# Data Lineage Plugin Development
+
+This document describes how to develop an external data lineage plugin for 
Apache Doris. The plugin receives a `LineageInfo` event after a supported DML 
statement succeeds, then converts the event and writes it to logs, a messaging 
system, or an external metadata governance platform.
+
+> Applicable version: Apache Doris community edition 4.0.6 and later. Compile 
the plugin against the exact Doris community release or source revision 
deployed on the FE.
+
+## Scope and lifecycle
+
+The lineage framework is an FE-side SPI. It does not store lineage data, 
provide a query API, or render a lineage graph. A plugin converts the in-memory 
Java objects in `LineageInfo` to the format and transport protocol required by 
its downstream system.
+
+The framework generates events only for successful `INSERT INTO ... SELECT`, 
`INSERT OVERWRITE ... SELECT`, and `CREATE TABLE AS SELECT` statements. 
`VALUES`-only writes and writes whose target is `__internal_schema` are 
skipped. `SELECT`, `UPDATE`, `DELETE`, and load jobs are outside the scope of 
this framework.
+
+![Data lineage collection architecture: successful supported DML statements 
are analyzed by Nereids, extracted into LineageInfo, queued in FE, and 
delivered by a plugin to an external governance 
system.](/images/data-lineage/lineage-architecture.svg)
+
+`eventFilter()` is called once before lineage extraction on the DML query path 
and again before worker dispatch. Return `false` when the plugin should not 
receive events. The method is called concurrently from query threads and the 
worker thread, so it must be thread-safe. `exec()` is called by one worker 
thread, but can run concurrently with `eventFilter()`.
+
+At FE startup, ServiceLoader first discovers each `LineagePluginFactory`. FE 
then calls the Factory's `create(context)` method and calls 
`initialize(context)` on the returned plugin. The default 
`LineagePluginFactory.create(context)` implementation delegates to the 
no-argument `create()`, so the minimal example in this guide implements only 
`create()`. `PluginContext` contains `plugin.name` and `plugin.path`. A 
production plugin that reads a configuration file from its plugin directory can 
override `initialize(context)`, locate the file through `plugin.path`, and 
initialize its resources there.
+
+## Lineage event model
+
+`LineageInfo` contains a target table, target output columns, source tables, 
direct lineage, indirect lineage, and a `LineageContext`.
+
+| Part | API | Meaning |

Review Comment:
   The phrase "`PluginContext` contains `plugin.name` and `plugin.path`" is 
imprecise. Looking at the `PluginContext` class 
(`fe-extension-spi/.../PluginContext.java`), it only exposes `getProperties()` 
which returns a `Map<String, String>`. The properties `"plugin.name"` and 
`"plugin.path"` are stored as map entries (set in `discoverPlugins()` via 
`props.put("plugin.path", ...)` and `props.put("plugin.name", ...)`). A 
developer reading this would look for `context.getPluginName()` or 
`context.pluginName` which don't exist. Please clarify: accessed via 
`context.getProperties().get("plugin.name")` and 
`context.getProperties().get("plugin.path")`. Same issue exists in the Chinese 
version at the corresponding position.



##########
docs/sql-manual/sql-statements/data-governance/data-lineage.md:
##########
@@ -0,0 +1,336 @@
+---
+{
+    "title": "Data Lineage Technical Guide",
+    "sidebar_label": "Data Lineage",
+    "language": "en",
+    "description": "Collect table-level and column-level lineage from 
supported Doris DML statements through an external LineagePlugin.",
+    "keywords": ["Apache Doris", "data lineage", "data governance", 
"LineagePlugin", "column-level lineage"]
+}
+---
+
+<!-- Knowledge type: Feature overview + technical design + user guide -->
+<!-- Use cases: Data governance integration / impact analysis / compliance 
traceability -->
+
+Data Lineage extracts table-level and column-level dependencies from supported 
Doris DML statements and sends them to external governance systems through a 
`LineagePlugin`. Doris is the lineage producer. It does not retain events, 
expose a lineage query API, or provide lineage visualization.
+
+> This capability is available in the Dev version. It was first released in 
Apache Doris 4.0.6.
+
+## Capabilities and limitations
+
+The framework generates an event only after one of the following statements 
succeeds:
+
+| Supported statement | Event behavior |
+| --- | --- |
+| `INSERT INTO ... SELECT` | Generates one event after the Insert succeeds. |
+| `INSERT OVERWRITE ... SELECT` | Generates one event after the Overwrite 
succeeds. |
+| `CREATE TABLE AS SELECT` | Generates one event after the internal Insert 
succeeds. |
+
+The following operations do not generate an event: `SELECT`, `UPDATE`, 
`DELETE`, load jobs, `VALUES`-only inserts, and writes whose target is 
`__internal_schema`.
+
+:::caution Delivery guarantee
+
+Lineage delivery is asynchronous and best effort. A successful DML statement 
is not rolled back when event collection or plugin delivery fails. Events can 
be discarded when the queue is full, and Doris does not retry or persist them.
+
+:::
+
+## Technical design
+
+### Collection flow
+
+For a supported statement, Doris records the analyzed Nereids logical plan in 
an `afterAnalyze` planner hook. After the DML succeeds, Doris extracts lineage 
from that plan and submits the event to an FE-local queue. A single daemon 
worker dispatches the event to every active plugin.
+
+![Data lineage collection architecture: successful supported DML statements 
are analyzed by Nereids, extracted into LineageInfo, queued in FE, and 
delivered by a plugin to an external governance 
system.](/images/data-lineage/lineage-architecture.svg)
+
+Before extraction, Doris calls `eventFilter()` on the loaded plugins. If no 
plugin is willing to receive events, extraction is skipped. The worker 
evaluates `eventFilter()` again before dispatch.
+
+### Lineage content
+
+Each `LineageInfo` event contains table lineage, direct column lineage, two 
kinds of indirect lineage, and query context. The complete SQL example below 
establishes the source tables, target table, and result used by the following 
sections.
+
+#### Example SQL and result
+
+The current user must have privileges to create a database and tables, write 
data, and query data, and the FE must have loaded a lineage plugin. The example 
uses two source tables and creates a customer summary through a CTE, Join, 
filters, aggregation, a window function, a conditional expression, and sorting.
+
+```sql
+CREATE DATABASE IF NOT EXISTS lineage_demo;
+USE lineage_demo;
+
+CREATE TABLE lineage_orders (
+    order_id BIGINT,
+    customer_id BIGINT,
+    region VARCHAR(16),
+    amount DECIMAL(18, 2),
+    status VARCHAR(16)
+)
+DUPLICATE KEY(order_id)
+DISTRIBUTED BY HASH(order_id) BUCKETS 1
+PROPERTIES ("replication_num" = "1");
+
+CREATE TABLE lineage_customers (
+    customer_id BIGINT,
+    customer_name VARCHAR(64),
+    customer_level VARCHAR(16)
+)
+DUPLICATE KEY(customer_id)
+DISTRIBUTED BY HASH(customer_id) BUCKETS 1
+PROPERTIES ("replication_num" = "1");
+
+CREATE TABLE lineage_customer_summary (
+    customer_id BIGINT,
+    region_label VARCHAR(16),
+    total_amount DECIMAL(18, 2),
+    customer_seq BIGINT,
+    region_group VARCHAR(16)
+)
+DUPLICATE KEY(customer_id)
+DISTRIBUTED BY HASH(customer_id) BUCKETS 1
+PROPERTIES ("replication_num" = "1");
+
+INSERT INTO lineage_customers VALUES
+    (1, 'Alice', 'VIP'),
+    (2, 'Bob', 'VIP'),
+    (3, 'Carol', 'REGULAR');
+
+INSERT INTO lineage_orders VALUES
+    (101, 1, 'east', 100.00, 'PAID'),
+    (102, 1, 'east',  50.00, 'PAID'),
+    (103, 2, 'west',  80.00, 'PAID'),
+    (104, 2, 'west',  30.00, 'CANCELLED'),
+    (105, 3, 'north', 200.00, 'PAID');
+
+INSERT INTO lineage_customer_summary
+WITH customer_totals AS (
+    SELECT
+        o.customer_id,
+        UPPER(o.region) AS region_label,
+        SUM(o.amount) AS total_amount
+    FROM lineage_orders o
+    JOIN lineage_customers c
+        ON o.customer_id = c.customer_id
+    WHERE o.status = 'PAID'
+      AND c.customer_level = 'VIP'
+    GROUP BY o.customer_id, UPPER(o.region)
+    HAVING SUM(o.amount) >= 50
+)
+SELECT
+    customer_id,
+    region_label,
+    total_amount,
+    ROW_NUMBER() OVER (ORDER BY customer_id) AS customer_seq,
+    CASE
+        WHEN region_label = 'EAST' THEN 'CORE'
+        ELSE 'OTHER'
+    END AS region_group
+FROM customer_totals
+ORDER BY total_amount DESC;
+
+SELECT customer_id, region_label, total_amount, customer_seq, region_group
+FROM lineage_customer_summary
+ORDER BY customer_id;
+```
+
+The query returns:
+
+```text
++-------------+--------------+--------------+--------------+--------------+
+| customer_id | region_label | total_amount | customer_seq | region_group |
++-------------+--------------+--------------+--------------+--------------+
+|           1 | EAST         |       150.00 |            1 | CORE         |
+|           2 | WEST         |        80.00 |            2 | OTHER        |
++-------------+--------------+--------------+--------------+--------------+
+```
+
+The two `VALUES`-only inserts do not generate lineage events. The `INSERT INTO 
lineage_customer_summary ... SELECT` statement generates one `LineageInfo` 
event, whose contents are described below.
+
+#### Table lineage
+
+Table lineage records one target table and every source table scanned by the 
analyzed logical plan. The query that writes `lineage_customer_summary` scans 
both `lineage_orders` and `lineage_customers`, so both source tables have a 
table-level relationship with the target. A CTE is resolved to its underlying 
tables, and every `UNION` branch contributes to the source-table set.
+
+This example produces the following table lineage:
+
+| Source table | Target table |
+| --- | --- |
+| `lineage_orders` | `lineage_customer_summary` |
+| `lineage_customers` | `lineage_customer_summary` |
+
+#### Direct column lineage
+
+Direct column lineage describes which source expression produces each 
target-column value. After resolving aliases and CTEs, Doris classifies 
expressions in this order: an expression containing an aggregate function is 
`AGGREGATION`; a plain source-column reference is `IDENTITY`; every other 
expression is `TRANSFORMATION`.
+
+| Type | Meaning | Example |
+| --- | --- | --- |
+| `IDENTITY` | The target value is taken directly from a source column without 
a function or operation. | `lineage_customer_summary.customer_id` comes 
directly from `lineage_orders.customer_id`. |
+| `TRANSFORMATION` | A non-aggregate expression transforms the target value, 
such as arithmetic, a string function, a window function, or a conditional 
expression. | `region_label` comes from `UPPER(lineage_orders.region)`; 
`customer_seq` comes from `ROW_NUMBER()`. |
+| `AGGREGATION` | The target value is produced by an expression that contains 
an aggregate function. It remains this type even when another expression wraps 
the aggregate. | `total_amount` comes from `SUM(lineage_orders.amount)`. |
+
+This example produces the following direct column lineage:
+
+| Target column | Source expression | Type |
+| --- | --- | --- |
+| `lineage_customer_summary.customer_id` | `lineage_orders.customer_id` | 
`IDENTITY` |
+| `lineage_customer_summary.region_label` | `UPPER(lineage_orders.region)` | 
`TRANSFORMATION` |
+| `lineage_customer_summary.total_amount` | `SUM(lineage_orders.amount)` | 
`AGGREGATION` |
+| `lineage_customer_summary.customer_seq` | `ROW_NUMBER() OVER (ORDER BY 
lineage_orders.customer_id)` | `TRANSFORMATION` |
+| `lineage_customer_summary.region_group` | `CASE WHEN 
UPPER(lineage_orders.region) = 'EAST' ... END` | `TRANSFORMATION` |
+
+#### Dataset indirect lineage
+
+Dataset indirect lineage records expressions that affect the complete result 
set without directly producing a target-column value. These dependencies are 
stored in an event-level map and can be applied to every target output column 
when needed.
+
+| Type | Meaning | Example |
+| --- | --- | --- |
+| `JOIN` | Join conditions determine which source rows can be combined. | 
`lineage_orders.customer_id = lineage_customers.customer_id`. |
+| `FILTER` | `WHERE` or `HAVING` conditions determine which rows or groups 
enter the result. | `status = 'PAID'`, `customer_level = 'VIP'`, and `HAVING 
SUM(amount) >= 50`. |
+| `GROUP_BY` | Grouping expressions determine the granularity of aggregate 
results. | Group by `customer_id` and `UPPER(region)`. |
+| `SORT` | `ORDER BY` or TopN expressions determine result order. | Sort by 
`total_amount DESC`, which resolves to `SUM(amount)`. |
+
+This example produces the following dataset indirect lineage:
+
+| Type | Example source expression in the event | Effect |
+| --- | --- | --- |
+| `JOIN` | `lineage_orders.customer_id = lineage_customers.customer_id` | 
Determines how orders match customers. |
+| `FILTER` | `status = 'PAID'`, `customer_level = 'VIP'`, and `SUM(amount) >= 
50` | The `WHERE` and `HAVING` predicates jointly determine which rows and 
groups enter the result. |
+| `GROUP_BY` | `lineage_orders.customer_id` and `UPPER(lineage_orders.region)` 
| Determine the aggregation granularity for each customer and region. |
+| `SORT` | `total_amount DESC`, resolved to `SUM(lineage_orders.amount)` | 
Determines the order of the Insert query result. |
+
+#### Output indirect lineage
+
+Output indirect lineage is attached only to the affected target column and 
does not apply to the other output columns.
+
+| Type | Meaning | Example |
+| --- | --- | --- |
+| `WINDOW` | The `PARTITION BY` and `ORDER BY` inputs of a window function 
affect its output column. | `customer_seq` is produced by `ROW_NUMBER() OVER 
(ORDER BY customer_id)`, whose `WINDOW` dependency is `customer_id`. |
+| `CONDITIONAL` | A `CASE` or `IF` condition, or the candidate expressions of 
`COALESCE`, affect its output column. | `region_group` is determined by `CASE 
WHEN region_label = 'EAST' ...`, whose `CONDITIONAL` dependency is that 
predicate. |
+
+The window or conditional expression itself still appears in direct column 
lineage. For example, the direct type of `customer_seq` is `TRANSFORMATION`, 
and it also has `WINDOW` indirect lineage. The direct type of `region_group` is 
also `TRANSFORMATION`, and it also has `CONDITIONAL` indirect lineage.
+
+This example produces the following output indirect lineage:
+
+| Target column | Type | Source expression in the event | Effect |
+| --- | --- | --- | --- |
+| `customer_seq` | `WINDOW` | `lineage_orders.customer_id` | Serves as the 
window ordering input to `ROW_NUMBER()` and affects only `customer_seq`. |
+| `region_group` | `CONDITIONAL` | `UPPER(lineage_orders.region) = 'EAST'` | 
Serves as the `CASE WHEN` condition and affects only `region_group`. |
+
+The other three target columns have no output indirect lineage. Dataset-level 
`JOIN`, `FILTER`, `GROUP_BY`, and `SORT` dependencies still affect the complete 
result set.
+
+#### Query context
+
+Query context identifies who produced the lineage event, in which session, and 
through which DML statement.
+
+| Field | Example or meaning |
+| --- | --- |
+| Source Command | `InsertIntoTableCommand`, indicating that the event comes 
from an Insert. |
+| Query ID and original SQL | The Query ID and the complete `INSERT INTO 
lineage_customer_summary ...` text. |
+| User and client IP | For example, `lineage_user` and `192.0.2.10`. |
+| Session database and catalog | `lineage_demo` and `internal` in this 
example. They describe session context and do not necessarily identify the 
target-table location. |
+| Execution state | A successful DML statement normally records `OK`. |
+| Timestamp and duration | The event creation time and the milliseconds from 
query start to event creation. The value is `-1` when unavailable. |
+| External catalog properties | Non-sensitive properties from external 
catalogs referenced by the query. Passwords, keys, and hidden properties are 
removed. The map is empty when only the `internal` catalog is used. |
+
+For this example, the original SQL is the complex Insert above, the Source 
Command is `InsertIntoTableCommand`, the session database is `lineage_demo`, 
the session catalog is `internal`, and the successful state is `OK`. The Query 
ID, user, client IP, timestamp, and duration use values from the actual 
execution context. Because the example uses only the Internal Catalog, the 
external catalog properties map is empty.
+
+Confirm in the plugin's downstream system that the event was received. During 
initial deployment, also check `fe.log` for `Loaded lineage plugin`, 
plugin-specific errors, and queue-full warnings.
+
+![LineageInfo event model: target and source tables feed direct and indirect 
lineage, which are combined with query context before a plugin converts the 
event for a downstream system.](/images/data-lineage/lineage-event-model.svg)
+
+The extractor resolves CTE producer expressions and expands `UNION` branches 
before the event is delivered. A downstream plugin must convert Doris Java 
objects such as `TableIf`, `SlotReference`, and `Expression` to stable 
identifiers or its own event schema.
+
+### Event processing behavior
+
+`lineage_event_queue_size` sets the maximum number of lineage events waiting 
in each FE-local queue. It counts events, not bytes. When the queue is full, 
the new event is discarded and the DML continues normally. The worker invokes 
plugins serially, so a slow plugin delays later events and can cause drops 
under sustained load. Exceptions thrown by a plugin are logged and do not stop 
the worker or affect the DML.
+
+Plugin discovery and initialization occur only when FE starts. Dynamic reload 
and unload are not supported.
+
+## Configure and use
+
+### Prerequisites
+
+1. Prepare an external lineage plugin. Doris does not ship a built-in sink. 
For the SPI contract, JAR packaging, and a complete minimal plugin, see [Data 
Lineage Plugin 
Development](/community/developer-guide/data-lineage-plugin-development).
+2. Copy the plugin JAR and any required third-party dependency JARs to every 
FE.
+3. If the plugin sends events to an external governance service, make sure 
that service is reachable from every FE that can execute the DML.
+
+### Deploy the plugin
+
+`FE_HOME` denotes a single FE installation directory that contains `bin/`, 
`conf/`, and `lib/`. Use the following layout on every FE. The loader scans 
only direct child directories under `lineage/`, plus JARs in each plugin 
directory and its `lib/` directory.
+
+```text
+$FE_HOME/plugins/
+└── lineage/
+    └── example-lineage/
+        ├── example-lineage-plugin.jar
+        └── lib/
+            └── downstream-client.jar
+```
+
+Configure `fe.conf` on every FE. Replace `example-lineage` with the value 
returned by the plugin factory's `name()` method:
+

Review Comment:
   The JAR filename `example-lineage-plugin.jar` is inconsistent with the 
developer guide 
(`community/developer-guide/data-lineage-plugin-development.md`), where the 
Maven `<finalName>` produces `example-lineage.jar`. Users who follow both 
guides may be confused about which JAR name is correct. Please align the JAR 
name across both documents — either change this to `example-lineage.jar` or 
update the dev guide's `pom.xml` `<finalName>` and build output references.



##########
community/developer-guide/data-lineage-plugin-development.md:
##########
@@ -0,0 +1,604 @@
+---
+title: Data Lineage Plugin Development
+language: en
+description: Build, package, deploy, and verify an Apache Doris data lineage 
plugin that logs lineage events or forwards them to an external governance 
system.
+keywords:
+    - Apache Doris
+    - data lineage
+    - LineagePlugin
+    - LineagePluginFactory
+    - ServiceLoader
+    - plugin development
+---
+
+<!--
+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.
+-->
+
+<!-- Knowledge type: Extension development -->
+<!-- Applicable scenario: Data governance integration / Kernel extension 
development -->
+
+# Data Lineage Plugin Development
+
+This document describes how to develop an external data lineage plugin for 
Apache Doris. The plugin receives a `LineageInfo` event after a supported DML 
statement succeeds, then converts the event and writes it to logs, a messaging 
system, or an external metadata governance platform.
+
+> Applicable version: Apache Doris community edition 4.0.6 and later. Compile 
the plugin against the exact Doris community release or source revision 
deployed on the FE.
+
+## Scope and lifecycle
+
+The lineage framework is an FE-side SPI. It does not store lineage data, 
provide a query API, or render a lineage graph. A plugin converts the in-memory 
Java objects in `LineageInfo` to the format and transport protocol required by 
its downstream system.
+
+The framework generates events only for successful `INSERT INTO ... SELECT`, 
`INSERT OVERWRITE ... SELECT`, and `CREATE TABLE AS SELECT` statements. 
`VALUES`-only writes and writes whose target is `__internal_schema` are 
skipped. `SELECT`, `UPDATE`, `DELETE`, and load jobs are outside the scope of 
this framework.
+
+![Data lineage collection architecture: successful supported DML statements 
are analyzed by Nereids, extracted into LineageInfo, queued in FE, and 
delivered by a plugin to an external governance 
system.](/images/data-lineage/lineage-architecture.svg)
+
+`eventFilter()` is called once before lineage extraction on the DML query path 
and again before worker dispatch. Return `false` when the plugin should not 
receive events. The method is called concurrently from query threads and the 
worker thread, so it must be thread-safe. `exec()` is called by one worker 
thread, but can run concurrently with `eventFilter()`.
+
+At FE startup, ServiceLoader first discovers each `LineagePluginFactory`. FE 
then calls the Factory's `create(context)` method and calls 
`initialize(context)` on the returned plugin. The default 
`LineagePluginFactory.create(context)` implementation delegates to the 
no-argument `create()`, so the minimal example in this guide implements only 
`create()`. `PluginContext` contains `plugin.name` and `plugin.path`. A 
production plugin that reads a configuration file from its plugin directory can 
override `initialize(context)`, locate the file through `plugin.path`, and 
initialize its resources there.

Review Comment:
   Same `INSERT OVERWRITE` issue as the user guide — the `TABLE` keyword is 
missing. Standard Doris syntax is `INSERT OVERWRITE TABLE ... SELECT ...`. The 
command class name `InsertOverwriteTableCommand` also confirms this. Please use 
the precise syntax throughout (applies to all files: EN user guide, ZH user 
guide, ZH dev guide, and all versioned copies).



##########
docs/sql-manual/sql-statements/data-governance/data-lineage.md:
##########
@@ -0,0 +1,336 @@
+---
+{
+    "title": "Data Lineage Technical Guide",
+    "sidebar_label": "Data Lineage",
+    "language": "en",
+    "description": "Collect table-level and column-level lineage from 
supported Doris DML statements through an external LineagePlugin.",
+    "keywords": ["Apache Doris", "data lineage", "data governance", 
"LineagePlugin", "column-level lineage"]
+}
+---
+
+<!-- Knowledge type: Feature overview + technical design + user guide -->
+<!-- Use cases: Data governance integration / impact analysis / compliance 
traceability -->
+
+Data Lineage extracts table-level and column-level dependencies from supported 
Doris DML statements and sends them to external governance systems through a 
`LineagePlugin`. Doris is the lineage producer. It does not retain events, 
expose a lineage query API, or provide lineage visualization.
+
+> This capability is available in the Dev version. It was first released in 
Apache Doris 4.0.6.
+
+## Capabilities and limitations
+
+The framework generates an event only after one of the following statements 
succeeds:
+
+| Supported statement | Event behavior |
+| --- | --- |
+| `INSERT INTO ... SELECT` | Generates one event after the Insert succeeds. |
+| `INSERT OVERWRITE ... SELECT` | Generates one event after the Overwrite 
succeeds. |
+| `CREATE TABLE AS SELECT` | Generates one event after the internal Insert 
succeeds. |
+
+The following operations do not generate an event: `SELECT`, `UPDATE`, 
`DELETE`, load jobs, `VALUES`-only inserts, and writes whose target is 
`__internal_schema`.
+
+:::caution Delivery guarantee
+
+Lineage delivery is asynchronous and best effort. A successful DML statement 
is not rolled back when event collection or plugin delivery fails. Events can 
be discarded when the queue is full, and Doris does not retry or persist them.
+
+:::
+
+## Technical design
+
+### Collection flow
+
+For a supported statement, Doris records the analyzed Nereids logical plan in 
an `afterAnalyze` planner hook. After the DML succeeds, Doris extracts lineage 
from that plan and submits the event to an FE-local queue. A single daemon 
worker dispatches the event to every active plugin.
+
+![Data lineage collection architecture: successful supported DML statements 
are analyzed by Nereids, extracted into LineageInfo, queued in FE, and 
delivered by a plugin to an external governance 
system.](/images/data-lineage/lineage-architecture.svg)
+
+Before extraction, Doris calls `eventFilter()` on the loaded plugins. If no 
plugin is willing to receive events, extraction is skipped. The worker 
evaluates `eventFilter()` again before dispatch.
+
+### Lineage content
+
+Each `LineageInfo` event contains table lineage, direct column lineage, two 
kinds of indirect lineage, and query context. The complete SQL example below 
establishes the source tables, target table, and result used by the following 
sections.
+
+#### Example SQL and result
+
+The current user must have privileges to create a database and tables, write 
data, and query data, and the FE must have loaded a lineage plugin. The example 
uses two source tables and creates a customer summary through a CTE, Join, 
filters, aggregation, a window function, a conditional expression, and sorting.
+
+```sql
+CREATE DATABASE IF NOT EXISTS lineage_demo;
+USE lineage_demo;
+
+CREATE TABLE lineage_orders (
+    order_id BIGINT,
+    customer_id BIGINT,
+    region VARCHAR(16),
+    amount DECIMAL(18, 2),
+    status VARCHAR(16)
+)
+DUPLICATE KEY(order_id)
+DISTRIBUTED BY HASH(order_id) BUCKETS 1
+PROPERTIES ("replication_num" = "1");
+
+CREATE TABLE lineage_customers (
+    customer_id BIGINT,
+    customer_name VARCHAR(64),
+    customer_level VARCHAR(16)
+)
+DUPLICATE KEY(customer_id)
+DISTRIBUTED BY HASH(customer_id) BUCKETS 1
+PROPERTIES ("replication_num" = "1");
+
+CREATE TABLE lineage_customer_summary (
+    customer_id BIGINT,
+    region_label VARCHAR(16),
+    total_amount DECIMAL(18, 2),
+    customer_seq BIGINT,
+    region_group VARCHAR(16)
+)
+DUPLICATE KEY(customer_id)
+DISTRIBUTED BY HASH(customer_id) BUCKETS 1
+PROPERTIES ("replication_num" = "1");
+
+INSERT INTO lineage_customers VALUES
+    (1, 'Alice', 'VIP'),
+    (2, 'Bob', 'VIP'),
+    (3, 'Carol', 'REGULAR');
+
+INSERT INTO lineage_orders VALUES
+    (101, 1, 'east', 100.00, 'PAID'),
+    (102, 1, 'east',  50.00, 'PAID'),
+    (103, 2, 'west',  80.00, 'PAID'),
+    (104, 2, 'west',  30.00, 'CANCELLED'),
+    (105, 3, 'north', 200.00, 'PAID');
+
+INSERT INTO lineage_customer_summary
+WITH customer_totals AS (
+    SELECT
+        o.customer_id,
+        UPPER(o.region) AS region_label,
+        SUM(o.amount) AS total_amount
+    FROM lineage_orders o
+    JOIN lineage_customers c
+        ON o.customer_id = c.customer_id
+    WHERE o.status = 'PAID'
+      AND c.customer_level = 'VIP'
+    GROUP BY o.customer_id, UPPER(o.region)
+    HAVING SUM(o.amount) >= 50
+)
+SELECT
+    customer_id,
+    region_label,
+    total_amount,
+    ROW_NUMBER() OVER (ORDER BY customer_id) AS customer_seq,
+    CASE
+        WHEN region_label = 'EAST' THEN 'CORE'
+        ELSE 'OTHER'
+    END AS region_group
+FROM customer_totals
+ORDER BY total_amount DESC;
+
+SELECT customer_id, region_label, total_amount, customer_seq, region_group
+FROM lineage_customer_summary
+ORDER BY customer_id;
+```
+
+The query returns:
+
+```text
++-------------+--------------+--------------+--------------+--------------+
+| customer_id | region_label | total_amount | customer_seq | region_group |
++-------------+--------------+--------------+--------------+--------------+
+|           1 | EAST         |       150.00 |            1 | CORE         |
+|           2 | WEST         |        80.00 |            2 | OTHER        |
++-------------+--------------+--------------+--------------+--------------+
+```
+
+The two `VALUES`-only inserts do not generate lineage events. The `INSERT INTO 
lineage_customer_summary ... SELECT` statement generates one `LineageInfo` 
event, whose contents are described below.
+
+#### Table lineage
+
+Table lineage records one target table and every source table scanned by the 
analyzed logical plan. The query that writes `lineage_customer_summary` scans 
both `lineage_orders` and `lineage_customers`, so both source tables have a 
table-level relationship with the target. A CTE is resolved to its underlying 
tables, and every `UNION` branch contributes to the source-table set.
+
+This example produces the following table lineage:
+
+| Source table | Target table |
+| --- | --- |
+| `lineage_orders` | `lineage_customer_summary` |
+| `lineage_customers` | `lineage_customer_summary` |
+
+#### Direct column lineage
+
+Direct column lineage describes which source expression produces each 
target-column value. After resolving aliases and CTEs, Doris classifies 
expressions in this order: an expression containing an aggregate function is 
`AGGREGATION`; a plain source-column reference is `IDENTITY`; every other 
expression is `TRANSFORMATION`.
+
+| Type | Meaning | Example |
+| --- | --- | --- |
+| `IDENTITY` | The target value is taken directly from a source column without 
a function or operation. | `lineage_customer_summary.customer_id` comes 
directly from `lineage_orders.customer_id`. |
+| `TRANSFORMATION` | A non-aggregate expression transforms the target value, 
such as arithmetic, a string function, a window function, or a conditional 
expression. | `region_label` comes from `UPPER(lineage_orders.region)`; 
`customer_seq` comes from `ROW_NUMBER()`. |
+| `AGGREGATION` | The target value is produced by an expression that contains 
an aggregate function. It remains this type even when another expression wraps 
the aggregate. | `total_amount` comes from `SUM(lineage_orders.amount)`. |
+
+This example produces the following direct column lineage:
+
+| Target column | Source expression | Type |
+| --- | --- | --- |
+| `lineage_customer_summary.customer_id` | `lineage_orders.customer_id` | 
`IDENTITY` |
+| `lineage_customer_summary.region_label` | `UPPER(lineage_orders.region)` | 
`TRANSFORMATION` |
+| `lineage_customer_summary.total_amount` | `SUM(lineage_orders.amount)` | 
`AGGREGATION` |
+| `lineage_customer_summary.customer_seq` | `ROW_NUMBER() OVER (ORDER BY 
lineage_orders.customer_id)` | `TRANSFORMATION` |
+| `lineage_customer_summary.region_group` | `CASE WHEN 
UPPER(lineage_orders.region) = 'EAST' ... END` | `TRANSFORMATION` |
+
+#### Dataset indirect lineage
+
+Dataset indirect lineage records expressions that affect the complete result 
set without directly producing a target-column value. These dependencies are 
stored in an event-level map and can be applied to every target output column 
when needed.
+
+| Type | Meaning | Example |
+| --- | --- | --- |
+| `JOIN` | Join conditions determine which source rows can be combined. | 
`lineage_orders.customer_id = lineage_customers.customer_id`. |
+| `FILTER` | `WHERE` or `HAVING` conditions determine which rows or groups 
enter the result. | `status = 'PAID'`, `customer_level = 'VIP'`, and `HAVING 
SUM(amount) >= 50`. |
+| `GROUP_BY` | Grouping expressions determine the granularity of aggregate 
results. | Group by `customer_id` and `UPPER(region)`. |
+| `SORT` | `ORDER BY` or TopN expressions determine result order. | Sort by 
`total_amount DESC`, which resolves to `SUM(amount)`. |
+
+This example produces the following dataset indirect lineage:
+
+| Type | Example source expression in the event | Effect |
+| --- | --- | --- |
+| `JOIN` | `lineage_orders.customer_id = lineage_customers.customer_id` | 
Determines how orders match customers. |
+| `FILTER` | `status = 'PAID'`, `customer_level = 'VIP'`, and `SUM(amount) >= 
50` | The `WHERE` and `HAVING` predicates jointly determine which rows and 
groups enter the result. |
+| `GROUP_BY` | `lineage_orders.customer_id` and `UPPER(lineage_orders.region)` 
| Determine the aggregation granularity for each customer and region. |
+| `SORT` | `total_amount DESC`, resolved to `SUM(lineage_orders.amount)` | 
Determines the order of the Insert query result. |
+
+#### Output indirect lineage
+
+Output indirect lineage is attached only to the affected target column and 
does not apply to the other output columns.
+
+| Type | Meaning | Example |
+| --- | --- | --- |
+| `WINDOW` | The `PARTITION BY` and `ORDER BY` inputs of a window function 
affect its output column. | `customer_seq` is produced by `ROW_NUMBER() OVER 
(ORDER BY customer_id)`, whose `WINDOW` dependency is `customer_id`. |
+| `CONDITIONAL` | A `CASE` or `IF` condition, or the candidate expressions of 
`COALESCE`, affect its output column. | `region_group` is determined by `CASE 
WHEN region_label = 'EAST' ...`, whose `CONDITIONAL` dependency is that 
predicate. |
+
+The window or conditional expression itself still appears in direct column 
lineage. For example, the direct type of `customer_seq` is `TRANSFORMATION`, 
and it also has `WINDOW` indirect lineage. The direct type of `region_group` is 
also `TRANSFORMATION`, and it also has `CONDITIONAL` indirect lineage.
+
+This example produces the following output indirect lineage:
+
+| Target column | Type | Source expression in the event | Effect |
+| --- | --- | --- | --- |
+| `customer_seq` | `WINDOW` | `lineage_orders.customer_id` | Serves as the 
window ordering input to `ROW_NUMBER()` and affects only `customer_seq`. |
+| `region_group` | `CONDITIONAL` | `UPPER(lineage_orders.region) = 'EAST'` | 
Serves as the `CASE WHEN` condition and affects only `region_group`. |
+
+The other three target columns have no output indirect lineage. Dataset-level 
`JOIN`, `FILTER`, `GROUP_BY`, and `SORT` dependencies still affect the complete 
result set.
+
+#### Query context
+
+Query context identifies who produced the lineage event, in which session, and 
through which DML statement.
+
+| Field | Example or meaning |
+| --- | --- |
+| Source Command | `InsertIntoTableCommand`, indicating that the event comes 
from an Insert. |
+| Query ID and original SQL | The Query ID and the complete `INSERT INTO 
lineage_customer_summary ...` text. |
+| User and client IP | For example, `lineage_user` and `192.0.2.10`. |
+| Session database and catalog | `lineage_demo` and `internal` in this 
example. They describe session context and do not necessarily identify the 
target-table location. |
+| Execution state | A successful DML statement normally records `OK`. |
+| Timestamp and duration | The event creation time and the milliseconds from 
query start to event creation. The value is `-1` when unavailable. |
+| External catalog properties | Non-sensitive properties from external 
catalogs referenced by the query. Passwords, keys, and hidden properties are 
removed. The map is empty when only the `internal` catalog is used. |
+
+For this example, the original SQL is the complex Insert above, the Source 
Command is `InsertIntoTableCommand`, the session database is `lineage_demo`, 
the session catalog is `internal`, and the successful state is `OK`. The Query 
ID, user, client IP, timestamp, and duration use values from the actual 
execution context. Because the example uses only the Internal Catalog, the 
external catalog properties map is empty.
+
+Confirm in the plugin's downstream system that the event was received. During 
initial deployment, also check `fe.log` for `Loaded lineage plugin`, 
plugin-specific errors, and queue-full warnings.
+
+![LineageInfo event model: target and source tables feed direct and indirect 
lineage, which are combined with query context before a plugin converts the 
event for a downstream system.](/images/data-lineage/lineage-event-model.svg)
+
+The extractor resolves CTE producer expressions and expands `UNION` branches 
before the event is delivered. A downstream plugin must convert Doris Java 
objects such as `TableIf`, `SlotReference`, and `Expression` to stable 
identifiers or its own event schema.
+
+### Event processing behavior
+
+`lineage_event_queue_size` sets the maximum number of lineage events waiting 
in each FE-local queue. It counts events, not bytes. When the queue is full, 
the new event is discarded and the DML continues normally. The worker invokes 
plugins serially, so a slow plugin delays later events and can cause drops 
under sustained load. Exceptions thrown by a plugin are logged and do not stop 
the worker or affect the DML.
+
+Plugin discovery and initialization occur only when FE starts. Dynamic reload 
and unload are not supported.
+
+## Configure and use
+
+### Prerequisites
+
+1. Prepare an external lineage plugin. Doris does not ship a built-in sink. 
For the SPI contract, JAR packaging, and a complete minimal plugin, see [Data 
Lineage Plugin 
Development](/community/developer-guide/data-lineage-plugin-development).
+2. Copy the plugin JAR and any required third-party dependency JARs to every 
FE.
+3. If the plugin sends events to an external governance service, make sure 
that service is reachable from every FE that can execute the DML.
+
+### Deploy the plugin
+
+`FE_HOME` denotes a single FE installation directory that contains `bin/`, 
`conf/`, and `lib/`. Use the following layout on every FE. The loader scans 
only direct child directories under `lineage/`, plus JARs in each plugin 
directory and its `lib/` directory.
+
+```text
+$FE_HOME/plugins/
+└── lineage/
+    └── example-lineage/
+        ├── example-lineage-plugin.jar
+        └── lib/
+            └── downstream-client.jar
+```
+
+Configure `fe.conf` on every FE. Replace `example-lineage` with the value 
returned by the plugin factory's `name()` method:
+
+```text
+plugin_dir = /opt/apache-doris/fe/plugins
+activate_lineage_plugin = example-lineage
+lineage_event_queue_size = 50000
+```
+
+| Configuration | Type and default | Required | Description |
+| --- | --- | --- | --- |
+| `plugin_dir` | String; `$FE_HOME/plugins` | No | Plugin root directory. The 
lineage loader scans its direct children under `lineage/`. Omit this setting 
when using the default directory. |
+| `activate_lineage_plugin` | String array; empty | Explicit configuration 
recommended | Comma-separated Factory names to instantiate. An empty value 
disables name filtering and instantiates all discovered factories. |
+| `lineage_event_queue_size` | Positive integer; `50000` | No | Maximum 
pending lineage events on the current FE. New events are discarded when the 
queue is full. |
+
+All three settings are configured in `$FE_HOME/conf/fe.conf` on each FE and 
apply only to that FE process. They are read only at FE startup, so restart the 
corresponding FE after changing them.
+
+#### Activate plugins
+
+Configure `activate_lineage_plugin` in `$FE_HOME/conf/fe.conf` on every FE. It 
controls which discovered factories are instantiated. It does not discover JARs 
or control queue capacity. Each name is case-sensitive and must exactly match 
`LineagePluginFactory.name()`. The `LineagePlugin.name()` implementation should 
return the same name. The plugin directory name is not used for matching. 
Separate multiple plugin names with commas; FE trims surrounding spaces:
+
+```text
+activate_lineage_plugin = example-lineage,governance-lineage
+```
+
+At startup, FE discovers built-in and external factories, then creates 
instances only for the configured names. Each FE reads its own configuration, 
so every FE that can execute DML must use the same plugin set. Restart FE after 
a change. This setting cannot be changed dynamically through `ADMIN SET 
FRONTEND CONFIG`.

Review Comment:
   This paragraph says: "Separate multiple plugin names with commas; FE trims 
surrounding spaces." Looking at the code, `Config.activate_lineage_plugin` is a 
`String[]`, and `discoverPlugins()` uses 
`Arrays.asList(Config.activate_lineage_plugin)` followed by `new 
HashSet<>(...)`. The raw `Arrays.asList()` does NOT trim whitespace from 
individual elements. Unless the Doris Config framework's String[] parser does 
the trimming (please verify), this claim may be incorrect. If a user writes 
`activate_lineage_plugin = example-lineage, governance-lineage`, the second 
entry would be `" governance-lineage"` (with leading space) and would fail name 
matching because the comparison is case-sensitive `HashSet.contains()`.



##########
static/images/data-lineage/lineage-architecture.svg:
##########
@@ -0,0 +1,65 @@
+<svg xmlns="http://www.w3.org/2000/svg"; width="1440" height="620" viewBox="0 0 
1440 620" role="img" aria-labelledby="title desc">
+  <title id="title">Apache Doris data lineage collection architecture</title>
+  <desc id="desc">Supported DML is analyzed by Nereids and, after successful 
execution, produces a LineageInfo event. The event enters a bounded FE queue, 
is processed by one worker, and is sent by a lineage plugin to an external 
governance system.</desc>
+  <defs>
+    <marker id="arrow" markerWidth="10" markerHeight="10" refX="9" refY="5" 
orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#4b6472"/></marker>
+    <style>
+      .title { font: 700 28px Arial, sans-serif; fill: #173b4d; }
+      .label { font: 700 18px Arial, sans-serif; fill: #173b4d; }
+      .body { font: 16px Arial, sans-serif; fill: #3b5360; }
+      .small { font: 14px Arial, sans-serif; fill: #526b77; }
+      .edge { fill: none; stroke: #4b6472; stroke-width: 3; marker-end: 
url(#arrow); }
+      .edge-label { font: 14px Arial, sans-serif; fill: #526b77; }
+    </style>
+  </defs>
+  <rect width="1440" height="620" fill="#f7faf9"/>
+  <text x="72" y="70" class="title">Data Lineage Collection and Delivery</text>
+  <text x="72" y="102" class="body">Collection occurs after a supported DML 
statement succeeds. Delivery is asynchronous and best effort.</text>
+
+  <rect x="72" y="186" width="230" height="174" rx="8" fill="#e7f4f1" 
stroke="#2f8f83" stroke-width="2"/>
+  <text x="96" y="226" class="label">Supported DML</text>
+  <text x="96" y="262" class="body">INSERT INTO SELECT</text>
+  <text x="96" y="290" class="body">INSERT OVERWRITE SELECT</text>
+  <text x="96" y="318" class="body">CREATE TABLE AS SELECT</text>

Review Comment:
   The SVG text uses `INSERT OVERWRITE SELECT` without the `TABLE` keyword. The 
standard Doris SQL syntax is `INSERT OVERWRITE TABLE ... SELECT ...`. Please 
add `TABLE` to match the actual syntax. Affects both `lineage-architecture.svg` 
and its zh-CN variant.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to