Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ExpressionBuilder allows invalid fields in update/set, causing runtime failures #1299

Closed
johnpyp opened this issue Dec 23, 2024 · 3 comments
Closed
Labels
bug Something isn't working duplicate This issue or pull request already exists typescript Related to Typescript

Comments

@johnpyp
Copy link

johnpyp commented Dec 23, 2024

Most set/update update styles in Kysely work as expected, disallowing extraneous properties not present in the schema, as otherwise the queries will fail when executed.

Unfortunately, there's one case that is quite natural to use, which dangerously doesn't check for this: using an ExpressionBuilder function which returns an object -

await db
  .insertInto("person")
  .values({ 
    first_name: "first name", 
    last_name: "last name",
    gender: "male"
  })
  .onConflict((oc) =>
    oc.column("first_name").doUpdateSet((eb) => ({
      first_name: "asdf",
      asdf: "asdf"
    }))
  )
  .execute();

Full example: https://kyse.link/klyC9

In the above example in the onConflict clause, the first_name property is required, and as expected, if no valid fields are present, then the query fails.

However, once at least one other valid property is in the object, you can add any other extraneous properties and they won't fail typechecking. This is incorrect and will lead to runtime query failures (my team uses this pattern extensively, and were just bitten by it 😢 ).

The example above is the exact case that my team ran into, but it looks like it also applies to cases like set((eb) => ({ ... }).


To mitigate this issue, you can pass the object directly to doUpdateSet instead, but you'll have to use a (eb) => closure in every field instead, which makes it the much less appealing option (for example when doing mass eb.ref("excluded.field").

@igalklebanov
Copy link
Member

igalklebanov commented Dec 23, 2024

Hey 👋

This is part of a long standing TypeScript issue.

edit: it is actually microsoft/TypeScript#241.

@johnpyp
Copy link
Author

johnpyp commented Dec 23, 2024

Ah interesting. So to generalize, this specific issue is because it's not possible to make return type checking as strict as you currently can in parameter checking?

Would it be possible to have some kind of simple "forwarding" function that converts this from effectively a return-type check to a parameter check? I'm not familiar enough with the type wizardry to know if this could actually work like I'm imagining.

Like change:

 qry.onConflict((oc) =>
    oc.column("first_name").doUpdateSet((eb) => ({
      first_name: "asdf",
      asdf: "asdf"
    }))
  )

to:

import { strictParams as sp } from 'kysely'
// ...
qry.onConflict((oc) =>
    oc.column("first_name").doUpdateSet((eb) => sp({
      first_name: "asdf",
      asdf: "asdf"
    }))
  )

@igalklebanov igalklebanov added bug Something isn't working duplicate This issue or pull request already exists typescript Related to Typescript labels Dec 23, 2024
@igalklebanov
Copy link
Member

I'm afraid this won't work. sp is not "bound", it doesn't know what it should expect.

There are probably some hacks out there, but they would hurt compile-time performance for everyone, they don't cover all cases probably, and they're not future-proof and could break with any new TypeScript version.

There is hope in catching these with typescript-eslint if this proposal gets through.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working duplicate This issue or pull request already exists typescript Related to Typescript
Projects
None yet
Development

No branches or pull requests

3 participants