---
title: "Action Jars"
sidebar_position: 98
---

<!--
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.
-->

# Action Jars

After the Flink Local Cluster has been started, you can execute the action jar by using the following command.

```bash
<FLINK_HOME>/bin/flink run \
 /path/to/paimon-flink-action-@@VERSION@@.jar \
 <action>
 <args>
``` 

The following command is used to compact a table.

```bash
<FLINK_HOME>/bin/flink run \
 /path/to/paimon-flink-action-@@VERSION@@.jar \
 compact \
 --path <TABLE_PATH>
```

## Merging into table

Paimon supports "MERGE INTO" via submitting the 'merge_into' job through `flink run`.

:::info

Important table properties setting:
1. Only [primary key table](../primary-key-table/) supports this feature.
2. The action won't produce UPDATE_BEFORE, so it's not recommended to set 'changelog-producer' = 'input'.

:::

The design referenced such syntax:
```sql
MERGE INTO target-table
  USING source_table | source-expr AS source-alias
  ON merge-condition
  WHEN MATCHED [AND matched-condition]
    THEN UPDATE SET xxx
  WHEN MATCHED [AND matched-condition]
    THEN DELETE
  WHEN NOT MATCHED [AND not_matched_condition]
    THEN INSERT VALUES (xxx)
  WHEN NOT MATCHED BY SOURCE [AND not-matched-by-source-condition]
    THEN UPDATE SET xxx
  WHEN NOT MATCHED BY SOURCE [AND not-matched-by-source-condition]
    THEN DELETE
```
The merge_into action use "upsert" semantics instead of "update", which means if the row exists,
then do update, else do insert. For example, for non-primary-key table, you can update every column,
but for primary key table, if you want to update primary keys, you have to insert a new row which has
different primary keys from rows in the table. In this scenario, "upsert" is useful.

Run the following command to submit a 'merge_into' job for the table.

```bash
<FLINK_HOME>/bin/flink run \
    /path/to/paimon-flink-action-@@VERSION@@.jar \
    merge_into \
    --warehouse <warehouse-path> \
    --database <database-name> \
    --table <target-table> \
    [--target_as <target-table-alias>] \
    --source_table <source_table-name> \
    [--source_sql <sql> ...]\
    --on <merge-condition> \
    --merge_actions <matched-upsert,matched-delete,not-matched-insert,not-matched-by-source-upsert,not-matched-by-source-delete> \
    --matched_upsert_condition <matched-condition> \
    --matched_upsert_set <upsert-changes> \
    --matched_delete_condition <matched-condition> \
    --not_matched_insert_condition <not-matched-condition> \
    --not_matched_insert_values <insert-values> \
    --not_matched_by_source_upsert_condition <not-matched-by-source-condition> \
    --not_matched_by_source_upsert_set <not-matched-upsert-changes> \
    --not_matched_by_source_delete_condition <not-matched-by-source-condition> \
    [--catalog_conf <paimon-catalog-conf> [--catalog_conf <paimon-catalog-conf> ...]]
    
You can pass sqls by '--source_sql <sql> [, --source_sql <sql> ...]' to config environment and create source table at runtime.
    
-- Examples:
-- Find all orders mentioned in the source table, then mark as important if the price is above 100 
-- or delete if the price is under 10.
./flink run \
    /path/to/paimon-flink-action-@@VERSION@@.jar \
    merge_into \
    --warehouse <warehouse-path> \
    --database <database-name> \
    --table T \
    --source_table S \
    --on "T.id = S.order_id" \
    --merge_actions \
    matched-upsert,matched-delete \
    --matched_upsert_condition "T.price > 100" \
    --matched_upsert_set "mark = 'important'" \
    --matched_delete_condition "T.price < 10" 
    
-- For matched order rows, increase the price, and if there is no match, insert the order from the 
-- source table:
./flink run \
    /path/to/paimon-flink-action-@@VERSION@@.jar \
    merge_into \
    --warehouse <warehouse-path> \
    --database <database-name> \
    --table T \
    --source_table S \
    --on "T.id = S.order_id" \
    --merge_actions \
    matched-upsert,not-matched-insert \
    --matched_upsert_set "price = T.price + 20" \
    --not_matched_insert_values * 

-- For not matched by source order rows (which are in the target table and does not match any row in the
-- source table based on the merge-condition), decrease the price or if the mark is 'trivial', delete them:
./flink run \
    /path/to/paimon-flink-action-@@VERSION@@.jar \
    merge_into \
    --warehouse <warehouse-path> \
    --database <database-name> \
    --table T \
    --source_table S \
    --on "T.id = S.order_id" \
    --merge_actions \
    not-matched-by-source-upsert,not-matched-by-source-delete \
    --not_matched_by_source_upsert_condition "T.mark <> 'trivial'" \
    --not_matched_by_source_upsert_set "price = T.price - 20" \
    --not_matched_by_source_delete_condition "T.mark = 'trivial'"
    
-- A --source_sql example: 
-- Create a temporary view S in new catalog and use it as source table
./flink run \
    /path/to/paimon-flink-action-@@VERSION@@.jar \
    merge_into \
    --warehouse <warehouse-path> \
    --database <database-name> \
    --table T \
    --source_sql "CREATE CATALOG test_cat WITH (...)" \
    --source_sql "CREATE TEMPORARY VIEW test_cat.`default`.S AS SELECT order_id, price, 'important' FROM important_order" \
    --source_table test_cat.default.S \
    --on "T.id = S.order_id" \
    --merge_actions not-matched-insert\
    --not_matched_insert_values *
```

