Repository: Altinn/altinn-events
Pull request #678: Add migration script for partitioning table trace_log
Author: login: eskebab
Labels: name: review/db-migration
Created: 2025-03-11T12:26:12Z | Merged: 2025-03-26T08:34:23Z

<!--- Provide a general summary of your changes in the Title above -->

## Description
<!--- Describe your changes in detail -->

## Related Issue(s)
- #659 

## Verification
- [ ] **Your** code builds clean without any errors or warnings
- [ ] Manual testing done (required)
- [ ] Relevant automated test added (if you find this hard, leave it and we'll help out)
- [ ] All tests run green

## Documentation
- [ ] User documentation is updated with a separate linked PR in [altinn-studio-docs.](https://github.com/Altinn/altinn-studio-docs) (if applicable)

Following these guidelines for creating a partitioned table:
https://www.postgresql.org/docs/current/ddl-partitioning.html

see comments in issue #659 regarding how this was tested


Diff:
diff --git a/src/Events/Migration/FunctionsAndProcedures/createlogspartition.sql b/src/Events/Migration/FunctionsAndProcedures/createlogspartition.sql
new file mode 100644
index 00000000..f8fe41ca
--- /dev/null
+++ b/src/Events/Migration/FunctionsAndProcedures/createlogspartition.sql
@@ -0,0 +1,37 @@
+CREATE OR REPLACE FUNCTION events.create_logs_partition(
+	)
+    RETURNS integer
+    LANGUAGE 'plpgsql'
+    COST 100
+    VOLATILE PARALLEL UNSAFE
+AS $BODY$
+DECLARE
+   thisMonth int; 
+   nextMonth int;
+   thisYear int;
+   nextYear int;
+   fromMonth varchar;
+   toMonth varchar;
+   partitionName varchar;
+BEGIN
+   SELECT (EXTRACT(MONTH from current_date)) INTO thisMonth;
+   SELECT (EXTRACT(YEAR from current_date)) INTO thisYear;
+
+nextYear = thisYear;
+   nextMonth = thisMonth + 1;
+
+   IF nextMonth = 13 THEN
+      nextMonth = 1;
+	  nextYear = nextYear + 1;
+  END IF;
+
+   fromMonth = thisYear || '-' || lpad(thisMonth::varchar, 2, '0') || '-01' ; 
+   toMonth = nextYear || '-' || lpad(nextMonth::varchar, 2, '0') || '-01';
+   partitionName = 'events.trace_log_y' || nextYear || 'm' || nextMonth;	
+   
+   EXECUTE
+      format('CREATE TABLE %s PARTITION OF events.trace_log FOR VALUES FROM (''%s'') TO (''%s'')', partitionName, fromMonth, toMonth);
+   
+   RETURN 1;
+END;
+$BODY$;
diff --git a/src/Events/Migration/v0.46/01-setup-tables.sql b/src/Events/Migration/v0.46/01-setup-tables.sql
new file mode 100644
index 00000000..74428fa3
--- /dev/null
+++ b/src/Events/Migration/v0.46/01-setup-tables.sql
@@ -0,0 +1,18 @@
+DROP TABLE IF EXISTS events.trace_log;
+
+CREATE TABLE IF NOT EXISTS events.trace_log
+(
+    cloudeventid uuid NOT NULL,
+    resource text COLLATE pg_catalog."default",
+    eventtype text COLLATE pg_catalog."default",
+    consumer text COLLATE pg_catalog."default",
+    "time" timestamp with time zone NOT NULL DEFAULT (now() AT TIME ZONE 'utc'::text),
+    subscriptionid bigint,
+    responsecode integer,
+    subscriberendpoint text COLLATE pg_catalog."default",
+    activity text COLLATE pg_catalog."default",
+    sequenceno BIGSERIAL NOT NULL,
+    CONSTRAINT trace_log_pkey PRIMARY KEY (sequenceno, "time")
+) PARTITION BY RANGE ("time");
+
+CREATE INDEX ON events.trace_log (time);
diff --git a/src/Events/Migration/v0.46/02-setup-grants.sql b/src/Events/Migration/v0.46/02-setup-grants.sql
new file mode 100644
index 00000000..27dd5ec3
--- /dev/null
+++ b/src/Events/Migration/v0.46/02-setup-grants.sql
@@ -0,0 +1,3 @@
+GRANT SELECT, INSERT, UPDATE, REFERENCES, DELETE, TRUNCATE, TRIGGER ON events.trace_log TO platform_events;
+
+GRANT ALL ON events.trace_log_sequenceno_seq TO platform_events;
diff --git a/src/Events/Migration/v0.46/03-setup-table-partitions.sql b/src/Events/Migration/v0.46/03-setup-table-partitions.sql
new file mode 100644
index 00000000..bae97e04
--- /dev/null
+++ b/src/Events/Migration/v0.46/03-setup-table-partitions.sql
@@ -0,0 +1,2 @@
+CREATE TABLE IF NOT EXISTS events.trace_log_y2025 PARTITION OF events.trace_log
+FOR VALUES FROM ('2025-01-01') TO ('2026-01-01');
diff --git a/src/Events/Migration/v0.46/04-functions-and-procedures.sql b/src/Events/Migration/v0.46/04-functions-and-procedures.sql
new file mode 100644
index 00000000..4f1fb694
--- /dev/null
+++ b/src/Events/Migration/v0.46/04-functions-and-procedures.sql
@@ -0,0 +1,279 @@
+-- This script is autogenerated from the tool DbTools. Do not edit manually.
+
+-- createlogspartition.sql:
+CREATE OR REPLACE FUNCTION events.create_logs_partition(
+	)
+    RETURNS integer
+    LANGUAGE 'plpgsql'
+    COST 100
+    VOLATILE PARALLEL UNSAFE
+AS $BODY$
+DECLARE
+   thisMonth int; 
+   nextMonth int;
+   thisYear int;
+   nextYear int;
+   fromMonth varchar;
+   toMonth varchar;
+   partitionName varchar;
+BEGIN
+   SELECT (EXTRACT(MONTH from current_date)) INTO thisMonth;
+   SELECT (EXTRACT(YEAR from current_date)) INTO thisYear;
+
+nextYear = thisYear;
+   nextMonth = thisMonth + 1;
+
+   IF nextMonth = 13 THEN
+      nextMonth = 1;
+	  nextYear = nextYear + 1;
+  END IF;
+
+   fromMonth = thisYear || '-' || lpad(thisMonth::varchar, 2, '0') || '-01' ; 
+   toMonth = nextYear || '-' || lpad(nextMonth::varchar, 2, '0') || '-01';
+   partitionName = 'events.trace_log_y' || nextYear || 'm' || nextMonth;	
+   
+   EXECUTE
+      format('CREATE TABLE %s PARTITION OF events.trace_log FOR VALUES FROM (''%s'') TO (''%s'')', partitionName, fromMonth, toMonth);
+   
+   RETURN 1;
+END;
+$BODY$;
+
+
+-- deletesubscription.sql:
+CREATE OR REPLACE PROCEDURE events.deletesubscription(_id integer)
+    LANGUAGE 'plpgsql'
+    
+AS $BODY$
+BEGIN
+  DELETE 
+	FROM events.subscription s
+  where s.id = _id;
+
+END;
+$BODY$;
+
+-- findsubscription.sql:
+CREATE OR REPLACE FUNCTION events.find_subscription(
+    _resourcefilter character varying,
+	_sourcefilter character varying,
+	_subjectfilter character varying,
+	_typefilter character varying,
+	_consumer character varying,
+	_endpointurl character varying)
+    RETURNS TABLE(id bigint, resourcefilter character varying, sourcefilter character varying, subjectfilter character varying, typefilter character varying, consumer character varying, endpointurl character varying, createdby character varying, validated boolean, "time" timestamp with time zone)
+    LANGUAGE 'plpgsql'
+AS $BODY$
+
+BEGIN
+RETURN query
+    SELECT
+        s.id, s.resourcefilter, s.sourcefilter, s.subjectfilter, s.typefilter, s.consumer, s.endpointurl, s.createdby, s.validated, s."time"
+    FROM
+        events.subscription s
+    WHERE
+        s.resourcefilter = _resourcefilter
+        AND ((_sourcefilter IS NULL AND s.sourcefilter IS NULL) OR s.sourcefilter = _sourcefilter)
+        AND ((_subjectfilter IS NULL AND s.subjectfilter IS NULL) OR s.subjectfilter = _subjectfilter)
+        AND ((_typefilter IS NULL AND s.typefilter IS NULL) OR s.typefilter = _typefilter)
+        AND s.consumer = _consumer
+        AND s.endpointurl = _endpointurl;
+END
+$BODY$;
+
+-- getappevents.sql:
+CREATE OR REPLACE FUNCTION events.getappevents_v2(
+	_subject character varying,
+	_after character varying,
+	_from timestamp with time zone,
+	_to timestamp with time zone,
+	_type text[],
+	_source text[],
+	_resource text,
+	_size integer)
+    RETURNS TABLE(cloudevents text) 
+    LANGUAGE 'plpgsql'
+AS $BODY$
+DECLARE
+_sequenceno_first bigint;
+_sequenceno_last bigint;
+BEGIN
+IF _after IS NOT NULL AND _after <> '' THEN
+	SELECT
+		case count(*)
+		when 0
+			then 0
+		else
+			(SELECT MIN(sequenceno)
+			FROM events.events
+			WHERE cloudevent->>'id' = _after)
+		end
+	INTO _sequenceno_first
+	FROM events.events
+	WHERE cloudevent->>'id' = _after;
+END IF;
+SELECT MAX(sequenceno) INTO _sequenceno_last FROM events.events WHERE registeredtime <= now() - interval '30 second';
+return query
+	SELECT cast(cloudevent as text) as cloudevents
+	FROM events.events
+	WHERE (_subject IS NULL OR cloudevent->>'subject' = _subject)
+		AND (_from IS NULL OR (cloudevent->>'time')::timestamptz >= _from)
+		AND (_to IS NULL OR (cloudevent->>'time')::timestamptz <= _to)
+		AND (_type IS NULL OR cloudevent->>'type' ILIKE ANY(_type))
+		AND (_source IS NULL OR cloudevent->>'source' ILIKE ANY(_source))
+		AND (_resource IS NULL OR cloudevent->>'resource' = _resource)
+		AND (_after IS NULL OR _after = '' OR sequenceno > _sequenceno_first)
+		AND (_sequenceno_last IS NULL OR sequenceno <= _sequenceno_last)
+	ORDER BY sequenceno
+	limit _size;
+END;
+$BODY$;
+
+
+-- getevents.sql:
+CREATE OR REPLACE FUNCTION events.getevents(
+	_resource character varying,
+	_subject character varying,
+	_alternativesubject character varying,
+	_after character varying,
+	_type text[],
+	_size integer)
+    RETURNS TABLE(cloudevents text) 
+    LANGUAGE 'plpgsql'
+AS $BODY$
+
+DECLARE
+_sequenceno bigint;
+BEGIN
+IF _after IS NOT NULL AND _after <> '' THEN
+	SELECT
+		case count(*)
+		when 0
+			then 0
+		else
+			(SELECT MIN(sequenceno) FROM events.events
+			WHERE cloudevent->>'id' = _after)
+		end
+	INTO _sequenceno
+	FROM events.events
+	WHERE cloudevent->>'id' = _after;
+END IF;
+return query
+	SELECT cast(cloudevent as text) as cloudevents
+	FROM events.events
+	WHERE  cloudevent->>'resource' = _resource
+	AND (_subject IS NULL OR cloudevent->>'subject' = _subject)
+	AND (_alternativeSubject IS NULL OR cloudevent->>'alternativesubject' = _alternativesubject)
+	AND (_type IS NULL OR cloudevent->>'type' LIKE ANY(_type) )
+	AND registeredtime <= now() - interval '30 second'
+	AND (_after IS NULL OR _after = '' OR sequenceno > _sequenceno)
+  ORDER BY sequenceno
+  limit _size;
+END;
+$BODY$;
+
+-- getsubscription.sql:
+CREATE OR REPLACE FUNCTION events.getsubscription_v2(
+	_id integer)
+    RETURNS TABLE(id bigint, resourcefilter character varying, sourcefilter character varying, subjectfilter character varying, typefilter character varying, consumer character varying, endpointurl character varying, createdby character varying, validated boolean, "time" timestamp with time zone)
+    LANGUAGE 'plpgsql'
+AS $BODY$
+
+BEGIN
+return query
+	SELECT s.id, s.resourcefilter, s.sourcefilter, s.subjectfilter, s.typefilter, s.consumer, s.endpointurl, s.createdby, s.validated, s."time"
+	FROM events.subscription s
+  where s.id = _id;
+
+END;
+$BODY$;
+
+-- getsubscriptions.sql:
+CREATE OR REPLACE FUNCTION events.getsubscriptions_v2(
+	sourcehashset character varying[],
+	subject character varying,
+	type character varying)
+    RETURNS TABLE(id bigint, resourcefilter character varying, sourcefilter character varying, subjectfilter character varying, typefilter character varying, consumer character varying, endpointurl character varying, createdby character varying, validated boolean, "time" timestamp with time zone)
+    LANGUAGE 'plpgsql'
+AS $BODY$
+
+BEGIN
+return query
+	SELECT s.id, s.resourcefilter, s.sourcefilter, s.subjectfilter, s.typefilter, s.consumer, s.endpointurl, s.createdby, s.validated, s."time"
+	FROM events.subscription s
+  WHERE  s.sourcefilterhash = ANY(sourcehashset)
+  AND (s.subjectfilter is NULL OR s.subjectfilter = subject)
+  AND (s.typefilter is NULL OR s.typefilter = type)
+  AND s.validated;
+
+END;
+$BODY$;
+
+-- getsubscriptionsbyconsumer.sql:
+CREATE OR REPLACE FUNCTION events.getsubscriptionsbyconsumer_v2(
+	_consumer character varying,
+	_includeinvalid boolean)
+    RETURNS TABLE(id bigint, resourcefilter character varying, sourcefilter character varying, subjectfilter character varying, typefilter character varying, consumer character varying, endpointurl character varying, createdby character varying, validated boolean, "time" timestamp with time zone)
+    LANGUAGE 'plpgsql'
+AS $BODY$
+
+
+BEGIN
+return query
+	SELECT s.id, s.resourcefilter, s.sourcefilter, s.subjectfilter, s.typefilter, s.consumer, s.endpointurl, s.createdby, s.validated, s."time"
+	FROM events.subscription s
+  WHERE s.consumer LIKE _consumer
+  AND s.validated =
+	  CASE WHEN _includeInvalid THEN
+		false or s.validated = true
+	  ELSE
+		true
+	  END;
+
+
+END
+$BODY$;
+
+-- insertsubscription.sql:
+CREATE OR REPLACE FUNCTION events.insert_subscription(
+    resourcefilter character varying,
+	sourcefilter character varying,
+	subjectfilter character varying,
+	typefilter character varying,
+	consumer character varying,
+	endpointurl character varying,
+	createdby character varying,
+	validated boolean,
+	sourcefilterhash character varying)
+    RETURNS SETOF events.subscription
+    LANGUAGE 'plpgsql'
+AS $BODY$
+
+DECLARE currentTime timestamptz;
+
+BEGIN
+  SET TIME ZONE UTC;
+  currentTime := NOW();
+
+  RETURN QUERY
+  INSERT INTO events.subscription(resourcefilter, sourcefilter, subjectfilter, typefilter, consumer, endpointurl, createdby, "time", validated, sourcefilterhash)
+  VALUES ($1, $2, $3, $4, $5, $6, $7,  currentTime, $8, $9) RETURNING *;
+
+END
+$BODY$;
+
+-- setvalidsubscription.sql:
+CREATE OR REPLACE PROCEDURE events.setvalidsubscription(_id integer)
+    LANGUAGE 'plpgsql'
+    
+AS $BODY$
+BEGIN
+  UPDATE 
+	events.subscription
+	  SET
+   validated = true
+	WHERE id = _id;
+END;
+$BODY$;
+
+


Reviews:
user: login: HenningNormann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T18:38:57Z
user: login: eskebab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T11:37:55Z
user: login: eskebab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T11:42:44Z
user: login: SandGrainOne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-12T11:52:38Z
user: login: SandGrainOne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T12:16:42Z
user: login: SandGrainOne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T12:19:46Z
user: login: SandGrainOne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T12:21:08Z
user: login: HenningNormann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T12:30:51Z
user: login: HenningNormann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T10:38:30Z
user: login: eskebab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T11:29:43Z
user: login: eskebab
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T12:34:28Z
user: login: SandGrainOne
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-21T12:10:18Z

Review comments:
diff_hunk: @@ -0,0 +1,21 @@
+-- Table: events.trace_log
+
+DROP TABLE IF EXISTS events.trace_log;
+
+CREATE TABLE IF NOT EXISTS events.trace_log
+(
+    cloudeventid uuid NOT NULL,
+    resource character varying COLLATE pg_catalog."default",
path: src/Events/Migration/v0.45/01-setup-tables.sql
user: login: HenningNormann
body: Why not data type text for all texts?
created_at: 2025-03-11T18:28:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,21 @@
+-- Table: events.trace_log
+
+DROP TABLE IF EXISTS events.trace_log;
+
+CREATE TABLE IF NOT EXISTS events.trace_log
+(
+    cloudeventid uuid NOT NULL,
+    resource character varying COLLATE pg_catalog."default",
+    eventtype character varying COLLATE pg_catalog."default",
+    consumer character varying COLLATE pg_catalog."default",
+    "time" timestamp with time zone NOT NULL DEFAULT (now() AT TIME ZONE 'utc'::text),
+    subscriptionid bigint,
+    responsecode integer,
+    subscriberendpoint character varying COLLATE pg_catalog."default",
+    activity character varying COLLATE pg_catalog."default",
+    sequenceno BIGSERIAL,
+	CONSTRAINT trace_log_pkey PRIMARY KEY (sequenceno, time)
+    
+) PARTITION BY RANGE (time);
+
+CREATE INDEX ON events.trace_log (time);
path: src/Events/Migration/v0.45/01-setup-tables.sql
user: login: HenningNormann
body: Shouldn't this index rather be created on the child tables?
created_at: 2025-03-11T18:38:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,21 @@
+-- Table: events.trace_log
+
+DROP TABLE IF EXISTS events.trace_log;
+
+CREATE TABLE IF NOT EXISTS events.trace_log
+(
+    cloudeventid uuid NOT NULL,
+    resource character varying COLLATE pg_catalog."default",
+    eventtype character varying COLLATE pg_catalog."default",
+    consumer character varying COLLATE pg_catalog."default",
+    "time" timestamp with time zone NOT NULL DEFAULT (now() AT TIME ZONE 'utc'::text),
+    subscriptionid bigint,
+    responsecode integer,
+    subscriberendpoint character varying COLLATE pg_catalog."default",
+    activity character varying COLLATE pg_catalog."default",
+    sequenceno BIGSERIAL,
+	CONSTRAINT trace_log_pkey PRIMARY KEY (sequenceno, time)
+    
+) PARTITION BY RANGE (time);
+
+CREATE INDEX ON events.trace_log (time);
path: src/Events/Migration/v0.45/01-setup-tables.sql
user: login: eskebab
body: from the doc: _Create an index on the key column(s), as well as any other indexes you might want, on the partitioned table. (The key index is not strictly necessary, but in most scenarios it is helpful.) This automatically creates a matching index on each partition, and any partitions you create or attach later will also have such an index._

I also tried testing this locally. It does indeed create the index on time for each individual partition.
created_at: 2025-03-12T11:37:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,21 @@
+-- Table: events.trace_log
+
+DROP TABLE IF EXISTS events.trace_log;
+
+CREATE TABLE IF NOT EXISTS events.trace_log
+(
+    cloudeventid uuid NOT NULL,
+    resource character varying COLLATE pg_catalog."default",
path: src/Events/Migration/v0.45/01-setup-tables.sql
user: login: eskebab
body: events.subscription is defined using character varying for all text columns. 
For consistency? Up for discussion 
created_at: 2025-03-12T11:42:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,21 @@
+-- Table: events.trace_log
+
+DROP TABLE IF EXISTS events.trace_log;
+
+CREATE TABLE IF NOT EXISTS events.trace_log
+(
+    cloudeventid uuid NOT NULL,
+    resource character varying COLLATE pg_catalog."default",
path: src/Events/Migration/v0.45/01-setup-tables.sql
user: login: SandGrainOne
body: Let's turn it around. Why text for all texts? 

The choice we made for subscription might not have been super well thought out. If text is better, then we use text.
created_at: 2025-03-12T11:52:37Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,44 @@
+CREATE OR REPLACE PROCEDURE events.deletesubscription(_id integer)
path: src/Events/Migration/FunctionsAndProcedures/createlogspartition.sql
user: login: SandGrainOne
body: Something wrong here. The new function is defined inside a script for an existing function.
created_at: 2025-03-13T12:16:42Z
author_association: MEMBER
path: src/Events/Migration/v0.45/01-functions-and-procedures.sql
user: login: SandGrainOne
body: I want you to call this file **03-** instead. Just to make sure it's applied last. I think the DBTools application will find it.
created_at: 2025-03-13T12:19:46Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,21 @@
+-- Table: events.trace_log
+
+DROP TABLE IF EXISTS events.trace_log;
+
+CREATE TABLE IF NOT EXISTS events.trace_log
+(
+    cloudeventid uuid NOT NULL,
+    resource character varying COLLATE pg_catalog."default",
path: src/Events/Migration/v0.45/01-setup-tables.sql
user: login: SandGrainOne
body: @HenningNormann Does using `text` have an advantage? 
created_at: 2025-03-13T12:21:08Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,21 @@
+-- Table: events.trace_log
+
+DROP TABLE IF EXISTS events.trace_log;
+
+CREATE TABLE IF NOT EXISTS events.trace_log
+(
+    cloudeventid uuid NOT NULL,
+    resource character varying COLLATE pg_catalog."default",
path: src/Events/Migration/v0.45/01-setup-tables.sql
user: login: HenningNormann
body: You avoid padding and length check. It's closer to C# string than the native alternatives. Text is Postgres specific and a natural choice in Postgres.
created_at: 2025-03-13T12:30:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,37 @@
+CREATE OR REPLACE FUNCTION events.create_logs_partition(
+	)
+    RETURNS integer
+    LANGUAGE 'plpgsql'
+    COST 100
+    VOLATILE PARALLEL UNSAFE
+AS $BODY$
+DECLARE
+   thisMonth int; 
+   nextMonth int;
+   thisYear int;
+   nextYear int;
+   fromMonth varchar;
+   toMonth varchar;
+   partitionName varchar;
+BEGIN
+   SELECT (EXTRACT(MONTH from current_date)) INTO thisMonth;
+   SELECT (EXTRACT(YEAR from current_date)) INTO thisYear;
+
+nextYear = thisYear;
+   nextMonth = thisMonth + 1;
+
+   IF nextMonth = 13 THEN
+      nextMonth = 1;
+	  nextYear = nextYear + 1;
+  END IF;
+
+   fromMonth = thisYear || '-' || lpad(thisMonth::varchar, 2, '0') || '-01' ; 
+   toMonth = nextYear || '-' || lpad(nextMonth::varchar, 2, '0') || '-01';
+   partitionName = 'events.trace_log_y' || nextYear || 'm' || nextMonth;	
+   
+   EXECUTE
path: src/Events/Migration/FunctionsAndProcedures/createlogspartition.sql
user: login: HenningNormann
body: No error handling?
created_at: 2025-03-14T10:38:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,21 @@
+-- Table: events.trace_log
+
+DROP TABLE IF EXISTS events.trace_log;
+
+CREATE TABLE IF NOT EXISTS events.trace_log
+(
+    cloudeventid uuid NOT NULL,
+    resource character varying COLLATE pg_catalog."default",
path: src/Events/Migration/v0.45/01-setup-tables.sql
user: login: eskebab
body: Changed all character varying columns to text and ran integration test with API locally. No changes in repository class necessary based on type change.
created_at: 2025-03-14T11:29:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,37 @@
+CREATE OR REPLACE FUNCTION events.create_logs_partition(
+	)
+    RETURNS integer
+    LANGUAGE 'plpgsql'
+    COST 100
+    VOLATILE PARALLEL UNSAFE
+AS $BODY$
+DECLARE
+   thisMonth int; 
+   nextMonth int;
+   thisYear int;
+   nextYear int;
+   fromMonth varchar;
+   toMonth varchar;
+   partitionName varchar;
+BEGIN
+   SELECT (EXTRACT(MONTH from current_date)) INTO thisMonth;
+   SELECT (EXTRACT(YEAR from current_date)) INTO thisYear;
+
+nextYear = thisYear;
+   nextMonth = thisMonth + 1;
+
+   IF nextMonth = 13 THEN
+      nextMonth = 1;
+	  nextYear = nextYear + 1;
+  END IF;
+
+   fromMonth = thisYear || '-' || lpad(thisMonth::varchar, 2, '0') || '-01' ; 
+   toMonth = nextYear || '-' || lpad(nextMonth::varchar, 2, '0') || '-01';
+   partitionName = 'events.trace_log_y' || nextYear || 'm' || nextMonth;	
+   
+   EXECUTE
path: src/Events/Migration/FunctionsAndProcedures/createlogspartition.sql
user: login: eskebab
body: I was thinking better to let the error bubble up and handle it elsewhere, but I'm open to suggestions. Most importantly, creating a new partition several times ie calling the function multiple times, does not result in duplicate partitions.
created_at: 2025-03-14T12:34:28Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-03-20T13:57:31Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=Altinn_altinn-events&pullRequest=678) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1 New issue](https://sonarcloud.io/project/issues?id=Altinn_altinn-events&pullRequest=678&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=Altinn_altinn-events&pullRequest=678&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=Altinn_altinn-events&pullRequest=678&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=Altinn_altinn-events&pullRequest=678&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=Altinn_altinn-events&pullRequest=678&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=Altinn_altinn-events&pullRequest=678)


author_association: NONE