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