From d4e47afce47e987b929f877cee9b91ba842659a7 Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Thu, 6 Oct 2022 23:50:32 +0300 Subject: [PATCH 1/5] open up avg, max, min & sum to null values. --- src/query-builder/function-module.ts | 69 +++++++++++++++++----------- 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/src/query-builder/function-module.ts b/src/query-builder/function-module.ts index 3901ac5f6..80b3441c6 100644 --- a/src/query-builder/function-module.ts +++ b/src/query-builder/function-module.ts @@ -1,4 +1,3 @@ -import { DynamicReferenceBuilder } from '../dynamic/dynamic-reference-builder.js' import { AggregateFunctionNode } from '../operation-node/aggregate-function-node.js' import { ExtractTypeFromReferenceExpression, @@ -52,14 +51,15 @@ export class FunctionModule { * Calls the `avg` function for the column given as the argument. * * If this is used in a `select` statement the type of the selected expression - * will be `number | string` by default. This is because Kysely can't know the - * type the db driver outputs. Sometimes the output can be larger than the - * largest javascript number and a string is returned instead. Most drivers - * allow you to configure the output type of large numbers and Kysely can't - * know if you've done so. + * will be `number | string | null` by default. This is because Kysely can't know + * the type the db driver outputs. Sometimes the output can be larger than the + * largest javascript number and a string is returned instead. Most drivers allow + * you to configure the output type of large numbers and Kysely can't know if + * you've done so. Sometimes a null is returned, e.g. when row count is 0, and + * no `group by` was used. * - * You can specify the output type of the expression by providing - * the type as the first type argument: + * You can specify the output type of the expression by providing the type as + * the first type argument: * * ```ts * const { avg } = db.fn @@ -68,9 +68,12 @@ export class FunctionModule { * .select(avg('price').as('avg_price')) * .execute() * ``` + * + * It is highly recommended to include null in the output type union and handle + * null values in post-execute code, or wrap the function with a coalesce function. */ avg< - O extends number | string, + O extends number | string | null, C extends SimpleReferenceExpression = SimpleReferenceExpression< DB, TB @@ -88,11 +91,11 @@ export class FunctionModule { * Calls the `count` function for the column given as the argument. * * If this is used in a `select` statement the type of the selected expression - * will be `number | string | bigint` by default. This is because Kysely can't - * know the type the db driver outputs. Sometimes the output can be larger than - * the largest javascript number and a string is returned instead. Most drivers - * allow you to configure the output type of large numbers and Kysely can't - * know if you've done so. + * will be `number | string | bigint | null` by default. This is because Kysely + * can't know the type the db driver outputs. Sometimes the output can be larger + * than the largest javascript number and a string is returned instead. Most + * drivers allow you to configure the output type of large numbers and Kysely + * can't know if you've done so. * * You can specify the output type of the expression by providing * the type as the first type argument: @@ -142,14 +145,19 @@ export class FunctionModule { * ``` */ max< - O extends number | string | bigint, - C extends SimpleReferenceExpression = DynamicReferenceBuilder + O extends number | string | bigint | null, + C extends SimpleReferenceExpression = SimpleReferenceExpression< + DB, + TB + > >( column: C ): AggregateFunctionBuilder< DB, TB, - ExtractTypeFromReferenceExpression + ExtractTypeFromReferenceExpression | null extends O + ? null + : never > { return new AggregateFunctionBuilder({ aggregateFunctionNode: AggregateFunctionNode.create( @@ -174,14 +182,19 @@ export class FunctionModule { * */ min< - O extends number | string | bigint, - C extends SimpleReferenceExpression = DynamicReferenceBuilder + O extends number | string | bigint | null, + C extends SimpleReferenceExpression = SimpleReferenceExpression< + DB, + TB + > >( column: C ): AggregateFunctionBuilder< DB, TB, - ExtractTypeFromReferenceExpression + ExtractTypeFromReferenceExpression | null extends O + ? null + : never > { return new AggregateFunctionBuilder({ aggregateFunctionNode: AggregateFunctionNode.create( @@ -195,11 +208,12 @@ export class FunctionModule { * Calls the `sum` function for the column given as the argument. * * If this is used in a `select` statement the type of the selected expression - * will be `number | string | bigint` by default. This is because Kysely can't - * know the type the db driver outputs. Sometimes the output can be larger than - * the largest javascript number and a string is returned instead. Most drivers - * allow you to configure the output type of large numbers and Kysely can't - * know if you've done so. + * will be `number | string | bigint | null` by default. This is because Kysely + * can't know the type the db driver outputs. Sometimes the output can be larger + * than the largest javascript number and a string is returned instead. Most + * drivers allow you to configure the output type of large numbers and Kysely + * can't know if you've done so. Sometimes a null is returned, e.g. when row + * count is 0, and no `group by` was used. * * You can specify the output type of the expression by providing * the type as the first type argument: @@ -211,9 +225,12 @@ export class FunctionModule { * .select(sum('price').as('total_price')) * .execute() * ``` + * + * It is highly recommended to include null in the output type union and handle + * null values in post-execute code, or wrap the function with a coalesce function. */ sum< - O extends number | string | bigint, + O extends number | string | bigint | null, C extends SimpleReferenceExpression = SimpleReferenceExpression< DB, TB From 8ab55165a986f0b1394533b71e5a83ead0d96c7d Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Thu, 6 Oct 2022 23:58:50 +0300 Subject: [PATCH 2/5] nullable should be opt-in. --- src/query-builder/function-module.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/query-builder/function-module.ts b/src/query-builder/function-module.ts index 80b3441c6..7fe059e96 100644 --- a/src/query-builder/function-module.ts +++ b/src/query-builder/function-module.ts @@ -73,7 +73,7 @@ export class FunctionModule { * null values in post-execute code, or wrap the function with a coalesce function. */ avg< - O extends number | string | null, + O extends number | string | null = number | string, C extends SimpleReferenceExpression = SimpleReferenceExpression< DB, TB @@ -145,7 +145,7 @@ export class FunctionModule { * ``` */ max< - O extends number | string | bigint | null, + O extends number | string | bigint | null = number | string | bigint, C extends SimpleReferenceExpression = SimpleReferenceExpression< DB, TB @@ -182,7 +182,7 @@ export class FunctionModule { * */ min< - O extends number | string | bigint | null, + O extends number | string | bigint | null = number | string | bigint, C extends SimpleReferenceExpression = SimpleReferenceExpression< DB, TB @@ -230,7 +230,7 @@ export class FunctionModule { * null values in post-execute code, or wrap the function with a coalesce function. */ sum< - O extends number | string | bigint | null, + O extends number | string | bigint | null = number | string | bigint, C extends SimpleReferenceExpression = SimpleReferenceExpression< DB, TB From cbce3d3ba601b78a1ad72ca7a3f8fb32d9d7b34b Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Sun, 16 Oct 2022 23:58:28 +0300 Subject: [PATCH 3/5] fix nullable return types @ FunctionModule. --- src/query-builder/function-module.ts | 58 +++++++++---- test/typings/test-d/function-module.test-d.ts | 81 +++++++++++++++---- 2 files changed, 111 insertions(+), 28 deletions(-) diff --git a/src/query-builder/function-module.ts b/src/query-builder/function-module.ts index 9b77b7cc7..efe02f01f 100644 --- a/src/query-builder/function-module.ts +++ b/src/query-builder/function-module.ts @@ -1,3 +1,4 @@ +import { DynamicReferenceBuilder } from '../dynamic/dynamic-reference-builder.js' import { AggregateFunctionNode } from '../operation-node/aggregate-function-node.js' import { CoalesceReferenceExpressionList, @@ -79,6 +80,14 @@ export class FunctionModule { * * It is highly recommended to include null in the output type union and handle * null values in post-execute code, or wrap the function with a coalesce function. + * + * ```ts + * const { avg } = db.fn + * + * db.selectFrom('toy') + * .select(avg('price').as('avg_price')) + * .execute() + * ``` */ avg< O extends number | string | null = number | string, @@ -198,21 +207,28 @@ export class FunctionModule { * .select(max('price').as('max_price')) * .execute() * ``` + * + * It is highly recommended to include null in the output type union and handle + * null values in post-execute code, or wrap the function with a coalesce function. + * + * ```ts + * const { max } = db.fn + * + * db.selectFrom('toy') + * .select(max('price').as('max_price')) + * .execute() + * ``` */ max< O extends number | string | bigint | null = number | string | bigint, - C extends SimpleReferenceExpression = SimpleReferenceExpression< - DB, - TB - > + C extends SimpleReferenceExpression = DynamicReferenceBuilder >( column: C ): AggregateFunctionBuilder< DB, TB, - ExtractTypeFromReferenceExpression | null extends O - ? null - : never + | ExtractTypeFromReferenceExpression + | (null extends O ? null : never) > { return new AggregateFunctionBuilder({ aggregateFunctionNode: AggregateFunctionNode.create( @@ -235,21 +251,27 @@ export class FunctionModule { * .execute() * ``` * + * It is highly recommended to include null in the output type union and handle + * null values in post-execute code, or wrap the function with a coalesce function. + * + * ```ts + * const { min } = db.fn + * + * db.selectFrom('toy') + * .select(min('price').as('min_price')) + * .execute() + * ``` */ min< O extends number | string | bigint | null = number | string | bigint, - C extends SimpleReferenceExpression = SimpleReferenceExpression< - DB, - TB - > + C extends SimpleReferenceExpression = DynamicReferenceBuilder >( column: C ): AggregateFunctionBuilder< DB, TB, - ExtractTypeFromReferenceExpression | null extends O - ? null - : never + | ExtractTypeFromReferenceExpression + | (null extends O ? null : never) > { return new AggregateFunctionBuilder({ aggregateFunctionNode: AggregateFunctionNode.create( @@ -283,6 +305,14 @@ export class FunctionModule { * * It is highly recommended to include null in the output type union and handle * null values in post-execute code, or wrap the function with a coalesce function. + * + * ```ts + * const { sum } = db.fn + * + * db.selectFrom('toy') + * .select(sum('price').as('total_price')) + * .execute() + * ``` */ sum< O extends number | string | bigint | null = number | string | bigint, diff --git a/test/typings/test-d/function-module.test-d.ts b/test/typings/test-d/function-module.test-d.ts index c3395f198..6dc92f900 100644 --- a/test/typings/test-d/function-module.test-d.ts +++ b/test/typings/test-d/function-module.test-d.ts @@ -54,10 +54,15 @@ async function testSelectWithDefaultGenerics(db: Kysely) { .executeTakeFirstOrThrow() expectAssignable(result.avg_age) + expectNotAssignable(result.avg_age) expectAssignable(result.total_people) + expectNotAssignable(result.total_people) expectAssignable(result.max_age) + expectNotAssignable(result.max_age) expectAssignable(result.min_age) + expectNotAssignable(result.min_age) expectAssignable(result.total_age) + expectNotAssignable(result.total_age) } async function testSelectWithCustomGenerics(db: Kysely) { @@ -66,22 +71,54 @@ async function testSelectWithCustomGenerics(db: Kysely) { const result = await db .selectFrom('person') .select(avg('age').as('avg_age')) + .select(avg('age').as('nullable_avg_age')) .select(count('age').as('total_people')) - .select(max('age').as('max_age')) - .select(min('age').as('min_age')) + .select(max('age').as('nullable_max_age')) + .select(max('age').as('lying_max_age')) + .select(max('age').as('nullable_lying_max_age')) + .select(min('age').as('nullable_min_age')) + .select(min('age').as('lying_min_age')) + .select(min('age').as('nullable_lying_min_age')) .select(sum('age').as('total_age')) + .select(sum('age').as('nullable_total_age')) .executeTakeFirstOrThrow() expectAssignable(result.avg_age) - expectNotAssignable(result.avg_age) + expectNotAssignable(result.avg_age) + expectAssignable(result.nullable_avg_age) + expectNotAssignable(result.nullable_avg_age) expectAssignable(result.total_people) - expectNotAssignable(result.total_people) - expectAssignable(result.max_age) - expectNotAssignable(result.max_age) - expectAssignable(result.min_age) - expectNotAssignable(result.min_age) + expectNotAssignable(result.total_people) + expectAssignable(result.nullable_max_age) + expectNotAssignable(result.nullable_max_age) + expectAssignable(result.lying_max_age) + expectNotAssignable(result.lying_max_age) + expectAssignable(result.nullable_lying_max_age) + expectNotAssignable(result.nullable_lying_max_age) + expectAssignable(result.nullable_min_age) + expectNotAssignable(result.nullable_min_age) + expectAssignable(result.lying_min_age) + expectNotAssignable(result.lying_min_age) + expectAssignable(result.nullable_lying_min_age) + expectNotAssignable(result.nullable_lying_min_age) expectAssignable(result.total_age) - expectNotAssignable(result.total_age) + expectNotAssignable(result.total_age) + expectAssignable(result.nullable_total_age) + expectNotAssignable(result.nullable_total_age) + + expectError( + db + .selectFrom('person') + .select(max('age').as('max_no_reference_generic')) + .executeTakeFirstOrThrow() + ) + + expectError( + db + .selectFrom('person') + .select(min('age').as('min_no_reference_generic')) + .executeTakeFirstOrThrow() + ) } async function testSelectUnexpectedColumn(db: Kysely) { @@ -155,32 +192,48 @@ async function testSelectWithDynamicReference(db: Kysely) { .selectFrom('person') .select(avg(dynamicReference).as('avg')) .select(avg(dynamicReference).as('another_avg')) + .select(avg(dynamicReference).as('nullable_avg')) .select(count(dynamicReference).as('count')) .select(count(dynamicReference).as('another_count')) .select(max(dynamicReference).as('max')) .select(max(dynamicReference).as('another_max')) + .select(max(dynamicReference).as('nullable_max')) .select(min(dynamicReference).as('min')) .select(min(dynamicReference).as('another_min')) + .select(max(dynamicReference).as('nullable_min')) .select(sum(dynamicReference).as('sum')) .select(sum(dynamicReference).as('another_sum')) + .select(sum(dynamicReference).as('nullable_sum')) .executeTakeFirstOrThrow() expectAssignable(result.avg) - expectNotAssignable(result.avg) + expectNotAssignable(result.avg) expectAssignable(result.another_avg) - expectNotAssignable(result.another_avg) + expectNotAssignable(result.another_avg) + expectAssignable(result.nullable_avg) + expectNotAssignable(result.nullable_avg) expectAssignable(result.count) + expectNotAssignable(result.count) expectAssignable(result.another_count) - expectNotAssignable(result.another_count) + expectNotAssignable(result.another_count) expectAssignable(result.max) + expectNotAssignable(result.max) expectAssignable(result.another_max) expectNotAssignable(result.another_max) + expectAssignable(result.nullable_max) + expectNotAssignable(result.nullable_max) expectAssignable(result.min) + expectNotAssignable(result.min) expectAssignable(result.another_min) - expectNotAssignable(result.another_min) + expectNotAssignable(result.another_min) + expectAssignable(result.nullable_min) + expectNotAssignable(result.nullable_min) expectAssignable(result.sum) + expectNotAssignable(result.sum) expectAssignable(result.another_sum) - expectNotAssignable(result.another_sum) + expectNotAssignable(result.another_sum) + expectAssignable(result.nullable_sum) + expectNotAssignable(result.nullable_sum) } async function testSelectWithDistinct(db: Kysely) { From c7367a58bb4d72aacbd83d72b1ec50cde1bfa4ae Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Mon, 17 Oct 2022 00:17:04 +0300 Subject: [PATCH 4/5] add coalesce + nullable aggregate function typings test case. --- test/typings/test-d/coalesce.test-d.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/typings/test-d/coalesce.test-d.ts b/test/typings/test-d/coalesce.test-d.ts index 14b77833b..5fcc3f1ec 100644 --- a/test/typings/test-d/coalesce.test-d.ts +++ b/test/typings/test-d/coalesce.test-d.ts @@ -90,6 +90,18 @@ async function testCoalesceMultiple(db: Kysely) { .groupBy(['last_name', 'first_name', 'age']) .execute() expectType<{ field: string }>(r2) + + // string | null, string -> string + const [r3] = await db + .selectFrom('person') + .select( + coalesce( + db.fn.max('first_name'), + sql`${sql.literal('N/A')}` + ).as('max_first_name') + ) + .execute() + expectType<{ max_first_name: string }>(r3) } function value(value: V): RawBuilder { From 5e6b1e0c1515020fbc5b4d1127f89cf935918954 Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Mon, 17 Oct 2022 09:42:08 +0300 Subject: [PATCH 5/5] fix ts docs @ FunctionModule. --- src/query-builder/function-module.ts | 48 +++++++++++++++++----------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/src/query-builder/function-module.ts b/src/query-builder/function-module.ts index efe02f01f..ca0c944c9 100644 --- a/src/query-builder/function-module.ts +++ b/src/query-builder/function-module.ts @@ -60,12 +60,11 @@ export class FunctionModule { * Calls the `avg` function for the column given as the argument. * * If this is used in a `select` statement the type of the selected expression - * will be `number | string | null` by default. This is because Kysely can't know - * the type the db driver outputs. Sometimes the output can be larger than the - * largest javascript number and a string is returned instead. Most drivers allow - * you to configure the output type of large numbers and Kysely can't know if - * you've done so. Sometimes a null is returned, e.g. when row count is 0, and - * no `group by` was used. + * will be `number | string` by default. This is because Kysely can't know the + * type the db driver outputs. Sometimes the output can be larger than the largest + * javascript number and a string is returned instead. Most drivers allow you + * to configure the output type of large numbers and Kysely can't know if you've + * done so. * * You can specify the output type of the expression by providing the type as * the first type argument: @@ -78,8 +77,10 @@ export class FunctionModule { * .execute() * ``` * - * It is highly recommended to include null in the output type union and handle - * null values in post-execute code, or wrap the function with a coalesce function. + * Sometimes a null is returned, e.g. when row count is 0, and no `group by` + * was used. It is highly recommended to include null in the output type union + * and handle null values in post-execute code, or wrap the function with a `coalesce` + * function. * * ```ts * const { avg } = db.fn @@ -155,7 +156,7 @@ export class FunctionModule { * Calls the `count` function for the column given as the argument. * * If this is used in a `select` statement the type of the selected expression - * will be `number | string | bigint | null` by default. This is because Kysely + * will be `number | string | bigint` by default. This is because Kysely * can't know the type the db driver outputs. Sometimes the output can be larger * than the largest javascript number and a string is returned instead. Most * drivers allow you to configure the output type of large numbers and Kysely @@ -198,6 +199,9 @@ export class FunctionModule { /** * Calls the `max` function for the column given as the argument. * + * If this is used in a `select` statement the type of the selected expression + * will be the referenced column's type. + * * ### Examples * * ```ts @@ -208,8 +212,10 @@ export class FunctionModule { * .execute() * ``` * - * It is highly recommended to include null in the output type union and handle - * null values in post-execute code, or wrap the function with a coalesce function. + * Sometimes a null is returned, e.g. when row count is 0, and no `group by` + * was used. It is highly recommended to include null in the output type union + * and handle null values in post-execute code, or wrap the function with a `coalesce` + * function. * * ```ts * const { max } = db.fn @@ -241,6 +247,9 @@ export class FunctionModule { /** * Calls the `min` function for the column given as the argument. * + * If this is used in a `select` statement the type of the selected expression + * will be the referenced column's type. + * * ### Examples * * ```ts @@ -251,8 +260,10 @@ export class FunctionModule { * .execute() * ``` * - * It is highly recommended to include null in the output type union and handle - * null values in post-execute code, or wrap the function with a coalesce function. + * Sometimes a null is returned, e.g. when row count is 0, and no `group by` + * was used. It is highly recommended to include null in the output type union + * and handle null values in post-execute code, or wrap the function with a `coalesce` + * function. * * ```ts * const { min } = db.fn @@ -285,12 +296,11 @@ export class FunctionModule { * Calls the `sum` function for the column given as the argument. * * If this is used in a `select` statement the type of the selected expression - * will be `number | string | bigint | null` by default. This is because Kysely + * will be `number | string | bigint` by default. This is because Kysely * can't know the type the db driver outputs. Sometimes the output can be larger * than the largest javascript number and a string is returned instead. Most * drivers allow you to configure the output type of large numbers and Kysely - * can't know if you've done so. Sometimes a null is returned, e.g. when row - * count is 0, and no `group by` was used. + * can't know if you've done so. * * You can specify the output type of the expression by providing * the type as the first type argument: @@ -303,8 +313,10 @@ export class FunctionModule { * .execute() * ``` * - * It is highly recommended to include null in the output type union and handle - * null values in post-execute code, or wrap the function with a coalesce function. + * Sometimes a null is returned, e.g. when row count is 0, and no `group by` + * was used. It is highly recommended to include null in the output type union + * and handle null values in post-execute code, or wrap the function with a `coalesce` + * function. * * ```ts * const { sum } = db.fn