diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml
index d9f6b0c061..24a74a2d76 100644
--- a/.github/workflows/develop.yml
+++ b/.github/workflows/develop.yml
@@ -795,6 +795,12 @@ jobs:
run: tar -xvf ./bins.tar.gz
- name: Run sqlness
run: RUST_BACKTRACE=1 ./bins/sqlness-runner bare ${{ matrix.mode.opts }} -c ./tests/cases --bins-dir ./bins --preserve-state
+ - if: matrix.mode.name == 'Basic'
+ name: Run soft-drop lifecycle sqlness
+ env:
+ GREPTIMEDB_METASRV__GC__SOFT_DROP__ENABLE: "true"
+ GREPTIMEDB_METASRV__GC__SOFT_DROP__RETENTION: "7d"
+ run: RUST_BACKTRACE=1 ./bins/sqlness-runner bare --enable-gc -c ./tests/cases-soft-drop --bins-dir ./bins --preserve-state -t soft_drop_table
- name: Upload sqlness logs
if: failure()
uses: actions/upload-artifact@v4
diff --git a/config/config.md b/config/config.md
index 93575fcce4..86374927dd 100644
--- a/config/config.md
+++ b/config/config.md
@@ -438,8 +438,8 @@
| `gc.enable` | Bool | `false` | Whether GC is enabled. Default to false. Need to be the same with datanode's `mito.gc.enable`
If set to false, no GC will be performed |
| `gc.gc_cooldown_period` | String | `5m` | Cooldown period between GC operations on the same region. |
| `gc.experimental_soft_drop` | -- | -- | -- |
-| `gc.experimental_soft_drop.enable` | Bool | `false` | Reserved experimental option. Currently ignored. |
-| `gc.experimental_soft_drop.retention` | String | `7d` | Reserved retention duration. Currently ignored. |
+| `gc.experimental_soft_drop.enable` | Bool | `false` | Whether soft drop is enabled. Requires `gc.enable = true`. |
+| `gc.experimental_soft_drop.retention` | String | `7d` | How long soft-dropped tables are retained before automatic purge. |
| `logging` | -- | -- | The logging options. |
| `logging.dir` | String | `./greptimedb_data/logs` | The directory to store the log files. If set to empty, logs will not be written to files. |
| `logging.level` | String | Unset | The log level. Can be `info`/`debug`/`warn`/`error`. |
diff --git a/config/metasrv.example.toml b/config/metasrv.example.toml
index 55e6497078..33f3378cbc 100644
--- a/config/metasrv.example.toml
+++ b/config/metasrv.example.toml
@@ -327,9 +327,9 @@ enable = false
gc_cooldown_period = "5m"
[gc.experimental_soft_drop]
-## Reserved experimental option. Currently ignored.
+## Whether soft drop is enabled. Requires `gc.enable = true`.
enable = false
-## Reserved retention duration. Currently ignored.
+## How long soft-dropped tables are retained before automatic purge.
retention = "7d"
## The logging options.
diff --git a/src/cmd/tests/load_config_test.rs b/src/cmd/tests/load_config_test.rs
index 5814cc33b4..78c10eb14d 100644
--- a/src/cmd/tests/load_config_test.rs
+++ b/src/cmd/tests/load_config_test.rs
@@ -233,6 +233,25 @@ fn test_load_metasrv_example_config() {
similar_asserts::assert_eq!(options, expected);
}
+#[test]
+fn test_load_metasrv_soft_drop_config() {
+ let config = tempfile::NamedTempFile::new().unwrap();
+ std::fs::write(
+ config.path(),
+ "[gc]\nenable = true\n[gc.experimental_soft_drop]\nenable = true\nretention = \"1d\"\n",
+ )
+ .unwrap();
+
+ let options =
+ GreptimeOptions::::load_layered_options(config.path().to_str(), "")
+ .unwrap();
+ assert!(options.component.gc.experimental_soft_drop.enable);
+ assert_eq!(
+ Duration::from_secs(24 * 60 * 60),
+ options.component.gc.experimental_soft_drop.retention
+ );
+}
+
#[test]
fn test_load_flownode_example_config() {
let example_config = common_test_util::find_workspace_path("config/flownode.example.toml");
diff --git a/src/common/function/src/admin.rs b/src/common/function/src/admin.rs
index c31b608967..02fd32bd77 100644
--- a/src/common/function/src/admin.rs
+++ b/src/common/function/src/admin.rs
@@ -17,6 +17,7 @@ mod flush_compact_region;
mod flush_compact_table;
mod gc;
mod migrate_region;
+mod purge_table;
mod reconcile_catalog;
mod reconcile_database;
mod reconcile_table;
@@ -25,6 +26,7 @@ use flush_compact_region::{CompactRegionFunction, FlushRegionFunction};
use flush_compact_table::{CompactTableFunction, FlushTableFunction};
use gc::{GcRegionsFunction, GcTableFunction};
use migrate_region::MigrateRegionFunction;
+use purge_table::PurgeTableFunction;
use reconcile_catalog::ReconcileCatalogFunction;
use reconcile_database::ReconcileDatabaseFunction;
use reconcile_table::ReconcileTableFunction;
@@ -52,4 +54,9 @@ impl AdminFunction {
registry.register(ReconcileDatabaseFunction::factory());
registry.register(ReconcileTableFunction::factory());
}
+
+ /// Register functions that must only be resolved by an ADMIN statement.
+ pub fn register_admin_only(registry: &FunctionRegistry) {
+ registry.register(PurgeTableFunction::factory());
+ }
}
diff --git a/src/common/function/src/admin/gc.rs b/src/common/function/src/admin/gc.rs
index b5e067ce72..e9ba33a050 100644
--- a/src/common/function/src/admin/gc.rs
+++ b/src/common/function/src/admin/gc.rs
@@ -266,6 +266,14 @@ mod tests {
#[async_trait]
impl ProcedureServiceHandler for MockProcedureServiceHandler {
+ async fn purge_table(
+ &self,
+ _table_name: table::table_name::TableName,
+ _query_ctx: QueryContextRef,
+ ) -> Result<()> {
+ unreachable!()
+ }
+
async fn migrate_region(&self, _request: MigrateRegionRequest) -> Result