@axinom/mosaic-db-common 0.35.0-rc.9 → 0.36.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1151,3 +1151,172 @@ BEGIN
1151
1151
  RETURN counter;
1152
1152
  END;
1153
1153
  $$;
1154
+
1155
+ /*-snippet
1156
+ {
1157
+ "body": [
1158
+ "SELECT ax_define.create_trx_table(",
1159
+ " '${1:app_hidden}',",
1160
+ " '${2|inbox,outbox|}',",
1161
+ " '{${3::DATABASE_ENV_OWNER},${4::DATABASE_GQL_ROLE}}',",
1162
+ " '${5|sequential,parallel|}');"
1163
+ ],
1164
+ "description": [
1165
+ "Create a new table for transaction inbox/outbox handling. \n"
1166
+ ]
1167
+ }
1168
+ snippet-*/
1169
+ CREATE OR REPLACE FUNCTION ax_define.create_trx_table (
1170
+ trx_schema TEXT,
1171
+ trx_table_name TEXT,
1172
+ additional_roles_to_grant TEXT[],
1173
+ concurrency TEXT
1174
+ ) RETURNS VOID
1175
+ LANGUAGE plpgsql
1176
+ AS $$
1177
+ DECLARE
1178
+ role_ TEXT;
1179
+ BEGIN
1180
+ EXECUTE 'DROP TABLE IF EXISTS ' || trx_schema || '.'|| trx_table_name ||' CASCADE;';
1181
+ EXECUTE 'CREATE TABLE ' || trx_schema || '.'|| trx_table_name ||' (
1182
+ id uuid PRIMARY KEY,
1183
+ aggregate_type TEXT NOT NULL,
1184
+ aggregate_id TEXT NOT NULL,
1185
+ message_type TEXT NOT NULL,
1186
+ payload JSONB NOT NULL,
1187
+ metadata JSONB,
1188
+ created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
1189
+ processed_at TIMESTAMPTZ,
1190
+ started_attempts smallint NOT NULL DEFAULT 0,
1191
+ finished_attempts smallint NOT NULL DEFAULT 0,
1192
+ segment TEXT,
1193
+ locked_until TIMESTAMPTZ NOT NULL DEFAULT to_timestamp(0),
1194
+ concurrency TEXT NOT NULL DEFAULT ''' || concurrency || ''',
1195
+ abandoned_at TIMESTAMPTZ
1196
+ );';
1197
+ EXECUTE 'ALTER TABLE ' || trx_schema || '.'|| trx_table_name ||' ADD CONSTRAINT '|| trx_table_name ||'_concurrency_check
1198
+ CHECK (concurrency IN (''sequential'', ''parallel''));';
1199
+
1200
+ -- The owner role has full access - no grants needed
1201
+ -- Grants for additional roles are given here
1202
+ FOREACH role_ IN ARRAY additional_roles_to_grant LOOP
1203
+ EXECUTE 'GRANT SELECT, INSERT, DELETE ON ' || trx_schema || '.'|| trx_table_name ||' TO ' || role_ || ';';
1204
+ EXECUTE 'GRANT UPDATE (locked_until, processed_at, abandoned_at, started_attempts, finished_attempts) ON ' || trx_schema || '.'|| trx_table_name ||' TO ' || role_ || ';';
1205
+ END LOOP;
1206
+
1207
+ EXECUTE 'SELECT ax_define.define_index(''segment'', '''|| trx_table_name ||''', '''||trx_schema||''');';
1208
+ EXECUTE 'SELECT ax_define.define_index(''created_at'', '''|| trx_table_name ||''', ''' || trx_schema || ''');';
1209
+ EXECUTE 'SELECT ax_define.define_index(''processed_at'', '''|| trx_table_name ||''', ''' || trx_schema || ''');';
1210
+ EXECUTE 'SELECT ax_define.define_index(''abandoned_at'', '''|| trx_table_name ||''', ''' || trx_schema || ''');';
1211
+ EXECUTE 'SELECT ax_define.define_index(''locked_until'', '''|| trx_table_name ||''', ''' || trx_schema || ''');';
1212
+ END;
1213
+ $$;
1214
+
1215
+ /*-snippet
1216
+ {
1217
+ "body": [
1218
+ "SELECT ax_define.create_trx_next_messages_function(",
1219
+ " '${1:app_hidden}',",
1220
+ " '${2:app_hidden}',",
1221
+ " '${3|inbox,outbox|}');"
1222
+ ],
1223
+ "description": [
1224
+ "Create next_messages function that is used to poll the inbox/outbox tables. \n"
1225
+ ]
1226
+ }
1227
+ snippet-*/
1228
+ CREATE OR REPLACE FUNCTION ax_define.create_trx_next_messages_function(
1229
+ trx_next_message_function_schema TEXT,
1230
+ trx_table_schema TEXT,
1231
+ trx_table_name TEXT
1232
+ ) RETURNS VOID
1233
+ LANGUAGE plpgsql
1234
+ AS $$
1235
+ BEGIN
1236
+ EXECUTE 'DROP FUNCTION IF EXISTS ' || trx_next_message_function_schema || '.next_'|| trx_table_name ||'_messages(integer, integer);';
1237
+ EXECUTE 'CREATE OR REPLACE FUNCTION ' || trx_next_message_function_schema || '.next_'|| trx_table_name ||'_messages(
1238
+ max_size integer, lock_ms integer)
1239
+ RETURNS SETOF ' || trx_table_schema || '.'|| trx_table_name ||'
1240
+ LANGUAGE ''plpgsql''
1241
+
1242
+ AS $BODY$
1243
+ DECLARE
1244
+ loop_row ' || trx_table_schema || '.'|| trx_table_name ||'%ROWTYPE;
1245
+ message_row ' || trx_table_schema || '.'|| trx_table_name ||'%ROWTYPE;
1246
+ ids uuid[] := ''{}'';
1247
+ BEGIN
1248
+
1249
+ IF max_size < 1 THEN
1250
+ RAISE EXCEPTION ''The max_size for the next messages batch must be at least one.'' using errcode = ''MAXNR'';
1251
+ END IF;
1252
+
1253
+ -- get (only) the oldest message of every segment but only return it if it is not locked
1254
+ FOR loop_row IN
1255
+ SELECT * FROM ' || trx_table_schema || '.'|| trx_table_name ||' m WHERE m.id in (SELECT DISTINCT ON (segment) id
1256
+ FROM ' || trx_table_schema || '.'|| trx_table_name ||'
1257
+ WHERE processed_at IS NULL AND abandoned_at IS NULL
1258
+ ORDER BY segment, created_at) order by created_at
1259
+ LOOP
1260
+ BEGIN
1261
+ EXIT WHEN cardinality(ids) >= max_size;
1262
+
1263
+ SELECT *
1264
+ INTO message_row
1265
+ FROM ' || trx_table_schema || '.'|| trx_table_name ||'
1266
+ WHERE id = loop_row.id
1267
+ FOR NO KEY UPDATE NOWAIT; -- throw/catch error when locked
1268
+
1269
+ IF message_row.locked_until > NOW() THEN
1270
+ CONTINUE;
1271
+ END IF;
1272
+
1273
+ ids := array_append(ids, message_row.id);
1274
+ EXCEPTION
1275
+ WHEN lock_not_available THEN
1276
+ CONTINUE;
1277
+ WHEN serialization_failure THEN
1278
+ CONTINUE;
1279
+ END;
1280
+ END LOOP;
1281
+
1282
+ -- if max_size not reached: get the oldest parallelizable message independent of segment
1283
+ IF cardinality(ids) < max_size THEN
1284
+ FOR loop_row IN
1285
+ SELECT * FROM ' || trx_table_schema || '.'|| trx_table_name ||'
1286
+ WHERE concurrency = ''parallel'' AND processed_at IS NULL AND abandoned_at IS NULL AND locked_until < NOW()
1287
+ AND id NOT IN (SELECT UNNEST(ids))
1288
+ order by created_at
1289
+ LOOP
1290
+ BEGIN
1291
+ EXIT WHEN cardinality(ids) >= max_size;
1292
+
1293
+ SELECT *
1294
+ INTO message_row
1295
+ FROM ' || trx_table_schema || '.'|| trx_table_name ||'
1296
+ WHERE id = loop_row.id
1297
+ FOR NO KEY UPDATE NOWAIT; -- throw/catch error when locked
1298
+
1299
+ ids := array_append(ids, message_row.id);
1300
+ EXCEPTION
1301
+ WHEN lock_not_available THEN
1302
+ CONTINUE;
1303
+ WHEN serialization_failure THEN
1304
+ CONTINUE;
1305
+ END;
1306
+ END LOOP;
1307
+ END IF;
1308
+
1309
+ -- set a short lock value so the the workers can each process a message
1310
+ IF cardinality(ids) > 0 THEN
1311
+
1312
+ RETURN QUERY
1313
+ UPDATE ' || trx_table_schema || '.'|| trx_table_name ||'
1314
+ SET locked_until = clock_timestamp() + (lock_ms || '' milliseconds'')::INTERVAL, started_attempts = started_attempts + 1
1315
+ WHERE ID = ANY(ids)
1316
+ RETURNING *;
1317
+
1318
+ END IF;
1319
+ END;
1320
+ $BODY$;';
1321
+ END;
1322
+ $$;
@@ -251,5 +251,41 @@
251
251
  "Make sure the dropped value is not used in other tables.\n",
252
252
  "This snippet has a simplified migration sample that might require adjustments."
253
253
  ]
254
+ },
255
+ "Create Transaction Inbox Outbox Tables and Functions (Ax Custom)": {
256
+ "scope": "sql",
257
+ "prefix": "ax-add-trx-inbox-outbox-tables-and-functions",
258
+ "body": [
259
+ "-- Create Transaction Inbox Table",
260
+ "SELECT ax_define.create_trx_table(",
261
+ "'${1:app_hidden}',",
262
+ "'${2:inbox}',",
263
+ "'{${3::DATABASE_ENV_OWNER},${4::DATABASE_GQL_ROLE}}',",
264
+ "'${5|sequential,parallel|}');",
265
+ "",
266
+ "-- Create Transaction Outbox Table",
267
+ "SELECT ax_define.create_trx_table(",
268
+ "'${1:app_hidden}',",
269
+ "'${6:outbox}',",
270
+ "'{${3::DATABASE_ENV_OWNER},${4::DATABASE_GQL_ROLE}}',",
271
+ "'${7|sequential,parallel|}');",
272
+ "",
273
+ "-- Create Transaction Inbox Next Message function",
274
+ "SELECT ax_define.create_trx_next_messages_function(",
275
+ "'${8:app_hidden}',",
276
+ "'${1:app_hidden}',",
277
+ "'${2:inbox}');",
278
+ "",
279
+ "-- Create Transaction Outbox Next Message function",
280
+ "SELECT ax_define.create_trx_next_messages_function(",
281
+ "'${8:app_hidden}',",
282
+ "'${1:app_hidden}',",
283
+ "'${6:outbox}');"
284
+ ],
285
+ "description": [
286
+ "Drops a value from the enum table.\n",
287
+ "Make sure the dropped value is not used in other tables.\n",
288
+ "This snippet has a simplified migration sample that might require adjustments."
289
+ ]
254
290
  }
255
291
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axinom/mosaic-db-common",
3
- "version": "0.35.0-rc.9",
3
+ "version": "0.36.0-rc.0",
4
4
  "description": "This library encapsulates database-related functionality to develop Mosaic based services.",
5
5
  "author": "Axinom",
6
6
  "license": "PROPRIETARY",
@@ -54,5 +54,5 @@
54
54
  "publishConfig": {
55
55
  "access": "public"
56
56
  },
57
- "gitHead": "f4c4a0b3a69aca4e15ec878adc9c3dd046b8447b"
57
+ "gitHead": "a853ae03afed074c98b13b05d781e144c041955d"
58
58
  }