// Copyright 2023 Greptime Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Worker requests.

use std::collections::HashMap;
use std::sync::Arc;
use std::time::Instant;

use api::helper::{
    ColumnDataTypeWrapper, is_column_type_value_eq, is_semantic_type_eq, proto_value_type,
};
use api::v1::column_def::options_from_column_schema;
use api::v1::{ColumnDataType, ColumnSchema, OpType, Rows, SemanticType, Value, WriteHint};
use common_telemetry::info;
use datatypes::prelude::DataType;
use partition::expr::PartitionExpr;
use prometheus::HistogramTimer;
use prost::Message;
use smallvec::SmallVec;
use snafu::{OptionExt, ResultExt, ensure};
use store_api::ManifestVersion;
use store_api::codec::{PrimaryKeyEncoding, infer_primary_key_encoding_from_hint};
use store_api::metadata::{ColumnMetadata, RegionMetadata, RegionMetadataRef};
use store_api::region_engine::{
    MitoCopyRegionFromResponse, SetRegionRoleStateResponse, SettableRegionRoleState,
};
use store_api::region_request::{
    AffectedRows, ApplyStagingManifestRequest, EnterStagingRequest, RegionAlterRequest,
    RegionBuildIndexRequest, RegionBulkInsertsRequest, RegionCatchupRequest, RegionCleanUpRequest,
    RegionCloseRequest, RegionCompactRequest, RegionCreateRequest, RegionDropRequest,
    RegionFlushRequest, RegionOpenRequest, RegionRequest, RegionTruncateRequest,
    StagingPartitionDirective,
};
use store_api::storage::{FileId, RegionId, SequenceNumber};
use tokio::sync::oneshot::{self, Receiver, Sender};

use crate::compaction::{CompactionExecution, CompactionPickFinished};
use crate::error::{
    CompactRegionSnafu, CompactionCancelledSnafu, ConvertColumnDataTypeSnafu, CreateDefaultSnafu,
    Error, FillDefaultSnafu, FlushRegionSnafu, InvalidPartitionExprSnafu, InvalidRequestSnafu,
    MissingPartitionExprSnafu, Result, UnexpectedSnafu,
};
use crate::flush::FlushReason;
use crate::manifest::action::{RegionEdit, TruncateKind};
use crate::memtable::MemtableId;
use crate::memtable::bulk::part::BulkPart;
use crate::metrics::COMPACTION_ELAPSED_TOTAL;
use crate::region::options::RegionOptions;
use crate::sst::file::FileMeta;
use crate::sst::index::IndexBuildType;
use crate::wal::EntryId;
use crate::wal::entry_distributor::WalEntryReceiver;

/// Request to write a region.
#[derive(Debug)]
pub struct WriteRequest {
    /// Region to write.
    pub region_id: RegionId,
    /// Type of the write request.
    pub op_type: OpType,
    /// Rows to write.
    pub rows: Rows,
    /// Map column name to column index in `rows`.
    pub name_to_index: HashMap<String, usize>,
    /// Whether each column has null.
    pub has_null: Vec<bool>,
    /// Write hint.
    pub hint: Option<WriteHint>,
    /// Region metadata on the time of this request is created.
    pub(crate) region_metadata: Option<RegionMetadataRef>,
    /// Partition expression version for the region.
    pub partition_expr_version: Option<u64>,
}

impl WriteRequest {
    /// Creates a new request.
    ///
    /// Returns `Err` if `rows` are invalid.
    pub fn new(
        region_id: RegionId,
        op_type: OpType,
        rows: Rows,
        region_metadata: Option<RegionMetadataRef>,
    ) -> Result<WriteRequest> {
        let mut name_to_index = HashMap::with_capacity(rows.schema.len());
        for (index, column) in rows.schema.iter().enumerate() {
            ensure!(
                name_to_index
                    .insert(column.column_name.clone(), index)
                    .is_none(),
                InvalidRequestSnafu {
                    region_id,
                    reason: format!("duplicate column {}", column.column_name),
                }
            );
        }

        let mut has_null = vec![false; rows.schema.len()];
        for row in &rows.rows {
            ensure!(
                row.values.len() == rows.schema.len(),
                InvalidRequestSnafu {
                    region_id,
                    reason: format!(
                        "row has {} columns but schema has {}",
                        row.values.len(),
                        rows.schema.len()
                    ),
                }
            );

            for (i, (value, column_schema)) in row.values.iter().zip(&rows.schema).enumerate() {
                validate_proto_value(region_id, value, column_schema)?;

                if value.value_data.is_none() {
                    has_null[i] = true;
                }
            }
        }

        Ok(WriteRequest {
            region_id,
            op_type,
            rows,
            name_to_index,
            has_null,
            hint: None,
            region_metadata,
            partition_expr_version: None,
        })
    }

    /// Sets the write hint.
    pub fn with_hint(mut self, hint: Option<WriteHint>) -> Self {
        self.hint = hint;
        self
    }

    pub fn with_partition_expr_version(mut self, partition_expr_version: Option<u64>) -> Self {
        self.partition_expr_version = partition_expr_version;
        self
    }

    /// Returns the encoding hint.
    pub fn primary_key_encoding(&self) -> PrimaryKeyEncoding {
        infer_primary_key_encoding_from_hint(self.hint.as_ref())
    }

    /// Returns estimated size of the request.
    pub(crate) fn estimated_size(&self) -> usize {
        let row_size = self
            .rows
            .rows
            .first()
            .map(|row| row.encoded_len())
            .unwrap_or(0);
        row_size * self.rows.rows.len()
    }

    /// Gets column index by name.
    pub fn column_index_by_name(&self, name: &str) -> Option<usize> {
        self.name_to_index.get(name).copied()
    }

    /// Checks schema of rows is compatible with schema of the region.
    ///
    /// If column with default value is missing, it returns a special [FillDefault](crate::error::Error::FillDefault)
    /// error.
    pub(crate) fn check_schema(&self, metadata: &RegionMetadata) -> Result<()> {
        debug_assert_eq!(self.region_id, metadata.region_id);

        let region_id = self.region_id;
        // Index all columns in rows.
        let mut rows_columns: HashMap<_, _> = self
            .rows
            .schema
            .iter()
            .map(|column| (&column.column_name, column))
            .collect();

        let mut need_fill_default = false;
        // Checks all columns in this region.
        for column in &metadata.column_metadatas {
            if let Some(input_col) = rows_columns.remove(&column.column_schema.name) {
                // Check data type.
                ensure!(
                    is_column_type_value_eq(
                        input_col.datatype,
                        input_col.datatype_extension.clone(),
                        &column.column_schema.data_type
                    ),
                    InvalidRequestSnafu {
                        region_id,
                        reason: format!(
                            "column {} expect type {:?}, given: {}({})",
                            column.column_schema.name,
                            column.column_schema.data_type,
                            ColumnDataType::try_from(input_col.datatype)
                                .map(|v| v.as_str_name())
                                .unwrap_or("Unknown"),
                            input_col.datatype,
                        )
                    }
                );

                // Check semantic type.
                ensure!(
                    is_semantic_type_eq(input_col.semantic_type, column.semantic_type),
                    InvalidRequestSnafu {
                        region_id,
                        reason: format!(
                            "column {} has semantic type {:?}, given: {}({})",
                            column.column_schema.name,
                            column.semantic_type,
                            api::v1::SemanticType::try_from(input_col.semantic_type)
                                .map(|v| v.as_str_name())
                                .unwrap_or("Unknown"),
                            input_col.semantic_type
                        ),
                    }
                );

                // Check nullable.
                // Safety: `rows_columns` ensures this column exists.
                let has_null = self.has_null[self.name_to_index[&column.column_schema.name]];
                ensure!(
                    !has_null || column.column_schema.is_nullable(),
                    InvalidRequestSnafu {
                        region_id,
                        reason: format!(
                            "column {} is not null but input has null",
                            column.column_schema.name
                        ),
                    }
                );
            } else {
                // Rows don't have this column.
                self.check_missing_column(column)?;

                need_fill_default = true;
            }
        }

        // Checks all columns in rows exist in the region.
        if !rows_columns.is_empty() {
            let names: Vec<_> = rows_columns.into_keys().collect();
            return InvalidRequestSnafu {
                region_id,
                reason: format!("unknown columns: {:?}", names),
            }
            .fail();
        }

        // If we need to fill default values, return a special error.
        ensure!(!need_fill_default, FillDefaultSnafu { region_id });

        Ok(())
    }

    /// Tries to fill missing columns.
    ///
    /// Currently, our protobuf format might be inefficient when we need to fill lots of null
    /// values.
    pub(crate) fn fill_missing_columns(&mut self, metadata: &RegionMetadata) -> Result<()> {
        debug_assert_eq!(self.region_id, metadata.region_id);

        let mut columns_to_fill = vec![];
        for column in &metadata.column_metadatas {
            if !self.name_to_index.contains_key(&column.column_schema.name) {
                columns_to_fill.push(column);
            }
        }
        self.fill_columns(columns_to_fill)?;

        Ok(())
    }

    /// Checks the schema and fill missing columns.
    pub(crate) fn maybe_fill_missing_columns(&mut self, metadata: &RegionMetadata) -> Result<()> {
        if let Err(e) = self.check_schema(metadata) {
            if e.is_fill_default() {
                // TODO(yingwen): Add metrics for this case.
                // We need to fill default value. The write request may be a request
                // sent before changing the schema.
                self.fill_missing_columns(metadata)?;
            } else {
                return Err(e);
            }
        }

        Ok(())
    }

    /// Fills default value for specific `columns`.
    fn fill_columns(&mut self, columns: Vec<&ColumnMetadata>) -> Result<()> {
        let mut default_values = Vec::with_capacity(columns.len());
        let mut columns_to_fill = Vec::with_capacity(columns.len());
        for column in columns {
            let default_value = self.column_default_value(column)?;
            if default_value.value_data.is_some() {
                default_values.push(default_value);
                columns_to_fill.push(column);
            }
        }

        for row in &mut self.rows.rows {
            row.values.extend(default_values.iter().cloned());
        }

        for column in columns_to_fill {
            let (datatype, datatype_ext) =
                ColumnDataTypeWrapper::try_from(column.column_schema.data_type.clone())
                    .with_context(|_| ConvertColumnDataTypeSnafu {
                        reason: format!(
                            "no protobuf type for column {} ({:?})",
                            column.column_schema.name, column.column_schema.data_type
                        ),
                    })?
                    .to_parts();
            self.rows.schema.push(ColumnSchema {
                column_name: column.column_schema.name.clone(),
                datatype: datatype as i32,
                semantic_type: column.semantic_type as i32,
                datatype_extension: datatype_ext,
                options: options_from_column_schema(&column.column_schema),
            });
        }

        Ok(())
    }

    /// Checks whether we should allow a row doesn't provide this column.
    fn check_missing_column(&self, column: &ColumnMetadata) -> Result<()> {
        if self.op_type == OpType::Delete {
            if column.semantic_type == SemanticType::Field {
                // For delete request, all tags and timestamp is required. We don't fill default
                // tag or timestamp while deleting rows.
                return Ok(());
            } else {
                return InvalidRequestSnafu {
                    region_id: self.region_id,
                    reason: format!("delete requests need column {}", column.column_schema.name),
                }
                .fail();
            }
        }

        // Not a delete request. Checks whether they have default value.
        ensure!(
            column.column_schema.is_nullable()
                || column.column_schema.default_constraint().is_some(),
            InvalidRequestSnafu {
                region_id: self.region_id,
                reason: format!("missing column {}", column.column_schema.name),
            }
        );

        Ok(())
    }

    /// Returns the default value for specific column.
    fn column_default_value(&self, column: &ColumnMetadata) -> Result<Value> {
        let default_value = match self.op_type {
            OpType::Delete => {
                ensure!(
                    column.semantic_type == SemanticType::Field,
                    InvalidRequestSnafu {
                        region_id: self.region_id,
                        reason: format!(
                            "delete requests need column {}",
                            column.column_schema.name
                        ),
                    }
                );

                // For delete request, we need a default value for padding so we
                // can delete a row even a field doesn't have a default value. So the
                // value doesn't need to following the default value constraint of the
                // column.
                if column.column_schema.is_nullable() {
                    datatypes::value::Value::Null
                } else {
                    column.column_schema.data_type.default_value()
                }
            }
            OpType::Put => {
                // For put requests, we use the default value from column schema.
                if column.column_schema.is_default_impure() {
                    UnexpectedSnafu {
                        reason: format!(
                            "unexpected impure default value with region_id: {}, column: {}, default_value: {:?}",
                            self.region_id,
                            column.column_schema.name,
                            column.column_schema.default_constraint(),
                        ),
                    }
                    .fail()?
                }
                column
                    .column_schema
                    .create_default()
                    .context(CreateDefaultSnafu {
                        region_id: self.region_id,
                        column: &column.column_schema.name,
                    })?
                    // This column doesn't have default value.
                    .with_context(|| InvalidRequestSnafu {
                        region_id: self.region_id,
                        reason: format!(
                            "column {} does not have default value",
                            column.column_schema.name
                        ),
                    })?
            }
        };

        // Convert default value into proto's value.
        Ok(api::helper::to_grpc_value(default_value))
    }
}

/// Validate proto value schema.
pub(crate) fn validate_proto_value(
    region_id: RegionId,
    value: &Value,
    column_schema: &ColumnSchema,
) -> Result<()> {
    if let Some(value_type) = proto_value_type(value) {
        let column_type = ColumnDataType::try_from(column_schema.datatype).map_err(|_| {
            InvalidRequestSnafu {
                region_id,
                reason: format!(
                    "column {} has unknown type {}",
                    column_schema.column_name, column_schema.datatype
                ),
            }
            .build()
        })?;
        ensure!(
            proto_value_type_match(column_type, value_type),
            InvalidRequestSnafu {
                region_id,
                reason: format!(
                    "value has type {:?}, but column {} has type {:?}({})",
                    value_type, column_schema.column_name, column_type, column_schema.datatype,
                ),
            }
        );
    }

    Ok(())
}

fn proto_value_type_match(column_type: ColumnDataType, value_type: ColumnDataType) -> bool {
    match (column_type, value_type) {
        (ct, vt) if ct == vt => true,
        (ColumnDataType::Vector, ColumnDataType::Binary) => true,
        (ColumnDataType::Json, ColumnDataType::Binary) => true,
        _ => false,
    }
}

/// Oneshot output result sender.
#[derive(Debug)]
pub struct OutputTx(Sender<Result<AffectedRows>>);

impl OutputTx {
    /// Creates a new output sender.
    pub(crate) fn new(sender: Sender<Result<AffectedRows>>) -> OutputTx {
        OutputTx(sender)
    }

