|
24 | 24 | #include "pg_lake/planner/query_pushdown.h" |
25 | 25 | #include "pg_lake/util/numeric.h" |
26 | 26 | #include "pg_lake/partitioning/partition_by_parser.h" |
| 27 | +#include "pg_lake/pgduck/map.h" |
27 | 28 | #include "pg_lake/pgduck/numeric.h" |
28 | 29 | #include "pg_lake/util/rel_utils.h" |
29 | 30 | #include "nodes/makefuncs.h" |
|
32 | 33 | #include "parser/parsetree.h" |
33 | 34 | #include "utils/rel.h" |
34 | 35 | #include "utils/lsyscache.h" |
| 36 | +#include "utils/typcache.h" |
35 | 37 |
|
36 | 38 |
|
37 | 39 | static RangeTblEntry *GetSelectRteFromInsertSelect(Query *query); |
38 | 40 | static RangeTblEntry *GetInsertRteFromInsertSelect(Query *query); |
| 41 | +static bool TypeContainsUnsuitableForPushdown(Oid typeId, int32 typmod, CopyDataFormat sourceFormat); |
39 | 42 |
|
40 | 43 | /* pg_lake_table.enable_insert_select_pushdown setting */ |
41 | 44 | bool EnableInsertSelectPushdown = true; |
@@ -377,6 +380,119 @@ RelationSuitableForPushdown(Relation relation, bool allowDefaultConsts) |
377 | 380 | } |
378 | 381 |
|
379 | 382 |
|
| 383 | +/* |
| 384 | + * TypeContainsUnsuitableForPushdown recursively checks whether the given type |
| 385 | + * (or any type nested within it) is unsuitable for pushdown. |
| 386 | + * |
| 387 | + * Returns true if the type is unsuitable (i.e., pushdown should be blocked). |
| 388 | + */ |
| 389 | +static bool |
| 390 | +TypeContainsUnsuitableForPushdown(Oid typeId, int32 typmod, CopyDataFormat sourceFormat) |
| 391 | +{ |
| 392 | + /* |
| 393 | + * The current pushdown implementation does not convert geometry to the |
| 394 | + * appropriate storage format yet. |
| 395 | + */ |
| 396 | + if (IsGeometryTypeId(typeId)) |
| 397 | + { |
| 398 | + ereport(DEBUG4, |
| 399 | + (errmsg("Geometry type is not pushdownable"))); |
| 400 | + return true; |
| 401 | + } |
| 402 | + |
| 403 | + /* |
| 404 | + * Interval is stored as struct(months, days, microseconds) in Iceberg. |
| 405 | + * PGDuckSerialize handles the conversion, but during pushdown DuckDB |
| 406 | + * writes directly—bypassing that conversion—so we must block it. |
| 407 | + */ |
| 408 | + if (typeId == INTERVALOID && sourceFormat == DATA_FORMAT_ICEBERG) |
| 409 | + { |
| 410 | + ereport(DEBUG4, |
| 411 | + (errmsg("Interval type is not pushdownable for Iceberg"))); |
| 412 | + |
| 413 | + return true; |
| 414 | + } |
| 415 | + |
| 416 | + /* |
| 417 | + * Map types are domains over arrays of key-value composites. Check the |
| 418 | + * key and value types rather than rejecting the map outright as a domain. |
| 419 | + * This must come before the generic domain check below. |
| 420 | + */ |
| 421 | + if (IsMapTypeOid(typeId)) |
| 422 | + { |
| 423 | + PGType keyType = GetMapKeyType(typeId); |
| 424 | + PGType valType = GetMapValueType(typeId); |
| 425 | + |
| 426 | + return TypeContainsUnsuitableForPushdown(keyType.postgresTypeOid, |
| 427 | + keyType.postgresTypeMod, sourceFormat) || |
| 428 | + TypeContainsUnsuitableForPushdown(valType.postgresTypeOid, |
| 429 | + valType.postgresTypeMod, sourceFormat); |
| 430 | + } |
| 431 | + |
| 432 | + /* |
| 433 | + * Domains may have constraints which are not checked on the DuckDB side. |
| 434 | + */ |
| 435 | + char typeType = get_typtype(typeId); |
| 436 | + |
| 437 | + if (typeType == TYPTYPE_DOMAIN) |
| 438 | + { |
| 439 | + ereport(DEBUG4, |
| 440 | + (errmsg("Domain type is not pushdownable"))); |
| 441 | + return true; |
| 442 | + } |
| 443 | + |
| 444 | + if (typeId == NUMERICOID) |
| 445 | + { |
| 446 | + /* may fail if unbounded numeric exceeds duckdb limits (38,38) */ |
| 447 | + if (typmod == -1) |
| 448 | + return false; |
| 449 | + |
| 450 | + int precision = numeric_typmod_precision(typmod); |
| 451 | + int scale = numeric_typmod_scale(typmod); |
| 452 | + |
| 453 | + if (!CanPushdownNumericToDuckdb(precision, scale)) |
| 454 | + { |
| 455 | + ereport(DEBUG4, |
| 456 | + (errmsg("Numeric type with precision(%d) and scale(%d) " |
| 457 | + "is not pushdownable", precision, scale))); |
| 458 | + return true; |
| 459 | + } |
| 460 | + } |
| 461 | + |
| 462 | + /* Recurse into array element type */ |
| 463 | + if (type_is_array(typeId)) |
| 464 | + { |
| 465 | + Oid elemType = get_element_type(typeId); |
| 466 | + |
| 467 | + return TypeContainsUnsuitableForPushdown(elemType, typmod, sourceFormat); |
| 468 | + } |
| 469 | + |
| 470 | + /* Recurse into composite type fields */ |
| 471 | + if (typeType == TYPTYPE_COMPOSITE) |
| 472 | + { |
| 473 | + TupleDesc tupdesc = lookup_rowtype_tupdesc(typeId, typmod); |
| 474 | + |
| 475 | + for (int i = 0; i < tupdesc->natts; i++) |
| 476 | + { |
| 477 | + Form_pg_attribute attr = TupleDescAttr(tupdesc, i); |
| 478 | + |
| 479 | + if (attr->attisdropped) |
| 480 | + continue; |
| 481 | + |
| 482 | + if (TypeContainsUnsuitableForPushdown(attr->atttypid, attr->atttypmod, sourceFormat)) |
| 483 | + { |
| 484 | + ReleaseTupleDesc(tupdesc); |
| 485 | + return true; |
| 486 | + } |
| 487 | + } |
| 488 | + |
| 489 | + ReleaseTupleDesc(tupdesc); |
| 490 | + } |
| 491 | + |
| 492 | + return false; |
| 493 | +} |
| 494 | + |
| 495 | + |
380 | 496 | /* |
381 | 497 | * RelationColumnsSuitableForPushdown checks whether the columns of a relation |
382 | 498 | * are suitable for pushdown. |
@@ -411,69 +527,8 @@ RelationColumnsSuitableForPushdown(Relation relation, CopyDataFormat sourceForma |
411 | 527 | } |
412 | 528 | } |
413 | 529 |
|
414 | | - /* |
415 | | - * The current pushdown implementation does not convert geometry to |
416 | | - * the appropriate storage format yet. |
417 | | - */ |
418 | | - if (IsGeometryTypeId(typeId)) |
419 | | - { |
420 | | - ereport(DEBUG4, |
421 | | - (errmsg("Geometry type is not pushdownable"))); |
422 | | - |
| 530 | + if (TypeContainsUnsuitableForPushdown(typeId, column->atttypmod, sourceFormat)) |
423 | 531 | return false; |
424 | | - } |
425 | | - |
426 | | - /* |
427 | | - * For Iceberg, intervals are stored as STRUCT(months, days, |
428 | | - * microseconds). The pushdown path sends INTERVAL directly to DuckDB, |
429 | | - * which cannot apply nested field_ids to a non-struct column. Fall |
430 | | - * back to the row-by-row path which serializes intervals as structs. |
431 | | - */ |
432 | | - Oid elemTypeId = get_element_type(typeId); |
433 | | - |
434 | | - if ((typeId == INTERVALOID || elemTypeId == INTERVALOID) && |
435 | | - sourceFormat == DATA_FORMAT_ICEBERG) |
436 | | - { |
437 | | - ereport(DEBUG4, |
438 | | - (errmsg("Interval type is not pushdownable for Iceberg"))); |
439 | | - |
440 | | - return false; |
441 | | - } |
442 | | - |
443 | | - if (typeId == NUMERICOID || typeId == NUMERICARRAYOID) |
444 | | - { |
445 | | - /* todo: handle numeric field in composite type */ |
446 | | - int typmod = column->atttypmod; |
447 | | - |
448 | | - /* may fail if unbounded numeric exceeds duckdb limits (38,38) */ |
449 | | - if (typmod == -1) |
450 | | - continue; |
451 | | - |
452 | | - int precision = numeric_typmod_precision(typmod); |
453 | | - int scale = numeric_typmod_scale(typmod); |
454 | | - |
455 | | - if (!CanPushdownNumericToDuckdb(precision, scale)) |
456 | | - { |
457 | | - ereport(DEBUG4, |
458 | | - (errmsg("Numeric type with precision(%d) and scale(%d) " |
459 | | - "is not pushdownable", precision, scale))); |
460 | | - |
461 | | - return false; |
462 | | - } |
463 | | - } |
464 | | - |
465 | | - /* |
466 | | - * Domains may have constraints which are not checked on the DuckDB |
467 | | - * side. |
468 | | - */ |
469 | | - char typeType = get_typtype(typeId); |
470 | | - |
471 | | - if (typeType == TYPTYPE_DOMAIN) |
472 | | - { |
473 | | - ereport(DEBUG4, |
474 | | - (errmsg("Domain type is not pushdownable"))); |
475 | | - return false; |
476 | | - } |
477 | 532 | } |
478 | 533 |
|
479 | 534 | return true; |
|
0 commit comments