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