    /// Sends the `result`.
    pub(crate) fn send(self, result: Result<AffectedRows>) {
        // Ignores send result.
        let _ = self.0.send(result);
    }
}

/// Optional output result sender.
#[derive(Debug)]
pub(crate) struct OptionOutputTx(Option<OutputTx>);

impl OptionOutputTx {
    /// Creates a sender.
    pub(crate) fn new(sender: Option<OutputTx>) -> OptionOutputTx {
        OptionOutputTx(sender)
    }

    /// Creates an empty sender.
    pub(crate) fn none() -> OptionOutputTx {
        OptionOutputTx(None)
    }

    /// Sends the `result` and consumes the inner sender.
    pub(crate) fn send_mut(&mut self, result: Result<AffectedRows>) {
        if let Some(sender) = self.0.take() {
            sender.send(result);
        }
    }

    /// Sends the `result` and consumes the sender.
    pub(crate) fn send(mut self, result: Result<AffectedRows>) {
        if let Some(sender) = self.0.take() {
            sender.send(result);
        }
    }

    /// Takes the inner sender.
    pub(crate) fn take_inner(&mut self) -> Option<OutputTx> {
        self.0.take()
    }
}

impl From<Sender<Result<AffectedRows>>> for OptionOutputTx {
    fn from(sender: Sender<Result<AffectedRows>>) -> Self {
        Self::new(Some(OutputTx::new(sender)))
    }
}

impl OnFailure for OptionOutputTx {
    fn on_failure(&mut self, err: Error) {
        self.send_mut(Err(err));
    }
}

/// Callback on failure.
pub(crate) trait OnFailure {
    /// Handles `err` on failure.
    fn on_failure(&mut self, err: Error);
}

/// Sender and write request.
#[derive(Debug)]
pub(crate) struct SenderWriteRequest {
    /// Result sender.
    pub(crate) sender: OptionOutputTx,
    pub(crate) request: WriteRequest,
}

pub(crate) struct SenderBulkRequest {
    pub(crate) sender: OptionOutputTx,
    pub(crate) region_id: RegionId,
    pub(crate) request: BulkPart,
    pub(crate) region_metadata: Option<RegionMetadataRef>,
    pub(crate) partition_expr_version: Option<u64>,
}

#[derive(Debug)]
pub(crate) struct BulkInsertRequest {
    pub(crate) metadata: Option<RegionMetadataRef>,
    pub(crate) request: RegionBulkInsertsRequest,
    pub(crate) sender: OptionOutputTx,
}

/// Request sent to a worker with timestamp
#[derive(Debug)]
pub(crate) struct WorkerRequestWithTime {
    pub(crate) request: WorkerRequest,
    pub(crate) created_at: Instant,
}

impl WorkerRequestWithTime {
    pub(crate) fn new(request: WorkerRequest) -> Self {
        Self {
            request,
            created_at: Instant::now(),
        }
    }
}

/// Request sent to a worker
#[derive(Debug)]
pub(crate) enum WorkerRequest {
    /// Write to a region.
    Write(SenderWriteRequest),

    /// Ddl request to a region.
    Ddl(SenderDdlRequest),

    /// Notifications from internal background jobs.
    Background {
        /// Id of the region to send.
        region_id: RegionId,
        /// Internal notification.
        notify: BackgroundNotify,
    },

    /// The internal commands.
    SetRegionRoleStateGracefully {
        /// Id of the region to send.
        region_id: RegionId,
        /// The [SettableRegionRoleState].
        region_role_state: SettableRegionRoleState,
        /// The sender of [SetReadonlyResponse].
        sender: Sender<SetRegionRoleStateResponse>,
    },

    /// Notify a worker to stop.
    Stop,

    /// Use [RegionEdit] to edit a region directly.
    EditRegion(RegionEditRequest),

    /// Keep the manifest of a region up to date.
    SyncRegion(RegionSyncRequest),

    /// Bulk inserts request and region metadata.
    BulkInserts(BulkInsertRequest),

    /// Remap manifests request.
    RemapManifests(RemapManifestsRequest),

    /// Copy region from request.
    CopyRegionFrom(CopyRegionFromRequest),
}

impl WorkerRequest {
    /// Creates a new open region request.
    pub(crate) fn new_open_region_request(
        region_id: RegionId,
        request: RegionOpenRequest,
        entry_receiver: Option<WalEntryReceiver>,
    ) -> (WorkerRequest, Receiver<Result<AffectedRows>>) {
        let (sender, receiver) = oneshot::channel();

        let worker_request = WorkerRequest::Ddl(SenderDdlRequest {
            region_id,
            sender: sender.into(),
            request: DdlRequest::Open((request, entry_receiver)),
        });

        (worker_request, receiver)
    }

    /// Creates a new catchup region request.
    pub(crate) fn new_catchup_region_request(
        region_id: RegionId,
        request: RegionCatchupRequest,
        entry_receiver: Option<WalEntryReceiver>,
    ) -> (WorkerRequest, Receiver<Result<AffectedRows>>) {
        let (sender, receiver) = oneshot::channel();
        let worker_request = WorkerRequest::Ddl(SenderDdlRequest {
            region_id,
            sender: sender.into(),
            request: DdlRequest::Catchup((request, entry_receiver)),
        });
        (worker_request, receiver)
    }

