@event-driven-io/emmett-postgresql 0.43.0-beta.15 → 0.43.0-beta.17

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.
package/dist/index.cjs CHANGED
@@ -9,7 +9,7 @@ let _event_driven_io_pongo_pg = require("@event-driven-io/pongo/pg");
9
9
  const emmettPrefix = "emt";
10
10
  const globalTag = "global";
11
11
  const defaultTag = `${"emt"}:default`;
12
- const unknownTag = `${"emt"}:unknown`;
12
+ const unknownTag$3 = `${"emt"}:unknown`;
13
13
  const globalNames = {
14
14
  module: `${"emt"}:module:${globalTag}`,
15
15
  tenant: `${"emt"}:tenant:${globalTag}`
@@ -99,36 +99,43 @@ const postgreSQLEventStoreMessageBatchPuller = ({ executor, batchSize, eachBatch
99
99
  let isRunning = false;
100
100
  let start;
101
101
  const pullMessages = async (options) => {
102
- let after;
103
102
  try {
104
- after = options.startFrom === "BEGINNING" ? 0n : options.startFrom === "END" ? (await readLastMessageGlobalPosition(executor)).currentGlobalPosition ?? 0n : (0, _event_driven_io_emmett.parseBigIntProcessorCheckpoint)(options.startFrom.lastCheckpoint);
105
- } catch (error) {
106
- options.started?.reject(error);
107
- throw error;
108
- }
109
- options.started?.resolve();
110
- const readMessagesOptions = {
111
- after,
112
- batchSize
113
- };
114
- let waitTime = 100;
115
- while (isRunning && !signal?.aborted) {
116
- const { messages, currentGlobalPosition, areMessagesLeft } = await readMessagesBatch(executor, readMessagesOptions);
117
- if (messages.length > 0) {
118
- const result = await eachBatch(messages);
119
- if (result && result.type === "STOP") {
103
+ let after;
104
+ try {
105
+ after = options.startFrom === "BEGINNING" ? 0n : options.startFrom === "END" ? (await readLastMessageGlobalPosition(executor)).currentGlobalPosition ?? 0n : (0, _event_driven_io_emmett.parseBigIntProcessorCheckpoint)(options.startFrom.lastCheckpoint);
106
+ } catch (error) {
107
+ console.log("Error occurred during reading last processor checkpoint:", error);
108
+ options.started?.reject(error);
109
+ throw error;
110
+ }
111
+ options.started?.resolve();
112
+ const readMessagesOptions = {
113
+ after,
114
+ batchSize
115
+ };
116
+ let waitTime = 100;
117
+ while (isRunning && !signal?.aborted) {
118
+ const { messages, currentGlobalPosition, areMessagesLeft } = await readMessagesBatch(executor, readMessagesOptions);
119
+ if (messages.length > 0) {
120
+ const result = await eachBatch(messages);
121
+ if (result && result.type === "STOP") {
122
+ isRunning = false;
123
+ break;
124
+ }
125
+ }
126
+ readMessagesOptions.after = currentGlobalPosition;
127
+ await new Promise((resolve) => setTimeout(resolve, waitTime));
128
+ if (stopWhen?.noMessagesLeft === true && !areMessagesLeft) {
129
+ console.log(`No messages left to process after reaching global position ${currentGlobalPosition}. Stopping the puller.`);
120
130
  isRunning = false;
121
131
  break;
122
132
  }
133
+ if (!areMessagesLeft) waitTime = Math.min(waitTime * 2, 1e3);
134
+ else waitTime = pullingFrequencyInMs;
123
135
  }
124
- readMessagesOptions.after = currentGlobalPosition;
125
- await new Promise((resolve) => setTimeout(resolve, waitTime));
126
- if (stopWhen?.noMessagesLeft === true && !areMessagesLeft) {
127
- isRunning = false;
128
- break;
129
- }
130
- if (!areMessagesLeft) waitTime = Math.min(waitTime * 2, 1e3);
131
- else waitTime = pullingFrequencyInMs;
136
+ } catch (error) {
137
+ console.log("Error occurred during message pulling:", error);
138
+ throw error;
132
139
  }
133
140
  };
134
141
  return {
@@ -240,7 +247,7 @@ CREATE OR REPLACE FUNCTION emt_try_acquire_processor_lock(
240
247
  p_processor_id TEXT,
241
248
  p_version INT,
242
249
  p_partition TEXT DEFAULT '${_event_driven_io_dumbo.SQL.plain(defaultTag)}',
243
- p_processor_instance_id TEXT DEFAULT '${_event_driven_io_dumbo.SQL.plain(unknownTag)}',
250
+ p_processor_instance_id TEXT DEFAULT '${_event_driven_io_dumbo.SQL.plain(unknownTag$3)}',
244
251
  p_projection_name TEXT DEFAULT NULL,
245
252
  p_projection_type VARCHAR(1) DEFAULT NULL,
246
253
  p_projection_kind TEXT DEFAULT NULL,
@@ -249,6 +256,9 @@ CREATE OR REPLACE FUNCTION emt_try_acquire_processor_lock(
249
256
  RETURNS TABLE (acquired BOOLEAN, checkpoint TEXT)
250
257
  LANGUAGE plpgsql
251
258
  AS $emt_try_acquire_processor_lock$
259
+ DECLARE
260
+ current_position TEXT;
261
+ v_current_time TIMESTAMPTZ := clock_timestamp();
252
262
  BEGIN
253
263
  RETURN QUERY
254
264
  WITH lock_check AS (
@@ -266,16 +276,16 @@ BEGIN
266
276
  created_at,
267
277
  last_updated
268
278
  )
269
- SELECT p_processor_id, p_partition, p_version, p_processor_instance_id, 'running', '${_event_driven_io_dumbo.SQL.plain(_event_driven_io_emmett.bigInt.toNormalizedString(0n))}', '0'::xid8, now(), now()
279
+ SELECT p_processor_id, p_partition, p_version, p_processor_instance_id, 'running', '${_event_driven_io_dumbo.SQL.plain(_event_driven_io_emmett.bigInt.toNormalizedString(0n))}', '0'::xid8, v_current_time, v_current_time
270
280
  WHERE (SELECT lock_acquired FROM lock_check) = true
271
281
  ON CONFLICT (processor_id, partition, version) DO UPDATE
272
282
  SET processor_instance_id = p_processor_instance_id,
273
283
  status = 'running',
274
- last_updated = now()
284
+ last_updated = v_current_time
275
285
  WHERE ${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}.processor_instance_id = p_processor_instance_id
276
- OR ${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}.processor_instance_id = '${_event_driven_io_dumbo.SQL.plain(unknownTag)}'
286
+ OR ${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}.processor_instance_id = '${_event_driven_io_dumbo.SQL.plain(unknownTag$3)}'
277
287
  OR ${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}.status = 'stopped'
278
- OR ${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}.last_updated < now() - (p_lock_timeout_seconds || ' seconds')::interval
288
+ OR ${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}.last_updated < v_current_time - (p_lock_timeout_seconds || ' seconds')::interval
279
289
  RETURNING last_processed_checkpoint
280
290
  ),
281
291
  projection_status AS (
@@ -307,7 +317,7 @@ CREATE OR REPLACE FUNCTION emt_release_processor_lock(
307
317
  p_processor_id TEXT,
308
318
  p_partition TEXT,
309
319
  p_version INT,
310
- p_processor_instance_id TEXT DEFAULT '${_event_driven_io_dumbo.SQL.plain(unknownTag)}',
320
+ p_processor_instance_id TEXT DEFAULT '${_event_driven_io_dumbo.SQL.plain(unknownTag$3)}',
311
321
  p_projection_name TEXT DEFAULT NULL
312
322
  )
313
323
  RETURNS BOOLEAN
@@ -327,7 +337,7 @@ BEGIN
327
337
 
328
338
  UPDATE ${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}
329
339
  SET status = 'stopped',
330
- processor_instance_id = '${_event_driven_io_dumbo.SQL.plain(unknownTag)}',
340
+ processor_instance_id = '${_event_driven_io_dumbo.SQL.plain(unknownTag$3)}',
331
341
  last_updated = now()
332
342
  WHERE processor_id = p_processor_id
333
343
  AND partition = p_partition
@@ -380,6 +390,7 @@ const tryAcquireProcessorLock = async (execute, options) => {
380
390
  projectionKind: options.projection?.kind ?? null,
381
391
  lockTimeoutSeconds: options.lockTimeoutSeconds ?? 300
382
392
  })));
393
+ console.log(`Lock acquisition attempt for processor '${options.processorId}' with processor instance '${options.processorInstanceId}' and lock '${options.lockKey}' resulted in: ${acquired}`);
383
394
  return acquired ? {
384
395
  acquired: true,
385
396
  checkpoint
@@ -405,6 +416,7 @@ const releaseProcessorLock = async (execute, options) => {
405
416
  processorInstanceId: options.processorInstanceId,
406
417
  projectionName: options.projectionName ?? null
407
418
  })));
419
+ console.log(`Lock for processor '${options.processorId}' with processor instance '${options.processorInstanceId}' and lock '${options.lockKey}' released: ${result}`);
408
420
  return result;
409
421
  };
410
422
 
@@ -416,17 +428,26 @@ const postgreSQLProcessorLock = (options) => {
416
428
  const lockKey = options.lockKey ?? toProcessorLockKey(options);
417
429
  return {
418
430
  tryAcquire: async (context) => {
419
- if (acquired) return true;
431
+ if (acquired) {
432
+ console.log(`Lock for processor '${options.processorId}' is already acquired by this instance. Reusing the lock.`);
433
+ return true;
434
+ }
420
435
  const result = await tryAcquireProcessorLockWithRetry(context.execute, {
421
436
  ...options,
422
437
  lockKey
423
438
  });
424
- if (!result.acquired && options.lockAcquisitionPolicy?.type !== "skip") throw new _event_driven_io_emmett.EmmettError(`Failed to acquire lock for processor '${options.processorId}'`);
439
+ if (!result.acquired && options.lockAcquisitionPolicy?.type !== "skip") {
440
+ console.log(`Failed to acquire lock for processor '${options.processorId}' with policy '${options.lockAcquisitionPolicy?.type}'.`);
441
+ throw new _event_driven_io_emmett.EmmettError(`Failed to acquire lock for processor '${options.processorId}'`);
442
+ }
425
443
  acquired = result.acquired;
426
444
  return acquired;
427
445
  },
428
446
  release: async (context) => {
429
- if (!acquired) return;
447
+ if (!acquired) {
448
+ console.log(`Lock for processor '${options.processorId}' is not acquired by this instance. Skipping release.`);
449
+ return;
450
+ }
430
451
  const { projection, ...releaseOptions } = options;
431
452
  await releaseProcessorLock(context.execute, {
432
453
  ...releaseOptions,
@@ -1217,156 +1238,118 @@ const appendEventsRaw = (execute, streamId, streamType, messages, options) => (0
1217
1238
  })));
1218
1239
 
1219
1240
  //#endregion
1220
- //#region src/eventStore/schema/migrations/0_38_7/0_38_7.migration.ts
1221
- const dropFutureConceptModuleAndTenantFunctions = _event_driven_io_dumbo.SQL`
1222
- DO $$
1223
- BEGIN
1224
- -- Check and drop functions related to future concept of modules and tenants
1225
- IF EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'add_module') THEN
1226
- DROP FUNCTION add_module(TEXT);
1227
- END IF;
1228
-
1229
- IF EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'add_tenant') THEN
1230
- DROP FUNCTION add_tenant(TEXT, TEXT);
1231
- END IF;
1232
-
1233
- IF EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'add_module_for_all_tenants') THEN
1234
- DROP FUNCTION add_module_for_all_tenants(TEXT);
1235
- END IF;
1236
-
1237
- IF EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'add_tenant_for_all_modules') THEN
1238
- DROP FUNCTION add_tenant_for_all_modules(TEXT);
1239
- END IF;
1240
- END $$;
1241
- `;
1242
- const dropOldAppendToSQLWithoutGlobalPositions = _event_driven_io_dumbo.SQL`
1243
- DO $$
1244
- DECLARE
1245
- v_current_return_type text;
1246
- BEGIN
1247
- -- Get the current return type definition as text
1248
- SELECT pg_get_function_result(p.oid)
1249
- INTO v_current_return_type
1250
- FROM pg_proc p
1251
- JOIN pg_namespace n ON p.pronamespace = n.oid
1252
- WHERE n.nspname = current_schema() -- or specify your schema
1253
- AND p.proname = 'emt_append_to_stream'
1254
- AND p.pronargs = 10; -- number of arguments
1255
-
1256
- -- Check if it contains the old column name
1257
- IF v_current_return_type IS NOT NULL AND
1258
- v_current_return_type LIKE '%last_global_position%' AND
1259
- v_current_return_type NOT LIKE '%global_positions%' THEN
1260
- DROP FUNCTION emt_append_to_stream(text[], jsonb[], jsonb[], text[], text[], text[], text, text, bigint, text);
1261
- RAISE NOTICE 'Old version of function dropped. Return type was: %', v_current_return_type;
1262
- END IF;
1263
- END $$;
1264
- `;
1265
- const migrationFromEventsToMessagesSQL = _event_driven_io_dumbo.SQL`
1266
- DO $$
1241
+ //#region src/eventStore/schema/storeProcessorCheckpoint.ts
1242
+ const storeSubscriptionCheckpointSQL = createFunctionIfDoesNotExistSQL("store_processor_checkpoint", _event_driven_io_dumbo.SQL`
1243
+ CREATE OR REPLACE FUNCTION store_processor_checkpoint(
1244
+ p_processor_id TEXT,
1245
+ p_version BIGINT,
1246
+ p_position TEXT,
1247
+ p_check_position TEXT,
1248
+ p_transaction_id xid8,
1249
+ p_partition TEXT DEFAULT '${_event_driven_io_dumbo.SQL.plain(defaultTag)}',
1250
+ p_processor_instance_id TEXT DEFAULT '${_event_driven_io_dumbo.SQL.plain(unknownTag$3)}'
1251
+ ) RETURNS INT AS $spc$
1267
1252
  DECLARE
1268
- partition_record RECORD;
1253
+ current_position TEXT;
1269
1254
  BEGIN
1270
- -- Rename the main table and its columns if it exists
1271
- IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'emt_events') THEN
1272
- -- Rename all partitions first
1273
- FOR partition_record IN
1274
- SELECT tablename
1275
- FROM pg_tables
1276
- WHERE tablename LIKE 'emt_events_%'
1277
- ORDER BY tablename DESC -- to handle child partitions first
1278
- LOOP
1279
- EXECUTE format('ALTER TABLE %I RENAME TO %I',
1280
- partition_record.tablename,
1281
- REPLACE(partition_record.tablename, 'events', 'messages'));
1282
- END LOOP;
1255
+ -- Handle the case when p_check_position is provided
1256
+ IF p_check_position IS NOT NULL THEN
1257
+ -- Try to update if the position matches p_check_position
1258
+ UPDATE "${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}"
1259
+ SET
1260
+ "last_processed_checkpoint" = p_position,
1261
+ "last_processed_transaction_id" = p_transaction_id,
1262
+ "last_updated" = now()
1263
+ WHERE "processor_id" = p_processor_id
1264
+ AND "last_processed_checkpoint" = p_check_position
1265
+ AND "partition" = p_partition
1266
+ AND "version" = p_version;
1283
1267
 
1284
- -- Rename the main table
1285
- ALTER TABLE emt_events RENAME TO emt_messages;
1286
-
1287
- -- Rename columns
1288
- ALTER TABLE emt_messages
1289
- RENAME COLUMN event_data TO message_data;
1290
- ALTER TABLE emt_messages
1291
- RENAME COLUMN event_metadata TO message_metadata;
1292
- ALTER TABLE emt_messages
1293
- RENAME COLUMN event_schema_version TO message_schema_version;
1294
- ALTER TABLE emt_messages
1295
- RENAME COLUMN event_type TO message_type;
1296
- ALTER TABLE emt_messages
1297
- RENAME COLUMN event_id TO message_id;
1298
- ALTER TABLE emt_messages
1299
- ADD COLUMN message_kind CHAR(1) NOT NULL DEFAULT 'E';
1268
+ IF FOUND THEN
1269
+ RETURN 1; -- Successfully updated
1270
+ END IF;
1300
1271
 
1301
- -- Rename sequence if it exists
1302
- IF EXISTS (SELECT 1 FROM pg_sequences WHERE sequencename = 'emt_global_event_position') THEN
1303
- ALTER SEQUENCE emt_global_event_position
1304
- RENAME TO emt_global_message_position;
1305
-
1306
- ALTER TABLE emt_messages
1307
- ALTER COLUMN global_position
1308
- SET DEFAULT nextval('emt_global_message_position');
1309
- END IF;
1310
- END IF;
1311
- END $$;`;
1312
- const migration_0_38_7_and_older = (0, _event_driven_io_dumbo.sqlMigration)("emt:postgresql:eventstore:0.38.7:migrate-events-to-messages", [
1313
- dropFutureConceptModuleAndTenantFunctions,
1314
- dropOldAppendToSQLWithoutGlobalPositions,
1315
- migrationFromEventsToMessagesSQL
1316
- ]);
1272
+ -- Retrieve the current position
1273
+ SELECT "last_processed_checkpoint" INTO current_position
1274
+ FROM "${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}"
1275
+ WHERE "processor_id" = p_processor_id
1276
+ AND "partition" = p_partition
1277
+ AND "version" = p_version;
1317
1278
 
1318
- //#endregion
1319
- //#region src/eventStore/schema/migrations/0_38_7/0_38_7.snapshot.ts
1320
- const schema_0_38_7 = _event_driven_io_dumbo.SQL`
1321
- DO $$
1322
- DECLARE
1323
- partition_record RECORD;
1324
- BEGIN
1325
- -- Rename the main table and its columns if it exists
1326
- IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'emt_events') THEN
1327
- -- Rename all partitions first
1328
- FOR partition_record IN
1329
- SELECT tablename
1330
- FROM pg_tables
1331
- WHERE tablename LIKE 'emt_events_%'
1332
- ORDER BY tablename DESC -- to handle child partitions first
1333
- LOOP
1334
- EXECUTE format('ALTER TABLE %I RENAME TO %I',
1335
- partition_record.tablename,
1336
- REPLACE(partition_record.tablename, 'events', 'messages'));
1337
- END LOOP;
1279
+ -- Return appropriate codes based on current position
1280
+ IF current_position = p_position THEN
1281
+ RETURN 0; -- Idempotent check: position already set
1282
+ ELSIF current_position > p_position THEN
1283
+ RETURN 3; -- Current ahead: another process has progressed further
1284
+ ELSE
1285
+ RETURN 2; -- Mismatch: check position doesn't match current
1286
+ END IF;
1287
+ END IF;
1338
1288
 
1339
- -- Rename the main table
1340
- ALTER TABLE emt_events RENAME TO emt_messages;
1341
-
1342
- -- Rename columns
1343
- ALTER TABLE emt_messages
1344
- RENAME COLUMN event_data TO message_data;
1345
- ALTER TABLE emt_messages
1346
- RENAME COLUMN event_metadata TO message_metadata;
1347
- ALTER TABLE emt_messages
1348
- RENAME COLUMN event_schema_version TO message_schema_version;
1349
- ALTER TABLE emt_messages
1350
- RENAME COLUMN event_type TO message_type;
1351
- ALTER TABLE emt_messages
1352
- RENAME COLUMN event_id TO message_id;
1353
- ALTER TABLE emt_messages
1354
- ADD COLUMN message_kind CHAR(1) NOT NULL DEFAULT 'E';
1289
+ -- Handle the case when p_check_position is NULL: Insert if not exists
1290
+ BEGIN
1291
+ INSERT INTO "${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}"("processor_id", "version", "last_processed_checkpoint", "partition", "last_processed_transaction_id", "created_at", "last_updated")
1292
+ VALUES (p_processor_id, p_version, p_position, p_partition, p_transaction_id, now(), now());
1293
+ RETURN 1; -- Successfully inserted
1294
+ EXCEPTION WHEN unique_violation THEN
1295
+ -- If insertion failed, it means the row already exists
1296
+ SELECT "last_processed_checkpoint" INTO current_position
1297
+ FROM "${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}"
1298
+ WHERE "processor_id" = p_processor_id
1299
+ AND "partition" = p_partition
1300
+ AND "version" = p_version;
1355
1301
 
1356
- -- Rename sequence if it exists
1357
- IF EXISTS (SELECT 1 FROM pg_sequences WHERE sequencename = 'emt_global_event_position') THEN
1358
- ALTER SEQUENCE emt_global_event_position
1359
- RENAME TO emt_global_message_position;
1360
-
1361
- ALTER TABLE emt_messages
1362
- ALTER COLUMN global_position
1363
- SET DEFAULT nextval('emt_global_message_position');
1364
- END IF;
1365
- END IF;
1366
- END $$;CREATE TABLE IF NOT EXISTS emt_streams(
1302
+ IF current_position = p_position THEN
1303
+ RETURN 0; -- Idempotent check: position already set
1304
+ ELSIF current_position > p_position THEN
1305
+ RETURN 3; -- Current ahead: another process has progressed further
1306
+ ELSE
1307
+ RETURN 2; -- Insertion failed, row already exists with different position
1308
+ END IF;
1309
+ END;
1310
+ END;
1311
+ $spc$ LANGUAGE plpgsql;
1312
+ `);
1313
+ const callStoreProcessorCheckpoint = (params) => _event_driven_io_dumbo.SQL`
1314
+ SELECT store_processor_checkpoint(
1315
+ ${params.processorId},
1316
+ ${params.version},
1317
+ ${params.position},
1318
+ ${params.checkPosition},
1319
+ pg_current_xact_id(),
1320
+ ${params.partition},
1321
+ ${params.processorInstanceId}
1322
+ ) as result;`;
1323
+ const storeProcessorCheckpoint = async (execute, options) => {
1324
+ try {
1325
+ const { result } = await (0, _event_driven_io_dumbo.single)(execute.command(callStoreProcessorCheckpoint({
1326
+ processorId: options.processorId,
1327
+ version: options.version ?? 1,
1328
+ position: options.newCheckpoint !== null ? options.newCheckpoint : null,
1329
+ checkPosition: options.lastProcessedCheckpoint !== null ? options.lastProcessedCheckpoint : null,
1330
+ partition: options.partition ?? defaultTag,
1331
+ processorInstanceId: options.processorInstanceId ?? unknownTag$3
1332
+ })));
1333
+ return result === 1 ? {
1334
+ success: true,
1335
+ newCheckpoint: options.newCheckpoint
1336
+ } : {
1337
+ success: false,
1338
+ reason: result === 0 ? "IGNORED" : result === 3 ? "CURRENT_AHEAD" : "MISMATCH"
1339
+ };
1340
+ } catch (error) {
1341
+ console.log(error);
1342
+ throw error;
1343
+ }
1344
+ };
1345
+
1346
+ //#endregion
1347
+ //#region src/eventStore/schema/tables.ts
1348
+ const streamsTableSQL = _event_driven_io_dumbo.SQL`
1349
+ CREATE TABLE IF NOT EXISTS ${_event_driven_io_dumbo.SQL.identifier(streamsTable.name)}(
1367
1350
  stream_id TEXT NOT NULL,
1368
1351
  stream_position BIGINT NOT NULL,
1369
- partition TEXT NOT NULL DEFAULT 'global',
1352
+ partition TEXT NOT NULL DEFAULT '${_event_driven_io_dumbo.SQL.plain(defaultTag)}',
1370
1353
  stream_type TEXT NOT NULL,
1371
1354
  stream_metadata JSONB NOT NULL,
1372
1355
  is_archived BOOLEAN NOT NULL DEFAULT FALSE,
@@ -1374,40 +1357,62 @@ END $$;CREATE TABLE IF NOT EXISTS emt_streams(
1374
1357
  ) PARTITION BY LIST (partition);
1375
1358
 
1376
1359
  CREATE UNIQUE INDEX IF NOT EXISTS idx_streams_unique
1377
- ON emt_streams(stream_id, partition, is_archived)
1378
- INCLUDE (stream_position);
1360
+ ON ${_event_driven_io_dumbo.SQL.identifier(streamsTable.name)}(stream_id, partition, is_archived)
1361
+ INCLUDE (stream_position);`;
1362
+ const messagesTableSQL = _event_driven_io_dumbo.SQL`
1379
1363
  CREATE SEQUENCE IF NOT EXISTS emt_global_message_position;
1380
1364
 
1381
- CREATE TABLE IF NOT EXISTS emt_messages(
1382
- stream_id TEXT NOT NULL,
1365
+ CREATE TABLE IF NOT EXISTS ${_event_driven_io_dumbo.SQL.identifier(messagesTable.name)}(
1383
1366
  stream_position BIGINT NOT NULL,
1384
- partition TEXT NOT NULL DEFAULT 'global',
1385
- message_kind CHAR(1) NOT NULL DEFAULT 'E',
1386
- message_data JSONB NOT NULL,
1387
- message_metadata JSONB NOT NULL,
1388
- message_schema_version TEXT NOT NULL,
1389
- message_type TEXT NOT NULL,
1390
- message_id TEXT NOT NULL,
1391
- is_archived BOOLEAN NOT NULL DEFAULT FALSE,
1392
1367
  global_position BIGINT DEFAULT nextval('emt_global_message_position'),
1393
1368
  transaction_id XID8 NOT NULL,
1394
1369
  created TIMESTAMPTZ NOT NULL DEFAULT now(),
1370
+ is_archived BOOLEAN NOT NULL DEFAULT FALSE,
1371
+ message_kind VARCHAR(1) NOT NULL DEFAULT 'E',
1372
+ stream_id TEXT NOT NULL,
1373
+ partition TEXT NOT NULL DEFAULT '${_event_driven_io_dumbo.SQL.plain(defaultTag)}',
1374
+ message_schema_version TEXT NOT NULL,
1375
+ message_id TEXT NOT NULL,
1376
+ message_type TEXT NOT NULL,
1377
+ message_data JSONB NOT NULL,
1378
+ message_metadata JSONB NOT NULL,
1395
1379
  PRIMARY KEY (stream_id, stream_position, partition, is_archived)
1380
+ ) PARTITION BY LIST (partition);`;
1381
+ const processorsTableSQL = _event_driven_io_dumbo.SQL`
1382
+ CREATE TABLE IF NOT EXISTS ${_event_driven_io_dumbo.SQL.identifier(processorsTable.name)}(
1383
+ last_processed_transaction_id XID8 NOT NULL,
1384
+ version INT NOT NULL DEFAULT 1,
1385
+ processor_id TEXT NOT NULL,
1386
+ partition TEXT NOT NULL DEFAULT '${_event_driven_io_dumbo.SQL.plain(defaultTag)}',
1387
+ status TEXT NOT NULL DEFAULT 'stopped',
1388
+ last_processed_checkpoint TEXT NOT NULL,
1389
+ processor_instance_id TEXT DEFAULT '${_event_driven_io_dumbo.SQL.plain(unknownTag$3)}',
1390
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
1391
+ last_updated TIMESTAMPTZ NOT NULL DEFAULT now(),
1392
+ PRIMARY KEY (processor_id, partition, version)
1396
1393
  ) PARTITION BY LIST (partition);
1397
- CREATE TABLE IF NOT EXISTS emt_subscriptions(
1398
- subscription_id TEXT NOT NULL,
1399
- version INT NOT NULL DEFAULT 1,
1400
- partition TEXT NOT NULL DEFAULT 'global',
1401
- last_processed_position BIGINT NOT NULL,
1402
- last_processed_transaction_id XID8 NOT NULL,
1403
- PRIMARY KEY (subscription_id, partition, version)
1394
+ `;
1395
+ const projectionsTableSQL = _event_driven_io_dumbo.SQL`
1396
+ CREATE TABLE IF NOT EXISTS ${_event_driven_io_dumbo.SQL.identifier(projectionsTable.name)}(
1397
+ version INT NOT NULL DEFAULT 1,
1398
+ type VARCHAR(1) NOT NULL,
1399
+ name TEXT NOT NULL,
1400
+ partition TEXT NOT NULL DEFAULT '${_event_driven_io_dumbo.SQL.plain(defaultTag)}',
1401
+ kind TEXT NOT NULL,
1402
+ status TEXT NOT NULL,
1403
+ definition JSONB NOT NULL DEFAULT '{}'::jsonb,
1404
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
1405
+ last_updated TIMESTAMPTZ NOT NULL DEFAULT now(),
1406
+ PRIMARY KEY (name, partition, version)
1404
1407
  ) PARTITION BY LIST (partition);
1405
- CREATE OR REPLACE FUNCTION emt_sanitize_name(input_name TEXT) RETURNS TEXT AS $$
1408
+ `;
1409
+ const sanitizeNameSQL = createFunctionIfDoesNotExistSQL("emt_sanitize_name", _event_driven_io_dumbo.SQL`CREATE OR REPLACE FUNCTION emt_sanitize_name(input_name TEXT) RETURNS TEXT AS $emt_sanitize_name$
1406
1410
  BEGIN
1407
1411
  RETURN REGEXP_REPLACE(input_name, '[^a-zA-Z0-9_]', '_', 'g');
1408
1412
  END;
1409
- $$ LANGUAGE plpgsql;
1410
- CREATE OR REPLACE FUNCTION emt_add_table_partition(tableName TEXT, partition_name TEXT) RETURNS void AS $$
1413
+ $emt_sanitize_name$ LANGUAGE plpgsql;`);
1414
+ const addTablePartitions = createFunctionIfDoesNotExistSQL("emt_add_table_partition", _event_driven_io_dumbo.SQL`
1415
+ CREATE OR REPLACE FUNCTION emt_add_table_partition(tableName TEXT, partition_name TEXT) RETURNS void AS $emt_add_table_partition$
1411
1416
  DECLARE
1412
1417
  v_main_partiton_name TEXT;
1413
1418
  v_active_partiton_name TEXT;
@@ -1437,20 +1442,215 @@ CREATE OR REPLACE FUNCTION emt_sanitize_name(input_name TEXT) RETURNS TEXT AS $$
1437
1442
  v_archived_partiton_name, v_main_partiton_name
1438
1443
  );
1439
1444
  END;
1440
- $$ LANGUAGE plpgsql;
1441
- CREATE OR REPLACE FUNCTION emt_add_partition(partition_name TEXT) RETURNS void AS $$
1445
+ $emt_add_table_partition$ LANGUAGE plpgsql;`);
1446
+ const addPartitionSQL = createFunctionIfDoesNotExistSQL("emt_add_partition", _event_driven_io_dumbo.SQL`
1447
+ CREATE OR REPLACE FUNCTION emt_add_partition(partition_name TEXT) RETURNS void AS $emt_add_partition$
1442
1448
  BEGIN
1443
- PERFORM emt_add_table_partition('emt_messages', partition_name);
1444
- PERFORM emt_add_table_partition('emt_streams', partition_name);
1449
+ PERFORM emt_add_table_partition('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}', partition_name);
1450
+ PERFORM emt_add_table_partition('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}', partition_name);
1451
+
1452
+ EXECUTE format('
1453
+ CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
1454
+ FOR VALUES IN (%L);',
1455
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}' || '_' || partition_name), '${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}', partition_name
1456
+ );
1445
1457
 
1446
1458
  EXECUTE format('
1447
1459
  CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
1448
1460
  FOR VALUES IN (%L);',
1449
- emt_sanitize_name('emt_subscriptions' || '_' || partition_name), 'emt_subscriptions', partition_name
1461
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(projectionsTable.name)}' || '_' || partition_name), '${_event_driven_io_dumbo.SQL.plain(projectionsTable.name)}', partition_name
1450
1462
  );
1451
1463
  END;
1452
- $$ LANGUAGE plpgsql;
1464
+ $emt_add_partition$ LANGUAGE plpgsql;`);
1465
+ const addModuleSQL = _event_driven_io_dumbo.SQL`
1466
+ CREATE OR REPLACE FUNCTION add_module(new_module TEXT) RETURNS void AS $$
1467
+ BEGIN
1468
+ -- For ${_event_driven_io_dumbo.SQL.plain(messagesTable.name)} table
1469
+ EXECUTE format('
1470
+ CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
1471
+ FOR VALUES IN (emt_sanitize_name(%L || ''__'' || %L)) PARTITION BY LIST (is_archived);',
1472
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || '${_event_driven_io_dumbo.SQL.plain(globalTag)}'), '${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}', new_module, '${_event_driven_io_dumbo.SQL.plain(globalTag)}'
1473
+ );
1474
+
1475
+ EXECUTE format('
1476
+ CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
1477
+ FOR VALUES IN (FALSE);',
1478
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || '${_event_driven_io_dumbo.SQL.plain(globalTag)}' || '_active'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || '${_event_driven_io_dumbo.SQL.plain(globalTag)}')
1479
+ );
1480
+
1481
+ EXECUTE format('
1482
+ CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
1483
+ FOR VALUES IN (TRUE);',
1484
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || '${_event_driven_io_dumbo.SQL.plain(globalTag)}' || '_archived'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || '${_event_driven_io_dumbo.SQL.plain(globalTag)}')
1485
+ );
1486
+
1487
+ -- For ${_event_driven_io_dumbo.SQL.plain(streamsTable.name)} table
1488
+ EXECUTE format('
1489
+ CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
1490
+ FOR VALUES IN (emt_sanitize_name(%L || ''__'' || %L)) PARTITION BY LIST (is_archived);',
1491
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || '${_event_driven_io_dumbo.SQL.plain(globalTag)}'), '${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}', new_module, '${_event_driven_io_dumbo.SQL.plain(globalTag)}'
1492
+ );
1493
+
1494
+ EXECUTE format('
1495
+ CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
1496
+ FOR VALUES IN (FALSE);',
1497
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || '${_event_driven_io_dumbo.SQL.plain(globalTag)}' || '_active'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || '${_event_driven_io_dumbo.SQL.plain(globalTag)}')
1498
+ );
1499
+
1500
+ EXECUTE format('
1501
+ CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
1502
+ FOR VALUES IN (TRUE);',
1503
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || '${_event_driven_io_dumbo.SQL.plain(globalTag)}' || '_archived'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || '${_event_driven_io_dumbo.SQL.plain(globalTag)}')
1504
+ );
1505
+ END;
1506
+ $$ LANGUAGE plpgsql;
1507
+ `;
1508
+ const addTenantSQL = _event_driven_io_dumbo.SQL`
1509
+ CREATE OR REPLACE FUNCTION add_tenant(new_module TEXT, new_tenant TEXT) RETURNS void AS $$
1510
+ BEGIN
1511
+ -- For ${_event_driven_io_dumbo.SQL.plain(messagesTable.name)} table
1512
+ EXECUTE format('
1513
+ CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
1514
+ FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
1515
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || new_tenant), '${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}', new_module, new_tenant
1516
+ );
1517
+
1518
+ EXECUTE format('
1519
+ CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
1520
+ FOR VALUES IN (FALSE);',
1521
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || new_tenant || '_active'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || new_tenant)
1522
+ );
1523
+
1524
+ EXECUTE format('
1525
+ CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
1526
+ FOR VALUES IN (TRUE);',
1527
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || new_tenant || '_archived'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || new_tenant)
1528
+ );
1529
+
1530
+ -- For ${_event_driven_io_dumbo.SQL.plain(streamsTable.name)} table
1531
+ EXECUTE format('
1532
+ CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
1533
+ FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
1534
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || new_tenant), '${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}', new_module, new_tenant
1535
+ );
1536
+
1537
+ EXECUTE format('
1538
+ CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
1539
+ FOR VALUES IN (FALSE);',
1540
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || new_tenant || '_active'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || new_tenant)
1541
+ );
1542
+
1543
+ EXECUTE format('
1544
+ CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
1545
+ FOR VALUES IN (TRUE);',
1546
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || new_tenant || '_archived'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || new_tenant)
1547
+ );
1548
+ END;
1549
+ $$ LANGUAGE plpgsql;
1550
+ `;
1551
+ const addModuleForAllTenantsSQL = _event_driven_io_dumbo.SQL`
1552
+ CREATE OR REPLACE FUNCTION add_module_for_all_tenants(new_module TEXT) RETURNS void AS $$
1553
+ DECLARE
1554
+ tenant_record RECORD;
1555
+ BEGIN
1556
+ PERFORM add_module(new_module);
1557
+
1558
+ FOR tenant_record IN SELECT DISTINCT tenant FROM ${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}
1559
+ LOOP
1560
+ -- For ${_event_driven_io_dumbo.SQL.plain(messagesTable.name)} table
1561
+ EXECUTE format('
1562
+ CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
1563
+ FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
1564
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || tenant_record.tenant), '${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}', new_module, tenant_record.tenant
1565
+ );
1566
+
1567
+ EXECUTE format('
1568
+ CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
1569
+ FOR VALUES IN (FALSE);',
1570
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || tenant_record.tenant || '_active'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || tenant_record.tenant)
1571
+ );
1572
+
1573
+ EXECUTE format('
1574
+ CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
1575
+ FOR VALUES IN (TRUE);',
1576
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || tenant_record.tenant || '_archived'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || tenant_record.tenant)
1577
+ );
1578
+
1579
+ -- For ${_event_driven_io_dumbo.SQL.plain(streamsTable.name)} table
1580
+ EXECUTE format('
1581
+ CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
1582
+ FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
1583
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant), '${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}', new_module, tenant_record.tenant
1584
+ );
1585
+
1586
+ EXECUTE format('
1587
+ CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
1588
+ FOR VALUES IN (FALSE);',
1589
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant || '_active'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant)
1590
+ );
1591
+
1592
+ EXECUTE format('
1593
+ CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
1594
+ FOR VALUES IN (TRUE);',
1595
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant || '_archived'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant)
1596
+ );
1597
+ END LOOP;
1598
+ END;
1599
+ $$ LANGUAGE plpgsql;
1600
+ `;
1601
+ const addTenantForAllModulesSQL = _event_driven_io_dumbo.SQL`
1602
+ CREATE OR REPLACE FUNCTION add_tenant_for_all_modules(new_tenant TEXT) RETURNS void AS $$
1603
+ DECLARE
1604
+ module_record RECORD;
1605
+ BEGIN
1606
+ FOR module_record IN SELECT DISTINCT partitionname FROM pg_partman.part_config WHERE parent_table = '${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}'
1607
+ LOOP
1608
+ -- For ${_event_driven_io_dumbo.SQL.plain(messagesTable.name)} table
1609
+ EXECUTE format('
1610
+ CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
1611
+ FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
1612
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || module_record.partitionname || '__' || new_tenant), '${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}', module_record.partitionname, new_tenant
1613
+ );
1614
+
1615
+ EXECUTE format('
1616
+ CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
1617
+ FOR VALUES IN (FALSE);',
1618
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || module_record.partitionname || '__' || new_tenant || '_active'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || module_record.partitionname || '__' || new_tenant)
1619
+ );
1620
+
1621
+ EXECUTE format('
1622
+ CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
1623
+ FOR VALUES IN (TRUE);',
1624
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || module_record.partitionname || '__' || new_tenant || '_archived'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || module_record.partitionname || '__' || new_tenant)
1625
+ );
1626
+
1627
+ -- For ${_event_driven_io_dumbo.SQL.plain(streamsTable.name)} table
1628
+ EXECUTE format('
1629
+ CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
1630
+ FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
1631
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant), '${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}', module_record.partitionname, new_tenant
1632
+ );
1633
+
1634
+ EXECUTE format('
1635
+ CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
1636
+ FOR VALUES IN (FALSE);',
1637
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant || '_active'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant)
1638
+ );
1639
+
1640
+ EXECUTE format('
1641
+ CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
1642
+ FOR VALUES IN (TRUE);',
1643
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant || '_archived'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant)
1644
+ );
1645
+ END LOOP;
1646
+ END;
1647
+ $$ LANGUAGE plpgsql;
1648
+ `;
1649
+ const addDefaultPartitionSQL = _event_driven_io_dumbo.SQL`SELECT emt_add_partition('${_event_driven_io_dumbo.SQL.plain(defaultTag)}');`;
1453
1650
 
1651
+ //#endregion
1652
+ //#region src/eventStore/schema/migrations/0_38_7/0_38_7.migration.ts
1653
+ const dropFutureConceptModuleAndTenantFunctions = _event_driven_io_dumbo.SQL`
1454
1654
  DO $$
