diff --git a/README.md b/README.md index 46e387f93..abe8f2a1d 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ You can find a more thorough introduction [here](https://www.jakso.me/blog/kysel - [Installation](#installation) - [3rd party dialects](#3rd-party-dialects) - [Minimal example](#minimal-example) +- [Playground](#playground) - [Generating types](#generating-types) - [Query examples](#query-examples) - [Select queries](#select-queries) @@ -180,6 +181,10 @@ type InsertablePerson = Insertable type UpdateablePerson = Updateable ``` +# Playground + +[@wirekang](https://github.com/wirekang) has created a [playground for Kysely](https://wirekang.github.io/kysely-playground/#eyJkaWFsZWN0IjoibXlzcWwiLCJ0cyI6ImludGVyZmFjZSBEQiB7XG4gIHVzZXI6IFVzZXJUYWJsZVxufVxuXG5pbnRlcmZhY2UgVXNlclRhYmxlIHtcbiAgaWQ6IEdlbmVyYXRlZDxzdHJpbmc+XG4gIGZpcnN0X25hbWU6IHN0cmluZyB8IG51bGxcbiAgbGFzdF9uYW1lOiBzdHJpbmcgfCBudWxsXG4gIGNyZWF0ZWRfYXQ6IEdlbmVyYXRlZDxEYXRlPlxufVxuXG5yZXN1bHQgPSBreXNlbHlcbiAgLnNlbGVjdEZyb20oXCJ1c2VyXCIpXG4gIC5zZWxlY3RBbGwoKVxuICAub3JkZXJCeShcImNyZWF0ZWRfYXRcIikifQ==). You can use to quickly test stuff out and for creating code examples for your issues, PRs and discord messages. + # Generating types If you want to generate the table types automatically from the database schema please diff --git a/src/operation-node/on-conflict-node.ts b/src/operation-node/on-conflict-node.ts index 2b614d1ab..bf9212c92 100644 --- a/src/operation-node/on-conflict-node.ts +++ b/src/operation-node/on-conflict-node.ts @@ -89,4 +89,18 @@ export const OnConflictNode = freeze({ : WhereNode.create(operation), }) }, + + cloneWithoutIndexWhere(node: OnConflictNode): OnConflictNode { + return freeze({ + ...node, + indexWhere: undefined, + }) + }, + + cloneWithoutUpdateWhere(node: OnConflictNode): OnConflictNode { + return freeze({ + ...node, + updateWhere: undefined, + }) + }, }) diff --git a/src/operation-node/query-node.ts b/src/operation-node/query-node.ts index 10c919ceb..171de71c6 100644 --- a/src/operation-node/query-node.ts +++ b/src/operation-node/query-node.ts @@ -80,4 +80,11 @@ export const QueryNode = freeze({ : ReturningNode.create(selections), }) }, + + cloneWithoutWhere(node: T): T { + return freeze({ + ...node, + where: undefined, + }) + }, }) diff --git a/src/operation-node/select-query-node.ts b/src/operation-node/select-query-node.ts index 9ba3793c2..5c963b61d 100644 --- a/src/operation-node/select-query-node.ts +++ b/src/operation-node/select-query-node.ts @@ -191,4 +191,32 @@ export const SelectQueryNode = freeze({ explain, }) }, + + cloneWithoutSelections(select: SelectQueryNode): SelectQueryNode { + return freeze({ + ...select, + selections: [], + }) + }, + + cloneWithoutLimit(select: SelectQueryNode): SelectQueryNode { + return freeze({ + ...select, + limit: undefined, + }) + }, + + cloneWithoutOffset(select: SelectQueryNode): SelectQueryNode { + return freeze({ + ...select, + offset: undefined, + }) + }, + + cloneWithoutOrderBy(select: SelectQueryNode): SelectQueryNode { + return freeze({ + ...select, + orderBy: undefined, + }) + }, }) diff --git a/src/parser/select-parser.ts b/src/parser/select-parser.ts index dd0840c0b..e33c0155a 100644 --- a/src/parser/select-parser.ts +++ b/src/parser/select-parser.ts @@ -98,7 +98,9 @@ type ExtractTypeFromSelectExpression< ? QA extends A ? ValueType : never - : SE extends (qb: any) => AliasedSelectQueryBuilder + : SE extends ( + qb: any + ) => AliasedSelectQueryBuilder ? QA extends A ? ValueType : never diff --git a/src/query-builder/delete-query-builder.ts b/src/query-builder/delete-query-builder.ts index b81fc8fbd..cc6a3c615 100644 --- a/src/query-builder/delete-query-builder.ts +++ b/src/query-builder/delete-query-builder.ts @@ -173,6 +173,13 @@ export class DeleteQueryBuilder }) } + clearWhere(): DeleteQueryBuilder { + return new DeleteQueryBuilder({ + ...this.#props, + queryNode: QueryNode.cloneWithoutWhere(this.#props.queryNode), + }) + } + /** * Joins another table to the query using an inner join. * diff --git a/src/query-builder/on-conflict-builder.ts b/src/query-builder/on-conflict-builder.ts index 52c091f1e..0f789a59f 100644 --- a/src/query-builder/on-conflict-builder.ts +++ b/src/query-builder/on-conflict-builder.ts @@ -259,6 +259,15 @@ export class OnConflictBuilder }) } + clearWhere(): OnConflictBuilder { + return new OnConflictBuilder({ + ...this.#props, + onConflictNode: OnConflictNode.cloneWithoutIndexWhere( + this.#props.onConflictNode + ), + }) + } + /** * Adds the "do nothing" conflict action. * @@ -524,6 +533,15 @@ export class OnConflictUpdateBuilder }) } + clearWhere(): OnConflictUpdateBuilder { + return new OnConflictUpdateBuilder({ + ...this.#props, + onConflictNode: OnConflictNode.cloneWithoutUpdateWhere( + this.#props.onConflictNode + ), + }) + } + toOperationNode(): OnConflictNode { return this.#props.onConflictNode } diff --git a/src/query-builder/select-query-builder.ts b/src/query-builder/select-query-builder.ts index 2df45c1e0..964444a4c 100644 --- a/src/query-builder/select-query-builder.ts +++ b/src/query-builder/select-query-builder.ts @@ -1465,6 +1465,114 @@ export class SelectQueryBuilder }) } + /** + * Clears all select clauses from the query. + * + * ### Examples + * + * ```ts + * db.selectFrom('person') + * .select(['id', 'first_name']) + * .clearSelect() + * .select(['id','gender']) + * ``` + * + * The generated SQL(PostgreSQL): + * + * ```sql + * select "id", "gender" from "person" + * ``` + */ + clearSelect(): SelectQueryBuilder { + return new SelectQueryBuilder({ + ...this.#props, + queryNode: SelectQueryNode.cloneWithoutSelections(this.#props.queryNode), + }) + } + + clearWhere(): SelectQueryBuilder { + return new SelectQueryBuilder({ + ...this.#props, + queryNode: QueryNode.cloneWithoutWhere(this.#props.queryNode), + }) + } + + /** + * Clears limit clause from the query. + * + * ### Examples + * + * ```ts + * db.selectFrom('person') + * .selectAll() + * .limit(10) + * .clearLimit() + * ``` + * + * The generated SQL(PostgreSQL): + * + * ```sql + * select * from "person" + * ``` + */ + clearLimit(): SelectQueryBuilder { + return new SelectQueryBuilder({ + ...this.#props, + queryNode: SelectQueryNode.cloneWithoutLimit(this.#props.queryNode), + }) + } + + /** + * Clears offset clause from the query. + * + * ### Examples + * + * ```ts + * db.selectFrom('person') + * .selectAll() + * .limit(10) + * .offset(20) + * .clearOffset() + * ``` + * + * The generated SQL(PostgreSQL): + * + * ```sql + * select * from "person" limit 10 + * ``` + */ + clearOffset(): SelectQueryBuilder { + return new SelectQueryBuilder({ + ...this.#props, + queryNode: SelectQueryNode.cloneWithoutOffset(this.#props.queryNode), + }) + } + + /** + * Clears all `order by` clauses from the query. + * + * ### Examples + * + * ```ts + * db.selectFrom('person') + * .selectAll() + * .orderBy('id') + * .clearOrderBy() + * ``` + * + * The generated SQL(PostgreSQL): + * + * ```sql + * select * from "person" + * ``` + */ + clearOrderBy(): SelectQueryBuilder { + return new SelectQueryBuilder({ + ...this.#props, + queryNode: SelectQueryNode.cloneWithoutOrderBy(this.#props.queryNode), + }) + } + /** * Simply calls the given function passing `this` as the only argument. * diff --git a/src/query-builder/update-query-builder.ts b/src/query-builder/update-query-builder.ts index 727e8d3f5..13fbac83c 100644 --- a/src/query-builder/update-query-builder.ts +++ b/src/query-builder/update-query-builder.ts @@ -183,6 +183,13 @@ export class UpdateQueryBuilder }) } + clearWhere(): UpdateQueryBuilder { + return new UpdateQueryBuilder({ + ...this.#props, + queryNode: QueryNode.cloneWithoutWhere(this.#props.queryNode), + }) + } + /** * Adds a from clause to the update query. * diff --git a/src/query-builder/where-interface.ts b/src/query-builder/where-interface.ts index 3dc58cf53..5ffa5e074 100644 --- a/src/query-builder/where-interface.ts +++ b/src/query-builder/where-interface.ts @@ -419,4 +419,24 @@ export interface WhereInterface { * Just like {@link whereExists} but creates an `or not exists` clause. */ orWhereNotExists(arg: ExistsExpression): WhereInterface + + /** + * Clears all where clauses from the query. + * + * ### Examples + * + * ```ts + * db.selectFrom('person') + * .selectAll() + * .where('id','=',42) + * .clearWhere() + * ``` + * + * The generated SQL(PostgreSQL): + * + * ```sql + * select * from "person" + * ``` + */ + clearWhere(): WhereInterface } diff --git a/src/raw-builder/sql.ts b/src/raw-builder/sql.ts index e839372f6..771306e07 100644 --- a/src/raw-builder/sql.ts +++ b/src/raw-builder/sql.ts @@ -353,7 +353,10 @@ export interface Sql { * BEFORE $1::varchar, (1 == 1)::varchar, (select * from "person")::varchar, false::varchar, "first_name" AFTER * ``` */ - join(array: readonly unknown[], separator?: RawBuilder): RawBuilder + join( + array: readonly unknown[], + separator?: RawBuilder + ): RawBuilder } export const sql: Sql = Object.assign( diff --git a/test/node/src/clear.test.ts b/test/node/src/clear.test.ts new file mode 100644 index 000000000..8322fcc03 --- /dev/null +++ b/test/node/src/clear.test.ts @@ -0,0 +1,229 @@ +import { + BUILT_IN_DIALECTS, + destroyTest, + initTest, + TestContext, + testSql, +} from './test-setup' + +for (const dialect of BUILT_IN_DIALECTS) { + describe(`${dialect} clear`, () => { + let ctx: TestContext + + before(async function () { + ctx = await initTest(this, dialect) + }) + + after(async () => { + await destroyTest(ctx) + }) + + it('should clear select', () => { + const query = ctx.db + .selectFrom('person') + .select(['id', 'first_name', 'last_name']) + .clearSelect() + .select(['id']) + + testSql(query, dialect, { + postgres: { + sql: `select "id" from "person"`, + parameters: [], + }, + mysql: { + sql: 'select `id` from `person`', + parameters: [], + }, + sqlite: { + sql: `select "id" from "person"`, + parameters: [], + }, + }) + }) + + it('SelectQueryBuilder should clear where', () => { + const query = ctx.db + .selectFrom('person') + .selectAll() + .where('gender', '=', 'other') + .clearWhere() + + testSql(query, dialect, { + postgres: { + sql: `select * from "person"`, + parameters: [], + }, + mysql: { + sql: 'select * from `person`', + parameters: [], + }, + sqlite: { + sql: `select * from "person"`, + parameters: [], + }, + }) + }) + + it('OnConflictBuilder should clear where', () => { + const query = ctx.db + .insertInto('person') + .onConflict((b) => b.where('id', '=', 3).clearWhere().doNothing()) + + testSql(query, dialect, { + postgres: { + sql: `insert into "person" on conflict do nothing`, + parameters: [], + }, + mysql: { + sql: 'insert into `person` on conflict do nothing', + parameters: [], + }, + sqlite: { + sql: `insert into "person" on conflict do nothing`, + parameters: [], + }, + }) + }) + + it('OnConflictUpdateBuilder should clear where', () => { + const query = ctx.db + .insertInto('person') + .onConflict((b) => + b + .doUpdateSet({ gender: 'other' }) + .where('gender', '=', 'male') + .clearWhere() + ) + + testSql(query, dialect, { + postgres: { + sql: `insert into "person" on conflict do update set "gender" = $1`, + parameters: ['other'], + }, + mysql: { + sql: 'insert into `person` on conflict do update set `gender` = ?', + parameters: ['other'], + }, + sqlite: { + sql: `insert into "person" on conflict do update set "gender" = ?`, + parameters: ['other'], + }, + }) + }) + + it('UpdateQueryBuilder should clear where', () => { + const query = ctx.db + .updateTable('person') + .set({ gender: 'other' }) + .where('gender', '=', 'other') + .clearWhere() + + testSql(query, dialect, { + postgres: { + sql: `update "person" set "gender" = $1`, + parameters: ['other'], + }, + mysql: { + sql: 'update `person` set `gender` = ?', + parameters: ['other'], + }, + sqlite: { + sql: `update "person" set "gender" = ?`, + parameters: ['other'], + }, + }) + }) + + it('DeleteQueryBuilder should clear where', () => { + const query = ctx.db + .deleteFrom('person') + .where('gender', '=', 'other') + .clearWhere() + + testSql(query, dialect, { + postgres: { + sql: `delete from "person"`, + parameters: [], + }, + mysql: { + sql: 'delete from `person`', + parameters: [], + }, + sqlite: { + sql: `delete from "person"`, + parameters: [], + }, + }) + }) + + it('should clear orderBy', () => { + const query = ctx.db + .selectFrom('person') + .selectAll() + .orderBy('id') + .clearOrderBy() + + testSql(query, dialect, { + postgres: { + sql: `select * from "person"`, + parameters: [], + }, + mysql: { + sql: 'select * from `person`', + parameters: [], + }, + sqlite: { + sql: `select * from "person"`, + parameters: [], + }, + }) + }) + + it('should clear limit', () => { + const query = ctx.db + .selectFrom('person') + .selectAll() + .limit(100) + .clearLimit() + + testSql(query, dialect, { + postgres: { + sql: `select * from "person"`, + parameters: [], + }, + mysql: { + sql: 'select * from `person`', + parameters: [], + }, + sqlite: { + sql: `select * from "person"`, + parameters: [], + }, + }) + }) + + it('should clear offset', () => { + const query = ctx.db + .selectFrom('person') + .selectAll() + .limit(1) + .offset(100) + .clearOffset() + + testSql(query, dialect, { + postgres: { + sql: `select * from "person" limit $1`, + parameters: [1], + }, + mysql: { + sql: 'select * from `person` limit ?', + parameters: [1], + }, + sqlite: { + sql: `select * from "person" limit ?`, + parameters: [1], + }, + }) + }) + }) +} diff --git a/test/typings/test-d/clear.test-d.ts b/test/typings/test-d/clear.test-d.ts new file mode 100644 index 000000000..ff0caf5a4 --- /dev/null +++ b/test/typings/test-d/clear.test-d.ts @@ -0,0 +1,23 @@ +import { Kysely } from '..' +import { Database } from '../shared' +import { expectType } from 'tsd' + +async function testClearSelect(db: Kysely) { + const r1 = await db + .selectFrom('person') + .select(['first_name', 'gender']) + .clearSelect() + .select('id') + .executeTakeFirstOrThrow() + + expectType<{ id: number }>(r1) + + const r2 = await db + .selectFrom('person') + .selectAll() + .clearSelect() + .select('age') + .executeTakeFirstOrThrow() + + expectType<{ age: number }>(r2) +}