    /// Converts request from a [RegionRequest].
    pub(crate) fn try_from_region_request(
        region_id: RegionId,
        value: RegionRequest,
        region_metadata: Option<RegionMetadataRef>,
    ) -> Result<(WorkerRequest, Receiver<Result<AffectedRows>>)> {
        let (sender, receiver) = oneshot::channel();
        let worker_request = match value {
            RegionRequest::Put(v) => {
                let mut write_request =
                    WriteRequest::new(region_id, OpType::Put, v.rows, region_metadata.clone())?
                        .with_hint(v.hint)
                        .with_partition_expr_version(v.partition_expr_version);
                if write_request.primary_key_encoding() == PrimaryKeyEncoding::Dense
                    && let Some(region_metadata) = &region_metadata
                {
                    write_request.maybe_fill_missing_columns(region_metadata)?;
                }
                WorkerRequest::Write(SenderWriteRequest {
                    sender: sender.into(),
                    request: write_request,
                })
            }
            RegionRequest::Delete(v) => {
                let mut write_request =
                    WriteRequest::new(region_id, OpType::Delete, v.rows, region_metadata.clone())?
                        .with_hint(v.hint)
                        .with_partition_expr_version(v.partition_expr_version);
                if write_request.primary_key_encoding() == PrimaryKeyEncoding::Dense
                    && let Some(region_metadata) = &region_metadata
                {
                    write_request.maybe_fill_missing_columns(region_metadata)?;
                }
                WorkerRequest::Write(SenderWriteRequest {
                    sender: sender.into(),
                    request: write_request,
                })
            }
            RegionRequest::Create(v) => WorkerRequest::Ddl(SenderDdlRequest {
                region_id,
                sender: sender.into(),
                request: DdlRequest::Create(v),
            }),
            RegionRequest::Drop(v) => WorkerRequest::Ddl(SenderDdlRequest {
                region_id,
                sender: sender.into(),
                request: DdlRequest::Drop(v),
            }),
            RegionRequest::Open(v) => WorkerRequest::Ddl(SenderDdlRequest {
                region_id,
                sender: sender.into(),
                request: DdlRequest::Open((v, None)),
            }),
            RegionRequest::CleanUp(v) => WorkerRequest::Ddl(SenderDdlRequest {
                region_id,
                sender: sender.into(),
                request: DdlRequest::OfflineCleanup(v),
            }),
            RegionRequest::Close(v) => WorkerRequest::Ddl(SenderDdlRequest {
                region_id,
                sender: sender.into(),
                request: DdlRequest::Close(v),
            }),
            RegionRequest::Alter(v) => WorkerRequest::Ddl(SenderDdlRequest {
                region_id,
                sender: sender.into(),
                request: DdlRequest::Alter(v),
            }),
            RegionRequest::Flush(v) => WorkerRequest::Ddl(SenderDdlRequest {
                region_id,
                sender: sender.into(),
                request: DdlRequest::Flush(v),
            }),
            RegionRequest::Compact(v) => WorkerRequest::Ddl(SenderDdlRequest {
                region_id,
                sender: sender.into(),
                request: DdlRequest::Compact(v),
            }),
            RegionRequest::BuildIndex(v) => WorkerRequest::Ddl(SenderDdlRequest {
                region_id,
                sender: sender.into(),
                request: DdlRequest::BuildIndex(v),
            }),
            RegionRequest::Truncate(v) => WorkerRequest::Ddl(SenderDdlRequest {
                region_id,
                sender: sender.into(),
                request: DdlRequest::Truncate(v),
            }),
            RegionRequest::Catchup(v) => WorkerRequest::Ddl(SenderDdlRequest {
                region_id,
                sender: sender.into(),
                request: DdlRequest::Catchup((v, None)),
            }),
            RegionRequest::EnterStaging(v) => WorkerRequest::Ddl(SenderDdlRequest {
                region_id,
                sender: sender.into(),
                request: DdlRequest::EnterStaging(v),
            }),
            RegionRequest::BulkInserts(region_bulk_inserts_request) => {
                WorkerRequest::BulkInserts(BulkInsertRequest {
                    metadata: region_metadata,
                    sender: sender.into(),
                    request: region_bulk_inserts_request,
                })
            }
            RegionRequest::ApplyStagingManifest(v) => WorkerRequest::Ddl(SenderDdlRequest {
                region_id,
                sender: sender.into(),
                request: DdlRequest::ApplyStagingManifest(v),
            }),
        };

        Ok((worker_request, receiver))
    }

    pub(crate) fn new_set_readonly_gracefully(
        region_id: RegionId,
        region_role_state: SettableRegionRoleState,
    ) -> (WorkerRequest, Receiver<SetRegionRoleStateResponse>) {
        let (sender, receiver) = oneshot::channel();

        (
            WorkerRequest::SetRegionRoleStateGracefully {
                region_id,
                region_role_state,
                sender,
            },
            receiver,
        )
    }

    pub(crate) fn new_sync_region_request(
        region_id: RegionId,
        manifest_version: ManifestVersion,
    ) -> (WorkerRequest, Receiver<Result<(ManifestVersion, bool)>>) {
        let (sender, receiver) = oneshot::channel();
        (
            WorkerRequest::SyncRegion(RegionSyncRequest {
                region_id,
                manifest_version,
                sender,
            }),
            receiver,
        )
    }

    /// Converts [RemapManifestsRequest] from a [RemapManifestsRequest](store_api::region_engine::RemapManifestsRequest).
    ///
    /// # Errors
    ///
    /// Returns an error if the partition expression is invalid or missing.
    /// Returns an error if the new partition expressions are not found for some regions.
    #[allow(clippy::type_complexity)]
    pub(crate) fn try_from_remap_manifests_request(
        store_api::region_engine::RemapManifestsRequest {
            region_id,
            input_regions,
            region_mapping,
            new_partition_exprs,
        }: store_api::region_engine::RemapManifestsRequest,
    ) -> Result<(WorkerRequest, Receiver<Result<HashMap<RegionId, String>>>)> {
        let (sender, receiver) = oneshot::channel();
        let new_partition_exprs = new_partition_exprs
            .into_iter()
            .map(|(k, v)| {
                Ok((
                    k,
                    PartitionExpr::from_json_str(&v)
                        .context(InvalidPartitionExprSnafu { expr: v })?
                        .context(MissingPartitionExprSnafu { region_id: k })?,
                ))
            })
            .collect::<Result<HashMap<_, _>>>()?;

        let request = RemapManifestsRequest {
            region_id,
            input_regions,
            region_mapping,
            new_partition_exprs,
            sender,
        };

        Ok((WorkerRequest::RemapManifests(request), receiver))
    }

    /// Converts [CopyRegionFromRequest] from a [MitoCopyRegionFromRequest](store_api::region_engine::MitoCopyRegionFromRequest).
    pub(crate) fn try_from_copy_region_from_request(
        region_id: RegionId,
        store_api::region_engine::MitoCopyRegionFromRequest {
            source_region_id,
            parallelism,
        }: store_api::region_engine::MitoCopyRegionFromRequest,
    ) -> Result<(WorkerRequest, Receiver<Result<MitoCopyRegionFromResponse>>)> {
        let (sender, receiver) = oneshot::channel();
        let request = CopyRegionFromRequest {
            region_id,
            source_region_id,
            parallelism,
            sender,
        };
        Ok((WorkerRequest::CopyRegionFrom(request), receiver))
    }
}

/// DDL request to a region.
#[derive(Debug)]
pub(crate) enum DdlRequest {
    Create(RegionCreateRequest),
    Drop(RegionDropRequest),
    Open((RegionOpenRequest, Option<WalEntryReceiver>)),
    OfflineCleanup(RegionCleanUpRequest),
    Close(RegionCloseRequest),
    Alter(RegionAlterRequest),
    Flush(RegionFlushRequest),
    Compact(RegionCompactRequest),
    BuildIndex(RegionBuildIndexRequest),
    Truncate(RegionTruncateRequest),
    Catchup((RegionCatchupRequest, Option<WalEntryReceiver>)),
    EnterStaging(EnterStagingRequest),
    ApplyStagingManifest(ApplyStagingManifestRequest),
}

/// Sender and Ddl request.
#[derive(Debug)]
pub(crate) struct SenderDdlRequest {
    /// Region id of the request.
    pub(crate) region_id: RegionId,
    /// Result sender.
    pub(crate) sender: OptionOutputTx,
    /// Ddl request.
    pub(crate) request: DdlRequest,
}

/// Notification from a background job.
#[derive(Debug)]
pub(crate) enum BackgroundNotify {
    /// Compaction planning has finished.
    CompactionPickFinished(CompactionPickFinished),
    /// Flush has finished.
    FlushFinished(FlushFinished),
    /// Flush has failed.
    FlushFailed(FlushFailed),
    /// Index build has finished.
    IndexBuildFinished(IndexBuildFinished),
    /// Index build has been stopped (aborted or succeeded).
    IndexBuildStopped(IndexBuildStopped),
    /// Index build has failed.
    IndexBuildFailed(IndexBuildFailed),
    /// An index build must be retried against the latest schema generation.
    IndexBuildRetry(BuildIndexRequest),
    /// Compaction has finished.
    CompactionFinished(CompactionFinished),
    /// Compaction has been cancelled cooperatively.
    CompactionCancelled(CompactionCancelled),
    /// Compaction has failed.
    CompactionFailed(CompactionFailed),
    /// Truncate result.
    Truncate(TruncateResult),
    /// Discard unflushed data result.
    DiscardUnflushed(DiscardUnflushedResult),
    /// Region change result.
    RegionChange(RegionChangeResult),
    /// Region edit result.
    RegionEdit(RegionEditResult),
    /// Enter staging result.
    EnterStaging(EnterStagingResult),
    /// Copy region result.
    CopyRegionFromFinished(CopyRegionFromFinished),
}

