Skip to content

Commit cd55b77

Browse files
svenklemmtimescale-automation
authored andcommitted
Read hypertable max time value with an ordered scan
The max value of a hypertable's open dimension was read with a max() aggregate. For UUID time columns the aggregate argument is an expression that extracts the timestamp from the UUID. Because the argument is an expression and not a plain column, the planner cannot rewrite the aggregate into a min/max aggregate, so it reads every row instead. Also lock down search_path for the SPI execution and fix a sql injection vulnerability in the UUID path. (cherry picked from commit 800fa71)
1 parent c0ee96f commit cd55b77

2 files changed

Lines changed: 37 additions & 19 deletions

File tree

.unreleased/pr_10379

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixes: #10379 Read hypertable max time value with an ordered scan

src/hypertable.c

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2185,7 +2185,7 @@ ts_hypertable_set_compress_interval(Hypertable *ht, int64 compress_interval)
21852185
* defined for the type so in that case the expression extracts the timestamp from the UUID.
21862186
*/
21872187
static const char *
2188-
get_expr_for_dim_max(const char *colname, Oid timetype)
2188+
get_expr_for_dim_time(const char *colname, Oid timetype)
21892189
{
21902190
if (timetype == UUIDOID)
21912191
{
@@ -2194,7 +2194,7 @@ get_expr_for_dim_max(const char *colname, Oid timetype)
21942194
initStringInfo(&expr);
21952195
appendStringInfo(&expr,
21962196
"%s.uuid_timestamp(%s)",
2197-
ts_extension_schema_name(),
2197+
quote_identifier(ts_extension_schema_name()),
21982198
quote_identifier(colname));
21992199
return expr.data;
22002200
}
@@ -2226,17 +2226,14 @@ ts_hypertable_get_open_dim_max_value(const Hypertable *ht, int dimension_index,
22262226

22272227
/*
22282228
* Query for the last bucket in the materialized hypertable.
2229-
* Since this might be run as part of a parallel operation
2230-
* we cannot use SET search_path here to lock down the
2231-
* search_path and instead have to fully schema-qualify
2232-
* everything.
22332229
*/
22342230
initStringInfo(&command);
22352231
appendStringInfo(&command,
2236-
"SELECT pg_catalog.max(%s) FROM %s.%s",
2237-
get_expr_for_dim_max(NameStr(dim->fd.column_name), timetype),
2232+
"SELECT %s FROM %s.%s ORDER BY %s DESC LIMIT 1",
2233+
get_expr_for_dim_time(NameStr(dim->fd.column_name), timetype),
22382234
quote_identifier(NameStr(ht->fd.schema_name)),
2239-
quote_identifier(NameStr(ht->fd.table_name)));
2235+
quote_identifier(NameStr(ht->fd.table_name)),
2236+
quote_identifier(NameStr(dim->fd.column_name)));
22402237

22412238
if (SPI_connect() != SPI_OK_CONNECT)
22422239
{
@@ -2245,6 +2242,10 @@ ts_hypertable_get_open_dim_max_value(const Hypertable *ht, int dimension_index,
22452242

22462243
int64 max_value;
22472244

2245+
/* Lock down search_path */
2246+
int save_nestlevel = NewGUCNestLevel();
2247+
RestrictSearchPath();
2248+
22482249
PG_TRY();
22492250
{
22502251
res = SPI_execute(command.data, true /* read_only */, 0 /*count*/);
@@ -2261,19 +2262,32 @@ ts_hypertable_get_open_dim_max_value(const Hypertable *ht, int dimension_index,
22612262
* first extract the timestamptz so the result type is timestamptz instead. */
22622263
Oid result_type = timetype == UUIDOID ? TIMESTAMPTZOID : timetype;
22632264

2264-
Ensure(SPI_gettypeid(SPI_tuptable->tupdesc, 1) == result_type,
2265-
"partition types for result (%d) and dimension (%d) do not match",
2266-
SPI_gettypeid(SPI_tuptable->tupdesc, 1),
2267-
ts_dimension_get_partition_type(dim));
2268-
maxdat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &max_isnull);
2269-
2270-
if (isnull)
2265+
/* An empty hypertable returns no rows, which we treat as a NULL result. */
2266+
if (SPI_processed == 0)
22712267
{
2272-
*isnull = max_isnull;
2268+
if (isnull)
2269+
{
2270+
*isnull = true;
2271+
}
2272+
2273+
max_value = ts_time_get_min(result_type);
22732274
}
2275+
else
2276+
{
2277+
Ensure(SPI_gettypeid(SPI_tuptable->tupdesc, 1) == result_type,
2278+
"partition types for result (%d) and dimension (%d) do not match",
2279+
SPI_gettypeid(SPI_tuptable->tupdesc, 1),
2280+
ts_dimension_get_partition_type(dim));
2281+
maxdat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &max_isnull);
2282+
2283+
if (isnull)
2284+
{
2285+
*isnull = max_isnull;
2286+
}
22742287

2275-
max_value = max_isnull ? ts_time_get_min(result_type) :
2276-
ts_time_value_to_internal(maxdat, result_type);
2288+
max_value = max_isnull ? ts_time_get_min(result_type) :
2289+
ts_time_value_to_internal(maxdat, result_type);
2290+
}
22772291
}
22782292
PG_CATCH();
22792293
{
@@ -2282,6 +2296,9 @@ ts_hypertable_get_open_dim_max_value(const Hypertable *ht, int dimension_index,
22822296
}
22832297
PG_END_TRY();
22842298

2299+
/* Restore search_path */
2300+
AtEOXact_GUC(false, save_nestlevel);
2301+
22852302
res = SPI_finish();
22862303
if (res != SPI_OK_FINISH)
22872304
{

0 commit comments

Comments
 (0)