1455
1655
  BEGIN
1456
1656
  -- Check and drop functions related to future concept of modules and tenants
@@ -1470,9 +1670,9 @@ CREATE OR REPLACE FUNCTION emt_sanitize_name(input_name TEXT) RETURNS TEXT AS $$
1470
1670
  DROP FUNCTION add_tenant_for_all_modules(TEXT);
1471
1671
  END IF;
1472
1672
  END $$;
1473
-
1474
-
1475
- DO $$
1673
+ `;
1674
+ const dropOldAppendToSQLWithoutGlobalPositions = _event_driven_io_dumbo.SQL`
1675
+ DO $$
1476
1676
  DECLARE
1477
1677
  v_current_return_type text;
1478
1678
  BEGIN
@@ -1491,158 +1691,156 @@ CREATE OR REPLACE FUNCTION emt_sanitize_name(input_name TEXT) RETURNS TEXT AS $$
1491
1691
  v_current_return_type NOT LIKE '%global_positions%' THEN
1492
1692
  DROP FUNCTION emt_append_to_stream(text[], jsonb[], jsonb[], text[], text[], text[], text, text, bigint, text);
1493
1693
  RAISE NOTICE 'Old version of function dropped. Return type was: %', v_current_return_type;
1494
- END IF;
1495
- END $$;
1496
- CREATE OR REPLACE FUNCTION emt_append_to_stream(
1497
- v_message_ids text[],
1498
- v_messages_data jsonb[],
1499
- v_messages_metadata jsonb[],
1500
- v_message_schema_versions text[],
1501
- v_message_types text[],
1502
- v_message_kinds text[],
1503
- v_stream_id text,
1504
- v_stream_type text,
1505
- v_expected_stream_position bigint DEFAULT NULL,
1506
- v_partition text DEFAULT emt_sanitize_name('default_partition')
1507
- ) RETURNS TABLE (
1508
- success boolean,
1509
- next_stream_position bigint,
1510
- global_positions bigint[],
1511
- transaction_id xid8
1512
- ) LANGUAGE plpgsql
1513
- AS $$
1514
- DECLARE
1515
- v_next_stream_position bigint;
1516
- v_position bigint;
1517
- v_updated_rows int;
1518
- v_transaction_id xid8;
1519
- v_global_positions bigint[];
1520
- BEGIN
1521
- v_transaction_id := pg_current_xact_id();
1522
-
1523
- IF v_expected_stream_position IS NULL THEN
1524
- SELECT COALESCE(
1525
- (SELECT stream_position
1526
- FROM emt_streams
1527
- WHERE stream_id = v_stream_id
1528
- AND partition = v_partition
1529
- AND is_archived = FALSE
1530
- LIMIT 1),
1531
- 0
1532
- ) INTO v_expected_stream_position;
1533
- END IF;
1534
-
1535
- v_next_stream_position := v_expected_stream_position + array_upper(v_messages_data, 1);
1536
-
1537
- IF v_expected_stream_position = 0 THEN
1538
- INSERT INTO emt_streams
1539
- (stream_id, stream_position, partition, stream_type, stream_metadata, is_archived)
1540
- VALUES
1541
- (v_stream_id, v_next_stream_position, v_partition, v_stream_type, '{}', FALSE);
1542
- ELSE
1543
- UPDATE emt_streams as s
1544
- SET stream_position = v_next_stream_position
1545
- WHERE stream_id = v_stream_id AND stream_position = v_expected_stream_position AND partition = v_partition AND is_archived = FALSE;
1546
-
1547
- get diagnostics v_updated_rows = row_count;
1548
1694
 
