2828#include <utils/typcache.h>
2929
3030#include <compat/compat.h>
31+ #include "estimate.h"
3132#include "gapfill.h"
3233#include "gapfill_internal.h"
3334#include "interpolate.h"
3435#include "locf.h"
3536#include "time_bucket.h"
3637#include <annotations.h>
3738
38- typedef enum GapFillBoundary
39- {
40- GAPFILL_START ,
41- GAPFILL_END ,
42- } GapFillBoundary ;
43-
4439typedef union GapFillColumnStateUnion
4540{
4641 GapFillColumnState * base ;
@@ -175,8 +170,8 @@ get_timezone_arg(GapFillState *state)
175170 return lthird (state -> args );
176171}
177172
178- static inline int64
179- gapfill_period_get_internal (Oid timetype , Oid argtype , Datum arg , Interval * * interval )
173+ int64
174+ gapfill_bucket_width_get_internal (Oid timetype , Oid argtype , Datum arg , Interval * * interval )
180175{
181176 switch (timetype )
182177 {
@@ -240,8 +235,8 @@ gapfill_state_create(CustomScan *cscan)
240235 return (Node * ) state ;
241236}
242237
243- static bool
244- is_const_null (Expr * expr )
238+ bool
239+ gapfill_is_const_null (Expr * expr )
245240{
246241 return IsA (expr , Const ) && castNode (Const , expr )-> constisnull ;
247242}
@@ -288,7 +283,7 @@ var_equal(Var *v1, Var *v2)
288283}
289284
290285static bool
291- is_simple_expr_walker (Node * node , void * context )
286+ gapfill_is_simple_expr_walker (Node * node , void * context )
292287{
293288 if (node == NULL )
294289 {
@@ -328,22 +323,22 @@ is_simple_expr_walker(Node *node, void *context)
328323 default :
329324 return true;
330325 }
331- return expression_tree_walker (node , is_simple_expr_walker , context );
326+ return expression_tree_walker (node , gapfill_is_simple_expr_walker , context );
332327}
333328
334329/*
335330 * check if expression is simple expression and contains only simple
336331 * subexpressions
337332 */
338- static bool
339- is_simple_expr (Expr * node )
333+ bool
334+ gapfill_is_simple_expr (Expr * node )
340335{
341336 /*
342337 * since expression_tree_walker does early exit on true and we use that to
343338 * skip processing on first non-simple expression we invert return value
344339 * from expression_tree_walker here
345340 */
346- return !is_simple_expr_walker ((Node * ) node , NULL );
341+ return !gapfill_is_simple_expr_walker ((Node * ) node , NULL );
347342}
348343
349344/*
@@ -360,7 +355,7 @@ align_with_time_bucket(GapFillState *state, Expr *expr)
360355 Datum value ;
361356 bool isnull ;
362357
363- if (!is_simple_expr (expr ))
358+ if (!gapfill_is_simple_expr (expr ))
364359 {
365360 ereport (ERROR ,
366361 (errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
@@ -399,39 +394,40 @@ align_with_time_bucket(GapFillState *state, Expr *expr)
399394 return gapfill_datum_get_internal (value , state -> gapfill_typid );
400395}
401396
402- static int64
403- get_boundary_expr_value (GapFillState * state , GapFillBoundary boundary , Expr * expr )
397+ /* Can be called either with PlannerInfo to estimate boundary value at planning time
398+ * or with GapFillState to evaluate boundary value at execution time.
399+ */
400+ static Datum
401+ get_boundary_expr_value (GapFillArgEvalContext * gapfill_ctx , GapFillBoundary boundary , Expr * expr ,
402+ bool * isnull )
404403{
405- Datum arg_value ;
406- bool isnull ;
407-
404+ * isnull = false;
405+ Datum arg_value = 0 ;
408406 /*
409407 * add an explicit cast here if types do not match
410408 */
411- if (exprType ((Node * ) expr ) != state -> gapfill_typid )
409+ Oid gapfill_typid = gapfill_ctx -> typid ;
410+ if (exprType ((Node * ) expr ) != gapfill_typid )
412411 {
413- Oid cast_oid = get_cast_func (exprType ((Node * ) expr ), state -> gapfill_typid );
414-
415- expr = (Expr * ) makeFuncExpr (cast_oid ,
416- state -> gapfill_typid ,
417- list_make1 (expr ),
418- InvalidOid ,
419- InvalidOid ,
420- 0 );
421- }
412+ Oid cast_oid = get_cast_func (exprType ((Node * ) expr ), gapfill_typid );
422413
423- arg_value = gapfill_exec_expr (state , state -> scanslot , expr , & isnull );
414+ expr = (Expr * )
415+ makeFuncExpr (cast_oid , gapfill_typid , list_make1 (expr ), InvalidOid , InvalidOid , 0 );
416+ }
424417
425- if (isnull )
418+ if (gapfill_ctx -> state )
426419 {
427- ereport (ERROR ,
428- (errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
429- errmsg ("invalid time_bucket_gapfill argument: %s cannot be NULL" ,
430- boundary == GAPFILL_START ? "start" : "finish" ),
431- errhint ("Specify start and finish as arguments or in the WHERE clause." )));
420+ GapFillState * state = (GapFillState * ) gapfill_ctx -> state ;
421+ arg_value = gapfill_exec_expr (state , state -> scanslot , expr , isnull );
422+ }
423+ else
424+ {
425+ /* Estimating start/end boundary at planning time:
426+ * bail out with no error if cannot estimate. */
427+ arg_value = gapfill_estimate_arg (gapfill_ctx , (Node * ) expr , isnull );
432428 }
433429
434- return gapfill_datum_get_internal ( arg_value , state -> gapfill_typid ) ;
430+ return arg_value ;
435431}
436432
437433typedef struct CollectBoundaryContext
@@ -543,15 +539,20 @@ collect_boundary_expressions(Node *node, Var *ts_var)
543539 return context .quals ;
544540}
545541
546- static int64
547- infer_gapfill_boundary (GapFillState * state , GapFillBoundary boundary )
542+ /* Generic method to collect WHERE quals and obtain suitable start/end gapfill boundaries.
543+ * Can be called from the planner with PlannerInfo to estimate boundaries
544+ * or with GapFillState to evaluate boundaries at execution time.
545+ */
546+ int64
547+ infer_gapfill_boundary (GapFillArgEvalContext * gapfill_ctx , GapFillBoundary boundary )
548548{
549- CustomScan * cscan = castNode (CustomScan , state -> csstate .ss .ps .plan );
550- FuncExpr * func = list_nth (cscan -> custom_private , GFP_GapfillFunc );
551- FromExpr * jt = list_nth (cscan -> custom_private , GFP_JoinTree );
549+ Oid gapfill_typid = gapfill_ctx -> typid ;
550+ List * args = gapfill_ctx -> args ;
551+ FromExpr * jt = gapfill_ctx -> jt ;
552+
552553 ListCell * lc ;
553554 Var * ts_var ;
554- TypeCacheEntry * tce = lookup_type_cache (state -> gapfill_typid , TYPECACHE_BTREE_OPFAMILY );
555+ TypeCacheEntry * tce = lookup_type_cache (gapfill_typid , TYPECACHE_BTREE_OPFAMILY );
555556 int strategy ;
556557 Oid lefttype , righttype ;
557558 List * quals ;
@@ -563,7 +564,7 @@ infer_gapfill_boundary(GapFillState *state, GapFillBoundary boundary)
563564 * if the second argument to time_bucket_gapfill is not a column reference
564565 * we cannot match WHERE clause to the time column
565566 */
566- if (!IsA (lsecond (func -> args ), Var ))
567+ if (!IsA (lsecond (args ), Var ))
567568 {
568569 ereport (ERROR ,
569570 (errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
@@ -572,7 +573,7 @@ infer_gapfill_boundary(GapFillState *state, GapFillBoundary boundary)
572573 errhint ("Specify start and finish as arguments or in the WHERE clause." )));
573574 }
574575
575- ts_var = castNode (Var , lsecond (func -> args ));
576+ ts_var = castNode (Var , lsecond (args ));
576577
577578 quals = collect_boundary_expressions ((Node * ) jt , ts_var );
578579
@@ -613,7 +614,7 @@ infer_gapfill_boundary(GapFillState *state, GapFillBoundary boundary)
613614 * at this stage and Vars will not work either because we execute in
614615 * separate execution context
615616 */
616- if (!is_simple_expr (expr ) || !var_equal (ts_var , var ))
617+ if (!gapfill_is_simple_expr (expr ) || !var_equal (ts_var , var ))
617618 {
618619 continue ;
619620 }
@@ -631,7 +632,35 @@ infer_gapfill_boundary(GapFillState *state, GapFillBoundary boundary)
631632 continue ;
632633 }
633634
634- value = get_boundary_expr_value (state , boundary , expr );
635+ bool isnull ;
636+ Datum arg_value = get_boundary_expr_value (gapfill_ctx , boundary , expr , & isnull );
637+ /* If boundary is NULL bail out on the rest of the quals */
638+ if (isnull )
639+ {
640+ /* bail out if during execution */
641+ if (gapfill_ctx -> state )
642+ {
643+ ereport (ERROR ,
644+ (errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
645+ errmsg ("invalid time_bucket_gapfill argument: %s cannot be NULL" ,
646+ boundary == GAPFILL_START ? "start" : "finish" ),
647+ errhint ("Specify start and finish as arguments or in the WHERE clause." )));
648+ }
649+ /* don't estimate the rest of the boundaries but allow planning */
650+ else
651+ {
652+ Assert (gapfill_ctx -> root && gapfill_ctx -> estimate_failed );
653+ return INVALID_ESTIMATE ;
654+ }
655+ }
656+ /* Cannot estimate this boundary at planning time: reset and try the next one */
657+ if (gapfill_ctx -> root && gapfill_ctx -> estimate_failed )
658+ {
659+ gapfill_ctx -> estimate_failed = false;
660+ continue ;
661+ }
662+
663+ value = gapfill_datum_get_internal (arg_value , gapfill_ctx -> typid );
635664
636665 /*
637666 * if the boundary expression operator does not match the operator
@@ -669,6 +698,15 @@ infer_gapfill_boundary(GapFillState *state, GapFillBoundary boundary)
669698 return boundary_value ;
670699 }
671700
701+ /* Could not estimate any boundaries at planning time */
702+ if (gapfill_ctx -> root )
703+ {
704+ gapfill_ctx -> estimate_failed = true;
705+ return INVALID_ESTIMATE ;
706+ }
707+
708+ /* Cannot evaluate boundaries at execution time: bail out */
709+ Assert (gapfill_ctx -> state );
672710 ereport (ERROR ,
673711 (errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
674712 errmsg ("missing time_bucket_gapfill argument: could not infer %s from WHERE clause" ,
@@ -677,7 +715,7 @@ infer_gapfill_boundary(GapFillState *state, GapFillBoundary boundary)
677715 pg_unreachable ();
678716}
679717
680- static Const *
718+ Const *
681719make_const_value_for_gapfill_internal (Oid typid , int64 value )
682720{
683721 TypeCacheEntry * tce = lookup_type_cache (typid , 0 );
@@ -807,7 +845,7 @@ gapfill_initialize_arguments(GapFillState *state)
807845 Datum arg_value ;
808846
809847 /* bucket_width */
810- if (!is_simple_expr (linitial (state -> args )))
848+ if (!gapfill_is_simple_expr (linitial (state -> args )))
811849 {
812850 ereport (ERROR ,
813851 (errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
@@ -823,10 +861,10 @@ gapfill_initialize_arguments(GapFillState *state)
823861 errmsg ("invalid time_bucket_gapfill argument: bucket_width cannot be NULL" )));
824862 }
825863
826- state -> gapfill_period = gapfill_period_get_internal (state -> gapfill_typid ,
827- exprType (linitial (state -> args )),
828- arg_value ,
829- & state -> gapfill_interval );
864+ state -> gapfill_period = gapfill_bucket_width_get_internal (state -> gapfill_typid ,
865+ exprType (linitial (state -> args )),
866+ arg_value ,
867+ & state -> gapfill_interval );
830868
831869 /*
832870 * this would error when trying to align start and stop to bucket_width as well below
@@ -844,9 +882,19 @@ gapfill_initialize_arguments(GapFillState *state)
844882 * check if gapfill start was left out so we have to infer from WHERE
845883 * clause
846884 */
847- if (is_const_null (get_start_arg (state )))
885+ CustomScan * cscan = castNode (CustomScan , state -> csstate .ss .ps .plan );
886+ GapFillArgEvalContext gapfill_exec_ctx = {
887+ .root = NULL ,
888+ .state = (CustomScanState * ) state ,
889+ .typid = state -> gapfill_typid ,
890+ .args = state -> args ,
891+ .jt = list_nth (cscan -> custom_private , GFP_JoinTree ),
892+ .estimate_failed = false,
893+ };
894+
895+ if (gapfill_is_const_null (get_start_arg (state )))
848896 {
849- int64 start = infer_gapfill_boundary (state , GAPFILL_START );
897+ int64 start = infer_gapfill_boundary (& gapfill_exec_ctx , GAPFILL_START );
850898 Const * expr = make_const_value_for_gapfill_internal (state -> gapfill_typid , start );
851899
852900 state -> gapfill_start = align_with_time_bucket (state , (Expr * ) expr );
@@ -863,13 +911,13 @@ gapfill_initialize_arguments(GapFillState *state)
863911 state -> next_offset = state -> gapfill_interval ;
864912
865913 /* gap fill end */
866- if (is_const_null (get_finish_arg (state )))
914+ if (gapfill_is_const_null (get_finish_arg (state )))
867915 {
868- state -> gapfill_end = infer_gapfill_boundary (state , GAPFILL_END );
916+ state -> gapfill_end = infer_gapfill_boundary (& gapfill_exec_ctx , GAPFILL_END );
869917 }
870918 else
871919 {
872- if (!is_simple_expr (get_finish_arg (state )))
920+ if (!gapfill_is_simple_expr (get_finish_arg (state )))
873921 {
874922 ereport (ERROR ,
875923 (errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
0 commit comments