Skip to content

Commit fe064d2

Browse files
committed
feat(index): support dry_run in drop_global_index
1 parent 07238cd commit fe064d2

3 files changed

Lines changed: 166 additions & 12 deletions

File tree

crates/integrations/datafusion/src/procedures.rs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
//! - `CALL sys.create_global_index(table => '...', index_column => '...', index_type => 'btree')`
2727
//! - `CALL sys.create_global_index(table => '...', index_column => '...', index_type => 'bitmap')`
2828
//! - `CALL sys.create_global_index(table => '...', index_column => '...', index_type => 'ivf-pq')`
29-
//! - `CALL sys.drop_global_index(table => '...', index_column => '...', index_type => 'btree')` (also 'bitmap', 'lumina', or a vindex type such as 'ivf-pq')
29+
//! - `CALL sys.drop_global_index(table => '...', index_column => '...', index_type => 'btree')` (also 'bitmap', 'lumina', or a vindex type such as 'ivf-pq'; optional `dry_run => true` previews the matched count without committing)
3030
//! - `CALL sys.create_lumina_index(table => '...', index_column => '...')`
3131
3232
use std::collections::HashMap;
@@ -599,17 +599,27 @@ async fn proc_drop_global_index(
599599
"drop_global_index partitions are not supported yet".to_string(),
600600
));
601601
}
602-
if args.contains_key("dry_run") {
603-
return Err(DataFusionError::NotImplemented(
604-
"drop_global_index dry_run is not supported yet".to_string(),
605-
));
606-
}
602+
let dry_run = match args.get("dry_run") {
603+
None => false,
604+
Some(v) if v.eq_ignore_ascii_case("true") => true,
605+
Some(v) if v.eq_ignore_ascii_case("false") => false,
606+
Some(v) => {
607+
return Err(DataFusionError::Plan(format!(
608+
"drop_global_index dry_run must be 'true' or 'false', got '{v}'"
609+
)));
610+
}
611+
};
607612

608613
let mut builder = table.new_global_index_drop_builder();
609614
builder.with_index_column(index_column);
610615
builder.with_index_type(index_type);
611-
builder.execute().await.map_err(to_datafusion_error)?;
612-
ok_result(ctx)
616+
builder.with_dry_run(dry_run);
617+
let matched = builder.execute().await.map_err(to_datafusion_error)?;
618+
if dry_run {
619+
message_result(ctx, format!("Would drop {matched} global index file(s)"))
620+
} else {
621+
ok_result(ctx)
622+
}
613623
}
614624

