1
0
mirror of https://github.com/timescale/timescaledb.git synced 2025-05-20 20:54:29 +08:00

Use prepared statements for parameterized data node scans

This allows us to avoid replanning the inner query on each new loop,
speeding up the joins.
This commit is contained in:
Alexander Kuzmenkov 2023-03-15 17:27:32 +04:00
parent 03a799b874
commit 827684f3e2
42 changed files with 2685 additions and 1180 deletions

@ -24,3 +24,9 @@ a4356f342f1732857a1d8057f71219b50f1919b2
# Cosmetic changes to create.c
230f368f4e5d146ce5f919cc5999b236997befaf
# Adding python and yaml linters
9133319081aef92705f1405087822fc281d215d4
44cd71a602ba96029001de6e97a1b44488730080
f75a51def79796ff7fef58ec950c859fe4e71618
21a3f8206c0de98932867096637c7d1e3d04d925

@ -42,9 +42,10 @@ static const struct config_enum_entry telemetry_level_options[] = {
#endif
static const struct config_enum_entry remote_data_fetchers[] = {
{ "auto", AutoFetcherType, false },
{ "copy", CopyFetcherType, false },
{ "cursor", CursorFetcherType, false },
{ "auto", AutoFetcherType, false },
{ "prepared", PreparedStatementFetcherType, false },
{ NULL, 0, false }
};
@ -337,8 +338,7 @@ _guc_init(void)
DefineCustomBoolVariable("timescaledb.enable_parameterized_data_node_scan",
"Enable parameterized data node scans",
"Disable this as a workaround in case these plans are incorrectly "
"chosen "
"by the query planner when they are suboptimal",
"chosen by the query planner when they are suboptimal",
&ts_guc_enable_parameterized_data_node_scan,
true,
PGC_USERSET,

@ -61,9 +61,10 @@ extern TSDLLEXPORT bool ts_guc_enable_compression_indexscan;
typedef enum DataFetcherType
{
CursorFetcherType,
AutoFetcherType = 1, /* Skip 0 to better catch uninitialized values. */
CopyFetcherType,
AutoFetcherType,
CursorFetcherType,
PreparedStatementFetcherType,
} DataFetcherType;
extern TSDLLEXPORT DataFetcherType ts_guc_remote_data_fetcher;

@ -538,12 +538,14 @@ timescaledb_planner(Query *parse, int cursor_opts, ParamListInfo bound_params)
if (context.num_distributed_tables >= 2)
{
if (ts_guc_remote_data_fetcher == CopyFetcherType)
if (ts_guc_remote_data_fetcher != CursorFetcherType &&
ts_guc_remote_data_fetcher != AutoFetcherType)
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY fetcher not supported"),
errhint("COPY fetching of data is not supported in "
errmsg("only cursor fetcher is supported for this query"),
errhint("COPY or prepared statement fetching of data is not "
"supported in "
"queries with multiple distributed hypertables."
" Use cursor fetcher instead.")));
}
@ -561,6 +563,8 @@ timescaledb_planner(Query *parse, int cursor_opts, ParamListInfo bound_params)
}
}
}
Assert(ts_data_node_fetcher_scan_type != AutoFetcherType);
}
if (prev_planner_hook != NULL)

@ -87,7 +87,7 @@ ts_test_error_injection(PG_FUNCTION_ARGS)
}
static int
throw_after_n_rows(int max_rows, int severity)
transaction_row_counter(void)
{
static LocalTransactionId last_lxid = 0;
static int rows_seen = 0;
@ -99,7 +99,13 @@ throw_after_n_rows(int max_rows, int severity)
last_lxid = MyProc->lxid;
}
rows_seen++;
return rows_seen++;
}
static int
throw_after_n_rows(int max_rows, int severity)
{
int rows_seen = transaction_row_counter();
if (max_rows <= rows_seen)
{
@ -124,6 +130,24 @@ ts_debug_shippable_fatal_after_n_rows(PG_FUNCTION_ARGS)
PG_RETURN_INT32(throw_after_n_rows(PG_GETARG_INT32(0), FATAL));
}
/*
* After how many rows should we error out according to the user-set option.
*/
static int
get_error_after_rows()
{
int error_after = 7103; /* default is an arbitrary prime */
const char *error_after_option =
GetConfigOption("timescaledb.debug_broken_sendrecv_error_after", true, false);
if (error_after_option)
{
error_after = pg_strtoint32(error_after_option);
}
return error_after;
}
/*
* Broken send/receive functions for int4 that throw after an (arbitrarily
* chosen prime or configured) number of rows.
@ -131,21 +155,12 @@ ts_debug_shippable_fatal_after_n_rows(PG_FUNCTION_ARGS)
static void
broken_sendrecv_throw()
{
int throw_after = 7103; /* an arbitrary prime */
const char *throw_after_option =
GetConfigOption("timescaledb.debug_broken_sendrecv_throw_after", true, false);
if (throw_after_option)
{
throw_after = pg_strtoint32(throw_after_option);
}
/*
* Use ERROR, not FATAL, because PG versions < 14 are unable to report a
* FATAL error to the access node before closing the connection, so the test
* results would be different.
*/
(void) throw_after_n_rows(throw_after, ERROR);
(void) throw_after_n_rows(get_error_after_rows(), ERROR);
}
TS_FUNCTION_INFO_V1(ts_debug_broken_int4recv);
@ -166,11 +181,25 @@ ts_debug_broken_int4send(PG_FUNCTION_ARGS)
return int4send(fcinfo);
}
TS_FUNCTION_INFO_V1(ts_debug_sleepy_int4recv);
/* An incorrect int4out that sometimes returns not a number. */
TS_FUNCTION_INFO_V1(ts_debug_incorrect_int4out);
/* Sleep after some rows. */
Datum
ts_debug_sleepy_int4recv(PG_FUNCTION_ARGS)
ts_debug_incorrect_int4out(PG_FUNCTION_ARGS)
{
int rows_seen = transaction_row_counter();
if (rows_seen >= get_error_after_rows())
{
PG_RETURN_CSTRING("surprise");
}
return int4out(fcinfo);
}
/* Sleeps after a certain number of calls. */
static void
ts_debug_sleepy_function()
{
static LocalTransactionId last_lxid = 0;
static int rows_seen = 0;
@ -184,7 +213,7 @@ ts_debug_sleepy_int4recv(PG_FUNCTION_ARGS)
rows_seen++;
if (rows_seen >= 1000)
if (rows_seen >= 997)
{
(void) WaitLatch(MyLatch,
WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
@ -194,10 +223,26 @@ ts_debug_sleepy_int4recv(PG_FUNCTION_ARGS)
rows_seen = 0;
}
}
TS_FUNCTION_INFO_V1(ts_debug_sleepy_int4recv);
Datum
ts_debug_sleepy_int4recv(PG_FUNCTION_ARGS)
{
ts_debug_sleepy_function();
return int4recv(fcinfo);
}
TS_FUNCTION_INFO_V1(ts_debug_sleepy_int4send);
Datum
ts_debug_sleepy_int4send(PG_FUNCTION_ARGS)
{
ts_debug_sleepy_function();
return int4send(fcinfo);
}
TS_FUNCTION_INFO_V1(ts_bgw_wait);
Datum
ts_bgw_wait(PG_FUNCTION_ARGS)

@ -44,6 +44,9 @@ data_node_scan_begin(CustomScanState *node, EState *estate, int eflags)
List *fdw_exprs = linitial(cscan->custom_exprs);
List *recheck_quals = lsecond(cscan->custom_exprs);
List *fdw_private = list_nth(cscan->custom_private, DataNodeScanFdwPrivate);
sss->fsstate.planned_fetcher_type =
intVal(list_nth(cscan->custom_private, DataNodeScanFetcherType));
Assert(sss->fsstate.planned_fetcher_type != AutoFetcherType);
if ((eflags & EXEC_FLAG_EXPLAIN_ONLY) && !ts_guc_enable_remote_explain)
return;
@ -167,5 +170,6 @@ data_node_scan_state_create(CustomScan *cscan)
dnss->async_state.fetch_data = fetch_data;
dnss->fsstate.planned_fetcher_type =
intVal(list_nth(cscan->custom_private, DataNodeScanFetcherType));
Assert(dnss->fsstate.planned_fetcher_type != AutoFetcherType);
return (Node *) dnss;
}

@ -1669,11 +1669,34 @@ data_node_scan_plan_create(PlannerInfo *root, RelOptInfo *rel, CustomPath *best_
"columns.")));
/* Should have determined the fetcher type by now. */
Assert(ts_data_node_fetcher_scan_type != AutoFetcherType);
DataFetcherType fetcher_type = ts_data_node_fetcher_scan_type;
Assert(fetcher_type != AutoFetcherType);
/* Check if we should use prepared statement data fetcher. */
if (fetcher_type == CopyFetcherType && list_length(scaninfo.params_list) > 0 &&
ts_guc_remote_data_fetcher == AutoFetcherType)
{
/*
* The path is parameterized by either Nested Loop params or InitPlan
* params. We can distinguish the join by presence of Path.param_info.
*
* For joins, it is optimal to use Prepared Statement fetcher, because
* this plan is likely to be ran multiple times, and this avoids
* re-planning the query on each inner loop.
*
* For InitPlans, COPY fetcher would be more optimal. Now it's not
* technically possible to use it, because the COPY statements cannot be
* parameterized. We need support for this case in deparsing, to encode
* the parameter values into the query itself. For now, also use the
* Prepared Statement fetcher for this case, because it does not prevent
* parallelism, unlike Cursor.
*/
fetcher_type = PreparedStatementFetcherType;
}
cscan->custom_private = list_make3(scaninfo.fdw_private,
list_make1_int(scaninfo.systemcol),
makeInteger(ts_data_node_fetcher_scan_type));
makeInteger(fetcher_type));
return &cscan->scan.plan;
}

@ -168,6 +168,13 @@ begin_foreign_scan(ForeignScanState *node, int eflags)
node->fdw_state = (TsFdwScanState *) palloc0(sizeof(TsFdwScanState));
/*
* This is a per-chunk FDW scan, not per-data-node scan, so we're going to
* scan multiple tables per data node, so we only can use the cursor data
* fetcher.
*/
((TsFdwScanState *) node->fdw_state)->planned_fetcher_type = CursorFetcherType;
fdw_scan_init(&node->ss,
node->fdw_state,
fsplan->fs_relids,

@ -20,6 +20,7 @@
#include "utils.h"
#include "remote/data_fetcher.h"
#include "remote/copy_fetcher.h"
#include "remote/prepared_statement_fetcher.h"
#include "remote/cursor_fetcher.h"
#include "guc.h"
#include "planner.h"
@ -114,6 +115,7 @@ create_data_fetcher(ScanState *ss, TsFdwScanState *fsstate)
{
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
fill_query_params_array(econtext, fsstate->param_flinfo, fsstate->param_exprs, values);
MemoryContextSwitchTo(oldcontext);
/*
* Notice that we do not specify param types, thus forcing the data
@ -124,58 +126,21 @@ create_data_fetcher(ScanState *ss, TsFdwScanState *fsstate)
* types.
*/
params = stmt_params_create_from_values(values, num_params);
MemoryContextSwitchTo(oldcontext);
}
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
TupleFactory *tf = tuplefactory_create_for_scan(ss, fsstate->retrieved_attrs);
if (!tuplefactory_is_binary(tf) && fsstate->planned_fetcher_type == CopyFetcherType)
{
if (ts_guc_remote_data_fetcher == AutoFetcherType)
{
/*
* The user-set fetcher type was auto, and the planner decided to
* use COPY fetcher, but at execution time (now) we found out
* there is no binary serialization for some data types. In this
* case we can revert to cursor fetcher which supports text
* serialization.
*/
fsstate->planned_fetcher_type = CursorFetcherType;
}
else
{
ereport(ERROR,
(errmsg("cannot use COPY fetcher because some of the column types do not "
"have binary serialization")));
}
}
/*
* COPY fetcher uses COPY statement that don't work with prepared
* statements. If this plan is parameterized, this means we'll have to
* revert to cursor fetcher.
*/
if (num_params > 0 && fsstate->planned_fetcher_type == CopyFetcherType)
{
if (ts_guc_remote_data_fetcher == AutoFetcherType)
{
fsstate->planned_fetcher_type = CursorFetcherType;
}
else
{
ereport(ERROR,
(errmsg("cannot use COPY fetcher because the plan is parameterized"),
errhint("Set \"timescaledb.remote_data_fetcher\" to \"cursor\" to explicitly "
"set the fetcher type or use \"auto\" to select the fetcher type "
"automatically.")));
}
}
if (fsstate->planned_fetcher_type == CursorFetcherType)
{
fetcher = cursor_fetcher_create_for_scan(fsstate->conn, fsstate->query, params, tf);
fetcher =
cursor_fetcher_create_for_scan(fsstate->conn, fsstate->query, params, fsstate->tf);
}
else if (fsstate->planned_fetcher_type == PreparedStatementFetcherType)
{
fetcher = prepared_statement_fetcher_create_for_scan(fsstate->conn,
fsstate->query,
params,
fsstate->tf);
}
else
{
@ -184,7 +149,7 @@ create_data_fetcher(ScanState *ss, TsFdwScanState *fsstate)
* point, so we shouldn't see 'auto' here.
*/
Assert(fsstate->planned_fetcher_type == CopyFetcherType);
fetcher = copy_fetcher_create_for_scan(fsstate->conn, fsstate->query, params, tf);
fetcher = copy_fetcher_create_for_scan(fsstate->conn, fsstate->query, params, fsstate->tf);
}
fsstate->fetcher = fetcher;
@ -319,6 +284,58 @@ fdw_scan_init(ScanState *ss, TsFdwScanState *fsstate, Bitmapset *scanrelids, Lis
&fsstate->param_values);
fsstate->fetcher = NULL;
fsstate->tf = tuplefactory_create_for_scan(ss, fsstate->retrieved_attrs);
Assert(fsstate->planned_fetcher_type != AutoFetcherType);
/*
* If the planner tells us to use the cursor fetcher because there are
* multiple distributed hypertables per query, we have no other option.
*/
if (fsstate->planned_fetcher_type == CursorFetcherType)
{
return;
}
if (!tuplefactory_is_binary(fsstate->tf) && fsstate->planned_fetcher_type == CopyFetcherType)
{
if (ts_guc_remote_data_fetcher == AutoFetcherType)
{
/*
* The user-set fetcher type was auto, and the planner decided to
* use COPY fetcher, but at execution time (now) we found out
* there is no binary serialization for some data types. In this
* case we can revert to cursor fetcher which supports text
* serialization.
*/
fsstate->planned_fetcher_type = CursorFetcherType;
}
else
{
ereport(ERROR,
(errmsg("cannot use COPY fetcher because some of the column types do not "
"have binary serialization")));
}
}
/*
* COPY fetcher uses COPY statement that don't work with prepared
* statements. We only end up here in case the COPY fetcher was chosen by
* the user, so error out.
* Note that this can be optimized for parameters coming from initplans,
* where the parameter takes only one value and technically we could deparse
* it into the query string and use a non-parameterized COPY statement.
*/
if (num_params > 0 && fsstate->planned_fetcher_type == CopyFetcherType)
{
Assert(ts_guc_remote_data_fetcher == CopyFetcherType);
ereport(ERROR,
(errmsg("cannot use COPY fetcher because the plan is parameterized"),
errhint("Set \"timescaledb.remote_data_fetcher\" to \"cursor\" to explicitly "
"set the fetcher type or use \"auto\" to select the fetcher type "
"automatically.")));
}
}
TupleTableSlot *
@ -343,6 +360,7 @@ fdw_scan_rescan(ScanState *ss, TsFdwScanState *fsstate)
/* If we haven't created the cursor yet, nothing to do. */
if (NULL == fsstate->fetcher)
return;
/*
* If any internal parameters affecting this node have changed, we'd
* better destroy and recreate the cursor. Otherwise, rewinding it should
@ -351,11 +369,33 @@ fdw_scan_rescan(ScanState *ss, TsFdwScanState *fsstate)
*/
if (ss->ps.chgParam != NULL)
{
data_fetcher_free(fsstate->fetcher);
fsstate->fetcher = NULL;
int num_params = fsstate->num_params;
Assert(num_params > 0);
ExprContext *econtext = ss->ps.ps_ExprContext;
/*
* Construct array of query parameter values in text format.
*/
const char **values = fsstate->param_values;
fill_query_params_array(econtext, fsstate->param_flinfo, fsstate->param_exprs, values);
/*
* Notice that we do not specify param types, thus forcing the data
* node to infer types for all parameters. Since we explicitly cast
* every parameter (see deparse.c), the "inference" is trivial and
* will produce the desired result. This allows us to avoid assuming
* that the data node has the same OIDs we do for the parameters'
* types.
*/
StmtParams *params = stmt_params_create_from_values(values, num_params);
fetcher->funcs->rescan(fsstate->fetcher, params);
}
else
{
fetcher->funcs->rewind(fsstate->fetcher);
}
}
void
@ -448,6 +488,8 @@ explain_fetcher_type(DataFetcherType type)
return "COPY";
case CursorFetcherType:
return "Cursor";
case PreparedStatementFetcherType:
return "Prepared statement";
default:
Assert(false);
return "";

@ -31,7 +31,8 @@ typedef struct TsFdwScanState
List *retrieved_attrs; /* list of retrieved attribute numbers */
/* for remote query execution */
struct TSConnection *conn; /* connection for the scan */
struct TSConnection *conn; /* connection for the scan */
TupleFactory *tf;
struct DataFetcher *fetcher; /* fetches tuples from data node */
int num_params; /* number of parameters passed to query */
FmgrInfo *param_flinfo; /* output conversion functions for them */
@ -39,10 +40,13 @@ typedef struct TsFdwScanState
const char **param_values; /* textual values of query parameters */
int fetch_size; /* number of tuples per fetch */
/*
* The type of data fetcher to use. Note that we still can revert to
* cursor fetcher if COPY fetcher was chosen automatically, but binary
* serialization turns out to be unavailable for some of the data types. We
* only check this when we execute the query.
* The type of data fetcher to use as determined by the planner. Can be
* either Cursor when there are multiple distributed hypertables, or COPY.
* Note that we still can revert to cursor fetcher if binary serialization
* is unavailable for some data types. We can also prefer the prepared
* statement data fetcher when the query is parameterized. We only check
* this when we execute the query.
*
*/
DataFetcherType planned_fetcher_type;
int row_counter;

@ -2,6 +2,7 @@ set(SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/async.c
${CMAKE_CURRENT_SOURCE_DIR}/connection.c
${CMAKE_CURRENT_SOURCE_DIR}/connection_cache.c
${CMAKE_CURRENT_SOURCE_DIR}/copy_fetcher.c
${CMAKE_CURRENT_SOURCE_DIR}/cursor_fetcher.c
${CMAKE_CURRENT_SOURCE_DIR}/data_fetcher.c
${CMAKE_CURRENT_SOURCE_DIR}/data_format.c
@ -9,14 +10,15 @@ set(SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/dist_commands.c
${CMAKE_CURRENT_SOURCE_DIR}/dist_copy.c
${CMAKE_CURRENT_SOURCE_DIR}/dist_ddl.c
${CMAKE_CURRENT_SOURCE_DIR}/prepared_statement_fetcher.c
${CMAKE_CURRENT_SOURCE_DIR}/copy_fetcher.c
${CMAKE_CURRENT_SOURCE_DIR}/healthcheck.c
${CMAKE_CURRENT_SOURCE_DIR}/stmt_params.c
${CMAKE_CURRENT_SOURCE_DIR}/tuplefactory.c
${CMAKE_CURRENT_SOURCE_DIR}/txn.c
${CMAKE_CURRENT_SOURCE_DIR}/txn_store.c
${CMAKE_CURRENT_SOURCE_DIR}/txn_id.c
${CMAKE_CURRENT_SOURCE_DIR}/txn_resolve.c
${CMAKE_CURRENT_SOURCE_DIR}/txn_store.c
${CMAKE_CURRENT_SOURCE_DIR}/utils.c)
target_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES})
target_include_directories(${TSL_LIBRARY_NAME} PRIVATE ${PG_INCLUDEDIR})

