risingwave_meta_model_migration/
m20261013_000000_backfill_iceberg_writer_fragment_flag.rs1use prost::Message;
16use risingwave_pb::id::FragmentId;
17use risingwave_pb::stream_plan::StreamNode;
18use risingwave_pb::stream_plan::stream_node::NodeBody;
19use sea_orm::{FromQueryResult, Statement};
20use sea_orm_migration::prelude::*;
21use thiserror_ext::AsReport;
22
23const ICEBERG_WITH_PK_INDEX_WRITER_FLAG: i32 = 1 << 18;
25
26#[derive(DeriveMigrationName)]
27pub struct Migration;
28
29#[async_trait::async_trait]
30impl MigrationTrait for Migration {
31 async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
32 let connection = manager.get_connection();
33 let database_backend = connection.get_database_backend();
34 let (sql, values) = Query::select()
35 .columns([
36 Fragment::FragmentId,
37 Fragment::FragmentTypeMask,
38 Fragment::StreamNode,
39 ])
40 .from(Fragment::Table)
41 .build_any(&*database_backend.get_query_builder());
42
43 for row in connection
44 .query_all(Statement::from_sql_and_values(
45 database_backend,
46 sql,
47 values,
48 ))
49 .await?
50 {
51 let fragment = FragmentEntity::from_query_result(&row, "")?;
52 if fragment.fragment_type_mask & ICEBERG_WITH_PK_INDEX_WRITER_FLAG != 0 {
53 continue;
54 }
55 let stream_node =
56 StreamNode::decode(fragment.stream_node.as_slice()).map_err(|err| {
57 DbErr::Custom(format!(
58 "failed to decode stream node for fragment {}: {}",
59 fragment.fragment_id,
60 err.as_report()
61 ))
62 })?;
63 if contains_iceberg_writer(&stream_node) {
64 manager
65 .exec_stmt(
66 Query::update()
67 .table(Fragment::Table)
68 .value(
69 Fragment::FragmentTypeMask,
70 fragment.fragment_type_mask | ICEBERG_WITH_PK_INDEX_WRITER_FLAG,
71 )
72 .and_where(Expr::col(Fragment::FragmentId).eq(fragment.fragment_id))
73 .to_owned(),
74 )
75 .await?;
76 }
77 }
78 Ok(())
79 }
80
81 async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
82 Err(DbErr::Custom(
83 "cannot rollback Iceberg writer fragment flag migration".to_owned(),
84 ))
85 }
86}
87
88fn contains_iceberg_writer(node: &StreamNode) -> bool {
89 matches!(node.node_body, Some(NodeBody::IcebergWithPkIndexWriter(_)))
90 || node.input.iter().any(contains_iceberg_writer)
91}
92
93#[derive(Debug, FromQueryResult)]
94#[sea_orm(entity = "Fragment")]
95struct FragmentEntity {
96 fragment_id: FragmentId,
97 fragment_type_mask: i32,
98 stream_node: Vec<u8>,
99}
100
101#[derive(DeriveIden)]
102enum Fragment {
103 Table,
104 FragmentId,
105 FragmentTypeMask,
106 StreamNode,
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn finds_nested_iceberg_writer() {
115 let node = StreamNode {
116 input: vec![StreamNode {
117 node_body: Some(NodeBody::IcebergWithPkIndexWriter(Default::default())),
118 ..Default::default()
119 }],
120 ..Default::default()
121 };
122 assert!(contains_iceberg_writer(&node));
123 assert!(!contains_iceberg_writer(&StreamNode::default()));
124 }
125}