1use std::collections::{HashMap, HashSet};
13use std::ops::Sub;
14use std::sync::Arc;
15
16use ailake_catalog::{
17 manifest_commit::{list_equality_deletes_from_metadata, list_files_from_metadata},
18 read_equality_delete_values as read_eq_delete_avro, CatalogProvider, DataFileEntry,
19 EqualityDeleteFile, IcebergMetadata, IcebergSnapshot, SchemaField, SnapshotId, TableIdent,
20};
21use ailake_core::{AilakeError, AilakeResult};
22use ailake_file::AilakeFileReader;
23use ailake_store::Store;
24use arrow_array::{
25 Array, ArrayRef, FixedSizeListArray, Float32Array, Int64Array, RecordBatch, StringArray,
26 UInt32Array,
27};
28use arrow_schema::{DataType, Field, Schema, SchemaRef};
29use arrow_select::take::take;
30use roaring::RoaringBitmap;
31
32use crate::dv::load_deletion_vector;
33use crate::schema_filler::SchemaFiller;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum ChangeType {
38 Insert,
39 UpdateBefore,
40 UpdateAfter,
41 Delete,
42}
43
44impl ChangeType {
45 fn as_str(&self) -> &'static str {
46 match self {
47 ChangeType::Insert => "insert",
48 ChangeType::UpdateBefore => "update_before",
49 ChangeType::UpdateAfter => "update_after",
50 ChangeType::Delete => "delete",
51 }
52 }
53}
54
55#[derive(Debug, Clone)]
57pub struct ChangeRecord {
58 pub row: RecordBatch,
59 pub change_type: ChangeType,
60 pub snapshot_id: SnapshotId,
61 pub sequence_number: i64,
62 pub timestamp_ms: i64,
63}
64
65#[derive(Debug, Clone, Default)]
67pub struct ChangeReaderConfig {
68 pub start_snapshot_id: Option<SnapshotId>,
70 pub end_snapshot_id: Option<SnapshotId>,
72 pub pk_columns: Vec<String>,
74 pub coalesce_updates: bool,
77}
78
79pub async fn read_changes(
94 catalog: Arc<dyn CatalogProvider>,
95 store: Arc<dyn Store>,
96 table: &TableIdent,
97 config: ChangeReaderConfig,
98) -> AilakeResult<RecordBatch> {
99 let meta = catalog.load_raw_metadata(table).await?;
100 let (start_id, end_id) =
101 resolve_snapshots(&meta, config.start_snapshot_id, config.end_snapshot_id)?;
102
103 let _start_snap = find_snapshot(&meta, start_id)?;
104 let end_snap = find_snapshot(&meta, end_id)?;
105
106 let vector_column = meta
107 .properties
108 .get("ailake.vector-column")
109 .cloned()
110 .unwrap_or_else(|| "embedding".to_string());
111 let dim = meta
112 .properties
113 .get("ailake.vector-dim")
114 .and_then(|s| s.parse::<u32>().ok())
115 .unwrap_or(0);
116
117 let schema_fields = meta.to_table_metadata().schema_fields;
118
119 let start_files = list_files_from_metadata(&*store, &meta, Some(start_id)).await?;
120 let end_files = list_files_from_metadata(&*store, &meta, Some(end_id)).await?;
121 let start_deletes = list_equality_deletes_from_metadata(&*store, &meta, Some(start_id)).await?;
122 let end_deletes = list_equality_deletes_from_metadata(&*store, &meta, Some(end_id)).await?;
123
124 let start_file_map: HashMap<&str, &DataFileEntry> =
125 start_files.iter().map(|f| (f.path.as_str(), f)).collect();
126 let end_file_map: HashMap<&str, &DataFileEntry> =
127 end_files.iter().map(|f| (f.path.as_str(), f)).collect();
128
129 let mut records: Vec<ChangeRecord> = Vec::new();
130
131 for file in &end_files {
133 if !start_file_map.contains_key(file.path.as_str()) {
134 let dv = load_dv(&store, file).await?;
135 let batch =
136 read_file_as_batch(&*store, file, &vector_column, dim, &schema_fields).await?;
137 let batch = apply_dv(batch, dv.as_ref())?;
138 for row in split_batch(batch)? {
139 records.push(ChangeRecord {
140 row,
141 change_type: ChangeType::Insert,
142 snapshot_id: end_snap.snapshot_id,
143 sequence_number: end_snap.sequence_number,
144 timestamp_ms: end_snap.timestamp_ms,
145 });
146 }
147 }
148 }
149
150 for file in &start_files {
152 if !end_file_map.contains_key(file.path.as_str()) {
153 let batch =
154 read_file_as_batch(&*store, file, &vector_column, dim, &schema_fields).await?;
155 for row in split_batch(batch)? {
156 records.push(ChangeRecord {
157 row,
158 change_type: ChangeType::Delete,
159 snapshot_id: end_snap.snapshot_id,
160 sequence_number: end_snap.sequence_number,
161 timestamp_ms: end_snap.timestamp_ms,
162 });
163 }
164 }
165 }
166
167 for file in &end_files {
169 if let Some(start_file) = start_file_map.get(file.path.as_str()) {
170 let start_dv = load_dv(&store, start_file).await?;
171 let end_dv = load_dv(&store, file).await?;
172 if dv_bitmap(&start_dv) != dv_bitmap(&end_dv) {
173 let batch =
174 read_file_as_batch(&*store, file, &vector_column, dim, &schema_fields).await?;
175 let deleted = diff_dv(start_dv.as_ref(), end_dv.as_ref());
176 let deleted_batch = take_rows(&batch, &deleted)?;
177 for row in split_batch(deleted_batch)? {
178 records.push(ChangeRecord {
179 row,
180 change_type: ChangeType::Delete,
181 snapshot_id: end_snap.snapshot_id,
182 sequence_number: end_snap.sequence_number,
183 timestamp_ms: end_snap.timestamp_ms,
184 });
185 }
186 }
187 }
188 }
189
190 let new_eq_predicates =
194 collect_equality_predicates(&store, &start_deletes, &end_deletes).await?;
195 if !new_eq_predicates.is_empty() {
196 for file in &end_files {
197 if let Some(start_file) = start_file_map.get(file.path.as_str()) {
198 let start_dv = load_dv(&store, start_file).await?;
199 let end_dv = load_dv(&store, file).await?;
200 let batch =
201 read_file_as_batch(&*store, file, &vector_column, dim, &schema_fields).await?;
202
203 let dv_deleted: HashSet<u32> = diff_dv(start_dv.as_ref(), end_dv.as_ref())
205 .into_iter()
206 .collect();
207
208 let eq_deleted =
210 find_equality_deleted_rows(&batch, file.sequence_number, &new_eq_predicates);
211
212 let mut deleted: Vec<u32> = dv_deleted
213 .iter()
214 .copied()
215 .chain(eq_deleted.into_iter().filter(|i| !dv_deleted.contains(i)))
216 .collect();
217 deleted.sort_unstable();
218 deleted.dedup();
219
220 let deleted_batch = take_rows(&batch, &deleted)?;
221 for row in split_batch(deleted_batch)? {
222 records.push(ChangeRecord {
223 row,
224 change_type: ChangeType::Delete,
225 snapshot_id: end_snap.snapshot_id,
226 sequence_number: end_snap.sequence_number,
227 timestamp_ms: end_snap.timestamp_ms,
228 });
229 }
230 }
231 }
232 }
233
234 if config.coalesce_updates && !config.pk_columns.is_empty() {
236 records = coalesce_updates(records, &config.pk_columns)?;
237 }
238
239 build_change_batch(records)
240}
241
242fn resolve_snapshots(
244 meta: &IcebergMetadata,
245 start: Option<SnapshotId>,
246 end: Option<SnapshotId>,
247) -> AilakeResult<(SnapshotId, SnapshotId)> {
248 let end_id = match end {
249 Some(id) => id,
250 None => meta
251 .current_snapshot_id
252 .ok_or_else(|| AilakeError::Catalog("table has no current snapshot".into()))?,
253 };
254
255 let start_id = match start {
256 Some(id) => id,
257 None => {
258 let end_snap = find_snapshot(meta, end_id)?;
259 match end_snap.parent_snapshot_id {
260 Some(id) => id,
261 None => {
262 return Err(AilakeError::Catalog(
263 "no start snapshot provided and end snapshot has no parent".into(),
264 ))
265 }
266 }
267 }
268 };
269
270 Ok((start_id, end_id))
271}
272
273fn find_snapshot(meta: &IcebergMetadata, id: SnapshotId) -> AilakeResult<IcebergSnapshot> {
274 meta.snapshots
275 .iter()
276 .find(|s| s.snapshot_id == id)
277 .cloned()
278 .ok_or_else(|| AilakeError::Catalog(format!("snapshot {id} not found")))
279}
280
281async fn load_dv(
282 store: &Arc<dyn Store>,
283 file: &DataFileEntry,
284) -> AilakeResult<Option<RoaringBitmap>> {
285 match &file.deletion_vector {
286 Some(dv) => match load_deletion_vector(store, dv).await {
287 Ok(bm) => Ok(Some(bm)),
288 Err(e) => {
289 tracing::warn!("cdc: failed to load deletion vector for {}: {e}", file.path);
290 Ok(None)
291 }
292 },
293 None => Ok(None),
294 }
295}
296
297fn dv_bitmap(dv: &Option<RoaringBitmap>) -> RoaringBitmap {
298 dv.clone().unwrap_or_default()
299}
300
301fn diff_dv(start: Option<&RoaringBitmap>, end: Option<&RoaringBitmap>) -> Vec<u32> {
302 let start = start.cloned().unwrap_or_default();
303 let end = end.cloned().unwrap_or_default();
304 (end.sub(start)).iter().collect()
305}
306
307async fn read_file_as_batch(
308 store: &dyn Store,
309 file: &DataFileEntry,
310 vector_column: &str,
311 dim: u32,
312 schema_fields: &[SchemaField],
313) -> AilakeResult<RecordBatch> {
314 let bytes = store.get(&file.path).await?;
315 let reader = AilakeFileReader::new(bytes, vector_column, dim);
316 let (mut batch, vectors) = reader.read_parquet()?;
317 batch = SchemaFiller::fill(batch, schema_fields)?;
318
319 if !vectors.is_empty() && dim > 0 {
321 batch = append_vector_column(batch, &vectors, vector_column, dim)?;
322 }
323
324 Ok(batch)
325}
326
327fn append_vector_column(
328 batch: RecordBatch,
329 vectors: &[Vec<f32>],
330 vector_column: &str,
331 dim: u32,
332) -> AilakeResult<RecordBatch> {
333 use arrow_schema::Field;
334 let flat: Vec<f32> = vectors.iter().flat_map(|v| v.iter().copied()).collect();
335 let item_field = Arc::new(Field::new("item", DataType::Float32, false));
336 let values_arr = Arc::new(Float32Array::from(flat)) as ArrayRef;
337 let vec_col = FixedSizeListArray::new(Arc::clone(&item_field), dim as i32, values_arr, None);
338 let mut fields: Vec<Field> = batch
339 .schema()
340 .fields()
341 .iter()
342 .map(|f| (**f).clone())
343 .collect();
344 let mut cols: Vec<ArrayRef> = batch.columns().to_vec();
345 fields.push(Field::new(
346 vector_column,
347 DataType::FixedSizeList(Arc::clone(&item_field), dim as i32),
348 true,
349 ));
350 cols.push(Arc::new(vec_col));
351 RecordBatch::try_new(Arc::new(Schema::new(fields)), cols)
352 .map_err(|e| AilakeError::Arrow(e.to_string()))
353}
354
355fn apply_dv(batch: RecordBatch, dv: Option<&RoaringBitmap>) -> AilakeResult<RecordBatch> {
356 let Some(dv) = dv else {
357 return Ok(batch);
358 };
359 if dv.is_empty() {
360 return Ok(batch);
361 }
362 let keep: Vec<u32> = (0..batch.num_rows() as u32)
363 .filter(|i| !dv.contains(*i))
364 .collect();
365 take_rows(&batch, &keep)
366}
367
368fn take_rows(batch: &RecordBatch, indices: &[u32]) -> AilakeResult<RecordBatch> {
369 if indices.is_empty() {
370 return Ok(RecordBatch::new_empty(batch.schema()));
371 }
372 let idx_arr = UInt32Array::from(indices.to_vec());
373 let cols: Vec<ArrayRef> = batch
374 .columns()
375 .iter()
376 .map(|col| {
377 take(col.as_ref(), &idx_arr, None).map_err(|e| AilakeError::Arrow(e.to_string()))
378 })
379 .collect::<AilakeResult<_>>()?;
380 RecordBatch::try_new(batch.schema(), cols).map_err(|e| AilakeError::Arrow(e.to_string()))
381}
382
383fn split_batch(batch: RecordBatch) -> AilakeResult<Vec<RecordBatch>> {
384 let mut out = Vec::with_capacity(batch.num_rows());
385 for i in 0..batch.num_rows() {
386 let idx = UInt32Array::from(vec![i as u32]);
387 let cols: Vec<ArrayRef> = batch
388 .columns()
389 .iter()
390 .map(|col| {
391 take(col.as_ref(), &idx, None).map_err(|e| AilakeError::Arrow(e.to_string()))
392 })
393 .collect::<AilakeResult<_>>()?;
394 out.push(
395 RecordBatch::try_new(batch.schema(), cols)
396 .map_err(|e| AilakeError::Arrow(e.to_string()))?,
397 );
398 }
399 Ok(out)
400}
401
402async fn read_equality_delete_values(
403 store: &Arc<dyn Store>,
404 eq_delete: &EqualityDeleteFile,
405) -> AilakeResult<Vec<(String, String)>> {
406 if let Some((col, vals)) = &eq_delete.inline_values {
409 return Ok(vals.iter().map(|v| (col.clone(), v.clone())).collect());
410 }
411 let bytes = store.get(&eq_delete.path).await?;
412 read_eq_delete_avro(&bytes).map_err(|e| {
413 AilakeError::Catalog(format!(
414 "failed to read equality delete {}: {e}",
415 eq_delete.path
416 ))
417 })
418}
419
420async fn collect_equality_predicates(
423 store: &Arc<dyn Store>,
424 start_deletes: &[EqualityDeleteFile],
425 end_deletes: &[EqualityDeleteFile],
426) -> AilakeResult<Vec<(String, String, i64)>> {
427 let mut predicates = Vec::new();
428 for ed in end_deletes {
429 if start_deletes.iter().any(|sed| sed.path == ed.path) {
430 continue;
431 }
432 let seq = ed.sequence_number;
433 let values = read_equality_delete_values(store, ed).await?;
434 for (col, val) in values {
435 predicates.push((col, val, seq));
436 }
437 }
438 Ok(predicates)
439}
440
441fn find_equality_deleted_rows(
444 batch: &RecordBatch,
445 file_sequence_number: i64,
446 predicates: &[(String, String, i64)],
447) -> Vec<u32> {
448 let mut matches = Vec::new();
449 let schema = batch.schema();
450 for (col, val, delete_seq) in predicates {
451 if *delete_seq <= file_sequence_number {
452 continue;
453 }
454 let Ok(col_idx) = schema.index_of(col) else {
455 continue;
456 };
457 let array = batch.column(col_idx);
458 for row in 0..batch.num_rows() {
459 if array_value_to_string(array, row) == *val {
460 matches.push(row as u32);
461 }
462 }
463 }
464 matches.sort_unstable();
465 matches.dedup();
466 matches
467}
468
469fn coalesce_updates(
470 records: Vec<ChangeRecord>,
471 pk_columns: &[String],
472) -> AilakeResult<Vec<ChangeRecord>> {
473 let mut inserts_by_pk: HashMap<String, Vec<usize>> = HashMap::new();
474 let mut deletes_by_pk: HashMap<String, Vec<usize>> = HashMap::new();
475
476 for (i, rec) in records.iter().enumerate() {
477 let key = pk_key(&rec.row, pk_columns)?;
478 match rec.change_type {
479 ChangeType::Insert => inserts_by_pk.entry(key).or_default().push(i),
480 ChangeType::Delete => deletes_by_pk.entry(key).or_default().push(i),
481 _ => {}
482 }
483 }
484
485 let mut used = HashSet::new();
486 let mut pairs: Vec<(usize, usize)> = Vec::new();
487
488 for (key, del_idxs) in deletes_by_pk {
490 for del_i in del_idxs {
491 if used.contains(&del_i) {
492 continue;
493 }
494 if let Some(ins_idxs) = inserts_by_pk.get(&key) {
495 if let Some(&ins_i) = ins_idxs.iter().find(|idx| !used.contains(*idx)) {
496 used.insert(del_i);
497 used.insert(ins_i);
498 pairs.push((del_i, ins_i));
499 }
500 }
501 }
502 }
503
504 let mut out: Vec<ChangeRecord> = Vec::with_capacity(records.len());
507 let mut pair_idx = 0;
508 for (i, rec) in records.iter().enumerate() {
509 if used.contains(&i) {
510 if pair_idx < pairs.len() && pairs[pair_idx].0 == i {
512 let (_, ins_i) = pairs[pair_idx];
513 pair_idx += 1;
514 let delete_rec = &records[i];
515 let insert_rec = &records[ins_i];
516 out.push(ChangeRecord {
517 row: delete_rec.row.clone(),
518 change_type: ChangeType::UpdateBefore,
519 snapshot_id: delete_rec.snapshot_id,
520 sequence_number: delete_rec.sequence_number,
521 timestamp_ms: delete_rec.timestamp_ms,
522 });
523 out.push(ChangeRecord {
524 row: insert_rec.row.clone(),
525 change_type: ChangeType::UpdateAfter,
526 snapshot_id: insert_rec.snapshot_id,
527 sequence_number: insert_rec.sequence_number,
528 timestamp_ms: insert_rec.timestamp_ms,
529 });
530 }
531 continue;
532 }
533 out.push(rec.clone());
534 }
535
536 Ok(out)
537}
538
539fn pk_key(batch: &RecordBatch, pk_columns: &[String]) -> AilakeResult<String> {
540 let mut parts = Vec::with_capacity(pk_columns.len());
541 for col in pk_columns {
542 let idx = batch
543 .schema()
544 .index_of(col)
545 .map_err(|e| AilakeError::Arrow(format!("pk column {col} not found: {e}")))?;
546 let array = batch.column(idx);
547 let val = if array.is_null(0) {
548 "NULL".to_string()
549 } else {
550 array_value_to_string(array, 0)
551 };
552 parts.push(val);
553 }
554 Ok(parts.join("|"))
555}
556
557fn array_value_to_string(array: &ArrayRef, row: usize) -> String {
558 use arrow_array::cast::AsArray;
559 use arrow_schema::DataType;
560 if array.is_null(row) {
561 return "NULL".to_string();
562 }
563 match array.data_type() {
564 DataType::Utf8 => array.as_string::<i32>().value(row).to_string(),
565 DataType::LargeUtf8 => array.as_string::<i64>().value(row).to_string(),
566 DataType::Int32 => array
567 .as_primitive::<arrow_array::types::Int32Type>()
568 .value(row)
569 .to_string(),
570 DataType::Int64 => array
571 .as_primitive::<arrow_array::types::Int64Type>()
572 .value(row)
573 .to_string(),
574 DataType::Float32 => array
575 .as_primitive::<arrow_array::types::Float32Type>()
576 .value(row)
577 .to_string(),
578 DataType::Float64 => array
579 .as_primitive::<arrow_array::types::Float64Type>()
580 .value(row)
581 .to_string(),
582 DataType::Boolean => array.as_boolean().value(row).to_string(),
583 _ => format!("{:?}", array),
584 }
585}
586
587fn build_change_batch(records: Vec<ChangeRecord>) -> AilakeResult<RecordBatch> {
588 if records.is_empty() {
589 let schema = Arc::new(Schema::new(vec![
590 Field::new("_change_type", DataType::Utf8, false),
591 Field::new("_snapshot_id", DataType::Int64, false),
592 Field::new("_sequence_number", DataType::Int64, false),
593 Field::new("_commit_timestamp", DataType::Int64, false),
594 ]));
595 return Ok(RecordBatch::new_empty(schema));
596 }
597
598 let base_schema = union_schema(&records);
602 let records = records
603 .into_iter()
604 .map(|rec| normalize_to_schema(rec, &base_schema))
605 .collect::<AilakeResult<Vec<_>>>()?;
606
607 let mut fields: Vec<Field> = base_schema.fields().iter().map(|f| (**f).clone()).collect();
608 fields.push(Field::new("_change_type", DataType::Utf8, false));
609 fields.push(Field::new("_snapshot_id", DataType::Int64, false));
610 fields.push(Field::new("_sequence_number", DataType::Int64, false));
611 fields.push(Field::new("_commit_timestamp", DataType::Int64, false));
612 let out_schema = Arc::new(Schema::new(fields));
613
614 let mut base_cols: Vec<Vec<ArrayRef>> = vec![vec![]; base_schema.fields().len()];
615 let mut types = Vec::with_capacity(records.len());
616 let mut snap_ids = Vec::with_capacity(records.len());
617 let mut seqs = Vec::with_capacity(records.len());
618 let mut timestamps = Vec::with_capacity(records.len());
619
620 for rec in records {
621 types.push(rec.change_type.as_str());
622 snap_ids.push(rec.snapshot_id);
623 seqs.push(rec.sequence_number);
624 timestamps.push(rec.timestamp_ms);
625 for (i, col) in rec.row.columns().iter().enumerate() {
626 base_cols[i].push(col.clone());
627 }
628 }
629
630 use arrow_select::concat::concat;
631 let mut out_cols: Vec<ArrayRef> = Vec::with_capacity(base_schema.fields().len() + 4);
632 for col_parts in base_cols {
633 let arrays: Vec<&dyn Array> = col_parts.iter().map(|a| a.as_ref()).collect();
634 out_cols.push(concat(&arrays).map_err(|e| AilakeError::Arrow(e.to_string()))?);
635 }
636 out_cols.push(Arc::new(StringArray::from(types)) as ArrayRef);
637 out_cols.push(Arc::new(Int64Array::from(snap_ids)) as ArrayRef);
638 out_cols.push(Arc::new(Int64Array::from(seqs)) as ArrayRef);
639 out_cols.push(Arc::new(Int64Array::from(timestamps)) as ArrayRef);
640
641 RecordBatch::try_new(out_schema, out_cols).map_err(|e| AilakeError::Arrow(e.to_string()))
642}
643
644fn union_schema(records: &[ChangeRecord]) -> SchemaRef {
649 let mut seen: HashMap<String, usize> = HashMap::new();
650 let mut fields: Vec<Field> = Vec::new();
651 for rec in records {
652 for f in rec.row.schema().fields() {
653 if let Some(&idx) = seen.get(f.name()) {
654 if f.is_nullable() && !fields[idx].is_nullable() {
655 fields[idx] = Field::new(f.name(), f.data_type().clone(), true);
656 }
657 } else {
658 seen.insert(f.name().clone(), fields.len());
659 fields.push((**f).clone());
660 }
661 }
662 }
663 let all_names: HashSet<String> = seen.keys().cloned().collect();
665 for rec in records {
666 let rec_names: HashSet<String> = rec
667 .row
668 .schema()
669 .fields()
670 .iter()
671 .map(|f| f.name().clone())
672 .collect();
673 for missing in all_names.difference(&rec_names) {
674 if let Some(&idx) = seen.get(missing) {
675 if !fields[idx].is_nullable() {
676 fields[idx] =
677 Field::new(fields[idx].name(), fields[idx].data_type().clone(), true);
678 }
679 }
680 }
681 }
682 Arc::new(Schema::new(fields))
683}
684
685fn normalize_to_schema(rec: ChangeRecord, schema: &SchemaRef) -> AilakeResult<ChangeRecord> {
688 if rec.row.schema() == *schema {
689 return Ok(rec);
690 }
691 let n = rec.row.num_rows();
692 let mut cols: Vec<ArrayRef> = Vec::with_capacity(schema.fields().len());
693 for field in schema.fields() {
694 if let Ok(idx) = rec.row.schema().index_of(field.name()) {
695 cols.push(rec.row.column(idx).clone());
696 } else {
697 cols.push(null_array_for(field.data_type(), n));
698 }
699 }
700 let batch = RecordBatch::try_new(schema.clone(), cols)
701 .map_err(|e| AilakeError::Arrow(e.to_string()))?;
702 Ok(ChangeRecord {
703 row: batch,
704 change_type: rec.change_type,
705 snapshot_id: rec.snapshot_id,
706 sequence_number: rec.sequence_number,
707 timestamp_ms: rec.timestamp_ms,
708 })
709}
710
711fn null_array_for(data_type: &DataType, n: usize) -> ArrayRef {
712 use arrow_array::*;
713 match data_type {
714 DataType::Utf8 => Arc::new(StringArray::from(vec![None::<&str>; n])) as ArrayRef,
715 DataType::LargeUtf8 => Arc::new(LargeStringArray::from(vec![None::<&str>; n])) as ArrayRef,
716 DataType::Int32 => Arc::new(Int32Array::from(vec![None::<i32>; n])) as ArrayRef,
717 DataType::Int64 => Arc::new(Int64Array::from(vec![None::<i64>; n])) as ArrayRef,
718 DataType::UInt32 => Arc::new(UInt32Array::from(vec![None::<u32>; n])) as ArrayRef,
719 DataType::Float32 => Arc::new(Float32Array::from(vec![None::<f32>; n])) as ArrayRef,
720 DataType::Float64 => Arc::new(Float64Array::from(vec![None::<f64>; n])) as ArrayRef,
721 DataType::Boolean => Arc::new(BooleanArray::from(vec![None::<bool>; n])) as ArrayRef,
722 DataType::FixedSizeList(item, dim) => {
723 let nulls = arrow_array::NullArray::new(n);
724 let values = Arc::new(null_array_for(item.data_type(), n * *dim as usize));
725 Arc::new(FixedSizeListArray::new(
726 Arc::clone(item),
727 *dim,
728 values,
729 nulls.nulls().cloned(),
730 )) as ArrayRef
731 }
732 DataType::List(item) => {
733 let offsets = arrow_buffer::OffsetBuffer::new(arrow_buffer::ScalarBuffer::from(vec![
734 0i32;
735 n + 1
736 ]));
737 let values = null_array_for(item.data_type(), 0);
738 Arc::new(ListArray::new(Arc::clone(item), offsets, values, None)) as ArrayRef
739 }
740 DataType::LargeList(item) => {
741 let offsets = arrow_buffer::OffsetBuffer::new(arrow_buffer::ScalarBuffer::from(vec![
742 0i64;
743 n + 1
744 ]));
745 let values = null_array_for(item.data_type(), 0);
746 Arc::new(LargeListArray::new(Arc::clone(item), offsets, values, None)) as ArrayRef
747 }
748 _ => Arc::new(arrow_array::NullArray::new(n)) as ArrayRef,
749 }
750}
751
752#[cfg(test)]
753mod tests {
754 use super::*;
755
756 #[test]
757 fn change_type_strings() {
758 assert_eq!(ChangeType::Insert.as_str(), "insert");
759 assert_eq!(ChangeType::Delete.as_str(), "delete");
760 assert_eq!(ChangeType::UpdateBefore.as_str(), "update_before");
761 assert_eq!(ChangeType::UpdateAfter.as_str(), "update_after");
762 }
763}