pg-boss 12.27.0 → 12.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,16 @@ import assert from 'node:assert';
2
2
  import * as plans from "./plans.js";
3
3
  import { resolveSchemaName } from "./tools.js";
4
4
  import * as types from "./types.js";
5
+ // 12.28.0 replaced the plain `string[]` partitionTables shape with { tableName, policy } records.
6
+ // TypeScript callers get a compile error; this keeps JavaScript callers from silently exporting a
7
+ // migration whose target list is wrong (a bare string has no policy, so every policy-scoped build
8
+ // would quietly drop it).
9
+ function assertPartitionMetadata(partitionTables) {
10
+ for (const partition of partitionTables) {
11
+ assert(typeof partition === 'object' && partition !== null &&
12
+ typeof partition.tableName === 'string' && typeof partition.policy === 'string', `partitionTables entries must be { tableName, policy } records, received: ${JSON.stringify(partition)}. The string[] form was removed in 12.28.0.`);
13
+ }
14
+ }
5
15
  // Mirrors the SQL job_table_format() function (src/plans.ts): rewrites a command targeting
6
16
  // the base `job` table to target a specific partition table.
7
17
  function formatJobTable(command, table) {
@@ -17,16 +27,25 @@ function formatJobTable(command, table) {
17
27
  // via BAM, one statement per target table, each prefixed with a provenance comment. The
18
28
  // CONCURRENTLY keyword is preserved (these are emitted after COMMIT), and IF NOT EXISTS is
19
29
  // added so the script is safe to re-run.
20
- function inlineAsyncCommand(schema, asyncCommand, version, partitionTables) {
21
- const nameMatch = asyncCommand.match(/job_table_run_async\(\s*'([^']+)'/);
22
- const bodyMatch = asyncCommand.match(/\$\$([\s\S]*?)\$\$/);
30
+ function inlineAsyncCommand(schema, asyncMigration, version, partitionTables) {
31
+ // Two spellings: the legacy string form embeds the whole job_table_run_async() call as SQL, so
32
+ // name/body/table-pin have to be parsed back out of it; the record form carries them as fields.
33
+ const isLegacy = typeof asyncMigration === 'string';
34
+ const asyncCommand = isLegacy ? asyncMigration : asyncMigration.command;
35
+ const nameMatch = isLegacy ? asyncCommand.match(/job_table_run_async\(\s*'([^']+)'/) : null;
36
+ const bodyMatch = isLegacy ? asyncCommand.match(/\$\$([\s\S]*?)\$\$/) : null;
23
37
  // An explicit table arg after the $$ body pins the command to a single table (e.g. i8 →
24
38
  // job_common); without it the command fans out across job_common + every partition.
25
- const tableMatch = asyncCommand.match(/\$\$\s*,\s*'([^']+)'/);
26
- assert(nameMatch && bodyMatch, `Unable to inline async migration command: ${asyncCommand}`);
27
- const commandName = nameMatch[1];
28
- const body = bodyMatch[1].trim();
29
- const targetTables = tableMatch ? [tableMatch[1]] : ['job_common', ...partitionTables];
39
+ const tableMatch = isLegacy ? asyncCommand.match(/\$\$\s*,\s*'([^']+)'/) : null;
40
+ assert(!isLegacy || (nameMatch && bodyMatch), `Unable to inline async migration command: ${asyncCommand}`);
41
+ const commandName = isLegacy ? nameMatch[1] : asyncMigration.name;
42
+ const body = (isLegacy ? bodyMatch[1] : asyncMigration.command).trim();
43
+ // A policy-scoped build only belongs on partitions of that policy; unscoped builds fan out to all.
44
+ const partitionPolicy = isLegacy ? undefined : asyncMigration.partitionPolicy;
45
+ const eligiblePartitions = partitionTables
46
+ .filter(partition => !partitionPolicy || partition.policy === partitionPolicy)
47
+ .map(partition => partition.tableName);
48
+ const targetTables = tableMatch ? [tableMatch[1]] : ['job_common', ...eligiblePartitions];
30
49
  return targetTables.map(table => {
31
50
  // Add IF NOT EXISTS so the exported script is re-runnable. The negative lookahead keeps it
32
51
  // idempotent when the async command already spells out IF NOT EXISTS (the live BAM path needs it
@@ -36,6 +55,32 @@ function inlineAsyncCommand(schema, asyncCommand, version, partitionTables) {
36
55
  return `${comment}\n${ddl}`;
37
56
  });
38
57
  }
58
+ function renderAsyncCommand(schema, asyncMigration, version) {
59
+ if (typeof asyncMigration === 'string') {
60
+ return asyncMigration.replace(/\$VERSION\$/g, String(version));
61
+ }
62
+ const name = asyncMigration.name.replaceAll("'", "''");
63
+ const policy = asyncMigration.partitionPolicy?.replaceAll("'", "''");
64
+ if (!policy) {
65
+ return `SELECT ${schema}.job_table_run_async('${name}', ${version}, $$${asyncMigration.command}$$)`;
66
+ }
67
+ // queue_name is passed alongside table_name so the enqueued rows record which queue each build
68
+ // belongs to, matching what the unscoped fan-out inside job_table_run_async() writes. For a
69
+ // partition row the function re-derives table_name from queue_name and lands on the same value;
70
+ // job_common has no queue of its own, so its row keeps queue NULL like the fan-out does.
71
+ return `SELECT ${schema}.job_table_run_async(
72
+ '${name}',
73
+ ${version},
74
+ $$${asyncMigration.command}$$,
75
+ targets.table_name,
76
+ targets.queue_name
77
+ )
78
+ FROM (
79
+ SELECT 'job_common'::text AS table_name, NULL::text AS queue_name
80
+ UNION ALL
81
+ SELECT table_name, name FROM ${schema}.queue WHERE partition = true AND policy = '${policy}'
82
+ ) targets`;
83
+ }
39
84
  function flatten(schema, commands, version, noAdvisoryLocks) {
40
85
  commands.unshift(plans.assertMigration(schema, version));
41
86
  commands.push(plans.setVersion(schema, version));
@@ -45,7 +90,20 @@ function rollback(schema, version, migrations, noAdvisoryLocks) {
45
90
  migrations = migrations || getAll(schema);
46
91
  const result = migrations.find(i => i.version === version);
47
92
  assert(result, `Version ${version} not found.`);
48
- return flatten(schema, result.uninstall || [], result.previous, noAdvisoryLocks);
93
+ // Async (BAM) index builds are enqueued as bam rows, and the BAM runner does not filter by schema
94
+ // version — so a row left unfinished by a rollback would rebuild, one version later, the very index
95
+ // the rollback just dropped. Clear this version's unfinished rows as part of the same transaction.
96
+ // Only migrations that enqueue async commands can have any, and the migration that introduced BAM
97
+ // is also the first one with async commands, so bam is guaranteed to exist here; ordering the delete
98
+ // ahead of the migration's own steps keeps it before that migration's DROP TABLE bam.
99
+ //
100
+ // No guard against a build that is genuinely running: the uninstall steps drop the index the build
101
+ // holds a ShareUpdateExclusiveLock on, so a live build already makes the whole rollback fail on the
102
+ // transaction's lock_timeout, taking this delete with it.
103
+ const clearAsync = result.async?.length
104
+ ? [`DELETE FROM ${schema}.bam WHERE version = ${result.version} AND status <> 'completed'`]
105
+ : [];
106
+ return flatten(schema, [...clearAsync, ...(result.uninstall || [])], result.previous, noAdvisoryLocks);
49
107
  }
50
108
  function next(schema, version, migrations, noAdvisoryLocks) {
51
109
  migrations = migrations || getAll(schema);
@@ -71,6 +129,7 @@ function migrateCommands(schema, version, migrations, noAdvisoryLocks, options =
71
129
  assert(version >= minPrevious, `Cannot migrate pg-boss schema from version ${version}: the oldest supported starting version is ${minPrevious}. ` +
72
130
  'Upgrade to a schema at or above that version using an older pg-boss release first.');
73
131
  }
132
+ assertPartitionMetadata(options.partitionTables || []);
74
133
  const concurrent = [];
75
134
  const result = migrations
76
135
  .filter(i => i.previous >= version)
@@ -85,7 +144,7 @@ function migrateCommands(schema, version, migrations, noAdvisoryLocks, options =
85
144
  }
86
145
  }
87
146
  else {
88
- const bamCommands = migration.async.map(cmd => cmd.replace(/\$VERSION\$/g, String(migration.version)));
147
+ const bamCommands = migration.async.map(cmd => renderAsyncCommand(schema, cmd, migration.version));
89
148
  acc.install = acc.install.concat(bamCommands);
90
149
  }
91
150
  }
@@ -675,6 +734,93 @@ const createQueueFn = {
675
734
  END;
676
735
  $$
677
736
  LANGUAGE plpgsql;
737
+ `,
738
+ 38: (schema) => `
739
+ CREATE OR REPLACE FUNCTION ${schema}.create_queue(queue_name text, options jsonb)
740
+ RETURNS VOID AS
741
+ $$
742
+ DECLARE
743
+ tablename varchar := CASE WHEN options->>'partition' = 'true'
744
+ THEN 'j' || encode(sha224(queue_name::bytea), 'hex')
745
+ ELSE 'job_common'
746
+ END;
747
+ queue_created_on timestamptz;
748
+ BEGIN
749
+
750
+ WITH q as (
751
+ INSERT INTO ${schema}.queue (
752
+ name,
753
+ policy,
754
+ retry_limit,
755
+ retry_delay,
756
+ retry_backoff,
757
+ retry_delay_max,
758
+ expire_seconds,
759
+ retention_seconds,
760
+ deletion_seconds,
761
+ warning_queued,
762
+ dead_letter,
763
+ partition,
764
+ table_name,
765
+ heartbeat_seconds,
766
+ notify
767
+ )
768
+ VALUES (
769
+ queue_name,
770
+ options->>'policy',
771
+ COALESCE((options->>'retryLimit')::int, 2),
772
+ COALESCE((options->>'retryDelay')::int, 0),
773
+ COALESCE((options->>'retryBackoff')::bool, false),
774
+ (options->>'retryDelayMax')::int,
775
+ COALESCE((options->>'expireInSeconds')::int, 900),
776
+ COALESCE((options->>'retentionSeconds')::int, 1209600),
777
+ COALESCE((options->>'deleteAfterSeconds')::int, 604800),
778
+ COALESCE((options->>'warningQueueSize')::int, 0),
779
+ options->>'deadLetter',
780
+ COALESCE((options->>'partition')::bool, false),
781
+ tablename,
782
+ (options->>'heartbeatSeconds')::int,
783
+ COALESCE((options->>'notify')::bool, false)
784
+ )
785
+ ON CONFLICT DO NOTHING
786
+ RETURNING created_on
787
+ )
788
+ SELECT created_on into queue_created_on from q;
789
+
790
+ IF queue_created_on IS NULL OR options->>'partition' IS DISTINCT FROM 'true' THEN
791
+ RETURN;
792
+ END IF;
793
+
794
+ EXECUTE format('CREATE TABLE ${schema}.%I (LIKE ${schema}.job INCLUDING DEFAULTS)', tablename);
795
+
796
+ EXECUTE ${schema}.job_table_format($cmd$ALTER TABLE ${schema}.job ADD PRIMARY KEY (name, id)$cmd$, tablename);
797
+ EXECUTE ${schema}.job_table_format($cmd$ALTER TABLE ${schema}.job ADD CONSTRAINT q_fkey FOREIGN KEY (name) REFERENCES ${schema}.queue (name) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED$cmd$, tablename);
798
+ EXECUTE ${schema}.job_table_format($cmd$ALTER TABLE ${schema}.job ADD CONSTRAINT dlq_fkey FOREIGN KEY (dead_letter) REFERENCES ${schema}.queue (name) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED$cmd$, tablename);
799
+
800
+ EXECUTE ${schema}.job_table_format($cmd$CREATE INDEX job_i5 ON ${schema}.job (name, start_after) WHERE state < 'active' AND NOT blocked$cmd$, tablename);
801
+ EXECUTE ${schema}.job_table_format($cmd$CREATE UNIQUE INDEX job_i4 ON ${schema}.job (name, singleton_on, COALESCE(singleton_key, '')) WHERE state <> 'cancelled' AND singleton_on IS NOT NULL$cmd$, tablename);
802
+ EXECUTE ${schema}.job_table_format($cmd$CREATE INDEX job_i7 ON ${schema}.job (name, group_id) WHERE state = 'active' AND group_id IS NOT NULL$cmd$, tablename);
803
+ EXECUTE ${schema}.job_table_format($cmd$CREATE INDEX job_i9 ON ${schema}.job (name, id) WHERE blocking AND state = 'completed'$cmd$, tablename);
804
+
805
+ IF options->>'policy' = 'short' THEN
806
+ EXECUTE ${schema}.job_table_format($cmd$CREATE UNIQUE INDEX job_i1 ON ${schema}.job (name, COALESCE(singleton_key, '')) WHERE state = 'created' AND policy = 'short'$cmd$, tablename);
807
+ ELSIF options->>'policy' = 'singleton' THEN
808
+ EXECUTE ${schema}.job_table_format($cmd$CREATE UNIQUE INDEX job_i2 ON ${schema}.job (name, COALESCE(singleton_key, '')) WHERE state = 'active' AND policy = 'singleton'$cmd$, tablename);
809
+ ELSIF options->>'policy' = 'stately' THEN
810
+ EXECUTE ${schema}.job_table_format($cmd$CREATE UNIQUE INDEX job_i3 ON ${schema}.job (name, state, COALESCE(singleton_key, '')) WHERE state <= 'active' AND policy = 'stately'$cmd$, tablename);
811
+ ELSIF options->>'policy' = 'exclusive' THEN
812
+ EXECUTE ${schema}.job_table_format($cmd$CREATE UNIQUE INDEX job_i6 ON ${schema}.job (name, COALESCE(singleton_key, '')) WHERE state <= 'active' AND policy = 'exclusive'$cmd$, tablename);
813
+ ELSIF options->>'policy' = 'key_strict_fifo' THEN
814
+ EXECUTE ${schema}.job_table_format($cmd$CREATE UNIQUE INDEX job_i8 ON ${schema}.job (name, singleton_key) WHERE state IN ('active', 'retry', 'failed') AND policy = 'key_strict_fifo'$cmd$, tablename);
815
+ EXECUTE ${schema}.job_table_format($cmd$CREATE INDEX job_i10 ON ${schema}.job (name, singleton_key, state DESC, created_on, id) INCLUDE (start_after) WHERE state < 'active' AND NOT blocked AND policy = 'key_strict_fifo'$cmd$, tablename);
816
+ EXECUTE ${schema}.job_table_format($cmd$ALTER TABLE ${schema}.job ADD CONSTRAINT job_key_strict_fifo_singleton_key_check CHECK (NOT (policy = 'key_strict_fifo' AND singleton_key IS NULL))$cmd$, tablename);
817
+ END IF;
818
+
819
+ EXECUTE format('ALTER TABLE ${schema}.%I ADD CONSTRAINT cjc CHECK (name=%L)', tablename, queue_name);
820
+ EXECUTE format('ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.%I FOR VALUES IN (%L)', tablename, queue_name);
821
+ END;
822
+ $$
823
+ LANGUAGE plpgsql;
678
824
  `
679
825
  };
680
826
  // Frozen per-version snapshots of the queue_stats DDL, version-keyed like createQueueFn above. A
@@ -1211,6 +1357,31 @@ function getAll(schema, noPartitioning = false, noCovering = false) {
1211
1357
  uninstall: noPartitioning
1212
1358
  ? []
1213
1359
  : [jobTableFormatFn[36](schema)]
1360
+ },
1361
+ {
1362
+ release: '12.28.0',
1363
+ version: 38,
1364
+ previous: 37,
1365
+ install: noPartitioning
1366
+ // noCovering backends (CockroachDB) have no INCLUDE, so they get the narrow index. The
1367
+ // partitioned path is PostgreSQL-only and always covering.
1368
+ ? [`CREATE INDEX job_i10 ON ${schema}.job (name, singleton_key, state DESC, created_on, id)${noCovering ? '' : ' INCLUDE (start_after)'} WHERE state < 'active' AND NOT blocked AND policy = 'key_strict_fifo'`]
1369
+ : [createQueueFn[38](schema)],
1370
+ async: noPartitioning
1371
+ ? []
1372
+ : [
1373
+ {
1374
+ name: 'key_strict_fifo_head_index',
1375
+ partitionPolicy: 'key_strict_fifo',
1376
+ command: `CREATE INDEX CONCURRENTLY IF NOT EXISTS job_i10 ON ${schema}.job (name, singleton_key, state DESC, created_on, id) INCLUDE (start_after) WHERE state < 'active' AND NOT blocked AND policy = 'key_strict_fifo'`
1377
+ }
1378
+ ],
1379
+ uninstall: noPartitioning
1380
+ ? [`DROP INDEX IF EXISTS ${schema}.job_i10`]
1381
+ : [
1382
+ createQueueFn[33](schema),
1383
+ `SELECT ${schema}.job_table_run($cmd$DROP INDEX IF EXISTS ${schema}.job_i10$cmd$)`
1384
+ ]
1214
1385
  }
1215
1386
  ];
1216
1387
  }
package/dist/plans.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { JobMatchStrategy, UpdateQueueOptions, ManagedIndex, ManagedFunction } from './types.ts';
1
+ import type { JobMatchStrategy, ManagedIndex, ManagedFunction } from './types.ts';
2
2
  import type { ExpectedColumns, ExpectedConstraints } from './drifter.ts';
3
3
  export interface SqlQuery {
4
4
  text: string;
@@ -50,7 +50,7 @@ export declare function trySetQueueDeletionTime(schema: string, queues: string[]
50
50
  export declare function trySetCronTime(schema: string, seconds: number): string;
51
51
  export declare function trySetBamTime(schema: string, seconds: number): string;
52
52
  export declare function trySetFlowTime(schema: string, seconds: number): string;
53
- export declare function updateQueue(schema: string, { deadLetter }?: UpdateQueueOptions): string;
53
+ export declare function updateQueue(schema: string): string;
54
54
  export declare function getQueues(schema: string, names?: string[]): SqlQuery;
55
55
  export declare function deleteJobsById(schema: string, table: string): string;
56
56
  export declare function deleteQueuedJobs(schema: string, table: string): string;
@@ -1 +1 @@
1
- {"version":3,"file":"plans.d.ts","sourceRoot":"","sources":["../src/plans.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plans.d.ts","sourceRoot":"","sources":["../src/plans.ts"],"names":[],"mappings":"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"}
package/dist/plans.js CHANGED
@@ -1,4 +1,4 @@
1
- import { indexKeysRaw, indexPredicateRaw, displayIndexDefinition, extractFunctionBody, normalizeFunctionBody } from "./drifter.js";
1
+ import { indexKeysRaw, indexIncludeRaw, indexPredicateRaw, displayIndexDefinition, extractFunctionBody, normalizeFunctionBody } from "./drifter.js";
2
2
  import schemaManifest from './schema.json' with { type: 'json' };
3
3
  import { normalizeSchemaName, resolveSchemaName } from "./tools.js";
4
4
  export const PG_ERROR = {
@@ -401,6 +401,7 @@ function createTableJobCommon(schema) {
401
401
  SELECT ${schema}.job_table_run($cmd$${createIndexJobPolicyStately(schema)}$cmd$, '${COMMON_JOB_TABLE}');
402
402
  SELECT ${schema}.job_table_run($cmd$${createIndexJobPolicyExclusive(schema)}$cmd$, '${COMMON_JOB_TABLE}');
403
403
  SELECT ${schema}.job_table_run($cmd$${createIndexJobPolicyKeyStrictFifo(schema)}$cmd$, '${COMMON_JOB_TABLE}');
404
+ SELECT ${schema}.job_table_run($cmd$${createIndexJobPolicyKeyStrictFifoHeads(schema)}$cmd$, '${COMMON_JOB_TABLE}');
404
405
  SELECT ${schema}.job_table_run($cmd$${createCheckConstraintKeyStrictFifo(schema)}$cmd$, '${COMMON_JOB_TABLE}');
405
406
  SELECT ${schema}.job_table_run($cmd$${createIndexJobThrottle(schema)}$cmd$, '${COMMON_JOB_TABLE}');
406
407
  SELECT ${schema}.job_table_run($cmd$${createIndexJobFetch(schema)}$cmd$, '${COMMON_JOB_TABLE}');
@@ -420,6 +421,7 @@ function createTableJobIndexes(schema, noDeferrableConstraints = false, noCoveri
420
421
  ${createIndexJobPolicyStately(schema)};
421
422
  ${createIndexJobPolicyExclusive(schema)};
422
423
  ${createIndexJobPolicyKeyStrictFifo(schema)};
424
+ ${createIndexJobPolicyKeyStrictFifoHeads(schema, noCoveringIndex)};
423
425
  ${createCheckConstraintKeyStrictFifo(schema)};
424
426
  ${createIndexJobThrottle(schema)};
425
427
  ${createIndexJobFetch(schema, noCoveringIndex)};
@@ -550,6 +552,7 @@ function createQueueFunction(schema, noPartitioning = false) {
550
552
  EXECUTE ${schema}.job_table_format($cmd$${createIndexJobPolicyExclusive(schema)}$cmd$, tablename);
551
553
  ELSIF options->>'policy' = '${QUEUE_POLICIES.key_strict_fifo}' THEN
552
554
  EXECUTE ${schema}.job_table_format($cmd$${createIndexJobPolicyKeyStrictFifo(schema)}$cmd$, tablename);
555
+ EXECUTE ${schema}.job_table_format($cmd$${createIndexJobPolicyKeyStrictFifoHeads(schema)}$cmd$, tablename);
553
556
  EXECUTE ${schema}.job_table_format($cmd$${createCheckConstraintKeyStrictFifo(schema)}$cmd$, tablename);
554
557
  END IF;
555
558
 
@@ -661,6 +664,21 @@ function createIndexJobPolicyExclusive(schema) {
661
664
  function createIndexJobPolicyKeyStrictFifo(schema) {
662
665
  return `CREATE UNIQUE INDEX job_i8 ON ${schema}.job (name, singleton_key) WHERE state IN ('${JOB_STATES.active}', '${JOB_STATES.retry}', '${JOB_STATES.failed}') AND policy = '${QUEUE_POLICIES.key_strict_fifo}'`;
663
666
  }
667
+ function createIndexJobPolicyKeyStrictFifoHeads(schema, noCoveringIndex = false) {
668
+ // Column order mirrors the strict_fifo_heads ORDER BY (state DESC puts a retry job ahead
669
+ // of created siblings) so the DISTINCT ON head-per-key scan stays an ordered index scan.
670
+ //
671
+ // INCLUDE (start_after) is load-bearing, not a covering-index habit: `policy` and `blocked`
672
+ // are satisfied by the partial predicate, so start_after is the only remaining filter in the
673
+ // heads CTE. Without it every index entry needs a heap fetch and the scan degrades from an
674
+ // Index Only Scan to an Index Scan (measured at 200k queued rows / 2,000 keys: 47ms -> 111ms,
675
+ // ~400k extra buffer hits). It costs no HOT updates that job_i5 doesn't already cost, since
676
+ // job_i5 indexes start_after too, and it only widens a partial index that key_strict_fifo
677
+ // rows enter. Backends without covering indexes (the CockroachDB profile) fall back to the
678
+ // narrow form and pay the heap fetches; they take a different fetch path anyway (noSkipLocked).
679
+ const include = noCoveringIndex ? '' : ' INCLUDE (start_after)';
680
+ return `CREATE INDEX job_i10 ON ${schema}.job (name, singleton_key, state DESC, created_on, id)${include} WHERE state < '${JOB_STATES.active}' AND NOT blocked AND policy = '${QUEUE_POLICIES.key_strict_fifo}'`;
681
+ }
664
682
  function createCheckConstraintKeyStrictFifo(schema) {
665
683
  return `ALTER TABLE ${schema}.job ADD CONSTRAINT job_key_strict_fifo_singleton_key_check CHECK (NOT (policy = '${QUEUE_POLICIES.key_strict_fifo}' AND singleton_key IS NULL))`;
666
684
  }
@@ -709,7 +727,7 @@ function trySetQueueTimestamp(schema, queues, column, seconds) {
709
727
  values: [queues]
710
728
  };
711
729
  }
712
- export function updateQueue(schema, { deadLetter } = {}) {
730
+ export function updateQueue(schema) {
713
731
  return `
714
732
  WITH options as (SELECT $2::jsonb as data)
715
733
  UPDATE ${schema}.queue SET
@@ -727,9 +745,9 @@ export function updateQueue(schema, { deadLetter } = {}) {
727
745
  THEN (o.data->>'heartbeatSeconds')::int
728
746
  ELSE heartbeat_seconds END,
729
747
  notify = COALESCE((o.data->>'notify')::bool, notify),
730
- ${deadLetter === undefined
731
- ? ''
732
- : `dead_letter = CASE WHEN '${deadLetter}' IS DISTINCT FROM dead_letter THEN '${deadLetter}' ELSE dead_letter END,`}
748
+ dead_letter = CASE WHEN jsonb_exists(o.data, 'deadLetter')
749
+ THEN o.data->>'deadLetter'
750
+ ELSE dead_letter END,
733
751
  updated_on = now()
734
752
  FROM options o
735
753
  WHERE name = $1
@@ -1108,7 +1126,7 @@ export function getSchemaCaseVariants(schema) {
1108
1126
  `;
1109
1127
  }
1110
1128
  export function getPartitionedQueueTables(schema) {
1111
- return `SELECT table_name FROM ${schema}.queue WHERE partition = true`;
1129
+ return `SELECT table_name, policy FROM ${schema}.queue WHERE partition = true`;
1112
1130
  }
1113
1131
  export function insertVersion(schema, version) {
1114
1132
  return `INSERT INTO ${schema}.version(version) VALUES ('${version}')`;
@@ -1187,6 +1205,7 @@ function buildFetchParams(options) {
1187
1205
  */
1188
1206
  export function fetchNextJob(options, noSkipLocked = false) {
1189
1207
  const { schema, table, name, policy, limit, includeMetadata, priority = true, orderByCreatedOn = true, ignoreStartAfter = false, groupConcurrency, minPriority, maxPriority } = options;
1208
+ const keyStrictFifo = policy === QUEUE_POLICIES.key_strict_fifo;
1190
1209
  const singletonFetch = limit > 1 && (policy === QUEUE_POLICIES.singleton || policy === QUEUE_POLICIES.stately);
1191
1210
  const hasIgnoreSingletons = options.ignoreSingletons != null && options.ignoreSingletons.length > 0;
1192
1211
  const hasIgnoreGroups = options.ignoreGroups != null && options.ignoreGroups.length > 0;
@@ -1247,6 +1266,23 @@ export function fetchNextJob(options, noSkipLocked = false) {
1247
1266
  : `(j.group_id IS NULL
1248
1267
  OR ${activeGroupCountExpression} < ${groupLimit})`
1249
1268
  : '';
1269
+ // state DESC (retry > created in the enum) makes a retry job its key's head. job_i8
1270
+ // guarantees at most one job per key in active/retry/failed, and the NOT EXISTS blocker
1271
+ // below rejects every sibling of a retry job — so if created_on picked the head, an older
1272
+ // deferred job whose start_after has since arrived would claim the head slot while being
1273
+ // unfetchable, and the retry job (fetchable but not the head) would deadlock the key.
1274
+ const strictFifoHeadsCte = keyStrictFifo
1275
+ ? `strict_fifo_heads AS MATERIALIZED (
1276
+ SELECT DISTINCT ON (h.singleton_key) h.id
1277
+ FROM ${schema}.${table} h
1278
+ WHERE h.name = '${name}'
1279
+ AND h.state < '${JOB_STATES.active}'
1280
+ AND NOT h.blocked
1281
+ AND h.policy = '${QUEUE_POLICIES.key_strict_fifo}'
1282
+ ${!ignoreStartAfter ? 'AND h.start_after <= now()' : ''}
1283
+ ORDER BY h.singleton_key, h.state DESC, h.created_on, h.id
1284
+ ), `
1285
+ : '';
1250
1286
  const whereConditions = [
1251
1287
  `j.name = '${name}'`,
1252
1288
  `j.state < '${JOB_STATES.active}'`,
@@ -1257,6 +1293,18 @@ export function fetchNextJob(options, noSkipLocked = false) {
1257
1293
  // timestamp, so `<` would leave freshly-inserted jobs invisible until the clock ticks.
1258
1294
  // NOTIFY gating already uses `start_after <= now()` for the same reason.
1259
1295
  !ignoreStartAfter ? 'j.start_after <= now()' : '',
1296
+ keyStrictFifo ? 'j.id IN (SELECT id FROM strict_fifo_heads)' : '',
1297
+ keyStrictFifo
1298
+ ? `NOT EXISTS (
1299
+ SELECT 1
1300
+ FROM ${schema}.${table} b
1301
+ WHERE b.name = j.name
1302
+ AND b.singleton_key = j.singleton_key
1303
+ AND b.state IN ('${JOB_STATES.active}', '${JOB_STATES.retry}', '${JOB_STATES.failed}')
1304
+ AND b.policy = '${QUEUE_POLICIES.key_strict_fifo}'
1305
+ AND b.id <> j.id
1306
+ )`
1307
+ : '',
1260
1308
  hasIgnoreSingletons ? `COALESCE(j.singleton_key, '') <> ALL(${params.ignoreSingletonsParam})` : '',
1261
1309
  hasIgnoreGroups ? `(j.group_id IS NULL OR j.group_id <> ALL(${params.ignoreGroupsParam}))` : '',
1262
1310
  hasMinPriority ? `j.priority >= ${params.minPriorityParam}` : '',
@@ -1315,6 +1363,7 @@ export function fetchNextJob(options, noSkipLocked = false) {
1315
1363
  return {
1316
1364
  text: `
1317
1365
  WITH
1366
+ ${strictFifoHeadsCte}
1318
1367
  ${activeGroupCountMapCte}
1319
1368
  ${nextCte}
1320
1369
  ${singletonCte}
@@ -1462,6 +1511,26 @@ export function restoreJobs(schema, table) {
1462
1511
  AND id = ANY($2::uuid[])
1463
1512
  `;
1464
1513
  }
1514
+ // A `startAfter` string is either an absolute date time or a delay expressed as a Postgres
1515
+ // interval. A trailing 'Z' has always marked a date time; a leading ISO 8601 calendar date
1516
+ // (YYYY-MM-DD) marks one as well, which is what lets the other 8601 zone designators through
1517
+ // ('+00:00', '+05:30', '-08:00') along with zone-less and date-only strings — all of which
1518
+ // used to reach the interval cast and fail as 'invalid input syntax for type interval'.
1519
+ //
1520
+ // Recognition is only ever widened, so anything that resolves as an interval today still
1521
+ // does: bare seconds ('0', '300'), phrases ('5 minutes'), ISO 8601 durations ('PT1H') and
1522
+ // the year-month form ('2027-01', which Postgres reads as 2027 years 1 mon) carry neither
1523
+ // mark. A date time with an explicit offset resolves to that exact instant.
1524
+ //
1525
+ // A zone-less date time would otherwise be cast in the database session's TimeZone, so the
1526
+ // public entry points pin it to UTC before it gets here (Attorney.pinZonelessDateTime). This
1527
+ // cast is still session-TZ dependent for anything that reaches it unpinned, which is why the
1528
+ // pin lives at the boundary rather than in this expression: a caller may legitimately pass a
1529
+ // form Postgres resolves itself ('2027-01-01 08:00:00 America/New_York'), and rewriting those
1530
+ // in SQL would mean re-implementing timestamp parsing in a regex.
1531
+ function isDateTimeString(expression) {
1532
+ return `(right(${expression}, 1) = 'Z' OR ${expression} ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}')`;
1533
+ }
1465
1534
  export function insertJobs(schema, { table, name, returnId = true, notify = false }) {
1466
1535
  // When notify is enabled we always RETURN start_after so the wrapper below can gate
1467
1536
  // the NOTIFY on immediate availability, regardless of whether the caller wants ids.
@@ -1520,7 +1589,7 @@ export function insertJobs(schema, { table, name, returnId = true, notify = fals
1520
1589
  FROM (
1521
1590
  SELECT *,
1522
1591
  CASE
1523
- WHEN right("startAfter", 1) = 'Z' THEN CAST("startAfter" as timestamp with time zone)
1592
+ WHEN ${isDateTimeString('"startAfter"')} THEN CAST("startAfter" as timestamp with time zone)
1524
1593
  ELSE now() + CAST(COALESCE("startAfter",'0') as interval)
1525
1594
  END as start_after
1526
1595
  FROM json_to_recordset($1::json) as x (
@@ -1794,10 +1863,11 @@ function failJobsBody(schema, table, where, output, forceTerminal = false) {
1794
1863
  SELECT * FROM failed_jobs
1795
1864
  ),
1796
1865
  dlq_jobs as (
1797
- INSERT INTO ${schema}.job (name, data, output, retry_limit, retry_backoff, retry_delay, keep_until, deletion_seconds,
1798
- expire_seconds, source_name, source_id, source_created_on, source_retry_count, singleton_key, heartbeat_seconds)
1866
+ INSERT INTO ${schema}.job (name, priority, data, output, retry_limit, retry_backoff, retry_delay, keep_until, deletion_seconds,
1867
+ expire_seconds, source_name, source_id, source_created_on, source_retry_count, singleton_key, group_id, group_tier, heartbeat_seconds)
1799
1868
  SELECT
1800
1869
  r.dead_letter,
1870
+ r.priority,
1801
1871
  r.data,
1802
1872
  r.output,
1803
1873
  q.retry_limit,
@@ -1811,7 +1881,9 @@ function failJobsBody(schema, table, where, output, forceTerminal = false) {
1811
1881
  r.created_on,
1812
1882
  r.retry_count,
1813
1883
  r.singleton_key,
1814
- r.heartbeat_seconds
1884
+ r.group_id,
1885
+ r.group_tier,
1886
+ q.heartbeat_seconds
1815
1887
  FROM results r
1816
1888
  JOIN ${schema}.queue q ON q.name = r.dead_letter
1817
1889
  WHERE state = '${JOB_STATES.failed}'
@@ -2006,9 +2078,10 @@ export function insertRetryJob(schema, table) {
2006
2078
  export function insertDeadLetterJob(schema) {
2007
2079
  return `
2008
2080
  INSERT INTO ${schema}.job (name, data, output, retry_limit, retry_backoff, retry_delay, keep_until, deletion_seconds,
2009
- expire_seconds, source_name, source_id, source_created_on, source_retry_count, singleton_key, heartbeat_seconds)
2081
+ expire_seconds, source_name, source_id, source_created_on, source_retry_count, singleton_key, heartbeat_seconds,
2082
+ priority, group_id, group_tier)
2010
2083
  SELECT $1, $2, $3, q.retry_limit, q.retry_backoff, q.retry_delay, now() + q.retention_seconds * interval '1s', q.deletion_seconds,
2011
- q.expire_seconds, $4, $5, $6, $7, $8, $9
2084
+ q.expire_seconds, $4, $5, $6, $7, $8, q.heartbeat_seconds, $9, $10, $11
2012
2085
  FROM ${schema}.queue q WHERE q.name = $1
2013
2086
  `;
2014
2087
  }
@@ -2017,7 +2090,11 @@ export function insertDeadLetterJob(schema) {
2017
2090
  // oldest-first, capped at $4. The JOIN in `candidates` only matches jobs whose destination queue
2018
2091
  // exists, so legacy/orphaned jobs (NULL source_name, no override) are never deleted — they stay
2019
2092
  // in the DLQ rather than being lost. Re-created jobs get a new id, `created` state, retry_count 0,
2020
- // cleared output, NULL source_*, and the destination queue's current retry/retention/policy config.
2093
+ // cleared output, NULL source_*, and every queue-config column (retry/retention/policy/expiry/
2094
+ // heartbeat/dead_letter) from the destination queue as it is configured now — per-job overrides
2095
+ // from the original send() are not preserved, since the DLQ copy never stored them. `dead_letter`
2096
+ // is the same value send() falls back to, so a second terminal failure re-enters the DLQ.
2097
+ // Job-identity columns (priority, singleton_key, group_id, group_tier) are carried over instead.
2021
2098
  export function redriveJobs(schema, table) {
2022
2099
  return `
2023
2100
  WITH candidates AS (
@@ -2039,11 +2116,12 @@ export function redriveJobs(schema, table) {
2039
2116
  ins AS (
2040
2117
  INSERT INTO ${schema}.job
2041
2118
  (name, data, priority, retry_limit, retry_backoff, retry_delay, retry_delay_max,
2042
- expire_seconds, keep_until, deletion_seconds, policy, singleton_key, heartbeat_seconds)
2119
+ expire_seconds, keep_until, deletion_seconds, policy, singleton_key, group_id, group_tier,
2120
+ heartbeat_seconds, dead_letter)
2043
2121
  SELECT COALESCE($2, m.source_name), m.data, m.priority, q.retry_limit, q.retry_backoff,
2044
2122
  q.retry_delay, q.retry_delay_max, q.expire_seconds,
2045
2123
  now() + q.retention_seconds * interval '1s', q.deletion_seconds, q.policy,
2046
- m.singleton_key, m.heartbeat_seconds
2124
+ m.singleton_key, m.group_id, m.group_tier, q.heartbeat_seconds, q.dead_letter
2047
2125
  FROM moved m JOIN ${schema}.queue q ON q.name = COALESCE($2, m.source_name)
2048
2126
  -- A destination queue's short/stately policy can still collide on (name, singleton_key)
2049
2127
  -- if two redriven jobs share a key (job_i1/job_i3); dropping just that row here, matching
@@ -2099,11 +2177,11 @@ export function updateJob(schema, table, name, by, match, notify = false) {
2099
2177
  const ordering = (by === 'singletonKey' && match !== 'all')
2100
2178
  ? `ORDER BY job.created_on ${match === 'oldest' ? 'ASC' : 'DESC'} LIMIT 1`
2101
2179
  : '';
2102
- // Resolve the incoming startAfter the same way insertJobs does (absolute 'Z' timestamp vs.
2180
+ // Resolve the incoming startAfter the same way insertJobs does (absolute date time vs.
2103
2181
  // relative interval), falling back to the row's current start_after when not supplied.
2104
2182
  const resolvedStartAfter = `
2105
2183
  CASE WHEN jsonb_exists(o.data, 'startAfter')
2106
- THEN CASE WHEN right(o.data->>'startAfter', 1) = 'Z'
2184
+ THEN CASE WHEN ${isDateTimeString("o.data->>'startAfter'")}
2107
2185
  THEN (o.data->>'startAfter')::timestamptz
2108
2186
  ELSE now() + CAST(o.data->>'startAfter' AS interval) END
2109
2187
  ELSE job.start_after END`;
@@ -2599,7 +2677,8 @@ const POLICY_JOB_INDEXES = {
2599
2677
  2: QUEUE_POLICIES.singleton,
2600
2678
  3: QUEUE_POLICIES.stately,
2601
2679
  6: QUEUE_POLICIES.exclusive,
2602
- 8: QUEUE_POLICIES.key_strict_fifo
2680
+ 8: QUEUE_POLICIES.key_strict_fifo,
2681
+ 10: QUEUE_POLICIES.key_strict_fifo
2603
2682
  };
2604
2683
  // job_iN indexes with no policy gate — created on every job table regardless of policy
2605
2684
  // (throttle i4, fetch i5, group-concurrency i7, blocking i9).
@@ -2699,7 +2778,7 @@ export function expectedManagedFunctions(schema, partitioned) {
2699
2778
  export function expectedManagedIndexes(schema, partitioned, partitions = []) {
2700
2779
  const managed = (name, table, indexdef) => {
2701
2780
  const def = applyManifestSchema(indexdef, schema);
2702
- return { name, table, keys: indexKeysRaw(def), predicate: indexPredicateRaw(def), definition: displayIndexDefinition(def) };
2781
+ return { name, table, keys: indexKeysRaw(def), include: indexIncludeRaw(def), predicate: indexPredicateRaw(def), definition: displayIndexDefinition(def) };
2703
2782
  };
2704
2783
  const jobTable = partitioned ? COMMON_JOB_TABLE : 'job';
2705
2784
  const out = [];