Skip to content

Commit 62cb274

Browse files
Fix nested insert select pushdown (#241)
Signed-off-by: Aykut Bozkurt <aykut.bozkurt@snowflake.com>
1 parent e8555d2 commit 62cb274

4 files changed

Lines changed: 561 additions & 62 deletions

File tree

pg_lake_table/src/planner/insert_select.c

Lines changed: 117 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "pg_lake/planner/query_pushdown.h"
2525
#include "pg_lake/util/numeric.h"
2626
#include "pg_lake/partitioning/partition_by_parser.h"
27+
#include "pg_lake/pgduck/map.h"
2728
#include "pg_lake/pgduck/numeric.h"
2829
#include "pg_lake/util/rel_utils.h"
2930
#include "nodes/makefuncs.h"
@@ -32,10 +33,12 @@
3233
#include "parser/parsetree.h"
3334
#include "utils/rel.h"
3435
#include "utils/lsyscache.h"
36+
#include "utils/typcache.h"
3537

3638

3739
static RangeTblEntry *GetSelectRteFromInsertSelect(Query *query);
3840
static RangeTblEntry *GetInsertRteFromInsertSelect(Query *query);
41+
static bool TypeContainsUnsuitableForPushdown(Oid typeId, int32 typmod, CopyDataFormat sourceFormat);
3942

4043
/* pg_lake_table.enable_insert_select_pushdown setting */
4144
bool EnableInsertSelectPushdown = true;
@@ -377,6 +380,119 @@ RelationSuitableForPushdown(Relation relation, bool allowDefaultConsts)
377380
}
378381

379382

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+
380496
/*
381497
* RelationColumnsSuitableForPushdown checks whether the columns of a relation
382498
* are suitable for pushdown.
@@ -411,69 +527,8 @@ RelationColumnsSuitableForPushdown(Relation relation, CopyDataFormat sourceForma
411527
}
412528
}
413529

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))
423531
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-
}
477532
}
478533

479534
return true;

0 commit comments

Comments
 (0)