1549
- IF v_updated_rows = 0 THEN
1550
- RETURN QUERY SELECT FALSE, NULL::bigint, NULL::bigint[], NULL::xid8;
1551
- RETURN;
1552
- END IF;
1695
+ CREATE OR REPLACE FUNCTION emt_append_to_stream(
1696
+ v_message_ids text[],
1697
+ v_messages_data jsonb[],
1698
+ v_messages_metadata jsonb[],
1699
+ v_message_schema_versions text[],
1700
+ v_message_types text[],
1701
+ v_message_kinds text[],
1702
+ v_stream_id text,
1703
+ v_stream_type text,
1704
+ v_expected_stream_position bigint DEFAULT NULL,
1705
+ v_partition text DEFAULT emt_sanitize_name('default_partition')
1706
+ ) RETURNS TABLE (
1707
+ success boolean,
1708
+ next_stream_position bigint,
1709
+ global_positions bigint[],
1710
+ transaction_id xid8
1711
+ ) LANGUAGE plpgsql
1712
+ AS $emt_append_to_stream$
1713
+ DECLARE
1714
+ v_next_stream_position bigint;
1715
+ v_position bigint;
1716
+ v_updated_rows int;
1717
+ v_transaction_id xid8;
1718
+ v_global_positions bigint[];
1719
+ BEGIN
1720
+ v_transaction_id := pg_current_xact_id();
1721
+
1722
+ IF v_expected_stream_position IS NULL THEN
1723
+ SELECT COALESCE(
1724
+ (SELECT stream_position
1725
+ FROM emt_streams
1726
+ WHERE stream_id = v_stream_id
1727
+ AND partition = v_partition
1728
+ AND is_archived = FALSE
1729
+ LIMIT 1),
1730
+ 0
1731
+ ) INTO v_expected_stream_position;
1732
+ END IF;
1733
+
1734
+ v_next_stream_position := v_expected_stream_position + array_upper(v_messages_data, 1);
1735
+
1736
+ IF v_expected_stream_position = 0 THEN
1737
+ INSERT INTO emt_streams
1738
+ (stream_id, stream_position, partition, stream_type, stream_metadata, is_archived)
1739
+ VALUES
1740
+ (v_stream_id, v_next_stream_position, v_partition, v_stream_type, '{}', FALSE);
1741
+ ELSE
1742
+ UPDATE emt_streams as s
1743
+ SET stream_position = v_next_stream_position
1744
+ WHERE stream_id = v_stream_id AND stream_position = v_expected_stream_position AND partition = v_partition AND is_archived = FALSE;
1745
+
1746
+ get diagnostics v_updated_rows = row_count;
1747
+
1748
+ IF v_updated_rows = 0 THEN
1749
+ RETURN QUERY SELECT FALSE, NULL::bigint, NULL::bigint[], NULL::xid8;
1750
+ RETURN;
1751
+ END IF;
1752
+ END IF;
1753
+
1754
+ WITH ev AS (
1755
+ SELECT row_number() OVER () + v_expected_stream_position AS stream_position,
1756
+ message_data,
1757
+ message_metadata,
1758
+ schema_version,
1759
+ message_id,
1760
+ message_type,
1761
+ message_kind
1762
+ FROM (
1763
+ SELECT *
1764
+ FROM
1765
+ unnest(v_message_ids, v_messages_data, v_messages_metadata, v_message_schema_versions, v_message_types, v_message_kinds)
1766
+ AS message(message_id, message_data, message_metadata, schema_version, message_type, message_kind)
1767
+ ) AS message
1768
+ ),
1769
+ all_messages_insert AS (
1770
+ INSERT INTO emt_messages
1771
+ (stream_id, stream_position, partition, message_data, message_metadata, message_schema_version, message_type, message_kind, message_id, transaction_id)
1772
+ SELECT
1773
+ v_stream_id, ev.stream_position, v_partition, ev.message_data, ev.message_metadata, ev.schema_version, ev.message_type, ev.message_kind, ev.message_id, v_transaction_id
1774
+ FROM ev
1775
+ RETURNING global_position
1776
+ )
1777
+ SELECT
1778
+ array_agg(global_position ORDER BY global_position) INTO v_global_positions
1779
+ FROM
1780
+ all_messages_insert;
1781
+
1782
+ RETURN QUERY SELECT TRUE, v_next_stream_position, v_global_positions, v_transaction_id;
1783
+ END;
1784
+ $emt_append_to_stream$;
1553
1785
  END IF;
