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