/// Notifies a flush job is finished.
#[derive(Debug)]
pub(crate) struct FlushFinished {
    /// Region id.
    pub(crate) region_id: RegionId,
    /// Reason to flush.
    pub(crate) flush_reason: FlushReason,
    /// Entry id of flushed data.
    pub(crate) flushed_entry_id: EntryId,
    /// Flush result senders.
    pub(crate) senders: Vec<OutputTx>,
    /// Flush timer.
    pub(crate) _timer: HistogramTimer,
    /// Region edit to apply.
    pub(crate) edit: RegionEdit,
    /// Memtables to remove.
    pub(crate) memtables_to_remove: SmallVec<[MemtableId; 2]>,
    /// Whether the region is in staging mode.
    pub(crate) is_staging: bool,
}

impl FlushFinished {
    /// Marks the flush job as successful and observes the timer.
    pub(crate) fn on_success(self) {
        for sender in self.senders {
            sender.send(Ok(0));
        }
    }
}

impl OnFailure for FlushFinished {
    fn on_failure(&mut self, err: Error) {
        let err = Arc::new(err);
        for sender in self.senders.drain(..) {
            sender.send(Err(err.clone()).context(FlushRegionSnafu {
                region_id: self.region_id,
            }));
        }
    }
}

/// Notifies a flush job is failed.
#[derive(Debug)]
pub(crate) struct FlushFailed {
    /// The error source of the failure.
    pub(crate) err: Arc<Error>,
}

impl FlushFailed {
    /// Returns whether the flush was cancelled cooperatively.
    pub(crate) fn is_cancelled(&self) -> bool {
        matches!(self.err.as_ref(), Error::FlushCancelled { .. })
    }
}

#[derive(Debug)]
pub(crate) struct IndexBuildFinished {
    pub(crate) manifest_version: ManifestVersion,
    pub(crate) file_meta: FileMeta,
}

/// Notifies an index build job has been stopped.
#[derive(Debug)]
pub(crate) struct IndexBuildStopped {
    pub(crate) file_id: FileId,
}

/// Notifies an index build job has failed.
#[derive(Debug)]
pub(crate) struct IndexBuildFailed {
    pub(crate) err: Arc<Error>,
}

/// Notifies a compaction job has finished.
#[derive(Debug)]
pub(crate) struct CompactionFinished {
    /// Region id.
    pub(crate) region_id: RegionId,
    /// Identity and reservation lease of the accepted execution.
    pub(crate) execution: CompactionExecution,
    /// Compaction result senders.
    pub(crate) senders: Vec<OutputTx>,
    /// Start time of compaction task.
    pub(crate) start_time: Instant,
    /// Region edit to apply.
    pub(crate) edit: RegionEdit,
}

/// Notifies a compaction job has been cancelled cooperatively.
#[derive(Debug)]
pub(crate) struct CompactionCancelled {
    /// Region id.
    pub(crate) region_id: RegionId,
    /// Identity and reservation lease of the accepted execution.
    pub(crate) execution: CompactionExecution,
    /// Waiters to wake once the cancellation has been observed by the worker.
    pub(crate) senders: Vec<OutputTx>,
}

impl CompactionCancelled {
    pub(crate) fn on_success(self) {
        for sender in self.senders {
            sender.send(CompactionCancelledSnafu {}.fail());
        }
        info!("Compaction cancelled for region: {}", self.region_id);
    }
}

impl CompactionFinished {
    pub fn on_success(self) {
        // only update compaction time on success
        COMPACTION_ELAPSED_TOTAL.observe(self.start_time.elapsed().as_secs_f64());

        for sender in self.senders {
            sender.send(Ok(0));
        }
        info!("Successfully compacted region: {}", self.region_id);
    }
}

impl OnFailure for CompactionFinished {
    /// Compaction succeeded but failed to update manifest or region's already been dropped.
    fn on_failure(&mut self, err: Error) {
        let err = Arc::new(err);
        for sender in self.senders.drain(..) {
            sender.send(Err(err.clone()).context(CompactRegionSnafu {
                region_id: self.region_id,
            }));
        }
    }
}

/// A failing compaction result.
#[derive(Debug)]
pub(crate) struct CompactionFailed {
    pub(crate) region_id: RegionId,
    /// Identity and reservation lease of the accepted execution.
    pub(crate) execution: CompactionExecution,
    /// The error source of the failure.
    pub(crate) err: Arc<Error>,
}

/// Notifies the truncate result of a region.
#[derive(Debug)]
pub(crate) struct TruncateResult {
    /// Region id.
    pub(crate) region_id: RegionId,
    /// Result sender.
    pub(crate) sender: OptionOutputTx,
    /// Truncate result.
    pub(crate) result: Result<()>,
    pub(crate) kind: TruncateKind,
}

/// Notifies the result of discarding unflushed data from a region.
#[derive(Debug)]
pub(crate) struct DiscardUnflushedResult {
    /// Region id.
    pub(crate) region_id: RegionId,
    /// Result sender.
    pub(crate) sender: OptionOutputTx,
    /// Manifest update result.
    pub(crate) result: Result<()>,
    /// Last WAL entry covered by the discard operation.
    pub(crate) discarded_entry_id: EntryId,
    /// Last sequence covered by the discard operation.
    pub(crate) discarded_sequence: SequenceNumber,
    /// Estimated number of discarded rows.
    pub(crate) discarded_rows: u64,
    /// Estimated number of discarded bytes.
    pub(crate) discarded_bytes: u64,
}

/// Notifies the region the result of writing region change action.
#[derive(Debug)]
pub(crate) struct RegionChangeResult {
    /// Region id.
    pub(crate) region_id: RegionId,
    /// The new region metadata to apply.
    pub(crate) new_meta: RegionMetadataRef,
    /// Result sender.
    pub(crate) sender: OptionOutputTx,
    /// Result from the manifest manager.
    pub(crate) result: Result<()>,
    /// Used for index build in schema change.
    pub(crate) need_index: bool,
    /// New options for the region.
    pub(crate) new_options: Option<RegionOptions>,
}

/// Notifies the region the result of entering staging.
#[derive(Debug)]
pub(crate) struct EnterStagingResult {
    /// Region id.
    pub(crate) region_id: RegionId,
    /// The new staging partition directive to apply.
    pub(crate) partition_directive: StagingPartitionDirective,
    /// Result sender.
    pub(crate) sender: OptionOutputTx,
    /// Result from the manifest manager.
    pub(crate) result: Result<()>,
}

#[derive(Debug)]
pub(crate) struct CopyRegionFromFinished {
    /// Region id.
    pub(crate) region_id: RegionId,
    /// Region edit to apply.
    pub(crate) edit: RegionEdit,
    /// Result sender.
    pub(crate) sender: Sender<Result<MitoCopyRegionFromResponse>>,
}

#[derive(Debug, Default)]
pub(crate) struct Waiters(SmallVec<[Sender<Result<()>>; 1]>);

impl Waiters {
    pub(crate) fn one(waiter: Sender<Result<()>>) -> Self {
        let mut waiters = SmallVec::new();
        waiters.push(waiter);
        Self(waiters)
    }

