From dc145b7485171f7d38ba563efecd0c311a00bdd2 Mon Sep 17 00:00:00 2001
From: Konstantina Skovola <konstantina@timescale.com>
Date: Tue, 28 Jun 2022 16:19:32 +0300
Subject: [PATCH] Add parameter check_config to alter_job

Previously users had no way to update the check function
registered with add_job. This commit adds a parameter check_config
to alter_job to allow updating the check function field.

Also, previously the signature expected from a check was of
the form (job_id, config) and there was no validation
that the check function given had the correct signature.
This commit removes the job_id as it is not required and
also checks that the check function has the correct signature
when it is registered with add_job, preventing an error being
thrown at job runtime.
---
 sql/job_api.sql                               |   5 +-
 sql/policy_internal.sql                       |   8 +-
 sql/updates/latest-dev.sql                    |   9 +-
 sql/updates/reverse-dev.sql                   |  27 +-
 sql/views.sql                                 |   4 +-
 src/bgw/job.c                                 |  77 ++--
 src/bgw_policy/policy.c                       |   2 -
 tsl/src/bgw_policy/compression_api.c          |   5 +-
 tsl/src/bgw_policy/continuous_aggregate_api.c |   5 +-
 tsl/src/bgw_policy/job_api.c                  | 102 +++++-
 tsl/src/bgw_policy/reorder_api.c              |   5 +-
 tsl/src/bgw_policy/retention_api.c            |   5 +-
 tsl/test/expected/bgw_custom.out              | 339 +++++++++++++++++-
 tsl/test/expected/bgw_db_scheduler.out        |  12 +-
 tsl/test/expected/bgw_reorder_drop_chunks.out |  80 ++---
 tsl/test/expected/cagg_bgw.out                |  12 +-
 tsl/test/expected/cagg_bgw_dist_ht.out        |  12 +-
 tsl/test/expected/cagg_bgw_drop_chunks.out    |   6 +-
 tsl/test/expected/cagg_policy.out             |   6 +-
 tsl/test/expected/cagg_usage.out              |   6 +-
 .../compress_bgw_reorder_drop_chunks.out      |   6 +-
 tsl/test/expected/compression_bgw.out         |  36 +-
 tsl/test/expected/continuous_aggs.out         |  18 +-
 .../expected/continuous_aggs_deprecated.out   |  18 +-
 tsl/test/expected/dist_compression.out        |  12 +-
 tsl/test/expected/tsl_tables.out              | 114 +++---
 tsl/test/shared/expected/extension.out        |  10 +-
 tsl/test/sql/bgw_custom.sql                   | 245 +++++++++++++
 28 files changed, 940 insertions(+), 246 deletions(-)

diff --git a/sql/job_api.sql b/sql/job_api.sql
index 7f7feb9c5..bf3c9a41b 100644
--- a/sql/job_api.sql
+++ b/sql/job_api.sql
@@ -24,9 +24,10 @@ CREATE OR REPLACE FUNCTION @extschema@.alter_job(
     scheduled BOOL = NULL,
     config JSONB = NULL,
     next_start TIMESTAMPTZ = NULL,
-    if_exists BOOL = FALSE
+    if_exists BOOL = FALSE, 
+    check_config REGPROC = NULL
 )
-RETURNS TABLE (job_id INTEGER, schedule_interval INTERVAL, max_runtime INTERVAL, max_retries INTEGER, retry_period INTERVAL, scheduled BOOL, config JSONB, next_start TIMESTAMPTZ)
+RETURNS TABLE (job_id INTEGER, schedule_interval INTERVAL, max_runtime INTERVAL, max_retries INTEGER, retry_period INTERVAL, scheduled BOOL, config JSONB, next_start TIMESTAMPTZ, check_config TEXT)
 AS '@MODULE_PATHNAME@', 'ts_job_alter'
 LANGUAGE C VOLATILE;
 
