Skip to content

Migration from others schedulers

Migrate jobs from pg_cron to pg_timetable

If you want to quickly export jobs scheduled from pg_cron to pg_timetable, you can use this SQL snippet:

SELECT timetable.add_job(
    job_name            => COALESCE(jobname, 'job: ' || command),
    job_schedule        => schedule,
    job_command         => command,
    job_kind            => 'SQL',
    job_live            => active
) FROM cron.job;

The timetable.add_job(), however, has some limitations. First of all, the function will mark the task created as autonomous, specifying scheduler should execute the task out of the chain transaction. It's not an error, but many autonomous chains may cause some extra connections to be used.

Secondly, database connection parameters are lost for source pg_cron jobs, making all jobs local. To export every information available precisely as possible, use this SQL snippet under the role they were scheduled in pg_cron:

SET ROLE 'scheduler'; -- set the role used by pg_cron

WITH cron_chain AS (
    SELECT
        nextval('timetable.chain_chain_id_seq'::regclass) AS cron_id,
        jobname,
        schedule,
        active,
        command,
        CASE WHEN 
            database != current_database()
            OR nodename != 'localhost'
            OR username != CURRENT_USER
            OR nodeport != inet_server_port() 
        THEN
            format('host=%s port=%s dbname=%s user=%s', nodename, nodeport, database, username)
        END AS connstr
    FROM
        cron.job
),
cte_chain AS (
    INSERT INTO timetable.chain (chain_id, chain_name, run_at, live)
        SELECT 
            cron_id, COALESCE(jobname, 'cronjob' || cron_id), schedule, active
        FROM
            cron_chain
),
cte_tasks AS (
    INSERT INTO timetable.task (chain_id, task_order, kind, command, database_connection)
        SELECT
            cron_id, 1, 'SQL', command, connstr
        FROM
            cron_chain
        RETURNING
            chain_id, task_id
)
SELECT * FROM cte_tasks;

Migrate jobs from pgAgent to pg_timetable

To migrate jobs from pgAgent, please use this script. pgAgent doesn't have concept of PROGRAM task, thus to emulate BATCH steps, pg_timetable will execute them inside the shell. You may change the shell by editing cte_shell CTE clause.

CREATE OR REPLACE FUNCTION bool_array_to_cron(bool[], start_with int4 DEFAULT 0) RETURNS TEXT AS
$$
WITH u AS (
    SELECT unnest($1) e, generate_series($2, array_length($1, 1)-1+$2) AS i 
)
SELECT COALESCE(string_agg(i::text, ','), '*') FROM u WHERE e
$$
LANGUAGE sql;


WITH
cte_shell(shell, cmd_param) AS (
    VALUES ('sh', '-c') -- set the shell you want to use for batch steps, e.g. "pwsh -c", "cmd /C"
),
pga_schedule AS (
    SELECT
        s.jscjobid,
        s.jscname,
        format('%s %s %s %s %s', 
            bool_array_to_cron(s.jscminutes), 
            bool_array_to_cron(s.jschours), 
            bool_array_to_cron(s.jscmonthdays), 
            bool_array_to_cron(s.jscmonths, 1), 
            bool_array_to_cron(s.jscweekdays, 1)) AS schedule
    FROM 
        pgagent.pga_schedule s  
    WHERE s.jscenabled 
            AND now() < COALESCE(s.jscend, 'infinity'::timestamptz)
            AND now() > s.jscstart
),
pga_chain AS (
    SELECT
        nextval('timetable.chain_chain_id_seq'::regclass) AS chain_id,
        jobid,
        format('%s @ %s', jobname, jscname) AS jobname,
        jobhostagent,
        jobenabled,
        schedule
    FROM
        pgagent.pga_job JOIN pga_schedule ON jobid = jscjobid
),
cte_chain AS (
    INSERT INTO timetable.chain (chain_id, chain_name, client_name, run_at, live)
        SELECT 
            chain_id, jobname, jobhostagent, schedule, jobenabled
        FROM
            pga_chain
),
pga_step AS (
    SELECT 
        c.chain_id,
        nextval('timetable.task_task_id_seq'::regclass) AS task_id,
        rank() OVER (ORDER BY jstname) AS jstorder,
        jstid,
        jstname,
        jstenabled,
        CASE jstkind WHEN 'b' THEN 'PROGRAM' ELSE 'SQL' END AS jstkind,
        jstcode,
        COALESCE(
            NULLIF(jstconnstr, ''), 
            CASE 
                WHEN jstdbname = current_database() THEN NULL
                WHEN jstdbname > '' THEN 'dbname=' || jstdbname 
            END
        ) AS jstconnstr,
        jstonerror != 'f' AS jstignoreerror
    FROM
        pga_chain c JOIN pgagent.pga_jobstep js ON c.jobid = js.jstjobid
),
cte_tasks AS (
    INSERT INTO timetable.task(task_id, chain_id, task_name, task_order, kind, command, database_connection)
        SELECT
            task_id, chain_id, jstname, jstorder, jstkind::timetable.command_kind, 
            CASE jstkind WHEN 'SQL' THEN jstcode ELSE sh.shell END,
            jstconnstr
        FROM
            pga_step, cte_shell sh
),
cte_parameters AS (
    INSERT INTO timetable.parameter (task_id, order_id, value)
        SELECT 
            task_id, 1, jsonb_build_array(sh.cmd_param, s.jstcode)
        FROM
            pga_step s, cte_shell sh
        WHERE 
            s.jstkind = 'PROGRAM'
)
SELECT * FROM pga_chain;