ts-safeql / safeql

Validate and auto-generate TypeScript types from raw SQL queries in PostgreSQL.
https://safeql.dev
MIT License
1.35k stars 22 forks source link

SafeQL Reports Error for Nullable Fields on boolean #242

Closed Eprince-hub closed 2 months ago

Eprince-hub commented 4 months ago

Describe the bug SafeQL throws an Incorrect type annotation error when a boolean field is also allowed to be null. Querying the is_admin field of the database table below with the type isAdmin: boolean | null will throw the error

CREATE TABLE animals (
  is_admin boolean
)

export type Animal = {
  isAdmin: boolean | null;
};

// showing incorrect type annotation error
export async function getAnimal() {
  return await sql<Animal[]>`
    SELECT
      animals.is_admin
    FROM
      animals
  `;
}

To Reproduce Steps to reproduce the behavior:

  1. Clone https://github.com/Eprince-hub/safeql-simple-repro
  2. Replace the public env variables with your database credentials
  3. Check the code in the database.ts file

Expected behavior There may be some cases where the union type of boolean | null is needed, and SafeQL should support that. So there shouldn't be an error when I have a field like this is_admin boolean and a type like this isAdmin: boolean | null

Screenshots

Screenshot 2024-07-05 at 14 23 08

Desktop (please complete the following information):

Additional context Add any other context about the problem here.

karlhorky commented 2 months ago

@Newbie012 what do you think about this one?

I can confirm that this can be reproduced with a nullable boolean field (without NOT NULL).

It appears that SafeQL is reading the TS type boolean | null to be null | false | true, which fails equality test with null | boolean from PostgreSQL inference.

Is it a small change in SafeQL to infer null | false | true instead of boolean | null from the TS type?

karlhorky commented 2 months ago

Workaround

Use Omit<> and a TypeScript intersection:

 export type Animal = {
   isAdmin: boolean | null;
 };

 export async function getAnimal() {
-  return await sql<Animal[]>`
+  return await sql<(Omit<Animal, 'isAdmin'> & { isAdmin: boolean | null })[]>`
     SELECT
       animals.is_admin
     FROM
       animals
   `;
 }
karlhorky commented 2 months ago

@Newbie012 thanks for the PR #264 and the release!

I can confirm that @ts-safeql/eslint-plugin@3.4.3 is working with nullable boolean fields 🎉