UNPKG

kysely

Version:
519 lines (518 loc) 18.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.NotMatchedThenableMergeQueryBuilder = exports.MatchedThenableMergeQueryBuilder = exports.WheneableMergeQueryBuilder = exports.MergeQueryBuilder = void 0; const insert_query_node_js_1 = require("../operation-node/insert-query-node.js"); const merge_query_node_js_1 = require("../operation-node/merge-query-node.js"); const query_node_js_1 = require("../operation-node/query-node.js"); const update_query_node_js_1 = require("../operation-node/update-query-node.js"); const insert_values_parser_js_1 = require("../parser/insert-values-parser.js"); const join_parser_js_1 = require("../parser/join-parser.js"); const merge_parser_js_1 = require("../parser/merge-parser.js"); const top_parser_js_1 = require("../parser/top-parser.js"); const noop_query_executor_js_1 = require("../query-executor/noop-query-executor.js"); const object_utils_js_1 = require("../util/object-utils.js"); const prevent_await_js_1 = require("../util/prevent-await.js"); const merge_result_js_1 = require("./merge-result.js"); const no_result_error_js_1 = require("./no-result-error.js"); const update_query_builder_js_1 = require("./update-query-builder.js"); class MergeQueryBuilder { #props; constructor(props) { this.#props = (0, object_utils_js_1.freeze)(props); } /** * Changes a `merge into` query to an `merge top into` query. * * `top` clause is only supported by some dialects like MS SQL Server. * * ### Examples * * Affect 5 matched rows at most: * * ```ts * await db.mergeInto('person') * .top(5) * .using('pet', 'person.id', 'pet.owner_id') * .whenMatched() * .thenDelete() * .execute() * ``` * * The generated SQL (MS SQL Server): * * ```sql * merge top(5) into "person" * using "pet" on "person"."id" = "pet"."owner_id" * when matched then * delete * ``` * * Affect 50% of matched rows: * * ```ts * await db.mergeInto('person') * .top(50, 'percent') * .using('pet', 'person.id', 'pet.owner_id') * .whenMatched() * .thenDelete() * .execute() * ``` * * The generated SQL (MS SQL Server): * * ```sql * merge top(50) percent into "person" * using "pet" on "person"."id" = "pet"."owner_id" * when matched then * delete * ``` */ top(expression, modifiers) { return new MergeQueryBuilder({ ...this.#props, queryNode: query_node_js_1.QueryNode.cloneWithTop(this.#props.queryNode, (0, top_parser_js_1.parseTop)(expression, modifiers)), }); } using(...args) { return new WheneableMergeQueryBuilder({ ...this.#props, queryNode: merge_query_node_js_1.MergeQueryNode.cloneWithUsing(this.#props.queryNode, (0, join_parser_js_1.parseJoin)('Using', args)), }); } } exports.MergeQueryBuilder = MergeQueryBuilder; (0, prevent_await_js_1.preventAwait)(MergeQueryBuilder, "don't await MergeQueryBuilder instances directly. To execute the query you need to call `execute` when available."); class WheneableMergeQueryBuilder { #props; constructor(props) { this.#props = (0, object_utils_js_1.freeze)(props); } /** * See {@link MergeQueryBuilder.top}. */ top(expression, modifiers) { return new WheneableMergeQueryBuilder({ ...this.#props, queryNode: query_node_js_1.QueryNode.cloneWithTop(this.#props.queryNode, (0, top_parser_js_1.parseTop)(expression, modifiers)), }); } /** * Adds a simple `when matched` clause to the query. * * For a `when matched` clause with an `and` condition, see {@link whenMatchedAnd}. * * For a simple `when not matched` clause, see {@link whenNotMatched}. * * For a `when not matched` clause with an `and` condition, see {@link whenNotMatchedAnd}. * * ### Examples * * ```ts * const result = await db.mergeInto('person') * .using('pet', 'person.id', 'pet.owner_id') * .whenMatched() * .thenDelete() * .execute() * ``` * * The generated SQL (PostgreSQL): * * ```sql * merge into "person" * using "pet" on "person"."id" = "pet"."owner_id" * when matched then * delete * ``` */ whenMatched() { return this.#whenMatched([]); } whenMatchedAnd(...args) { return this.#whenMatched(args); } /** * Adds the `when matched` clause to the query with an `and` condition. But unlike * {@link whenMatchedAnd}, this method accepts a column reference as the 3rd argument. * * This method is similar to {@link SelectQueryBuilder.whereRef}, so see the documentation * for that method for more examples. */ whenMatchedAndRef(lhs, op, rhs) { return this.#whenMatched([lhs, op, rhs], true); } #whenMatched(args, refRight) { return new MatchedThenableMergeQueryBuilder({ ...this.#props, queryNode: merge_query_node_js_1.MergeQueryNode.cloneWithWhen(this.#props.queryNode, (0, merge_parser_js_1.parseMergeWhen)({ isMatched: true }, args, refRight)), }); } /** * Adds a simple `when not matched` clause to the query. * * For a `when not matched` clause with an `and` condition, see {@link whenNotMatchedAnd}. * * For a simple `when matched` clause, see {@link whenMatched}. * * For a `when matched` clause with an `and` condition, see {@link whenMatchedAnd}. * * ### Examples * * ```ts * const result = await db.mergeInto('person') * .using('pet', 'person.id', 'pet.owner_id') * .whenNotMatched() * .thenInsertValues({ * first_name: 'John', * last_name: 'Doe', * }) * .execute() * ``` * * The generated SQL (PostgreSQL): * * ```sql * merge into "person" * using "pet" on "person"."id" = "pet"."owner_id" * when not matched then * insert ("first_name", "last_name") values ($1, $2) * ``` */ whenNotMatched() { return this.#whenNotMatched([]); } whenNotMatchedAnd(...args) { return this.#whenNotMatched(args); } /** * Adds the `when not matched` clause to the query with an `and` condition. But unlike * {@link whenNotMatchedAnd}, this method accepts a column reference as the 3rd argument. * * Unlike {@link whenMatchedAndRef}, you cannot reference columns from the target table. * * This method is similar to {@link SelectQueryBuilder.whereRef}, so see the documentation * for that method for more examples. */ whenNotMatchedAndRef(lhs, op, rhs) { return this.#whenNotMatched([lhs, op, rhs], true); } /** * Adds a simple `when not matched by source` clause to the query. * * Supported in MS SQL Server. * * Similar to {@link whenNotMatched}, but returns a {@link MatchedThenableMergeQueryBuilder}. */ whenNotMatchedBySource() { return this.#whenNotMatched([], false, true); } whenNotMatchedBySourceAnd(...args) { return this.#whenNotMatched(args, false, true); } /** * Adds the `when not matched by source` clause to the query with an `and` condition. * * Similar to {@link whenNotMatchedAndRef}, but you can reference columns from * the target table, and not from source table and returns a {@link MatchedThenableMergeQueryBuilder}. */ whenNotMatchedBySourceAndRef(lhs, op, rhs) { return this.#whenNotMatched([lhs, op, rhs], true, true); } #whenNotMatched(args, refRight = false, bySource = false) { const props = { ...this.#props, queryNode: merge_query_node_js_1.MergeQueryNode.cloneWithWhen(this.#props.queryNode, (0, merge_parser_js_1.parseMergeWhen)({ isMatched: false, bySource }, args, refRight)), }; const Builder = bySource ? MatchedThenableMergeQueryBuilder : NotMatchedThenableMergeQueryBuilder; return new Builder(props); } /** * Simply calls the provided function passing `this` as the only argument. `$call` returns * what the provided function returns. * * If you want to conditionally call a method on `this`, see * the {@link $if} method. * * ### Examples * * The next example uses a helper function `log` to log a query: * * ```ts * function log<T extends Compilable>(qb: T): T { * console.log(qb.compile()) * return qb * } * * db.updateTable('person') * .set(values) * .$call(log) * .execute() * ``` */ $call(func) { return func(this); } /** * Call `func(this)` if `condition` is true. * * This method is especially handy with optional selects. Any `returning` or `returningAll` * method calls add columns as optional fields to the output type when called inside * the `func` callback. This is because we can't know if those selections were actually * made before running the code. * * You can also call any other methods inside the callback. * * ### Examples * * ```ts * async function updatePerson(id: number, updates: UpdateablePerson, returnLastName: boolean) { * return await db * .updateTable('person') * .set(updates) * .where('id', '=', id) * .returning(['id', 'first_name']) * .$if(returnLastName, (qb) => qb.returning('last_name')) * .executeTakeFirstOrThrow() * } * ``` * * Any selections added inside the `if` callback will be added as optional fields to the * output type since we can't know if the selections were actually made before running * the code. In the example above the return type of the `updatePerson` function is: * * ```ts * { * id: number * first_name: string * last_name?: string * } * ``` */ $if(condition, func) { if (condition) { return func(this); } return new WheneableMergeQueryBuilder({ ...this.#props, }); } toOperationNode() { return this.#props.executor.transformQuery(this.#props.queryNode, this.#props.queryId); } compile() { return this.#props.executor.compileQuery(this.#props.queryNode, this.#props.queryId); } /** * Executes the query and returns an array of rows. * * Also see the {@link executeTakeFirst} and {@link executeTakeFirstOrThrow} methods. */ async execute() { const compiledQuery = this.compile(); const result = await this.#props.executor.executeQuery(compiledQuery, this.#props.queryId); return [new merge_result_js_1.MergeResult(result.numAffectedRows)]; } /** * Executes the query and returns the first result or undefined if * the query returned no result. */ async executeTakeFirst() { const [result] = await this.execute(); return result; } /** * Executes the query and returns the first result or throws if * the query returned no result. * * By default an instance of {@link NoResultError} is thrown, but you can * provide a custom error class, or callback as the only argument to throw a different * error. */ async executeTakeFirstOrThrow(errorConstructor = no_result_error_js_1.NoResultError) { const result = await this.executeTakeFirst(); if (result === undefined) { const error = (0, no_result_error_js_1.isNoResultErrorConstructor)(errorConstructor) ? new errorConstructor(this.toOperationNode()) : errorConstructor(this.toOperationNode()); throw error; } return result; } } exports.WheneableMergeQueryBuilder = WheneableMergeQueryBuilder; (0, prevent_await_js_1.preventAwait)(WheneableMergeQueryBuilder, "don't await WheneableMergeQueryBuilder instances directly. To execute the query you need to call `execute`."); class MatchedThenableMergeQueryBuilder { #props; constructor(props) { this.#props = (0, object_utils_js_1.freeze)(props); } /** * Performs the `delete` action. * * To perform the `do nothing` action, see {@link thenDoNothing}. * * To perform the `update` action, see {@link thenUpdate} or {@link thenUpdateSet}. * * ### Examples * * ```ts * const result = await db.mergeInto('person') * .using('pet', 'person.id', 'pet.owner_id') * .whenMatched() * .thenDelete() * .execute() * ``` * * The generated SQL (PostgreSQL): * * ```sql * merge into "person" * using "pet" on "person"."id" = "pet"."owner_id" * when matched then * delete * ``` */ thenDelete() { return new WheneableMergeQueryBuilder({ ...this.#props, queryNode: merge_query_node_js_1.MergeQueryNode.cloneWithThen(this.#props.queryNode, (0, merge_parser_js_1.parseMergeThen)('delete')), }); } /** * Performs the `do nothing` action. * * This is supported in PostgreSQL. * * To perform the `delete` action, see {@link thenDelete}. * * To perform the `update` action, see {@link thenUpdate} or {@link thenUpdateSet}. * * ### Examples * * ```ts * const result = await db.mergeInto('person') * .using('pet', 'person.id', 'pet.owner_id') * .whenMatched() * .thenDoNothing() * .execute() * ``` * * The generated SQL (PostgreSQL): * * ```sql * merge into "person" * using "pet" on "person"."id" = "pet"."owner_id" * when matched then * do nothing * ``` */ thenDoNothing() { return new WheneableMergeQueryBuilder({ ...this.#props, queryNode: merge_query_node_js_1.MergeQueryNode.cloneWithThen(this.#props.queryNode, (0, merge_parser_js_1.parseMergeThen)('do nothing')), }); } /** * Perform an `update` operation with a full-fledged {@link UpdateQueryBuilder}. * This is handy when multiple `set` invocations are needed. * * For a shorthand version of this method, see {@link thenUpdateSet}. * * To perform the `delete` action, see {@link thenDelete}. * * To perform the `do nothing` action, see {@link thenDoNothing}. * * ### Examples * * ```ts * import { sql } from 'kysely' * * const result = await db.mergeInto('person') * .using('pet', 'person.id', 'pet.owner_id') * .whenMatched() * .thenUpdate((ub) => ub * .set(sql`metadata['has_pets']`, 'Y') * .set({ * updated_at: Date.now(), * }) * ) * .execute() * ``` * * The generated SQL (PostgreSQL): * * ```sql * merge into "person" * using "pet" on "person"."id" = "pet"."owner_id" * when matched then * update set metadata['has_pets'] = $1, "updated_at" = $2 * ``` */ thenUpdate(set) { return new WheneableMergeQueryBuilder({ ...this.#props, queryNode: merge_query_node_js_1.MergeQueryNode.cloneWithThen(this.#props.queryNode, (0, merge_parser_js_1.parseMergeThen)(set(new update_query_builder_js_1.UpdateQueryBuilder({ queryId: this.#props.queryId, executor: noop_query_executor_js_1.NOOP_QUERY_EXECUTOR, queryNode: update_query_node_js_1.UpdateQueryNode.createWithoutTable(), })))), }); } thenUpdateSet(...args) { // @ts-ignore not sure how to type this so it won't complain about set(...args). return this.thenUpdate((ub) => ub.set(...args)); } } exports.MatchedThenableMergeQueryBuilder = MatchedThenableMergeQueryBuilder; (0, prevent_await_js_1.preventAwait)(MatchedThenableMergeQueryBuilder, "don't await MatchedThenableMergeQueryBuilder instances directly. To execute the query you need to call `execute` when available."); class NotMatchedThenableMergeQueryBuilder { #props; constructor(props) { this.#props = (0, object_utils_js_1.freeze)(props); } /** * Performs the `do nothing` action. * * This is supported in PostgreSQL. * * To perform the `insert` action, see {@link thenInsertValues}. * * ### Examples * * ```ts * const result = await db.mergeInto('person') * .using('pet', 'person.id', 'pet.owner_id') * .whenNotMatched() * .thenDoNothing() * .execute() * ``` * * The generated SQL (PostgreSQL): * * ```sql * merge into "person" * using "pet" on "person"."id" = "pet"."owner_id" * when not matched then * do nothing * ``` */ thenDoNothing() { return new WheneableMergeQueryBuilder({ ...this.#props, queryNode: merge_query_node_js_1.MergeQueryNode.cloneWithThen(this.#props.queryNode, (0, merge_parser_js_1.parseMergeThen)('do nothing')), }); } thenInsertValues(insert) { const [columns, values] = (0, insert_values_parser_js_1.parseInsertExpression)(insert); return new WheneableMergeQueryBuilder({ ...this.#props, queryNode: merge_query_node_js_1.MergeQueryNode.cloneWithThen(this.#props.queryNode, (0, merge_parser_js_1.parseMergeThen)(insert_query_node_js_1.InsertQueryNode.cloneWith(insert_query_node_js_1.InsertQueryNode.createWithoutInto(), { columns, values, }))), }); } } exports.NotMatchedThenableMergeQueryBuilder = NotMatchedThenableMergeQueryBuilder; (0, prevent_await_js_1.preventAwait)(NotMatchedThenableMergeQueryBuilder, "don't await NotMatchedThenableMergeQueryBuilder instances directly. To execute the query you need to call `execute` when available.");