Skip to main content

mito2/read/
compat.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//! Utilities to adapt readers with different schema.
16
17use std::collections::HashMap;
18use std::sync::Arc;
19
20use api::v1::SemanticType;
21use datatypes::arrow::array::{
22    Array, ArrayRef, BinaryArray, BinaryBuilder, DictionaryArray, UInt32Array, new_null_array,
23};
24use datatypes::arrow::compute::{TakeOptions, take};
25use datatypes::arrow::datatypes::{FieldRef, Schema, SchemaRef};
26use datatypes::arrow::record_batch::RecordBatch;
27use datatypes::data_type::ConcreteDataType;
28use datatypes::prelude::DataType;
29use datatypes::value::Value;
30use datatypes::vectors::{Helper, VectorRef};
31use mito_codec::row_converter::{
32    CompositeValues, PrimaryKeyCodec, SortField, build_primary_key_codec,
33    build_primary_key_codec_with_fields,
34};
35use snafu::{OptionExt, ResultExt, ensure};
36use store_api::codec::PrimaryKeyEncoding;
37use store_api::metadata::{RegionMetadata, RegionMetadataRef};
38use store_api::storage::ColumnId;
39
40use crate::error::{
41    CompatReaderSnafu, ComputeArrowSnafu, CreateDefaultSnafu, DecodeSnafu, EncodeSnafu,
42    NewRecordBatchSnafu, Result, UnexpectedSnafu, UnsupportedOperationSnafu,
43};
44use crate::read::flat_projection::{FlatProjectionMapper, flat_projected_columns};
45use crate::sst::internal_fields;
46use crate::sst::parquet::flat_format::{FlatReadFormat, primary_key_column_index};
47use crate::sst::parquet::format::{INTERNAL_COLUMN_NUM, PrimaryKeyArray};
48
49/// Returns true if the columns in the `projection_mapper` and `read_format` have same data types
50/// and primary key encodings.
51pub(crate) fn has_same_columns_and_pk_encoding(
52    projection_mapper: &FlatProjectionMapper,
53    read_format: &FlatReadFormat,
54    compaction: bool,
55) -> bool {
56    let left = projection_mapper.metadata();
57    let right = read_format.metadata();
58    if left.primary_key_encoding != right.primary_key_encoding {
59        return false;
60    }
61
62    if left.column_metadatas.len() != right.column_metadatas.len() {
63        return false;
64    }
65
66    for (left_col, right_col) in left.column_metadatas.iter().zip(&right.column_metadatas) {
67        if left_col.column_id != right_col.column_id {
68            return false;
69        }
70        debug_assert_eq!(left_col.semantic_type, right_col.semantic_type);
71    }
72
73    &projection_mapper.input_arrow_schema(compaction) == read_format.arrow_schema()
74}
75
76/// A helper struct to adapt schema of the batch to an expected schema.
77pub(crate) struct FlatCompatBatch {
78    /// Indices to convert actual fields to expect fields.
79    index_or_defaults: Vec<IndexOrDefault>,
80    /// Expected arrow schema.
81    arrow_schema: SchemaRef,
82    /// Primary key adapter.
83    compat_pk: FlatCompatPrimaryKey,
84}
85
86impl FlatCompatBatch {
87    /// Creates a [FlatCompatBatch].
88    ///
89    /// - `mapper` is built from the metadata users expect to see.
90    /// - `read_format` is the [FlatReadFormat] of the input parquet.
91    /// - `compaction` indicates whether the reader is for compaction.
92    pub(crate) fn try_new(
93        mapper: &FlatProjectionMapper,
94        read_format: &FlatReadFormat,
95        compaction: bool,
96    ) -> Result<Option<Self>> {
97        let actual = read_format.metadata();
98        let actual_schema = flat_projected_columns(
99            actual,
100            read_format.format_projection(),
101            read_format.json_target_types(),
102        );
103
104        let expect_schema = mapper.batch_schema();
105        if expect_schema == actual_schema
106            && actual.primary_key == mapper.metadata().primary_key
107            && actual.primary_key_encoding == mapper.metadata().primary_key_encoding
108        {
109            // Although the SST has a different schema, but the schema after projection is the same
110            // as expected schema.
111            return Ok(None);
112        }
113
114        if actual.primary_key_encoding == PrimaryKeyEncoding::Sparse && compaction {
115            // Special handling for sparse encoding in compaction.
116            return FlatCompatBatch::try_new_compact_sparse(mapper, actual);
117        }
118
119        let (index_or_defaults, fields) = Self::compute_index_and_fields(
120            &actual_schema,
121            expect_schema,
122            mapper.metadata(),
123            mapper.input_arrow_schema(compaction).fields(),
124        )?;
125
126        let compat_pk = FlatCompatPrimaryKey::new(mapper.metadata(), actual)?;
127
128        Ok(Some(Self {
129            index_or_defaults,
130            arrow_schema: Arc::new(Schema::new(fields)),
131            compat_pk,
132        }))
133    }
134
135    fn compute_index_and_fields(
136        actual_schema: &[(ColumnId, ConcreteDataType)],
137        expect_schema: &[(ColumnId, ConcreteDataType)],
138        expect_metadata: &RegionMetadata,
139        target_fields: &[FieldRef],
140    ) -> Result<(Vec<IndexOrDefault>, Vec<FieldRef>)> {
141        // Maps column id to the index and data type in the actual schema.
142        let actual_schema_index: HashMap<_, _> = actual_schema
143            .iter()
144            .enumerate()
145            .map(|(idx, (column_id, data_type))| (*column_id, (idx, data_type)))
146            .collect();
147
148        let mut index_or_defaults = Vec::with_capacity(expect_schema.len());
149        let mut fields = Vec::with_capacity(expect_schema.len());
150        for (field_index, (column_id, expect_data_type)) in expect_schema.iter().enumerate() {
151            // Safety: expect_schema comes from the same mapper.
152            let column_index = expect_metadata.column_index_by_id(*column_id).unwrap();
153            let expect_column = &expect_metadata.column_metadatas[column_index];
154            // Query target fields follow mapper.batch_schema(); compaction reads all columns in
155            // PK -> fields -> timestamp order (fields -> timestamp for sparse encoding), before
156            // trailing internal columns. Keep this order consistent with expect_schema.
157            // The mapper already applies string-tag dictionary types and JSON2 compaction layouts,
158            // which can differ from the logical types in expect_schema.
159            let column_field = &target_fields[field_index];
160            debug_assert_eq!(column_field.name(), &expect_column.column_schema.name);
161            fields.push(column_field.clone());
162
163            if let Some((index, actual_data_type)) = actual_schema_index.get(column_id) {
164                let mut cast_type = None;
165
166                // Same column different type.
167                if expect_data_type != *actual_data_type {
168                    ensure!(
169                        !expect_data_type.is_json2() && !actual_data_type.is_json2(),
170                        CompatReaderSnafu {
171                            region_id: expect_metadata.region_id,
172                            reason: format!(
173                                "JSON2 column '{}' must be aligned before FlatCompatBatch, actual: {}, expected: {}",
174                                expect_column.column_schema.name,
175                                actual_data_type,
176                                expect_data_type,
177                            ),
178                        }
179                    );
180                    cast_type = Some(expect_data_type.clone());
181                }
182                // Source has this column.
183                index_or_defaults.push(IndexOrDefault::Index {
184                    pos: *index,
185                    cast_type,
186                });
187            } else {
188                // Create a default vector with 1 element for that column.
189                let default_vector = if expect_data_type.is_json2() {
190                    // JSON2 columns do not support defaults, so missing columns use NULL.
191                    // A logical JSON2 placeholder cannot describe a compaction layout.
192                    Helper::try_into_vector(new_null_array(column_field.data_type(), 1)).context(
193                        CreateDefaultSnafu {
194                            region_id: expect_metadata.region_id,
195                            column: &expect_column.column_schema.name,
196                        },
197                    )?
198                } else {
199                    expect_column
200                        .column_schema
201                        .create_default_vector(1)
202                        .context(CreateDefaultSnafu {
203                            region_id: expect_metadata.region_id,
204                            column: &expect_column.column_schema.name,
205                        })?
206                        .with_context(|| CompatReaderSnafu {
207                            region_id: expect_metadata.region_id,
208                            reason: format!(
209                                "column {} does not have a default value to read",
210                                expect_column.column_schema.name
211                            ),
212                        })?
213                };
214                index_or_defaults.push(IndexOrDefault::DefaultValue {
215                    default_vector,
216                    semantic_type: expect_column.semantic_type,
217                });
218            };
219        }
220        fields.extend_from_slice(&internal_fields());
221
222        Ok((index_or_defaults, fields))
223    }
224
225    fn try_new_compact_sparse(
226        mapper: &FlatProjectionMapper,
227        actual: &RegionMetadataRef,
228    ) -> Result<Option<Self>> {
229        // Currently, we don't support converting sparse encoding back to dense encoding in
230        // flat format.
231        ensure!(
232            mapper.metadata().primary_key_encoding == PrimaryKeyEncoding::Sparse,
233            UnsupportedOperationSnafu {
234                err_msg: "Flat format doesn't support converting sparse encoding back to dense encoding"
235            }
236        );
237
238        // For sparse encoding, we don't need to check the primary keys.
239        // Since this is for compaction, we always read all columns.
240        let actual_schema: Vec<_> = actual
241            .field_columns()
242            .chain([actual.time_index_column()])
243            .map(|col| (col.column_id, col.column_schema.data_type.clone()))
244            .collect();
245        let expect_schema: Vec<_> = mapper
246            .metadata()
247            .field_columns()
248            .chain([mapper.metadata().time_index_column()])
249            .map(|col| (col.column_id, col.column_schema.data_type.clone()))
250            .collect();
251
252        let (index_or_defaults, fields) = Self::compute_index_and_fields(
253            &actual_schema,
254            &expect_schema,
255            mapper.metadata(),
256            mapper.input_arrow_schema(true).fields(),
257        )?;
258
259        let compat_pk = FlatCompatPrimaryKey::default();
260
261        Ok(Some(Self {
262            index_or_defaults,
263            arrow_schema: Arc::new(Schema::new(fields)),
264            compat_pk,
265        }))
266    }
267
268    /// Make columns of the `batch` compatible.
269    pub(crate) fn compat(&self, batch: RecordBatch) -> Result<RecordBatch> {
270        let len = batch.num_rows();
271        let columns = self
272            .index_or_defaults
273            .iter()
274            .map(|index_or_default| match index_or_default {
275                IndexOrDefault::Index { pos, cast_type } => {
276                    let old_column = batch.column(*pos);
277
278                    if let Some(ty) = cast_type {
279                        let casted =
280                            datatypes::arrow::compute::cast(old_column, &ty.as_arrow_type())
281                                .context(ComputeArrowSnafu)?;
282                        Ok(casted)
283                    } else {
284                        Ok(old_column.clone())
285                    }
286                }
287                IndexOrDefault::DefaultValue {
288                    default_vector,
289                    semantic_type,
290                } => repeat_vector(default_vector, len, *semantic_type == SemanticType::Tag),
291            })
292            .chain(
293                // Adds internal columns.
294                batch.columns()[batch.num_columns() - INTERNAL_COLUMN_NUM..]
295                    .iter()
296                    .map(|col| Ok(col.clone())),
297            )
298            .collect::<Result<Vec<_>>>()?;
299
300        let mut columns = columns;
301        let primary_key_index = primary_key_column_index(columns.len());
302        columns[primary_key_index] = self.compat_primary_key(&columns[primary_key_index])?;
303
304        RecordBatch::try_new(self.arrow_schema.clone(), columns).context(NewRecordBatchSnafu)
305    }
306
307    /// Makes an encoded primary-key array compatible with the expected metadata.
308    pub(crate) fn compat_primary_key(&self, primary_key: &ArrayRef) -> Result<ArrayRef> {
309        self.compat_pk.compat(primary_key)
310    }
311}
312
313/// Repeats the vector value `to_len` times.
314fn repeat_vector(vector: &VectorRef, to_len: usize, is_tag: bool) -> Result<ArrayRef> {
315    assert_eq!(1, vector.len());
316    let data_type = vector.data_type();
317    if is_tag && data_type.is_string() {
318        let values = vector.to_arrow_array();
319        if values.is_null(0) {
320            // Creates a dictionary array with `to_len` null keys.
321            let keys = UInt32Array::new_null(to_len);
322            Ok(Arc::new(DictionaryArray::new(keys, values.slice(0, 0))))
323        } else {
324            let keys = UInt32Array::from_value(0, to_len);
325            Ok(Arc::new(DictionaryArray::new(keys, values)))
326        }
327    } else {
328        let keys = UInt32Array::from_value(0, to_len);
329        take(
330            &vector.to_arrow_array(),
331            &keys,
332            Some(TakeOptions {
333                check_bounds: false,
334            }),
335        )
336        .context(ComputeArrowSnafu)
337    }
338}
339
340/// Returns true if the actual primary keys is the same as expected.
341fn is_primary_key_same(expect: &RegionMetadata, actual: &RegionMetadata) -> Result<bool> {
342    ensure!(
343        actual.primary_key.len() <= expect.primary_key.len(),
344        CompatReaderSnafu {
345            region_id: expect.region_id,
346            reason: format!(
347                "primary key has more columns {} than expect {}",
348                actual.primary_key.len(),
349                expect.primary_key.len()
350            ),
351        }
352    );
353    ensure!(
354        actual.primary_key == expect.primary_key[..actual.primary_key.len()],
355        CompatReaderSnafu {
356            region_id: expect.region_id,
357            reason: format!(
358                "primary key has different prefix, expect: {:?}, actual: {:?}",
359                expect.primary_key, actual.primary_key
360            ),
361        }
362    );
363
364    Ok(actual.primary_key.len() == expect.primary_key.len())
365}
366
367/// Index in source batch or a default value to fill a column.
368#[derive(Debug)]
369enum IndexOrDefault {
370    /// Index of the column in source batch.
371    Index {
372        pos: usize,
373        cast_type: Option<ConcreteDataType>,
374    },
375    /// Default value for the column.
376    DefaultValue {
377        /// Default value. The vector has only 1 element.
378        default_vector: VectorRef,
379        /// Semantic type of the column.
380        semantic_type: SemanticType,
381    },
382}
383
384/// Helper to rewrite primary key to another encoding for flat format.
385struct FlatRewritePrimaryKey {
386    /// New primary key encoder.
387    codec: Arc<dyn PrimaryKeyCodec>,
388    /// Metadata of the expected region.
389    metadata: RegionMetadataRef,
390    /// Original primary key codec.
391    /// If we need to rewrite the primary key.
392    old_codec: Arc<dyn PrimaryKeyCodec>,
393}
394
395impl FlatRewritePrimaryKey {
396    fn new(
397        expect: &RegionMetadataRef,
398        actual: &RegionMetadataRef,
399    ) -> Option<FlatRewritePrimaryKey> {
400        if expect.primary_key_encoding == actual.primary_key_encoding {
401            return None;
402        }
403        let codec = build_primary_key_codec(expect);
404        let old_codec = build_primary_key_codec(actual);
405
406        Some(FlatRewritePrimaryKey {
407            codec,
408            metadata: expect.clone(),
409            old_codec,
410        })
411    }
412
413    /// Rewrites the primary key of the `batch`.
414    /// It also appends the values to the primary key.
415    fn rewrite_key(
416        &self,
417        append_values: &[(ColumnId, Value)],
418        primary_key: &ArrayRef,
419    ) -> Result<ArrayRef> {
420        if let Some(old_pk_dict_array) = primary_key.as_any().downcast_ref::<PrimaryKeyArray>() {
421            let old_pk_values_array = old_pk_dict_array
422                .values()
423                .as_any()
424                .downcast_ref::<BinaryArray>()
425                .context(UnexpectedSnafu {
426                    reason: "Primary-key dictionary values are not binary",
427                })?;
428            let new_pk_values_array =
429                Arc::new(self.rewrite_values(append_values, old_pk_values_array)?);
430            return Ok(Arc::new(PrimaryKeyArray::new(
431                old_pk_dict_array.keys().clone(),
432                new_pk_values_array,
433            )));
434        }
435
436        let old_pk_values_array =
437            primary_key
438                .as_any()
439                .downcast_ref::<BinaryArray>()
440                .context(UnexpectedSnafu {
441                    reason: format!(
442                        "Primary-key column is neither binary nor dictionary, got {:?}",
443                        primary_key.data_type()
444                    ),
445                })?;
446        Ok(Arc::new(
447            self.rewrite_values(append_values, old_pk_values_array)?,
448        ))
449    }
450
451    fn rewrite_values(
452        &self,
453        append_values: &[(ColumnId, Value)],
454        old_pk_values_array: &BinaryArray,
455    ) -> Result<BinaryArray> {
456        let mut builder = BinaryBuilder::with_capacity(
457            old_pk_values_array.len(),
458            old_pk_values_array.value_data().len(),
459        );
460
461        // Binary buffer for the primary key.
462        let mut buffer = Vec::with_capacity(
463            old_pk_values_array.value_data().len() / old_pk_values_array.len().max(1),
464        );
465        let mut column_id_values = Vec::new();
466        // Iterates the binary array and rewrites the primary key.
467        for value in old_pk_values_array.iter() {
468            let Some(old_pk) = value else {
469                builder.append_null();
470                continue;
471            };
472            // Decodes the old primary key.
473            let mut pk_values = self.old_codec.decode(old_pk).context(DecodeSnafu)?;
474            pk_values.extend(append_values);
475
476            buffer.clear();
477            column_id_values.clear();
478            // Encodes the new primary key.
479            match pk_values {
480                CompositeValues::Dense(dense_values) => {
481                    self.codec
482                        .encode_values(dense_values.as_slice(), &mut buffer)
483                        .context(EncodeSnafu)?;
484                }
485                CompositeValues::Sparse(sparse_values) => {
486                    for id in &self.metadata.primary_key {
487                        let value = sparse_values.get_or_null(*id);
488                        column_id_values.push((*id, value.clone()));
489                    }
490                    self.codec
491                        .encode_values(&column_id_values, &mut buffer)
492                        .context(EncodeSnafu)?;
493                }
494            }
495            builder.append_value(&buffer);
496        }
497        Ok(builder.finish())
498    }
499}
500
501/// Helper to make primary key compatible for flat format.
502#[derive(Default)]
503struct FlatCompatPrimaryKey {
504    /// Primary key rewriter.
505    rewriter: Option<FlatRewritePrimaryKey>,
506    /// Converter to append values to primary keys.
507    converter: Option<Arc<dyn PrimaryKeyCodec>>,
508    /// Default values to append.
509    values: Vec<(ColumnId, Value)>,
510}
511
512impl FlatCompatPrimaryKey {
513    fn new(expect: &RegionMetadataRef, actual: &RegionMetadataRef) -> Result<Self> {
514        let rewriter = FlatRewritePrimaryKey::new(expect, actual);
515
516        if is_primary_key_same(expect, actual)? {
517            return Ok(Self {
518                rewriter,
519                converter: None,
520                values: Vec::new(),
521            });
522        }
523
524        // We need to append default values to the primary key.
525        let to_add = &expect.primary_key[actual.primary_key.len()..];
526        let mut values = Vec::with_capacity(to_add.len());
527        let mut fields = Vec::with_capacity(to_add.len());
528        for column_id in to_add {
529            // Safety: The id comes from expect region metadata.
530            let column = expect.column_by_id(*column_id).unwrap();
531            fields.push((
532                *column_id,
533                SortField::new(column.column_schema.data_type.clone()),
534            ));
535            let default_value = column
536                .column_schema
537                .create_default()
538                .context(CreateDefaultSnafu {
539                    region_id: expect.region_id,
540                    column: &column.column_schema.name,
541                })?
542                .with_context(|| CompatReaderSnafu {
543                    region_id: expect.region_id,
544                    reason: format!(
545                        "key column {} does not have a default value to read",
546                        column.column_schema.name
547                    ),
548                })?;
549            values.push((*column_id, default_value));
550        }
551        // is_primary_key_same() is false so we have different number of primary key columns.
552        debug_assert!(!fields.is_empty());
553
554        // Create converter to append values.
555        let converter = Some(build_primary_key_codec_with_fields(
556            expect.primary_key_encoding,
557            fields.into_iter(),
558        ));
559
560        Ok(Self {
561            rewriter,
562            converter,
563            values,
564        })
565    }
566
567    /// Makes an encoded primary-key array compatible.
568    fn compat(&self, primary_key: &ArrayRef) -> Result<ArrayRef> {
569        if let Some(rewriter) = &self.rewriter {
570            // If we have different encoding, rewrite the whole primary key.
571            return rewriter.rewrite_key(&self.values, primary_key);
572        }
573
574        self.append_key(primary_key)
575    }
576
577    /// Appends values to the primary key array.
578    fn append_key(&self, primary_key: &ArrayRef) -> Result<ArrayRef> {
579        let Some(converter) = &self.converter else {
580            return Ok(primary_key.clone());
581        };
582
583        if let Some(old_pk_dict_array) = primary_key.as_any().downcast_ref::<PrimaryKeyArray>() {
584            let old_pk_values_array = old_pk_dict_array
585                .values()
586                .as_any()
587                .downcast_ref::<BinaryArray>()
588                .context(UnexpectedSnafu {
589                    reason: "Primary-key dictionary values are not binary",
590                })?;
591            let new_pk_values_array =
592                Arc::new(self.append_values(old_pk_values_array, converter.as_ref())?);
593            return Ok(Arc::new(PrimaryKeyArray::new(
594                old_pk_dict_array.keys().clone(),
595                new_pk_values_array,
596            )));
597        }
598
599        let old_pk_values_array =
600            primary_key
601                .as_any()
602                .downcast_ref::<BinaryArray>()
603                .context(UnexpectedSnafu {
604                    reason: format!(
605                        "Primary-key column is neither binary nor dictionary, got {:?}",
606                        primary_key.data_type()
607                    ),
608                })?;
609        Ok(Arc::new(
610            self.append_values(old_pk_values_array, converter.as_ref())?,
611        ))
612    }
613
614    fn append_values(
615        &self,
616        old_pk_values_array: &BinaryArray,
617        converter: &dyn PrimaryKeyCodec,
618    ) -> Result<BinaryArray> {
619        let mut builder = BinaryBuilder::with_capacity(
620            old_pk_values_array.len(),
621            old_pk_values_array.value_data().len()
622                + converter.estimated_size().unwrap_or_default() * old_pk_values_array.len(),
623        );
624
625        // Binary buffer for the primary key.
626        let mut buffer = Vec::with_capacity(
627            old_pk_values_array.value_data().len() / old_pk_values_array.len().max(1)
628                + converter.estimated_size().unwrap_or_default(),
629        );
630
631        // Iterates the binary array and appends values to the primary key.
632        for value in old_pk_values_array.iter() {
633            let Some(old_pk) = value else {
634                builder.append_null();
635                continue;
636            };
637
638            buffer.clear();
639            buffer.extend_from_slice(old_pk);
640            converter
641                .encode_values(&self.values, &mut buffer)
642                .context(EncodeSnafu)?;
643
644            builder.append_value(&buffer);
645        }
646
647        Ok(builder.finish())
648    }
649}
650
651#[cfg(test)]
652mod tests {
653    use std::collections::BTreeMap;
654    use std::sync::Arc;
655
656    use api::v1::{OpType, SemanticType};
657    use datatypes::arrow::array::{
658        ArrayRef, BinaryArray, BinaryDictionaryBuilder, Int64Array, StringDictionaryBuilder,
659        TimestampMicrosecondArray, TimestampMillisecondArray, UInt8Array, UInt64Array,
660    };
661    use datatypes::arrow::datatypes::UInt32Type;
662    use datatypes::arrow::record_batch::RecordBatch;
663    use datatypes::extension::json::JsonMetadata;
664    use datatypes::json::JsonSettings;
665    use datatypes::prelude::ConcreteDataType;
666    use datatypes::schema::{ColumnDefaultConstraint, ColumnSchema};
667    use datatypes::types::json_type::JsonNativeType;
668    use datatypes::types::parse_string_to_jsonb;
669    use datatypes::value::ValueRef;
670    use datatypes::vectors::json::array::JsonArray;
671    use datatypes::vectors::json::json2_physical_data_type;
672    use mito_codec::row_converter::{
673        DensePrimaryKeyCodec, PrimaryKeyCodecExt, SparsePrimaryKeyCodec,
674    };
675    use store_api::codec::PrimaryKeyEncoding;
676    use store_api::metadata::{ColumnMetadata, RegionMetadataBuilder};
677    use store_api::storage::RegionId;
678
679    use super::*;
680    use crate::read::flat_projection::FlatProjectionMapper;
681    use crate::read::read_columns::ReadColumns;
682    use crate::sst::parquet::flat_format::FlatReadFormat;
683    use crate::sst::parquet::{Json2RewriteTargets, Json2TargetLayout};
684    use crate::sst::{FlatSchemaOptions, to_flat_sst_arrow_schema};
685    use crate::test_util;
686
687    /// Creates a new [RegionMetadata].
688    fn new_metadata(
689        semantic_types: &[(ColumnId, SemanticType, ConcreteDataType)],
690        primary_key: &[ColumnId],
691    ) -> RegionMetadata {
692        let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 1));
693        for (id, semantic_type, data_type) in semantic_types {
694            let column_schema = match semantic_type {
695                SemanticType::Tag => {
696                    ColumnSchema::new(format!("tag_{id}"), data_type.clone(), true)
697                }
698                SemanticType::Field => {
699                    ColumnSchema::new(format!("field_{id}"), data_type.clone(), true)
700                }
701                SemanticType::Timestamp => ColumnSchema::new("ts", data_type.clone(), false),
702            };
703
704            builder.push_column_metadata(ColumnMetadata {
705                column_schema,
706                semantic_type: *semantic_type,
707                column_id: *id,
708            });
709        }
710        builder.primary_key(primary_key.to_vec());
711        builder.build().unwrap()
712    }
713
714    /// Encode primary key.
715    fn encode_key(keys: &[Option<&str>]) -> Vec<u8> {
716        let fields = (0..keys.len())
717            .map(|_| (0, SortField::new(ConcreteDataType::string_datatype())))
718            .collect();
719        let converter = DensePrimaryKeyCodec::with_fields(fields);
720        let row = keys.iter().map(|str_opt| match str_opt {
721            Some(v) => ValueRef::String(v),
722            None => ValueRef::Null,
723        });
724
725        converter.encode(row).unwrap()
726    }
727
728    /// Encode sparse primary key.
729    fn encode_sparse_key(keys: &[(ColumnId, Option<&str>)]) -> Vec<u8> {
730        let fields = (0..keys.len())
731            .map(|_| (1, SortField::new(ConcreteDataType::string_datatype())))
732            .collect();
733        let converter = SparsePrimaryKeyCodec::with_fields(fields);
734        let row = keys
735            .iter()
736            .map(|(id, str_opt)| match str_opt {
737                Some(v) => (*id, ValueRef::String(v)),
738                None => (*id, ValueRef::Null),
739            })
740            .collect::<Vec<_>>();
741        let mut buffer = vec![];
742        converter.encode_value_refs(&row, &mut buffer).unwrap();
743        buffer
744    }
745
746    /// Creates a primary key array for flat format testing.
747    fn build_flat_test_pk_array(primary_keys: &[&[u8]]) -> ArrayRef {
748        let mut builder = BinaryDictionaryBuilder::<UInt32Type>::new();
749        for &pk in primary_keys {
750            builder.append(pk).unwrap();
751        }
752        Arc::new(builder.finish())
753    }
754
755    #[test]
756    fn test_flat_compat_batch_with_missing_columns() {
757        let actual_metadata = Arc::new(new_metadata(
758            &[
759                (
760                    0,
761                    SemanticType::Timestamp,
762                    ConcreteDataType::timestamp_millisecond_datatype(),
763                ),
764                (1, SemanticType::Tag, ConcreteDataType::string_datatype()),
765                (2, SemanticType::Field, ConcreteDataType::int64_datatype()),
766            ],
767            &[1],
768        ));
769
770        let expected_metadata = Arc::new(new_metadata(
771            &[
772                (
773                    0,
774                    SemanticType::Timestamp,
775                    ConcreteDataType::timestamp_millisecond_datatype(),
776                ),
777                (1, SemanticType::Tag, ConcreteDataType::string_datatype()),
778                (2, SemanticType::Field, ConcreteDataType::int64_datatype()),
779                // Adds a new field.
780                (3, SemanticType::Field, ConcreteDataType::int64_datatype()),
781            ],
782            &[1],
783        ));
784
785        let mapper = FlatProjectionMapper::all(&expected_metadata).unwrap();
786        let read_format = FlatReadFormat::new(
787            actual_metadata.clone(),
788            ReadColumns::new([0, 1, 2, 3]),
789            None,
790            "test",
791            false,
792        )
793        .unwrap();
794
795        let compat_batch = FlatCompatBatch::try_new(&mapper, &read_format, false)
796            .unwrap()
797            .unwrap();
798
799        let mut tag_builder = StringDictionaryBuilder::<UInt32Type>::new();
800        tag_builder.append_value("tag1");
801        tag_builder.append_value("tag1");
802        let tag_dict_array = Arc::new(tag_builder.finish());
803
804        let k1 = encode_key(&[Some("tag1")]);
805        let input_columns: Vec<ArrayRef> = vec![
806            tag_dict_array.clone(),
807            Arc::new(Int64Array::from(vec![100, 200])),
808            Arc::new(TimestampMillisecondArray::from_iter_values([1000, 2000])),
809            build_flat_test_pk_array(&[&k1, &k1]),
810            Arc::new(UInt64Array::from_iter_values([1, 2])),
811            Arc::new(UInt8Array::from_iter_values([
812                OpType::Put as u8,
813                OpType::Put as u8,
814            ])),
815        ];
816        let input_schema =
817            to_flat_sst_arrow_schema(&actual_metadata, &FlatSchemaOptions::default());
818        let input_batch = RecordBatch::try_new(input_schema, input_columns).unwrap();
819
820        let result = compat_batch.compat(input_batch).unwrap();
821
822        let expected_schema =
823            to_flat_sst_arrow_schema(&expected_metadata, &FlatSchemaOptions::default());
824
825        let expected_columns: Vec<ArrayRef> = vec![
826            tag_dict_array.clone(),
827            Arc::new(Int64Array::from(vec![100, 200])),
828            Arc::new(Int64Array::from(vec![None::<i64>, None::<i64>])),
829            Arc::new(TimestampMillisecondArray::from_iter_values([1000, 2000])),
830            build_flat_test_pk_array(&[&k1, &k1]),
831            Arc::new(UInt64Array::from_iter_values([1, 2])),
832            Arc::new(UInt8Array::from_iter_values([
833                OpType::Put as u8,
834                OpType::Put as u8,
835            ])),
836        ];
837        let expected_batch = RecordBatch::try_new(expected_schema, expected_columns).unwrap();
838
839        assert_eq!(expected_batch, result);
840    }
841
842    #[test]
843    fn test_flat_compat_batch_uses_projected_json2_type() -> Result<()> {
844        let json2 = ConcreteDataType::json2(JsonNativeType::object());
845        let actual_metadata = Arc::new(new_metadata(
846            &[
847                (
848                    0,
849                    SemanticType::Timestamp,
850                    ConcreteDataType::timestamp_millisecond_datatype(),
851                ),
852                (1, SemanticType::Field, json2.clone()),
853            ],
854            &[],
855        ));
856        let expected_metadata = Arc::new(new_metadata(
857            &[
858                (
859                    0,
860                    SemanticType::Timestamp,
861                    ConcreteDataType::timestamp_millisecond_datatype(),
862                ),
863                (1, SemanticType::Field, json2),
864                (2, SemanticType::Field, ConcreteDataType::int64_datatype()),
865            ],
866            &[],
867        ));
868        let read_columns = ReadColumns::new([0, 1, 2])
869            .with_json_target_types(BTreeMap::from([(1, JsonNativeType::Variant)]));
870        let mapper = FlatProjectionMapper::new_with_read_columns(
871            &expected_metadata,
872            vec![0, 1, 2],
873            read_columns.clone(),
874        )?;
875        let read_format = FlatReadFormat::new(actual_metadata, read_columns, None, "test", false)?;
876
877        let compat = FlatCompatBatch::try_new(&mapper, &read_format, false)?.unwrap();
878        let json_index = mapper
879            .batch_schema()
880            .iter()
881            .position(|(id, _)| *id == 1)
882            .unwrap();
883        assert!(matches!(
884            &compat.index_or_defaults[json_index],
885            IndexOrDefault::Index {
886                cast_type: None,
887                ..
888            }
889        ));
890        Ok(())
891    }
892
893    #[test]
894    fn test_flat_compat_batch_with_read_projection_superset() {
895        let actual_metadata = Arc::new(new_metadata(
896            &[
897                (
898                    0,
899                    SemanticType::Timestamp,
900                    ConcreteDataType::timestamp_millisecond_datatype(),
901                ),
902                (1, SemanticType::Tag, ConcreteDataType::string_datatype()),
903                (2, SemanticType::Field, ConcreteDataType::int64_datatype()),
904            ],
905            &[1],
906        ));
907
908        let expected_metadata = Arc::new(new_metadata(
909            &[
910                (
911                    0,
912                    SemanticType::Timestamp,
913                    ConcreteDataType::timestamp_millisecond_datatype(),
914                ),
915                (1, SemanticType::Tag, ConcreteDataType::string_datatype()),
916                (2, SemanticType::Field, ConcreteDataType::int64_datatype()),
917                // Adds a new field.
918                (3, SemanticType::Field, ConcreteDataType::int64_datatype()),
919            ],
920            &[1],
921        ));
922
923        let mapper = FlatProjectionMapper::new_with_read_columns(
924            &expected_metadata,
925            vec![1, 2],
926            ReadColumns::new([1, 2, 3]),
927        )
928        .unwrap();
929        let read_format = FlatReadFormat::new(
930            actual_metadata.clone(),
931            ReadColumns::new([1, 2, 3]),
932            None,
933            "test",
934            false,
935        )
936        .unwrap();
937
938        let compat_batch = FlatCompatBatch::try_new(&mapper, &read_format, false)
939            .unwrap()
940            .unwrap();
941
942        let mut tag_builder = StringDictionaryBuilder::<UInt32Type>::new();
943        tag_builder.append_value("tag1");
944        tag_builder.append_value("tag1");
945        let tag_dict_array = Arc::new(tag_builder.finish());
946
947        let k1 = encode_key(&[Some("tag1")]);
948        let input_columns: Vec<ArrayRef> = vec![
949            tag_dict_array.clone(),
950            Arc::new(Int64Array::from(vec![100, 200])),
951            Arc::new(TimestampMillisecondArray::from_iter_values([1000, 2000])),
952            build_flat_test_pk_array(&[&k1, &k1]),
953            Arc::new(UInt64Array::from_iter_values([1, 2])),
954            Arc::new(UInt8Array::from_iter_values([
955                OpType::Put as u8,
956                OpType::Put as u8,
957            ])),
958        ];
959        let input_schema =
960            to_flat_sst_arrow_schema(&actual_metadata, &FlatSchemaOptions::default());
961        let input_batch = RecordBatch::try_new(input_schema, input_columns).unwrap();
962
963        let result = compat_batch.compat(input_batch).unwrap();
964
965        let expected_schema =
966            to_flat_sst_arrow_schema(&expected_metadata, &FlatSchemaOptions::default());
967        let expected_columns: Vec<ArrayRef> = vec![
968            tag_dict_array.clone(),
969            Arc::new(Int64Array::from(vec![100, 200])),
970            Arc::new(Int64Array::from(vec![None::<i64>, None::<i64>])),
971            Arc::new(TimestampMillisecondArray::from_iter_values([1000, 2000])),
972            build_flat_test_pk_array(&[&k1, &k1]),
973            Arc::new(UInt64Array::from_iter_values([1, 2])),
974            Arc::new(UInt8Array::from_iter_values([
975                OpType::Put as u8,
976                OpType::Put as u8,
977            ])),
978        ];
979        let expected_batch = RecordBatch::try_new(expected_schema, expected_columns).unwrap();
980
981        assert_eq!(expected_batch, result);
982    }
983
984    #[test]
985    fn test_flat_compat_batch_with_different_pk_encoding() {
986        let mut actual_metadata = new_metadata(
987            &[
988                (
989                    0,
990                    SemanticType::Timestamp,
991                    ConcreteDataType::timestamp_millisecond_datatype(),
992                ),
993                (1, SemanticType::Tag, ConcreteDataType::string_datatype()),
994                (2, SemanticType::Field, ConcreteDataType::int64_datatype()),
995            ],
996            &[1],
997        );
998        actual_metadata.primary_key_encoding = PrimaryKeyEncoding::Dense;
999        let actual_metadata = Arc::new(actual_metadata);
1000
1001        let mut expected_metadata = new_metadata(
1002            &[
1003                (
1004                    0,
1005                    SemanticType::Timestamp,
1006                    ConcreteDataType::timestamp_millisecond_datatype(),
1007                ),
1008                (1, SemanticType::Tag, ConcreteDataType::string_datatype()),
1009                (2, SemanticType::Field, ConcreteDataType::int64_datatype()),
1010                (3, SemanticType::Tag, ConcreteDataType::string_datatype()),
1011            ],
1012            &[1, 3],
1013        );
1014        expected_metadata.primary_key_encoding = PrimaryKeyEncoding::Sparse;
1015        let expected_metadata = Arc::new(expected_metadata);
1016
1017        let mapper = FlatProjectionMapper::all(&expected_metadata).unwrap();
1018        let read_format = FlatReadFormat::new(
1019            actual_metadata.clone(),
1020            ReadColumns::new([0, 1, 2, 3]),
1021            None,
1022            "test",
1023            false,
1024        )
1025        .unwrap();
1026
1027        let compat_batch = FlatCompatBatch::try_new(&mapper, &read_format, false)
1028            .unwrap()
1029            .unwrap();
1030
1031        // Tag array.
1032        let mut tag1_builder = StringDictionaryBuilder::<UInt32Type>::new();
1033        tag1_builder.append_value("tag1");
1034        tag1_builder.append_value("tag1");
1035        let tag1_dict_array = Arc::new(tag1_builder.finish());
1036
1037        let k1 = encode_key(&[Some("tag1")]);
1038        let input_columns: Vec<ArrayRef> = vec![
1039            tag1_dict_array.clone(),
1040            Arc::new(Int64Array::from(vec![100, 200])),
1041            Arc::new(TimestampMillisecondArray::from_iter_values([1000, 2000])),
1042            build_flat_test_pk_array(&[&k1, &k1]),
1043            Arc::new(UInt64Array::from_iter_values([1, 2])),
1044            Arc::new(UInt8Array::from_iter_values([
1045                OpType::Put as u8,
1046                OpType::Put as u8,
1047            ])),
1048        ];
1049        let input_schema =
1050            to_flat_sst_arrow_schema(&actual_metadata, &FlatSchemaOptions::default());
1051        let input_batch = RecordBatch::try_new(input_schema, input_columns).unwrap();
1052
1053        let result = compat_batch.compat(input_batch).unwrap();
1054
1055        let sparse_k1 = encode_sparse_key(&[(1, Some("tag1")), (3, None)]);
1056        let mut null_tag_builder = StringDictionaryBuilder::<UInt32Type>::new();
1057        null_tag_builder.append_nulls(2);
1058        let null_tag_dict_array = Arc::new(null_tag_builder.finish());
1059        let expected_columns: Vec<ArrayRef> = vec![
1060            tag1_dict_array.clone(),
1061            null_tag_dict_array,
1062            Arc::new(Int64Array::from(vec![100, 200])),
1063            Arc::new(TimestampMillisecondArray::from_iter_values([1000, 2000])),
1064            build_flat_test_pk_array(&[&sparse_k1, &sparse_k1]),
1065            Arc::new(UInt64Array::from_iter_values([1, 2])),
1066            Arc::new(UInt8Array::from_iter_values([
1067                OpType::Put as u8,
1068                OpType::Put as u8,
1069            ])),
1070        ];
1071        let output_schema =
1072            to_flat_sst_arrow_schema(&expected_metadata, &FlatSchemaOptions::default());
1073        let expected_batch = RecordBatch::try_new(output_schema, expected_columns).unwrap();
1074
1075        assert_eq!(expected_batch, result);
1076    }
1077
1078    #[test]
1079    fn test_compat_primary_key_with_different_encoding_only() {
1080        let mut actual_metadata = new_metadata(
1081            &[
1082                (
1083                    0,
1084                    SemanticType::Timestamp,
1085                    ConcreteDataType::timestamp_millisecond_datatype(),
1086                ),
1087                (1, SemanticType::Tag, ConcreteDataType::string_datatype()),
1088                (2, SemanticType::Field, ConcreteDataType::int64_datatype()),
1089            ],
1090            &[1],
1091        );
1092        actual_metadata.primary_key_encoding = PrimaryKeyEncoding::Dense;
1093        let actual_metadata = Arc::new(actual_metadata);
1094
1095        let mut expected_metadata = (*actual_metadata).clone();
1096        expected_metadata.primary_key_encoding = PrimaryKeyEncoding::Sparse;
1097        let expected_metadata = Arc::new(expected_metadata);
1098
1099        let mapper = FlatProjectionMapper::all(&expected_metadata).unwrap();
1100        let read_format = FlatReadFormat::new(
1101            actual_metadata,
1102            ReadColumns::new([0, 1, 2]),
1103            None,
1104            "test",
1105            false,
1106        )
1107        .unwrap();
1108        let compat = FlatCompatBatch::try_new(&mapper, &read_format, false)
1109            .unwrap()
1110            .unwrap();
1111
1112        let dense_key = encode_key(&[Some("tag1")]);
1113        let sparse_key = encode_sparse_key(&[(1, Some("tag1"))]);
1114
1115        let dictionary_key = build_flat_test_pk_array(&[&dense_key, &dense_key]);
1116        let result = compat.compat_primary_key(&dictionary_key).unwrap();
1117        let result = result.as_any().downcast_ref::<PrimaryKeyArray>().unwrap();
1118        let values = result
1119            .values()
1120            .as_any()
1121            .downcast_ref::<BinaryArray>()
1122            .unwrap();
1123        assert_eq!(values.value(result.keys().value(0) as usize), sparse_key);
1124        assert_eq!(values.value(result.keys().value(1) as usize), sparse_key);
1125
1126        let binary_key: ArrayRef = Arc::new(BinaryArray::from(vec![
1127            Some(dense_key.as_slice()),
1128            Some(dense_key.as_slice()),
1129        ]));
1130        let result = compat.compat_primary_key(&binary_key).unwrap();
1131        let result = result.as_any().downcast_ref::<BinaryArray>().unwrap();
1132        assert_eq!(result.value(0), sparse_key);
1133        assert_eq!(result.value(1), sparse_key);
1134    }
1135
1136    /// A widened time index unit must be cast on compat (values rescaled to
1137    /// the expected unit), and identical units must skip compat entirely.
1138    #[test]
1139    fn test_flat_compat_batch_with_time_index_unit_change() {
1140        let actual_metadata = Arc::new(new_metadata(
1141            &[
1142                (
1143                    0,
1144                    SemanticType::Timestamp,
1145                    ConcreteDataType::timestamp_millisecond_datatype(),
1146                ),
1147                (1, SemanticType::Tag, ConcreteDataType::string_datatype()),
1148                (2, SemanticType::Field, ConcreteDataType::int64_datatype()),
1149            ],
1150            &[1],
1151        ));
1152
1153        let mut expected_metadata = (*actual_metadata).clone();
1154        for column in expected_metadata.column_metadatas.iter_mut() {
1155            if column.semantic_type == SemanticType::Timestamp {
1156                column.column_schema.data_type = ConcreteDataType::timestamp_microsecond_datatype();
1157            }
1158        }
1159        // Keep the schema consistent with the column metadata.
1160        let expected_metadata = Arc::new(
1161            RegionMetadataBuilder::from_existing(expected_metadata)
1162                .build()
1163                .unwrap(),
1164        );
1165
1166        // Same units: nothing to compat.
1167        let same_unit_metadata = Arc::new((*actual_metadata).clone());
1168        let mapper = FlatProjectionMapper::all(&same_unit_metadata).unwrap();
1169        let read_format = FlatReadFormat::new(
1170            actual_metadata.clone(),
1171            ReadColumns::new([0, 1, 2]),
1172            None,
1173            "test",
1174            false,
1175        )
1176        .unwrap();
1177        assert!(
1178            FlatCompatBatch::try_new(&mapper, &read_format, false)
1179                .unwrap()
1180                .is_none()
1181        );
1182
1183        // Widened unit: the time index column is rescaled, others pass through.
1184        let mapper = FlatProjectionMapper::all(&expected_metadata).unwrap();
1185        let read_format = FlatReadFormat::new(
1186            actual_metadata.clone(),
1187            ReadColumns::new([0, 1, 2]),
1188            None,
1189            "test",
1190            false,
1191        )
1192        .unwrap();
1193        let compat_batch = FlatCompatBatch::try_new(&mapper, &read_format, false)
1194            .unwrap()
1195            .unwrap();
1196
1197        let mut tag_builder = StringDictionaryBuilder::<UInt32Type>::new();
1198        tag_builder.append_value("tag1");
1199        tag_builder.append_value("tag1");
1200        let tag_dict_array = Arc::new(tag_builder.finish());
1201
1202        let k1 = encode_key(&[Some("tag1")]);
1203        let input_columns: Vec<ArrayRef> = vec![
1204            tag_dict_array.clone(),
1205            Arc::new(Int64Array::from(vec![100, 200])),
1206            Arc::new(TimestampMillisecondArray::from_iter_values([1000, 2000])),
1207            build_flat_test_pk_array(&[&k1, &k1]),
1208            Arc::new(UInt64Array::from_iter_values([1, 2])),
1209            Arc::new(UInt8Array::from_iter_values([
1210                OpType::Put as u8,
1211                OpType::Put as u8,
1212            ])),
1213        ];
1214        let input_schema =
1215            to_flat_sst_arrow_schema(&actual_metadata, &FlatSchemaOptions::default());
1216        let input_batch = RecordBatch::try_new(input_schema, input_columns).unwrap();
1217
1218        let result = compat_batch.compat(input_batch).unwrap();
1219
1220        let expected_columns: Vec<ArrayRef> = vec![
1221            tag_dict_array.clone(),
1222            Arc::new(Int64Array::from(vec![100, 200])),
1223            // Rescaled, not reinterpreted: 1000ms -> 1_000_000us.
1224            Arc::new(TimestampMicrosecondArray::from_iter_values([
1225                1_000_000, 2_000_000,
1226            ])),
1227            build_flat_test_pk_array(&[&k1, &k1]),
1228            Arc::new(UInt64Array::from_iter_values([1, 2])),
1229            Arc::new(UInt8Array::from_iter_values([
1230                OpType::Put as u8,
1231                OpType::Put as u8,
1232            ])),
1233        ];
1234        let expected_schema =
1235            to_flat_sst_arrow_schema(&expected_metadata, &FlatSchemaOptions::default());
1236        let expected_batch = RecordBatch::try_new(expected_schema, expected_columns).unwrap();
1237
1238        assert_eq!(expected_batch, result);
1239    }
1240
1241    /// A widened time index unit combined with an added column (the realistic
1242    /// multi-alter sequence) must rescale and default-fill in one pass.
1243    #[test]
1244    fn test_flat_compat_batch_time_index_unit_change_with_added_column() {
1245        let actual_metadata = Arc::new(new_metadata(
1246            &[
1247                (
1248                    0,
1249                    SemanticType::Timestamp,
1250                    ConcreteDataType::timestamp_millisecond_datatype(),
1251                ),
1252                (1, SemanticType::Tag, ConcreteDataType::string_datatype()),
1253                (2, SemanticType::Field, ConcreteDataType::int64_datatype()),
1254            ],
1255            &[1],
1256        ));
1257
1258        let mut expected_metadata = new_metadata(
1259            &[
1260                (
1261                    0,
1262                    SemanticType::Timestamp,
1263                    ConcreteDataType::timestamp_microsecond_datatype(),
1264                ),
1265                (1, SemanticType::Tag, ConcreteDataType::string_datatype()),
1266                (2, SemanticType::Field, ConcreteDataType::int64_datatype()),
1267                (3, SemanticType::Field, ConcreteDataType::int64_datatype()),
1268            ],
1269            &[1],
1270        );
1271        expected_metadata.primary_key_encoding = PrimaryKeyEncoding::Dense;
1272        let expected_metadata = Arc::new(expected_metadata);
1273
1274        let mapper = FlatProjectionMapper::all(&expected_metadata).unwrap();
1275        let read_format = FlatReadFormat::new(
1276            actual_metadata.clone(),
1277            ReadColumns::new([0, 1, 2, 3]),
1278            None,
1279            "test",
1280            false,
1281        )
1282        .unwrap();
1283
1284        let compat_batch = FlatCompatBatch::try_new(&mapper, &read_format, false)
1285            .unwrap()
1286            .unwrap();
1287
1288        let mut tag_builder = StringDictionaryBuilder::<UInt32Type>::new();
1289        tag_builder.append_value("tag1");
1290        tag_builder.append_value("tag1");
1291        let tag_dict_array = Arc::new(tag_builder.finish());
1292
1293        let k1 = encode_key(&[Some("tag1")]);
1294        let input_columns: Vec<ArrayRef> = vec![
1295            tag_dict_array.clone(),
1296            Arc::new(Int64Array::from(vec![100, 200])),
1297            Arc::new(TimestampMillisecondArray::from_iter_values([1000, 2000])),
1298            build_flat_test_pk_array(&[&k1, &k1]),
1299            Arc::new(UInt64Array::from_iter_values([1, 2])),
1300            Arc::new(UInt8Array::from_iter_values([
1301                OpType::Put as u8,
1302                OpType::Put as u8,
1303            ])),
1304        ];
1305        let input_schema =
1306            to_flat_sst_arrow_schema(&actual_metadata, &FlatSchemaOptions::default());
1307        let input_batch = RecordBatch::try_new(input_schema, input_columns).unwrap();
1308
1309        let result = compat_batch.compat(input_batch).unwrap();
1310
1311        let expected_columns: Vec<ArrayRef> = vec![
1312            tag_dict_array.clone(),
1313            Arc::new(Int64Array::from(vec![100, 200])),
1314            Arc::new(Int64Array::from(vec![None::<i64>, None::<i64>])),
1315            Arc::new(TimestampMicrosecondArray::from_iter_values([
1316                1_000_000, 2_000_000,
1317            ])),
1318            build_flat_test_pk_array(&[&k1, &k1]),
1319            Arc::new(UInt64Array::from_iter_values([1, 2])),
1320            Arc::new(UInt8Array::from_iter_values([
1321                OpType::Put as u8,
1322                OpType::Put as u8,
1323            ])),
1324        ];
1325        let expected_schema =
1326            to_flat_sst_arrow_schema(&expected_metadata, &FlatSchemaOptions::default());
1327        let expected_batch = RecordBatch::try_new(expected_schema, expected_columns).unwrap();
1328
1329        assert_eq!(expected_batch, result);
1330    }
1331
1332    /// The sparse compaction compat path must also rescale a widened time
1333    /// index unit (metric-engine regions compact with sparse encoding).
1334    #[test]
1335    fn test_flat_compat_batch_compact_sparse_time_index_unit_change() {
1336        let mut actual_metadata = new_metadata(
1337            &[
1338                (
1339                    0,
1340                    SemanticType::Timestamp,
1341                    ConcreteDataType::timestamp_millisecond_datatype(),
1342                ),
1343                (2, SemanticType::Field, ConcreteDataType::int64_datatype()),
1344            ],
1345            &[],
1346        );
1347        let reserved = crate::test_util::sst_util::sst_region_metadata_with_encoding(
1348            PrimaryKeyEncoding::Sparse,
1349        );
1350        actual_metadata.primary_key = reserved.primary_key.into_iter().take(2).collect();
1351        actual_metadata
1352            .column_metadatas
1353            .extend(reserved.column_metadatas.into_iter().take(2));
1354        actual_metadata.primary_key_encoding = PrimaryKeyEncoding::Sparse;
1355        let actual_metadata = Arc::new(
1356            RegionMetadataBuilder::from_existing(actual_metadata)
1357                .build()
1358                .unwrap(),
1359        );
1360
1361        let mut expected_metadata = (*actual_metadata).clone();
1362        for column in expected_metadata.column_metadatas.iter_mut() {
1363            if column.semantic_type == SemanticType::Timestamp {
1364                column.column_schema.data_type = ConcreteDataType::timestamp_microsecond_datatype();
1365            }
1366        }
1367        let expected_metadata = Arc::new(
1368            RegionMetadataBuilder::from_existing(expected_metadata)
1369                .build()
1370                .unwrap(),
1371        );
1372
1373        let mapper = FlatProjectionMapper::all(&expected_metadata).unwrap();
1374        let read_format = FlatReadFormat::new(
1375            actual_metadata.clone(),
1376            ReadColumns::new([0, 2]),
1377            None,
1378            "test",
1379            true,
1380        )
1381        .unwrap();
1382
1383        let compat_batch = FlatCompatBatch::try_new(&mapper, &read_format, true)
1384            .unwrap()
1385            .unwrap();
1386
1387        let mut sparse_k1 = Vec::new();
1388        SparsePrimaryKeyCodec::schemaless()
1389            .encode_internal(42, 7, &mut sparse_k1)
1390            .unwrap();
1391        let input_columns: Vec<ArrayRef> = vec![
1392            Arc::new(Int64Array::from(vec![100, 200])),
1393            Arc::new(TimestampMillisecondArray::from_iter_values([1000, 2000])),
1394            build_flat_test_pk_array(&[&sparse_k1, &sparse_k1]),
1395            Arc::new(UInt64Array::from_iter_values([1, 2])),
1396            Arc::new(UInt8Array::from_iter_values([
1397                OpType::Put as u8,
1398                OpType::Put as u8,
1399            ])),
1400        ];
1401        let input_schema = to_flat_sst_arrow_schema(
1402            &actual_metadata,
1403            &FlatSchemaOptions::from_encoding(PrimaryKeyEncoding::Sparse),
1404        );
1405        let input_batch = RecordBatch::try_new(input_schema, input_columns).unwrap();
1406
1407        let result = compat_batch.compat(input_batch).unwrap();
1408
1409        let expected_columns: Vec<ArrayRef> = vec![
1410            Arc::new(Int64Array::from(vec![100, 200])),
1411            Arc::new(TimestampMicrosecondArray::from_iter_values([
1412                1_000_000, 2_000_000,
1413            ])),
1414            build_flat_test_pk_array(&[&sparse_k1, &sparse_k1]),
1415            Arc::new(UInt64Array::from_iter_values([1, 2])),
1416            Arc::new(UInt8Array::from_iter_values([
1417                OpType::Put as u8,
1418                OpType::Put as u8,
1419            ])),
1420        ];
1421        let output_schema = to_flat_sst_arrow_schema(
1422            &expected_metadata,
1423            &FlatSchemaOptions::from_encoding(PrimaryKeyEncoding::Sparse),
1424        );
1425        let expected_batch = RecordBatch::try_new(output_schema, expected_columns).unwrap();
1426
1427        assert_eq!(expected_batch, result);
1428    }
1429
1430    #[test]
1431    fn test_flat_compat_batch_compact_sparse() {
1432        let mut actual_metadata = new_metadata(
1433            &[
1434                (
1435                    0,
1436                    SemanticType::Timestamp,
1437                    ConcreteDataType::timestamp_millisecond_datatype(),
1438                ),
1439                (2, SemanticType::Field, ConcreteDataType::int64_datatype()),
1440            ],
1441            &[],
1442        );
1443        actual_metadata.primary_key_encoding = PrimaryKeyEncoding::Sparse;
1444        let actual_metadata = Arc::new(actual_metadata);
1445
1446        let mut expected_metadata = new_metadata(
1447            &[
1448                (
1449                    0,
1450                    SemanticType::Timestamp,
1451                    ConcreteDataType::timestamp_millisecond_datatype(),
1452                ),
1453                (2, SemanticType::Field, ConcreteDataType::int64_datatype()),
1454                (3, SemanticType::Field, ConcreteDataType::int64_datatype()),
1455            ],
1456            &[],
1457        );
1458        expected_metadata.primary_key_encoding = PrimaryKeyEncoding::Sparse;
1459        let expected_metadata = Arc::new(expected_metadata);
1460
1461        let mapper = FlatProjectionMapper::all(&expected_metadata).unwrap();
1462        let read_format = FlatReadFormat::new(
1463            actual_metadata.clone(),
1464            ReadColumns::new([0, 2, 3]),
1465            None,
1466            "test",
1467            true,
1468        )
1469        .unwrap();
1470
1471        let compat_batch = FlatCompatBatch::try_new(&mapper, &read_format, true)
1472            .unwrap()
1473            .unwrap();
1474
1475        let sparse_k1 = encode_sparse_key(&[]);
1476        let input_columns: Vec<ArrayRef> = vec![
1477            Arc::new(Int64Array::from(vec![100, 200])),
1478            Arc::new(TimestampMillisecondArray::from_iter_values([1000, 2000])),
1479            build_flat_test_pk_array(&[&sparse_k1, &sparse_k1]),
1480            Arc::new(UInt64Array::from_iter_values([1, 2])),
1481            Arc::new(UInt8Array::from_iter_values([
1482                OpType::Put as u8,
1483                OpType::Put as u8,
1484            ])),
1485        ];
1486        let input_schema =
1487            to_flat_sst_arrow_schema(&actual_metadata, &FlatSchemaOptions::default());
1488        let input_batch = RecordBatch::try_new(input_schema, input_columns).unwrap();
1489
1490        let result = compat_batch.compat(input_batch).unwrap();
1491
1492        let expected_columns: Vec<ArrayRef> = vec![
1493            Arc::new(Int64Array::from(vec![100, 200])),
1494            Arc::new(Int64Array::from(vec![None::<i64>, None::<i64>])),
1495            Arc::new(TimestampMillisecondArray::from_iter_values([1000, 2000])),
1496            build_flat_test_pk_array(&[&sparse_k1, &sparse_k1]),
1497            Arc::new(UInt64Array::from_iter_values([1, 2])),
1498            Arc::new(UInt8Array::from_iter_values([
1499                OpType::Put as u8,
1500                OpType::Put as u8,
1501            ])),
1502        ];
1503        let output_schema =
1504            to_flat_sst_arrow_schema(&expected_metadata, &FlatSchemaOptions::default());
1505        let expected_batch = RecordBatch::try_new(output_schema, expected_columns).unwrap();
1506
1507        assert_eq!(expected_batch, result);
1508    }
1509    #[test]
1510    fn test_compaction_json2_uses_target_fields()
1511    -> std::result::Result<(), Box<dyn std::error::Error>> {
1512        let (metadata, batch) = test_util::sst_util::new_json2_record_batch()?;
1513        let logical_settings = JsonSettings::default();
1514        let layout = JsonSettings::try_new(vec![], Some(0))?;
1515        let targets: Json2RewriteTargets = Arc::new(BTreeMap::from([(
1516            0,
1517            Json2TargetLayout {
1518                extension_metadata: serde_json::to_string(&JsonMetadata::new(
1519                    logical_settings.clone(),
1520                ))?,
1521                target_layout: layout.clone(),
1522            },
1523        )]));
1524        // Model the existing Rewrite output; the compatibility layer only selects or fills arrays.
1525        let json = JsonArray::from(batch.column(0)).rewrite_to_v2(
1526            batch.schema_ref().field(0),
1527            &logical_settings,
1528            &layout,
1529        )?;
1530        let mut fields = batch.schema_ref().fields().to_vec();
1531        fields[0] = Arc::new(
1532            fields[0]
1533                .as_ref()
1534                .clone()
1535                .with_data_type(json.data_type().clone()),
1536        );
1537        let mut columns = batch.columns().to_vec();
1538        columns[0] = json;
1539        let batch = RecordBatch::try_new(Arc::new(Schema::new(fields)), columns)?;
1540        for encoding in [PrimaryKeyEncoding::Dense, PrimaryKeyEncoding::Sparse] {
1541            let batch = if encoding == PrimaryKeyEncoding::Sparse {
1542                let mut key = Vec::new();
1543                SparsePrimaryKeyCodec::schemaless().encode_internal(42, 7, &mut key)?;
1544                let mut columns = batch.columns().to_vec();
1545                columns[primary_key_column_index(batch.num_columns())] =
1546                    build_flat_test_pk_array(&[&key, &key]);
1547                RecordBatch::try_new(batch.schema(), columns)?
1548            } else {
1549                batch.clone()
1550            };
1551            for missing_json in [false, true] {
1552                let source = if missing_json {
1553                    new_metadata(
1554                        &[(
1555                            1,
1556                            SemanticType::Timestamp,
1557                            ConcreteDataType::timestamp_millisecond_datatype(),
1558                        )],
1559                        &[],
1560                    )
1561                } else {
1562                    metadata.as_ref().clone()
1563                };
1564                let mut actual_builder = RegionMetadataBuilder::from_existing(source);
1565                actual_builder.primary_key_encoding(encoding);
1566                let mut expected_builder =
1567                    RegionMetadataBuilder::from_existing(metadata.as_ref().clone());
1568                expected_builder.primary_key_encoding(encoding);
1569                expected_builder.push_column_metadata(ColumnMetadata {
1570                    column_schema: ColumnSchema::new(
1571                        "added",
1572                        ConcreteDataType::int64_datatype(),
1573                        true,
1574                    )
1575                    .with_default_constraint(Some(
1576                        ColumnDefaultConstraint::Value(Value::Int64(42)),
1577                    ))?,
1578                    semantic_type: SemanticType::Field,
1579                    column_id: 2,
1580                });
1581                if encoding == PrimaryKeyEncoding::Sparse {
1582                    let reserved = test_util::sst_util::sst_region_metadata_with_encoding(encoding);
1583                    for builder in [&mut actual_builder, &mut expected_builder] {
1584                        builder.primary_key(reserved.primary_key[..2].to_vec());
1585                        for column in reserved.column_metadatas.iter().take(2) {
1586                            builder.push_column_metadata(column.clone());
1587                        }
1588                    }
1589                }
1590                let actual = Arc::new(actual_builder.build()?);
1591                let expected = Arc::new(expected_builder.build()?);
1592                let read_columns = ReadColumns::new([0, 1, 2]);
1593                let mapper = FlatProjectionMapper::new_with_json2_rewrite_targets(
1594                    &expected,
1595                    vec![0, 1, 2],
1596                    read_columns.clone(),
1597                    &targets,
1598                )?;
1599                let read_format = FlatReadFormat::new(actual, read_columns, None, "test", true)?;
1600                let compat = FlatCompatBatch::try_new(&mapper, &read_format, true)?
1601                    .ok_or("missing columns require compatibility")?;
1602                let input = if missing_json {
1603                    batch.project(&[1, 2, 3, 4])?
1604                } else {
1605                    batch.clone()
1606                };
1607                let output = compat.compat(input)?;
1608                assert_eq!(
1609                    output.column(0).data_type(),
1610                    &json2_physical_data_type(&layout)
1611                );
1612                assert_eq!(
1613                    output.column(0).null_count(),
1614                    if missing_json { 2 } else { 0 }
1615                );
1616                let defaults = output
1617                    .column(1)
1618                    .as_any()
1619                    .downcast_ref::<Int64Array>()
1620                    .ok_or("expected int64 defaults")?;
1621                assert_eq!(defaults.values().as_ref(), &[42, 42]);
1622                if !missing_json {
1623                    let jsonb = JsonArray::from(output.column(0)).project_to_v2(
1624                        output.schema_ref().field(0),
1625                        &datatypes::arrow::datatypes::DataType::Binary,
1626                    )?;
1627                    let jsonb = jsonb
1628                        .as_any()
1629                        .downcast_ref::<BinaryArray>()
1630                        .ok_or("expected JSONB")?;
1631                    assert_eq!(jsonb.value(0), parse_string_to_jsonb(r#"{"a":1}"#)?);
1632                    assert_eq!(jsonb.value(1), parse_string_to_jsonb(r#"{"a":2}"#)?);
1633                }
1634            }
1635        }
1636        Ok(())
1637    }
1638}