mirror of
https://github.com/timescale/timescaledb.git
synced 2025-05-15 18:13:18 +08:00
Improve evaluation of stable functions such as now() on access node
We have to evaluate the stable functions on the access node and not on data nodes, where the result might differ, to get consistent results of the query. Before this change, we used to substitute 'now()' with textual value of the current timestamp in the query text before sending it to the data node. This is not sufficient if now() is further wrapped in a whitelisted (for sending to data nodes) stable function such as '::timestamp'. Use the standard expression evaluation machinery to also handle such cases. Fixes #3598
This commit is contained in:
parent
02f273dfe3
commit
fb3613fe04
@ -12,10 +12,13 @@ accidentally triggering the load of a previous DB version.**
|
||||
* #3661 Fix SkipScan path generation with constant DISTINCT column
|
||||
* #3708 Fix crash in get_aggsplit
|
||||
|
||||
**Improvements**
|
||||
* #3598 Improve evaluation of stable functions such as now() on access node
|
||||
|
||||
**Thanks**
|
||||
* @binakot and @sebvett for reporting an issue with DISTINCT queries
|
||||
* @hardikm10, @DavidPavlicek and @pafiti for reporting bugs on TRUNCATE
|
||||
* @phemmer for reporting an issue with aggregate queries on multinode
|
||||
* @phemmer for reporting the issues on multinode with aggregate queries and evaluation of now()
|
||||
|
||||
## 2.4.2 (2021-09-21)
|
||||
|
||||
|
@ -435,4 +435,12 @@ get_reindex_options(ReindexStmt *stmt)
|
||||
#define raw_parser_compat(cmd) raw_parser(cmd, RAW_PARSE_DEFAULT)
|
||||
#endif
|
||||
|
||||
#if PG14_LT
|
||||
#define expand_function_arguments_compat(args, result_type, func_tuple) \
|
||||
expand_function_arguments(args, result_type, func_tuple)
|
||||
#else
|
||||
#define expand_function_arguments_compat(args, result_type, func_tuple) \
|
||||
expand_function_arguments(args, false, result_type, func_tuple)
|
||||
#endif
|
||||
|
||||
#endif /* TIMESCALEDB_COMPAT_H */
|
||||
|
@ -96,15 +96,13 @@ typedef struct foreign_glob_cxt
|
||||
*/
|
||||
typedef struct deparse_expr_cxt
|
||||
{
|
||||
PlannerInfo *root; /* global planner state */
|
||||
RelOptInfo *foreignrel; /* the foreign relation we are planning for */
|
||||
RelOptInfo *scanrel; /* the underlying scan relation. Same as
|
||||
* foreignrel, when that represents a join or
|
||||
* a base relation. */
|
||||
StringInfo buf; /* output buffer to append to */
|
||||
List **params_list; /* exprs that will become remote Params */
|
||||
List **current_time_idx; /* locations in the sql output that need to
|
||||
* have the current time appended */
|
||||
PlannerInfo *root; /* global planner state */
|
||||
RelOptInfo *foreignrel; /* the foreign relation we are planning for */
|
||||
RelOptInfo *scanrel; /* the underlying scan relation. Same as
|
||||
* foreignrel, when that represents a join or
|
||||
* a base relation. */
|
||||
StringInfo buf; /* output buffer to append to */
|
||||
List **params_list; /* exprs that will become remote Params */
|
||||
DataNodeChunkAssignment *sca;
|
||||
} deparse_expr_cxt;
|
||||
|
||||
@ -732,8 +730,8 @@ build_tlist_to_deparse(RelOptInfo *foreignrel)
|
||||
* For a base relation fpinfo->attrs_used is used to construct SELECT clause,
|
||||
* hence the tlist is ignored for a base relation.
|
||||
*
|
||||
* remote_conds is the list of conditions to be deparsed into the WHERE clause
|
||||
* (or, in the case of upper relations, into the HAVING clause).
|
||||
* remote_where is the list of conditions to be deparsed into the WHERE clause,
|
||||
* and remote_having into the HAVING clause (this is useful for upper relations).
|
||||
*
|
||||
* If params_list is not NULL, it receives a list of Params and other-relation
|
||||
* Vars used in the clauses; these values must be transmitted to the data
|
||||
@ -751,13 +749,11 @@ build_tlist_to_deparse(RelOptInfo *foreignrel)
|
||||
*/
|
||||
void
|
||||
deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel, List *tlist,
|
||||
List *remote_conds, List *pathkeys, bool is_subquery,
|
||||
List **retrieved_attrs, List **params_list, DataNodeChunkAssignment *sca,
|
||||
List **current_time_idx)
|
||||
List *remote_where, List *remote_having, List *pathkeys, bool is_subquery,
|
||||
List **retrieved_attrs, List **params_list, DataNodeChunkAssignment *sca)
|
||||
{
|
||||
deparse_expr_cxt context;
|
||||
TsFdwRelInfo *fpinfo = fdw_relinfo_get(rel);
|
||||
List *quals;
|
||||
|
||||
/*
|
||||
* We handle relations for foreign tables, joins between those and upper
|
||||
@ -771,29 +767,13 @@ deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel, List
|
||||
context.foreignrel = rel;
|
||||
context.scanrel = IS_UPPER_REL(rel) ? fpinfo->outerrel : rel;
|
||||
context.params_list = params_list;
|
||||
context.current_time_idx = current_time_idx;
|
||||
context.sca = sca;
|
||||
|
||||
/* Construct SELECT clause */
|
||||
deparseSelectSql(tlist, is_subquery, retrieved_attrs, &context);
|
||||
|
||||
/*
|
||||
* For upper relations, the WHERE clause is built from the remote
|
||||
* conditions of the underlying scan relation; otherwise, we can use the
|
||||
* supplied list of remote conditions directly.
|
||||
*/
|
||||
if (IS_UPPER_REL(rel))
|
||||
{
|
||||
TsFdwRelInfo *ofpinfo;
|
||||
|
||||
ofpinfo = fdw_relinfo_get(fpinfo->outerrel);
|
||||
quals = ofpinfo->remote_conds;
|
||||
}
|
||||
else
|
||||
quals = remote_conds;
|
||||
|
||||
/* Construct FROM and WHERE clauses */
|
||||
deparseFromExpr(quals, &context);
|
||||
deparseFromExpr(remote_where, &context);
|
||||
|
||||
if (IS_UPPER_REL(rel))
|
||||
{
|
||||
@ -801,10 +781,10 @@ deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel, List
|
||||
appendGroupByClause(tlist, &context);
|
||||
|
||||
/* Append HAVING clause */
|
||||
if (remote_conds)
|
||||
if (remote_having)
|
||||
{
|
||||
appendStringInfoString(buf, " HAVING ");
|
||||
appendConditions(remote_conds, &context, true);
|
||||
appendConditions(remote_having, &context, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2953,14 +2933,6 @@ appendFunctionName(Oid funcid, deparse_expr_cxt *context)
|
||||
|
||||
proname = NameStr(procform->proname);
|
||||
|
||||
/*
|
||||
* If the function is the 'now' function, we'll have to replace it before pushing
|
||||
* it down to the data node. For now just make a note of the index at which we're
|
||||
* inserting it into the sql statement.
|
||||
*/
|
||||
if (funcid == F_NOW && context->current_time_idx)
|
||||
*context->current_time_idx = lappend_int(*context->current_time_idx, buf->len);
|
||||
|
||||
/* Always print the function name */
|
||||
appendStringInfoString(buf, quote_identifier(proname));
|
||||
|
||||
|
@ -47,9 +47,9 @@ extern void classify_conditions(PlannerInfo *root, RelOptInfo *baserel, List *in
|
||||
extern List *build_tlist_to_deparse(RelOptInfo *foreignrel);
|
||||
|
||||
extern void deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel, List *tlist,
|
||||
List *remote_conds, List *pathkeys, bool is_subquery,
|
||||
List **retrieved_attrs, List **params_list,
|
||||
DataNodeChunkAssignment *swa, List **current_time_idx);
|
||||
List *remote_where, List *remote_having, List *pathkeys,
|
||||
bool is_subquery, List **retrieved_attrs, List **params_list,
|
||||
DataNodeChunkAssignment *swa);
|
||||
|
||||
extern const char *get_jointype_name(JoinType jointype);
|
||||
extern void deparseStringLiteral(StringInfo buf, const char *val);
|
||||
|
@ -41,8 +41,6 @@ enum FdwScanPrivateIndex
|
||||
FdwScanPrivateServerId,
|
||||
/* OID list of chunk oids, used by EXPLAIN */
|
||||
FdwScanPrivateChunkOids,
|
||||
/* Places in the remote query that need to have the current timestamp inserted */
|
||||
FdwScanCurrentTimeIndexes,
|
||||
/*
|
||||
* String describing join i.e. names of relations being joined and types
|
||||
* of join, added when the scan is join
|
||||
@ -193,43 +191,6 @@ fdw_scan_debug_override_current_timestamp(TimestampTz time)
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This function takes a sql statement char string and list of indicies to occurrences of `now()`
|
||||
* within that string and then returns a new string which will be the same sql statement, only with
|
||||
* the now calls replaced with the current transaction timestamp.
|
||||
*/
|
||||
static char *
|
||||
generate_updated_sql_using_current_timestamp(const char *original_sql, List *now_indicies)
|
||||
{
|
||||
static const char string_to_replace[] = "now()";
|
||||
int replace_length = strlen(string_to_replace);
|
||||
StringInfoData new_query;
|
||||
ListCell *lc;
|
||||
int curr_index = 0;
|
||||
TimestampTz now;
|
||||
|
||||
initStringInfo(&new_query);
|
||||
now = GetSQLCurrentTimestamp(-1);
|
||||
#ifdef TS_DEBUG
|
||||
if (ts_current_timestamp_override_value >= 0)
|
||||
now = ts_current_timestamp_override_value;
|
||||
#endif
|
||||
|
||||
foreach (lc, now_indicies)
|
||||
{
|
||||
int next_index = lfirst_int(lc);
|
||||
|
||||
Assert(next_index < strlen(original_sql) &&
|
||||
strncmp(string_to_replace, original_sql + next_index, replace_length) == 0);
|
||||
appendBinaryStringInfo(&new_query, original_sql + curr_index, next_index - curr_index);
|
||||
appendStringInfo(&new_query, "('%s'::timestamptz)", timestamptz_to_str(now));
|
||||
curr_index = next_index + replace_length;
|
||||
}
|
||||
|
||||
appendStringInfo(&new_query, "%s", original_sql + curr_index);
|
||||
return new_query.data;
|
||||
}
|
||||
|
||||
static TSConnection *
|
||||
get_connection(ScanState *ss, Oid const server_id, Bitmapset *scanrelids, List *exprs)
|
||||
{
|
||||
@ -278,14 +239,7 @@ fdw_scan_init(ScanState *ss, TsFdwScanState *fsstate, Bitmapset *scanrelids, Lis
|
||||
fdw_exprs);
|
||||
|
||||
/* Get private info created by planner functions. */
|
||||
if (list_nth(fdw_private, FdwScanCurrentTimeIndexes) == NIL)
|
||||
fsstate->query = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql));
|
||||
else
|
||||
fsstate->query =
|
||||
generate_updated_sql_using_current_timestamp(strVal(list_nth(fdw_private,
|
||||
FdwScanPrivateSelectSql)),
|
||||
list_nth(fdw_private,
|
||||
FdwScanCurrentTimeIndexes));
|
||||
fsstate->query = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql));
|
||||
fsstate->retrieved_attrs = (List *) list_nth(fdw_private, FdwScanPrivateRetrievedAttrs);
|
||||
fsstate->fetch_size = intVal(list_nth(fdw_private, FdwScanPrivateFetchSize));
|
||||
|
||||
@ -479,15 +433,7 @@ fdw_scan_explain(ScanState *ss, List *fdw_private, ExplainState *es, TsFdwScanSt
|
||||
ExplainPropertyText("Chunks", chunk_names.data, es);
|
||||
}
|
||||
|
||||
if (list_nth(fdw_private, FdwScanCurrentTimeIndexes) != NIL)
|
||||
sql =
|
||||
generate_updated_sql_using_current_timestamp(strVal(
|
||||
list_nth(fdw_private,
|
||||
FdwScanPrivateSelectSql)),
|
||||
list_nth(fdw_private,
|
||||
FdwScanCurrentTimeIndexes));
|
||||
else
|
||||
sql = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql));
|
||||
sql = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql));
|
||||
|
||||
ExplainPropertyText("Remote SQL", sql, es);
|
||||
|
||||
|
@ -15,6 +15,8 @@
|
||||
#include <optimizer/tlist.h>
|
||||
#include <optimizer/paths.h>
|
||||
#include <utils/builtins.h>
|
||||
#include <utils/fmgroids.h>
|
||||
#include <utils/syscache.h>
|
||||
#include <utils/lsyscache.h>
|
||||
#include <utils/selfuncs.h>
|
||||
#include <miscadmin.h>
|
||||
@ -30,6 +32,7 @@
|
||||
#include "scan_plan.h"
|
||||
#include "debug.h"
|
||||
#include "fdw_utils.h"
|
||||
#include "scan_exec.h"
|
||||
|
||||
/*
|
||||
* get_useful_pathkeys_for_relation
|
||||
@ -173,15 +176,274 @@ fdw_add_upper_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel, Pa
|
||||
add_paths_with_pathkeys_for_rel(root, rel, epq_path, NULL, create_upper_path);
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ParamListInfo boundParams;
|
||||
PlannerInfo *root;
|
||||
List *active_fns;
|
||||
Node *case_val;
|
||||
bool estimate;
|
||||
} eval_stable_functions_context;
|
||||
|
||||
static Node *eval_stable_functions_mutator(Node *node, void *context);
|
||||
|
||||
static Expr *
|
||||
evaluate_stable_function(Oid funcid, Oid result_type, int32 result_typmod, Oid result_collid,
|
||||
Oid input_collid, List *args, bool funcvariadic, Form_pg_proc funcform)
|
||||
{
|
||||
bool has_nonconst_input = false;
|
||||
bool has_null_input = false;
|
||||
ListCell *arg;
|
||||
FuncExpr *newexpr;
|
||||
|
||||
#ifdef TS_DEBUG
|
||||
/* Allow tests to specify the time to push down in place of now() */
|
||||
if (funcid == F_NOW && ts_current_timestamp_override_value != -1)
|
||||
{
|
||||
return (Expr *) makeConst(TIMESTAMPTZOID,
|
||||
-1,
|
||||
InvalidOid,
|
||||
sizeof(TimestampTz),
|
||||
TimestampTzGetDatum(ts_current_timestamp_override_value),
|
||||
false,
|
||||
FLOAT8PASSBYVAL);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Can't simplify if it returns a set or a RECORD. See the comments for
|
||||
* eval_const_expressions(). We should only see the whitelisted functions
|
||||
* here, no sets or RECORDS among them.
|
||||
*/
|
||||
Assert(!funcform->proretset);
|
||||
Assert(funcform->prorettype != RECORDOID);
|
||||
|
||||
/*
|
||||
* Check for constant inputs and especially constant-NULL inputs.
|
||||
*/
|
||||
foreach (arg, args)
|
||||
{
|
||||
if (IsA(lfirst(arg), Const))
|
||||
has_null_input |= ((Const *) lfirst(arg))->constisnull;
|
||||
else
|
||||
has_nonconst_input = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* The simplification of strict functions with constant NULL inputs must
|
||||
* have been already performed by eval_const_expressions().
|
||||
*/
|
||||
Assert(!(funcform->proisstrict && has_null_input));
|
||||
|
||||
/*
|
||||
* Otherwise, can simplify only if all inputs are constants. (For a
|
||||
* non-strict function, constant NULL inputs are treated the same as
|
||||
* constant non-NULL inputs.)
|
||||
*/
|
||||
if (has_nonconst_input)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* This is called on the access node for the expressions that will be pushed
|
||||
* down to data nodes. These expressions can contain only whitelisted stable
|
||||
* functions, so we shouldn't see volatile functions here. Immutable
|
||||
* functions can also occur here for expressions like
|
||||
* `immutable(stable(....))`, after we evaluate the stable function.
|
||||
*/
|
||||
Assert(funcform->provolatile != PROVOLATILE_VOLATILE);
|
||||
|
||||
/*
|
||||
* OK, looks like we can simplify this operator/function.
|
||||
*
|
||||
* Build a new FuncExpr node containing the already-simplified arguments.
|
||||
*/
|
||||
newexpr = makeNode(FuncExpr);
|
||||
newexpr->funcid = funcid;
|
||||
newexpr->funcresulttype = result_type;
|
||||
newexpr->funcretset = false;
|
||||
newexpr->funcvariadic = funcvariadic;
|
||||
newexpr->funcformat = COERCE_EXPLICIT_CALL; /* doesn't matter */
|
||||
newexpr->funccollid = result_collid; /* doesn't matter */
|
||||
newexpr->inputcollid = input_collid;
|
||||
newexpr->args = args;
|
||||
newexpr->location = -1;
|
||||
|
||||
return evaluate_expr((Expr *) newexpr, result_type, result_typmod, result_collid);
|
||||
}
|
||||
|
||||
/*
|
||||
* Execute the function to deliver a constant result.
|
||||
*/
|
||||
static Expr *
|
||||
simplify_stable_function(Oid funcid, Oid result_type, int32 result_typmod, Oid result_collid,
|
||||
Oid input_collid, List **args_p, bool funcvariadic)
|
||||
{
|
||||
List *args = *args_p;
|
||||
HeapTuple func_tuple;
|
||||
Form_pg_proc funcform;
|
||||
Expr *newexpr;
|
||||
|
||||
func_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
|
||||
if (!HeapTupleIsValid(func_tuple))
|
||||
elog(ERROR, "cache lookup failed for function %u", funcid);
|
||||
funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
|
||||
|
||||
/*
|
||||
* Process the function arguments. Here we must deal with named or defaulted
|
||||
* arguments, and then recursively apply eval_stable_functions to the whole
|
||||
* argument list.
|
||||
*/
|
||||
args = expand_function_arguments_compat(args, result_type, func_tuple);
|
||||
args = (List *) expression_tree_mutator((Node *) args, eval_stable_functions_mutator, NULL);
|
||||
/* Argument processing done, give it back to the caller */
|
||||
*args_p = args;
|
||||
|
||||
/* Now attempt simplification of the function call proper. */
|
||||
newexpr = evaluate_stable_function(funcid,
|
||||
result_type,
|
||||
result_typmod,
|
||||
result_collid,
|
||||
input_collid,
|
||||
args,
|
||||
funcvariadic,
|
||||
funcform);
|
||||
|
||||
ReleaseSysCache(func_tuple);
|
||||
|
||||
return newexpr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Recursive guts of eval_stable_functions.
|
||||
* We don't use 'context' here but it is required by the signature of
|
||||
* expression_tree_mutator.
|
||||
*/
|
||||
static Node *
|
||||
eval_stable_functions_mutator(Node *node, void *context)
|
||||
{
|
||||
if (node == NULL)
|
||||
return NULL;
|
||||
switch (nodeTag(node))
|
||||
{
|
||||
case T_FuncExpr:
|
||||
{
|
||||
FuncExpr *expr = (FuncExpr *) node;
|
||||
List *args = expr->args;
|
||||
Expr *simple;
|
||||
FuncExpr *newexpr;
|
||||
|
||||
/*
|
||||
* Code for op/func reduction is pretty bulky, so split it out
|
||||
* as a separate function. Note: exprTypmod normally returns
|
||||
* -1 for a FuncExpr, but not when the node is recognizably a
|
||||
* length coercion; we want to preserve the typmod in the
|
||||
* eventual Const if so.
|
||||
*/
|
||||
simple = simplify_stable_function(expr->funcid,
|
||||
expr->funcresulttype,
|
||||
exprTypmod(node),
|
||||
expr->funccollid,
|
||||
expr->inputcollid,
|
||||
&args,
|
||||
expr->funcvariadic);
|
||||
if (simple) /* successfully simplified it */
|
||||
return (Node *) simple;
|
||||
|
||||
/*
|
||||
* The expression cannot be simplified any further, so build
|
||||
* and return a replacement FuncExpr node using the
|
||||
* possibly-simplified arguments. Note that we have also
|
||||
* converted the argument list to positional notation.
|
||||
*/
|
||||
newexpr = makeNode(FuncExpr);
|
||||
newexpr->funcid = expr->funcid;
|
||||
newexpr->funcresulttype = expr->funcresulttype;
|
||||
newexpr->funcretset = expr->funcretset;
|
||||
newexpr->funcvariadic = expr->funcvariadic;
|
||||
newexpr->funcformat = expr->funcformat;
|
||||
newexpr->funccollid = expr->funccollid;
|
||||
newexpr->inputcollid = expr->inputcollid;
|
||||
newexpr->args = args;
|
||||
newexpr->location = expr->location;
|
||||
return (Node *) newexpr;
|
||||
}
|
||||
case T_OpExpr:
|
||||
{
|
||||
OpExpr *expr = (OpExpr *) node;
|
||||
List *args = expr->args;
|
||||
Expr *simple;
|
||||
OpExpr *newexpr;
|
||||
|
||||
/*
|
||||
* Need to get OID of underlying function. Okay to scribble
|
||||
* on input to this extent.
|
||||
*/
|
||||
set_opfuncid(expr);
|
||||
|
||||
/*
|
||||
* Code for op/func reduction is pretty bulky, so split it out
|
||||
* as a separate function.
|
||||
*/
|
||||
simple = simplify_stable_function(expr->opfuncid,
|
||||
expr->opresulttype,
|
||||
-1,
|
||||
expr->opcollid,
|
||||
expr->inputcollid,
|
||||
&args,
|
||||
false);
|
||||
if (simple) /* successfully simplified it */
|
||||
return (Node *) simple;
|
||||
|
||||
/*
|
||||
* The expression cannot be simplified any further, so build
|
||||
* and return a replacement OpExpr node using the
|
||||
* possibly-simplified arguments.
|
||||
*/
|
||||
newexpr = makeNode(OpExpr);
|
||||
newexpr->opno = expr->opno;
|
||||
newexpr->opfuncid = expr->opfuncid;
|
||||
newexpr->opresulttype = expr->opresulttype;
|
||||
newexpr->opretset = expr->opretset;
|
||||
newexpr->opcollid = expr->opcollid;
|
||||
newexpr->inputcollid = expr->inputcollid;
|
||||
newexpr->args = args;
|
||||
newexpr->location = expr->location;
|
||||
return (Node *) newexpr;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* For any node type not handled above, copy the node unchanged but
|
||||
* const-simplify its subexpressions. This is the correct thing for node
|
||||
* types whose behavior might change between planning and execution, such
|
||||
* as CurrentOfExpr. It's also a safe default for new node types not
|
||||
* known to this routine.
|
||||
*/
|
||||
return expression_tree_mutator((Node *) node, eval_stable_functions_mutator, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Try to evaluate stable functions and operators on the access node. This
|
||||
* function is similar to eval_const_expressions, but much simpler, because it
|
||||
* only evaluates the functions and doesn't have to perform any additional
|
||||
* canonicalizations.
|
||||
*/
|
||||
static Node *
|
||||
eval_stable_functions(PlannerInfo *root, Node *node)
|
||||
{
|
||||
return eval_stable_functions_mutator(node, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
fdw_scan_info_init(ScanInfo *scaninfo, PlannerInfo *root, RelOptInfo *rel, Path *best_path,
|
||||
List *scan_clauses)
|
||||
{
|
||||
TsFdwRelInfo *fpinfo = fdw_relinfo_get(rel);
|
||||
List *remote_exprs = NIL;
|
||||
List *remote_where = NIL;
|
||||
List *remote_having = NIL;
|
||||
List *local_exprs = NIL;
|
||||
List *params_list = NIL;
|
||||
List *current_time_idx = NIL;
|
||||
List *fdw_scan_tlist = NIL;
|
||||
List *fdw_recheck_quals = NIL;
|
||||
List *retrieved_attrs;
|
||||
@ -225,11 +487,11 @@ fdw_scan_info_init(ScanInfo *scaninfo, PlannerInfo *root, RelOptInfo *rel, Path
|
||||
continue;
|
||||
|
||||
if (list_member_ptr(fpinfo->remote_conds, rinfo))
|
||||
remote_exprs = lappend(remote_exprs, rinfo->clause);
|
||||
remote_where = lappend(remote_where, rinfo->clause);
|
||||
else if (list_member_ptr(fpinfo->local_conds, rinfo))
|
||||
local_exprs = lappend(local_exprs, rinfo->clause);
|
||||
else if (is_foreign_expr(root, rel, rinfo->clause))
|
||||
remote_exprs = lappend(remote_exprs, rinfo->clause);
|
||||
remote_where = lappend(remote_where, rinfo->clause);
|
||||
else
|
||||
local_exprs = lappend(local_exprs, rinfo->clause);
|
||||
}
|
||||
@ -238,7 +500,7 @@ fdw_scan_info_init(ScanInfo *scaninfo, PlannerInfo *root, RelOptInfo *rel, Path
|
||||
* For a base-relation scan, we have to support EPQ recheck, which
|
||||
* should recheck all the remote quals.
|
||||
*/
|
||||
fdw_recheck_quals = remote_exprs;
|
||||
fdw_recheck_quals = remote_where;
|
||||
}
|
||||
else if (IS_JOIN_REL(rel))
|
||||
{
|
||||
@ -263,8 +525,13 @@ fdw_scan_info_init(ScanInfo *scaninfo, PlannerInfo *root, RelOptInfo *rel, Path
|
||||
/*
|
||||
* Instead we get the conditions to apply from the fdw_private
|
||||
* structure.
|
||||
* For upper relations, the WHERE clause is built from the remote
|
||||
* conditions of the underlying scan relation.
|
||||
*/
|
||||
remote_exprs = extract_actual_clauses(fpinfo->remote_conds, false);
|
||||
TsFdwRelInfo *ofpinfo;
|
||||
ofpinfo = fdw_relinfo_get(fpinfo->outerrel);
|
||||
remote_where = extract_actual_clauses(ofpinfo->remote_conds, false);
|
||||
remote_having = extract_actual_clauses(fpinfo->remote_conds, false);
|
||||
local_exprs = extract_actual_clauses(fpinfo->local_conds, false);
|
||||
|
||||
/*
|
||||
@ -282,6 +549,19 @@ fdw_scan_info_init(ScanInfo *scaninfo, PlannerInfo *root, RelOptInfo *rel, Path
|
||||
fdw_scan_tlist = build_tlist_to_deparse(rel);
|
||||
}
|
||||
|
||||
/*
|
||||
* Try to locally evaluate the stable functions such as now() before pushing
|
||||
* them to the remote node.
|
||||
* We have to do this at the execution stage as oppossed to the planning stage, because stable
|
||||
* functions must be recalculated with each execution of a prepared
|
||||
* statement.
|
||||
* Note that the query planner currently only pushes down to remote side
|
||||
* the whitelisted stable functions, see `function_is_whitelisted()`. So
|
||||
* this code only has to deal with such functions.
|
||||
*/
|
||||
remote_where = (List *) eval_stable_functions(root, (Node *) remote_where);
|
||||
remote_having = (List *) eval_stable_functions(root, (Node *) remote_having);
|
||||
|
||||
/*
|
||||
* Build the query string to be sent for execution, and identify
|
||||
* expressions to be sent as parameters.
|
||||
@ -291,16 +571,16 @@ fdw_scan_info_init(ScanInfo *scaninfo, PlannerInfo *root, RelOptInfo *rel, Path
|
||||
root,
|
||||
rel,
|
||||
fdw_scan_tlist,
|
||||
remote_exprs,
|
||||
remote_where,
|
||||
remote_having,
|
||||
best_path->pathkeys,
|
||||
false,
|
||||
&retrieved_attrs,
|
||||
¶ms_list,
|
||||
fpinfo->sca,
|
||||
¤t_time_idx);
|
||||
fpinfo->sca);
|
||||
|
||||
/* Remember remote_exprs for possible use by PlanDirectModify */
|
||||
fpinfo->final_remote_exprs = remote_exprs;
|
||||
fpinfo->final_remote_exprs = remote_where;
|
||||
|
||||
/*
|
||||
* Build the fdw_private list that will be available to the executor.
|
||||
@ -311,7 +591,6 @@ fdw_scan_info_init(ScanInfo *scaninfo, PlannerInfo *root, RelOptInfo *rel, Path
|
||||
makeInteger(fpinfo->fetch_size),
|
||||
makeInteger(fpinfo->server->serverid),
|
||||
(fpinfo->sca != NULL ? list_copy(fpinfo->sca->chunk_oids) : NIL));
|
||||
fdw_private = lappend(fdw_private, current_time_idx);
|
||||
Assert(!IS_JOIN_REL(rel));
|
||||
|
||||
if (IS_UPPER_REL(rel))
|
||||
|
@ -5247,6 +5247,373 @@ NOTICE: adding not-null constraint to column "time"
|
||||
(1 row)
|
||||
|
||||
DROP TABLE test;
|
||||
-- Test that stable functions are calculated on the access node.
|
||||
--
|
||||
-- As a stable function to test, use the timestamp -> timestamptz conversion
|
||||
-- that is stable because it uses the current timezone.
|
||||
-- We have to be careful about `timestamp < timestamptz` comparison. From postgres
|
||||
-- docs:
|
||||
-- When comparing a timestamp without time zone to a timestamp with time zone,
|
||||
-- the former value is assumed to be given in the time zone specified by the
|
||||
-- TimeZone configuration parameter, and is rotated to UTC for comparison to
|
||||
-- the latter value (which is already in UTC internally).
|
||||
-- We don't want this to happen on data node, so we cast the filter value to
|
||||
-- timestamp, and check that this cast happens on the access node and uses the
|
||||
-- current timezone.
|
||||
SELECT test.tsl_override_current_timestamptz(null);
|
||||
tsl_override_current_timestamptz
|
||||
----------------------------------
|
||||
|
||||
(1 row)
|
||||
|
||||
CREATE TABLE test_tz (time timestamp, v int);
|
||||
SELECT create_distributed_hypertable('test_tz','time',
|
||||
chunk_time_interval => interval '1 hour');
|
||||
NOTICE: adding not-null constraint to column "time"
|
||||
create_distributed_hypertable
|
||||
-------------------------------
|
||||
(31,public,test_tz,t)
|
||||
(1 row)
|
||||
|
||||
INSERT INTO test_tz VALUES ('2018-01-02 12:00:00', 2), ('2018-01-02 11:00:00', 1),
|
||||
('2018-01-02 13:00:00', 3), ('2018-01-02 14:00:00', 4);
|
||||
SET TIME ZONE 'Etc/GMT';
|
||||
SELECT '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
timestamp
|
||||
--------------------------
|
||||
Tue Jan 02 12:00:00 2018
|
||||
(1 row)
|
||||
|
||||
-- Normal WHERE clause on baserel
|
||||
SELECT * FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
time | v
|
||||
--------------------------+---
|
||||
Tue Jan 02 13:00:00 2018 | 3
|
||||
Tue Jan 02 14:00:00 2018 | 4
|
||||
(2 rows)
|
||||
|
||||
EXPLAIN (verbose, costs off)
|
||||
SELECT * FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
QUERY PLAN
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Custom Scan (AsyncAppend)
|
||||
Output: test_tz."time", test_tz.v
|
||||
-> Append
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_1
|
||||
Output: test_tz_1."time", test_tz_1.v
|
||||
Data node: db_dist_hypertable_1
|
||||
Chunks: _dist_hyper_31_97_chunk
|
||||
Remote SQL: SELECT "time", v FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[45]) AND (("time" > '2018-01-02 12:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Index Scan using _dist_hyper_31_97_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_97_chunk
|
||||
Output: _dist_hyper_31_97_chunk."time", _dist_hyper_31_97_chunk.v
|
||||
Index Cond: (_dist_hyper_31_97_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_2
|
||||
Output: test_tz_2."time", test_tz_2.v
|
||||
Data node: db_dist_hypertable_2
|
||||
Chunks: _dist_hyper_31_95_chunk, _dist_hyper_31_96_chunk, _dist_hyper_31_98_chunk
|
||||
Remote SQL: SELECT "time", v FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[43, 44, 45]) AND (("time" > '2018-01-02 12:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Append
|
||||
-> Index Scan using _dist_hyper_31_95_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_95_chunk
|
||||
Output: _dist_hyper_31_95_chunk."time", _dist_hyper_31_95_chunk.v
|
||||
Index Cond: (_dist_hyper_31_95_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
-> Index Scan using _dist_hyper_31_98_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_98_chunk
|
||||
Output: _dist_hyper_31_98_chunk."time", _dist_hyper_31_98_chunk.v
|
||||
Index Cond: (_dist_hyper_31_98_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
|
||||
(27 rows)
|
||||
|
||||
-- Also test different code paths used with aggregation pushdown.
|
||||
SELECT count(*) FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
count
|
||||
-------
|
||||
2
|
||||
(1 row)
|
||||
|
||||
EXPLAIN (verbose, costs off)
|
||||
SELECT count(*) FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
QUERY PLAN
|
||||
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Aggregate
|
||||
Output: count(*)
|
||||
-> Custom Scan (AsyncAppend)
|
||||
-> Append
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_1
|
||||
Data node: db_dist_hypertable_1
|
||||
Chunks: _dist_hyper_31_97_chunk
|
||||
Remote SQL: SELECT NULL FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[45]) AND (("time" > '2018-01-02 12:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Result
|
||||
Output: NULL::text
|
||||
-> Index Only Scan using _dist_hyper_31_97_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_97_chunk
|
||||
Index Cond: (_dist_hyper_31_97_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_2
|
||||
Data node: db_dist_hypertable_2
|
||||
Chunks: _dist_hyper_31_95_chunk, _dist_hyper_31_96_chunk, _dist_hyper_31_98_chunk
|
||||
Remote SQL: SELECT NULL FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[43, 44, 45]) AND (("time" > '2018-01-02 12:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Result
|
||||
Output: NULL::text
|
||||
-> Append
|
||||
-> Index Only Scan using _dist_hyper_31_95_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_95_chunk
|
||||
Index Cond: (_dist_hyper_31_95_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
-> Index Only Scan using _dist_hyper_31_98_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_98_chunk
|
||||
Index Cond: (_dist_hyper_31_98_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
|
||||
(27 rows)
|
||||
|
||||
-- TODO: test HAVING here and in the later now() tests as well.
|
||||
-- Change the timezone and check that the conversion is applied correctly.
|
||||
SET TIME ZONE 'Etc/GMT+1';
|
||||
SELECT '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
timestamp
|
||||
--------------------------
|
||||
Tue Jan 02 11:00:00 2018
|
||||
(1 row)
|
||||
|
||||
SELECT * FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
time | v
|
||||
--------------------------+---
|
||||
Tue Jan 02 13:00:00 2018 | 3
|
||||
Tue Jan 02 12:00:00 2018 | 2
|
||||
Tue Jan 02 14:00:00 2018 | 4
|
||||
(3 rows)
|
||||
|
||||
EXPLAIN (verbose, costs off)
|
||||
SELECT * FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
QUERY PLAN
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Custom Scan (AsyncAppend)
|
||||
Output: test_tz."time", test_tz.v
|
||||
-> Append
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_1
|
||||
Output: test_tz_1."time", test_tz_1.v
|
||||
Data node: db_dist_hypertable_1
|
||||
Chunks: _dist_hyper_31_97_chunk
|
||||
Remote SQL: SELECT "time", v FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[45]) AND (("time" > '2018-01-02 11:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Index Scan using _dist_hyper_31_97_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_97_chunk
|
||||
Output: _dist_hyper_31_97_chunk."time", _dist_hyper_31_97_chunk.v
|
||||
Index Cond: (_dist_hyper_31_97_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_2
|
||||
Output: test_tz_2."time", test_tz_2.v
|
||||
Data node: db_dist_hypertable_2
|
||||
Chunks: _dist_hyper_31_95_chunk, _dist_hyper_31_96_chunk, _dist_hyper_31_98_chunk
|
||||
Remote SQL: SELECT "time", v FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[43, 44, 45]) AND (("time" > '2018-01-02 11:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Append
|
||||
-> Index Scan using _dist_hyper_31_95_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_95_chunk
|
||||
Output: _dist_hyper_31_95_chunk."time", _dist_hyper_31_95_chunk.v
|
||||
Index Cond: (_dist_hyper_31_95_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
-> Index Scan using _dist_hyper_31_96_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_96_chunk
|
||||
Output: _dist_hyper_31_96_chunk."time", _dist_hyper_31_96_chunk.v
|
||||
Index Cond: (_dist_hyper_31_96_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
-> Index Scan using _dist_hyper_31_98_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_98_chunk
|
||||
Output: _dist_hyper_31_98_chunk."time", _dist_hyper_31_98_chunk.v
|
||||
Index Cond: (_dist_hyper_31_98_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
|
||||
(30 rows)
|
||||
|
||||
SELECT count(*) FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
count
|
||||
-------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
EXPLAIN (verbose, costs off)
|
||||
SELECT count(*) FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
QUERY PLAN
|
||||
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Aggregate
|
||||
Output: count(*)
|
||||
-> Custom Scan (AsyncAppend)
|
||||
-> Append
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_1
|
||||
Data node: db_dist_hypertable_1
|
||||
Chunks: _dist_hyper_31_97_chunk
|
||||
Remote SQL: SELECT NULL FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[45]) AND (("time" > '2018-01-02 11:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Result
|
||||
Output: NULL::text
|
||||
-> Index Only Scan using _dist_hyper_31_97_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_97_chunk
|
||||
Index Cond: (_dist_hyper_31_97_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_2
|
||||
Data node: db_dist_hypertable_2
|
||||
Chunks: _dist_hyper_31_95_chunk, _dist_hyper_31_96_chunk, _dist_hyper_31_98_chunk
|
||||
Remote SQL: SELECT NULL FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[43, 44, 45]) AND (("time" > '2018-01-02 11:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Result
|
||||
Output: NULL::text
|
||||
-> Append
|
||||
-> Index Only Scan using _dist_hyper_31_95_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_95_chunk
|
||||
Index Cond: (_dist_hyper_31_95_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
-> Index Only Scan using _dist_hyper_31_96_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_96_chunk
|
||||
Index Cond: (_dist_hyper_31_96_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
-> Index Only Scan using _dist_hyper_31_98_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_98_chunk
|
||||
Index Cond: (_dist_hyper_31_98_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
|
||||
(29 rows)
|
||||
|
||||
-- Conversion to timestamptz cannot be evaluated at the access node, because the
|
||||
-- argument is a column reference.
|
||||
SELECT count(*) FROM test_tz WHERE time::timestamptz > now();
|
||||
count
|
||||
-------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
-- According to our docs, JIT is not recommended for use on access node in
|
||||
-- multi-node environment. Turn it off so that it doesn't ruin EXPLAIN for the
|
||||
-- next query.
|
||||
SET jit = 0;
|
||||
-- Test that operators are evaluated as well. Comparison of timestamp with
|
||||
-- timestamptz is a stable operator, and comparison of two timestamps is an
|
||||
-- immutable operator. This also test that immutable functions using these
|
||||
-- operators as arguments are evaluated.
|
||||
EXPLAIN (verbose, costs off)
|
||||
WITH dummy AS (SELECT '2018-01-02 12:00:00 +00'::timestamptz::timestamp x)
|
||||
SELECT * FROM test_tz, dummy
|
||||
WHERE time > x
|
||||
+ (x = x)::int -- stable
|
||||
* (x = '2018-01-02 11:00:00'::timestamp)::int -- immutable
|
||||
* INTERVAL '1 hour';
|
||||
QUERY PLAN
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Custom Scan (AsyncAppend)
|
||||
Output: test_tz."time", test_tz.v, (('Tue Jan 02 11:00:00 2018 -01'::timestamp with time zone)::timestamp without time zone)
|
||||
-> Append
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_1
|
||||
Output: test_tz_1."time", test_tz_1.v, ('Tue Jan 02 11:00:00 2018 -01'::timestamp with time zone)::timestamp without time zone
|
||||
Data node: db_dist_hypertable_1
|
||||
Chunks: _dist_hyper_31_97_chunk
|
||||
Remote SQL: SELECT "time", v FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[45]) AND (("time" > '2018-01-02 12:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Index Scan using _dist_hyper_31_97_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_97_chunk
|
||||
Output: _dist_hyper_31_97_chunk."time", _dist_hyper_31_97_chunk.v
|
||||
Index Cond: (_dist_hyper_31_97_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_2
|
||||
Output: test_tz_2."time", test_tz_2.v, ('Tue Jan 02 11:00:00 2018 -01'::timestamp with time zone)::timestamp without time zone
|
||||
Data node: db_dist_hypertable_2
|
||||
Chunks: _dist_hyper_31_95_chunk, _dist_hyper_31_96_chunk, _dist_hyper_31_98_chunk
|
||||
Remote SQL: SELECT "time", v FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[43, 44, 45]) AND (("time" > '2018-01-02 12:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Append
|
||||
-> Index Scan using _dist_hyper_31_95_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_95_chunk
|
||||
Output: _dist_hyper_31_95_chunk."time", _dist_hyper_31_95_chunk.v
|
||||
Index Cond: (_dist_hyper_31_95_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
-> Index Scan using _dist_hyper_31_98_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_98_chunk
|
||||
Output: _dist_hyper_31_98_chunk."time", _dist_hyper_31_98_chunk.v
|
||||
Index Cond: (_dist_hyper_31_98_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
|
||||
(27 rows)
|
||||
|
||||
-- Reference value for the above test.
|
||||
WITH dummy AS (SELECT '2018-01-02 12:00:00 +00'::timestamptz::timestamp x)
|
||||
SELECT x + (x = x)::int * (x = '2018-01-02 11:00:00'::timestamp)::int * INTERVAL '1 hour'
|
||||
FROM dummy;
|
||||
?column?
|
||||
--------------------------
|
||||
Tue Jan 02 12:00:00 2018
|
||||
(1 row)
|
||||
|
||||
-- Check that the test function for partly overriding now() works. It's very
|
||||
-- hacky and only has effect when we estimate some costs or evaluate sTABLE
|
||||
-- functions in quals on access node, and has no effect in other cases.
|
||||
-- Consider deleting it altogether.
|
||||
SELECT test.tsl_override_current_timestamptz('2018-01-02 12:00:00 +00'::timestamptz);
|
||||
tsl_override_current_timestamptz
|
||||
----------------------------------
|
||||
|
||||
(1 row)
|
||||
|
||||
SELECT count(*) FROM test_tz WHERE time > now();
|
||||
count
|
||||
-------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
SELECT test.tsl_override_current_timestamptz(null);
|
||||
tsl_override_current_timestamptz
|
||||
----------------------------------
|
||||
|
||||
(1 row)
|
||||
|
||||
RESET TIME ZONE;
|
||||
DROP TABLE test_tz;
|
||||
-- Check that now() is evaluated on the access node. Also check that it is evaluated
|
||||
-- anew on every execution of a prepared statement.
|
||||
CREATE TABLE test_now (time timestamp, v int);
|
||||
SELECT create_distributed_hypertable('test_now','time',
|
||||
chunk_time_interval => interval '1 hour');
|
||||
NOTICE: adding not-null constraint to column "time"
|
||||
create_distributed_hypertable
|
||||
-------------------------------
|
||||
(32,public,test_now,t)
|
||||
(1 row)
|
||||
|
||||
PREPARE test_query as
|
||||
SELECT count(*) FILTER (WHERE time = now()), count(*) FILTER (WHERE time < now()),
|
||||
count(*) FILTER (WHERE time > now()) FROM test_now;
|
||||
;
|
||||
BEGIN; -- to fix the value of now();
|
||||
INSERT INTO test_now VALUES
|
||||
(now(), 1), (now() + INTERVAL '1 hour', 1),
|
||||
(now() + INTERVAL '2 hour', 2 ), (now() + INTERVAL '3 hour', 3);
|
||||
SELECT count(*) FILTER (WHERE time = now()), count(*) FILTER (WHERE time < now()),
|
||||
count(*) FILTER (WHERE time > now()) FROM test_now;
|
||||
count | count | count
|
||||
-------+-------+-------
|
||||
1 | 0 | 3
|
||||
(1 row)
|
||||
|
||||
EXECUTE test_query;
|
||||
count | count | count
|
||||
-------+-------+-------
|
||||
1 | 0 | 3
|
||||
(1 row)
|
||||
|
||||
-- Also test different code paths used with aggregation pushdown.
|
||||
-- We can't run EXPLAIN here, because now() is different every time. But the
|
||||
-- strict equality should be enough to detect if now() is being erroneously
|
||||
-- evaluated on data node, where it will differ from time to time.
|
||||
SELECT count(*) FROM test_now WHERE time = now();
|
||||
count
|
||||
-------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
COMMIT;
|
||||
-- now() will be different in a new transaction.
|
||||
BEGIN;
|
||||
SELECT count(*) FILTER (WHERE time = now()), count(*) FILTER (WHERE time < now()),
|
||||
count(*) FILTER (WHERE time > now()) FROM test_now;
|
||||
count | count | count
|
||||
-------+-------+-------
|
||||
0 | 1 | 3
|
||||
(1 row)
|
||||
|
||||
EXECUTE test_query;
|
||||
count | count | count
|
||||
-------+-------+-------
|
||||
0 | 1 | 3
|
||||
(1 row)
|
||||
|
||||
SELECT count(*) FROM test_now WHERE time = now();
|
||||
count
|
||||
-------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
COMMIT;
|
||||
DROP TABLE test_now;
|
||||
DEALLOCATE test_query;
|
||||
-- Cleanup
|
||||
DROP DATABASE :DATA_NODE_1;
|
||||
DROP DATABASE :DATA_NODE_2;
|
||||
DROP DATABASE :DATA_NODE_3;
|
||||
|
@ -5246,6 +5246,373 @@ NOTICE: adding not-null constraint to column "time"
|
||||
(1 row)
|
||||
|
||||
DROP TABLE test;
|
||||
-- Test that stable functions are calculated on the access node.
|
||||
--
|
||||
-- As a stable function to test, use the timestamp -> timestamptz conversion
|
||||
-- that is stable because it uses the current timezone.
|
||||
-- We have to be careful about `timestamp < timestamptz` comparison. From postgres
|
||||
-- docs:
|
||||
-- When comparing a timestamp without time zone to a timestamp with time zone,
|
||||
-- the former value is assumed to be given in the time zone specified by the
|
||||
-- TimeZone configuration parameter, and is rotated to UTC for comparison to
|
||||
-- the latter value (which is already in UTC internally).
|
||||
-- We don't want this to happen on data node, so we cast the filter value to
|
||||
-- timestamp, and check that this cast happens on the access node and uses the
|
||||
-- current timezone.
|
||||
SELECT test.tsl_override_current_timestamptz(null);
|
||||
tsl_override_current_timestamptz
|
||||
----------------------------------
|
||||
|
||||
(1 row)
|
||||
|
||||
CREATE TABLE test_tz (time timestamp, v int);
|
||||
SELECT create_distributed_hypertable('test_tz','time',
|
||||
chunk_time_interval => interval '1 hour');
|
||||
NOTICE: adding not-null constraint to column "time"
|
||||
create_distributed_hypertable
|
||||
-------------------------------
|
||||
(31,public,test_tz,t)
|
||||
(1 row)
|
||||
|
||||
INSERT INTO test_tz VALUES ('2018-01-02 12:00:00', 2), ('2018-01-02 11:00:00', 1),
|
||||
('2018-01-02 13:00:00', 3), ('2018-01-02 14:00:00', 4);
|
||||
SET TIME ZONE 'Etc/GMT';
|
||||
SELECT '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
timestamp
|
||||
--------------------------
|
||||
Tue Jan 02 12:00:00 2018
|
||||
(1 row)
|
||||
|
||||
-- Normal WHERE clause on baserel
|
||||
SELECT * FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
time | v
|
||||
--------------------------+---
|
||||
Tue Jan 02 13:00:00 2018 | 3
|
||||
Tue Jan 02 14:00:00 2018 | 4
|
||||
(2 rows)
|
||||
|
||||
EXPLAIN (verbose, costs off)
|
||||
SELECT * FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
QUERY PLAN
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Custom Scan (AsyncAppend)
|
||||
Output: test_tz."time", test_tz.v
|
||||
-> Append
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_1
|
||||
Output: test_tz_1."time", test_tz_1.v
|
||||
Data node: db_dist_hypertable_1
|
||||
Chunks: _dist_hyper_31_97_chunk
|
||||
Remote SQL: SELECT "time", v FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[45]) AND (("time" > '2018-01-02 12:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Index Scan using _dist_hyper_31_97_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_97_chunk
|
||||
Output: _dist_hyper_31_97_chunk."time", _dist_hyper_31_97_chunk.v
|
||||
Index Cond: (_dist_hyper_31_97_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_2
|
||||
Output: test_tz_2."time", test_tz_2.v
|
||||
Data node: db_dist_hypertable_2
|
||||
Chunks: _dist_hyper_31_95_chunk, _dist_hyper_31_96_chunk, _dist_hyper_31_98_chunk
|
||||
Remote SQL: SELECT "time", v FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[43, 44, 45]) AND (("time" > '2018-01-02 12:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Append
|
||||
-> Index Scan using _dist_hyper_31_95_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_95_chunk
|
||||
Output: _dist_hyper_31_95_chunk."time", _dist_hyper_31_95_chunk.v
|
||||
Index Cond: (_dist_hyper_31_95_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
-> Index Scan using _dist_hyper_31_98_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_98_chunk
|
||||
Output: _dist_hyper_31_98_chunk."time", _dist_hyper_31_98_chunk.v
|
||||
Index Cond: (_dist_hyper_31_98_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
|
||||
(27 rows)
|
||||
|
||||
-- Also test different code paths used with aggregation pushdown.
|
||||
SELECT count(*) FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
count
|
||||
-------
|
||||
2
|
||||
(1 row)
|
||||
|
||||
EXPLAIN (verbose, costs off)
|
||||
SELECT count(*) FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
QUERY PLAN
|
||||
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Aggregate
|
||||
Output: count(*)
|
||||
-> Custom Scan (AsyncAppend)
|
||||
-> Append
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_1
|
||||
Data node: db_dist_hypertable_1
|
||||
Chunks: _dist_hyper_31_97_chunk
|
||||
Remote SQL: SELECT NULL FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[45]) AND (("time" > '2018-01-02 12:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Result
|
||||
Output: NULL::text
|
||||
-> Index Only Scan using _dist_hyper_31_97_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_97_chunk
|
||||
Index Cond: (_dist_hyper_31_97_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_2
|
||||
Data node: db_dist_hypertable_2
|
||||
Chunks: _dist_hyper_31_95_chunk, _dist_hyper_31_96_chunk, _dist_hyper_31_98_chunk
|
||||
Remote SQL: SELECT NULL FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[43, 44, 45]) AND (("time" > '2018-01-02 12:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Result
|
||||
Output: NULL::text
|
||||
-> Append
|
||||
-> Index Only Scan using _dist_hyper_31_95_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_95_chunk
|
||||
Index Cond: (_dist_hyper_31_95_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
-> Index Only Scan using _dist_hyper_31_98_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_98_chunk
|
||||
Index Cond: (_dist_hyper_31_98_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
|
||||
(27 rows)
|
||||
|
||||
-- TODO: test HAVING here and in the later now() tests as well.
|
||||
-- Change the timezone and check that the conversion is applied correctly.
|
||||
SET TIME ZONE 'Etc/GMT+1';
|
||||
SELECT '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
timestamp
|
||||
--------------------------
|
||||
Tue Jan 02 11:00:00 2018
|
||||
(1 row)
|
||||
|
||||
SELECT * FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
time | v
|
||||
--------------------------+---
|
||||
Tue Jan 02 13:00:00 2018 | 3
|
||||
Tue Jan 02 12:00:00 2018 | 2
|
||||
Tue Jan 02 14:00:00 2018 | 4
|
||||
(3 rows)
|
||||
|
||||
EXPLAIN (verbose, costs off)
|
||||
SELECT * FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
QUERY PLAN
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Custom Scan (AsyncAppend)
|
||||
Output: test_tz."time", test_tz.v
|
||||
-> Append
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_1
|
||||
Output: test_tz_1."time", test_tz_1.v
|
||||
Data node: db_dist_hypertable_1
|
||||
Chunks: _dist_hyper_31_97_chunk
|
||||
Remote SQL: SELECT "time", v FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[45]) AND (("time" > '2018-01-02 11:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Index Scan using _dist_hyper_31_97_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_97_chunk
|
||||
Output: _dist_hyper_31_97_chunk."time", _dist_hyper_31_97_chunk.v
|
||||
Index Cond: (_dist_hyper_31_97_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_2
|
||||
Output: test_tz_2."time", test_tz_2.v
|
||||
Data node: db_dist_hypertable_2
|
||||
Chunks: _dist_hyper_31_95_chunk, _dist_hyper_31_96_chunk, _dist_hyper_31_98_chunk
|
||||
Remote SQL: SELECT "time", v FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[43, 44, 45]) AND (("time" > '2018-01-02 11:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Append
|
||||
-> Index Scan using _dist_hyper_31_95_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_95_chunk
|
||||
Output: _dist_hyper_31_95_chunk."time", _dist_hyper_31_95_chunk.v
|
||||
Index Cond: (_dist_hyper_31_95_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
-> Index Scan using _dist_hyper_31_96_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_96_chunk
|
||||
Output: _dist_hyper_31_96_chunk."time", _dist_hyper_31_96_chunk.v
|
||||
Index Cond: (_dist_hyper_31_96_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
-> Index Scan using _dist_hyper_31_98_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_98_chunk
|
||||
Output: _dist_hyper_31_98_chunk."time", _dist_hyper_31_98_chunk.v
|
||||
Index Cond: (_dist_hyper_31_98_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
|
||||
(30 rows)
|
||||
|
||||
SELECT count(*) FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
count
|
||||
-------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
EXPLAIN (verbose, costs off)
|
||||
SELECT count(*) FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
QUERY PLAN
|
||||
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Aggregate
|
||||
Output: count(*)
|
||||
-> Custom Scan (AsyncAppend)
|
||||
-> Append
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_1
|
||||
Data node: db_dist_hypertable_1
|
||||
Chunks: _dist_hyper_31_97_chunk
|
||||
Remote SQL: SELECT NULL FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[45]) AND (("time" > '2018-01-02 11:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Result
|
||||
Output: NULL::text
|
||||
-> Index Only Scan using _dist_hyper_31_97_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_97_chunk
|
||||
Index Cond: (_dist_hyper_31_97_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_2
|
||||
Data node: db_dist_hypertable_2
|
||||
Chunks: _dist_hyper_31_95_chunk, _dist_hyper_31_96_chunk, _dist_hyper_31_98_chunk
|
||||
Remote SQL: SELECT NULL FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[43, 44, 45]) AND (("time" > '2018-01-02 11:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Result
|
||||
Output: NULL::text
|
||||
-> Append
|
||||
-> Index Only Scan using _dist_hyper_31_95_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_95_chunk
|
||||
Index Cond: (_dist_hyper_31_95_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
-> Index Only Scan using _dist_hyper_31_96_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_96_chunk
|
||||
Index Cond: (_dist_hyper_31_96_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
-> Index Only Scan using _dist_hyper_31_98_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_98_chunk
|
||||
Index Cond: (_dist_hyper_31_98_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
|
||||
(29 rows)
|
||||
|
||||
-- Conversion to timestamptz cannot be evaluated at the access node, because the
|
||||
-- argument is a column reference.
|
||||
SELECT count(*) FROM test_tz WHERE time::timestamptz > now();
|
||||
count
|
||||
-------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
-- According to our docs, JIT is not recommended for use on access node in
|
||||
-- multi-node environment. Turn it off so that it doesn't ruin EXPLAIN for the
|
||||
-- next query.
|
||||
SET jit = 0;
|
||||
-- Test that operators are evaluated as well. Comparison of timestamp with
|
||||
-- timestamptz is a stable operator, and comparison of two timestamps is an
|
||||
-- immutable operator. This also test that immutable functions using these
|
||||
-- operators as arguments are evaluated.
|
||||
EXPLAIN (verbose, costs off)
|
||||
WITH dummy AS (SELECT '2018-01-02 12:00:00 +00'::timestamptz::timestamp x)
|
||||
SELECT * FROM test_tz, dummy
|
||||
WHERE time > x
|
||||
+ (x = x)::int -- stable
|
||||
* (x = '2018-01-02 11:00:00'::timestamp)::int -- immutable
|
||||
* INTERVAL '1 hour';
|
||||
QUERY PLAN
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Custom Scan (AsyncAppend)
|
||||
Output: test_tz."time", test_tz.v, (('Tue Jan 02 11:00:00 2018 -01'::timestamp with time zone)::timestamp without time zone)
|
||||
-> Append
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_1
|
||||
Output: test_tz_1."time", test_tz_1.v, ('Tue Jan 02 11:00:00 2018 -01'::timestamp with time zone)::timestamp without time zone
|
||||
Data node: db_dist_hypertable_1
|
||||
Chunks: _dist_hyper_31_97_chunk
|
||||
Remote SQL: SELECT "time", v FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[45]) AND (("time" > '2018-01-02 12:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Index Scan using _dist_hyper_31_97_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_97_chunk
|
||||
Output: _dist_hyper_31_97_chunk."time", _dist_hyper_31_97_chunk.v
|
||||
Index Cond: (_dist_hyper_31_97_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_2
|
||||
Output: test_tz_2."time", test_tz_2.v, ('Tue Jan 02 11:00:00 2018 -01'::timestamp with time zone)::timestamp without time zone
|
||||
Data node: db_dist_hypertable_2
|
||||
Chunks: _dist_hyper_31_95_chunk, _dist_hyper_31_96_chunk, _dist_hyper_31_98_chunk
|
||||
Remote SQL: SELECT "time", v FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[43, 44, 45]) AND (("time" > '2018-01-02 12:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Append
|
||||
-> Index Scan using _dist_hyper_31_95_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_95_chunk
|
||||
Output: _dist_hyper_31_95_chunk."time", _dist_hyper_31_95_chunk.v
|
||||
Index Cond: (_dist_hyper_31_95_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
-> Index Scan using _dist_hyper_31_98_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_98_chunk
|
||||
Output: _dist_hyper_31_98_chunk."time", _dist_hyper_31_98_chunk.v
|
||||
Index Cond: (_dist_hyper_31_98_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
|
||||
(27 rows)
|
||||
|
||||
-- Reference value for the above test.
|
||||
WITH dummy AS (SELECT '2018-01-02 12:00:00 +00'::timestamptz::timestamp x)
|
||||
SELECT x + (x = x)::int * (x = '2018-01-02 11:00:00'::timestamp)::int * INTERVAL '1 hour'
|
||||
FROM dummy;
|
||||
?column?
|
||||
--------------------------
|
||||
Tue Jan 02 12:00:00 2018
|
||||
(1 row)
|
||||
|
||||
-- Check that the test function for partly overriding now() works. It's very
|
||||
-- hacky and only has effect when we estimate some costs or evaluate sTABLE
|
||||
-- functions in quals on access node, and has no effect in other cases.
|
||||
-- Consider deleting it altogether.
|
||||
SELECT test.tsl_override_current_timestamptz('2018-01-02 12:00:00 +00'::timestamptz);
|
||||
tsl_override_current_timestamptz
|
||||
----------------------------------
|
||||
|
||||
(1 row)
|
||||
|
||||
SELECT count(*) FROM test_tz WHERE time > now();
|
||||
count
|
||||
-------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
SELECT test.tsl_override_current_timestamptz(null);
|
||||
tsl_override_current_timestamptz
|
||||
----------------------------------
|
||||
|
||||
(1 row)
|
||||
|
||||
RESET TIME ZONE;
|
||||
DROP TABLE test_tz;
|
||||
-- Check that now() is evaluated on the access node. Also check that it is evaluated
|
||||
-- anew on every execution of a prepared statement.
|
||||
CREATE TABLE test_now (time timestamp, v int);
|
||||
SELECT create_distributed_hypertable('test_now','time',
|
||||
chunk_time_interval => interval '1 hour');
|
||||
NOTICE: adding not-null constraint to column "time"
|
||||
create_distributed_hypertable
|
||||
-------------------------------
|
||||
(32,public,test_now,t)
|
||||
(1 row)
|
||||
|
||||
PREPARE test_query as
|
||||
SELECT count(*) FILTER (WHERE time = now()), count(*) FILTER (WHERE time < now()),
|
||||
count(*) FILTER (WHERE time > now()) FROM test_now;
|
||||
;
|
||||
BEGIN; -- to fix the value of now();
|
||||
INSERT INTO test_now VALUES
|
||||
(now(), 1), (now() + INTERVAL '1 hour', 1),
|
||||
(now() + INTERVAL '2 hour', 2 ), (now() + INTERVAL '3 hour', 3);
|
||||
SELECT count(*) FILTER (WHERE time = now()), count(*) FILTER (WHERE time < now()),
|
||||
count(*) FILTER (WHERE time > now()) FROM test_now;
|
||||
count | count | count
|
||||
-------+-------+-------
|
||||
1 | 0 | 3
|
||||
(1 row)
|
||||
|
||||
EXECUTE test_query;
|
||||
count | count | count
|
||||
-------+-------+-------
|
||||
1 | 0 | 3
|
||||
(1 row)
|
||||
|
||||
-- Also test different code paths used with aggregation pushdown.
|
||||
-- We can't run EXPLAIN here, because now() is different every time. But the
|
||||
-- strict equality should be enough to detect if now() is being erroneously
|
||||
-- evaluated on data node, where it will differ from time to time.
|
||||
SELECT count(*) FROM test_now WHERE time = now();
|
||||
count
|
||||
-------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
COMMIT;
|
||||
-- now() will be different in a new transaction.
|
||||
BEGIN;
|
||||
SELECT count(*) FILTER (WHERE time = now()), count(*) FILTER (WHERE time < now()),
|
||||
count(*) FILTER (WHERE time > now()) FROM test_now;
|
||||
count | count | count
|
||||
-------+-------+-------
|
||||
0 | 1 | 3
|
||||
(1 row)
|
||||
|
||||
EXECUTE test_query;
|
||||
count | count | count
|
||||
-------+-------+-------
|
||||
0 | 1 | 3
|
||||
(1 row)
|
||||
|
||||
SELECT count(*) FROM test_now WHERE time = now();
|
||||
count
|
||||
-------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
COMMIT;
|
||||
DROP TABLE test_now;
|
||||
DEALLOCATE test_query;
|
||||
-- Cleanup
|
||||
DROP DATABASE :DATA_NODE_1;
|
||||
DROP DATABASE :DATA_NODE_2;
|
||||
DROP DATABASE :DATA_NODE_3;
|
||||
|
@ -5249,6 +5249,373 @@ NOTICE: adding not-null constraint to column "time"
|
||||
(1 row)
|
||||
|
||||
DROP TABLE test;
|
||||
-- Test that stable functions are calculated on the access node.
|
||||
--
|
||||
-- As a stable function to test, use the timestamp -> timestamptz conversion
|
||||
-- that is stable because it uses the current timezone.
|
||||
-- We have to be careful about `timestamp < timestamptz` comparison. From postgres
|
||||
-- docs:
|
||||
-- When comparing a timestamp without time zone to a timestamp with time zone,
|
||||
-- the former value is assumed to be given in the time zone specified by the
|
||||
-- TimeZone configuration parameter, and is rotated to UTC for comparison to
|
||||
-- the latter value (which is already in UTC internally).
|
||||
-- We don't want this to happen on data node, so we cast the filter value to
|
||||
-- timestamp, and check that this cast happens on the access node and uses the
|
||||
-- current timezone.
|
||||
SELECT test.tsl_override_current_timestamptz(null);
|
||||
tsl_override_current_timestamptz
|
||||
----------------------------------
|
||||
|
||||
(1 row)
|
||||
|
||||
CREATE TABLE test_tz (time timestamp, v int);
|
||||
SELECT create_distributed_hypertable('test_tz','time',
|
||||
chunk_time_interval => interval '1 hour');
|
||||
NOTICE: adding not-null constraint to column "time"
|
||||
create_distributed_hypertable
|
||||
-------------------------------
|
||||
(31,public,test_tz,t)
|
||||
(1 row)
|
||||
|
||||
INSERT INTO test_tz VALUES ('2018-01-02 12:00:00', 2), ('2018-01-02 11:00:00', 1),
|
||||
('2018-01-02 13:00:00', 3), ('2018-01-02 14:00:00', 4);
|
||||
SET TIME ZONE 'Etc/GMT';
|
||||
SELECT '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
timestamp
|
||||
--------------------------
|
||||
Tue Jan 02 12:00:00 2018
|
||||
(1 row)
|
||||
|
||||
-- Normal WHERE clause on baserel
|
||||
SELECT * FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
time | v
|
||||
--------------------------+---
|
||||
Tue Jan 02 13:00:00 2018 | 3
|
||||
Tue Jan 02 14:00:00 2018 | 4
|
||||
(2 rows)
|
||||
|
||||
EXPLAIN (verbose, costs off)
|
||||
SELECT * FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
QUERY PLAN
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Custom Scan (AsyncAppend)
|
||||
Output: test_tz."time", test_tz.v
|
||||
-> Append
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_1
|
||||
Output: test_tz_1."time", test_tz_1.v
|
||||
Data node: db_dist_hypertable_1
|
||||
Chunks: _dist_hyper_31_97_chunk
|
||||
Remote SQL: SELECT "time", v FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[45]) AND (("time" > '2018-01-02 12:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Index Scan using _dist_hyper_31_97_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_97_chunk
|
||||
Output: _dist_hyper_31_97_chunk."time", _dist_hyper_31_97_chunk.v
|
||||
Index Cond: (_dist_hyper_31_97_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_2
|
||||
Output: test_tz_2."time", test_tz_2.v
|
||||
Data node: db_dist_hypertable_2
|
||||
Chunks: _dist_hyper_31_95_chunk, _dist_hyper_31_96_chunk, _dist_hyper_31_98_chunk
|
||||
Remote SQL: SELECT "time", v FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[43, 44, 45]) AND (("time" > '2018-01-02 12:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Append
|
||||
-> Index Scan using _dist_hyper_31_95_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_95_chunk
|
||||
Output: _dist_hyper_31_95_chunk."time", _dist_hyper_31_95_chunk.v
|
||||
Index Cond: (_dist_hyper_31_95_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
-> Index Scan using _dist_hyper_31_98_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_98_chunk
|
||||
Output: _dist_hyper_31_98_chunk."time", _dist_hyper_31_98_chunk.v
|
||||
Index Cond: (_dist_hyper_31_98_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
|
||||
(27 rows)
|
||||
|
||||
-- Also test different code paths used with aggregation pushdown.
|
||||
SELECT count(*) FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
count
|
||||
-------
|
||||
2
|
||||
(1 row)
|
||||
|
||||
EXPLAIN (verbose, costs off)
|
||||
SELECT count(*) FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
QUERY PLAN
|
||||
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Aggregate
|
||||
Output: count(*)
|
||||
-> Custom Scan (AsyncAppend)
|
||||
-> Append
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_1
|
||||
Data node: db_dist_hypertable_1
|
||||
Chunks: _dist_hyper_31_97_chunk
|
||||
Remote SQL: SELECT NULL FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[45]) AND (("time" > '2018-01-02 12:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Result
|
||||
Output: NULL::text
|
||||
-> Index Only Scan using _dist_hyper_31_97_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_97_chunk
|
||||
Index Cond: (_dist_hyper_31_97_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_2
|
||||
Data node: db_dist_hypertable_2
|
||||
Chunks: _dist_hyper_31_95_chunk, _dist_hyper_31_96_chunk, _dist_hyper_31_98_chunk
|
||||
Remote SQL: SELECT NULL FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[43, 44, 45]) AND (("time" > '2018-01-02 12:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Result
|
||||
Output: NULL::text
|
||||
-> Append
|
||||
-> Index Only Scan using _dist_hyper_31_95_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_95_chunk
|
||||
Index Cond: (_dist_hyper_31_95_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
-> Index Only Scan using _dist_hyper_31_98_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_98_chunk
|
||||
Index Cond: (_dist_hyper_31_98_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
|
||||
(27 rows)
|
||||
|
||||
-- TODO: test HAVING here and in the later now() tests as well.
|
||||
-- Change the timezone and check that the conversion is applied correctly.
|
||||
SET TIME ZONE 'Etc/GMT+1';
|
||||
SELECT '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
timestamp
|
||||
--------------------------
|
||||
Tue Jan 02 11:00:00 2018
|
||||
(1 row)
|
||||
|
||||
SELECT * FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
time | v
|
||||
--------------------------+---
|
||||
Tue Jan 02 13:00:00 2018 | 3
|
||||
Tue Jan 02 12:00:00 2018 | 2
|
||||
Tue Jan 02 14:00:00 2018 | 4
|
||||
(3 rows)
|
||||
|
||||
EXPLAIN (verbose, costs off)
|
||||
SELECT * FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
QUERY PLAN
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Custom Scan (AsyncAppend)
|
||||
Output: test_tz."time", test_tz.v
|
||||
-> Append
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_1
|
||||
Output: test_tz_1."time", test_tz_1.v
|
||||
Data node: db_dist_hypertable_1
|
||||
Chunks: _dist_hyper_31_97_chunk
|
||||
Remote SQL: SELECT "time", v FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[45]) AND (("time" > '2018-01-02 11:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Index Scan using _dist_hyper_31_97_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_97_chunk
|
||||
Output: _dist_hyper_31_97_chunk."time", _dist_hyper_31_97_chunk.v
|
||||
Index Cond: (_dist_hyper_31_97_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_2
|
||||
Output: test_tz_2."time", test_tz_2.v
|
||||
Data node: db_dist_hypertable_2
|
||||
Chunks: _dist_hyper_31_95_chunk, _dist_hyper_31_96_chunk, _dist_hyper_31_98_chunk
|
||||
Remote SQL: SELECT "time", v FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[43, 44, 45]) AND (("time" > '2018-01-02 11:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Append
|
||||
-> Index Scan using _dist_hyper_31_95_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_95_chunk
|
||||
Output: _dist_hyper_31_95_chunk."time", _dist_hyper_31_95_chunk.v
|
||||
Index Cond: (_dist_hyper_31_95_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
-> Index Scan using _dist_hyper_31_96_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_96_chunk
|
||||
Output: _dist_hyper_31_96_chunk."time", _dist_hyper_31_96_chunk.v
|
||||
Index Cond: (_dist_hyper_31_96_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
-> Index Scan using _dist_hyper_31_98_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_98_chunk
|
||||
Output: _dist_hyper_31_98_chunk."time", _dist_hyper_31_98_chunk.v
|
||||
Index Cond: (_dist_hyper_31_98_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
|
||||
(30 rows)
|
||||
|
||||
SELECT count(*) FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
count
|
||||
-------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
EXPLAIN (verbose, costs off)
|
||||
SELECT count(*) FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
QUERY PLAN
|
||||
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Aggregate
|
||||
Output: count(*)
|
||||
-> Custom Scan (AsyncAppend)
|
||||
-> Append
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_1
|
||||
Data node: db_dist_hypertable_1
|
||||
Chunks: _dist_hyper_31_97_chunk
|
||||
Remote SQL: SELECT NULL FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[45]) AND (("time" > '2018-01-02 11:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Result
|
||||
Output: NULL::text
|
||||
-> Index Only Scan using _dist_hyper_31_97_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_97_chunk
|
||||
Index Cond: (_dist_hyper_31_97_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_2
|
||||
Data node: db_dist_hypertable_2
|
||||
Chunks: _dist_hyper_31_95_chunk, _dist_hyper_31_96_chunk, _dist_hyper_31_98_chunk
|
||||
Remote SQL: SELECT NULL FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[43, 44, 45]) AND (("time" > '2018-01-02 11:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Result
|
||||
Output: NULL::text
|
||||
-> Append
|
||||
-> Index Only Scan using _dist_hyper_31_95_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_95_chunk
|
||||
Index Cond: (_dist_hyper_31_95_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
-> Index Only Scan using _dist_hyper_31_96_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_96_chunk
|
||||
Index Cond: (_dist_hyper_31_96_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
-> Index Only Scan using _dist_hyper_31_98_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_98_chunk
|
||||
Index Cond: (_dist_hyper_31_98_chunk."time" > '2018-01-02 11:00:00'::timestamp without time zone)
|
||||
|
||||
(29 rows)
|
||||
|
||||
-- Conversion to timestamptz cannot be evaluated at the access node, because the
|
||||
-- argument is a column reference.
|
||||
SELECT count(*) FROM test_tz WHERE time::timestamptz > now();
|
||||
count
|
||||
-------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
-- According to our docs, JIT is not recommended for use on access node in
|
||||
-- multi-node environment. Turn it off so that it doesn't ruin EXPLAIN for the
|
||||
-- next query.
|
||||
SET jit = 0;
|
||||
-- Test that operators are evaluated as well. Comparison of timestamp with
|
||||
-- timestamptz is a stable operator, and comparison of two timestamps is an
|
||||
-- immutable operator. This also test that immutable functions using these
|
||||
-- operators as arguments are evaluated.
|
||||
EXPLAIN (verbose, costs off)
|
||||
WITH dummy AS (SELECT '2018-01-02 12:00:00 +00'::timestamptz::timestamp x)
|
||||
SELECT * FROM test_tz, dummy
|
||||
WHERE time > x
|
||||
+ (x = x)::int -- stable
|
||||
* (x = '2018-01-02 11:00:00'::timestamp)::int -- immutable
|
||||
* INTERVAL '1 hour';
|
||||
QUERY PLAN
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Custom Scan (AsyncAppend)
|
||||
Output: test_tz."time", test_tz.v, (('Tue Jan 02 11:00:00 2018 -01'::timestamp with time zone)::timestamp without time zone)
|
||||
-> Append
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_1
|
||||
Output: test_tz_1."time", test_tz_1.v, ('Tue Jan 02 11:00:00 2018 -01'::timestamp with time zone)::timestamp without time zone
|
||||
Data node: db_dist_hypertable_1
|
||||
Chunks: _dist_hyper_31_97_chunk
|
||||
Remote SQL: SELECT "time", v FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[45]) AND (("time" > '2018-01-02 12:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Index Scan using _dist_hyper_31_97_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_97_chunk
|
||||
Output: _dist_hyper_31_97_chunk."time", _dist_hyper_31_97_chunk.v
|
||||
Index Cond: (_dist_hyper_31_97_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
|
||||
-> Custom Scan (DataNodeScan) on public.test_tz test_tz_2
|
||||
Output: test_tz_2."time", test_tz_2.v, ('Tue Jan 02 11:00:00 2018 -01'::timestamp with time zone)::timestamp without time zone
|
||||
Data node: db_dist_hypertable_2
|
||||
Chunks: _dist_hyper_31_95_chunk, _dist_hyper_31_96_chunk, _dist_hyper_31_98_chunk
|
||||
Remote SQL: SELECT "time", v FROM public.test_tz WHERE _timescaledb_internal.chunks_in(public.test_tz.*, ARRAY[43, 44, 45]) AND (("time" > '2018-01-02 12:00:00'::timestamp without time zone))
|
||||
Remote EXPLAIN:
|
||||
Append
|
||||
-> Index Scan using _dist_hyper_31_95_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_95_chunk
|
||||
Output: _dist_hyper_31_95_chunk."time", _dist_hyper_31_95_chunk.v
|
||||
Index Cond: (_dist_hyper_31_95_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
-> Index Scan using _dist_hyper_31_98_chunk_test_tz_time_idx on _timescaledb_internal._dist_hyper_31_98_chunk
|
||||
Output: _dist_hyper_31_98_chunk."time", _dist_hyper_31_98_chunk.v
|
||||
Index Cond: (_dist_hyper_31_98_chunk."time" > '2018-01-02 12:00:00'::timestamp without time zone)
|
||||
|
||||
(27 rows)
|
||||
|
||||
-- Reference value for the above test.
|
||||
WITH dummy AS (SELECT '2018-01-02 12:00:00 +00'::timestamptz::timestamp x)
|
||||
SELECT x + (x = x)::int * (x = '2018-01-02 11:00:00'::timestamp)::int * INTERVAL '1 hour'
|
||||
FROM dummy;
|
||||
?column?
|
||||
--------------------------
|
||||
Tue Jan 02 12:00:00 2018
|
||||
(1 row)
|
||||
|
||||
-- Check that the test function for partly overriding now() works. It's very
|
||||
-- hacky and only has effect when we estimate some costs or evaluate sTABLE
|
||||
-- functions in quals on access node, and has no effect in other cases.
|
||||
-- Consider deleting it altogether.
|
||||
SELECT test.tsl_override_current_timestamptz('2018-01-02 12:00:00 +00'::timestamptz);
|
||||
tsl_override_current_timestamptz
|
||||
----------------------------------
|
||||
|
||||
(1 row)
|
||||
|
||||
SELECT count(*) FROM test_tz WHERE time > now();
|
||||
count
|
||||
-------
|
||||
3
|
||||
(1 row)
|
||||
|
||||
SELECT test.tsl_override_current_timestamptz(null);
|
||||
tsl_override_current_timestamptz
|
||||
----------------------------------
|
||||
|
||||
(1 row)
|
||||
|
||||
RESET TIME ZONE;
|
||||
DROP TABLE test_tz;
|
||||
-- Check that now() is evaluated on the access node. Also check that it is evaluated
|
||||
-- anew on every execution of a prepared statement.
|
||||
CREATE TABLE test_now (time timestamp, v int);
|
||||
SELECT create_distributed_hypertable('test_now','time',
|
||||
chunk_time_interval => interval '1 hour');
|
||||
NOTICE: adding not-null constraint to column "time"
|
||||
create_distributed_hypertable
|
||||
-------------------------------
|
||||
(32,public,test_now,t)
|
||||
(1 row)
|
||||
|
||||
PREPARE test_query as
|
||||
SELECT count(*) FILTER (WHERE time = now()), count(*) FILTER (WHERE time < now()),
|
||||
count(*) FILTER (WHERE time > now()) FROM test_now;
|
||||
;
|
||||
BEGIN; -- to fix the value of now();
|
||||
INSERT INTO test_now VALUES
|
||||
(now(), 1), (now() + INTERVAL '1 hour', 1),
|
||||
(now() + INTERVAL '2 hour', 2 ), (now() + INTERVAL '3 hour', 3);
|
||||
SELECT count(*) FILTER (WHERE time = now()), count(*) FILTER (WHERE time < now()),
|
||||
count(*) FILTER (WHERE time > now()) FROM test_now;
|
||||
count | count | count
|
||||
-------+-------+-------
|
||||
1 | 0 | 3
|
||||
(1 row)
|
||||
|
||||
EXECUTE test_query;
|
||||
count | count | count
|
||||
-------+-------+-------
|
||||
1 | 0 | 3
|
||||
(1 row)
|
||||
|
||||
-- Also test different code paths used with aggregation pushdown.
|
||||
-- We can't run EXPLAIN here, because now() is different every time. But the
|
||||
-- strict equality should be enough to detect if now() is being erroneously
|
||||
-- evaluated on data node, where it will differ from time to time.
|
||||
SELECT count(*) FROM test_now WHERE time = now();
|
||||
count
|
||||
-------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
COMMIT;
|
||||
-- now() will be different in a new transaction.
|
||||
BEGIN;
|
||||
SELECT count(*) FILTER (WHERE time = now()), count(*) FILTER (WHERE time < now()),
|
||||
count(*) FILTER (WHERE time > now()) FROM test_now;
|
||||
count | count | count
|
||||
-------+-------+-------
|
||||
0 | 1 | 3
|
||||
(1 row)
|
||||
|
||||
EXECUTE test_query;
|
||||
count | count | count
|
||||
-------+-------+-------
|
||||
0 | 1 | 3
|
||||
(1 row)
|
||||
|
||||
SELECT count(*) FROM test_now WHERE time = now();
|
||||
count
|
||||
-------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
COMMIT;
|
||||
DROP TABLE test_now;
|
||||
DEALLOCATE test_query;
|
||||
-- Cleanup
|
||||
DROP DATABASE :DATA_NODE_1;
|
||||
DROP DATABASE :DATA_NODE_2;
|
||||
DROP DATABASE :DATA_NODE_3;
|
||||
|
@ -33,7 +33,7 @@ SET client_min_messages TO notice;
|
||||
-- Please see the included NOTICE for copyright information and
|
||||
-- LICENSE-TIMESCALE for a copy of the license.
|
||||
CREATE OR REPLACE FUNCTION test.tsl_override_current_timestamptz(new_value TIMESTAMPTZ)
|
||||
RETURNS VOID AS :TSL_MODULE_PATHNAME, 'ts_test_override_current_timestamptz' LANGUAGE C VOLATILE STRICT;
|
||||
RETURNS VOID AS :TSL_MODULE_PATHNAME, 'ts_test_override_current_timestamptz' LANGUAGE C VOLATILE;
|
||||
\set DN_DBNAME_1 :TEST_DBNAME _1
|
||||
\set DN_DBNAME_2 :TEST_DBNAME _2
|
||||
\set DN_DBNAME_3 :TEST_DBNAME _3
|
||||
|
@ -1764,6 +1764,147 @@ CREATE TABLE test (time timestamp, v int);
|
||||
SELECT create_distributed_hypertable('test','time');
|
||||
DROP TABLE test;
|
||||
|
||||
|
||||
-- Test that stable functions are calculated on the access node.
|
||||
--
|
||||
-- As a stable function to test, use the timestamp -> timestamptz conversion
|
||||
-- that is stable because it uses the current timezone.
|
||||
-- We have to be careful about `timestamp < timestamptz` comparison. From postgres
|
||||
-- docs:
|
||||
-- When comparing a timestamp without time zone to a timestamp with time zone,
|
||||
-- the former value is assumed to be given in the time zone specified by the
|
||||
-- TimeZone configuration parameter, and is rotated to UTC for comparison to
|
||||
-- the latter value (which is already in UTC internally).
|
||||
-- We don't want this to happen on data node, so we cast the filter value to
|
||||
-- timestamp, and check that this cast happens on the access node and uses the
|
||||
-- current timezone.
|
||||
|
||||
SELECT test.tsl_override_current_timestamptz(null);
|
||||
CREATE TABLE test_tz (time timestamp, v int);
|
||||
SELECT create_distributed_hypertable('test_tz','time',
|
||||
chunk_time_interval => interval '1 hour');
|
||||
INSERT INTO test_tz VALUES ('2018-01-02 12:00:00', 2), ('2018-01-02 11:00:00', 1),
|
||||
('2018-01-02 13:00:00', 3), ('2018-01-02 14:00:00', 4);
|
||||
|
||||
SET TIME ZONE 'Etc/GMT';
|
||||
|
||||
SELECT '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
|
||||
-- Normal WHERE clause on baserel
|
||||
SELECT * FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
|
||||
EXPLAIN (verbose, costs off)
|
||||
SELECT * FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
|
||||
-- Also test different code paths used with aggregation pushdown.
|
||||
SELECT count(*) FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
|
||||
EXPLAIN (verbose, costs off)
|
||||
SELECT count(*) FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
|
||||
-- TODO: test HAVING here and in the later now() tests as well.
|
||||
|
||||
-- Change the timezone and check that the conversion is applied correctly.
|
||||
SET TIME ZONE 'Etc/GMT+1';
|
||||
SELECT '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
|
||||
SELECT * FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
|
||||
EXPLAIN (verbose, costs off)
|
||||
SELECT * FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
|
||||
SELECT count(*) FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
|
||||
EXPLAIN (verbose, costs off)
|
||||
SELECT count(*) FROM test_tz WHERE time > '2018-01-02 12:00:00 +00'::timestamptz::timestamp;
|
||||
|
||||
-- Conversion to timestamptz cannot be evaluated at the access node, because the
|
||||
-- argument is a column reference.
|
||||
SELECT count(*) FROM test_tz WHERE time::timestamptz > now();
|
||||
|
||||
-- According to our docs, JIT is not recommended for use on access node in
|
||||
-- multi-node environment. Turn it off so that it doesn't ruin EXPLAIN for the
|
||||
-- next query.
|
||||
SET jit = 0;
|
||||
|
||||
-- Test that operators are evaluated as well. Comparison of timestamp with
|
||||
-- timestamptz is a stable operator, and comparison of two timestamps is an
|
||||
-- immutable operator. This also test that immutable functions using these
|
||||
-- operators as arguments are evaluated.
|
||||
EXPLAIN (verbose, costs off)
|
||||
WITH dummy AS (SELECT '2018-01-02 12:00:00 +00'::timestamptz::timestamp x)
|
||||
SELECT * FROM test_tz, dummy
|
||||
WHERE time > x
|
||||
+ (x = x)::int -- stable
|
||||
* (x = '2018-01-02 11:00:00'::timestamp)::int -- immutable
|
||||
* INTERVAL '1 hour';
|
||||
|
||||
-- Reference value for the above test.
|
||||
WITH dummy AS (SELECT '2018-01-02 12:00:00 +00'::timestamptz::timestamp x)
|
||||
SELECT x + (x = x)::int * (x = '2018-01-02 11:00:00'::timestamp)::int * INTERVAL '1 hour'
|
||||
FROM dummy;
|
||||
|
||||
|
||||
-- Check that the test function for partly overriding now() works. It's very
|
||||
-- hacky and only has effect when we estimate some costs or evaluate sTABLE
|
||||
-- functions in quals on access node, and has no effect in other cases.
|
||||
-- Consider deleting it altogether.
|
||||
SELECT test.tsl_override_current_timestamptz('2018-01-02 12:00:00 +00'::timestamptz);
|
||||
SELECT count(*) FROM test_tz WHERE time > now();
|
||||
SELECT test.tsl_override_current_timestamptz(null);
|
||||
|
||||
RESET TIME ZONE;
|
||||
DROP TABLE test_tz;
|
||||
|
||||
-- Check that now() is evaluated on the access node. Also check that it is evaluated
|
||||
-- anew on every execution of a prepared statement.
|
||||
CREATE TABLE test_now (time timestamp, v int);
|
||||
|
||||
SELECT create_distributed_hypertable('test_now','time',
|
||||
chunk_time_interval => interval '1 hour');
|
||||
|
||||
PREPARE test_query as
|
||||
SELECT count(*) FILTER (WHERE time = now()), count(*) FILTER (WHERE time < now()),
|
||||
count(*) FILTER (WHERE time > now()) FROM test_now;
|
||||
;
|
||||
|
||||
BEGIN; -- to fix the value of now();
|
||||
|
||||
INSERT INTO test_now VALUES
|
||||
(now(), 1), (now() + INTERVAL '1 hour', 1),
|
||||
(now() + INTERVAL '2 hour', 2 ), (now() + INTERVAL '3 hour', 3);
|
||||
|
||||
SELECT count(*) FILTER (WHERE time = now()), count(*) FILTER (WHERE time < now()),
|
||||
count(*) FILTER (WHERE time > now()) FROM test_now;
|
||||
|
||||
EXECUTE test_query;
|
||||
|
||||
-- Also test different code paths used with aggregation pushdown.
|
||||
-- We can't run EXPLAIN here, because now() is different every time. But the
|
||||
-- strict equality should be enough to detect if now() is being erroneously
|
||||
-- evaluated on data node, where it will differ from time to time.
|
||||
SELECT count(*) FROM test_now WHERE time = now();
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- now() will be different in a new transaction.
|
||||
BEGIN;
|
||||
|
||||
SELECT count(*) FILTER (WHERE time = now()), count(*) FILTER (WHERE time < now()),
|
||||
count(*) FILTER (WHERE time > now()) FROM test_now;
|
||||
|
||||
EXECUTE test_query;
|
||||
|
||||
SELECT count(*) FROM test_now WHERE time = now();
|
||||
|
||||
COMMIT;
|
||||
|
||||
DROP TABLE test_now;
|
||||
|
||||
DEALLOCATE test_query;
|
||||
|
||||
|
||||
-- Cleanup
|
||||
DROP DATABASE :DATA_NODE_1;
|
||||
DROP DATABASE :DATA_NODE_2;
|
||||
DROP DATABASE :DATA_NODE_3;
|
||||
|
@ -3,4 +3,4 @@
|
||||
-- LICENSE-TIMESCALE for a copy of the license.
|
||||
|
||||
CREATE OR REPLACE FUNCTION test.tsl_override_current_timestamptz(new_value TIMESTAMPTZ)
|
||||
RETURNS VOID AS :TSL_MODULE_PATHNAME, 'ts_test_override_current_timestamptz' LANGUAGE C VOLATILE STRICT;
|
||||
RETURNS VOID AS :TSL_MODULE_PATHNAME, 'ts_test_override_current_timestamptz' LANGUAGE C VOLATILE;
|
||||
|
@ -14,7 +14,14 @@ Datum
|
||||
ts_test_override_current_timestamptz(PG_FUNCTION_ARGS)
|
||||
{
|
||||
#ifdef TS_DEBUG
|
||||
fdw_scan_debug_override_current_timestamp(PG_GETARG_INT64(0));
|
||||
if (PG_ARGISNULL(0))
|
||||
{
|
||||
fdw_scan_debug_override_current_timestamp(-1);
|
||||
}
|
||||
else
|
||||
{
|
||||
fdw_scan_debug_override_current_timestamp(PG_GETARG_INT64(0));
|
||||
}
|
||||
PG_RETURN_VOID();
|
||||
#else
|
||||
elog(ERROR, "unable to handle ts_test_is_frontend_session without TS_DEBUG flag set");
|
||||
|
Loading…
x
Reference in New Issue
Block a user