@ -190,6 +190,18 @@ fill_simple_error(TSConnectionError *err, int errcode, const char *errmsg, const
err->errcode = errcode;
err->msg = errmsg;
if (err->msg == NULL || strlen(err->msg) == 0)
{
char *connection_message = PQerrorMessage(conn->pg_conn);
if (connection_message)
{
err->msg = pstrdup(connection_message);
}
else
{
err->msg = "unknown error";
}
}
err->host = pstrdup(PQhost(conn->pg_conn));
err->nodename = pstrdup(remote_connection_node_name(conn));
@ -324,8 +336,32 @@ fill_result_error(TSConnectionError *err, int errcode, const char *errmsg, const
err->remote.hint = get_error_field_copy(res, PG_DIAG_MESSAGE_HINT);
err->remote.context = get_error_field_copy(res, PG_DIAG_CONTEXT);
err->remote.stmtpos = get_error_field_copy(res, PG_DIAG_STATEMENT_POSITION);
if (err->remote.msg == NULL)
err->remote.msg = pstrdup(PQresultErrorMessage(res));
/*
* Try to find at least some non-empty error message. The result error
* message may be not set if a node segfaults.
*/
if (err->remote.msg == NULL || strlen(err->remote.msg) == 0)
{
char *result_message = PQresultErrorMessage(res);
if (result_message && strlen(result_message))
{
err->remote.msg = pstrdup(result_message);
}
}
if (err->remote.msg == NULL || strlen(err->remote.msg) == 0)
{
char *connection_message = PQerrorMessage(entry->conn->pg_conn);
if (connection_message && strlen(connection_message))
{
err->remote.msg = pstrdup(connection_message);
}
}
if (err->remote.msg == NULL || strlen(err->remote.msg) == 0)
{
err->remote.msg = "unknown error";
}
sqlstate = err->remote.sqlstate;
@ -708,6 +744,11 @@ static const char *default_connection_options[] = {
"SET datestyle = ISO",
"SET intervalstyle = postgres",
"SET extra_float_digits = 3",
/*
* Prepared statement data fetcher sets it to "force" which might be
* suboptimal for other kinds of queries.
*/
"RESET plan_cache_mode",
"SET statement_timeout = 0",
NULL,
};
@ -910,13 +951,13 @@ remote_connection_node_name(const TSConnection *conn)
void
remote_connection_get_error(const TSConnection *conn, TSConnectionError *err)
{
fill_connection_error(err, ERRCODE_CONNECTION_FAILURE, "", conn);
fill_connection_error(err, ERRCODE_CONNECTION_FAILURE, NULL, conn);
}
void
remote_connection_get_result_error(const PGresult *res, TSConnectionError *err)
{
fill_result_error(err, ERRCODE_CONNECTION_EXCEPTION, "", res);
fill_result_error(err, ERRCODE_CONNECTION_EXCEPTION, NULL, res);
}
static long
@ -2265,13 +2306,13 @@ remote_connection_begin_copy(TSConnection *conn, const char *copycmd, bool binar
#ifndef NDEBUG
/* Set some variables for testing. */
const char *throw_after_option =
GetConfigOption("timescaledb.debug_broken_sendrecv_throw_after", true, false);
if (throw_after_option)
const char *error_after_option =
GetConfigOption("timescaledb.debug_broken_sendrecv_error_after", true, false);
if (error_after_option)
{
res = PQexec(pg_conn,
psprintf("set timescaledb.debug_broken_sendrecv_throw_after = '%s';",
throw_after_option));
psprintf("set timescaledb.debug_broken_sendrecv_error_after = '%s';",
error_after_option));
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
remote_connection_get_result_error(res, err);

@ -31,17 +31,18 @@ static int copy_fetcher_fetch_data(DataFetcher *df);
static void copy_fetcher_set_fetch_size(DataFetcher *df, int fetch_size);
static void copy_fetcher_set_tuple_memcontext(DataFetcher *df, MemoryContext mctx);
static void copy_fetcher_store_next_tuple(DataFetcher *df, TupleTableSlot *slot);
static void copy_fetcher_rescan(DataFetcher *df);
static void copy_fetcher_rewind(DataFetcher *df);
static void copy_fetcher_close(DataFetcher *df);
static DataFetcherFuncs funcs = {
.send_fetch_request = copy_fetcher_send_fetch_request,
.close = copy_fetcher_close,
.fetch_data = copy_fetcher_fetch_data,
.rescan = data_fetcher_rescan,
.rewind = copy_fetcher_rewind,
.send_fetch_request = copy_fetcher_send_fetch_request,
.set_fetch_size = copy_fetcher_set_fetch_size,
.set_tuple_mctx = copy_fetcher_set_tuple_memcontext,
.store_next_tuple = copy_fetcher_store_next_tuple,
.rewind = copy_fetcher_rescan,
.close = copy_fetcher_close,
};
static void
@ -113,7 +114,7 @@ copy_fetcher_send_fetch_request(DataFetcher *df)
* Single-row mode doesn't really influence the COPY queries, but setting
* it here is a convenient way to prevent concurrent COPY requests on the
* same connection. This can happen if we have multiple tables on the same
* data node and still use the row-by-row fetcher.
* data node and still use the COPY fetcher.
*/
if (!async_request_set_single_row_mode(req))
{
@ -122,7 +123,7 @@ copy_fetcher_send_fetch_request(DataFetcher *df)
errmsg("could not set single-row mode on connection to \"%s\"",
remote_connection_node_name(fetcher->state.conn)),
errdetail("The aborted statement is: %s.", fetcher->state.stmt),
errhint("Copy fetcher is not supported together with sub-queries."
errhint("COPY fetcher is not supported together with sub-queries."
" Use cursor fetcher instead.")));
}
@ -544,6 +545,12 @@ copy_fetcher_complete(CopyFetcher *fetcher)
attconv->typmods[att]);
nulls[att] = false;
}
/*
* We expect one row per message here, check that no data is
* left.
*/
Assert(copy_data.cursor = copy_data.len);
}
MemoryContextSwitchTo(fetcher->state.batch_mctx);
PQfreemem(copy_data.data);
@ -670,7 +677,7 @@ copy_fetcher_close(DataFetcher *df)
}
static void
copy_fetcher_rescan(DataFetcher *df)
copy_fetcher_rewind(DataFetcher *df)
{
CopyFetcher *fetcher = cast_fetcher(CopyFetcher, df);

@ -55,13 +55,14 @@ static void cursor_fetcher_rewind(DataFetcher *df);
static void cursor_fetcher_close(DataFetcher *df);
static DataFetcherFuncs funcs = {
.send_fetch_request = cursor_fetcher_send_fetch_request,
.close = cursor_fetcher_close,
.fetch_data = cursor_fetcher_fetch_data,
.rescan = data_fetcher_rescan,
.rewind = cursor_fetcher_rewind,
.send_fetch_request = cursor_fetcher_send_fetch_request,
.set_fetch_size = cursor_fetcher_set_fetch_size,
.set_tuple_mctx = cursor_fetcher_set_tuple_memcontext,
.store_next_tuple = cursor_fetcher_store_next_tuple,
.rewind = cursor_fetcher_rewind,
.close = cursor_fetcher_close,
};
static void
@ -327,7 +328,14 @@ cursor_fetcher_fetch_data(DataFetcher *df)
return 0;
if (!cursor->state.open)
{
if (cursor->create_req == NULL)
{
cursor_create_req(cursor);
}
cursor_fetcher_wait_until_open(df);
}
if (cursor->state.data_req == NULL)
cursor_fetcher_send_fetch_request(df);

@ -28,11 +28,13 @@ data_fetcher_init(DataFetcher *df, TSConnection *conn, const char *stmt, StmtPar
df->tf = tf;
tuplefactory_set_per_tuple_mctx_reset(df->tf, false);
df->batch_mctx =
AllocSetContextCreate(CurrentMemoryContext, "cursor tuple data", ALLOCSET_DEFAULT_SIZES);
df->batch_mctx = AllocSetContextCreate(CurrentMemoryContext,
"data fetcher tuple batch data",
ALLOCSET_DEFAULT_SIZES);
df->tuple_mctx = df->batch_mctx;
df->req_mctx =
AllocSetContextCreate(CurrentMemoryContext, "async req/resp", ALLOCSET_DEFAULT_SIZES);
df->req_mctx = AllocSetContextCreate(CurrentMemoryContext,
"data fetcher async request/response",
ALLOCSET_DEFAULT_SIZES);
df->fetch_size = DEFAULT_FETCH_SIZE;
}
@ -44,7 +46,7 @@ data_fetcher_validate(DataFetcher *df)
if (df->next_tuple_idx != 0 && df->next_tuple_idx < df->num_tuples)
ereport(ERROR,
(errcode(ERRCODE_TS_INTERNAL_ERROR),
errmsg("invalid cursor state. sql: %s", df->stmt),
errmsg("invalid data fetcher state. sql: %s", df->stmt),
errhint("Shouldn't fetch new data before consuming existing.")));
}
@ -112,6 +114,19 @@ data_fetcher_reset(DataFetcher *df)
MemoryContextReset(df->batch_mctx);
}
/*
* This is the default implementation of starting the scan with the new
* parameters. It just closes the current scan and updates the parameter
* values, and the next scan is initialized from scratch. The prepared statement
* fetcher is more efficient than that, and reuses the prepared statement.
*/
void
data_fetcher_rescan(DataFetcher *df, StmtParams *params)
{
df->funcs->close(df);
df->stmt_params = params;
}
void
data_fetcher_free(DataFetcher *df)
{

@ -20,17 +20,33 @@ typedef struct DataFetcher DataFetcher;
typedef struct DataFetcherFuncs
{
void (*close)(DataFetcher *data_fetcher);
/*
* Read data in response to a fetch request. If no request has been sent,
* send it first.
*/
int (*fetch_data)(DataFetcher *data_fetcher);
/*
* Restart the parameterized remote scan with the new parameter values.
*/
void (*rescan)(DataFetcher *data_fetcher, StmtParams *params);
/*
* Restart the non-parameterized remote scan. This happens in some nested
* loop-type plans. Ideally we should materialize the data locally in this
* case, probably on plan level by putting a Materialize node above it.
*/
void (*rewind)(DataFetcher *data_fetcher);
/* Send a request for new data. This doesn't read the data itself */
void (*send_fetch_request)(DataFetcher *data_fetcher);
/* Read data in response to a fetch request. If no request has been sent,
* send it first. */
int (*fetch_data)(DataFetcher *data_fetcher);
/* Set the fetch (batch) size */
void (*set_fetch_size)(DataFetcher *data_fetcher, int fetch_size);
void (*set_tuple_mctx)(DataFetcher *data_fetcher, MemoryContext mctx);
void (*store_next_tuple)(DataFetcher *data_fetcher, TupleTableSlot *slot);
void (*rewind)(DataFetcher *data_fetcher);
void (*close)(DataFetcher *data_fetcher);
} DataFetcherFuncs;
typedef struct DataFetcher
@ -71,6 +87,7 @@ extern void data_fetcher_set_fetch_size(DataFetcher *df, int fetch_size);
extern void data_fetcher_set_tuple_mctx(DataFetcher *df, MemoryContext mctx);
extern void data_fetcher_validate(DataFetcher *df);
extern void data_fetcher_reset(DataFetcher *df);
extern void data_fetcher_rescan(DataFetcher *df, StmtParams *params);
#ifdef USE_ASSERT_CHECKING
static inline DataFetcher *

@ -0,0 +1,416 @@
/*
* This file and its contents are licensed under the Timescale License.
* Please see the included NOTICE for copyright information and
* LICENSE-TIMESCALE for a copy of the license.
*/
#include <postgres.h>
#include "prepared_statement_fetcher.h"
#include "tuplefactory.h"
#include "async.h"
typedef struct PreparedStatementFetcher
{
DataFetcher state;
/* Data for virtual tuples of the current retrieved batch. */
Datum *batch_values;
bool *batch_nulls;
} PreparedStatementFetcher;
static void prepared_statement_fetcher_send_fetch_request(DataFetcher *df);
static void prepared_statement_fetcher_reset(PreparedStatementFetcher *fetcher);
static int prepared_statement_fetcher_fetch_data(DataFetcher *df);
static void prepared_statement_fetcher_set_fetch_size(DataFetcher *df, int fetch_size);
static void prepared_statement_fetcher_set_tuple_memcontext(DataFetcher *df, MemoryContext mctx);
static void prepared_statement_fetcher_store_next_tuple(DataFetcher *df, TupleTableSlot *slot);
static void prepared_statement_fetcher_rewind(DataFetcher *df);
static void prepared_statement_fetcher_rescan(DataFetcher *df, StmtParams *params);
static void prepared_statement_fetcher_close(DataFetcher *df);
static DataFetcherFuncs funcs = {
.close = prepared_statement_fetcher_close,
.fetch_data = prepared_statement_fetcher_fetch_data,
.rescan = prepared_statement_fetcher_rescan,
.rewind = prepared_statement_fetcher_rewind,
.send_fetch_request = prepared_statement_fetcher_send_fetch_request,
.set_fetch_size = prepared_statement_fetcher_set_fetch_size,
.set_tuple_mctx = prepared_statement_fetcher_set_tuple_memcontext,
.store_next_tuple = prepared_statement_fetcher_store_next_tuple,
};
static void
prepared_statement_fetcher_set_fetch_size(DataFetcher *df, int fetch_size)
{
PreparedStatementFetcher *fetcher = cast_fetcher(PreparedStatementFetcher, df);
data_fetcher_set_fetch_size(&fetcher->state, fetch_size);
}
static void
prepared_statement_fetcher_set_tuple_memcontext(DataFetcher *df, MemoryContext mctx)
{
PreparedStatementFetcher *fetcher = cast_fetcher(PreparedStatementFetcher, df);
data_fetcher_set_tuple_mctx(&fetcher->state, mctx);
}
static void
prepared_statement_fetcher_reset(PreparedStatementFetcher *fetcher)
{
/* Drain the connection, reporting any errors. */
TSConnection *conn = fetcher->state.conn;
PGresult *res;
while ((res = remote_connection_get_result(conn, TS_NO_TIMEOUT)) != NULL)
{
char *sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE);
if (sqlstate != NULL && strcmp(sqlstate, "00000") == 0)
{
remote_result_elog(res, ERROR);
}
PQclear(res);
}
fetcher->state.open = false;
data_fetcher_reset(&fetcher->state);
}
static void
prepared_statement_fetcher_send_fetch_request(DataFetcher *df)
{
PreparedStatementFetcher *fetcher = cast_fetcher(PreparedStatementFetcher, df);
if (fetcher->state.open)
{
/* data request has already been sent */
Assert(fetcher->state.data_req != NULL);
return;
}
/* make sure to have a clean state */
prepared_statement_fetcher_reset(fetcher);
TSConnection *conn = fetcher->state.conn;
if (remote_connection_get_status(conn) != CONN_IDLE)
{
elog(ERROR, "unexpected activity on data node connection when sending fetch request");
}
PGresult *pgres = remote_connection_get_result(conn, TS_NO_TIMEOUT);
if (pgres != NULL)
{
char *sqlstate = PQresultErrorField(pgres, PG_DIAG_SQLSTATE);
if (sqlstate != NULL && strcmp(sqlstate, "00000") == 0)
{
remote_result_elog(pgres, ERROR);
}
elog(ERROR,
"unexpected activity on data node connection when sending fetch request "
"(PQresultStatus %d)",
PQresultStatus(pgres));
}
PGconn *pg_conn = remote_connection_get_pg_conn(conn);
int ret = PQsendQueryPrepared(pg_conn,
/* stmtName = */ "",
stmt_params_num_params(fetcher->state.stmt_params),
stmt_params_values(fetcher->state.stmt_params),
stmt_params_lengths(fetcher->state.stmt_params),
stmt_params_formats(fetcher->state.stmt_params),
tuplefactory_is_binary(fetcher->state.tf) ? FORMAT_BINARY :
FORMAT_TEXT);
if (ret != 1)
{
TSConnectionError err;
remote_connection_get_error(conn, &err);
remote_connection_error_elog(&err, ERROR);
}
if (!remote_connection_set_single_row_mode(conn))
ereport(ERROR,
(errcode(ERRCODE_CONNECTION_FAILURE),
errmsg("could not set single-row mode on connection to \"%s\"",
remote_connection_node_name(fetcher->state.conn)),
errdetail("The aborted statement is: %s.", fetcher->state.stmt),
errhint("Row-by-row fetching of data is not supported together with sub-queries."
" Use cursor fetcher instead.")));
fetcher->state.data_req = (void *) 1;
fetcher->state.open = true;
}
/*
* Process response for ongoing async request
*/
static int
prepared_statement_fetcher_complete(PreparedStatementFetcher *fetcher)
{
MemoryContext oldcontext;
Assert(fetcher->state.open);
Assert(fetcher->state.data_req != NULL);
data_fetcher_validate(&fetcher->state);
/*
* We'll store the tuples in the batch_mctx. First, flush the previous
* batch.
*/
MemoryContextReset(fetcher->state.batch_mctx);
oldcontext = MemoryContextSwitchTo(fetcher->state.batch_mctx);
const int nattrs = tuplefactory_get_nattrs(fetcher->state.tf);
const int total = nattrs * fetcher->state.fetch_size;
fetcher->batch_nulls = palloc(sizeof(bool) * total);
for (int i = 0; i < total; i++)
{
fetcher->batch_nulls[i] = true;
}
fetcher->batch_values = palloc0(sizeof(Datum) * total);
TSConnection *conn = fetcher->state.conn;
PGconn *pg_conn = remote_connection_get_pg_conn(conn);
if (PQsetnonblocking(pg_conn, 0) != 0)
{
remote_connection_elog(conn, ERROR);
}
PG_TRY();
{
int i;
for (i = 0; i < fetcher->state.fetch_size; i++)
{
PGresult *res;
res = remote_connection_get_result(conn, TS_NO_TIMEOUT);
if (!(PQresultStatus(res) == PGRES_SINGLE_TUPLE ||
PQresultStatus(res) == PGRES_TUPLES_OK))
{
remote_result_elog(res, ERROR);
}
if (PQresultStatus(res) == PGRES_TUPLES_OK)
{
/* fetched all the data */
Assert(PQntuples(res) == 0);
PQclear(res);
fetcher->state.eof = true;
break;
}
Assert(PQresultStatus(res) == PGRES_SINGLE_TUPLE);
/* Allow creating tuples in alternative memory context if user has set
* it explicitly, otherwise same as batch_mctx */
MemoryContextSwitchTo(fetcher->state.tuple_mctx);
PG_USED_FOR_ASSERTS_ONLY ItemPointer ctid =
tuplefactory_make_virtual_tuple(fetcher->state.tf,
res,
0,
PQbinaryTuples(res),
&fetcher->batch_values[i * nattrs],
&fetcher->batch_nulls[i * nattrs]);
/*
* This fetcher uses virtual tuples that can't hold ctid, so if we're
* receiving a ctid here, we're doing something wrong.
*/
Assert(ctid == NULL);
PQclear(res);
}
/* We need to manually reset the context since we've turned off per tuple reset */
tuplefactory_reset_mctx(fetcher->state.tf);
fetcher->state.num_tuples = i;
fetcher->state.next_tuple_idx = 0;
fetcher->state.batch_count++;
if (fetcher->state.eof)
{
fetcher->state.data_req = NULL;
}
}
PG_CATCH();
{
if (NULL != fetcher->state.data_req)
{
fetcher->state.data_req = NULL;
}
PG_RE_THROW();
}
PG_END_TRY();
MemoryContextSwitchTo(oldcontext);
return fetcher->state.num_tuples;
}
static int
prepared_statement_fetcher_fetch_data(DataFetcher *df)
{
PreparedStatementFetcher *fetcher = cast_fetcher(PreparedStatementFetcher, df);
if (fetcher->state.eof)
return 0;
if (!fetcher->state.open)
prepared_statement_fetcher_send_fetch_request(df);
return prepared_statement_fetcher_complete(fetcher);
}
static void
prepared_statement_fetcher_store_tuple(DataFetcher *df, int row, TupleTableSlot *slot)
{
PreparedStatementFetcher *fetcher = cast_fetcher(PreparedStatementFetcher, df);
ExecClearTuple(slot);
if (row >= df->num_tuples)
{
if (df->eof || df->funcs->fetch_data(df) == 0)
{
return;
}
row = 0;
Assert(row == df->next_tuple_idx);
}
Assert(fetcher->batch_values != NULL);
Assert(fetcher->batch_nulls != NULL);
Assert(row >= 0 && row < df->num_tuples);
const int nattrs = tuplefactory_get_nattrs(fetcher->state.tf);
slot->tts_values = &fetcher->batch_values[nattrs * row];
slot->tts_isnull = &fetcher->batch_nulls[nattrs * row];
ExecStoreVirtualTuple(slot);
}
static void
prepared_statement_fetcher_store_next_tuple(DataFetcher *df, TupleTableSlot *slot)
{
prepared_statement_fetcher_store_tuple(df, df->next_tuple_idx, slot);
if (!TupIsNull(slot))
df->next_tuple_idx++;
Assert(df->next_tuple_idx <= df->num_tuples);
}
DataFetcher *
prepared_statement_fetcher_create_for_scan(TSConnection *conn, const char *stmt, StmtParams *params,
TupleFactory *tf)
{
PreparedStatementFetcher *fetcher = palloc0(sizeof(PreparedStatementFetcher));
data_fetcher_init(&fetcher->state, conn, stmt, params, tf);
fetcher->state.type = PreparedStatementFetcherType;
fetcher->state.funcs = &funcs;
PGconn *pg_conn = remote_connection_get_pg_conn(conn);
if (remote_connection_get_status(conn) != CONN_IDLE)
{
elog(ERROR,
"unexpected activity on data node connection when creating the row-by-row fetcher");
}
/*
* Force using the generic plan for each execution of the data node query,
* because it would be very expensive and pointless to replan it for each
* subsequent parameter value.
*/
PGresult *res = remote_connection_exec(conn, "SET plan_cache_mode = 'force_generic_plan'");
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
TSConnectionError err;
remote_connection_get_result_error(res, &err);
remote_connection_error_elog(&err, ERROR);
}
PQclear(res);
if (1 != PQsendPrepare(pg_conn,
/* stmtName = */ "",
stmt,
stmt_params_num_params(params),
/* paramTypes = */ NULL))
{
TSConnectionError err;
remote_connection_get_error(conn, &err);
remote_connection_error_elog(&err, ERROR);
}
res = remote_connection_get_result(conn, TS_NO_TIMEOUT);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
TSConnectionError err;
remote_connection_get_result_error(res, &err);
remote_connection_error_elog(&err, ERROR);
}
PQclear(res);
return &fetcher->state;
}
static void
prepared_statement_fetcher_close(DataFetcher *df)
{
PreparedStatementFetcher *fetcher = cast_fetcher(PreparedStatementFetcher, df);
if (fetcher->state.open)
{
if (fetcher->state.data_req != NULL)
{
fetcher->state.data_req = NULL;
}
prepared_statement_fetcher_reset(fetcher);
}
else
{
Assert(fetcher->state.data_req == NULL);
Assert(fetcher->state.num_tuples == 0);
#ifdef USE_ASSERT_CHECKING
TSConnection *conn = fetcher->state.conn;
PGconn *pg_conn = remote_connection_get_pg_conn(conn);
Assert(PQtransactionStatus(pg_conn) != PQTRANS_ACTIVE);
#endif
}
PGresult *res = remote_connection_exec(fetcher->state.conn, "RESET plan_cache_mode");
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
TSConnectionError err;
remote_connection_get_result_error(res, &err);
remote_connection_error_elog(&err, ERROR);
}
PQclear(res);
}
static void
prepared_statement_fetcher_rewind(DataFetcher *df)
{
PreparedStatementFetcher *fetcher = cast_fetcher(PreparedStatementFetcher, df);
if (fetcher->state.batch_count > 1)
/* we're over the first batch so we need to reset fetcher and restart from clean state */
prepared_statement_fetcher_reset(fetcher);
else
/* we can reuse current batch of results */
fetcher->state.next_tuple_idx = 0;
}
static void
prepared_statement_fetcher_rescan(DataFetcher *df, StmtParams *params)
{
PreparedStatementFetcher *fetcher = cast_fetcher(PreparedStatementFetcher, df);
prepared_statement_fetcher_reset(fetcher);
df->stmt_params = params;
}

@ -0,0 +1,17 @@
/*
* This file and its contents are licensed under the Timescale License.
* Please see the included NOTICE for copyright information and
* LICENSE-TIMESCALE for a copy of the license.
*/
#ifndef TIMESCALEDB_TSL_PREPARED_STATEMENT_FETCHER_H
#define TIMESCALEDB_TSL_PREPARED_STATEMENT_FETCHER_H
#include <postgres.h>
#include "data_fetcher.h"
extern DataFetcher *prepared_statement_fetcher_create_for_scan(TSConnection *conn, const char *stmt,
StmtParams *params,
TupleFactory *tf);
#endif /* TIMESCALEDB_TSL_PREPARED_STATEMENT_FETCHER_H */

