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    /// Files in `edit` written without building their indexes.
1152    pub(crate) unindexed_files: Vec<FileId>,
1153    /// Memtables to remove.
1154    pub(crate) memtables_to_remove: SmallVec<[MemtableId; 2]>,
1155    /// Whether the region is in staging mode.
1156    pub(crate) is_staging: bool,
1157}
1158
1159impl FlushFinished {
1160    /// Marks the flush job as successful and observes the timer.
1161    pub(crate) fn on_success(self) {
1162        for sender in self.senders {
1163            sender.send(Ok(0));
1164        }
1165    }
1166}
1167
1168impl OnFailure for FlushFinished {
1169    fn on_failure(&mut self, err: Error) {
1170        let err = Arc::new(err);
1171        for sender in self.senders.drain(..) {
1172            sender.send(Err(err.clone()).context(FlushRegionSnafu {
1173                region_id: self.region_id,
1174            }));
1175        }
1176    }
1177}
1178
1179/// Notifies a flush job is failed.
1180#[derive(Debug)]
1181pub(crate) struct FlushFailed {
1182    /// The error source of the failure.
1183    pub(crate) err: Arc<Error>,
1184}
1185
1186impl FlushFailed {
1187    /// Returns whether the flush was cancelled cooperatively.
1188    pub(crate) fn is_cancelled(&self) -> bool {
1189        matches!(self.err.as_ref(), Error::FlushCancelled { .. })
1190    }
1191}
1192
1193#[derive(Debug)]
1194pub(crate) struct IndexBuildFinished {
1195    pub(crate) manifest_version: ManifestVersion,
1196    pub(crate) file_meta: FileMeta,
1197    /// Waiters of the build, notified after the worker applies the edit.
1198    pub(crate) result_senders: Vec<ResultMpscSender>,
1199}
1200
1201/// Notifies an index build job has been stopped.
1202#[derive(Debug)]
1203pub(crate) struct IndexBuildStopped {
1204    pub(crate) file_id: FileId,
1205}
1206
1207/// A local unit event carries only identity, never an input reservation lease.
1208#[derive(Debug)]
1209pub(crate) enum CompactionUnitNotification {
1210    /// A persisted edit or error awaits worker apply or failure acknowledgement.
1211    Finished {
1212        plan_id: u64,
1213        result: std::result::Result<RegionEdit, Arc<Error>>,
1214        applied: tokio::sync::oneshot::Sender<()>,
1215    },
1216    /// The task has exited and released its owned resources.
1217    Released {
1218        /// Identifies the exited attempt, not the whole compaction request.
1219        plan_id: u64,
1220    },
1221}
1222
1223/// Notifies a compaction job has finished.
1224#[derive(Debug)]
1225pub(crate) struct CompactionFinished {
1226    /// Region id.
1227    pub(crate) region_id: RegionId,
1228    /// Identity and reservation lease of the accepted execution.
1229    pub(crate) execution: CompactionExecution,
1230    /// Compaction result senders.
1231    pub(crate) senders: Vec<OutputTx>,
1232    /// Start time of compaction task.
1233    pub(crate) start_time: Instant,
1234    /// Region edit to apply.
1235    pub(crate) edit: RegionEdit,
1236}
1237
1238impl CompactionFinished {
1239    pub fn on_success(self) {
1240        // only update compaction time on success
1241        COMPACTION_ELAPSED_TOTAL
1242            .with_label_values(&["remote"])
1243            .observe(self.start_time.elapsed().as_secs_f64());
1244
1245        for sender in self.senders {
1246            sender.send(Ok(0));
1247        }
1248        info!("Successfully compacted region: {}", self.region_id);
1249    }
1250}
1251
1252impl OnFailure for CompactionFinished {
1253    /// Compaction succeeded but failed to update manifest or region's already been dropped.
1254    fn on_failure(&mut self, err: Error) {
1255        let err = Arc::new(err);
1256        for sender in self.senders.drain(..) {
1257            sender.send(Err(err.clone()).context(CompactRegionSnafu {
1258                region_id: self.region_id,
1259            }));
1260        }
1261    }
1262}
1263
1264/// A failing compaction result.
1265#[derive(Debug)]
1266pub(crate) struct CompactionFailed {
1267    pub(crate) region_id: RegionId,
1268    /// Identity and reservation lease of the accepted execution.
1269    pub(crate) execution: CompactionExecution,
1270    /// The error source of the failure.
1271    pub(crate) err: Arc<Error>,
1272}
1273
1274/// Notifies the truncate result of a region.
1275#[derive(Debug)]
1276pub(crate) struct TruncateResult {
1277    /// Region id.
1278    pub(crate) region_id: RegionId,
1279    /// Result sender.
1280    pub(crate) sender: OptionOutputTx,
1281    /// Truncate result.
1282    pub(crate) result: Result<()>,
1283    pub(crate) kind: TruncateKind,
1284}
1285
1286/// Notifies the result of discarding unflushed data from a region.
1287#[derive(Debug)]
1288pub(crate) struct DiscardUnflushedResult {
1289    /// Region id.
1290    pub(crate) region_id: RegionId,
1291    /// Result sender.
1292    pub(crate) sender: OptionOutputTx,
1293    /// Manifest update result.
1294    pub(crate) result: Result<()>,
1295    /// Last WAL entry covered by the discard operation.
1296    pub(crate) discarded_entry_id: EntryId,
1297    /// Last sequence covered by the discard operation.
1298    pub(crate) discarded_sequence: SequenceNumber,
1299    /// Estimated number of discarded rows.
1300    pub(crate) discarded_rows: u64,
1301    /// Estimated number of discarded bytes.
1302    pub(crate) discarded_bytes: u64,
1303}
1304
1305/// Notifies the region the result of writing region change action.
1306#[derive(Debug)]
1307pub(crate) struct RegionChangeResult {
1308    /// Region id.
1309    pub(crate) region_id: RegionId,
1310    /// The new region metadata to apply.
1311    pub(crate) new_meta: RegionMetadataRef,
1312    /// Result sender.
1313    pub(crate) sender: OptionOutputTx,
1314    /// Result from the manifest manager.
1315    pub(crate) result: Result<()>,
1316    /// Used for index build in schema change.
1317    pub(crate) need_index: bool,
1318    /// New options for the region.
1319    pub(crate) new_options: Option<RegionOptions>,
1320}
1321
1322/// Notifies the region the result of entering staging.
1323#[derive(Debug)]
1324pub(crate) struct EnterStagingResult {
1325    /// Region id.
1326    pub(crate) region_id: RegionId,
1327    /// The new staging partition directive to apply.
1328    pub(crate) partition_directive: StagingPartitionDirective,
1329    /// Result sender.
1330    pub(crate) sender: OptionOutputTx,
1331    /// Result from the manifest manager.
1332    pub(crate) result: Result<()>,
1333}
1334
1335#[derive(Debug)]
1336pub(crate) struct CopyRegionFromFinished {
1337    /// Region id.
1338    pub(crate) region_id: RegionId,
1339    /// Region edit to apply.
1340    pub(crate) edit: RegionEdit,
1341    /// Result sender.
1342    pub(crate) sender: Sender<Result<MitoCopyRegionFromResponse>>,
1343}
1344
1345#[derive(Debug, Default)]
1346pub(crate) struct Waiters(SmallVec<[Sender<Result<()>>; 1]>);
1347
1348impl Waiters {
1349    pub(crate) fn one(waiter: Sender<Result<()>>) -> Self {
1350        let mut waiters = SmallVec::new();
1351        waiters.push(waiter);
1352        Self(waiters)
1353    }
1354
1355    pub(crate) fn reply_with<F: Fn() -> Result<()>>(self, f: F) {
1356        for tx in self.0 {
1357            let _ = tx.send(f());
1358        }
1359    }
1360
1361    pub(crate) fn merge(&mut self, other: Self) {
1362        self.0.extend(other.0);
1363    }
1364}
1365
1366/// Request to edit a region directly.
1367#[derive(Debug)]
1368pub(crate) struct RegionEditRequest {
1369    pub(crate) region_id: RegionId,
1370    pub(crate) edit: RegionEdit,
1371    /// Whether to preload SST files into the write cache.
1372    pub(crate) preload_sst_cache: bool,
1373    /// The waiters that are waiting for this region edit's result.
1374    pub(crate) waiters: Waiters,
1375}
1376
1377impl RegionEditRequest {
1378    pub(crate) fn new(
1379        region_id: RegionId,
1380        edit: RegionEdit,
1381        preload_sst_cache: bool,
1382        waiter: Sender<Result<()>>,
1383    ) -> Self {
1384        Self {
1385            region_id,
1386            edit,
1387            preload_sst_cache,
1388            waiters: Waiters::one(waiter),
1389        }
1390    }
1391}
1392
1393/// Notifies the regin the result of editing region.
1394#[derive(Debug)]
1395pub(crate) struct RegionEditResult {
1396    /// Region id.
1397    pub(crate) region_id: RegionId,
1398    /// Result waiters.
1399    pub(crate) waiters: Waiters,
1400    /// Region edit to apply.
1401    pub(crate) edit: RegionEdit,
1402    /// Result from the manifest manager.
1403    pub(crate) result: std::result::Result<(), Arc<Error>>,
1404    /// Whether region state need to be set to Writable after handling this request.
1405    pub(crate) update_region_state: bool,
1406    /// The region is in staging mode before handling this request.
1407    pub(crate) is_staging: bool,
1408}
1409
1410#[derive(Debug)]
1411pub(crate) struct BuildIndexRequest {
1412    pub(crate) region_id: RegionId,
1413    pub(crate) build_type: IndexBuildType,
1414    /// files need to build index, empty means all.
1415    pub(crate) file_metas: Vec<FileMeta>,
1416    /// Waiters carried over from a previous build of the same files.
1417    pub(crate) result_senders: Vec<ResultMpscSender>,
1418}
1419
1420#[derive(Debug)]
1421pub(crate) struct RegionSyncRequest {
1422    pub(crate) region_id: RegionId,
1423    pub(crate) manifest_version: ManifestVersion,
1424    /// Returns the latest manifest version and a boolean indicating whether new maniefst is installed.
1425    pub(crate) sender: Sender<Result<(ManifestVersion, bool)>>,
1426}
1427
1428#[derive(Debug)]
1429pub(crate) struct RemapManifestsRequest {
1430    /// The [`RegionId`] of a staging region used to obtain table directory and storage configuration for the remap operation.
1431    pub(crate) region_id: RegionId,
1432    /// Regions to remap manifests from.
1433    pub(crate) input_regions: Vec<RegionId>,
1434    /// For each old region, which new regions should receive its files
1435    pub(crate) region_mapping: HashMap<RegionId, Vec<RegionId>>,
1436    /// New partition expressions for the new regions.
1437    pub(crate) new_partition_exprs: HashMap<RegionId, PartitionExpr>,
1438    /// Sender for the result of the remap operation.
1439    ///
1440    /// The result is a map from region IDs to their corresponding staging manifest paths.
1441    pub(crate) sender: Sender<Result<HashMap<RegionId, String>>>,
1442}
1443
1444#[derive(Debug)]
1445pub(crate) struct CopyRegionFromRequest {
1446    /// The [`RegionId`] of the target region.
1447    pub(crate) region_id: RegionId,
1448    /// The [`RegionId`] of the source region.
1449    pub(crate) source_region_id: RegionId,
1450    /// The parallelism of the copy operation.
1451    pub(crate) parallelism: usize,
1452    /// Result sender.
1453    pub(crate) sender: Sender<Result<MitoCopyRegionFromResponse>>,
1454}
1455
1456#[cfg(test)]
1457mod tests {
1458    use api::v1::value::ValueData;
1459    use api::v1::{Row, SemanticType};
1460    use datatypes::prelude::ConcreteDataType;
1461    use datatypes::schema::ColumnDefaultConstraint;
1462    use mito_codec::test_util::i64_value;
1463    use store_api::metadata::RegionMetadataBuilder;
1464    use tokio::sync::oneshot;
1465
1466    use super::*;
1467    use crate::error::Error;
1468    use crate::test_util::ts_ms_value;
1469
1470    fn new_column_schema(
1471        name: &str,
1472        data_type: ColumnDataType,
1473        semantic_type: SemanticType,
1474    ) -> ColumnSchema {
1475        ColumnSchema {
1476            column_name: name.to_string(),
1477            datatype: data_type as i32,
1478            semantic_type: semantic_type as i32,
1479            ..Default::default()
1480        }
1481    }
1482
1483    fn check_invalid_request(err: &Error, expect: &str) {
1484        if let Error::InvalidRequest {
1485            region_id: _,
1486            reason,
1487            location: _,
1488        } = err
1489        {
1490            assert_eq!(reason, expect);
1491        } else {
1492            panic!("Unexpected error {err}")
1493        }
1494    }
1495
1496    fn waiter() -> (Sender<Result<()>>, Receiver<Result<()>>) {
1497        oneshot::channel()
1498    }
1499
1500    fn assert_waiter_ok(rx: &mut Receiver<Result<()>>) {
1501        rx.try_recv().unwrap().unwrap();
1502    }
1503
1504    #[test]
1505    fn test_waiters_reply_with_single_waiter() {
1506        let (tx, mut rx) = waiter();
1507        Waiters::one(tx).reply_with(|| Ok(()));
1508        assert_waiter_ok(&mut rx);
1509    }
1510
1511    #[test]
1512    fn test_waiters_reply_with_many_waiters() {
1513        let (tx1, mut rx1) = waiter();
1514        let (tx2, mut rx2) = waiter();
1515        let (tx3, mut rx3) = waiter();
1516
1517        let waiters = Waiters(vec![tx1, tx2, tx3].into());
1518        waiters.reply_with(|| Ok(()));
1519
1520        assert_waiter_ok(&mut rx1);
1521        assert_waiter_ok(&mut rx2);
1522        assert_waiter_ok(&mut rx3);
1523    }
1524
1525    #[test]
1526    fn test_waiters_merge() {
1527        let (tx1, mut rx1) = waiter();
1528        let (tx2, mut rx2) = waiter();
1529        let (tx3, mut rx3) = waiter();
1530        let (tx4, mut rx4) = waiter();
1531
1532        let mut waiters = Waiters::one(tx1);
1533        waiters.merge(Waiters::one(tx2));
1534        waiters.merge(Waiters(vec![tx3, tx4].into()));
1535        assert_eq!(4, waiters.0.len());
1536
1537        waiters.reply_with(|| Ok(()));
1538
1539        assert_waiter_ok(&mut rx1);
1540        assert_waiter_ok(&mut rx2);
1541        assert_waiter_ok(&mut rx3);
1542        assert_waiter_ok(&mut rx4);
1543    }
1544
1545    #[test]
1546    fn test_write_request_duplicate_column() {
1547        let rows = Rows {
1548            schema: vec![
1549                new_column_schema("c0", ColumnDataType::Int64, SemanticType::Tag),
1550                new_column_schema("c0", ColumnDataType::Int64, SemanticType::Tag),
1551            ],
1552            rows: vec![],
1553        };
1554
1555        let err = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap_err();
1556        check_invalid_request(&err, "duplicate column c0");
1557    }
1558
1559    #[test]
1560    fn test_valid_write_request() {
1561        let rows = Rows {
1562            schema: vec![
1563                new_column_schema("c0", ColumnDataType::Int64, SemanticType::Tag),
1564                new_column_schema("c1", ColumnDataType::Int64, SemanticType::Tag),
1565            ],
1566            rows: vec![Row {
1567                values: vec![i64_value(1), i64_value(2)],
1568            }],
1569        };
1570
1571        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1572        assert_eq!(0, request.column_index_by_name("c0").unwrap());
1573        assert_eq!(1, request.column_index_by_name("c1").unwrap());
1574        assert_eq!(None, request.column_index_by_name("c2"));
1575    }
1576
1577    #[test]
1578    fn test_write_request_column_num() {
1579        let rows = Rows {
1580            schema: vec![
1581                new_column_schema("c0", ColumnDataType::Int64, SemanticType::Tag),
1582                new_column_schema("c1", ColumnDataType::Int64, SemanticType::Tag),
1583            ],
1584            rows: vec![Row {
1585                values: vec![i64_value(1), i64_value(2), i64_value(3)],
1586            }],
1587        };
1588
1589        let err = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap_err();
1590        check_invalid_request(&err, "row has 3 columns but schema has 2");
1591    }
1592
1593    fn new_region_metadata() -> RegionMetadata {
1594        let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 1));
1595        builder
1596            .push_column_metadata(ColumnMetadata {
1597                column_schema: datatypes::schema::ColumnSchema::new(
1598                    "ts",
1599                    ConcreteDataType::timestamp_millisecond_datatype(),
1600                    false,
1601                ),
1602                semantic_type: SemanticType::Timestamp,
1603                column_id: 1,
1604            })
1605            .push_column_metadata(ColumnMetadata {
1606                column_schema: datatypes::schema::ColumnSchema::new(
1607                    "k0",
1608                    ConcreteDataType::int64_datatype(),
1609                    true,
1610                ),
1611                semantic_type: SemanticType::Tag,
1612                column_id: 2,
1613            })
1614            .primary_key(vec![2]);
1615        builder.build().unwrap()
1616    }
1617
1618    #[test]
1619    fn test_check_schema() {
1620        let rows = Rows {
1621            schema: vec![
1622                new_column_schema(
1623                    "ts",
1624                    ColumnDataType::TimestampMillisecond,
1625                    SemanticType::Timestamp,
1626                ),
1627                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1628            ],
1629            rows: vec![Row {
1630                values: vec![ts_ms_value(1), i64_value(2)],
1631            }],
1632        };
1633        let metadata = new_region_metadata();
1634
1635        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1636        request.check_schema(&metadata).unwrap();
1637    }
1638
1639    #[test]
1640    fn test_column_type() {
1641        let rows = Rows {
1642            schema: vec![
1643                new_column_schema("ts", ColumnDataType::Int64, SemanticType::Timestamp),
1644                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1645            ],
1646            rows: vec![Row {
1647                values: vec![i64_value(1), i64_value(2)],
1648            }],
1649        };
1650        let metadata = new_region_metadata();
1651
1652        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1653        let err = request.check_schema(&metadata).unwrap_err();
1654        check_invalid_request(
1655            &err,
1656            "column ts expect type Timestamp(Millisecond(TimestampMillisecondType)), given: INT64(4)",
1657        );
1658    }
1659
1660    #[test]
1661    fn test_semantic_type() {
1662        let rows = Rows {
1663            schema: vec![
1664                new_column_schema(
1665                    "ts",
1666                    ColumnDataType::TimestampMillisecond,
1667                    SemanticType::Tag,
1668                ),
1669                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1670            ],
1671            rows: vec![Row {
1672                values: vec![ts_ms_value(1), i64_value(2)],
1673            }],
1674        };
1675        let metadata = new_region_metadata();
1676
1677        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1678        let err = request.check_schema(&metadata).unwrap_err();
1679        check_invalid_request(&err, "column ts has semantic type Timestamp, given: TAG(0)");
1680    }
1681
1682    #[test]
1683    fn test_column_nullable() {
1684        let rows = Rows {
1685            schema: vec![
1686                new_column_schema(
1687                    "ts",
1688                    ColumnDataType::TimestampMillisecond,
1689                    SemanticType::Timestamp,
1690                ),
1691                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1692            ],
1693            rows: vec![Row {
1694                values: vec![Value { value_data: None }, i64_value(2)],
1695            }],
1696        };
1697        let metadata = new_region_metadata();
1698
1699        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1700        let err = request.check_schema(&metadata).unwrap_err();
1701        check_invalid_request(&err, "column ts is not null but input has null");
1702    }
1703
1704    #[test]
1705    fn test_column_default() {
1706        let rows = Rows {
1707            schema: vec![new_column_schema(
1708                "k0",
1709                ColumnDataType::Int64,
1710                SemanticType::Tag,
1711            )],
1712            rows: vec![Row {
1713                values: vec![i64_value(1)],
1714            }],
1715        };
1716        let metadata = new_region_metadata();
1717
1718        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1719        let err = request.check_schema(&metadata).unwrap_err();
1720        check_invalid_request(&err, "missing column ts");
1721    }
1722
1723    #[test]
1724    fn test_unknown_column() {
1725        let rows = Rows {
1726            schema: vec![
1727                new_column_schema(
1728                    "ts",
1729                    ColumnDataType::TimestampMillisecond,
1730                    SemanticType::Timestamp,
1731                ),
1732                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1733                new_column_schema("k1", ColumnDataType::Int64, SemanticType::Tag),
1734            ],
1735            rows: vec![Row {
1736                values: vec![ts_ms_value(1), i64_value(2), i64_value(3)],
1737            }],
1738        };
1739        let metadata = new_region_metadata();
1740
1741        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1742        let err = request.check_schema(&metadata).unwrap_err();
1743        check_invalid_request(&err, r#"unknown columns: ["k1"]"#);
1744    }
1745
1746    #[test]
1747    fn test_fill_impure_columns_err() {
1748        let rows = Rows {
1749            schema: vec![new_column_schema(
1750                "k0",
1751                ColumnDataType::Int64,
1752                SemanticType::Tag,
1753            )],
1754            rows: vec![Row {
1755                values: vec![i64_value(1)],
1756            }],
1757        };
1758        let metadata = {
1759            let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 1));
1760            builder
1761                .push_column_metadata(ColumnMetadata {
1762                    column_schema: datatypes::schema::ColumnSchema::new(
1763                        "ts",
1764                        ConcreteDataType::timestamp_millisecond_datatype(),
1765                        false,
1766                    )
1767                    .with_default_constraint(Some(ColumnDefaultConstraint::Function(
1768                        "now()".to_string(),
1769                    )))
1770                    .unwrap(),
1771                    semantic_type: SemanticType::Timestamp,
1772                    column_id: 1,
1773                })
1774                .push_column_metadata(ColumnMetadata {
1775                    column_schema: datatypes::schema::ColumnSchema::new(
1776                        "k0",
1777                        ConcreteDataType::int64_datatype(),
1778                        true,
1779                    ),
1780                    semantic_type: SemanticType::Tag,
1781                    column_id: 2,
1782                })
1783                .primary_key(vec![2]);
1784            builder.build().unwrap()
1785        };
1786
1787        let mut request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1788        let err = request.check_schema(&metadata).unwrap_err();
1789        assert!(err.is_fill_default());
1790        assert!(
1791            request
1792                .fill_missing_columns(&metadata)
1793                .unwrap_err()
1794                .to_string()
1795                .contains("unexpected impure default value with region_id")
1796        );
1797    }
1798
1799    #[test]
1800    fn test_fill_missing_columns() {
1801        let rows = Rows {
1802            schema: vec![new_column_schema(
1803                "ts",
1804                ColumnDataType::TimestampMillisecond,
1805                SemanticType::Timestamp,
1806            )],
1807            rows: vec![Row {
1808                values: vec![ts_ms_value(1)],
1809            }],
1810        };
1811        let metadata = new_region_metadata();
1812
1813        let mut request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
1814        let err = request.check_schema(&metadata).unwrap_err();
1815        assert!(err.is_fill_default());
1816        request.fill_missing_columns(&metadata).unwrap();
1817
1818        let expect_rows = Rows {
1819            schema: vec![new_column_schema(
1820                "ts",
1821                ColumnDataType::TimestampMillisecond,
1822                SemanticType::Timestamp,
1823            )],
1824            rows: vec![Row {
1825                values: vec![ts_ms_value(1)],
1826            }],
1827        };
1828        assert_eq!(expect_rows, request.rows);
1829    }
1830
1831    fn builder_with_ts_tag() -> RegionMetadataBuilder {
1832        let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 1));
1833        builder
1834            .push_column_metadata(ColumnMetadata {
1835                column_schema: datatypes::schema::ColumnSchema::new(
1836                    "ts",
1837                    ConcreteDataType::timestamp_millisecond_datatype(),
1838                    false,
1839                ),
1840                semantic_type: SemanticType::Timestamp,
1841                column_id: 1,
1842            })
1843            .push_column_metadata(ColumnMetadata {
1844                column_schema: datatypes::schema::ColumnSchema::new(
1845                    "k0",
1846                    ConcreteDataType::int64_datatype(),
1847                    true,
1848                ),
1849                semantic_type: SemanticType::Tag,
1850                column_id: 2,
1851            })
1852            .primary_key(vec![2]);
1853        builder
1854    }
1855
1856    fn region_metadata_two_fields() -> RegionMetadata {
1857        let mut builder = builder_with_ts_tag();
1858        builder
1859            .push_column_metadata(ColumnMetadata {
1860                column_schema: datatypes::schema::ColumnSchema::new(
1861                    "f0",
1862                    ConcreteDataType::int64_datatype(),
1863                    true,
1864                ),
1865                semantic_type: SemanticType::Field,
1866                column_id: 3,
1867            })
1868            // Column is not nullable.
1869            .push_column_metadata(ColumnMetadata {
1870                column_schema: datatypes::schema::ColumnSchema::new(
1871                    "f1",
1872                    ConcreteDataType::int64_datatype(),
1873                    false,
1874                )
1875                .with_default_constraint(Some(ColumnDefaultConstraint::Value(
1876                    datatypes::value::Value::Int64(100),
1877                )))
1878                .unwrap(),
1879                semantic_type: SemanticType::Field,
1880                column_id: 4,
1881            });
1882        builder.build().unwrap()
1883    }
1884
1885    #[test]
1886    fn test_fill_missing_for_delete() {
1887        let rows = Rows {
1888            schema: vec![new_column_schema(
1889                "ts",
1890                ColumnDataType::TimestampMillisecond,
1891                SemanticType::Timestamp,
1892            )],
1893            rows: vec![Row {
1894                values: vec![ts_ms_value(1)],
1895            }],
1896        };
1897        let metadata = region_metadata_two_fields();
1898
1899        let mut request =
1900            WriteRequest::new(RegionId::new(1, 1), OpType::Delete, rows, None).unwrap();
1901        let err = request.check_schema(&metadata).unwrap_err();
1902        check_invalid_request(&err, "delete requests need column k0");
1903        let err = request.fill_missing_columns(&metadata).unwrap_err();
1904        check_invalid_request(&err, "delete requests need column k0");
1905
1906        let rows = Rows {
1907            schema: vec![
1908                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1909                new_column_schema(
1910                    "ts",
1911                    ColumnDataType::TimestampMillisecond,
1912                    SemanticType::Timestamp,
1913                ),
1914            ],
1915            rows: vec![Row {
1916                values: vec![i64_value(100), ts_ms_value(1)],
1917            }],
1918        };
1919        let mut request =
1920            WriteRequest::new(RegionId::new(1, 1), OpType::Delete, rows, None).unwrap();
1921        let err = request.check_schema(&metadata).unwrap_err();
1922        assert!(err.is_fill_default());
1923        request.fill_missing_columns(&metadata).unwrap();
1924
1925        let expect_rows = Rows {
1926            schema: vec![
1927                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1928                new_column_schema(
1929                    "ts",
1930                    ColumnDataType::TimestampMillisecond,
1931                    SemanticType::Timestamp,
1932                ),
1933                new_column_schema("f1", ColumnDataType::Int64, SemanticType::Field),
1934            ],
1935            // Column f1 is not nullable and we use 0 for padding.
1936            rows: vec![Row {
1937                values: vec![i64_value(100), ts_ms_value(1), i64_value(0)],
1938            }],
1939        };
1940        assert_eq!(expect_rows, request.rows);
1941    }
1942
1943    #[test]
1944    fn test_fill_missing_without_default_in_delete() {
1945        let mut builder = builder_with_ts_tag();
1946        builder
1947            // f0 is nullable.
1948            .push_column_metadata(ColumnMetadata {
1949                column_schema: datatypes::schema::ColumnSchema::new(
1950                    "f0",
1951                    ConcreteDataType::int64_datatype(),
1952                    true,
1953                ),
1954                semantic_type: SemanticType::Field,
1955                column_id: 3,
1956            })
1957            // f1 is not nullable and don't has default.
1958            .push_column_metadata(ColumnMetadata {
1959                column_schema: datatypes::schema::ColumnSchema::new(
1960                    "f1",
1961                    ConcreteDataType::int64_datatype(),
1962                    false,
1963                ),
1964                semantic_type: SemanticType::Field,
1965                column_id: 4,
1966            });
1967        let metadata = builder.build().unwrap();
1968
1969        let rows = Rows {
1970            schema: vec![
1971                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1972                new_column_schema(
1973                    "ts",
1974                    ColumnDataType::TimestampMillisecond,
1975                    SemanticType::Timestamp,
1976                ),
1977            ],
1978            // Missing f0 (nullable), f1 (not nullable).
1979            rows: vec![Row {
1980                values: vec![i64_value(100), ts_ms_value(1)],
1981            }],
1982        };
1983        let mut request =
1984            WriteRequest::new(RegionId::new(1, 1), OpType::Delete, rows, None).unwrap();
1985        let err = request.check_schema(&metadata).unwrap_err();
1986        assert!(err.is_fill_default());
1987        request.fill_missing_columns(&metadata).unwrap();
1988
1989        let expect_rows = Rows {
1990            schema: vec![
1991                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
1992                new_column_schema(
1993                    "ts",
1994                    ColumnDataType::TimestampMillisecond,
1995                    SemanticType::Timestamp,
1996                ),
1997                new_column_schema("f1", ColumnDataType::Int64, SemanticType::Field),
1998            ],
1999            // Column f1 is not nullable and we use 0 for padding.
2000            rows: vec![Row {
2001                values: vec![i64_value(100), ts_ms_value(1), i64_value(0)],
2002            }],
2003        };
2004        assert_eq!(expect_rows, request.rows);
2005    }
2006
2007    #[test]
2008    fn test_no_default() {
2009        let rows = Rows {
2010            schema: vec![new_column_schema(
2011                "k0",
2012                ColumnDataType::Int64,
2013                SemanticType::Tag,
2014            )],
2015            rows: vec![Row {
2016                values: vec![i64_value(1)],
2017            }],
2018        };
2019        let metadata = new_region_metadata();
2020
2021        let mut request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
2022        let err = request.fill_missing_columns(&metadata).unwrap_err();
2023        check_invalid_request(&err, "column ts does not have default value");
2024    }
2025
2026    #[test]
2027    fn test_missing_and_invalid() {
2028        // Missing f0 and f1 has invalid type (string).
2029        let rows = Rows {
2030            schema: vec![
2031                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
2032                new_column_schema(
2033                    "ts",
2034                    ColumnDataType::TimestampMillisecond,
2035                    SemanticType::Timestamp,
2036                ),
2037                new_column_schema("f1", ColumnDataType::String, SemanticType::Field),
2038            ],
2039            rows: vec![Row {
2040                values: vec![
2041                    i64_value(100),
2042                    ts_ms_value(1),
2043                    Value {
2044                        value_data: Some(ValueData::StringValue("xxxxx".to_string())),
2045                    },
2046                ],
2047            }],
2048        };
2049        let metadata = region_metadata_two_fields();
2050
2051        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
2052        let err = request.check_schema(&metadata).unwrap_err();
2053        check_invalid_request(
2054            &err,
2055            "column f1 expect type Int64(Int64Type), given: STRING(12)",
2056        );
2057    }
2058
2059    #[test]
2060    fn test_delete_request_defaults_to_writing_wal() {
2061        let (request, _receiver) = WorkerRequest::try_from_region_request(
2062            RegionId::new(1, 1),
2063            RegionRequest::Delete(store_api::region_request::RegionDeleteRequest {
2064                rows: Rows::default(),
2065                hint: None,
2066                partition_expr_version: None,
2067            }),
2068            None,
2069        )
2070        .unwrap();
2071        let WorkerRequest::Write(request) = request else {
2072            panic!("expected a write request");
2073        };
2074        assert_eq!(request.request.op_type, OpType::Delete);
2075        assert!(!request.request.skip_wal);
2076    }
2077
2078    #[test]
2079    fn test_write_request_metadata() {
2080        let rows = Rows {
2081            schema: vec![
2082                new_column_schema("c0", ColumnDataType::Int64, SemanticType::Tag),
2083                new_column_schema("c1", ColumnDataType::Int64, SemanticType::Tag),
2084            ],
2085            rows: vec![Row {
2086                values: vec![i64_value(1), i64_value(2)],
2087            }],
2088        };
2089
2090        let metadata = Arc::new(new_region_metadata());
2091        let request = WriteRequest::new(
2092            RegionId::new(1, 1),
2093            OpType::Put,
2094            rows,
2095            Some(metadata.clone()),
2096        )
2097        .unwrap();
2098
2099        assert!(request.region_metadata.is_some());
2100        assert_eq!(
2101            request.region_metadata.unwrap().region_id,
2102            RegionId::new(1, 1)
2103        );
2104    }
2105}