Handle field isNullable update (#22362)

## Context

Setting isNullable on a field via the app SDK manifest was silently
ignored when re-syncing an existing field. The first sync that creates a
field honored isNullable correctly, but any later manifest change to
isNullable had no effect, neither on the field metadata nor on the
underlying Postgres column.

Two compounding gaps caused this:

The diff never detected the change. isNullable was configured with
toCompare: false, so compareTwoFlatEntity excluded it from the diff and
no update action was ever generated.
There was no DDL to apply it. Even if detected, the update field action
handler only altered name, options, defaultValue, and settings. The
column manager had no way to alter a column's NOT NULL constraint.

## Fix

- Set isNullable.toCompare: true so manifest changes are detected and
persisted to the field metadata (via the existing executeForMetadata
path).
- Add WorkspaceSchemaColumnManagerService.alterColumnNullable(): emits
SET NOT NULL / DROP NOT NULL, with an optional pre-serialized backfill
(UPDATE … WHERE col IS NULL) applied only on the nullable → non-nullable
transition.
- Add handleFieldNullableUpdate() to the update field action handler,
dispatched after the defaultValue block so the default is in place
before NOT NULL is enforced.
It is composite-aware (mirrors the per-sub-column parentIsNullable ||
!property.isRequired rule used at column creation) and skips
relation/morph join columns and TS_VECTOR, which are always nullable by
design.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22362?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Weiko
2026-07-01 13:31:22 +02:00
committed by GitHub
parent 49a80c72d2
commit fab0358df5
9 changed files with 323 additions and 2 deletions
@@ -97,6 +97,27 @@ Unquoted strings are reserved for computed defaults, evaluated when a record is
The same convention applies to string sub-fields of composite defaults (e.g. `{ source: "'MANUAL'" }` on an `ACTOR` field) and to `SELECT`/`MULTI_SELECT` values. A literal string default left unquoted raises a warning when your app is built.
## Nullability
`isNullable` controls whether a field accepts `NULL`. It defaults to `true` — omit it for optional fields. Set `isNullable: false` to make a field required at the database level.
Changes to `isNullable` are applied on every sync, including syncs that update an existing field — so you can flip a field's nullability by editing the manifest and re-syncing.
<Note>
**Making an existing field non-nullable requires a default value.** When you change a field to `isNullable: false`, you must also provide a non-null `defaultValue`. The default backfills any existing `NULL` rows before the `NOT NULL` constraint is applied; without it the sync fails with `Default value cannot be null for non-nullable fields`. Relation fields and `TS_VECTOR` fields are always nullable, so `isNullable` has no effect on them.
</Note>
```ts
{
universalIdentifier: 'b1a7c0de-1234-4f00-9abc-000000000000',
name: 'reference',
type: FieldType.TEXT,
label: 'Reference',
isNullable: false,
defaultValue: "'N/A'",
}
```
## What's next
- **Connect this object to others** — see [Relations](/developers/extend/apps/data/relations) for the bidirectional relation pattern.