K
Kysely15mo ago
Robin

New type error in 0.26.3

I recently upgraded from 0.26.1 to 0.26.3 and I'm getting a type error trying to call a reusable expression builder that used to work.
export function isOwner() {
return (eb: ExpressionBuilder<DB & Record<"r", MzCatalogMzRoles>, "r">) =>
eb
.or([
eb.fn("mz_is_superuser", []),
eb.fn("has_role", [sql.ref("current_user"), `r.oid`, sql.lit("USAGE")]),
])
.$castTo<boolean>()
.as("isOwner");
}
export function isOwner() {
return (eb: ExpressionBuilder<DB & Record<"r", MzCatalogMzRoles>, "r">) =>
eb
.or([
eb.fn("mz_is_superuser", []),
eb.fn("has_role", [sql.ref("current_user"), `r.oid`, sql.lit("USAGE")]),
])
.$castTo<boolean>()
.as("isOwner");
}
They query I'm calling it from has a bunch of other tables joined, but it have MzCatalogMzRoles as r so I would expect this to work The type error is pretty unfortunate, I can't make much sense of it. I've attached it to the post in case it is useful. Any suggestions appreciated!
21 Replies
Robin
RobinOP15mo ago
In case it wasn't clear, the function is called like this:
.select((eb) => [
"c.id",
"c.name",
"sc.name as schemaName",
"d.name as databaseName",
"c.type",
eb.fn.count<bigint>("so.id").as("numSources"),
eb.fn.count<bigint>("si.id").as("numSinks"),
isOwner(),
])
.select((eb) => [
"c.id",
"c.name",
"sc.name as schemaName",
"d.name as databaseName",
"c.type",
eb.fn.count<bigint>("so.id").as("numSources"),
eb.fn.count<bigint>("si.id").as("numSinks"),
isOwner(),
])
I dug into this further, and it seems like the presence of a left join somehow triggers this, though it's not clear why. This seems to be the relevant bit of the typescript error:
Types of parameters 'eb' and 'eb' are incompatible.
Type 'ExpressionBuilder<DB & { c: MzCatalogMzConnections; } & { sc: MzCatalogMzSchemas; } & { d: MzCatalogMzDatabases; } & { r: MzCatalogMzRoles; } & { so: Nullable<MzCatalogMzSources>; } & { si: Nullable<MzCatalogMzSinks>; }, "d" | "r" | "sc" | "c" | "so" | "si">' is not assignable to type 'ExpressionBuilder<DB & Record<"r", { oid: number; }>, "r">'.
The types of 'fn.agg' are incompatible between these types.
Type '<O>(name: string, args?: readonly ReferenceExpression<DB & { c: MzCatalogMzConnections; } & { sc: MzCatalogMzSchemas; } & { d: MzCatalogMzDatabases; } & { r: MzCatalogMzRoles; } & { so: Nullable<MzCatalogMzSources>; } & { si: Nullable<MzCatalogMzSinks>; }, "d" | "r" | "sc" | "c" | "so" | "si">[] | undefined) => AggregateFunctionBuilder<DB & { c: MzCatalogMzConnections; } & { sc: MzCatalogMzSchemas; } & { d: MzCatalogMzDatabases; } & { r: MzCatalogMzRoles; } & { so: Nullable<MzCatalogMzSources>; } & { si: Nullable<MzCatalogMzSinks>; }, "d" | "r" | "sc" | "c" | "so" | "si", O>' is not assignable to type '<O>(name: string, args?: readonly ReferenceExpression<DB & Record<"r", { oid: number; }>, "r">[] | undefined) => AggregateFunctionBuilder<DB & Record<"r", { oid: number; }>, "r", O>'.
Types of parameters 'args' and 'args' are incompatible.
Type 'readonly ReferenceExpression<DB & Record<"r", { oid: number; }>, "r">[] | undefined' is not assignable to type 'readonly ReferenceExpression<DB & { c: MzCatalogMzConnections; } & { sc: MzCatalogMzSchemas; } & { d: MzCatalogMzDatabases; } & { r: MzCatalogMzRoles; } & { so: Nullable<MzCatalogMzSources>; } & { si: Nullable<MzCatalogMzSinks>; }, "d" | "r" | "sc" | "c" | "so" | "si">[] | undefined'.
Types of parameters 'eb' and 'eb' are incompatible.
Type 'ExpressionBuilder<DB & { c: MzCatalogMzConnections; } & { sc: MzCatalogMzSchemas; } & { d: MzCatalogMzDatabases; } & { r: MzCatalogMzRoles; } & { so: Nullable<MzCatalogMzSources>; } & { si: Nullable<MzCatalogMzSinks>; }, "d" | "r" | "sc" | "c" | "so" | "si">' is not assignable to type 'ExpressionBuilder<DB & Record<"r", { oid: number; }>, "r">'.
The types of 'fn.agg' are incompatible between these types.
Type '<O>(name: string, args?: readonly ReferenceExpression<DB & { c: MzCatalogMzConnections; } & { sc: MzCatalogMzSchemas; } & { d: MzCatalogMzDatabases; } & { r: MzCatalogMzRoles; } & { so: Nullable<MzCatalogMzSources>; } & { si: Nullable<MzCatalogMzSinks>; }, "d" | "r" | "sc" | "c" | "so" | "si">[] | undefined) => AggregateFunctionBuilder<DB & { c: MzCatalogMzConnections; } & { sc: MzCatalogMzSchemas; } & { d: MzCatalogMzDatabases; } & { r: MzCatalogMzRoles; } & { so: Nullable<MzCatalogMzSources>; } & { si: Nullable<MzCatalogMzSinks>; }, "d" | "r" | "sc" | "c" | "so" | "si", O>' is not assignable to type '<O>(name: string, args?: readonly ReferenceExpression<DB & Record<"r", { oid: number; }>, "r">[] | undefined) => AggregateFunctionBuilder<DB & Record<"r", { oid: number; }>, "r", O>'.
Types of parameters 'args' and 'args' are incompatible.
Type 'readonly ReferenceExpression<DB & Record<"r", { oid: number; }>, "r">[] | undefined' is not assignable to type 'readonly ReferenceExpression<DB & { c: MzCatalogMzConnections; } & { sc: MzCatalogMzSchemas; } & { d: MzCatalogMzDatabases; } & { r: MzCatalogMzRoles; } & { so: Nullable<MzCatalogMzSources>; } & { si: Nullable<MzCatalogMzSinks>; }, "d" | "r" | "sc" | "c" | "so" | "si">[] | undefined'.
koskimas
koskimas15mo ago
The old types were wrong. Left joined tables are nullable, so you need to use something like
Record<"r", Nullable<MzCatalogMzRoles>>
Record<"r", Nullable<MzCatalogMzRoles>>
Nullable being
type Nullable<O> = {
[K in keyof O]: O[K] | null
}
type Nullable<O> = {
[K in keyof O]: O[K] | null
}
Robin
RobinOP15mo ago
@koskimas thanks for the response, unfortunately that didn't seem to fix the problem, I still see the same type error
Igal
Igal15mo ago
Hey 👋🏻 Can you provide a kyse.link ?
Robin
RobinOP14mo ago
@Igal sorry I didn't see your message! Here is a link: https://kyse.link/?p=s&i=SOTpUvdbZzJKCQJdlX77 it doesn't seem to fail though, what version of typescript is the playground running? I'm on 5.1.3 I tried downgrading to 5.0.4, which appears to be the version in the playground, but I still get the error locally, I'm not really sure what's going on maybe there is something else in my project that's required to reproduce the error, I'm not sure
Robin
RobinOP14mo ago
I created a github repo that shows the issue: https://github.com/RobinClowers/kysely-type-error
GitHub
GitHub - RobinClowers/kysely-type-error
Contribute to RobinClowers/kysely-type-error development by creating an account on GitHub.
Robin
RobinOP14mo ago
just
git clone [email protected]:RobinClowers/kysely-type-error.git
cd kysely-type-error
npm i && npx ts-node index.ts
git clone [email protected]:RobinClowers/kysely-type-error.git
cd kysely-type-error
npm i && npx ts-node index.ts
Igal
Igal14mo ago
can you push your tsconfigs?
Robin
RobinOP14mo ago
Yes, I'll do that later tonight I didn't have a tsconfig in that example though, so it should repro with the defaults
Igal
Igal14mo ago
defaults on my machine might differ from yours changing to:
function isOwner() {
return (
eb: ExpressionBuilder<DB & { r: MzCatalogMzRoles }, 'r'>
) =>
eb
.or([
eb.fn('mz_is_superuser', []),
eb.fn('has_role', [sql.ref('current_user'), 'r.oid', sql.lit('USAGE')]),
])
.$castTo<boolean>()
.as('isOwner');
}
function isOwner() {
return (
eb: ExpressionBuilder<DB & { r: MzCatalogMzRoles }, 'r'>
) =>
eb
.or([
eb.fn('mz_is_superuser', []),
eb.fn('has_role', [sql.ref('current_user'), 'r.oid', sql.lit('USAGE')]),
])
.$castTo<boolean>()
.as('isOwner');
}
makes it work Nullable<MzCatalogMzRoles> causes an error, and feels wrong. because "mz_catalog.mz_roles as r" is MzCatalogMzRoles and not Nullable<MzCatalogMzRoles>
Robin
RobinOP14mo ago
@Igal thanks for getting back to me! I finally had a chance to try this out, and it doesn't work with left joins. I've pushed an update to that repo also including my tsconfig, but here is the new code:
interface DB {
"mz_catalog.mz_roles": MzCatalogMzRoles;
"mz_catalog.mz_sources": { id: Generated<string> };
}

