Skip to content

Both migration generators emit NOT NULL on a multiple: true column, which SqlDriver.createColumn returns before ever constraining — and the pin asserts the generator side #17231

Description

@claude

Found while implementing #16294 cause 3 (PR #17230), which repaired the column DEFAULT divergence between os generate migration and driver-sql. This is the same producer pair and the same "the generator should reproduce the platform" question, one property over — nullability on a multi-value column — and it is deliberately out of that PR's diff because repairing it contradicts a landed pin (see below).

⛔ Not graded or prioritised here. No labels applied, no assignee — this is for triage.

Driven, not read

Measured on a private PostgreSQL 16.13 cluster with the same harness #16294 used: all three producers run from ONE object and their columns read back out of information_schema.columnsdriver-sql through its own initObjects, os generate migration --format sql through db.raw of the emitted DDL, and --format ts by importing the emitted module and calling up(db).

{ d_multi_notnull: { type: 'lookup', referenceTo: 'sys_user', multiple: true, storage: { notNull: true } } }

field              driver              sqlgen              tsgen               verdict
d_multi_notnull    null=YES default=-  null=NO  default=-  null=NO  default=-  DIVERGED

This was the ONE row still diverging in a 23-column defaultValue / nullability probe after #17230 — before it as well as after, i.e. #17230 neither caused nor repaired it.

Why

SqlDriver.createColumn short-circuits on the flag and returns before it reaches either the nullability line or the column DEFAULT:

    if (field.multiple) {
      this.jsonColumn(table, name);
      return;
    }

So the driver's multi-value column is nullable, always, whatever storage.notNull says. Both generators instead apply declaredNotNull(fieldDef) to their JSON column: the sql format emits "tags_nn" JSONB NOT NULL and the ts format table.jsonb('tags_nn').notNullable().

Consequence, in the same direction #16294's cause 2 named one property over: a table created from a generated migration constrains a column the platform's own table leaves open. An INSERT that omits the field is accepted by the platform's table and refused by both generated ones.

⚠️ Why it was NOT repaired under #16294

The current generator behaviour is pinned as intendedpackages/cli/src/commands/generate-multiple-json-column.pin.test.ts:

  it('nullability comes from `storage.notNull`, not from the flag and not from `required`', () => {
    const constrained = { type: 'lookup', multiple: true, storage: { notNull: true } };
    ...
    expect(out).toContain('"tags_nn" JSONB NOT NULL');
    expect(ts).toContain("table.jsonb('tags_nn').notNullable();");
  });

That pin's stated subject (#14829) is "multiple does not decide nullability", and it was rewritten under #16318 when the vehicle moved from required to storage.notNull — but neither card measured the assertion against the DRIVER, which returns before the question is asked. So the pin encodes a rule the platform does not follow. Moving it is therefore a deliberate change to a landed pin, not a rider on an unrelated diff, and the direction is not free to pick by an executor: it is the same "which side moves" question #16318 answered for column TYPE and #17218 still carries for required.

The two sides

⛔ Not answered here. Note the two sides land in different repos' worth of surface — A is domain:cli, B is domain:engine plus possibly a spec refusal — so the routing depends on the ruling.

Reproducing

One object with the field above, driven through all three producers into a live PostgreSQL 16.13 and read back from information_schema.columns. A postgresql-16 server package is present in the standard dev container, so a private cluster is initdb plus pg_ctl with no external service. The same divergence is visible without a server: PRAGMA table_info on three in-memory better-sqlite3 databases, the harness packages/cli/src/commands/generate-declared-column-default.pin.test.ts already uses.


Generated by Claude Code

Activity

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions