Skip to content

Translate ExecuteUpdate to null for a value set inside a JSON column #72

Open

Description

@CaffeinatedCoder

ExecuteUpdate that sets a value set inside a ToJson() column to null is refused (#57). It could be translated instead.

EF Core and Npgsql write the setter as jsonb_set("Doc", '{MaybeTags}', to_jsonb(NULL)). Measured against PostgreSQL 17:

SQL Result
jsonb_set(doc, '{t}', NULL) NULL, the whole document
jsonb_set(doc, '{t}', to_jsonb(NULL::text[])) NULL, the whole document
jsonb_set(doc, '{t}', 'null'::jsonb) {"a": 1, "t": null}
jsonb_set_lax(doc, '{t}', NULL) {"a": 1, "t": null} (default use_json_null)

SaveChanges writes a null value set as JSON null, and that reads back as null. So rewriting a setter whose value is the constant NULL (a null parameter is inlined as one) to 'null'::jsonb would write what SaveChanges writes. A value that may be null (CASE … THEN NULL ELSE @p END, a nullable column) would need COALESCE(to_jsonb(…), 'null'::jsonb), and its non-null branch still writes a JSON array, so it stays refused unless the non-null write is fixed as well.

Ranges and multiranges fail the same way, with Npgsql's 42804, so the same rewrite could cover them. That changes a query that threw into one that runs, so it's a 9.1.0 addition.

Open questions:

  • Where to do the rewrite: in the processor decorator that runs ValueSetSqlGuard, or in a translator.
  • Whether the non-null value set setter can be written as to_jsonb(CAST(@p AS text)), which gives the literal the document holds. That would need checking against the plugin's own literal (quoting, temporal precision) before anyone relies on it.

Activity

  1. added this to the 9.1.0 milestone on Oct 17, 2026
  2. removed this from the 9.1.0 milestone on Oct 17, 2026
  3. CaffeinatedCoder commented on Oct 17, 2026

    @CaffeinatedCoder
    OwnerAuthor

    Moved off 9.1.0 into the backlog (#83), with no milestone.

    release/9.x has been cut, and 9.1.0 ships with the work that's already finished. Nothing has started on this issue, and it shouldn't hold up the tag. This is an addition, so the 10.0.0 epic (#58) doesn't take it either: that epic is kept to the silent and breaking fixes so they aren't held up behind new features.

    When someone picks it up, it goes under the epic of the release it will ship in and takes that milestone. A 9.x minor would need the change cherry-picked onto release/9.x.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/value-setsValue set families and wrapper aritiesenhancementNew feature or requestpackage/efcoreAffects CodoMetis.ValueRanges.EFCore.PostgreSQL

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions