From cc4627950832f49eb6aa6a6b1a4e4531c8309b9f Mon Sep 17 00:00:00 2001 From: PhuocOng <122703392+PhuocOng@users.noreply.github.com> Date: Mon, 14 Sep 2026 12:03:36 -0700 Subject: [PATCH] fix(postgrest): allow null reference values in Set --- .../Postgrest.Tests/Linq/LinqQueryTests.cs | 38 ++++++++++++++++ .../Postgrest.Tests/Linq/SetClauseTests.cs | 43 +++++++++++++++++-- .../Models/UserWithJsonData.cs | 15 +++++++ ...ull_GivenAJsonColumnSetToNull.verified.txt | 1 + .../Writing/UpdateApprovalTests.cs | 9 ++++ packages/Postgrest/Postgrest/Table.cs | 2 +- 6 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 packages/Postgrest/Postgrest.Tests/Models/UserWithJsonData.cs create mode 100644 packages/Postgrest/Postgrest.Tests/Writing/Data/UpdateApprovalTests.UpdateRequest_ShouldIncludeExplicitNull_GivenAJsonColumnSetToNull.verified.txt diff --git a/packages/Postgrest/Postgrest.Tests/Linq/LinqQueryTests.cs b/packages/Postgrest/Postgrest.Tests/Linq/LinqQueryTests.cs index d4323f2c..b94b5c95 100644 --- a/packages/Postgrest/Postgrest.Tests/Linq/LinqQueryTests.cs +++ b/packages/Postgrest/Postgrest.Tests/Linq/LinqQueryTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Nodes; using System.Threading.Tasks; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -166,6 +167,43 @@ public async Task Set_ShouldUpdateOnlyTheAssignedColumns() record.IntValue.Should().Be(original.IntValue + 1); } + [TestMethod] + public async Task Set_ShouldClearOnlyTheMatchingRowsJsonColumn_GivenNull() + { + var client = LocalStack.Client(); + var targetUsername = $"set-null-{Guid.NewGuid():N}"; + var neighborUsername = $"{targetUsername}-neighbor"; + await client.Table().Insert(new List + { + new() { Username = targetUsername, Data = new JsonObject { ["value"] = "target" } }, + new() { Username = neighborUsername, Data = new JsonObject { ["value"] = "neighbor" } } + }); + + try + { + var updated = await client.Table() + .Where(user => user.Username == targetUsername) + .Set(user => user.Data!, null) + .Update(new QueryOptions { Returning = QueryOptions.ReturnType.Representation }); + updated.Models.Should().ContainSingle().Which.Data.Should().BeNull(); + + var cleared = await client.Table() + .Where(user => user.Username == targetUsername) + .Filter("data", Operator.Is, "null").Single(); + cleared.Should().NotBeNull(); + + var neighbor = await client.Table() + .Where(user => user.Username == neighborUsername).Single(); + neighbor.Should().NotBeNull(); + neighbor!.Data!["value"]!.GetValue().Should().Be("neighbor"); + } + finally + { + await client.Table() + .Where(user => user.Username == targetUsername || user.Username == neighborUsername).Delete(); + } + } + [TestMethod] public async Task Delete_ShouldRemoveRowsMatchingThePredicate() { diff --git a/packages/Postgrest/Postgrest.Tests/Linq/SetClauseTests.cs b/packages/Postgrest/Postgrest.Tests/Linq/SetClauseTests.cs index dd40489e..4b2233b1 100644 --- a/packages/Postgrest/Postgrest.Tests/Linq/SetClauseTests.cs +++ b/packages/Postgrest/Postgrest.Tests/Linq/SetClauseTests.cs @@ -19,31 +19,66 @@ public class SetClauseTests private const string BaseUrl = "http://localhost:54321/rest/v1"; private readonly Client client = new(BaseUrl); + [TestMethod] + public void Set_ShouldAcceptNull_GivenAReferenceTypeColumn() + { + var act = () => this.client.Table().Set(model => model.ListOfStrings!, null); + act.Should().NotThrow(); + } + + [TestMethod] + public void Set_ShouldAcceptNull_GivenAJsonObjectColumn() + { + var act = () => this.client.Table().Set(user => user.Data!, null); + act.Should().NotThrow(); + } + + [TestMethod] + public void Set_ShouldAcceptNull_GivenAStringColumn() + { + var act = () => this.client.Table().Set(model => model.StringValue!, null); + act.Should().NotThrow(); + } + + [TestMethod] + public void Set_ShouldAcceptNull_GivenANullableValueTypeColumn() + { + var act = () => this.client.Table().Set(model => model.IntValue!, null); + act.Should().NotThrow(); + } + + [TestMethod] + public void Set_ShouldThrow_GivenNullForANonNullableValueTypeColumn() + { + var act = () => this.client.Table().Set(model => model.BooleanValue, null); + act.Should().Throw(); + } + [TestMethod] public void Set_ShouldThrow_GivenAValueOfTheWrongType() { - var act = () => client.Table().Set(x => x.Name!, DateTime.Now); + var act = () => this.client.Table().Set(x => x.Name!, DateTime.Now); act.Should().Throw(); } [TestMethod] public void Set_ShouldThrow_GivenAKeyThatIsNotAColumn() { - var act = () => client.Table().Set(x => DateTime.Now, "value"); + var act = () => this.client.Table().Set(x => DateTime.Now, "value"); act.Should().Throw(); } [TestMethod] public void Set_ShouldThrow_GivenAKeyValuePairWithAMismatchedValueType() { - var act = () => client.Table().Set(x => new KeyValuePair(x.Name!, DateTime.Now)); + var act = () => this.client.Table().Set(x => new KeyValuePair(x.Name!, DateTime.Now)); act.Should().Throw(); } [TestMethod] public void Set_ShouldThrow_GivenAKeyValuePairWhoseKeyIsNotAColumn() { - var act = () => client.Table().Set(x => new KeyValuePair(DateTime.Now, "value")); + var act = () => this.client.Table().Set(x => new KeyValuePair(DateTime.Now, "value")); act.Should().Throw(); } } diff --git a/packages/Postgrest/Postgrest.Tests/Models/UserWithJsonData.cs b/packages/Postgrest/Postgrest.Tests/Models/UserWithJsonData.cs new file mode 100644 index 00000000..e4fc66e2 --- /dev/null +++ b/packages/Postgrest/Postgrest.Tests/Models/UserWithJsonData.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Nodes; +using Supabase.Postgrest.Attributes; +using Supabase.Postgrest.Models; + +namespace Postgrest.Tests.Models; + +[Table("users")] +public class UserWithJsonData : BaseModel +{ + [PrimaryKey("username", true)] + public string? Username { get; set; } + + [Column("data")] + public JsonObject? Data { get; set; } +} diff --git a/packages/Postgrest/Postgrest.Tests/Writing/Data/UpdateApprovalTests.UpdateRequest_ShouldIncludeExplicitNull_GivenAJsonColumnSetToNull.verified.txt b/packages/Postgrest/Postgrest.Tests/Writing/Data/UpdateApprovalTests.UpdateRequest_ShouldIncludeExplicitNull_GivenAJsonColumnSetToNull.verified.txt new file mode 100644 index 00000000..e70a03ae --- /dev/null +++ b/packages/Postgrest/Postgrest.Tests/Writing/Data/UpdateApprovalTests.UpdateRequest_ShouldIncludeExplicitNull_GivenAJsonColumnSetToNull.verified.txt @@ -0,0 +1 @@ +{"data":null} \ No newline at end of file diff --git a/packages/Postgrest/Postgrest.Tests/Writing/UpdateApprovalTests.cs b/packages/Postgrest/Postgrest.Tests/Writing/UpdateApprovalTests.cs index c3deef9a..8233c352 100644 --- a/packages/Postgrest/Postgrest.Tests/Writing/UpdateApprovalTests.cs +++ b/packages/Postgrest/Postgrest.Tests/Writing/UpdateApprovalTests.cs @@ -33,4 +33,13 @@ await this.Client.Table().Filter("id", Operator.Equals, "1") .Update(); await this.Verify(this.EmittedRequestBody).UseDirectory("Data"); } + + [TestMethod] + public async Task UpdateRequest_ShouldIncludeExplicitNull_GivenAJsonColumnSetToNull() + { + await this.Client.Table().Filter("username", Operator.Equals, "null-set-target") + .Set(user => user.Data!, null) + .Update(); + await this.Verify(this.EmittedRequestBody).UseDirectory("Data"); + } } diff --git a/packages/Postgrest/Postgrest/Table.cs b/packages/Postgrest/Postgrest/Table.cs index 92cae782..1d602c8a 100644 --- a/packages/Postgrest/Postgrest/Table.cs +++ b/packages/Postgrest/Postgrest/Table.cs @@ -481,7 +481,7 @@ public IPostgrestTable Set(Expression> keySelector, throw new ArgumentException( "Expression should return a KeyValuePair with a key of a Model Property and a value."); - if (value == null && visitor.ExpectedType != typeof(string)) + if (value == null && visitor.ExpectedType.IsValueType) { if (Nullable.GetUnderlyingType(visitor.ExpectedType) == null) throw new ArgumentException(