diff --git a/sql/policy_internal.sql b/sql/policy_internal.sql
index b84aae31a..d938c0053 100644
--- a/sql/policy_internal.sql
+++ b/sql/policy_internal.sql
@@ -6,7 +6,7 @@ CREATE OR REPLACE PROCEDURE _timescaledb_internal.policy_retention(job_id INTEGE
 AS '@MODULE_PATHNAME@', 'ts_policy_retention_proc'
 LANGUAGE C;
 
-CREATE OR REPLACE FUNCTION _timescaledb_internal.policy_retention_check(job_id INTEGER, config JSONB)
+CREATE OR REPLACE FUNCTION _timescaledb_internal.policy_retention_check(config JSONB)
 RETURNS void AS '@MODULE_PATHNAME@', 'ts_policy_retention_check'
 LANGUAGE C;
 
@@ -14,7 +14,7 @@ CREATE OR REPLACE PROCEDURE _timescaledb_internal.policy_reorder(job_id INTEGER,
 AS '@MODULE_PATHNAME@', 'ts_policy_reorder_proc'
 LANGUAGE C;
 
-CREATE OR REPLACE FUNCTION _timescaledb_internal.policy_reorder_check(job_id INTEGER, config JSONB)
+CREATE OR REPLACE FUNCTION _timescaledb_internal.policy_reorder_check(config JSONB)
 RETURNS void AS '@MODULE_PATHNAME@', 'ts_policy_reorder_check'
 LANGUAGE C;
 
@@ -22,7 +22,7 @@ CREATE OR REPLACE PROCEDURE _timescaledb_internal.policy_recompression(job_id IN
 AS '@MODULE_PATHNAME@', 'ts_policy_recompression_proc'
 LANGUAGE C;
 
-CREATE OR REPLACE FUNCTION _timescaledb_internal.policy_compression_check(job_id INTEGER, config JSONB)
+CREATE OR REPLACE FUNCTION _timescaledb_internal.policy_compression_check(config JSONB)
 RETURNS void AS '@MODULE_PATHNAME@', 'ts_policy_compression_check'
 LANGUAGE C;
 
@@ -30,7 +30,7 @@ CREATE OR REPLACE PROCEDURE _timescaledb_internal.policy_refresh_continuous_aggr
 AS '@MODULE_PATHNAME@', 'ts_policy_refresh_cagg_proc'
 LANGUAGE C;
 
-CREATE OR REPLACE FUNCTION _timescaledb_internal.policy_refresh_continuous_aggregate_check(job_id INTEGER, config JSONB)
+CREATE OR REPLACE FUNCTION _timescaledb_internal.policy_refresh_continuous_aggregate_check(config JSONB)
 RETURNS void AS '@MODULE_PATHNAME@', 'ts_policy_refresh_cagg_check'
 LANGUAGE C;
 
diff --git a/sql/updates/latest-dev.sql b/sql/updates/latest-dev.sql
index 1f58810e6..3afaa00c4 100644
--- a/sql/updates/latest-dev.sql
+++ b/sql/updates/latest-dev.sql
@@ -22,11 +22,8 @@ ALTER TABLE _timescaledb_catalog.chunk
 
 CREATE INDEX chunk_osm_chunk_idx ON _timescaledb_catalog.chunk (osm_chunk, hypertable_id);
 
-DROP FUNCTION IF EXISTS @extschema@.add_job;
-DROP FUNCTION IF EXISTS _timescaledb_internal.policy_retention_check(INTEGER, JSONB);
-DROP FUNCTION IF EXISTS _timescaledb_internal.policy_compression_check(INTEGER, JSONB);
-DROP FUNCTION IF EXISTS _timescaledb_internal.policy_reorder_check(INTEGER, JSONB);
-DROP FUNCTION IF EXISTS _timescaledb_internal.policy_refresh_continuous_aggregate_check(INTEGER, JSONB);
+DROP FUNCTION IF EXISTS @extschema@.add_job(REGPROC, INTERVAL, JSONB, TIMESTAMPTZ, BOOL);
+DROP FUNCTION IF EXISTS @extschema@.alter_job(INTEGER, INTERVAL, INTERVAL, INTEGER, INTERVAL, BOOL, JSONB, TIMESTAMPTZ, BOOL);
 
 -- add fields for check function
 ALTER TABLE _timescaledb_config.bgw_job
@@ -71,3 +68,5 @@ SET
   check_name = 'policy_refresh_continuous_aggregate_check'
 WHERE proc_schema = '_timescaledb_internal'
   AND proc_name = 'policy_refresh_continuous_aggregate';
+
+DROP VIEW IF EXISTS timescaledb_information.jobs;
diff --git a/sql/updates/reverse-dev.sql b/sql/updates/reverse-dev.sql
index 16204fb53..f0824418b 100644
--- a/sql/updates/reverse-dev.sql
+++ b/sql/updates/reverse-dev.sql
@@ -176,7 +176,7 @@ DROP TABLE _timescaledb_config.bgw_job;
 
 CREATE SEQUENCE _timescaledb_config.bgw_job_id_seq MINVALUE 1000;
 SELECT pg_catalog.pg_extension_config_dump('_timescaledb_config.bgw_job_id_seq', '');
-SELECT setval('_timescaledb_config.bgw_job_id_seq', last_value, is_called)
+SELECT pg_catalog.setval('_timescaledb_config.bgw_job_id_seq', last_value, is_called)
 FROM _timescaledb_internal.tmp_bgw_job_seq_value;
 DROP TABLE _timescaledb_internal.tmp_bgw_job_seq_value;
 
@@ -218,8 +218,31 @@ ALTER TABLE _timescaledb_internal.bgw_policy_chunk_stats
           FOREIGN KEY(job_id) REFERENCES _timescaledb_config.bgw_job(id)
           ON DELETE CASCADE;
 
-DROP FUNCTION IF EXISTS @extschema@.add_job;
+DROP FUNCTION IF EXISTS @extschema@.add_job(REGPROC, INTERVAL, JSONB, TIMESTAMPTZ, BOOL, REGPROC);
 DROP FUNCTION IF EXISTS _timescaledb_internal.policy_retention_check(JSONB);
 DROP FUNCTION IF EXISTS _timescaledb_internal.policy_compression_check(JSONB);
 DROP FUNCTION IF EXISTS _timescaledb_internal.policy_reorder_check(JSONB);
 DROP FUNCTION IF EXISTS _timescaledb_internal.policy_refresh_continuous_aggregate_check(JSONB);
+DROP FUNCTION IF EXISTS @extschema@.alter_job(INTEGER, INTERVAL, INTERVAL, INTEGER, INTERVAL, BOOL, JSONB, TIMESTAMPTZ, BOOL, REGPROC);
+
+CREATE FUNCTION @extschema@.add_job(proc REGPROC,
+  schedule_interval INTERVAL,
+  config JSONB = NULL,
+  initial_start TIMESTAMPTZ = NULL,
+  scheduled BOOL = true)
+RETURNS INTEGER AS '@MODULE_PATHNAME@', 'ts_job_add' LANGUAGE C VOLATILE;
+
+CREATE FUNCTION @extschema@.alter_job(
+    job_id INTEGER,
+    schedule_interval INTERVAL = NULL,
+    max_runtime INTERVAL = NULL,
+    max_retries INTEGER = NULL,
+    retry_period INTERVAL = NULL,
+    scheduled BOOL = NULL,
+    config JSONB = NULL,
+    next_start TIMESTAMPTZ = NULL,
+    if_exists BOOL = FALSE
+)
+RETURNS TABLE (job_id INTEGER, schedule_interval INTERVAL, max_runtime INTERVAL, max_retries INTEGER, retry_period INTERVAL, scheduled BOOL, config JSONB, next_start TIMESTAMPTZ)
+AS '@MODULE_PATHNAME@', 'ts_job_alter'
+LANGUAGE C VOLATILE;
diff --git a/sql/views.sql b/sql/views.sql
index 4c811a1dc..1bd2d975d 100644
--- a/sql/views.sql
+++ b/sql/views.sql
@@ -98,7 +98,9 @@ SELECT j.id AS job_id,
   j.config,
   js.next_start,
   ht.schema_name AS hypertable_schema,
-  ht.table_name AS hypertable_name
+  ht.table_name AS hypertable_name,
+  j.check_schema,
+  j.check_name
 FROM _timescaledb_config.bgw_job j
   LEFT JOIN _timescaledb_catalog.hypertable ht ON ht.id = j.hypertable_id
   LEFT JOIN _timescaledb_internal.bgw_job_stat js ON js.job_id = j.id;
diff --git a/src/bgw/job.c b/src/bgw/job.c
index 397accab7..f2ca69ace 100644
--- a/src/bgw/job.c
+++ b/src/bgw/job.c
@@ -10,6 +10,7 @@
 #include <catalog/pg_authid.h>
 #include <nodes/makefuncs.h>
 #include <parser/parse_func.h>
+#include <parser/parser.h>
 #include <postmaster/bgworker.h>
 #include <storage/ipc.h>
 #include <tcop/tcopprot.h>
@@ -109,16 +110,21 @@ job_execute_procedure(FuncExpr *funcexpr)
 void
 ts_bgw_job_run_config_check(Oid check, int32 job_id, Jsonb *config)
 {
-	List *args =
-		list_make2(makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum(job_id), false, true),
-				   makeConst(JSONBOID, -1, InvalidOid, -1, JsonbPGetDatum(config), false, false));
-	FuncExpr *funcexpr =
-		makeFuncExpr(check, VOIDOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL);
-
 	/* Nothing to check if there is no check function provided */
 	if (!OidIsValid(check))
 		return;
 
+	/* NULL config may be valid */
+	Const *arg;
+	if (config == NULL)
+		arg = makeNullConst(JSONBOID, -1, InvalidOid);
+	else
+		arg = makeConst(JSONBOID, -1, InvalidOid, -1, JsonbPGetDatum(config), false, false);
+
+	List *args = list_make1(arg);
+	FuncExpr *funcexpr =
+		makeFuncExpr(check, VOIDOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL);
+
 	switch (get_func_prokind(check))
 	{
 		case PROKIND_FUNCTION:
@@ -140,10 +146,8 @@ ts_bgw_job_run_config_check(Oid check, int32 job_id, Jsonb *config)
 static void
 job_config_check(BgwJob *job, Jsonb *config)
 {
-	const Oid proc_args[] = { INT4OID, JSONBOID };
-	List *name;
 	Oid proc;
-	bool started = false;
+	ObjectWithArgs *object;
 
 	/* Both should either be empty or contain a schema and name */
 	Assert((strlen(NameStr(job->fd.check_schema)) == 0) ==
@@ -153,27 +157,25 @@ job_config_check(BgwJob *job, Jsonb *config)
 	if (strlen(NameStr(job->fd.check_name)) == 0)
 		return;
 
-	if (!IsTransactionOrTransactionBlock())
-	{
-		started = true;
-		StartTransactionCommand();
-		/* executing sql functions requires snapshot */
-		PushActiveSnapshot(GetTransactionSnapshot());
-	}
+	object = makeNode(ObjectWithArgs);
 
-	/* We are using LookupFuncName here, which will find functions but not
-	 * procedures. We could use LookupFuncWithArgs here instead. */
-	name = list_make2(makeString(NameStr(job->fd.check_schema)),
-					  makeString(NameStr(job->fd.check_name)));
-	proc = LookupFuncName(name, 2, proc_args, false);
-	ts_bgw_job_run_config_check(proc, job->fd.id, config);
-	if (started)
-	{
-		/* if job does its own transaction handling it might not have set a snapshot */
-		if (ActiveSnapshotSet())
-			PopActiveSnapshot();
-		CommitTransactionCommand();
-	}
+	object->objname = list_make2(makeString(NameStr(job->fd.check_schema)),
+								 makeString(NameStr(job->fd.check_name)));
+	object->objargs = list_make1(SystemTypeName("jsonb"));
+	proc = LookupFuncWithArgs(OBJECT_ROUTINE, object, true);
+
+	/* a check function has been registered but it can't be found anymore
+	 because it was dropped or renamed. Allow alter_job to run if that's the case
+	 without validating the config but also print a warning */
+	if (OidIsValid(proc))
+		ts_bgw_job_run_config_check(proc, job->fd.id, config);
+	else
+		elog(WARNING,
+			 "function or procedure %s.%s(config jsonb) not found, skipping config validation for "
+			 "job %d",
+			 NameStr(job->fd.check_schema),
+			 NameStr(job->fd.check_name),
+			 job->fd.id);
 }
 
 static BgwJob *
@@ -782,9 +784,24 @@ bgw_job_tuple_update_by_id(TupleInfo *ti, void *const data)
 	repl[AttrNumberGetAttrOffset(Anum_bgw_job_scheduled)] = true;
 
 	repl[AttrNumberGetAttrOffset(Anum_bgw_job_config)] = true;
+
+	values[AttrNumberGetAttrOffset(Anum_bgw_job_check_schema)] =
+		NameGetDatum(&updated_job->fd.check_schema);
+	repl[AttrNumberGetAttrOffset(Anum_bgw_job_check_schema)] = true;
+
+	values[AttrNumberGetAttrOffset(Anum_bgw_job_check_name)] =
+		NameGetDatum(&updated_job->fd.check_name);
+	repl[AttrNumberGetAttrOffset(Anum_bgw_job_check_name)] = true;
+
+	if (strlen(NameStr(updated_job->fd.check_name)) == 0)
+	{
+		isnull[AttrNumberGetAttrOffset(Anum_bgw_job_check_name)] = true;
+		isnull[AttrNumberGetAttrOffset(Anum_bgw_job_check_schema)] = true;
+	}
+
 	if (updated_job->fd.config)
 	{
-		job_config_check(bgw_job_from_tupleinfo(ti, sizeof(BgwJob)), updated_job->fd.config);
+		job_config_check(updated_job, updated_job->fd.config);
 		values[AttrNumberGetAttrOffset(Anum_bgw_job_config)] =
 			JsonbPGetDatum(updated_job->fd.config);
 	}
diff --git a/src/bgw_policy/policy.c b/src/bgw_policy/policy.c
index 4f15d84e5..942b641d2 100644
--- a/src/bgw_policy/policy.c
+++ b/src/bgw_policy/policy.c
@@ -6,10 +6,8 @@
 
 #include <postgres.h>
 #include <utils/builtins.h>
-#include <jsonb_utils.h>
 
 #include "bgw/job.h"
-#include "dimension.h"
 #include "policy.h"
 
 void
diff --git a/tsl/src/bgw_policy/compression_api.c b/tsl/src/bgw_policy/compression_api.c
index c27452972..a571ad99c 100644
--- a/tsl/src/bgw_policy/compression_api.c
+++ b/tsl/src/bgw_policy/compression_api.c
@@ -158,11 +158,8 @@ validate_compress_after_type(Oid partitioning_type, Oid compress_after_type)
 Datum
 policy_compression_check(PG_FUNCTION_ARGS)
 {
-	if (PG_NARGS() != 2 || PG_ARGISNULL(0) || PG_ARGISNULL(1))
-		PG_RETURN_VOID();
-
 	PolicyCompressionData policy_data;
-	policy_compression_read_and_validate_config(PG_GETARG_JSONB_P(1), &policy_data);
+	policy_compression_read_and_validate_config(PG_GETARG_JSONB_P(0), &policy_data);
 	ts_cache_release(policy_data.hcache);
 
 	PG_RETURN_VOID();
diff --git a/tsl/src/bgw_policy/continuous_aggregate_api.c b/tsl/src/bgw_policy/continuous_aggregate_api.c
index a880f6b81..1930cc5b7 100644
--- a/tsl/src/bgw_policy/continuous_aggregate_api.c
+++ b/tsl/src/bgw_policy/continuous_aggregate_api.c
@@ -220,10 +220,7 @@ policy_refresh_cagg_proc(PG_FUNCTION_ARGS)
 Datum
 policy_refresh_cagg_check(PG_FUNCTION_ARGS)
 {
-	if (PG_NARGS() != 2 || PG_ARGISNULL(0) || PG_ARGISNULL(1))
-		PG_RETURN_VOID();
-
-	policy_refresh_cagg_read_and_validate_config(PG_GETARG_JSONB_P(1), NULL);
+	policy_refresh_cagg_read_and_validate_config(PG_GETARG_JSONB_P(0), NULL);
 
 	PG_RETURN_VOID();
 }
diff --git a/tsl/src/bgw_policy/job_api.c b/tsl/src/bgw_policy/job_api.c
index ed7947b81..3ecb797db 100644
--- a/tsl/src/bgw_policy/job_api.c
+++ b/tsl/src/bgw_policy/job_api.c
@@ -10,6 +10,9 @@
 #include <utils/acl.h>
 #include <utils/builtins.h>
 
+#include <parser/parse_func.h>
+#include <parser/parser.h>
+
 #include <bgw/job.h>
 #include <bgw/job_stat.h>
 
@@ -25,7 +28,37 @@
 /* Default retry period for reorder_jobs is currently 5 minutes */
 #define DEFAULT_RETRY_PERIOD (5 * USECS_PER_MINUTE)
 
-#define ALTER_JOB_NUM_COLS 8
+#define ALTER_JOB_NUM_COLS 9
+
+/*
+ * This function ensures that the check function has the required signature
+ * @param check A valid Oid
+ */
+static inline void
+validate_check_signature(Oid check)
+{
+	Oid proc = InvalidOid;
+	ObjectWithArgs *object;
+	NameData check_name = { 0 };
+	NameData check_schema = { 0 };
+
+	namestrcpy(&check_schema, get_namespace_name(get_func_namespace(check)));
+	namestrcpy(&check_name, get_func_name(check));
+
+	object = makeNode(ObjectWithArgs);
+	object->objname =
+		list_make2(makeString(NameStr(check_schema)), makeString(NameStr(check_name)));
+	object->objargs = list_make1(SystemTypeName("jsonb"));
+	proc = LookupFuncWithArgs(OBJECT_ROUTINE, object, true);
+
+	if (!OidIsValid(proc))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("function or procedure %s.%s(config jsonb) not found",
+						NameStr(check_schema),
+						NameStr(check_name)),
+				 errhint("The check function's signature must be (config jsonb).")));
+}
 
 /*
  * CREATE FUNCTION add_job(
@@ -110,8 +143,11 @@ job_add(PG_FUNCTION_ARGS)
 	namestrcpy(&proc_name, func_name);
 	namestrcpy(&owner_name, GetUserNameFromId(owner, false));
 
-	if (config)
-		ts_bgw_job_run_config_check(check, 0, config);
+	/* The check exists but may not have the expected signature: (config jsonb) */
+	if (OidIsValid(check))
+		validate_check_signature(check);
+
+	ts_bgw_job_run_config_check(check, 0, config);
 
 	job_id = ts_bgw_job_insert_relation(&application_name,
 										schedule_interval,
@@ -206,6 +242,7 @@ job_run(PG_FUNCTION_ARGS)
  * 6    config JSONB = NULL,
  * 7    next_start TIMESTAMPTZ = NULL
  * 8    if_exists BOOL = FALSE,
+ * 9    check_config REGPROC = NULL
  * ) RETURNS TABLE (
  *      job_id INTEGER,
  *      schedule_interval INTERVAL,
@@ -215,6 +252,7 @@ job_run(PG_FUNCTION_ARGS)
  *      scheduled BOOL,
  *      config JSONB,
  *      next_start TIMESTAMPTZ
+ *      check_config TEXT
  * )
  */
 Datum
@@ -229,6 +267,13 @@ job_alter(PG_FUNCTION_ARGS)
 	int job_id = PG_GETARG_INT32(0);
 	bool if_exists = PG_GETARG_BOOL(8);
 	BgwJob *job;
+	NameData check_name = { 0 };
+	NameData check_schema = { 0 };
+	Oid check = PG_ARGISNULL(9) ? InvalidOid : PG_GETARG_OID(9);
+	char *check_name_str = NULL;
+	/* Added space for period and NULL */
+	char schema_qualified_check_name[2 * NAMEDATALEN + 2] = { 0 };
+	bool unregister_check = (!PG_ARGISNULL(9) && !OidIsValid(check));
 
 	TS_PREVENT_FUNC_IF_READ_ONLY();
 
@@ -259,6 +304,50 @@ job_alter(PG_FUNCTION_ARGS)
 	if (!PG_ARGISNULL(6))
 		job->fd.config = PG_GETARG_JSONB_P(6);
 
+	if (!PG_ARGISNULL(9))
+	{
+		if (OidIsValid(check))
+		{
+			check_name_str = get_func_name(check);
+			if (check_name_str == NULL)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("function with OID %d does not exist", check)));
+
+			if (pg_proc_aclcheck(check, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK)
+				ereport(ERROR,
+						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+						 errmsg("permission denied for function \"%s\"", check_name_str),
+						 errhint("Job owner must have EXECUTE privilege on the function.")));
+
+			namestrcpy(&check_schema, get_namespace_name(get_func_namespace(check)));
+			namestrcpy(&check_name, check_name_str);
+
+			/* The check exists but may not have the expected signature: (config jsonb) */
+			validate_check_signature(check);
+
+			namestrcpy(&job->fd.check_schema, NameStr(check_schema));
+			namestrcpy(&job->fd.check_name, NameStr(check_name));
+			snprintf(schema_qualified_check_name,
+					 sizeof(schema_qualified_check_name) / sizeof(schema_qualified_check_name[0]),
+					 "%s.%s",
+					 NameStr(check_schema),
+					 check_name_str);
+		}
+	}
+	else
+		snprintf(schema_qualified_check_name,
+				 sizeof(schema_qualified_check_name) / sizeof(schema_qualified_check_name[0]),
+				 "%s.%s",
+				 NameStr(job->fd.check_schema),
+				 NameStr(job->fd.check_name));
+
+	if (unregister_check)
+	{
+		NameData empty_namedata = { 0 };
+		namestrcpy(&job->fd.check_schema, NameStr(empty_namedata));
+		namestrcpy(&job->fd.check_name, NameStr(empty_namedata));
+	}
 	ts_bgw_job_update_by_id(job_id, job);
 
 	if (!PG_ARGISNULL(7))
@@ -285,6 +374,13 @@ job_alter(PG_FUNCTION_ARGS)
 
 	values[7] = TimestampTzGetDatum(next_start);
 
+	if (unregister_check)
+		nulls[8] = true;
+	else if (strlen(NameStr(job->fd.check_schema)) > 0)
+		values[8] = CStringGetTextDatum(schema_qualified_check_name);
+	else
+		nulls[8] = true;
+
 	tuple = heap_form_tuple(tupdesc, values, nulls);
 	return HeapTupleGetDatum(tuple);
 }
diff --git a/tsl/src/bgw_policy/reorder_api.c b/tsl/src/bgw_policy/reorder_api.c
index a77e46ed1..1f7cccc32 100644
--- a/tsl/src/bgw_policy/reorder_api.c
+++ b/tsl/src/bgw_policy/reorder_api.c
@@ -109,12 +109,9 @@ check_valid_index(Hypertable *ht, Name index_name)
 Datum
 policy_reorder_check(PG_FUNCTION_ARGS)
 {
-	if (PG_NARGS() != 2 || PG_ARGISNULL(0) || PG_ARGISNULL(1))
-		PG_RETURN_VOID();
-
 	TS_PREVENT_FUNC_IF_READ_ONLY();
 
-	policy_reorder_read_and_validate_config(PG_GETARG_JSONB_P(1), NULL);
+	policy_reorder_read_and_validate_config(PG_GETARG_JSONB_P(0), NULL);
 
 	PG_RETURN_VOID();
 }
diff --git a/tsl/src/bgw_policy/retention_api.c b/tsl/src/bgw_policy/retention_api.c
index 12faf427e..e9d30cdab 100644
--- a/tsl/src/bgw_policy/retention_api.c
+++ b/tsl/src/bgw_policy/retention_api.c
@@ -43,12 +43,9 @@ policy_retention_proc(PG_FUNCTION_ARGS)
 Datum
 policy_retention_check(PG_FUNCTION_ARGS)
 {
-	if (PG_NARGS() != 2 || PG_ARGISNULL(0) || PG_ARGISNULL(1))
-		PG_RETURN_VOID();
-
 	TS_PREVENT_FUNC_IF_READ_ONLY();
 
-	policy_retention_read_and_validate_config(PG_GETARG_JSONB_P(1), NULL);
+	policy_retention_read_and_validate_config(PG_GETARG_JSONB_P(0), NULL);
 
 	PG_RETURN_VOID();
 }
diff --git a/tsl/test/expected/bgw_custom.out b/tsl/test/expected/bgw_custom.out
index 0f1611753..ec1a86ab9 100644
--- a/tsl/test/expected/bgw_custom.out
+++ b/tsl/test/expected/bgw_custom.out
@@ -74,13 +74,13 @@ SELECT add_job('custom_func_definer', '1h', config:='{"type":"function"}'::jsonb
 (1 row)
 
 SELECT * FROM timescaledb_information.jobs WHERE job_id != 1 ORDER BY 1;
- job_id |      application_name      | schedule_interval | max_runtime | max_retries | retry_period | proc_schema |      proc_name      |       owner       | scheduled |        config         | next_start | hypertable_schema | hypertable_name 
---------+----------------------------+-------------------+-------------+-------------+--------------+-------------+---------------------+-------------------+-----------+-----------------------+------------+-------------------+-----------------
-   1000 | User-Defined Action [1000] | @ 1 hour          | @ 0         |          -1 | @ 5 mins     | public      | custom_func         | default_perm_user | t         | {"type": "function"}  |            |                   | 
-   1001 | User-Defined Action [1001] | @ 1 hour          | @ 0         |          -1 | @ 5 mins     | public      | custom_proc         | default_perm_user | t         | {"type": "procedure"} |            |                   | 
-   1002 | User-Defined Action [1002] | @ 1 hour          | @ 0         |          -1 | @ 5 mins     | public      | custom_proc2        | default_perm_user | t         | {"type": "procedure"} |            |                   | 
-   1003 | User-Defined Action [1003] | @ 1 hour          | @ 0         |          -1 | @ 5 mins     | public      | custom_func         | default_perm_user | t         | {"type": "function"}  |            |                   | 
-   1004 | User-Defined Action [1004] | @ 1 hour          | @ 0         |          -1 | @ 5 mins     | public      | custom_func_definer | default_perm_user | t         | {"type": "function"}  |            |                   | 
+ job_id |      application_name      | schedule_interval | max_runtime | max_retries | retry_period | proc_schema |      proc_name      |       owner       | scheduled |        config         | next_start | hypertable_schema | hypertable_name | check_schema | check_name 
+--------+----------------------------+-------------------+-------------+-------------+--------------+-------------+---------------------+-------------------+-----------+-----------------------+------------+-------------------+-----------------+--------------+------------
+   1000 | User-Defined Action [1000] | @ 1 hour          | @ 0         |          -1 | @ 5 mins     | public      | custom_func         | default_perm_user | t         | {"type": "function"}  |            |                   |                 |              | 
+   1001 | User-Defined Action [1001] | @ 1 hour          | @ 0         |          -1 | @ 5 mins     | public      | custom_proc         | default_perm_user | t         | {"type": "procedure"} |            |                   |                 |              | 
+   1002 | User-Defined Action [1002] | @ 1 hour          | @ 0         |          -1 | @ 5 mins     | public      | custom_proc2        | default_perm_user | t         | {"type": "procedure"} |            |                   |                 |              | 
+   1003 | User-Defined Action [1003] | @ 1 hour          | @ 0         |          -1 | @ 5 mins     | public      | custom_func         | default_perm_user | t         | {"type": "function"}  |            |                   |                 |              | 
+   1004 | User-Defined Action [1004] | @ 1 hour          | @ 0         |          -1 | @ 5 mins     | public      | custom_func_definer | default_perm_user | t         | {"type": "function"}  |            |                   |                 |              | 
 (5 rows)
 
 SELECT count(*) FROM _timescaledb_config.bgw_job WHERE config->>'type' IN ('procedure', 'function');
@@ -574,3 +574,328 @@ SELECT _timescaledb_internal.stop_background_workers();
  t
 (1 row)
 
+SELECT _timescaledb_internal.restart_background_workers();
+ restart_background_workers 
+----------------------------
+ t
+(1 row)
+
+\set ON_ERROR_STOP 0
+-- add test for custom jobs with custom check functions
+-- create the functions/procedures to be used as checking functions
+CREATE OR REPLACE PROCEDURE test_config_check_proc(config jsonb)
+LANGUAGE PLPGSQL
+AS $$
+DECLARE
+  drop_after interval;
+BEGIN 
+    SELECT jsonb_object_field_text (config, 'drop_after')::interval INTO STRICT drop_after;
+    IF drop_after IS NULL THEN 
+        RAISE EXCEPTION 'Config must be not NULL and have drop_after';
+    END IF ;
+END
+$$;
+CREATE OR REPLACE FUNCTION test_config_check_func(config jsonb) RETURNS VOID
+AS $$
+DECLARE
+  drop_after interval;
+BEGIN 
+    IF config IS NULL THEN
+        RETURN;
+    END IF;
+    SELECT jsonb_object_field_text (config, 'drop_after')::interval INTO STRICT drop_after;
+    IF drop_after IS NULL THEN 
+        RAISE EXCEPTION 'Config can be NULL but must have drop_after if not';
+    END IF ;
+END
+$$ LANGUAGE PLPGSQL;
+-- step 2, create a procedure to run as a custom job
+CREATE OR REPLACE PROCEDURE test_proc_with_check(job_id int, config jsonb)
+LANGUAGE PLPGSQL
+AS $$
+BEGIN
+  RAISE NOTICE 'Will only print this if config passes checks, my config is %', config; 
+END
+$$;
+-- step 3, add the job with the config check function passed as argument
+-- test procedures
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_proc'::regproc);
+ERROR:  Config must be not NULL and have drop_after
+select add_job('test_proc_with_check', '5 secs', config => NULL, check_config => 'test_config_check_proc'::regproc);
+ERROR:  Config must be not NULL and have drop_after
+select add_job('test_proc_with_check', '5 secs', config => '{"drop_after": "chicken"}', check_config => 'test_config_check_proc'::regproc);
+ERROR:  invalid input syntax for type interval: "chicken"
+select add_job('test_proc_with_check', '5 secs', config => '{"drop_after": "2 weeks"}', check_config => 'test_config_check_proc'::regproc)
+as job_with_proc_check_id \gset
+-- test functions
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_func'::regproc);
+ERROR:  Config can be NULL but must have drop_after if not
+select add_job('test_proc_with_check', '5 secs', config => NULL, check_config => 'test_config_check_func'::regproc);
+ add_job 
+---------
+    1006
+(1 row)
+
+select add_job('test_proc_with_check', '5 secs', config => '{"drop_after": "chicken"}', check_config => 'test_config_check_func'::regproc);
+ERROR:  invalid input syntax for type interval: "chicken"
+select add_job('test_proc_with_check', '5 secs', config => '{"drop_after": "2 weeks"}', check_config => 'test_config_check_func'::regproc) 
+as job_with_func_check_id \gset
+--- test alter_job
+select alter_job(:job_with_func_check_id, config => '{"drop_after":"chicken"}');
+ERROR:  invalid input syntax for type interval: "chicken"
+select alter_job(:job_with_func_check_id, config => '{"drop_after":"5 years"}');
+                                                    alter_job                                                    
+-----------------------------------------------------------------------------------------------------------------
+ (1007,"@ 5 secs","@ 0",-1,"@ 5 mins",t,"{""drop_after"": ""5 years""}",-infinity,public.test_config_check_func)
+(1 row)
+
+select alter_job(:job_with_proc_check_id, config => '{"drop_after":"4 days"}');
+                                                   alter_job                                                    
+----------------------------------------------------------------------------------------------------------------
+ (1005,"@ 5 secs","@ 0",-1,"@ 5 mins",t,"{""drop_after"": ""4 days""}",-infinity,public.test_config_check_proc)
+(1 row)
+
+-- test that jobs with an incorrect check function signature will not be registered
+-- these are all incorrect function signatures 
+CREATE OR REPLACE FUNCTION test_config_check_func_0args() RETURNS VOID
+AS $$
+BEGIN 
+    RAISE NOTICE 'I take no arguments and will validate anything you give me!';
+END
+$$ LANGUAGE PLPGSQL;
+CREATE OR REPLACE FUNCTION test_config_check_func_2args(config jsonb, intarg int) RETURNS VOID
+AS $$
+BEGIN 
+    RAISE NOTICE 'I take two arguments (jsonb, int) and I should fail to run!';
+END
+$$ LANGUAGE PLPGSQL;
+CREATE OR REPLACE FUNCTION test_config_check_func_intarg(config int) RETURNS VOID
+AS $$
+BEGIN 
+    RAISE NOTICE 'I take one argument which is an integer and I should fail to run!';
+END
+$$ LANGUAGE PLPGSQL;
+-- -- this should fail, it has an incorrect check function 
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_func_0args'::regproc);
+ERROR:  function or procedure public.test_config_check_func_0args(config jsonb) not found
+-- -- so should this
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_func_2args'::regproc);
+ERROR:  function or procedure public.test_config_check_func_2args(config jsonb) not found
+-- and this
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_func_intarg'::regproc);
+ERROR:  function or procedure public.test_config_check_func_intarg(config jsonb) not found
+-- and this fails as it calls a nonexistent function
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_nonexistent_check_func'::regproc);
+ERROR:  function "test_nonexistent_check_func" does not exist at character 82
+-- when called with a valid check function and a NULL config no check should occur
+CREATE OR REPLACE FUNCTION test_config_check_func(config jsonb) RETURNS VOID
+AS $$
+BEGIN 
+    RAISE NOTICE 'This message will get printed for both NULL and not NULL config';
+END
+$$ LANGUAGE PLPGSQL;
+SET client_min_messages = NOTICE;
+-- check done for both NULL and non-NULL config
+select add_job('test_proc_with_check', '5 secs', config => NULL, check_config => 'test_config_check_func'::regproc);
+NOTICE:  This message will get printed for both NULL and not NULL config
+ add_job 
+---------
+    1008
+(1 row)
+
+-- check done
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_func'::regproc) as job_id \gset
+NOTICE:  This message will get printed for both NULL and not NULL config
+-- check function not returning void
+CREATE OR REPLACE FUNCTION test_config_check_func_returns_int(config jsonb) RETURNS INT
+AS $$
+BEGIN 
+    raise notice 'I print a message, and then I return least(1,2)';
+    RETURN LEAST(1, 2);
+END
+$$ LANGUAGE PLPGSQL;
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_func_returns_int'::regproc) as job_id_int \gset
+NOTICE:  I print a message, and then I return least(1,2)
+-- drop the registered check function, verify that alter_job will work and print a warning that 
+-- the check is being skipped due to the check function missing
+ALTER FUNCTION test_config_check_func RENAME TO renamed_func;
+select alter_job(:job_id, schedule_interval => '1 hour');
+WARNING:  function or procedure public.test_config_check_func(config jsonb) not found, skipping config validation for job 1009
+                                     alter_job                                      
+------------------------------------------------------------------------------------
+ (1009,"@ 1 hour","@ 0",-1,"@ 5 mins",t,{},-infinity,public.test_config_check_func)
+(1 row)
+
+DROP FUNCTION test_config_check_func_returns_int;
+select alter_job(:job_id_int, config => '{"field":"value"}');
+WARNING:  function or procedure public.test_config_check_func_returns_int(config jsonb) not found, skipping config validation for job 1010
+                                                      alter_job                                                       
+----------------------------------------------------------------------------------------------------------------------
+ (1010,"@ 5 secs","@ 0",-1,"@ 5 mins",t,"{""field"": ""value""}",-infinity,public.test_config_check_func_returns_int)
+(1 row)
+
+-- rename the check function and then call alter_job to register the new name
+select alter_job(:job_id, check_config => 'renamed_func'::regproc);
+NOTICE:  This message will get printed for both NULL and not NULL config
+                                alter_job                                 
+--------------------------------------------------------------------------
+ (1009,"@ 1 hour","@ 0",-1,"@ 5 mins",t,{},-infinity,public.renamed_func)
+(1 row)
+
+-- run alter again, should get a config check
+select alter_job(:job_id, config => '{}');
+NOTICE:  This message will get printed for both NULL and not NULL config
+                                alter_job                                 
+--------------------------------------------------------------------------
+ (1009,"@ 1 hour","@ 0",-1,"@ 5 mins",t,{},-infinity,public.renamed_func)
+(1 row)
+
+-- do not drop the current check function but register a new one
+CREATE OR REPLACE FUNCTION substitute_check_func(config jsonb) RETURNS VOID
+AS $$
+BEGIN 
+    RAISE NOTICE 'This message is a substitute of the previously printed one';
+END
+$$ LANGUAGE PLPGSQL;
+-- register the new check
+select alter_job(:job_id, check_config => 'substitute_check_func');
+NOTICE:  This message is a substitute of the previously printed one
+                                     alter_job                                     
+-----------------------------------------------------------------------------------
+ (1009,"@ 1 hour","@ 0",-1,"@ 5 mins",t,{},-infinity,public.substitute_check_func)
+(1 row)
+
+select alter_job(:job_id, config => '{}');
+NOTICE:  This message is a substitute of the previously printed one
+                                     alter_job                                     
+-----------------------------------------------------------------------------------
+ (1009,"@ 1 hour","@ 0",-1,"@ 5 mins",t,{},-infinity,public.substitute_check_func)
+(1 row)
+
+RESET client_min_messages;
+-- test an oid that doesn't exist
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 17424217::regproc);
+ERROR:  function with OID 17424217 does not exist
+\c :TEST_DBNAME :ROLE_SUPERUSER
+-- test a function with insufficient privileges
+create schema test_schema;
+create role user_noexec with login;
+grant usage on schema test_schema to user_noexec;
+CREATE OR REPLACE FUNCTION test_schema.test_config_check_func_privileges(config jsonb) RETURNS VOID
+AS $$
+BEGIN 
+    RAISE NOTICE 'This message will only get printed if privileges suffice';
+END
+$$ LANGUAGE PLPGSQL;
+revoke execute on function test_schema.test_config_check_func_privileges from public;
+-- verify the user doesn't have execute permissions on the function
+select has_function_privilege('user_noexec', 'test_schema.test_config_check_func_privileges(jsonb)', 'execute');
+ has_function_privilege 
+------------------------
+ f
+(1 row)
+
+\c :TEST_DBNAME user_noexec
+-- user_noexec should not have exec permissions on this function
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_schema.test_config_check_func_privileges'::regproc);
+ERROR:  permission denied for function "test_config_check_func_privileges"
+\c :TEST_DBNAME :ROLE_SUPERUSER
+-- check that alter_job rejects a check function with invalid signature
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'renamed_func') as job_id_alter \gset
+NOTICE:  This message will get printed for both NULL and not NULL config
+select alter_job(:job_id_alter, check_config => 'test_config_check_func_0args');
+ERROR:  function or procedure public.test_config_check_func_0args(config jsonb) not found
+select alter_job(:job_id_alter);
+NOTICE:  This message will get printed for both NULL and not NULL config
+                                alter_job                                 
+--------------------------------------------------------------------------
+ (1011,"@ 5 secs","@ 0",-1,"@ 5 mins",t,{},-infinity,public.renamed_func)
+(1 row)
+
+-- test that we can unregister the check function
+select alter_job(:job_id_alter, check_config => 0);
+                       alter_job                       
+-------------------------------------------------------
+ (1011,"@ 5 secs","@ 0",-1,"@ 5 mins",t,{},-infinity,)
+(1 row)
+
+-- no message printed now
+select alter_job(:job_id_alter, config => '{}'); 
+                       alter_job                       
+-------------------------------------------------------
+ (1011,"@ 5 secs","@ 0",-1,"@ 5 mins",t,{},-infinity,)
+(1 row)
+
+-- test what happens if the check function contains a COMMIT
+-- procedure with transaction handling
+CREATE OR REPLACE PROCEDURE custom_proc2_jsonb(config jsonb) LANGUAGE PLPGSQL AS
+$$
+BEGIN
+--   RAISE NOTICE 'Starting some transactions inside procedure';
+  INSERT INTO custom_log VALUES(1, $1, 'custom_proc 1 COMMIT');
+  COMMIT;
+END
+$$;
+select add_job('test_proc_with_check', '5 secs', config => '{}') as job_id_err \gset
+select alter_job(:job_id_err, check_config => 'custom_proc2_jsonb');
+ERROR:  portal snapshots (0) did not account for all active snapshots (1)
+select alter_job(:job_id_err, schedule_interval => '3 minutes');
+                       alter_job                       
+-------------------------------------------------------
+ (1012,"@ 3 mins","@ 0",-1,"@ 5 mins",t,{},-infinity,)
+(1 row)
+
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'custom_proc2_jsonb') as job_id_commit \gset
+ERROR:  portal snapshots (0) did not account for all active snapshots (1)
+-- test the case where we have a background job that registers jobs with a check fn
+CREATE OR REPLACE PROCEDURE add_scheduled_jobs_with_check(job_id int, config jsonb) LANGUAGE PLPGSQL AS 
+$$
+BEGIN
+    perform add_job('test_proc_with_check', schedule_interval => '10 secs', config => '{}', check_config => 'renamed_func');
+END
+$$;
+select add_job('add_scheduled_jobs_with_check', schedule_interval => '1 hour') as last_job_id \gset
+-- wait for enough time
+SELECT wait_for_job_to_run(:last_job_id, 1);
+ wait_for_job_to_run 
+---------------------
+ t
+(1 row)
+
+select total_runs, total_successes, last_run_status from timescaledb_information.job_stats where job_id = :last_job_id;
+ total_runs | total_successes | last_run_status 
+------------+-----------------+-----------------
+          1 |               1 | Success
+(1 row)
+
+-- test coverage for alter_job
+-- registering an invalid oid
+select alter_job(:job_id_alter, check_config => 123456789::regproc);
+ERROR:  function with OID 123456789 does not exist
+-- registering a function with insufficient privileges
+\c :TEST_DBNAME user_noexec
+select * from add_job('test_proc_with_check', '5 secs', config => '{}') as job_id_owner \gset
+select * from alter_job(:job_id_owner, check_config => 'test_schema.test_config_check_func_privileges'::regproc);
+ERROR:  permission denied for function "test_config_check_func_privileges"
+\c :TEST_DBNAME :ROLE_SUPERUSER
+DROP SCHEMA test_schema CASCADE;
+NOTICE:  drop cascades to function test_schema.test_config_check_func_privileges(jsonb)
+DROP ROLE user_noexec;
+-- test with aggregate check proc
+create function jsonb_add (j1 jsonb, j2 jsonb) returns jsonb
+AS $$
+BEGIN 
+    RETURN j1 || j2;
+END
+$$ LANGUAGE PLPGSQL;
+create table jsonb_values (j jsonb, i int);
+insert into jsonb_values values ('{"refresh_after":"2 weeks"}', 1), ('{"compress_after":"2 weeks"}', 2), ('{"drop_after":"2 weeks"}', 3);
+CREATE AGGREGATE sum_jsb (jsonb)
+(
+    sfunc = jsonb_add,
+    stype = jsonb,
+    initcond = '{}'
+);
+-- for test coverage, check unsupported aggregate type
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'sum_jsb'::regproc);
+ERROR:  unsupported function type
diff --git a/tsl/test/expected/bgw_db_scheduler.out b/tsl/test/expected/bgw_db_scheduler.out
index f620b1cbc..2ab2e3d35 100644
--- a/tsl/test/expected/bgw_db_scheduler.out
+++ b/tsl/test/expected/bgw_db_scheduler.out
@@ -1420,9 +1420,9 @@ SELECT wait_for_timer_to_run(0);
 SELECT insert_job('another', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s') AS job_id \gset
 -- call alter_job to trigger cache invalidation
 SELECT alter_job(:job_id,scheduled:=true);
-                           alter_job                            
-----------------------------------------------------------------
- (1026,"@ 0.1 secs","@ 1 min 40 secs",5,"@ 1 sec",t,,-infinity)
+                            alter_job                            
+-----------------------------------------------------------------
+ (1026,"@ 0.1 secs","@ 1 min 40 secs",5,"@ 1 sec",t,,-infinity,)
 (1 row)
 
 SELECT ts_bgw_params_reset_time(50000, true);
@@ -1522,9 +1522,9 @@ SELECT wait_for_timer_to_run(400000);
 SELECT insert_job('new_job', 'bgw_test_job_1', INTERVAL '10ms', INTERVAL '100s', INTERVAL '1s') AS job_id \gset
 -- call alter_job to trigger cache invalidation
 SELECT alter_job(:job_id,scheduled:=true);
-                            alter_job                            
------------------------------------------------------------------
- (1027,"@ 0.01 secs","@ 1 min 40 secs",5,"@ 1 sec",t,,-infinity)
+                            alter_job                             
+------------------------------------------------------------------
+ (1027,"@ 0.01 secs","@ 1 min 40 secs",5,"@ 1 sec",t,,-infinity,)
 (1 row)
 
 SELECT ts_bgw_params_reset_time(450000, true);
diff --git a/tsl/test/expected/bgw_reorder_drop_chunks.out b/tsl/test/expected/bgw_reorder_drop_chunks.out
index b3322ff98..ac765f15f 100644
--- a/tsl/test/expected/bgw_reorder_drop_chunks.out
+++ b/tsl/test/expected/bgw_reorder_drop_chunks.out
@@ -102,9 +102,9 @@ SELECT count(*) FROM _timescaledb_config.bgw_job WHERE proc_schema = '_timescale
 
 -- job was created
 SELECT * FROM timescaledb_information.jobs WHERE job_id=:reorder_job_id;
- job_id |   application_name    | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema      |   proc_name    |       owner       | scheduled |                              config                               | next_start | hypertable_schema |  hypertable_name   
---------+-----------------------+-------------------+-------------+-------------+--------------+-----------------------+----------------+-------------------+-----------+-------------------------------------------------------------------+------------+-------------------+--------------------
-   1000 | Reorder Policy [1000] | @ 4 days          | @ 0         |          -1 | @ 5 mins     | _timescaledb_internal | policy_reorder | default_perm_user | t         | {"index_name": "test_reorder_table_time_idx", "hypertable_id": 1} |            | public            | test_reorder_table
+ job_id |   application_name    | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema      |   proc_name    |       owner       | scheduled |                              config                               | next_start | hypertable_schema |  hypertable_name   |     check_schema      |      check_name      
+--------+-----------------------+-------------------+-------------+-------------+--------------+-----------------------+----------------+-------------------+-----------+-------------------------------------------------------------------+------------+-------------------+--------------------+-----------------------+----------------------
+   1000 | Reorder Policy [1000] | @ 4 days          | @ 0         |          -1 | @ 5 mins     | _timescaledb_internal | policy_reorder | default_perm_user | t         | {"index_name": "test_reorder_table_time_idx", "hypertable_id": 1} |            | public            | test_reorder_table | _timescaledb_internal | policy_reorder_check
 (1 row)
 
 -- no stats
@@ -138,9 +138,9 @@ SELECT * FROM sorted_bgw_log;
 (2 rows)
 
 SELECT * FROM timescaledb_information.jobs WHERE job_id=:reorder_job_id;
- job_id |   application_name    | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema      |   proc_name    |       owner       | scheduled |                              config                               |          next_start          | hypertable_schema |  hypertable_name   
---------+-----------------------+-------------------+-------------+-------------+--------------+-----------------------+----------------+-------------------+-----------+-------------------------------------------------------------------+------------------------------+-------------------+--------------------
-   1000 | Reorder Policy [1000] | @ 4 days          | @ 0         |          -1 | @ 5 mins     | _timescaledb_internal | policy_reorder | default_perm_user | t         | {"index_name": "test_reorder_table_time_idx", "hypertable_id": 1} | Fri Dec 31 16:00:00 1999 PST | public            | test_reorder_table
+ job_id |   application_name    | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema      |   proc_name    |       owner       | scheduled |                              config                               |          next_start          | hypertable_schema |  hypertable_name   |     check_schema      |      check_name      
+--------+-----------------------+-------------------+-------------+-------------+--------------+-----------------------+----------------+-------------------+-----------+-------------------------------------------------------------------+------------------------------+-------------------+--------------------+-----------------------+----------------------
+   1000 | Reorder Policy [1000] | @ 4 days          | @ 0         |          -1 | @ 5 mins     | _timescaledb_internal | policy_reorder | default_perm_user | t         | {"index_name": "test_reorder_table_time_idx", "hypertable_id": 1} | Fri Dec 31 16:00:00 1999 PST | public            | test_reorder_table | _timescaledb_internal | policy_reorder_check
 (1 row)
 
 -- job ran once, successfully
@@ -178,9 +178,9 @@ SELECT * FROM sorted_bgw_log;
 (4 rows)
 
 SELECT * FROM timescaledb_information.jobs WHERE job_id=:reorder_job_id;
- job_id |   application_name    | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema      |   proc_name    |       owner       | scheduled |                              config                               |            next_start            | hypertable_schema |  hypertable_name   
---------+-----------------------+-------------------+-------------+-------------+--------------+-----------------------+----------------+-------------------+-----------+-------------------------------------------------------------------+----------------------------------+-------------------+--------------------
-   1000 | Reorder Policy [1000] | @ 4 days          | @ 0         |          -1 | @ 5 mins     | _timescaledb_internal | policy_reorder | default_perm_user | t         | {"index_name": "test_reorder_table_time_idx", "hypertable_id": 1} | Fri Dec 31 16:00:00.025 1999 PST | public            | test_reorder_table
+ job_id |   application_name    | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema      |   proc_name    |       owner       | scheduled |                              config                               |            next_start            | hypertable_schema |  hypertable_name   |     check_schema      |      check_name      
+--------+-----------------------+-------------------+-------------+-------------+--------------+-----------------------+----------------+-------------------+-----------+-------------------------------------------------------------------+----------------------------------+-------------------+--------------------+-----------------------+----------------------
+   1000 | Reorder Policy [1000] | @ 4 days          | @ 0         |          -1 | @ 5 mins     | _timescaledb_internal | policy_reorder | default_perm_user | t         | {"index_name": "test_reorder_table_time_idx", "hypertable_id": 1} | Fri Dec 31 16:00:00.025 1999 PST | public            | test_reorder_table | _timescaledb_internal | policy_reorder_check
 (1 row)
 
 -- two runs
@@ -222,9 +222,9 @@ SELECT * FROM sorted_bgw_log;
 (6 rows)
 
 SELECT * FROM timescaledb_information.jobs WHERE job_id=:reorder_job_id;
- job_id |   application_name    | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema      |   proc_name    |       owner       | scheduled |                              config                               |           next_start            | hypertable_schema |  hypertable_name   
---------+-----------------------+-------------------+-------------+-------------+--------------+-----------------------+----------------+-------------------+-----------+-------------------------------------------------------------------+---------------------------------+-------------------+--------------------
-   1000 | Reorder Policy [1000] | @ 4 days          | @ 0         |          -1 | @ 5 mins     | _timescaledb_internal | policy_reorder | default_perm_user | t         | {"index_name": "test_reorder_table_time_idx", "hypertable_id": 1} | Tue Jan 04 16:00:00.05 2000 PST | public            | test_reorder_table
+ job_id |   application_name    | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema      |   proc_name    |       owner       | scheduled |                              config                               |           next_start            | hypertable_schema |  hypertable_name   |     check_schema      |      check_name      
+--------+-----------------------+-------------------+-------------+-------------+--------------+-----------------------+----------------+-------------------+-----------+-------------------------------------------------------------------+---------------------------------+-------------------+--------------------+-----------------------+----------------------
+   1000 | Reorder Policy [1000] | @ 4 days          | @ 0         |          -1 | @ 5 mins     | _timescaledb_internal | policy_reorder | default_perm_user | t         | {"index_name": "test_reorder_table_time_idx", "hypertable_id": 1} | Tue Jan 04 16:00:00.05 2000 PST | public            | test_reorder_table | _timescaledb_internal | policy_reorder_check
 (1 row)
 
 SELECT *
@@ -266,9 +266,9 @@ SELECT * FROM sorted_bgw_log;
 (7 rows)
 
 SELECT * FROM timescaledb_information.jobs WHERE job_id=:reorder_job_id;
- job_id |   application_name    | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema      |   proc_name    |       owner       | scheduled |                              config                               |           next_start            | hypertable_schema |  hypertable_name   
---------+-----------------------+-------------------+-------------+-------------+--------------+-----------------------+----------------+-------------------+-----------+-------------------------------------------------------------------+---------------------------------+-------------------+--------------------
-   1000 | Reorder Policy [1000] | @ 4 days          | @ 0         |          -1 | @ 5 mins     | _timescaledb_internal | policy_reorder | default_perm_user | t         | {"index_name": "test_reorder_table_time_idx", "hypertable_id": 1} | Tue Jan 04 16:00:00.05 2000 PST | public            | test_reorder_table
+ job_id |   application_name    | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema      |   proc_name    |       owner       | scheduled |                              config                               |           next_start            | hypertable_schema |  hypertable_name   |     check_schema      |      check_name      
+--------+-----------------------+-------------------+-------------+-------------+--------------+-----------------------+----------------+-------------------+-----------+-------------------------------------------------------------------+---------------------------------+-------------------+--------------------+-----------------------+----------------------
+   1000 | Reorder Policy [1000] | @ 4 days          | @ 0         |          -1 | @ 5 mins     | _timescaledb_internal | policy_reorder | default_perm_user | t         | {"index_name": "test_reorder_table_time_idx", "hypertable_id": 1} | Tue Jan 04 16:00:00.05 2000 PST | public            | test_reorder_table | _timescaledb_internal | policy_reorder_check
 (1 row)
 
 SELECT *
@@ -305,8 +305,8 @@ SELECT remove_reorder_policy('test_reorder_table');
 (1 row)
 
 SELECT * FROM timescaledb_information.jobs WHERE job_id=:reorder_job_id;
- job_id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | config | next_start | hypertable_schema | hypertable_name 
---------+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+--------+------------+-------------------+-----------------
+ job_id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | config | next_start | hypertable_schema | hypertable_name | check_schema | check_name 
+--------+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+--------+------------+-------------------+-----------------+--------------+------------
 (0 rows)
 
 SELECT job_id, next_start, last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes
@@ -336,8 +336,8 @@ SELECT * FROM sorted_bgw_log;
 (8 rows)
 
 SELECT * FROM timescaledb_information.jobs WHERE job_id=:reorder_job_id;
- job_id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | config | next_start | hypertable_schema | hypertable_name 
---------+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+--------+------------+-------------------+-----------------
+ job_id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | config | next_start | hypertable_schema | hypertable_name | check_schema | check_name 
+--------+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+--------+------------+-------------------+-----------------+--------------+------------
 (0 rows)
 
 -- still only 3 chunks clustered
@@ -409,15 +409,15 @@ SELECT count(*) FROM _timescaledb_config.bgw_job WHERE proc_schema = '_timescale
 (1 row)
 
 SELECT alter_job(:drop_chunks_job_id, schedule_interval => INTERVAL '1 second');
-                                                  alter_job                                                   
---------------------------------------------------------------------------------------------------------------
- (1001,"@ 1 sec","@ 5 mins",-1,"@ 5 mins",t,"{""drop_after"": ""@ 4 mons"", ""hypertable_id"": 2}",-infinity)
+                                                                         alter_job                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------
+ (1001,"@ 1 sec","@ 5 mins",-1,"@ 5 mins",t,"{""drop_after"": ""@ 4 mons"", ""hypertable_id"": 2}",-infinity,_timescaledb_internal.policy_retention_check)
 (1 row)
 
 SELECT * FROM timescaledb_information.jobs WHERE job_id=:drop_chunks_job_id;
- job_id |    application_name     | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema      |    proc_name     |       owner       | scheduled |                     config                     | next_start | hypertable_schema |    hypertable_name     
---------+-------------------------+-------------------+-------------+-------------+--------------+-----------------------+------------------+-------------------+-----------+------------------------------------------------+------------+-------------------+------------------------
-   1001 | Retention Policy [1001] | @ 1 sec           | @ 5 mins    |          -1 | @ 5 mins     | _timescaledb_internal | policy_retention | default_perm_user | t         | {"drop_after": "@ 4 mons", "hypertable_id": 2} |            | public            | test_drop_chunks_table
+ job_id |    application_name     | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema      |    proc_name     |       owner       | scheduled |                     config                     | next_start | hypertable_schema |    hypertable_name     |     check_schema      |       check_name       
+--------+-------------------------+-------------------+-------------+-------------+--------------+-----------------------+------------------+-------------------+-----------+------------------------------------------------+------------+-------------------+------------------------+-----------------------+------------------------
+   1001 | Retention Policy [1001] | @ 1 sec           | @ 5 mins    |          -1 | @ 5 mins     | _timescaledb_internal | policy_retention | default_perm_user | t         | {"drop_after": "@ 4 mons", "hypertable_id": 2} |            | public            | test_drop_chunks_table | _timescaledb_internal | policy_retention_check
 (1 row)
 
 -- no stats
@@ -454,9 +454,9 @@ SELECT * FROM sorted_bgw_log;
 (2 rows)
 
 SELECT * FROM timescaledb_information.jobs WHERE job_id=:drop_chunks_job_id;
- job_id |    application_name     | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema      |    proc_name     |       owner       | scheduled |                     config                     |          next_start          | hypertable_schema |    hypertable_name     
---------+-------------------------+-------------------+-------------+-------------+--------------+-----------------------+------------------+-------------------+-----------+------------------------------------------------+------------------------------+-------------------+------------------------
-   1001 | Retention Policy [1001] | @ 1 sec           | @ 5 mins    |          -1 | @ 5 mins     | _timescaledb_internal | policy_retention | default_perm_user | t         | {"drop_after": "@ 4 mons", "hypertable_id": 2} | Fri Dec 31 16:00:01 1999 PST | public            | test_drop_chunks_table
+ job_id |    application_name     | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema      |    proc_name     |       owner       | scheduled |                     config                     |          next_start          | hypertable_schema |    hypertable_name     |     check_schema      |       check_name       
+--------+-------------------------+-------------------+-------------+-------------+--------------+-----------------------+------------------+-------------------+-----------+------------------------------------------------+------------------------------+-------------------+------------------------+-----------------------+------------------------
+   1001 | Retention Policy [1001] | @ 1 sec           | @ 5 mins    |          -1 | @ 5 mins     | _timescaledb_internal | policy_retention | default_perm_user | t         | {"drop_after": "@ 4 mons", "hypertable_id": 2} | Fri Dec 31 16:00:01 1999 PST | public            | test_drop_chunks_table | _timescaledb_internal | policy_retention_check
 (1 row)
 
 -- job ran once, successfully
@@ -493,9 +493,9 @@ SELECT * FROM sorted_bgw_log;
 (3 rows)
 
 SELECT * FROM timescaledb_information.jobs WHERE job_id=:drop_chunks_job_id;
- job_id |    application_name     | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema      |    proc_name     |       owner       | scheduled |                     config                     |          next_start          | hypertable_schema |    hypertable_name     
---------+-------------------------+-------------------+-------------+-------------+--------------+-----------------------+------------------+-------------------+-----------+------------------------------------------------+------------------------------+-------------------+------------------------
-   1001 | Retention Policy [1001] | @ 1 sec           | @ 5 mins    |          -1 | @ 5 mins     | _timescaledb_internal | policy_retention | default_perm_user | t         | {"drop_after": "@ 4 mons", "hypertable_id": 2} | Fri Dec 31 16:00:01 1999 PST | public            | test_drop_chunks_table
+ job_id |    application_name     | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema      |    proc_name     |       owner       | scheduled |                     config                     |          next_start          | hypertable_schema |    hypertable_name     |     check_schema      |       check_name       
+--------+-------------------------+-------------------+-------------+-------------+--------------+-----------------------+------------------+-------------------+-----------+------------------------------------------------+------------------------------+-------------------+------------------------+-----------------------+------------------------
+   1001 | Retention Policy [1001] | @ 1 sec           | @ 5 mins    |          -1 | @ 5 mins     | _timescaledb_internal | policy_retention | default_perm_user | t         | {"drop_after": "@ 4 mons", "hypertable_id": 2} | Fri Dec 31 16:00:01 1999 PST | public            | test_drop_chunks_table | _timescaledb_internal | policy_retention_check
 (1 row)
 
 -- still only 1 run
@@ -545,9 +545,9 @@ SELECT * FROM sorted_bgw_log;
 (6 rows)
 
 SELECT * FROM timescaledb_information.jobs WHERE job_id=:drop_chunks_job_id;
- job_id |    application_name     | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema      |    proc_name     |       owner       | scheduled |                     config                     |          next_start          | hypertable_schema |    hypertable_name     
---------+-------------------------+-------------------+-------------+-------------+--------------+-----------------------+------------------+-------------------+-----------+------------------------------------------------+------------------------------+-------------------+------------------------
-   1001 | Retention Policy [1001] | @ 1 sec           | @ 5 mins    |          -1 | @ 5 mins     | _timescaledb_internal | policy_retention | default_perm_user | t         | {"drop_after": "@ 4 mons", "hypertable_id": 2} | Fri Dec 31 16:00:02 1999 PST | public            | test_drop_chunks_table
+ job_id |    application_name     | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema      |    proc_name     |       owner       | scheduled |                     config                     |          next_start          | hypertable_schema |    hypertable_name     |     check_schema      |       check_name       
+--------+-------------------------+-------------------+-------------+-------------+--------------+-----------------------+------------------+-------------------+-----------+------------------------------------------------+------------------------------+-------------------+------------------------+-----------------------+------------------------
+   1001 | Retention Policy [1001] | @ 1 sec           | @ 5 mins    |          -1 | @ 5 mins     | _timescaledb_internal | policy_retention | default_perm_user | t         | {"drop_after": "@ 4 mons", "hypertable_id": 2} | Fri Dec 31 16:00:02 1999 PST | public            | test_drop_chunks_table | _timescaledb_internal | policy_retention_check
 (1 row)
 
 -- 2 runs
@@ -622,16 +622,16 @@ SELECT add_retention_policy('test_drop_chunks_table_tsntz', INTERVAL '4 months')
 -- Test that retention policy is being logged
 SELECT alter_job(id,config:=jsonb_set(config,'{verbose_log}', 'true'))
  FROM _timescaledb_config.bgw_job WHERE id = :drop_chunks_date_job_id;
-                                                              alter_job                                                              
--------------------------------------------------------------------------------------------------------------------------------------
- (1002,"@ 1 day","@ 5 mins",-1,"@ 5 mins",t,"{""drop_after"": ""@ 4 mons"", ""verbose_log"": true, ""hypertable_id"": 3}",-infinity)
+                                                                                    alter_job                                                                                     
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (1002,"@ 1 day","@ 5 mins",-1,"@ 5 mins",t,"{""drop_after"": ""@ 4 mons"", ""verbose_log"": true, ""hypertable_id"": 3}",-infinity,_timescaledb_internal.policy_retention_check)
 (1 row)
 
 SELECT alter_job(id,config:=jsonb_set(config,'{verbose_log}', 'true'))
  FROM _timescaledb_config.bgw_job WHERE id = :drop_chunks_tsntz_job_id;
-                                                              alter_job                                                              
--------------------------------------------------------------------------------------------------------------------------------------
- (1003,"@ 1 day","@ 5 mins",-1,"@ 5 mins",t,"{""drop_after"": ""@ 4 mons"", ""verbose_log"": true, ""hypertable_id"": 4}",-infinity)
+                                                                                    alter_job                                                                                     
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (1003,"@ 1 day","@ 5 mins",-1,"@ 5 mins",t,"{""drop_after"": ""@ 4 mons"", ""verbose_log"": true, ""hypertable_id"": 4}",-infinity,_timescaledb_internal.policy_retention_check)
 (1 row)
 
 CALL run_job(:drop_chunks_date_job_id);
diff --git a/tsl/test/expected/cagg_bgw.out b/tsl/test/expected/cagg_bgw.out
index f7aaf2955..d3cc12ba0 100644
--- a/tsl/test/expected/cagg_bgw.out
+++ b/tsl/test/expected/cagg_bgw.out
@@ -269,9 +269,9 @@ SELECT ts_bgw_params_reset_time((extract(epoch from interval '12 hour')::bigint
 
 --alter the refresh interval and check if next_start is altered
 SELECT alter_job(:job_id, schedule_interval => '1m', retry_period => '1m');
-                                                                  alter_job                                                                   
-----------------------------------------------------------------------------------------------------------------------------------------------
- (1000,"@ 1 min","@ 0",-1,"@ 1 min",t,"{""end_offset"": 4, ""start_offset"": null, ""mat_hypertable_id"": 2}","Sat Jan 01 04:01:00 2000 PST")
+                                                                                                  alter_job                                                                                                   
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (1000,"@ 1 min","@ 0",-1,"@ 1 min",t,"{""end_offset"": 4, ""start_offset"": null, ""mat_hypertable_id"": 2}","Sat Jan 01 04:01:00 2000 PST",_timescaledb_internal.policy_refresh_continuous_aggregate_check)
 (1 row)
 
 SELECT job_id, next_start - last_finish as until_next, total_runs
@@ -309,9 +309,9 @@ SELECT (next_start - '30s'::interval) AS "NEW_NEXT_START"
 FROM _timescaledb_internal.bgw_job_stat
 WHERE job_id=:job_id \gset
 SELECT alter_job(:job_id, next_start => :'NEW_NEXT_START');
-                                                                  alter_job                                                                   
-----------------------------------------------------------------------------------------------------------------------------------------------
- (1000,"@ 1 min","@ 0",-1,"@ 1 min",t,"{""end_offset"": 4, ""start_offset"": null, ""mat_hypertable_id"": 2}","Sat Jan 01 04:02:30 2000 PST")
+                                                                                                  alter_job                                                                                                   
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (1000,"@ 1 min","@ 0",-1,"@ 1 min",t,"{""end_offset"": 4, ""start_offset"": null, ""mat_hypertable_id"": 2}","Sat Jan 01 04:02:30 2000 PST",_timescaledb_internal.policy_refresh_continuous_aggregate_check)
 (1 row)
 
 SELECT ts_bgw_params_reset_time((extract(epoch from interval '12 hour')::bigint * 1000000)+(extract(epoch from interval '2 minute 30 seconds')::bigint * 1000000), true);
diff --git a/tsl/test/expected/cagg_bgw_dist_ht.out b/tsl/test/expected/cagg_bgw_dist_ht.out
index 1cf4042d3..6baa5a317 100644
--- a/tsl/test/expected/cagg_bgw_dist_ht.out
+++ b/tsl/test/expected/cagg_bgw_dist_ht.out
@@ -301,9 +301,9 @@ SELECT ts_bgw_params_reset_time((extract(epoch from interval '12 hour')::bigint
 
 --alter the refresh interval and check if next_start is altered
 SELECT alter_job(:job_id, schedule_interval => '1m', retry_period => '1m');
-                                                                  alter_job                                                                   
-----------------------------------------------------------------------------------------------------------------------------------------------
- (1000,"@ 1 min","@ 0",-1,"@ 1 min",t,"{""end_offset"": 4, ""start_offset"": null, ""mat_hypertable_id"": 2}","Sat Jan 01 04:01:00 2000 PST")
+                                                                                                  alter_job                                                                                                   
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (1000,"@ 1 min","@ 0",-1,"@ 1 min",t,"{""end_offset"": 4, ""start_offset"": null, ""mat_hypertable_id"": 2}","Sat Jan 01 04:01:00 2000 PST",_timescaledb_internal.policy_refresh_continuous_aggregate_check)
 (1 row)
 
 SELECT job_id, next_start - last_finish as until_next, total_runs
@@ -341,9 +341,9 @@ SELECT (next_start - '30s'::interval) AS "NEW_NEXT_START"
 FROM _timescaledb_internal.bgw_job_stat
 WHERE job_id=:job_id \gset
 SELECT alter_job(:job_id, next_start => :'NEW_NEXT_START');
-                                                                  alter_job                                                                   
-----------------------------------------------------------------------------------------------------------------------------------------------
- (1000,"@ 1 min","@ 0",-1,"@ 1 min",t,"{""end_offset"": 4, ""start_offset"": null, ""mat_hypertable_id"": 2}","Sat Jan 01 04:02:30 2000 PST")
+                                                                                                  alter_job                                                                                                   
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (1000,"@ 1 min","@ 0",-1,"@ 1 min",t,"{""end_offset"": 4, ""start_offset"": null, ""mat_hypertable_id"": 2}","Sat Jan 01 04:02:30 2000 PST",_timescaledb_internal.policy_refresh_continuous_aggregate_check)
 (1 row)
 
 SELECT ts_bgw_params_reset_time((extract(epoch from interval '12 hour')::bigint * 1000000)+(extract(epoch from interval '2 minute 30 seconds')::bigint * 1000000), true);
diff --git a/tsl/test/expected/cagg_bgw_drop_chunks.out b/tsl/test/expected/cagg_bgw_drop_chunks.out
index f17b26a9b..d0d349ed9 100644
--- a/tsl/test/expected/cagg_bgw_drop_chunks.out
+++ b/tsl/test/expected/cagg_bgw_drop_chunks.out
@@ -97,9 +97,9 @@ WHERE hypertable_name = '_materialized_hypertable_2' ORDER BY range_start_intege
 
 SELECT add_retention_policy( 'drop_chunks_view1', drop_after => 10) as drop_chunks_job_id1 \gset
 SELECT alter_job(:drop_chunks_job_id1, schedule_interval => INTERVAL '1 second');
-                                             alter_job                                              
-----------------------------------------------------------------------------------------------------
- (1000,"@ 1 sec","@ 5 mins",-1,"@ 5 mins",t,"{""drop_after"": 10, ""hypertable_id"": 2}",-infinity)
+                                                                    alter_job                                                                    
+-------------------------------------------------------------------------------------------------------------------------------------------------
+ (1000,"@ 1 sec","@ 5 mins",-1,"@ 5 mins",t,"{""drop_after"": 10, ""hypertable_id"": 2}",-infinity,_timescaledb_internal.policy_retention_check)
 (1 row)
 
 SELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(2000000);
diff --git a/tsl/test/expected/cagg_policy.out b/tsl/test/expected/cagg_policy.out
index 0b7e020e2..569ccbe78 100644
--- a/tsl/test/expected/cagg_policy.out
+++ b/tsl/test/expected/cagg_policy.out
@@ -579,9 +579,9 @@ SELECT _timescaledb_internal.alter_job_set_hypertable_id( :job_id, 'max_mat_view
 (1 row)
 
 SELECT * FROM timescaledb_information.jobs WHERE job_id != 1 ORDER BY 1;
- job_id |      application_name      | schedule_interval | max_runtime | max_retries | retry_period | proc_schema |  proc_name  |       owner       | scheduled |        config        | next_start |   hypertable_schema   |      hypertable_name       
---------+----------------------------+-------------------+-------------+-------------+--------------+-------------+-------------+-------------------+-----------+----------------------+------------+-----------------------+----------------------------
-   1026 | User-Defined Action [1026] | @ 1 hour          | @ 0         |          -1 | @ 5 mins     | public      | custom_func | default_perm_user | t         | {"type": "function"} |            | _timescaledb_internal | _materialized_hypertable_5
+ job_id |      application_name      | schedule_interval | max_runtime | max_retries | retry_period | proc_schema |  proc_name  |       owner       | scheduled |        config        | next_start |   hypertable_schema   |      hypertable_name       | check_schema | check_name 
+--------+----------------------------+-------------------+-------------+-------------+--------------+-------------+-------------+-------------------+-----------+----------------------+------------+-----------------------+----------------------------+--------------+------------
+   1026 | User-Defined Action [1026] | @ 1 hour          | @ 0         |          -1 | @ 5 mins     | public      | custom_func | default_perm_user | t         | {"type": "function"} |            | _timescaledb_internal | _materialized_hypertable_5 |              | 
 (1 row)
 
 SELECT timescaledb_experimental.remove_all_policies('max_mat_view_date', true); -- ignore custom job
diff --git a/tsl/test/expected/cagg_usage.out b/tsl/test/expected/cagg_usage.out
index 7b4792cdc..229a934c5 100644
--- a/tsl/test/expected/cagg_usage.out
+++ b/tsl/test/expected/cagg_usage.out
@@ -101,9 +101,9 @@ SELECT schedule_interval FROM _timescaledb_config.bgw_job WHERE id = 1000;
 
 -- You can change this setting with ALTER VIEW (equivalently, specify in WITH clause of CREATE VIEW)
 SELECT alter_job(1000, schedule_interval := '1h');
-                                                               alter_job                                                                
-----------------------------------------------------------------------------------------------------------------------------------------
- (1000,"@ 1 hour","@ 0",-1,"@ 2 hours",t,"{""end_offset"": ""@ 2 hours"", ""start_offset"": null, ""mat_hypertable_id"": 2}",-infinity)
+                                                                                               alter_job                                                                                                
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (1000,"@ 1 hour","@ 0",-1,"@ 2 hours",t,"{""end_offset"": ""@ 2 hours"", ""start_offset"": null, ""mat_hypertable_id"": 2}",-infinity,_timescaledb_internal.policy_refresh_continuous_aggregate_check)
 (1 row)
 
 SELECT schedule_interval FROM _timescaledb_config.bgw_job WHERE id = 1000;
diff --git a/tsl/test/expected/compress_bgw_reorder_drop_chunks.out b/tsl/test/expected/compress_bgw_reorder_drop_chunks.out
index 3ee50ff4d..30a854b49 100644
--- a/tsl/test/expected/compress_bgw_reorder_drop_chunks.out
+++ b/tsl/test/expected/compress_bgw_reorder_drop_chunks.out
@@ -81,9 +81,9 @@ SELECT count(*) FROM _timescaledb_config.bgw_job WHERE proc_schema = '_timescale
 (1 row)
 
 SELECT alter_job(:retention_job_id, schedule_interval => INTERVAL '1 second');
-                                                  alter_job                                                   
---------------------------------------------------------------------------------------------------------------
- (1000,"@ 1 sec","@ 5 mins",-1,"@ 5 mins",t,"{""drop_after"": ""@ 4 mons"", ""hypertable_id"": 1}",-infinity)
+                                                                         alter_job                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------
+ (1000,"@ 1 sec","@ 5 mins",-1,"@ 5 mins",t,"{""drop_after"": ""@ 4 mons"", ""hypertable_id"": 1}",-infinity,_timescaledb_internal.policy_retention_check)
 (1 row)
 
 SELECT * FROM _timescaledb_config.bgw_job where id=:retention_job_id;
diff --git a/tsl/test/expected/compression_bgw.out b/tsl/test/expected/compression_bgw.out
index bce201067..4e44480d1 100644
--- a/tsl/test/expected/compression_bgw.out
+++ b/tsl/test/expected/compression_bgw.out
@@ -38,17 +38,17 @@ select * from _timescaledb_config.bgw_job where id = :compressjob_id;
 (1 row)
 
 select * from alter_job(:compressjob_id, schedule_interval=>'1s');
- job_id | schedule_interval | max_runtime | max_retries | retry_period | scheduled |                       config                        | next_start 
---------+-------------------+-------------+-------------+--------------+-----------+-----------------------------------------------------+------------
-   1000 | @ 1 sec           | @ 0         |          -1 | @ 1 hour     | t         | {"hypertable_id": 1, "compress_after": "@ 60 days"} | -infinity
+ job_id | schedule_interval | max_runtime | max_retries | retry_period | scheduled |                       config                        | next_start |                  check_config                  
+--------+-------------------+-------------+-------------+--------------+-----------+-----------------------------------------------------+------------+------------------------------------------------
+   1000 | @ 1 sec           | @ 0         |          -1 | @ 1 hour     | t         | {"hypertable_id": 1, "compress_after": "@ 60 days"} | -infinity  | _timescaledb_internal.policy_compression_check
 (1 row)
 
 --enable maxchunks to 1 so that only 1 chunk is compressed by the job
 SELECT alter_job(id,config:=jsonb_set(config,'{maxchunks_to_compress}', '1'))
  FROM _timescaledb_config.bgw_job WHERE id = :compressjob_id;
-                                                                 alter_job                                                                  
---------------------------------------------------------------------------------------------------------------------------------------------
- (1000,"@ 1 sec","@ 0",-1,"@ 1 hour",t,"{""hypertable_id"": 1, ""compress_after"": ""@ 60 days"", ""maxchunks_to_compress"": 1}",-infinity)
+                                                                                         alter_job                                                                                         
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (1000,"@ 1 sec","@ 0",-1,"@ 1 hour",t,"{""hypertable_id"": 1, ""compress_after"": ""@ 60 days"", ""maxchunks_to_compress"": 1}",-infinity,_timescaledb_internal.policy_compression_check)
 (1 row)
 
 select * from _timescaledb_config.bgw_job where id >= 1000 ORDER BY id;
@@ -328,16 +328,16 @@ SELECT add_compression_policy AS job_id
 -- job compresses only 1 chunk at a time --
 SELECT alter_job(id,config:=jsonb_set(config,'{maxchunks_to_compress}', '1'))
  FROM _timescaledb_config.bgw_job WHERE id = :job_id;
-                                                                  alter_job                                                                   
-----------------------------------------------------------------------------------------------------------------------------------------------
- (1004,"@ 12 hours","@ 0",-1,"@ 1 hour",t,"{""hypertable_id"": 11, ""compress_after"": ""@ 1 day"", ""maxchunks_to_compress"": 1}",-infinity)
+                                                                                          alter_job                                                                                          
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (1004,"@ 12 hours","@ 0",-1,"@ 1 hour",t,"{""hypertable_id"": 11, ""compress_after"": ""@ 1 day"", ""maxchunks_to_compress"": 1}",-infinity,_timescaledb_internal.policy_compression_check)
 (1 row)
 
 SELECT alter_job(id,config:=jsonb_set(config,'{verbose_log}', 'true'))
  FROM _timescaledb_config.bgw_job WHERE id = :job_id;
-                                                                              alter_job                                                                              
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
- (1004,"@ 12 hours","@ 0",-1,"@ 1 hour",t,"{""verbose_log"": true, ""hypertable_id"": 11, ""compress_after"": ""@ 1 day"", ""maxchunks_to_compress"": 1}",-infinity)
+                                                                                                     alter_job                                                                                                      
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (1004,"@ 12 hours","@ 0",-1,"@ 1 hour",t,"{""verbose_log"": true, ""hypertable_id"": 11, ""compress_after"": ""@ 1 day"", ""maxchunks_to_compress"": 1}",-infinity,_timescaledb_internal.policy_compression_check)
 (1 row)
 
 set client_min_messages TO LOG;
@@ -553,9 +553,9 @@ SELECT chunk_status FROM compressed_chunk_info_view WHERE hypertable_name = 'met
 
 -- disable recompress in compress job
 SELECT alter_job(id,config:=jsonb_set(config,'{recompress}','false')) FROM _timescaledb_config.bgw_job WHERE id = :JOB_COMPRESS;
-                                                              alter_job                                                               
---------------------------------------------------------------------------------------------------------------------------------------
- (1006,"@ 7 days","@ 0",-1,"@ 5 mins",t,"{""recompress"": false, ""hypertable_id"": 16, ""compress_after"": ""@ 7 days""}",-infinity)
+                                                               alter_job                                                               
+---------------------------------------------------------------------------------------------------------------------------------------
+ (1006,"@ 7 days","@ 0",-1,"@ 5 mins",t,"{""recompress"": false, ""hypertable_id"": 16, ""compress_after"": ""@ 7 days""}",-infinity,)
 (1 row)
 
 -- nothing to do
@@ -587,9 +587,9 @@ SELECT chunk_status FROM compressed_chunk_info_view WHERE hypertable_name = 'met
 
 -- reenable recompress in compress job
 SELECT alter_job(id,config:=jsonb_set(config,'{recompress}','true')) FROM _timescaledb_config.bgw_job WHERE id = :JOB_COMPRESS;
-                                                              alter_job                                                              
--------------------------------------------------------------------------------------------------------------------------------------
- (1006,"@ 7 days","@ 0",-1,"@ 5 mins",t,"{""recompress"": true, ""hypertable_id"": 16, ""compress_after"": ""@ 7 days""}",-infinity)
+                                                              alter_job                                                               
+--------------------------------------------------------------------------------------------------------------------------------------
+ (1006,"@ 7 days","@ 0",-1,"@ 5 mins",t,"{""recompress"": true, ""hypertable_id"": 16, ""compress_after"": ""@ 7 days""}",-infinity,)
 (1 row)
 
 -- should recompress now
diff --git a/tsl/test/expected/continuous_aggs.out b/tsl/test/expected/continuous_aggs.out
index d9d642574..84ecf3619 100644
--- a/tsl/test/expected/continuous_aggs.out
+++ b/tsl/test/expected/continuous_aggs.out
@@ -855,9 +855,9 @@ SELECT add_continuous_aggregate_policy('mat_with_test', NULL, '5 h'::interval, '
 (1 row)
 
 SELECT alter_job(id, schedule_interval => '1h') FROM _timescaledb_config.bgw_job;
-                                                                alter_job                                                                 
-------------------------------------------------------------------------------------------------------------------------------------------
- (1001,"@ 1 hour","@ 0",-1,"@ 12 hours",t,"{""end_offset"": ""@ 5 hours"", ""start_offset"": null, ""mat_hypertable_id"": 20}",-infinity)
+                                                                                                alter_job                                                                                                 
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (1001,"@ 1 hour","@ 0",-1,"@ 12 hours",t,"{""end_offset"": ""@ 5 hours"", ""start_offset"": null, ""mat_hypertable_id"": 20}",-infinity,_timescaledb_internal.policy_refresh_continuous_aggregate_check)
 (1 row)
 
 SELECT schedule_interval FROM _timescaledb_config.bgw_job;
@@ -867,9 +867,9 @@ SELECT schedule_interval FROM _timescaledb_config.bgw_job;
 (1 row)
 
 SELECT alter_job(id, schedule_interval => '2h') FROM _timescaledb_config.bgw_job;
-                                                                 alter_job                                                                 
--------------------------------------------------------------------------------------------------------------------------------------------
- (1001,"@ 2 hours","@ 0",-1,"@ 12 hours",t,"{""end_offset"": ""@ 5 hours"", ""start_offset"": null, ""mat_hypertable_id"": 20}",-infinity)
+                                                                                                 alter_job                                                                                                 
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (1001,"@ 2 hours","@ 0",-1,"@ 12 hours",t,"{""end_offset"": ""@ 5 hours"", ""start_offset"": null, ""mat_hypertable_id"": 20}",-infinity,_timescaledb_internal.policy_refresh_continuous_aggregate_check)
 (1 row)
 
 SELECT schedule_interval FROM _timescaledb_config.bgw_job;
@@ -948,9 +948,9 @@ SELECT add_continuous_aggregate_policy('mat_with_test', NULL, 500::integer, '12
 (1 row)
 
 SELECT alter_job(id, schedule_interval => '2h') FROM _timescaledb_config.bgw_job;
-                                                            alter_job                                                            
----------------------------------------------------------------------------------------------------------------------------------
- (1002,"@ 2 hours","@ 0",-1,"@ 12 hours",t,"{""end_offset"": 500, ""start_offset"": null, ""mat_hypertable_id"": 23}",-infinity)
+                                                                                            alter_job                                                                                            
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (1002,"@ 2 hours","@ 0",-1,"@ 12 hours",t,"{""end_offset"": 500, ""start_offset"": null, ""mat_hypertable_id"": 23}",-infinity,_timescaledb_internal.policy_refresh_continuous_aggregate_check)
 (1 row)
 
 SELECT schedule_interval FROM _timescaledb_config.bgw_job;
diff --git a/tsl/test/expected/continuous_aggs_deprecated.out b/tsl/test/expected/continuous_aggs_deprecated.out
index eeb60fdb1..c7491f786 100644
--- a/tsl/test/expected/continuous_aggs_deprecated.out
+++ b/tsl/test/expected/continuous_aggs_deprecated.out
@@ -869,9 +869,9 @@ SELECT add_continuous_aggregate_policy('mat_with_test', NULL, '5 h'::interval, '
 (1 row)
 
 SELECT alter_job(id, schedule_interval => '1h') FROM _timescaledb_config.bgw_job;
-                                                                alter_job                                                                 
-------------------------------------------------------------------------------------------------------------------------------------------
- (1001,"@ 1 hour","@ 0",-1,"@ 12 hours",t,"{""end_offset"": ""@ 5 hours"", ""start_offset"": null, ""mat_hypertable_id"": 20}",-infinity)
+                                                                                                alter_job                                                                                                 
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (1001,"@ 1 hour","@ 0",-1,"@ 12 hours",t,"{""end_offset"": ""@ 5 hours"", ""start_offset"": null, ""mat_hypertable_id"": 20}",-infinity,_timescaledb_internal.policy_refresh_continuous_aggregate_check)
 (1 row)
 
 SELECT schedule_interval FROM _timescaledb_config.bgw_job;
@@ -881,9 +881,9 @@ SELECT schedule_interval FROM _timescaledb_config.bgw_job;
 (1 row)
 
 SELECT alter_job(id, schedule_interval => '2h') FROM _timescaledb_config.bgw_job;
-                                                                 alter_job                                                                 
--------------------------------------------------------------------------------------------------------------------------------------------
- (1001,"@ 2 hours","@ 0",-1,"@ 12 hours",t,"{""end_offset"": ""@ 5 hours"", ""start_offset"": null, ""mat_hypertable_id"": 20}",-infinity)
+                                                                                                 alter_job                                                                                                 
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (1001,"@ 2 hours","@ 0",-1,"@ 12 hours",t,"{""end_offset"": ""@ 5 hours"", ""start_offset"": null, ""mat_hypertable_id"": 20}",-infinity,_timescaledb_internal.policy_refresh_continuous_aggregate_check)
 (1 row)
 
 SELECT schedule_interval FROM _timescaledb_config.bgw_job;
@@ -967,9 +967,9 @@ SELECT add_continuous_aggregate_policy('mat_with_test', NULL, 500::integer, '12
 (1 row)
 
 SELECT alter_job(id, schedule_interval => '2h') FROM _timescaledb_config.bgw_job;
-                                                            alter_job                                                            
----------------------------------------------------------------------------------------------------------------------------------
- (1002,"@ 2 hours","@ 0",-1,"@ 12 hours",t,"{""end_offset"": 500, ""start_offset"": null, ""mat_hypertable_id"": 23}",-infinity)
+                                                                                            alter_job                                                                                            
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (1002,"@ 2 hours","@ 0",-1,"@ 12 hours",t,"{""end_offset"": 500, ""start_offset"": null, ""mat_hypertable_id"": 23}",-infinity,_timescaledb_internal.policy_refresh_continuous_aggregate_check)
 (1 row)
 
 SELECT schedule_interval FROM _timescaledb_config.bgw_job;
diff --git a/tsl/test/expected/dist_compression.out b/tsl/test/expected/dist_compression.out
index 61452839a..9ca5fde96 100644
--- a/tsl/test/expected/dist_compression.out
+++ b/tsl/test/expected/dist_compression.out
@@ -660,9 +660,9 @@ select * from _timescaledb_config.bgw_job where id = :compressjob_id;
 (1 row)
 
 select * from alter_job(:compressjob_id, schedule_interval=>'1s');
- job_id | schedule_interval | max_runtime | max_retries | retry_period | scheduled |                       config                        | next_start 
---------+-------------------+-------------+-------------+--------------+-----------+-----------------------------------------------------+------------
-   1000 | @ 1 sec           | @ 0         |          -1 | @ 1 hour     | t         | {"hypertable_id": 2, "compress_after": "@ 60 days"} | -infinity
+ job_id | schedule_interval | max_runtime | max_retries | retry_period | scheduled |                       config                        | next_start |                  check_config                  
+--------+-------------------+-------------+-------------+--------------+-----------+-----------------------------------------------------+------------+------------------------------------------------
+   1000 | @ 1 sec           | @ 0         |          -1 | @ 1 hour     | t         | {"hypertable_id": 2, "compress_after": "@ 60 days"} | -infinity  | _timescaledb_internal.policy_compression_check
 (1 row)
 
 select * from _timescaledb_config.bgw_job where id >= 1000 ORDER BY id;
@@ -674,9 +674,9 @@ select * from _timescaledb_config.bgw_job where id >= 1000 ORDER BY id;
 -- we want only 1 chunk to be compressed --
 SELECT alter_job(id,config:=jsonb_set(config,'{maxchunks_to_compress}', '1'))
 FROM _timescaledb_config.bgw_job WHERE id = :compressjob_id;
-                                                                 alter_job                                                                  
---------------------------------------------------------------------------------------------------------------------------------------------
- (1000,"@ 1 sec","@ 0",-1,"@ 1 hour",t,"{""hypertable_id"": 2, ""compress_after"": ""@ 60 days"", ""maxchunks_to_compress"": 1}",-infinity)
+                                                                                         alter_job                                                                                         
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ (1000,"@ 1 sec","@ 0",-1,"@ 1 hour",t,"{""hypertable_id"": 2, ""compress_after"": ""@ 60 days"", ""maxchunks_to_compress"": 1}",-infinity,_timescaledb_internal.policy_compression_check)
 (1 row)
 
 insert into conditions
diff --git a/tsl/test/expected/tsl_tables.out b/tsl/test/expected/tsl_tables.out
index 95a18b285..a11b04570 100644
--- a/tsl/test/expected/tsl_tables.out
+++ b/tsl/test/expected/tsl_tables.out
@@ -626,72 +626,72 @@ select add_reorder_policy('test_table', 'test_table_time_idx') as job_id \gset
 
 -- No change
 select * from alter_job(:job_id);
- job_id | schedule_interval | max_runtime | max_retries | retry_period | scheduled |                          config                           | next_start 
---------+-------------------+-------------+-------------+--------------+-----------+-----------------------------------------------------------+------------
-   1014 | @ 84 hours        | @ 0         |          -1 | @ 5 mins     | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity
+ job_id | schedule_interval | max_runtime | max_retries | retry_period | scheduled |                          config                           | next_start |                check_config                
+--------+-------------------+-------------+-------------+--------------+-----------+-----------------------------------------------------------+------------+--------------------------------------------
+   1014 | @ 84 hours        | @ 0         |          -1 | @ 5 mins     | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity  | _timescaledb_internal.policy_reorder_check
 (1 row)
 
 -- Changes expected
 select * from alter_job(:job_id, INTERVAL '3 years', INTERVAL '5 min', 5, INTERVAL '123 sec');
- job_id | schedule_interval | max_runtime | max_retries |  retry_period   | scheduled |                          config                           | next_start 
---------+-------------------+-------------+-------------+-----------------+-----------+-----------------------------------------------------------+------------
-   1014 | @ 3 years         | @ 5 mins    |           5 | @ 2 mins 3 secs | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity
+ job_id | schedule_interval | max_runtime | max_retries |  retry_period   | scheduled |                          config                           | next_start |                check_config                
+--------+-------------------+-------------+-------------+-----------------+-----------+-----------------------------------------------------------+------------+--------------------------------------------
+   1014 | @ 3 years         | @ 5 mins    |           5 | @ 2 mins 3 secs | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity  | _timescaledb_internal.policy_reorder_check
 (1 row)
 
 select * from alter_job(:job_id, INTERVAL '123 years');
- job_id | schedule_interval | max_runtime | max_retries |  retry_period   | scheduled |                          config                           | next_start 
---------+-------------------+-------------+-------------+-----------------+-----------+-----------------------------------------------------------+------------
-   1014 | @ 123 years       | @ 5 mins    |           5 | @ 2 mins 3 secs | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity
+ job_id | schedule_interval | max_runtime | max_retries |  retry_period   | scheduled |                          config                           | next_start |                check_config                
+--------+-------------------+-------------+-------------+-----------------+-----------+-----------------------------------------------------------+------------+--------------------------------------------
+   1014 | @ 123 years       | @ 5 mins    |           5 | @ 2 mins 3 secs | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity  | _timescaledb_internal.policy_reorder_check
 (1 row)
 
 select * from alter_job(:job_id, retry_period => INTERVAL '33 hours');
- job_id | schedule_interval | max_runtime | max_retries | retry_period | scheduled |                          config                           | next_start 
---------+-------------------+-------------+-------------+--------------+-----------+-----------------------------------------------------------+------------
-   1014 | @ 123 years       | @ 5 mins    |           5 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity
+ job_id | schedule_interval | max_runtime | max_retries | retry_period | scheduled |                          config                           | next_start |                check_config                
+--------+-------------------+-------------+-------------+--------------+-----------+-----------------------------------------------------------+------------+--------------------------------------------
+   1014 | @ 123 years       | @ 5 mins    |           5 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity  | _timescaledb_internal.policy_reorder_check
 (1 row)
 
 select * from alter_job(:job_id, max_runtime => INTERVAL '456 sec');
- job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           | next_start 
---------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------
-   1014 | @ 123 years       | @ 7 mins 36 secs |           5 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity
+ job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           | next_start |                check_config                
+--------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------+--------------------------------------------
+   1014 | @ 123 years       | @ 7 mins 36 secs |           5 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity  | _timescaledb_internal.policy_reorder_check
 (1 row)
 
 select * from alter_job(:job_id, max_retries => 0);
- job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           | next_start 
---------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------
-   1014 | @ 123 years       | @ 7 mins 36 secs |           0 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity
+ job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           | next_start |                check_config                
+--------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------+--------------------------------------------
+   1014 | @ 123 years       | @ 7 mins 36 secs |           0 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity  | _timescaledb_internal.policy_reorder_check
 (1 row)
 
 select * from alter_job(:job_id, max_retries => -1);
- job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           | next_start 
---------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------
-   1014 | @ 123 years       | @ 7 mins 36 secs |          -1 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity
+ job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           | next_start |                check_config                
+--------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------+--------------------------------------------
+   1014 | @ 123 years       | @ 7 mins 36 secs |          -1 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity  | _timescaledb_internal.policy_reorder_check
 (1 row)
 
 select * from alter_job(:job_id, max_retries => 20);
- job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           | next_start 
---------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------
-   1014 | @ 123 years       | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity
+ job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           | next_start |                check_config                
+--------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------+--------------------------------------------
+   1014 | @ 123 years       | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity  | _timescaledb_internal.policy_reorder_check
 (1 row)
 
 -- No change
 select * from alter_job(:job_id, max_runtime => NULL);
- job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           | next_start 
---------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------
-   1014 | @ 123 years       | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity
+ job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           | next_start |                check_config                
+--------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------+--------------------------------------------
+   1014 | @ 123 years       | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity  | _timescaledb_internal.policy_reorder_check
 (1 row)
 
 select * from alter_job(:job_id, max_retries => NULL);
- job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           | next_start 
---------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------
-   1014 | @ 123 years       | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity
+ job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           | next_start |                check_config                
+--------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------+--------------------------------------------
+   1014 | @ 123 years       | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity  | _timescaledb_internal.policy_reorder_check
 (1 row)
 
 --change schedule_interval when bgw_job_stat does not exist
 select * from alter_job(:job_id, schedule_interval=>'1 min');
- job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           | next_start 
---------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------
-   1014 | @ 1 min           | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity
+ job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           | next_start |                check_config                
+--------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------+--------------------------------------------
+   1014 | @ 1 min           | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity  | _timescaledb_internal.policy_reorder_check
 (1 row)
 
 select count(*) = 0 from _timescaledb_internal.bgw_job_stat where job_id = :job_id;
@@ -702,23 +702,23 @@ select count(*) = 0 from _timescaledb_internal.bgw_job_stat where job_id = :job_
 
 --set next_start when bgw_job_stat does not exist
 select * from alter_job(:job_id, next_start=>'2001-01-01 01:01:01');
- job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           |          next_start          
---------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------------------------
-   1014 | @ 1 min           | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | Mon Jan 01 01:01:01 2001 PST
+ job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           |          next_start          |                check_config                
+--------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------------------------+--------------------------------------------
+   1014 | @ 1 min           | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | Mon Jan 01 01:01:01 2001 PST | _timescaledb_internal.policy_reorder_check
 (1 row)
 
 --change schedule_interval when no last_finish set
 select * from alter_job(:job_id, schedule_interval=>'10 min');
- job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           | next_start 
---------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------
-   1014 | @ 10 mins         | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity
+ job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           | next_start |                check_config                
+--------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------+--------------------------------------------
+   1014 | @ 10 mins         | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | -infinity  | _timescaledb_internal.policy_reorder_check
 (1 row)
 
 --next_start overrides any schedule_interval changes
 select * from alter_job(:job_id, schedule_interval=>'20 min', next_start=>'2002-01-01 01:01:01');
- job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           |          next_start          
---------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------------------------
-   1014 | @ 20 mins         | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | Tue Jan 01 01:01:01 2002 PST
+ job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           |          next_start          |                check_config                
+--------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------------------------+--------------------------------------------
+   1014 | @ 20 mins         | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | Tue Jan 01 01:01:01 2002 PST | _timescaledb_internal.policy_reorder_check
 (1 row)
 
 --set the last_finish manually
@@ -727,30 +727,30 @@ UPDATE _timescaledb_internal.bgw_job_stat SET last_finish = '2003-01-01:01:01:01
 \c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER
 --not changing the interval doesn't change the next_start
 select * from alter_job(:job_id, schedule_interval=>'20 min');
- job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           |          next_start          
---------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------------------------
-   1014 | @ 20 mins         | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | Tue Jan 01 01:01:01 2002 PST
+ job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           |          next_start          |                check_config                
+--------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------------------------+--------------------------------------------
+   1014 | @ 20 mins         | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | Tue Jan 01 01:01:01 2002 PST | _timescaledb_internal.policy_reorder_check
 (1 row)
 
 --changing the interval changes next_start
 select * from alter_job(:job_id, schedule_interval=>'30 min');
- job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           |          next_start          
---------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------------------------
-   1014 | @ 30 mins         | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | Wed Jan 01 01:31:01 2003 PST
+ job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           |          next_start          |                check_config                
+--------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------------------------+--------------------------------------------
+   1014 | @ 30 mins         | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | Wed Jan 01 01:31:01 2003 PST | _timescaledb_internal.policy_reorder_check
 (1 row)
 
 --explicit next start overrides.
 select * from alter_job(:job_id, schedule_interval=>'40 min', next_start=>'2004-01-01 01:01:01');
- job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           |          next_start          
---------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------------------------
-   1014 | @ 40 mins         | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | Thu Jan 01 01:01:01 2004 PST
+ job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           |          next_start          |                check_config                
+--------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------------------------+--------------------------------------------
+   1014 | @ 40 mins         | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | Thu Jan 01 01:01:01 2004 PST | _timescaledb_internal.policy_reorder_check
 (1 row)
 
 --test pausing
 select * from alter_job(:job_id, next_start=>'infinity');
- job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           | next_start 
---------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------
-   1014 | @ 40 mins         | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | infinity
+ job_id | schedule_interval |   max_runtime    | max_retries | retry_period | scheduled |                          config                           | next_start |                check_config                
+--------+-------------------+------------------+-------------+--------------+-----------+-----------------------------------------------------------+------------+--------------------------------------------
+   1014 | @ 40 mins         | @ 7 mins 36 secs |          20 | @ 33 hours   | t         | {"index_name": "test_table_time_idx", "hypertable_id": 7} | infinity   | _timescaledb_internal.policy_reorder_check
 (1 row)
 
 --test that you can use now() to unpause
@@ -805,9 +805,9 @@ ERROR:  configuration hypertable id 47 not found
 -- Check if_exists boolean works correctly
 select * from alter_job(1234, if_exists => TRUE);
 NOTICE:  job 1234 not found, skipping
- job_id | schedule_interval | max_runtime | max_retries | retry_period | scheduled | config | next_start 
---------+-------------------+-------------+-------------+--------------+-----------+--------+------------
-        |                   |             |             |              |           |        | 
+ job_id | schedule_interval | max_runtime | max_retries | retry_period | scheduled | config | next_start | check_config 
+--------+-------------------+-------------+-------------+--------------+-----------+--------+------------+--------------
+        |                   |             |             |              |           |        |            | 
 (1 row)
 
 \set ON_ERROR_STOP 0
diff --git a/tsl/test/shared/expected/extension.out b/tsl/test/shared/expected/extension.out
index b9d566712..ecd0832dc 100644
--- a/tsl/test/shared/expected/extension.out
+++ b/tsl/test/shared/expected/extension.out
@@ -98,15 +98,15 @@ ORDER BY pronamespace::regnamespace::text COLLATE "C", p.oid::regprocedure::text
  _timescaledb_internal.partialize_agg(anyelement)
  _timescaledb_internal.ping_data_node(name)
  _timescaledb_internal.policy_compression(integer,jsonb)
- _timescaledb_internal.policy_compression_check(integer,jsonb)
+ _timescaledb_internal.policy_compression_check(jsonb)
  _timescaledb_internal.policy_compression_execute(integer,integer,anyelement,integer,boolean,boolean)
  _timescaledb_internal.policy_recompression(integer,jsonb)
  _timescaledb_internal.policy_refresh_continuous_aggregate(integer,jsonb)
- _timescaledb_internal.policy_refresh_continuous_aggregate_check(integer,jsonb)
+ _timescaledb_internal.policy_refresh_continuous_aggregate_check(jsonb)
  _timescaledb_internal.policy_reorder(integer,jsonb)
- _timescaledb_internal.policy_reorder_check(integer,jsonb)
+ _timescaledb_internal.policy_reorder_check(jsonb)
  _timescaledb_internal.policy_retention(integer,jsonb)
- _timescaledb_internal.policy_retention_check(integer,jsonb)
+ _timescaledb_internal.policy_retention_check(jsonb)
  _timescaledb_internal.process_ddl_event()
  _timescaledb_internal.range_value_to_pretty(bigint,regtype)
  _timescaledb_internal.relation_size(regclass)
@@ -140,7 +140,7 @@ ORDER BY pronamespace::regnamespace::text COLLATE "C", p.oid::regprocedure::text
  add_job(regproc,interval,jsonb,timestamp with time zone,boolean,regproc)
  add_reorder_policy(regclass,name,boolean)
  add_retention_policy(regclass,"any",boolean,interval)
- alter_job(integer,interval,interval,integer,interval,boolean,jsonb,timestamp with time zone,boolean)
+ alter_job(integer,interval,interval,integer,interval,boolean,jsonb,timestamp with time zone,boolean,regproc)
  approximate_row_count(regclass)
  attach_data_node(name,regclass,boolean,boolean)
  attach_tablespace(name,regclass,boolean)
diff --git a/tsl/test/sql/bgw_custom.sql b/tsl/test/sql/bgw_custom.sql
index 7f404a88c..5d73bc300 100644
--- a/tsl/test/sql/bgw_custom.sql
+++ b/tsl/test/sql/bgw_custom.sql
@@ -317,3 +317,248 @@ FROM _timescaledb_config.bgw_job WHERE id = :job_id_5;
 
 -- Stop Background Workers
 SELECT _timescaledb_internal.stop_background_workers();
+
+SELECT _timescaledb_internal.restart_background_workers();
+
+\set ON_ERROR_STOP 0
+-- add test for custom jobs with custom check functions
+-- create the functions/procedures to be used as checking functions
+CREATE OR REPLACE PROCEDURE test_config_check_proc(config jsonb)
+LANGUAGE PLPGSQL
+AS $$
+DECLARE
+  drop_after interval;
+BEGIN 
+    SELECT jsonb_object_field_text (config, 'drop_after')::interval INTO STRICT drop_after;
+    IF drop_after IS NULL THEN 
+        RAISE EXCEPTION 'Config must be not NULL and have drop_after';
+    END IF ;
+END
+$$;
+
+CREATE OR REPLACE FUNCTION test_config_check_func(config jsonb) RETURNS VOID
+AS $$
+DECLARE
+  drop_after interval;
+BEGIN 
+    IF config IS NULL THEN
+        RETURN;
+    END IF;
+    SELECT jsonb_object_field_text (config, 'drop_after')::interval INTO STRICT drop_after;
+    IF drop_after IS NULL THEN 
+        RAISE EXCEPTION 'Config can be NULL but must have drop_after if not';
+    END IF ;
+END
+$$ LANGUAGE PLPGSQL;
+
+-- step 2, create a procedure to run as a custom job
+CREATE OR REPLACE PROCEDURE test_proc_with_check(job_id int, config jsonb)
+LANGUAGE PLPGSQL
+AS $$
+BEGIN
+  RAISE NOTICE 'Will only print this if config passes checks, my config is %', config; 
+END
+$$;
+
+-- step 3, add the job with the config check function passed as argument
+-- test procedures
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_proc'::regproc);
+select add_job('test_proc_with_check', '5 secs', config => NULL, check_config => 'test_config_check_proc'::regproc);
+select add_job('test_proc_with_check', '5 secs', config => '{"drop_after": "chicken"}', check_config => 'test_config_check_proc'::regproc);
+select add_job('test_proc_with_check', '5 secs', config => '{"drop_after": "2 weeks"}', check_config => 'test_config_check_proc'::regproc)
+as job_with_proc_check_id \gset
+
+-- test functions
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_func'::regproc);
+select add_job('test_proc_with_check', '5 secs', config => NULL, check_config => 'test_config_check_func'::regproc);
+select add_job('test_proc_with_check', '5 secs', config => '{"drop_after": "chicken"}', check_config => 'test_config_check_func'::regproc);
+select add_job('test_proc_with_check', '5 secs', config => '{"drop_after": "2 weeks"}', check_config => 'test_config_check_func'::regproc) 
+as job_with_func_check_id \gset
+
+
+--- test alter_job
+select alter_job(:job_with_func_check_id, config => '{"drop_after":"chicken"}');
+select alter_job(:job_with_func_check_id, config => '{"drop_after":"5 years"}');
+
+select alter_job(:job_with_proc_check_id, config => '{"drop_after":"4 days"}');
+
+
+-- test that jobs with an incorrect check function signature will not be registered
+-- these are all incorrect function signatures 
+
+CREATE OR REPLACE FUNCTION test_config_check_func_0args() RETURNS VOID
+AS $$
+BEGIN 
+    RAISE NOTICE 'I take no arguments and will validate anything you give me!';
+END
+$$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION test_config_check_func_2args(config jsonb, intarg int) RETURNS VOID
+AS $$
+BEGIN 
+    RAISE NOTICE 'I take two arguments (jsonb, int) and I should fail to run!';
+END
+$$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION test_config_check_func_intarg(config int) RETURNS VOID
+AS $$
+BEGIN 
+    RAISE NOTICE 'I take one argument which is an integer and I should fail to run!';
+END
+$$ LANGUAGE PLPGSQL;
+
+-- -- this should fail, it has an incorrect check function 
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_func_0args'::regproc);
+-- -- so should this
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_func_2args'::regproc);
+-- and this
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_func_intarg'::regproc);
+-- and this fails as it calls a nonexistent function
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_nonexistent_check_func'::regproc);
+
+-- when called with a valid check function and a NULL config no check should occur
+CREATE OR REPLACE FUNCTION test_config_check_func(config jsonb) RETURNS VOID
+AS $$
+BEGIN 
+    RAISE NOTICE 'This message will get printed for both NULL and not NULL config';
+END
+$$ LANGUAGE PLPGSQL;
+
+SET client_min_messages = NOTICE;
+-- check done for both NULL and non-NULL config
+select add_job('test_proc_with_check', '5 secs', config => NULL, check_config => 'test_config_check_func'::regproc);
+-- check done
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_func'::regproc) as job_id \gset
+
+-- check function not returning void
+CREATE OR REPLACE FUNCTION test_config_check_func_returns_int(config jsonb) RETURNS INT
+AS $$
+BEGIN 
+    raise notice 'I print a message, and then I return least(1,2)';
+    RETURN LEAST(1, 2);
+END
+$$ LANGUAGE PLPGSQL;
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_func_returns_int'::regproc) as job_id_int \gset
+
+-- drop the registered check function, verify that alter_job will work and print a warning that 
+-- the check is being skipped due to the check function missing
+ALTER FUNCTION test_config_check_func RENAME TO renamed_func;
+select alter_job(:job_id, schedule_interval => '1 hour');
+DROP FUNCTION test_config_check_func_returns_int;
+select alter_job(:job_id_int, config => '{"field":"value"}');
+
+-- rename the check function and then call alter_job to register the new name
+select alter_job(:job_id, check_config => 'renamed_func'::regproc);
+-- run alter again, should get a config check
+select alter_job(:job_id, config => '{}');
+-- do not drop the current check function but register a new one
+CREATE OR REPLACE FUNCTION substitute_check_func(config jsonb) RETURNS VOID
+AS $$
+BEGIN 
+    RAISE NOTICE 'This message is a substitute of the previously printed one';
+END
+$$ LANGUAGE PLPGSQL;
+-- register the new check
+select alter_job(:job_id, check_config => 'substitute_check_func');
+select alter_job(:job_id, config => '{}');
+
+RESET client_min_messages;
+
+-- test an oid that doesn't exist
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 17424217::regproc);
+
+\c :TEST_DBNAME :ROLE_SUPERUSER
+-- test a function with insufficient privileges
+create schema test_schema;
+create role user_noexec with login;
+grant usage on schema test_schema to user_noexec;
+
+CREATE OR REPLACE FUNCTION test_schema.test_config_check_func_privileges(config jsonb) RETURNS VOID
+AS $$
+BEGIN 
+    RAISE NOTICE 'This message will only get printed if privileges suffice';
+END
+$$ LANGUAGE PLPGSQL;
+
+revoke execute on function test_schema.test_config_check_func_privileges from public;
+-- verify the user doesn't have execute permissions on the function
+select has_function_privilege('user_noexec', 'test_schema.test_config_check_func_privileges(jsonb)', 'execute');
+
+\c :TEST_DBNAME user_noexec
+-- user_noexec should not have exec permissions on this function
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_schema.test_config_check_func_privileges'::regproc);
+
+\c :TEST_DBNAME :ROLE_SUPERUSER
+
+-- check that alter_job rejects a check function with invalid signature
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'renamed_func') as job_id_alter \gset
+select alter_job(:job_id_alter, check_config => 'test_config_check_func_0args');
+select alter_job(:job_id_alter);
+-- test that we can unregister the check function
+select alter_job(:job_id_alter, check_config => 0);
+-- no message printed now
+select alter_job(:job_id_alter, config => '{}'); 
+
+-- test what happens if the check function contains a COMMIT
+-- procedure with transaction handling
+CREATE OR REPLACE PROCEDURE custom_proc2_jsonb(config jsonb) LANGUAGE PLPGSQL AS
+$$
+BEGIN
+--   RAISE NOTICE 'Starting some transactions inside procedure';
+  INSERT INTO custom_log VALUES(1, $1, 'custom_proc 1 COMMIT');
+  COMMIT;
+END
+$$;
+
+select add_job('test_proc_with_check', '5 secs', config => '{}') as job_id_err \gset
+select alter_job(:job_id_err, check_config => 'custom_proc2_jsonb');
+select alter_job(:job_id_err, schedule_interval => '3 minutes');
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'custom_proc2_jsonb') as job_id_commit \gset
+
+-- test the case where we have a background job that registers jobs with a check fn
+CREATE OR REPLACE PROCEDURE add_scheduled_jobs_with_check(job_id int, config jsonb) LANGUAGE PLPGSQL AS 
+$$
+BEGIN
+    perform add_job('test_proc_with_check', schedule_interval => '10 secs', config => '{}', check_config => 'renamed_func');
+END
+$$;
+
+select add_job('add_scheduled_jobs_with_check', schedule_interval => '1 hour') as last_job_id \gset
+-- wait for enough time
+SELECT wait_for_job_to_run(:last_job_id, 1);
+select total_runs, total_successes, last_run_status from timescaledb_information.job_stats where job_id = :last_job_id;
+
+-- test coverage for alter_job
+-- registering an invalid oid
+select alter_job(:job_id_alter, check_config => 123456789::regproc);
+-- registering a function with insufficient privileges
+\c :TEST_DBNAME user_noexec
+select * from add_job('test_proc_with_check', '5 secs', config => '{}') as job_id_owner \gset
+select * from alter_job(:job_id_owner, check_config => 'test_schema.test_config_check_func_privileges'::regproc);
+
+\c :TEST_DBNAME :ROLE_SUPERUSER
+DROP SCHEMA test_schema CASCADE;
+DROP ROLE user_noexec;
+
+-- test with aggregate check proc
+create function jsonb_add (j1 jsonb, j2 jsonb) returns jsonb
+AS $$
+BEGIN 
+    RETURN j1 || j2;
+END
+$$ LANGUAGE PLPGSQL;
+
+create table jsonb_values (j jsonb, i int);
+insert into jsonb_values values ('{"refresh_after":"2 weeks"}', 1), ('{"compress_after":"2 weeks"}', 2), ('{"drop_after":"2 weeks"}', 3);
+
+CREATE AGGREGATE sum_jsb (jsonb)
+(
+    sfunc = jsonb_add,
+    stype = jsonb,
+    initcond = '{}'
+);
+
+-- for test coverage, check unsupported aggregate type
+select add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'sum_jsb'::regproc);
+
+