1554
-
1555
- WITH ev AS (
1556
- SELECT row_number() OVER () + v_expected_stream_position AS stream_position,
1557
- message_data,
1558
- message_metadata,
1559
- schema_version,
1560
- message_id,
1561
- message_type,
1562
- message_kind
1563
- FROM (
1564
- SELECT *
1565
- FROM
1566
- unnest(v_message_ids, v_messages_data, v_messages_metadata, v_message_schema_versions, v_message_types, v_message_kinds)
1567
- AS message(message_id, message_data, message_metadata, schema_version, message_type, message_kind)
1568
- ) AS message
1569
- ),
1570
- all_messages_insert AS (
1571
- INSERT INTO emt_messages
1572
- (stream_id, stream_position, partition, message_data, message_metadata, message_schema_version, message_type, message_kind, message_id, transaction_id)
1573
- SELECT
1574
- v_stream_id, ev.stream_position, v_partition, ev.message_data, ev.message_metadata, ev.schema_version, ev.message_type, ev.message_kind, ev.message_id, v_transaction_id
1575
- FROM ev
1576
- RETURNING global_position
1577
- )
1578
- SELECT
1579
- array_agg(global_position ORDER BY global_position) INTO v_global_positions
1580
- FROM
1581
- all_messages_insert;
1582
-
1583
- RETURN QUERY SELECT TRUE, v_next_stream_position, v_global_positions, v_transaction_id;
1584
- END;
1585
- $$;
1586
- SELECT emt_add_partition('emt:default');
1587
- CREATE OR REPLACE FUNCTION store_subscription_checkpoint(
1588
- p_subscription_id VARCHAR(100),
1589
- p_version BIGINT,
1590
- p_position BIGINT,
1591
- p_check_position BIGINT,
1592
- p_transaction_id xid8,
1593
- p_partition TEXT DEFAULT 'emt:default'
1594
- ) RETURNS INT AS $$
1786
+ END $$;
1787
+ `;
1788
+ const migrationFromEventsToMessagesSQL = _event_driven_io_dumbo.SQL`
1789
+ DO $$
1595
1790
  DECLARE
1596
- current_position BIGINT;
1791
+ partition_record RECORD;
1597
1792
  BEGIN
1598
- -- Handle the case when p_check_position is provided
1599
- IF p_check_position IS NOT NULL THEN
1600
- -- Try to update if the position matches p_check_position
1601
- UPDATE "emt_subscriptions"
1602
- SET
1603
- "last_processed_position" = p_position,
1604
- "last_processed_transaction_id" = p_transaction_id
1605
- WHERE "subscription_id" = p_subscription_id AND "last_processed_position" = p_check_position AND "partition" = p_partition;
1606
-
1607
- IF FOUND THEN
1608
- RETURN 1; -- Successfully updated
1609
- END IF;
1610
-
1611
- -- Retrieve the current position
1612
- SELECT "last_processed_position" INTO current_position
1613
- FROM "emt_subscriptions"
1614
- WHERE "subscription_id" = p_subscription_id AND "partition" = p_partition;
1793
+ -- Rename the main table and its columns if it exists
1794
+ IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'emt_events') THEN
1795
+ -- Rename all partitions first
1796
+ FOR partition_record IN
1797
+ SELECT tablename
1798
+ FROM pg_tables
1799
+ WHERE tablename LIKE 'emt_events_%'
1800
+ ORDER BY tablename DESC -- to handle child partitions first
1801
+ LOOP
1802
+ EXECUTE format('ALTER TABLE %I RENAME TO %I',
1803
+ partition_record.tablename,
1804
+ REPLACE(partition_record.tablename, 'events', 'messages'));
1805
+ END LOOP;
1615
1806
 
1616
- -- Return appropriate codes based on current position
1617
- IF current_position = p_position THEN
1618
- RETURN 0; -- Idempotent check: position already set
1619
- ELSIF current_position > p_check_position THEN
1620
- RETURN 2; -- Failure: current position is greater
1621
- ELSE
1622
- RETURN 2; -- Default failure case for mismatched positions
1623
- END IF;
1624
- END IF;
1807
+ -- Rename the main table
1808
+ ALTER TABLE emt_events RENAME TO emt_messages;
1809
+
1810
+ -- Rename columns
1811
+ ALTER TABLE emt_messages
1812
+ RENAME COLUMN event_data TO message_data;
1813
+ ALTER TABLE emt_messages
1814
+ RENAME COLUMN event_metadata TO message_metadata;
1815
+ ALTER TABLE emt_messages
1816
+ RENAME COLUMN event_schema_version TO message_schema_version;
1817
+ ALTER TABLE emt_messages
1818
+ RENAME COLUMN event_type TO message_type;
1819
+ ALTER TABLE emt_messages
1820
+ RENAME COLUMN event_id TO message_id;
1821
+ ALTER TABLE emt_messages
1822
+ ADD COLUMN message_kind CHAR(1) NOT NULL DEFAULT 'E';
1625
1823
 
1626
- -- Handle the case when p_check_position is NULL: Insert if not exists
1627
- BEGIN
1628
- INSERT INTO "emt_subscriptions"("subscription_id", "version", "last_processed_position", "partition", "last_processed_transaction_id")
1629
- VALUES (p_subscription_id, p_version, p_position, p_partition, p_transaction_id);
1630
- RETURN 1; -- Successfully inserted
1631
- EXCEPTION WHEN unique_violation THEN
1632
- -- If insertion failed, it means the row already exists
1633
- SELECT "last_processed_position" INTO current_position
1634
- FROM "emt_subscriptions"
1635
- WHERE "subscription_id" = p_subscription_id AND "partition" = p_partition;
1824
+ -- Rename sequence if it exists
1825
+ IF EXISTS (SELECT 1 FROM pg_sequences WHERE sequencename = 'emt_global_event_position') THEN
1826
+ ALTER SEQUENCE emt_global_event_position
1827
+ RENAME TO emt_global_message_position;
1828
+
1829
+ ALTER TABLE emt_messages
1830
+ ALTER COLUMN global_position
1831
+ SET DEFAULT nextval('emt_global_message_position');
1832
+ END IF;
1833
+ END IF;
1834
+ END $$;`;
1835
+ const migration_0_38_7_and_older = (0, _event_driven_io_dumbo.sqlMigration)("emt:postgresql:eventstore:0.38.7:migrate-events-to-messages:with_append_to_stream_update", [
1836
+ dropFutureConceptModuleAndTenantFunctions,
1837
+ dropOldAppendToSQLWithoutGlobalPositions,
1838
+ migrationFromEventsToMessagesSQL
1839
+ ]);
1636
1840
 
1637
- IF current_position = p_position THEN
1638
- RETURN 0; -- Idempotent check: position already set
1639
- ELSE
1640
- RETURN 2; -- Insertion failed, row already exists with different position
1641
- END IF;
1642
- END;
1643
- END;
1644
- $$ LANGUAGE plpgsql;
1645
- `;
1841
+ //#endregion
1842
+ //#region src/eventStore/schema/migrations/0_38_7/index.ts
1843
+ const migrations_0_38_7 = [migration_0_38_7_and_older];
1646
1844
 
1647
1845
  //#endregion
1648
1846
  //#region src/eventStore/schema/migrations/0_42_0/0_42_0.migration.ts
@@ -2177,1064 +2375,216 @@ END;
2177
2375
  $emt_deactivate_projection$;
2178
2376
  `;
2179
2377
  const migration_0_42_0_2_AddProcessorProjectionFunctions = (0, _event_driven_io_dumbo.sqlMigration)("emt:postgresql:eventstore:0.42.0-2:add-processor-projection-functions", [migration_0_42_0_2_AddProcessorProjectionFunctionsSQL]);
2378
+ const migration_0_42_0_3_FixProcessorLockTimeoutSQL = _event_driven_io_dumbo.SQL`
2379
+ CREATE OR REPLACE FUNCTION emt_try_acquire_processor_lock(
2380
+ p_lock_key BIGINT,
2381
+ p_processor_id TEXT,
2382
+ p_version INT,
2383
+ p_partition TEXT DEFAULT '${_event_driven_io_dumbo.SQL.plain(defaultTag)}',
2384
+ p_processor_instance_id TEXT DEFAULT '${_event_driven_io_dumbo.SQL.plain(unknownTag$3)}',
2385
+ p_projection_name TEXT DEFAULT NULL,
2386
+ p_projection_type VARCHAR(1) DEFAULT NULL,
2387
+ p_projection_kind TEXT DEFAULT NULL,
2388
+ p_lock_timeout_seconds INT DEFAULT 300
2389
+ )
2390
+ RETURNS TABLE (acquired BOOLEAN, checkpoint TEXT)
2391
+ LANGUAGE plpgsql
2392
+ AS $emt_try_acquire_processor_lock$
2393
+ DECLARE
2394
+ current_position TEXT;
2395
+ v_current_time TIMESTAMPTZ := clock_timestamp();
2396
+ BEGIN
2397
+ RETURN QUERY
2398
+ WITH lock_check AS (
2399
+ SELECT pg_try_advisory_xact_lock(p_lock_key) AS lock_acquired
2400
+ ),
2401
+ ownership_check AS (
2402
+ INSERT INTO emt_processors (
2403
+ processor_id,
2404
+ partition,
2405
+ version,
2406
+ processor_instance_id,
2407
+ status,
2408
+ last_processed_checkpoint,
2409
+ last_processed_transaction_id,
2410
+ created_at,
2411
+ last_updated
2412
+ )
2413
+ SELECT p_processor_id, p_partition, p_version, p_processor_instance_id, 'running', '0000000000000000000', '0'::xid8, v_current_time, v_current_time
2414
+ WHERE (SELECT lock_acquired FROM lock_check) = true
2415
+ ON CONFLICT (processor_id, partition, version) DO UPDATE
2416
+ SET processor_instance_id = p_processor_instance_id,
2417
+ status = 'running',
2418
+ last_updated = v_current_time
2419
+ WHERE emt_processors.processor_instance_id = p_processor_instance_id
2420
+ OR emt_processors.processor_instance_id = 'emt:unknown'
2421
+ OR emt_processors.status = 'stopped'
2422
+ OR emt_processors.last_updated < v_current_time - (p_lock_timeout_seconds || ' seconds')::interval
2423
+ RETURNING last_processed_checkpoint
2424
+ ),
2425
+ projection_status AS (
2426
+ INSERT INTO emt_projections (
2427
+ name,
2428
+ partition,
2429
+ version,
2430
+ type,
2431
+ kind,
2432
+ status,
2433
+ definition
2434
+ )
2435
+ SELECT p_projection_name, p_partition, p_version, p_projection_type, p_projection_kind, 'async_processing', '{}'::jsonb
2436
+ WHERE p_projection_name IS NOT NULL
2437
+ AND (SELECT last_processed_checkpoint FROM ownership_check) IS NOT NULL
2438
+ ON CONFLICT (name, partition, version) DO UPDATE
2439
+ SET status = 'async_processing'
2440
+ RETURNING name
2441
+ )
2442
+ SELECT
2443
+ (SELECT COUNT(*) > 0 FROM ownership_check),
2444
+ (SELECT oc.last_processed_checkpoint FROM ownership_check oc);
2445
+ END;
2446
+ $emt_try_acquire_processor_lock$;
2447
+ `;
2448
+ const migration_0_42_0_3_FixProcessorLockTimeout = (0, _event_driven_io_dumbo.sqlMigration)("emt:postgresql:eventstore:0.42.0-3:fix-processor-lock-timeout", [migration_0_42_0_3_FixProcessorLockTimeoutSQL]);
2180
2449
 
2181
2450
  //#endregion
2182
- //#region src/eventStore/schema/migrations/0_42_0/0_42_0.snapshot.ts
2183
- const schema_0_42_0 = _event_driven_io_dumbo.SQL`
2184
- CREATE TABLE IF NOT EXISTS emt_streams(
2185
- stream_id TEXT NOT NULL,
2186
- stream_position BIGINT NOT NULL,
2187
- partition TEXT NOT NULL DEFAULT 'emt:default',
2188
- stream_type TEXT NOT NULL,
2189
- stream_metadata JSONB NOT NULL,
2190
- is_archived BOOLEAN NOT NULL DEFAULT FALSE,
2191
- PRIMARY KEY (stream_id, partition, is_archived)
2192
- ) PARTITION BY LIST (partition);
2193
-
2194
- CREATE UNIQUE INDEX IF NOT EXISTS idx_streams_unique
2195
- ON emt_streams(stream_id, partition, is_archived)
2196
- INCLUDE (stream_position);
2197
- CREATE SEQUENCE IF NOT EXISTS emt_global_message_position;
2451
+ //#region src/eventStore/schema/migrations/0_42_0/index.ts
2452
+ const migrations_0_42_0 = [
2453
+ migration_0_42_0_FromSubscriptionsToProcessors,
2454
+ migration_0_42_0_2_AddProcessorProjectionFunctions,
2455
+ migration_0_42_0_3_FixProcessorLockTimeout
2456
+ ];
2198
2457
 
