Skip to content

[BUG] Nullability check by SQL EXPLAIN ... can be inaccurate. #5762

@intaniger

Description

@intaniger

Hi,

Before all, my sincere apologies if details provided here need further explanation and/or grammar issue(s). I will get back to answer every questions after Thai election has been passed (2026-02-08T17:00:00+07) and I've got some sleep 😴

I just found that the condition that check nullability of the query result, here:

if plan.join_type.as_deref() == Some("Full") || plan.parent_relation.as_deref() == Some("Inner") {

has a false positives on a certain case.

considering that we have two tables: CarBrand and Car. And with the following query:

SELECT c.*, cb.name FROM "Car" c LEFT JOIN "CarBrand" cb ON c.carBrandId = cb.id

will intuitively resulted in all columns from Car table returned without null (for non-null columns) and a cb.name that might be a null, or nullable in other words.

However, what I've found after pnpm prisma generate --sql (yes, I use typedSql) is that:

import * as $runtime from "@prisma/client/runtime/client"
/* ... */
export type Result = {
    /* Assuming that "Car" table has two columns: `id` and `licensePlate` */
    id: string | null
    licensePlate: string | null
    name: string /* came from "CarBrand" table */
  }

all "Car" columns are inferred as nullable, while "name", a column from "CarBrand", is not nullable (but it is supposed to be nullable).

After all-night dig down 🦉 🦉 and have fun with rust code / query planner / and some pg internal tables knowledge recall ☕ , I found that inferring nullability by checking Join Type and Parent Relationship can be false-positive if the query planner decided to join with ...

Hash Right Join

Image

I'm not good with reading query plan either, but I think it would be better if the condition I mentioned first also aware of hash join direction.

Also, I saw a tribute credit here, not sure if this issue should be raised at https://github.com/launchbadge/sqlx as well.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions