feat: Feature flag for relational tables (#10599)

# Which Problems Are Solved

This PR introduces a new feature flag `EnableRelationalTables` that will
be used in following implementations to decide whether Zitadel should
use the relational model or the event sourcing one.

# TODO

  - [x] Implement flag at system level
- [x] Display the flag on console:
https://github.com/zitadel/zitadel/pull/10615

# How the Problems Are Solved

  - Implement loading the flag from config
- Add persistence of the flag through gRPC endpoint
(SetInstanceFeatures)
- Implement reading of the flag through gRPC endpoint
(GetInstanceFeatures)

# Additional Changes

Some minor refactoring to remove un-needed generics annotations

# Additional Context

- Closes #10574

---------

Co-authored-by: Silvan <[email protected]>
This commit is contained in:
Marco A.
2025-09-02 09:48:46 +00:00
committed by GitHub
co-authored by Silvan
parent e3dff2482e
commit 75a67be669
46 changed files with 669 additions and 91 deletions
@@ -26,6 +26,7 @@ func systemFeaturesToCommand(req *feature_pb.SetSystemFeaturesRequest) (*command
EnableBackChannelLogout: req.EnableBackChannelLogout,
LoginV2: loginV2,
PermissionCheckV2: req.PermissionCheckV2,
EnableRelationalTables: req.EnableRelationalTables,
}, nil
}
@@ -40,6 +41,7 @@ func systemFeaturesToPb(f *query.SystemFeatures) *feature_pb.GetSystemFeaturesRe
EnableBackChannelLogout: featureSourceToFlagPb(&f.EnableBackChannelLogout),
LoginV2: loginV2ToLoginV2FlagPb(f.LoginV2),
PermissionCheckV2: featureSourceToFlagPb(&f.PermissionCheckV2),
EnableRelationalTables: featureSourceToFlagPb(&f.EnableRelationalTables),
}
}
@@ -59,6 +61,7 @@ func instanceFeaturesToCommand(req *feature_pb.SetInstanceFeaturesRequest) (*com
LoginV2: loginV2,
PermissionCheckV2: req.PermissionCheckV2,
ConsoleUseV2UserApi: req.ConsoleUseV2UserApi,
EnableRelationalTables: req.EnableRelationalTables,
}, nil
}
@@ -75,6 +78,7 @@ func instanceFeaturesToPb(f *query.InstanceFeatures) *feature_pb.GetInstanceFeat
LoginV2: loginV2ToLoginV2FlagPb(f.LoginV2),
PermissionCheckV2: featureSourceToFlagPb(&f.PermissionCheckV2),
ConsoleUseV2UserApi: featureSourceToFlagPb(&f.ConsoleUseV2UserApi),
EnableRelationalTables: featureSourceToFlagPb(&f.EnableRelationalTables),
}
}
+49 -2
View File
@@ -18,6 +18,8 @@ import (
)
func Test_systemFeaturesToCommand(t *testing.T) {
t.Parallel()
// Given
arg := &feature_pb.SetSystemFeaturesRequest{
LoginDefaultOrg: gu.Ptr(true),
UserSchema: gu.Ptr(true),
@@ -28,6 +30,9 @@ func Test_systemFeaturesToCommand(t *testing.T) {
Required: true,
BaseUri: gu.Ptr("https://login.com"),
},
EnableRelationalTables: gu.Ptr(true),
EnableBackChannelLogout: gu.Ptr(true),
PermissionCheckV2: gu.Ptr(true),
}
want := &command.SystemFeatures{
LoginDefaultOrg: gu.Ptr(true),
@@ -39,13 +44,23 @@ func Test_systemFeaturesToCommand(t *testing.T) {
Required: true,
BaseURI: &url.URL{Scheme: "https", Host: "login.com"},
},
EnableRelationalTables: gu.Ptr(true),
EnableBackChannelLogout: gu.Ptr(true),
PermissionCheckV2: gu.Ptr(true),
}
// Test
got, err := systemFeaturesToCommand(arg)
// Verify
assert.Equal(t, want, got)
assert.NoError(t, err)
}
func Test_systemFeaturesToPb(t *testing.T) {
t.Parallel()
// Given
arg := &query.SystemFeatures{
Details: &domain.ObjectDetails{
Sequence: 22,
@@ -87,6 +102,10 @@ func Test_systemFeaturesToPb(t *testing.T) {
Level: feature.LevelSystem,
Value: true,
},
EnableRelationalTables: query.FeatureSource[bool]{
Level: feature.LevelSystem,
Value: true,
},
}
want := &feature_pb.GetSystemFeaturesResponse{
Details: &object.Details{
@@ -127,12 +146,22 @@ func Test_systemFeaturesToPb(t *testing.T) {
Enabled: true,
Source: feature_pb.Source_SOURCE_SYSTEM,
},
EnableRelationalTables: &feature_pb.FeatureFlag{
Enabled: true,
Source: feature_pb.Source_SOURCE_SYSTEM,
},
}
// Test
got := systemFeaturesToPb(arg)
// Verify
assert.Equal(t, want, got)
}
func Test_instanceFeaturesToCommand(t *testing.T) {
t.Parallel()
// Given
arg := &feature_pb.SetInstanceFeaturesRequest{
LoginDefaultOrg: gu.Ptr(true),
UserSchema: gu.Ptr(true),
@@ -145,7 +174,9 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
Required: true,
BaseUri: gu.Ptr("https://login.com"),
},
ConsoleUseV2UserApi: gu.Ptr(true),
ConsoleUseV2UserApi: gu.Ptr(true),
PermissionCheckV2: gu.Ptr(false),
EnableRelationalTables: gu.Ptr(true),
}
want := &command.InstanceFeatures{
LoginDefaultOrg: gu.Ptr(true),
@@ -159,14 +190,22 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
Required: true,
BaseURI: &url.URL{Scheme: "https", Host: "login.com"},
},
ConsoleUseV2UserApi: gu.Ptr(true),
ConsoleUseV2UserApi: gu.Ptr(true),
PermissionCheckV2: gu.Ptr(false),
EnableRelationalTables: gu.Ptr(true),
}
// Test
got, err := instanceFeaturesToCommand(arg)
// Verify
assert.Equal(t, want, got)
assert.NoError(t, err)
}
func Test_instanceFeaturesToPb(t *testing.T) {
t.Parallel()
arg := &query.InstanceFeatures{
Details: &domain.ObjectDetails{
Sequence: 22,
@@ -212,6 +251,10 @@ func Test_instanceFeaturesToPb(t *testing.T) {
Level: feature.LevelInstance,
Value: true,
},
EnableRelationalTables: query.FeatureSource[bool]{
Level: feature.LevelInstance,
Value: true,
},
}
want := &feature_pb.GetInstanceFeaturesResponse{
Details: &object.Details{
@@ -260,6 +303,10 @@ func Test_instanceFeaturesToPb(t *testing.T) {
Enabled: true,
Source: feature_pb.Source_SOURCE_INSTANCE,
},
EnableRelationalTables: &feature_pb.FeatureFlag{
Enabled: true,
Source: feature_pb.Source_SOURCE_INSTANCE,
},
}
got := instanceFeaturesToPb(arg)
assert.Equal(t, want, got)
@@ -76,7 +76,8 @@ func TestServer_SetSystemFeatures(t *testing.T) {
args: args{
ctx: SystemCTX,
req: &feature.SetSystemFeaturesRequest{
LoginDefaultOrg: gu.Ptr(true),
LoginDefaultOrg: gu.Ptr(true),
EnableRelationalTables: gu.Ptr(true),
},
},
want: &feature.SetSystemFeaturesResponse{
@@ -170,8 +171,9 @@ func TestServer_GetSystemFeatures(t *testing.T) {
name: "some features",
prepare: func(t *testing.T) {
_, err := Client.SetSystemFeatures(SystemCTX, &feature.SetSystemFeaturesRequest{
LoginDefaultOrg: gu.Ptr(true),
UserSchema: gu.Ptr(false),
LoginDefaultOrg: gu.Ptr(true),
UserSchema: gu.Ptr(false),
EnableRelationalTables: gu.Ptr(true),
})
require.NoError(t, err)
},
@@ -188,6 +190,10 @@ func TestServer_GetSystemFeatures(t *testing.T) {
Enabled: false,
Source: feature.Source_SOURCE_SYSTEM,
},
EnableRelationalTables: &feature.FeatureFlag{
Enabled: true,
Source: feature.Source_SOURCE_SYSTEM,
},
},
},
}
@@ -209,6 +215,7 @@ func TestServer_GetSystemFeatures(t *testing.T) {
require.NoError(t, err)
assertFeatureFlag(t, tt.want.LoginDefaultOrg, got.LoginDefaultOrg)
assertFeatureFlag(t, tt.want.UserSchema, got.UserSchema)
assertFeatureFlag(t, tt.want.EnableRelationalTables, got.EnableRelationalTables)
})
}
}
@@ -247,7 +254,8 @@ func TestServer_SetInstanceFeatures(t *testing.T) {
args: args{
ctx: IamCTX,
req: &feature.SetInstanceFeaturesRequest{
LoginDefaultOrg: gu.Ptr(true),
LoginDefaultOrg: gu.Ptr(true),
EnableRelationalTables: gu.Ptr(true),
},
},
want: &feature.SetInstanceFeaturesResponse{
@@ -363,14 +371,19 @@ func TestServer_GetInstanceFeatures(t *testing.T) {
Enabled: false,
Source: feature.Source_SOURCE_UNSPECIFIED,
},
EnableRelationalTables: &feature.FeatureFlag{
Enabled: false,
Source: feature.Source_SOURCE_UNSPECIFIED,
},
},
},
{
name: "some features, no inheritance",
prepare: func(t *testing.T) {
_, err := Client.SetInstanceFeatures(IamCTX, &feature.SetInstanceFeaturesRequest{
LoginDefaultOrg: gu.Ptr(true),
UserSchema: gu.Ptr(true),
LoginDefaultOrg: gu.Ptr(true),
UserSchema: gu.Ptr(true),
EnableRelationalTables: gu.Ptr(true),
})
require.NoError(t, err)
},
@@ -387,6 +400,10 @@ func TestServer_GetInstanceFeatures(t *testing.T) {
Enabled: true,
Source: feature.Source_SOURCE_INSTANCE,
},
EnableRelationalTables: &feature.FeatureFlag{
Enabled: true,
Source: feature.Source_SOURCE_INSTANCE,
},
},
},
{
@@ -412,6 +429,10 @@ func TestServer_GetInstanceFeatures(t *testing.T) {
Enabled: false,
Source: feature.Source_SOURCE_UNSPECIFIED,
},
EnableRelationalTables: &feature.FeatureFlag{
Enabled: false,
Source: feature.Source_SOURCE_UNSPECIFIED,
},
},
},
}
@@ -433,6 +454,7 @@ func TestServer_GetInstanceFeatures(t *testing.T) {
require.NoError(t, err)
assertFeatureFlag(t, tt.want.LoginDefaultOrg, got.LoginDefaultOrg)
assertFeatureFlag(t, tt.want.UserSchema, got.UserSchema)
assertFeatureFlag(t, tt.want.EnableRelationalTables, got.EnableRelationalTables)
})
}
}