2199
- CREATE TABLE IF NOT EXISTS emt_messages(
2200
- stream_position BIGINT NOT NULL,
2201
- global_position BIGINT DEFAULT nextval('emt_global_message_position'),
2202
- transaction_id XID8 NOT NULL,
2203
- created TIMESTAMPTZ NOT NULL DEFAULT now(),
2204
- is_archived BOOLEAN NOT NULL DEFAULT FALSE,
2205
- message_kind VARCHAR(1) NOT NULL DEFAULT 'E',
2206
- stream_id TEXT NOT NULL,
2207
- partition TEXT NOT NULL DEFAULT 'emt:default',
2208
- message_schema_version TEXT NOT NULL,
2209
- message_id TEXT NOT NULL,
2210
- message_type TEXT NOT NULL,
2211
- message_data JSONB NOT NULL,
2212
- message_metadata JSONB NOT NULL,
2213
- PRIMARY KEY (stream_id, stream_position, partition, is_archived)
2214
- ) PARTITION BY LIST (partition);
2215
- CREATE TABLE IF NOT EXISTS emt_projections(
2216
- version INT NOT NULL DEFAULT 1,
2217
- type VARCHAR(1) NOT NULL,
2218
- name TEXT NOT NULL,
2219
- partition TEXT NOT NULL DEFAULT 'emt:default',
2220
- kind TEXT NOT NULL,
2221
- status TEXT NOT NULL,
2222
- definition JSONB NOT NULL DEFAULT '{}'::jsonb,
2223
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
2224
- last_updated TIMESTAMPTZ NOT NULL DEFAULT now(),
2225
- PRIMARY KEY (name, partition, version)
2226
- ) PARTITION BY LIST (partition);
2227
-
2228
- CREATE TABLE IF NOT EXISTS emt_processors(
2229
- last_processed_transaction_id XID8 NOT NULL,
2230
- version INT NOT NULL DEFAULT 1,
2231
- processor_id TEXT NOT NULL,
2232
- partition TEXT NOT NULL DEFAULT 'emt:default',
2233
- status TEXT NOT NULL DEFAULT 'stopped',
2234
- last_processed_checkpoint TEXT NOT NULL,
2235
- processor_instance_id TEXT DEFAULT 'emt:unknown',
2236
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
2237
- last_updated TIMESTAMPTZ NOT NULL DEFAULT now(),
2238
- PRIMARY KEY (processor_id, partition, version)
2239
- ) PARTITION BY LIST (partition);
2240
-
2241
- DO $$
2242
- BEGIN
2243
- IF NOT EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'emt_sanitize_name') THEN
2244
- CREATE OR REPLACE FUNCTION emt_sanitize_name(input_name TEXT) RETURNS TEXT AS $emt_sanitize_name$
2245
- BEGIN
2246
- RETURN REGEXP_REPLACE(input_name, '[^a-zA-Z0-9_]', '_', 'g');
2247
- END;
2248
- $emt_sanitize_name$ LANGUAGE plpgsql;
2249
- END IF;
2250
- END $$;
2251
-
2252
- DO $$
2253
- BEGIN
2254
- IF NOT EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'emt_add_table_partition') THEN
2255
-
2256
- CREATE OR REPLACE FUNCTION emt_add_table_partition(tableName TEXT, partition_name TEXT) RETURNS void AS $emt_add_table_partition$
2257
- DECLARE
2258
- v_main_partiton_name TEXT;
2259
- v_active_partiton_name TEXT;
2260
- v_archived_partiton_name TEXT;
2261
- BEGIN
2262
- v_main_partiton_name := emt_sanitize_name(tableName || '_' || partition_name);
2263
- v_active_partiton_name := emt_sanitize_name(v_main_partiton_name || '_active');
2264
- v_archived_partiton_name := emt_sanitize_name(v_main_partiton_name || '_archived');
2265
-
2266
-
2267
- -- create default partition
2268
- EXECUTE format('
2269
- CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
2270
- FOR VALUES IN (%L) PARTITION BY LIST (is_archived);',
2271
- v_main_partiton_name, tableName, partition_name
2272
- );
2273
-
2274
- EXECUTE format('
2275
- CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
2276
- FOR VALUES IN (FALSE);',
2277
- v_active_partiton_name, v_main_partiton_name
2278
- );
2279
-
2280
- EXECUTE format('
2281
- CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
2282
- FOR VALUES IN (TRUE);',
2283
- v_archived_partiton_name, v_main_partiton_name
2284
- );
2285
- END;
2286
- $emt_add_table_partition$ LANGUAGE plpgsql;
2287
- END IF;
2288
- END $$;
2289
-
2290
- DO $$
2291
- BEGIN
2292
- IF NOT EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'emt_add_partition') THEN
2293
-
2294
- CREATE OR REPLACE FUNCTION emt_add_partition(partition_name TEXT) RETURNS void AS $emt_add_partition$
2295
- BEGIN
2296
- PERFORM emt_add_table_partition('emt_messages', partition_name);
2297
- PERFORM emt_add_table_partition('emt_streams', partition_name);
2298
-
2299
- EXECUTE format('
2300
- CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
2301
- FOR VALUES IN (%L);',
2302
- emt_sanitize_name('emt_processors' || '_' || partition_name), 'emt_processors', partition_name
2303
- );
2304
-
2305
- EXECUTE format('
2306
- CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
2307
- FOR VALUES IN (%L);',
2308
- emt_sanitize_name('emt_projections' || '_' || partition_name), 'emt_projections', partition_name
2309
- );
2310
- END;
2311
- $emt_add_partition$ LANGUAGE plpgsql;
2312
- END IF;
2313
- END $$;
2314
-
2315
- DO $$
2316
- BEGIN
2317
- IF NOT EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'emt_append_to_stream') THEN
2318
- CREATE OR REPLACE FUNCTION emt_append_to_stream(
2319
- v_message_ids text[],
2320
- v_messages_data jsonb[],
2321
- v_messages_metadata jsonb[],
2322
- v_message_schema_versions text[],
2323
- v_message_types text[],
2324
- v_message_kinds text[],
2325
- v_stream_id text,
2326
- v_stream_type text,
2327
- v_expected_stream_position bigint DEFAULT NULL,
2328
- v_partition text DEFAULT emt_sanitize_name('default_partition')
2329
- ) RETURNS TABLE (
2330
- success boolean,
2331
- next_stream_position bigint,
2332
- global_positions bigint[],
2333
- transaction_id xid8
2334
- ) LANGUAGE plpgsql
2335
- AS $emt_append_to_stream$
2336
- DECLARE
2337
- v_next_stream_position bigint;
2338
- v_position bigint;
2339
- v_updated_rows int;
2340
- v_transaction_id xid8;
2341
- v_global_positions bigint[];
2342
- BEGIN
2343
- v_transaction_id := pg_current_xact_id();
2344
-
2345
- IF v_expected_stream_position IS NULL THEN
2346
- SELECT COALESCE(
2347
- (SELECT stream_position
2348
- FROM emt_streams
2349
- WHERE stream_id = v_stream_id
2350
- AND partition = v_partition
2351
- AND is_archived = FALSE
2352
- LIMIT 1),
2353
- 0
2354
- ) INTO v_expected_stream_position;
2355
- END IF;
2356
-
2357
- v_next_stream_position := v_expected_stream_position + array_upper(v_messages_data, 1);
2358
-
2359
- IF v_expected_stream_position = 0 THEN
2360
- INSERT INTO emt_streams
2361
- (stream_id, stream_position, partition, stream_type, stream_metadata, is_archived)
2362
- VALUES
2363
- (v_stream_id, v_next_stream_position, v_partition, v_stream_type, '{}', FALSE);
2364
- ELSE
2365
- UPDATE emt_streams as s
2366
- SET stream_position = v_next_stream_position
2367
- WHERE stream_id = v_stream_id AND stream_position = v_expected_stream_position AND partition = v_partition AND is_archived = FALSE;
2368
-
2369
- get diagnostics v_updated_rows = row_count;
2370
-
2371
- IF v_updated_rows = 0 THEN
2372
- RETURN QUERY SELECT FALSE, NULL::bigint, NULL::bigint[], NULL::xid8;
2373
- RETURN;
2374
- END IF;
2375
- END IF;
2376
-
2377
- WITH ev AS (
2378
- SELECT row_number() OVER () + v_expected_stream_position AS stream_position,
2379
- message_data,
2380
- message_metadata,
2381
- schema_version,
2382
- message_id,
2383
- message_type,
2384
- message_kind
2385
- FROM (
2386
- SELECT *
2387
- FROM
2388
- unnest(v_message_ids, v_messages_data, v_messages_metadata, v_message_schema_versions, v_message_types, v_message_kinds)
2389
- AS message(message_id, message_data, message_metadata, schema_version, message_type, message_kind)
2390
- ) AS message
2391
- ),
2392
- all_messages_insert AS (
2393
- INSERT INTO emt_messages
2394
- (stream_id, stream_position, partition, message_data, message_metadata, message_schema_version, message_type, message_kind, message_id, transaction_id)
2395
- SELECT
2396
- v_stream_id, ev.stream_position, v_partition, ev.message_data, ev.message_metadata, ev.schema_version, ev.message_type, ev.message_kind, ev.message_id, v_transaction_id
2397
- FROM ev
2398
- RETURNING global_position
2399
- )
2400
- SELECT
2401
- array_agg(global_position ORDER BY global_position) INTO v_global_positions
2402
- FROM
2403
- all_messages_insert;
2404
-
2405
- RETURN QUERY SELECT TRUE, v_next_stream_position, v_global_positions, v_transaction_id;
2406
- END;
2407
- $emt_append_to_stream$;
2408
-
2409
- END IF;
2410
- END $$;
2411
- SELECT emt_add_partition('emt:default');
2412
- DO $$
2413
- BEGIN
2414
- IF NOT EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'store_processor_checkpoint') THEN
2415
-
2416
- CREATE OR REPLACE FUNCTION store_processor_checkpoint(
2417
- p_processor_id TEXT,
2418
- p_version BIGINT,
2419
- p_position TEXT,
2420
- p_check_position TEXT,
2421
- p_transaction_id xid8,
2422
- p_partition TEXT DEFAULT 'emt:default',
2423
- p_processor_instance_id TEXT DEFAULT 'emt:unknown'
2424
- ) RETURNS INT AS $spc$
2425
- DECLARE
2426
- current_position TEXT;
2427
- BEGIN
2428
- -- Handle the case when p_check_position is provided
2429
- IF p_check_position IS NOT NULL THEN
2430
- -- Try to update if the position matches p_check_position
2431
- UPDATE "emt_processors"
2432
- SET
2433
- "last_processed_checkpoint" = p_position,
2434
- "last_processed_transaction_id" = p_transaction_id,
2435
- "last_updated" = now()
2436
- WHERE "processor_id" = p_processor_id
2437
- AND "last_processed_checkpoint" = p_check_position
2438
- AND "partition" = p_partition
2439
- AND "version" = p_version;
2440
-
2441
- IF FOUND THEN
2442
- RETURN 1; -- Successfully updated
2443
- END IF;
2444
-
2445
- -- Retrieve the current position
2446
- SELECT "last_processed_checkpoint" INTO current_position
2447
- FROM "emt_processors"
2448
- WHERE "processor_id" = p_processor_id
2449
- AND "partition" = p_partition
2450
- AND "version" = p_version;
2451
-
2452
- -- Return appropriate codes based on current position
2453
- IF current_position = p_position THEN
2454
- RETURN 0; -- Idempotent check: position already set
2455
- ELSIF current_position > p_position THEN
2456
- RETURN 3; -- Current ahead: another process has progressed further
2457
- ELSE
2458
- RETURN 2; -- Mismatch: check position doesn't match current
2459
- END IF;
2460
- END IF;
2461
-
2462
- -- Handle the case when p_check_position is NULL: Insert if not exists
2463
- BEGIN
2464
- INSERT INTO "emt_processors"("processor_id", "version", "last_processed_checkpoint", "partition", "last_processed_transaction_id", "created_at", "last_updated")
2465
- VALUES (p_processor_id, p_version, p_position, p_partition, p_transaction_id, now(), now());
2466
- RETURN 1; -- Successfully inserted
2467
- EXCEPTION WHEN unique_violation THEN
2468
- -- If insertion failed, it means the row already exists
2469
- SELECT "last_processed_checkpoint" INTO current_position
2470
- FROM "emt_processors"
2471
- WHERE "processor_id" = p_processor_id
2472
- AND "partition" = p_partition
2473
- AND "version" = p_version;
2474
-
2475
- IF current_position = p_position THEN
2476
- RETURN 0; -- Idempotent check: position already set
2477
- ELSIF current_position > p_position THEN
2478
- RETURN 3; -- Current ahead: another process has progressed further
2479
- ELSE
2480
- RETURN 2; -- Insertion failed, row already exists with different position
2481
- END IF;
2482
- END;
2483
- END;
2484
- $spc$ LANGUAGE plpgsql;
2485
-
2486
- END IF;
2487
- END $$;
2488
-
2489
- CREATE OR REPLACE FUNCTION emt_try_acquire_processor_lock(
2490
- p_lock_key BIGINT,
2491
- p_processor_id TEXT,
2492
- p_version INT,
2493
- p_partition TEXT DEFAULT 'emt:default',
2494
- p_processor_instance_id TEXT DEFAULT 'emt:unknown',
2495
- p_projection_name TEXT DEFAULT NULL,
2496
- p_projection_type VARCHAR(1) DEFAULT NULL,
2497
- p_projection_kind TEXT DEFAULT NULL,
2498
- p_lock_timeout_seconds INT DEFAULT 300
2499
- )
2500
- RETURNS TABLE (acquired BOOLEAN, checkpoint TEXT)
2501
- LANGUAGE plpgsql
2502
- AS $emt_try_acquire_processor_lock$
2503
- BEGIN
2504
- RETURN QUERY
2505
- WITH lock_check AS (
2506
- SELECT pg_try_advisory_xact_lock(p_lock_key) AS lock_acquired
2507
- ),
2508
- ownership_check AS (
2509
- INSERT INTO emt_processors (
2510
- processor_id,
2511
- partition,
2512
- version,
2513
- processor_instance_id,
2514
- status,
2515
- last_processed_checkpoint,
2516
- last_processed_transaction_id,
2517
- created_at,
2518
- last_updated
2519
- )
2520
- SELECT p_processor_id, p_partition, p_version, p_processor_instance_id, 'running', '0000000000000000000', '0'::xid8, now(), now()
2521
- WHERE (SELECT lock_acquired FROM lock_check) = true
2522
- ON CONFLICT (processor_id, partition, version) DO UPDATE
2523
- SET processor_instance_id = p_processor_instance_id,
2524
- status = 'running',
2525
- last_updated = now()
2526
- WHERE emt_processors.processor_instance_id = p_processor_instance_id
2527
- OR emt_processors.processor_instance_id = 'emt:unknown'
2528
- OR emt_processors.status = 'stopped'
2529
- OR emt_processors.last_updated < now() - (p_lock_timeout_seconds || ' seconds')::interval
2530
- RETURNING last_processed_checkpoint
2531
- ),
2532
- projection_status AS (
2533
- INSERT INTO emt_projections (
2534
- name,
2535
- partition,
2536
- version,
2537
- type,
2538
- kind,
2539
- status,
2540
- definition
2541
- )
2542
- SELECT p_projection_name, p_partition, p_version, p_projection_type, p_projection_kind, 'async_processing', '{}'::jsonb
2543
- WHERE p_projection_name IS NOT NULL
2544
- AND (SELECT last_processed_checkpoint FROM ownership_check) IS NOT NULL
2545
- ON CONFLICT (name, partition, version) DO UPDATE
2546
- SET status = 'async_processing'
2547
- RETURNING name
2548
- )
2549
- SELECT
2550
- (SELECT COUNT(*) > 0 FROM ownership_check),
2551
- (SELECT oc.last_processed_checkpoint FROM ownership_check oc);
2552
- END;
2553
- $emt_try_acquire_processor_lock$;
2554
-
2555
- CREATE OR REPLACE FUNCTION emt_release_processor_lock(
2556
- p_lock_key BIGINT,
2557
- p_processor_id TEXT,
2558
- p_partition TEXT,
2559
- p_version INT,
2560
- p_processor_instance_id TEXT DEFAULT 'emt:unknown',
2561
- p_projection_name TEXT DEFAULT NULL
2562
- )
2563
- RETURNS BOOLEAN
2564
- LANGUAGE plpgsql
2565
- AS $emt_release_processor_lock$
2566
- DECLARE
2567
- v_rows_updated INT;
2568
- BEGIN
2569
- IF p_projection_name IS NOT NULL THEN
2570
- UPDATE emt_projections
2571
- SET status = 'active',
2572
- last_updated = now()
2573
- WHERE partition = p_partition
2574
- AND name = p_projection_name
2575
- AND version = p_version;
2576
- END IF;
2577
-
2578
- UPDATE emt_processors
2579
- SET status = 'stopped',
2580
- processor_instance_id = 'emt:unknown',
2581
- last_updated = now()
2582
- WHERE processor_id = p_processor_id
2583
- AND partition = p_partition
2584
- AND version = p_version
2585
- AND processor_instance_id = p_processor_instance_id;
2586
-
2587
- GET DIAGNOSTICS v_rows_updated = ROW_COUNT;
2588
-
2589
- PERFORM pg_advisory_unlock(p_lock_key);
2590
-
2591
- RETURN v_rows_updated > 0;
2592
- END;
2593
- $emt_release_processor_lock$;
2594
-
2595
- CREATE OR REPLACE FUNCTION emt_try_acquire_projection_lock(
2596
- p_lock_key BIGINT,
2597
- p_partition TEXT,
2598
- p_name TEXT,
2599
- p_version INT
2600
- )
2601
- RETURNS TABLE (acquired BOOLEAN, is_active BOOLEAN)
2602
- LANGUAGE plpgsql
2603
- AS $emt_try_acquire_projection_lock$
2604
- BEGIN
2605
- RETURN QUERY
2606
- WITH lock_check AS (
2607
- SELECT pg_try_advisory_xact_lock_shared(p_lock_key) AS acquired
2608
- ),
2609
- status_check AS (
2610
- SELECT status = 'active' AS is_active
2611
- FROM emt_projections
2612
- WHERE partition = p_partition AND name = p_name AND version = p_version
2613
- )
2614
- SELECT
2615
- COALESCE((SELECT lc.acquired FROM lock_check lc), false),
2616
- COALESCE((SELECT sc.is_active FROM status_check sc), true);
2617
- END;
2618
- $emt_try_acquire_projection_lock$;
2619
-
2620
- CREATE OR REPLACE FUNCTION emt_register_projection(
2621
- p_lock_key BIGINT,
2622
- p_name TEXT,
2623
- p_partition TEXT,
2624
- p_version INT,
2625
- p_type VARCHAR(1),
2626
- p_kind TEXT,
2627
- p_status TEXT,
2628
- p_definition JSONB
2629
- )
2630
- RETURNS BOOLEAN
2631
- LANGUAGE plpgsql
2632
- AS $emt_register_projection$
2633
- DECLARE
2634
- v_result BOOLEAN;
2635
- BEGIN
2636
- WITH lock_check AS (
2637
- SELECT pg_try_advisory_xact_lock(p_lock_key) AS lock_acquired
2638
- ),
2639
- upsert_result AS (
2640
- INSERT INTO emt_projections (
2641
- name, partition, version, type, kind, status, definition, created_at, last_updated
2642
- )
2643
- SELECT p_name, p_partition, p_version, p_type, p_kind, p_status, p_definition, now(), now()
2644
- WHERE (SELECT lock_acquired FROM lock_check) = true
2645
- ON CONFLICT (name, partition, version) DO UPDATE
2646
- SET definition = EXCLUDED.definition,
2647
- last_updated = now()
2648
- RETURNING name
2649
- )
2650
- SELECT COUNT(*) > 0 INTO v_result FROM upsert_result;
2651
-
2652
- RETURN v_result;
2653
- END;
2654
- $emt_register_projection$;
2655
-
2656
- CREATE OR REPLACE FUNCTION emt_activate_projection(
2657
- p_lock_key BIGINT,
2658
- p_name TEXT,
2659
- p_partition TEXT,
2660
- p_version INT
2661
- )
2662
- RETURNS BOOLEAN
2663
- LANGUAGE plpgsql
2664
- AS $emt_activate_projection$
2665
- DECLARE
2666
- v_result BOOLEAN;
2667
- BEGIN
2668
- WITH lock_check AS (
2669
- SELECT pg_try_advisory_xact_lock(p_lock_key) AS lock_acquired
2670
- ),
2671
- update_result AS (
2672
- UPDATE emt_projections
2673
- SET status = 'active',
2674
- last_updated = now()
2675
- WHERE name = p_name
2676
- AND partition = p_partition
2677
- AND version = p_version
2678
- AND (SELECT lock_acquired FROM lock_check) = true
2679
- RETURNING name
2680
- )
2681
- SELECT COUNT(*) > 0 INTO v_result FROM update_result;
2682
-
2683
- RETURN v_result;
2684
- END;
2685
- $emt_activate_projection$;
2686
-
2687
- CREATE OR REPLACE FUNCTION emt_deactivate_projection(
2688
- p_lock_key BIGINT,
2689
- p_name TEXT,
2690
- p_partition TEXT,
2691
- p_version INT
2692
- )
2693
- RETURNS BOOLEAN
2694
- LANGUAGE plpgsql
2695
- AS $emt_deactivate_projection$
2696
- DECLARE
2697
- v_result BOOLEAN;
2698
- BEGIN
2699
- WITH lock_check AS (
2700
- SELECT pg_try_advisory_xact_lock(p_lock_key) AS lock_acquired
2701
- ),
2702
- update_result AS (
2703
- UPDATE emt_projections
2704
- SET status = 'inactive',
2705
- last_updated = now()
2706
- WHERE name = p_name
2707
- AND partition = p_partition
2708
- AND version = p_version
2709
- AND (SELECT lock_acquired FROM lock_check) = true
2710
- RETURNING name
2711
- )
2712
- SELECT COUNT(*) > 0 INTO v_result FROM update_result;
2713
-
2714
- RETURN v_result;
2715
- END;
2716
- $emt_deactivate_projection$;
2717
- `;
2718
-
2719
- //#endregion
2720
- //#region src/eventStore/schema/storeProcessorCheckpoint.ts
2721
- const storeSubscriptionCheckpointSQL = createFunctionIfDoesNotExistSQL("store_processor_checkpoint", _event_driven_io_dumbo.SQL`
2722
- CREATE OR REPLACE FUNCTION store_processor_checkpoint(
2723
- p_processor_id TEXT,
2724
- p_version BIGINT,
2725
- p_position TEXT,
2726
- p_check_position TEXT,
2727
- p_transaction_id xid8,
2728
- p_partition TEXT DEFAULT '${_event_driven_io_dumbo.SQL.plain(defaultTag)}',
2729
- p_processor_instance_id TEXT DEFAULT '${_event_driven_io_dumbo.SQL.plain(unknownTag)}'
2730
- ) RETURNS INT AS $spc$
2731
- DECLARE
2732
- current_position TEXT;
2733
- BEGIN
2734
- -- Handle the case when p_check_position is provided
2735
- IF p_check_position IS NOT NULL THEN
2736
- -- Try to update if the position matches p_check_position
2737
- UPDATE "${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}"
2738
- SET
2739
- "last_processed_checkpoint" = p_position,
2740
- "last_processed_transaction_id" = p_transaction_id,
2741
- "last_updated" = now()
2742
- WHERE "processor_id" = p_processor_id
2743
- AND "last_processed_checkpoint" = p_check_position
2744
- AND "partition" = p_partition
2745
- AND "version" = p_version;
2746
-
2747
- IF FOUND THEN
2748
- RETURN 1; -- Successfully updated
2749
- END IF;
2750
-
2751
- -- Retrieve the current position
2752
- SELECT "last_processed_checkpoint" INTO current_position
2753
- FROM "${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}"
2754
- WHERE "processor_id" = p_processor_id
2755
- AND "partition" = p_partition
2756
- AND "version" = p_version;
2757
-
2758
- -- Return appropriate codes based on current position
2759
- IF current_position = p_position THEN
2760
- RETURN 0; -- Idempotent check: position already set
2761
- ELSIF current_position > p_position THEN
2762
- RETURN 3; -- Current ahead: another process has progressed further
2763
- ELSE
2764
- RETURN 2; -- Mismatch: check position doesn't match current
2765
- END IF;
2766
- END IF;
2767
-
2768
- -- Handle the case when p_check_position is NULL: Insert if not exists
2769
- BEGIN
2770
- INSERT INTO "${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}"("processor_id", "version", "last_processed_checkpoint", "partition", "last_processed_transaction_id", "created_at", "last_updated")
2771
- VALUES (p_processor_id, p_version, p_position, p_partition, p_transaction_id, now(), now());
2772
- RETURN 1; -- Successfully inserted
2773
- EXCEPTION WHEN unique_violation THEN
2774
- -- If insertion failed, it means the row already exists
2775
- SELECT "last_processed_checkpoint" INTO current_position
2776
- FROM "${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}"
2777
- WHERE "processor_id" = p_processor_id
2778
- AND "partition" = p_partition
2779
- AND "version" = p_version;
2780
-
2781
- IF current_position = p_position THEN
2782
- RETURN 0; -- Idempotent check: position already set
2783
- ELSIF current_position > p_position THEN
2784
- RETURN 3; -- Current ahead: another process has progressed further
2785
- ELSE
2786
- RETURN 2; -- Insertion failed, row already exists with different position
2787
- END IF;
2788
- END;
2789
- END;
2790
- $spc$ LANGUAGE plpgsql;
2791
- `);
2792
- const callStoreProcessorCheckpoint = (params) => _event_driven_io_dumbo.SQL`
2793
- SELECT store_processor_checkpoint(
2794
- ${params.processorId},
2795
- ${params.version},
2796
- ${params.position},
2797
- ${params.checkPosition},
2798
- pg_current_xact_id(),
2799
- ${params.partition},
2800
- ${params.processorInstanceId}
2801
- ) as result;`;
2802
- const storeProcessorCheckpoint = async (execute, options) => {
2803
- try {
2804
- const { result } = await (0, _event_driven_io_dumbo.single)(execute.command(callStoreProcessorCheckpoint({
2805
- processorId: options.processorId,
2806
- version: options.version ?? 1,
2807
- position: options.newCheckpoint !== null ? options.newCheckpoint : null,
2808
- checkPosition: options.lastProcessedCheckpoint !== null ? options.lastProcessedCheckpoint : null,
2809
- partition: options.partition ?? defaultTag,
2810
- processorInstanceId: options.processorInstanceId ?? unknownTag
2811
- })));
2812
- return result === 1 ? {
2813
- success: true,
2814
- newCheckpoint: options.newCheckpoint
2815
- } : {
2816
- success: false,
2817
- reason: result === 0 ? "IGNORED" : result === 3 ? "CURRENT_AHEAD" : "MISMATCH"
2818
- };
2819
- } catch (error) {
2820
- console.log(error);
2821
- throw error;
2822
- }
2823
- };
2824
-
2825
- //#endregion
2826
- //#region src/eventStore/schema/migrations/0_43_0/index.ts
2827
- const migration_0_43_0_cleanupLegacySubscriptionSQL = _event_driven_io_dumbo.SQL`
2828
- DO $$
2829
- BEGIN
2830
- IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'emt_subscriptions') THEN
2831
- -- Restore clean emt_add_partition (remove creation of emt_subscriptions partitions)
2832
- CREATE OR REPLACE FUNCTION emt_add_partition(partition_name TEXT) RETURNS void AS $fnpar$
2833
- BEGIN
2834
- PERFORM emt_add_table_partition('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}', partition_name);
2835
- PERFORM emt_add_table_partition('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}', partition_name);
2836
-
2837
- EXECUTE format('
2838
- CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
2839
- FOR VALUES IN (%L);',
2840
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}' || '_' || partition_name), '${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}', partition_name
2841
- );
2842
-
2843
- EXECUTE format('
2844
- CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
2845
- FOR VALUES IN (%L);',
2846
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(projectionsTable.name)}' || '_' || partition_name), '${_event_driven_io_dumbo.SQL.plain(projectionsTable.name)}', partition_name
2847
- );
2848
- END;
2849
- $fnpar$ LANGUAGE plpgsql;
2850
-
2851
- -- Drop old subscriptions table if it exists
2852
- DROP TABLE IF EXISTS emt_subscriptions CASCADE;
2853
-
2854
- -- Drop old function if it exists
2855
- DROP FUNCTION IF EXISTS store_subscription_checkpoint(character varying, bigint, bigint, bigint, xid8, text);
2856
-
2857
- -- Restore clean store_processor_checkpoint (remove dual-write logic)
2858
- CREATE OR REPLACE FUNCTION store_processor_checkpoint(
2859
- p_processor_id TEXT,
2860
- p_version BIGINT,
2861
- p_position TEXT,
2862
- p_check_position TEXT,
2863
- p_transaction_id xid8,
2864
- p_partition TEXT DEFAULT '${_event_driven_io_dumbo.SQL.plain(defaultTag)}',
2865
- p_processor_instance_id TEXT DEFAULT 'emt:unknown'
2866
- ) RETURNS INT AS $fn$
2867
- DECLARE
2868
- current_position TEXT;
2869
- BEGIN
2870
- IF p_check_position IS NOT NULL THEN
2871
- UPDATE "emt_processors"
2872
- SET
2873
- "last_processed_checkpoint" = p_position,
2874
- "last_processed_transaction_id" = p_transaction_id,
2875
- "last_updated" = now()
2876
- WHERE "processor_id" = p_processor_id
2877
- AND "last_processed_checkpoint" = p_check_position
2878
- AND "partition" = p_partition
2879
- AND "version" = p_version;
2880
-
2881
- IF FOUND THEN
2882
- RETURN 1;
2883
- END IF;
2884
-
2885
- SELECT "last_processed_checkpoint" INTO current_position
2886
- FROM "emt_processors"
2887
- WHERE "processor_id" = p_processor_id
2888
- AND "partition" = p_partition
2889
- AND "version" = p_version ;
2890
-
2891
- IF current_position = p_position THEN
2892
- RETURN 0;
2893
- ELSIF current_position > p_position THEN
2894
- RETURN 3;
2895
- ELSE
2896
- RETURN 2;
2897
- END IF;
2898
- END IF;
2899
-
2900
- BEGIN
2901
- INSERT INTO "emt_processors"("processor_id", "version", "last_processed_checkpoint", "partition", "last_processed_transaction_id", "created_at", "last_updated")
2902
- VALUES (p_processor_id, p_version, p_position, p_partition, p_transaction_id, now(), now());
2903
- RETURN 1;
2904
- EXCEPTION WHEN unique_violation THEN
2905
- SELECT "last_processed_checkpoint" INTO current_position
2906
- FROM "emt_processors"
2907
- WHERE "processor_id" = p_processor_id
2908
- AND "partition" = p_partition
2909
- AND "version" = p_version;
2910
-
2911
- IF current_position = p_position THEN
2912
- RETURN 0;
2913
- ELSE
2914
- RETURN 2;
2915
- END IF;
2916
- END;
2917
- END;
2918
- $fn$ LANGUAGE plpgsql;
2919
- END IF;
2920
- END $$;
2921
- `;
2922
- const migration_0_43_0_cleanupLegacySubscription = (0, _event_driven_io_dumbo.sqlMigration)("emt:postgresql:eventstore:0.43.0:cleanup-legacy-subscription", [migration_0_43_0_cleanupLegacySubscriptionSQL]);
2923
- const cleanupLegacySubscriptionTables = async (connectionString) => {
2924
- const pool = (0, _event_driven_io_dumbo.dumbo)({ connectionString });
2925
- try {
2926
- await pool.withTransaction(async ({ execute }) => {
2927
- await execute.command(migration_0_43_0_cleanupLegacySubscriptionSQL);
2928
- });
2929
- } finally {
2930
- await pool.close();
2931
- }
2932
- };
2933
-
2934
- //#endregion
2935
- //#region src/eventStore/schema/tables.ts
2936
- const streamsTableSQL = _event_driven_io_dumbo.SQL`
2937
- CREATE TABLE IF NOT EXISTS ${_event_driven_io_dumbo.SQL.identifier(streamsTable.name)}(
2938
- stream_id TEXT NOT NULL,
2939
- stream_position BIGINT NOT NULL,
2940
- partition TEXT NOT NULL DEFAULT '${_event_driven_io_dumbo.SQL.plain(defaultTag)}',
2941
- stream_type TEXT NOT NULL,
2942
- stream_metadata JSONB NOT NULL,
2943
- is_archived BOOLEAN NOT NULL DEFAULT FALSE,
2944
- PRIMARY KEY (stream_id, partition, is_archived)
2945
- ) PARTITION BY LIST (partition);
2946
-
2947
- CREATE UNIQUE INDEX IF NOT EXISTS idx_streams_unique
2948
- ON ${_event_driven_io_dumbo.SQL.identifier(streamsTable.name)}(stream_id, partition, is_archived)
2949
- INCLUDE (stream_position);`;
2950
- const messagesTableSQL = _event_driven_io_dumbo.SQL`
2951
- CREATE SEQUENCE IF NOT EXISTS emt_global_message_position;
2952
-
2953
- CREATE TABLE IF NOT EXISTS ${_event_driven_io_dumbo.SQL.identifier(messagesTable.name)}(
2954
- stream_position BIGINT NOT NULL,
2955
- global_position BIGINT DEFAULT nextval('emt_global_message_position'),
2956
- transaction_id XID8 NOT NULL,
2957
- created TIMESTAMPTZ NOT NULL DEFAULT now(),
2958
- is_archived BOOLEAN NOT NULL DEFAULT FALSE,
2959
- message_kind VARCHAR(1) NOT NULL DEFAULT 'E',
2960
- stream_id TEXT NOT NULL,
2961
- partition TEXT NOT NULL DEFAULT '${_event_driven_io_dumbo.SQL.plain(defaultTag)}',
2962
- message_schema_version TEXT NOT NULL,
2963
- message_id TEXT NOT NULL,
2964
- message_type TEXT NOT NULL,
2965
- message_data JSONB NOT NULL,
2966
- message_metadata JSONB NOT NULL,
2967
- PRIMARY KEY (stream_id, stream_position, partition, is_archived)
2968
- ) PARTITION BY LIST (partition);`;
2969
- const processorsTableSQL = _event_driven_io_dumbo.SQL`
2970
- CREATE TABLE IF NOT EXISTS ${_event_driven_io_dumbo.SQL.identifier(processorsTable.name)}(
2971
- last_processed_transaction_id XID8 NOT NULL,
2972
- version INT NOT NULL DEFAULT 1,
2973
- processor_id TEXT NOT NULL,
2974
- partition TEXT NOT NULL DEFAULT '${_event_driven_io_dumbo.SQL.plain(defaultTag)}',
2975
- status TEXT NOT NULL DEFAULT 'stopped',
2976
- last_processed_checkpoint TEXT NOT NULL,
2977
- processor_instance_id TEXT DEFAULT '${_event_driven_io_dumbo.SQL.plain(unknownTag)}',
2978
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
2979
- last_updated TIMESTAMPTZ NOT NULL DEFAULT now(),
2980
- PRIMARY KEY (processor_id, partition, version)
2981
- ) PARTITION BY LIST (partition);
2982
- `;
2983
- const projectionsTableSQL = _event_driven_io_dumbo.SQL`
2984
- CREATE TABLE IF NOT EXISTS ${_event_driven_io_dumbo.SQL.identifier(projectionsTable.name)}(
2985
- version INT NOT NULL DEFAULT 1,
2986
- type VARCHAR(1) NOT NULL,
2987
- name TEXT NOT NULL,
2988
- partition TEXT NOT NULL DEFAULT '${_event_driven_io_dumbo.SQL.plain(defaultTag)}',
2989
- kind TEXT NOT NULL,
2990
- status TEXT NOT NULL,
2991
- definition JSONB NOT NULL DEFAULT '{}'::jsonb,
2992
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
2993
- last_updated TIMESTAMPTZ NOT NULL DEFAULT now(),
2994
- PRIMARY KEY (name, partition, version)
2995
- ) PARTITION BY LIST (partition);
2996
- `;
2997
- const sanitizeNameSQL = createFunctionIfDoesNotExistSQL("emt_sanitize_name", _event_driven_io_dumbo.SQL`CREATE OR REPLACE FUNCTION emt_sanitize_name(input_name TEXT) RETURNS TEXT AS $emt_sanitize_name$
2998
- BEGIN
2999
- RETURN REGEXP_REPLACE(input_name, '[^a-zA-Z0-9_]', '_', 'g');
3000
- END;
3001
- $emt_sanitize_name$ LANGUAGE plpgsql;`);
3002
- const addTablePartitions = createFunctionIfDoesNotExistSQL("emt_add_table_partition", _event_driven_io_dumbo.SQL`
3003
- CREATE OR REPLACE FUNCTION emt_add_table_partition(tableName TEXT, partition_name TEXT) RETURNS void AS $emt_add_table_partition$
3004
- DECLARE
3005
- v_main_partiton_name TEXT;
3006
- v_active_partiton_name TEXT;
3007
- v_archived_partiton_name TEXT;
3008
- BEGIN
3009
- v_main_partiton_name := emt_sanitize_name(tableName || '_' || partition_name);
3010
- v_active_partiton_name := emt_sanitize_name(v_main_partiton_name || '_active');
3011
- v_archived_partiton_name := emt_sanitize_name(v_main_partiton_name || '_archived');
3012
-
3013
-
3014
- -- create default partition
3015
- EXECUTE format('
3016
- CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
3017
- FOR VALUES IN (%L) PARTITION BY LIST (is_archived);',
3018
- v_main_partiton_name, tableName, partition_name
3019
- );
3020
-
3021
- EXECUTE format('
3022
- CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
3023
- FOR VALUES IN (FALSE);',
3024
- v_active_partiton_name, v_main_partiton_name
3025
- );
3026
-
3027
- EXECUTE format('
3028
- CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
3029
- FOR VALUES IN (TRUE);',
3030
- v_archived_partiton_name, v_main_partiton_name
3031
- );
3032
- END;
3033
- $emt_add_table_partition$ LANGUAGE plpgsql;`);
3034
- const addPartitionSQL = createFunctionIfDoesNotExistSQL("emt_add_partition", _event_driven_io_dumbo.SQL`
3035
- CREATE OR REPLACE FUNCTION emt_add_partition(partition_name TEXT) RETURNS void AS $emt_add_partition$
3036
- BEGIN
3037
- PERFORM emt_add_table_partition('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}', partition_name);
3038
- PERFORM emt_add_table_partition('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}', partition_name);
3039
-
3040
- EXECUTE format('
3041
- CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
3042
- FOR VALUES IN (%L);',
3043
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}' || '_' || partition_name), '${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}', partition_name
3044
- );
3045
-
3046
- EXECUTE format('
3047
- CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
3048
- FOR VALUES IN (%L);',
3049
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(projectionsTable.name)}' || '_' || partition_name), '${_event_driven_io_dumbo.SQL.plain(projectionsTable.name)}', partition_name
3050
- );
3051
- END;
3052
- $emt_add_partition$ LANGUAGE plpgsql;`);
3053
- const addModuleSQL = _event_driven_io_dumbo.SQL`
3054
- CREATE OR REPLACE FUNCTION add_module(new_module TEXT) RETURNS void AS $$
3055
- BEGIN
3056
- -- For ${_event_driven_io_dumbo.SQL.plain(messagesTable.name)} table
3057
- EXECUTE format('
3058
- CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
3059
- FOR VALUES IN (emt_sanitize_name(%L || ''__'' || %L)) PARTITION BY LIST (is_archived);',
3060
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || '${_event_driven_io_dumbo.SQL.plain(globalTag)}'), '${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}', new_module, '${_event_driven_io_dumbo.SQL.plain(globalTag)}'
3061
- );
3062
-
3063
- EXECUTE format('
3064
- CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
3065
- FOR VALUES IN (FALSE);',
3066
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || '${_event_driven_io_dumbo.SQL.plain(globalTag)}' || '_active'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || '${_event_driven_io_dumbo.SQL.plain(globalTag)}')
3067
- );
3068
-
3069
- EXECUTE format('
3070
- CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
3071
- FOR VALUES IN (TRUE);',
3072
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || '${_event_driven_io_dumbo.SQL.plain(globalTag)}' || '_archived'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || '${_event_driven_io_dumbo.SQL.plain(globalTag)}')
3073
- );
3074
-
3075
- -- For ${_event_driven_io_dumbo.SQL.plain(streamsTable.name)} table
3076
- EXECUTE format('
3077
- CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
3078
- FOR VALUES IN (emt_sanitize_name(%L || ''__'' || %L)) PARTITION BY LIST (is_archived);',
3079
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || '${_event_driven_io_dumbo.SQL.plain(globalTag)}'), '${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}', new_module, '${_event_driven_io_dumbo.SQL.plain(globalTag)}'
3080
- );
3081
-
3082
- EXECUTE format('
3083
- CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
3084
- FOR VALUES IN (FALSE);',
3085
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || '${_event_driven_io_dumbo.SQL.plain(globalTag)}' || '_active'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || '${_event_driven_io_dumbo.SQL.plain(globalTag)}')
3086
- );
3087
-
3088
- EXECUTE format('
3089
- CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
3090
- FOR VALUES IN (TRUE);',
3091
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || '${_event_driven_io_dumbo.SQL.plain(globalTag)}' || '_archived'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || '${_event_driven_io_dumbo.SQL.plain(globalTag)}')
3092
- );
3093
- END;
3094
- $$ LANGUAGE plpgsql;
3095
- `;
3096
- const addTenantSQL = _event_driven_io_dumbo.SQL`
3097
- CREATE OR REPLACE FUNCTION add_tenant(new_module TEXT, new_tenant TEXT) RETURNS void AS $$
3098
- BEGIN
3099
- -- For ${_event_driven_io_dumbo.SQL.plain(messagesTable.name)} table
3100
- EXECUTE format('
3101
- CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
3102
- FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
3103
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || new_tenant), '${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}', new_module, new_tenant
3104
- );
3105
-
3106
- EXECUTE format('
3107
- CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
3108
- FOR VALUES IN (FALSE);',
3109
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || new_tenant || '_active'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || new_tenant)
3110
- );
3111
-
3112
- EXECUTE format('
3113
- CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
3114
- FOR VALUES IN (TRUE);',
3115
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || new_tenant || '_archived'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || new_tenant)
3116
- );
3117
-
3118
- -- For ${_event_driven_io_dumbo.SQL.plain(streamsTable.name)} table
2458
+ //#endregion
2459
+ //#region src/eventStore/schema/migrations/0_43_0/0_43_0.migration.ts
2460
+ const migration_0_43_0_cleanupLegacySubscriptionSQL = _event_driven_io_dumbo.SQL`
2461
+ DO $$
2462
+ BEGIN
2463
+ IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'emt_subscriptions') THEN
2464
+ -- Restore clean emt_add_partition (remove creation of emt_subscriptions partitions)
2465
+ CREATE OR REPLACE FUNCTION emt_add_partition(partition_name TEXT) RETURNS void AS $fnpar$
2466
+ BEGIN
2467
+ PERFORM emt_add_table_partition('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}', partition_name);
2468
+ PERFORM emt_add_table_partition('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}', partition_name);
2469
+
3119
2470
  EXECUTE format('
3120
2471
  CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
3121
- FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
3122
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || new_tenant), '${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}', new_module, new_tenant
3123
- );
3124
-
3125
- EXECUTE format('
3126
- CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
3127
- FOR VALUES IN (FALSE);',
3128
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || new_tenant || '_active'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || new_tenant)
2472
+ FOR VALUES IN (%L);',
2473
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}' || '_' || partition_name), '${_event_driven_io_dumbo.SQL.plain(processorsTable.name)}', partition_name
3129
2474
  );
3130
-
2475
+
3131
2476
  EXECUTE format('
3132
- CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
3133
- FOR VALUES IN (TRUE);',
3134
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || new_tenant || '_archived'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || new_tenant)
2477
+ CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
2478
+ FOR VALUES IN (%L);',
2479
+ emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(projectionsTable.name)}' || '_' || partition_name), '${_event_driven_io_dumbo.SQL.plain(projectionsTable.name)}', partition_name
3135
2480
  );
3136
2481
  END;
3137
- $$ LANGUAGE plpgsql;
3138
- `;
3139
- const addModuleForAllTenantsSQL = _event_driven_io_dumbo.SQL`
3140
- CREATE OR REPLACE FUNCTION add_module_for_all_tenants(new_module TEXT) RETURNS void AS $$
3141
- DECLARE
3142
- tenant_record RECORD;
3143
- BEGIN
3144
- PERFORM add_module(new_module);
3145
-
3146
- FOR tenant_record IN SELECT DISTINCT tenant FROM ${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}
3147
- LOOP
3148
- -- For ${_event_driven_io_dumbo.SQL.plain(messagesTable.name)} table
3149
- EXECUTE format('
3150
- CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
3151
- FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
3152
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || tenant_record.tenant), '${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}', new_module, tenant_record.tenant
3153
- );
3154
-
3155
- EXECUTE format('
3156
- CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
3157
- FOR VALUES IN (FALSE);',
3158
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || tenant_record.tenant || '_active'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || tenant_record.tenant)
3159
- );
3160
-
3161
- EXECUTE format('
3162
- CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
3163
- FOR VALUES IN (TRUE);',
3164
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || tenant_record.tenant || '_archived'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || new_module || '__' || tenant_record.tenant)
3165
- );
3166
-
3167
- -- For ${_event_driven_io_dumbo.SQL.plain(streamsTable.name)} table
3168
- EXECUTE format('
3169
- CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
3170
- FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
3171
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant), '${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}', new_module, tenant_record.tenant
3172
- );
3173
-
3174
- EXECUTE format('
3175
- CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
3176
- FOR VALUES IN (FALSE);',
3177
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant || '_active'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant)
3178
- );
3179
-
3180
- EXECUTE format('
3181
- CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
3182
- FOR VALUES IN (TRUE);',
3183
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant || '_archived'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant)
3184
- );
3185
- END LOOP;
3186
- END;
3187
- $$ LANGUAGE plpgsql;
3188
- `;
3189
- const addTenantForAllModulesSQL = _event_driven_io_dumbo.SQL`
3190
- CREATE OR REPLACE FUNCTION add_tenant_for_all_modules(new_tenant TEXT) RETURNS void AS $$
2482
+ $fnpar$ LANGUAGE plpgsql;
2483
+
2484
+ -- Drop old subscriptions table if it exists
2485
+ DROP TABLE IF EXISTS emt_subscriptions CASCADE;
2486
+
2487
+ -- Drop old function if it exists
2488
+ DROP FUNCTION IF EXISTS store_subscription_checkpoint(character varying, bigint, bigint, bigint, xid8, text);
2489
+
2490
+ -- Restore clean store_processor_checkpoint (remove dual-write logic)
2491
+ CREATE OR REPLACE FUNCTION store_processor_checkpoint(
2492
+ p_processor_id TEXT,
2493
+ p_version BIGINT,
2494
+ p_position TEXT,
2495
+ p_check_position TEXT,
2496
+ p_transaction_id xid8,
2497
+ p_partition TEXT DEFAULT '${_event_driven_io_dumbo.SQL.plain(defaultTag)}',
2498
+ p_processor_instance_id TEXT DEFAULT 'emt:unknown'
2499
+ ) RETURNS INT AS $fn$
3191
2500
  DECLARE
3192
- module_record RECORD;
2501
+ current_position TEXT;
3193
2502
  BEGIN
3194
- FOR module_record IN SELECT DISTINCT partitionname FROM pg_partman.part_config WHERE parent_table = '${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}'
3195
- LOOP
3196
- -- For ${_event_driven_io_dumbo.SQL.plain(messagesTable.name)} table
3197
- EXECUTE format('
3198
- CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
3199
- FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
3200
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || module_record.partitionname || '__' || new_tenant), '${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}', module_record.partitionname, new_tenant
3201
- );
3202
-
3203
- EXECUTE format('
3204
- CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
3205
- FOR VALUES IN (FALSE);',
3206
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || module_record.partitionname || '__' || new_tenant || '_active'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || module_record.partitionname || '__' || new_tenant)
3207
- );
3208
-
3209
- EXECUTE format('
3210
- CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
3211
- FOR VALUES IN (TRUE);',
3212
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || module_record.partitionname || '__' || new_tenant || '_archived'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(messagesTable.name)}_' || module_record.partitionname || '__' || new_tenant)
3213
- );
3214
-
3215
- -- For ${_event_driven_io_dumbo.SQL.plain(streamsTable.name)} table
3216
- EXECUTE format('
3217
- CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
3218
- FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
3219
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant), '${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}', module_record.partitionname, new_tenant
3220
- );
3221
-
3222
- EXECUTE format('
3223
- CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
3224
- FOR VALUES IN (FALSE);',
3225
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant || '_active'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant)
3226
- );
3227
-
3228
- EXECUTE format('
3229
- CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
3230
- FOR VALUES IN (TRUE);',
3231
- emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant || '_archived'), emt_sanitize_name('${_event_driven_io_dumbo.SQL.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant)
3232
- );
3233
- END LOOP;
2503
+ IF p_check_position IS NOT NULL THEN
2504
+ UPDATE "emt_processors"
2505
+ SET
2506
+ "last_processed_checkpoint" = p_position,
2507
+ "last_processed_transaction_id" = p_transaction_id,
2508
+ "last_updated" = now()
2509
+ WHERE "processor_id" = p_processor_id
2510
+ AND "last_processed_checkpoint" = p_check_position
2511
+ AND "partition" = p_partition
2512
+ AND "version" = p_version;
2513
+
2514
+ IF FOUND THEN
2515
+ RETURN 1;
2516
+ END IF;
2517
+
2518
+ SELECT "last_processed_checkpoint" INTO current_position
2519
+ FROM "emt_processors"
2520
+ WHERE "processor_id" = p_processor_id
2521
+ AND "partition" = p_partition
2522
+ AND "version" = p_version ;
2523
+
2524
+ IF current_position = p_position THEN
2525
+ RETURN 0;
2526
+ ELSIF current_position > p_position THEN
2527
+ RETURN 3;
2528
+ ELSE
2529
+ RETURN 2;
2530
+ END IF;
2531
+ END IF;
2532
+
2533
+ BEGIN
2534
+ INSERT INTO "emt_processors"("processor_id", "version", "last_processed_checkpoint", "partition", "last_processed_transaction_id", "created_at", "last_updated")
2535
+ VALUES (p_processor_id, p_version, p_position, p_partition, p_transaction_id, now(), now());
2536
+ RETURN 1;
2537
+ EXCEPTION WHEN unique_violation THEN
2538
+ SELECT "last_processed_checkpoint" INTO current_position
2539
+ FROM "emt_processors"
2540
+ WHERE "processor_id" = p_processor_id
2541
+ AND "partition" = p_partition
2542
+ AND "version" = p_version;
2543
+
2544
+ IF current_position = p_position THEN
2545
+ RETURN 0;
2546
+ ELSE
2547
+ RETURN 2;
2548
+ END IF;
2549
+ END;
3234
2550
  END;
3235
- $$ LANGUAGE plpgsql;
3236
- `;
3237
- const addDefaultPartitionSQL = _event_driven_io_dumbo.SQL`SELECT emt_add_partition('${_event_driven_io_dumbo.SQL.plain(defaultTag)}');`;
2551
+ $fn$ LANGUAGE plpgsql;
2552
+ END IF;
2553
+ END $$;
2554
+ `;
2555
+ const migration_0_43_0_cleanupLegacySubscription = (0, _event_driven_io_dumbo.sqlMigration)("emt:postgresql:eventstore:0.43.0:cleanup-legacy-subscription", [migration_0_43_0_cleanupLegacySubscriptionSQL]);
2556
+
2557
+ //#endregion
2558
+ //#region src/eventStore/schema/migrations/0_43_0/index.ts
2559
+ const migrations_0_43_0 = [migration_0_43_0_cleanupLegacySubscription];
2560
+
2561
+ //#endregion
2562
+ //#region src/eventStore/schema/migrations/index.ts
2563
+ const schemaSQL$1 = [
2564
+ streamsTableSQL,
2565
+ messagesTableSQL,
2566
+ projectionsTableSQL,
2567
+ processorsTableSQL,
2568
+ sanitizeNameSQL,
2569
+ addTablePartitions,
2570
+ addPartitionSQL,
2571
+ appendToStreamSQL,
2572
+ addDefaultPartitionSQL,
2573
+ storeSubscriptionCheckpointSQL,
2574
+ tryAcquireProcessorLockSQL,
2575
+ releaseProcessorLockSQL,
2576
+ registerProjectionSQL,
2577
+ activateProjectionSQL,
2578
+ deactivateProjectionSQL
2579
+ ];
2580
+ const currentPostgreSQLEventStoreSchemaVersion = "0.43.0";
2581
+ const schemaMigration$1 = (0, _event_driven_io_dumbo.sqlMigration)("emt:postgresql:eventstore:initial", schemaSQL$1);
2582
+ const pastEventStoreSchemaMigrations = [
2583
+ ...migrations_0_38_7,
2584
+ ...migrations_0_42_0,
2585
+ ...migrations_0_43_0
2586
+ ];
2587
+ const eventStoreSchemaMigrations = [...pastEventStoreSchemaMigrations, schemaMigration$1];
3238
2588
 
3239
2589
  //#endregion
3240
2590
  //#region src/eventStore/schema/readProcessorCheckpoint.ts
@@ -3316,12 +2666,6 @@ const schemaSQL = [
3316
2666
  deactivateProjectionSQL
3317
2667
  ];
3318
2668
  const schemaMigration = (0, _event_driven_io_dumbo.sqlMigration)("emt:postgresql:eventstore:initial", schemaSQL);
3319
- const eventStoreSchemaMigrations = [
3320
- migration_0_38_7_and_older,
3321
- migration_0_42_0_FromSubscriptionsToProcessors,
3322
- migration_0_42_0_2_AddProcessorProjectionFunctions,
3323
- schemaMigration
3324
- ];
3325
2669
  const createEventStoreSchema = (connectionString, pool, hooks, options) => {
3326
2670
  return pool.withTransaction(async (tx) => {
3327
2671
  const context = await transactionToPostgreSQLProjectionHandlerContext(connectionString, pool, tx);
@@ -3372,24 +2716,32 @@ const getPostgreSQLEventStore = (connectionString, options = defaultPostgreSQLOp
3372
2716
  const autoGenerateSchema = options.schema?.autoMigration === void 0 || options.schema?.autoMigration !== "None";
3373
2717
  const inlineProjections = (options.projections ?? []).filter(({ type }) => type === "inline").map(({ projection }) => projection);
3374
2718
  const migrate = async (migrationOptions) => {
3375
- if (!migrateSchema) migrateSchema = createEventStoreSchema(connectionString, pool, {
3376
- onBeforeSchemaCreated: async (context) => {
3377
- if (options.hooks?.onBeforeSchemaCreated) await options.hooks.onBeforeSchemaCreated(context);
3378
- },
3379
- onAfterSchemaCreated: async (context) => {
3380
- for (const projection of inlineProjections) if (projection.init) await projection.init({
3381
- version: projection.version ?? 1,
3382
- status: "active",
3383
- registrationType: "inline",
3384
- context: {
3385
- ...context,
3386
- migrationOptions
3387
- }
3388
- });
3389
- if (options.hooks?.onAfterSchemaCreated) await options.hooks.onAfterSchemaCreated(context);
3390
- }
3391
- }, migrationOptions);
3392
- return migrateSchema;
2719
+ if (!migrateSchema) {
2720
+ migrateSchema = createEventStoreSchema(connectionString, pool, {
2721
+ onBeforeSchemaCreated: async (context) => {
2722
+ if (options.hooks?.onBeforeSchemaCreated) await options.hooks.onBeforeSchemaCreated(context);
2723
+ },
2724
+ onAfterSchemaCreated: async (context) => {
2725
+ for (const projection of inlineProjections) if (projection.init) await projection.init({
2726
+ version: projection.version ?? 1,
2727
+ status: "active",
2728
+ registrationType: "inline",
2729
+ context: {
2730
+ ...context,
2731
+ migrationOptions
2732
+ }
2733
+ });
2734
+ if (options.hooks?.onAfterSchemaCreated) await options.hooks.onAfterSchemaCreated(context);
2735
+ }
2736
+ }, migrationOptions);
2737
+ return migrateSchema;
2738
+ }
2739
+ const result = await migrateSchema;
2740
+ if (migrationOptions?.dryRun) migrateSchema = void 0;
2741
+ return {
2742
+ applied: [],
2743
+ skipped: result.applied.concat(result.skipped)
2744
+ };
3393
2745
  };
3394
2746
  const ensureSchemaExists = () => {
3395
2747
  if (!autoGenerateSchema) return Promise.resolve();
@@ -3441,7 +2793,7 @@ const getPostgreSQLEventStore = (connectionString, options = defaultPostgreSQLOp
3441
2793
  appendToStream: async (streamName, events, appendOptions) => {
3442
2794
  await ensureSchemaExists();
3443
2795
  const [firstPart, ...rest] = streamName.split("-");
3444
- const appendResult = await appendToStream(pool, streamName, firstPart && rest.length > 0 ? firstPart : unknownTag, (0, _event_driven_io_emmett.downcastRecordedMessages)(events, appendOptions?.schema?.versioning), {
2796
+ const appendResult = await appendToStream(pool, streamName, firstPart && rest.length > 0 ? firstPart : _event_driven_io_emmett.unknownTag, (0, _event_driven_io_emmett.downcastRecordedMessages)(events, appendOptions?.schema?.versioning), {
3445
2797
  ...appendOptions,
3446
2798
  beforeCommitHook
3447
2799
  });
@@ -3702,10 +3054,15 @@ const postgreSQLEventStoreConsumer = (options) => {
3702
3054
  reason: "No active processors"
3703
3055
  };
3704
3056
  return (await Promise.allSettled(activeProcessors.map(async (s) => {
3705
- return await s.handle(messagesBatch, { connection: {
3706
- connectionString: options.connectionString,
3707
- pool
3708
- } });
3057
+ try {
3058
+ return await s.handle(messagesBatch, { connection: {
3059
+ connectionString: options.connectionString,
3060
+ pool
3061
+ } });
3062
+ } catch (error) {
3063
+ console.log(`Error during message batch processing for processor: ${s.id}`, error);
3064
+ throw error;
3065
+ }
3709
3066
  }))).some((r) => r.status === "fulfilled" && r.value?.type !== "STOP") ? void 0 : { type: "STOP" };
3710
3067
  };
3711
3068
  const processorContext = {
@@ -3726,7 +3083,11 @@ const postgreSQLEventStoreConsumer = (options) => {
3726
3083
  abortController?.abort();
3727
3084
  await messagePuller.stop();
3728
3085
  }
3729
- await start;
3086
+ try {
3087
+ await start;
3088
+ } catch (error) {
3089
+ console.log("Error during consumer stop:", error);
3090
+ }
3730
3091
  messagePuller = void 0;
3731
3092
  abortController = null;
3732
3093
  await stopProcessors();
@@ -3734,7 +3095,16 @@ const postgreSQLEventStoreConsumer = (options) => {
3734
3095
  const init = async () => {
3735
3096
  if (isInitialized) return;
3736
3097
  const postgresProcessors = processors;
3737
- for (const processor of postgresProcessors) if (processor.init) await processor.init(processorContext);
3098
+ for (const processor of postgresProcessors) if (processor.init) try {
3099
+ await processor.init(processorContext);
3100
+ } catch (error) {
3101
+ console.log(`Error during processor initialization for processor: ${processor.id}. Stopping it.`, error);
3102
+ await processor.close(processorContext).catch((closeError) => {
3103
+ console.log(`Error during processor cleanup after failed initialization for processor: ${processor.id}`, closeError);
3104
+ });
3105
+ console.log(`Processor ${processor.id} stopped successfully after failed initialization.`);
3106
+ throw error;
3107
+ }
3738
3108
  isInitialized = true;
3739
3109
  };
3740
3110
  return {
@@ -3761,9 +3131,13 @@ const postgreSQLEventStoreConsumer = (options) => {
3761
3131
  return processor;
3762
3132
  },
3763
3133
  start: () => {
3764
- if (isRunning) return start;
3134
+ if (isRunning) {
3135
+ console.log("Consumer is already running. Returning the existing start promise.");
3136
+ return start;
3137
+ }
3765
3138
  startedAwaiter.reset();
3766
3139
  if (processors.length === 0) {
3140
+ console.log("Cannot start consumer without at least a single processor");
3767
3141
  const error = new _event_driven_io_emmett.EmmettError("Cannot start consumer without at least a single processor");
3768
3142
  startedAwaiter.reject(error);
3769
3143
  return Promise.reject(error);
@@ -3781,16 +3155,25 @@ const postgreSQLEventStoreConsumer = (options) => {
3781
3155
  pullingFrequencyInMs: pulling?.pullingFrequencyInMs ?? 50,
3782
3156
  signal: abortController.signal
3783
3157
  });
3784
- if (!isInitialized) await init();
3158
+ if (!isInitialized) {
3159
+ console.log("Initializing consumer before starting message pulling.");
3160
+ await init();
3161
+ }
3785
3162
  const startFrom = zipPostgreSQLEventStoreMessageBatchPullerStartFrom(await Promise.all(processors.map(async (o) => {
3786
- return await o.start({
3787
- execute: pool.execute,
3788
- connection: {
3789
- connectionString: options.connectionString,
3790
- pool
3791
- }
3792
- });
3163
+ try {
3164
+ return await o.start({
3165
+ execute: pool.execute,
3166
+ connection: {
3167
+ connectionString: options.connectionString,
3168
+ pool
3169
+ }
3170
+ });
3171
+ } catch (error) {
3172
+ console.log(`Error during processor start position retrieval for processor: ${o.id}. Stopping it.`, error);
3173
+ throw error;
3174
+ }
3793
3175
  })));
3176
+ console.log(`Starting message pulling with start position: ${_event_driven_io_dumbo.JSONSerializer.serialize(startFrom)}. Waiting for messages...`);
3794
3177
  await messagePuller.start({
3795
3178
  startFrom,
3796
3179
  started: startedAwaiter
@@ -3873,30 +3256,26 @@ exports.callReleaseProcessorLock = callReleaseProcessorLock;
3873
3256
  exports.callStoreProcessorCheckpoint = callStoreProcessorCheckpoint;
3874
3257
  exports.callTryAcquireProcessorLock = callTryAcquireProcessorLock;
3875
3258
  exports.callTryAcquireProjectionLock = callTryAcquireProjectionLock;
3876
- exports.cleanupLegacySubscriptionTables = cleanupLegacySubscriptionTables;
3877
3259
  exports.createEventStoreSchema = createEventStoreSchema;
3260
+ exports.currentPostgreSQLEventStoreSchemaVersion = currentPostgreSQLEventStoreSchemaVersion;
3878
3261
  exports.deactivateProjection = deactivateProjection;
3879
3262
  exports.deactivateProjectionSQL = deactivateProjectionSQL;
3880
3263
  exports.defaultPostgreSQLOptions = defaultPostgreSQLOptions;
3881
- exports.defaultTag = defaultTag;
3882
3264
  exports.documentDoesNotExist = documentDoesNotExist;
3883
3265
  exports.documentExists = documentExists;
3884
3266
  exports.documentMatchingExists = documentMatchingExists;
3885
3267
  exports.documentsAreTheSame = documentsAreTheSame;
3886
3268
  exports.documentsMatchingHaveCount = documentsMatchingHaveCount;
3887
- exports.emmettPrefix = emmettPrefix;
3888
3269
  exports.eventInStream = eventInStream;
3889
3270
  exports.eventStoreSchemaMigrations = eventStoreSchemaMigrations;
3890
3271
  exports.eventsInStream = eventsInStream;
3891
3272
  exports.expectPongoDocuments = expectPongoDocuments;
3892
3273
  exports.expectSQL = expectSQL;
3893
3274
  exports.getPostgreSQLEventStore = getPostgreSQLEventStore;
3894
- exports.globalNames = globalNames;
3895
- exports.globalTag = globalTag;
3896
3275
  exports.handleProjections = handleProjections;
3897
- exports.messagesTable = messagesTable;
3898
3276
  exports.messagesTableSQL = messagesTableSQL;
3899
3277
  exports.newEventsInStream = newEventsInStream;
3278
+ exports.pastEventStoreSchemaMigrations = pastEventStoreSchemaMigrations;
3900
3279
  exports.pongoMultiStreamProjection = pongoMultiStreamProjection;
3901
3280
  exports.pongoProjection = pongoProjection;
3902
3281
  exports.pongoSingleStreamProjection = pongoSingleStreamProjection;
@@ -3911,9 +3290,7 @@ exports.postgreSQLRawBatchSQLProjection = postgreSQLRawBatchSQLProjection;
3911
3290
  exports.postgreSQLRawSQLProjection = postgreSQLRawSQLProjection;
3912
3291
  exports.postgreSQLReactor = postgreSQLReactor;
3913
3292
  exports.postgreSQLWorkflowProcessor = postgreSQLWorkflowProcessor;
3914
- exports.processorsTable = processorsTable;
3915
3293
  exports.processorsTableSQL = processorsTableSQL;
3916
- exports.projectionsTable = projectionsTable;
3917
3294
  exports.projectionsTableSQL = projectionsTableSQL;
3918
3295
  exports.readLastMessageGlobalPosition = readLastMessageGlobalPosition;
3919
3296
  exports.readMessagesBatch = readMessagesBatch;
@@ -3930,13 +3307,11 @@ exports.schemaSQL = schemaSQL;
3930
3307
  exports.storeProcessorCheckpoint = storeProcessorCheckpoint;
3931
3308
  exports.storeSubscriptionCheckpointSQL = storeSubscriptionCheckpointSQL;
3932
3309
  exports.streamExists = streamExists;
3933
- exports.streamsTable = streamsTable;
3934
3310
  exports.streamsTableSQL = streamsTableSQL;
3935
3311
  exports.toProcessorLockKey = toProcessorLockKey;
3936
3312
  exports.toProjectionLockKey = toProjectionLockKey;
3937
3313
  exports.transactionToPostgreSQLProjectionHandlerContext = transactionToPostgreSQLProjectionHandlerContext;
3938
3314
  exports.tryAcquireProcessorLockSQL = tryAcquireProcessorLockSQL;
3939
3315
  exports.tryAcquireProjectionLockSQL = tryAcquireProjectionLockSQL;
3940
- exports.unknownTag = unknownTag;
3941
3316
  exports.zipPostgreSQLEventStoreMessageBatchPullerStartFrom = zipPostgreSQLEventStoreMessageBatchPullerStartFrom;
3942
3317
  //# sourceMappingURL=index.cjs.map