1#include "duckdb/storage/statistics/numeric_stats.hpp"
2#include "duckdb/storage/statistics/base_statistics.hpp"
3#include "duckdb/common/field_writer.hpp"
4#include "duckdb/common/types/vector.hpp"
5#include "duckdb/common/operator/comparison_operators.hpp"
6
7namespace duckdb {
8
9template <>
10void NumericStats::Update<interval_t>(BaseStatistics &stats, interval_t new_value) {
11}
12
13template <>
14void NumericStats::Update<list_entry_t>(BaseStatistics &stats, list_entry_t new_value) {
15}
16
17//===--------------------------------------------------------------------===//
18// NumericStats
19//===--------------------------------------------------------------------===//
20BaseStatistics NumericStats::CreateUnknown(LogicalType type) {
21 BaseStatistics result(std::move(type));
22 result.InitializeUnknown();
23 SetMin(stats&: result, val: Value(result.GetType()));
24 SetMax(stats&: result, val: Value(result.GetType()));
25 return result;
26}
27
28BaseStatistics NumericStats::CreateEmpty(LogicalType type) {
29 BaseStatistics result(std::move(type));
30 result.InitializeEmpty();
31 SetMin(stats&: result, val: Value::MaximumValue(type: result.GetType()));
32 SetMax(stats&: result, val: Value::MinimumValue(type: result.GetType()));
33 return result;
34}
35
36NumericStatsData &NumericStats::GetDataUnsafe(BaseStatistics &stats) {
37 D_ASSERT(stats.GetStatsType() == StatisticsType::NUMERIC_STATS);
38 return stats.stats_union.numeric_data;
39}
40
41const NumericStatsData &NumericStats::GetDataUnsafe(const BaseStatistics &stats) {
42 D_ASSERT(stats.GetStatsType() == StatisticsType::NUMERIC_STATS);
43 return stats.stats_union.numeric_data;
44}
45
46void NumericStats::Merge(BaseStatistics &stats, const BaseStatistics &other) {
47 if (other.GetType().id() == LogicalTypeId::VALIDITY) {
48 return;
49 }
50 D_ASSERT(stats.GetType() == other.GetType());
51 if (NumericStats::HasMin(stats: other) && NumericStats::HasMin(stats)) {
52 auto other_min = NumericStats::Min(stats: other);
53 if (other_min < NumericStats::Min(stats)) {
54 NumericStats::SetMin(stats, val: other_min);
55 }
56 } else {
57 NumericStats::SetMin(stats, val: Value());
58 }
59 if (NumericStats::HasMax(stats: other) && NumericStats::HasMax(stats)) {
60 auto other_max = NumericStats::Max(stats: other);
61 if (other_max > NumericStats::Max(stats)) {
62 NumericStats::SetMax(stats, val: other_max);
63 }
64 } else {
65 NumericStats::SetMax(stats, val: Value());
66 }
67}
68
69struct GetNumericValueUnion {
70 template <class T>
71 static T Operation(const NumericValueUnion &v);
72};
73
74template <>
75int8_t GetNumericValueUnion::Operation(const NumericValueUnion &v) {
76 return v.value_.tinyint;
77}
78
79template <>
80int16_t GetNumericValueUnion::Operation(const NumericValueUnion &v) {
81 return v.value_.smallint;
82}
83
84template <>
85int32_t GetNumericValueUnion::Operation(const NumericValueUnion &v) {
86 return v.value_.integer;
87}
88
89template <>
90int64_t GetNumericValueUnion::Operation(const NumericValueUnion &v) {
91 return v.value_.bigint;
92}
93
94template <>
95hugeint_t GetNumericValueUnion::Operation(const NumericValueUnion &v) {
96 return v.value_.hugeint;
97}
98
99template <>
100uint8_t GetNumericValueUnion::Operation(const NumericValueUnion &v) {
101 return v.value_.utinyint;
102}
103
104template <>
105uint16_t GetNumericValueUnion::Operation(const NumericValueUnion &v) {
106 return v.value_.usmallint;
107}
108
109template <>
110uint32_t GetNumericValueUnion::Operation(const NumericValueUnion &v) {
111 return v.value_.uinteger;
112}
113
114template <>
115uint64_t GetNumericValueUnion::Operation(const NumericValueUnion &v) {
116 return v.value_.ubigint;
117}
118
119template <>
120float GetNumericValueUnion::Operation(const NumericValueUnion &v) {
121 return v.value_.float_;
122}
123
124template <>
125double GetNumericValueUnion::Operation(const NumericValueUnion &v) {
126 return v.value_.double_;
127}
128
129template <class T>
130T NumericStats::GetMinUnsafe(const BaseStatistics &stats) {
131 return GetNumericValueUnion::Operation<T>(NumericStats::GetDataUnsafe(stats).min);
132}
133
134template <class T>
135T NumericStats::GetMaxUnsafe(const BaseStatistics &stats) {
136 return GetNumericValueUnion::Operation<T>(NumericStats::GetDataUnsafe(stats).max);
137}
138
139template <class T>
140bool ConstantExactRange(T min, T max, T constant) {
141 return Equals::Operation(constant, min) && Equals::Operation(constant, max);
142}
143
144template <class T>
145bool ConstantValueInRange(T min, T max, T constant) {
146 return !(LessThan::Operation(constant, min) || GreaterThan::Operation(constant, max));
147}
148
149template <class T>
150FilterPropagateResult CheckZonemapTemplated(const BaseStatistics &stats, ExpressionType comparison_type,
151 const Value &constant_value) {
152 T min_value = NumericStats::GetMinUnsafe<T>(stats);
153 T max_value = NumericStats::GetMaxUnsafe<T>(stats);
154 T constant = constant_value.GetValueUnsafe<T>();
155 switch (comparison_type) {
156 case ExpressionType::COMPARE_EQUAL:
157 if (ConstantExactRange(min_value, max_value, constant)) {
158 return FilterPropagateResult::FILTER_ALWAYS_TRUE;
159 }
160 if (ConstantValueInRange(min_value, max_value, constant)) {
161 return FilterPropagateResult::NO_PRUNING_POSSIBLE;
162 }
163 return FilterPropagateResult::FILTER_ALWAYS_FALSE;
164 case ExpressionType::COMPARE_NOTEQUAL:
165 if (!ConstantValueInRange(min_value, max_value, constant)) {
166 return FilterPropagateResult::FILTER_ALWAYS_TRUE;
167 } else if (ConstantExactRange(min_value, max_value, constant)) {
168 // corner case of a cluster with one numeric equal to the target constant
169 return FilterPropagateResult::FILTER_ALWAYS_FALSE;
170 }
171 return FilterPropagateResult::NO_PRUNING_POSSIBLE;
172 case ExpressionType::COMPARE_GREATERTHANOREQUALTO:
173 // GreaterThanEquals::Operation(X, C)
174 // this can be true only if max(X) >= C
175 // if min(X) >= C, then this is always true
176 if (GreaterThanEquals::Operation(min_value, constant)) {
177 return FilterPropagateResult::FILTER_ALWAYS_TRUE;
178 } else if (GreaterThanEquals::Operation(max_value, constant)) {
179 return FilterPropagateResult::NO_PRUNING_POSSIBLE;
180 } else {
181 return FilterPropagateResult::FILTER_ALWAYS_FALSE;
182 }
183 case ExpressionType::COMPARE_GREATERTHAN:
184 // GreaterThan::Operation(X, C)
185 // this can be true only if max(X) > C
186 // if min(X) > C, then this is always true
187 if (GreaterThan::Operation(min_value, constant)) {
188 return FilterPropagateResult::FILTER_ALWAYS_TRUE;
189 } else if (GreaterThan::Operation(max_value, constant)) {
190 return FilterPropagateResult::NO_PRUNING_POSSIBLE;
191 } else {
192 return FilterPropagateResult::FILTER_ALWAYS_FALSE;
193 }
194 case ExpressionType::COMPARE_LESSTHANOREQUALTO:
195 // LessThanEquals::Operation(X, C)
196 // this can be true only if min(X) <= C
197 // if max(X) <= C, then this is always true
198 if (LessThanEquals::Operation(max_value, constant)) {
199 return FilterPropagateResult::FILTER_ALWAYS_TRUE;
200 } else if (LessThanEquals::Operation(min_value, constant)) {
201 return FilterPropagateResult::NO_PRUNING_POSSIBLE;
202 } else {
203 return FilterPropagateResult::FILTER_ALWAYS_FALSE;
204 }
205 case ExpressionType::COMPARE_LESSTHAN:
206 // LessThan::Operation(X, C)
207 // this can be true only if min(X) < C
208 // if max(X) < C, then this is always true
209 if (LessThan::Operation(max_value, constant)) {
210 return FilterPropagateResult::FILTER_ALWAYS_TRUE;
211 } else if (LessThan::Operation(min_value, constant)) {
212 return FilterPropagateResult::NO_PRUNING_POSSIBLE;
213 } else {
214 return FilterPropagateResult::FILTER_ALWAYS_FALSE;
215 }
216 default:
217 throw InternalException("Expression type in zonemap check not implemented");
218 }
219}
220
221FilterPropagateResult NumericStats::CheckZonemap(const BaseStatistics &stats, ExpressionType comparison_type,
222 const Value &constant) {
223 D_ASSERT(constant.type() == stats.GetType());
224 if (constant.IsNull()) {
225 return FilterPropagateResult::FILTER_ALWAYS_FALSE;
226 }
227 if (!NumericStats::HasMinMax(stats)) {
228 return FilterPropagateResult::NO_PRUNING_POSSIBLE;
229 }
230 switch (stats.GetType().InternalType()) {
231 case PhysicalType::INT8:
232 return CheckZonemapTemplated<int8_t>(stats, comparison_type, constant_value: constant);
233 case PhysicalType::INT16:
234 return CheckZonemapTemplated<int16_t>(stats, comparison_type, constant_value: constant);
235 case PhysicalType::INT32:
236 return CheckZonemapTemplated<int32_t>(stats, comparison_type, constant_value: constant);
237 case PhysicalType::INT64:
238 return CheckZonemapTemplated<int64_t>(stats, comparison_type, constant_value: constant);
239 case PhysicalType::UINT8:
240 return CheckZonemapTemplated<uint8_t>(stats, comparison_type, constant_value: constant);
241 case PhysicalType::UINT16:
242 return CheckZonemapTemplated<uint16_t>(stats, comparison_type, constant_value: constant);
243 case PhysicalType::UINT32:
244 return CheckZonemapTemplated<uint32_t>(stats, comparison_type, constant_value: constant);
245 case PhysicalType::UINT64:
246 return CheckZonemapTemplated<uint64_t>(stats, comparison_type, constant_value: constant);
247 case PhysicalType::INT128:
248 return CheckZonemapTemplated<hugeint_t>(stats, comparison_type, constant_value: constant);
249 case PhysicalType::FLOAT:
250 return CheckZonemapTemplated<float>(stats, comparison_type, constant_value: constant);
251 case PhysicalType::DOUBLE:
252 return CheckZonemapTemplated<double>(stats, comparison_type, constant_value: constant);
253 default:
254 throw InternalException("Unsupported type for NumericStats::CheckZonemap");
255 }
256}
257
258bool NumericStats::IsConstant(const BaseStatistics &stats) {
259 return NumericStats::Max(stats) <= NumericStats::Min(stats);
260}
261
262void SetNumericValueInternal(const Value &input, const LogicalType &type, NumericValueUnion &val, bool &has_val) {
263 if (input.IsNull()) {
264 has_val = false;
265 return;
266 }
267 if (input.type().InternalType() != type.InternalType()) {
268 throw InternalException("SetMin or SetMax called with Value that does not match statistics' column value");
269 }
270 has_val = true;
271 switch (type.InternalType()) {
272 case PhysicalType::BOOL:
273 val.value_.boolean = BooleanValue::Get(value: input);
274 break;
275 case PhysicalType::INT8:
276 val.value_.tinyint = TinyIntValue::Get(value: input);
277 break;
278 case PhysicalType::INT16:
279 val.value_.smallint = SmallIntValue::Get(value: input);
280 break;
281 case PhysicalType::INT32:
282 val.value_.integer = IntegerValue::Get(value: input);
283 break;
284 case PhysicalType::INT64:
285 val.value_.bigint = BigIntValue::Get(value: input);
286 break;
287 case PhysicalType::UINT8:
288 val.value_.utinyint = UTinyIntValue::Get(value: input);
289 break;
290 case PhysicalType::UINT16:
291 val.value_.usmallint = USmallIntValue::Get(value: input);
292 break;
293 case PhysicalType::UINT32:
294 val.value_.uinteger = UIntegerValue::Get(value: input);
295 break;
296 case PhysicalType::UINT64:
297 val.value_.ubigint = UBigIntValue::Get(value: input);
298 break;
299 case PhysicalType::INT128:
300 val.value_.hugeint = HugeIntValue::Get(value: input);
301 break;
302 case PhysicalType::FLOAT:
303 val.value_.float_ = FloatValue::Get(value: input);
304 break;
305 case PhysicalType::DOUBLE:
306 val.value_.double_ = DoubleValue::Get(value: input);
307 break;
308 default:
309 throw InternalException("Unsupported type for NumericStatistics::SetValueInternal");
310 }
311}
312
313void NumericStats::SetMin(BaseStatistics &stats, const Value &new_min) {
314 auto &data = NumericStats::GetDataUnsafe(stats);
315 SetNumericValueInternal(input: new_min, type: stats.GetType(), val&: data.min, has_val&: data.has_min);
316}
317
318void NumericStats::SetMax(BaseStatistics &stats, const Value &new_max) {
319 auto &data = NumericStats::GetDataUnsafe(stats);
320 SetNumericValueInternal(input: new_max, type: stats.GetType(), val&: data.max, has_val&: data.has_max);
321}
322
323Value NumericValueUnionToValueInternal(const LogicalType &type, const NumericValueUnion &val) {
324 switch (type.InternalType()) {
325 case PhysicalType::BOOL:
326 return Value::BOOLEAN(value: val.value_.boolean);
327 case PhysicalType::INT8:
328 return Value::TINYINT(value: val.value_.tinyint);
329 case PhysicalType::INT16:
330 return Value::SMALLINT(value: val.value_.smallint);
331 case PhysicalType::INT32:
332 return Value::INTEGER(value: val.value_.integer);
333 case PhysicalType::INT64:
334 return Value::BIGINT(value: val.value_.bigint);
335 case PhysicalType::UINT8:
336 return Value::UTINYINT(value: val.value_.utinyint);
337 case PhysicalType::UINT16:
338 return Value::USMALLINT(value: val.value_.usmallint);
339 case PhysicalType::UINT32:
340 return Value::UINTEGER(value: val.value_.uinteger);
341 case PhysicalType::UINT64:
342 return Value::UBIGINT(value: val.value_.ubigint);
343 case PhysicalType::INT128:
344 return Value::HUGEINT(value: val.value_.hugeint);
345 case PhysicalType::FLOAT:
346 return Value::FLOAT(value: val.value_.float_);
347 case PhysicalType::DOUBLE:
348 return Value::DOUBLE(value: val.value_.double_);
349 default:
350 throw InternalException("Unsupported type for NumericValueUnionToValue");
351 }
352}
353
354Value NumericValueUnionToValue(const LogicalType &type, const NumericValueUnion &val) {
355 Value result = NumericValueUnionToValueInternal(type, val);
356 result.GetTypeMutable() = type;
357 return result;
358}
359
360bool NumericStats::HasMinMax(const BaseStatistics &stats) {
361 return NumericStats::HasMin(stats) && NumericStats::HasMax(stats);
362}
363
364bool NumericStats::HasMin(const BaseStatistics &stats) {
365 if (stats.GetType().id() == LogicalTypeId::SQLNULL) {
366 return false;
367 }
368 return NumericStats::GetDataUnsafe(stats).has_min;
369}
370
371bool NumericStats::HasMax(const BaseStatistics &stats) {
372 if (stats.GetType().id() == LogicalTypeId::SQLNULL) {
373 return false;
374 }
375 return NumericStats::GetDataUnsafe(stats).has_max;
376}
377
378Value NumericStats::Min(const BaseStatistics &stats) {
379 if (!NumericStats::HasMin(stats)) {
380 throw InternalException("Min() called on statistics that does not have min");
381 }
382 return NumericValueUnionToValue(type: stats.GetType(), val: NumericStats::GetDataUnsafe(stats).min);
383}
384
385Value NumericStats::Max(const BaseStatistics &stats) {
386 if (!NumericStats::HasMax(stats)) {
387 throw InternalException("Max() called on statistics that does not have max");
388 }
389 return NumericValueUnionToValue(type: stats.GetType(), val: NumericStats::GetDataUnsafe(stats).max);
390}
391
392Value NumericStats::MinOrNull(const BaseStatistics &stats) {
393 if (!NumericStats::HasMin(stats)) {
394 return Value(stats.GetType());
395 }
396 return NumericStats::Min(stats);
397}
398
399Value NumericStats::MaxOrNull(const BaseStatistics &stats) {
400 if (!NumericStats::HasMax(stats)) {
401 return Value(stats.GetType());
402 }
403 return NumericStats::Max(stats);
404}
405
406void SerializeNumericStatsValue(const LogicalType &type, NumericValueUnion val, bool has_value, FieldWriter &writer) {
407 writer.WriteField<bool>(element: !has_value);
408 if (!has_value) {
409 return;
410 }
411 switch (type.InternalType()) {
412 case PhysicalType::BOOL:
413 writer.WriteField<bool>(element: val.value_.boolean);
414 break;
415 case PhysicalType::INT8:
416 writer.WriteField<int8_t>(element: val.value_.tinyint);
417 break;
418 case PhysicalType::INT16:
419 writer.WriteField<int16_t>(element: val.value_.smallint);
420 break;
421 case PhysicalType::INT32:
422 writer.WriteField<int32_t>(element: val.value_.integer);
423 break;
424 case PhysicalType::INT64:
425 writer.WriteField<int64_t>(element: val.value_.bigint);
426 break;
427 case PhysicalType::UINT8:
428 writer.WriteField<int8_t>(element: val.value_.utinyint);
429 break;
430 case PhysicalType::UINT16:
431 writer.WriteField<int16_t>(element: val.value_.usmallint);
432 break;
433 case PhysicalType::UINT32:
434 writer.WriteField<int32_t>(element: val.value_.uinteger);
435 break;
436 case PhysicalType::UINT64:
437 writer.WriteField<int64_t>(element: val.value_.ubigint);
438 break;
439 case PhysicalType::INT128:
440 writer.WriteField<hugeint_t>(element: val.value_.hugeint);
441 break;
442 case PhysicalType::FLOAT:
443 writer.WriteField<float>(element: val.value_.float_);
444 break;
445 case PhysicalType::DOUBLE:
446 writer.WriteField<double>(element: val.value_.double_);
447 break;
448 default:
449 throw InternalException("Unsupported type for serializing numeric statistics");
450 }
451}
452
453void NumericStats::Serialize(const BaseStatistics &stats, FieldWriter &writer) {
454 auto &numeric_stats = NumericStats::GetDataUnsafe(stats);
455 SerializeNumericStatsValue(type: stats.GetType(), val: numeric_stats.min, has_value: numeric_stats.has_min, writer);
456 SerializeNumericStatsValue(type: stats.GetType(), val: numeric_stats.max, has_value: numeric_stats.has_max, writer);
457}
458
459void DeserializeNumericStatsValue(const LogicalType &type, FieldReader &reader, NumericValueUnion &result,
460 bool &has_stats) {
461 auto is_null = reader.ReadRequired<bool>();
462 if (is_null) {
463 has_stats = false;
464 return;
465 }
466 has_stats = true;
467 switch (type.InternalType()) {
468 case PhysicalType::BOOL:
469 result.value_.boolean = reader.ReadRequired<bool>();
470 break;
471 case PhysicalType::INT8:
472 result.value_.tinyint = reader.ReadRequired<int8_t>();
473 break;
474 case PhysicalType::INT16:
475 result.value_.smallint = reader.ReadRequired<int16_t>();
476 break;
477 case PhysicalType::INT32:
478 result.value_.integer = reader.ReadRequired<int32_t>();
479 break;
480 case PhysicalType::INT64:
481 result.value_.bigint = reader.ReadRequired<int64_t>();
482 break;
483 case PhysicalType::UINT8:
484 result.value_.utinyint = reader.ReadRequired<uint8_t>();
485 break;
486 case PhysicalType::UINT16:
487 result.value_.usmallint = reader.ReadRequired<uint16_t>();
488 break;
489 case PhysicalType::UINT32:
490 result.value_.uinteger = reader.ReadRequired<uint32_t>();
491 break;
492 case PhysicalType::UINT64:
493 result.value_.ubigint = reader.ReadRequired<uint64_t>();
494 break;
495 case PhysicalType::INT128:
496 result.value_.hugeint = reader.ReadRequired<hugeint_t>();
497 break;
498 case PhysicalType::FLOAT:
499 result.value_.float_ = reader.ReadRequired<float>();
500 break;
501 case PhysicalType::DOUBLE:
502 result.value_.double_ = reader.ReadRequired<double>();
503 break;
504 default:
505 throw InternalException("Unsupported type for deserializing numeric statistics");
506 }
507}
508
509BaseStatistics NumericStats::Deserialize(FieldReader &reader, LogicalType type) {
510 BaseStatistics result(std::move(type));
511 auto &numeric_stats = NumericStats::GetDataUnsafe(stats&: result);
512 DeserializeNumericStatsValue(type: result.GetType(), reader, result&: numeric_stats.min, has_stats&: numeric_stats.has_min);
513 DeserializeNumericStatsValue(type: result.GetType(), reader, result&: numeric_stats.max, has_stats&: numeric_stats.has_max);
514 return result;
515}
516
517string NumericStats::ToString(const BaseStatistics &stats) {
518 return StringUtil::Format(fmt_str: "[Min: %s, Max: %s]", params: NumericStats::MinOrNull(stats).ToString(),
519 params: NumericStats::MaxOrNull(stats).ToString());
520}
521
522template <class T>
523void NumericStats::TemplatedVerify(const BaseStatistics &stats, Vector &vector, const SelectionVector &sel,
524 idx_t count) {
525 UnifiedVectorFormat vdata;
526 vector.ToUnifiedFormat(count, data&: vdata);
527
528 auto data = UnifiedVectorFormat::GetData<T>(vdata);
529 auto min_value = NumericStats::MinOrNull(stats);
530 auto max_value = NumericStats::MaxOrNull(stats);
531 for (idx_t i = 0; i < count; i++) {
532 auto idx = sel.get_index(idx: i);
533 auto index = vdata.sel->get_index(idx);
534 if (!vdata.validity.RowIsValid(row_idx: index)) {
535 continue;
536 }
537 if (!min_value.IsNull() && LessThan::Operation(data[index], min_value.GetValueUnsafe<T>())) { // LCOV_EXCL_START
538 throw InternalException("Statistics mismatch: value is smaller than min.\nStatistics: %s\nVector: %s",
539 stats.ToString(), vector.ToString(count));
540 } // LCOV_EXCL_STOP
541 if (!max_value.IsNull() && GreaterThan::Operation(data[index], max_value.GetValueUnsafe<T>())) {
542 throw InternalException("Statistics mismatch: value is bigger than max.\nStatistics: %s\nVector: %s",
543 stats.ToString(), vector.ToString(count));
544 }
545 }
546}
547
548void NumericStats::Verify(const BaseStatistics &stats, Vector &vector, const SelectionVector &sel, idx_t count) {
549 auto &type = stats.GetType();
550 switch (type.InternalType()) {
551 case PhysicalType::BOOL:
552 break;
553 case PhysicalType::INT8:
554 TemplatedVerify<int8_t>(stats, vector, sel, count);
555 break;
556 case PhysicalType::INT16:
557 TemplatedVerify<int16_t>(stats, vector, sel, count);
558 break;
559 case PhysicalType::INT32:
560 TemplatedVerify<int32_t>(stats, vector, sel, count);
561 break;
562 case PhysicalType::INT64:
563 TemplatedVerify<int64_t>(stats, vector, sel, count);
564 break;
565 case PhysicalType::UINT8:
566 TemplatedVerify<uint8_t>(stats, vector, sel, count);
567 break;
568 case PhysicalType::UINT16:
569 TemplatedVerify<uint16_t>(stats, vector, sel, count);
570 break;
571 case PhysicalType::UINT32:
572 TemplatedVerify<uint32_t>(stats, vector, sel, count);
573 break;
574 case PhysicalType::UINT64:
575 TemplatedVerify<uint64_t>(stats, vector, sel, count);
576 break;
577 case PhysicalType::INT128:
578 TemplatedVerify<hugeint_t>(stats, vector, sel, count);
579 break;
580 case PhysicalType::FLOAT:
581 TemplatedVerify<float>(stats, vector, sel, count);
582 break;
583 case PhysicalType::DOUBLE:
584 TemplatedVerify<double>(stats, vector, sel, count);
585 break;
586 default:
587 throw InternalException("Unsupported type %s for numeric statistics verify", type.ToString());
588 }
589}
590
591} // namespace duckdb
592