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}
530
531impl RequestColumn for BulkColumn<'_> {
532    fn has_type_of(&self, column: &ColumnMetadata) -> bool {
533        let actual = self.array.data_type();
534        let data_type = &column.column_schema.data_type;
535        // The memtables convert tags of any string encoding.
536        if column.semantic_type == SemanticType::Tag && data_type.is_string() {
537            return match actual {
538                ArrowDataType::Dictionary(key, value) => key.is_integer() && value.is_string(),
539                actual => actual.is_string(),
540            };
541        }
542        *actual == data_type.as_arrow_type()
543    }
544
545    fn type_name(&self) -> String {
546        self.array.data_type().to_string()
547    }
548
549    fn semantic_type(&self) -> Option<i32> {
550        None
551    }
552
553    fn has_null(&self) -> bool {
554        self.array.logical_null_count() > 0
555    }
556}
557
558/// Checks the batch of a bulk insert against the region schema with the rules
559/// [WriteRequest::check_schema] applies to rows: column names are unique, every
560/// column is a column of the region with its Arrow type and contains nulls only
561/// if the region column is nullable, and a missing column is nullable or has a
562/// default value. Tags may use any string encoding. Sparse batches carry the
563/// tags in one encoded primary key column instead. Returns whether the batch
564/// misses columns that the writer fills.
565pub fn check_bulk_insert_batch(metadata: &RegionMetadata, batch: &RecordBatch) -> Result<bool> {
566    let region_id = metadata.region_id;
567    let sparse = metadata.primary_key_encoding == PrimaryKeyEncoding::Sparse;
568    let mut primary_key = None;
569    let mut columns = HashMap::with_capacity(batch.num_columns());
570    for (field, array) in batch.schema_ref().fields().iter().zip(batch.columns()) {
571        let duplicate = if sparse && field.name() == PRIMARY_KEY_COLUMN_NAME {
572            primary_key.replace(array).is_some()
573        } else {
574            columns
575                .insert(field.name().as_str(), BulkColumn { array })
576                .is_some()
577        };
578        ensure!(
579            !duplicate,
580            InvalidRequestSnafu {
581                region_id,
582                reason: format!("duplicate column {}", field.name()),
583            }
584        );
585    }
586    if sparse {
587        let primary_key = primary_key.with_context(|| InvalidRequestSnafu {
588            region_id,
589            reason: format!("missing column {PRIMARY_KEY_COLUMN_NAME}"),
590        })?;
591        // The writer casts the encoded primary key to a dictionary of binary.
592        let type_matches = match primary_key.data_type() {
593            ArrowDataType::Binary => true,
594            ArrowDataType::Dictionary(key, value) => {
595                key.is_integer() && **value == ArrowDataType::Binary
596            }
597            _ => false,
598        };
599        ensure!(
600            type_matches && primary_key.logical_null_count() == 0,
601            InvalidRequestSnafu {
602                region_id,
603                reason: format!(
604                    "column {PRIMARY_KEY_COLUMN_NAME} must be binary without nulls, given: {}",
605                    primary_key.data_type()
606                ),
607            }
608        );
609    }
610    let mut needs_fill = false;
611    check_request_columns(region_id, metadata, columns, |column| {
612        if sparse && column.semantic_type == SemanticType::Tag {
613            Ok(())
614        } else {
615            needs_fill = true;
616            check_column_can_be_filled(region_id, column)
617        }
618    })?;
619    Ok(needs_fill)
620}
621
622/// Validate proto value schema.
623pub(crate) fn validate_proto_value(
624    region_id: RegionId,
625    value: &Value,
626    column_schema: &ColumnSchema,
627) -> Result<()> {
628    if let Some(value_type) = proto_value_type(value) {
629        let column_type = ColumnDataType::try_from(column_schema.datatype).map_err(|_| {
630            InvalidRequestSnafu {
631                region_id,
632                reason: format!(
633                    "column {} has unknown type {}",
634                    column_schema.column_name, column_schema.datatype
635                ),
636            }
637            .build()
638        })?;
639        ensure!(
640            proto_value_type_match(column_type, value_type),
641            InvalidRequestSnafu {
642                region_id,
643                reason: format!(
644                    "value has type {:?}, but column {} has type {:?}({})",
645                    value_type, column_schema.column_name, column_type, column_schema.datatype,
646                ),
647            }
648        );
649    }
650
651    Ok(())
652}
653
654/// Oneshot output result sender.
655#[derive(Debug)]
656pub struct OutputTx(Sender<Result<AffectedRows>>);
657
658impl OutputTx {
659    /// Creates a new output sender.
660    pub(crate) fn new(sender: Sender<Result<AffectedRows>>) -> OutputTx {
661        OutputTx(sender)
662    }
663
664    /// Sends the `result`.
665    pub(crate) fn send(self, result: Result<AffectedRows>) {
666        // Ignores send result.
667        let _ = self.0.send(result);
668    }
669}
670
671/// Optional output result sender.
672#[derive(Debug)]
673pub(crate) struct OptionOutputTx(Option<OutputTx>);
674
675impl OptionOutputTx {
676    /// Creates a sender.
677    pub(crate) fn new(sender: Option<OutputTx>) -> OptionOutputTx {
678        OptionOutputTx(sender)
679    }
680
681    /// Creates an empty sender.
682    pub(crate) fn none() -> OptionOutputTx {
683        OptionOutputTx(None)
684    }
685
686    /// Sends the `result` and consumes the inner sender.
687    pub(crate) fn send_mut(&mut self, result: Result<AffectedRows>) {
688        if let Some(sender) = self.0.take() {
689            sender.send(result);
690        }
691    }
692
693    /// Sends the `result` and consumes the sender.
694    pub(crate) fn send(mut self, result: Result<AffectedRows>) {
695        if let Some(sender) = self.0.take() {
696            sender.send(result);
697        }
698    }
699
700    /// Takes the inner sender.
701    pub(crate) fn take_inner(&mut self) -> Option<OutputTx> {
702        self.0.take()
703    }
704}
705
706impl From<Sender<Result<AffectedRows>>> for OptionOutputTx {
707    fn from(sender: Sender<Result<AffectedRows>>) -> Self {
708        Self::new(Some(OutputTx::new(sender)))
709    }
710}
711
712impl OnFailure for OptionOutputTx {
713    fn on_failure(&mut self, err: Error) {
714        self.send_mut(Err(err));
715    }
716}
717
718/// Callback on failure.
719pub(crate) trait OnFailure {
720    /// Handles `err` on failure.
721    fn on_failure(&mut self, err: Error);
722}
723
724/// Sender and write request.
725#[derive(Debug)]
726pub(crate) struct SenderWriteRequest {
727    /// Result sender.
728    pub(crate) sender: OptionOutputTx,
729    pub(crate) request: WriteRequest,
730}
731
732pub(crate) struct SenderBulkRequest {
733    pub(crate) skip_wal: bool,
734    pub(crate) sender: OptionOutputTx,
735    pub(crate) region_id: RegionId,
736    pub(crate) request: BulkPart,
737    pub(crate) region_metadata: Option<RegionMetadataRef>,
738    pub(crate) partition_expr_version: Option<u64>,
739}
740
741#[derive(Debug)]
742pub(crate) struct BulkInsertRequest {
743    pub(crate) metadata: Option<RegionMetadataRef>,
744    pub(crate) request: RegionBulkInsertsRequest,
745    pub(crate) sender: OptionOutputTx,
746}
747
748/// Request sent to a worker with timestamp
749#[derive(Debug)]
750pub(crate) struct WorkerRequestWithTime {
751    pub(crate) request: WorkerRequest,
752    pub(crate) created_at: Instant,
753}
754
755impl WorkerRequestWithTime {
756    pub(crate) fn new(request: WorkerRequest) -> Self {
757        Self {
758            request,
759            created_at: Instant::now(),
760        }
761    }
762}
763
764/// Request sent to a worker
765#[derive(Debug)]
766pub(crate) enum WorkerRequest {
767    /// Write to a region.
768    Write(SenderWriteRequest),
769
770    /// Writes of one request to regions of the same worker.
771    BatchWrite(Vec<SenderWriteRequest>),
772
773    /// Ddl request to a region.
774    Ddl(SenderDdlRequest),
775
776    /// Notifications from internal background jobs.
777    Background {
778        /// Id of the region to send.
779        region_id: RegionId,
780        /// Internal notification.
781        notify: BackgroundNotify,
782    },
783
784    /// The internal commands.
785    SetRegionRoleStateGracefully {
786        /// Id of the region to send.
787        region_id: RegionId,
788        /// The [SettableRegionRoleState].
789        region_role_state: SettableRegionRoleState,
790        /// The sender of [SetReadonlyResponse].
791        sender: Sender<SetRegionRoleStateResponse>,
792    },
793
794    /// Notify a worker to stop.
795    Stop,
796
797    /// Use [RegionEdit] to edit a region directly.
798    EditRegion(RegionEditRequest),
799
800    /// Keep the manifest of a region up to date.
801    SyncRegion(RegionSyncRequest),
802
803    /// Bulk inserts request and region metadata.
804    BulkInserts(BulkInsertRequest),
805
806    /// Remap manifests request.
807    RemapManifests(RemapManifestsRequest),
808
809    /// Copy region from request.
810    CopyRegionFrom(CopyRegionFromRequest),
811}
812
813impl WorkerRequest {
814    /// Creates a new open region request.
815    pub(crate) fn new_open_region_request(
816        region_id: RegionId,
817        request: RegionOpenRequest,
818        entry_receiver: Option<WalEntryReceiver>,
819    ) -> (WorkerRequest, Receiver<Result<AffectedRows>>) {
820        let (sender, receiver) = oneshot::channel();
821
822        let worker_request = WorkerRequest::Ddl(SenderDdlRequest {
823            region_id,
824            sender: sender.into(),
825            request: DdlRequest::Open((request, entry_receiver)),
826        });
827
828        (worker_request, receiver)
829    }
830
831    /// Creates a new catchup region request.
832    pub(crate) fn new_catchup_region_request(
833        region_id: RegionId,
834        request: RegionCatchupRequest,
835        entry_receiver: Option<WalEntryReceiver>,
836    ) -> (WorkerRequest, Receiver<Result<AffectedRows>>) {
837        let (sender, receiver) = oneshot::channel();
838        let worker_request = WorkerRequest::Ddl(SenderDdlRequest {
839            region_id,
840            sender: sender.into(),
841            request: DdlRequest::Catchup((request, entry_receiver)),
842        });
843        (worker_request, receiver)
844    }
845
846    /// Converts request from a [RegionRequest].
847    pub(crate) fn try_from_region_request(
848        region_id: RegionId,
849        value: RegionRequest,
850        region_metadata: Option<RegionMetadataRef>,
851    ) -> Result<(WorkerRequest, Receiver<Result<AffectedRows>>)> {
852        let (sender, receiver) = oneshot::channel();
853        let worker_request = match value {
854            RegionRequest::Put(v) => {
855                let mut write_request =
856                    WriteRequest::new(region_id, OpType::Put, v.rows, region_metadata.clone())?
857                        .with_hint(v.hint)
858                        .with_skip_wal(v.skip_wal)
859                        .with_partition_expr_version(v.partition_expr_version);
860                if write_request.primary_key_encoding() == PrimaryKeyEncoding::Dense
861                    && let Some(region_metadata) = &region_metadata
862                {
863                    write_request.maybe_fill_missing_columns(region_metadata)?;
864                }
865                WorkerRequest::Write(SenderWriteRequest {
866                    sender: sender.into(),
867                    request: write_request,
868                })
869            }
870            RegionRequest::Delete(v) => {
871                let mut write_request =
872                    WriteRequest::new(region_id, OpType::Delete, v.rows, region_metadata.clone())?
873                        .with_hint(v.hint)
874                        .with_partition_expr_version(v.partition_expr_version);
875                if write_request.primary_key_encoding() == PrimaryKeyEncoding::Dense
876                    && let Some(region_metadata) = &region_metadata
877                {
878                    write_request.maybe_fill_missing_columns(region_metadata)?;
879                }
880                WorkerRequest::Write(SenderWriteRequest {
881                    sender: sender.into(),
882                    request: write_request,
883                })
884            }
885            RegionRequest::Create(v) => WorkerRequest::Ddl(SenderDdlRequest {
886                region_id,
887                sender: sender.into(),
888                request: DdlRequest::Create(v),
889            }),
890            RegionRequest::Drop(v) => WorkerRequest::Ddl(SenderDdlRequest {
891                region_id,
892                sender: sender.into(),
893                request: DdlRequest::Drop(v),
894            }),
895            RegionRequest::Open(v) => WorkerRequest::Ddl(SenderDdlRequest {
896                region_id,
897                sender: sender.into(),
898                request: DdlRequest::Open((v, None)),
899            }),
900            RegionRequest::CleanUp(v) => WorkerRequest::Ddl(SenderDdlRequest {
901                region_id,
902                sender: sender.into(),
903                request: DdlRequest::OfflineCleanup(v),
904            }),
905            RegionRequest::Close(v) => WorkerRequest::Ddl(SenderDdlRequest {
906                region_id,
907                sender: sender.into(),
908                request: DdlRequest::Close(v),
909            }),
910            RegionRequest::Alter(v) => WorkerRequest::Ddl(SenderDdlRequest {
911                region_id,
912                sender: sender.into(),
913                request: DdlRequest::Alter(v),
914            }),
915            RegionRequest::Flush(v) => WorkerRequest::Ddl(SenderDdlRequest {
916                region_id,
917                sender: sender.into(),
918                request: DdlRequest::Flush(v),
919            }),
920            RegionRequest::Compact(v) => WorkerRequest::Ddl(SenderDdlRequest {
921                region_id,
922                sender: sender.into(),
923                request: DdlRequest::Compact(v),
924            }),
925            RegionRequest::BuildIndex(v) => WorkerRequest::Ddl(SenderDdlRequest {
926                region_id,
927                sender: sender.into(),
928                request: DdlRequest::BuildIndex(v),
929            }),
930            RegionRequest::Truncate(v) => WorkerRequest::Ddl(SenderDdlRequest {
931                region_id,
932                sender: sender.into(),
933                request: DdlRequest::Truncate(v),
934            }),
935            RegionRequest::Catchup(v) => WorkerRequest::Ddl(SenderDdlRequest {
936                region_id,
937                sender: sender.into(),
938                request: DdlRequest::Catchup((v, None)),
939            }),
940            RegionRequest::EnterStaging(v) => WorkerRequest::Ddl(SenderDdlRequest {
941                region_id,
942                sender: sender.into(),
943                request: DdlRequest::EnterStaging(v),
944            }),
945            RegionRequest::BulkInserts(mut region_bulk_inserts_request) => {
946                // The worker answers empty batches without writing them.
947                if let Some(region_metadata) = &region_metadata
948                    && region_bulk_inserts_request.payload.num_rows() > 0
949                    && check_bulk_insert_batch(
950                        region_metadata,
951                        &region_bulk_inserts_request.payload,
952                    )?
953                {
954                    // Lets the worker fill the missing columns.
955                    region_bulk_inserts_request.aligned_schema_version = None;
956                }
957                WorkerRequest::BulkInserts(BulkInsertRequest {
958                    metadata: region_metadata,
959                    sender: sender.into(),
960                    request: region_bulk_inserts_request,
961                })
962            }
963            RegionRequest::ApplyStagingManifest(v) => WorkerRequest::Ddl(SenderDdlRequest {
964                region_id,
965                sender: sender.into(),
966                request: DdlRequest::ApplyStagingManifest(v),
967            }),
968        };
969
970        Ok((worker_request, receiver))
971    }
972
973    pub(crate) fn new_set_readonly_gracefully(
974        region_id: RegionId,
975        region_role_state: SettableRegionRoleState,
976    ) -> (WorkerRequest, Receiver<SetRegionRoleStateResponse>) {
977        let (sender, receiver) = oneshot::channel();
978
979        (
980            WorkerRequest::SetRegionRoleStateGracefully {
981                region_id,
982                region_role_state,
983                sender,
984            },
985            receiver,
986        )
987    }
988
989    pub(crate) fn new_sync_region_request(
990        region_id: RegionId,
991        manifest_version: ManifestVersion,
992    ) -> (WorkerRequest, Receiver<Result<(ManifestVersion, bool)>>) {
993        let (sender, receiver) = oneshot::channel();
994        (
995            WorkerRequest::SyncRegion(RegionSyncRequest {
996                region_id,
997                manifest_version,
998                sender,
999            }),
1000            receiver,
1001        )
1002    }
1003
1004    /// Converts [RemapManifestsRequest] from a [RemapManifestsRequest](store_api::region_engine::RemapManifestsRequest).
1005    ///
1006    /// # Errors
1007    ///
1008    /// Returns an error if the partition expression is invalid or missing.
1009    /// Returns an error if the new partition expressions are not found for some regions.
1010    #[allow(clippy::type_complexity)]
1011    pub(crate) fn try_from_remap_manifests_request(
1012        store_api::region_engine::RemapManifestsRequest {
1013            region_id,
1014            input_regions,
1015            region_mapping,
1016            new_partition_exprs,
1017        }: store_api::region_engine::RemapManifestsRequest,
1018    ) -> Result<(WorkerRequest, Receiver<Result<HashMap<RegionId, String>>>)> {
1019        let (sender, receiver) = oneshot::channel();
1020        let new_partition_exprs = new_partition_exprs
1021            .into_iter()
1022            .map(|(k, v)| {
1023                Ok((
1024                    k,
1025                    PartitionExpr::from_json_str(&v)
1026                        .context(InvalidPartitionExprSnafu { expr: v })?
1027                        .context(MissingPartitionExprSnafu { region_id: k })?,
1028                ))
1029            })
1030            .collect::<Result<HashMap<_, _>>>()?;
1031
1032        let request = RemapManifestsRequest {
1033            region_id,
1034            input_regions,
1035            region_mapping,
1036            new_partition_exprs,
1037            sender,
1038        };
1039
1040        Ok((WorkerRequest::RemapManifests(request), receiver))
1041    }
1042
1043    /// Converts [CopyRegionFromRequest] from a [MitoCopyRegionFromRequest](store_api::region_engine::MitoCopyRegionFromRequest).
1044    pub(crate) fn try_from_copy_region_from_request(
1045        region_id: RegionId,
1046        store_api::region_engine::MitoCopyRegionFromRequest {
1047            source_region_id,
1048            parallelism,
1049        }: store_api::region_engine::MitoCopyRegionFromRequest,
1050    ) -> Result<(WorkerRequest, Receiver<Result<MitoCopyRegionFromResponse>>)> {
1051        let (sender, receiver) = oneshot::channel();
1052        let request = CopyRegionFromRequest {
1053            region_id,
1054            source_region_id,
1055            parallelism,
1056            sender,
1057        };
1058        Ok((WorkerRequest::CopyRegionFrom(request), receiver))
1059    }
1060}
1061
1062/// DDL request to a region.
1063#[derive(Debug)]
1064pub(crate) enum DdlRequest {
1065    Create(RegionCreateRequest),
1066    Drop(RegionDropRequest),
1067    Open((RegionOpenRequest, Option<WalEntryReceiver>)),
1068    OfflineCleanup(RegionCleanUpRequest),
1069    Close(RegionCloseRequest),
1070    Alter(RegionAlterRequest),
1071    Flush(RegionFlushRequest),
1072    Compact(RegionCompactRequest),
1073    BuildIndex(RegionBuildIndexRequest),
1074    Truncate(RegionTruncateRequest),
1075    Catchup((RegionCatchupRequest, Option<WalEntryReceiver>)),
1076    EnterStaging(EnterStagingRequest),
1077    ApplyStagingManifest(ApplyStagingManifestRequest),
1078}
1079
1080/// Sender and Ddl request.
1081#[derive(Debug)]
1082pub(crate) struct SenderDdlRequest {
1083    /// Region id of the request.
1084    pub(crate) region_id: RegionId,
1085    /// Result sender.
1086    pub(crate) sender: OptionOutputTx,
1087    /// Ddl request.
1088    pub(crate) request: DdlRequest,
1089}
1090
1091/// Notification from a background job.
1092#[derive(Debug)]
1093pub(crate) enum BackgroundNotify {
1094    /// Registers a loaded region and its scheduling state before acknowledging open.
1095    RegionOpened {
1096        region: MitoRegionRef,
1097        registered: Sender<()>,
1098    },
1099    /// Local unit publication and resource completion events.
1100    CompactionUnit(CompactionUnitNotification),
1101    /// A DDL released by compaction has actually completed, including failure.
1102    CompactionDdlComplete {
1103        generation: u64,
1104        request_index: usize,
1105    },
1106    /// Compaction planning has finished.
1107    CompactionPickFinished(CompactionPickFinished),
1108    /// Flush has finished.
1109    FlushFinished(FlushFinished),
1110    /// Flush has failed.
1111    FlushFailed(FlushFailed),
1112    /// Index build has finished.
1113    IndexBuildFinished(IndexBuildFinished),
1114    /// Index build has been stopped (aborted or succeeded).
1115    IndexBuildStopped(IndexBuildStopped),
1116    /// An index build must be retried against the latest schema generation.
1117    IndexBuildRetry(BuildIndexRequest),
1118    /// Compaction has finished.
1119    CompactionFinished(CompactionFinished),
1120    /// Compaction has failed.
1121    CompactionFailed(CompactionFailed),
1122    /// Truncate result.
1123    Truncate(TruncateResult),
1124    /// Discard unflushed data result.
1125    DiscardUnflushed(DiscardUnflushedResult),
1126    /// Region change result.
1127    RegionChange(RegionChangeResult),
1128    /// Region edit result.
1129    RegionEdit(RegionEditResult),
1130    /// Enter staging result.
1131    EnterStaging(EnterStagingResult),
1132    /// Copy region result.
1133    CopyRegionFromFinished(CopyRegionFromFinished),
1134}
1135
1136/// Notifies a flush job is finished.
1137#[derive(Debug)]
1138pub(crate) struct FlushFinished {
1139    /// Region id.
1140    pub(crate) region_id: RegionId,
1141    /// Reason to flush.
1142    pub(crate) flush_reason: FlushReason,
1143    /// Entry id of flushed data.
1144    pub(crate) flushed_entry_id: EntryId,
1145    /// Flush result senders.
1146    pub(crate) senders: Vec<OutputTx>,
1147    /// Flush timer.
1148    pub(crate) _timer: HistogramTimer,
1149    /// Region edit to apply.
1150    pub(crate) edit: RegionEdit,
1151    /// Memtables to remove.
1152    pub(crate) memtables_to_remove: SmallVec<[MemtableId; 2]>,
1153    /// Whether the region is in staging mode.
1154    pub(crate) is_staging: bool,
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}
1374
1375impl RegionEditRequest {
1376    pub(crate) fn new(
1377        region_id: RegionId,
1378        edit: RegionEdit,
1379        preload_sst_cache: bool,
1380        waiter: Sender<Result<()>>,
1381    ) -> Self {
1382        Self {
1383            region_id,
1384            edit,
1385            preload_sst_cache,
1386            waiters: Waiters::one(waiter),
1387        }
1388    }
1389}
1390
1391/// Notifies the regin the result of editing region.
1392#[derive(Debug)]
1393pub(crate) struct RegionEditResult {
1394    /// Region id.
1395    pub(crate) region_id: RegionId,
1396    /// Result waiters.
1397    pub(crate) waiters: Waiters,
1398    /// Region edit to apply.
1399    pub(crate) edit: RegionEdit,
1400    /// Result from the manifest manager.
1401    pub(crate) result: std::result::Result<(), Arc<Error>>,
1402    /// Whether region state need to be set to Writable after handling this request.
1403    pub(crate) update_region_state: bool,
1404    /// The region is in staging mode before handling this request.
1405    pub(crate) is_staging: bool,
1406}
1407
1408#[derive(Debug)]
1409pub(crate) struct BuildIndexRequest {
1410    pub(crate) region_id: RegionId,
1411    pub(crate) build_type: IndexBuildType,
1412    /// files need to build index, empty means all.
1413    pub(crate) file_metas: Vec<FileMeta>,
1414    /// Waiters carried over from a previous build of the same files.
1415    pub(crate) result_senders: Vec<ResultMpscSender>,
1416}
1417
1418#[derive(Debug)]
1419pub(crate) struct RegionSyncRequest {
1420    pub(crate) region_id: RegionId,
1421    pub(crate) manifest_version: ManifestVersion,
1422    /// Returns the latest manifest version and a boolean indicating whether new maniefst is installed.
1423    pub(crate) sender: Sender<Result<(ManifestVersion, bool)>>,
1424}
1425
1426#[derive(Debug)]
1427pub(crate) struct RemapManifestsRequest {
1428    /// The [`RegionId`] of a staging region used to obtain table directory and storage configuration for the remap operation.
1429    pub(crate) region_id: RegionId,
1430    /// Regions to remap manifests from.
1431    pub(crate) input_regions: Vec<RegionId>,
1432    /// For each old region, which new regions should receive its files
1433    pub(crate) region_mapping: HashMap<RegionId, Vec<RegionId>>,
1434    /// New partition expressions for the new regions.
1435    pub(crate) new_partition_exprs: HashMap<RegionId, PartitionExpr>,
1436    /// Sender for the result of the remap operation.
1437    ///
1438    /// The result is a map from region IDs to their corresponding staging manifest paths.
1439    pub(crate) sender: Sender<Result<HashMap<RegionId, String>>>,
1440}
1441
1442#[derive(Debug)]
1443pub(crate) struct CopyRegionFromRequest {
1444    /// The [`RegionId`] of the target region.
1445    pub(crate) region_id: RegionId,
1446    /// The [`RegionId`] of the source region.
1447    pub(crate) source_region_id: RegionId,
1448    /// The parallelism of the copy operation.
1449    pub(crate) parallelism: usize,
1450    /// Result sender.
1451    pub(crate) sender: Sender<Result<MitoCopyRegionFromResponse>>,
1452}
1453
1454#[cfg(test)]
1455mod tests {
1456    use api::v1::value::ValueData;
1457    use api::v1::{Row, SemanticType};
1458    use datatypes::prelude::ConcreteDataType;
1459    use datatypes::schema::ColumnDefaultConstraint;
1460    use mito_codec::test_util::i64_value;
1461    use store_api::metadata::RegionMetadataBuilder;
1462    use tokio::sync::oneshot;
1463
1464    use super::*;
1465    use crate::error::Error;
1466    use crate::test_util::ts_ms_value;
1467
1468    fn new_column_schema(
1469        name: &str,
1470        data_type: ColumnDataType,
1471        semantic_type: SemanticType,
1472    ) -> ColumnSchema {
1473        ColumnSchema {
1474            column_name: name.to_string(),
1475            datatype: data_type as i32,
1476            semantic_type: semantic_type as i32,
1477            ..Default::default()
1478        }
1479    }
1480
1481    fn check_invalid_request(err: &Error, expect: &str) {
1482        if let Error::InvalidRequest {
1483            region_id: _,
1484            reason,
1485            location: _,
1486        } = err
1487        {
1488            assert_eq!(reason, expect);
1489        } else {
1490            panic!("Unexpected error {err}")
1491        }
1492    }
1493
1494    fn waiter() -> (Sender<Result<()>>, Receiver<Result<()>>) {
1495        oneshot::channel()
1496    }
1497
1498    fn assert_waiter_ok(rx: &mut Receiver<Result<()>>) {
1499        rx.try_recv().unwrap().unwrap();
1500    }
1501
1502    #[test]
1503    fn test_waiters_reply_with_single_waiter() {
1504        let (tx, mut rx) = waiter();
1505        Waiters::one(tx).reply_with(|| Ok(()));
1506        assert_waiter_ok(&mut rx);
1507    }
1508
1509    #[test]
1510    fn test_waiters_reply_with_many_waiters() {
1511        let (tx1, mut rx1) = waiter();
1512        let (tx2, mut rx2) = waiter();
1513        let (tx3, mut rx3) = waiter();
1514
1515        let waiters = Waiters(vec![tx1, tx2, tx3].into());
1516        waiters.reply_with(|| Ok(()));
1517
1518        assert_waiter_ok(&mut rx1);
1519        assert_waiter_ok(&mut rx2);
1520        assert_waiter_ok(&mut rx3);
1521    }
1522
1523    #[test]
1524    fn test_waiters_merge() {
1525        let (tx1, mut rx1) = waiter();
1526        let (tx2, mut rx2) = waiter();
1527        let (tx3, mut rx3) = waiter();
1528        let (tx4, mut rx4) = waiter();
1529
1530        let mut waiters = Waiters::one(tx1);
1531        waiters.merge(Waiters::one(tx2));
1532        waiters.merge(Waiters(vec![tx3, tx4].into()));
1533        assert_eq!(4, waiters.0.len());
1534
1535        waiters.reply_with(|| Ok(()));
1536
1537        assert_waiter_ok(&mut rx1);
1538        assert_waiter_ok(&mut rx2);
1539        assert_waiter_ok(&mut rx3);
1540        assert_waiter_ok(&mut rx4);
1541    }
1542
1543    #[test]
1544    fn test_write_request_duplicate_column() {
1545        let rows = Rows {
1546            schema: vec![
1547                new_column_schema("c0", ColumnDataType::Int64, SemanticType::Tag),
1548                new_column_schema("c0", ColumnDataType::Int64, SemanticType::Tag),
1549            ],
1550            rows: vec![],
1551        };
1552
1553        let err = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap_err();
1554        check_invalid_request(&err, "duplicate column c0");
1555    }
1556
1557    #[test]
1558    fn test_valid_write_request() {
1559        let rows = Rows {
1560            schema: vec![
1561                new_column_schema("c0", ColumnDataType::Int64, SemanticType::Tag),
1562                new_column_schema("c1", ColumnDataType::Int64, SemanticType::Tag),
1563            ],
1564            rows: vec![Row {
1565                values: vec![i64_value(1), i64_value(2)],
1566            }],
1567        };
1568
1569        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1570        assert_eq!(0, request.column_index_by_name("c0").unwrap());
1571        assert_eq!(1, request.column_index_by_name("c1").unwrap());
1572        assert_eq!(None, request.column_index_by_name("c2"));
1573    }
1574
1575    #[test]
1576    fn test_write_request_column_num() {
1577        let rows = Rows {
1578            schema: vec![
1579                new_column_schema("c0", ColumnDataType::Int64, SemanticType::Tag),
1580                new_column_schema("c1", ColumnDataType::Int64, SemanticType::Tag),
1581            ],
1582            rows: vec![Row {
1583                values: vec![i64_value(1), i64_value(2), i64_value(3)],
1584            }],
1585        };
1586
1587        let err = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap_err();
1588        check_invalid_request(&err, "row has 3 columns but schema has 2");
1589    }
1590
1591    fn new_region_metadata() -> RegionMetadata {
1592        let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 1));
1593        builder
1594            .push_column_metadata(ColumnMetadata {
1595                column_schema: datatypes::schema::ColumnSchema::new(
1596                    "ts",
1597                    ConcreteDataType::timestamp_millisecond_datatype(),
1598                    false,
1599                ),
1600                semantic_type: SemanticType::Timestamp,
1601                column_id: 1,
1602            })
1603            .push_column_metadata(ColumnMetadata {
1604                column_schema: datatypes::schema::ColumnSchema::new(
1605                    "k0",
1606                    ConcreteDataType::int64_datatype(),
1607                    true,
1608                ),
1609                semantic_type: SemanticType::Tag,
1610                column_id: 2,
1611            })
1612            .primary_key(vec![2]);
1613        builder.build().unwrap()
1614    }
1615
1616    #[test]
1617    fn test_check_schema() {
1618        let rows = Rows {
1619            schema: vec![
1620                new_column_schema(
1621                    "ts",
1622                    ColumnDataType::TimestampMillisecond,
1623                    SemanticType::Timestamp,
1624                ),
1625                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1626            ],
1627            rows: vec![Row {
1628                values: vec![ts_ms_value(1), i64_value(2)],
1629            }],
1630        };
1631        let metadata = new_region_metadata();
1632
1633        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1634        request.check_schema(&metadata).unwrap();
1635    }
1636
1637    #[test]
1638    fn test_column_type() {
1639        let rows = Rows {
1640            schema: vec![
1641                new_column_schema("ts", ColumnDataType::Int64, SemanticType::Timestamp),
1642                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1643            ],
1644            rows: vec![Row {
1645                values: vec![i64_value(1), i64_value(2)],
1646            }],
1647        };
1648        let metadata = new_region_metadata();
1649
1650        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1651        let err = request.check_schema(&metadata).unwrap_err();
1652        check_invalid_request(
1653            &err,
1654            "column ts expect type Timestamp(Millisecond(TimestampMillisecondType)), given: INT64(4)",
1655        );
1656    }
1657
1658    #[test]
1659    fn test_semantic_type() {
1660        let rows = Rows {
1661            schema: vec![
1662                new_column_schema(
1663                    "ts",
1664                    ColumnDataType::TimestampMillisecond,
1665                    SemanticType::Tag,
1666                ),
1667                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1668            ],
1669            rows: vec![Row {
1670                values: vec![ts_ms_value(1), i64_value(2)],
1671            }],
1672        };
1673        let metadata = new_region_metadata();
1674
1675        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1676        let err = request.check_schema(&metadata).unwrap_err();
1677        check_invalid_request(&err, "column ts has semantic type Timestamp, given: TAG(0)");
1678    }
1679
1680    #[test]
1681    fn test_column_nullable() {
1682        let rows = Rows {
1683            schema: vec![
1684                new_column_schema(
1685                    "ts",
1686                    ColumnDataType::TimestampMillisecond,
1687                    SemanticType::Timestamp,
1688                ),
1689                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1690            ],
1691            rows: vec![Row {
1692                values: vec![Value { value_data: None }, i64_value(2)],
1693            }],
1694        };
1695        let metadata = new_region_metadata();
1696
1697        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1698        let err = request.check_schema(&metadata).unwrap_err();
1699        check_invalid_request(&err, "column ts is not null but input has null");
1700    }
1701
1702    #[test]
1703    fn test_column_default() {
1704        let rows = Rows {
1705            schema: vec![new_column_schema(
1706                "k0",
1707                ColumnDataType::Int64,
1708                SemanticType::Tag,
1709            )],
1710            rows: vec![Row {
1711                values: vec![i64_value(1)],
1712            }],
1713        };
1714        let metadata = new_region_metadata();
1715
1716        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1717        let err = request.check_schema(&metadata).unwrap_err();
1718        check_invalid_request(&err, "missing column ts");
1719    }
1720
1721    #[test]
1722    fn test_unknown_column() {
1723        let rows = Rows {
1724            schema: vec![
1725                new_column_schema(
1726                    "ts",
1727                    ColumnDataType::TimestampMillisecond,
1728                    SemanticType::Timestamp,
1729                ),
1730                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1731                new_column_schema("k1", ColumnDataType::Int64, SemanticType::Tag),
1732            ],
1733            rows: vec![Row {
1734                values: vec![ts_ms_value(1), i64_value(2), i64_value(3)],
1735            }],
1736        };
1737        let metadata = new_region_metadata();
1738
1739        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1740        let err = request.check_schema(&metadata).unwrap_err();
1741        check_invalid_request(&err, r#"unknown columns: ["k1"]"#);
1742    }
1743
1744    #[test]
1745    fn test_fill_impure_columns_err() {
1746        let rows = Rows {
1747            schema: vec![new_column_schema(
1748                "k0",
1749                ColumnDataType::Int64,
1750                SemanticType::Tag,
1751            )],
1752            rows: vec![Row {
1753                values: vec![i64_value(1)],
1754            }],
1755        };
1756        let metadata = {
1757            let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 1));
1758            builder
1759                .push_column_metadata(ColumnMetadata {
1760                    column_schema: datatypes::schema::ColumnSchema::new(
1761                        "ts",
1762                        ConcreteDataType::timestamp_millisecond_datatype(),
1763                        false,
1764                    )
1765                    .with_default_constraint(Some(ColumnDefaultConstraint::Function(
1766                        "now()".to_string(),
1767                    )))
1768                    .unwrap(),
1769                    semantic_type: SemanticType::Timestamp,
1770                    column_id: 1,
1771                })
1772                .push_column_metadata(ColumnMetadata {
1773                    column_schema: datatypes::schema::ColumnSchema::new(
1774                        "k0",
1775                        ConcreteDataType::int64_datatype(),
1776                        true,
1777                    ),
1778                    semantic_type: SemanticType::Tag,
1779                    column_id: 2,
1780                })
1781                .primary_key(vec![2]);
1782            builder.build().unwrap()
1783        };
1784
1785        let mut request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1786        let err = request.check_schema(&metadata).unwrap_err();
1787        assert!(err.is_fill_default());
1788        assert!(
1789            request
1790                .fill_missing_columns(&metadata)
1791                .unwrap_err()
1792                .to_string()
1793                .contains("unexpected impure default value with region_id")
1794        );
1795    }
1796
1797    #[test]
1798    fn test_fill_missing_columns() {
1799        let rows = Rows {
1800            schema: vec![new_column_schema(
1801                "ts",
1802                ColumnDataType::TimestampMillisecond,
1803                SemanticType::Timestamp,
1804            )],
1805            rows: vec![Row {
1806                values: vec![ts_ms_value(1)],
1807            }],
1808        };
1809        let metadata = new_region_metadata();
1810
1811        let mut request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1812        let err = request.check_schema(&metadata).unwrap_err();
1813        assert!(err.is_fill_default());
1814        request.fill_missing_columns(&metadata).unwrap();
1815
1816        let expect_rows = Rows {
1817            schema: vec![new_column_schema(
1818                "ts",
1819                ColumnDataType::TimestampMillisecond,
1820                SemanticType::Timestamp,
1821            )],
1822            rows: vec![Row {
1823                values: vec![ts_ms_value(1)],
1824            }],
1825        };
1826        assert_eq!(expect_rows, request.rows);
1827    }
1828
1829    fn builder_with_ts_tag() -> RegionMetadataBuilder {
1830        let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 1));
1831        builder
1832            .push_column_metadata(ColumnMetadata {
1833                column_schema: datatypes::schema::ColumnSchema::new(
1834                    "ts",
1835                    ConcreteDataType::timestamp_millisecond_datatype(),
1836                    false,
1837                ),
1838                semantic_type: SemanticType::Timestamp,
1839                column_id: 1,
1840            })
1841            .push_column_metadata(ColumnMetadata {
1842                column_schema: datatypes::schema::ColumnSchema::new(
1843                    "k0",
1844                    ConcreteDataType::int64_datatype(),
1845                    true,
1846                ),
1847                semantic_type: SemanticType::Tag,
1848                column_id: 2,
1849            })
1850            .primary_key(vec![2]);
1851        builder
1852    }
1853
1854    fn region_metadata_two_fields() -> RegionMetadata {
1855        let mut builder = builder_with_ts_tag();
1856        builder
1857            .push_column_metadata(ColumnMetadata {
1858                column_schema: datatypes::schema::ColumnSchema::new(
1859                    "f0",
1860                    ConcreteDataType::int64_datatype(),
1861                    true,
1862                ),
1863                semantic_type: SemanticType::Field,
1864                column_id: 3,
1865            })
1866            // Column is not nullable.
1867            .push_column_metadata(ColumnMetadata {
1868                column_schema: datatypes::schema::ColumnSchema::new(
1869                    "f1",
1870                    ConcreteDataType::int64_datatype(),
1871                    false,
1872                )
1873                .with_default_constraint(Some(ColumnDefaultConstraint::Value(
1874                    datatypes::value::Value::Int64(100),
1875                )))
1876                .unwrap(),
1877                semantic_type: SemanticType::Field,
1878                column_id: 4,
1879            });
1880        builder.build().unwrap()
1881    }
1882
1883    #[test]
1884    fn test_fill_missing_for_delete() {
1885        let rows = Rows {
1886            schema: vec![new_column_schema(
1887                "ts",
1888                ColumnDataType::TimestampMillisecond,
1889                SemanticType::Timestamp,
1890            )],
1891            rows: vec![Row {
1892                values: vec![ts_ms_value(1)],
1893            }],
1894        };
1895        let metadata = region_metadata_two_fields();
1896
1897        let mut request =
1898            WriteRequest::new(RegionId::new(1, 1), OpType::Delete, rows, None).unwrap();
1899        let err = request.check_schema(&metadata).unwrap_err();
1900        check_invalid_request(&err, "delete requests need column k0");
1901        let err = request.fill_missing_columns(&metadata).unwrap_err();
1902        check_invalid_request(&err, "delete requests need column k0");
1903
1904        let rows = Rows {
1905            schema: vec![
1906                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1907                new_column_schema(
1908                    "ts",
1909                    ColumnDataType::TimestampMillisecond,
1910                    SemanticType::Timestamp,
1911                ),
1912            ],
1913            rows: vec![Row {
1914                values: vec![i64_value(100), ts_ms_value(1)],
1915            }],
1916        };
1917        let mut request =
1918            WriteRequest::new(RegionId::new(1, 1), OpType::Delete, rows, None).unwrap();
1919        let err = request.check_schema(&metadata).unwrap_err();
1920        assert!(err.is_fill_default());
1921        request.fill_missing_columns(&metadata).unwrap();
1922
1923        let expect_rows = Rows {
1924            schema: vec![
1925                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1926                new_column_schema(
1927                    "ts",
1928                    ColumnDataType::TimestampMillisecond,
1929                    SemanticType::Timestamp,
1930                ),
1931                new_column_schema("f1", ColumnDataType::Int64, SemanticType::Field),
1932            ],
1933            // Column f1 is not nullable and we use 0 for padding.
1934            rows: vec![Row {
1935                values: vec![i64_value(100), ts_ms_value(1), i64_value(0)],
1936            }],
1937        };
1938        assert_eq!(expect_rows, request.rows);
1939    }
1940
1941    #[test]
1942    fn test_fill_missing_without_default_in_delete() {
1943        let mut builder = builder_with_ts_tag();
1944        builder
1945            // f0 is nullable.
1946            .push_column_metadata(ColumnMetadata {
1947                column_schema: datatypes::schema::ColumnSchema::new(
1948                    "f0",
1949                    ConcreteDataType::int64_datatype(),
1950                    true,
1951                ),
1952                semantic_type: SemanticType::Field,
1953                column_id: 3,
1954            })
1955            // f1 is not nullable and don't has default.
1956            .push_column_metadata(ColumnMetadata {
1957                column_schema: datatypes::schema::ColumnSchema::new(
1958                    "f1",
1959                    ConcreteDataType::int64_datatype(),
1960                    false,
1961                ),
1962                semantic_type: SemanticType::Field,
1963                column_id: 4,
1964            });
1965        let metadata = builder.build().unwrap();
1966
1967        let rows = Rows {
1968            schema: vec![
1969                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1970                new_column_schema(
1971                    "ts",
1972                    ColumnDataType::TimestampMillisecond,
1973                    SemanticType::Timestamp,
1974                ),
1975            ],
1976            // Missing f0 (nullable), f1 (not nullable).
1977            rows: vec![Row {
1978                values: vec![i64_value(100), ts_ms_value(1)],
1979            }],
1980        };
1981        let mut request =
1982            WriteRequest::new(RegionId::new(1, 1), OpType::Delete, rows, None).unwrap();
1983        let err = request.check_schema(&metadata).unwrap_err();
1984        assert!(err.is_fill_default());
1985        request.fill_missing_columns(&metadata).unwrap();
1986
1987        let expect_rows = Rows {
1988            schema: vec![
1989                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1990                new_column_schema(
1991                    "ts",
1992                    ColumnDataType::TimestampMillisecond,
1993                    SemanticType::Timestamp,
1994                ),
1995                new_column_schema("f1", ColumnDataType::Int64, SemanticType::Field),
1996            ],
1997            // Column f1 is not nullable and we use 0 for padding.
1998            rows: vec![Row {
1999                values: vec![i64_value(100), ts_ms_value(1), i64_value(0)],
2000            }],
2001        };
2002        assert_eq!(expect_rows, request.rows);
2003    }
2004
2005    #[test]
2006    fn test_no_default() {
2007        let rows = Rows {
2008            schema: vec![new_column_schema(
2009                "k0",
2010                ColumnDataType::Int64,
2011                SemanticType::Tag,
2012            )],
2013            rows: vec![Row {
2014                values: vec![i64_value(1)],
2015            }],
2016        };
2017        let metadata = new_region_metadata();
2018
2019        let mut request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
2020        let err = request.fill_missing_columns(&metadata).unwrap_err();
2021        check_invalid_request(&err, "column ts does not have default value");
2022    }
2023
2024    #[test]
2025    fn test_missing_and_invalid() {
2026        // Missing f0 and f1 has invalid type (string).
2027        let rows = Rows {
2028            schema: vec![
2029                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
2030                new_column_schema(
2031                    "ts",
2032                    ColumnDataType::TimestampMillisecond,
2033                    SemanticType::Timestamp,
2034                ),
2035                new_column_schema("f1", ColumnDataType::String, SemanticType::Field),
2036            ],
2037            rows: vec![Row {
2038                values: vec![
2039                    i64_value(100),
2040                    ts_ms_value(1),
2041                    Value {
2042                        value_data: Some(ValueData::StringValue("xxxxx".to_string())),
2043                    },
2044                ],
2045            }],
2046        };
2047        let metadata = region_metadata_two_fields();
2048
2049        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
2050        let err = request.check_schema(&metadata).unwrap_err();
2051        check_invalid_request(
2052            &err,
2053            "column f1 expect type Int64(Int64Type), given: STRING(12)",
2054        );
2055    }
2056
2057    #[test]
2058    fn test_delete_request_defaults_to_writing_wal() {
2059        let (request, _receiver) = WorkerRequest::try_from_region_request(
2060            RegionId::new(1, 1),
2061            RegionRequest::Delete(store_api::region_request::RegionDeleteRequest {
2062                rows: Rows::default(),
2063                hint: None,
2064                partition_expr_version: None,
2065            }),
2066            None,
2067        )
2068        .unwrap();
2069        let WorkerRequest::Write(request) = request else {
2070            panic!("expected a write request");
2071        };
2072        assert_eq!(request.request.op_type, OpType::Delete);
2073        assert!(!request.request.skip_wal);
2074    }
2075
2076    #[test]
2077    fn test_write_request_metadata() {
2078        let rows = Rows {
2079            schema: vec![
2080                new_column_schema("c0", ColumnDataType::Int64, SemanticType::Tag),
2081                new_column_schema("c1", ColumnDataType::Int64, SemanticType::Tag),
2082            ],
2083            rows: vec![Row {
2084                values: vec![i64_value(1), i64_value(2)],
2085            }],
2086        };
2087
2088        let metadata = Arc::new(new_region_metadata());
2089        let request = WriteRequest::new(
2090            RegionId::new(1, 1),
2091            OpType::Put,
2092            rows,
2093            Some(metadata.clone()),
2094        )
2095        .unwrap();
2096
2097        assert!(request.region_metadata.is_some());
2098        assert_eq!(
2099            request.region_metadata.unwrap().region_id,
2100            RegionId::new(1, 1)
2101        );
2102    }
2103}