The term 'matched' explanation:
1. matched: changed rows are from target table and each can match a source table row based on
   merge-condition and optional matched-condition (source ∩ target).
2. not matched: changed rows are from source table and all rows cannot match any target table
   row based on merge-condition and optional not_matched_condition (source - target).
3. not matched by source: changed rows are from target table and all row cannot match any source
   table row based on merge-condition and optional not-matched-by-source-condition (target - source).

Parameters format:
1. matched_upsert_changes:\
   col = \<source_table>.col | expression [, ...] (Means setting \<target_table>.col with given value. Do not
   add '\<target_table>.' before 'col'.)\
   Especially, you can use '*' to set columns with all source columns (require target table's
   schema is equal to source's).
2. not_matched_upsert_changes is similar to matched_upsert_changes, but you cannot reference
   source table's column or use '*'.
3. insert_values:\
   col1, col2, ..., col_end\
   Must specify values of all columns. For each column, you can reference \<source_table>.col or
   use an expression.\
   Especially, you can use '*' to insert with all source columns (require target table's schema
   is equal to source's).
4. not_matched_condition cannot use target table's columns to construct condition expression.
5. not_matched_by_source_condition cannot use source table's columns to construct condition expression.

:::warning

1. Target alias cannot be duplicated with existed table name.
2. If the source table is not in the current catalog and current database, the source-table-name must be
   qualified (database.table or catalog.database.table if created a new catalog).
   For examples:\
   (1) If source table 'my_source' is in 'my_db', qualify it:\
   \--source_table "my_db.my_source"\
   (2) Example for sqls:\
   When sqls changed current catalog and database, it's OK to not qualify the source table name:\
   \--source_sql "CREATE CATALOG my_cat WITH (...)"\
   \--source_sql "USE CATALOG my_cat"\
   \--source_sql "CREATE DATABASE my_db"\
   \--source_sql "USE my_db"\
   \--source_sql "CREATE TABLE S ..."\
   \--source_table S\
   but you must qualify it in the following case:\
   \--source_sql "CREATE CATALOG my_cat WITH (...)"\
   \--source_sql "CREATE TABLE my_cat.\`default`.S ..."\
   \--source_table my_cat.default.S\
   You can use just 'S' as source table name in following arguments.
3. At least one merge action must be specified.
4. If both matched-upsert and matched-delete actions are present, their conditions must both be present too
   (same to not-matched-by-source-upsert and not-matched-by-source-delete). Otherwise, all conditions are optional.
5. All conditions, set changes and values should use Flink SQL syntax. To ensure the whole command runs normally
   in Shell, please quote them with \"\" to escape blank spaces and use '\\' to escape special characters in statement.
   For example:\
   \--source_sql "CREATE TABLE T (k INT) WITH ('special-key' = '123\\!')"

:::

For more information of 'merge_into', see

```bash
<FLINK_HOME>/bin/flink run \
    /path/to/paimon-flink-action-@@VERSION@@.jar \
    merge_into --help
```

## Deleting from table

In Flink 1.16 and previous versions, Paimon only supports deleting records via submitting the 'delete' job through `flink run`.

Run the following command to submit a 'delete' job for the table.

```bash
<FLINK_HOME>/bin/flink run \
    /path/to/paimon-flink-action-@@VERSION@@.jar \
    delete \
    --warehouse <warehouse-path> \
    --database <database-name> \
    --table <table-name> \
    --where <filter_spec> \
    [--catalog_conf <paimon-catalog-conf> [--catalog_conf <paimon-catalog-conf> ...]]
    
filter_spec is equal to the 'WHERE' clause in SQL DELETE statement. Examples:
    age >= 18 AND age <= 60
    animal <> 'cat'
    id > (SELECT count(*) FROM employee)
```

For more information of 'delete', see

```bash
<FLINK_HOME>/bin/flink run \
    /path/to/paimon-flink-action-@@VERSION@@.jar \
    delete --help
```

## Deleting from a Data Evolution table

For a non-primary-key append table in
[Data Evolution](../multimodal-table/data-evolution) mode, Paimon supports
logically deleting matching rows through the same `delete` action used by
primary-key tables. The action dispatches to the appropriate implementation
based on the target table type. Regular append-only tables are not supported.

The action evaluates the filter against a fixed snapshot of the
`$row_tracking` system table and records matched rows in deletion vectors. It
does not rewrite existing data files or dedicated BLOB files.

:::info

The target table must:

1. have no primary key;
2. use bucket-unaware mode (`bucket = -1`);
3. enable `row-tracking.enabled`;
4. enable `data-evolution.enabled`;
5. enable `deletion-vectors.enabled`.

:::

Run the following command to submit a `delete` job:

```bash
<FLINK_HOME>/bin/flink run \
    /path/to/paimon-flink-action-@@VERSION@@.jar \
    delete \
    --warehouse <warehouse-path> \
    --database <database-name> \
    --table <table-name> \
    [--source_sql <sql> [--source_sql <sql> ...]] \
    --where "<filter_spec>" \
    [--sink_parallelism <sink-parallelism>] \
    [--catalog_conf <paimon-catalog-conf> [--catalog_conf <paimon-catalog-conf> ...]]
```

`filter_spec` uses Flink SQL expression syntax and is equivalent to the
predicate in a SQL `WHERE` clause. For example:

```text
last_access_time < TIMESTAMP '2026-07-01 00:00:00'
status = 'expired'
id >= 100 AND id < 200
```

`source_sql` is repeatable. Each statement is executed in order before the
target query, so any bounded table supported by Flink SQL and optional views
can be registered and referenced from a subquery in `filter_spec`. This is not
limited to a particular external system: JDBC databases, data warehouses, and
other bounded connectors can all be used as long as the corresponding
connector is available in the action job's classpath.

The subquery is also where source-side filtering belongs. In the example below,
the action reads an access-state table, selects only cold URLs, and deletes the
matching rows from the target Paimon table:

```bash
<FLINK_HOME>/bin/flink run \
    /path/to/paimon-flink-action-@@VERSION@@.jar \
    delete \
    --warehouse <warehouse-path> \
    --database <database-name> \
    --table <table-name> \
    --source_sql "CREATE TEMPORARY TABLE access_state (
        url STRING,
        last_ingest_time TIMESTAMP(3),
        last_request_time TIMESTAMP(3)
    ) WITH (
        'connector' = 'jdbc',
        'url' = '<jdbc-url>',
        'table-name' = '<access-state-table>'
    )" \
    --where "url IN (
        SELECT url
        FROM access_state
        WHERE last_ingest_time < TIMESTAMP '2026-07-01 00:00:00'
          AND (last_request_time IS NULL
               OR last_request_time < TIMESTAMP '2026-07-01 00:00:00')
    )" \
    --sink_parallelism 8
```

Using a subquery avoids copying a large candidate set into a temporary Paimon
table. The external source must be bounded so the action can finish and commit
one delete snapshot.

:::warning

- This action performs a logical delete. Physical data and BLOB file
  reclamation still depends on subsequent Data Evolution compaction with
  `data-evolution.compaction.rewrite-row-ids=true` and snapshot expiration.
- Do not run multiple delete actions, or concurrent `APPEND`, `COMPACT`, or
  `OVERWRITE` operations, against the same table. A conflicting
  commit causes the action to fail instead of silently overwriting deletion
  vectors.
- Row positions are aggregated in parallel per anchor file. Deletion vectors
  are then written in parallel across independent rewrite groups. One existing
  deletion-vector index file is an atomic rewrite group and always has a single
  writer owner, because it can contain deletion vectors for several anchor
  files.
- Split large deletes into bounded batches to limit row-tracking planning,
  deletion-vector memory usage, and external-source scan size.
- Snapshot retention must preserve the action's fixed base snapshot until the
  job finishes.

:::

For more information, run:

```bash
<FLINK_HOME>/bin/flink run \
    /path/to/paimon-flink-action-@@VERSION@@.jar \
    delete --help
```

## Drop Partition

Run the following command to submit a 'drop_partition' job for the table.

```bash
<FLINK_HOME>/bin/flink run \
    /path/to/paimon-flink-action-@@VERSION@@.jar \
    drop_partition \
    --warehouse <warehouse-path> \
    --database <database-name> \
    --table <table-name> \
    [--partition <partition_spec> [--partition <partition_spec> ...]] \
    [--catalog_conf <paimon-catalog-conf> [--catalog_conf <paimon-catalog-conf> ...]]

partition_spec:
key1=value1,key2=value2...
```

For more information of 'drop_partition', see

```bash
<FLINK_HOME>/bin/flink run \
    /path/to/paimon-flink-action-@@VERSION@@.jar \
    drop_partition --help
```

## Rewrite File Index

Run the following command to submit a 'rewrite_file_index' job for the table.

```bash
<FLINK_HOME>/bin/flink run \
    /path/to/paimon-flink-action-@@VERSION@@.jar \
    rewrite_file_index \
    --warehouse <warehouse-path> \
    --identifier <database.table> \
    [--partitions <partition_spec>] \
    [--catalog_conf <paimon-catalog-conf> [--catalog_conf <paimon-catalog-conf> ...]]
```

For more information of 'rewrite_file_index', see

```bash
<FLINK_HOME>/bin/flink run \
    /path/to/paimon-flink-action-@@VERSION@@.jar \
    rewrite_file_index --help
```

## Reassign Row ID

Run the following command to submit a 'reassign_row_id' job for a data evolution table.
This action rewrites metadata to make partition row-id ranges non-overlapping.

```bash
<FLINK_HOME>/bin/flink run \
    /path/to/paimon-flink-action-@@VERSION@@.jar \
    reassign_row_id \
    --warehouse <warehouse-path> \
    --database <database-name> \
    --table <table-name> \
    [--table_conf <paimon-table-conf> [--table_conf <paimon-table-conf> ...]] \
    [--partition <partition_spec> [--partition <partition_spec> ...]] \
    [--catalog_conf <paimon-catalog-conf> [--catalog_conf <paimon-catalog-conf> ...]]

partition_spec:
key1=value1,key2=value2...
```

For more information of 'reassign_row_id', see

```bash
<FLINK_HOME>/bin/flink run \
    /path/to/paimon-flink-action-@@VERSION@@.jar \
    reassign_row_id --help
```

## Force Start Flink Job

Some actions, like `create_tag`, are lightweight and by default will not be submitted as a job to Flink cluster. If you
have the need to unify the experience regardless of actions, you can use the `--force_start_flink_job` flag to make sure
submitting them as jobs. For example,

```bash
<FLINK_HOME>/bin/flink run \
    /path/to/paimon-flink-action-@@VERSION@@.jar \
    drop_partition \
    --warehouse <warehouse-path> \
    --database <database-name> \
    --table <table-name> \
    [--partition <partition_spec> [--partition <partition_spec> ...]] \
    [--catalog_conf <paimon-catalog-conf> [--catalog_conf <paimon-catalog-conf> ...]]
    --force_start_flink_job true
```
