Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support all time_bucket variants in plan time chunk exclusion #6552

Merged
merged 1 commit into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 91 additions & 59 deletions src/planner/expand_hypertable.c
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,52 @@
constraint->inputcollid);
}

static bool
extract_opexpr_parts(Expr *node, OpExpr **op, FuncExpr **time_bucket, Expr **value, Oid *opno)
{
if (!IsA(node, OpExpr))
{
return false;
}

*op = castNode(OpExpr, node);
if (list_length((*op)->args) != 2)
{
return false;
}

Expr *left = linitial((*op)->args);
Expr *right = lsecond((*op)->args);

if (IsA(left, FuncExpr) && IsA(right, Const))
{
*time_bucket = castNode(FuncExpr, left);
*value = right;
*opno = (*op)->opno;
}
else if (IsA(right, FuncExpr))
{
*time_bucket = castNode(FuncExpr, right);
*value = left;
*opno = get_commutator((*op)->opno);

if (!OidIsValid(opno))
return false;

Check warning on line 386 in src/planner/expand_hypertable.c

View check run for this annotation

Codecov / codecov/patch

src/planner/expand_hypertable.c#L386

Added line #L386 was not covered by tests
}
else
{
return false;
}

if (!is_time_bucket_function((Expr *) *time_bucket) || !IsA(*value, Const) ||
castNode(Const, *value)->constisnull)
{
return false;
}

return true;
}

/*
* Transform time_bucket calls of the following form in WHERE clause:
*
Expand Down Expand Up @@ -390,79 +436,53 @@
Expr *
ts_transform_time_bucket_comparison(Expr *node)
{
if (!IsA(node, OpExpr))
{
return NULL;
}
FuncExpr *time_bucket;
Expr *value;
OpExpr *op;
Oid opno;

OpExpr *op = castNode(OpExpr, node);
if (list_length(op->args) != 2)
if (!extract_opexpr_parts(node, &op, &time_bucket, &value, &opno))
{
return NULL;
}

Expr *left = linitial(op->args);
Expr *right = lsecond(op->args);
Const *width = linitial(time_bucket->args);

FuncExpr *time_bucket = NULL;
Expr *value = NULL;
if (IsA(left, FuncExpr) && IsA(right, Const))
{
time_bucket = castNode(FuncExpr, left);
value = right;
}
else if (IsA(right, FuncExpr))
{
time_bucket = castNode(FuncExpr, right);
value = left;
}
else
{
if (!IsA(width, Const) || width->constisnull)
return NULL;
}

if (list_length(time_bucket->args) != 2)
{
/* 3 or more args should have Const 3rd arg */
if (list_length(time_bucket->args) > 2 && !IsA(lthird(time_bucket->args), Const))
return NULL;
}

if (!is_time_bucket_function((Expr *) time_bucket))
{
/* 5 args variants should have Const 4rd and 5th arg */
if (list_length(time_bucket->args) == 5 &&
(!IsA(lfourth(time_bucket->args), Const) || !IsA(lfifth(time_bucket->args), Const)))
return NULL;
}

Const *width = linitial(time_bucket->args);
Oid opno = op->opno;
Assert(list_length(time_bucket->args) == 2 || list_length(time_bucket->args) == 3 ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see where a 4 arg variant would bail out above?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is only 2,3 and 5 args versions

list_length(time_bucket->args) == 5);

TypeCacheEntry *tce;
int strategy;

if (list_length(time_bucket->args) != 2 || !IsA(value, Const) || !IsA(width, Const))
return NULL;

/*
* if time_bucket call is on wrong side we switch operator
*/
if (IsA(right, FuncExpr))
{
opno = get_commutator(op->opno);

if (!OidIsValid(opno))
return NULL;
}

tce = lookup_type_cache(exprType((Node *) time_bucket), TYPECACHE_BTREE_OPFAMILY);
strategy = get_op_opfamily_strategy(opno, tce->btree_opf);