interface MzCatalogMzRoles {
id: Generated<string>;
oid: Generated<number>;
name: Generated<string>;
inherit: Generated<boolean>;
}

const db = new Kysely<DB>({
dialect: {
createAdapter() {
return new PostgresAdapter();
},
createDriver() {
return new DummyDriver();
},
createIntrospector(datbase: Kysely<unknown>) {
return new PostgresIntrospector(datbase);
},
createQueryCompiler() {
return new PostgresQueryCompiler();
},
},
});

function isOwner() {
return (eb: ExpressionBuilder<DB & { r: MzCatalogMzRoles }, "r">) =>
eb
.or([
eb.fn("mz_is_superuser", []),
eb.fn("has_role", [sql.ref("current_user"), `r.oid`, sql.lit("USAGE")]),
])
.$castTo<boolean>()
.as("isOwner");
}

const query = db
.selectFrom("mz_catalog.mz_roles as r")
.leftJoin("mz_catalog.mz_sources as s", "s.id", "r.id")
.select(isOwner())
.compile();
interface DB {
"mz_catalog.mz_roles": MzCatalogMzRoles;
"mz_catalog.mz_sources": { id: Generated<string> };
}

interface MzCatalogMzRoles {
id: Generated<string>;
oid: Generated<number>;
name: Generated<string>;
inherit: Generated<boolean>;
}

