Skip to main content

mito2/
request.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Worker requests.
16
17use std::collections::HashMap;
18use std::sync::Arc;
19use std::time::Instant;
20
21use api::helper::{
22    ColumnDataTypeWrapper, is_column_type_value_eq, is_semantic_type_eq, proto_value_type,
23    proto_value_type_match,
24};
25use api::v1::column_def::options_from_column_schema;
26use api::v1::{ColumnDataType, ColumnSchema, OpType, Rows, SemanticType, Value, WriteHint};
27use common_telemetry::info;
28use datatypes::arrow::array::{Array, ArrayRef};
29use datatypes::arrow::datatypes::DataType as ArrowDataType;
30use datatypes::arrow::record_batch::RecordBatch;
31use datatypes::prelude::DataType;
32use partition::expr::PartitionExpr;
33use prometheus::HistogramTimer;
34use prost::Message;
35use smallvec::SmallVec;
36use snafu::{OptionExt, ResultExt, ensure};
37use store_api::ManifestVersion;
38use store_api::codec::{PrimaryKeyEncoding, infer_primary_key_encoding_from_hint};
39use store_api::metadata::{ColumnMetadata, RegionMetadata, RegionMetadataRef};
40use store_api::region_engine::{
41    MitoCopyRegionFromResponse, SetRegionRoleStateResponse, SettableRegionRoleState,
42};
43use store_api::region_request::{
44    AffectedRows, ApplyStagingManifestRequest, EnterStagingRequest, RegionAlterRequest,
45    RegionBuildIndexRequest, RegionBulkInsertsRequest, RegionCatchupRequest, RegionCleanUpRequest,
46    RegionCloseRequest, RegionCompactRequest, RegionCreateRequest, RegionDropRequest,
47    RegionFlushRequest, RegionOpenRequest, RegionRequest, RegionTruncateRequest,
48    StagingPartitionDirective,
49};
50use store_api::storage::consts::PRIMARY_KEY_COLUMN_NAME;
51use store_api::storage::{FileId, RegionId, SequenceNumber};
52use tokio::sync::oneshot::{self, Receiver, Sender};
53
54use crate::compaction::{CompactionExecution, CompactionPickFinished};
55use crate::error::{
56    CompactRegionSnafu, ConvertColumnDataTypeSnafu, CreateDefaultSnafu, Error, FillDefaultSnafu,
57    FlushRegionSnafu, InvalidPartitionExprSnafu, InvalidRequestSnafu, MissingPartitionExprSnafu,
58    Result, UnexpectedSnafu,
59};
60use crate::flush::FlushReason;
61use crate::manifest::action::{RegionEdit, TruncateKind};
62use crate::memtable::MemtableId;
63use crate::memtable::bulk::part::BulkPart;
64use crate::metrics::COMPACTION_ELAPSED_TOTAL;
65use crate::region::MitoRegionRef;
66use crate::region::options::RegionOptions;
67use crate::sst::file::FileMeta;
68use crate::sst::index::{IndexBuildType, ResultMpscSender};
69use crate::wal::EntryId;
70use crate::wal::entry_distributor::WalEntryReceiver;
71
72/// Request to write a region.
73#[derive(Debug)]
74pub struct WriteRequest {
75    /// Region to write.
76    pub region_id: RegionId,
77    /// Type of the write request.
78    pub op_type: OpType,
79    /// Rows to write.
80    pub rows: Rows,
81    /// Map column name to column index in `rows`.
82    pub name_to_index: HashMap<String, usize>,
83    /// Whether each column has null.
84    pub has_null: Vec<bool>,
85    /// Whether this insert should skip WAL. Never applies to deletes.
86    pub skip_wal: bool,
87    /// Write hint.
88    pub hint: Option<WriteHint>,
89    /// Region metadata on the time of this request is created.
90    pub(crate) region_metadata: Option<RegionMetadataRef>,
91    /// Partition expression version for the region.
92    pub partition_expr_version: Option<u64>,
93}
94
95impl WriteRequest {
96    /// Creates a new request.
97    ///
98    /// Returns `Err` if `rows` are invalid.
99    pub fn new(
100        region_id: RegionId,
101        op_type: OpType,
102        rows: Rows,
103        region_metadata: Option<RegionMetadataRef>,
104    ) -> Result<WriteRequest> {
105        let mut name_to_index = HashMap::with_capacity(rows.schema.len());
106        for (index, column) in rows.schema.iter().enumerate() {
107            ensure!(
108                name_to_index
109                    .insert(column.column_name.clone(), index)
110                    .is_none(),
111                InvalidRequestSnafu {
112                    region_id,
113                    reason: format!("duplicate column {}", column.column_name),
114                }
115            );
116        }
117
118        let mut has_null = vec![false; rows.schema.len()];
119        for row in &rows.rows {
120            ensure!(
121                row.values.len() == rows.schema.len(),
122                InvalidRequestSnafu {
123                    region_id,
124                    reason: format!(
125                        "row has {} columns but schema has {}",
126                        row.values.len(),
127                        rows.schema.len()
128                    ),
129                }
130            );
131
132            for (i, (value, column_schema)) in row.values.iter().zip(&rows.schema).enumerate() {
133                validate_proto_value(region_id, value, column_schema)?;
134
135                if value.value_data.is_none() {
136                    has_null[i] = true;
137                }
138            }
139        }
140
141        Ok(WriteRequest {
142            region_id,
143            op_type,
144            rows,
145            name_to_index,
146            has_null,
147            hint: None,
148            skip_wal: false,
149            region_metadata,
150            partition_expr_version: None,
151        })
152    }
153
154    /// Sets the request-level WAL policy.
155    pub fn with_skip_wal(mut self, skip_wal: bool) -> Self {
156        self.skip_wal = skip_wal;
157        self
158    }
159
160    /// Sets the write hint.
161    pub fn with_hint(mut self, hint: Option<WriteHint>) -> Self {
162        self.hint = hint;
163        self
164    }
165
166    pub fn with_partition_expr_version(mut self, partition_expr_version: Option<u64>) -> Self {
167        self.partition_expr_version = partition_expr_version;
168        self
169    }
170
171    /// Returns the encoding hint.
172    pub fn primary_key_encoding(&self) -> PrimaryKeyEncoding {
173        infer_primary_key_encoding_from_hint(self.hint.as_ref())
174    }
175
176    /// Returns estimated size of the request.
177    pub(crate) fn estimated_size(&self) -> usize {
178        let row_size = self
179            .rows
180            .rows
181            .first()
182            .map(|row| row.encoded_len())
183            .unwrap_or(0);
184        row_size * self.rows.rows.len()
185    }
186
187    /// Gets column index by name.
188    pub fn column_index_by_name(&self, name: &str) -> Option<usize> {
189        self.name_to_index.get(name).copied()
190    }
191
192    /// Checks schema of rows is compatible with schema of the region.
193    ///
194    /// If column with default value is missing, it returns a special [FillDefault](crate::error::Error::FillDefault)
195    /// error.
196    pub(crate) fn check_schema(&self, metadata: &RegionMetadata) -> Result<()> {
197        debug_assert_eq!(self.region_id, metadata.region_id);
198
199        let region_id = self.region_id;
200        // Index all columns in rows.
201        let rows_columns = self
202            .rows
203            .schema
204            .iter()
205            .map(|schema| {
206                let column = RowColumn {
207                    request: self,
208                    schema,
209                };
210                (schema.column_name.as_str(), column)
211            })
212            .collect();
213        let need_fill_default =
214            check_request_columns(region_id, metadata, rows_columns, |column| {
215                self.check_missing_column(column)
216            })?;
217
218        // If we need to fill default values, return a special error.
219        ensure!(!need_fill_default, FillDefaultSnafu { region_id });
220
221        Ok(())
222    }
223
224    /// Tries to fill missing columns.
225    ///
226    /// Currently, our protobuf format might be inefficient when we need to fill lots of null
227    /// values.
228    pub(crate) fn fill_missing_columns(&mut self, metadata: &RegionMetadata) -> Result<()> {
229        debug_assert_eq!(self.region_id, metadata.region_id);
230
231        let mut columns_to_fill = vec![];
232        for column in &metadata.column_metadatas {
233            if !self.name_to_index.contains_key(&column.column_schema.name) {
234                columns_to_fill.push(column);
235            }
236        }
237        self.fill_columns(columns_to_fill)?;
238
239        Ok(())
240    }
241
242    /// Checks the schema and fill missing columns.
243    pub(crate) fn maybe_fill_missing_columns(&mut self, metadata: &RegionMetadata) -> Result<()> {
244        // Removes the columns filled for an older schema, so the request is checked
245        // as sent and filled again for this schema.
246        let num_columns = self.name_to_index.len();
247        if self.rows.schema.len() > num_columns {
248            self.rows.schema.truncate(num_columns);
249            for row in &mut self.rows.rows {
250                row.values.truncate(num_columns);
251            }
252        }
253        if let Err(e) = self.check_schema(metadata) {
254            if e.is_fill_default() {
255                // TODO(yingwen): Add metrics for this case.
256                // We need to fill default value. The write request may be a request
257                // sent before changing the schema.
258                self.fill_missing_columns(metadata)?;
259            } else {
260                return Err(e);
261            }
262        }
263
264        Ok(())
265    }
266
267    /// Fills default value for specific `columns`.
268    fn fill_columns(&mut self, columns: Vec<&ColumnMetadata>) -> Result<()> {
269        let mut default_values = Vec::with_capacity(columns.len());
270        let mut columns_to_fill = Vec::with_capacity(columns.len());
271        for column in columns {
272            let default_value = self.column_default_value(column)?;
273            if default_value.value_data.is_some() {
274                default_values.push(default_value);
275                columns_to_fill.push(column);
276            }
277        }
278
279        for row in &mut self.rows.rows {
280            row.values.extend(default_values.iter().cloned());
281        }
282
283        for column in columns_to_fill {
284            let (datatype, datatype_ext) =
285                ColumnDataTypeWrapper::try_from(column.column_schema.data_type.clone())
286                    .with_context(|_| ConvertColumnDataTypeSnafu {
287                        reason: format!(
288                            "no protobuf type for column {} ({:?})",
289                            column.column_schema.name, column.column_schema.data_type
290                        ),
291                    })?
292                    .to_parts();
293            self.rows.schema.push(ColumnSchema {
294                column_name: column.column_schema.name.clone(),
295                datatype: datatype as i32,
296                semantic_type: column.semantic_type as i32,
297                datatype_extension: datatype_ext,
298                options: options_from_column_schema(&column.column_schema),
299            });
300        }
301
302        Ok(())
303    }
304
305    /// Checks whether we should allow a row doesn't provide this column.
306    fn check_missing_column(&self, column: &ColumnMetadata) -> Result<()> {
307        if self.op_type == OpType::Delete {
308            if column.semantic_type == SemanticType::Field {
309                // For delete request, all tags and timestamp is required. We don't fill default
310                // tag or timestamp while deleting rows.
311                return Ok(());
312            } else {
313                return InvalidRequestSnafu {
314                    region_id: self.region_id,
315                    reason: format!("delete requests need column {}", column.column_schema.name),
316                }
317                .fail();
318            }
319        }
320
321        // Not a delete request. Checks whether they have default value.
322        check_column_can_be_filled(self.region_id, column)
323    }
324
325    /// Returns the default value for specific column.
326    fn column_default_value(&self, column: &ColumnMetadata) -> Result<Value> {
327        let default_value = match self.op_type {
328            OpType::Delete => {
329                ensure!(
330                    column.semantic_type == SemanticType::Field,
331                    InvalidRequestSnafu {
332                        region_id: self.region_id,
333                        reason: format!(
334                            "delete requests need column {}",
335                            column.column_schema.name
336                        ),
337                    }
338                );
339
340                // For delete request, we need a default value for padding so we
341                // can delete a row even a field doesn't have a default value. So the
342                // value doesn't need to following the default value constraint of the
343                // column.
344                if column.column_schema.is_nullable() {
345                    datatypes::value::Value::Null
346                } else {
347                    column.column_schema.data_type.default_value()
348                }
349            }
350            OpType::Put => {
351                // For put requests, we use the default value from column schema.
352                if column.column_schema.is_default_impure() {
353                    UnexpectedSnafu {
354                        reason: format!(
355                            "unexpected impure default value with region_id: {}, column: {}, default_value: {:?}",
356                            self.region_id,
357                            column.column_schema.name,
358                            column.column_schema.default_constraint(),
359                        ),
360                    }
361                    .fail()?
362                }
363                column
364                    .column_schema
365                    .create_default()
366                    .context(CreateDefaultSnafu {
367                        region_id: self.region_id,
368                        column: &column.column_schema.name,
369                    })?
370                    // This column doesn't have default value.
371                    .with_context(|| InvalidRequestSnafu {
372                        region_id: self.region_id,
373                        reason: format!(
374                            "column {} does not have default value",
375                            column.column_schema.name
376                        ),
377                    })?
378            }
379        };
380
381        // Convert default value into proto's value.
382        Ok(api::helper::to_grpc_value(default_value))
383    }
384}
385
386/// A column of a write request, as [check_request_columns] sees it.
387trait RequestColumn {
388    /// Returns whether the column has the data type of `column`.
389    fn has_type_of(&self, column: &ColumnMetadata) -> bool;
390
391    /// Describes the data type of the column in errors.
392    fn type_name(&self) -> String;
393
394    /// Returns the semantic type of the column if the request carries one.
395    fn semantic_type(&self) -> Option<i32>;
396
397    /// Returns whether the column contains nulls.
398    fn has_null(&self) -> bool;
399}
400
401/// Checks the columns of a write request against the region schema. Each column
402/// must be a column of the region, have its data type and, if the request carries
403/// one, its semantic type, and contain nulls only if the region column is
404/// nullable. `check_missing` checks the region columns that the request doesn't
405/// have. Returns whether there are such columns.
406fn check_request_columns<C: RequestColumn>(
407    region_id: RegionId,
408    metadata: &RegionMetadata,
409    mut columns: HashMap<&str, C>,
410    mut check_missing: impl FnMut(&ColumnMetadata) -> Result<()>,
411) -> Result<bool> {
412    let mut has_missing = false;
413    for column in &metadata.column_metadatas {
414        let Some(input_col) = columns.remove(column.column_schema.name.as_str()) else {
415            check_missing(column)?;
416            has_missing = true;
417            continue;
418        };
419        // Check data type.
420        ensure!(
421            input_col.has_type_of(column),
422            InvalidRequestSnafu {
423                region_id,
424                reason: format!(
425                    "column {} expect type {:?}, given: {}",
426                    column.column_schema.name,
427                    column.column_schema.data_type,
428                    input_col.type_name(),
429                )
430            }
431        );
432
433        // Check semantic type.
434        if let Some(semantic_type) = input_col.semantic_type() {
435            ensure!(
436                is_semantic_type_eq(semantic_type, column.semantic_type),
437                InvalidRequestSnafu {
438                    region_id,
439                    reason: format!(
440                        "column {} has semantic type {:?}, given: {}({})",
441                        column.column_schema.name,
442                        column.semantic_type,
443                        api::v1::SemanticType::try_from(semantic_type)
444                            .map(|v| v.as_str_name())
445                            .unwrap_or("Unknown"),
446                        semantic_type
447                    ),
448                }
449            );
450        }
451
452        // Check nullable.
453        ensure!(
454            !input_col.has_null() || column.column_schema.is_nullable(),
455            InvalidRequestSnafu {
456                region_id,
457                reason: format!(
458                    "column {} is not null but input has null",
459                    column.column_schema.name
460                ),
461            }
462        );
463    }
464
465    // Checks all columns in the request exist in the region.
466    if !columns.is_empty() {
467        let names: Vec<_> = columns.into_keys().collect();
468        return InvalidRequestSnafu {
469            region_id,
470            reason: format!("unknown columns: {:?}", names),
471        }
472        .fail();
473    }
474
475    Ok(has_missing)
476}
477
478/// Checks that a write may leave out `column`, which is then filled with its
479/// default value or null.
480fn check_column_can_be_filled(region_id: RegionId, column: &ColumnMetadata) -> Result<()> {
481    ensure!(
482        column.column_schema.is_nullable() || column.column_schema.default_constraint().is_some(),
483        InvalidRequestSnafu {
484            region_id,
485            reason: format!("missing column {}", column.column_schema.name),
486        }
487    );
488    Ok(())
489}
490
491/// A column of the rows of a [WriteRequest].
492struct RowColumn<'a> {
493    request: &'a WriteRequest,
494    schema: &'a ColumnSchema,
495}
496
497impl RequestColumn for RowColumn<'_> {
498    fn has_type_of(&self, column: &ColumnMetadata) -> bool {
499        is_column_type_value_eq(
500            self.schema.datatype,
501            self.schema.datatype_extension.clone(),
502            &column.column_schema.data_type,
503        )
504    }
505
506    fn type_name(&self) -> String {
507        format!(
508            "{}({})",
509            ColumnDataType::try_from(self.schema.datatype)
510                .map(|v| v.as_str_name())
511                .unwrap_or("Unknown"),
512            self.schema.datatype,
513        )
514    }
515
516    fn semantic_type(&self) -> Option<i32> {
517        Some(self.schema.semantic_type)
518    }
519
520    fn has_null(&self) -> bool {
521        // Safety: `name_to_index` has every column of the request.
522        self.request.has_null[self.request.name_to_index[&self.schema.column_name]]
523    }
524}
525
526/// A column of the batch of a bulk insert.
527struct BulkColumn<'a> {
528    array: &'a ArrayRef,
529    field: &'a datatypes::arrow::datatypes::Field,
530}
531
532impl RequestColumn for BulkColumn<'_> {
533    fn has_type_of(&self, column: &ColumnMetadata) -> bool {
534        let actual = self.array.data_type();
535        let data_type = &column.column_schema.data_type;
536        // The memtables convert tags of any string encoding.
537        if column.semantic_type == SemanticType::Tag && data_type.is_string() {
538            return match actual {
539                ArrowDataType::Dictionary(key, value) => key.is_integer() && value.is_string(),
540                actual => actual.is_string(),
541            };
542        }
543        if data_type.is_json2() && datatypes::extension::json::is_json2_extension_type(self.field) {
544            return matches!(actual, ArrowDataType::Struct(_) | ArrowDataType::Null)
545                && datatypes::extension::json::json2_remainder_field(self.field).is_ok()
546                && datatypes::types::json_type::JsonNativeType::try_from(actual).is_ok();
547        }
548        *actual == data_type.as_arrow_type()
549    }
550
551    fn type_name(&self) -> String {
552        self.array.data_type().to_string()
553    }
554
555    fn semantic_type(&self) -> Option<i32> {
556        None
557    }
558
559    fn has_null(&self) -> bool {
560        self.array.logical_null_count() > 0
561    }
562}
563
564/// Checks the batch of a bulk insert against the region schema with the rules
565/// [WriteRequest::check_schema] applies to rows: column names are unique, every
566/// column is a column of the region with its Arrow type and contains nulls only
567/// if the region column is nullable, and a missing column is nullable or has a
568/// default value. Tags may use any string encoding. Sparse batches may carry the
569/// tags in one encoded primary key column instead. Returns whether the batch
570/// misses columns that the writer fills.
571pub fn check_bulk_insert_batch(metadata: &RegionMetadata, batch: &RecordBatch) -> Result<bool> {
572    let region_id = metadata.region_id;
573    let sparse = metadata.primary_key_encoding == PrimaryKeyEncoding::Sparse;
574    let mut primary_key = None;
575    let mut columns = HashMap::with_capacity(batch.num_columns());
576    for (field, array) in batch.schema_ref().fields().iter().zip(batch.columns()) {
577        let duplicate = if sparse && field.name() == PRIMARY_KEY_COLUMN_NAME {
578            primary_key.replace(array).is_some()
579        } else {
580            columns
581                .insert(field.name().as_str(), BulkColumn { array, field })
582                .is_some()
583        };
584        ensure!(
585            !duplicate,
586            InvalidRequestSnafu {
587                region_id,
588                reason: format!("duplicate column {}", field.name()),
589            }
590        );
591    }
592    if let Some(primary_key) = primary_key {
593        // The writer casts the encoded primary key to a dictionary of binary.
594        let type_matches = match primary_key.data_type() {
595            ArrowDataType::Binary => true,
596            ArrowDataType::Dictionary(key, value) => {
597                key.is_integer() && **value == ArrowDataType::Binary
598            }
599            _ => false,
600        };
601        ensure!(
602            type_matches && primary_key.logical_null_count() == 0,
603            InvalidRequestSnafu {
604                region_id,
605                reason: format!(
606                    "column {PRIMARY_KEY_COLUMN_NAME} must be binary without nulls, given: {}",
607                    primary_key.data_type()
608                ),
609            }
610        );
611    }
612    let mut needs_fill = false;
613    check_request_columns(region_id, metadata, columns, |column| {
614        if primary_key.is_some() && column.semantic_type == SemanticType::Tag {
615            Ok(())
616        } else {
617            needs_fill = true;
618            check_column_can_be_filled(region_id, column)
619        }
620    })?;
621    Ok(needs_fill)
622}
623
624/// Validate proto value schema.
625pub(crate) fn validate_proto_value(
626    region_id: RegionId,
627    value: &Value,
628    column_schema: &ColumnSchema,
629) -> Result<()> {
630    if let Some(value_type) = proto_value_type(value) {
631        let column_type = ColumnDataType::try_from(column_schema.datatype).map_err(|_| {
632            InvalidRequestSnafu {
633                region_id,
634                reason: format!(
635                    "column {} has unknown type {}",
636                    column_schema.column_name, column_schema.datatype
637                ),
638            }
639            .build()
640        })?;
641        ensure!(
642            proto_value_type_match(column_type, value_type),
643            InvalidRequestSnafu {
644                region_id,
645                reason: format!(
646                    "value has type {:?}, but column {} has type {:?}({})",
647                    value_type, column_schema.column_name, column_type, column_schema.datatype,
648                ),
649            }
650        );
651    }
652
653    Ok(())
654}
655
656/// Oneshot output result sender.
657#[derive(Debug)]
658pub struct OutputTx(Sender<Result<AffectedRows>>);
659
660impl OutputTx {
661    /// Creates a new output sender.
662    pub(crate) fn new(sender: Sender<Result<AffectedRows>>) -> OutputTx {
663        OutputTx(sender)
664    }
665
666    /// Sends the `result`.
667    pub(crate) fn send(self, result: Result<AffectedRows>) {
668        // Ignores send result.
669        let _ = self.0.send(result);
670    }
671}
672
673/// Optional output result sender.
674#[derive(Debug)]
675pub(crate) struct OptionOutputTx(Option<OutputTx>);
676
677impl OptionOutputTx {
678    /// Creates a sender.
679    pub(crate) fn new(sender: Option<OutputTx>) -> OptionOutputTx {
680        OptionOutputTx(sender)
681    }
682
683    /// Creates an empty sender.
684    pub(crate) fn none() -> OptionOutputTx {
685        OptionOutputTx(None)
686    }
687
688    /// Sends the `result` and consumes the inner sender.
689    pub(crate) fn send_mut(&mut self, result: Result<AffectedRows>) {
690        if let Some(sender) = self.0.take() {
691            sender.send(result);
692        }
693    }
694
695    /// Sends the `result` and consumes the sender.
696    pub(crate) fn send(mut self, result: Result<AffectedRows>) {
697        if let Some(sender) = self.0.take() {
698            sender.send(result);
699        }
700    }
701
702    /// Takes the inner sender.
703    pub(crate) fn take_inner(&mut self) -> Option<OutputTx> {
704        self.0.take()
705    }
706}
707
708impl From<Sender<Result<AffectedRows>>> for OptionOutputTx {
709    fn from(sender: Sender<Result<AffectedRows>>) -> Self {
710        Self::new(Some(OutputTx::new(sender)))
711    }
712}
713
714impl OnFailure for OptionOutputTx {
715    fn on_failure(&mut self, err: Error) {
716        self.send_mut(Err(err));
717    }
718}
719
720/// Callback on failure.
721pub(crate) trait OnFailure {
722    /// Handles `err` on failure.
723    fn on_failure(&mut self, err: Error);
724}
725
726/// Sender and write request.
727#[derive(Debug)]
728pub(crate) struct SenderWriteRequest {
729    /// Result sender.
730    pub(crate) sender: OptionOutputTx,
731    pub(crate) request: WriteRequest,
732}
733
734pub(crate) struct SenderBulkRequest {
735    pub(crate) skip_wal: bool,
736    pub(crate) sender: OptionOutputTx,
737    pub(crate) region_id: RegionId,
738    pub(crate) request: BulkPart,
739    pub(crate) region_metadata: Option<RegionMetadataRef>,
740    pub(crate) partition_expr_version: Option<u64>,
741}
742
743#[derive(Debug)]
744pub(crate) struct BulkInsertRequest {
745    pub(crate) metadata: Option<RegionMetadataRef>,
746    pub(crate) request: RegionBulkInsertsRequest,
747    pub(crate) sender: OptionOutputTx,
748}
749
750/// Request sent to a worker with timestamp
751#[derive(Debug)]
752pub(crate) struct WorkerRequestWithTime {
753    pub(crate) request: WorkerRequest,
754    pub(crate) created_at: Instant,
755}
756
757impl WorkerRequestWithTime {
758    pub(crate) fn new(request: WorkerRequest) -> Self {
759        Self {
760            request,
761            created_at: Instant::now(),
762        }
763    }
764}
765
766/// Request sent to a worker
767#[derive(Debug)]
768pub(crate) enum WorkerRequest {
769    /// Write to a region.
770    Write(SenderWriteRequest),
771
772    /// Writes of one request to regions of the same worker.
773    BatchWrite(Vec<SenderWriteRequest>),
774
775    /// Ddl request to a region.
776    Ddl(SenderDdlRequest),
777
778    /// Notifications from internal background jobs.
779    Background {
780        /// Id of the region to send.
781        region_id: RegionId,
782        /// Internal notification.
783        notify: BackgroundNotify,
784    },
785
786    /// The internal commands.
787    SetRegionRoleStateGracefully {
788        /// Id of the region to send.
789        region_id: RegionId,
790        /// The [SettableRegionRoleState].
791        region_role_state: SettableRegionRoleState,
792        /// The sender of [SetReadonlyResponse].
793        sender: Sender<SetRegionRoleStateResponse>,
794    },
795
796    /// Notify a worker to stop.
797    Stop,
798
799    /// Use [RegionEdit] to edit a region directly.
800    EditRegion(RegionEditRequest),
801
802    /// Keep the manifest of a region up to date.
803    SyncRegion(RegionSyncRequest),
804
805    /// Bulk inserts request and region metadata.
806    BulkInserts(BulkInsertRequest),
807
808    /// Remap manifests request.
809    RemapManifests(RemapManifestsRequest),
810
811    /// Copy region from request.
812    CopyRegionFrom(CopyRegionFromRequest),
813}
814
815impl WorkerRequest {
816    /// Creates a new open region request.
817    pub(crate) fn new_open_region_request(
818        region_id: RegionId,
819        request: RegionOpenRequest,
820        entry_receiver: Option<WalEntryReceiver>,
821    ) -> (WorkerRequest, Receiver<Result<AffectedRows>>) {
822        let (sender, receiver) = oneshot::channel();
823
824        let worker_request = WorkerRequest::Ddl(SenderDdlRequest {
825            region_id,
826            sender: sender.into(),
827            request: DdlRequest::Open((request, entry_receiver)),
828        });
829
830        (worker_request, receiver)
831    }
832
833    /// Creates a new catchup region request.
834    pub(crate) fn new_catchup_region_request(
835        region_id: RegionId,
836        request: RegionCatchupRequest,
837        entry_receiver: Option<WalEntryReceiver>,
838    ) -> (WorkerRequest, Receiver<Result<AffectedRows>>) {
839        let (sender, receiver) = oneshot::channel();
840        let worker_request = WorkerRequest::Ddl(SenderDdlRequest {
841            region_id,
842            sender: sender.into(),
843            request: DdlRequest::Catchup((request, entry_receiver)),
844        });
845        (worker_request, receiver)
846    }
847
848    /// Converts request from a [RegionRequest].
849    pub(crate) fn try_from_region_request(
850        region_id: RegionId,
851        value: RegionRequest,
852        region_metadata: Option<RegionMetadataRef>,
853    ) -> Result<(WorkerRequest, Receiver<Result<AffectedRows>>)> {
854        let (sender, receiver) = oneshot::channel();
855        let worker_request = match value {
856            RegionRequest::Put(v) => {
857                let mut write_request =
858                    WriteRequest::new(region_id, OpType::Put, v.rows, region_metadata.clone())?
859                        .with_hint(v.hint)
860                        .with_skip_wal(v.skip_wal)
861                        .with_partition_expr_version(v.partition_expr_version);
862                if write_request.primary_key_encoding() == PrimaryKeyEncoding::Dense
863                    && let Some(region_metadata) = &region_metadata
864                {
865                    write_request.maybe_fill_missing_columns(region_metadata)?;
866                }
867                WorkerRequest::Write(SenderWriteRequest {
868                    sender: sender.into(),
869                    request: write_request,
870                })
871            }
872            RegionRequest::Delete(v) => {
873                let mut write_request =
874                    WriteRequest::new(region_id, OpType::Delete, v.rows, region_metadata.clone())?
875                        .with_hint(v.hint)
876                        .with_partition_expr_version(v.partition_expr_version);
877                if write_request.primary_key_encoding() == PrimaryKeyEncoding::Dense
878                    && let Some(region_metadata) = &region_metadata
879                {
880                    write_request.maybe_fill_missing_columns(region_metadata)?;
881                }
882                WorkerRequest::Write(SenderWriteRequest {
883                    sender: sender.into(),
884                    request: write_request,
885                })
886            }
887            RegionRequest::Create(v) => WorkerRequest::Ddl(SenderDdlRequest {
888                region_id,
889                sender: sender.into(),
890                request: DdlRequest::Create(v),
891            }),
892            RegionRequest::Drop(v) => WorkerRequest::Ddl(SenderDdlRequest {
893                region_id,
894                sender: sender.into(),
895                request: DdlRequest::Drop(v),
896            }),
897            RegionRequest::Open(v) => WorkerRequest::Ddl(SenderDdlRequest {
898                region_id,
899                sender: sender.into(),
900                request: DdlRequest::Open((v, None)),
901            }),
902            RegionRequest::CleanUp(v) => WorkerRequest::Ddl(SenderDdlRequest {
903                region_id,
904                sender: sender.into(),
905                request: DdlRequest::OfflineCleanup(v),
906            }),
907            RegionRequest::Close(v) => WorkerRequest::Ddl(SenderDdlRequest {
908                region_id,
909                sender: sender.into(),
910                request: DdlRequest::Close(v),
911            }),
912            RegionRequest::Alter(v) => WorkerRequest::Ddl(SenderDdlRequest {
913                region_id,
914                sender: sender.into(),
915                request: DdlRequest::Alter(v),
916            }),
917            RegionRequest::Flush(v) => WorkerRequest::Ddl(SenderDdlRequest {
918                region_id,
919                sender: sender.into(),
920                request: DdlRequest::Flush(v),
921            }),
922            RegionRequest::Compact(v) => WorkerRequest::Ddl(SenderDdlRequest {
923                region_id,
924                sender: sender.into(),
925                request: DdlRequest::Compact(v),
926            }),
927            RegionRequest::BuildIndex(v) => WorkerRequest::Ddl(SenderDdlRequest {
928                region_id,
929                sender: sender.into(),
930                request: DdlRequest::BuildIndex(v),
931            }),
932            RegionRequest::Truncate(v) => WorkerRequest::Ddl(SenderDdlRequest {
933                region_id,
934                sender: sender.into(),
935                request: DdlRequest::Truncate(v),
936            }),
937            RegionRequest::Catchup(v) => WorkerRequest::Ddl(SenderDdlRequest {
938                region_id,
939                sender: sender.into(),
940                request: DdlRequest::Catchup((v, None)),
941            }),
942            RegionRequest::EnterStaging(v) => WorkerRequest::Ddl(SenderDdlRequest {
943                region_id,
944                sender: sender.into(),
945                request: DdlRequest::EnterStaging(v),
946            }),
947            RegionRequest::BulkInserts(mut region_bulk_inserts_request) => {
948                // The worker answers empty batches without writing them.
949                if let Some(region_metadata) = &region_metadata
950                    && region_bulk_inserts_request.payload.num_rows() > 0
951                    && check_bulk_insert_batch(
952                        region_metadata,
953                        &region_bulk_inserts_request.payload,
954                    )?
955                {
956                    // Lets the worker fill the missing columns.
957                    region_bulk_inserts_request.aligned_schema_version = None;
958                }
959                WorkerRequest::BulkInserts(BulkInsertRequest {
960                    metadata: region_metadata,
961                    sender: sender.into(),
962                    request: region_bulk_inserts_request,
963                })
964            }
965            RegionRequest::ApplyStagingManifest(v) => WorkerRequest::Ddl(SenderDdlRequest {
966                region_id,
967                sender: sender.into(),
968                request: DdlRequest::ApplyStagingManifest(v),
969            }),
970        };
971
972        Ok((worker_request, receiver))
973    }
974
975    pub(crate) fn new_set_readonly_gracefully(
976        region_id: RegionId,
977        region_role_state: SettableRegionRoleState,
978    ) -> (WorkerRequest, Receiver<SetRegionRoleStateResponse>) {
979        let (sender, receiver) = oneshot::channel();
980
981        (
982            WorkerRequest::SetRegionRoleStateGracefully {
983                region_id,
984                region_role_state,
985                sender,
986            },
987            receiver,
988        )
989    }
990
991    pub(crate) fn new_sync_region_request(
992        region_id: RegionId,
993        manifest_version: ManifestVersion,
994    ) -> (WorkerRequest, Receiver<Result<(ManifestVersion, bool)>>) {
995        let (sender, receiver) = oneshot::channel();
996        (
997            WorkerRequest::SyncRegion(RegionSyncRequest {
998                region_id,
999                manifest_version,
1000                sender,
1001            }),
1002            receiver,
1003        )
1004    }
1005
1006    /// Converts [RemapManifestsRequest] from a [RemapManifestsRequest](store_api::region_engine::RemapManifestsRequest).
1007    ///
1008    /// # Errors
1009    ///
1010    /// Returns an error if the partition expression is invalid or missing.
1011    /// Returns an error if the new partition expressions are not found for some regions.
1012    #[allow(clippy::type_complexity)]
1013    pub(crate) fn try_from_remap_manifests_request(
1014        store_api::region_engine::RemapManifestsRequest {
1015            region_id,
1016            input_regions,
1017            region_mapping,
1018            new_partition_exprs,
1019        }: store_api::region_engine::RemapManifestsRequest,
1020    ) -> Result<(WorkerRequest, Receiver<Result<HashMap<RegionId, String>>>)> {
1021        let (sender, receiver) = oneshot::channel();
1022        let new_partition_exprs = new_partition_exprs
1023            .into_iter()
1024            .map(|(k, v)| {
1025                Ok((
1026                    k,
1027                    PartitionExpr::from_json_str(&v)
1028                        .context(InvalidPartitionExprSnafu { expr: v })?
1029                        .context(MissingPartitionExprSnafu { region_id: k })?,
1030                ))
1031            })
1032            .collect::<Result<HashMap<_, _>>>()?;
1033
1034        let request = RemapManifestsRequest {
1035            region_id,
1036            input_regions,
1037            region_mapping,
1038            new_partition_exprs,
1039            sender,
1040        };
1041
1042        Ok((WorkerRequest::RemapManifests(request), receiver))
1043    }
1044
1045    /// Converts [CopyRegionFromRequest] from a [MitoCopyRegionFromRequest](store_api::region_engine::MitoCopyRegionFromRequest).
1046    pub(crate) fn try_from_copy_region_from_request(
1047        region_id: RegionId,
1048        store_api::region_engine::MitoCopyRegionFromRequest {
1049            source_region_id,
1050            parallelism,
1051        }: store_api::region_engine::MitoCopyRegionFromRequest,
1052    ) -> Result<(WorkerRequest, Receiver<Result<MitoCopyRegionFromResponse>>)> {
1053        let (sender, receiver) = oneshot::channel();
1054        let request = CopyRegionFromRequest {
1055            region_id,
1056            source_region_id,
1057            parallelism,
1058            sender,
1059        };
1060        Ok((WorkerRequest::CopyRegionFrom(request), receiver))
1061    }
1062}
1063
1064/// DDL request to a region.
1065#[derive(Debug)]
1066pub(crate) enum DdlRequest {
1067    Create(RegionCreateRequest),
1068    Drop(RegionDropRequest),
1069    Open((RegionOpenRequest, Option<WalEntryReceiver>)),
1070    OfflineCleanup(RegionCleanUpRequest),
1071    Close(RegionCloseRequest),
1072    Alter(RegionAlterRequest),
1073    Flush(RegionFlushRequest),
1074    Compact(RegionCompactRequest),
1075    BuildIndex(RegionBuildIndexRequest),
1076    Truncate(RegionTruncateRequest),
1077    Catchup((RegionCatchupRequest, Option<WalEntryReceiver>)),
1078    EnterStaging(EnterStagingRequest),
1079    ApplyStagingManifest(ApplyStagingManifestRequest),
1080}
1081
1082/// Sender and Ddl request.
1083#[derive(Debug)]
1084pub(crate) struct SenderDdlRequest {
1085    /// Region id of the request.
1086    pub(crate) region_id: RegionId,
1087    /// Result sender.
1088    pub(crate) sender: OptionOutputTx,
1089    /// Ddl request.
1090    pub(crate) request: DdlRequest,
1091}
1092
1093/// Notification from a background job.
1094#[derive(Debug)]
1095pub(crate) enum BackgroundNotify {
1096    /// Registers a loaded region and its scheduling state before acknowledging open.
1097    RegionOpened {
1098        region: MitoRegionRef,
1099        registered: Sender<()>,
1100    },
1101    /// Local unit publication and resource completion events.
1102    CompactionUnit(CompactionUnitNotification),
1103    /// A DDL released by compaction has actually completed, including failure.
1104    CompactionDdlComplete {
1105        generation: u64,
1106        request_index: usize,
1107    },
1108    /// Compaction planning has finished.
1109    CompactionPickFinished(CompactionPickFinished),
1110    /// Flush has finished.
1111    FlushFinished(FlushFinished),
1112    /// Flush has failed.
1113    FlushFailed(FlushFailed),
1114    /// Index build has finished.
1115    IndexBuildFinished(IndexBuildFinished),
1116    /// Index build has been stopped (aborted or succeeded).
1117    IndexBuildStopped(IndexBuildStopped),
1118    /// An index build must be retried against the latest schema generation.
1119    IndexBuildRetry(BuildIndexRequest),
1120    /// Compaction has finished.
1121    CompactionFinished(CompactionFinished),
1122    /// Compaction has failed.
1123    CompactionFailed(CompactionFailed),
1124    /// Truncate result.
1125    Truncate(TruncateResult),
1126    /// Discard unflushed data result.
1127    DiscardUnflushed(DiscardUnflushedResult),
1128    /// Region change result.
1129    RegionChange(RegionChangeResult),
1130    /// Region edit result.
1131    RegionEdit(RegionEditResult),
1132    /// Enter staging result.
1133    EnterStaging(EnterStagingResult),
1134    /// Copy region result.
1135    CopyRegionFromFinished(CopyRegionFromFinished),
1136}
1137
1138/// Notifies a flush job is finished.
1139#[derive(Debug)]
1140pub(crate) struct FlushFinished {
1141    /// Region id.
1142    pub(crate) region_id: RegionId,
1143    /// Reason to flush.
1144    pub(crate) flush_reason: FlushReason,
1145    /// Entry id of flushed data.
1146    pub(crate) flushed_entry_id: EntryId,
1147    /// Flush result senders.
1148    pub(crate) senders: Vec<OutputTx>,
1149    /// Flush timer.
1150    pub(crate) _timer: HistogramTimer,
1151    /// Region edit to apply.
1152    pub(crate) edit: RegionEdit,
1153    /// Memtables to remove.
1154    pub(crate) memtables_to_remove: SmallVec<[MemtableId; 2]>,
1155}
1156
1157impl FlushFinished {
1158    /// Marks the flush job as successful and observes the timer.
1159    pub(crate) fn on_success(self) {
1160        for sender in self.senders {
1161            sender.send(Ok(0));
1162        }
1163    }
1164}
1165
1166impl OnFailure for FlushFinished {
1167    fn on_failure(&mut self, err: Error) {
1168        let err = Arc::new(err);
1169        for sender in self.senders.drain(..) {
1170            sender.send(Err(err.clone()).context(FlushRegionSnafu {
1171                region_id: self.region_id,
1172            }));
1173        }
1174    }
1175}
1176
1177/// Notifies a flush job is failed.
1178#[derive(Debug)]
1179pub(crate) struct FlushFailed {
1180    /// The error source of the failure.
1181    pub(crate) err: Arc<Error>,
1182}
1183
1184impl FlushFailed {
1185    /// Returns whether the flush was cancelled cooperatively.
1186    pub(crate) fn is_cancelled(&self) -> bool {
1187        matches!(self.err.as_ref(), Error::FlushCancelled { .. })
1188    }
1189}
1190
1191#[derive(Debug)]
1192pub(crate) struct IndexBuildFinished {
1193    pub(crate) manifest_version: ManifestVersion,
1194    pub(crate) file_meta: FileMeta,
1195    /// Waiters of the build, notified after the worker applies the edit.
1196    pub(crate) result_senders: Vec<ResultMpscSender>,
1197}
1198
1199/// Notifies an index build job has been stopped.
1200#[derive(Debug)]
1201pub(crate) struct IndexBuildStopped {
1202    pub(crate) file_id: FileId,
1203}
1204
1205/// A local unit event carries only identity, never an input reservation lease.
1206#[derive(Debug)]
1207pub(crate) enum CompactionUnitNotification {
1208    /// A persisted edit or error awaits worker apply or failure acknowledgement.
1209    Finished {
1210        plan_id: u64,
1211        result: std::result::Result<RegionEdit, Arc<Error>>,
1212        applied: tokio::sync::oneshot::Sender<()>,
1213    },
1214    /// The task has exited and released its owned resources.
1215    Released {
1216        /// Identifies the exited attempt, not the whole compaction request.
1217        plan_id: u64,
1218    },
1219}
1220
1221/// Notifies a compaction job has finished.
1222#[derive(Debug)]
1223pub(crate) struct CompactionFinished {
1224    /// Region id.
1225    pub(crate) region_id: RegionId,
1226    /// Identity and reservation lease of the accepted execution.
1227    pub(crate) execution: CompactionExecution,
1228    /// Compaction result senders.
1229    pub(crate) senders: Vec<OutputTx>,
1230    /// Start time of compaction task.
1231    pub(crate) start_time: Instant,
1232    /// Region edit to apply.
1233    pub(crate) edit: RegionEdit,
1234}
1235
1236impl CompactionFinished {
1237    pub fn on_success(self) {
1238        // only update compaction time on success
1239        COMPACTION_ELAPSED_TOTAL
1240            .with_label_values(&["remote"])
1241            .observe(self.start_time.elapsed().as_secs_f64());
1242
1243        for sender in self.senders {
1244            sender.send(Ok(0));
1245        }
1246        info!("Successfully compacted region: {}", self.region_id);
1247    }
1248}
1249
1250impl OnFailure for CompactionFinished {
1251    /// Compaction succeeded but failed to update manifest or region's already been dropped.
1252    fn on_failure(&mut self, err: Error) {
1253        let err = Arc::new(err);
1254        for sender in self.senders.drain(..) {
1255            sender.send(Err(err.clone()).context(CompactRegionSnafu {
1256                region_id: self.region_id,
1257            }));
1258        }
1259    }
1260}
1261
1262/// A failing compaction result.
1263#[derive(Debug)]
1264pub(crate) struct CompactionFailed {
1265    pub(crate) region_id: RegionId,
1266    /// Identity and reservation lease of the accepted execution.
1267    pub(crate) execution: CompactionExecution,
1268    /// The error source of the failure.
1269    pub(crate) err: Arc<Error>,
1270}
1271
1272/// Notifies the truncate result of a region.
1273#[derive(Debug)]
1274pub(crate) struct TruncateResult {
1275    /// Region id.
1276    pub(crate) region_id: RegionId,
1277    /// Result sender.
1278    pub(crate) sender: OptionOutputTx,
1279    /// Truncate result.
1280    pub(crate) result: Result<()>,
1281    pub(crate) kind: TruncateKind,
1282}
1283
1284/// Notifies the result of discarding unflushed data from a region.
1285#[derive(Debug)]
1286pub(crate) struct DiscardUnflushedResult {
1287    /// Region id.
1288    pub(crate) region_id: RegionId,
1289    /// Result sender.
1290    pub(crate) sender: OptionOutputTx,
1291    /// Manifest update result.
1292    pub(crate) result: Result<()>,
1293    /// Last WAL entry covered by the discard operation.
1294    pub(crate) discarded_entry_id: EntryId,
1295    /// Last sequence covered by the discard operation.
1296    pub(crate) discarded_sequence: SequenceNumber,
1297    /// Estimated number of discarded rows.
1298    pub(crate) discarded_rows: u64,
1299    /// Estimated number of discarded bytes.
1300    pub(crate) discarded_bytes: u64,
1301}
1302
1303/// Notifies the region the result of writing region change action.
1304#[derive(Debug)]
1305pub(crate) struct RegionChangeResult {
1306    /// Region id.
1307    pub(crate) region_id: RegionId,
1308    /// The new region metadata to apply.
1309    pub(crate) new_meta: RegionMetadataRef,
1310    /// Result sender.
1311    pub(crate) sender: OptionOutputTx,
1312    /// Result from the manifest manager.
1313    pub(crate) result: Result<()>,
1314    /// Used for index build in schema change.
1315    pub(crate) need_index: bool,
1316    /// New options for the region.
1317    pub(crate) new_options: Option<RegionOptions>,
1318}
1319
1320/// Notifies the region the result of entering staging.
1321#[derive(Debug)]
1322pub(crate) struct EnterStagingResult {
1323    /// Region id.
1324    pub(crate) region_id: RegionId,
1325    /// The new staging partition directive to apply.
1326    pub(crate) partition_directive: StagingPartitionDirective,
1327    /// Result sender.
1328    pub(crate) sender: OptionOutputTx,
1329    /// Result from the manifest manager.
1330    pub(crate) result: Result<()>,
1331}
1332
1333#[derive(Debug)]
1334pub(crate) struct CopyRegionFromFinished {
1335    /// Region id.
1336    pub(crate) region_id: RegionId,
1337    /// Region edit to apply.
1338    pub(crate) edit: RegionEdit,
1339    /// Result sender.
1340    pub(crate) sender: Sender<Result<MitoCopyRegionFromResponse>>,
1341}
1342
1343#[derive(Debug, Default)]
1344pub(crate) struct Waiters(SmallVec<[Sender<Result<()>>; 1]>);
1345
1346impl Waiters {
1347    pub(crate) fn one(waiter: Sender<Result<()>>) -> Self {
1348        let mut waiters = SmallVec::new();
1349        waiters.push(waiter);
1350        Self(waiters)
1351    }
1352
1353    pub(crate) fn reply_with<F: Fn() -> Result<()>>(self, f: F) {
1354        for tx in self.0 {
1355            let _ = tx.send(f());
1356        }
1357    }
1358
1359    pub(crate) fn merge(&mut self, other: Self) {
1360        self.0.extend(other.0);
1361    }
1362}
1363
1364/// Request to edit a region directly.
1365#[derive(Debug)]
1366pub(crate) struct RegionEditRequest {
1367    pub(crate) region_id: RegionId,
1368    pub(crate) edit: RegionEdit,
1369    /// Whether to preload SST files into the write cache.
1370    pub(crate) preload_sst_cache: bool,
1371    /// The waiters that are waiting for this region edit's result.
1372    pub(crate) waiters: Waiters,
1373    /// The partition expression of the staging this edit belongs to, if any.
1374    /// The edit must not run unless the region is staging for that expression.
1375    pub(crate) staging_partition_expr: Option<String>,
1376}
1377
1378impl RegionEditRequest {
1379    pub(crate) fn new(
1380        region_id: RegionId,
1381        edit: RegionEdit,
1382        preload_sst_cache: bool,
1383        waiter: Sender<Result<()>>,
1384    ) -> Self {
1385        Self {
1386            region_id,
1387            edit,
1388            preload_sst_cache,
1389            waiters: Waiters::one(waiter),
1390            staging_partition_expr: None,
1391        }
1392    }
1393
1394    /// Binds the edit to the staging for `partition_expr`.
1395    pub(crate) fn for_staging(mut self, partition_expr: String) -> Self {
1396        self.staging_partition_expr = Some(partition_expr);
1397        self
1398    }
1399}
1400
1401/// Notifies the regin the result of editing region.
1402#[derive(Debug)]
1403pub(crate) struct RegionEditResult {
1404    /// Region id.
1405    pub(crate) region_id: RegionId,
1406    /// Result waiters.
1407    pub(crate) waiters: Waiters,
1408    /// Region edit to apply.
1409    pub(crate) edit: RegionEdit,
1410    /// Result from the manifest manager.
1411    pub(crate) result: std::result::Result<(), Arc<Error>>,
1412    /// Whether region state need to be set to Writable after handling this request.
1413    pub(crate) update_region_state: bool,
1414    /// The region is in staging mode before handling this request.
1415    pub(crate) is_staging: bool,
1416}
1417
1418#[derive(Debug)]
1419pub(crate) struct BuildIndexRequest {
1420    pub(crate) region_id: RegionId,
1421    pub(crate) build_type: IndexBuildType,
1422    /// files need to build index, empty means all.
1423    pub(crate) file_metas: Vec<FileMeta>,
1424    /// Waiters carried over from a previous build of the same files.
1425    pub(crate) result_senders: Vec<ResultMpscSender>,
1426}
1427
1428#[derive(Debug)]
1429pub(crate) struct RegionSyncRequest {
1430    pub(crate) region_id: RegionId,
1431    pub(crate) manifest_version: ManifestVersion,
1432    /// Returns the latest manifest version and a boolean indicating whether new maniefst is installed.
1433    pub(crate) sender: Sender<Result<(ManifestVersion, bool)>>,
1434}
1435
1436#[derive(Debug)]
1437pub(crate) struct RemapManifestsRequest {
1438    /// The [`RegionId`] of a staging region used to obtain table directory and storage configuration for the remap operation.
1439    pub(crate) region_id: RegionId,
1440    /// Regions to remap manifests from.
1441    pub(crate) input_regions: Vec<RegionId>,
1442    /// For each old region, which new regions should receive its files
1443    pub(crate) region_mapping: HashMap<RegionId, Vec<RegionId>>,
1444    /// New partition expressions for the new regions.
1445    pub(crate) new_partition_exprs: HashMap<RegionId, PartitionExpr>,
1446    /// Sender for the result of the remap operation.
1447    ///
1448    /// The result is a map from region IDs to their corresponding staging manifest paths.
1449    pub(crate) sender: Sender<Result<HashMap<RegionId, String>>>,
1450}
1451
1452#[derive(Debug)]
1453pub(crate) struct CopyRegionFromRequest {
1454    /// The [`RegionId`] of the target region.
1455    pub(crate) region_id: RegionId,
1456    /// The [`RegionId`] of the source region.
1457    pub(crate) source_region_id: RegionId,
1458    /// The parallelism of the copy operation.
1459    pub(crate) parallelism: usize,
1460    /// Result sender.
1461    pub(crate) sender: Sender<Result<MitoCopyRegionFromResponse>>,
1462}
1463
1464#[cfg(test)]
1465mod tests {
1466    use api::v1::value::ValueData;
1467    use api::v1::{Row, SemanticType};
1468    use datatypes::prelude::ConcreteDataType;
1469    use datatypes::schema::ColumnDefaultConstraint;
1470    use mito_codec::test_util::i64_value;
1471    use store_api::metadata::RegionMetadataBuilder;
1472    use tokio::sync::oneshot;
1473
1474    use super::*;
1475    use crate::error::Error;
1476    use crate::test_util::ts_ms_value;
1477
1478    fn new_column_schema(
1479        name: &str,
1480        data_type: ColumnDataType,
1481        semantic_type: SemanticType,
1482    ) -> ColumnSchema {
1483        ColumnSchema {
1484            column_name: name.to_string(),
1485            datatype: data_type as i32,
1486            semantic_type: semantic_type as i32,
1487            ..Default::default()
1488        }
1489    }
1490
1491    fn check_invalid_request(err: &Error, expect: &str) {
1492        if let Error::InvalidRequest {
1493            region_id: _,
1494            reason,
1495            location: _,
1496        } = err
1497        {
1498            assert_eq!(reason, expect);
1499        } else {
1500            panic!("Unexpected error {err}")
1501        }
1502    }
1503
1504    fn waiter() -> (Sender<Result<()>>, Receiver<Result<()>>) {
1505        oneshot::channel()
1506    }
1507
1508    fn assert_waiter_ok(rx: &mut Receiver<Result<()>>) {
1509        rx.try_recv().unwrap().unwrap();
1510    }
1511
1512    #[test]
1513    fn test_waiters_reply_with_single_waiter() {
1514        let (tx, mut rx) = waiter();
1515        Waiters::one(tx).reply_with(|| Ok(()));
1516        assert_waiter_ok(&mut rx);
1517    }
1518
1519    #[test]
1520    fn test_waiters_reply_with_many_waiters() {
1521        let (tx1, mut rx1) = waiter();
1522        let (tx2, mut rx2) = waiter();
1523        let (tx3, mut rx3) = waiter();
1524
1525        let waiters = Waiters(vec![tx1, tx2, tx3].into());
1526        waiters.reply_with(|| Ok(()));
1527
1528        assert_waiter_ok(&mut rx1);
1529        assert_waiter_ok(&mut rx2);
1530        assert_waiter_ok(&mut rx3);
1531    }
1532
1533    #[test]
1534    fn test_waiters_merge() {
1535        let (tx1, mut rx1) = waiter();
1536        let (tx2, mut rx2) = waiter();
1537        let (tx3, mut rx3) = waiter();
1538        let (tx4, mut rx4) = waiter();
1539
1540        let mut waiters = Waiters::one(tx1);
1541        waiters.merge(Waiters::one(tx2));
1542        waiters.merge(Waiters(vec![tx3, tx4].into()));
1543        assert_eq!(4, waiters.0.len());
1544
1545        waiters.reply_with(|| Ok(()));
1546
1547        assert_waiter_ok(&mut rx1);
1548        assert_waiter_ok(&mut rx2);
1549        assert_waiter_ok(&mut rx3);
1550        assert_waiter_ok(&mut rx4);
1551    }
1552
1553    #[test]
1554    fn test_write_request_duplicate_column() {
1555        let rows = Rows {
1556            schema: vec![
1557                new_column_schema("c0", ColumnDataType::Int64, SemanticType::Tag),
1558                new_column_schema("c0", ColumnDataType::Int64, SemanticType::Tag),
1559            ],
1560            rows: vec![],
1561        };
1562
1563        let err = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap_err();
1564        check_invalid_request(&err, "duplicate column c0");
1565    }
1566
1567    #[test]
1568    fn test_valid_write_request() {
1569        let rows = Rows {
1570            schema: vec![
1571                new_column_schema("c0", ColumnDataType::Int64, SemanticType::Tag),
1572                new_column_schema("c1", ColumnDataType::Int64, SemanticType::Tag),
1573            ],
1574            rows: vec![Row {
1575                values: vec![i64_value(1), i64_value(2)],
1576            }],
1577        };
1578
1579        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1580        assert_eq!(0, request.column_index_by_name("c0").unwrap());
1581        assert_eq!(1, request.column_index_by_name("c1").unwrap());
1582        assert_eq!(None, request.column_index_by_name("c2"));
1583    }
1584
1585    #[test]
1586    fn test_write_request_column_num() {
1587        let rows = Rows {
1588            schema: vec![
1589                new_column_schema("c0", ColumnDataType::Int64, SemanticType::Tag),
1590                new_column_schema("c1", ColumnDataType::Int64, SemanticType::Tag),
1591            ],
1592            rows: vec![Row {
1593                values: vec![i64_value(1), i64_value(2), i64_value(3)],
1594            }],
1595        };
1596
1597        let err = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap_err();
1598        check_invalid_request(&err, "row has 3 columns but schema has 2");
1599    }
1600
1601    fn new_region_metadata() -> RegionMetadata {
1602        let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 1));
1603        builder
1604            .push_column_metadata(ColumnMetadata {
1605                column_schema: datatypes::schema::ColumnSchema::new(
1606                    "ts",
1607                    ConcreteDataType::timestamp_millisecond_datatype(),
1608                    false,
1609                ),
1610                semantic_type: SemanticType::Timestamp,
1611                column_id: 1,
1612            })
1613            .push_column_metadata(ColumnMetadata {
1614                column_schema: datatypes::schema::ColumnSchema::new(
1615                    "k0",
1616                    ConcreteDataType::int64_datatype(),
1617                    true,
1618                ),
1619                semantic_type: SemanticType::Tag,
1620                column_id: 2,
1621            })
1622            .primary_key(vec![2]);
1623        builder.build().unwrap()
1624    }
1625
1626    #[test]
1627    fn test_check_schema() {
1628        let rows = Rows {
1629            schema: vec![
1630                new_column_schema(
1631                    "ts",
1632                    ColumnDataType::TimestampMillisecond,
1633                    SemanticType::Timestamp,
1634                ),
1635                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1636            ],
1637            rows: vec![Row {
1638                values: vec![ts_ms_value(1), i64_value(2)],
1639            }],
1640        };
1641        let metadata = new_region_metadata();
1642
1643        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1644        request.check_schema(&metadata).unwrap();
1645    }
1646
1647    #[test]
1648    fn test_column_type() {
1649        let rows = Rows {
1650            schema: vec![
1651                new_column_schema("ts", ColumnDataType::Int64, SemanticType::Timestamp),
1652                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1653            ],
1654            rows: vec![Row {
1655                values: vec![i64_value(1), i64_value(2)],
1656            }],
1657        };
1658        let metadata = new_region_metadata();
1659
1660        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1661        let err = request.check_schema(&metadata).unwrap_err();
1662        check_invalid_request(
1663            &err,
1664            "column ts expect type Timestamp(Millisecond(TimestampMillisecondType)), given: INT64(4)",
1665        );
1666    }
1667
1668    #[test]
1669    fn test_semantic_type() {
1670        let rows = Rows {
1671            schema: vec![
1672                new_column_schema(
1673                    "ts",
1674                    ColumnDataType::TimestampMillisecond,
1675                    SemanticType::Tag,
1676                ),
1677                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1678            ],
1679            rows: vec![Row {
1680                values: vec![ts_ms_value(1), i64_value(2)],
1681            }],
1682        };
1683        let metadata = new_region_metadata();
1684
1685        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1686        let err = request.check_schema(&metadata).unwrap_err();
1687        check_invalid_request(&err, "column ts has semantic type Timestamp, given: TAG(0)");
1688    }
1689
1690    #[test]
1691    fn test_column_nullable() {
1692        let rows = Rows {
1693            schema: vec![
1694                new_column_schema(
1695                    "ts",
1696                    ColumnDataType::TimestampMillisecond,
1697                    SemanticType::Timestamp,
1698                ),
1699                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1700            ],
1701            rows: vec![Row {
1702                values: vec![Value { value_data: None }, i64_value(2)],
1703            }],
1704        };
1705        let metadata = new_region_metadata();
1706
1707        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1708        let err = request.check_schema(&metadata).unwrap_err();
1709        check_invalid_request(&err, "column ts is not null but input has null");
1710    }
1711
1712    #[test]
1713    fn test_column_default() {
1714        let rows = Rows {
1715            schema: vec![new_column_schema(
1716                "k0",
1717                ColumnDataType::Int64,
1718                SemanticType::Tag,
1719            )],
1720            rows: vec![Row {
1721                values: vec![i64_value(1)],
1722            }],
1723        };
1724        let metadata = new_region_metadata();
1725
1726        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1727        let err = request.check_schema(&metadata).unwrap_err();
1728        check_invalid_request(&err, "missing column ts");
1729    }
1730
1731    #[test]
1732    fn test_unknown_column() {
1733        let rows = Rows {
1734            schema: vec![
1735                new_column_schema(
1736                    "ts",
1737                    ColumnDataType::TimestampMillisecond,
1738                    SemanticType::Timestamp,
1739                ),
1740                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1741                new_column_schema("k1", ColumnDataType::Int64, SemanticType::Tag),
1742            ],
1743            rows: vec![Row {
1744                values: vec![ts_ms_value(1), i64_value(2), i64_value(3)],
1745            }],
1746        };
1747        let metadata = new_region_metadata();
1748
1749        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1750        let err = request.check_schema(&metadata).unwrap_err();
1751        check_invalid_request(&err, r#"unknown columns: ["k1"]"#);
1752    }
1753
1754    #[test]
1755    fn test_fill_impure_columns_err() {
1756        let rows = Rows {
1757            schema: vec![new_column_schema(
1758                "k0",
1759                ColumnDataType::Int64,
1760                SemanticType::Tag,
1761            )],
1762            rows: vec![Row {
1763                values: vec![i64_value(1)],
1764            }],
1765        };
1766        let metadata = {
1767            let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 1));
1768            builder
1769                .push_column_metadata(ColumnMetadata {
1770                    column_schema: datatypes::schema::ColumnSchema::new(
1771                        "ts",
1772                        ConcreteDataType::timestamp_millisecond_datatype(),
1773                        false,
1774                    )
1775                    .with_default_constraint(Some(ColumnDefaultConstraint::Function(
1776                        "now()".to_string(),
1777                    )))
1778                    .unwrap(),
1779                    semantic_type: SemanticType::Timestamp,
1780                    column_id: 1,
1781                })
1782                .push_column_metadata(ColumnMetadata {
1783                    column_schema: datatypes::schema::ColumnSchema::new(
1784                        "k0",
1785                        ConcreteDataType::int64_datatype(),
1786                        true,
1787                    ),
1788                    semantic_type: SemanticType::Tag,
1789                    column_id: 2,
1790                })
1791                .primary_key(vec![2]);
1792            builder.build().unwrap()
1793        };
1794
1795        let mut request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1796        let err = request.check_schema(&metadata).unwrap_err();
1797        assert!(err.is_fill_default());
1798        assert!(
1799            request
1800                .fill_missing_columns(&metadata)
1801                .unwrap_err()
1802                .to_string()
1803                .contains("unexpected impure default value with region_id")
1804        );
1805    }
1806
1807    #[test]
1808    fn test_fill_missing_columns() {
1809        let rows = Rows {
1810            schema: vec![new_column_schema(
1811                "ts",
1812                ColumnDataType::TimestampMillisecond,
1813                SemanticType::Timestamp,
1814            )],
1815            rows: vec![Row {
1816                values: vec![ts_ms_value(1)],
1817            }],
1818        };
1819        let metadata = new_region_metadata();
1820
1821        let mut request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1822        let err = request.check_schema(&metadata).unwrap_err();
1823        assert!(err.is_fill_default());
1824        request.fill_missing_columns(&metadata).unwrap();
1825
1826        let expect_rows = Rows {
1827            schema: vec![new_column_schema(
1828                "ts",
1829                ColumnDataType::TimestampMillisecond,
1830                SemanticType::Timestamp,
1831            )],
1832            rows: vec![Row {
1833                values: vec![ts_ms_value(1)],
1834            }],
1835        };
1836        assert_eq!(expect_rows, request.rows);
1837    }
1838
1839    fn builder_with_ts_tag() -> RegionMetadataBuilder {
1840        let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 1));
1841        builder
1842            .push_column_metadata(ColumnMetadata {
1843                column_schema: datatypes::schema::ColumnSchema::new(
1844                    "ts",
1845                    ConcreteDataType::timestamp_millisecond_datatype(),
1846                    false,
1847                ),
1848                semantic_type: SemanticType::Timestamp,
1849                column_id: 1,
1850            })
1851            .push_column_metadata(ColumnMetadata {
1852                column_schema: datatypes::schema::ColumnSchema::new(
1853                    "k0",
1854                    ConcreteDataType::int64_datatype(),
1855                    true,
1856                ),
1857                semantic_type: SemanticType::Tag,
1858                column_id: 2,
1859            })
1860            .primary_key(vec![2]);
1861        builder
1862    }
1863
1864    fn region_metadata_two_fields() -> RegionMetadata {
1865        let mut builder = builder_with_ts_tag();
1866        builder
1867            .push_column_metadata(ColumnMetadata {
1868                column_schema: datatypes::schema::ColumnSchema::new(
1869                    "f0",
1870                    ConcreteDataType::int64_datatype(),
1871                    true,
1872                ),
1873                semantic_type: SemanticType::Field,
1874                column_id: 3,
1875            })
1876            // Column is not nullable.
1877            .push_column_metadata(ColumnMetadata {
1878                column_schema: datatypes::schema::ColumnSchema::new(
1879                    "f1",
1880                    ConcreteDataType::int64_datatype(),
1881                    false,
1882                )
1883                .with_default_constraint(Some(ColumnDefaultConstraint::Value(
1884                    datatypes::value::Value::Int64(100),
1885                )))
1886                .unwrap(),
1887                semantic_type: SemanticType::Field,
1888                column_id: 4,
1889            });
1890        builder.build().unwrap()
1891    }
1892
1893    #[test]
1894    fn test_fill_missing_for_delete() {
1895        let rows = Rows {
1896            schema: vec![new_column_schema(
1897                "ts",
1898                ColumnDataType::TimestampMillisecond,
1899                SemanticType::Timestamp,
1900            )],
1901            rows: vec![Row {
1902                values: vec![ts_ms_value(1)],
1903            }],
1904        };
1905        let metadata = region_metadata_two_fields();
1906
1907        let mut request =
1908            WriteRequest::new(RegionId::new(1, 1), OpType::Delete, rows, None).unwrap();
1909        let err = request.check_schema(&metadata).unwrap_err();
1910        check_invalid_request(&err, "delete requests need column k0");
1911        let err = request.fill_missing_columns(&metadata).unwrap_err();
1912        check_invalid_request(&err, "delete requests need column k0");
1913
1914        let rows = Rows {
1915            schema: vec![
1916                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1917                new_column_schema(
1918                    "ts",
1919                    ColumnDataType::TimestampMillisecond,
1920                    SemanticType::Timestamp,
1921                ),
1922            ],
1923            rows: vec![Row {
1924                values: vec![i64_value(100), ts_ms_value(1)],
1925            }],
1926        };
1927        let mut request =
1928            WriteRequest::new(RegionId::new(1, 1), OpType::Delete, rows, None).unwrap();
1929        let err = request.check_schema(&metadata).unwrap_err();
1930        assert!(err.is_fill_default());
1931        request.fill_missing_columns(&metadata).unwrap();
1932
1933        let expect_rows = Rows {
1934            schema: vec![
1935                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1936                new_column_schema(
1937                    "ts",
1938                    ColumnDataType::TimestampMillisecond,
1939                    SemanticType::Timestamp,
1940                ),
1941                new_column_schema("f1", ColumnDataType::Int64, SemanticType::Field),
1942            ],
1943            // Column f1 is not nullable and we use 0 for padding.
1944            rows: vec![Row {
1945                values: vec![i64_value(100), ts_ms_value(1), i64_value(0)],
1946            }],
1947        };
1948        assert_eq!(expect_rows, request.rows);
1949    }
1950
1951    #[test]
1952    fn test_fill_missing_without_default_in_delete() {
1953        let mut builder = builder_with_ts_tag();
1954        builder
1955            // f0 is nullable.
1956            .push_column_metadata(ColumnMetadata {
1957                column_schema: datatypes::schema::ColumnSchema::new(
1958                    "f0",
1959                    ConcreteDataType::int64_datatype(),
1960                    true,
1961                ),
1962                semantic_type: SemanticType::Field,
1963                column_id: 3,
1964            })
1965            // f1 is not nullable and don't has default.
1966            .push_column_metadata(ColumnMetadata {
1967                column_schema: datatypes::schema::ColumnSchema::new(
1968                    "f1",
1969                    ConcreteDataType::int64_datatype(),
1970                    false,
1971                ),
1972                semantic_type: SemanticType::Field,
1973                column_id: 4,
1974            });
1975        let metadata = builder.build().unwrap();
1976
1977        let rows = Rows {
1978            schema: vec![
1979                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1980                new_column_schema(
1981                    "ts",
1982                    ColumnDataType::TimestampMillisecond,
1983                    SemanticType::Timestamp,
1984                ),
1985            ],
1986            // Missing f0 (nullable), f1 (not nullable).
1987            rows: vec![Row {
1988                values: vec![i64_value(100), ts_ms_value(1)],
1989            }],
1990        };
1991        let mut request =
1992            WriteRequest::new(RegionId::new(1, 1), OpType::Delete, rows, None).unwrap();
1993        let err = request.check_schema(&metadata).unwrap_err();
1994        assert!(err.is_fill_default());
1995        request.fill_missing_columns(&metadata).unwrap();
1996
1997        let expect_rows = Rows {
1998            schema: vec![
1999                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
2000                new_column_schema(
2001                    "ts",
2002                    ColumnDataType::TimestampMillisecond,
2003                    SemanticType::Timestamp,
2004                ),
2005                new_column_schema("f1", ColumnDataType::Int64, SemanticType::Field),
2006            ],
2007            // Column f1 is not nullable and we use 0 for padding.
2008            rows: vec![Row {
2009                values: vec![i64_value(100), ts_ms_value(1), i64_value(0)],
2010            }],
2011        };
2012        assert_eq!(expect_rows, request.rows);
2013    }
2014
2015    #[test]
2016    fn test_no_default() {
2017        let rows = Rows {
2018            schema: vec![new_column_schema(
2019                "k0",
2020                ColumnDataType::Int64,
2021                SemanticType::Tag,
2022            )],
2023            rows: vec![Row {
2024                values: vec![i64_value(1)],
2025            }],
2026        };
2027        let metadata = new_region_metadata();
2028
2029        let mut request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
2030        let err = request.fill_missing_columns(&metadata).unwrap_err();
2031        check_invalid_request(&err, "column ts does not have default value");
2032    }
2033
2034    #[test]
2035    fn test_missing_and_invalid() {
2036        // Missing f0 and f1 has invalid type (string).
2037        let rows = Rows {
2038            schema: vec![
2039                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
2040                new_column_schema(
2041                    "ts",
2042                    ColumnDataType::TimestampMillisecond,
2043                    SemanticType::Timestamp,
2044                ),
2045                new_column_schema("f1", ColumnDataType::String, SemanticType::Field),
2046            ],
2047            rows: vec![Row {
2048                values: vec![
2049                    i64_value(100),
2050                    ts_ms_value(1),
2051                    Value {
2052                        value_data: Some(ValueData::StringValue("xxxxx".to_string())),
2053                    },
2054                ],
2055            }],
2056        };
2057        let metadata = region_metadata_two_fields();
2058
2059        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
2060        let err = request.check_schema(&metadata).unwrap_err();
2061        check_invalid_request(
2062            &err,
2063            "column f1 expect type Int64(Int64Type), given: STRING(12)",
2064        );
2065    }
2066
2067    #[test]
2068    fn test_delete_request_defaults_to_writing_wal() {
2069        let (request, _receiver) = WorkerRequest::try_from_region_request(
2070            RegionId::new(1, 1),
2071            RegionRequest::Delete(store_api::region_request::RegionDeleteRequest {
2072                rows: Rows::default(),
2073                hint: None,
2074                partition_expr_version: None,
2075            }),
2076            None,
2077        )
2078        .unwrap();
2079        let WorkerRequest::Write(request) = request else {
2080            panic!("expected a write request");
2081        };
2082        assert_eq!(request.request.op_type, OpType::Delete);
2083        assert!(!request.request.skip_wal);
2084    }
2085
2086    #[test]
2087    fn test_write_request_metadata() {
2088        let rows = Rows {
2089            schema: vec![
2090                new_column_schema("c0", ColumnDataType::Int64, SemanticType::Tag),
2091                new_column_schema("c1", ColumnDataType::Int64, SemanticType::Tag),
2092            ],
2093            rows: vec![Row {
2094                values: vec![i64_value(1), i64_value(2)],
2095            }],
2096        };
2097
2098        let metadata = Arc::new(new_region_metadata());
2099        let request = WriteRequest::new(
2100            RegionId::new(1, 1),
2101            OpType::Put,
2102            rows,
2103            Some(metadata.clone()),
2104        )
2105        .unwrap();
2106
2107        assert!(request.region_metadata.is_some());
2108        assert_eq!(
2109            request.region_metadata.unwrap().region_id,
2110            RegionId::new(1, 1)
2111        );
2112    }
2113}