if (strategy == BTGreaterStrategyNumber || strategy == BTGreaterEqualStrategyNumber)
{
/* column > value */
/* Since time_bucket will always shift the input to the left this
* transformation is always safe even in the presence of offset variants.
*
* column > value
*/
op = copyObject(op);
op->args = list_make2(lsecond(time_bucket->args), value);

/*
* if we switched operator we need to adjust OpExpr as well
*/
if (IsA(right, FuncExpr))
if (op->opno != opno)
{
op->opno = opno;
op->opfuncid = InvalidOid;
Expand All @@ -477,14 +497,15 @@
Datum datum;
int64 integralValue, integralWidth;

if (castNode(Const, value)->constisnull || width->constisnull)
return NULL;

switch (tce->type_id)
{
case INT2OID:
case INT4OID:
case INT8OID:
/* We can support the offset variants of time_bucket as the
* amount of shifting they do is never bigger than the bucketing
* width.
*/
integralValue = const_datum_get_int(castNode(Const, value));
integralWidth = const_datum_get_int(width);

Expand All @@ -493,10 +514,11 @@

/*
* When the time_bucket constraint matches the start of the bucket
* and we have a less than constraint we can skip adding the
* full bucket.
* and we have a less than constraint and no offset we can skip
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT:

Suggested change
* and we have a less than constraint and no offset we can skip
* and we have a less than constraint and no offset we can skip

* adding the full bucket.
*/
if (strategy == BTLessStrategyNumber && integralValue % integralWidth == 0)
if (strategy == BTLessStrategyNumber && list_length(time_bucket->args) == 2 &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we try to handle the offset % period = 0 case here too? This is just an optimization? In that case probably not worth handling. But if there are correctness implications...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just an optimization, with the additional args it gets a bit more complicated

integralValue % integralWidth == 0)
datum = int_get_datum(integralValue, tce->type_id);
else
datum = int_get_datum(integralValue + integralWidth, tce->type_id);
Expand All @@ -512,6 +534,10 @@

case DATEOID:
{
/* We can support the offset/origin variants of time_bucket
* as the amount of shifting they do is never bigger than the
* bucketing width.
*/
Assert(width->consttype == INTERVALOID);
Interval *interval = DatumGetIntervalP(width->constvalue);

Expand All @@ -534,10 +560,11 @@

/*
* When the time_bucket constraint matches the start of the bucket
* and we have a less than constraint we can skip adding the
* full bucket.
* and we have a less than constraint and no offset or origin we can
* skip adding the full bucket.
*/
if (strategy == BTLessStrategyNumber && integralValue % integralWidth == 0)
if (strategy == BTLessStrategyNumber && list_length(time_bucket->args) == 2 &&
integralValue % integralWidth == 0)
datum = DateADTGetDatum(integralValue);
else
datum = DateADTGetDatum(integralValue + integralWidth);
Expand All @@ -555,6 +582,10 @@
case TIMESTAMPOID:
case TIMESTAMPTZOID:
{
/* We can support the offset/origin/timezone variants of time_bucket
* as the amount of shifting they do is never bigger than the
* bucketing width.
*/
Assert(width->consttype == INTERVALOID);
Interval *interval = DatumGetIntervalP(width->constvalue);

Expand Down Expand Up @@ -587,10 +618,11 @@

/*
* When the time_bucket constraint matches the start of the bucket
* and we have a less than constraint we can skip adding the
* full bucket.
* and we have a less than constraint and no other modifying arguments
* we can skip adding the full bucket.
*/
if (strategy == BTLessStrategyNumber && integralValue % integralWidth == 0)
if (strategy == BTLessStrategyNumber && list_length(time_bucket->args) == 2 &&
integralValue % integralWidth == 0)
datum = int_get_datum(integralValue, tce->type_id);
else
datum = int_get_datum(integralValue + integralWidth, tce->type_id);
Expand Down
Loading
Loading