    pub(crate) fn reply_with<F: Fn() -> Result<()>>(self, f: F) {
        for tx in self.0 {
            let _ = tx.send(f());
        }
    }

    pub(crate) fn merge(&mut self, other: Self) {
        self.0.extend(other.0);
    }
}

/// Request to edit a region directly.
#[derive(Debug)]
pub(crate) struct RegionEditRequest {
    pub(crate) region_id: RegionId,
    pub(crate) edit: RegionEdit,
    /// Whether to preload SST files into the write cache.
    pub(crate) preload_sst_cache: bool,
    /// The waiters that are waiting for this region edit's result.
    pub(crate) waiters: Waiters,
}

impl RegionEditRequest {
    pub(crate) fn new(
        region_id: RegionId,
        edit: RegionEdit,
        preload_sst_cache: bool,
        waiter: Sender<Result<()>>,
    ) -> Self {
        Self {
            region_id,
            edit,
            preload_sst_cache,
            waiters: Waiters::one(waiter),
        }
    }
}

/// Notifies the regin the result of editing region.
#[derive(Debug)]
pub(crate) struct RegionEditResult {
    /// Region id.
    pub(crate) region_id: RegionId,
    /// Result waiters.
    pub(crate) waiters: Waiters,
    /// Region edit to apply.
    pub(crate) edit: RegionEdit,
    /// Result from the manifest manager.
    pub(crate) result: std::result::Result<(), Arc<Error>>,
    /// Whether region state need to be set to Writable after handling this request.
    pub(crate) update_region_state: bool,
    /// The region is in staging mode before handling this request.
    pub(crate) is_staging: bool,
}

#[derive(Debug)]
pub(crate) struct BuildIndexRequest {
    pub(crate) region_id: RegionId,
    pub(crate) build_type: IndexBuildType,
    /// files need to build index, empty means all.
    pub(crate) file_metas: Vec<FileMeta>,
}

#[derive(Debug)]
pub(crate) struct RegionSyncRequest {
    pub(crate) region_id: RegionId,
    pub(crate) manifest_version: ManifestVersion,
    /// Returns the latest manifest version and a boolean indicating whether new maniefst is installed.
    pub(crate) sender: Sender<Result<(ManifestVersion, bool)>>,
}

#[derive(Debug)]
pub(crate) struct RemapManifestsRequest {
    /// The [`RegionId`] of a staging region used to obtain table directory and storage configuration for the remap operation.
    pub(crate) region_id: RegionId,
    /// Regions to remap manifests from.
    pub(crate) input_regions: Vec<RegionId>,
    /// For each old region, which new regions should receive its files
    pub(crate) region_mapping: HashMap<RegionId, Vec<RegionId>>,
    /// New partition expressions for the new regions.
    pub(crate) new_partition_exprs: HashMap<RegionId, PartitionExpr>,
    /// Sender for the result of the remap operation.
    ///
    /// The result is a map from region IDs to their corresponding staging manifest paths.
    pub(crate) sender: Sender<Result<HashMap<RegionId, String>>>,
}

#[derive(Debug)]
pub(crate) struct CopyRegionFromRequest {
    /// The [`RegionId`] of the target region.
    pub(crate) region_id: RegionId,
    /// The [`RegionId`] of the source region.
    pub(crate) source_region_id: RegionId,
    /// The parallelism of the copy operation.
    pub(crate) parallelism: usize,
    /// Result sender.
    pub(crate) sender: Sender<Result<MitoCopyRegionFromResponse>>,
}

#[cfg(test)]
mod tests {
    use api::v1::value::ValueData;
    use api::v1::{Row, SemanticType};
    use common_error::ext::ErrorExt;
    use common_error::status_code::StatusCode;
    use datatypes::prelude::ConcreteDataType;
    use datatypes::schema::ColumnDefaultConstraint;
    use mito_codec::test_util::i64_value;
    use store_api::metadata::RegionMetadataBuilder;
    use tokio::sync::oneshot;

    use super::*;
    use crate::error::Error;
    use crate::test_util::ts_ms_value;

    fn new_column_schema(
        name: &str,
        data_type: ColumnDataType,
        semantic_type: SemanticType,
    ) -> ColumnSchema {
        ColumnSchema {
            column_name: name.to_string(),
            datatype: data_type as i32,
            semantic_type: semantic_type as i32,
            ..Default::default()
        }
    }

    fn check_invalid_request(err: &Error, expect: &str) {
        if let Error::InvalidRequest {
            region_id: _,
            reason,
            location: _,
        } = err
        {
            assert_eq!(reason, expect);
        } else {
            panic!("Unexpected error {err}")
        }
    }

    fn waiter() -> (Sender<Result<()>>, Receiver<Result<()>>) {
        oneshot::channel()
    }

    fn assert_waiter_ok(rx: &mut Receiver<Result<()>>) {
        rx.try_recv().unwrap().unwrap();
    }

    #[test]
    fn test_waiters_reply_with_single_waiter() {
        let (tx, mut rx) = waiter();
        Waiters::one(tx).reply_with(|| Ok(()));
        assert_waiter_ok(&mut rx);
    }

    #[test]
    fn test_waiters_reply_with_many_waiters() {
        let (tx1, mut rx1) = waiter();
        let (tx2, mut rx2) = waiter();
        let (tx3, mut rx3) = waiter();

        let waiters = Waiters(vec![tx1, tx2, tx3].into());
        waiters.reply_with(|| Ok(()));

        assert_waiter_ok(&mut rx1);
        assert_waiter_ok(&mut rx2);
        assert_waiter_ok(&mut rx3);
    }

    #[test]
    fn test_waiters_merge() {
        let (tx1, mut rx1) = waiter();
        let (tx2, mut rx2) = waiter();
        let (tx3, mut rx3) = waiter();
        let (tx4, mut rx4) = waiter();

        let mut waiters = Waiters::one(tx1);
        waiters.merge(Waiters::one(tx2));
        waiters.merge(Waiters(vec![tx3, tx4].into()));
        assert_eq!(4, waiters.0.len());

        waiters.reply_with(|| Ok(()));

        assert_waiter_ok(&mut rx1);
        assert_waiter_ok(&mut rx2);
        assert_waiter_ok(&mut rx3);
        assert_waiter_ok(&mut rx4);
    }

    #[test]
    fn test_write_request_duplicate_column() {
        let rows = Rows {
            schema: vec![
                new_column_schema("c0", ColumnDataType::Int64, SemanticType::Tag),
                new_column_schema("c0", ColumnDataType::Int64, SemanticType::Tag),
            ],
            rows: vec![],
        };

        let err = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap_err();
        check_invalid_request(&err, "duplicate column c0");
    }

    #[test]
    fn test_valid_write_request() {
        let rows = Rows {
            schema: vec![
                new_column_schema("c0", ColumnDataType::Int64, SemanticType::Tag),
                new_column_schema("c1", ColumnDataType::Int64, SemanticType::Tag),
            ],
            rows: vec![Row {
                values: vec![i64_value(1), i64_value(2)],
            }],
        };

        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
        assert_eq!(0, request.column_index_by_name("c0").unwrap());
        assert_eq!(1, request.column_index_by_name("c1").unwrap());
        assert_eq!(None, request.column_index_by_name("c2"));
    }

