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