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 <27845747+adlerhurst@users.noreply.github.com>
This commit is contained in:
Marco A.
2025-09-02 11:48:46 +02:00
committed by GitHub
parent e3dff2482e
commit 75a67be669
46 changed files with 669 additions and 91 deletions

View File

@@ -1146,6 +1146,7 @@ DefaultInstance:
# BaseURI: "" # ZITADEL_DEFAULTINSTANCE_FEATURES_LOGINV2_BASEURI
# PermissionCheckV2: false # ZITADEL_DEFAULTINSTANCE_FEATURES_PERMISSIONCHECKV2
# ConsoleUseV2UserApi: false # ZITADEL_DEFAULTINSTANCE_FEATURES_CONSOLEUSEV2USERAPI
# EnableRelationalTables: false # ZITADEL_DEFAULTINSTANCE_FEATURES_ENABLERELATIONALTABLES
Limits:
# AuditLogRetention limits the number of events that can be queried via the events API by their age.

View File

@@ -36,6 +36,7 @@ const FEATURE_KEYS = [
'oidcTokenExchange',
'permissionCheckV2',
'userSchema',
'enableRelationalTables',
] as const;
export type ToggleState = { source: Source; enabled: boolean };

View File

@@ -1653,7 +1653,9 @@
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Когато този флаг е активиран, конзолата използва V2 User API за създаване на нови потребители. С V2 API новосъздадените потребители започват без начален статус.",
"LOGINV2": "Вход V2",
"LOGINV2_DESCRIPTION": "Активирането на това включва новия потребителски интерфейс за вход, базиран на TypeScript, с подобрена сигурност, производителност и възможности за персонализиране.",
"LOGINV2_BASEURI": "Базов URI"
"LOGINV2_BASEURI": "Базов URI",
"ENABLERELATIONALTABLES": "Релационни таблици",
"ENABLERELATIONALTABLES_DESCRIPTION": "Активирайте това, за да използвате релационни таблици вместо проекции за съхранение на данни. В момента този превключвател не прави нищо."
},
"DIALOG": {
"RESET": {

View File

@@ -1654,7 +1654,9 @@
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Když je tato příznak povolen, konzole používá V2 User API k vytvoření nových uživatelů. S V2 API nově vytvoření uživatelé začínají bez počátečního stavu.",
"LOGINV2": "Přihlášení V2",
"LOGINV2_DESCRIPTION": "Povolením této možnosti se aktivuje nové přihlašovací rozhraní založené na TypeScriptu s vylepšeným zabezpečením, výkonem a přizpůsobitelností.",
"LOGINV2_BASEURI": "Základní URI"
"LOGINV2_BASEURI": "Základní URI",
"ENABLERELATIONALTABLES": "Relační tabulky",
"ENABLERELATIONALTABLES_DESCRIPTION": "Povolte toto pro použití relačních tabulek místo projekcí pro ukládání dat. Momentálně tento přepínač nic nedělá."
},
"DIALOG": {
"RESET": {

View File

@@ -1654,7 +1654,9 @@
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Wenn diese Option aktiviert ist, verwendet die Konsole die V2 User API, um neue Benutzer zu erstellen. Mit der V2 API starten neu erstellte Benutzer nicht im Initial Zustand.",
"LOGINV2": "Login V2",
"LOGINV2_DESCRIPTION": "Durch das Aktivieren wird das neue TypeScript-basierte Login-UI mit verbesserter Sicherheit, Leistung und Anpassbarkeit aktiviert.",
"LOGINV2_BASEURI": "Basis-URI"
"LOGINV2_BASEURI": "Basis-URI",
"ENABLERELATIONALTABLES": "Relationale Tabellen",
"ENABLERELATIONALTABLES_DESCRIPTION": "Aktivieren Sie diese Option, um relationale Tabellen anstelle von Projektionen zur Datenspeicherung zu verwenden. Diese Option hat derzeit keine Auswirkung."
},
"DIALOG": {
"RESET": {

View File

@@ -1658,7 +1658,9 @@
"CONSOLEUSEV2USERAPI_DESCRIPTION": "When this flag is enabled, the console uses the V2 User API to create new users. With the V2 API, newly created users start without an initial state.",
"LOGINV2": "Login V2",
"LOGINV2_DESCRIPTION": "Enabling this activates the new TypeScript-based login UI with improved security, performance, and customization.",
"LOGINV2_BASEURI": "Base URI"
"LOGINV2_BASEURI": "Base URI",
"ENABLERELATIONALTABLES": "Relational Tables",
"ENABLERELATIONALTABLES_DESCRIPTION": "Enable this to use relational tables instead of projections for storing data. Currently this toggle does nothing."
},
"DIALOG": {
"RESET": {

View File

@@ -1655,7 +1655,9 @@
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Cuando esta opción está habilitada, la consola utiliza la API V2 de usuario para crear nuevos usuarios. Con la API V2, los usuarios recién creados comienzan sin un estado inicial.",
"LOGINV2": "Inicio de sesión V2",
"LOGINV2_DESCRIPTION": "Al habilitar esto, se activa la nueva interfaz de inicio de sesión basada en TypeScript con mejoras en seguridad, rendimiento y personalización.",
"LOGINV2_BASEURI": "URI base"
"LOGINV2_BASEURI": "URI base",
"ENABLERELATIONALTABLES": "Tablas relacionales",
"ENABLERELATIONALTABLES_DESCRIPTION": "Habilitar esto para usar tablas relacionales en lugar de proyecciones para almacenar datos. Actualmente este interruptor no hace nada."
},
"DIALOG": {
"RESET": {

View File

@@ -1654,7 +1654,9 @@
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Lorsque ce drapeau est activé, la console utilise l'API V2 User pour créer de nouveaux utilisateurs. Avec l'API V2, les nouveaux utilisateurs commencent sans état initial.",
"LOGINV2": "Connexion V2",
"LOGINV2_DESCRIPTION": "Lactivation de cette option lance la nouvelle interface de connexion basée sur TypeScript, avec une sécurité, des performances et une personnalisation améliorées.",
"LOGINV2_BASEURI": "URI de base"
"LOGINV2_BASEURI": "URI de base",
"ENABLERELATIONALTABLES": "Tables relationnelles",
"ENABLERELATIONALTABLES_DESCRIPTION": "Activez ceci pour utiliser des tables relationnelles au lieu de projections pour stocker des données. Actuellement, ce commutateur ne fait rien."
},
"DIALOG": {
"RESET": {

View File

@@ -1652,7 +1652,9 @@
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Ha ez a jelző engedélyezve van, a konzol a V2 User API-t használja új felhasználók létrehozásához. A V2 API-val az újonnan létrehozott felhasználók kezdeti állapot nélkül indulnak.",
"LOGINV2": "Bejelentkezés V2",
"LOGINV2_DESCRIPTION": "Ennek engedélyezésével aktiválódik az új, TypeScript-alapú bejelentkezési felület, amely jobb biztonságot, teljesítményt és testreszabhatóságot nyújt.",
"LOGINV2_BASEURI": "Alap URI"
"LOGINV2_BASEURI": "Alap URI",
"ENABLERELATIONALTABLES": "Kapcsolati táblák",
"ENABLERELATIONALTABLES_DESCRIPTION": "Engedélyez ezt a relációs táblák használatához a projekciók helyett az adatok tárolásához. Jelenleg ez a kapcsoló semmit sem csinál."
},
"DIALOG": {
"RESET": {

View File

@@ -1521,7 +1521,9 @@
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Ketika flag ini diaktifkan, konsol menggunakan API Pengguna V2 untuk membuat pengguna baru. Dengan API V2, pengguna yang baru dibuat dimulai tanpa keadaan awal.",
"LOGINV2": "Login V2",
"LOGINV2_DESCRIPTION": "Mengaktifkan ini akan mengaktifkan antarmuka login baru berbasis TypeScript dengan keamanan, performa, dan kustomisasi yang lebih baik.",
"LOGINV2_BASEURI": "URI dasar"
"LOGINV2_BASEURI": "URI dasar",
"ENABLERELATIONALTABLES": "Tabel Relasional",
"ENABLERELATIONALTABLES_DESCRIPTION": "Aktifkan ini untuk menggunakan tabel relasional alih-alih proyeksi untuk menyimpan data. Saat ini, toggle ini tidak berfungsi."
},
"DIALOG": {
"RESET": {

View File

@@ -1654,7 +1654,9 @@
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Quando questa opzione è abilitata, la console utilizza l'API V2 User per creare nuovi utenti. Con l'API V2, i nuovi utenti creati iniziano senza uno stato iniziale.",
"LOGINV2": "Accesso V2",
"LOGINV2_DESCRIPTION": "Abilitando questa opzione si attiva la nuova interfaccia di login basata su TypeScript con sicurezza, prestazioni e personalizzazione migliorate.",
"LOGINV2_BASEURI": "URI di base"
"LOGINV2_BASEURI": "URI di base",
"ENABLERELATIONALTABLES": "Tabelle relazionali",
"ENABLERELATIONALTABLES_DESCRIPTION": "Abilita questo toggle per utilizzare le tabelle relazionali invece di proiezioni per memorizzare i dati. Attualmente questo toggle non fa nulla."
},
"DIALOG": {
"RESET": {

View File

@@ -1654,7 +1654,9 @@
"CONSOLEUSEV2USERAPI_DESCRIPTION": "このフラグが有効化されると、コンソールはV2ユーザーAPIを使用して新しいユーザーを作成します。V2 APIでは、新しく作成されたユーザーは初期状態なしで開始します。",
"LOGINV2": "ログイン V2",
"LOGINV2_DESCRIPTION": "これを有効にすると、セキュリティ、パフォーマンス、およびカスタマイズ性が向上した、TypeScript ベースの新しいログイン UI が有効になります。",
"LOGINV2_BASEURI": "ベースURI"
"LOGINV2_BASEURI": "ベースURI",
"ENABLERELATIONALTABLES": "リレーショナルテーブル",
"ENABLERELATIONALTABLES_DESCRIPTION": "データを保存するためにプロジェクションの代わりにリレーショナルテーブルを使用するには、これを有効にします。現在、このトグルは何もしません。"
},
"DIALOG": {
"RESET": {

View File

@@ -1654,7 +1654,9 @@
"CONSOLEUSEV2USERAPI_DESCRIPTION": "이 플래그가 활성화되면 콘솔은 V2 사용자 API를 사용하여 새 사용자를 생성합니다. V2 API를 사용하면 새로 생성된 사용자는 초기 상태 없이 시작합니다.",
"LOGINV2": "로그인 V2",
"LOGINV2_DESCRIPTION": "이 옵션을 활성화하면 보안, 성능 및 사용자 정의 기능이 향상된 새로운 TypeScript 기반 로그인 UI가 활성화됩니다.",
"LOGINV2_BASEURI": "기본 URI"
"LOGINV2_BASEURI": "기본 URI",
"ENABLERELATIONALTABLES": "관계형 테이블",
"ENABLERELATIONALTABLES_DESCRIPTION": "데이터 저장을 위해 프로젝션 대신 관계형 테이블을 사용하려면 이를 활성화하십시오. 현재 이 토글은 아무런 기능을 하지 않습니다."
},
"DIALOG": {
"RESET": {

View File

@@ -1655,7 +1655,9 @@
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Кога ова знаме е овозможено, конзолата го користи V2 User API за креирање на нови корисници. Со V2 API, новосоздадените корисници започнуваат без почетна состојба.",
"LOGINV2": "Најава V2",
"LOGINV2_DESCRIPTION": "Овозможувањето на ова ја активира новата TypeScript-базирана најава со подобрена безбедност, перформанси и прилагодливост.",
"LOGINV2_BASEURI": "Основен URI"
"LOGINV2_BASEURI": "Основен URI",
"ENABLERELATIONALTABLES": "Релациски табели",
"ENABLERELATIONALTABLES_DESCRIPTION": "Овозможете го ова за користење на релациски табели наместо проекции за складирање податоци. Во моментов, овој прекинувач не прави ништо."
},
"DIALOG": {
"RESET": {

View File

@@ -1654,7 +1654,9 @@
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Wanneer deze vlag is ingeschakeld, gebruikt de console de V2 User API om nieuwe gebruikers aan te maken. Met de V2 API beginnen nieuw aangemaakte gebruikers zonder een initiële status.",
"LOGINV2": "Inloggen V2",
"LOGINV2_DESCRIPTION": "Door dit in te schakelen wordt de nieuwe TypeScript-gebaseerde login-UI geactiveerd met verbeterde beveiliging, prestaties en aanpasbaarheid.",
"LOGINV2_BASEURI": "Basis-URI"
"LOGINV2_BASEURI": "Basis-URI",
"ENABLERELATIONALTABLES": "Relationele tabellen",
"ENABLERELATIONALTABLES_DESCRIPTION": "Schakel dit in om relationele tabellen te gebruiken in plaats van projecties voor het opslaan van gegevens. Momenteel doet deze schakelaar niets."
},
"DIALOG": {
"RESET": {

View File

@@ -1653,7 +1653,9 @@
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Gdy ta flaga jest włączona, konsola używa API V2 User do tworzenia nowych użytkowników. W przypadku API V2 nowo utworzeni użytkownicy rozpoczynają bez stanu początkowego.",
"LOGINV2": "Logowanie V2",
"LOGINV2_DESCRIPTION": "Włączenie tej opcji aktywuje nowy interfejs logowania oparty na TypeScript z ulepszonym bezpieczeństwem, wydajnością i możliwością dostosowania.",
"LOGINV2_BASEURI": "Podstawowy URI"
"LOGINV2_BASEURI": "Podstawowy URI",
"ENABLERELATIONALTABLES": "Tabele relacyjne",
"ENABLERELATIONALTABLES_DESCRIPTION": "Włącz to, aby używać tabel relacyjnych zamiast projekcji do przechowywania danych. Obecnie ten przełącznik nic nie robi."
},
"DIALOG": {
"RESET": {

View File

@@ -1655,7 +1655,9 @@
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Quando esta opção está ativada, o console utiliza a API V2 de Usuários para criar novos usuários. Com a API V2, os novos usuários criados começam sem um estado inicial.",
"LOGINV2": "Login V2",
"LOGINV2_DESCRIPTION": "Ativar esta opção ativa a nova interface de login baseada em TypeScript, com melhorias na segurança, desempenho e personalização.",
"LOGINV2_BASEURI": "URI base"
"LOGINV2_BASEURI": "URI base",
"ENABLERELATIONALTABLES": "Tabelas relacionais",
"ENABLERELATIONALTABLES_DESCRIPTION": "Ative isso para usar tabelas relacionais em vez de projeções para armazenar dados. Atualmente, este interruptor não faz nada."
},
"DIALOG": {
"RESET": {

View File

@@ -1652,7 +1652,9 @@
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Când acest indicator este activat, consola utilizează API-ul de utilizator V2 pentru a crea utilizatori noi. Cu API-ul V2, utilizatorii nou creați încep fără o stare inițială.",
"LOGINV2": "Autentificare V2",
"LOGINV2_DESCRIPTION": "Activarea acestei opțiuni pornește noua interfață de autentificare bazată pe TypeScript, cu securitate, performanță și personalizare îmbunătățite.",
"LOGINV2_BASEURI": "URI de bază"
"LOGINV2_BASEURI": "URI de bază",
"ENABLERELATIONALTABLES": "Tabele relaționale",
"ENABLERELATIONALTABLES_DESCRIPTION": "Activați acest lucru pentru a utiliza tabele relaționale în loc de proiecții pentru stocarea datelor. În prezent, acest comutator nu face nimic."
},
"DIALOG": {
"RESET": {

View File

@@ -1707,7 +1707,9 @@
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Когда этот флаг включен, консоль использует V2 User API для создания новых пользователей. С API V2 новые пользователи создаются без начального состояния.",
"LOGINV2": "Вход V2",
"LOGINV2_DESCRIPTION": "Включение этой опции активирует новый интерфейс входа на основе TypeScript с улучшенной безопасностью, производительностью и возможностью настройки.",
"LOGINV2_BASEURI": "Базовый URI"
"LOGINV2_BASEURI": "Базовый URI",
"ENABLERELATIONALTABLES": "Реляционные таблицы",
"ENABLERELATIONALTABLES_DESCRIPTION": "Включите это, чтобы использовать реляционные таблицы вместо проекций для хранения данных. В настоящее время этот переключатель ничего не делает."
},
"DIALOG": {
"RESET": {

View File

@@ -1658,7 +1658,9 @@
"CONSOLEUSEV2USERAPI_DESCRIPTION": "När denna flagga är aktiverad använder konsolen V2 User API för att skapa nya användare. Med V2 API startar nyligen skapade användare utan ett initialt tillstånd.",
"LOGINV2": "Inloggning V2",
"LOGINV2_DESCRIPTION": "Att aktivera detta startar det nya inloggningsgränssnittet baserat på TypeScript med förbättrad säkerhet, prestanda och anpassning.",
"LOGINV2_BASEURI": "Bas-URI"
"LOGINV2_BASEURI": "Bas-URI",
"ENABLERELATIONALTABLES": "Relationella tabeller",
"ENABLERELATIONALTABLES_DESCRIPTION": "Aktivera detta för att använda relationella tabeller istället för projektioner för att lagra data. För närvarande gör denna växel ingenting."
},
"DIALOG": {
"RESET": {

View File

@@ -1657,7 +1657,9 @@
"CONSOLEUSEV2USERAPI_DESCRIPTION": "Bu bayrak etkinleştirildiğinde, konsol yeni kullanıcılar oluşturmak için V2 Kullanıcı API'sini kullanır. V2 API ile yeni oluşturulan kullanıcılar başlangıç durumu olmadan başlar.",
"LOGINV2": "Giriş V2",
"LOGINV2_DESCRIPTION": "Bunu etkinleştirmek, gelişmiş güvenlik, performans ve özelleştirme ile yeni TypeScript tabanlı giriş UI'ını etkinleştirir.",
"LOGINV2_BASEURI": "Temel URI"
"LOGINV2_BASEURI": "Temel URI",
"ENABLERELATIONALTABLES": "İlişkisel Tablolar",
"ENABLERELATIONALTABLES_DESCRIPTION": "Veri depolamak için projeksiyonlar yerine ilişkisel tabloları kullanmak için bunu etkinleştirin. Şu anda bu anahtar hiçbir şey yapmıyor."
},
"DIALOG": {
"RESET": {

View File

@@ -1657,7 +1657,9 @@
"CONSOLEUSEV2USERAPI_DESCRIPTION": "启用此标志时控制台使用V2用户API创建新用户。使用V2 API新创建的用户将以无初始状态开始。",
"LOGINV2": "登录 V2",
"LOGINV2_DESCRIPTION": "启用此选项将激活基于 TypeScript 的新登录界面,具有更高的安全性、性能和可定制性。",
"LOGINV2_BASEURI": "基础 URI"
"LOGINV2_BASEURI": "基础 URI",
"ENABLERELATIONALTABLES": "关系表",
"ENABLERELATIONALTABLES_DESCRIPTION": "启用此功能以使用关系表而不是投影来存储数据。目前此切换没有任何作用。"
},
"DIALOG": {
"RESET": {

View File

@@ -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),
}
}

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)

View File

@@ -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)
})
}
}

View File

@@ -141,6 +141,7 @@ type InstanceSetupFeatures struct {
LoginV2 *InstanceSetupFeatureLoginV2
PermissionCheckV2 *bool
ConsoleUseV2UserApi *bool
EnableRelationalTables *bool
}
type InstanceSetupFeatureLoginV2 struct {
@@ -174,6 +175,7 @@ func (f *InstanceSetupFeatures) ToInstanceFeatures() (_ *InstanceFeatures, err e
LoginV2: loginV2,
PermissionCheckV2: f.PermissionCheckV2,
ConsoleUseV2UserApi: f.ConsoleUseV2UserApi,
EnableRelationalTables: f.EnableRelationalTables,
}, nil
}

View File

@@ -23,10 +23,11 @@ type InstanceFeatures struct {
LoginV2 *feature.LoginV2
PermissionCheckV2 *bool
ConsoleUseV2UserApi *bool
EnableRelationalTables *bool
}
func (m *InstanceFeatures) isEmpty() bool {
return m.LoginDefaultOrg == nil &&
return m == nil || (m.LoginDefaultOrg == nil &&
m.UserSchema == nil &&
m.TokenExchange == nil &&
// nil check to allow unset improvements
@@ -35,7 +36,9 @@ func (m *InstanceFeatures) isEmpty() bool {
m.OIDCSingleV1SessionTermination == nil &&
m.EnableBackChannelLogout == nil &&
m.LoginV2 == nil &&
m.PermissionCheckV2 == nil && m.ConsoleUseV2UserApi == nil
m.PermissionCheckV2 == nil &&
m.ConsoleUseV2UserApi == nil &&
m.EnableRelationalTables == nil)
}
func (c *Commands) SetInstanceFeatures(ctx context.Context, f *InstanceFeatures) (*domain.ObjectDetails, error) {

View File

@@ -76,6 +76,7 @@ func (m *InstanceFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder {
feature_v2.InstanceLoginVersion,
feature_v2.InstancePermissionCheckV2,
feature_v2.InstanceConsoleUseV2UserApi,
feature_v2.InstanceEnableRelationalTables,
).
Builder().ResourceOwner(m.ResourceOwner)
}
@@ -117,6 +118,9 @@ func reduceInstanceFeature(features *InstanceFeatures, key feature.Key, value an
case feature.KeyConsoleUseV2UserApi:
v := value.(bool)
features.ConsoleUseV2UserApi = &v
case feature.KeyEnableRelationalTables:
v := value.(bool)
features.EnableRelationalTables = &v
}
}
@@ -133,5 +137,6 @@ func (wm *InstanceFeaturesWriteModel) setCommands(ctx context.Context, f *Instan
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.LoginV2, f.LoginV2, feature_v2.InstanceLoginVersion)
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.PermissionCheckV2, f.PermissionCheckV2, feature_v2.InstancePermissionCheckV2)
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.ConsoleUseV2UserApi, f.ConsoleUseV2UserApi, feature_v2.InstanceConsoleUseV2UserApi)
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.EnableRelationalTables, f.EnableRelationalTables, feature_v2.InstanceEnableRelationalTables)
return cmds
}

View File

@@ -0,0 +1,144 @@
package command
import (
"testing"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/internal/feature"
)
func Test_reduceInstanceFeature(t *testing.T) {
type args struct {
features *InstanceFeatures
key feature.Key
value any
}
tt := []struct {
name string
args args
expected *InstanceFeatures
}{
{
name: "key unspecified",
args: args{
features: &InstanceFeatures{},
key: feature.KeyUnspecified,
value: true,
},
expected: &InstanceFeatures{},
},
{
name: "login default org",
args: args{
features: &InstanceFeatures{},
key: feature.KeyLoginDefaultOrg,
value: true,
},
expected: &InstanceFeatures{LoginDefaultOrg: gu.Ptr(true)},
},
{
name: "token exchange",
args: args{
features: &InstanceFeatures{},
key: feature.KeyTokenExchange,
value: true,
},
expected: &InstanceFeatures{TokenExchange: gu.Ptr(true)},
},
{
name: "user schema",
args: args{
features: &InstanceFeatures{},
key: feature.KeyUserSchema,
value: true,
},
expected: &InstanceFeatures{UserSchema: gu.Ptr(true)},
},
{
name: "improved performance",
args: args{
features: &InstanceFeatures{},
key: feature.KeyImprovedPerformance,
value: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeProject},
},
expected: &InstanceFeatures{ImprovedPerformance: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeProject}},
},
{
name: "debug OIDC parent error",
args: args{
features: &InstanceFeatures{},
key: feature.KeyDebugOIDCParentError,
value: true,
},
expected: &InstanceFeatures{DebugOIDCParentError: gu.Ptr(true)},
},
{
name: "OIDC single v1 session termination",
args: args{
features: &InstanceFeatures{},
key: feature.KeyOIDCSingleV1SessionTermination,
value: true,
},
expected: &InstanceFeatures{OIDCSingleV1SessionTermination: gu.Ptr(true)},
},
{
name: "enable back channel logout",
args: args{
features: &InstanceFeatures{},
key: feature.KeyEnableBackChannelLogout,
value: true,
},
expected: &InstanceFeatures{EnableBackChannelLogout: gu.Ptr(true)},
},
{
name: "login v2",
args: args{
features: &InstanceFeatures{},
key: feature.KeyLoginV2,
value: &feature.LoginV2{},
},
expected: &InstanceFeatures{LoginV2: &feature.LoginV2{}},
},
{
name: "permission check v2",
args: args{
features: &InstanceFeatures{},
key: feature.KeyPermissionCheckV2,
value: true,
},
expected: &InstanceFeatures{PermissionCheckV2: gu.Ptr(true)},
},
{
name: "console use v2 user api",
args: args{
features: &InstanceFeatures{},
key: feature.KeyConsoleUseV2UserApi,
value: true,
},
expected: &InstanceFeatures{ConsoleUseV2UserApi: gu.Ptr(true)},
},
{
name: "enable relational tables",
args: args{
features: &InstanceFeatures{},
key: feature.KeyEnableRelationalTables,
value: true,
},
expected: &InstanceFeatures{EnableRelationalTables: gu.Ptr(true)},
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// Test
reduceInstanceFeature(tc.args.features, tc.args.key, tc.args.value)
// Verify
assert.Equal(t, tc.expected, tc.args.features)
})
}
}

View File

@@ -12,12 +12,15 @@ import (
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/feature"
feature_v1 "github.com/zitadel/zitadel/internal/repository/feature"
"github.com/zitadel/zitadel/internal/repository/feature/feature_v2"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestCommands_SetInstanceFeatures(t *testing.T) {
t.Parallel()
ctx := authz.WithInstanceID(context.Background(), "instance1")
aggregate := feature_v2.NewAggregate("instance1", "instance1")
@@ -53,7 +56,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
eventstore: expectEventstore(
expectFilter(),
expectPush(
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceLoginDefaultOrgEventType, true,
),
@@ -70,7 +73,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
name: "set LoginDefaultOrg, update from v1",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(feature_v1.NewSetEvent[feature_v1.Boolean](
eventFromEventPusher(feature_v1.NewSetEvent(
ctx, &eventstore.Aggregate{
ID: "instance1",
ResourceOwner: "instance1",
@@ -82,7 +85,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
)),
),
expectPush(
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceLoginDefaultOrgEventType, true,
),
@@ -100,7 +103,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
eventstore: expectEventstore(
expectFilter(),
expectPush(
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceUserSchemaEventType, true,
),
@@ -118,7 +121,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
eventstore: expectEventstore(
expectFilter(),
expectPushFailed(io.ErrClosedPipe,
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceConsoleUseV2UserApi, true,
),
@@ -134,24 +137,27 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
eventstore: expectEventstore(
expectFilter(),
expectPush(
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceLoginDefaultOrgEventType, true,
),
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceUserSchemaEventType, true,
),
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType, true,
),
feature_v2.NewSetEvent(ctx, aggregate,
feature_v2.InstanceEnableRelationalTables, true),
),
),
args: args{ctx, &InstanceFeatures{
LoginDefaultOrg: gu.Ptr(true),
UserSchema: gu.Ptr(true),
OIDCSingleV1SessionTermination: gu.Ptr(true),
EnableRelationalTables: gu.Ptr(true),
}},
want: &domain.ObjectDetails{
ResourceOwner: "instance1",
@@ -162,7 +168,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
eventstore: expectEventstore(
// throw in some set events, reset and set again.
expectFilter(
eventFromEventPusher(feature_v2.NewSetEvent[bool](
eventFromEventPusher(feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceLoginDefaultOrgEventType, true,
)),
@@ -170,17 +176,17 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
ctx, aggregate,
feature_v2.InstanceResetEventType,
)),
eventFromEventPusher(feature_v2.NewSetEvent[bool](
eventFromEventPusher(feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceLoginDefaultOrgEventType, false,
)),
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType, false,
),
),
expectPush(
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceLoginDefaultOrgEventType, true,
),
@@ -197,6 +203,8 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
c := &Commands{
eventstore: tt.eventstore(t),
}
@@ -227,7 +235,7 @@ func TestCommands_ResetInstanceFeatures(t *testing.T) {
name: "push error",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(feature_v2.NewSetEvent[bool](
eventFromEventPusher(feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceLoginDefaultOrgEventType, true,
)),
@@ -242,7 +250,7 @@ func TestCommands_ResetInstanceFeatures(t *testing.T) {
name: "success",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(feature_v2.NewSetEvent[bool](
eventFromEventPusher(feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceLoginDefaultOrgEventType, true,
)),
@@ -259,7 +267,7 @@ func TestCommands_ResetInstanceFeatures(t *testing.T) {
name: "no change after previous reset",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(feature_v2.NewSetEvent[bool](
eventFromEventPusher(feature_v2.NewSetEvent(
ctx, aggregate,
feature_v2.InstanceLoginDefaultOrgEventType, true,
)),
@@ -294,3 +302,71 @@ func TestCommands_ResetInstanceFeatures(t *testing.T) {
})
}
}
func TestInstanceFeatures_isEmpty(t *testing.T) {
t.Parallel()
tt := []struct {
name string
features *InstanceFeatures
want bool
}{
{
name: "nil features",
features: nil,
want: true,
},
{
name: "empty features",
features: &InstanceFeatures{},
want: true,
},
{
name: "LoginDefaultOrg set",
features: &InstanceFeatures{
LoginDefaultOrg: gu.Ptr(true),
},
want: false,
},
{
name: "UserSchema set",
features: &InstanceFeatures{
UserSchema: gu.Ptr(true),
},
want: false,
},
{
name: "TokenExchange set",
features: &InstanceFeatures{
TokenExchange: gu.Ptr(true),
},
want: false,
},
{
name: "ImprovedPerformance set",
features: &InstanceFeatures{
ImprovedPerformance: []feature.ImprovedPerformanceType{},
},
want: false,
},
{
name: "multiple fields set",
features: &InstanceFeatures{
LoginDefaultOrg: gu.Ptr(true),
UserSchema: gu.Ptr(false),
PermissionCheckV2: gu.Ptr(true),
EnableRelationalTables: gu.Ptr(true),
},
want: false,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := tc.features.isEmpty()
assert.Equal(t, tc.want, got)
})
}
}

View File

@@ -3,12 +3,15 @@ package command
import (
"context"
"encoding/json"
"net/url"
"slices"
"strings"
"testing"
"time"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"golang.org/x/text/language"
@@ -18,6 +21,7 @@ import (
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/feature"
"github.com/zitadel/zitadel/internal/id"
id_mock "github.com/zitadel/zitadel/internal/id/mock"
"github.com/zitadel/zitadel/internal/repository/instance"
@@ -1727,3 +1731,126 @@ func TestCommandSide_RemoveInstance(t *testing.T) {
})
}
}
func TestInstanceSetupFeatures_ToInstanceFeatures(t *testing.T) {
t.Parallel()
type fields struct {
LoginDefaultOrg *bool
UserSchema *bool
TokenExchange *bool
ImprovedPerformance []feature.ImprovedPerformanceType
DebugOIDCParentError *bool
OIDCSingleV1SessionTermination *bool
EnableBackChannelLogout *bool
LoginV2 *InstanceSetupFeatureLoginV2
PermissionCheckV2 *bool
ConsoleUseV2UserApi *bool
EnableRelationalTables *bool
}
correctlyParsedURI, err := url.Parse("https://example.com")
require.NoError(t, err)
tt := []struct {
name string
fields fields
want *InstanceFeatures
wantErr bool
}{
{
name: "nil features returns nil",
fields: fields{},
want: &InstanceFeatures{},
},
{
name: "all fields no login v2",
fields: fields{
LoginDefaultOrg: gu.Ptr(true),
UserSchema: gu.Ptr(false),
TokenExchange: gu.Ptr(true),
ImprovedPerformance: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeOrgDomainVerified},
DebugOIDCParentError: gu.Ptr(true),
OIDCSingleV1SessionTermination: gu.Ptr(false),
EnableBackChannelLogout: gu.Ptr(true),
PermissionCheckV2: gu.Ptr(true),
ConsoleUseV2UserApi: gu.Ptr(false),
EnableRelationalTables: gu.Ptr(true),
},
want: &InstanceFeatures{
LoginDefaultOrg: gu.Ptr(true),
UserSchema: gu.Ptr(false),
TokenExchange: gu.Ptr(true),
ImprovedPerformance: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeOrgDomainVerified},
DebugOIDCParentError: gu.Ptr(true),
OIDCSingleV1SessionTermination: gu.Ptr(false),
EnableBackChannelLogout: gu.Ptr(true),
LoginV2: nil,
PermissionCheckV2: gu.Ptr(true),
ConsoleUseV2UserApi: gu.Ptr(false),
EnableRelationalTables: gu.Ptr(true),
},
},
{
name: "with login v2 no base uri",
fields: fields{
LoginV2: &InstanceSetupFeatureLoginV2{
Required: true,
},
},
want: &InstanceFeatures{
LoginV2: &feature.LoginV2{
Required: true,
},
},
},
{
name: "with login v2 valid base uri",
fields: fields{
LoginV2: &InstanceSetupFeatureLoginV2{
Required: true,
BaseURI: gu.Ptr("https://example.com"),
},
},
want: &InstanceFeatures{
LoginV2: &feature.LoginV2{
Required: true,
BaseURI: correctlyParsedURI,
},
},
},
{
name: "with login v2 invalid base uri",
fields: fields{
LoginV2: &InstanceSetupFeatureLoginV2{
Required: true,
BaseURI: gu.Ptr("://invalid"),
},
},
wantErr: true,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
f := &InstanceSetupFeatures{
LoginDefaultOrg: tc.fields.LoginDefaultOrg,
UserSchema: tc.fields.UserSchema,
TokenExchange: tc.fields.TokenExchange,
ImprovedPerformance: tc.fields.ImprovedPerformance,
DebugOIDCParentError: tc.fields.DebugOIDCParentError,
OIDCSingleV1SessionTermination: tc.fields.OIDCSingleV1SessionTermination,
EnableBackChannelLogout: tc.fields.EnableBackChannelLogout,
LoginV2: tc.fields.LoginV2,
PermissionCheckV2: tc.fields.PermissionCheckV2,
ConsoleUseV2UserApi: tc.fields.ConsoleUseV2UserApi,
EnableRelationalTables: tc.fields.EnableRelationalTables,
}
got, err := f.ToInstanceFeatures()
require.Equal(t, tc.wantErr, err != nil)
assert.Equal(t, tc.want, got)
})
}
}

View File

@@ -18,10 +18,11 @@ type SystemFeatures struct {
EnableBackChannelLogout *bool
LoginV2 *feature.LoginV2
PermissionCheckV2 *bool
EnableRelationalTables *bool
}
func (m *SystemFeatures) isEmpty() bool {
return m.LoginDefaultOrg == nil &&
return m == nil || (m.LoginDefaultOrg == nil &&
m.UserSchema == nil &&
m.TokenExchange == nil &&
// nil check to allow unset improvements
@@ -29,7 +30,8 @@ func (m *SystemFeatures) isEmpty() bool {
m.OIDCSingleV1SessionTermination == nil &&
m.EnableBackChannelLogout == nil &&
m.LoginV2 == nil &&
m.PermissionCheckV2 == nil
m.PermissionCheckV2 == nil &&
m.EnableRelationalTables == nil)
}
func (c *Commands) SetSystemFeatures(ctx context.Context, f *SystemFeatures) (*domain.ObjectDetails, error) {

View File

@@ -67,6 +67,7 @@ func (m *SystemFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder {
feature_v2.SystemEnableBackChannelLogout,
feature_v2.SystemLoginVersion,
feature_v2.SystemPermissionCheckV2,
feature_v2.SystemEnableRelationalTables,
).
Builder().ResourceOwner(m.ResourceOwner)
}
@@ -101,6 +102,9 @@ func reduceSystemFeature(features *SystemFeatures, key feature.Key, value any) {
case feature.KeyPermissionCheckV2:
v := value.(bool)
features.PermissionCheckV2 = &v
case feature.KeyEnableRelationalTables:
v := value.(bool)
features.EnableRelationalTables = &v
}
}
@@ -115,23 +119,24 @@ func (wm *SystemFeaturesWriteModel) setCommands(ctx context.Context, f *SystemFe
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.EnableBackChannelLogout, f.EnableBackChannelLogout, feature_v2.SystemEnableBackChannelLogout)
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.LoginV2, f.LoginV2, feature_v2.SystemLoginVersion)
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.PermissionCheckV2, f.PermissionCheckV2, feature_v2.SystemPermissionCheckV2)
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.EnableRelationalTables, f.EnableRelationalTables, feature_v2.SystemEnableRelationalTables)
return cmds
}
func appendFeatureUpdate[T comparable](ctx context.Context, cmds []eventstore.Command, aggregate *feature_v2.Aggregate, oldValue, newValue *T, eventType eventstore.EventType) []eventstore.Command {
if newValue != nil && (oldValue == nil || *oldValue != *newValue) {
cmds = append(cmds, feature_v2.NewSetEvent[T](ctx, aggregate, eventType, *newValue))
cmds = append(cmds, feature_v2.NewSetEvent(ctx, aggregate, eventType, *newValue))
}
return cmds
}
func appendFeatureSliceUpdate[T comparable](ctx context.Context, cmds []eventstore.Command, aggregate *feature_v2.Aggregate, oldValues, newValues []T, eventType eventstore.EventType) []eventstore.Command {
if len(newValues) != len(oldValues) {
return append(cmds, feature_v2.NewSetEvent[[]T](ctx, aggregate, eventType, newValues))
return append(cmds, feature_v2.NewSetEvent(ctx, aggregate, eventType, newValues))
}
for i, oldValue := range oldValues {
if oldValue != newValues[i] {
return append(cmds, feature_v2.NewSetEvent[[]T](ctx, aggregate, eventType, newValues))
return append(cmds, feature_v2.NewSetEvent(ctx, aggregate, eventType, newValues))
}
}
return cmds

View File

@@ -11,11 +11,14 @@ import (
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/feature"
"github.com/zitadel/zitadel/internal/repository/feature/feature_v2"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestCommands_SetSystemFeatures(t *testing.T) {
t.Parallel()
aggregate := feature_v2.NewAggregate("SYSTEM", "SYSTEM")
type args struct {
@@ -50,7 +53,7 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
eventstore: expectEventstore(
expectFilter(),
expectPush(
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemLoginDefaultOrgEventType, true,
),
@@ -68,7 +71,7 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
eventstore: expectEventstore(
expectFilter(),
expectPush(
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemUserSchemaEventType, true,
),
@@ -86,7 +89,7 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
eventstore: expectEventstore(
expectFilter(),
expectPushFailed(io.ErrClosedPipe,
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemEnableBackChannelLogout, true,
),
@@ -102,24 +105,29 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
eventstore: expectEventstore(
expectFilter(),
expectPush(
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemLoginDefaultOrgEventType, true,
),
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemUserSchemaEventType, true,
),
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemOIDCSingleV1SessionTerminationEventType, true,
),
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemEnableRelationalTables, true,
),
),
),
args: args{context.Background(), &SystemFeatures{
LoginDefaultOrg: gu.Ptr(true),
UserSchema: gu.Ptr(true),
OIDCSingleV1SessionTermination: gu.Ptr(true),
EnableRelationalTables: gu.Ptr(true),
}},
want: &domain.ObjectDetails{
ResourceOwner: "SYSTEM",
@@ -130,7 +138,7 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
eventstore: expectEventstore(
// throw in some set events, reset and set again.
expectFilter(
eventFromEventPusher(feature_v2.NewSetEvent[bool](
eventFromEventPusher(feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemLoginDefaultOrgEventType, true,
)),
@@ -138,21 +146,21 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
context.Background(), aggregate,
feature_v2.SystemResetEventType,
)),
eventFromEventPusher(feature_v2.NewSetEvent[bool](
eventFromEventPusher(feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemLoginDefaultOrgEventType, false,
)),
),
expectPush(
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemLoginDefaultOrgEventType, true,
),
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemUserSchemaEventType, true,
),
feature_v2.NewSetEvent[bool](
feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemOIDCSingleV1SessionTerminationEventType, false,
),
@@ -170,6 +178,8 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
c := &Commands{
eventstore: tt.eventstore(t),
}
@@ -199,7 +209,7 @@ func TestCommands_ResetSystemFeatures(t *testing.T) {
name: "push error",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(feature_v2.NewSetEvent[bool](
eventFromEventPusher(feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemLoginDefaultOrgEventType, true,
)),
@@ -214,7 +224,7 @@ func TestCommands_ResetSystemFeatures(t *testing.T) {
name: "success",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(feature_v2.NewSetEvent[bool](
eventFromEventPusher(feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemLoginDefaultOrgEventType, true,
)),
@@ -231,7 +241,7 @@ func TestCommands_ResetSystemFeatures(t *testing.T) {
name: "no change after previous reset",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(feature_v2.NewSetEvent[bool](
eventFromEventPusher(feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemLoginDefaultOrgEventType, true,
)),
@@ -266,3 +276,32 @@ func TestCommands_ResetSystemFeatures(t *testing.T) {
})
}
}
func TestSystemFeatures_isEmpty(t *testing.T) {
t.Parallel()
tests := []struct {
name string
sysFeatures *SystemFeatures
want bool
}{
{name: "nil features", sysFeatures: nil, want: true},
{name: "empty features", sysFeatures: &SystemFeatures{}, want: true},
{name: "LoginDefaultOrg set", sysFeatures: &SystemFeatures{LoginDefaultOrg: gu.Ptr(true)}, want: false},
{name: "UserSchema set", sysFeatures: &SystemFeatures{UserSchema: gu.Ptr(true)}, want: false},
{name: "TokenExchange set", sysFeatures: &SystemFeatures{TokenExchange: gu.Ptr(true)}, want: false},
{name: "ImprovedPerformance set", sysFeatures: &SystemFeatures{ImprovedPerformance: []feature.ImprovedPerformanceType{}}, want: false},
{name: "OIDCSingleV1SessionTermination set", sysFeatures: &SystemFeatures{OIDCSingleV1SessionTermination: gu.Ptr(true)}, want: false},
{name: "EnableBackChannelLogout set", sysFeatures: &SystemFeatures{EnableBackChannelLogout: gu.Ptr(true)}, want: false},
{name: "LoginV2 set", sysFeatures: &SystemFeatures{LoginV2: &feature.LoginV2{}}, want: false},
{name: "PermissionCheckV2 set", sysFeatures: &SystemFeatures{PermissionCheckV2: gu.Ptr(true)}, want: false},
{name: "EnableRelationalTables set", sysFeatures: &SystemFeatures{EnableRelationalTables: gu.Ptr(true)}, want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := tt.sysFeatures.isEmpty()
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -22,6 +22,7 @@ const (
KeyLoginV2 Key = 13
KeyPermissionCheckV2 Key = 14
KeyConsoleUseV2UserApi Key = 15
KeyEnableRelationalTables Key = 16
)
//go:generate enumer -type Level -transform snake -trimprefix Level
@@ -49,6 +50,7 @@ type Features struct {
LoginV2 LoginV2 `json:"login_v2,omitempty"`
PermissionCheckV2 bool `json:"permission_check_v2,omitempty"`
ConsoleUseV2UserApi bool `json:"console_use_v2_user_api,omitempty"`
EnableRelationalTables bool `json:"enable_relational_tables,omitempty"`
}
/* Note: do not generate the stringer or enumer for this type, is it breaks existing events */

View File

@@ -16,8 +16,8 @@ const (
_KeyLowerName_2 = "improved_performance"
_KeyName_3 = "debug_oidc_parent_erroroidc_single_v1_session_termination"
_KeyLowerName_3 = "debug_oidc_parent_erroroidc_single_v1_session_termination"
_KeyName_4 = "enable_back_channel_logoutlogin_v2permission_check_v2console_use_v2_user_api"
_KeyLowerName_4 = "enable_back_channel_logoutlogin_v2permission_check_v2console_use_v2_user_api"
_KeyName_4 = "enable_back_channel_logoutlogin_v2permission_check_v2console_use_v2_user_apienable_relational_tables"
_KeyLowerName_4 = "enable_back_channel_logoutlogin_v2permission_check_v2console_use_v2_user_apienable_relational_tables"
)
var (
@@ -25,7 +25,7 @@ var (
_KeyIndex_1 = [...]uint8{0, 11, 25}
_KeyIndex_2 = [...]uint8{0, 20}
_KeyIndex_3 = [...]uint8{0, 23, 57}
_KeyIndex_4 = [...]uint8{0, 26, 34, 53, 76}
_KeyIndex_4 = [...]uint8{0, 26, 34, 53, 76, 100}
)
func (i Key) String() string {
@@ -40,7 +40,7 @@ func (i Key) String() string {
case 9 <= i && i <= 10:
i -= 9
return _KeyName_3[_KeyIndex_3[i]:_KeyIndex_3[i+1]]
case 12 <= i && i <= 15:
case 12 <= i && i <= 16:
i -= 12
return _KeyName_4[_KeyIndex_4[i]:_KeyIndex_4[i+1]]
default:
@@ -63,33 +63,36 @@ func _KeyNoOp() {
_ = x[KeyLoginV2-(13)]
_ = x[KeyPermissionCheckV2-(14)]
_ = x[KeyConsoleUseV2UserApi-(15)]
_ = x[KeyEnableRelationalTables-(16)]
}
var _KeyValues = []Key{KeyUnspecified, KeyLoginDefaultOrg, KeyUserSchema, KeyTokenExchange, KeyImprovedPerformance, KeyDebugOIDCParentError, KeyOIDCSingleV1SessionTermination, KeyEnableBackChannelLogout, KeyLoginV2, KeyPermissionCheckV2, KeyConsoleUseV2UserApi}
var _KeyValues = []Key{KeyUnspecified, KeyLoginDefaultOrg, KeyUserSchema, KeyTokenExchange, KeyImprovedPerformance, KeyDebugOIDCParentError, KeyOIDCSingleV1SessionTermination, KeyEnableBackChannelLogout, KeyLoginV2, KeyPermissionCheckV2, KeyConsoleUseV2UserApi, KeyEnableRelationalTables}
var _KeyNameToValueMap = map[string]Key{
_KeyName_0[0:11]: KeyUnspecified,
_KeyLowerName_0[0:11]: KeyUnspecified,
_KeyName_0[11:28]: KeyLoginDefaultOrg,
_KeyLowerName_0[11:28]: KeyLoginDefaultOrg,
_KeyName_1[0:11]: KeyUserSchema,
_KeyLowerName_1[0:11]: KeyUserSchema,
_KeyName_1[11:25]: KeyTokenExchange,
_KeyLowerName_1[11:25]: KeyTokenExchange,
_KeyName_2[0:20]: KeyImprovedPerformance,
_KeyLowerName_2[0:20]: KeyImprovedPerformance,
_KeyName_3[0:23]: KeyDebugOIDCParentError,
_KeyLowerName_3[0:23]: KeyDebugOIDCParentError,
_KeyName_3[23:57]: KeyOIDCSingleV1SessionTermination,
_KeyLowerName_3[23:57]: KeyOIDCSingleV1SessionTermination,
_KeyName_4[0:26]: KeyEnableBackChannelLogout,
_KeyLowerName_4[0:26]: KeyEnableBackChannelLogout,
_KeyName_4[26:34]: KeyLoginV2,
_KeyLowerName_4[26:34]: KeyLoginV2,
_KeyName_4[34:53]: KeyPermissionCheckV2,
_KeyLowerName_4[34:53]: KeyPermissionCheckV2,
_KeyName_4[53:76]: KeyConsoleUseV2UserApi,
_KeyLowerName_4[53:76]: KeyConsoleUseV2UserApi,
_KeyName_0[0:11]: KeyUnspecified,
_KeyLowerName_0[0:11]: KeyUnspecified,
_KeyName_0[11:28]: KeyLoginDefaultOrg,
_KeyLowerName_0[11:28]: KeyLoginDefaultOrg,
_KeyName_1[0:11]: KeyUserSchema,
_KeyLowerName_1[0:11]: KeyUserSchema,
_KeyName_1[11:25]: KeyTokenExchange,
_KeyLowerName_1[11:25]: KeyTokenExchange,
_KeyName_2[0:20]: KeyImprovedPerformance,
_KeyLowerName_2[0:20]: KeyImprovedPerformance,
_KeyName_3[0:23]: KeyDebugOIDCParentError,
_KeyLowerName_3[0:23]: KeyDebugOIDCParentError,
_KeyName_3[23:57]: KeyOIDCSingleV1SessionTermination,
_KeyLowerName_3[23:57]: KeyOIDCSingleV1SessionTermination,
_KeyName_4[0:26]: KeyEnableBackChannelLogout,
_KeyLowerName_4[0:26]: KeyEnableBackChannelLogout,
_KeyName_4[26:34]: KeyLoginV2,
_KeyLowerName_4[26:34]: KeyLoginV2,
_KeyName_4[34:53]: KeyPermissionCheckV2,
_KeyLowerName_4[34:53]: KeyPermissionCheckV2,
_KeyName_4[53:76]: KeyConsoleUseV2UserApi,
_KeyLowerName_4[53:76]: KeyConsoleUseV2UserApi,
_KeyName_4[76:100]: KeyEnableRelationalTables,
_KeyLowerName_4[76:100]: KeyEnableRelationalTables,
}
var _KeyNames = []string{
@@ -104,6 +107,7 @@ var _KeyNames = []string{
_KeyName_4[26:34],
_KeyName_4[34:53],
_KeyName_4[53:76],
_KeyName_4[76:100],
}
// KeyString retrieves an enum value from the enum constants string name.

View File

@@ -19,6 +19,7 @@ type InstanceFeatures struct {
LoginV2 FeatureSource[*feature.LoginV2]
PermissionCheckV2 FeatureSource[bool]
ConsoleUseV2UserApi FeatureSource[bool]
EnableRelationalTables FeatureSource[bool]
}
func (q *Queries) GetInstanceFeatures(ctx context.Context, cascade bool) (_ *InstanceFeatures, err error) {

View File

@@ -72,11 +72,13 @@ func (m *InstanceFeaturesReadModel) Query() *eventstore.SearchQueryBuilder {
feature_v2.InstanceLoginVersion,
feature_v2.InstancePermissionCheckV2,
feature_v2.InstanceConsoleUseV2UserApi,
feature_v2.InstanceEnableRelationalTables,
).
Builder().ResourceOwner(m.ResourceOwner)
}
func (m *InstanceFeaturesReadModel) reduceReset() {
m.instance.EnableRelationalTables = FeatureSource[bool]{}
if m.populateFromSystem() {
return
}
@@ -126,6 +128,8 @@ func reduceInstanceFeatureSet[T any](features *InstanceFeatures, event *feature_
features.PermissionCheckV2.set(level, event.Value)
case feature.KeyConsoleUseV2UserApi:
features.ConsoleUseV2UserApi.set(level, event.Value)
case feature.KeyEnableRelationalTables:
features.EnableRelationalTables.set(level, event.Value)
}
return nil
}

View File

@@ -104,6 +104,10 @@ func (*instanceFeatureProjection) Reducers() []handler.AggregateReducer {
Event: instance.InstanceRemovedEventType,
Reduce: reduceInstanceRemovedHelper(InstanceDomainInstanceIDCol),
},
{
Event: feature_v2.InstanceEnableRelationalTables,
Reduce: reduceInstanceSetFeature[bool],
},
},
}}
}

View File

@@ -28,6 +28,7 @@ type SystemFeatures struct {
EnableBackChannelLogout FeatureSource[bool]
LoginV2 FeatureSource[*feature.LoginV2]
PermissionCheckV2 FeatureSource[bool]
EnableRelationalTables FeatureSource[bool]
}
func (q *Queries) GetSystemFeatures(ctx context.Context) (_ *SystemFeatures, err error) {

View File

@@ -63,6 +63,7 @@ func (m *SystemFeaturesReadModel) Query() *eventstore.SearchQueryBuilder {
feature_v2.SystemEnableBackChannelLogout,
feature_v2.SystemLoginVersion,
feature_v2.SystemPermissionCheckV2,
feature_v2.SystemEnableRelationalTables,
).
Builder().ResourceOwner(m.ResourceOwner)
}
@@ -96,6 +97,8 @@ func reduceSystemFeatureSet[T any](features *SystemFeatures, event *feature_v2.S
features.LoginV2.set(level, event.Value)
case feature.KeyPermissionCheckV2:
features.PermissionCheckV2.set(level, event.Value)
case feature.KeyEnableRelationalTables:
features.EnableRelationalTables.set(level, event.Value)
}
return nil
}

View File

@@ -53,6 +53,10 @@ func TestQueries_GetSystemFeatures(t *testing.T) {
context.Background(), aggregate,
feature_v2.SystemUserSchemaEventType, false,
)),
eventFromEventPusher(feature_v2.NewSetEvent(
context.Background(), aggregate,
feature_v2.SystemEnableRelationalTables, false,
)),
),
),
want: &SystemFeatures{
@@ -67,6 +71,10 @@ func TestQueries_GetSystemFeatures(t *testing.T) {
Level: feature.LevelSystem,
Value: false,
},
EnableRelationalTables: FeatureSource[bool]{
Level: feature.LevelSystem,
Value: false,
},
},
},
{

View File

@@ -15,6 +15,7 @@ func init() {
eventstore.RegisterFilterEventMapper(AggregateType, SystemEnableBackChannelLogout, eventstore.GenericEventMapper[SetEvent[bool]])
eventstore.RegisterFilterEventMapper(AggregateType, SystemLoginVersion, eventstore.GenericEventMapper[SetEvent[*feature.LoginV2]])
eventstore.RegisterFilterEventMapper(AggregateType, SystemPermissionCheckV2, eventstore.GenericEventMapper[SetEvent[bool]])
eventstore.RegisterFilterEventMapper(AggregateType, SystemEnableRelationalTables, eventstore.GenericEventMapper[SetEvent[bool]])
eventstore.RegisterFilterEventMapper(AggregateType, InstanceResetEventType, eventstore.GenericEventMapper[ResetEvent])
eventstore.RegisterFilterEventMapper(AggregateType, InstanceLoginDefaultOrgEventType, eventstore.GenericEventMapper[SetEvent[bool]])
@@ -27,4 +28,5 @@ func init() {
eventstore.RegisterFilterEventMapper(AggregateType, InstanceLoginVersion, eventstore.GenericEventMapper[SetEvent[*feature.LoginV2]])
eventstore.RegisterFilterEventMapper(AggregateType, InstancePermissionCheckV2, eventstore.GenericEventMapper[SetEvent[bool]])
eventstore.RegisterFilterEventMapper(AggregateType, InstanceConsoleUseV2UserApi, eventstore.GenericEventMapper[SetEvent[bool]])
eventstore.RegisterFilterEventMapper(AggregateType, InstanceEnableRelationalTables, eventstore.GenericEventMapper[SetEvent[bool]])
}

View File

@@ -20,6 +20,7 @@ var (
SystemEnableBackChannelLogout = setEventTypeFromFeature(feature.LevelSystem, feature.KeyEnableBackChannelLogout)
SystemLoginVersion = setEventTypeFromFeature(feature.LevelSystem, feature.KeyLoginV2)
SystemPermissionCheckV2 = setEventTypeFromFeature(feature.LevelSystem, feature.KeyPermissionCheckV2)
SystemEnableRelationalTables = setEventTypeFromFeature(feature.LevelSystem, feature.KeyEnableRelationalTables)
InstanceResetEventType = resetEventTypeFromFeature(feature.LevelInstance)
InstanceLoginDefaultOrgEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyLoginDefaultOrg)
@@ -32,6 +33,7 @@ var (
InstanceLoginVersion = setEventTypeFromFeature(feature.LevelInstance, feature.KeyLoginV2)
InstancePermissionCheckV2 = setEventTypeFromFeature(feature.LevelInstance, feature.KeyPermissionCheckV2)
InstanceConsoleUseV2UserApi = setEventTypeFromFeature(feature.LevelInstance, feature.KeyConsoleUseV2UserApi)
InstanceEnableRelationalTables = setEventTypeFromFeature(feature.LevelInstance, feature.KeyEnableRelationalTables)
)
const (

View File

@@ -84,6 +84,13 @@ message SetInstanceFeaturesRequest{
description: "If this is enabled the console web client will use the new User v2 API for certain calls";
}
];
optional bool enable_relational_tables = 16 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "true";
description: "If this is enabled Zitadel will use the relational tables instead of the projections as the main source to store the data. Regardless of the flag state, both the relational table and the projections are kept up to date. Currently the flag has no effect.";
}
];
}
message SetInstanceFeaturesResponse {
@@ -178,4 +185,11 @@ message GetInstanceFeaturesResponse {
description: "If this is enabled the console web client will use the new User v2 API for certain calls";
}
];
FeatureFlag enable_relational_tables = 17 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "true";
description: "If this is enabled Zitadel will use the relational tables instead of the projections as the main source to store the data. Regardless of the flag state, both the relational table and the projections are kept up to date. Currently the flag has no effect.";
}
];
}

View File

@@ -70,6 +70,14 @@ message SetSystemFeaturesRequest{
description: "Enable a newer, more performant, permission check used for v2 and v3 resource based APIs.";
}
];
optional bool enable_relational_tables = 13 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "true";
description: "If this is enabled Zitadel will use the relational tables instead of the projections as the main source to store the data. Regardless of the flag state, both the relational table and the projections are kept up to date. Currently the flag has no effect.";
}
];
}
message SetSystemFeaturesResponse {
@@ -143,4 +151,11 @@ message GetSystemFeaturesResponse {
description: "Enable a newer, more performant, permission check used for v2 and v3 resource based APIs.";
}
];
FeatureFlag enable_relational_tables = 14 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "true";
description: "If this is enabled Zitadel will use the relational tables instead of the projections as the main source to store the data. Regardless of the flag state, both the relational table and the projections are kept up to date. Currently the flag has no effect.";
}
];
}