    #[test]
    fn test_compaction_cancelled_sends_cancelled_error() {
        let (tx, rx) = oneshot::channel();
        let request = CompactionCancelled {
            region_id: RegionId::new(1, 1),
            execution: crate::compaction::CompactionExecution::for_test(0),
            senders: vec![OutputTx::new(tx)],
        };

        request.on_success();

        let err = rx.blocking_recv().unwrap().unwrap_err();
        assert!(matches!(err, Error::CompactionCancelled { .. }));
        assert_eq!(err.status_code(), StatusCode::Cancelled);
    }

    #[test]
    fn test_write_request_column_num() {
        let rows = Rows {
            schema: vec![
                new_column_schema("c0", ColumnDataType::Int64, SemanticType::Tag),
                new_column_schema("c1", ColumnDataType::Int64, SemanticType::Tag),
            ],
            rows: vec![Row {
                values: vec![i64_value(1), i64_value(2), i64_value(3)],
            }],
        };

        let err = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap_err();
        check_invalid_request(&err, "row has 3 columns but schema has 2");
    }

    fn new_region_metadata() -> RegionMetadata {
        let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 1));
        builder
            .push_column_metadata(ColumnMetadata {
                column_schema: datatypes::schema::ColumnSchema::new(
                    "ts",
                    ConcreteDataType::timestamp_millisecond_datatype(),
                    false,
                ),
                semantic_type: SemanticType::Timestamp,
                column_id: 1,
            })
            .push_column_metadata(ColumnMetadata {
                column_schema: datatypes::schema::ColumnSchema::new(
                    "k0",
                    ConcreteDataType::int64_datatype(),
                    true,
                ),
                semantic_type: SemanticType::Tag,
                column_id: 2,
            })
            .primary_key(vec![2]);
        builder.build().unwrap()
    }

    #[test]
    fn test_check_schema() {
        let rows = Rows {
            schema: vec![
                new_column_schema(
                    "ts",
                    ColumnDataType::TimestampMillisecond,
                    SemanticType::Timestamp,
                ),
                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
            ],
            rows: vec![Row {
                values: vec![ts_ms_value(1), i64_value(2)],
            }],
        };
        let metadata = new_region_metadata();

        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
        request.check_schema(&metadata).unwrap();
    }

    #[test]
    fn test_column_type() {
        let rows = Rows {
            schema: vec![
                new_column_schema("ts", ColumnDataType::Int64, SemanticType::Timestamp),
                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
            ],
            rows: vec![Row {
                values: vec![i64_value(1), i64_value(2)],
            }],
        };
        let metadata = new_region_metadata();

        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
        let err = request.check_schema(&metadata).unwrap_err();
        check_invalid_request(
            &err,
            "column ts expect type Timestamp(Millisecond(TimestampMillisecondType)), given: INT64(4)",
        );
    }

    #[test]
    fn test_semantic_type() {
        let rows = Rows {
            schema: vec![
                new_column_schema(
                    "ts",
                    ColumnDataType::TimestampMillisecond,
                    SemanticType::Tag,
                ),
                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
            ],
            rows: vec![Row {
                values: vec![ts_ms_value(1), i64_value(2)],
            }],
        };
        let metadata = new_region_metadata();

        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
        let err = request.check_schema(&metadata).unwrap_err();
        check_invalid_request(&err, "column ts has semantic type Timestamp, given: TAG(0)");
    }

    #[test]
    fn test_column_nullable() {
        let rows = Rows {
            schema: vec![
                new_column_schema(
                    "ts",
                    ColumnDataType::TimestampMillisecond,
                    SemanticType::Timestamp,
                ),
                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
            ],
            rows: vec![Row {
                values: vec![Value { value_data: None }, i64_value(2)],
            }],
        };
        let metadata = new_region_metadata();

        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
        let err = request.check_schema(&metadata).unwrap_err();
        check_invalid_request(&err, "column ts is not null but input has null");
    }

    #[test]
    fn test_column_default() {
        let rows = Rows {
            schema: vec![new_column_schema(
                "k0",
                ColumnDataType::Int64,
                SemanticType::Tag,
            )],
            rows: vec![Row {
                values: vec![i64_value(1)],
            }],
        };
        let metadata = new_region_metadata();

        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
        let err = request.check_schema(&metadata).unwrap_err();
        check_invalid_request(&err, "missing column ts");
    }

    #[test]
    fn test_unknown_column() {
        let rows = Rows {
            schema: vec![
                new_column_schema(
                    "ts",
                    ColumnDataType::TimestampMillisecond,
                    SemanticType::Timestamp,
                ),
                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
                new_column_schema("k1", ColumnDataType::Int64, SemanticType::Tag),
            ],
            rows: vec![Row {
                values: vec![ts_ms_value(1), i64_value(2), i64_value(3)],
            }],
        };
        let metadata = new_region_metadata();

        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
        let err = request.check_schema(&metadata).unwrap_err();
        check_invalid_request(&err, r#"unknown columns: ["k1"]"#);
    }

    #[test]
    fn test_fill_impure_columns_err() {
        let rows = Rows {
            schema: vec![new_column_schema(
                "k0",
                ColumnDataType::Int64,
                SemanticType::Tag,
            )],
            rows: vec![Row {
                values: vec![i64_value(1)],
            }],
        };
        let metadata = {
            let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 1));
            builder
                .push_column_metadata(ColumnMetadata {
                    column_schema: datatypes::schema::ColumnSchema::new(
                        "ts",
                        ConcreteDataType::timestamp_millisecond_datatype(),
                        false,
                    )
                    .with_default_constraint(Some(ColumnDefaultConstraint::Function(
                        "now()".to_string(),
                    )))
                    .unwrap(),
                    semantic_type: SemanticType::Timestamp,
                    column_id: 1,
                })
                .push_column_metadata(ColumnMetadata {
                    column_schema: datatypes::schema::ColumnSchema::new(
                        "k0",
                        ConcreteDataType::int64_datatype(),
                        true,
                    ),
                    semantic_type: SemanticType::Tag,
                    column_id: 2,
                })
                .primary_key(vec![2]);
            builder.build().unwrap()
        };

        let mut request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
        let err = request.check_schema(&metadata).unwrap_err();
        assert!(err.is_fill_default());
        assert!(
            request
                .fill_missing_columns(&metadata)
                .unwrap_err()
                .to_string()
                .contains("unexpected impure default value with region_id")
        );
    }

    #[test]
    fn test_fill_missing_columns() {
        let rows = Rows {
            schema: vec![new_column_schema(
                "ts",
                ColumnDataType::TimestampMillisecond,
                SemanticType::Timestamp,
            )],
            rows: vec![Row {
                values: vec![ts_ms_value(1)],
            }],
        };
        let metadata = new_region_metadata();

        let mut request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
        let err = request.check_schema(&metadata).unwrap_err();
        assert!(err.is_fill_default());
        request.fill_missing_columns(&metadata).unwrap();

        let expect_rows = Rows {
            schema: vec![new_column_schema(
                "ts",
                ColumnDataType::TimestampMillisecond,
                SemanticType::Timestamp,
            )],
            rows: vec![Row {
                values: vec![ts_ms_value(1)],
            }],
        };
        assert_eq!(expect_rows, request.rows);
    }

    fn builder_with_ts_tag() -> RegionMetadataBuilder {
        let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 1));
        builder
            .push_column_metadata(ColumnMetadata {
                column_schema: datatypes::schema::ColumnSchema::new(
                    "ts",
                    ConcreteDataType::timestamp_millisecond_datatype(),
                    false,
                ),
                semantic_type: SemanticType::Timestamp,
                column_id: 1,
            })
            .push_column_metadata(ColumnMetadata {
                column_schema: datatypes::schema::ColumnSchema::new(
                    "k0",
                    ConcreteDataType::int64_datatype(),
                    true,
                ),
                semantic_type: SemanticType::Tag,
                column_id: 2,
            })
            .primary_key(vec![2]);
        builder
    }

    fn region_metadata_two_fields() -> RegionMetadata {
        let mut builder = builder_with_ts_tag();
        builder
            .push_column_metadata(ColumnMetadata {
                column_schema: datatypes::schema::ColumnSchema::new(
                    "f0",
                    ConcreteDataType::int64_datatype(),
                    true,
                ),
                semantic_type: SemanticType::Field,
                column_id: 3,
            })
            // Column is not nullable.
            .push_column_metadata(ColumnMetadata {
                column_schema: datatypes::schema::ColumnSchema::new(
                    "f1",
                    ConcreteDataType::int64_datatype(),
                    false,
                )
                .with_default_constraint(Some(ColumnDefaultConstraint::Value(
                    datatypes::value::Value::Int64(100),
                )))
                .unwrap(),
                semantic_type: SemanticType::Field,
                column_id: 4,
            });
        builder.build().unwrap()
    }

    #[test]
    fn test_fill_missing_for_delete() {
        let rows = Rows {
            schema: vec![new_column_schema(
                "ts",
                ColumnDataType::TimestampMillisecond,
                SemanticType::Timestamp,
            )],
            rows: vec![Row {
                values: vec![ts_ms_value(1)],
            }],
        };
        let metadata = region_metadata_two_fields();

        let mut request =
            WriteRequest::new(RegionId::new(1, 1), OpType::Delete, rows, None).unwrap();
        let err = request.check_schema(&metadata).unwrap_err();
        check_invalid_request(&err, "delete requests need column k0");
        let err = request.fill_missing_columns(&metadata).unwrap_err();
        check_invalid_request(&err, "delete requests need column k0");

        let rows = Rows {
            schema: vec![
                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
                new_column_schema(
                    "ts",
                    ColumnDataType::TimestampMillisecond,
                    SemanticType::Timestamp,
                ),
            ],
            rows: vec![Row {
                values: vec![i64_value(100), ts_ms_value(1)],
            }],
        };
        let mut request =
            WriteRequest::new(RegionId::new(1, 1), OpType::Delete, rows, None).unwrap();
        let err = request.check_schema(&metadata).unwrap_err();
        assert!(err.is_fill_default());
        request.fill_missing_columns(&metadata).unwrap();

        let expect_rows = Rows {
            schema: vec![
                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
                new_column_schema(
                    "ts",
                    ColumnDataType::TimestampMillisecond,
                    SemanticType::Timestamp,
                ),
                new_column_schema("f1", ColumnDataType::Int64, SemanticType::Field),
            ],
            // Column f1 is not nullable and we use 0 for padding.
            rows: vec![Row {
                values: vec![i64_value(100), ts_ms_value(1), i64_value(0)],
            }],
        };
        assert_eq!(expect_rows, request.rows);
    }

    #[test]
    fn test_fill_missing_without_default_in_delete() {
        let mut builder = builder_with_ts_tag();
        builder
            // f0 is nullable.
            .push_column_metadata(ColumnMetadata {
                column_schema: datatypes::schema::ColumnSchema::new(
                    "f0",
                    ConcreteDataType::int64_datatype(),
                    true,
                ),
                semantic_type: SemanticType::Field,
                column_id: 3,
            })
            // f1 is not nullable and don't has default.
            .push_column_metadata(ColumnMetadata {
                column_schema: datatypes::schema::ColumnSchema::new(
                    "f1",
                    ConcreteDataType::int64_datatype(),
                    false,
                ),
                semantic_type: SemanticType::Field,
                column_id: 4,
            });
        let metadata = builder.build().unwrap();

        let rows = Rows {
            schema: vec![
                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
                new_column_schema(
                    "ts",
                    ColumnDataType::TimestampMillisecond,
                    SemanticType::Timestamp,
                ),
            ],
            // Missing f0 (nullable), f1 (not nullable).
            rows: vec![Row {
                values: vec![i64_value(100), ts_ms_value(1)],
            }],
        };
        let mut request =
            WriteRequest::new(RegionId::new(1, 1), OpType::Delete, rows, None).unwrap();
        let err = request.check_schema(&metadata).unwrap_err();
        assert!(err.is_fill_default());
        request.fill_missing_columns(&metadata).unwrap();

        let expect_rows = Rows {
            schema: vec![
                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
                new_column_schema(
                    "ts",
                    ColumnDataType::TimestampMillisecond,
                    SemanticType::Timestamp,
                ),
                new_column_schema("f1", ColumnDataType::Int64, SemanticType::Field),
            ],
            // Column f1 is not nullable and we use 0 for padding.
            rows: vec![Row {
                values: vec![i64_value(100), ts_ms_value(1), i64_value(0)],
            }],
        };
        assert_eq!(expect_rows, request.rows);
    }

    #[test]
    fn test_no_default() {
        let rows = Rows {
            schema: vec![new_column_schema(
                "k0",
                ColumnDataType::Int64,
                SemanticType::Tag,
            )],
            rows: vec![Row {
                values: vec![i64_value(1)],
            }],
        };
        let metadata = new_region_metadata();

        let mut request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
        let err = request.fill_missing_columns(&metadata).unwrap_err();
        check_invalid_request(&err, "column ts does not have default value");
    }

    #[test]
    fn test_missing_and_invalid() {
        // Missing f0 and f1 has invalid type (string).
        let rows = Rows {
            schema: vec![
                new_column_schema("k0", ColumnDataType::Int64, SemanticType::Tag),
                new_column_schema(
                    "ts",
                    ColumnDataType::TimestampMillisecond,
                    SemanticType::Timestamp,
                ),
                new_column_schema("f1", ColumnDataType::String, SemanticType::Field),
            ],
            rows: vec![Row {
                values: vec![
                    i64_value(100),
                    ts_ms_value(1),
                    Value {
                        value_data: Some(ValueData::StringValue("xxxxx".to_string())),
                    },
                ],
            }],
        };
        let metadata = region_metadata_two_fields();

        let request = WriteRequest::new(RegionId::new(1, 1), OpType::Put, rows, None).unwrap();
        let err = request.check_schema(&metadata).unwrap_err();
        check_invalid_request(
            &err,
            "column f1 expect type Int64(Int64Type), given: STRING(12)",
        );
    }

    #[test]
    fn test_write_request_metadata() {
        let rows = Rows {
            schema: vec![
                new_column_schema("c0", ColumnDataType::Int64, SemanticType::Tag),
                new_column_schema("c1", ColumnDataType::Int64, SemanticType::Tag),
            ],
            rows: vec![Row {
                values: vec![i64_value(1), i64_value(2)],
            }],
        };

        let metadata = Arc::new(new_region_metadata());
        let request = WriteRequest::new(
            RegionId::new(1, 1),
            OpType::Put,
            rows,
            Some(metadata.clone()),
        )
        .unwrap();

        assert!(request.region_metadata.is_some());
        assert_eq!(
            request.region_metadata.unwrap().region_id,
            RegionId::new(1, 1)
        );
    }
}
