From 4ac16a119ff0b6a6f4b9a46e87b13b0bbf22c485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Thu, 22 Jan 2026 10:21:56 +0100 Subject: [PATCH] fix(features): increment version to deal with removed event types (#11402) # Which Problems Are Solved User with older instances, created at version 3.3.3 or earlier may have been affected with feature flags no longer applying. Older version of Zitadel had a bug in console where toggling a single feature would apply a state to all features, creating around 12 events each request. Then 4 feature flags and their event types were removed in v4, meaning that the `offset` value in the projection's current state could be wrong by 4 if the instance was affected by the above bug. # How the Problems Are Solved Increment the feature projection versions, so that the tables get recomputed and the current states entries becomes accurate again. # Additional Changes - none # Additional Context - Original bug: https://github.com/zitadel/zitadel/issues/10459 - Reported through support --- internal/query/instance_by_domain.sql | 4 ++-- internal/query/instance_by_id.sql | 4 ++-- internal/query/projection/instance_features.go | 2 +- internal/query/projection/instance_features_test.go | 8 ++++---- internal/query/projection/system_features.go | 2 +- internal/query/projection/system_features_test.go | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/query/instance_by_domain.sql b/internal/query/instance_by_domain.sql index e15728ee2..102b6a181 100644 --- a/internal/query/instance_by_domain.sql +++ b/internal/query/instance_by_domain.sql @@ -6,14 +6,14 @@ with domain as ( ), instance_features as ( select i.* from domain d - join projections.instance_features2 i on d.instance_id = i.instance_id + join projections.instance_features3 i on d.instance_id = i.instance_id ), features as ( select instance_id, json_object_agg( coalesce(i.key, s.key), coalesce(i.value, s.value) ) features from domain d - cross join projections.system_features s + cross join projections.system_features2 s full outer join instance_features i using (instance_id, key) group by instance_id ), external_domains as ( diff --git a/internal/query/instance_by_id.sql b/internal/query/instance_by_id.sql index 66bc94bf4..703e3d610 100644 --- a/internal/query/instance_by_id.sql +++ b/internal/query/instance_by_id.sql @@ -4,8 +4,8 @@ with features as ( coalesce(i.value, s.value) ) features from (select $1::text instance_id) x - cross join projections.system_features s - full outer join projections.instance_features2 i using (key, instance_id) + cross join projections.system_features2 s + full outer join projections.instance_features3 i using (key, instance_id) group by instance_id ), external_domains as ( select instance_id, array_agg(domain) as domains diff --git a/internal/query/projection/instance_features.go b/internal/query/projection/instance_features.go index fee041909..96671b251 100644 --- a/internal/query/projection/instance_features.go +++ b/internal/query/projection/instance_features.go @@ -14,7 +14,7 @@ import ( ) const ( - InstanceFeatureTable = "projections.instance_features2" + InstanceFeatureTable = "projections.instance_features3" InstanceFeatureInstanceIDCol = "instance_id" InstanceFeatureKeyCol = "key" diff --git a/internal/query/projection/instance_features_test.go b/internal/query/projection/instance_features_test.go index 703a0ce00..cbb9ba4fb 100644 --- a/internal/query/projection/instance_features_test.go +++ b/internal/query/projection/instance_features_test.go @@ -38,7 +38,7 @@ func TestInstanceFeaturesProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.instance_features2 (instance_id, key, creation_date, change_date, sequence, value) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (instance_id, key) DO UPDATE SET (creation_date, change_date, sequence, value) = (projections.instance_features2.creation_date, EXCLUDED.change_date, EXCLUDED.sequence, EXCLUDED.value)", + expectedStmt: "INSERT INTO projections.instance_features3 (instance_id, key, creation_date, change_date, sequence, value) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (instance_id, key) DO UPDATE SET (creation_date, change_date, sequence, value) = (projections.instance_features3.creation_date, EXCLUDED.change_date, EXCLUDED.sequence, EXCLUDED.value)", expectedArgs: []interface{}{ "agg-id", "user_schema", @@ -69,7 +69,7 @@ func TestInstanceFeaturesProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.instance_features2 (instance_id, key, creation_date, change_date, sequence, value) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (instance_id, key) DO UPDATE SET (creation_date, change_date, sequence, value) = (projections.instance_features2.creation_date, EXCLUDED.change_date, EXCLUDED.sequence, EXCLUDED.value)", + expectedStmt: "INSERT INTO projections.instance_features3 (instance_id, key, creation_date, change_date, sequence, value) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (instance_id, key) DO UPDATE SET (creation_date, change_date, sequence, value) = (projections.instance_features3.creation_date, EXCLUDED.change_date, EXCLUDED.sequence, EXCLUDED.value)", expectedArgs: []interface{}{ "instance-id", "login_default_org", @@ -100,7 +100,7 @@ func TestInstanceFeaturesProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.instance_features2 WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.instance_features3 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, @@ -126,7 +126,7 @@ func TestInstanceFeaturesProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.instance_features2 WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.instance_features3 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, diff --git a/internal/query/projection/system_features.go b/internal/query/projection/system_features.go index 28f3bb67f..b456ccab7 100644 --- a/internal/query/projection/system_features.go +++ b/internal/query/projection/system_features.go @@ -12,7 +12,7 @@ import ( ) const ( - SystemFeatureTable = "projections.system_features" + SystemFeatureTable = "projections.system_features2" SystemFeatureKeyCol = "key" SystemFeatureCreationDateCol = "creation_date" diff --git a/internal/query/projection/system_features_test.go b/internal/query/projection/system_features_test.go index b64db7fb0..69598a9d5 100644 --- a/internal/query/projection/system_features_test.go +++ b/internal/query/projection/system_features_test.go @@ -36,7 +36,7 @@ func TestSystemFeaturesProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.system_features (key, creation_date, change_date, sequence, value) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (key) DO UPDATE SET (creation_date, change_date, sequence, value) = (projections.system_features.creation_date, EXCLUDED.change_date, EXCLUDED.sequence, EXCLUDED.value)", + expectedStmt: "INSERT INTO projections.system_features2 (key, creation_date, change_date, sequence, value) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (key) DO UPDATE SET (creation_date, change_date, sequence, value) = (projections.system_features2.creation_date, EXCLUDED.change_date, EXCLUDED.sequence, EXCLUDED.value)", expectedArgs: []interface{}{ "user_schema", anyArg{}, @@ -66,7 +66,7 @@ func TestSystemFeaturesProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.system_features WHERE (key IS NOT NULL)", + expectedStmt: "DELETE FROM projections.system_features2 WHERE (key IS NOT NULL)", expectedArgs: []interface{}{}, }, },