Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pola-rs
GitHub Repository: pola-rs/polars
Path: blob/main/crates/polars-parquet/src/arrow/read/statistics.rs
6940 views
1
//! APIs exposing `crate::parquet`'s statistics as arrow's statistics.
2
use arrow::array::{
3
Array, BinaryViewArray, BooleanArray, FixedSizeBinaryArray, MutableBinaryViewArray,
4
MutableBooleanArray, MutableFixedSizeBinaryArray, MutablePrimitiveArray, NullArray,
5
PrimitiveArray, Utf8ViewArray,
6
};
7
use arrow::datatypes::{ArrowDataType, Field, IntegerType, IntervalUnit, TimeUnit};
8
use arrow::types::{NativeType, days_ms, f16, i256};
9
use ethnum::I256;
10
use polars_utils::IdxSize;
11
use polars_utils::pl_str::PlSmallStr;
12
13
use super::{ParquetTimeUnit, RowGroupMetadata};
14
use crate::parquet::error::{ParquetError, ParquetResult};
15
use crate::parquet::schema::types::PhysicalType as ParquetPhysicalType;
16
use crate::parquet::statistics::Statistics as ParquetStatistics;
17
use crate::read::{
18
ColumnChunkMetadata, PrimitiveLogicalType, convert_days_ms, convert_i128, convert_i256,
19
convert_year_month, int96_to_i64_ns,
20
};
21
22
/// Parquet statistics for a nesting level
23
#[derive(Debug, PartialEq)]
24
pub enum Statistics {
25
Column(Box<ColumnStatistics>),
26
27
List(Option<Box<Statistics>>),
28
FixedSizeList(Option<Box<Statistics>>, usize),
29
30
Struct(Box<[Option<Statistics>]>),
31
Dictionary(IntegerType, Option<Box<Statistics>>, bool),
32
}
33
34
/// Arrow-deserialized parquet statistics of a leaf-column
35
#[derive(Debug, PartialEq)]
36
pub struct ColumnStatistics {
37
field: Field,
38
39
logical_type: Option<PrimitiveLogicalType>,
40
physical_type: ParquetPhysicalType,
41
42
/// Statistics of the leaf array of the column
43
statistics: ParquetStatistics,
44
}
45
46
#[derive(Debug, PartialEq)]
47
pub enum ColumnPathSegment {
48
List { is_large: bool },
49
FixedSizeList { width: usize },
50
Dictionary { key: IntegerType, is_sorted: bool },
51
Struct { column_idx: usize },
52
}
53
54
/// Arrow-deserialized parquet statistics of a leaf-column
55
#[derive(Debug, PartialEq)]
56
pub struct ArrowColumnStatistics {
57
pub null_count: Option<u64>,
58
pub distinct_count: Option<u64>,
59
60
// While these two are Box<dyn Array>, they will only ever contain one valid value. This might
61
// seems dumb, and don't get me wrong it is, but arrow::Scalar is basically useless.
62
pub min_value: Option<Box<dyn Array>>,
63
pub max_value: Option<Box<dyn Array>>,
64
}
65
66
/// Arrow-deserialized parquet statistics of a leaf-column
67
pub struct ArrowColumnStatisticsArrays {
68
pub null_count: PrimitiveArray<IdxSize>,
69
pub distinct_count: PrimitiveArray<IdxSize>,
70
pub min_value: Box<dyn Array>,
71
pub max_value: Box<dyn Array>,
72
}
73
74
fn timestamp(logical_type: Option<&PrimitiveLogicalType>, time_unit: TimeUnit, x: i64) -> i64 {
75
let unit = if let Some(PrimitiveLogicalType::Timestamp { unit, .. }) = logical_type {
76
unit
77
} else {
78
return x;
79
};
80
81
match (unit, time_unit) {
82
(ParquetTimeUnit::Milliseconds, TimeUnit::Second) => x / 1_000,
83
(ParquetTimeUnit::Microseconds, TimeUnit::Second) => x / 1_000_000,
84
(ParquetTimeUnit::Nanoseconds, TimeUnit::Second) => x * 1_000_000_000,
85
86
(ParquetTimeUnit::Milliseconds, TimeUnit::Millisecond) => x,
87
(ParquetTimeUnit::Microseconds, TimeUnit::Millisecond) => x / 1_000,
88
(ParquetTimeUnit::Nanoseconds, TimeUnit::Millisecond) => x / 1_000_000,
89
90
(ParquetTimeUnit::Milliseconds, TimeUnit::Microsecond) => x * 1_000,
91
(ParquetTimeUnit::Microseconds, TimeUnit::Microsecond) => x,
92
(ParquetTimeUnit::Nanoseconds, TimeUnit::Microsecond) => x / 1_000,
93
94
(ParquetTimeUnit::Milliseconds, TimeUnit::Nanosecond) => x * 1_000_000,
95
(ParquetTimeUnit::Microseconds, TimeUnit::Nanosecond) => x * 1_000,
96
(ParquetTimeUnit::Nanoseconds, TimeUnit::Nanosecond) => x,
97
}
98
}
99
100
impl ColumnStatistics {
101
pub fn into_arrow(self) -> ParquetResult<ArrowColumnStatistics> {
102
use ParquetStatistics as S;
103
let (null_count, distinct_count) = match &self.statistics {
104
S::Binary(s) => (s.null_count, s.distinct_count),
105
S::Boolean(s) => (s.null_count, s.distinct_count),
106
S::FixedLen(s) => (s.null_count, s.distinct_count),
107
S::Int32(s) => (s.null_count, s.distinct_count),
108
S::Int64(s) => (s.null_count, s.distinct_count),
109
S::Int96(s) => (s.null_count, s.distinct_count),
110
S::Float(s) => (s.null_count, s.distinct_count),
111
S::Double(s) => (s.null_count, s.distinct_count),
112
};
113
114
let null_count = null_count.map(|v| v as u64);
115
let distinct_count = distinct_count.map(|v| v as u64);
116
117
macro_rules! rmap {
118
($expect:ident, $map:expr) => {{
119
let s = self.statistics.$expect();
120
121
let min = s.min_value;
122
let max = s.max_value;
123
124
let min = ($map)(min)?.map(|x| Box::new(x) as Box<dyn Array>);
125
let max = ($map)(max)?.map(|x| Box::new(x) as Box<dyn Array>);
126
127
(min, max)
128
}};
129
($expect:ident, @prim $from:ty $(as $to:ty)? $(, $map:expr)?) => {{
130
rmap!(
131
$expect,
132
|x: Option<$from>| {
133
$(
134
let x = x.map(|x| x as $to);
135
)?
136
$(
137
let x = x.map($map);
138
)?
139
ParquetResult::Ok(x.map(|x| PrimitiveArray::$(<$to>::)?new(
140
self.field.dtype().clone(),
141
vec![x].into(),
142
None,
143
)))
144
}
145
)
146
}};
147
(@binary $(, $map:expr)?) => {{
148
rmap!(
149
expect_binary,
150
|x: Option<Vec<u8>>| {
151
$(
152
let x = x.map($map);
153
)?
154
ParquetResult::Ok(x.map(|x| BinaryViewArray::from_slice([Some(x)])))
155
}
156
)
157
}};
158
(@string) => {{
159
rmap!(
160
expect_binary,
161
|x: Option<Vec<u8>>| {
162
let x = x.map(String::from_utf8).transpose().map_err(|_| {
163
ParquetError::oos("Invalid UTF8 in Statistics")
164
})?;
165
ParquetResult::Ok(x.map(|x| Utf8ViewArray::from_slice([Some(x)])))
166
}
167
)
168
}};
169
}
170
171
use {ArrowDataType as D, ParquetPhysicalType as PPT};
172
let (min_value, max_value) = match (self.field.dtype(), &self.physical_type) {
173
(D::Null, _) => (None, None),
174
175
(D::Boolean, _) => rmap!(expect_boolean, |x: Option<bool>| ParquetResult::Ok(
176
x.map(|x| BooleanArray::new(ArrowDataType::Boolean, vec![x].into(), None,))
177
)),
178
179
(D::Int8, _) => rmap!(expect_int32, @prim i32 as i8),
180
(D::Int16, _) => rmap!(expect_int32, @prim i32 as i16),
181
(D::Int32 | D::Date32 | D::Time32(_), _) => rmap!(expect_int32, @prim i32 as i32),
182
183
// some implementations of parquet write arrow's date64 into i32.
184
(D::Date64, PPT::Int32) => rmap!(expect_int32, @prim i32 as i64, |x| x * 86400000),
185
186
(D::Int64 | D::Time64(_) | D::Duration(_), _) | (D::Date64, PPT::Int64) => {
187
rmap!(expect_int64, @prim i64 as i64)
188
},
189
190
(D::Interval(IntervalUnit::YearMonth), _) => rmap!(
191
expect_binary,
192
@prim Vec<u8>,
193
|x| convert_year_month(&x)
194
),
195
(D::Interval(IntervalUnit::DayTime), _) => rmap!(
196
expect_binary,
197
@prim Vec<u8>,
198
|x| convert_days_ms(&x)
199
),
200
201
(D::UInt8, _) => rmap!(expect_int32, @prim i32 as u8),
202
(D::UInt16, _) => rmap!(expect_int32, @prim i32 as u16),
203
(D::UInt32, PPT::Int32) => rmap!(expect_int32, @prim i32 as u32),
204
205
// some implementations of parquet write arrow's u32 into i64.
206
(D::UInt32, PPT::Int64) => rmap!(expect_int64, @prim i64 as u32),
207
(D::UInt64, _) => rmap!(expect_int64, @prim i64 as u64),
208
209
(D::Timestamp(time_unit, _), PPT::Int96) => {
210
rmap!(expect_int96, @prim [u32; 3], |x| {
211
timestamp(self.logical_type.as_ref(), *time_unit, int96_to_i64_ns(x))
212
})
213
},
214
(D::Timestamp(time_unit, _), PPT::Int64) => {
215
rmap!(expect_int64, @prim i64, |x| {
216
timestamp(self.logical_type.as_ref(), *time_unit, x)
217
})
218
},
219
220
// Read Float16, since we don't have a f16 type in Polars we read it to a Float32.
221
(_, PPT::FixedLenByteArray(2))
222
if matches!(
223
self.logical_type.as_ref(),
224
Some(PrimitiveLogicalType::Float16)
225
) =>
226
{
227
rmap!(expect_fixedlen, @prim Vec<u8>, |v| f16::from_le_bytes([v[0], v[1]]).to_f32())
228
},
229
(D::Float32, _) => rmap!(expect_float, @prim f32),
230
(D::Float64, _) => rmap!(expect_double, @prim f64),
231
232
(D::Decimal(_, _), PPT::Int32) => rmap!(expect_int32, @prim i32 as i128),
233
(D::Decimal(_, _), PPT::Int64) => rmap!(expect_int64, @prim i64 as i128),
234
(D::Decimal(_, _), PPT::FixedLenByteArray(n)) if *n > 16 => {
235
return Err(ParquetError::not_supported(format!(
236
"Can't decode Decimal128 type from Fixed Size Byte Array of len {n:?}",
237
)));
238
},
239
(D::Decimal(_, _), PPT::FixedLenByteArray(n)) => rmap!(
240
expect_fixedlen,
241
@prim Vec<u8>,
242
|x| convert_i128(&x, *n)
243
),
244
(D::Decimal256(_, _), PPT::Int32) => {
245
rmap!(expect_int32, @prim i32, |x: i32| i256(I256::new(x.into())))
246
},
247
(D::Decimal256(_, _), PPT::Int64) => {
248
rmap!(expect_int64, @prim i64, |x: i64| i256(I256::new(x.into())))
249
},
250
(D::Decimal256(_, _), PPT::FixedLenByteArray(n)) if *n > 16 => {
251
return Err(ParquetError::not_supported(format!(
252
"Can't decode Decimal256 type from Fixed Size Byte Array of len {n:?}",
253
)));
254
},
255
(D::Decimal256(_, _), PPT::FixedLenByteArray(_)) => rmap!(
256
expect_fixedlen,
257
@prim Vec<u8>,
258
|x| convert_i256(&x)
259
),
260
(D::Binary, _) => rmap!(@binary),
261
(D::LargeBinary, _) => rmap!(@binary),
262
(D::Utf8, _) => rmap!(@string),
263
(D::LargeUtf8, _) => rmap!(@string),
264
265
(D::BinaryView, _) => rmap!(@binary),
266
(D::Utf8View, _) => rmap!(@string),
267
268
(D::FixedSizeBinary(_), _) => {
269
rmap!(expect_fixedlen, |x: Option<Vec<u8>>| ParquetResult::Ok(
270
x.map(|x| FixedSizeBinaryArray::new(
271
self.field.dtype().clone(),
272
x.into(),
273
None
274
))
275
))
276
},
277
278
other => todo!("{:?}", other),
279
};
280
281
Ok(ArrowColumnStatistics {
282
null_count,
283
distinct_count,
284
285
min_value,
286
max_value,
287
})
288
}
289
}
290
291
/// Deserializes the statistics in the column chunks from a single `row_group`
292
/// into [`Statistics`] associated from `field`'s name.
293
///
294
/// # Errors
295
/// This function errors if the deserialization of the statistics fails (e.g. invalid utf8)
296
pub fn deserialize_all(
297
field: &Field,
298
row_groups: &[RowGroupMetadata],
299
field_idx: usize,
300
) -> ParquetResult<Option<ArrowColumnStatisticsArrays>> {
301
assert!(!row_groups.is_empty());
302
use ArrowDataType as D;
303
match field.dtype() {
304
// @TODO: These are all a bit more complex, skip for now.
305
D::List(..) | D::LargeList(..) => Ok(None),
306
D::Dictionary(..) => Ok(None),
307
D::FixedSizeList(..) => Ok(None),
308
D::Struct(..) => Ok(None),
309
310
_ => {
311
let mut null_count = MutablePrimitiveArray::<IdxSize>::with_capacity(row_groups.len());
312
let mut distinct_count =
313
MutablePrimitiveArray::<IdxSize>::with_capacity(row_groups.len());
314
315
let primitive_type = &row_groups[0].parquet_columns()[field_idx]
316
.descriptor()
317
.descriptor
318
.primitive_type;
319
320
let logical_type = &primitive_type.logical_type;
321
let physical_type = &primitive_type.physical_type;
322
323
macro_rules! rmap {
324
($expect:ident, $map:expr, $arr:ty$(, $arg:expr)?) => {{
325
let mut min_arr = <$arr>::with_capacity(row_groups.len()$(, $arg)?);
326
let mut max_arr = <$arr>::with_capacity(row_groups.len()$(, $arg)?);
327
328
for rg in row_groups {
329
let column = &rg.parquet_columns()[field_idx];
330
let s = column.statistics().transpose()?;
331
332
let (v_min, v_max, v_null_count, v_distinct_count) = match s {
333
None => (None, None, None, None),
334
Some(s) => {
335
let s = s.$expect();
336
337
let min = s.min_value;
338
let max = s.max_value;
339
340
let min = ($map)(min)?;
341
let max = ($map)(max)?;
342
343
(
344
min,
345
max,
346
s.null_count.map(|v| v as IdxSize),
347
s.distinct_count.map(|v| v as IdxSize),
348
)
349
}
350
};
351
352
min_arr.push(v_min);
353
max_arr.push(v_max);
354
null_count.push(v_null_count);
355
distinct_count.push(v_distinct_count);
356
}
357
358
(min_arr.freeze().to_boxed(), max_arr.freeze().to_boxed())
359
}};
360
($expect:ident, $arr:ty, @prim $from:ty $(as $to:ty)? $(, $map:expr)?) => {{
361
rmap!(
362
$expect,
363
|x: Option<$from>| {
364
$(
365
let x = x.map(|x| x as $to);
366
)?
367
$(
368
let x = x.map($map);
369
)?
370
ParquetResult::Ok(x)
371
},
372
$arr
373
)
374
}};
375
(@binary $(, $map:expr)?) => {{
376
rmap!(
377
expect_binary,
378
|x: Option<Vec<u8>>| {
379
$(
380
let x = x.map($map);
381
)?
382
ParquetResult::Ok(x)
383
},
384
MutableBinaryViewArray<[u8]>
385
)
386
}};
387
(@string) => {{
388
rmap!(
389
expect_binary,
390
|x: Option<Vec<u8>>| {
391
let x = x.map(String::from_utf8).transpose().map_err(|_| {
392
ParquetError::oos("Invalid UTF8 in Statistics")
393
})?;
394
ParquetResult::Ok(x)
395
},
396
MutableBinaryViewArray<str>
397
)
398
}};
399
}
400
401
use {ArrowDataType as D, ParquetPhysicalType as PPT};
402
let (min_value, max_value) = match (field.dtype(), physical_type) {
403
(D::Null, _) => (
404
NullArray::new(ArrowDataType::Null, row_groups.len()).to_boxed(),
405
NullArray::new(ArrowDataType::Null, row_groups.len()).to_boxed(),
406
),
407
408
(D::Boolean, _) => rmap!(
409
expect_boolean,
410
|x: Option<bool>| ParquetResult::Ok(x),
411
MutableBooleanArray
412
),
413
414
(D::Int8, _) => rmap!(expect_int32, MutablePrimitiveArray::<i8>, @prim i32 as i8),
415
(D::Int16, _) => {
416
rmap!(expect_int32, MutablePrimitiveArray::<i16>, @prim i32 as i16)
417
},
418
(D::Int32 | D::Date32 | D::Time32(_), _) => {
419
rmap!(expect_int32, MutablePrimitiveArray::<i32>, @prim i32 as i32)
420
},
421
422
// some implementations of parquet write arrow's date64 into i32.
423
(D::Date64, PPT::Int32) => {
424
rmap!(expect_int32, MutablePrimitiveArray::<i64>, @prim i32 as i64, |x| x * 86400000)
425
},
426
427
(D::Int64 | D::Time64(_) | D::Duration(_), _) | (D::Date64, PPT::Int64) => {
428
rmap!(expect_int64, MutablePrimitiveArray::<i64>, @prim i64 as i64)
429
},
430
431
(D::Interval(IntervalUnit::YearMonth), _) => rmap!(
432
expect_binary,
433
MutablePrimitiveArray::<i32>,
434
@prim Vec<u8>,
435
|x| convert_year_month(&x)
436
),
437
(D::Interval(IntervalUnit::DayTime), _) => rmap!(
438
expect_binary,
439
MutablePrimitiveArray::<days_ms>,
440
@prim Vec<u8>,
441
|x| convert_days_ms(&x)
442
),
443
444
(D::UInt8, _) => rmap!(expect_int32, MutablePrimitiveArray::<u8>, @prim i32 as u8),
445
(D::UInt16, _) => {
446
rmap!(expect_int32, MutablePrimitiveArray::<u16>, @prim i32 as u16)
447
},
448
(D::UInt32, PPT::Int32) => {
449
rmap!(expect_int32, MutablePrimitiveArray::<u32>, @prim i32 as u32)
450
},
451
452
// some implementations of parquet write arrow's u32 into i64.
453
(D::UInt32, PPT::Int64) => {
454
rmap!(expect_int64, MutablePrimitiveArray::<u32>, @prim i64 as u32)
455
},
456
(D::UInt64, _) => {
457
rmap!(expect_int64, MutablePrimitiveArray::<u64>, @prim i64 as u64)
458
},
459
460
(D::Timestamp(time_unit, _), PPT::Int96) => {
461
rmap!(expect_int96, MutablePrimitiveArray::<i64>, @prim [u32; 3], |x| {
462
timestamp(logical_type.as_ref(), *time_unit, int96_to_i64_ns(x))
463
})
464
},
465
(D::Timestamp(time_unit, _), PPT::Int64) => {
466
rmap!(expect_int64, MutablePrimitiveArray::<i64>, @prim i64, |x| {
467
timestamp(logical_type.as_ref(), *time_unit, x)
468
})
469
},
470
471
// Read Float16, since we don't have a f16 type in Polars we read it to a Float32.
472
(_, PPT::FixedLenByteArray(2))
473
if matches!(logical_type.as_ref(), Some(PrimitiveLogicalType::Float16)) =>
474
{
475
rmap!(expect_fixedlen, MutablePrimitiveArray::<f32>, @prim Vec<u8>, |v| f16::from_le_bytes([v[0], v[1]]).to_f32())
476
},
477
(D::Float32, _) => rmap!(expect_float, MutablePrimitiveArray::<f32>, @prim f32),
478
(D::Float64, _) => rmap!(expect_double, MutablePrimitiveArray::<f64>, @prim f64),
479
480
(D::Decimal(_, _), PPT::Int32) => {
481
rmap!(expect_int32, MutablePrimitiveArray::<i128>, @prim i32 as i128)
482
},
483
(D::Decimal(_, _), PPT::Int64) => {
484
rmap!(expect_int64, MutablePrimitiveArray::<i128>, @prim i64 as i128)
485
},
486
(D::Decimal(_, _), PPT::FixedLenByteArray(n)) if *n > 16 => {
487
return Err(ParquetError::not_supported(format!(
488
"Can't decode Decimal128 type from Fixed Size Byte Array of len {n:?}",
489
)));
490
},
491
(D::Decimal(_, _), PPT::FixedLenByteArray(n)) => rmap!(
492
expect_fixedlen,
493
MutablePrimitiveArray::<i128>,
494
@prim Vec<u8>,
495
|x| convert_i128(&x, *n)
496
),
497
(D::Decimal256(_, _), PPT::Int32) => {
498
rmap!(expect_int32, MutablePrimitiveArray::<i256>, @prim i32, |x: i32| i256(I256::new(x.into())))
499
},
500
(D::Decimal256(_, _), PPT::Int64) => {
501
rmap!(expect_int64, MutablePrimitiveArray::<i256>, @prim i64, |x: i64| i256(I256::new(x.into())))
502
},
503
(D::Decimal256(_, _), PPT::FixedLenByteArray(n)) if *n > 16 => {
504
return Err(ParquetError::not_supported(format!(
505
"Can't decode Decimal256 type from Fixed Size Byte Array of len {n:?}",
506
)));
507
},
508
(D::Decimal256(_, _), PPT::FixedLenByteArray(_)) => rmap!(
509
expect_fixedlen,
510
MutablePrimitiveArray::<i256>,
511
@prim Vec<u8>,
512
|x| convert_i256(&x)
513
),
514
(D::Binary, _) => rmap!(@binary),
515
(D::LargeBinary, _) => rmap!(@binary),
516
(D::Utf8, _) => rmap!(@string),
517
(D::LargeUtf8, _) => rmap!(@string),
518
519
(D::BinaryView, _) => rmap!(@binary),
520
(D::Utf8View, _) => rmap!(@string),
521
522
(D::FixedSizeBinary(width), _) => {
523
rmap!(
524
expect_fixedlen,
525
|x: Option<Vec<u8>>| ParquetResult::Ok(x),
526
MutableFixedSizeBinaryArray,
527
*width
528
)
529
},
530
531
other => todo!("{:?}", other),
532
};
533
534
Ok(Some(ArrowColumnStatisticsArrays {
535
null_count: null_count.freeze(),
536
distinct_count: distinct_count.freeze(),
537
min_value,
538
max_value,
539
}))
540
},
541
}
542
}
543
544
/// Deserializes the statistics in the column chunks from a single `row_group`
545
/// into [`Statistics`] associated from `field`'s name.
546
///
547
/// # Errors
548
/// This function errors if the deserialization of the statistics fails (e.g. invalid utf8)
549
pub fn deserialize<'a>(
550
field: &Field,
551
columns: &mut impl ExactSizeIterator<Item = &'a ColumnChunkMetadata>,
552
) -> ParquetResult<Option<Statistics>> {
553
use ArrowDataType as D;
554
match field.dtype() {
555
D::List(field) | D::LargeList(field) => Ok(Some(Statistics::List(
556
deserialize(field.as_ref(), columns)?.map(Box::new),
557
))),
558
D::Dictionary(key, dtype, is_sorted) => Ok(Some(Statistics::Dictionary(
559
*key,
560
deserialize(
561
&Field::new(PlSmallStr::EMPTY, dtype.as_ref().clone(), true),
562
columns,
563
)?
564
.map(Box::new),
565
*is_sorted,
566
))),
567
D::FixedSizeList(field, width) => Ok(Some(Statistics::FixedSizeList(
568
deserialize(field.as_ref(), columns)?.map(Box::new),
569
*width,
570
))),
571
D::Struct(fields) => {
572
let field_columns = fields
573
.iter()
574
.map(|f| deserialize(f, columns))
575
.collect::<ParquetResult<_>>()?;
576
Ok(Some(Statistics::Struct(field_columns)))
577
},
578
_ => {
579
let column = columns.next().unwrap();
580
581
Ok(column.statistics().transpose()?.map(|statistics| {
582
let primitive_type = &column.descriptor().descriptor.primitive_type;
583
584
Statistics::Column(Box::new(ColumnStatistics {
585
field: field.clone(),
586
587
logical_type: primitive_type.logical_type,
588
physical_type: primitive_type.physical_type,
589
590
statistics,
591
}))
592
}))
593
},
594
}
595
}
596
597