Fix crash on SELECT WHERE NOT with empty table

Modify table state is not created with an empty tables, which lead
to NULL pointer evaluation.

Starting from PG12 the planner injects a gating plan node above
any node that has pseusdo-constant quals.

To fix this, we need to check for such a gating node and handle the case.
We could optionally prune the extra node, since there's already
such a node below ChunkDispatch.

Fixes #1883.
This commit is contained in:
Dmitry Simonenko 2020-05-15 12:08:56 +03:00
parent 74dcff5807
commit d0c92dd433
6 changed files with 273 additions and 20 deletions

View File

@ -21,6 +21,29 @@
#include "chunk_dispatch_plan.h" #include "chunk_dispatch_plan.h"
#include "hypertable_cache.h" #include "hypertable_cache.h"
static PlanState *
get_chunk_dispatch_state(ModifyTableState *mtstate, PlanState *substate)
{
switch (nodeTag(substate))
{
case T_CustomScanState:
{
CustomScanState *csstate = castNode(CustomScanState, substate);
if (strcmp(csstate->methods->CustomName, CHUNK_DISPATCH_STATE_NAME) == 0)
return substate;
break;
}
case T_ResultState:
return get_chunk_dispatch_state(mtstate, castNode(ResultState, substate)->ps.lefttree);
default:
break;
}
return NULL;
}
/* /*
* HypertableInsert (with corresponding executor node) is a plan node that * HypertableInsert (with corresponding executor node) is a plan node that
* implements INSERTs for hypertables. It is mostly a wrapper around the * implements INSERTs for hypertables. It is mostly a wrapper around the
@ -36,35 +59,33 @@ static void
hypertable_insert_begin(CustomScanState *node, EState *estate, int eflags) hypertable_insert_begin(CustomScanState *node, EState *estate, int eflags)
{ {
HypertableInsertState *state = (HypertableInsertState *) node; HypertableInsertState *state = (HypertableInsertState *) node;
ModifyTableState *mtstate;
PlanState *ps; PlanState *ps;
bool PG_USED_FOR_ASSERTS_ONLY found_cdstate = false;
int i;
ps = ExecInitNode(&state->mt->plan, estate, eflags); ps = ExecInitNode(&state->mt->plan, estate, eflags);
node->custom_ps = list_make1(ps); node->custom_ps = list_make1(ps);
mtstate = castNode(ModifyTableState, ps);
if (IsA(ps, ModifyTableState)) /*
* Find all ChunkDispatchState subnodes and set their parent
* ModifyTableState node
*/
for (i = 0; i < mtstate->mt_nplans; i++)
{ {
ModifyTableState *mtstate = (ModifyTableState *) ps; ChunkDispatchState *cdstate =
int i; (ChunkDispatchState *) get_chunk_dispatch_state(mtstate, mtstate->mt_plans[i]);
/* if (cdstate)
* Find all ChunkDispatchState subnodes and set their parent
* ModifyTableState node
*/
for (i = 0; i < mtstate->mt_nplans; i++)
{ {
if (IsA(mtstate->mt_plans[i], CustomScanState)) found_cdstate = true;
{ ts_chunk_dispatch_state_set_parent(cdstate, mtstate);
CustomScanState *csstate = (CustomScanState *) mtstate->mt_plans[i];
if (strcmp(csstate->methods->CustomName, CHUNK_DISPATCH_STATE_NAME) == 0)
{
ChunkDispatchState *cdstate = (ChunkDispatchState *) mtstate->mt_plans[i];
ts_chunk_dispatch_state_set_parent(cdstate, mtstate);
}
}
} }
} }
/* Ensure that we found at least one ChunkDispatchState node */
Assert(found_cdstate);
} }
static TupleTableSlot * static TupleTableSlot *

View File

@ -586,3 +586,53 @@ SELECT insert_test(), insert_test(), insert_test();
| | | |
(1 row) (1 row)
-- test Postgres crashes on INSERT ... SELECT ... WHERE NOT EXISTS with empty table
-- https://github.com/timescale/timescaledb/issues/1883
CREATE TABLE readings (
toe TIMESTAMPTZ NOT NULL,
sensor_id INT NOT NULL,
value INT NOT NULL
);
SELECT create_hypertable(
'readings',
'toe',
chunk_time_interval => interval '1 day',
if_not_exists => TRUE,
migrate_data => TRUE
);
create_hypertable
------------------------
(11,public,readings,t)
(1 row)
EXPLAIN (costs off)
INSERT INTO readings
SELECT '2020-05-09 10:34:35.296288+00', 1, 0
WHERE NOT EXISTS (
SELECT 1
FROM readings
WHERE sensor_id = 1
AND toe = '2020-05-09 10:34:35.296288+00'
);
QUERY PLAN
----------------------------------------------------------
Custom Scan (HypertableInsert)
-> Insert on readings
-> Custom Scan (ChunkDispatch)
-> Subquery Scan on "*SELECT*"
-> Result
One-Time Filter: (NOT $0)
InitPlan 1 (returns $0)
-> Result
One-Time Filter: false
(9 rows)
INSERT INTO readings
SELECT '2020-05-09 10:34:35.296288+00', 1, 0
WHERE NOT EXISTS (
SELECT 1
FROM readings
WHERE sensor_id = 1
AND toe = '2020-05-09 10:34:35.296288+00'
);
DROP TABLE readings;

View File