const db = new Kysely<DB>({
dialect: {
createAdapter() {
return new PostgresAdapter();
},
createDriver() {
return new DummyDriver();
},
createIntrospector(datbase: Kysely<unknown>) {
return new PostgresIntrospector(datbase);
},
createQueryCompiler() {
return new PostgresQueryCompiler();
},
},
});

function isOwner() {
return (eb: ExpressionBuilder<DB & { r: MzCatalogMzRoles }, "r">) =>
eb
.or([
eb.fn("mz_is_superuser", []),
eb.fn("has_role", [sql.ref("current_user"), `r.oid`, sql.lit("USAGE")]),
])
.$castTo<boolean>()
.as("isOwner");
}

const query = db
.selectFrom("mz_catalog.mz_roles as r")
.leftJoin("mz_catalog.mz_sources as s", "s.id", "r.id")
.select(isOwner())
.compile();
@koskimas originally suggested the Nullable, since left joined relations are nullable, however, I think that wasn't quite right, since the mz_roles relation was an inner join... It seems like we need a way to express that there may be some nullable relations included, but I have no good idea what that would look like
Robin
RobinOP14mo ago
@Igal or @koskimas any chance you can take a look at my updated code? I posted it ^ but also in this repo: I created a github repo that shows the issue: https://github.com/RobinClowers/kysely-type-error It's blocking me from upgrading to typescript 5.2 😦
GitHub
GitHub - RobinClowers/kysely-type-error
Contribute to RobinClowers/kysely-type-error development by creating an account on GitHub.
Unknown User
Unknown User14mo ago
Message Not Public
Sign In & Join Server To View
Robin
RobinOP14mo ago
Thanks, I'll take a look Super weird, using the Nullable helper seems to work in the playground, but not in my project I even tried downgrading typescript to the same version used in the project
Unknown User
Unknown User14mo ago
Message Not Public
Sign In & Join Server To View
Robin
RobinOP14mo ago
I don't understand why the pick would be any different though? It still suffers from the same left join nullable issue oh interesting, that does fix it though @emiliewood thanks! I glossed right over the pick when I first read your response I wish I understood why that works though...
Unknown User
Unknown User14mo ago
Message Not Public
Sign In & Join Server To View
Robin
RobinOP14mo ago
oooooh:
When you leftJoin a table that has a column that some other table also has, that column becomes nullable too because it gets overridden by the similarly named column
that actually makes a ton of sense oh, but this still doesn't work if I left join a table that also includes on oid column 😢
Unknown User
Unknown User14mo ago
Message Not Public
Sign In & Join Server To View
Robin
RobinOP14mo ago
@emiliewood I figured out a way around this limitation that worked for my case, FYI https://github.com/kysely-org/kysely/issues/670#issuecomment-1783588105
GitHub
Question: Reusable select query helpers · Issue #670 · kysely-org/k...
Hi all, thanks for such a great library! I am currently using Kysely v0.26.1 with the Postgres Dialect. On this version I am able to create handy little reusable selections which I can pass into th...
Robin
RobinOP14mo ago
I used a subquery instead of an expression builder
Want results from more Discord servers?
Add your server