diff --git a/backend/src/Squidex.Data.EntityFramework/Providers/Postgres/json_function.sql b/backend/src/Squidex.Data.EntityFramework/Providers/Postgres/json_function.sql index df44e001c..717129342 100644 --- a/backend/src/Squidex.Data.EntityFramework/Providers/Postgres/json_function.sql +++ b/backend/src/Squidex.Data.EntityFramework/Providers/Postgres/json_function.sql @@ -42,6 +42,7 @@ BEGIN SELECT 1 FROM jsonb_array_elements(val) AS e WHERE e #>> '{}' = target ); END IF; + IF val IS NULL OR jsonb_typeof(val) = 'null' THEN RETURN TRUE; END IF; RETURN val #>> '{}' != target; END; $$; @@ -186,7 +187,7 @@ BEGIN WHERE jsonb_typeof(e) = 'number' AND (e #>> '{}')::numeric = target ); END IF; - IF jsonb_typeof(val) != 'number' THEN RETURN FALSE; END IF; + IF jsonb_typeof(val) != 'number' THEN RETURN TRUE; END IF; RETURN (val #>> '{}')::numeric != target; END; $$; @@ -310,7 +311,7 @@ BEGIN WHERE jsonb_typeof(e) = 'boolean' AND (e #>> '{}')::boolean = target ); END IF; - IF jsonb_typeof(val) != 'boolean' THEN RETURN FALSE; END IF; + IF jsonb_typeof(val) != 'boolean' THEN RETURN TRUE; END IF; RETURN (val #>> '{}')::boolean != target; END; $$; diff --git a/backend/tests/Squidex.Data.Tests/EntityFramework/Infrastructure/Queries/EFQueryTests.cs b/backend/tests/Squidex.Data.Tests/EntityFramework/Infrastructure/Queries/EFQueryTests.cs index e4dce705a..c76f042bc 100644 --- a/backend/tests/Squidex.Data.Tests/EntityFramework/Infrastructure/Queries/EFQueryTests.cs +++ b/backend/tests/Squidex.Data.Tests/EntityFramework/Infrastructure/Queries/EFQueryTests.cs @@ -615,6 +615,39 @@ public abstract class EFQueryTests(ISqlFixture fixture) Assert.Equal(Range(1, 10), actual.Order().ToArray()); } + [Fact] + public async Task Should_filter_by_number_not_equal_in_mixed_json() + { + var actual = await QueryAsync(new ClrQuery + { + Filter = ClrFilter.Ne("Json.mixed", 999), + }); + + Assert.Equal(Range(1, 20), actual.Order().ToArray()); + } + + [Fact] + public async Task Should_filter_by_string_not_equal_in_mixed_json() + { + var actual = await QueryAsync(new ClrQuery + { + Filter = ClrFilter.Ne("Json.mixed", "neverexists"), + }); + + Assert.Equal(AllExept(6, 12, 18), actual.Order().ToArray()); + } + + [Fact] + public async Task Should_filter_by_boolean_not_equal_in_mixed_json() + { + var actual = await QueryAsync(new ClrQuery + { + Filter = ClrFilter.Ne("Json.mixed", false), + }); + + Assert.Equal(AllExept(6, 12, 18), actual.Order().ToArray()); + } + [Fact] public async Task Should_filter_by_number_equal_in_mixed_json() { @@ -1517,6 +1550,28 @@ public abstract class EFQueryTests(ISqlFixture fixture) Assert.Equal(Range(1, 10), actual.Order().ToArray()); } + [Fact] + public async Task Should_filter_by_empty_on_nullable_number() + { + var actual = await QueryAsync(new ClrQuery + { + Filter = ClrFilter.Empty("NumberOrNull"), + }, includeSpecialCase: true); + + Assert.Equal([.. Range(11, 20), 21], actual.Order().ToArray()); + } + + [Fact] + public async Task Should_filter_by_exists_on_nullable_number() + { + var actual = await QueryAsync(new ClrQuery + { + Filter = ClrFilter.Exists("NumberOrNull"), + }); + + Assert.Equal(Range(1, 10), actual.Order().ToArray()); + } + [Fact] public async Task Should_filter_by_null_on_boolean() { @@ -1539,6 +1594,28 @@ public abstract class EFQueryTests(ISqlFixture fixture) Assert.Equal(Range(11, 20), actual.Order().ToArray()); } + [Fact] + public async Task Should_filter_by_empty_on_nullable_boolean() + { + var actual = await QueryAsync(new ClrQuery + { + Filter = ClrFilter.Empty("BooleanOrNull"), + }, includeSpecialCase: true); + + Assert.Equal([.. Range(1, 10), 21], actual.Order().ToArray()); + } + + [Fact] + public async Task Should_filter_by_exists_on_nullable_boolean() + { + var actual = await QueryAsync(new ClrQuery + { + Filter = ClrFilter.Exists("BooleanOrNull"), + }); + + Assert.Equal(Range(11, 20), actual.Order().ToArray()); + } + [Fact] public async Task Should_filter_by_null_in_json() {