615625
fn is_sorted_global_index_type(index_type: &str) -> bool {
@@ -636,15 +646,16 @@ fn parse_key_value_options(options: &str) -> DFResult<HashMap<String, String>> {
636646
}
637647

638648
fn ok_result(ctx: &SessionContext) -> DFResult<DataFrame> {
649+
message_result(ctx, "OK".to_string())
650+
}
651+
652+
fn message_result(ctx: &SessionContext, message: String) -> DFResult<DataFrame> {
639653
let schema = Arc::new(Schema::new(vec![Field::new(
640654
"result",
641655
ArrowDataType::Utf8,
642656
false,
643657
)]));
644-
let batch = RecordBatch::try_new(
645-
schema.clone(),
646-
vec![Arc::new(StringArray::from(vec!["OK"]))],
647-
)?;
658+
let batch = RecordBatch::try_new(schema, vec![Arc::new(StringArray::from(vec![message]))])?;
648659
ctx.read_batch(batch)
649660
}
650661

crates/integrations/datafusion/tests/procedures.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,60 @@ async fn test_drop_global_index_removes_btree_and_reads_fallback() {
325325
assert_eq!(rows, vec![(2, "bob".to_string()), (3, "carol".to_string())]);
326326
}
327327

328+
#[tokio::test]
329+
async fn test_drop_global_index_dry_run_previews_without_removing() {
330+
let (_tmp, sql_context) = setup_btree_global_index_table("btree_dry_run").await;
331+
exec(
332+
&sql_context,
333+
"INSERT INTO paimon.test_db.btree_dry_run (id, name) VALUES (1, 'alice'), (2, 'bob')",
334+
)
335+
.await;
336+
exec(
337+
&sql_context,
338+
"CALL sys.create_global_index(table => 'test_db.btree_dry_run', index_column => 'id')",
339+
)
340+
.await;
341+
342+
// dry-run: must NOT remove the index.
343+
exec(
344+
&sql_context,
345+
"CALL sys.drop_global_index(table => 'test_db.btree_dry_run', index_column => 'id', index_type => 'btree', dry_run => true)",
346+
)
347+
.await;
348+
let after_dry = row_count(
349+
&sql_context,
350+
"SELECT * FROM paimon.test_db.`btree_dry_run$table_indexes` \
351+
WHERE index_type = 'btree' AND index_field_name = 'id'",
352+
)
353+
.await;
354+
assert_eq!(after_dry, 1);
355+
356+
// real drop removes it.
357+
exec(
358+
&sql_context,
359+
"CALL sys.drop_global_index(table => 'test_db.btree_dry_run', index_column => 'id', index_type => 'btree')",
360+
)
361+
.await;
362+
let after_real = row_count(
363+
&sql_context,
364+
"SELECT * FROM paimon.test_db.`btree_dry_run$table_indexes` \
365+
WHERE index_type = 'btree' AND index_field_name = 'id'",
366+
)
367+
.await;
368+
assert_eq!(after_real, 0);
369+
}
370+
371+
#[tokio::test]
372+
async fn test_drop_global_index_rejects_invalid_dry_run() {
373+
let (_tmp, sql_context) = setup_btree_global_index_table("btree_bad_dry_run").await;
374+
assert_sql_error(
375+
&sql_context,
376+
"CALL sys.drop_global_index(table => 'test_db.btree_bad_dry_run', index_column => 'id', dry_run => 'maybe')",
377+
"dry_run",
378+
)
379+
.await;
380+
}
381+
328382
#[tokio::test]
329383
async fn test_drop_global_index_removes_bitmap() {
330384
let (_tmp, sql_context) = setup_btree_global_index_table("bitmap_drop").await;

crates/paimon/src/table/global_index_drop_builder.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub struct GlobalIndexDropBuilder<'a> {
2828
table: &'a Table,
2929
index_column: Option<String>,
3030
index_type: String,
31+
dry_run: bool,
3132
}
3233

3334
impl<'a> GlobalIndexDropBuilder<'a> {
@@ -36,6 +37,7 @@ impl<'a> GlobalIndexDropBuilder<'a> {
3637
table,
3738
index_column: None,
3839
index_type: BTREE_GLOBAL_INDEX_TYPE.to_string(),
40+
dry_run: false,
3941
}
4042
}
4143

@@ -49,6 +51,11 @@ impl<'a> GlobalIndexDropBuilder<'a> {
4951
self
5052
}
5153

54+
pub fn with_dry_run(&mut self, dry_run: bool) -> &mut Self {
55+
self.dry_run = dry_run;
56+
self
57+
}
58+
5259
pub async fn execute(&self) -> Result<usize> {
5360
let index_type =
5461
normalize_global_index_type_for_drop(&self.index_type).ok_or_else(|| {
@@ -108,6 +115,9 @@ impl<'a> GlobalIndexDropBuilder<'a> {
108115
.or_default()
109116
.push(entry.index_file);
110117
}
118+
if self.dry_run {
119+
return Ok(dropped);
120+
}
111121
if dropped == 0 {
112122
return Ok(0);
113123
}
@@ -477,4 +487,83 @@ mod tests {
477487
if message.contains("unsupported global index type") && message.contains("ivf-pq")
478488
));
479489
}
490+
491+
#[tokio::test]
492+
async fn test_dry_run_previews_without_committing() {
493+
let table = test_table("memory:/test_drop_dry_run_preview");
494+
setup_dirs(&table).await;
495+
496+
let mut message = CommitMessage::new(
497+
BinaryRow::new(0).to_serialized_bytes(),
498+
0,
499+
vec![data_file("data-0.parquet")],
500+
);
501+
message.new_index_files = vec![global_index_file(
502+
BTREE_GLOBAL_INDEX_TYPE,
503+
"btree-id.index",
504+
0,
505+
0,
506+
9,
507+
)];
508+
TableCommit::new(table.clone(), "test-user".to_string())
509+
.commit(vec![message])
510+
.await
511+
.unwrap();
512+
513+
// dry-run: returns the matched count, commits nothing.
514+
let would_drop = table
515+
.new_global_index_drop_builder()
516+
.with_index_column("id")
517+
.with_dry_run(true)
518+
.execute()
519+
.await
520+
.unwrap();
521+
assert_eq!(would_drop, 1);
522+
523+
// Entry is still present (no commit happened).
524+
let after_dry = latest_index_entries(&table)
525+
.await
526+
.into_iter()
527+
.map(|e| e.index_file.file_name)
528+
.collect::<Vec<_>>();
529+
assert_eq!(after_dry, vec!["btree-id.index".to_string()]);
530+
531+
// A real drop (dry_run defaults false) removes it.
532+
let dropped = table
533+
.new_global_index_drop_builder()
534+
.with_index_column("id")
535+
.execute()
536+
.await
537+
.unwrap();
538+
assert_eq!(dropped, 1);
539+
assert!(latest_index_entries(&table).await.is_empty());
540+
}
541+
542+
#[tokio::test]
543+
async fn test_dry_run_without_match_returns_zero() {
544+
let table = test_table("memory:/test_drop_dry_run_no_match");
545+
setup_dirs(&table).await;
546+
547+
let mut message = CommitMessage::new(vec![], 0, vec![data_file("data-0.parquet")]);
548+
message.new_index_files = vec![global_index_file(
549+
BTREE_GLOBAL_INDEX_TYPE,
550+
"btree-name.index",
551+
1,
552+
0,
553+
9,
554+
)];
555+
TableCommit::new(table.clone(), "test-user".to_string())
556+
.commit(vec![message])
557+
.await
558+
.unwrap();
559+
560+
let would_drop = table
561+
.new_global_index_drop_builder()
562+
.with_index_column("id")
563+
.with_dry_run(true)
564+
.execute()
565+
.await
566+
.unwrap();
567+
assert_eq!(would_drop, 0);
568+
}
480569
}

0 commit comments

Comments
 (0)