@ -8,9 +8,8 @@
\set TEST_BASE_NAME data_fetcher
SELECT format('include/%s_run.sql', :'TEST_BASE_NAME') as "TEST_QUERY_NAME",
format('%s/results/%s_results_cursor.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as "TEST_RESULTS_CURSOR",
format('%s/results/%s_results_copy.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as "TEST_RESULTS_COPY"
\gset
SELECT format('\! diff %s %s', :'TEST_RESULTS_CURSOR', :'TEST_RESULTS_COPY') as "DIFF_CMD"
format('%s/results/%s_results_copy.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as "TEST_RESULTS_COPY",
format('%s/results/%s_results_prepared.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as "TEST_RESULTS_PREPARED"
\gset
SET ROLE :ROLE_CLUSTER_SUPERUSER;
SELECT node_name, database, node_created, database_created, extension_created
@ -104,6 +103,30 @@ SELECT count(*), count(value) FROM one_batch;
SELECT count(*), count(value) FROM one_batch_default;
\o
-- compare results
SELECT format('\! diff %s %s', :'TEST_RESULTS_CURSOR', :'TEST_RESULTS_COPY') as "DIFF_CMD"
\gset
:DIFF_CMD
-- run queries using prepares statement fetcher
SET timescaledb.remote_data_fetcher = 'prepared';
\o :TEST_RESULTS_PREPARED
\ir :TEST_QUERY_NAME
-- This file and its contents are licensed under the Timescale License.
-- Please see the included NOTICE for copyright information and
-- LICENSE-TIMESCALE for a copy of the license.
ANALYZE disttable;
SELECT count(*) FROM disttable;
SELECT time_bucket('1 hour', time) AS time, device, avg(temp)
FROM disttable
GROUP BY 1,2
ORDER BY 1,2;
-- Test for #5323 - ensure that no NULL tuples are generated
-- if the last element of the batch is the file trailer.
SELECT count(*), count(value) FROM one_batch;
SELECT count(*), count(value) FROM one_batch_default;
\o
-- compare results
SELECT format('\! diff %s %s', :'TEST_RESULTS_CURSOR', :'TEST_RESULTS_PREPARED') as "DIFF_CMD"
\gset
:DIFF_CMD
-- Test custom FDW settings. Instead of the tests above, we are not interersted
-- in comparing the results of the fetchers. In the following tests we are

@ -19,6 +19,7 @@ grant usage on foreign server data_node_1 to public;
grant create on schema public to :ROLE_1;
set role :ROLE_1;
reset client_min_messages;
\set ON_ERROR_STOP 0
-- helper function: float -> pseudorandom float [0..1].
create or replace function mix(x float4) returns float4 as $$ select ((hashfloat4(x) / (pow(2., 31) - 1) + 1) / 2)::float4 $$ language sql;
-- distributed hypertable
@ -154,6 +155,122 @@ order by id
(21 rows)
reset timescaledb.enable_parameterized_data_node_scan;
-- All fetcher types with join
set timescaledb.remote_data_fetcher = 'copy';
select id, max(value), count(*)
from metric_dist
where id in (select id from metric_name where name like 'cpu%')
and ts between '2022-02-02 02:02:02+03' and '2022-03-03 02:02:02+03'
group by id
order by id
;
ERROR: cannot use COPY fetcher because the plan is parameterized
set timescaledb.remote_data_fetcher = 'cursor';
select id, max(value), count(*)
from metric_dist
where id in (select id from metric_name where name like 'cpu%')
and ts between '2022-02-02 02:02:02+03' and '2022-03-03 02:02:02+03'
group by id
order by id
;
id | max | count
----+------------------+-------
1 | 49.9941974878311 | 4174
3 | 49.9958902597427 | 4119
7 | 49.9881327152252 | 4316
(3 rows)
set timescaledb.remote_data_fetcher = 'prepared';
select id, max(value), count(*)
from metric_dist
where id in (select id from metric_name where name like 'cpu%')
and ts between '2022-02-02 02:02:02+03' and '2022-03-03 02:02:02+03'
group by id
order by id
;
id | max | count
----+------------------+-------
1 | 49.9941974878311 | 4174
3 | 49.9958902597427 | 4119
7 | 49.9881327152252 | 4316
(3 rows)
-- All fetcher types with initplan
set timescaledb.remote_data_fetcher = 'copy';
select id, max(value), count(*)
from metric_dist
where id = any((select array_agg(id) from metric_name where name like 'cpu%')::int[])
and ts between '2022-02-02 02:02:02+03' and '2022-03-03 02:02:02+03'
group by id
order by id
;
ERROR: cannot use COPY fetcher because the plan is parameterized
set timescaledb.remote_data_fetcher = 'cursor';
select id, max(value), count(*)
from metric_dist
where id = any((select array_agg(id) from metric_name where name like 'cpu%')::int[])
and ts between '2022-02-02 02:02:02+03' and '2022-03-03 02:02:02+03'
group by id
order by id
;
id | max | count
----+------------------+-------
1 | 49.9941974878311 | 4174
3 | 49.9958902597427 | 4119
7 | 49.9881327152252 | 4316
(3 rows)
set timescaledb.remote_data_fetcher = 'prepared';
select id, max(value), count(*)
from metric_dist
where id = any((select array_agg(id) from metric_name where name like 'cpu%')::int[])
and ts between '2022-02-02 02:02:02+03' and '2022-03-03 02:02:02+03'
group by id
order by id
;
id | max | count
----+------------------+-------
1 | 49.9941974878311 | 4174
3 | 49.9958902597427 | 4119
7 | 49.9881327152252 | 4316
(3 rows)
-- Should prefer prepared statement data fetcher for these queries.
set timescaledb.remote_data_fetcher = 'auto';
explain (analyze, verbose, costs off, timing off, summary off)
select id, max(value), count(*)
from metric_dist
where id in (select id from metric_name where name like 'cpu%')
and ts between '2022-02-02 02:02:02+03' and '2022-03-03 02:02:02+03'
group by id
order by id
;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
GroupAggregate (actual rows=3 loops=1)
Output: metric_dist.id, max(metric_dist.value), count(*)
Group Key: metric_dist.id
-> Sort (actual rows=12609 loops=1)
Output: metric_dist.id, metric_dist.value
Sort Key: metric_dist.id
Sort Method: quicksort
-> Nested Loop (actual rows=12609 loops=1)
Output: metric_dist.id, metric_dist.value
-> Index Scan using metric_name_name on public.metric_name (actual rows=3 loops=1)
Output: metric_name.id, metric_name.name
Index Cond: ((metric_name.name >= 'cpu'::text) AND (metric_name.name < 'cpv'::text))
Filter: (metric_name.name ~~ 'cpu%'::text)
-> Custom Scan (DataNodeScan) on public.metric_dist (actual rows=4203 loops=3)
Output: metric_dist.id, metric_dist.value
Data node: data_node_1
Fetcher Type: Prepared statement
Chunks: _dist_hyper_1_3_chunk, _dist_hyper_1_16_chunk, _dist_hyper_1_20_chunk, _dist_hyper_1_37_chunk, _dist_hyper_1_52_chunk
Remote SQL: SELECT id, value FROM public.metric_dist WHERE _timescaledb_internal.chunks_in(public.metric_dist.*, ARRAY[3, 16, 20, 37, 52]) AND ((ts >= '2022-02-01 15:02:02-08'::timestamp with time zone)) AND ((ts <= '2022-03-02 15:02:02-08'::timestamp with time zone)) AND (($1::integer = id))
(19 rows)
-- Should reset the prepared cache mode after using the prepared statement fetcher.
call distributed_exec('create or replace procedure assert_auto_plan_cache_mode() as $$ begin assert (select setting from pg_settings where name = ''plan_cache_mode'') = ''auto''; end; $$ language plpgsql;');
call distributed_exec('call assert_auto_plan_cache_mode();');
-- Shippable EC join
select name, max(value), count(*)
from metric_dist join metric_name using (id)

@ -0,0 +1 @@
dist_remote_error-14.out

@ -0,0 +1 @@
dist_remote_error-14.out

@ -0,0 +1,430 @@
-- This file and its contents are licensed under the Timescale License.
-- Please see the included NOTICE for copyright information and
-- LICENSE-TIMESCALE for a copy of the license.
--\set DATA_NODE_1 data_node_1
--\set DATA_NODE_2 data_node_2
--\set DATA_NODE_3 data_node_3
-- Set up the data nodes.
\set DATA_NODE_1 :TEST_DBNAME _1
\set DATA_NODE_2 :TEST_DBNAME _2
\set DATA_NODE_3 :TEST_DBNAME _3
\c :TEST_DBNAME :ROLE_SUPERUSER
SELECT node_name, database, node_created, database_created, extension_created
FROM (
SELECT (add_data_node(name, host => 'localhost', DATABASE => name)).*
FROM (VALUES (:'DATA_NODE_1'), (:'DATA_NODE_2'), (:'DATA_NODE_3')) v(name)
) a;
node_name | database | node_created | database_created | extension_created
------------------------+------------------------+--------------+------------------+-------------------
db_dist_remote_error_1 | db_dist_remote_error_1 | t | t | t
db_dist_remote_error_2 | db_dist_remote_error_2 | t | t | t
db_dist_remote_error_3 | db_dist_remote_error_3 | t | t | t
(3 rows)
GRANT USAGE ON FOREIGN SERVER :DATA_NODE_1, :DATA_NODE_2, :DATA_NODE_3 TO PUBLIC;
GRANT CREATE ON SCHEMA public TO :ROLE_1;
-- Import setup file to data nodes.
\unset ECHO
-- Disable SSL to get stable error output across versions. SSL adds some output
-- that changed in PG 14.
set timescaledb.debug_enable_ssl to off;
set client_min_messages to error;
SET timescaledb.hide_data_node_name_in_errors = 'on';
-- A relatively big table on one data node
CREATE TABLE metrics_dist_remote_error(filler_1 int, filler_2 int, filler_3 int, time timestamptz NOT NULL, device_id int, v0 int, v1 int, v2 float, v3 float);
SELECT create_distributed_hypertable('metrics_dist_remote_error','time','device_id',3,
data_nodes => ARRAY[:'DATA_NODE_1']);
create_distributed_hypertable
----------------------------------------
(1,public,metrics_dist_remote_error,t)
(1 row)
ALTER TABLE metrics_dist_remote_error DROP COLUMN filler_1;
INSERT INTO metrics_dist_remote_error(time,device_id,v0,v1,v2,v3) SELECT time, device_id, device_id+1, device_id + 2, device_id + 0.5, NULL FROM generate_series('2000-01-01 0:00:00+0'::timestamptz,'2000-01-05 23:55:00+0','6m') gtime(time), generate_series(1,5,1) gdevice(device_id);
ALTER TABLE metrics_dist_remote_error DROP COLUMN filler_2;
INSERT INTO metrics_dist_remote_error(time,device_id,v0,v1,v2,v3) SELECT time, device_id, device_id+1, device_id + 2, device_id + 0.5, NULL FROM generate_series('2000-01-06 0:00:00+0'::timestamptz,'2000-01-12 23:55:00+0','6m') gtime(time), generate_series(1,5,1) gdevice(device_id);
ALTER TABLE metrics_dist_remote_error DROP COLUMN filler_3;
INSERT INTO metrics_dist_remote_error(time,device_id,v0,v1,v2,v3) SELECT time, device_id, device_id+1, device_id + 2, device_id + 0.5, NULL FROM generate_series('2000-01-13 0:00:00+0'::timestamptz,'2000-01-19 23:55:00+0','6m') gtime(time), generate_series(1,5,1) gdevice(device_id);
ANALYZE metrics_dist_remote_error;
-- The error messages vary wildly between the Postgres versions, dependent on
-- the particular behavior of libqp in this or that case. The purpose of this
-- test is not to solidify this accidental behavior, but to merely exercise the
-- error handling code to make sure it doesn't have fatal errors. Unfortunately,
-- there is no way to suppress error output from a psql script.
set client_min_messages to ERROR;
\set ON_ERROR_STOP off
set timescaledb.remote_data_fetcher = 'copy';
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(0, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 0 rows, 0 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(1, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(2, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(701, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 701 rows, 701 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 10000 rows, 10000 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(16384, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 16384 rows, 16384 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000000, device_id)::int != 0;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Custom Scan (DataNodeScan) on public.metrics_dist_remote_error (actual rows=22799 loops=1)
Output: 1
Data node: db_dist_remote_error_1
Fetcher Type: COPY
Chunks: _dist_hyper_1_1_chunk, _dist_hyper_1_2_chunk, _dist_hyper_1_3_chunk, _dist_hyper_1_4_chunk, _dist_hyper_1_5_chunk, _dist_hyper_1_6_chunk, _dist_hyper_1_7_chunk, _dist_hyper_1_8_chunk, _dist_hyper_1_9_chunk
Remote SQL: SELECT NULL FROM public.metrics_dist_remote_error WHERE _timescaledb_internal.chunks_in(public.metrics_dist_remote_error.*, ARRAY[1, 2, 3, 4, 5, 6, 7, 8, 9]) AND ((public.ts_debug_shippable_error_after_n_rows(10000000, device_id) <> 0))
(6 rows)
-- We don't test fatal errors here, because PG versions before 14 are unable to
-- report them properly to the access node, so we get different errors in these
-- versions.
-- Now test the same with the cursor fetcher.
set timescaledb.remote_data_fetcher = 'cursor';
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(0, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 0 rows, 0 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(1, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(2, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(701, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 701 rows, 701 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 10000 rows, 10000 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000000, device_id)::int != 0;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Custom Scan (DataNodeScan) on public.metrics_dist_remote_error (actual rows=22799 loops=1)
Output: 1
Data node: db_dist_remote_error_1
Fetcher Type: Cursor
Chunks: _dist_hyper_1_1_chunk, _dist_hyper_1_2_chunk, _dist_hyper_1_3_chunk, _dist_hyper_1_4_chunk, _dist_hyper_1_5_chunk, _dist_hyper_1_6_chunk, _dist_hyper_1_7_chunk, _dist_hyper_1_8_chunk, _dist_hyper_1_9_chunk
Remote SQL: SELECT NULL FROM public.metrics_dist_remote_error WHERE _timescaledb_internal.chunks_in(public.metrics_dist_remote_error.*, ARRAY[1, 2, 3, 4, 5, 6, 7, 8, 9]) AND ((public.ts_debug_shippable_error_after_n_rows(10000000, device_id) <> 0))
(6 rows)
-- Now test the same with the prepared statement fetcher.
set timescaledb.remote_data_fetcher = 'prepared';
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(0, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 0 rows, 0 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(1, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(2, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(701, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 701 rows, 701 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 10000 rows, 10000 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000000, device_id)::int != 0;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Custom Scan (DataNodeScan) on public.metrics_dist_remote_error (actual rows=22799 loops=1)
Output: 1
Data node: db_dist_remote_error_1
Fetcher Type: Prepared statement
Chunks: _dist_hyper_1_1_chunk, _dist_hyper_1_2_chunk, _dist_hyper_1_3_chunk, _dist_hyper_1_4_chunk, _dist_hyper_1_5_chunk, _dist_hyper_1_6_chunk, _dist_hyper_1_7_chunk, _dist_hyper_1_8_chunk, _dist_hyper_1_9_chunk
Remote SQL: SELECT NULL FROM public.metrics_dist_remote_error WHERE _timescaledb_internal.chunks_in(public.metrics_dist_remote_error.*, ARRAY[1, 2, 3, 4, 5, 6, 7, 8, 9]) AND ((public.ts_debug_shippable_error_after_n_rows(10000000, device_id) <> 0))
(6 rows)
reset timescaledb.remote_data_fetcher;
-- Table with broken send for a data type.
create table metrics_dist_bs(like metrics_dist_remote_error);
alter table metrics_dist_bs alter column v0 type bs;
select table_name from create_distributed_hypertable('metrics_dist_bs',
'time', 'device_id');
table_name
-----------------
metrics_dist_bs
(1 row)
set timescaledb.enable_connection_binary_data to off;
insert into metrics_dist_bs
select * from metrics_dist_remote_error;
set timescaledb.enable_connection_binary_data to on;
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_bs;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
drop table metrics_dist_bs;
-- Table with broken receive for a data type.
create table metrics_dist_br(like metrics_dist_remote_error);
alter table metrics_dist_br alter column v0 type br;
select table_name from create_distributed_hypertable('metrics_dist_br',
'time', 'device_id');
table_name
-----------------
metrics_dist_br
(1 row)
select hypertable_name, replication_factor from timescaledb_information.hypertables
where hypertable_name = 'metrics_dist_br';
hypertable_name | replication_factor
-----------------+--------------------
metrics_dist_br | 1
(1 row)
-- Test that INSERT and COPY fail on data nodes.
-- Note that we use the text format for the COPY input, so that the access node
-- doesn't call `recv` and fail by itself. It's going to use binary format for
-- transfer to data nodes regardless of the input format.
set timescaledb.dist_copy_transfer_format = 'binary';
-- First, create the reference.
\copy (select * from metrics_dist_remote_error) to 'dist_remote_error.text' with (format text);
-- We have to test various interleavings of COPY and INSERT to check that
-- one can recover from connection failure states introduced by another.
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
-- Fail at different points
set timescaledb.debug_broken_sendrecv_error_after = 1;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
set timescaledb.debug_broken_sendrecv_error_after = 2;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
set timescaledb.debug_broken_sendrecv_error_after = 1023;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1023 rows, 1023 rows seen
set timescaledb.debug_broken_sendrecv_error_after = 1024;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1024 rows, 1024 rows seen
set timescaledb.debug_broken_sendrecv_error_after = 1025;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1025 rows, 1025 rows seen
reset timescaledb.debug_broken_sendrecv_error_after;
-- Same with different replication factor
truncate metrics_dist_br;
select set_replication_factor('metrics_dist_br', 2);
set_replication_factor
------------------------
(1 row)
select hypertable_name, replication_factor from timescaledb_information.hypertables
where hypertable_name = 'metrics_dist_br';
hypertable_name | replication_factor
-----------------+--------------------
metrics_dist_br | 2
(1 row)
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
set timescaledb.debug_broken_sendrecv_error_after = 1;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
set timescaledb.debug_broken_sendrecv_error_after = 2;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
set timescaledb.debug_broken_sendrecv_error_after = 1023;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1023 rows, 1023 rows seen
set timescaledb.debug_broken_sendrecv_error_after = 1024;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1024 rows, 1024 rows seen
set timescaledb.debug_broken_sendrecv_error_after = 1025;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1025 rows, 1025 rows seen
-- Should succeed with text format for data transfer.
set timescaledb.dist_copy_transfer_format = 'text';
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
-- Final check.
set timescaledb.enable_connection_binary_data = false;
select count(*) from metrics_dist_br;
count
-------
22800
(1 row)
set timescaledb.enable_connection_binary_data = true;
reset timescaledb.debug_broken_sendrecv_error_after;
drop table metrics_dist_br;
-- Table with sleepy receive for a data type, to improve coverage of the waiting
-- code on the access node.
create table metrics_dist_sr(like metrics_dist_remote_error);
alter table metrics_dist_sr alter column v0 type sr;
select table_name from create_distributed_hypertable('metrics_dist_sr',
'time', 'device_id');
table_name
-----------------
metrics_dist_sr
(1 row)
-- We're using sleepy recv function, so need the binary transfer format for it
-- to be called on the data nodes.
set timescaledb.dist_copy_transfer_format = 'binary';
-- Test INSERT and COPY with slow data node.
\copy metrics_dist_sr from 'dist_remote_error.text' with (format text);
insert into metrics_dist_sr select * from metrics_dist_remote_error;
select count(*) from metrics_dist_sr;
count
-------
45600
(1 row)
drop table metrics_dist_sr;
-- Table with sleepy send for a data type, on one data node, to improve coverage
-- of waiting in data fetchers.
create table metrics_dist_ss(like metrics_dist_remote_error);
alter table metrics_dist_ss alter column v0 type ss;
select table_name from create_distributed_hypertable('metrics_dist_ss',
'time', 'device_id');
table_name
-----------------
metrics_dist_ss
(1 row)
-- Populate the table, using text COPY to avoid the sleepy stuff.
set timescaledb.dist_copy_transfer_format = 'text';
\copy metrics_dist_ss from 'dist_remote_error.text' with (format text);
-- We're using sleepy send function, so need the binary transfer format for it
-- to be called on the data nodes.
set timescaledb.enable_connection_binary_data = true;
set timescaledb.remote_data_fetcher = 'prepared';
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_ss;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Custom Scan (AsyncAppend) (actual rows=22800 loops=1)
Output: metrics_dist_ss."time", metrics_dist_ss.device_id, metrics_dist_ss.v0, metrics_dist_ss.v1, metrics_dist_ss.v2, metrics_dist_ss.v3
-> Append (actual rows=22800 loops=1)
-> Custom Scan (DataNodeScan) on public.metrics_dist_ss metrics_dist_ss_1 (actual rows=4560 loops=1)
Output: metrics_dist_ss_1."time", metrics_dist_ss_1.device_id, metrics_dist_ss_1.v0, metrics_dist_ss_1.v1, metrics_dist_ss_1.v2, metrics_dist_ss_1.v3
Data node: db_dist_remote_error_1
Fetcher Type: Prepared statement
Chunks: _dist_hyper_5_96_chunk, _dist_hyper_5_99_chunk, _dist_hyper_5_102_chunk
Remote SQL: SELECT "time", device_id, v0, v1, v2, v3 FROM public.metrics_dist_ss WHERE _timescaledb_internal.chunks_in(public.metrics_dist_ss.*, ARRAY[54, 55, 56])
-> Custom Scan (DataNodeScan) on public.metrics_dist_ss metrics_dist_ss_2 (actual rows=13680 loops=1)
Output: metrics_dist_ss_2."time", metrics_dist_ss_2.device_id, metrics_dist_ss_2.v0, metrics_dist_ss_2.v1, metrics_dist_ss_2.v2, metrics_dist_ss_2.v3
Data node: db_dist_remote_error_2
Fetcher Type: Prepared statement
Chunks: _dist_hyper_5_97_chunk, _dist_hyper_5_100_chunk, _dist_hyper_5_103_chunk
Remote SQL: SELECT "time", device_id, v0, v1, v2, v3 FROM public.metrics_dist_ss WHERE _timescaledb_internal.chunks_in(public.metrics_dist_ss.*, ARRAY[48, 49, 50])
-> Custom Scan (DataNodeScan) on public.metrics_dist_ss metrics_dist_ss_3 (actual rows=4560 loops=1)
Output: metrics_dist_ss_3."time", metrics_dist_ss_3.device_id, metrics_dist_ss_3.v0, metrics_dist_ss_3.v1, metrics_dist_ss_3.v2, metrics_dist_ss_3.v3
Data node: db_dist_remote_error_3
Fetcher Type: Prepared statement
Chunks: _dist_hyper_5_98_chunk, _dist_hyper_5_101_chunk, _dist_hyper_5_104_chunk
Remote SQL: SELECT "time", device_id, v0, v1, v2, v3 FROM public.metrics_dist_ss WHERE _timescaledb_internal.chunks_in(public.metrics_dist_ss.*, ARRAY[32, 33, 34])
(21 rows)
set timescaledb.remote_data_fetcher = 'copy';
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_ss;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Custom Scan (AsyncAppend) (actual rows=22800 loops=1)
Output: metrics_dist_ss."time", metrics_dist_ss.device_id, metrics_dist_ss.v0, metrics_dist_ss.v1, metrics_dist_ss.v2, metrics_dist_ss.v3
-> Append (actual rows=22800 loops=1)
-> Custom Scan (DataNodeScan) on public.metrics_dist_ss metrics_dist_ss_1 (actual rows=4560 loops=1)
Output: metrics_dist_ss_1."time", metrics_dist_ss_1.device_id, metrics_dist_ss_1.v0, metrics_dist_ss_1.v1, metrics_dist_ss_1.v2, metrics_dist_ss_1.v3
Data node: db_dist_remote_error_1
Fetcher Type: COPY
Chunks: _dist_hyper_5_96_chunk, _dist_hyper_5_99_chunk, _dist_hyper_5_102_chunk
Remote SQL: SELECT "time", device_id, v0, v1, v2, v3 FROM public.metrics_dist_ss WHERE _timescaledb_internal.chunks_in(public.metrics_dist_ss.*, ARRAY[54, 55, 56])
-> Custom Scan (DataNodeScan) on public.metrics_dist_ss metrics_dist_ss_2 (actual rows=13680 loops=1)
Output: metrics_dist_ss_2."time", metrics_dist_ss_2.device_id, metrics_dist_ss_2.v0, metrics_dist_ss_2.v1, metrics_dist_ss_2.v2, metrics_dist_ss_2.v3
Data node: db_dist_remote_error_2
Fetcher Type: COPY
Chunks: _dist_hyper_5_97_chunk, _dist_hyper_5_100_chunk, _dist_hyper_5_103_chunk
Remote SQL: SELECT "time", device_id, v0, v1, v2, v3 FROM public.metrics_dist_ss WHERE _timescaledb_internal.chunks_in(public.metrics_dist_ss.*, ARRAY[48, 49, 50])
-> Custom Scan (DataNodeScan) on public.metrics_dist_ss metrics_dist_ss_3 (actual rows=4560 loops=1)
Output: metrics_dist_ss_3."time", metrics_dist_ss_3.device_id, metrics_dist_ss_3.v0, metrics_dist_ss_3.v1, metrics_dist_ss_3.v2, metrics_dist_ss_3.v3
Data node: db_dist_remote_error_3
Fetcher Type: COPY
Chunks: _dist_hyper_5_98_chunk, _dist_hyper_5_101_chunk, _dist_hyper_5_104_chunk
Remote SQL: SELECT "time", device_id, v0, v1, v2, v3 FROM public.metrics_dist_ss WHERE _timescaledb_internal.chunks_in(public.metrics_dist_ss.*, ARRAY[32, 33, 34])
(21 rows)
set timescaledb.remote_data_fetcher = 'cursor';
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_ss;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Custom Scan (AsyncAppend) (actual rows=22800 loops=1)
Output: metrics_dist_ss."time", metrics_dist_ss.device_id, metrics_dist_ss.v0, metrics_dist_ss.v1, metrics_dist_ss.v2, metrics_dist_ss.v3
-> Append (actual rows=22800 loops=1)
-> Custom Scan (DataNodeScan) on public.metrics_dist_ss metrics_dist_ss_1 (actual rows=4560 loops=1)
Output: metrics_dist_ss_1."time", metrics_dist_ss_1.device_id, metrics_dist_ss_1.v0, metrics_dist_ss_1.v1, metrics_dist_ss_1.v2, metrics_dist_ss_1.v3
Data node: db_dist_remote_error_1
Fetcher Type: Cursor
Chunks: _dist_hyper_5_96_chunk, _dist_hyper_5_99_chunk, _dist_hyper_5_102_chunk
Remote SQL: SELECT "time", device_id, v0, v1, v2, v3 FROM public.metrics_dist_ss WHERE _timescaledb_internal.chunks_in(public.metrics_dist_ss.*, ARRAY[54, 55, 56])
-> Custom Scan (DataNodeScan) on public.metrics_dist_ss metrics_dist_ss_2 (actual rows=13680 loops=1)
Output: metrics_dist_ss_2."time", metrics_dist_ss_2.device_id, metrics_dist_ss_2.v0, metrics_dist_ss_2.v1, metrics_dist_ss_2.v2, metrics_dist_ss_2.v3
Data node: db_dist_remote_error_2
Fetcher Type: Cursor
Chunks: _dist_hyper_5_97_chunk, _dist_hyper_5_100_chunk, _dist_hyper_5_103_chunk
Remote SQL: SELECT "time", device_id, v0, v1, v2, v3 FROM public.metrics_dist_ss WHERE _timescaledb_internal.chunks_in(public.metrics_dist_ss.*, ARRAY[48, 49, 50])
-> Custom Scan (DataNodeScan) on public.metrics_dist_ss metrics_dist_ss_3 (actual rows=4560 loops=1)
Output: metrics_dist_ss_3."time", metrics_dist_ss_3.device_id, metrics_dist_ss_3.v0, metrics_dist_ss_3.v1, metrics_dist_ss_3.v2, metrics_dist_ss_3.v3
Data node: db_dist_remote_error_3
Fetcher Type: Cursor
Chunks: _dist_hyper_5_98_chunk, _dist_hyper_5_101_chunk, _dist_hyper_5_104_chunk
Remote SQL: SELECT "time", device_id, v0, v1, v2, v3 FROM public.metrics_dist_ss WHERE _timescaledb_internal.chunks_in(public.metrics_dist_ss.*, ARRAY[32, 33, 34])
(21 rows)
-- Incorrect int output, to cover the error handling in tuplefactory.
create table metrics_dist_io(like metrics_dist_remote_error);
alter table metrics_dist_io alter column v0 type io;
select table_name from create_distributed_hypertable('metrics_dist_io',
'time', 'device_id');
table_name
-----------------
metrics_dist_io
(1 row)
-- Populate the table, using binary COPY to avoid the broken in4out.
set timescaledb.enable_connection_binary_data = true;
set timescaledb.dist_copy_transfer_format = 'binary';
\copy metrics_dist_io from 'dist_remote_error.text' with (format text);
-- For testing, force the text format to exerices our broken out function.
set timescaledb.enable_connection_binary_data = false;
set timescaledb.dist_copy_transfer_format = 'text';
set timescaledb.remote_data_fetcher = 'prepared';
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_io;
ERROR: invalid input syntax for type integer: "surprise"
set timescaledb.remote_data_fetcher = 'copy';
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_io;
ERROR: cannot use COPY fetcher because some of the column types do not have binary serialization
set timescaledb.remote_data_fetcher = 'cursor';
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_io;
ERROR: invalid input syntax for type integer: "surprise"
-- cleanup
\c :TEST_DBNAME :ROLE_SUPERUSER;
DROP DATABASE :DATA_NODE_1;
DROP DATABASE :DATA_NODE_2;
DROP DATABASE :DATA_NODE_3;

@ -0,0 +1,433 @@
-- This file and its contents are licensed under the Timescale License.
-- Please see the included NOTICE for copyright information and
-- LICENSE-TIMESCALE for a copy of the license.
--\set DATA_NODE_1 data_node_1
--\set DATA_NODE_2 data_node_2
--\set DATA_NODE_3 data_node_3
-- Set up the data nodes.
\set DATA_NODE_1 :TEST_DBNAME _1
\set DATA_NODE_2 :TEST_DBNAME _2
\set DATA_NODE_3 :TEST_DBNAME _3
\c :TEST_DBNAME :ROLE_SUPERUSER
SELECT node_name, database, node_created, database_created, extension_created
FROM (
SELECT (add_data_node(name, host => 'localhost', DATABASE => name)).*
FROM (VALUES (:'DATA_NODE_1'), (:'DATA_NODE_2'), (:'DATA_NODE_3')) v(name)
) a;
node_name | database | node_created | database_created | extension_created
------------------------+------------------------+--------------+------------------+-------------------
db_dist_remote_error_1 | db_dist_remote_error_1 | t | t | t
db_dist_remote_error_2 | db_dist_remote_error_2 | t | t | t
db_dist_remote_error_3 | db_dist_remote_error_3 | t | t | t
(3 rows)
GRANT USAGE ON FOREIGN SERVER :DATA_NODE_1, :DATA_NODE_2, :DATA_NODE_3 TO PUBLIC;
GRANT CREATE ON SCHEMA public TO :ROLE_1;
-- Import setup file to data nodes.
\unset ECHO
-- Disable SSL to get stable error output across versions. SSL adds some output
-- that changed in PG 14.
set timescaledb.debug_enable_ssl to off;
set client_min_messages to error;
SET timescaledb.hide_data_node_name_in_errors = 'on';
-- A relatively big table on one data node
CREATE TABLE metrics_dist_remote_error(filler_1 int, filler_2 int, filler_3 int, time timestamptz NOT NULL, device_id int, v0 int, v1 int, v2 float, v3 float);
SELECT create_distributed_hypertable('metrics_dist_remote_error','time','device_id',3,
data_nodes => ARRAY[:'DATA_NODE_1']);
create_distributed_hypertable
----------------------------------------
(1,public,metrics_dist_remote_error,t)
(1 row)
ALTER TABLE metrics_dist_remote_error DROP COLUMN filler_1;
INSERT INTO metrics_dist_remote_error(time,device_id,v0,v1,v2,v3) SELECT time, device_id, device_id+1, device_id + 2, device_id + 0.5, NULL FROM generate_series('2000-01-01 0:00:00+0'::timestamptz,'2000-01-05 23:55:00+0','6m') gtime(time), generate_series(1,5,1) gdevice(device_id);
ALTER TABLE metrics_dist_remote_error DROP COLUMN filler_2;
INSERT INTO metrics_dist_remote_error(time,device_id,v0,v1,v2,v3) SELECT time, device_id, device_id+1, device_id + 2, device_id + 0.5, NULL FROM generate_series('2000-01-06 0:00:00+0'::timestamptz,'2000-01-12 23:55:00+0','6m') gtime(time), generate_series(1,5,1) gdevice(device_id);
ALTER TABLE metrics_dist_remote_error DROP COLUMN filler_3;
INSERT INTO metrics_dist_remote_error(time,device_id,v0,v1,v2,v3) SELECT time, device_id, device_id+1, device_id + 2, device_id + 0.5, NULL FROM generate_series('2000-01-13 0:00:00+0'::timestamptz,'2000-01-19 23:55:00+0','6m') gtime(time), generate_series(1,5,1) gdevice(device_id);
ANALYZE metrics_dist_remote_error;
-- The error messages vary wildly between the Postgres versions, dependent on
-- the particular behavior of libqp in this or that case. The purpose of this
-- test is not to solidify this accidental behavior, but to merely exercise the
-- error handling code to make sure it doesn't have fatal errors. Unfortunately,
-- there is no way to suppress error output from a psql script.
set client_min_messages to ERROR;
\set ON_ERROR_STOP off
set timescaledb.remote_data_fetcher = 'copy';
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(0, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 0 rows, 0 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(1, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(2, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(701, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 701 rows, 701 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 10000 rows, 10000 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(16384, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 16384 rows, 16384 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000000, device_id)::int != 0;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Result (actual rows=22799 loops=1)
Output: 1
-> Custom Scan (DataNodeScan) on public.metrics_dist_remote_error (actual rows=22799 loops=1)
Data node: db_dist_remote_error_1
Fetcher Type: COPY
Chunks: _dist_hyper_1_1_chunk, _dist_hyper_1_2_chunk, _dist_hyper_1_3_chunk, _dist_hyper_1_4_chunk, _dist_hyper_1_5_chunk, _dist_hyper_1_6_chunk, _dist_hyper_1_7_chunk, _dist_hyper_1_8_chunk, _dist_hyper_1_9_chunk
Remote SQL: SELECT NULL FROM public.metrics_dist_remote_error WHERE _timescaledb_internal.chunks_in(public.metrics_dist_remote_error.*, ARRAY[1, 2, 3, 4, 5, 6, 7, 8, 9]) AND ((public.ts_debug_shippable_error_after_n_rows(10000000, device_id) <> 0))
(7 rows)
-- We don't test fatal errors here, because PG versions before 14 are unable to
-- report them properly to the access node, so we get different errors in these
-- versions.
-- Now test the same with the cursor fetcher.
set timescaledb.remote_data_fetcher = 'cursor';
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(0, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 0 rows, 0 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(1, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(2, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(701, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 701 rows, 701 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 10000 rows, 10000 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000000, device_id)::int != 0;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Result (actual rows=22799 loops=1)
Output: 1
-> Custom Scan (DataNodeScan) on public.metrics_dist_remote_error (actual rows=22799 loops=1)
Data node: db_dist_remote_error_1
Fetcher Type: Cursor
Chunks: _dist_hyper_1_1_chunk, _dist_hyper_1_2_chunk, _dist_hyper_1_3_chunk, _dist_hyper_1_4_chunk, _dist_hyper_1_5_chunk, _dist_hyper_1_6_chunk, _dist_hyper_1_7_chunk, _dist_hyper_1_8_chunk, _dist_hyper_1_9_chunk
Remote SQL: SELECT NULL FROM public.metrics_dist_remote_error WHERE _timescaledb_internal.chunks_in(public.metrics_dist_remote_error.*, ARRAY[1, 2, 3, 4, 5, 6, 7, 8, 9]) AND ((public.ts_debug_shippable_error_after_n_rows(10000000, device_id) <> 0))
(7 rows)
-- Now test the same with the prepared statement fetcher.
set timescaledb.remote_data_fetcher = 'prepared';
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(0, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 0 rows, 0 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(1, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(2, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(701, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 701 rows, 701 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 10000 rows, 10000 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000000, device_id)::int != 0;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Result (actual rows=22799 loops=1)
Output: 1
-> Custom Scan (DataNodeScan) on public.metrics_dist_remote_error (actual rows=22799 loops=1)
Data node: db_dist_remote_error_1
Fetcher Type: Prepared statement
Chunks: _dist_hyper_1_1_chunk, _dist_hyper_1_2_chunk, _dist_hyper_1_3_chunk, _dist_hyper_1_4_chunk, _dist_hyper_1_5_chunk, _dist_hyper_1_6_chunk, _dist_hyper_1_7_chunk, _dist_hyper_1_8_chunk, _dist_hyper_1_9_chunk
Remote SQL: SELECT NULL FROM public.metrics_dist_remote_error WHERE _timescaledb_internal.chunks_in(public.metrics_dist_remote_error.*, ARRAY[1, 2, 3, 4, 5, 6, 7, 8, 9]) AND ((public.ts_debug_shippable_error_after_n_rows(10000000, device_id) <> 0))
(7 rows)
reset timescaledb.remote_data_fetcher;
-- Table with broken send for a data type.
create table metrics_dist_bs(like metrics_dist_remote_error);
alter table metrics_dist_bs alter column v0 type bs;
select table_name from create_distributed_hypertable('metrics_dist_bs',
'time', 'device_id');
table_name
-----------------
metrics_dist_bs
(1 row)
set timescaledb.enable_connection_binary_data to off;
insert into metrics_dist_bs
select * from metrics_dist_remote_error;
set timescaledb.enable_connection_binary_data to on;
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_bs;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
drop table metrics_dist_bs;
-- Table with broken receive for a data type.
create table metrics_dist_br(like metrics_dist_remote_error);
alter table metrics_dist_br alter column v0 type br;
select table_name from create_distributed_hypertable('metrics_dist_br',
'time', 'device_id');
table_name
-----------------
metrics_dist_br
(1 row)
select hypertable_name, replication_factor from timescaledb_information.hypertables
where hypertable_name = 'metrics_dist_br';
hypertable_name | replication_factor
-----------------+--------------------
metrics_dist_br | 1
(1 row)
-- Test that INSERT and COPY fail on data nodes.
-- Note that we use the text format for the COPY input, so that the access node
-- doesn't call `recv` and fail by itself. It's going to use binary format for
-- transfer to data nodes regardless of the input format.
set timescaledb.dist_copy_transfer_format = 'binary';
-- First, create the reference.
\copy (select * from metrics_dist_remote_error) to 'dist_remote_error.text' with (format text);
-- We have to test various interleavings of COPY and INSERT to check that
-- one can recover from connection failure states introduced by another.
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
-- Fail at different points
set timescaledb.debug_broken_sendrecv_error_after = 1;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
set timescaledb.debug_broken_sendrecv_error_after = 2;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
set timescaledb.debug_broken_sendrecv_error_after = 1023;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1023 rows, 1023 rows seen
set timescaledb.debug_broken_sendrecv_error_after = 1024;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1024 rows, 1024 rows seen
set timescaledb.debug_broken_sendrecv_error_after = 1025;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1025 rows, 1025 rows seen
reset timescaledb.debug_broken_sendrecv_error_after;
-- Same with different replication factor
truncate metrics_dist_br;
select set_replication_factor('metrics_dist_br', 2);
set_replication_factor
------------------------
(1 row)
select hypertable_name, replication_factor from timescaledb_information.hypertables
where hypertable_name = 'metrics_dist_br';
hypertable_name | replication_factor
-----------------+--------------------
metrics_dist_br | 2
(1 row)
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
set timescaledb.debug_broken_sendrecv_error_after = 1;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
set timescaledb.debug_broken_sendrecv_error_after = 2;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
set timescaledb.debug_broken_sendrecv_error_after = 1023;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1023 rows, 1023 rows seen
set timescaledb.debug_broken_sendrecv_error_after = 1024;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1024 rows, 1024 rows seen
set timescaledb.debug_broken_sendrecv_error_after = 1025;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1025 rows, 1025 rows seen
-- Should succeed with text format for data transfer.
set timescaledb.dist_copy_transfer_format = 'text';
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
-- Final check.
set timescaledb.enable_connection_binary_data = false;
select count(*) from metrics_dist_br;
count
-------
22800
(1 row)
set timescaledb.enable_connection_binary_data = true;
reset timescaledb.debug_broken_sendrecv_error_after;
drop table metrics_dist_br;
-- Table with sleepy receive for a data type, to improve coverage of the waiting
-- code on the access node.
create table metrics_dist_sr(like metrics_dist_remote_error);
alter table metrics_dist_sr alter column v0 type sr;
select table_name from create_distributed_hypertable('metrics_dist_sr',
'time', 'device_id');
table_name
-----------------
metrics_dist_sr
(1 row)
-- We're using sleepy recv function, so need the binary transfer format for it
-- to be called on the data nodes.
set timescaledb.dist_copy_transfer_format = 'binary';
-- Test INSERT and COPY with slow data node.
\copy metrics_dist_sr from 'dist_remote_error.text' with (format text);
insert into metrics_dist_sr select * from metrics_dist_remote_error;
select count(*) from metrics_dist_sr;
count
-------
45600
(1 row)
drop table metrics_dist_sr;
-- Table with sleepy send for a data type, on one data node, to improve coverage
-- of waiting in data fetchers.
create table metrics_dist_ss(like metrics_dist_remote_error);
alter table metrics_dist_ss alter column v0 type ss;
select table_name from create_distributed_hypertable('metrics_dist_ss',
'time', 'device_id');
table_name
-----------------
metrics_dist_ss
(1 row)
-- Populate the table, using text COPY to avoid the sleepy stuff.
set timescaledb.dist_copy_transfer_format = 'text';
\copy metrics_dist_ss from 'dist_remote_error.text' with (format text);
-- We're using sleepy send function, so need the binary transfer format for it
-- to be called on the data nodes.
set timescaledb.enable_connection_binary_data = true;
set timescaledb.remote_data_fetcher = 'prepared';
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_ss;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Custom Scan (AsyncAppend) (actual rows=22800 loops=1)
Output: metrics_dist_ss."time", metrics_dist_ss.device_id, metrics_dist_ss.v0, metrics_dist_ss.v1, metrics_dist_ss.v2, metrics_dist_ss.v3
-> Append (actual rows=22800 loops=1)
-> Custom Scan (DataNodeScan) on public.metrics_dist_ss metrics_dist_ss_1 (actual rows=4560 loops=1)
Output: metrics_dist_ss_1."time", metrics_dist_ss_1.device_id, metrics_dist_ss_1.v0, metrics_dist_ss_1.v1, metrics_dist_ss_1.v2, metrics_dist_ss_1.v3
Data node: db_dist_remote_error_1
Fetcher Type: Prepared statement
Chunks: _dist_hyper_5_96_chunk, _dist_hyper_5_99_chunk, _dist_hyper_5_102_chunk
Remote SQL: SELECT "time", device_id, v0, v1, v2, v3 FROM public.metrics_dist_ss WHERE _timescaledb_internal.chunks_in(public.metrics_dist_ss.*, ARRAY[54, 55, 56])
-> Custom Scan (DataNodeScan) on public.metrics_dist_ss metrics_dist_ss_2 (actual rows=13680 loops=1)
Output: metrics_dist_ss_2."time", metrics_dist_ss_2.device_id, metrics_dist_ss_2.v0, metrics_dist_ss_2.v1, metrics_dist_ss_2.v2, metrics_dist_ss_2.v3
Data node: db_dist_remote_error_2
Fetcher Type: Prepared statement
Chunks: _dist_hyper_5_97_chunk, _dist_hyper_5_100_chunk, _dist_hyper_5_103_chunk
Remote SQL: SELECT "time", device_id, v0, v1, v2, v3 FROM public.metrics_dist_ss WHERE _timescaledb_internal.chunks_in(public.metrics_dist_ss.*, ARRAY[48, 49, 50])
-> Custom Scan (DataNodeScan) on public.metrics_dist_ss metrics_dist_ss_3 (actual rows=4560 loops=1)
Output: metrics_dist_ss_3."time", metrics_dist_ss_3.device_id, metrics_dist_ss_3.v0, metrics_dist_ss_3.v1, metrics_dist_ss_3.v2, metrics_dist_ss_3.v3
Data node: db_dist_remote_error_3
Fetcher Type: Prepared statement
Chunks: _dist_hyper_5_98_chunk, _dist_hyper_5_101_chunk, _dist_hyper_5_104_chunk
Remote SQL: SELECT "time", device_id, v0, v1, v2, v3 FROM public.metrics_dist_ss WHERE _timescaledb_internal.chunks_in(public.metrics_dist_ss.*, ARRAY[32, 33, 34])
(21 rows)
set timescaledb.remote_data_fetcher = 'copy';
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_ss;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Custom Scan (AsyncAppend) (actual rows=22800 loops=1)
Output: metrics_dist_ss."time", metrics_dist_ss.device_id, metrics_dist_ss.v0, metrics_dist_ss.v1, metrics_dist_ss.v2, metrics_dist_ss.v3
-> Append (actual rows=22800 loops=1)
-> Custom Scan (DataNodeScan) on public.metrics_dist_ss metrics_dist_ss_1 (actual rows=4560 loops=1)
Output: metrics_dist_ss_1."time", metrics_dist_ss_1.device_id, metrics_dist_ss_1.v0, metrics_dist_ss_1.v1, metrics_dist_ss_1.v2, metrics_dist_ss_1.v3
Data node: db_dist_remote_error_1
Fetcher Type: COPY
Chunks: _dist_hyper_5_96_chunk, _dist_hyper_5_99_chunk, _dist_hyper_5_102_chunk
Remote SQL: SELECT "time", device_id, v0, v1, v2, v3 FROM public.metrics_dist_ss WHERE _timescaledb_internal.chunks_in(public.metrics_dist_ss.*, ARRAY[54, 55, 56])
-> Custom Scan (DataNodeScan) on public.metrics_dist_ss metrics_dist_ss_2 (actual rows=13680 loops=1)
Output: metrics_dist_ss_2."time", metrics_dist_ss_2.device_id, metrics_dist_ss_2.v0, metrics_dist_ss_2.v1, metrics_dist_ss_2.v2, metrics_dist_ss_2.v3
Data node: db_dist_remote_error_2
Fetcher Type: COPY
Chunks: _dist_hyper_5_97_chunk, _dist_hyper_5_100_chunk, _dist_hyper_5_103_chunk
Remote SQL: SELECT "time", device_id, v0, v1, v2, v3 FROM public.metrics_dist_ss WHERE _timescaledb_internal.chunks_in(public.metrics_dist_ss.*, ARRAY[48, 49, 50])
-> Custom Scan (DataNodeScan) on public.metrics_dist_ss metrics_dist_ss_3 (actual rows=4560 loops=1)
Output: metrics_dist_ss_3."time", metrics_dist_ss_3.device_id, metrics_dist_ss_3.v0, metrics_dist_ss_3.v1, metrics_dist_ss_3.v2, metrics_dist_ss_3.v3
Data node: db_dist_remote_error_3
Fetcher Type: COPY
Chunks: _dist_hyper_5_98_chunk, _dist_hyper_5_101_chunk, _dist_hyper_5_104_chunk
Remote SQL: SELECT "time", device_id, v0, v1, v2, v3 FROM public.metrics_dist_ss WHERE _timescaledb_internal.chunks_in(public.metrics_dist_ss.*, ARRAY[32, 33, 34])
(21 rows)
set timescaledb.remote_data_fetcher = 'cursor';
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_ss;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Custom Scan (AsyncAppend) (actual rows=22800 loops=1)
Output: metrics_dist_ss."time", metrics_dist_ss.device_id, metrics_dist_ss.v0, metrics_dist_ss.v1, metrics_dist_ss.v2, metrics_dist_ss.v3
-> Append (actual rows=22800 loops=1)
-> Custom Scan (DataNodeScan) on public.metrics_dist_ss metrics_dist_ss_1 (actual rows=4560 loops=1)
Output: metrics_dist_ss_1."time", metrics_dist_ss_1.device_id, metrics_dist_ss_1.v0, metrics_dist_ss_1.v1, metrics_dist_ss_1.v2, metrics_dist_ss_1.v3
Data node: db_dist_remote_error_1
Fetcher Type: Cursor
Chunks: _dist_hyper_5_96_chunk, _dist_hyper_5_99_chunk, _dist_hyper_5_102_chunk
Remote SQL: SELECT "time", device_id, v0, v1, v2, v3 FROM public.metrics_dist_ss WHERE _timescaledb_internal.chunks_in(public.metrics_dist_ss.*, ARRAY[54, 55, 56])
-> Custom Scan (DataNodeScan) on public.metrics_dist_ss metrics_dist_ss_2 (actual rows=13680 loops=1)
Output: metrics_dist_ss_2."time", metrics_dist_ss_2.device_id, metrics_dist_ss_2.v0, metrics_dist_ss_2.v1, metrics_dist_ss_2.v2, metrics_dist_ss_2.v3
Data node: db_dist_remote_error_2
Fetcher Type: Cursor
Chunks: _dist_hyper_5_97_chunk, _dist_hyper_5_100_chunk, _dist_hyper_5_103_chunk
Remote SQL: SELECT "time", device_id, v0, v1, v2, v3 FROM public.metrics_dist_ss WHERE _timescaledb_internal.chunks_in(public.metrics_dist_ss.*, ARRAY[48, 49, 50])
-> Custom Scan (DataNodeScan) on public.metrics_dist_ss metrics_dist_ss_3 (actual rows=4560 loops=1)
Output: metrics_dist_ss_3."time", metrics_dist_ss_3.device_id, metrics_dist_ss_3.v0, metrics_dist_ss_3.v1, metrics_dist_ss_3.v2, metrics_dist_ss_3.v3
Data node: db_dist_remote_error_3
Fetcher Type: Cursor
Chunks: _dist_hyper_5_98_chunk, _dist_hyper_5_101_chunk, _dist_hyper_5_104_chunk
Remote SQL: SELECT "time", device_id, v0, v1, v2, v3 FROM public.metrics_dist_ss WHERE _timescaledb_internal.chunks_in(public.metrics_dist_ss.*, ARRAY[32, 33, 34])
(21 rows)
-- Incorrect int output, to cover the error handling in tuplefactory.
create table metrics_dist_io(like metrics_dist_remote_error);
alter table metrics_dist_io alter column v0 type io;
select table_name from create_distributed_hypertable('metrics_dist_io',
'time', 'device_id');
table_name
-----------------
metrics_dist_io
(1 row)
-- Populate the table, using binary COPY to avoid the broken in4out.
set timescaledb.enable_connection_binary_data = true;
set timescaledb.dist_copy_transfer_format = 'binary';
\copy metrics_dist_io from 'dist_remote_error.text' with (format text);
-- For testing, force the text format to exerices our broken out function.
set timescaledb.enable_connection_binary_data = false;
set timescaledb.dist_copy_transfer_format = 'text';
set timescaledb.remote_data_fetcher = 'prepared';
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_io;
ERROR: invalid input syntax for type integer: "surprise"
set timescaledb.remote_data_fetcher = 'copy';
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_io;
ERROR: cannot use COPY fetcher because some of the column types do not have binary serialization
set timescaledb.remote_data_fetcher = 'cursor';
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_io;
ERROR: invalid input syntax for type integer: "surprise"
-- cleanup
\c :TEST_DBNAME :ROLE_SUPERUSER;
DROP DATABASE :DATA_NODE_1;
DROP DATABASE :DATA_NODE_2;
DROP DATABASE :DATA_NODE_3;

@ -60,7 +60,7 @@ LOCATION: ts_test_bad_remote_query, connection.c:216
SELECT * FROM test.get_connection_stats();
connections_created | connections_closed | results_created | results_cleared
---------------------+--------------------+-----------------+-----------------
1 | 1 | 8 | 8
1 | 1 | 9 | 9
(1 row)
SELECT test.remote_connection_tests();

@ -66,12 +66,17 @@ QUERY PLAN
Remote SQL: SELECT id FROM public.distinct_on_distributed WHERE _timescaledb_internal.chunks_in(public.distinct_on_distributed.*, ARRAY[..])
(19 rows)
-- This query can't work with copy fetcher.
-- This query can't work with copy or prepared fetcher.
set timescaledb.remote_data_fetcher = 'copy';
select 1 x from distinct_on_distributed t1, distinct_on_distributed t2
where t1.id = t2.id + 1
limit 1;
ERROR: COPY fetcher not supported
ERROR: only cursor fetcher is supported for this query
set timescaledb.remote_data_fetcher = 'prepared';
select 1 x from distinct_on_distributed t1, distinct_on_distributed t2
where t1.id = t2.id + 1
limit 1;
ERROR: only cursor fetcher is supported for this query
-- Check once again that 'auto' is used after 'copy'.
set timescaledb.remote_data_fetcher = 'auto';
select 1 x from distinct_on_distributed t1, distinct_on_distributed t2
@ -104,7 +109,7 @@ WHERE
) AS l
WHERE d.name ~~ d.name
)
ORDER BY 1,2;
ORDER BY 1, 2;
device_id | name
-----------+------
(0 rows)
@ -170,6 +175,26 @@ select * from disttable_with_bytea;
1001 |
(2 rows)
-- Prepared statement fetcher with bytea data
set timescaledb.remote_data_fetcher = 'prepared';
explain (analyze, verbose, costs off, timing off, summary off)
select * from disttable_with_bytea;
QUERY PLAN
Custom Scan (DataNodeScan) on public.disttable_with_bytea (actual rows=2 loops=1)
Output: disttable_with_bytea."time", disttable_with_bytea.bdata
Data node: data_node_3
Fetcher Type: Prepared statement
Chunks: _dist_hyper_X_X_chunk
Remote SQL: SELECT "time", bdata FROM public.disttable_with_bytea WHERE _timescaledb_internal.chunks_in(public.disttable_with_bytea.*, ARRAY[..])
(6 rows)
select * from disttable_with_bytea;
time | bdata
------+-------
1001 | \x
1001 |
(2 rows)
-- #4515 test for assertion failure in copy_fetcher_close
SET timescaledb.remote_data_fetcher = 'copy';
SELECT *
@ -190,6 +215,44 @@ WHERE EXISTS (
------+--------+-------
(0 rows)
SET timescaledb.remote_data_fetcher = 'prepared';
SELECT *
FROM
conditions ref_0
WHERE EXISTS (
SELECT FROM
distinct_on_distributed,
LATERAL (
SELECT *
FROM pg_class,
LATERAL (
SELECT ref_0.device FROM pg_class WHERE false LIMIT 1) as lat_1
) as lat_2
WHERE (SELECT 1 FROM pg_class LIMIT 1) >= ref_0.device
);
time | device | value
------+--------+-------
(0 rows)
SET timescaledb.remote_data_fetcher = 'cursor';
SELECT *
FROM
conditions ref_0
WHERE EXISTS (
SELECT FROM
distinct_on_distributed,
LATERAL (
SELECT *
FROM pg_class,
LATERAL (
SELECT ref_0.device FROM pg_class WHERE false LIMIT 1) as lat_1
) as lat_2
WHERE (SELECT 1 FROM pg_class LIMIT 1) >= ref_0.device
);
time | device | value
------+--------+-------
(0 rows)
-- #4518
-- test error handling for queries with multiple distributed hypertables
SET timescaledb.remote_data_fetcher = 'copy';
@ -201,8 +264,21 @@ WHERE EXISTS (
LATERAL (select * from metrics as ref_2) as subq_3
WHERE
(SELECT device_id FROM metrics_compressed limit 1 offset 3) >= ref_0.device
);
ERROR: COPY fetcher not supported
)
ORDER BY 1, 2;
ERROR: only cursor fetcher is supported for this query
SET timescaledb.remote_data_fetcher = 'prepared';
SELECT * FROM
conditions_dist1 ref_0
WHERE EXISTS (
SELECT FROM
distinct_on_distributed as ref_1,
LATERAL (select * from metrics as ref_2) as subq_3
WHERE
(SELECT device_id FROM metrics_compressed limit 1 offset 3) >= ref_0.device
)
ORDER BY 1, 2;
ERROR: only cursor fetcher is supported for this query
SET timescaledb.remote_data_fetcher = 'auto';
SELECT * FROM
conditions_dist1 ref_0
@ -213,7 +289,7 @@ WHERE EXISTS (
WHERE
(SELECT device_id FROM metrics_compressed limit 1 offset 3) >= ref_0.device
)
ORDER BY 1,2;
ORDER BY 1, 2;
time | device | value
------------------------------+--------+-------
Sun Jan 01 06:01:00 2017 PST | 1 | 1.2
@ -267,7 +343,7 @@ WHERE
AND m.ts BETWEEN '2021-08-17 00:00:00' AND '2021-08-17 01:00:00'
ORDER BY 1 DESC LIMIT 1;
ERROR: cannot use COPY fetcher because the plan is parameterized
-- Test copy fetcher when query is aborted before EOF due to LIMIT
-- Test fetcher when query is aborted before EOF due to LIMIT
SET timescaledb.remote_data_fetcher = 'copy';
SELECT * FROM metrics_dist ORDER BY time, device_id LIMIT 11;
time | device_id | v0 | v1 | v2 | v3
@ -285,6 +361,40 @@ SELECT * FROM metrics_dist ORDER BY time, device_id LIMIT 11;
Fri Dec 31 16:04:00 1999 PST | 1 | 2 | 3 | 1.5 |
(11 rows)
SET timescaledb.remote_data_fetcher = 'prepared';
SELECT * FROM metrics_dist ORDER BY time, device_id LIMIT 11;
time | device_id | v0 | v1 | v2 | v3
------------------------------+-----------+----+----+-----+----
Fri Dec 31 16:00:00 1999 PST | 1 | 2 | 3 | 1.5 |
Fri Dec 31 16:00:00 1999 PST | 2 | 3 | 4 | 2.5 |
Fri Dec 31 16:00:00 1999 PST | 3 | 4 | 5 | 3.5 |
Fri Dec 31 16:00:00 1999 PST | 4 | 5 | 6 | 4.5 |
Fri Dec 31 16:00:00 1999 PST | 5 | 6 | 7 | 5.5 |
Fri Dec 31 16:02:00 1999 PST | 1 | 2 | 3 | 1.5 |
Fri Dec 31 16:02:00 1999 PST | 2 | 3 | 4 | 2.5 |
Fri Dec 31 16:02:00 1999 PST | 3 | 4 | 5 | 3.5 |
Fri Dec 31 16:02:00 1999 PST | 4 | 5 | 6 | 4.5 |
Fri Dec 31 16:02:00 1999 PST | 5 | 6 | 7 | 5.5 |
Fri Dec 31 16:04:00 1999 PST | 1 | 2 | 3 | 1.5 |
(11 rows)
SET timescaledb.remote_data_fetcher = 'cursor';
SELECT * FROM metrics_dist ORDER BY time, device_id LIMIT 11;
time | device_id | v0 | v1 | v2 | v3
------------------------------+-----------+----+----+-----+----
Fri Dec 31 16:00:00 1999 PST | 1 | 2 | 3 | 1.5 |
Fri Dec 31 16:00:00 1999 PST | 2 | 3 | 4 | 2.5 |
Fri Dec 31 16:00:00 1999 PST | 3 | 4 | 5 | 3.5 |
Fri Dec 31 16:00:00 1999 PST | 4 | 5 | 6 | 4.5 |
Fri Dec 31 16:00:00 1999 PST | 5 | 6 | 7 | 5.5 |
Fri Dec 31 16:02:00 1999 PST | 1 | 2 | 3 | 1.5 |
Fri Dec 31 16:02:00 1999 PST | 2 | 3 | 4 | 2.5 |
Fri Dec 31 16:02:00 1999 PST | 3 | 4 | 5 | 3.5 |
Fri Dec 31 16:02:00 1999 PST | 4 | 5 | 6 | 4.5 |
Fri Dec 31 16:02:00 1999 PST | 5 | 6 | 7 | 5.5 |
Fri Dec 31 16:04:00 1999 PST | 1 | 2 | 3 | 1.5 |
(11 rows)
-- Verify that cursor fetcher can be rewind before EOF due to an
-- intermediate JOIN product reaching LIMIT
SET timescaledb.remote_data_fetcher = 'cursor';

@ -66,12 +66,17 @@ QUERY PLAN
Remote SQL: SELECT id FROM public.distinct_on_distributed WHERE _timescaledb_internal.chunks_in(public.distinct_on_distributed.*, ARRAY[..])
(19 rows)
-- This query can't work with copy fetcher.
-- This query can't work with copy or prepared fetcher.
set timescaledb.remote_data_fetcher = 'copy';
select 1 x from distinct_on_distributed t1, distinct_on_distributed t2
where t1.id = t2.id + 1
limit 1;
ERROR: COPY fetcher not supported
ERROR: only cursor fetcher is supported for this query
set timescaledb.remote_data_fetcher = 'prepared';
select 1 x from distinct_on_distributed t1, distinct_on_distributed t2
where t1.id = t2.id + 1
limit 1;
ERROR: only cursor fetcher is supported for this query
-- Check once again that 'auto' is used after 'copy'.
set timescaledb.remote_data_fetcher = 'auto';
select 1 x from distinct_on_distributed t1, distinct_on_distributed t2
@ -104,7 +109,7 @@ WHERE
) AS l
WHERE d.name ~~ d.name
)
ORDER BY 1,2;
ORDER BY 1, 2;
device_id | name
-----------+------
(0 rows)
@ -170,6 +175,26 @@ select * from disttable_with_bytea;
1001 |
(2 rows)
-- Prepared statement fetcher with bytea data
set timescaledb.remote_data_fetcher = 'prepared';
explain (analyze, verbose, costs off, timing off, summary off)
select * from disttable_with_bytea;
QUERY PLAN
Custom Scan (DataNodeScan) on public.disttable_with_bytea (actual rows=2 loops=1)
Output: disttable_with_bytea."time", disttable_with_bytea.bdata
Data node: data_node_3
Fetcher Type: Prepared statement
Chunks: _dist_hyper_X_X_chunk
Remote SQL: SELECT "time", bdata FROM public.disttable_with_bytea WHERE _timescaledb_internal.chunks_in(public.disttable_with_bytea.*, ARRAY[..])
(6 rows)
select * from disttable_with_bytea;
time | bdata
------+-------
1001 | \x
1001 |
(2 rows)
-- #4515 test for assertion failure in copy_fetcher_close
SET timescaledb.remote_data_fetcher = 'copy';
SELECT *
@ -190,6 +215,44 @@ WHERE EXISTS (
------+--------+-------
(0 rows)
SET timescaledb.remote_data_fetcher = 'prepared';
SELECT *
FROM
conditions ref_0
WHERE EXISTS (
SELECT FROM
distinct_on_distributed,
LATERAL (
SELECT *
FROM pg_class,
LATERAL (
SELECT ref_0.device FROM pg_class WHERE false LIMIT 1) as lat_1
) as lat_2
WHERE (SELECT 1 FROM pg_class LIMIT 1) >= ref_0.device
);
time | device | value
------+--------+-------
(0 rows)
SET timescaledb.remote_data_fetcher = 'cursor';
SELECT *
FROM
conditions ref_0
WHERE EXISTS (
SELECT FROM
distinct_on_distributed,
LATERAL (
SELECT *
FROM pg_class,
LATERAL (
SELECT ref_0.device FROM pg_class WHERE false LIMIT 1) as lat_1
) as lat_2
WHERE (SELECT 1 FROM pg_class LIMIT 1) >= ref_0.device
);
time | device | value
------+--------+-------
(0 rows)
-- #4518
-- test error handling for queries with multiple distributed hypertables
SET timescaledb.remote_data_fetcher = 'copy';
@ -201,8 +264,21 @@ WHERE EXISTS (
LATERAL (select * from metrics as ref_2) as subq_3
WHERE
(SELECT device_id FROM metrics_compressed limit 1 offset 3) >= ref_0.device
);
ERROR: COPY fetcher not supported
)
ORDER BY 1, 2;
ERROR: only cursor fetcher is supported for this query
SET timescaledb.remote_data_fetcher = 'prepared';
SELECT * FROM
conditions_dist1 ref_0
WHERE EXISTS (
SELECT FROM
distinct_on_distributed as ref_1,
LATERAL (select * from metrics as ref_2) as subq_3
WHERE
(SELECT device_id FROM metrics_compressed limit 1 offset 3) >= ref_0.device
)
ORDER BY 1, 2;
ERROR: only cursor fetcher is supported for this query
SET timescaledb.remote_data_fetcher = 'auto';
SELECT * FROM
conditions_dist1 ref_0
@ -213,7 +289,7 @@ WHERE EXISTS (
WHERE
(SELECT device_id FROM metrics_compressed limit 1 offset 3) >= ref_0.device
)
ORDER BY 1,2;
ORDER BY 1, 2;
time | device | value
------------------------------+--------+-------
Sun Jan 01 06:01:00 2017 PST | 1 | 1.2
@ -267,7 +343,7 @@ WHERE
AND m.ts BETWEEN '2021-08-17 00:00:00' AND '2021-08-17 01:00:00'
ORDER BY 1 DESC LIMIT 1;
ERROR: cannot use COPY fetcher because the plan is parameterized
-- Test copy fetcher when query is aborted before EOF due to LIMIT
-- Test fetcher when query is aborted before EOF due to LIMIT
SET timescaledb.remote_data_fetcher = 'copy';
SELECT * FROM metrics_dist ORDER BY time, device_id LIMIT 11;
time | device_id | v0 | v1 | v2 | v3
@ -285,6 +361,40 @@ SELECT * FROM metrics_dist ORDER BY time, device_id LIMIT 11;
Fri Dec 31 16:04:00 1999 PST | 1 | 2 | 3 | 1.5 |
(11 rows)
SET timescaledb.remote_data_fetcher = 'prepared';
SELECT * FROM metrics_dist ORDER BY time, device_id LIMIT 11;
time | device_id | v0 | v1 | v2 | v3
------------------------------+-----------+----+----+-----+----
Fri Dec 31 16:00:00 1999 PST | 1 | 2 | 3 | 1.5 |
Fri Dec 31 16:00:00 1999 PST | 2 | 3 | 4 | 2.5 |
Fri Dec 31 16:00:00 1999 PST | 3 | 4 | 5 | 3.5 |
Fri Dec 31 16:00:00 1999 PST | 4 | 5 | 6 | 4.5 |
Fri Dec 31 16:00:00 1999 PST | 5 | 6 | 7 | 5.5 |
Fri Dec 31 16:02:00 1999 PST | 1 | 2 | 3 | 1.5 |
Fri Dec 31 16:02:00 1999 PST | 2 | 3 | 4 | 2.5 |
Fri Dec 31 16:02:00 1999 PST | 3 | 4 | 5 | 3.5 |
Fri Dec 31 16:02:00 1999 PST | 4 | 5 | 6 | 4.5 |
Fri Dec 31 16:02:00 1999 PST | 5 | 6 | 7 | 5.5 |
Fri Dec 31 16:04:00 1999 PST | 1 | 2 | 3 | 1.5 |
(11 rows)
SET timescaledb.remote_data_fetcher = 'cursor';
SELECT * FROM metrics_dist ORDER BY time, device_id LIMIT 11;
time | device_id | v0 | v1 | v2 | v3
------------------------------+-----------+----+----+-----+----
Fri Dec 31 16:00:00 1999 PST | 1 | 2 | 3 | 1.5 |
Fri Dec 31 16:00:00 1999 PST | 2 | 3 | 4 | 2.5 |
Fri Dec 31 16:00:00 1999 PST | 3 | 4 | 5 | 3.5 |
Fri Dec 31 16:00:00 1999 PST | 4 | 5 | 6 | 4.5 |
Fri Dec 31 16:00:00 1999 PST | 5 | 6 | 7 | 5.5 |
Fri Dec 31 16:02:00 1999 PST | 1 | 2 | 3 | 1.5 |
Fri Dec 31 16:02:00 1999 PST | 2 | 3 | 4 | 2.5 |
Fri Dec 31 16:02:00 1999 PST | 3 | 4 | 5 | 3.5 |
Fri Dec 31 16:02:00 1999 PST | 4 | 5 | 6 | 4.5 |
Fri Dec 31 16:02:00 1999 PST | 5 | 6 | 7 | 5.5 |
Fri Dec 31 16:04:00 1999 PST | 1 | 2 | 3 | 1.5 |
(11 rows)
-- Verify that cursor fetcher can be rewind before EOF due to an
-- intermediate JOIN product reaching LIMIT
SET timescaledb.remote_data_fetcher = 'cursor';

@ -66,12 +66,17 @@ QUERY PLAN
Remote SQL: SELECT id FROM public.distinct_on_distributed WHERE _timescaledb_internal.chunks_in(public.distinct_on_distributed.*, ARRAY[..])
(19 rows)
-- This query can't work with copy fetcher.
-- This query can't work with copy or prepared fetcher.
set timescaledb.remote_data_fetcher = 'copy';
select 1 x from distinct_on_distributed t1, distinct_on_distributed t2
where t1.id = t2.id + 1
limit 1;
ERROR: COPY fetcher not supported
ERROR: only cursor fetcher is supported for this query
set timescaledb.remote_data_fetcher = 'prepared';
select 1 x from distinct_on_distributed t1, distinct_on_distributed t2
where t1.id = t2.id + 1
limit 1;
ERROR: only cursor fetcher is supported for this query
-- Check once again that 'auto' is used after 'copy'.
set timescaledb.remote_data_fetcher = 'auto';
select 1 x from distinct_on_distributed t1, distinct_on_distributed t2
@ -104,7 +109,7 @@ WHERE
) AS l
WHERE d.name ~~ d.name
)
ORDER BY 1,2;
ORDER BY 1, 2;
device_id | name
-----------+------
(0 rows)
@ -170,6 +175,26 @@ select * from disttable_with_bytea;
1001 |
(2 rows)
-- Prepared statement fetcher with bytea data
set timescaledb.remote_data_fetcher = 'prepared';
explain (analyze, verbose, costs off, timing off, summary off)
select * from disttable_with_bytea;
QUERY PLAN
Custom Scan (DataNodeScan) on public.disttable_with_bytea (actual rows=2 loops=1)
Output: disttable_with_bytea."time", disttable_with_bytea.bdata
Data node: data_node_3
Fetcher Type: Prepared statement
Chunks: _dist_hyper_X_X_chunk
Remote SQL: SELECT "time", bdata FROM public.disttable_with_bytea WHERE _timescaledb_internal.chunks_in(public.disttable_with_bytea.*, ARRAY[..])
(6 rows)
select * from disttable_with_bytea;
time | bdata
------+-------
1001 | \x
1001 |
(2 rows)
-- #4515 test for assertion failure in copy_fetcher_close
SET timescaledb.remote_data_fetcher = 'copy';
SELECT *
@ -190,6 +215,44 @@ WHERE EXISTS (
------+--------+-------
(0 rows)
SET timescaledb.remote_data_fetcher = 'prepared';
SELECT *
FROM
conditions ref_0
WHERE EXISTS (
SELECT FROM
distinct_on_distributed,
LATERAL (
SELECT *
FROM pg_class,
LATERAL (
SELECT ref_0.device FROM pg_class WHERE false LIMIT 1) as lat_1
) as lat_2
WHERE (SELECT 1 FROM pg_class LIMIT 1) >= ref_0.device
);
time | device | value
------+--------+-------
(0 rows)
SET timescaledb.remote_data_fetcher = 'cursor';
SELECT *
FROM
conditions ref_0
WHERE EXISTS (
SELECT FROM
distinct_on_distributed,
LATERAL (
SELECT *
FROM pg_class,
LATERAL (
SELECT ref_0.device FROM pg_class WHERE false LIMIT 1) as lat_1
) as lat_2
WHERE (SELECT 1 FROM pg_class LIMIT 1) >= ref_0.device
);
time | device | value
------+--------+-------
(0 rows)
-- #4518
-- test error handling for queries with multiple distributed hypertables
SET timescaledb.remote_data_fetcher = 'copy';
@ -201,8 +264,21 @@ WHERE EXISTS (
LATERAL (select * from metrics as ref_2) as subq_3
WHERE
(SELECT device_id FROM metrics_compressed limit 1 offset 3) >= ref_0.device
);
ERROR: COPY fetcher not supported
)
ORDER BY 1, 2;
ERROR: only cursor fetcher is supported for this query
SET timescaledb.remote_data_fetcher = 'prepared';
SELECT * FROM
conditions_dist1 ref_0
WHERE EXISTS (
SELECT FROM
distinct_on_distributed as ref_1,
LATERAL (select * from metrics as ref_2) as subq_3
WHERE
(SELECT device_id FROM metrics_compressed limit 1 offset 3) >= ref_0.device
)
ORDER BY 1, 2;
ERROR: only cursor fetcher is supported for this query
SET timescaledb.remote_data_fetcher = 'auto';
SELECT * FROM
conditions_dist1 ref_0
@ -213,7 +289,7 @@ WHERE EXISTS (
WHERE
(SELECT device_id FROM metrics_compressed limit 1 offset 3) >= ref_0.device
)
ORDER BY 1,2;
ORDER BY 1, 2;
time | device | value
------------------------------+--------+-------
Sun Jan 01 06:01:00 2017 PST | 1 | 1.2
@ -267,7 +343,7 @@ WHERE
AND m.ts BETWEEN '2021-08-17 00:00:00' AND '2021-08-17 01:00:00'
ORDER BY 1 DESC LIMIT 1;
ERROR: cannot use COPY fetcher because the plan is parameterized
-- Test copy fetcher when query is aborted before EOF due to LIMIT
-- Test fetcher when query is aborted before EOF due to LIMIT
SET timescaledb.remote_data_fetcher = 'copy';
SELECT * FROM metrics_dist ORDER BY time, device_id LIMIT 11;
time | device_id | v0 | v1 | v2 | v3
@ -285,6 +361,40 @@ SELECT * FROM metrics_dist ORDER BY time, device_id LIMIT 11;
Fri Dec 31 16:04:00 1999 PST | 1 | 2 | 3 | 1.5 |
(11 rows)
SET timescaledb.remote_data_fetcher = 'prepared';
SELECT * FROM metrics_dist ORDER BY time, device_id LIMIT 11;
time | device_id | v0 | v1 | v2 | v3
------------------------------+-----------+----+----+-----+----
Fri Dec 31 16:00:00 1999 PST | 1 | 2 | 3 | 1.5 |
Fri Dec 31 16:00:00 1999 PST | 2 | 3 | 4 | 2.5 |
Fri Dec 31 16:00:00 1999 PST | 3 | 4 | 5 | 3.5 |
Fri Dec 31 16:00:00 1999 PST | 4 | 5 | 6 | 4.5 |
Fri Dec 31 16:00:00 1999 PST | 5 | 6 | 7 | 5.5 |
Fri Dec 31 16:02:00 1999 PST | 1 | 2 | 3 | 1.5 |
Fri Dec 31 16:02:00 1999 PST | 2 | 3 | 4 | 2.5 |
Fri Dec 31 16:02:00 1999 PST | 3 | 4 | 5 | 3.5 |
Fri Dec 31 16:02:00 1999 PST | 4 | 5 | 6 | 4.5 |
Fri Dec 31 16:02:00 1999 PST | 5 | 6 | 7 | 5.5 |
Fri Dec 31 16:04:00 1999 PST | 1 | 2 | 3 | 1.5 |
(11 rows)
SET timescaledb.remote_data_fetcher = 'cursor';
SELECT * FROM metrics_dist ORDER BY time, device_id LIMIT 11;
time | device_id | v0 | v1 | v2 | v3
------------------------------+-----------+----+----+-----+----
Fri Dec 31 16:00:00 1999 PST | 1 | 2 | 3 | 1.5 |
Fri Dec 31 16:00:00 1999 PST | 2 | 3 | 4 | 2.5 |
Fri Dec 31 16:00:00 1999 PST | 3 | 4 | 5 | 3.5 |
Fri Dec 31 16:00:00 1999 PST | 4 | 5 | 6 | 4.5 |
Fri Dec 31 16:00:00 1999 PST | 5 | 6 | 7 | 5.5 |
Fri Dec 31 16:02:00 1999 PST | 1 | 2 | 3 | 1.5 |
Fri Dec 31 16:02:00 1999 PST | 2 | 3 | 4 | 2.5 |
Fri Dec 31 16:02:00 1999 PST | 3 | 4 | 5 | 3.5 |
Fri Dec 31 16:02:00 1999 PST | 4 | 5 | 6 | 4.5 |
Fri Dec 31 16:02:00 1999 PST | 5 | 6 | 7 | 5.5 |
Fri Dec 31 16:04:00 1999 PST | 1 | 2 | 3 | 1.5 |
(11 rows)
-- Verify that cursor fetcher can be rewind before EOF due to an
-- intermediate JOIN product reaching LIMIT
SET timescaledb.remote_data_fetcher = 'cursor';

@ -67,12 +67,17 @@ QUERY PLAN
Remote SQL: SELECT id FROM public.distinct_on_distributed WHERE _timescaledb_internal.chunks_in(public.distinct_on_distributed.*, ARRAY[..])
(19 rows)
-- This query can't work with copy fetcher.
-- This query can't work with copy or prepared fetcher.
set timescaledb.remote_data_fetcher = 'copy';
select 1 x from distinct_on_distributed t1, distinct_on_distributed t2
where t1.id = t2.id + 1
limit 1;
ERROR: COPY fetcher not supported
ERROR: only cursor fetcher is supported for this query
set timescaledb.remote_data_fetcher = 'prepared';
select 1 x from distinct_on_distributed t1, distinct_on_distributed t2
where t1.id = t2.id + 1
limit 1;
ERROR: only cursor fetcher is supported for this query
-- Check once again that 'auto' is used after 'copy'.
set timescaledb.remote_data_fetcher = 'auto';
select 1 x from distinct_on_distributed t1, distinct_on_distributed t2
@ -105,7 +110,7 @@ WHERE
) AS l
WHERE d.name ~~ d.name
)
ORDER BY 1,2;
ORDER BY 1, 2;
device_id | name
-----------+------
(0 rows)
@ -171,6 +176,26 @@ select * from disttable_with_bytea;
1001 |
(2 rows)
-- Prepared statement fetcher with bytea data
set timescaledb.remote_data_fetcher = 'prepared';
explain (analyze, verbose, costs off, timing off, summary off)
select * from disttable_with_bytea;
QUERY PLAN
Custom Scan (DataNodeScan) on public.disttable_with_bytea (actual rows=2 loops=1)
Output: disttable_with_bytea."time", disttable_with_bytea.bdata
Data node: data_node_3
Fetcher Type: Prepared statement
Chunks: _dist_hyper_X_X_chunk
Remote SQL: SELECT "time", bdata FROM public.disttable_with_bytea WHERE _timescaledb_internal.chunks_in(public.disttable_with_bytea.*, ARRAY[..])
(6 rows)
select * from disttable_with_bytea;
time | bdata
------+-------
1001 | \x
1001 |
(2 rows)
-- #4515 test for assertion failure in copy_fetcher_close
SET timescaledb.remote_data_fetcher = 'copy';
SELECT *
@ -191,6 +216,44 @@ WHERE EXISTS (
------+--------+-------
(0 rows)
SET timescaledb.remote_data_fetcher = 'prepared';
SELECT *
FROM
conditions ref_0
WHERE EXISTS (
SELECT FROM
distinct_on_distributed,
LATERAL (
SELECT *
FROM pg_class,
LATERAL (
SELECT ref_0.device FROM pg_class WHERE false LIMIT 1) as lat_1
) as lat_2
WHERE (SELECT 1 FROM pg_class LIMIT 1) >= ref_0.device
);
time | device | value
------+--------+-------
(0 rows)
SET timescaledb.remote_data_fetcher = 'cursor';
SELECT *
FROM
conditions ref_0
WHERE EXISTS (
SELECT FROM
distinct_on_distributed,
LATERAL (
SELECT *
FROM pg_class,
LATERAL (
SELECT ref_0.device FROM pg_class WHERE false LIMIT 1) as lat_1
) as lat_2
WHERE (SELECT 1 FROM pg_class LIMIT 1) >= ref_0.device
);
time | device | value
------+--------+-------
(0 rows)
-- #4518
-- test error handling for queries with multiple distributed hypertables
SET timescaledb.remote_data_fetcher = 'copy';
@ -202,8 +265,21 @@ WHERE EXISTS (
LATERAL (select * from metrics as ref_2) as subq_3
WHERE
(SELECT device_id FROM metrics_compressed limit 1 offset 3) >= ref_0.device
);
ERROR: COPY fetcher not supported
)
ORDER BY 1, 2;
ERROR: only cursor fetcher is supported for this query
SET timescaledb.remote_data_fetcher = 'prepared';
SELECT * FROM
conditions_dist1 ref_0
WHERE EXISTS (
SELECT FROM
distinct_on_distributed as ref_1,
LATERAL (select * from metrics as ref_2) as subq_3
WHERE
(SELECT device_id FROM metrics_compressed limit 1 offset 3) >= ref_0.device
)
ORDER BY 1, 2;
ERROR: only cursor fetcher is supported for this query
SET timescaledb.remote_data_fetcher = 'auto';
SELECT * FROM
conditions_dist1 ref_0
@ -214,7 +290,7 @@ WHERE EXISTS (
WHERE
(SELECT device_id FROM metrics_compressed limit 1 offset 3) >= ref_0.device
)
ORDER BY 1,2;
ORDER BY 1, 2;
time | device | value
------------------------------+--------+-------
Sun Jan 01 06:01:00 2017 PST | 1 | 1.2
@ -268,7 +344,7 @@ WHERE
AND m.ts BETWEEN '2021-08-17 00:00:00' AND '2021-08-17 01:00:00'
ORDER BY 1 DESC LIMIT 1;
ERROR: cannot use COPY fetcher because the plan is parameterized
-- Test copy fetcher when query is aborted before EOF due to LIMIT
-- Test fetcher when query is aborted before EOF due to LIMIT
SET timescaledb.remote_data_fetcher = 'copy';
SELECT * FROM metrics_dist ORDER BY time, device_id LIMIT 11;
time | device_id | v0 | v1 | v2 | v3
@ -286,6 +362,40 @@ SELECT * FROM metrics_dist ORDER BY time, device_id LIMIT 11;
Fri Dec 31 16:04:00 1999 PST | 1 | 2 | 3 | 1.5 |
(11 rows)
SET timescaledb.remote_data_fetcher = 'prepared';
SELECT * FROM metrics_dist ORDER BY time, device_id LIMIT 11;
time | device_id | v0 | v1 | v2 | v3
------------------------------+-----------+----+----+-----+----
Fri Dec 31 16:00:00 1999 PST | 1 | 2 | 3 | 1.5 |
Fri Dec 31 16:00:00 1999 PST | 2 | 3 | 4 | 2.5 |
Fri Dec 31 16:00:00 1999 PST | 3 | 4 | 5 | 3.5 |
Fri Dec 31 16:00:00 1999 PST | 4 | 5 | 6 | 4.5 |
Fri Dec 31 16:00:00 1999 PST | 5 | 6 | 7 | 5.5 |
Fri Dec 31 16:02:00 1999 PST | 1 | 2 | 3 | 1.5 |
Fri Dec 31 16:02:00 1999 PST | 2 | 3 | 4 | 2.5 |
Fri Dec 31 16:02:00 1999 PST | 3 | 4 | 5 | 3.5 |
Fri Dec 31 16:02:00 1999 PST | 4 | 5 | 6 | 4.5 |
Fri Dec 31 16:02:00 1999 PST | 5 | 6 | 7 | 5.5 |
Fri Dec 31 16:04:00 1999 PST | 1 | 2 | 3 | 1.5 |
(11 rows)
SET timescaledb.remote_data_fetcher = 'cursor';
SELECT * FROM metrics_dist ORDER BY time, device_id LIMIT 11;
time | device_id | v0 | v1 | v2 | v3
------------------------------+-----------+----+----+-----+----
Fri Dec 31 16:00:00 1999 PST | 1 | 2 | 3 | 1.5 |
Fri Dec 31 16:00:00 1999 PST | 2 | 3 | 4 | 2.5 |
Fri Dec 31 16:00:00 1999 PST | 3 | 4 | 5 | 3.5 |
Fri Dec 31 16:00:00 1999 PST | 4 | 5 | 6 | 4.5 |
Fri Dec 31 16:00:00 1999 PST | 5 | 6 | 7 | 5.5 |
Fri Dec 31 16:02:00 1999 PST | 1 | 2 | 3 | 1.5 |
Fri Dec 31 16:02:00 1999 PST | 2 | 3 | 4 | 2.5 |
Fri Dec 31 16:02:00 1999 PST | 3 | 4 | 5 | 3.5 |
Fri Dec 31 16:02:00 1999 PST | 4 | 5 | 6 | 4.5 |
Fri Dec 31 16:02:00 1999 PST | 5 | 6 | 7 | 5.5 |
Fri Dec 31 16:04:00 1999 PST | 1 | 2 | 3 | 1.5 |
(11 rows)
-- Verify that cursor fetcher can be rewind before EOF due to an
-- intermediate JOIN product reaching LIMIT
SET timescaledb.remote_data_fetcher = 'cursor';

@ -1,229 +0,0 @@
-- This file and its contents are licensed under the Timescale License.
-- Please see the included NOTICE for copyright information and
-- LICENSE-TIMESCALE for a copy of the license.
-- Import setup file to data nodes.
\unset ECHO
-- Disable SSL to get stable error output across versions. SSL adds some output
-- that changed in PG 14.
set timescaledb.debug_enable_ssl to off;
set client_min_messages to error;
SET timescaledb.hide_data_node_name_in_errors = 'on';
-- A relatively big table on one data node
create table metrics_dist_remote_error(like metrics_dist);
select table_name from create_distributed_hypertable('metrics_dist_remote_error', 'time', 'device_id',
data_nodes => '{"data_node_1"}');
table_name
metrics_dist_remote_error
(1 row)
insert into metrics_dist_remote_error select * from metrics_dist order by metrics_dist limit 20000;
-- The error messages vary wildly between the Postgres versions, dependent on
-- the particular behavior of libqp in this or that case. The purpose of this
-- test is not to solidify this accidental behavior, but to merely exercise the
-- error handling code to make sure it doesn't have fatal errors. Unfortunately,
-- there is no way to suppress error output from a psql script.
set client_min_messages to ERROR;
\set ON_ERROR_STOP off
set timescaledb.remote_data_fetcher = 'copy';
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(0, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 0 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(1, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(2, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(701, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 701 rows, 701 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 10000 rows, 10000 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(16384, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 16384 rows, 16384 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000000, device_id)::int != 0;
QUERY PLAN
Custom Scan (DataNodeScan) on public.metrics_dist_remote_error (actual rows=20000 loops=1)
Output: 1
Data node: data_node_1
Fetcher Type: COPY
Chunks: _dist_hyper_X_X_chunk, _dist_hyper_X_X_chunk
Remote SQL: SELECT NULL FROM public.metrics_dist_remote_error WHERE _timescaledb_internal.chunks_in(public.metrics_dist_remote_error.*, ARRAY[..]) AND ((public.ts_debug_shippable_error_after_n_rows(10000000, device_id) <> 0))
(6 rows)
-- We don't test fatal errors here, because PG versions before 14 are unable to
-- report them properly to the access node, so we get different errors in these
-- versions.
-- Now test the same with the cursor fetcher.
set timescaledb.remote_data_fetcher = 'cursor';
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(0, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 0 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(1, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(2, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(701, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 701 rows, 701 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 10000 rows, 10000 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000000, device_id)::int != 0;
QUERY PLAN
Custom Scan (DataNodeScan) on public.metrics_dist_remote_error (actual rows=20000 loops=1)
Output: 1
Data node: data_node_1
Fetcher Type: Cursor
Chunks: _dist_hyper_X_X_chunk, _dist_hyper_X_X_chunk
Remote SQL: SELECT NULL FROM public.metrics_dist_remote_error WHERE _timescaledb_internal.chunks_in(public.metrics_dist_remote_error.*, ARRAY[..]) AND ((public.ts_debug_shippable_error_after_n_rows(10000000, device_id) <> 0))
(6 rows)
-- Table with broken send for a data type.
create table metrics_dist_bs(like metrics_dist);
alter table metrics_dist_bs alter column v0 type bs;
select table_name from create_distributed_hypertable('metrics_dist_bs',
'time', 'device_id');
table_name
metrics_dist_bs
(1 row)
set timescaledb.enable_connection_binary_data to off;
insert into metrics_dist_bs
select * from metrics_dist_remote_error;
set timescaledb.enable_connection_binary_data to on;
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_bs;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
drop table metrics_dist_bs;
-- Table with broken receive for a data type.
create table metrics_dist_br(like metrics_dist);
alter table metrics_dist_br alter column v0 type br;
select table_name from create_distributed_hypertable('metrics_dist_br',
'time', 'device_id');
table_name
metrics_dist_br
(1 row)
select hypertable_name, replication_factor from timescaledb_information.hypertables
where hypertable_name = 'metrics_dist_br';
hypertable_name | replication_factor
-----------------+--------------------
metrics_dist_br | 1
(1 row)
-- Test that INSERT and COPY fail on data nodes.
-- Note that we use the text format for the COPY input, so that the access node
-- doesn't call `recv` and fail by itself. It's going to use binary format for
-- transfer to data nodes regardless of the input format.
set timescaledb.dist_copy_transfer_format = 'binary';
-- First, create the reference.
\copy (select * from metrics_dist_remote_error) to 'dist_remote_error.text' with (format text);
-- We have to test various interleavings of COPY and INSERT to check that
-- one can recover from connection failure states introduced by another.
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
-- Fail at different points
set timescaledb.debug_broken_sendrecv_throw_after = 1;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 2;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1023;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1023 rows, 1023 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1024;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1024 rows, 1024 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1025;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1025 rows, 1025 rows seen
reset timescaledb.debug_broken_sendrecv_throw_after;
-- Same with different replication factor
truncate metrics_dist_br;
select set_replication_factor('metrics_dist_br', 2);
set_replication_factor
(1 row)
select hypertable_name, replication_factor from timescaledb_information.hypertables
where hypertable_name = 'metrics_dist_br';
hypertable_name | replication_factor
-----------------+--------------------
metrics_dist_br | 2
(1 row)
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 2;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1023;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1023 rows, 1023 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1024;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1024 rows, 1024 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1025;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1025 rows, 1025 rows seen
-- Should succeed with text format for data transfer.
set timescaledb.dist_copy_transfer_format = 'text';
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
-- Final check.
set timescaledb.enable_connection_binary_data = false;
select count(*) from metrics_dist_br;
count
20000
(1 row)
set timescaledb.enable_connection_binary_data = true;
reset timescaledb.debug_broken_sendrecv_throw_after;
drop table metrics_dist_br;
-- Table with sleepy receive for a data type, to improve coverage of the waiting
-- code on the access node.
create table metrics_dist_bl(like metrics_dist);
alter table metrics_dist_bl alter column v0 type bl;
select table_name from create_distributed_hypertable('metrics_dist_bl',
'time', 'device_id');
table_name
metrics_dist_bl
(1 row)
-- We're using sleepy recv function, so need the binary transfer format for it
-- to be called on the data nodes.
set timescaledb.dist_copy_transfer_format = 'binary';
-- Test INSERT and COPY with slow data node.
\copy metrics_dist_bl from 'dist_remote_error.text' with (format text);
insert into metrics_dist_bl select * from metrics_dist_remote_error;
select count(*) from metrics_dist_bl;
count
40000
(1 row)
drop table metrics_dist_bl;
drop table metrics_dist_remote_error;

@ -1,229 +0,0 @@
-- This file and its contents are licensed under the Timescale License.
-- Please see the included NOTICE for copyright information and
-- LICENSE-TIMESCALE for a copy of the license.
-- Import setup file to data nodes.
\unset ECHO
-- Disable SSL to get stable error output across versions. SSL adds some output
-- that changed in PG 14.
set timescaledb.debug_enable_ssl to off;
set client_min_messages to error;
SET timescaledb.hide_data_node_name_in_errors = 'on';
-- A relatively big table on one data node
create table metrics_dist_remote_error(like metrics_dist);
select table_name from create_distributed_hypertable('metrics_dist_remote_error', 'time', 'device_id',
data_nodes => '{"data_node_1"}');
table_name
metrics_dist_remote_error
(1 row)
insert into metrics_dist_remote_error select * from metrics_dist order by metrics_dist limit 20000;
-- The error messages vary wildly between the Postgres versions, dependent on
-- the particular behavior of libqp in this or that case. The purpose of this
-- test is not to solidify this accidental behavior, but to merely exercise the
-- error handling code to make sure it doesn't have fatal errors. Unfortunately,
-- there is no way to suppress error output from a psql script.
set client_min_messages to ERROR;
\set ON_ERROR_STOP off
set timescaledb.remote_data_fetcher = 'copy';
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(0, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 0 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(1, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(2, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(701, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 701 rows, 701 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 10000 rows, 10000 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(16384, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 16384 rows, 16384 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000000, device_id)::int != 0;
QUERY PLAN
Custom Scan (DataNodeScan) on public.metrics_dist_remote_error (actual rows=20000 loops=1)
Output: 1
Data node: data_node_1
Fetcher Type: COPY
Chunks: _dist_hyper_X_X_chunk, _dist_hyper_X_X_chunk
Remote SQL: SELECT NULL FROM public.metrics_dist_remote_error WHERE _timescaledb_internal.chunks_in(public.metrics_dist_remote_error.*, ARRAY[..]) AND ((public.ts_debug_shippable_error_after_n_rows(10000000, device_id) <> 0))
(6 rows)
-- We don't test fatal errors here, because PG versions before 14 are unable to
-- report them properly to the access node, so we get different errors in these
-- versions.
-- Now test the same with the cursor fetcher.
set timescaledb.remote_data_fetcher = 'cursor';
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(0, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 0 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(1, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(2, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(701, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 701 rows, 701 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 10000 rows, 10000 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000000, device_id)::int != 0;
QUERY PLAN
Custom Scan (DataNodeScan) on public.metrics_dist_remote_error (actual rows=20000 loops=1)
Output: 1
Data node: data_node_1
Fetcher Type: Cursor
Chunks: _dist_hyper_X_X_chunk, _dist_hyper_X_X_chunk
Remote SQL: SELECT NULL FROM public.metrics_dist_remote_error WHERE _timescaledb_internal.chunks_in(public.metrics_dist_remote_error.*, ARRAY[..]) AND ((public.ts_debug_shippable_error_after_n_rows(10000000, device_id) <> 0))
(6 rows)
-- Table with broken send for a data type.
create table metrics_dist_bs(like metrics_dist);
alter table metrics_dist_bs alter column v0 type bs;
select table_name from create_distributed_hypertable('metrics_dist_bs',
'time', 'device_id');
table_name
metrics_dist_bs
(1 row)
set timescaledb.enable_connection_binary_data to off;
insert into metrics_dist_bs
select * from metrics_dist_remote_error;
set timescaledb.enable_connection_binary_data to on;
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_bs;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
drop table metrics_dist_bs;
-- Table with broken receive for a data type.
create table metrics_dist_br(like metrics_dist);
alter table metrics_dist_br alter column v0 type br;
select table_name from create_distributed_hypertable('metrics_dist_br',
'time', 'device_id');
table_name
metrics_dist_br
(1 row)
select hypertable_name, replication_factor from timescaledb_information.hypertables
where hypertable_name = 'metrics_dist_br';
hypertable_name | replication_factor
-----------------+--------------------
metrics_dist_br | 1
(1 row)
-- Test that INSERT and COPY fail on data nodes.
-- Note that we use the text format for the COPY input, so that the access node
-- doesn't call `recv` and fail by itself. It's going to use binary format for
-- transfer to data nodes regardless of the input format.
set timescaledb.dist_copy_transfer_format = 'binary';
-- First, create the reference.
\copy (select * from metrics_dist_remote_error) to 'dist_remote_error.text' with (format text);
-- We have to test various interleavings of COPY and INSERT to check that
-- one can recover from connection failure states introduced by another.
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
-- Fail at different points
set timescaledb.debug_broken_sendrecv_throw_after = 1;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 2;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1023;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1023 rows, 1023 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1024;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1024 rows, 1024 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1025;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1025 rows, 1025 rows seen
reset timescaledb.debug_broken_sendrecv_throw_after;
-- Same with different replication factor
truncate metrics_dist_br;
select set_replication_factor('metrics_dist_br', 2);
set_replication_factor
(1 row)
select hypertable_name, replication_factor from timescaledb_information.hypertables
where hypertable_name = 'metrics_dist_br';
hypertable_name | replication_factor
-----------------+--------------------
metrics_dist_br | 2
(1 row)
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 2;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1023;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1023 rows, 1023 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1024;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1024 rows, 1024 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1025;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1025 rows, 1025 rows seen
-- Should succeed with text format for data transfer.
set timescaledb.dist_copy_transfer_format = 'text';
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
-- Final check.
set timescaledb.enable_connection_binary_data = false;
select count(*) from metrics_dist_br;
count
20000
(1 row)
set timescaledb.enable_connection_binary_data = true;
reset timescaledb.debug_broken_sendrecv_throw_after;
drop table metrics_dist_br;
-- Table with sleepy receive for a data type, to improve coverage of the waiting
-- code on the access node.
create table metrics_dist_bl(like metrics_dist);
alter table metrics_dist_bl alter column v0 type bl;
select table_name from create_distributed_hypertable('metrics_dist_bl',
'time', 'device_id');
table_name
metrics_dist_bl
(1 row)
-- We're using sleepy recv function, so need the binary transfer format for it
-- to be called on the data nodes.
set timescaledb.dist_copy_transfer_format = 'binary';
-- Test INSERT and COPY with slow data node.
\copy metrics_dist_bl from 'dist_remote_error.text' with (format text);
insert into metrics_dist_bl select * from metrics_dist_remote_error;
select count(*) from metrics_dist_bl;
count
40000
(1 row)
drop table metrics_dist_bl;
drop table metrics_dist_remote_error;

@ -1,229 +0,0 @@
-- This file and its contents are licensed under the Timescale License.
-- Please see the included NOTICE for copyright information and
-- LICENSE-TIMESCALE for a copy of the license.
-- Import setup file to data nodes.
\unset ECHO
-- Disable SSL to get stable error output across versions. SSL adds some output
-- that changed in PG 14.
set timescaledb.debug_enable_ssl to off;
set client_min_messages to error;
SET timescaledb.hide_data_node_name_in_errors = 'on';
-- A relatively big table on one data node
create table metrics_dist_remote_error(like metrics_dist);
select table_name from create_distributed_hypertable('metrics_dist_remote_error', 'time', 'device_id',
data_nodes => '{"data_node_1"}');
table_name
metrics_dist_remote_error
(1 row)
insert into metrics_dist_remote_error select * from metrics_dist order by metrics_dist limit 20000;
-- The error messages vary wildly between the Postgres versions, dependent on
-- the particular behavior of libqp in this or that case. The purpose of this
-- test is not to solidify this accidental behavior, but to merely exercise the
-- error handling code to make sure it doesn't have fatal errors. Unfortunately,
-- there is no way to suppress error output from a psql script.
set client_min_messages to ERROR;
\set ON_ERROR_STOP off
set timescaledb.remote_data_fetcher = 'copy';
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(0, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 0 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(1, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(2, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(701, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 701 rows, 701 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 10000 rows, 10000 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(16384, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 16384 rows, 16384 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000000, device_id)::int != 0;
QUERY PLAN
Custom Scan (DataNodeScan) on public.metrics_dist_remote_error (actual rows=20000 loops=1)
Output: 1
Data node: data_node_1
Fetcher Type: COPY
Chunks: _dist_hyper_X_X_chunk, _dist_hyper_X_X_chunk
Remote SQL: SELECT NULL FROM public.metrics_dist_remote_error WHERE _timescaledb_internal.chunks_in(public.metrics_dist_remote_error.*, ARRAY[..]) AND ((public.ts_debug_shippable_error_after_n_rows(10000000, device_id) <> 0))
(6 rows)
-- We don't test fatal errors here, because PG versions before 14 are unable to
-- report them properly to the access node, so we get different errors in these
-- versions.
-- Now test the same with the cursor fetcher.
set timescaledb.remote_data_fetcher = 'cursor';
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(0, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 0 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(1, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(2, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(701, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 701 rows, 701 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 10000 rows, 10000 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000000, device_id)::int != 0;
QUERY PLAN
Custom Scan (DataNodeScan) on public.metrics_dist_remote_error (actual rows=20000 loops=1)
Output: 1
Data node: data_node_1
Fetcher Type: Cursor
Chunks: _dist_hyper_X_X_chunk, _dist_hyper_X_X_chunk
Remote SQL: SELECT NULL FROM public.metrics_dist_remote_error WHERE _timescaledb_internal.chunks_in(public.metrics_dist_remote_error.*, ARRAY[..]) AND ((public.ts_debug_shippable_error_after_n_rows(10000000, device_id) <> 0))
(6 rows)
-- Table with broken send for a data type.
create table metrics_dist_bs(like metrics_dist);
alter table metrics_dist_bs alter column v0 type bs;
select table_name from create_distributed_hypertable('metrics_dist_bs',
'time', 'device_id');
table_name
metrics_dist_bs
(1 row)
set timescaledb.enable_connection_binary_data to off;
insert into metrics_dist_bs
select * from metrics_dist_remote_error;
set timescaledb.enable_connection_binary_data to on;
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_bs;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
drop table metrics_dist_bs;
-- Table with broken receive for a data type.
create table metrics_dist_br(like metrics_dist);
alter table metrics_dist_br alter column v0 type br;
select table_name from create_distributed_hypertable('metrics_dist_br',
'time', 'device_id');
table_name
metrics_dist_br
(1 row)
select hypertable_name, replication_factor from timescaledb_information.hypertables
where hypertable_name = 'metrics_dist_br';
hypertable_name | replication_factor
-----------------+--------------------
metrics_dist_br | 1
(1 row)
-- Test that INSERT and COPY fail on data nodes.
-- Note that we use the text format for the COPY input, so that the access node
-- doesn't call `recv` and fail by itself. It's going to use binary format for
-- transfer to data nodes regardless of the input format.
set timescaledb.dist_copy_transfer_format = 'binary';
-- First, create the reference.
\copy (select * from metrics_dist_remote_error) to 'dist_remote_error.text' with (format text);
-- We have to test various interleavings of COPY and INSERT to check that
-- one can recover from connection failure states introduced by another.
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
-- Fail at different points
set timescaledb.debug_broken_sendrecv_throw_after = 1;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 2;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1023;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1023 rows, 1023 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1024;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1024 rows, 1024 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1025;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1025 rows, 1025 rows seen
reset timescaledb.debug_broken_sendrecv_throw_after;
-- Same with different replication factor
truncate metrics_dist_br;
select set_replication_factor('metrics_dist_br', 2);
set_replication_factor
(1 row)
select hypertable_name, replication_factor from timescaledb_information.hypertables
where hypertable_name = 'metrics_dist_br';
hypertable_name | replication_factor
-----------------+--------------------
metrics_dist_br | 2
(1 row)
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 2;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1023;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1023 rows, 1023 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1024;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1024 rows, 1024 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1025;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1025 rows, 1025 rows seen
-- Should succeed with text format for data transfer.
set timescaledb.dist_copy_transfer_format = 'text';
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
-- Final check.
set timescaledb.enable_connection_binary_data = false;
select count(*) from metrics_dist_br;
count
20000
(1 row)
set timescaledb.enable_connection_binary_data = true;
reset timescaledb.debug_broken_sendrecv_throw_after;
drop table metrics_dist_br;
-- Table with sleepy receive for a data type, to improve coverage of the waiting
-- code on the access node.
create table metrics_dist_bl(like metrics_dist);
alter table metrics_dist_bl alter column v0 type bl;
select table_name from create_distributed_hypertable('metrics_dist_bl',
'time', 'device_id');
table_name
metrics_dist_bl
(1 row)
-- We're using sleepy recv function, so need the binary transfer format for it
-- to be called on the data nodes.
set timescaledb.dist_copy_transfer_format = 'binary';
-- Test INSERT and COPY with slow data node.
\copy metrics_dist_bl from 'dist_remote_error.text' with (format text);
insert into metrics_dist_bl select * from metrics_dist_remote_error;
select count(*) from metrics_dist_bl;
count
40000
(1 row)
drop table metrics_dist_bl;
drop table metrics_dist_remote_error;

@ -1,231 +0,0 @@
-- This file and its contents are licensed under the Timescale License.
-- Please see the included NOTICE for copyright information and
-- LICENSE-TIMESCALE for a copy of the license.
-- Import setup file to data nodes.
\unset ECHO
-- Disable SSL to get stable error output across versions. SSL adds some output
-- that changed in PG 14.
set timescaledb.debug_enable_ssl to off;
set client_min_messages to error;
SET timescaledb.hide_data_node_name_in_errors = 'on';
-- A relatively big table on one data node
create table metrics_dist_remote_error(like metrics_dist);
select table_name from create_distributed_hypertable('metrics_dist_remote_error', 'time', 'device_id',
data_nodes => '{"data_node_1"}');
table_name
metrics_dist_remote_error
(1 row)
insert into metrics_dist_remote_error select * from metrics_dist order by metrics_dist limit 20000;
-- The error messages vary wildly between the Postgres versions, dependent on
-- the particular behavior of libqp in this or that case. The purpose of this
-- test is not to solidify this accidental behavior, but to merely exercise the
-- error handling code to make sure it doesn't have fatal errors. Unfortunately,
-- there is no way to suppress error output from a psql script.
set client_min_messages to ERROR;
\set ON_ERROR_STOP off
set timescaledb.remote_data_fetcher = 'copy';
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(0, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 0 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(1, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(2, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(701, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 701 rows, 701 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 10000 rows, 10000 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(16384, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 16384 rows, 16384 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000000, device_id)::int != 0;
QUERY PLAN
Result (actual rows=20000 loops=1)
Output: 1
-> Custom Scan (DataNodeScan) on public.metrics_dist_remote_error (actual rows=20000 loops=1)
Data node: data_node_1
Fetcher Type: COPY
Chunks: _dist_hyper_X_X_chunk, _dist_hyper_X_X_chunk
Remote SQL: SELECT NULL FROM public.metrics_dist_remote_error WHERE _timescaledb_internal.chunks_in(public.metrics_dist_remote_error.*, ARRAY[..]) AND ((public.ts_debug_shippable_error_after_n_rows(10000000, device_id) <> 0))
(7 rows)
-- We don't test fatal errors here, because PG versions before 14 are unable to
-- report them properly to the access node, so we get different errors in these
-- versions.
-- Now test the same with the cursor fetcher.
set timescaledb.remote_data_fetcher = 'cursor';
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(0, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 0 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(1, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(2, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(701, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 701 rows, 701 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000, device_id)::int != 0;
ERROR: [<hidden node name>]: debug point: requested to error out after 10000 rows, 10000 rows seen
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000000, device_id)::int != 0;
QUERY PLAN
Result (actual rows=20000 loops=1)
Output: 1
-> Custom Scan (DataNodeScan) on public.metrics_dist_remote_error (actual rows=20000 loops=1)
Data node: data_node_1
Fetcher Type: Cursor
Chunks: _dist_hyper_X_X_chunk, _dist_hyper_X_X_chunk
Remote SQL: SELECT NULL FROM public.metrics_dist_remote_error WHERE _timescaledb_internal.chunks_in(public.metrics_dist_remote_error.*, ARRAY[..]) AND ((public.ts_debug_shippable_error_after_n_rows(10000000, device_id) <> 0))
(7 rows)
-- Table with broken send for a data type.
create table metrics_dist_bs(like metrics_dist);
alter table metrics_dist_bs alter column v0 type bs;
select table_name from create_distributed_hypertable('metrics_dist_bs',
'time', 'device_id');
table_name
metrics_dist_bs
(1 row)
set timescaledb.enable_connection_binary_data to off;
insert into metrics_dist_bs
select * from metrics_dist_remote_error;
set timescaledb.enable_connection_binary_data to on;
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_bs;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
drop table metrics_dist_bs;
-- Table with broken receive for a data type.
create table metrics_dist_br(like metrics_dist);
alter table metrics_dist_br alter column v0 type br;
select table_name from create_distributed_hypertable('metrics_dist_br',
'time', 'device_id');
table_name
metrics_dist_br
(1 row)
select hypertable_name, replication_factor from timescaledb_information.hypertables
where hypertable_name = 'metrics_dist_br';
hypertable_name | replication_factor
-----------------+--------------------
metrics_dist_br | 1
(1 row)
-- Test that INSERT and COPY fail on data nodes.
-- Note that we use the text format for the COPY input, so that the access node
-- doesn't call `recv` and fail by itself. It's going to use binary format for
-- transfer to data nodes regardless of the input format.
set timescaledb.dist_copy_transfer_format = 'binary';
-- First, create the reference.
\copy (select * from metrics_dist_remote_error) to 'dist_remote_error.text' with (format text);
-- We have to test various interleavings of COPY and INSERT to check that
-- one can recover from connection failure states introduced by another.
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
-- Fail at different points
set timescaledb.debug_broken_sendrecv_throw_after = 1;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 2;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1023;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1023 rows, 1023 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1024;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1024 rows, 1024 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1025;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1025 rows, 1025 rows seen
reset timescaledb.debug_broken_sendrecv_throw_after;
-- Same with different replication factor
truncate metrics_dist_br;
select set_replication_factor('metrics_dist_br', 2);
set_replication_factor
(1 row)
select hypertable_name, replication_factor from timescaledb_information.hypertables
where hypertable_name = 'metrics_dist_br';
hypertable_name | replication_factor
-----------------+--------------------
metrics_dist_br | 2
(1 row)
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
insert into metrics_dist_br select * from metrics_dist_remote_error;
ERROR: [<hidden node name>]: debug point: requested to error out after 7103 rows, 7103 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1 rows, 1 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 2;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 2 rows, 2 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1023;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1023 rows, 1023 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1024;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1024 rows, 1024 rows seen
set timescaledb.debug_broken_sendrecv_throw_after = 1025;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
ERROR: [<hidden node name>]: debug point: requested to error out after 1025 rows, 1025 rows seen
-- Should succeed with text format for data transfer.
set timescaledb.dist_copy_transfer_format = 'text';
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
-- Final check.
set timescaledb.enable_connection_binary_data = false;
select count(*) from metrics_dist_br;
count
20000
(1 row)
set timescaledb.enable_connection_binary_data = true;
reset timescaledb.debug_broken_sendrecv_throw_after;
drop table metrics_dist_br;
-- Table with sleepy receive for a data type, to improve coverage of the waiting
-- code on the access node.
create table metrics_dist_bl(like metrics_dist);
alter table metrics_dist_bl alter column v0 type bl;
select table_name from create_distributed_hypertable('metrics_dist_bl',
'time', 'device_id');
table_name
metrics_dist_bl
(1 row)
-- We're using sleepy recv function, so need the binary transfer format for it
-- to be called on the data nodes.
set timescaledb.dist_copy_transfer_format = 'binary';
-- Test INSERT and COPY with slow data node.
\copy metrics_dist_bl from 'dist_remote_error.text' with (format text);
insert into metrics_dist_bl select * from metrics_dist_remote_error;
select count(*) from metrics_dist_bl;
count
40000
(1 row)
drop table metrics_dist_bl;
drop table metrics_dist_remote_error;

@ -29,8 +29,7 @@ endif()
if(CMAKE_BUILD_TYPE MATCHES Debug)
list(APPEND TEST_FILES_SHARED dist_parallel_agg.sql dist_queries.sql
timestamp_limits.sql with_clause_parser.sql)
list(APPEND TEST_TEMPLATES_SHARED constify_now.sql.in
dist_remote_error.sql.in)
list(APPEND TEST_TEMPLATES_SHARED constify_now.sql.in)
endif(CMAKE_BUILD_TYPE MATCHES Debug)
# Regression tests that vary with PostgreSQL version. Generated test files are

@ -28,12 +28,17 @@ select 1 x from distinct_on_distributed t1, distinct_on_distributed t2
where t1.id = t2.id
limit 1;
-- This query can't work with copy fetcher.
-- This query can't work with copy or prepared fetcher.
set timescaledb.remote_data_fetcher = 'copy';
select 1 x from distinct_on_distributed t1, distinct_on_distributed t2
where t1.id = t2.id + 1
limit 1;
set timescaledb.remote_data_fetcher = 'prepared';
select 1 x from distinct_on_distributed t1, distinct_on_distributed t2
where t1.id = t2.id + 1
limit 1;
-- Check once again that 'auto' is used after 'copy'.
set timescaledb.remote_data_fetcher = 'auto';
select 1 x from distinct_on_distributed t1, distinct_on_distributed t2
@ -64,7 +69,7 @@ WHERE
) AS l
WHERE d.name ~~ d.name
)
ORDER BY 1,2;
ORDER BY 1, 2;
RESET jit;
@ -96,8 +101,16 @@ explain (analyze, verbose, costs off, timing off, summary off)
select * from disttable_with_bytea;
select * from disttable_with_bytea;
-- Prepared statement fetcher with bytea data
set timescaledb.remote_data_fetcher = 'prepared';
explain (analyze, verbose, costs off, timing off, summary off)
select * from disttable_with_bytea;
select * from disttable_with_bytea;
-- #4515 test for assertion failure in copy_fetcher_close
SET timescaledb.remote_data_fetcher = 'copy';
SELECT *
FROM
conditions ref_0
@ -113,6 +126,41 @@ WHERE EXISTS (
WHERE (SELECT 1 FROM pg_class LIMIT 1) >= ref_0.device
);
SET timescaledb.remote_data_fetcher = 'prepared';
SELECT *
FROM
conditions ref_0
WHERE EXISTS (
SELECT FROM
distinct_on_distributed,
LATERAL (
SELECT *
FROM pg_class,
LATERAL (
SELECT ref_0.device FROM pg_class WHERE false LIMIT 1) as lat_1
) as lat_2
WHERE (SELECT 1 FROM pg_class LIMIT 1) >= ref_0.device
);
SET timescaledb.remote_data_fetcher = 'cursor';
SELECT *
FROM
conditions ref_0
WHERE EXISTS (
SELECT FROM
distinct_on_distributed,
LATERAL (
SELECT *
FROM pg_class,
LATERAL (
SELECT ref_0.device FROM pg_class WHERE false LIMIT 1) as lat_1
) as lat_2
WHERE (SELECT 1 FROM pg_class LIMIT 1) >= ref_0.device
);
-- #4518
-- test error handling for queries with multiple distributed hypertables
SET timescaledb.remote_data_fetcher = 'copy';
@ -124,7 +172,21 @@ WHERE EXISTS (
LATERAL (select * from metrics as ref_2) as subq_3
WHERE
(SELECT device_id FROM metrics_compressed limit 1 offset 3) >= ref_0.device
);
)
ORDER BY 1, 2;
SET timescaledb.remote_data_fetcher = 'prepared';
SELECT * FROM
conditions_dist1 ref_0
WHERE EXISTS (
SELECT FROM
distinct_on_distributed as ref_1,
LATERAL (select * from metrics as ref_2) as subq_3
WHERE
(SELECT device_id FROM metrics_compressed limit 1 offset 3) >= ref_0.device
)
ORDER BY 1, 2;
SET timescaledb.remote_data_fetcher = 'auto';
SELECT * FROM
conditions_dist1 ref_0
@ -135,7 +197,7 @@ WHERE EXISTS (
WHERE
(SELECT device_id FROM metrics_compressed limit 1 offset 3) >= ref_0.device
)
ORDER BY 1,2;
ORDER BY 1, 2;
-- Check that we don't use COPY fetcher for parameterized plans.
CREATE TABLE lookup (id SERIAL NOT NULL, key TEXT, val TEXT);
@ -174,10 +236,18 @@ WHERE
AND m.ts BETWEEN '2021-08-17 00:00:00' AND '2021-08-17 01:00:00'
ORDER BY 1 DESC LIMIT 1;
-- Test copy fetcher when query is aborted before EOF due to LIMIT
-- Test fetcher when query is aborted before EOF due to LIMIT
SET timescaledb.remote_data_fetcher = 'copy';
SELECT * FROM metrics_dist ORDER BY time, device_id LIMIT 11;
SET timescaledb.remote_data_fetcher = 'prepared';
SELECT * FROM metrics_dist ORDER BY time, device_id LIMIT 11;
SET timescaledb.remote_data_fetcher = 'cursor';
SELECT * FROM metrics_dist ORDER BY time, device_id LIMIT 11;
-- Verify that cursor fetcher can be rewind before EOF due to an
-- intermediate JOIN product reaching LIMIT
SET timescaledb.remote_data_fetcher = 'cursor';

@ -1,79 +0,0 @@
-- This file and its contents are licensed under the Timescale License.
-- Please see the included NOTICE for copyright information and
-- LICENSE-TIMESCALE for a copy of the license.
-- bs is for "broken send", the type is int4
create type bs;
create or replace function bssend(bs) returns bytea
as :MODULE_PATHNAME, 'ts_debug_broken_int4send'
language c immutable strict parallel safe;
create or replace function bsrecv(internal) returns bs as 'int4recv' language internal;
create or replace function bsin(cstring) returns bs as 'int4in' language internal;
create or replace function bsout(bs) returns cstring as 'int4out' language internal;
create type bs(input = bsin, output = bsout, send = bssend, receive = bsrecv,
internallength = 4, passedbyvalue = true);
create cast (int4 as bs) without function as implicit;
create cast (bs as int4) without function as implicit;
-- same but for broken recv
create type br;
create or replace function brsend(br) returns bytea as 'int4send' language internal;
create or replace function brrecv(internal) returns br
as :MODULE_PATHNAME, 'ts_debug_broken_int4recv'
language c immutable strict parallel safe;
create or replace function brin(cstring) returns br as 'int4in' language internal;
create or replace function brout(br) returns cstring as 'int4out' language internal;
create type br(input = brin, output = brout, send = brsend, receive = brrecv,
internallength = 4, passedbyvalue = true);
create cast (int4 as br) without function as implicit;
create cast (br as int4) without function as implicit;
-- recv that sleeps, optionally (want that only on one data node)
create type bl;
create or replace function blsend(bl) returns bytea as 'int4send' language internal;
\if :{?sleepy_recv}
create or replace function blrecv(internal) returns bl
as :MODULE_PATHNAME, 'ts_debug_sleepy_int4recv'
language c immutable strict parallel safe;
\else
create or replace function blrecv(internal) returns bl as 'int4recv' language internal;
\endif
create or replace function blin(cstring) returns bl as 'int4in' language internal;
create or replace function blout(bl) returns cstring as 'int4out' language internal;
create type bl(input = blin, output = blout, send = blsend, receive = blrecv,
internallength = 4, passedbyvalue = true);
create cast (int4 as bl) without function as implicit;
create cast (bl as int4) without function as implicit;
-- Create a function that raises an error every nth row.
-- It's stable, takes a second argument and returns current number of rows,
-- so that it is shipped to data nodes and not optimized out.
-- It's written in one line because I don't know how to make \set accept
-- several lines.
CREATE OR REPLACE FUNCTION ts_debug_shippable_error_after_n_rows(integer, anyelement)
RETURNS integer AS :MODULE_PATHNAME LANGUAGE C STABLE STRICT;
-- Same as above, but fatal.
CREATE OR REPLACE FUNCTION ts_debug_shippable_fatal_after_n_rows(integer, anyelement)
RETURNS integer AS :MODULE_PATHNAME LANGUAGE C STABLE STRICT;

@ -12,6 +12,7 @@
/dist_hypertable-*.sql
/dist_partial_agg-*.sql
/dist_ref_table_join-*.sql
/dist_ref_table_join-*.sql
/hypertable_distributed-*.sql
/jit-*.sql
/modify_exclusion-*.sql

@ -159,6 +159,7 @@ if(CMAKE_BUILD_TYPE MATCHES Debug)
remote_copy.sql.in
dist_grant.sql.in
dist_ref_table_join.sql.in
dist_remote_error.sql.in
dist_partial_agg.sql.in
dist_query.sql.in
cagg_invalidation_dist_ht.sql.in

@ -11,9 +11,8 @@
\set TEST_BASE_NAME data_fetcher
SELECT format('include/%s_run.sql', :'TEST_BASE_NAME') as "TEST_QUERY_NAME",
format('%s/results/%s_results_cursor.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as "TEST_RESULTS_CURSOR",
format('%s/results/%s_results_copy.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as "TEST_RESULTS_COPY"
\gset
SELECT format('\! diff %s %s', :'TEST_RESULTS_CURSOR', :'TEST_RESULTS_COPY') as "DIFF_CMD"
format('%s/results/%s_results_copy.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as "TEST_RESULTS_COPY",
format('%s/results/%s_results_prepared.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as "TEST_RESULTS_PREPARED"
\gset
SET ROLE :ROLE_CLUSTER_SUPERUSER;
@ -63,6 +62,18 @@ SET timescaledb.remote_data_fetcher = 'cursor';
\ir :TEST_QUERY_NAME
\o
-- compare results
SELECT format('\! diff %s %s', :'TEST_RESULTS_CURSOR', :'TEST_RESULTS_COPY') as "DIFF_CMD"
\gset
:DIFF_CMD
-- run queries using prepares statement fetcher
SET timescaledb.remote_data_fetcher = 'prepared';
\o :TEST_RESULTS_PREPARED
\ir :TEST_QUERY_NAME
\o
-- compare results
SELECT format('\! diff %s %s', :'TEST_RESULTS_CURSOR', :'TEST_RESULTS_PREPARED') as "DIFF_CMD"
\gset
:DIFF_CMD
-- Test custom FDW settings. Instead of the tests above, we are not interersted

@ -16,6 +16,7 @@ grant usage on foreign server data_node_1 to public;
grant create on schema public to :ROLE_1;
set role :ROLE_1;
reset client_min_messages;
\set ON_ERROR_STOP 0
-- helper function: float -> pseudorandom float [0..1].
create or replace function mix(x float4) returns float4 as $$ select ((hashfloat4(x) / (pow(2., 31) - 1) + 1) / 2)::float4 $$ language sql;
@ -94,6 +95,87 @@ order by id
reset timescaledb.enable_parameterized_data_node_scan;
-- All fetcher types with join
set timescaledb.remote_data_fetcher = 'copy';
select id, max(value), count(*)
from metric_dist
where id in (select id from metric_name where name like 'cpu%')
and ts between '2022-02-02 02:02:02+03' and '2022-03-03 02:02:02+03'
group by id
order by id
;
set timescaledb.remote_data_fetcher = 'cursor';
select id, max(value), count(*)
from metric_dist
where id in (select id from metric_name where name like 'cpu%')
and ts between '2022-02-02 02:02:02+03' and '2022-03-03 02:02:02+03'
group by id
order by id
;
set timescaledb.remote_data_fetcher = 'prepared';
select id, max(value), count(*)
from metric_dist
where id in (select id from metric_name where name like 'cpu%')
and ts between '2022-02-02 02:02:02+03' and '2022-03-03 02:02:02+03'
group by id
order by id
;
-- All fetcher types with initplan
set timescaledb.remote_data_fetcher = 'copy';
select id, max(value), count(*)
from metric_dist
where id = any((select array_agg(id) from metric_name where name like 'cpu%')::int[])
and ts between '2022-02-02 02:02:02+03' and '2022-03-03 02:02:02+03'
group by id
order by id
;
set timescaledb.remote_data_fetcher = 'cursor';
select id, max(value), count(*)
from metric_dist
where id = any((select array_agg(id) from metric_name where name like 'cpu%')::int[])
and ts between '2022-02-02 02:02:02+03' and '2022-03-03 02:02:02+03'
group by id
order by id
;
set timescaledb.remote_data_fetcher = 'prepared';
select id, max(value), count(*)
from metric_dist
where id = any((select array_agg(id) from metric_name where name like 'cpu%')::int[])
and ts between '2022-02-02 02:02:02+03' and '2022-03-03 02:02:02+03'
group by id
order by id
;
-- Should prefer prepared statement data fetcher for these queries.
set timescaledb.remote_data_fetcher = 'auto';
explain (analyze, verbose, costs off, timing off, summary off)
select id, max(value), count(*)
from metric_dist
where id in (select id from metric_name where name like 'cpu%')
and ts between '2022-02-02 02:02:02+03' and '2022-03-03 02:02:02+03'
group by id
order by id
;
-- Should reset the prepared cache mode after using the prepared statement fetcher.
call distributed_exec('create or replace procedure assert_auto_plan_cache_mode() as $$ begin assert (select setting from pg_settings where name = ''plan_cache_mode'') = ''auto''; end; $$ language plpgsql;');
call distributed_exec('call assert_auto_plan_cache_mode();');
-- Shippable EC join
select name, max(value), count(*)
from metric_dist join metric_name using (id)
@ -269,6 +351,7 @@ where name like 'cpu%' and texteq(location, 'Yerevan')
group by id
;
-- Multiple joins on different variables. Use a table instead of a CTE for saner
-- stats.
create table max_value_times as

@ -2,21 +2,39 @@
-- Please see the included NOTICE for copyright information and
-- LICENSE-TIMESCALE for a copy of the license.
--\set DATA_NODE_1 data_node_1
--\set DATA_NODE_2 data_node_2
--\set DATA_NODE_3 data_node_3
-- Set up the data nodes.
\set DATA_NODE_1 :TEST_DBNAME _1
\set DATA_NODE_2 :TEST_DBNAME _2
\set DATA_NODE_3 :TEST_DBNAME _3
\c :TEST_DBNAME :ROLE_SUPERUSER
SELECT node_name, database, node_created, database_created, extension_created
FROM (
SELECT (add_data_node(name, host => 'localhost', DATABASE => name)).*
FROM (VALUES (:'DATA_NODE_1'), (:'DATA_NODE_2'), (:'DATA_NODE_3')) v(name)
) a;
GRANT USAGE ON FOREIGN SERVER :DATA_NODE_1, :DATA_NODE_2, :DATA_NODE_3 TO PUBLIC;
GRANT CREATE ON SCHEMA public TO :ROLE_1;
-- Import setup file to data nodes.
\unset ECHO
\c data_node_1 :ROLE_SUPERUSER
\c :DATA_NODE_1 :ROLE_SUPERUSER
set client_min_messages to error;
\ir include/dist_remote_error_setup.sql
\c data_node_2 :ROLE_SUPERUSER
\c :DATA_NODE_2 :ROLE_SUPERUSER
set client_min_messages to error;
\ir include/dist_remote_error_setup.sql
\set sleepy_recv 1
\c data_node_3 :ROLE_SUPERUSER
\set sleepy_sendrecv 1
\c :DATA_NODE_3 :ROLE_SUPERUSER
set client_min_messages to error;
\ir include/dist_remote_error_setup.sql
\unset sleepy_recv
\unset sleepy_sendrecv
\c :TEST_DBNAME :ROLE_SUPERUSER
set client_min_messages to error;
@ -32,10 +50,17 @@ set client_min_messages to error;
SET timescaledb.hide_data_node_name_in_errors = 'on';
-- A relatively big table on one data node
create table metrics_dist_remote_error(like metrics_dist);
select table_name from create_distributed_hypertable('metrics_dist_remote_error', 'time', 'device_id',
data_nodes => '{"data_node_1"}');
insert into metrics_dist_remote_error select * from metrics_dist order by metrics_dist limit 20000;
CREATE TABLE metrics_dist_remote_error(filler_1 int, filler_2 int, filler_3 int, time timestamptz NOT NULL, device_id int, v0 int, v1 int, v2 float, v3 float);
SELECT create_distributed_hypertable('metrics_dist_remote_error','time','device_id',3,
data_nodes => ARRAY[:'DATA_NODE_1']);
ALTER TABLE metrics_dist_remote_error DROP COLUMN filler_1;
INSERT INTO metrics_dist_remote_error(time,device_id,v0,v1,v2,v3) SELECT time, device_id, device_id+1, device_id + 2, device_id + 0.5, NULL FROM generate_series('2000-01-01 0:00:00+0'::timestamptz,'2000-01-05 23:55:00+0','6m') gtime(time), generate_series(1,5,1) gdevice(device_id);
ALTER TABLE metrics_dist_remote_error DROP COLUMN filler_2;
INSERT INTO metrics_dist_remote_error(time,device_id,v0,v1,v2,v3) SELECT time, device_id, device_id+1, device_id + 2, device_id + 0.5, NULL FROM generate_series('2000-01-06 0:00:00+0'::timestamptz,'2000-01-12 23:55:00+0','6m') gtime(time), generate_series(1,5,1) gdevice(device_id);
ALTER TABLE metrics_dist_remote_error DROP COLUMN filler_3;
INSERT INTO metrics_dist_remote_error(time,device_id,v0,v1,v2,v3) SELECT time, device_id, device_id+1, device_id + 2, device_id + 0.5, NULL FROM generate_series('2000-01-13 0:00:00+0'::timestamptz,'2000-01-19 23:55:00+0','6m') gtime(time), generate_series(1,5,1) gdevice(device_id);
ANALYZE metrics_dist_remote_error;
-- The error messages vary wildly between the Postgres versions, dependent on
-- the particular behavior of libqp in this or that case. The purpose of this
@ -94,9 +119,33 @@ select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_r
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000000, device_id)::int != 0;
-- Now test the same with the prepared statement fetcher.
set timescaledb.remote_data_fetcher = 'prepared';
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(0, device_id)::int != 0;
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(1, device_id)::int != 0;
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(2, device_id)::int != 0;
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(701, device_id)::int != 0;
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000, device_id)::int != 0;
explain (analyze, verbose, costs off, timing off, summary off)
select 1 from metrics_dist_remote_error where ts_debug_shippable_error_after_n_rows(10000000, device_id)::int != 0;
reset timescaledb.remote_data_fetcher;
-- Table with broken send for a data type.
create table metrics_dist_bs(like metrics_dist);
create table metrics_dist_bs(like metrics_dist_remote_error);
alter table metrics_dist_bs alter column v0 type bs;
@ -114,7 +163,7 @@ select * from metrics_dist_bs;
drop table metrics_dist_bs;
-- Table with broken receive for a data type.
create table metrics_dist_br(like metrics_dist);
create table metrics_dist_br(like metrics_dist_remote_error);
alter table metrics_dist_br alter column v0 type br;
@ -142,17 +191,17 @@ insert into metrics_dist_br select * from metrics_dist_remote_error;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
-- Fail at different points
set timescaledb.debug_broken_sendrecv_throw_after = 1;
set timescaledb.debug_broken_sendrecv_error_after = 1;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
set timescaledb.debug_broken_sendrecv_throw_after = 2;
set timescaledb.debug_broken_sendrecv_error_after = 2;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
set timescaledb.debug_broken_sendrecv_throw_after = 1023;
set timescaledb.debug_broken_sendrecv_error_after = 1023;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
set timescaledb.debug_broken_sendrecv_throw_after = 1024;
set timescaledb.debug_broken_sendrecv_error_after = 1024;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
set timescaledb.debug_broken_sendrecv_throw_after = 1025;
set timescaledb.debug_broken_sendrecv_error_after = 1025;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
reset timescaledb.debug_broken_sendrecv_throw_after;
reset timescaledb.debug_broken_sendrecv_error_after;
-- Same with different replication factor
@ -165,15 +214,15 @@ where hypertable_name = 'metrics_dist_br';
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
insert into metrics_dist_br select * from metrics_dist_remote_error;
insert into metrics_dist_br select * from metrics_dist_remote_error;
set timescaledb.debug_broken_sendrecv_throw_after = 1;
set timescaledb.debug_broken_sendrecv_error_after = 1;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
set timescaledb.debug_broken_sendrecv_throw_after = 2;
set timescaledb.debug_broken_sendrecv_error_after = 2;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
set timescaledb.debug_broken_sendrecv_throw_after = 1023;
set timescaledb.debug_broken_sendrecv_error_after = 1023;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
set timescaledb.debug_broken_sendrecv_throw_after = 1024;
set timescaledb.debug_broken_sendrecv_error_after = 1024;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
set timescaledb.debug_broken_sendrecv_throw_after = 1025;
set timescaledb.debug_broken_sendrecv_error_after = 1025;
\copy metrics_dist_br from 'dist_remote_error.text' with (format text);
-- Should succeed with text format for data transfer.
@ -185,16 +234,16 @@ set timescaledb.enable_connection_binary_data = false;
select count(*) from metrics_dist_br;
set timescaledb.enable_connection_binary_data = true;
reset timescaledb.debug_broken_sendrecv_throw_after;
reset timescaledb.debug_broken_sendrecv_error_after;
drop table metrics_dist_br;
-- Table with sleepy receive for a data type, to improve coverage of the waiting
-- code on the access node.
create table metrics_dist_bl(like metrics_dist);
create table metrics_dist_sr(like metrics_dist_remote_error);
alter table metrics_dist_bl alter column v0 type bl;
alter table metrics_dist_sr alter column v0 type sr;
select table_name from create_distributed_hypertable('metrics_dist_bl',
select table_name from create_distributed_hypertable('metrics_dist_sr',
'time', 'device_id');
-- We're using sleepy recv function, so need the binary transfer format for it
@ -202,12 +251,74 @@ select table_name from create_distributed_hypertable('metrics_dist_bl',
set timescaledb.dist_copy_transfer_format = 'binary';
-- Test INSERT and COPY with slow data node.
\copy metrics_dist_bl from 'dist_remote_error.text' with (format text);
\copy metrics_dist_sr from 'dist_remote_error.text' with (format text);
insert into metrics_dist_bl select * from metrics_dist_remote_error;
insert into metrics_dist_sr select * from metrics_dist_remote_error;
select count(*) from metrics_dist_bl;
select count(*) from metrics_dist_sr;
drop table metrics_dist_bl;
drop table metrics_dist_sr;
drop table metrics_dist_remote_error;
-- Table with sleepy send for a data type, on one data node, to improve coverage
-- of waiting in data fetchers.
create table metrics_dist_ss(like metrics_dist_remote_error);
alter table metrics_dist_ss alter column v0 type ss;
select table_name from create_distributed_hypertable('metrics_dist_ss',
'time', 'device_id');
-- Populate the table, using text COPY to avoid the sleepy stuff.
set timescaledb.dist_copy_transfer_format = 'text';
\copy metrics_dist_ss from 'dist_remote_error.text' with (format text);
-- We're using sleepy send function, so need the binary transfer format for it
-- to be called on the data nodes.
set timescaledb.enable_connection_binary_data = true;
set timescaledb.remote_data_fetcher = 'prepared';
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_ss;
set timescaledb.remote_data_fetcher = 'copy';
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_ss;
set timescaledb.remote_data_fetcher = 'cursor';
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_ss;
-- Incorrect int output, to cover the error handling in tuplefactory.
create table metrics_dist_io(like metrics_dist_remote_error);
alter table metrics_dist_io alter column v0 type io;
select table_name from create_distributed_hypertable('metrics_dist_io',
'time', 'device_id');
-- Populate the table, using binary COPY to avoid the broken in4out.
set timescaledb.enable_connection_binary_data = true;
set timescaledb.dist_copy_transfer_format = 'binary';
\copy metrics_dist_io from 'dist_remote_error.text' with (format text);
-- For testing, force the text format to exerices our broken out function.
set timescaledb.enable_connection_binary_data = false;
set timescaledb.dist_copy_transfer_format = 'text';
set timescaledb.remote_data_fetcher = 'prepared';
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_io;
set timescaledb.remote_data_fetcher = 'copy';
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_io;
set timescaledb.remote_data_fetcher = 'cursor';
explain (analyze, verbose, costs off, timing off, summary off)
select * from metrics_dist_io;
-- cleanup
\c :TEST_DBNAME :ROLE_SUPERUSER;
DROP DATABASE :DATA_NODE_1;
DROP DATABASE :DATA_NODE_2;
DROP DATABASE :DATA_NODE_3;

@ -0,0 +1,122 @@
-- This file and its contents are licensed under the Timescale License.
-- Please see the included NOTICE for copyright information and
-- LICENSE-TIMESCALE for a copy of the license.
-- bs is for "broken send", the type is int4
create type bs;
create or replace function bssend(bs) returns bytea
as :MODULE_PATHNAME, 'ts_debug_broken_int4send'
language c immutable strict parallel safe;
create or replace function bsrecv(internal) returns bs as 'int4recv' language internal;
create or replace function bsin(cstring) returns bs as 'int4in' language internal;
create or replace function bsout(bs) returns cstring as 'int4out' language internal;
create type bs(input = bsin, output = bsout, send = bssend, receive = bsrecv,
internallength = 4, passedbyvalue = true);
create cast (int4 as bs) without function as implicit;
create cast (bs as int4) without function as implicit;
-- same but for broken recv
create type br;
create or replace function brsend(br) returns bytea as 'int4send' language internal;
create or replace function brrecv(internal) returns br
as :MODULE_PATHNAME, 'ts_debug_broken_int4recv'
language c immutable strict parallel safe;
create or replace function brin(cstring) returns br as 'int4in' language internal;
create or replace function brout(br) returns cstring as 'int4out' language internal;
create type br(input = brin, output = brout, send = brsend, receive = brrecv,
internallength = 4, passedbyvalue = true);
create cast (int4 as br) without function as implicit;
create cast (br as int4) without function as implicit;
-- recv that sleeps, optionally (want that only on one data node)
create type sr;
create or replace function srsend(sr) returns bytea as 'int4send' language internal;
\if :{?sleepy_sendrecv}
create or replace function srrecv(internal) returns sr
as :MODULE_PATHNAME, 'ts_debug_sleepy_int4recv'
language c immutable strict parallel safe;
\else
create or replace function srrecv(internal) returns sr as 'int4recv' language internal;
\endif
create or replace function srin(cstring) returns sr as 'int4in' language internal;
create or replace function srout(sr) returns cstring as 'int4out' language internal;
create type sr(input = srin, output = srout, send = srsend, receive = srrecv,
internallength = 4, passedbyvalue = true);
create cast (int4 as sr) without function as implicit;
create cast (sr as int4) without function as implicit;
-- send that sleeps, optionally (want that only on one data node)
create type ss;
create or replace function ssrecv(internal) returns ss as 'int4recv' language internal;
\if :{?sleepy_sendrecv}
create or replace function sssend(ss) returns bytea
as :MODULE_PATHNAME, 'ts_debug_sleepy_int4send'
language c immutable strict parallel safe;
\else
create or replace function sssend(ss) returns bytea as 'int4send' language internal;
\endif
create or replace function ssin(cstring) returns ss as 'int4in' language internal;
create or replace function ssout(ss) returns cstring as 'int4out' language internal;
create type ss(input = ssin, output = ssout, send = sssend, receive = ssrecv,
internallength = 4, passedbyvalue = true);
create cast (int4 as ss) without function as implicit;
create cast (ss as int4) without function as implicit;
-- int4out that sometimes outputs not an int (name is abbreviation of Incorrect Out)
create type io;
create or replace function iorecv(internal) returns io as 'int4recv' language internal;
create or replace function iosend(io) returns bytea as 'int4send' language internal;
create or replace function ioin(cstring) returns io as 'int4in' language internal;
create or replace function ioout(io) returns cstring
as :MODULE_PATHNAME, 'ts_debug_incorrect_int4out'
language c immutable strict parallel safe;
create type io(input = ioin, output = ioout, send = iosend, receive = iorecv,
internallength = 4, passedbyvalue = true);
create cast (int4 as io) without function as implicit;
create cast (io as int4) without function as implicit;
-- Create a function that raises an error every nth row.
-- It's stable, takes a second argument and returns current number of rows,
-- so that it is shipped to data nodes and not optimized out.
create or replace function ts_debug_shippable_error_after_n_rows(integer, anyelement)
returns integer as :MODULE_PATHNAME language C stable strict;
-- Same as above, but fatal.
create or replace function ts_debug_shippable_fatal_after_n_rows(integer, anyelement)
returns integer as :MODULE_PATHNAME language C stable strict;