@ -586,3 +586,53 @@ SELECT insert_test(), insert_test(), insert_test();
| | | |
(1 row) (1 row)
-- test Postgres crashes on INSERT ... SELECT ... WHERE NOT EXISTS with empty table
-- https://github.com/timescale/timescaledb/issues/1883
CREATE TABLE readings (
toe TIMESTAMPTZ NOT NULL,
sensor_id INT NOT NULL,
value INT NOT NULL
);
SELECT create_hypertable(
'readings',
'toe',
chunk_time_interval => interval '1 day',
if_not_exists => TRUE,
migrate_data => TRUE
);
create_hypertable
------------------------
(11,public,readings,t)
(1 row)
EXPLAIN (costs off)
INSERT INTO readings
SELECT '2020-05-09 10:34:35.296288+00', 1, 0
WHERE NOT EXISTS (
SELECT 1
FROM readings
WHERE sensor_id = 1
AND toe = '2020-05-09 10:34:35.296288+00'
);
QUERY PLAN
----------------------------------------------------------
Custom Scan (HypertableInsert)
-> Insert on readings
-> Custom Scan (ChunkDispatch)
-> Subquery Scan on "*SELECT*"
-> Result
One-Time Filter: (NOT $0)
InitPlan 1 (returns $0)
-> Result
One-Time Filter: false
(9 rows)
INSERT INTO readings
SELECT '2020-05-09 10:34:35.296288+00', 1, 0
WHERE NOT EXISTS (
SELECT 1
FROM readings
WHERE sensor_id = 1
AND toe = '2020-05-09 10:34:35.296288+00'
);
DROP TABLE readings;

View File

@ -585,3 +585,54 @@ SELECT insert_test(), insert_test(), insert_test();
| | | |
(1 row) (1 row)
-- test Postgres crashes on INSERT ... SELECT ... WHERE NOT EXISTS with empty table
-- https://github.com/timescale/timescaledb/issues/1883
CREATE TABLE readings (
toe TIMESTAMPTZ NOT NULL,
sensor_id INT NOT NULL,
value INT NOT NULL
);
SELECT create_hypertable(
'readings',
'toe',
chunk_time_interval => interval '1 day',
if_not_exists => TRUE,
migrate_data => TRUE
);
create_hypertable
------------------------
(11,public,readings,t)
(1 row)
EXPLAIN (costs off)
INSERT INTO readings
SELECT '2020-05-09 10:34:35.296288+00', 1, 0
WHERE NOT EXISTS (
SELECT 1
FROM readings
WHERE sensor_id = 1
AND toe = '2020-05-09 10:34:35.296288+00'
);
QUERY PLAN
-----------------------------------------------------
Custom Scan (HypertableInsert)
InitPlan 1 (returns $0)
-> Result
One-Time Filter: false
-> Insert on readings
-> Result
One-Time Filter: (NOT $0)
-> Custom Scan (ChunkDispatch)
-> Result
One-Time Filter: (NOT $0)
(10 rows)
INSERT INTO readings
SELECT '2020-05-09 10:34:35.296288+00', 1, 0
WHERE NOT EXISTS (
SELECT 1
FROM readings
WHERE sensor_id = 1
AND toe = '2020-05-09 10:34:35.296288+00'
);
DROP TABLE readings;

View File

@ -586,3 +586,53 @@ SELECT insert_test(), insert_test(), insert_test();
| | | |
(1 row) (1 row)
-- test Postgres crashes on INSERT ... SELECT ... WHERE NOT EXISTS with empty table
-- https://github.com/timescale/timescaledb/issues/1883
CREATE TABLE readings (
toe TIMESTAMPTZ NOT NULL,
sensor_id INT NOT NULL,
value INT NOT NULL
);
SELECT create_hypertable(
'readings',
'toe',
chunk_time_interval => interval '1 day',
if_not_exists => TRUE,
migrate_data => TRUE
);
create_hypertable
------------------------
(11,public,readings,t)
(1 row)
EXPLAIN (costs off)
INSERT INTO readings
SELECT '2020-05-09 10:34:35.296288+00', 1, 0
WHERE NOT EXISTS (
SELECT 1
FROM readings
WHERE sensor_id = 1
AND toe = '2020-05-09 10:34:35.296288+00'
);
QUERY PLAN
----------------------------------------------------------
Custom Scan (HypertableInsert)
-> Insert on readings
-> Custom Scan (ChunkDispatch)
-> Subquery Scan on "*SELECT*"
-> Result
One-Time Filter: (NOT $0)
InitPlan 1 (returns $0)
-> Result
One-Time Filter: false
(9 rows)
INSERT INTO readings
SELECT '2020-05-09 10:34:35.296288+00', 1, 0
WHERE NOT EXISTS (
SELECT 1
FROM readings
WHERE sensor_id = 1
AND toe = '2020-05-09 10:34:35.296288+00'
);
DROP TABLE readings;

View File

@ -148,4 +148,35 @@ $$;
SELECT insert_test(), insert_test(), insert_test(); SELECT insert_test(), insert_test(), insert_test();
-- test Postgres crashes on INSERT ... SELECT ... WHERE NOT EXISTS with empty table
-- https://github.com/timescale/timescaledb/issues/1883
CREATE TABLE readings (
toe TIMESTAMPTZ NOT NULL,
sensor_id INT NOT NULL,
value INT NOT NULL
);
SELECT create_hypertable(
'readings',
'toe',
chunk_time_interval => interval '1 day',
if_not_exists => TRUE,
migrate_data => TRUE
);
EXPLAIN (costs off)
INSERT INTO readings
SELECT '2020-05-09 10:34:35.296288+00', 1, 0
WHERE NOT EXISTS (
SELECT 1
FROM readings
WHERE sensor_id = 1
AND toe = '2020-05-09 10:34:35.296288+00'
);
INSERT INTO readings
SELECT '2020-05-09 10:34:35.296288+00', 1, 0
WHERE NOT EXISTS (
SELECT 1
FROM readings
WHERE sensor_id = 1
AND toe = '2020-05-09 10:34:35.296288+00'
);
DROP TABLE readings;