1use std::sync::Arc;
18
19use api::v1::SemanticType;
20use common_error::ext::BoxedError;
21use common_recordbatch::error::{
22 ArrowComputeSnafu, DataTypesSnafu, ExternalSnafu, NewDfRecordBatchSnafu,
23};
24use common_recordbatch::{DfRecordBatch, RecordBatch};
25use datatypes::arrow::array::Array;
26use datatypes::arrow::datatypes::{DataType as ArrowDataType, Field};
27use datatypes::extension::json::is_json2_extension_type;
28use datatypes::prelude::{ConcreteDataType, DataType};
29use datatypes::schema::{Schema, SchemaRef};
30use datatypes::value::Value;
31use datatypes::vectors::Helper;
32use datatypes::vectors::json::array::JsonArray;
33use datatypes::vectors::json::json2_physical_data_type;
34use snafu::{OptionExt, ResultExt};
35use store_api::metadata::{RegionMetadata, RegionMetadataRef};
36use store_api::storage::ColumnId;
37
38use crate::cache::CacheStrategy;
39use crate::error::{InvalidRequestSnafu, RecordBatchSnafu, Result};
40use crate::read::projection::{read_column_ids_from_projection, repeated_vector_with_cache};
41use crate::read::read_columns::{JsonTargetTypes, ReadColumns};
42use crate::sst::parquet::Json2RewriteTargets;
43use crate::sst::parquet::flat_format::sst_column_id_indices;
44use crate::sst::parquet::format::FormatProjection;
45use crate::sst::{
46 FlatSchemaOptions, internal_fields, tag_maybe_to_dictionary_field, to_flat_sst_arrow_schema,
47 with_field_id,
48};
49
50pub struct FlatProjectionMapper {
55 metadata: RegionMetadataRef,
57 output_schema: SchemaRef,
59 read_cols: ReadColumns,
64 batch_schema: Vec<(ColumnId, ConcreteDataType)>,
69 is_empty_projection: bool,
71 batch_indices: Vec<usize>,
73 input_arrow_schema: datatypes::arrow::datatypes::SchemaRef,
75}
76
77impl FlatProjectionMapper {
78 pub fn new(
83 metadata: &RegionMetadataRef,
84 projection: impl IntoIterator<Item = usize>,
85 ) -> Result<Self> {
86 let projection: Vec<_> = projection.into_iter().collect();
87 let read_column_ids = read_column_ids_from_projection(metadata, &projection)?;
88 let read_cols = ReadColumns::new(read_column_ids);
89 Self::new_with_read_columns(metadata, projection, read_cols)
90 }
91
92 pub fn new_with_read_columns(
94 metadata: &RegionMetadataRef,
95 projection: Vec<usize>,
96 read_cols: ReadColumns,
97 ) -> Result<Self> {
98 Self::new_with_json2_rewrite_targets(
99 metadata,
100 projection,
101 read_cols,
102 &Json2RewriteTargets::default(),
103 )
104 }
105
106 pub(crate) fn new_with_json2_rewrite_targets(
108 metadata: &RegionMetadataRef,
109 projection: Vec<usize>,
110 read_cols: ReadColumns,
111 json2_rewrite_targets: &Json2RewriteTargets,
112 ) -> Result<Self> {
113 let is_empty_projection = projection.is_empty();
115
116 let mut col_schemas = Vec::with_capacity(projection.len());
118 let mut output_col_ids = Vec::with_capacity(projection.len());
120 for idx in &projection {
121 let col = metadata
122 .column_metadatas
123 .get(*idx)
124 .with_context(|| InvalidRequestSnafu {
125 region_id: metadata.region_id,
126 reason: format!("projection index {} is out of bound", idx),
127 })?;
128 output_col_ids.push(col.column_id);
129
130 let mut schema = col.column_schema.clone();
131 if let Some(data_type) = read_cols.json_target_type(col.column_id) {
132 schema.data_type = ConcreteDataType::json2(data_type.clone());
133 }
134 col_schemas.push(schema);
135 }
136
137 let id_to_index = sst_column_id_indices(metadata);
139
140 let format_projection = FormatProjection::compute_format_projection(
142 metadata,
143 &id_to_index,
144 metadata.column_metadatas.len() + 3,
146 read_cols.clone(),
147 );
148
149 let batch_schema =
150 flat_projected_columns(metadata, &format_projection, read_cols.json_target_types());
151
152 let input_arrow_schema =
153 compute_input_arrow_schema(metadata, &batch_schema, &read_cols, json2_rewrite_targets);
154
155 let output_schema = if is_empty_projection {
157 Arc::new(Schema::new(vec![]))
158 } else {
159 Arc::new(Schema::new(col_schemas))
161 };
162
163 let batch_indices = if is_empty_projection {
164 vec![]
165 } else {
166 output_col_ids
167 .iter()
168 .map(|id| {
169 format_projection
171 .column_id_to_projected_index
172 .get(id)
173 .copied()
174 .with_context(|| {
175 let name = metadata
176 .column_by_id(*id)
177 .map(|column| column.column_schema.name.clone())
178 .unwrap_or_else(|| id.to_string());
179 InvalidRequestSnafu {
180 region_id: metadata.region_id,
181 reason: format!(
182 "output column {} is missing in read projection",
183 name
184 ),
185 }
186 })
187 })
188 .collect::<Result<Vec<_>>>()?
189 };
190
191 Ok(FlatProjectionMapper {
192 metadata: metadata.clone(),
193 output_schema,
194 read_cols,
195 batch_schema,
196 is_empty_projection,
197 batch_indices,
198 input_arrow_schema,
199 })
200 }
201
202 pub fn all(metadata: &RegionMetadataRef) -> Result<Self> {
204 FlatProjectionMapper::new(metadata, 0..metadata.column_metadatas.len())
205 }
206
207 pub(crate) fn with_pk_dictionary_encoding(mut self) -> Self {
209 let mut changed = false;
210 let columns = self
211 .output_schema
212 .column_schemas()
213 .iter()
214 .map(|column| {
215 let mut column = column.clone();
216 if column.data_type == ConcreteDataType::string_datatype()
217 && self
218 .metadata
219 .column_by_name(&column.name)
220 .is_some_and(|metadata| metadata.semantic_type == SemanticType::Tag)
221 {
222 changed = true;
223 column.data_type = ConcreteDataType::dictionary_datatype(
224 ConcreteDataType::uint32_datatype(),
225 column.data_type.clone(),
226 );
227 }
228 column
229 })
230 .collect();
231 if changed {
232 self.output_schema = Arc::new(Schema::new_with_version(
233 columns,
234 self.output_schema.version(),
235 ));
236 }
237 self
238 }
239
240 pub(crate) fn metadata(&self) -> &RegionMetadataRef {
242 &self.metadata
243 }
244 pub(crate) fn read_columns(&self) -> &ReadColumns {
246 &self.read_cols
247 }
248
249 pub(crate) fn field_column_start(&self) -> usize {
251 for (idx, column_id) in self
252 .batch_schema
253 .iter()
254 .map(|(column_id, _)| column_id)
255 .enumerate()
256 {
257 if self
259 .metadata
260 .column_by_id(*column_id)
261 .unwrap()
262 .semantic_type
263 == SemanticType::Field
264 {
265 return idx;
266 }
267 }
268
269 self.batch_schema.len()
270 }
271
272 pub(crate) fn batch_schema(&self) -> &[(ColumnId, ConcreteDataType)] {
274 &self.batch_schema
275 }
276
277 pub(crate) fn input_arrow_schema(
281 &self,
282 compaction: bool,
283 ) -> datatypes::arrow::datatypes::SchemaRef {
284 if !compaction {
285 self.input_arrow_schema.clone()
286 } else {
287 let mut options = FlatSchemaOptions::from_encoding(self.metadata.primary_key_encoding);
289 options.concretized_json_types = self
290 .input_arrow_schema
291 .fields()
292 .iter()
293 .filter(|&field| is_json2_extension_type(field))
294 .map(|field| (field.name().clone(), field.data_type().clone()))
295 .collect();
296 to_flat_sst_arrow_schema(&self.metadata, &options)
297 }
298 }
299
300 pub(crate) fn output_schema(&self) -> SchemaRef {
304 self.output_schema.clone()
305 }
306
307 pub(crate) fn convert(
311 &self,
312 batch: &datatypes::arrow::record_batch::RecordBatch,
313 cache_strategy: &CacheStrategy,
314 ) -> common_recordbatch::error::Result<RecordBatch> {
315 if self.is_empty_projection {
316 return RecordBatch::new_with_count(self.output_schema.clone(), batch.num_rows());
317 }
318 let mut arrays = Vec::with_capacity(self.output_schema.num_columns());
321 for (output_idx, index) in self.batch_indices.iter().enumerate() {
322 let mut array = batch.column(*index).clone();
323 if let ArrowDataType::Dictionary(_key_type, value_type) = array.data_type()
325 && !matches!(
326 self.output_schema.arrow_schema().fields()[output_idx].data_type(),
327 ArrowDataType::Dictionary(_, _)
328 )
329 {
330 if let Some(dict_array) = single_value_string_dictionary(
333 &array,
334 &self.output_schema.column_schemas()[output_idx].data_type,
335 value_type.as_ref(),
336 ) {
337 let dict_values = dict_array.values();
338 let value = if dict_values.is_null(0) {
339 Value::Null
340 } else {
341 Value::from(datatypes::arrow_array::string_array_value(dict_values, 0))
342 };
343
344 let repeated = repeated_vector_with_cache(
345 &self.output_schema.column_schemas()[output_idx].data_type,
346 &value,
347 batch.num_rows(),
348 cache_strategy,
349 )?;
350 array = repeated.to_arrow_array();
351 } else {
352 let casted = datatypes::arrow::compute::cast(&array, value_type)
353 .context(ArrowComputeSnafu)?;
354 array = casted;
355 }
356 }
357
358 let field = &self.output_schema.arrow_schema().fields()[output_idx];
359 if is_json2_extension_type(field) && array.data_type() != field.data_type() {
360 array = JsonArray::from(&array)
361 .project_to_v2(batch.schema_ref().field(*index), field.data_type())
362 .context(DataTypesSnafu)?;
363 }
364
365 arrays.push(array);
366 }
367
368 let df_record_batch =
369 DfRecordBatch::try_new(self.output_schema.arrow_schema().clone(), arrays)
370 .context(NewDfRecordBatchSnafu)?;
371 Ok(RecordBatch::from_df_record_batch(
372 self.output_schema.clone(),
373 df_record_batch,
374 ))
375 }
376
377 pub(crate) fn project_vectors(
379 &self,
380 batch: &datatypes::arrow::record_batch::RecordBatch,
381 ) -> common_recordbatch::error::Result<Vec<datatypes::vectors::VectorRef>> {
382 let mut columns = Vec::with_capacity(self.output_schema.num_columns());
383 for index in &self.batch_indices {
384 let mut array = batch.column(*index).clone();
385 if let datatypes::arrow::datatypes::DataType::Dictionary(_key_type, value_type) =
387 array.data_type()
388 {
389 let casted = datatypes::arrow::compute::cast(&array, value_type)
390 .context(ArrowComputeSnafu)?;
391 array = casted;
392 }
393 let vector = Helper::try_into_vector(array)
394 .map_err(BoxedError::new)
395 .context(ExternalSnafu)?;
396 columns.push(vector);
397 }
398 Ok(columns)
399 }
400}
401
402fn single_value_string_dictionary<'a>(
403 array: &'a Arc<dyn Array>,
404 output_type: &ConcreteDataType,
405 value_type: &ArrowDataType,
406) -> Option<&'a datatypes::arrow::array::DictionaryArray<datatypes::arrow::datatypes::UInt32Type>> {
407 if !matches!(
408 value_type,
409 ArrowDataType::Utf8 | ArrowDataType::LargeUtf8 | ArrowDataType::Utf8View
410 ) || !output_type.is_string()
411 {
412 return None;
413 }
414
415 let dict_array = array
416 .as_any()
417 .downcast_ref::<datatypes::arrow::array::DictionaryArray<
418 datatypes::arrow::datatypes::UInt32Type,
419 >>()?;
420
421 (dict_array.values().len() == 1 && dict_array.null_count() == 0).then_some(dict_array)
422}
423
424pub(crate) fn flat_projected_columns(
428 metadata: &RegionMetadata,
429 format_projection: &FormatProjection,
430 json_target_types: &JsonTargetTypes,
431) -> Vec<(ColumnId, ConcreteDataType)> {
432 let time_index = metadata.time_index_column();
433 let num_columns = if format_projection
434 .column_id_to_projected_index
435 .contains_key(&time_index.column_id)
436 {
437 format_projection.column_id_to_projected_index.len()
438 } else {
439 format_projection.column_id_to_projected_index.len() + 1
440 };
441 let mut schema = vec![None; num_columns];
442 for (column_id, index) in &format_projection.column_id_to_projected_index {
443 let data_type = if let Some(json_type) = json_target_types.get(column_id) {
444 ConcreteDataType::json2(json_type.clone())
445 } else {
446 metadata
448 .column_by_id(*column_id)
449 .unwrap()
450 .column_schema
451 .data_type
452 .clone()
453 };
454 schema[*index] = Some((*column_id, data_type));
455 }
456 if num_columns != format_projection.column_id_to_projected_index.len() {
457 schema[num_columns - 1] = Some((
458 time_index.column_id,
459 time_index.column_schema.data_type.clone(),
460 ));
461 }
462
463 schema.into_iter().map(|id_type| id_type.unwrap()).collect()
465}
466
467pub(crate) fn compute_input_arrow_schema(
472 metadata: &RegionMetadata,
473 batch_schema: &[(ColumnId, ConcreteDataType)],
474 read_cols: &ReadColumns,
475 json2_rewrite_targets: &Json2RewriteTargets,
476) -> datatypes::arrow::datatypes::SchemaRef {
477 let mut new_fields = Vec::with_capacity(batch_schema.len() + 3);
478 for (column_id, data_type) in batch_schema {
479 let data_type = json2_rewrite_targets
480 .get(column_id)
481 .map(|x| json2_physical_data_type(&x.target_layout))
482 .or_else(|| {
483 read_cols
484 .json_target_type(*column_id)
485 .map(|x| x.as_arrow_type())
486 })
487 .unwrap_or_else(|| data_type.as_arrow_type());
488
489 let column_metadata = metadata.column_by_id(*column_id).unwrap();
490 let field = Field::new(
491 &column_metadata.column_schema.name,
492 data_type,
493 column_metadata.column_schema.is_nullable(),
494 )
495 .with_metadata(column_metadata.column_schema.metadata().clone());
496 let field = with_field_id(field, *column_id);
497 if column_metadata.semantic_type == SemanticType::Tag {
498 new_fields.push(tag_maybe_to_dictionary_field(
499 &column_metadata.column_schema.data_type,
500 &Arc::new(field),
501 ));
502 } else {
503 new_fields.push(Arc::new(field));
504 }
505 }
506 new_fields.extend_from_slice(&internal_fields());
507
508 Arc::new(datatypes::arrow::datatypes::Schema::new(new_fields))
509}
510
511pub(crate) struct CompactionProjectionMapper {
514 mapper: FlatProjectionMapper,
515 assembler: DfBatchAssembler,
516}
517
518impl CompactionProjectionMapper {
519 pub(crate) fn try_new(metadata: &RegionMetadataRef) -> Result<Self> {
520 let projection = metadata
521 .column_metadatas
522 .iter()
523 .enumerate()
524 .filter_map(|(idx, col)| {
525 if matches!(col.semantic_type, SemanticType::Field) {
526 Some(idx)
527 } else {
528 None
529 }
530 })
531 .chain([metadata.time_index_column_pos()])
532 .collect::<Vec<_>>();
533
534 let read_col_ids = metadata.column_metadatas.iter().map(|col| col.column_id);
535 let read_cols = ReadColumns::new(read_col_ids);
536 let mapper = FlatProjectionMapper::new_with_read_columns(metadata, projection, read_cols)?;
537 let assembler = DfBatchAssembler::new(mapper.output_schema());
538
539 Ok(Self { mapper, assembler })
540 }
541
542 pub(crate) fn project(&self, batch: DfRecordBatch) -> Result<DfRecordBatch> {
546 let columns = self
547 .mapper
548 .project_vectors(&batch)
549 .context(RecordBatchSnafu)?;
550 self.assembler
551 .build_df_record_batch_with_internal(&batch, columns)
552 .context(RecordBatchSnafu)
553 }
554}
555
556pub(crate) struct DfBatchAssembler {
558 output_arrow_schema_with_internal: datatypes::arrow::datatypes::SchemaRef,
559}
560
561impl DfBatchAssembler {
562 pub(crate) fn new(output_schema: SchemaRef) -> Self {
564 let fields = output_schema
565 .arrow_schema()
566 .fields()
567 .into_iter()
568 .chain(internal_fields().iter())
569 .cloned()
570 .collect::<Vec<_>>();
571 let output_arrow_schema_with_internal =
572 Arc::new(datatypes::arrow::datatypes::Schema::new(fields));
573 Self {
574 output_arrow_schema_with_internal,
575 }
576 }
577
578 pub(crate) fn build_df_record_batch_with_internal(
583 &self,
584 batch: &datatypes::arrow::record_batch::RecordBatch,
585 mut columns: Vec<datatypes::vectors::VectorRef>,
586 ) -> common_recordbatch::error::Result<DfRecordBatch> {
587 let num_columns = batch.columns().len();
588 let internal_indices = [num_columns - 3, num_columns - 2, num_columns - 1];
590 for index in internal_indices.iter() {
591 let array = batch.column(*index).clone();
592 let vector = Helper::try_into_vector(array)
593 .map_err(BoxedError::new)
594 .context(ExternalSnafu)?;
595 columns.push(vector);
596 }
597 RecordBatch::to_df_record_batch(self.output_arrow_schema_with_internal.clone(), columns)
598 }
599}
600
601#[cfg(test)]
602mod tests {
603 use datatypes::schema::ColumnSchema;
604 use store_api::metadata::{ColumnMetadata, RegionMetadataBuilder};
605 use store_api::storage::RegionId;
606
607 use super::*;
608
609 fn metadata_with_legacy_json() -> RegionMetadataRef {
610 let mut builder = RegionMetadataBuilder::new(RegionId::new(1024, 0));
611 builder
612 .push_column_metadata(ColumnMetadata {
613 column_schema: datatypes::schema::ColumnSchema::new(
614 "j",
615 ConcreteDataType::json_datatype(),
616 true,
617 ),
618 semantic_type: SemanticType::Field,
619 column_id: 0,
620 })
621 .push_column_metadata(ColumnMetadata {
622 column_schema: datatypes::schema::ColumnSchema::new(
623 "ts",
624 ConcreteDataType::timestamp_millisecond_datatype(),
625 false,
626 ),
627 semantic_type: SemanticType::Timestamp,
628 column_id: 1,
629 });
630 Arc::new(builder.build().unwrap())
631 }
632
633 #[test]
634 fn test_tag_projection_preserves_dictionary() {
635 let mut builder = RegionMetadataBuilder::new(RegionId::new(1024, 0));
636 builder
637 .push_column_metadata(ColumnMetadata {
638 column_schema: ColumnSchema::new("tag", ConcreteDataType::string_datatype(), true),
639 semantic_type: SemanticType::Tag,
640 column_id: 0,
641 })
642 .push_column_metadata(ColumnMetadata {
643 column_schema: ColumnSchema::new(
644 "ts",
645 ConcreteDataType::timestamp_millisecond_datatype(),
646 false,
647 ),
648 semantic_type: SemanticType::Timestamp,
649 column_id: 1,
650 })
651 .primary_key(vec![0]);
652 let metadata = Arc::new(builder.build().unwrap());
653
654 let mapper = FlatProjectionMapper::new(&metadata, [0, 1]).unwrap();
655 assert_eq!(
656 &ArrowDataType::Utf8,
657 mapper.output_schema().arrow_schema().field(0).data_type()
658 );
659
660 let mapper = mapper.with_pk_dictionary_encoding();
661 assert_eq!(
662 &ArrowDataType::Dictionary(
663 Box::new(ArrowDataType::UInt32),
664 Box::new(ArrowDataType::Utf8),
665 ),
666 mapper.output_schema().arrow_schema().field(0).data_type()
667 );
668 }
669
670 #[test]
671 fn test_json_type_hint_does_not_concretize_legacy_json() {
672 let metadata = metadata_with_legacy_json();
673 let mapper = FlatProjectionMapper::new_with_read_columns(
674 &metadata,
675 vec![0, 1],
676 ReadColumns::new([0, 1]),
677 )
678 .unwrap();
679
680 assert_eq!(
681 mapper.batch_schema()[0],
682 (0, ConcreteDataType::json_datatype())
683 );
684 }
685}