From 3bb11e068ceb5ab0b2239e124a1863f2686b288c Mon Sep 17 00:00:00 2001 From: Joe de Ronde Date: Tue, 12 May 2026 10:46:49 +0100 Subject: [PATCH 1/6] fix: use CREATE OR ALTER/REPLACE FUNCTION to prevent startup failure on restart On application restart the hosted service fails with: SqlException: There is already an object named 'json_exists' in the database. The #if RELEASE guard in JsonFunction.cs skips DROP FUNCTION IF EXISTS statements in release builds, so CREATE FUNCTION fails if the functions already exist from a previous run. Replace the DROP + CREATE pattern with idempotent alternatives: - SQL Server: CREATE OR ALTER FUNCTION (supported since SQL Server 2016 SP1) - MySQL: CREATE OR REPLACE FUNCTION PostgreSQL was already using CREATE OR REPLACE FUNCTION correctly. DROP FUNCTION IF EXISTS statements are removed from both SQL files. --- .../Providers/MySql/json_function.sql | 75 +++++++------------ .../Providers/SqlServer/json_function.sql | 66 ++++++---------- 2 files changed, 47 insertions(+), 94 deletions(-) diff --git a/backend/src/Squidex.Data.EntityFramework/Providers/MySql/json_function.sql b/backend/src/Squidex.Data.EntityFramework/Providers/MySql/json_function.sql index 39e89b7d7f..3932573aec 100644 --- a/backend/src/Squidex.Data.EntityFramework/Providers/MySql/json_function.sql +++ b/backend/src/Squidex.Data.EntityFramework/Providers/MySql/json_function.sql @@ -1,8 +1,7 @@ -- ============================================================================= -- TYPE-AGNOSTIC -- ============================================================================= -DROP FUNCTION IF EXISTS json_empty;; -CREATE FUNCTION json_empty(col JSON, path VARCHAR(500)) +CREATE OR REPLACE FUNCTION json_empty(col JSON, path VARCHAR(500)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -14,8 +13,7 @@ BEGIN RETURN 0; END;; -DROP FUNCTION IF EXISTS json_exists;; -CREATE FUNCTION json_exists(col JSON, path VARCHAR(500)) +CREATE OR REPLACE FUNCTION json_exists(col JSON, path VARCHAR(500)) RETURNS TINYINT(1) DETERMINISTIC BEGIN RETURN NOT json_empty(col, path); @@ -25,8 +23,7 @@ END;; -- ============================================================================= -- NULL -- ============================================================================= -DROP FUNCTION IF EXISTS json_null_equals;; -CREATE FUNCTION json_null_equals(col JSON, path VARCHAR(500)) +CREATE OR REPLACE FUNCTION json_null_equals(col JSON, path VARCHAR(500)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -40,8 +37,7 @@ BEGIN RETURN COALESCE(JSON_TYPE(val), 'NULL') = 'NULL'; END;; -DROP FUNCTION IF EXISTS json_null_notequals;; -CREATE FUNCTION json_null_notequals(col JSON, path VARCHAR(500)) +CREATE OR REPLACE FUNCTION json_null_notequals(col JSON, path VARCHAR(500)) RETURNS TINYINT(1) DETERMINISTIC BEGIN RETURN NOT json_null_equals(col, path); @@ -51,8 +47,7 @@ END;; -- ============================================================================= -- TEXT -- ============================================================================= -DROP FUNCTION IF EXISTS json_text_equals;; -CREATE FUNCTION json_text_equals(col JSON, path VARCHAR(500), target VARCHAR(2000)) +CREATE OR REPLACE FUNCTION json_text_equals(col JSON, path VARCHAR(500), target VARCHAR(2000)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -66,8 +61,7 @@ BEGIN RETURN JSON_UNQUOTE(val) = target; END;; -DROP FUNCTION IF EXISTS json_text_notequals;; -CREATE FUNCTION json_text_notequals(col JSON, path VARCHAR(500), target VARCHAR(2000)) +CREATE OR REPLACE FUNCTION json_text_notequals(col JSON, path VARCHAR(500), target VARCHAR(2000)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -81,8 +75,7 @@ BEGIN RETURN JSON_UNQUOTE(val) != target; END;; -DROP FUNCTION IF EXISTS json_text_lessthan;; -CREATE FUNCTION json_text_lessthan(col JSON, path VARCHAR(500), target VARCHAR(2000)) +CREATE OR REPLACE FUNCTION json_text_lessthan(col JSON, path VARCHAR(500), target VARCHAR(2000)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -96,8 +89,7 @@ BEGIN RETURN JSON_UNQUOTE(val) < target; END;; -DROP FUNCTION IF EXISTS json_text_lessthanorequal;; -CREATE FUNCTION json_text_lessthanorequal(col JSON, path VARCHAR(500), target VARCHAR(2000)) +CREATE OR REPLACE FUNCTION json_text_lessthanorequal(col JSON, path VARCHAR(500), target VARCHAR(2000)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -111,8 +103,7 @@ BEGIN RETURN JSON_UNQUOTE(val) <= target; END;; -DROP FUNCTION IF EXISTS json_text_greaterthan;; -CREATE FUNCTION json_text_greaterthan(col JSON, path VARCHAR(500), target VARCHAR(2000)) +CREATE OR REPLACE FUNCTION json_text_greaterthan(col JSON, path VARCHAR(500), target VARCHAR(2000)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -126,8 +117,7 @@ BEGIN RETURN JSON_UNQUOTE(val) > target; END;; -DROP FUNCTION IF EXISTS json_text_greaterthanorequal;; -CREATE FUNCTION json_text_greaterthanorequal(col JSON, path VARCHAR(500), target VARCHAR(2000)) +CREATE OR REPLACE FUNCTION json_text_greaterthanorequal(col JSON, path VARCHAR(500), target VARCHAR(2000)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -141,8 +131,7 @@ BEGIN RETURN JSON_UNQUOTE(val) >= target; END;; -DROP FUNCTION IF EXISTS json_text_contains;; -CREATE FUNCTION json_text_contains(col JSON, path VARCHAR(500), target VARCHAR(2000)) +CREATE OR REPLACE FUNCTION json_text_contains(col JSON, path VARCHAR(500), target VARCHAR(2000)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -156,8 +145,7 @@ BEGIN RETURN JSON_UNQUOTE(val) LIKE CONCAT('%', target, '%'); END;; -DROP FUNCTION IF EXISTS json_text_startswith;; -CREATE FUNCTION json_text_startswith(col JSON, path VARCHAR(500), target VARCHAR(2000)) +CREATE OR REPLACE FUNCTION json_text_startswith(col JSON, path VARCHAR(500), target VARCHAR(2000)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -171,8 +159,7 @@ BEGIN RETURN JSON_UNQUOTE(val) LIKE CONCAT(target, '%'); END;; -DROP FUNCTION IF EXISTS json_text_endswith;; -CREATE FUNCTION json_text_endswith(col JSON, path VARCHAR(500), target VARCHAR(2000)) +CREATE OR REPLACE FUNCTION json_text_endswith(col JSON, path VARCHAR(500), target VARCHAR(2000)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -186,8 +173,7 @@ BEGIN RETURN JSON_UNQUOTE(val) LIKE CONCAT('%', target); END;; -DROP FUNCTION IF EXISTS json_text_matchs;; -CREATE FUNCTION json_text_matchs(col JSON, path VARCHAR(500), target VARCHAR(2000)) +CREATE OR REPLACE FUNCTION json_text_matchs(col JSON, path VARCHAR(500), target VARCHAR(2000)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -201,8 +187,7 @@ BEGIN RETURN JSON_UNQUOTE(val) REGEXP target; END;; -DROP FUNCTION IF EXISTS json_text_in;; -CREATE FUNCTION json_text_in(col JSON, path VARCHAR(500), targets JSON) +CREATE OR REPLACE FUNCTION json_text_in(col JSON, path VARCHAR(500), targets JSON) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -220,8 +205,7 @@ END;; -- ============================================================================= -- NUMBER -- ============================================================================= -DROP FUNCTION IF EXISTS json_number_equals;; -CREATE FUNCTION json_number_equals(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) +CREATE OR REPLACE FUNCTION json_number_equals(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -238,8 +222,7 @@ BEGIN RETURN CAST(JSON_UNQUOTE(val) AS DECIMAL(65, 10)) = target; END;; -DROP FUNCTION IF EXISTS json_number_notequals;; -CREATE FUNCTION json_number_notequals(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) +CREATE OR REPLACE FUNCTION json_number_notequals(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -256,8 +239,7 @@ BEGIN RETURN CAST(JSON_UNQUOTE(val) AS DECIMAL(65, 10)) != target; END;; -DROP FUNCTION IF EXISTS json_number_lessthan;; -CREATE FUNCTION json_number_lessthan(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) +CREATE OR REPLACE FUNCTION json_number_lessthan(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -274,8 +256,7 @@ BEGIN RETURN CAST(JSON_UNQUOTE(val) AS DECIMAL(65, 10)) < target; END;; -DROP FUNCTION IF EXISTS json_number_lessthanorequal;; -CREATE FUNCTION json_number_lessthanorequal(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) +CREATE OR REPLACE FUNCTION json_number_lessthanorequal(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -292,8 +273,7 @@ BEGIN RETURN CAST(JSON_UNQUOTE(val) AS DECIMAL(65, 10)) <= target; END;; -DROP FUNCTION IF EXISTS json_number_greaterthan;; -CREATE FUNCTION json_number_greaterthan(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) +CREATE OR REPLACE FUNCTION json_number_greaterthan(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -310,8 +290,7 @@ BEGIN RETURN CAST(JSON_UNQUOTE(val) AS DECIMAL(65, 10)) > target; END;; -DROP FUNCTION IF EXISTS json_number_greaterthanorequal;; -CREATE FUNCTION json_number_greaterthanorequal(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) +CREATE OR REPLACE FUNCTION json_number_greaterthanorequal(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -328,8 +307,7 @@ BEGIN RETURN CAST(JSON_UNQUOTE(val) AS DECIMAL(65, 10)) >= target; END;; -DROP FUNCTION IF EXISTS json_number_in;; -CREATE FUNCTION json_number_in(col JSON, path VARCHAR(500), targets JSON) +CREATE OR REPLACE FUNCTION json_number_in(col JSON, path VARCHAR(500), targets JSON) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -349,8 +327,7 @@ END;; -- ============================================================================= -- BOOLEAN -- ============================================================================= -DROP FUNCTION IF EXISTS json_boolean_equals;; -CREATE FUNCTION json_boolean_equals(col JSON, path VARCHAR(500), target TINYINT(1)) +CREATE OR REPLACE FUNCTION json_boolean_equals(col JSON, path VARCHAR(500), target TINYINT(1)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -367,8 +344,7 @@ BEGIN RETURN val = target_json; END;; -DROP FUNCTION IF EXISTS json_boolean_notequals;; -CREATE FUNCTION json_boolean_notequals(col JSON, path VARCHAR(500), target TINYINT(1)) +CREATE OR REPLACE FUNCTION json_boolean_notequals(col JSON, path VARCHAR(500), target TINYINT(1)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -385,8 +361,7 @@ BEGIN RETURN val != target_json; END;; -DROP FUNCTION IF EXISTS json_boolean_in;; -CREATE FUNCTION json_boolean_in(col JSON, path VARCHAR(500), targets JSON) +CREATE OR REPLACE FUNCTION json_boolean_in(col JSON, path VARCHAR(500), targets JSON) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; diff --git a/backend/src/Squidex.Data.EntityFramework/Providers/SqlServer/json_function.sql b/backend/src/Squidex.Data.EntityFramework/Providers/SqlServer/json_function.sql index 4f198081c2..5d06efc94f 100644 --- a/backend/src/Squidex.Data.EntityFramework/Providers/SqlServer/json_function.sql +++ b/backend/src/Squidex.Data.EntityFramework/Providers/SqlServer/json_function.sql @@ -1,8 +1,7 @@ -- ============================================================================= -- TYPE-AGNOSTIC -- ============================================================================= -DROP FUNCTION IF EXISTS dbo.json_empty;; -CREATE FUNCTION dbo.json_empty(@col NVARCHAR(MAX), @path NVARCHAR(500)) +CREATE OR ALTER FUNCTION dbo.json_empty(@col NVARCHAR(MAX), @path NVARCHAR(500)) RETURNS BIT AS BEGIN @@ -17,8 +16,7 @@ BEGIN RETURN 0; END;; -DROP FUNCTION IF EXISTS dbo.json_exists;; -CREATE FUNCTION dbo.json_exists(@col NVARCHAR(MAX), @path NVARCHAR(500)) +CREATE OR ALTER FUNCTION dbo.json_exists(@col NVARCHAR(MAX), @path NVARCHAR(500)) RETURNS BIT AS BEGIN @@ -29,8 +27,7 @@ END;; -- ============================================================================= -- NULL -- ============================================================================= -DROP FUNCTION IF EXISTS dbo.json_null_equals;; -CREATE FUNCTION dbo.json_null_equals(@col NVARCHAR(MAX), @path NVARCHAR(500)) +CREATE OR ALTER FUNCTION dbo.json_null_equals(@col NVARCHAR(MAX), @path NVARCHAR(500)) RETURNS BIT AS BEGIN @@ -42,8 +39,7 @@ BEGIN RETURN CASE WHEN JSON_VALUE(@col, @path) IS NULL THEN 1 ELSE 0 END; END;; -DROP FUNCTION IF EXISTS dbo.json_null_notequals;; -CREATE FUNCTION dbo.json_null_notequals(@col NVARCHAR(MAX), @path NVARCHAR(500)) +CREATE OR ALTER FUNCTION dbo.json_null_notequals(@col NVARCHAR(MAX), @path NVARCHAR(500)) RETURNS BIT AS BEGIN @@ -54,8 +50,7 @@ END;; -- ============================================================================= -- TEXT -- ============================================================================= -DROP FUNCTION IF EXISTS dbo.json_text_equals;; -CREATE FUNCTION dbo.json_text_equals(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) +CREATE OR ALTER FUNCTION dbo.json_text_equals(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) RETURNS BIT AS BEGIN @@ -67,8 +62,7 @@ BEGIN RETURN CASE WHEN JSON_VALUE(@col, @path) = @target THEN 1 ELSE 0 END; END;; -DROP FUNCTION IF EXISTS dbo.json_text_notequals;; -CREATE FUNCTION dbo.json_text_notequals(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) +CREATE OR ALTER FUNCTION dbo.json_text_notequals(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) RETURNS BIT AS BEGIN @@ -80,8 +74,7 @@ BEGIN RETURN CASE WHEN JSON_VALUE(@col, @path) != @target THEN 1 ELSE 0 END; END;; -DROP FUNCTION IF EXISTS dbo.json_text_lessthan;; -CREATE FUNCTION dbo.json_text_lessthan(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) +CREATE OR ALTER FUNCTION dbo.json_text_lessthan(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) RETURNS BIT AS BEGIN @@ -93,8 +86,7 @@ BEGIN RETURN CASE WHEN JSON_VALUE(@col, @path) < @target THEN 1 ELSE 0 END; END;; -DROP FUNCTION IF EXISTS dbo.json_text_lessthanorequal;; -CREATE FUNCTION dbo.json_text_lessthanorequal(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) +CREATE OR ALTER FUNCTION dbo.json_text_lessthanorequal(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) RETURNS BIT AS BEGIN @@ -106,8 +98,7 @@ BEGIN RETURN CASE WHEN JSON_VALUE(@col, @path) <= @target THEN 1 ELSE 0 END; END;; -DROP FUNCTION IF EXISTS dbo.json_text_greaterthan;; -CREATE FUNCTION dbo.json_text_greaterthan(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) +CREATE OR ALTER FUNCTION dbo.json_text_greaterthan(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) RETURNS BIT AS BEGIN @@ -119,8 +110,7 @@ BEGIN RETURN CASE WHEN JSON_VALUE(@col, @path) > @target THEN 1 ELSE 0 END; END;; -DROP FUNCTION IF EXISTS dbo.json_text_greaterthanorequal;; -CREATE FUNCTION dbo.json_text_greaterthanorequal(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) +CREATE OR ALTER FUNCTION dbo.json_text_greaterthanorequal(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) RETURNS BIT AS BEGIN @@ -132,8 +122,7 @@ BEGIN RETURN CASE WHEN JSON_VALUE(@col, @path) >= @target THEN 1 ELSE 0 END; END;; -DROP FUNCTION IF EXISTS dbo.json_text_contains;; -CREATE FUNCTION dbo.json_text_contains(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) +CREATE OR ALTER FUNCTION dbo.json_text_contains(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) RETURNS BIT AS BEGIN @@ -145,8 +134,7 @@ BEGIN RETURN CASE WHEN JSON_VALUE(@col, @path) LIKE '%' + @target + '%' THEN 1 ELSE 0 END; END;; -DROP FUNCTION IF EXISTS dbo.json_text_startswith;; -CREATE FUNCTION dbo.json_text_startswith(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) +CREATE OR ALTER FUNCTION dbo.json_text_startswith(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) RETURNS BIT AS BEGIN @@ -158,8 +146,7 @@ BEGIN RETURN CASE WHEN JSON_VALUE(@col, @path) LIKE @target + '%' THEN 1 ELSE 0 END; END;; -DROP FUNCTION IF EXISTS dbo.json_text_endswith;; -CREATE FUNCTION dbo.json_text_endswith(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) +CREATE OR ALTER FUNCTION dbo.json_text_endswith(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) RETURNS BIT AS BEGIN @@ -171,8 +158,7 @@ BEGIN RETURN CASE WHEN JSON_VALUE(@col, @path) LIKE '%' + @target THEN 1 ELSE 0 END; END;; -DROP FUNCTION IF EXISTS dbo.json_text_matchs;; -CREATE FUNCTION dbo.json_text_matchs(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) +CREATE OR ALTER FUNCTION dbo.json_text_matchs(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) RETURNS BIT AS BEGIN @@ -188,8 +174,7 @@ END;; -- ============================================================================= -- NUMBER -- ============================================================================= -DROP FUNCTION IF EXISTS dbo.json_number_equals;; -CREATE FUNCTION dbo.json_number_equals(@col NVARCHAR(MAX), @path NVARCHAR(500), @target DECIMAL(38, 10)) +CREATE OR ALTER FUNCTION dbo.json_number_equals(@col NVARCHAR(MAX), @path NVARCHAR(500), @target DECIMAL(38, 10)) RETURNS BIT AS BEGIN @@ -203,8 +188,7 @@ BEGIN RETURN CASE WHEN TRY_CAST(JSON_VALUE(@col, @path) AS DECIMAL(38, 10)) = @target THEN 1 ELSE 0 END; END;; -DROP FUNCTION IF EXISTS dbo.json_number_notequals;; -CREATE FUNCTION dbo.json_number_notequals(@col NVARCHAR(MAX), @path NVARCHAR(500), @target DECIMAL(38, 10)) +CREATE OR ALTER FUNCTION dbo.json_number_notequals(@col NVARCHAR(MAX), @path NVARCHAR(500), @target DECIMAL(38, 10)) RETURNS BIT AS BEGIN @@ -218,8 +202,7 @@ BEGIN RETURN CASE WHEN TRY_CAST(JSON_VALUE(@col, @path) AS DECIMAL(38, 10)) != @target THEN 1 ELSE 0 END; END;; -DROP FUNCTION IF EXISTS dbo.json_number_lessthan;; -CREATE FUNCTION dbo.json_number_lessthan(@col NVARCHAR(MAX), @path NVARCHAR(500), @target DECIMAL(38, 10)) +CREATE OR ALTER FUNCTION dbo.json_number_lessthan(@col NVARCHAR(MAX), @path NVARCHAR(500), @target DECIMAL(38, 10)) RETURNS BIT AS BEGIN @@ -233,8 +216,7 @@ BEGIN RETURN CASE WHEN TRY_CAST(JSON_VALUE(@col, @path) AS DECIMAL(38, 10)) < @target THEN 1 ELSE 0 END; END;; -DROP FUNCTION IF EXISTS dbo.json_number_lessthanorequal;; -CREATE FUNCTION dbo.json_number_lessthanorequal(@col NVARCHAR(MAX), @path NVARCHAR(500), @target DECIMAL(38, 10)) +CREATE OR ALTER FUNCTION dbo.json_number_lessthanorequal(@col NVARCHAR(MAX), @path NVARCHAR(500), @target DECIMAL(38, 10)) RETURNS BIT AS BEGIN @@ -248,8 +230,7 @@ BEGIN RETURN CASE WHEN TRY_CAST(JSON_VALUE(@col, @path) AS DECIMAL(38, 10)) <= @target THEN 1 ELSE 0 END; END;; -DROP FUNCTION IF EXISTS dbo.json_number_greaterthan;; -CREATE FUNCTION dbo.json_number_greaterthan(@col NVARCHAR(MAX), @path NVARCHAR(500), @target DECIMAL(38, 10)) +CREATE OR ALTER FUNCTION dbo.json_number_greaterthan(@col NVARCHAR(MAX), @path NVARCHAR(500), @target DECIMAL(38, 10)) RETURNS BIT AS BEGIN @@ -263,8 +244,7 @@ BEGIN RETURN CASE WHEN TRY_CAST(JSON_VALUE(@col, @path) AS DECIMAL(38, 10)) > @target THEN 1 ELSE 0 END; END;; -DROP FUNCTION IF EXISTS dbo.json_number_greaterthanorequal;; -CREATE FUNCTION dbo.json_number_greaterthanorequal(@col NVARCHAR(MAX), @path NVARCHAR(500), @target DECIMAL(38, 10)) +CREATE OR ALTER FUNCTION dbo.json_number_greaterthanorequal(@col NVARCHAR(MAX), @path NVARCHAR(500), @target DECIMAL(38, 10)) RETURNS BIT AS BEGIN @@ -282,8 +262,7 @@ END;; -- ============================================================================= -- BOOLEAN -- ============================================================================= -DROP FUNCTION IF EXISTS dbo.json_boolean_equals;; -CREATE FUNCTION dbo.json_boolean_equals(@col NVARCHAR(MAX), @path NVARCHAR(500), @target BIT) +CREATE OR ALTER FUNCTION dbo.json_boolean_equals(@col NVARCHAR(MAX), @path NVARCHAR(500), @target BIT) RETURNS BIT AS BEGIN @@ -297,8 +276,7 @@ BEGIN RETURN CASE WHEN IIF(JSON_VALUE(@col, @path) = 'true', 1, IIF(JSON_VALUE(@col, @path) = 'false', 0, NULL)) = @target THEN 1 ELSE 0 END; END;; -DROP FUNCTION IF EXISTS dbo.json_boolean_notequals;; -CREATE FUNCTION dbo.json_boolean_notequals(@col NVARCHAR(MAX), @path NVARCHAR(500), @target BIT) +CREATE OR ALTER FUNCTION dbo.json_boolean_notequals(@col NVARCHAR(MAX), @path NVARCHAR(500), @target BIT) RETURNS BIT AS BEGIN From 14fcb6b28989ac4bef049cb63b3856266b0d282e Mon Sep 17 00:00:00 2001 From: Joe de Ronde Date: Tue, 12 May 2026 16:18:21 +0100 Subject: [PATCH 2/6] Move JSON function creation into EF Core migrations - Replace SqlDialectInitializer startup logic with proper EF Core migrations for all three providers (MySQL, SQL Server, Postgres) - SQL Server: uses CREATE OR ALTER FUNCTION (idempotent, no DROP needed) - MySQL: uses DROP FUNCTION IF EXISTS + CREATE FUNCTION in migration - Remove SqlDialectInitializer registration from production ServiceExtensions - Add migration tests: idempotency and upgrade-from-pre-migration-database --- ...0260512000000_AddJsonFunctions.Designer.cs | 1628 ++++++++++++++++ .../20260512000000_AddJsonFunctions.cs | 67 + .../Providers/MySql/JsonFunction.cs | 7 - .../Providers/MySql/json_function.sql | 75 +- ...0260512000002_AddJsonFunctions.Designer.cs | 1629 ++++++++++++++++ .../20260512000002_AddJsonFunctions.cs | 59 + ...0260512000001_AddJsonFunctions.Designer.cs | 1631 +++++++++++++++++ .../20260512000001_AddJsonFunctions.cs | 64 + .../ServiceExtensions.cs | 1 - .../Migrations/MySqlMigrationTests.cs | 87 +- .../Migrations/SqlServerMigrationTests.cs | 77 + 11 files changed, 5291 insertions(+), 34 deletions(-) create mode 100644 backend/src/Squidex.Data.EntityFramework/Providers/MySql/App/Migrations/20260512000000_AddJsonFunctions.Designer.cs create mode 100644 backend/src/Squidex.Data.EntityFramework/Providers/MySql/App/Migrations/20260512000000_AddJsonFunctions.cs create mode 100644 backend/src/Squidex.Data.EntityFramework/Providers/Postgres/App/Migrations/20260512000002_AddJsonFunctions.Designer.cs create mode 100644 backend/src/Squidex.Data.EntityFramework/Providers/Postgres/App/Migrations/20260512000002_AddJsonFunctions.cs create mode 100644 backend/src/Squidex.Data.EntityFramework/Providers/SqlServer/App/Migrations/20260512000001_AddJsonFunctions.Designer.cs create mode 100644 backend/src/Squidex.Data.EntityFramework/Providers/SqlServer/App/Migrations/20260512000001_AddJsonFunctions.cs diff --git a/backend/src/Squidex.Data.EntityFramework/Providers/MySql/App/Migrations/20260512000000_AddJsonFunctions.Designer.cs b/backend/src/Squidex.Data.EntityFramework/Providers/MySql/App/Migrations/20260512000000_AddJsonFunctions.Designer.cs new file mode 100644 index 0000000000..7b85f9a415 --- /dev/null +++ b/backend/src/Squidex.Data.EntityFramework/Providers/MySql/App/Migrations/20260512000000_AddJsonFunctions.Designer.cs @@ -0,0 +1,1628 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NetTopologySuite.Geometries; +using Squidex.Providers.MySql.App; + +#nullable disable + +namespace Squidex.Providers.MySql.App.Migrations +{ + [DbContext(typeof(MySqlAppDbContext))] + [Migration("20260512000000_AddJsonFunctions")] + partial class AddJsonFunctions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnd") + .HasColumnType("datetime(6)"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("longtext"); + + b.Property("PhoneNumber") + .HasColumnType("longtext"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("SecurityStamp") + .HasColumnType("longtext"); + + b.Property("TwoFactorEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("ProviderKey") + .HasColumnType("varchar(255)"); + + b.Property("ProviderDisplayName") + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("RoleId") + .HasColumnType("varchar(255)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("Name") + .HasColumnType("varchar(255)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("varchar(255)"); + + b.Property("ApplicationId") + .HasColumnType("varchar(255)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Properties") + .HasColumnType("longtext"); + + b.Property("Scopes") + .HasColumnType("longtext"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("varchar(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("varchar(255)"); + + b.Property("ApplicationId") + .HasColumnType("varchar(255)"); + + b.Property("AuthorizationId") + .HasColumnType("varchar(255)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Payload") + .HasColumnType("longtext"); + + b.Property("Properties") + .HasColumnType("longtext"); + + b.Property("RedemptionDate") + .HasColumnType("datetime(6)"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("varchar(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", (string)null); + }); + + modelBuilder.Entity("Squidex.AI.EntityFramework.EFChatEntity", b => + { + b.Property("Id") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("LastUpdated") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("LastUpdated"); + + b.ToTable("Chats", (string)null); + }); + + modelBuilder.Entity("Squidex.Assets.EntityFramework.EFAssetKeyValueEntity", b => + { + b.Property("Key") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Expires") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Key"); + + b.HasIndex("Expires"); + + b.ToTable("AssetKeyValueStore_TusMetadata", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Apps.EFAppEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Document") + .HasColumnType("json"); + + b.Property("IndexedCreated") + .HasColumnType("datetime(6)") + .HasColumnName("Created"); + + b.Property("IndexedDeleted") + .HasColumnType("tinyint(1)") + .HasColumnName("Deleted"); + + b.Property("IndexedName") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("Name"); + + b.Property("IndexedTeamId") + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasColumnName("TeamId"); + + b.Property("IndexedUserIds") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("UserIds"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_App", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Assets.EFAssetEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Created") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("FileHash") + .HasColumnType("longtext"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("FileVersion") + .HasColumnType("bigint"); + + b.Property("Id") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("IndexedAppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)"); + + b.Property("IsProtected") + .HasColumnType("tinyint(1)"); + + b.Property("LastModified") + .HasColumnType("datetime(6)"); + + b.Property("LastModifiedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("json"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ParentId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Slug") + .HasColumnType("longtext"); + + b.Property("Tags") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("TotalSize") + .HasColumnType("bigint"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.HasIndex("IndexedAppId", "Id"); + + b.ToTable("Assets"); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Assets.EFAssetFolderEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Created") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("FolderName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Id") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("IndexedAppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)"); + + b.Property("LastModified") + .HasColumnType("datetime(6)"); + + b.Property("LastModifiedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("ParentId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.HasIndex("IndexedAppId", "Id"); + + b.ToTable("AssetFolders"); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.EFContentCompleteEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Created") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("json"); + + b.Property("Id") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("IndexedAppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("IndexedSchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)"); + + b.Property("LastModified") + .HasColumnType("datetime(6)"); + + b.Property("LastModifiedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("NewData") + .HasColumnType("json"); + + b.Property("NewStatus") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("ScheduleJob") + .HasColumnType("json"); + + b.Property("ScheduledAt") + .HasColumnType("datetime(6)"); + + b.Property("SchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("TranslationStatus") + .HasColumnType("json"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("ContentsAll", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.EFContentPublishedEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Created") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("json"); + + b.Property("Id") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("IndexedAppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("IndexedSchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)"); + + b.Property("LastModified") + .HasColumnType("datetime(6)"); + + b.Property("LastModifiedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("NewData") + .HasColumnType("json"); + + b.Property("NewStatus") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("ScheduleJob") + .HasColumnType("json"); + + b.Property("ScheduledAt") + .HasColumnType("datetime(6)"); + + b.Property("SchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("TranslationStatus") + .HasColumnType("json"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("ContentsPublished", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.EFContentTableEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("SchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("AppId", "SchemaId") + .IsUnique(); + + b.ToTable("ContentTables", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.EFReferenceCompleteEntity", b => + { + b.Property("AppId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("FromKey") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("ToId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("FromSchema") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasDefaultValue("00000000-0000-0000-0000-000000000000"); + + b.HasKey("AppId", "FromKey", "ToId"); + + b.ToTable("ContentReferencesAll", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.EFReferencePublishedEntity", b => + { + b.Property("AppId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("FromKey") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("ToId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("FromSchema") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasDefaultValue("00000000-0000-0000-0000-000000000000"); + + b.HasKey("AppId", "FromKey", "ToId"); + + b.ToTable("ContentReferencesPublished", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.Text.EFTextIndexGeoEntity", b => + { + b.Property("Id") + .HasMaxLength(400) + .HasColumnType("varchar(400)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("ContentId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("GeoField") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("GeoObject") + .IsRequired() + .HasColumnType("geometry"); + + b.Property("SchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("ServeAll") + .HasColumnType("tinyint(1)"); + + b.Property("ServePublished") + .HasColumnType("tinyint(1)"); + + b.Property("Stage") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.ToTable("Geos"); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.Text.EFTextIndexTextEntity", b => + { + b.Property("Id") + .HasMaxLength(400) + .HasColumnType("varchar(400)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("ContentId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("SchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("ServeAll") + .HasColumnType("tinyint(1)"); + + b.Property("ServePublished") + .HasColumnType("tinyint(1)"); + + b.Property("Stage") + .HasColumnType("tinyint unsigned"); + + b.Property("Texts") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Texts"); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.Text.EFTextIndexUserInfoEntity", b => + { + b.Property("Id") + .HasMaxLength(400) + .HasColumnType("varchar(400)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("ContentId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("SchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("ServeAll") + .HasColumnType("tinyint(1)"); + + b.Property("ServePublished") + .HasColumnType("tinyint(1)"); + + b.Property("Stage") + .HasColumnType("tinyint unsigned"); + + b.Property("UserInfoApiKey") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserInfoRole") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("UserInfoApiKey"); + + b.ToTable("UserInfos"); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.Text.State.TextContentState", b => + { + b.Property("UniqueContentId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("State") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("UniqueContentId"); + + b.ToTable("TextState", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.History.HistoryEvent", b => + { + b.Property("Id") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Actor") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Channel") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Created") + .HasColumnType("datetime(6)"); + + b.Property("EventType") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Parameters") + .IsRequired() + .HasColumnType("json"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("HistoryEvent"); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Rules.EFRuleEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Document") + .HasColumnType("json"); + + b.Property("IndexedAppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasColumnName("AppId"); + + b.Property("IndexedDeleted") + .HasColumnType("tinyint(1)") + .HasColumnName("Deleted"); + + b.Property("IndexedId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasColumnName("Id"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Rule", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Schemas.EFSchemaEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Document") + .HasColumnType("json"); + + b.Property("IndexedAppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasColumnName("AppId"); + + b.Property("IndexedDeleted") + .HasColumnType("tinyint(1)") + .HasColumnName("Deleted"); + + b.Property("IndexedId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasColumnName("Id"); + + b.Property("IndexedName") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("Name"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Schema", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Teams.EFTeamEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Document") + .HasColumnType("json"); + + b.Property("IndexedAuthDomain") + .HasColumnType("longtext") + .HasColumnName("AuthDomain"); + + b.Property("IndexedDeleted") + .HasColumnType("tinyint(1)") + .HasColumnName("Deleted"); + + b.Property("IndexedUserIds") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("UserIds"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Team", (string)null); + }); + + modelBuilder.Entity("Squidex.Events.EntityFramework.EFEventCommit", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("EventStream") + .IsRequired() + .HasMaxLength(750) + .HasColumnType("varchar(750)"); + + b.Property("EventStreamOffset") + .HasColumnType("bigint"); + + b.PrimitiveCollection("Events") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("EventsCount") + .HasColumnType("bigint"); + + b.Property("Position") + .HasColumnType("bigint"); + + b.Property("Timestamp") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("EventStream", "EventStreamOffset") + .IsUnique(); + + b.HasIndex("EventStream", "Position"); + + b.HasIndex("EventStream", "Timestamp"); + + b.ToTable("Events"); + }); + + modelBuilder.Entity("Squidex.Events.EntityFramework.EFPosition", b => + { + b.Property("Id") + .HasColumnType("bigint"); + + b.Property("Position") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("EventPosition"); + }); + + modelBuilder.Entity("Squidex.Flows.EntityFramework.EFCronJobEntity", b => + { + b.Property("Id") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("DueTime") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("DueTime"); + + b.ToTable("CronJobs", (string)null); + }); + + modelBuilder.Entity("Squidex.Flows.EntityFramework.EFFlowStateEntity", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Created") + .HasColumnType("datetime(6)"); + + b.Property("DefinitionId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("DueTime") + .HasColumnType("datetime(6)"); + + b.Property("OwnerId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("SchedulePartition") + .HasColumnType("int"); + + b.Property("State") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("DueTime", "SchedulePartition"); + + b.ToTable("Flows", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.Caching.EFCacheEntity", b => + { + b.Property("Key") + .HasColumnType("varchar(255)"); + + b.Property("Expires") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longblob"); + + b.HasKey("Key"); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.Log.EFRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Key") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("Properties") + .IsRequired() + .HasColumnType("json"); + + b.Property("Timestamp") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("Key"); + + b.ToTable("Requests", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.Migrations.EFMigrationEntity", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("IsLocked") + .HasColumnType("tinyint(1)"); + + b.Property("Version") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Migrations", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Document") + .HasColumnType("json"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_UISettings", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Document") + .HasColumnType("json"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Index_TagHistory", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Document") + .HasColumnType("json"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_UsageNotifications", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Document") + .HasColumnType("json"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Counters", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Document") + .HasColumnType("json"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_JobsState", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Document") + .HasColumnType("json"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_UsageTracker", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Document") + .HasColumnType("json"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Index_Tags", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Document") + .HasColumnType("json"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Identity_Keys", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Document") + .HasColumnType("json"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Identity_Xml", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Document") + .HasColumnType("json"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_EventConsumerState", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Document") + .HasColumnType("json"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Names", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.UsageTracking.EFUsageCounterEntity", b => + { + b.Property("Key") + .HasColumnType("varchar(255)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Category") + .HasColumnType("varchar(255)"); + + b.Property("CounterKey") + .HasColumnType("varchar(255)"); + + b.Property("CounterValue") + .HasColumnType("double"); + + b.HasKey("Key", "Date", "Category", "CounterKey"); + + b.ToTable("Counter", (string)null); + }); + + modelBuilder.Entity("Squidex.Messaging.EntityFramework.EFMessage", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("ChannelName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("MessageData") + .IsRequired() + .HasColumnType("longblob"); + + b.Property("MessageHeaders") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("QueueName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("TimeHandled") + .HasColumnType("datetime(6)"); + + b.Property("TimeToLive") + .HasColumnType("datetime(6)"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ChannelName", "TimeHandled"); + + b.ToTable("Messages", (string)null); + }); + + modelBuilder.Entity("Squidex.Messaging.EntityFramework.EFMessagingDataEntity", b => + { + b.Property("Group") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Key") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Expiration") + .HasColumnType("datetime(6)"); + + b.Property("ValueData") + .IsRequired() + .HasColumnType("longblob"); + + b.Property("ValueFormat") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("ValueType") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.HasKey("Group", "Key"); + + b.HasIndex("Expiration"); + + b.ToTable("MessagingData", (string)null); + }); + + modelBuilder.Entity("YDotNet.Server.EntityFramework.YDotNetDocument", b => + { + b.Property("Id") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("longblob"); + + b.Property("Expiration") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.ToTable("YDotNetDocument", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/src/Squidex.Data.EntityFramework/Providers/MySql/App/Migrations/20260512000000_AddJsonFunctions.cs b/backend/src/Squidex.Data.EntityFramework/Providers/MySql/App/Migrations/20260512000000_AddJsonFunctions.cs new file mode 100644 index 0000000000..9eabada7b4 --- /dev/null +++ b/backend/src/Squidex.Data.EntityFramework/Providers/MySql/App/Migrations/20260512000000_AddJsonFunctions.cs @@ -0,0 +1,67 @@ +using System.IO; +using Microsoft.EntityFrameworkCore.Migrations; +using Squidex.Providers.MySql; + +#nullable disable + +namespace Squidex.Providers.MySql.App.Migrations +{ + /// + public partial class AddJsonFunctions : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + var assembly = typeof(MySqlDialect).Assembly; + + using var sqlStream = assembly.GetManifestResourceStream("Squidex.Providers.MySql.json_function.sql")!; + using var reader = new StreamReader(sqlStream); + + var sqlText = reader.ReadToEnd(); + var statements = sqlText.Split(";;", System.StringSplitOptions.RemoveEmptyEntries | System.StringSplitOptions.TrimEntries); + + foreach (var statement in statements) + { + migrationBuilder.Sql(statement, suppressTransaction: true); + } + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + var functions = new[] + { + "json_empty", + "json_exists", + "json_null_equals", + "json_null_notequals", + "json_text_contains", + "json_text_endswith", + "json_text_equals", + "json_text_greaterthan", + "json_text_greaterthanorequal", + "json_text_in", + "json_text_lessthan", + "json_text_lessthanorequal", + "json_text_matchs", + "json_text_notequals", + "json_text_startswith", + "json_boolean_equals", + "json_boolean_in", + "json_boolean_notequals", + "json_number_equals", + "json_number_greaterthan", + "json_number_greaterthanorequal", + "json_number_in", + "json_number_lessthan", + "json_number_lessthanorequal", + "json_number_notequals", + }; + + foreach (var function in functions) + { + migrationBuilder.Sql($"DROP FUNCTION IF EXISTS {function}", suppressTransaction: true); + } + } + } +} diff --git a/backend/src/Squidex.Data.EntityFramework/Providers/MySql/JsonFunction.cs b/backend/src/Squidex.Data.EntityFramework/Providers/MySql/JsonFunction.cs index 425d787034..b75f041bbc 100644 --- a/backend/src/Squidex.Data.EntityFramework/Providers/MySql/JsonFunction.cs +++ b/backend/src/Squidex.Data.EntityFramework/Providers/MySql/JsonFunction.cs @@ -57,15 +57,8 @@ public static async Task InitializeAsync(DbContext dbContext, sqlText = sqlText.Replace("}", "}}", StringComparison.Ordinal); var statements = sqlText.Split(";;", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - // We want to filter out the drop statements and multiple function creations are not supported. foreach (var statement in statements) { -#if RELEASE - if (statement.StartsWith("DROP", StringComparison.Ordinal)) - { - continue; - } -#endif await dbContext.Database.ExecuteSqlRawAsync(statement, ct); } } diff --git a/backend/src/Squidex.Data.EntityFramework/Providers/MySql/json_function.sql b/backend/src/Squidex.Data.EntityFramework/Providers/MySql/json_function.sql index 3932573aec..39e89b7d7f 100644 --- a/backend/src/Squidex.Data.EntityFramework/Providers/MySql/json_function.sql +++ b/backend/src/Squidex.Data.EntityFramework/Providers/MySql/json_function.sql @@ -1,7 +1,8 @@ -- ============================================================================= -- TYPE-AGNOSTIC -- ============================================================================= -CREATE OR REPLACE FUNCTION json_empty(col JSON, path VARCHAR(500)) +DROP FUNCTION IF EXISTS json_empty;; +CREATE FUNCTION json_empty(col JSON, path VARCHAR(500)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -13,7 +14,8 @@ BEGIN RETURN 0; END;; -CREATE OR REPLACE FUNCTION json_exists(col JSON, path VARCHAR(500)) +DROP FUNCTION IF EXISTS json_exists;; +CREATE FUNCTION json_exists(col JSON, path VARCHAR(500)) RETURNS TINYINT(1) DETERMINISTIC BEGIN RETURN NOT json_empty(col, path); @@ -23,7 +25,8 @@ END;; -- ============================================================================= -- NULL -- ============================================================================= -CREATE OR REPLACE FUNCTION json_null_equals(col JSON, path VARCHAR(500)) +DROP FUNCTION IF EXISTS json_null_equals;; +CREATE FUNCTION json_null_equals(col JSON, path VARCHAR(500)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -37,7 +40,8 @@ BEGIN RETURN COALESCE(JSON_TYPE(val), 'NULL') = 'NULL'; END;; -CREATE OR REPLACE FUNCTION json_null_notequals(col JSON, path VARCHAR(500)) +DROP FUNCTION IF EXISTS json_null_notequals;; +CREATE FUNCTION json_null_notequals(col JSON, path VARCHAR(500)) RETURNS TINYINT(1) DETERMINISTIC BEGIN RETURN NOT json_null_equals(col, path); @@ -47,7 +51,8 @@ END;; -- ============================================================================= -- TEXT -- ============================================================================= -CREATE OR REPLACE FUNCTION json_text_equals(col JSON, path VARCHAR(500), target VARCHAR(2000)) +DROP FUNCTION IF EXISTS json_text_equals;; +CREATE FUNCTION json_text_equals(col JSON, path VARCHAR(500), target VARCHAR(2000)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -61,7 +66,8 @@ BEGIN RETURN JSON_UNQUOTE(val) = target; END;; -CREATE OR REPLACE FUNCTION json_text_notequals(col JSON, path VARCHAR(500), target VARCHAR(2000)) +DROP FUNCTION IF EXISTS json_text_notequals;; +CREATE FUNCTION json_text_notequals(col JSON, path VARCHAR(500), target VARCHAR(2000)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -75,7 +81,8 @@ BEGIN RETURN JSON_UNQUOTE(val) != target; END;; -CREATE OR REPLACE FUNCTION json_text_lessthan(col JSON, path VARCHAR(500), target VARCHAR(2000)) +DROP FUNCTION IF EXISTS json_text_lessthan;; +CREATE FUNCTION json_text_lessthan(col JSON, path VARCHAR(500), target VARCHAR(2000)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -89,7 +96,8 @@ BEGIN RETURN JSON_UNQUOTE(val) < target; END;; -CREATE OR REPLACE FUNCTION json_text_lessthanorequal(col JSON, path VARCHAR(500), target VARCHAR(2000)) +DROP FUNCTION IF EXISTS json_text_lessthanorequal;; +CREATE FUNCTION json_text_lessthanorequal(col JSON, path VARCHAR(500), target VARCHAR(2000)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -103,7 +111,8 @@ BEGIN RETURN JSON_UNQUOTE(val) <= target; END;; -CREATE OR REPLACE FUNCTION json_text_greaterthan(col JSON, path VARCHAR(500), target VARCHAR(2000)) +DROP FUNCTION IF EXISTS json_text_greaterthan;; +CREATE FUNCTION json_text_greaterthan(col JSON, path VARCHAR(500), target VARCHAR(2000)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -117,7 +126,8 @@ BEGIN RETURN JSON_UNQUOTE(val) > target; END;; -CREATE OR REPLACE FUNCTION json_text_greaterthanorequal(col JSON, path VARCHAR(500), target VARCHAR(2000)) +DROP FUNCTION IF EXISTS json_text_greaterthanorequal;; +CREATE FUNCTION json_text_greaterthanorequal(col JSON, path VARCHAR(500), target VARCHAR(2000)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -131,7 +141,8 @@ BEGIN RETURN JSON_UNQUOTE(val) >= target; END;; -CREATE OR REPLACE FUNCTION json_text_contains(col JSON, path VARCHAR(500), target VARCHAR(2000)) +DROP FUNCTION IF EXISTS json_text_contains;; +CREATE FUNCTION json_text_contains(col JSON, path VARCHAR(500), target VARCHAR(2000)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -145,7 +156,8 @@ BEGIN RETURN JSON_UNQUOTE(val) LIKE CONCAT('%', target, '%'); END;; -CREATE OR REPLACE FUNCTION json_text_startswith(col JSON, path VARCHAR(500), target VARCHAR(2000)) +DROP FUNCTION IF EXISTS json_text_startswith;; +CREATE FUNCTION json_text_startswith(col JSON, path VARCHAR(500), target VARCHAR(2000)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -159,7 +171,8 @@ BEGIN RETURN JSON_UNQUOTE(val) LIKE CONCAT(target, '%'); END;; -CREATE OR REPLACE FUNCTION json_text_endswith(col JSON, path VARCHAR(500), target VARCHAR(2000)) +DROP FUNCTION IF EXISTS json_text_endswith;; +CREATE FUNCTION json_text_endswith(col JSON, path VARCHAR(500), target VARCHAR(2000)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -173,7 +186,8 @@ BEGIN RETURN JSON_UNQUOTE(val) LIKE CONCAT('%', target); END;; -CREATE OR REPLACE FUNCTION json_text_matchs(col JSON, path VARCHAR(500), target VARCHAR(2000)) +DROP FUNCTION IF EXISTS json_text_matchs;; +CREATE FUNCTION json_text_matchs(col JSON, path VARCHAR(500), target VARCHAR(2000)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -187,7 +201,8 @@ BEGIN RETURN JSON_UNQUOTE(val) REGEXP target; END;; -CREATE OR REPLACE FUNCTION json_text_in(col JSON, path VARCHAR(500), targets JSON) +DROP FUNCTION IF EXISTS json_text_in;; +CREATE FUNCTION json_text_in(col JSON, path VARCHAR(500), targets JSON) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -205,7 +220,8 @@ END;; -- ============================================================================= -- NUMBER -- ============================================================================= -CREATE OR REPLACE FUNCTION json_number_equals(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) +DROP FUNCTION IF EXISTS json_number_equals;; +CREATE FUNCTION json_number_equals(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -222,7 +238,8 @@ BEGIN RETURN CAST(JSON_UNQUOTE(val) AS DECIMAL(65, 10)) = target; END;; -CREATE OR REPLACE FUNCTION json_number_notequals(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) +DROP FUNCTION IF EXISTS json_number_notequals;; +CREATE FUNCTION json_number_notequals(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -239,7 +256,8 @@ BEGIN RETURN CAST(JSON_UNQUOTE(val) AS DECIMAL(65, 10)) != target; END;; -CREATE OR REPLACE FUNCTION json_number_lessthan(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) +DROP FUNCTION IF EXISTS json_number_lessthan;; +CREATE FUNCTION json_number_lessthan(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -256,7 +274,8 @@ BEGIN RETURN CAST(JSON_UNQUOTE(val) AS DECIMAL(65, 10)) < target; END;; -CREATE OR REPLACE FUNCTION json_number_lessthanorequal(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) +DROP FUNCTION IF EXISTS json_number_lessthanorequal;; +CREATE FUNCTION json_number_lessthanorequal(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -273,7 +292,8 @@ BEGIN RETURN CAST(JSON_UNQUOTE(val) AS DECIMAL(65, 10)) <= target; END;; -CREATE OR REPLACE FUNCTION json_number_greaterthan(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) +DROP FUNCTION IF EXISTS json_number_greaterthan;; +CREATE FUNCTION json_number_greaterthan(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -290,7 +310,8 @@ BEGIN RETURN CAST(JSON_UNQUOTE(val) AS DECIMAL(65, 10)) > target; END;; -CREATE OR REPLACE FUNCTION json_number_greaterthanorequal(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) +DROP FUNCTION IF EXISTS json_number_greaterthanorequal;; +CREATE FUNCTION json_number_greaterthanorequal(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -307,7 +328,8 @@ BEGIN RETURN CAST(JSON_UNQUOTE(val) AS DECIMAL(65, 10)) >= target; END;; -CREATE OR REPLACE FUNCTION json_number_in(col JSON, path VARCHAR(500), targets JSON) +DROP FUNCTION IF EXISTS json_number_in;; +CREATE FUNCTION json_number_in(col JSON, path VARCHAR(500), targets JSON) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -327,7 +349,8 @@ END;; -- ============================================================================= -- BOOLEAN -- ============================================================================= -CREATE OR REPLACE FUNCTION json_boolean_equals(col JSON, path VARCHAR(500), target TINYINT(1)) +DROP FUNCTION IF EXISTS json_boolean_equals;; +CREATE FUNCTION json_boolean_equals(col JSON, path VARCHAR(500), target TINYINT(1)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -344,7 +367,8 @@ BEGIN RETURN val = target_json; END;; -CREATE OR REPLACE FUNCTION json_boolean_notequals(col JSON, path VARCHAR(500), target TINYINT(1)) +DROP FUNCTION IF EXISTS json_boolean_notequals;; +CREATE FUNCTION json_boolean_notequals(col JSON, path VARCHAR(500), target TINYINT(1)) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; @@ -361,7 +385,8 @@ BEGIN RETURN val != target_json; END;; -CREATE OR REPLACE FUNCTION json_boolean_in(col JSON, path VARCHAR(500), targets JSON) +DROP FUNCTION IF EXISTS json_boolean_in;; +CREATE FUNCTION json_boolean_in(col JSON, path VARCHAR(500), targets JSON) RETURNS TINYINT(1) DETERMINISTIC BEGIN DECLARE val JSON; diff --git a/backend/src/Squidex.Data.EntityFramework/Providers/Postgres/App/Migrations/20260512000002_AddJsonFunctions.Designer.cs b/backend/src/Squidex.Data.EntityFramework/Providers/Postgres/App/Migrations/20260512000002_AddJsonFunctions.Designer.cs new file mode 100644 index 0000000000..2a288f4a4b --- /dev/null +++ b/backend/src/Squidex.Data.EntityFramework/Providers/Postgres/App/Migrations/20260512000002_AddJsonFunctions.Designer.cs @@ -0,0 +1,1629 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NetTopologySuite.Geometries; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Squidex.Providers.Postgres.App; + +#nullable disable + +namespace Squidex.Providers.Postgres.App.Migrations +{ + [DbContext(typeof(PostgresAppDbContext))] + [Migration("20260512000002_AddJsonFunctions")] + partial class AddJsonFunctions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Scopes") + .HasColumnType("text"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("AuthorizationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Payload") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedemptionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", (string)null); + }); + + modelBuilder.Entity("Squidex.AI.EntityFramework.EFChatEntity", b => + { + b.Property("Id") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("LastUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LastUpdated"); + + b.ToTable("Chats", (string)null); + }); + + modelBuilder.Entity("Squidex.Assets.EntityFramework.EFAssetKeyValueEntity", b => + { + b.Property("Key") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Key"); + + b.HasIndex("Expires"); + + b.ToTable("AssetKeyValueStore_TusMetadata", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Apps.EFAppEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Document") + .HasColumnType("jsonb"); + + b.Property("IndexedCreated") + .HasColumnType("timestamp with time zone") + .HasColumnName("Created"); + + b.Property("IndexedDeleted") + .HasColumnType("boolean") + .HasColumnName("Deleted"); + + b.Property("IndexedName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("Name"); + + b.Property("IndexedTeamId") + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("TeamId"); + + b.Property("IndexedUserIds") + .IsRequired() + .HasColumnType("text") + .HasColumnName("UserIds"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_App", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Assets.EFAssetEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("FileHash") + .HasColumnType("text"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("FileVersion") + .HasColumnType("bigint"); + + b.Property("Id") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndexedAppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsProtected") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("ParentId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Slug") + .HasColumnType("text"); + + b.Property("Tags") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("TotalSize") + .HasColumnType("bigint"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.HasIndex("IndexedAppId", "Id"); + + b.ToTable("Assets"); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Assets.EFAssetFolderEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("FolderName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Id") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndexedAppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ParentId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.HasIndex("IndexedAppId", "Id"); + + b.ToTable("AssetFolders"); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.EFContentCompleteEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Id") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndexedAppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndexedSchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("NewData") + .HasColumnType("jsonb"); + + b.Property("NewStatus") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ScheduleJob") + .HasColumnType("jsonb"); + + b.Property("ScheduledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TranslationStatus") + .HasColumnType("jsonb"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("ContentsAll", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.EFContentPublishedEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Id") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndexedAppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndexedSchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("NewData") + .HasColumnType("jsonb"); + + b.Property("NewStatus") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ScheduleJob") + .HasColumnType("jsonb"); + + b.Property("ScheduledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TranslationStatus") + .HasColumnType("jsonb"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("ContentsPublished", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.EFContentTableEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("AppId", "SchemaId") + .IsUnique(); + + b.ToTable("ContentTables", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.EFReferenceCompleteEntity", b => + { + b.Property("AppId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("FromKey") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ToId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("FromSchema") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasDefaultValue("00000000-0000-0000-0000-000000000000"); + + b.HasKey("AppId", "FromKey", "ToId"); + + b.ToTable("ContentReferencesAll", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.EFReferencePublishedEntity", b => + { + b.Property("AppId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("FromKey") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ToId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("FromSchema") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasDefaultValue("00000000-0000-0000-0000-000000000000"); + + b.HasKey("AppId", "FromKey", "ToId"); + + b.ToTable("ContentReferencesPublished", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.Text.EFTextIndexGeoEntity", b => + { + b.Property("Id") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ContentId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("GeoField") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("GeoObject") + .IsRequired() + .HasColumnType("geometry"); + + b.Property("SchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ServeAll") + .HasColumnType("boolean"); + + b.Property("ServePublished") + .HasColumnType("boolean"); + + b.Property("Stage") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.ToTable("Geos"); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.Text.EFTextIndexTextEntity", b => + { + b.Property("Id") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ContentId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ServeAll") + .HasColumnType("boolean"); + + b.Property("ServePublished") + .HasColumnType("boolean"); + + b.Property("Stage") + .HasColumnType("smallint"); + + b.Property("Texts") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Texts"); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.Text.EFTextIndexUserInfoEntity", b => + { + b.Property("Id") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ContentId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ServeAll") + .HasColumnType("boolean"); + + b.Property("ServePublished") + .HasColumnType("boolean"); + + b.Property("Stage") + .HasColumnType("smallint"); + + b.Property("UserInfoApiKey") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UserInfoRole") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("UserInfoApiKey"); + + b.ToTable("UserInfos"); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.Text.State.TextContentState", b => + { + b.Property("UniqueContentId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("State") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("UniqueContentId"); + + b.ToTable("TextState", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.History.HistoryEvent", b => + { + b.Property("Id") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Actor") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Channel") + .IsRequired() + .HasColumnType("text"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("EventType") + .IsRequired() + .HasColumnType("text"); + + b.Property("OwnerId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Parameters") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("HistoryEvent"); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Rules.EFRuleEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Document") + .HasColumnType("jsonb"); + + b.Property("IndexedAppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("AppId"); + + b.Property("IndexedDeleted") + .HasColumnType("boolean") + .HasColumnName("Deleted"); + + b.Property("IndexedId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("Id"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Rule", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Schemas.EFSchemaEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Document") + .HasColumnType("jsonb"); + + b.Property("IndexedAppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("AppId"); + + b.Property("IndexedDeleted") + .HasColumnType("boolean") + .HasColumnName("Deleted"); + + b.Property("IndexedId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("Id"); + + b.Property("IndexedName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("Name"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Schema", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Teams.EFTeamEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Document") + .HasColumnType("jsonb"); + + b.Property("IndexedAuthDomain") + .HasColumnType("text") + .HasColumnName("AuthDomain"); + + b.Property("IndexedDeleted") + .HasColumnType("boolean") + .HasColumnName("Deleted"); + + b.Property("IndexedUserIds") + .IsRequired() + .HasColumnType("text") + .HasColumnName("UserIds"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Team", (string)null); + }); + + modelBuilder.Entity("Squidex.Events.EntityFramework.EFEventCommit", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("EventStream") + .IsRequired() + .HasMaxLength(750) + .HasColumnType("character varying(750)"); + + b.Property("EventStreamOffset") + .HasColumnType("bigint"); + + b.PrimitiveCollection("Events") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("EventsCount") + .HasColumnType("bigint"); + + b.Property("Position") + .HasColumnType("bigint"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("EventStream", "EventStreamOffset") + .IsUnique(); + + b.HasIndex("EventStream", "Position"); + + b.HasIndex("EventStream", "Timestamp"); + + b.ToTable("Events"); + }); + + modelBuilder.Entity("Squidex.Events.EntityFramework.EFPosition", b => + { + b.Property("Id") + .HasColumnType("bigint"); + + b.Property("Position") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("EventPosition"); + }); + + modelBuilder.Entity("Squidex.Flows.EntityFramework.EFCronJobEntity", b => + { + b.Property("Id") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("DueTime") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DueTime"); + + b.ToTable("CronJobs", (string)null); + }); + + modelBuilder.Entity("Squidex.Flows.EntityFramework.EFFlowStateEntity", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("DefinitionId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("DueTime") + .HasColumnType("timestamp with time zone"); + + b.Property("OwnerId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SchedulePartition") + .HasColumnType("integer"); + + b.Property("State") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DueTime", "SchedulePartition"); + + b.ToTable("Flows", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.Caching.EFCacheEntity", b => + { + b.Property("Key") + .HasColumnType("text"); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Key"); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.Log.EFRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Properties") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Key"); + + b.ToTable("Requests", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.Migrations.EFMigrationEntity", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("IsLocked") + .HasColumnType("boolean"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("Migrations", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Document") + .HasColumnType("jsonb"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_UISettings", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Document") + .HasColumnType("jsonb"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Index_TagHistory", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Document") + .HasColumnType("jsonb"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_UsageNotifications", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Document") + .HasColumnType("jsonb"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Counters", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Document") + .HasColumnType("jsonb"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_JobsState", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Document") + .HasColumnType("jsonb"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_UsageTracker", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Document") + .HasColumnType("jsonb"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Index_Tags", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Document") + .HasColumnType("jsonb"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Identity_Keys", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Document") + .HasColumnType("jsonb"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Identity_Xml", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Document") + .HasColumnType("jsonb"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_EventConsumerState", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Document") + .HasColumnType("jsonb"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Names", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.UsageTracking.EFUsageCounterEntity", b => + { + b.Property("Key") + .HasColumnType("text"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Category") + .HasColumnType("text"); + + b.Property("CounterKey") + .HasColumnType("text"); + + b.Property("CounterValue") + .HasColumnType("double precision"); + + b.HasKey("Key", "Date", "Category", "CounterKey"); + + b.ToTable("Counter", (string)null); + }); + + modelBuilder.Entity("Squidex.Messaging.EntityFramework.EFMessage", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ChannelName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("MessageData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("MessageHeaders") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("QueueName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TimeHandled") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeToLive") + .HasColumnType("timestamp with time zone"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ChannelName", "TimeHandled"); + + b.ToTable("Messages", (string)null); + }); + + modelBuilder.Entity("Squidex.Messaging.EntityFramework.EFMessagingDataEntity", b => + { + b.Property("Group") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Key") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ValueData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("ValueFormat") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ValueType") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Group", "Key"); + + b.HasIndex("Expiration"); + + b.ToTable("MessagingData", (string)null); + }); + + modelBuilder.Entity("YDotNet.Server.EntityFramework.YDotNetDocument", b => + { + b.Property("Id") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("YDotNetDocument", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/src/Squidex.Data.EntityFramework/Providers/Postgres/App/Migrations/20260512000002_AddJsonFunctions.cs b/backend/src/Squidex.Data.EntityFramework/Providers/Postgres/App/Migrations/20260512000002_AddJsonFunctions.cs new file mode 100644 index 0000000000..988c75f5b1 --- /dev/null +++ b/backend/src/Squidex.Data.EntityFramework/Providers/Postgres/App/Migrations/20260512000002_AddJsonFunctions.cs @@ -0,0 +1,59 @@ +using System.IO; +using Microsoft.EntityFrameworkCore.Migrations; +using Squidex.Providers.Postgres; + +#nullable disable + +namespace Squidex.Providers.Postgres.App.Migrations +{ + /// + public partial class AddJsonFunctions : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + var assembly = typeof(PostgresDialect).Assembly; + + using var sqlStream = assembly.GetManifestResourceStream("Squidex.Providers.Postgres.json_function.sql")!; + using var reader = new StreamReader(sqlStream); + + var sqlText = reader.ReadToEnd(); + var statements = sqlText.Split(";;", System.StringSplitOptions.RemoveEmptyEntries | System.StringSplitOptions.TrimEntries); + + foreach (var statement in statements) + { + migrationBuilder.Sql(statement); + } + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_empty(jsonb)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_exists(jsonb)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_null_equals(jsonb)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_null_notequals(jsonb)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_text_equals(jsonb, text)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_text_notequals(jsonb, text)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_text_lessthan(jsonb, text)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_text_lessthanorequal(jsonb, text)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_text_greaterthan(jsonb, text)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_text_greaterthanorequal(jsonb, text)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_text_contains(jsonb, text)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_text_startswith(jsonb, text)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_text_endswith(jsonb, text)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_text_matchs(jsonb, text)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_text_in(jsonb, text[])"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_number_equals(jsonb, numeric)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_number_notequals(jsonb, numeric)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_number_lessthan(jsonb, numeric)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_number_lessthanorequal(jsonb, numeric)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_number_greaterthan(jsonb, numeric)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_number_greaterthanorequal(jsonb, numeric)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_number_in(jsonb, numeric[])"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_boolean_equals(jsonb, boolean)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_boolean_notequals(jsonb, boolean)"); + migrationBuilder.Sql("DROP FUNCTION IF EXISTS jsonb_boolean_in(jsonb, boolean[])"); + } + } +} diff --git a/backend/src/Squidex.Data.EntityFramework/Providers/SqlServer/App/Migrations/20260512000001_AddJsonFunctions.Designer.cs b/backend/src/Squidex.Data.EntityFramework/Providers/SqlServer/App/Migrations/20260512000001_AddJsonFunctions.Designer.cs new file mode 100644 index 0000000000..9834a7d41a --- /dev/null +++ b/backend/src/Squidex.Data.EntityFramework/Providers/SqlServer/App/Migrations/20260512000001_AddJsonFunctions.Designer.cs @@ -0,0 +1,1631 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NetTopologySuite.Geometries; +using Squidex.Providers.SqlServer.App; + +#nullable disable + +namespace Squidex.Providers.SqlServer.App.Migrations +{ + [DbContext(typeof(SqlServerAppDbContext))] + [Migration("20260512000001_AddJsonFunctions")] + partial class AddJsonFunctions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("ApplicationId") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("Properties") + .HasColumnType("nvarchar(max)"); + + b.Property("Scopes") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("nvarchar(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("ApplicationId") + .HasColumnType("nvarchar(450)"); + + b.Property("AuthorizationId") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("ExpirationDate") + .HasColumnType("datetime2"); + + b.Property("Payload") + .HasColumnType("nvarchar(max)"); + + b.Property("Properties") + .HasColumnType("nvarchar(max)"); + + b.Property("RedemptionDate") + .HasColumnType("datetime2"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("nvarchar(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique() + .HasFilter("[ReferenceId] IS NOT NULL"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", (string)null); + }); + + modelBuilder.Entity("Squidex.AI.EntityFramework.EFChatEntity", b => + { + b.Property("Id") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("LastUpdated") + .HasColumnType("datetime2"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("LastUpdated"); + + b.ToTable("Chats", (string)null); + }); + + modelBuilder.Entity("Squidex.Assets.EntityFramework.EFAssetKeyValueEntity", b => + { + b.Property("Key") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Expires") + .HasColumnType("datetimeoffset"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Key"); + + b.HasIndex("Expires"); + + b.ToTable("AssetKeyValueStore_TusMetadata", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Apps.EFAppEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Document") + .HasColumnType("nvarchar(max)"); + + b.Property("IndexedCreated") + .HasColumnType("datetimeoffset") + .HasColumnName("Created"); + + b.Property("IndexedDeleted") + .HasColumnType("bit") + .HasColumnName("Deleted"); + + b.Property("IndexedName") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("Name"); + + b.Property("IndexedTeamId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)") + .HasColumnName("TeamId"); + + b.Property("IndexedUserIds") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("UserIds"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_App", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Assets.EFAssetEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Created") + .HasColumnType("datetimeoffset"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("FileHash") + .HasColumnType("nvarchar(max)"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("FileVersion") + .HasColumnType("bigint"); + + b.Property("Id") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("IndexedAppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsProtected") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetimeoffset"); + + b.Property("LastModifiedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ParentId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Slug") + .HasColumnType("nvarchar(max)"); + + b.Property("Tags") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("TotalSize") + .HasColumnType("bigint"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.HasIndex("IndexedAppId", "Id"); + + b.ToTable("Assets"); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Assets.EFAssetFolderEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Created") + .HasColumnType("datetimeoffset"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("FolderName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Id") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("IndexedAppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetimeoffset"); + + b.Property("LastModifiedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ParentId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.HasIndex("IndexedAppId", "Id"); + + b.ToTable("AssetFolders"); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.EFContentCompleteEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Created") + .HasColumnType("datetimeoffset"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Id") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("IndexedAppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("IndexedSchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetimeoffset"); + + b.Property("LastModifiedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("NewData") + .HasColumnType("nvarchar(max)"); + + b.Property("NewStatus") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ScheduleJob") + .HasColumnType("nvarchar(max)"); + + b.Property("ScheduledAt") + .HasColumnType("datetimeoffset"); + + b.Property("SchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TranslationStatus") + .HasColumnType("nvarchar(max)"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("ContentsAll", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.EFContentPublishedEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Created") + .HasColumnType("datetimeoffset"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Id") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("IndexedAppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("IndexedSchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetimeoffset"); + + b.Property("LastModifiedBy") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("NewData") + .HasColumnType("nvarchar(max)"); + + b.Property("NewStatus") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ScheduleJob") + .HasColumnType("nvarchar(max)"); + + b.Property("ScheduledAt") + .HasColumnType("datetimeoffset"); + + b.Property("SchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TranslationStatus") + .HasColumnType("nvarchar(max)"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("ContentsPublished", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.EFContentTableEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("SchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("AppId", "SchemaId") + .IsUnique(); + + b.ToTable("ContentTables", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.EFReferenceCompleteEntity", b => + { + b.Property("AppId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("FromKey") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("ToId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("FromSchema") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)") + .HasDefaultValue("00000000-0000-0000-0000-000000000000"); + + b.HasKey("AppId", "FromKey", "ToId"); + + b.ToTable("ContentReferencesAll", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.EFReferencePublishedEntity", b => + { + b.Property("AppId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("FromKey") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("ToId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("FromSchema") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)") + .HasDefaultValue("00000000-0000-0000-0000-000000000000"); + + b.HasKey("AppId", "FromKey", "ToId"); + + b.ToTable("ContentReferencesPublished", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.Text.EFTextIndexGeoEntity", b => + { + b.Property("Id") + .HasMaxLength(400) + .HasColumnType("nvarchar(400)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("ContentId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("GeoField") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("GeoObject") + .IsRequired() + .HasColumnType("geography"); + + b.Property("SchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("ServeAll") + .HasColumnType("bit"); + + b.Property("ServePublished") + .HasColumnType("bit"); + + b.Property("Stage") + .HasColumnType("tinyint"); + + b.HasKey("Id"); + + b.ToTable("Geos"); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.Text.EFTextIndexTextEntity", b => + { + b.Property("Id") + .HasMaxLength(400) + .HasColumnType("nvarchar(400)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("ContentId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("SchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("ServeAll") + .HasColumnType("bit"); + + b.Property("ServePublished") + .HasColumnType("bit"); + + b.Property("Stage") + .HasColumnType("tinyint"); + + b.Property("Texts") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Texts"); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.Text.EFTextIndexUserInfoEntity", b => + { + b.Property("Id") + .HasMaxLength(400) + .HasColumnType("nvarchar(400)"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("ContentId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("SchemaId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("ServeAll") + .HasColumnType("bit"); + + b.Property("ServePublished") + .HasColumnType("bit"); + + b.Property("Stage") + .HasColumnType("tinyint"); + + b.Property("UserInfoApiKey") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("UserInfoRole") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("UserInfoApiKey"); + + b.ToTable("UserInfos"); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Contents.Text.State.TextContentState", b => + { + b.Property("UniqueContentId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("State") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("UniqueContentId"); + + b.ToTable("TextState", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.History.HistoryEvent", b => + { + b.Property("Id") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Actor") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Channel") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetimeoffset"); + + b.Property("EventType") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("OwnerId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Parameters") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("HistoryEvent"); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Rules.EFRuleEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Document") + .HasColumnType("nvarchar(max)"); + + b.Property("IndexedAppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)") + .HasColumnName("AppId"); + + b.Property("IndexedDeleted") + .HasColumnType("bit") + .HasColumnName("Deleted"); + + b.Property("IndexedId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)") + .HasColumnName("Id"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Rule", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Schemas.EFSchemaEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Document") + .HasColumnType("nvarchar(max)"); + + b.Property("IndexedAppId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)") + .HasColumnName("AppId"); + + b.Property("IndexedDeleted") + .HasColumnType("bit") + .HasColumnName("Deleted"); + + b.Property("IndexedId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)") + .HasColumnName("Id"); + + b.Property("IndexedName") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("Name"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Schema", (string)null); + }); + + modelBuilder.Entity("Squidex.Domain.Apps.Entities.Teams.EFTeamEntity", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Document") + .HasColumnType("nvarchar(max)"); + + b.Property("IndexedAuthDomain") + .HasColumnType("nvarchar(max)") + .HasColumnName("AuthDomain"); + + b.Property("IndexedDeleted") + .HasColumnType("bit") + .HasColumnName("Deleted"); + + b.Property("IndexedUserIds") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("UserIds"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Team", (string)null); + }); + + modelBuilder.Entity("Squidex.Events.EntityFramework.EFEventCommit", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("EventStream") + .IsRequired() + .HasMaxLength(750) + .HasColumnType("nvarchar(750)"); + + b.Property("EventStreamOffset") + .HasColumnType("bigint"); + + b.PrimitiveCollection("Events") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EventsCount") + .HasColumnType("bigint"); + + b.Property("Position") + .HasColumnType("bigint"); + + b.Property("Timestamp") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("EventStream", "EventStreamOffset") + .IsUnique(); + + b.HasIndex("EventStream", "Position"); + + b.HasIndex("EventStream", "Timestamp"); + + b.ToTable("Events"); + }); + + modelBuilder.Entity("Squidex.Events.EntityFramework.EFPosition", b => + { + b.Property("Id") + .HasColumnType("bigint"); + + b.Property("Position") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("EventPosition"); + }); + + modelBuilder.Entity("Squidex.Flows.EntityFramework.EFCronJobEntity", b => + { + b.Property("Id") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DueTime") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.HasIndex("DueTime"); + + b.ToTable("CronJobs", (string)null); + }); + + modelBuilder.Entity("Squidex.Flows.EntityFramework.EFFlowStateEntity", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Created") + .HasColumnType("datetimeoffset"); + + b.Property("DefinitionId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("DueTime") + .HasColumnType("datetimeoffset"); + + b.Property("OwnerId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("SchedulePartition") + .HasColumnType("int"); + + b.Property("State") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DueTime", "SchedulePartition"); + + b.ToTable("Flows", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.Caching.EFCacheEntity", b => + { + b.Property("Key") + .HasColumnType("nvarchar(450)"); + + b.Property("Expires") + .HasColumnType("datetime2"); + + b.Property("Value") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.HasKey("Key"); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.Log.EFRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Key") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Properties") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Timestamp") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.HasIndex("Key"); + + b.ToTable("Requests", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.Migrations.EFMigrationEntity", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("IsLocked") + .HasColumnType("bit"); + + b.Property("Version") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Migrations", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Document") + .HasColumnType("nvarchar(max)"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_UISettings", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Document") + .HasColumnType("nvarchar(max)"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Index_TagHistory", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Document") + .HasColumnType("nvarchar(max)"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_UsageNotifications", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Document") + .HasColumnType("nvarchar(max)"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Counters", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Document") + .HasColumnType("nvarchar(max)"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_JobsState", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Document") + .HasColumnType("nvarchar(max)"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_UsageTracker", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Document") + .HasColumnType("nvarchar(max)"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Index_Tags", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Document") + .HasColumnType("nvarchar(max)"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Identity_Keys", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Document") + .HasColumnType("nvarchar(max)"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Identity_Xml", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Document") + .HasColumnType("nvarchar(max)"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_EventConsumerState", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.States.EFState", b => + { + b.Property("DocumentId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Document") + .HasColumnType("nvarchar(max)"); + + b.Property("Version") + .HasColumnType("bigint"); + + b.HasKey("DocumentId"); + + b.ToTable("States_Names", (string)null); + }); + + modelBuilder.Entity("Squidex.Infrastructure.UsageTracking.EFUsageCounterEntity", b => + { + b.Property("Key") + .HasColumnType("nvarchar(450)"); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.Property("Category") + .HasColumnType("nvarchar(450)"); + + b.Property("CounterKey") + .HasColumnType("nvarchar(450)"); + + b.Property("CounterValue") + .HasColumnType("float"); + + b.HasKey("Key", "Date", "Category", "CounterKey"); + + b.ToTable("Counter", (string)null); + }); + + modelBuilder.Entity("Squidex.Messaging.EntityFramework.EFMessage", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ChannelName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("MessageData") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("MessageHeaders") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("QueueName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("TimeHandled") + .HasColumnType("datetime2"); + + b.Property("TimeToLive") + .HasColumnType("datetime2"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ChannelName", "TimeHandled"); + + b.ToTable("Messages", (string)null); + }); + + modelBuilder.Entity("Squidex.Messaging.EntityFramework.EFMessagingDataEntity", b => + { + b.Property("Group") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Key") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.Property("ValueData") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("ValueFormat") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("ValueType") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.HasKey("Group", "Key"); + + b.HasIndex("Expiration"); + + b.ToTable("MessagingData", (string)null); + }); + + modelBuilder.Entity("YDotNet.Server.EntityFramework.YDotNetDocument", b => + { + b.Property("Id") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("YDotNetDocument", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/src/Squidex.Data.EntityFramework/Providers/SqlServer/App/Migrations/20260512000001_AddJsonFunctions.cs b/backend/src/Squidex.Data.EntityFramework/Providers/SqlServer/App/Migrations/20260512000001_AddJsonFunctions.cs new file mode 100644 index 0000000000..b33425c266 --- /dev/null +++ b/backend/src/Squidex.Data.EntityFramework/Providers/SqlServer/App/Migrations/20260512000001_AddJsonFunctions.cs @@ -0,0 +1,64 @@ +using System.IO; +using Microsoft.EntityFrameworkCore.Migrations; +using Squidex.Providers.SqlServer; + +#nullable disable + +namespace Squidex.Providers.SqlServer.App.Migrations +{ + /// + public partial class AddJsonFunctions : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + var assembly = typeof(SqlServerDialect).Assembly; + + using var sqlStream = assembly.GetManifestResourceStream("Squidex.Providers.SqlServer.json_function.sql")!; + using var reader = new StreamReader(sqlStream); + + var sqlText = reader.ReadToEnd(); + var statements = sqlText.Split(";;", System.StringSplitOptions.RemoveEmptyEntries | System.StringSplitOptions.TrimEntries); + + foreach (var statement in statements) + { + migrationBuilder.Sql(statement); + } + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + var functions = new[] + { + "dbo.json_empty", + "dbo.json_exists", + "dbo.json_null_equals", + "dbo.json_null_notequals", + "dbo.json_text_contains", + "dbo.json_text_endswith", + "dbo.json_text_equals", + "dbo.json_text_greaterthan", + "dbo.json_text_greaterthanorequal", + "dbo.json_text_lessthan", + "dbo.json_text_lessthanorequal", + "dbo.json_text_matchs", + "dbo.json_text_notequals", + "dbo.json_text_startswith", + "dbo.json_boolean_equals", + "dbo.json_boolean_notequals", + "dbo.json_number_equals", + "dbo.json_number_greaterthan", + "dbo.json_number_greaterthanorequal", + "dbo.json_number_lessthan", + "dbo.json_number_lessthanorequal", + "dbo.json_number_notequals", + }; + + foreach (var function in functions) + { + migrationBuilder.Sql($"DROP FUNCTION IF EXISTS {function}"); + } + } + } +} diff --git a/backend/src/Squidex.Data.EntityFramework/ServiceExtensions.cs b/backend/src/Squidex.Data.EntityFramework/ServiceExtensions.cs index 1679cdefbb..8c0ad4c61f 100644 --- a/backend/src/Squidex.Data.EntityFramework/ServiceExtensions.cs +++ b/backend/src/Squidex.Data.EntityFramework/ServiceExtensions.cs @@ -271,7 +271,6 @@ private static void AddSquidexEntityFramework(this IS .AddEntityFrameworkStore(); services.AddEntityFrameworkAssetKeyValueStore(); - services.AddSingletonAs>(); } public static void AddSquidexEntityFrameworkEventStore(this IServiceCollection services, IConfiguration config) diff --git a/backend/tests/Squidex.Data.Tests/EntityFramework/Migrations/MySqlMigrationTests.cs b/backend/tests/Squidex.Data.Tests/EntityFramework/Migrations/MySqlMigrationTests.cs index 722021ea85..d869f433e0 100644 --- a/backend/tests/Squidex.Data.Tests/EntityFramework/Migrations/MySqlMigrationTests.cs +++ b/backend/tests/Squidex.Data.Tests/EntityFramework/Migrations/MySqlMigrationTests.cs @@ -8,7 +8,9 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Squidex.Domain.Apps.Core.TestHelpers; +using Squidex.Infrastructure; using Squidex.Infrastructure.Migrations; +using Squidex.Infrastructure.Queries; using Squidex.Providers.MySql; using Squidex.Providers.MySql.App; using Testcontainers.MySql; @@ -18,7 +20,9 @@ namespace Squidex.EntityFramework.Migrations; [Trait("Category", "TestContainer")] public class MySqlMigrationTests : IAsyncLifetime { - private readonly MySqlContainer mysql = new MySqlBuilder("mysql:8.0").Build(); + private readonly MySqlContainer mysql = new MySqlBuilder("mysql:8.0") + .WithCommand("--log-bin-trust-function-creators=1") + .Build(); public async ValueTask InitializeAsync() { @@ -60,4 +64,85 @@ public async Task Should_migrate() var migrations = await dbContext.Database.GetAppliedMigrationsAsync(); Assert.NotEmpty(migrations); } + + [Fact] + public async Task Should_migrate_idempotent_and_functions_callable() + { + var connectionString = mysql.GetConnectionString(); + + var services = + new ServiceCollection() + .AddDbContextFactory(b => + { + b.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString), options => + { + options.UseNetTopologySuite(); + options.UseMicrosoftJson(MySqlCommonJsonChangeTrackingOptions.FullHierarchyOptimizedSemantically); + }); + }) + .AddSingleton() + .AddSingleton(TestUtils.DefaultSerializer) + .AddSingleton>() + .BuildServiceProvider(); + + var databaseMigrator = services.GetRequiredService>(); + var databaseFactory = services.GetRequiredService>(); + + // Run migrations twice to verify idempotency. + await databaseMigrator.InitializeAsync(default); + await databaseMigrator.InitializeAsync(default); + + // Verify the json_exists function was created and is callable. + // Note: {{ and }} are escaped braces for ExecuteSqlRawAsync's string.Format-style parser. + await using var dbContext = await databaseFactory.CreateDbContextAsync(); + var result = await dbContext.Database.ExecuteSqlRawAsync( + "SELECT json_exists('{{\"a\":1}}', '$.a')"); + Assert.Equal(-1, result); + } + + [Fact] + public async Task Should_migrate_when_functions_already_exist() + { + var connectionString = mysql.GetConnectionString(); + + var services = + new ServiceCollection() + .AddDbContextFactory(b => + { + b.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString), options => + { + options.UseNetTopologySuite(); + options.UseMicrosoftJson(MySqlCommonJsonChangeTrackingOptions.FullHierarchyOptimizedSemantically); + }); + }) + .AddSingleton() + .AddSingleton(TestUtils.DefaultSerializer) + .AddSingleton>() + .BuildServiceProvider(); + + var databaseMigrator = services.GetRequiredService>(); + var databaseFactory = services.GetRequiredService>(); + + // Simulate a pre-migration database: apply all migrations up to (but not including) + // AddJsonFunctions, then create the functions via the old SqlDialectInitializer path. + await using (var dbContext = await databaseFactory.CreateDbContextAsync()) + { + await dbContext.Database.MigrateAsync("20260323154443_MigrateToNet10"); + + if (dbContext is IDbContextWithDialect withDialect) + { + await withDialect.Dialect.InitializeAsync(dbContext, default); + } + } + + // Run the full migration — should apply AddJsonFunctions on top of already-existing functions. + await databaseMigrator.InitializeAsync(default); + + // Verify the functions are still callable after migration. + // Note: {{ and }} are escaped braces for ExecuteSqlRawAsync's string.Format-style parser. + await using var verifyContext = await databaseFactory.CreateDbContextAsync(); + var result = await verifyContext.Database.ExecuteSqlRawAsync( + "SELECT json_exists('{{\"a\":1}}', '$.a')"); + Assert.Equal(-1, result); + } } diff --git a/backend/tests/Squidex.Data.Tests/EntityFramework/Migrations/SqlServerMigrationTests.cs b/backend/tests/Squidex.Data.Tests/EntityFramework/Migrations/SqlServerMigrationTests.cs index 38808e059d..ec49451918 100644 --- a/backend/tests/Squidex.Data.Tests/EntityFramework/Migrations/SqlServerMigrationTests.cs +++ b/backend/tests/Squidex.Data.Tests/EntityFramework/Migrations/SqlServerMigrationTests.cs @@ -8,7 +8,9 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Squidex.Domain.Apps.Core.TestHelpers; +using Squidex.Infrastructure; using Squidex.Infrastructure.Migrations; +using Squidex.Infrastructure.Queries; using Squidex.Providers.SqlServer; using Squidex.Providers.SqlServer.App; using Testcontainers.MsSql; @@ -59,4 +61,79 @@ public async Task Should_migrate() var migrations = await dbContext.Database.GetAppliedMigrationsAsync(); Assert.NotEmpty(migrations); } + + [Fact] + public async Task Should_migrate_idempotent_and_functions_callable() + { + var services = + new ServiceCollection() + .AddDbContextFactory(b => + { + b.UseSqlServer(sqlServer.GetConnectionString(), options => + { + options.UseNetTopologySuite(); + }); + }) + .AddSingleton() + .AddSingleton(TestUtils.DefaultSerializer) + .AddSingleton>() + .BuildServiceProvider(); + + var databaseMigrator = services.GetRequiredService>(); + var databaseFactory = services.GetRequiredService>(); + + // Run migrations twice to verify idempotency. + await databaseMigrator.InitializeAsync(default); + await databaseMigrator.InitializeAsync(default); + + // Verify the dbo.json_exists function was created and is callable. + // Note: {{ and }} are escaped braces for ExecuteSqlRawAsync's string.Format-style parser. + await using var dbContext = await databaseFactory.CreateDbContextAsync(); + var result = await dbContext.Database.ExecuteSqlRawAsync( + "SELECT dbo.json_exists('{{\"a\":1}}', '$.a')"); + Assert.Equal(-1, result); + } + + [Fact] + public async Task Should_migrate_when_functions_already_exist() + { + var services = + new ServiceCollection() + .AddDbContextFactory(b => + { + b.UseSqlServer(sqlServer.GetConnectionString(), options => + { + options.UseNetTopologySuite(); + }); + }) + .AddSingleton() + .AddSingleton(TestUtils.DefaultSerializer) + .AddSingleton>() + .BuildServiceProvider(); + + var databaseMigrator = services.GetRequiredService>(); + var databaseFactory = services.GetRequiredService>(); + + // Simulate a pre-migration database: apply all migrations up to (but not including) + // AddJsonFunctions, then create the functions via the old SqlDialectInitializer path. + await using (var dbContext = await databaseFactory.CreateDbContextAsync()) + { + await dbContext.Database.MigrateAsync("20260323155035_MigrateToNet10"); + + if (dbContext is IDbContextWithDialect withDialect) + { + await withDialect.Dialect.InitializeAsync(dbContext, default); + } + } + + // Run the full migration — should apply AddJsonFunctions on top of already-existing functions. + await databaseMigrator.InitializeAsync(default); + + // Verify the functions are still callable after migration. + // Note: {{ and }} are escaped braces for ExecuteSqlRawAsync's string.Format-style parser. + await using var verifyContext = await databaseFactory.CreateDbContextAsync(); + var result = await verifyContext.Database.ExecuteSqlRawAsync( + "SELECT dbo.json_exists('{{\"a\":1}}', '$.a')"); + Assert.Equal(-1, result); + } } From 08773ee63423c33f2f34bcfdba0dda97c70d1868 Mon Sep 17 00:00:00 2001 From: Joe de Ronde Date: Wed, 13 May 2026 08:57:53 +0100 Subject: [PATCH 3/6] Add Postgres migration tests; fix image tag for arm64 compatibility --- .../Migrations/PostgresMigrationTests.cs | 79 ++++++++++++++++++- .../TestHelpers/PostgresFixture.cs | 2 +- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/backend/tests/Squidex.Data.Tests/EntityFramework/Migrations/PostgresMigrationTests.cs b/backend/tests/Squidex.Data.Tests/EntityFramework/Migrations/PostgresMigrationTests.cs index d7dd7a589e..6e7a7dd70e 100644 --- a/backend/tests/Squidex.Data.Tests/EntityFramework/Migrations/PostgresMigrationTests.cs +++ b/backend/tests/Squidex.Data.Tests/EntityFramework/Migrations/PostgresMigrationTests.cs @@ -8,7 +8,9 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Squidex.Domain.Apps.Core.TestHelpers; +using Squidex.Infrastructure; using Squidex.Infrastructure.Migrations; +using Squidex.Infrastructure.Queries; using Squidex.Providers.Postgres; using Squidex.Providers.Postgres.App; using Testcontainers.PostgreSql; @@ -19,7 +21,7 @@ namespace Squidex.EntityFramework.Migrations; public class PostgresMigrationTests : IAsyncLifetime { private readonly PostgreSqlContainer postgreSql = - new PostgreSqlBuilder("postgis/postgis") + new PostgreSqlBuilder("imresamu/postgis:16-3.4") .Build(); public async ValueTask InitializeAsync() @@ -59,4 +61,79 @@ public async Task Should_migrate() var migrations = await dbContext.Database.GetAppliedMigrationsAsync(); Assert.NotEmpty(migrations); } + + [Fact] + public async Task Should_migrate_idempotent_and_functions_callable() + { + var services = + new ServiceCollection() + .AddDbContextFactory(b => + { + b.UseNpgsql(postgreSql.GetConnectionString(), options => + { + options.UseNetTopologySuite(); + }); + }) + .AddSingleton() + .AddSingleton(TestUtils.DefaultSerializer) + .AddSingleton>() + .BuildServiceProvider(); + + var databaseMigrator = services.GetRequiredService>(); + var databaseFactory = services.GetRequiredService>(); + + // Run migrations twice to verify idempotency. + await databaseMigrator.InitializeAsync(default); + await databaseMigrator.InitializeAsync(default); + + // Verify the jsonb_exists function was created and is callable. + // Note: {{ and }} are escaped braces for ExecuteSqlRawAsync's string.Format-style parser. + await using var dbContext = await databaseFactory.CreateDbContextAsync(); + var result = await dbContext.Database.ExecuteSqlRawAsync( + "SELECT jsonb_exists('{{\"a\":1}}'::jsonb)"); + Assert.Equal(-1, result); + } + + [Fact] + public async Task Should_migrate_when_functions_already_exist() + { + var services = + new ServiceCollection() + .AddDbContextFactory(b => + { + b.UseNpgsql(postgreSql.GetConnectionString(), options => + { + options.UseNetTopologySuite(); + }); + }) + .AddSingleton() + .AddSingleton(TestUtils.DefaultSerializer) + .AddSingleton>() + .BuildServiceProvider(); + + var databaseMigrator = services.GetRequiredService>(); + var databaseFactory = services.GetRequiredService>(); + + // Simulate a pre-migration database: apply all migrations up to (but not including) + // AddJsonFunctions, then create the functions via the old SqlDialectInitializer path. + await using (var dbContext = await databaseFactory.CreateDbContextAsync()) + { + await dbContext.Database.MigrateAsync("20260323155026_MigrateToNet10"); + + if (dbContext is IDbContextWithDialect withDialect) + { + await withDialect.Dialect.InitializeAsync(dbContext, default); + } + } + + // Run the full migration — should apply AddJsonFunctions on top of already-existing functions. + await databaseMigrator.InitializeAsync(default); + + // Verify the functions are still callable after migration. + // Note: {{ and }} are escaped braces for ExecuteSqlRawAsync's string.Format-style parser. + await using var verifyContext = await databaseFactory.CreateDbContextAsync(); + var result = await verifyContext.Database.ExecuteSqlRawAsync( + "SELECT jsonb_exists('{{\"a\":1}}'::jsonb)"); + Assert.Equal(-1, result); + } } diff --git a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/PostgresFixture.cs b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/PostgresFixture.cs index 4f613fd466..db5d7e09c2 100644 --- a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/PostgresFixture.cs +++ b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/PostgresFixture.cs @@ -23,7 +23,7 @@ namespace Squidex.EntityFramework.TestHelpers; public class PostgresFixture(string? reuseId) : IAsyncLifetime, ISqlContentFixture { private readonly PostgreSqlContainer postgreSql = - new PostgreSqlBuilder("postgis/postgis") + new PostgreSqlBuilder("imresamu/postgis:16-3.4") .WithReuse(true) .WithLabel("reuse-id", reuseId) .Build(); From 8613d62b84a98bad502486887839d48086fe9619 Mon Sep 17 00:00:00 2001 From: Joe de Ronde Date: Wed, 13 May 2026 09:15:17 +0100 Subject: [PATCH 4/6] Replace DatabaseCreator+SqlDialectInitializer with DatabaseMigrator in test fixtures - Test fixtures now use the same code path as production (MigrateAsync) - DatabaseCreator and SqlDialectInitializer are no longer needed and deleted - Functions are created via the AddJsonFunctions migration, not at every startup --- .../Migrations/DatabaseCreator.cs | 42 ------------------- .../Queries/SqlDialectInitializer.cs | 26 ------------ .../TestHelpers/MySqlFixture.cs | 7 ++-- .../TestHelpers/PostgresFixture.cs | 7 ++-- .../TestHelpers/SqlServerFixture.cs | 7 ++-- 5 files changed, 9 insertions(+), 80 deletions(-) delete mode 100644 backend/src/Squidex.Data.EntityFramework/Infrastructure/Migrations/DatabaseCreator.cs delete mode 100644 backend/src/Squidex.Data.EntityFramework/Infrastructure/Queries/SqlDialectInitializer.cs diff --git a/backend/src/Squidex.Data.EntityFramework/Infrastructure/Migrations/DatabaseCreator.cs b/backend/src/Squidex.Data.EntityFramework/Infrastructure/Migrations/DatabaseCreator.cs deleted file mode 100644 index 852f918a40..0000000000 --- a/backend/src/Squidex.Data.EntityFramework/Infrastructure/Migrations/DatabaseCreator.cs +++ /dev/null @@ -1,42 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage; -using Squidex.Hosting; - -#pragma warning disable RECS0108 // Warns about static fields in generic types - -namespace Squidex.Infrastructure.Migrations; - -public sealed class DatabaseCreator(IDbContextFactory dbContextFactory) : IInitializable - where TContext : DbContext -{ - private static readonly TimeSpan WaitTime = TimeSpan.FromSeconds(30); - - public int Order => -1000; - - public async Task InitializeAsync( - CancellationToken ct) - { - await using var dbContext = await dbContextFactory.CreateDbContextAsync(ct); - - using var cts = new CancellationTokenSource(WaitTime); - while (!await dbContext.Database.CanConnectAsync(cts.Token)) - { - await Task.Delay(100, cts.Token); - } - - if (dbContext.Database.GetService() is not RelationalDatabaseCreator relationalDatabaseCreator) - { - return; - } - - await relationalDatabaseCreator.EnsureCreatedAsync(ct); - } -} diff --git a/backend/src/Squidex.Data.EntityFramework/Infrastructure/Queries/SqlDialectInitializer.cs b/backend/src/Squidex.Data.EntityFramework/Infrastructure/Queries/SqlDialectInitializer.cs deleted file mode 100644 index 13a5efe719..0000000000 --- a/backend/src/Squidex.Data.EntityFramework/Infrastructure/Queries/SqlDialectInitializer.cs +++ /dev/null @@ -1,26 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Microsoft.EntityFrameworkCore; -using Squidex.Hosting; - -namespace Squidex.Infrastructure.Queries; - -public sealed class SqlDialectInitializer(IDbContextFactory dbContextFactory) - : IInitializable where TContext : DbContext -{ - public async Task InitializeAsync(CancellationToken ct) - { - await using var dbContext = await dbContextFactory.CreateDbContextAsync(ct); - if (dbContext is not IDbContextWithDialect withDialect) - { - return; - } - - await withDialect.Dialect.InitializeAsync(dbContext, ct); - } -} diff --git a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/MySqlFixture.cs b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/MySqlFixture.cs index b497a60032..e926064ea3 100644 --- a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/MySqlFixture.cs +++ b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/MySqlFixture.cs @@ -11,9 +11,7 @@ using PhenX.EntityFrameworkCore.BulkInsert.MySql; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Hosting; -using Squidex.Infrastructure; using Squidex.Infrastructure.Migrations; -using Squidex.Infrastructure.Queries; using Squidex.Providers.MySql; using Squidex.Providers.MySql.Content; using Testcontainers.MySql; @@ -66,15 +64,16 @@ public async ValueTask InitializeAsync() w.Ignore(RelationalEventId.PendingModelChangesWarning)); }) .AddSingleton() - .AddSingletonAs>().Done() + .AddSingleton>() .AddSingleton(TestUtils.DefaultSerializer) - .AddSingleton>() .BuildServiceProvider(); foreach (var service in services.GetRequiredService>()) { await service.InitializeAsync(default); } + + await services.GetRequiredService>().InitializeAsync(default); } public async ValueTask DisposeAsync() diff --git a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/PostgresFixture.cs b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/PostgresFixture.cs index db5d7e09c2..f66026637b 100644 --- a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/PostgresFixture.cs +++ b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/PostgresFixture.cs @@ -11,9 +11,7 @@ using PhenX.EntityFrameworkCore.BulkInsert.PostgreSql; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Hosting; -using Squidex.Infrastructure; using Squidex.Infrastructure.Migrations; -using Squidex.Infrastructure.Queries; using Squidex.Providers.Postgres; using Squidex.Providers.Postgres.Content; using Testcontainers.PostgreSql; @@ -61,15 +59,16 @@ public async ValueTask InitializeAsync() w.Ignore(RelationalEventId.PendingModelChangesWarning)); }) .AddSingleton() - .AddSingletonAs>().Done() + .AddSingleton>() .AddSingleton(TestUtils.DefaultSerializer) - .AddSingleton>() .BuildServiceProvider(); foreach (var service in services.GetRequiredService>()) { await service.InitializeAsync(default); } + + await services.GetRequiredService>().InitializeAsync(default); } public async ValueTask DisposeAsync() diff --git a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/SqlServerFixture.cs b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/SqlServerFixture.cs index 26a5608f99..2b86ca40f7 100644 --- a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/SqlServerFixture.cs +++ b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/SqlServerFixture.cs @@ -12,9 +12,7 @@ using PhenX.EntityFrameworkCore.BulkInsert.SqlServer; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Hosting; -using Squidex.Infrastructure; using Squidex.Infrastructure.Migrations; -using Squidex.Infrastructure.Queries; using Squidex.Providers.SqlServer; using Squidex.Providers.SqlServer.Content; using Testcontainers.MsSql; @@ -63,15 +61,16 @@ public async ValueTask InitializeAsync() w.Ignore(RelationalEventId.PendingModelChangesWarning)); }) .AddSingleton() - .AddSingletonAs>().Done() + .AddSingleton>() .AddSingleton(TestUtils.DefaultSerializer) - .AddSingleton>() .BuildServiceProvider(); foreach (var service in services.GetRequiredService>()) { await service.InitializeAsync(default); } + + await services.GetRequiredService>().InitializeAsync(default); } public async ValueTask DisposeAsync() From e2177ba5559b67e8dee8b1ab92dffb330543059a Mon Sep 17 00:00:00 2001 From: Joe de Ronde Date: Wed, 13 May 2026 09:21:45 +0100 Subject: [PATCH 5/6] Fix missing Squidex.Infrastructure using in test fixtures --- .../EntityFramework/TestHelpers/MySqlFixture.cs | 1 + .../EntityFramework/TestHelpers/PostgresFixture.cs | 1 + .../EntityFramework/TestHelpers/SqlServerFixture.cs | 1 + 3 files changed, 3 insertions(+) diff --git a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/MySqlFixture.cs b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/MySqlFixture.cs index e926064ea3..8ffc389754 100644 --- a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/MySqlFixture.cs +++ b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/MySqlFixture.cs @@ -11,6 +11,7 @@ using PhenX.EntityFrameworkCore.BulkInsert.MySql; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Hosting; +using Squidex.Infrastructure; using Squidex.Infrastructure.Migrations; using Squidex.Providers.MySql; using Squidex.Providers.MySql.Content; diff --git a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/PostgresFixture.cs b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/PostgresFixture.cs index f66026637b..4e50bf8996 100644 --- a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/PostgresFixture.cs +++ b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/PostgresFixture.cs @@ -11,6 +11,7 @@ using PhenX.EntityFrameworkCore.BulkInsert.PostgreSql; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Hosting; +using Squidex.Infrastructure; using Squidex.Infrastructure.Migrations; using Squidex.Providers.Postgres; using Squidex.Providers.Postgres.Content; diff --git a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/SqlServerFixture.cs b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/SqlServerFixture.cs index 2b86ca40f7..e7d3556ef7 100644 --- a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/SqlServerFixture.cs +++ b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/SqlServerFixture.cs @@ -12,6 +12,7 @@ using PhenX.EntityFrameworkCore.BulkInsert.SqlServer; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Hosting; +using Squidex.Infrastructure; using Squidex.Infrastructure.Migrations; using Squidex.Providers.SqlServer; using Squidex.Providers.SqlServer.Content; From b7b995f63a8167d8bd95fe272311e71552ac38cb Mon Sep 17 00:00:00 2001 From: Joe de Ronde Date: Fri, 15 May 2026 14:07:00 +0100 Subject: [PATCH 6/6] Fix test fixtures: use EnsureCreated+Dialect.InitializeAsync and per-prefix migration history MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs fixed in the EF Core test fixtures (PostgresFixture, MySqlFixture, SqlServerFixture): 1. Replace DatabaseMigrator with EnsureCreatedAsync + Dialect.InitializeAsync TestDbContext* are test-only contexts with no EF migration files, so DatabaseMigrator.InitializeAsync called MigrateAsync which was a complete no-op — no tables were ever created and all integration tests failed with 'relation does not exist'. EnsureCreatedAsync builds the schema directly from the EF Core model, which is the correct approach for contexts without migrations. Dialect.InitializeAsync is then called explicitly to create the database-specific JSON functions (json_exists etc.) that EnsureCreated does not set up. 2. Add per-prefix MigrationsHistoryTable for named ContentDbContext registrations DynamicTables.PrepareAsync calls MigrateAsync on the named ContentDbContext (e.g. PostgresContentDbContext) to create per-app/schema dedicated tables such as '__c5_ContentsAll'. The migration (AddInitial) reads TableName.Prefix to build the table name at runtime. All named contexts shared the default '__EFMigrationsHistory' table, so after the first prefix ran AddInitial and recorded it, every subsequent prefix saw the migration as already applied and skipped it — leaving its dedicated tables uncreated and causing 'relation __cN_ContentsAll does not exist' failures in all but the first dedicated-table test. Setting options.MigrationsHistoryTable(\$"{name}MigrationHistory") gives each prefix its own independent migration history, so AddInitial runs once per prefix and creates the correct tables each time. Also add *.lscache to .gitignore (C# language server cache files). --- .gitignore | 3 +++ .../EntityFramework/TestHelpers/MySqlFixture.cs | 9 +++++++-- .../EntityFramework/TestHelpers/PostgresFixture.cs | 13 ++++++++++--- .../EntityFramework/TestHelpers/SqlServerFixture.cs | 13 ++++++++++--- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 7226094e15..54dce8582e 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ launchSettings.json /frontend/app-config/localhost-key.pem /frontend/app-config/localhost.pem + +# C# language server cache +*.lscache diff --git a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/MySqlFixture.cs b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/MySqlFixture.cs index 8ffc389754..12a49042aa 100644 --- a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/MySqlFixture.cs +++ b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/MySqlFixture.cs @@ -59,13 +59,13 @@ public async ValueTask InitializeAsync() builder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString), options => { options.UseMicrosoftJson(MySqlCommonJsonChangeTrackingOptions.FullHierarchyOptimizedSemantically); + options.MigrationsHistoryTable($"{name}MigrationHistory"); }); builder.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning)); }) .AddSingleton() - .AddSingleton>() .AddSingleton(TestUtils.DefaultSerializer) .BuildServiceProvider(); @@ -74,7 +74,12 @@ public async ValueTask InitializeAsync() await service.InitializeAsync(default); } - await services.GetRequiredService>().InitializeAsync(default); + await using var dbContext = await services.GetRequiredService>().CreateDbContextAsync(); + await dbContext.Database.EnsureCreatedAsync(); + if (dbContext is IDbContextWithDialect withDialect) + { + await withDialect.Dialect.InitializeAsync(dbContext, default); + } } public async ValueTask DisposeAsync() diff --git a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/PostgresFixture.cs b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/PostgresFixture.cs index 4e50bf8996..de1791a258 100644 --- a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/PostgresFixture.cs +++ b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/PostgresFixture.cs @@ -54,13 +54,15 @@ public async ValueTask InitializeAsync() .AddNamedDbContext((builder, name) => { builder.UseBulkInsertPostgreSql(); - builder.UseNpgsql(connectionString); + builder.UseNpgsql(connectionString, options => + { + options.MigrationsHistoryTable($"{name}MigrationHistory"); + }); builder.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning)); }) .AddSingleton() - .AddSingleton>() .AddSingleton(TestUtils.DefaultSerializer) .BuildServiceProvider(); @@ -69,7 +71,12 @@ public async ValueTask InitializeAsync() await service.InitializeAsync(default); } - await services.GetRequiredService>().InitializeAsync(default); + await using var dbContext = await services.GetRequiredService>().CreateDbContextAsync(); + await dbContext.Database.EnsureCreatedAsync(); + if (dbContext is IDbContextWithDialect withDialect) + { + await withDialect.Dialect.InitializeAsync(dbContext, default); + } } public async ValueTask DisposeAsync() diff --git a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/SqlServerFixture.cs b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/SqlServerFixture.cs index e7d3556ef7..51db44a6d8 100644 --- a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/SqlServerFixture.cs +++ b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/SqlServerFixture.cs @@ -56,13 +56,15 @@ public async ValueTask InitializeAsync() .AddNamedDbContext((builder, name) => { builder.UseBulkInsertSqlServer(); - builder.UseSqlServer(connectionString); + builder.UseSqlServer(connectionString, options => + { + options.MigrationsHistoryTable($"{name}MigrationHistory"); + }); builder.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning)); }) .AddSingleton() - .AddSingleton>() .AddSingleton(TestUtils.DefaultSerializer) .BuildServiceProvider(); @@ -71,7 +73,12 @@ public async ValueTask InitializeAsync() await service.InitializeAsync(default); } - await services.GetRequiredService>().InitializeAsync(default); + await using var dbContext = await services.GetRequiredService>().CreateDbContextAsync(); + await dbContext.Database.EnsureCreatedAsync(); + if (dbContext is IDbContextWithDialect withDialect) + { + await withDialect.Dialect.InitializeAsync(dbContext, default); + } } public async ValueTask DisposeAsync()