From 671699fe62c41dccd0c7c4bb08e476f7e3074949 Mon Sep 17 00:00:00 2001 From: Igal Klebanov Date: Sun, 24 Nov 2024 02:16:48 +0200 Subject: [PATCH] chore: fix jsdoc errors and add some examples, pt. 2. (#1277) --- deno.check.d.ts | 9 +- src/query-builder/function-module.ts | 123 +++----- src/query-builder/insert-query-builder.ts | 213 ++++++++++---- src/query-builder/insert-result.ts | 15 +- src/query-builder/merge-query-builder.ts | 20 +- src/query-builder/on-conflict-builder.ts | 57 ++-- src/query-builder/output-interface.ts | 53 +++- src/query-builder/returning-interface.ts | 10 +- src/query-builder/select-query-builder.ts | 330 ++++++++++++++++++---- src/query-builder/update-query-builder.ts | 72 +++-- src/query-builder/where-interface.ts | 6 +- src/query-creator.ts | 35 ++- src/raw-builder/raw-builder.ts | 13 +- src/raw-builder/sql.ts | 25 +- src/schema/alter-column-builder.ts | 20 +- 15 files changed, 726 insertions(+), 275 deletions(-) diff --git a/deno.check.d.ts b/deno.check.d.ts index ef9b573cd..48196b508 100644 --- a/deno.check.d.ts +++ b/deno.check.d.ts @@ -11,6 +11,7 @@ import type { interface Database { person: PersonTable pet: PetTable + toy: ToyTable wine: WineTable wine_stock_change: WineStockChangeTable } @@ -45,6 +46,12 @@ interface PetTable { species: Species } +interface ToyTable { + id: Generated + pet_id: number + price: number +} + interface WineTable { name: string stock: number @@ -61,7 +68,7 @@ export type PersonUpdate = Updateable export type Pet = Selectable export type NewPet = Insertable export type PetUpdate = Updateable -export type Species = 'dog' | 'cat' +export type Species = 'dog' | 'cat' | 'hamster' declare global { // @ts-ignore diff --git a/src/query-builder/function-module.ts b/src/query-builder/function-module.ts index bb8748dd6..cfb7cda3c 100644 --- a/src/query-builder/function-module.ts +++ b/src/query-builder/function-module.ts @@ -106,9 +106,10 @@ export interface FunctionModule { * ### Examples * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .selectAll('person') * .where(db.fn('upper', ['first_name']), '=', 'JENNIFER') + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -122,9 +123,12 @@ export interface FunctionModule { * If you prefer readability over type-safety, you can always use raw `sql`: * * ```ts - * db.selectFrom('person') + * import { sql } from 'kysely' + * + * await db.selectFrom('person') * .selectAll('person') * .where(sql`upper(first_name)`, '=', 'JENNIFER') + * .execute() * ``` */ = ReferenceExpression>( @@ -145,11 +149,12 @@ export interface FunctionModule { * ### Examples * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .select(({ fn }) => [ * fn.agg('rank').over().as('rank'), * fn.agg('group_concat', ['first_name']).distinct().as('first_names') * ]) + * .execute() * ``` * * The generated SQL (MySQL): @@ -177,7 +182,7 @@ export interface FunctionModule { * ### Examples * * ```ts - * db.selectFrom('toy') + * await db.selectFrom('toy') * .select((eb) => eb.fn.avg('price').as('avg_price')) * .execute() * ``` @@ -188,14 +193,6 @@ export interface FunctionModule { * select avg("price") as "avg_price" from "toy" * ``` * - * You can limit column range to only columns participating in current query: - * - * ```ts - * db.selectFrom('toy') - * .select((eb) => eb.fn.avg('price').as('avg_price')) - * .execute() - * ``` - * * If this function 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 @@ -207,7 +204,7 @@ export interface FunctionModule { * the first type argument: * * ```ts - * db.selectFrom('toy') + * await db.selectFrom('toy') * .select((eb) => eb.fn.avg('price').as('avg_price')) * .execute() * ``` @@ -218,7 +215,7 @@ export interface FunctionModule { * function. * * ```ts - * db.selectFrom('toy') + * await db.selectFrom('toy') * .select((eb) => eb.fn.avg('price').as('avg_price')) * .execute() * ``` @@ -251,35 +248,25 @@ export interface FunctionModule { * ### Examples * * ```ts - * db.selectFrom('participant') - * .select((eb) => eb.fn.coalesce('nickname', sql`''`).as('nickname')) - * .where('room_id', '=', roomId) + * import { sql } from 'kysely' + * + * await db.selectFrom('person') + * .select((eb) => eb.fn.coalesce('nullable_column', sql.lit('')).as('column')) + * .where('first_name', '=', 'Jessie') * .execute() * ``` * * The generated SQL (PostgreSQL): * * ```sql - * select coalesce("nickname", '') as "nickname" - * from "participant" where "room_id" = $1 - * ``` - * - * You can limit column range to only columns participating in current query: - * - * ```ts - * db.selectFrom('participant') - * .select((eb) => - * eb.fn.coalesce('nickname', sql`''`).as('nickname') - * ) - * .where('room_id', '=', roomId) - * .execute() + * select coalesce("nullable_column", '') as "column" from "person" where "first_name" = $1 * ``` * * You can combine this function with other helpers in this module: * * ```ts - * db.selectFrom('person') - * .select((eb) => eb.fn.coalesce(eb.fn.avg('age'), sql`0`).as('avg_age')) + * await db.selectFrom('person') + * .select((eb) => eb.fn.coalesce(eb.fn.avg('age'), eb.lit(0)).as('avg_age')) * .where('first_name', '=', 'Jennifer') * .execute() * ``` @@ -357,7 +344,7 @@ export interface FunctionModule { * ### Examples * * ```ts - * db.selectFrom('toy') + * await db.selectFrom('toy') * .select((eb) => eb.fn.count('id').as('num_toys')) * .execute() * ``` @@ -379,18 +366,10 @@ export interface FunctionModule { * the type as the first type argument: * * ```ts - * db.selectFrom('toy') + * await db.selectFrom('toy') * .select((eb) => eb.fn.count('id').as('num_toys')) * .execute() * ``` - * - * You can limit column range to only columns participating in current query: - * - * ```ts - * db.selectFrom('toy') - * .select((eb) => eb.fn.count('id').as('num_toys')) - * .execute() - * ``` */ count< O extends number | string | bigint, @@ -415,7 +394,7 @@ export interface FunctionModule { * ### Examples * * ```ts - * db.selectFrom('toy') + * await db.selectFrom('toy') * .select((eb) => eb.fn.countAll().as('num_toys')) * .execute() * ``` @@ -437,7 +416,7 @@ export interface FunctionModule { * the type as the first type argument: * * ```ts - * db.selectFrom('toy') + * await db.selectFrom('toy') * .select((eb) => eb.fn.countAll().as('num_toys')) * .execute() * ``` @@ -446,7 +425,7 @@ export interface FunctionModule { * table: * * ```ts - * db.selectFrom('toy') + * await db.selectFrom('toy') * .innerJoin('pet', 'pet.id', 'toy.pet_id') * .select((eb) => eb.fn.countAll('toy').as('num_toys')) * .execute() @@ -458,15 +437,6 @@ export interface FunctionModule { * select count("toy".*) as "num_toys" * from "toy" inner join "pet" on "pet"."id" = "toy"."pet_id" * ``` - * - * You can limit table range to only tables participating in current query: - * - * ```ts - * db.selectFrom('toy') - * .innerJoin('pet', 'pet.id', 'toy.pet_id') - * .select((eb) => eb.fn.countAll('toy').as('num_toys')) - * .execute() - * ``` */ countAll( table: T, @@ -494,7 +464,7 @@ export interface FunctionModule { * ### Examples * * ```ts - * db.selectFrom('toy') + * await db.selectFrom('toy') * .select((eb) => eb.fn.max('price').as('max_price')) * .execute() * ``` @@ -505,21 +475,13 @@ export interface FunctionModule { * select max("price") as "max_price" from "toy" * ``` * - * You can limit column range to only columns participating in current query: - * - * ```ts - * db.selectFrom('toy') - * .select((eb) => eb.fn.max('price').as('max_price')) - * .execute() - * ``` - * * 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 {@link coalesce} * function. * * ```ts - * db.selectFrom('toy') + * await db.selectFrom('toy') * .select((eb) => eb.fn.max('price').as('max_price')) * .execute() * ``` @@ -553,7 +515,7 @@ export interface FunctionModule { * ### Examples * * ```ts - * db.selectFrom('toy') + * await db.selectFrom('toy') * .select((eb) => eb.fn.min('price').as('min_price')) * .execute() * ``` @@ -564,21 +526,13 @@ export interface FunctionModule { * select min("price") as "min_price" from "toy" * ``` * - * You can limit column range to only columns participating in current query: - * - * ```ts - * db.selectFrom('toy') - * .select((eb) => eb.fn.min('price').as('min_price')) - * .execute() - * ``` - * * 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 {@link coalesce} * function. * * ```ts - * db.selectFrom('toy') + * await db.selectFrom('toy') * .select((eb) => eb.fn.min('price').as('min_price')) * .execute() * ``` @@ -608,7 +562,7 @@ export interface FunctionModule { * ### Examples * * ```ts - * db.selectFrom('toy') + * await db.selectFrom('toy') * .select((eb) => eb.fn.sum('price').as('total_price')) * .execute() * ``` @@ -619,14 +573,6 @@ export interface FunctionModule { * select sum("price") as "total_price" from "toy" * ``` * - * You can limit column range to only columns participating in current query: - * - * ```ts - * db.selectFrom('toy') - * .select((eb) => eb.fn.sum('price').as('total_price')) - * .execute() - * ``` - * * If this function 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 @@ -638,7 +584,7 @@ export interface FunctionModule { * the first type argument: * * ```ts - * db.selectFrom('toy') + * await db.selectFrom('toy') * .select((eb) => eb.fn.sum('price').as('total_price')) * .execute() * ``` @@ -649,7 +595,7 @@ export interface FunctionModule { * function. * * ```ts - * db.selectFrom('toy') + * await db.selectFrom('toy') * .select((eb) => eb.fn.sum('price').as('total_price')) * .execute() * ``` @@ -671,11 +617,12 @@ export interface FunctionModule { * In the following example, `nicknames` is assumed to be a column of type `string[]`: * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .selectAll('person') * .where((eb) => eb( * eb.val('Jen'), '=', eb.fn.any('person.nicknames') * )) + * .execute() * ``` * * @@ -711,7 +658,7 @@ export interface FunctionModule { * This function is only available on PostgreSQL. * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .innerJoin('pet', 'pet.owner_id', 'person.id') * .select((eb) => ['first_name', eb.fn.jsonAgg('pet').as('pets')]) * .groupBy('person.first_name') @@ -745,7 +692,7 @@ export interface FunctionModule { * This function is only available on PostgreSQL. * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .innerJoin('pet', 'pet.owner_id', 'person.id') * .select((eb) => ['first_name', eb.fn.toJson('pet').as('pet')]) * .execute() diff --git a/src/query-builder/insert-query-builder.ts b/src/query-builder/insert-query-builder.ts index 6307a6a5b..2dc2b1483 100644 --- a/src/query-builder/insert-query-builder.ts +++ b/src/query-builder/insert-query-builder.ts @@ -230,7 +230,7 @@ export class InsertQueryBuilder * You can also use the callback version of subqueries or raw expressions: * * ```ts - * db.with('jennifer', (db) => db + * await db.with('jennifer', (db) => db * .selectFrom('person') * .where('first_name', '=', 'Jennifer') * .select(['id', 'first_name', 'gender']) @@ -240,6 +240,24 @@ export class InsertQueryBuilder * name: eb.selectFrom('jennifer').select('first_name'), * species: 'cat', * })) + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * with "jennifer" as ( + * select "id", "first_name", "gender" + * from "person" + * where "first_name" = $1 + * limit $2 + * ) + * insert into "pet" ("owner_id", "name", "species") + * values ( + * (select "id" from "jennifer"), + * (select "first_name" from "jennifer"), + * $3 + * ) * ``` */ values(insert: InsertExpression): InsertQueryBuilder { @@ -264,9 +282,10 @@ export class InsertQueryBuilder * ### Examples * * ```ts - * db.insertInto('person') + * await db.insertInto('person') * .columns(['first_name']) * .expression((eb) => eb.selectFrom('pet').select('pet.name')) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -340,6 +359,12 @@ export class InsertQueryBuilder * .defaultValues() * .execute() * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * insert into "person" default values + * ``` */ defaultValues(): InsertQueryBuilder { return new InsertQueryBuilder({ @@ -356,16 +381,22 @@ export class InsertQueryBuilder * ### Examples * * ```ts + * import { sql } from 'kysely' + * * await db.insertInto('person') - * .values(values) - * .modifyEnd(sql.raw('-- This is a comment')) + * .values({ + * first_name: 'John', + * last_name: 'Doe', + * gender: 'male', + * }) + * .modifyEnd(sql`-- This is a comment`) * .execute() * ``` * * The generated SQL (MySQL): * * ```sql - * insert into `person` + * insert into `person` ("first_name", "last_name", "gender") * values (?, ?, ?) -- This is a comment * ``` */ @@ -396,9 +427,19 @@ export class InsertQueryBuilder * ```ts * await db.insertInto('person') * .ignore() - * .values(values) + * .values({ + * first_name: 'John', + * last_name: 'Doe', + * gender: 'female', + * }) * .execute() * ``` + * + * The generated SQL (MySQL): + * + * ```sql + * insert ignore into `person` ("first_name", "last_name", "gender") values (?, ?, ?) + * ``` */ ignore(): InsertQueryBuilder { return new InsertQueryBuilder({ @@ -419,6 +460,8 @@ export class InsertQueryBuilder * Insert the first 5 rows: * * ```ts + * import { sql } from 'kysely' + * * await db.insertInto('person') * .top(5) * .columns(['first_name', 'gender']) @@ -437,6 +480,8 @@ export class InsertQueryBuilder * Insert the first 50 percent of rows: * * ```ts + * import { sql } from 'kysely' + * * await db.insertInto('person') * .top(50, 'percent') * .columns(['first_name', 'gender']) @@ -479,6 +524,7 @@ export class InsertQueryBuilder * .values({ * name: 'Catto', * species: 'cat', + * owner_id: 3, * }) * .onConflict((oc) => oc * .column('name') @@ -490,10 +536,10 @@ export class InsertQueryBuilder * The generated SQL (PostgreSQL): * * ```sql - * insert into "pet" ("name", "species") - * values ($1, $2) + * insert into "pet" ("name", "species", "owner_id") + * values ($1, $2, $3) * on conflict ("name") - * do update set "species" = $3 + * do update set "species" = $4 * ``` * * You can provide the name of the constraint instead of a column name: @@ -504,6 +550,7 @@ export class InsertQueryBuilder * .values({ * name: 'Catto', * species: 'cat', + * owner_id: 3, * }) * .onConflict((oc) => oc * .constraint('pet_name_key') @@ -515,10 +562,10 @@ export class InsertQueryBuilder * The generated SQL (PostgreSQL): * * ```sql - * insert into "pet" ("name", "species") - * values ($1, $2) + * insert into "pet" ("name", "species", "owner_id") + * values ($1, $2, $3) * on conflict on constraint "pet_name_key" - * do update set "species" = $3 + * do update set "species" = $4 * ``` * * You can also specify an expression as the conflict target in case @@ -532,6 +579,7 @@ export class InsertQueryBuilder * .values({ * name: 'Catto', * species: 'cat', + * owner_id: 3, * }) * .onConflict((oc) => oc * .expression(sql`lower(name)`) @@ -543,10 +591,10 @@ export class InsertQueryBuilder * The generated SQL (PostgreSQL): * * ```sql - * insert into "pet" ("name", "species") - * values ($1, $2) + * insert into "pet" ("name", "species", "owner_id") + * values ($1, $2, $3) * on conflict (lower(name)) - * do update set "species" = $3 + * do update set "species" = $4 * ``` * * You can add a filter for the update statement like this: @@ -557,6 +605,7 @@ export class InsertQueryBuilder * .values({ * name: 'Catto', * species: 'cat', + * owner_id: 3, * }) * .onConflict((oc) => oc * .column('name') @@ -569,11 +618,11 @@ export class InsertQueryBuilder * The generated SQL (PostgreSQL): * * ```sql - * insert into "pet" ("name", "species") - * values ($1, $2) + * insert into "pet" ("name", "species", "owner_id") + * values ($1, $2, $3) * on conflict ("name") - * do update set "species" = $3 - * where "excluded"."name" != $4 + * do update set "species" = $4 + * where "excluded"."name" != $5 * ``` * * You can create an `on conflict do nothing` clauses like this: @@ -584,6 +633,7 @@ export class InsertQueryBuilder * .values({ * name: 'Catto', * species: 'cat', + * owner_id: 3, * }) * .onConflict((oc) => oc * .column('name') @@ -595,8 +645,8 @@ export class InsertQueryBuilder * The generated SQL (PostgreSQL): * * ```sql - * insert into "pet" ("name", "species") - * values ($1, $2) + * insert into "pet" ("name", "species", "owner_id") + * values ($1, $2, $3) * on conflict ("name") do nothing * ``` * @@ -605,8 +655,13 @@ export class InsertQueryBuilder * `ExpressionBuilder`: * * ```ts - * db.insertInto('person') - * .values(person) + * await db.insertInto('person') + * .values({ + * id: 1, + * first_name: 'John', + * last_name: 'Doe', + * gender: 'male', + * }) * .onConflict(oc => oc * .column('id') * .doUpdateSet({ @@ -614,6 +669,18 @@ export class InsertQueryBuilder * last_name: (eb) => eb.ref('excluded.last_name') * }) * ) + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * insert into "person" ("id", "first_name", "last_name", "gender") + * values ($1, $2, $3, $4) + * on conflict ("id") + * do update set + * "first_name" = "excluded"."first_name", + * "last_name" = "excluded"."last_name" * ``` */ onConflict( @@ -652,8 +719,22 @@ export class InsertQueryBuilder * ```ts * await db * .insertInto('person') - * .values(values) - * .onDuplicateKeyUpdate({ species: 'hamster' }) + * .values({ + * id: 1, + * first_name: 'John', + * last_name: 'Doe', + * gender: 'male', + * }) + * .onDuplicateKeyUpdate({ updated_at: new Date().toISOString() }) + * .execute() + * ``` + * + * The generated SQL (MySQL): + * + * ```sql + * insert into `person` (`id`, `first_name`, `last_name`, `gender`) + * values (?, ?, ?, ?) + * on duplicate key update `updated_at` = ? * ``` */ onDuplicateKeyUpdate( @@ -755,16 +836,17 @@ export class InsertQueryBuilder * ### Examples * * ```ts - * db.insertInto('person') - * .values({ first_name: 'James', last_name: 'Smith', age: 42 }) + * await db.insertInto('person') + * .values({ first_name: 'James', last_name: 'Smith', gender: 'male' }) * .returning(['first_name']) * .clearReturning() + * .execute() * ``` * * The generated SQL(PostgreSQL): * * ```sql - * insert into "person" ("James", "Smith", 42) + * insert into "person" ("first_name", "last_name", "gender") values ($1, $2, $3) * ``` */ clearReturning(): InsertQueryBuilder { @@ -786,13 +868,15 @@ export class InsertQueryBuilder * The next example uses a helper function `log` to log a query: * * ```ts + * import type { Compilable } from 'kysely' + * * function log(qb: T): T { * console.log(qb.compile()) * return qb * } * - * db.updateTable('person') - * .set(values) + * await db.insertInto('person') + * .values({ first_name: 'John', last_name: 'Doe', gender: 'male' }) * .$call(log) * .execute() * ``` @@ -814,7 +898,9 @@ export class InsertQueryBuilder * ### Examples * * ```ts - * async function insertPerson(values: InsertablePerson, returnLastName: boolean) { + * import type { NewPerson } from 'type-editor' // imaginary module + * + * async function insertPerson(values: NewPerson, returnLastName: boolean) { * return await db * .insertInto('person') * .values(values) @@ -881,23 +967,41 @@ export class InsertQueryBuilder * Turn this code: * * ```ts + * import type { Person } from 'type-editor' // imaginary module + * * const person = await db.insertInto('person') - * .values({ ...inputPerson, nullable_column: 'hell yeah!' }) + * .values({ + * first_name: 'John', + * last_name: 'Doe', + * gender: 'male', + * nullable_column: 'hell yeah!' + * }) * .returningAll() * .executeTakeFirstOrThrow() * - * if (nullable_column) { + * if (isWithNoNullValue(person)) { * functionThatExpectsPersonWithNonNullValue(person) * } + * + * function isWithNoNullValue(person: Person): person is Person & { nullable_column: string } { + * return person.nullable_column != null + * } * ``` * * Into this: * * ```ts + * import type { NotNull } from 'kysely' + * * const person = await db.insertInto('person') - * .values({ ...inputPerson, nullable_column: 'hell yeah!' }) + * .values({ + * first_name: 'John', + * last_name: 'Doe', + * gender: 'male', + * nullable_column: 'hell yeah!' + * }) * .returningAll() - * .$narrowType<{ nullable_column: string }>() + * .$narrowType<{ nullable_column: NotNull }>() * .executeTakeFirstOrThrow() * * functionThatExpectsPersonWithNonNullValue(person) @@ -930,22 +1034,29 @@ export class InsertQueryBuilder * ### Examples * * ```ts - * const result = await db - * .with('new_person', (qb) => qb - * .insertInto('person') - * .values(person) - * .returning('id') - * .$assertType<{ id: string }>() - * ) - * .with('new_pet', (qb) => qb - * .insertInto('pet') - * .values((eb) => ({ owner_id: eb.selectFrom('new_person').select('id'), ...pet })) - * .returning(['name as pet_name', 'species']) - * .$assertType<{ pet_name: string, species: Species }>() - * ) - * .selectFrom(['new_person', 'new_pet']) - * .selectAll() - * .executeTakeFirstOrThrow() + * import type { NewPerson, NewPet, Species } from 'type-editor' // imaginary module + * + * async function insertPersonAndPet(person: NewPerson, pet: Omit) { + * return await db + * .with('new_person', (qb) => qb + * .insertInto('person') + * .values(person) + * .returning('id') + * .$assertType<{ id: number }>() + * ) + * .with('new_pet', (qb) => qb + * .insertInto('pet') + * .values((eb) => ({ + * owner_id: eb.selectFrom('new_person').select('id'), + * ...pet + * })) + * .returning(['name as pet_name', 'species']) + * .$assertType<{ pet_name: string, species: Species }>() + * ) + * .selectFrom(['new_person', 'new_pet']) + * .selectAll() + * .executeTakeFirstOrThrow() + * } * ``` */ $assertType(): O extends T diff --git a/src/query-builder/insert-result.ts b/src/query-builder/insert-result.ts index e5e8a10e1..d06c37030 100644 --- a/src/query-builder/insert-result.ts +++ b/src/query-builder/insert-result.ts @@ -13,12 +13,17 @@ * ### Examples * * ```ts - * const result = await db - * .insertInto('person') - * .values(person) - * .executeTakeFirst() + * import type { NewPerson } from 'type-editor' // imaginary module * - * console.log(result.insertId) + * async function insertPerson(person: NewPerson) { + * const result = await db + * .insertInto('person') + * .values(person) + * .executeTakeFirstOrThrow() + * + * console.log(result.insertId) // relevant on MySQL + * console.log(result.numInsertedOrUpdatedRows) // always relevant + * } * ``` */ export class InsertResult { diff --git a/src/query-builder/merge-query-builder.ts b/src/query-builder/merge-query-builder.ts index c6de45ed6..d7c8fe20e 100644 --- a/src/query-builder/merge-query-builder.ts +++ b/src/query-builder/merge-query-builder.ts @@ -76,7 +76,9 @@ export class MergeQueryBuilder * ### Examples * * ```ts - * const result = await db + * import { sql } from 'kysely' + * + * await db * .mergeInto('person') * .using('pet', 'pet.owner_id', 'person.id') * .whenMatched() @@ -292,7 +294,9 @@ export class WheneableMergeQueryBuilder< * ### Examples * * ```ts - * const result = await db + * import { sql } from 'kysely' + * + * await db * .mergeInto('person') * .using('pet', 'pet.owner_id', 'person.id') * .whenMatched() @@ -691,13 +695,15 @@ export class WheneableMergeQueryBuilder< * The next example uses a helper function `log` to log a query: * * ```ts + * import type { Compilable } from 'kysely' + * * function log(qb: T): T { * console.log(qb.compile()) * return qb * } * - * db.updateTable('person') - * .set(values) + * await db.updateTable('person') + * .set({ first_name: 'John' }) * .$call(log) * .execute() * ``` @@ -719,7 +725,9 @@ export class WheneableMergeQueryBuilder< * ### Examples * * ```ts - * async function updatePerson(id: number, updates: UpdateablePerson, returnLastName: boolean) { + * import type { PersonUpdate } from 'type-editor' // imaginary module + * + * async function updatePerson(id: number, updates: PersonUpdate, returnLastName: boolean) { * return await db * .updateTable('person') * .set(updates) @@ -945,7 +953,7 @@ export class MatchedThenableMergeQueryBuilder< * .thenUpdate((ub) => ub * .set(sql`metadata['has_pets']`, 'Y') * .set({ - * updated_at: Date.now(), + * updated_at: new Date().toISOString(), * }) * ) * .execute() diff --git a/src/query-builder/on-conflict-builder.ts b/src/query-builder/on-conflict-builder.ts index cfdfaeda8..6fce43e1c 100644 --- a/src/query-builder/on-conflict-builder.ts +++ b/src/query-builder/on-conflict-builder.ts @@ -157,21 +157,25 @@ export class OnConflictBuilder * ### Examples * * ```ts + * const id = 1 + * const first_name = 'John' + * * await db * .insertInto('person') - * .values({ first_name, pic }) + * .values({ first_name, id }) * .onConflict((oc) => oc - * .column('pic') + * .column('id') * .doNothing() * ) + * .execute() * ``` * * The generated SQL (PostgreSQL): * * ```sql - * insert into "person" ("first_name", "pic") + * insert into "person" ("first_name", "id") * values ($1, $2) - * on conflict ("pic") do nothing + * on conflict ("id") do nothing * ``` */ doNothing(): OnConflictDoNothingBuilder { @@ -189,21 +193,25 @@ export class OnConflictBuilder * ### Examples * * ```ts + * const id = 1 + * const first_name = 'John' + * * await db * .insertInto('person') - * .values({ first_name, pic }) + * .values({ first_name, id }) * .onConflict((oc) => oc - * .column('pic') + * .column('id') * .doUpdateSet({ first_name }) * ) + * .execute() * ``` * * The generated SQL (PostgreSQL): * * ```sql - * insert into "person" ("first_name", "pic") + * insert into "person" ("first_name", "id") * values ($1, $2) - * on conflict ("pic") + * on conflict ("id") * do update set "first_name" = $3 * ``` * @@ -212,15 +220,32 @@ export class OnConflictBuilder * to create an upsert operation: * * ```ts - * db.insertInto('person') - * .values(person) - * .onConflict((oc) => oc - * .column('id') - * .doUpdateSet((eb) => ({ - * first_name: eb.ref('excluded.first_name'), - * last_name: eb.ref('excluded.last_name') - * })) + * import type { NewPerson } from 'type-editor' // imaginary module + * + * async function upsertPerson(person: NewPerson): Promise { + * await db.insertInto('person') + * .values(person) + * .onConflict((oc) => oc + * .column('id') + * .doUpdateSet((eb) => ({ + * first_name: eb.ref('excluded.first_name'), + * last_name: eb.ref('excluded.last_name') + * }) + * ) * ) + * .execute() + * } + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * insert into "person" ("first_name", "last_name") + * values ($1, $2) + * on conflict ("id") + * do update set + * "first_name" = excluded."first_name", + * "last_name" = excluded."last_name" * ``` */ doUpdateSet( diff --git a/src/query-builder/output-interface.ts b/src/query-builder/output-interface.ts index 75743a59e..7f9a927ba 100644 --- a/src/query-builder/output-interface.ts +++ b/src/query-builder/output-interface.ts @@ -30,15 +30,24 @@ export interface OutputInterface< * .output('inserted.id') * .values({ * first_name: 'Jennifer', - * last_name: 'Aniston' + * last_name: 'Aniston', + * gender: 'female', * }) - * .executeTakeFirst() + * .executeTakeFirstOrThrow() + * ``` + * + * The generated SQL (MSSQL): + * + * ```sql + * insert into "person" ("first_name", "last_name", "gender") + * output "inserted"."id" + * values (@1, @2, @3) * ``` * * Return multiple columns: * * ```ts - * const { id, first_name } = await db + * const { old_first_name, old_last_name, new_first_name, new_last_name } = await db * .updateTable('person') * .set({ first_name: 'John', last_name: 'Doe' }) * .output([ @@ -48,7 +57,19 @@ export interface OutputInterface< * 'inserted.last_name as new_last_name', * ]) * .where('created_at', '<', new Date()) - * .executeTakeFirst() + * .executeTakeFirstOrThrow() + * ``` + * + * The generated SQL (MSSQL): + * + * ```sql + * update "person" + * set "first_name" = @1, "last_name" = @2 + * output "deleted"."first_name" as "old_first_name", + * "deleted"."last_name" as "old_last_name", + * "inserted"."first_name" as "new_first_name", + * "inserted"."last_name" as "new_last_name" + * where "created_at" < @3 * ``` * * Return arbitrary expressions: @@ -56,13 +77,21 @@ export interface OutputInterface< * ```ts * import { sql } from 'kysely' * - * const { id, full_name } = await db + * const { full_name } = await db * .deleteFrom('person') * .output((eb) => sql`concat(${eb.ref('deleted.first_name')}, ' ', ${eb.ref('deleted.last_name')})`.as('full_name')) * .where('created_at', '<', new Date()) * .executeTakeFirstOrThrow() * ``` * + * The generated SQL (MSSQL): + * + * ```sql + * delete from "person" + * output concat("deleted"."first_name", ' ', "deleted"."last_name") as "full_name" + * where "created_at" < @1 + * ``` + * * Return the action performed on the row: * * ```ts @@ -81,7 +110,21 @@ export interface OutputInterface< * 'inserted.id as inserted_id', * 'deleted.id as deleted_id', * ]) + * .execute() * ``` + * + * The generated SQL (MSSQL): + * + * ```sql + * merge into "person" + * using "pet" on "pet"."owner_id" = "person"."id" + * when matched then delete + * when not matched then + * insert ("first_name", "last_name", "gender") + * values (@1, @2, @3) + * output "inserted"."id" as "inserted_id", "deleted"."id" as "deleted_id" + * ``` + * */ output>( selections: ReadonlyArray, diff --git a/src/query-builder/returning-interface.ts b/src/query-builder/returning-interface.ts index b89cc6bbe..8803677f7 100644 --- a/src/query-builder/returning-interface.ts +++ b/src/query-builder/returning-interface.ts @@ -30,20 +30,20 @@ export interface ReturningInterface { * last_name: 'Aniston' * }) * .returning('id') - * .executeTakeFirst() + * .executeTakeFirstOrThrow() * ``` * * Return multiple columns: * * ```ts - * const { id, first_name } = await db + * const { id, last_name } = await db * .insertInto('person') * .values({ * first_name: 'Jennifer', * last_name: 'Aniston' * }) * .returning(['id', 'last_name']) - * .executeTakeFirst() + * .executeTakeFirstOrThrow() * ``` * * Return arbitrary expressions: @@ -60,9 +60,9 @@ export interface ReturningInterface { * .returning((eb) => [ * 'id as id', * sql`concat(first_name, ' ', last_name)`.as('full_name'), - * eb.selectFrom('pets').select('pet.id').limit(1).as('first_pet_id') + * eb.selectFrom('pet').select('pet.id').limit(1).as('first_pet_id') * ]) - * .executeTakeFirst() + * .executeTakeFirstOrThrow() * ``` */ returning>( diff --git a/src/query-builder/select-query-builder.ts b/src/query-builder/select-query-builder.ts index 588214904..da098a5ad 100644 --- a/src/query-builder/select-query-builder.ts +++ b/src/query-builder/select-query-builder.ts @@ -335,13 +335,13 @@ export interface SelectQueryBuilder * const { ref } = db.dynamic * * // Some column name provided by the user. Value not known at compile time. - * const columnFromUserInput = req.query.select; + * const columnFromUserInput: string = 'first_name'; * * // A type that lists all possible values `columnFromUserInput` can have. * // You can use `keyof Person` if any column of an interface is allowed. - * type PossibleColumns = 'last_name' | 'first_name' | 'birth_date' + * type PossibleColumns = 'last_name' | 'first_name' | 'birthdate' * - * const persons = await db + * const people = await db * .selectFrom('person') * .select([ * ref(columnFromUserInput), @@ -352,12 +352,12 @@ export interface SelectQueryBuilder * // The resulting type contains all `PossibleColumns` as optional fields * // because we cannot know which field was actually selected before * // running the code. - * const lastName: string | undefined = persons[0].last_name - * const firstName: string | undefined = persons[0].first_name - * const birthDate: string | undefined = persons[0].birth_date + * const lastName: string | null | undefined = people[0].last_name + * const firstName: string | undefined = people[0].first_name + * const birthDate: Date | null | undefined = people[0].birthdate * * // The result type also contains the compile time selection `id`. - * persons[0].id + * people[0].id * ``` */ select>( @@ -411,7 +411,9 @@ export interface SelectQueryBuilder * ### Examples * * ```ts - * db.selectFrom('person') + * import { sql } from 'kysely' + * + * await db.selectFrom('person') * .modifyFront(sql`sql_no_cache`) * .select('first_name') * .execute() @@ -435,7 +437,9 @@ export interface SelectQueryBuilder * ### Examples * * ```ts - * db.selectFrom('person') + * import { sql } from 'kysely' + * + * await db.selectFrom('person') * .select('first_name') * .modifyEnd(sql`for update`) * .execute() @@ -778,7 +782,7 @@ export interface SelectQueryBuilder * ### Examples * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .innerJoinLateral( * (eb) => * eb.selectFrom('pet') @@ -789,6 +793,20 @@ export interface SelectQueryBuilder * ) * .select(['first_name', 'p.name']) * .orderBy('first_name') + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * select "person"."first_name", "p"."name" + * from "person" + * inner join lateral ( + * select "name" + * from "pet" + * where "pet"."owner_id" = "person"."id" + * ) as "p" on true + * order by "first_name" * ``` */ innerJoinLateral< @@ -814,7 +832,7 @@ export interface SelectQueryBuilder * ### Examples * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .leftJoinLateral( * (eb) => * eb.selectFrom('pet') @@ -825,6 +843,20 @@ export interface SelectQueryBuilder * ) * .select(['first_name', 'p.name']) * .orderBy('first_name') + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * select "person"."first_name", "p"."name" + * from "person" + * left join lateral ( + * select "name" + * from "pet" + * where "pet"."owner_id" = "person"."id" + * ) as "p" on true + * order by "first_name" * ``` */ leftJoinLateral< @@ -1079,7 +1111,7 @@ export interface SelectQueryBuilder * Select the first 10 rows of the result: * * ```ts - * return await db + * await db * .selectFrom('person') * .select('first_name') * .limit(10) @@ -1095,7 +1127,7 @@ export interface SelectQueryBuilder * Select rows from index 10 to index 19 of the result: * * ```ts - * return await db + * await db * .selectFrom('person') * .select('first_name') * .limit(10) @@ -1121,7 +1153,7 @@ export interface SelectQueryBuilder * Select rows from index 10 to index 19 of the result: * * ```ts - * return await db + * await db * .selectFrom('person') * .select('first_name') * .limit(10) @@ -1147,7 +1179,7 @@ export interface SelectQueryBuilder * ### Examples * * ```ts - * return await db + * await db * .selectFrom('person') * .select('first_name') * .orderBy('first_name') @@ -1181,7 +1213,7 @@ export interface SelectQueryBuilder * Select 10 biggest ages: * * ```ts - * return await db + * await db * .selectFrom('person') * .select('age') * .top(10) @@ -1198,7 +1230,7 @@ export interface SelectQueryBuilder * Select 10% first rows: * * ```ts - * return await db + * await db * .selectFrom('person') * .selectAll() * .top(10, 'percent') @@ -1224,22 +1256,48 @@ export interface SelectQueryBuilder * ### Examples * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .select(['id', 'first_name as name']) * .union(db.selectFrom('pet').select(['id', 'name'])) * .orderBy('name') + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * select "id", "first_name" as "name" + * from "person" + * union + * select "id", "name" + * from "pet" + * order by "name" * ``` * * You can provide a callback to get an expression builder. * In the following example, this allows us to wrap the query in parentheses: * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .select(['id', 'first_name as name']) * .union((eb) => eb.parens( * eb.selectFrom('pet').select(['id', 'name']) * )) * .orderBy('name') + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * select "id", "first_name" as "name" + * from "person" + * union + * ( + * select "id", "name" + * from "pet" + * ) + * order by "name" * ``` */ union>( @@ -1254,22 +1312,48 @@ export interface SelectQueryBuilder * ### Examples * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .select(['id', 'first_name as name']) * .unionAll(db.selectFrom('pet').select(['id', 'name'])) * .orderBy('name') + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * select "id", "first_name" as "name" + * from "person" + * union all + * select "id", "name" + * from "pet" + * order by "name" * ``` * * You can provide a callback to get an expression builder. * In the following example, this allows us to wrap the query in parentheses: * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .select(['id', 'first_name as name']) * .unionAll((eb) => eb.parens( * eb.selectFrom('pet').select(['id', 'name']) * )) * .orderBy('name') + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * select "id", "first_name" as "name" + * from "person" + * union all + * ( + * select "id", "name" + * from "pet" + * ) + * order by "name" * ``` */ unionAll>( @@ -1284,22 +1368,48 @@ export interface SelectQueryBuilder * ### Examples * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .select(['id', 'first_name as name']) * .intersect(db.selectFrom('pet').select(['id', 'name'])) * .orderBy('name') + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * select "id", "first_name" as "name" + * from "person" + * intersect + * select "id", "name" + * from "pet" + * order by "name" * ``` * * You can provide a callback to get an expression builder. * In the following example, this allows us to wrap the query in parentheses: * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .select(['id', 'first_name as name']) * .intersect((eb) => eb.parens( * eb.selectFrom('pet').select(['id', 'name']) * )) * .orderBy('name') + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * select "id", "first_name" as "name" + * from "person" + * intersect + * ( + * select "id", "name" + * from "pet" + * ) + * order by "name" * ``` */ intersect>( @@ -1314,22 +1424,48 @@ export interface SelectQueryBuilder * ### Examples * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .select(['id', 'first_name as name']) * .intersectAll(db.selectFrom('pet').select(['id', 'name'])) * .orderBy('name') + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * select "id", "first_name" as "name" + * from "person" + * intersect all + * select "id", "name" + * from "pet" + * order by "name" * ``` * * You can provide a callback to get an expression builder. * In the following example, this allows us to wrap the query in parentheses: * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .select(['id', 'first_name as name']) * .intersectAll((eb) => eb.parens( * eb.selectFrom('pet').select(['id', 'name']) * )) * .orderBy('name') + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * select "id", "first_name" as "name" + * from "person" + * intersect all + * ( + * select "id", "name" + * from "pet" + * ) + * order by "name" * ``` */ intersectAll>( @@ -1344,22 +1480,48 @@ export interface SelectQueryBuilder * ### Examples * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .select(['id', 'first_name as name']) * .except(db.selectFrom('pet').select(['id', 'name'])) * .orderBy('name') + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * select "id", "first_name" as "name" + * from "person" + * except + * select "id", "name" + * from "pet" + * order by "name" * ``` * * You can provide a callback to get an expression builder. * In the following example, this allows us to wrap the query in parentheses: * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .select(['id', 'first_name as name']) * .except((eb) => eb.parens( * eb.selectFrom('pet').select(['id', 'name']) * )) * .orderBy('name') + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * select "id", "first_name" as "name" + * from "person" + * except + * ( + * select "id", "name" + * from "pet" + * ) + * order by "name" * ``` */ except>( @@ -1374,22 +1536,48 @@ export interface SelectQueryBuilder * ### Examples * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .select(['id', 'first_name as name']) * .exceptAll(db.selectFrom('pet').select(['id', 'name'])) * .orderBy('name') + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * select "id", "first_name" as "name" + * from "person" + * except all + * select "id", "name" + * from "pet" + * order by "name" * ``` * * You can provide a callback to get an expression builder. * In the following example, this allows us to wrap the query in parentheses: * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .select(['id', 'first_name as name']) * .exceptAll((eb) => eb.parens( * eb.selectFrom('pet').select(['id', 'name']) * )) * .orderBy('name') + * .execute() + * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * select "id", "first_name" as "name" + * from "person" + * except all + * ( + * select "id", "name" + * from "pet" + * ) + * order by "name" * ``` */ exceptAll>( @@ -1414,6 +1602,17 @@ export interface SelectQueryBuilder * * pets[0].owner_first_name * ``` + * + * The generated SQL (PostgreSQL): + * + * ```sql + * select "pet".*, ( + * select "first_name" + * from "person" + * where "pet"."owner_id" = "person"."id" + * ) as "owner_first_name" + * from "pet" + * ``` */ as(alias: A): AliasedSelectQueryBuilder @@ -1423,10 +1622,11 @@ export interface SelectQueryBuilder * ### Examples * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .select(['id', 'first_name']) * .clearSelect() * .select(['id', 'gender']) + * .execute() * ``` * * The generated SQL(PostgreSQL): @@ -1445,10 +1645,11 @@ export interface SelectQueryBuilder * ### Examples * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .selectAll() * .limit(10) * .clearLimit() + * .execute() * ``` * * The generated SQL(PostgreSQL): @@ -1465,11 +1666,12 @@ export interface SelectQueryBuilder * ### Examples * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .selectAll() * .limit(10) * .offset(20) * .clearOffset() + * .execute() * ``` * * The generated SQL(PostgreSQL): @@ -1486,10 +1688,11 @@ export interface SelectQueryBuilder * ### Examples * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .selectAll() * .orderBy('id') * .clearOrderBy() + * .execute() * ``` * * The generated SQL(PostgreSQL): @@ -1506,10 +1709,11 @@ export interface SelectQueryBuilder * ### Examples * * ```ts - * db.selectFrom('person') + * await db.selectFrom('person') * .selectAll() * .groupBy('id') * .clearGroupBy() + * .execute() * ``` * * The generated SQL(PostgreSQL): @@ -1532,12 +1736,14 @@ export interface SelectQueryBuilder * The next example uses a helper function `log` to log a query: * * ```ts + * import type { Compilable } from 'kysely' + * * function log(qb: T): T { * console.log(qb.compile()) * return qb * } * - * db.selectFrom('person') + * await db.selectFrom('person') * .selectAll() * .$call(log) * .execute() @@ -1553,17 +1759,19 @@ export interface SelectQueryBuilder * like this: * * ```ts - * let query = db.selectFrom('person').selectAll() + * async function getPeople(firstName?: string, lastName?: string) { + * let query = db.selectFrom('person').selectAll() * - * if (firstName) { - * query = query.where('first_name', '=', firstName) - * } + * if (firstName) { + * query = query.where('first_name', '=', firstName) + * } * - * if (lastName) { - * query = query.where('last_name', '=', lastName) - * } + * if (lastName) { + * query = query.where('last_name', '=', lastName) + * } * - * const result = await query.execute() + * return await query.execute() + * } * ``` * * This method is mainly useful with optional selects. Any `select` or `selectAll` @@ -1600,14 +1808,17 @@ export interface SelectQueryBuilder * You can also call any other methods inside the callback: * * ```ts - * db.selectFrom('person') - * .select('person.id') - * .$if(filterByFirstName, (qb) => qb.where('first_name', '=', firstName)) - * .$if(filterByPetCount, (qb) => qb - * .innerJoin('pet', 'pet.owner_id', 'person.id') - * .having((eb) => eb.fn.count('pet.id'), '>', petCountLimit) - * .groupBy('person.id') - * ) + * async function getPeople(firstName?: string, petCountLimit?: number) { + * return await db.selectFrom('person') + * .select('person.id') + * .$if(firstName != null, (qb) => qb.where('first_name', '=', firstName!)) + * .$if(petCountLimit != null, (qb) => qb + * .innerJoin('pet', 'pet.owner_id', 'person.id') + * .having((eb) => eb.fn.count('pet.id'), '>', petCountLimit!) + * .groupBy('person.id') + * ) + * .execute() + * } * ``` */ $if( @@ -1648,6 +1859,7 @@ export interface SelectQueryBuilder * .where('pet.species', '!=', 'cat') * .$asTuple('name', 'species') * )) + * .execute() * ``` * * The generated SQL(PostgreSQL): @@ -1734,23 +1946,31 @@ export interface SelectQueryBuilder * Turn this code: * * ```ts + * import type { Person } from 'type-editor' // imaginary module + * * const person = await db.selectFrom('person') * .where('nullable_column', 'is not', null) * .selectAll() * .executeTakeFirstOrThrow() * - * if (person.nullable_column) { + * if (isWithNoNullValue(person)) { * functionThatExpectsPersonWithNonNullValue(person) * } + * + * function isWithNoNullValue(person: Person): person is Person & { nullable_column: string } { + * return person.nullable_column != null + * } * ``` * * Into this: * * ```ts + * import type { NotNull } from 'kysely' + * * const person = await db.selectFrom('person') * .where('nullable_column', 'is not', null) * .selectAll() - * .$narrowType<{ nullable_column: string }>() + * .$narrowType<{ nullable_column: NotNull }>() * .executeTakeFirstOrThrow() * * functionThatExpectsPersonWithNonNullValue(person) @@ -1801,12 +2021,12 @@ export interface SelectQueryBuilder * .with('first_and_last', (qb) => qb * .selectFrom('person') * .select(['first_name', 'last_name']) - * .$assertType<{ first_name: string, last_name: string }>() + * .$assertType<{ first_name: string, last_name: string | null }>() * ) * .with('age', (qb) => qb * .selectFrom('person') * .select('age') - * .$assertType<{ age: number }>() + * .$assertType<{ age: number | null }>() * ) * .selectFrom(['first_and_last', 'age']) * .selectAll() diff --git a/src/query-builder/update-query-builder.ts b/src/query-builder/update-query-builder.ts index d900676fa..6e3de86c2 100644 --- a/src/query-builder/update-query-builder.ts +++ b/src/query-builder/update-query-builder.ts @@ -327,7 +327,7 @@ export class UpdateQueryBuilder * ```ts * await db.selectFrom('person') * .innerJoin( - * qb.selectFrom('pet') + * db.selectFrom('pet') * .select(['owner_id', 'name']) * .where('name', '=', 'Doggo') * .as('doggos'), @@ -468,15 +468,17 @@ export class UpdateQueryBuilder * Update the first 2 rows in the 'person' table: * * ```ts - * return await db + * await db * .updateTable('person') * .set({ first_name: 'Foo' }) - * .limit(2); + * .limit(2) + * .execute() * ``` * * The generated SQL (MySQL): + * * ```sql - * update `person` set `first_name` = 'Foo' limit 2 + * update `person` set `first_name` = ? limit ? * ``` */ limit( @@ -519,7 +521,7 @@ export class UpdateQueryBuilder * first_name: 'Jennifer', * last_name: 'Aniston' * }) - * .where('id', '=', '1') + * .where('id', '=', 1) * .executeTakeFirst() * * console.log(result.numUpdatedRows) @@ -544,7 +546,7 @@ export class UpdateQueryBuilder * first_name: eb.selectFrom('pet').select('name').limit(1), * last_name: 'updated', * })) - * .where('id', '=', '1') + * .where('id', '=', 1) * .executeTakeFirst() * * console.log(result.numUpdatedRows) @@ -566,13 +568,15 @@ export class UpdateQueryBuilder * (or other target) and the second as the value: * * ```ts + * import { sql } from 'kysely' + * * const result = await db * .updateTable('person') * .set('first_name', 'Foo') * // As always, both arguments can be arbitrary expressions or * // callbacks that give you access to an expression builder: * .set(sql`address['postalCode']`, (eb) => eb.val('61710')) - * .where('id', '=', '1') + * .where('id', '=', 1) * .executeTakeFirst() * ``` * @@ -757,11 +761,13 @@ export class UpdateQueryBuilder * ### Examples * * ```ts + * import { sql } from 'kysely' + * * await db.updateTable('person') - * .set({ age: 39 }) - * .where('first_name', '=', 'John') - * .modifyEnd(sql.raw('-- This is a comment')) - * .execute() + * .set({ age: 39 }) + * .where('first_name', '=', 'John') + * .modifyEnd(sql.raw('-- This is a comment')) + * .execute() * ``` * * The generated SQL (MySQL): @@ -820,11 +826,18 @@ export class UpdateQueryBuilder * The next example uses a helper function `log` to log a query: * * ```ts + * import type { Compilable } from 'kysely' + * import type { PersonUpdate } from 'type-editor' // imaginary module + * * function log(qb: T): T { * console.log(qb.compile()) * return qb * } * + * const values = { + * first_name: 'John', + * } satisfies PersonUpdate + * * db.updateTable('person') * .set(values) * .$call(log) @@ -848,7 +861,9 @@ export class UpdateQueryBuilder * ### Examples * * ```ts - * async function updatePerson(id: number, updates: UpdateablePerson, returnLastName: boolean) { + * import type { PersonUpdate } from 'type-editor' // imaginary module + * + * async function updatePerson(id: number, updates: PersonUpdate, returnLastName: boolean) { * return await db * .updateTable('person') * .set(updates) @@ -916,27 +931,41 @@ export class UpdateQueryBuilder * Turn this code: * * ```ts + * import type { Person } from 'type-editor' // imaginary module + * + * const id = 1 + * const now = new Date().toISOString() + * * const person = await db.updateTable('person') - * .set({ deletedAt: now }) + * .set({ deleted_at: now }) * .where('id', '=', id) * .where('nullable_column', 'is not', null) * .returningAll() * .executeTakeFirstOrThrow() * - * if (person.nullable_column) { + * if (isWithNoNullValue(person)) { * functionThatExpectsPersonWithNonNullValue(person) * } + * + * function isWithNoNullValue(person: Person): person is Person & { nullable_column: string } { + * return person.nullable_column != null + * } * ``` * * Into this: * * ```ts + * import type { NotNull } from 'kysely' + * + * const id = 1 + * const now = new Date().toISOString() + * * const person = await db.updateTable('person') - * .set({ deletedAt: now }) + * .set({ deleted_at: now }) * .where('id', '=', id) * .where('nullable_column', 'is not', null) * .returningAll() - * .$narrowType<{ deletedAt: Date; nullable_column: string }>() + * .$narrowType<{ deleted_at: Date; nullable_column: NotNull }>() * .executeTakeFirstOrThrow() * * functionThatExpectsPersonWithNonNullValue(person) @@ -969,6 +998,17 @@ export class UpdateQueryBuilder * ### Examples * * ```ts + * import type { PersonUpdate, PetUpdate, Species } from 'type-editor' // imaginary module + * + * const person = { + * id: 1, + * gender: 'other', + * } satisfies PersonUpdate + * + * const pet = { + * name: 'Fluffy', + * } satisfies PetUpdate + * * const result = await db * .with('updated_person', (qb) => qb * .updateTable('person') diff --git a/src/query-builder/where-interface.ts b/src/query-builder/where-interface.ts index 2564a3194..cca8d593d 100644 --- a/src/query-builder/where-interface.ts +++ b/src/query-builder/where-interface.ts @@ -40,6 +40,8 @@ export interface WhereInterface { * you can always use: * * ```ts + * import { sql } from 'kysely' + * * sql`your operator` * ``` * @@ -51,7 +53,7 @@ export interface WhereInterface { * const persons = await db * .selectFrom('person') * .selectAll() - * .where('id', 'in', ['1', '2', '3']) + * .where('id', 'in', [1, 2, 3]) * .execute() * ``` * @@ -299,6 +301,8 @@ export interface WhereInterface { * import { sql } from 'kysely' * const { ref } = db.dynamic * + * const columnFromUserInput: string = 'id' + * * const persons = await db * .selectFrom('person') * .selectAll() diff --git a/src/query-creator.ts b/src/query-creator.ts index 6292263e2..63df58180 100644 --- a/src/query-creator.ts +++ b/src/query-creator.ts @@ -214,20 +214,19 @@ export class QueryCreator { * ### Examples * * ```ts - * const result = db.selectNoFrom((eb) => [ + * const result = await db.selectNoFrom((eb) => [ * eb.selectFrom('person') * .select('id') * .where('first_name', '=', 'Jennifer') * .limit(1) * .as('jennifer_id'), - * * eb.selectFrom('pet') * .select('id') * .where('name', '=', 'Doggo') * .limit(1) * .as('doggo_id') - * ]) - * .executeTakeFirstOrThrow() + * ]) + * .executeTakeFirstOrThrow() * * console.log(result.jennifer_id) * console.log(result.doggo_id) @@ -309,7 +308,7 @@ export class QueryCreator { * last_name: 'Aniston' * }) * .returning('id') - * .executeTakeFirst() + * .executeTakeFirstOrThrow() * ``` */ insertInto( @@ -382,7 +381,7 @@ export class QueryCreator { * ```ts * const result = await db * .deleteFrom('person') - * .where('person.id', '=', '1') + * .where('person.id', '=', 1) * .executeTakeFirst() * * console.log(result.numDeletedRows) @@ -400,7 +399,7 @@ export class QueryCreator { * const result = await db * .deleteFrom(['person', 'pet']) * .using('person') - * .innerJoin('pet', 'pet.owner_id', '=', 'person.id') + * .innerJoin('pet', 'pet.owner_id', 'person.id') * .where('person.id', '=', 1) * .executeTakeFirst() * ``` @@ -547,24 +546,24 @@ export class QueryCreator { * * ```ts * const result = await db - * .mergeInto("wine as target") + * .mergeInto('wine as target') * .using( - * "wine_stock_change as source", - * "source.wine_name", - * "target.name", + * 'wine_stock_change as source', + * 'source.wine_name', + * 'target.name', * ) - * .whenNotMatchedAnd("source.stock_delta", ">", 0) + * .whenNotMatchedAnd('source.stock_delta', '>', 0) * .thenInsertValues(({ ref }) => ({ - * name: ref("source.wine_name"), - * stock: ref("source.stock_delta"), + * name: ref('source.wine_name'), + * stock: ref('source.stock_delta'), * })) * .whenMatchedAnd( - * (eb) => eb("target.stock", "+", eb.ref("source.stock_delta")), - * ">", + * (eb) => eb('target.stock', '+', eb.ref('source.stock_delta')), + * '>', * 0, * ) - * .thenUpdateSet("stock", (eb) => - * eb("target.stock", "+", eb.ref("source.stock_delta")), + * .thenUpdateSet('stock', (eb) => + * eb('target.stock', '+', eb.ref('source.stock_delta')), * ) * .whenMatched() * .thenDelete() diff --git a/src/raw-builder/raw-builder.ts b/src/raw-builder/raw-builder.ts index 491eff6b3..976fee89f 100644 --- a/src/raw-builder/raw-builder.ts +++ b/src/raw-builder/raw-builder.ts @@ -33,6 +33,8 @@ export interface RawBuilder extends AliasableExpression { * this method also provides strict typing: * * ```ts + * import { sql } from 'kysely' + * * const result = await db * .selectFrom('person') * .select( @@ -55,6 +57,8 @@ export interface RawBuilder extends AliasableExpression { * provide the alias as the only type argument: * * ```ts + * import { sql } from 'kysely' + * * const values = sql<{ a: number, b: string }>`(values (1, 'foo'))` * * // The alias is `t(a, b)` which specifies the column names @@ -69,6 +73,7 @@ export interface RawBuilder extends AliasableExpression { * .expression( * db.selectFrom(aliasedValues).select(['t.a', 't.b']) * ) + * .execute() * ``` * * The generated SQL (PostgreSQL): @@ -112,8 +117,10 @@ export interface RawBuilder extends AliasableExpression { * ### Examples * * ```ts - * const { sql } = sql`select * from ${sql.table('person')}`.compile(db) - * console.log(sql) + * import { sql } from 'kysely' + * + * const compiledQuery = sql`select * from ${sql.table('person')}`.compile(db) + * console.log(compiledQuery.sql) * ``` */ compile(executorProvider: QueryExecutorProvider): CompiledQuery @@ -124,6 +131,8 @@ export interface RawBuilder extends AliasableExpression { * ### Examples * * ```ts + * import { sql } from 'kysely' + * * const result = await sql`select * from ${sql.table('person')}`.execute(db) * ``` */ diff --git a/src/raw-builder/sql.ts b/src/raw-builder/sql.ts index 721aa9eed..3320d4e1b 100644 --- a/src/raw-builder/sql.ts +++ b/src/raw-builder/sql.ts @@ -15,6 +15,7 @@ export interface Sql { * * ```ts * import { sql } from 'kysely' + * import type { Person } from 'type-editor' // imaginary module * * const id = 123 * const snippet = sql`select * from person where id = ${id}` @@ -36,6 +37,12 @@ export interface Sql { * of methods: * * ```ts + * import { sql } from 'kysely' + * + * const nicknames = ['johnny', 'john', 'jon'] + * const date1 = new Date('2000-01-01') + * const date2 = new Date('2001-01-01') + * * const persons = await db * .selectFrom('person') * .select( @@ -66,19 +73,24 @@ export interface Sql { * instance as the only argument: * * ```ts - * const result = await sql`select * from person`.execute(db) + * import { sql } from 'kysely' + * import type { Person } from 'type-editor' + * + * const { rows: results } = await sql`select * from person`.execute(db) * ``` * * You can merge other `sql` expressions and queries using substitutions: * * ```ts + * import { sql } from 'kysely' + * * const petName = db.selectFrom('pet').select('name').limit(1) * const fullName = sql`concat(first_name, ' ', last_name)` * - * sql` + * sql<{ full_name: string; pet_name: string }[]>` * select ${fullName} as full_name, ${petName} as pet_name * from person - * ` + * `.execute(db) * ``` * * Substitutions also handle {@link ExpressionBuilder.ref}, @@ -115,6 +127,11 @@ export interface Sql { * `sql.val(value)` is a shortcut for: * * ```ts + * import { sql } from 'kysely' + * + * const value = 123 + * type ValueType = typeof value + * * sql`${value}` * ``` */ @@ -319,6 +336,8 @@ export interface Sql { * ### Examples * * ```ts + * import type { Person } from 'type-editor' // imaginary module + * * function findByNicknames(nicknames: string[]): Promise { * return db * .selectFrom('person') diff --git a/src/schema/alter-column-builder.ts b/src/schema/alter-column-builder.ts index 61cf708cd..b54398f4f 100644 --- a/src/schema/alter-column-builder.ts +++ b/src/schema/alter-column-builder.ts @@ -69,14 +69,28 @@ preventAwait(AlterColumnBuilder, "don't await AlterColumnBuilder instances") /** * Allows us to force consumers to do exactly one alteration to a column. * - * Basically, deny the following: + * One cannot do no alterations: * * ```ts - * db.schema.alterTable('person').alterColumn('age', (ac) => ac) + * await db.schema + * .alterTable('person') + * // .execute() // Property 'execute' does not exist on type 'AlteredColumnBuilder'. * ``` * * ```ts - * db.schema.alterTable('person').alterColumn('age', (ac) => ac.dropNotNull().setNotNull()) + * await db.schema + * .alterTable('person') + * // .alterColumn('age', (ac) => ac) // Type 'AlterColumnBuilder' is not assignable to type 'AlteredColumnBuilder'. + * // .execute() + * ``` + * + * One cannot do multiple alterations: + * + * ```ts + * await db.schema + * .alterTable('person') + * // .alterColumn('age', (ac) => ac.dropNotNull().setNotNull()) // Property 'setNotNull' does not exist on type 'AlteredColumnBuilder'. + * // .execute() * ``` * * Which would now throw a compilation error, instead of a runtime error.