UNPKG

drizzle-orm

Version:

Drizzle ORM package for SQL databases

665 lines • 21 kB
import { entityKind, is } from "../../entity.js"; import { TypedQueryBuilder } from "../../query-builders/query-builder.js"; import { QueryPromise } from "../../query-promise.js"; import { SelectionProxyHandler } from "../../selection-proxy.js"; import { SQL, View } from "../../sql/sql.js"; import { Subquery } from "../../subquery.js"; import { Table } from "../../table.js"; import { applyMixins, getTableColumns, getTableLikeName, haveSameKeys, orderSelectedFields } from "../../utils.js"; import { ViewBaseConfig } from "../../view-common.js"; import { SQLiteViewBase } from "../view-base.js"; class SQLiteSelectBuilder { static [entityKind] = "SQLiteSelectBuilder"; fields; session; dialect; withList; distinct; constructor(config) { this.fields = config.fields; this.session = config.session; this.dialect = config.dialect; this.withList = config.withList; this.distinct = config.distinct; } from(source) { const isPartialSelect = !!this.fields; let fields; if (this.fields) { fields = this.fields; } else if (is(source, Subquery)) { fields = Object.fromEntries( Object.keys(source._.selectedFields).map((key) => [key, source[key]]) ); } else if (is(source, SQLiteViewBase)) { fields = source[ViewBaseConfig].selectedFields; } else if (is(source, SQL)) { fields = {}; } else { fields = getTableColumns(source); } return new SQLiteSelectBase({ table: source, fields, isPartialSelect, session: this.session, dialect: this.dialect, withList: this.withList, distinct: this.distinct }); } } class SQLiteSelectQueryBuilderBase extends TypedQueryBuilder { static [entityKind] = "SQLiteSelectQueryBuilder"; _; /** @internal */ config; joinsNotNullableMap; tableName; isPartialSelect; session; dialect; constructor({ table, fields, isPartialSelect, session, dialect, withList, distinct }) { super(); this.config = { withList, table, fields: { ...fields }, distinct, setOperators: [] }; this.isPartialSelect = isPartialSelect; this.session = session; this.dialect = dialect; this._ = { selectedFields: fields }; this.tableName = getTableLikeName(table); this.joinsNotNullableMap = typeof this.tableName === "string" ? { [this.tableName]: true } : {}; } createJoin(joinType) { return (table, on) => { const baseTableName = this.tableName; const tableName = getTableLikeName(table); if (typeof tableName === "string" && this.config.joins?.some((join) => join.alias === tableName)) { throw new Error(`Alias "${tableName}" is already used in this query`); } if (!this.isPartialSelect) { if (Object.keys(this.joinsNotNullableMap).length === 1 && typeof baseTableName === "string") { this.config.fields = { [baseTableName]: this.config.fields }; } if (typeof tableName === "string" && !is(table, SQL)) { const selection = is(table, Subquery) ? table._.selectedFields : is(table, View) ? table[ViewBaseConfig].selectedFields : table[Table.Symbol.Columns]; this.config.fields[tableName] = selection; } } if (typeof on === "function") { on = on( new Proxy( this.config.fields, new SelectionProxyHandler({ sqlAliasedBehavior: "sql", sqlBehavior: "sql" }) ) ); } if (!this.config.joins) { this.config.joins = []; } this.config.joins.push({ on, table, joinType, alias: tableName }); if (typeof tableName === "string") { switch (joinType) { case "left": { this.joinsNotNullableMap[tableName] = false; break; } case "right": { this.joinsNotNullableMap = Object.fromEntries( Object.entries(this.joinsNotNullableMap).map(([key]) => [key, false]) ); this.joinsNotNullableMap[tableName] = true; break; } case "cross": case "inner": { this.joinsNotNullableMap[tableName] = true; break; } case "full": { this.joinsNotNullableMap = Object.fromEntries( Object.entries(this.joinsNotNullableMap).map(([key]) => [key, false]) ); this.joinsNotNullableMap[tableName] = false; break; } } } return this; }; } /** * Executes a `left join` operation by adding another table to the current query. * * Calling this method associates each row of the table with the corresponding row from the joined table, if a match is found. If no matching row exists, it sets all columns of the joined table to null. * * See docs: {@link https://orm.drizzle.team/docs/joins#left-join} * * @param table the table to join. * @param on the `on` clause. * * @example * * ```ts * // Select all users and their pets * const usersWithPets: { user: User; pets: Pet | null; }[] = await db.select() * .from(users) * .leftJoin(pets, eq(users.id, pets.ownerId)) * * // Select userId and petId * const usersIdsAndPetIds: { userId: number; petId: number | null; }[] = await db.select({ * userId: users.id, * petId: pets.id, * }) * .from(users) * .leftJoin(pets, eq(users.id, pets.ownerId)) * ``` */ leftJoin = this.createJoin("left"); /** * Executes a `right join` operation by adding another table to the current query. * * Calling this method associates each row of the joined table with the corresponding row from the main table, if a match is found. If no matching row exists, it sets all columns of the main table to null. * * See docs: {@link https://orm.drizzle.team/docs/joins#right-join} * * @param table the table to join. * @param on the `on` clause. * * @example * * ```ts * // Select all users and their pets * const usersWithPets: { user: User | null; pets: Pet; }[] = await db.select() * .from(users) * .rightJoin(pets, eq(users.id, pets.ownerId)) * * // Select userId and petId * const usersIdsAndPetIds: { userId: number | null; petId: number; }[] = await db.select({ * userId: users.id, * petId: pets.id, * }) * .from(users) * .rightJoin(pets, eq(users.id, pets.ownerId)) * ``` */ rightJoin = this.createJoin("right"); /** * Executes an `inner join` operation, creating a new table by combining rows from two tables that have matching values. * * Calling this method retrieves rows that have corresponding entries in both joined tables. Rows without matching entries in either table are excluded, resulting in a table that includes only matching pairs. * * See docs: {@link https://orm.drizzle.team/docs/joins#inner-join} * * @param table the table to join. * @param on the `on` clause. * * @example * * ```ts * // Select all users and their pets * const usersWithPets: { user: User; pets: Pet; }[] = await db.select() * .from(users) * .innerJoin(pets, eq(users.id, pets.ownerId)) * * // Select userId and petId * const usersIdsAndPetIds: { userId: number; petId: number; }[] = await db.select({ * userId: users.id, * petId: pets.id, * }) * .from(users) * .innerJoin(pets, eq(users.id, pets.ownerId)) * ``` */ innerJoin = this.createJoin("inner"); /** * Executes a `full join` operation by combining rows from two tables into a new table. * * Calling this method retrieves all rows from both main and joined tables, merging rows with matching values and filling in `null` for non-matching columns. * * See docs: {@link https://orm.drizzle.team/docs/joins#full-join} * * @param table the table to join. * @param on the `on` clause. * * @example * * ```ts * // Select all users and their pets * const usersWithPets: { user: User | null; pets: Pet | null; }[] = await db.select() * .from(users) * .fullJoin(pets, eq(users.id, pets.ownerId)) * * // Select userId and petId * const usersIdsAndPetIds: { userId: number | null; petId: number | null; }[] = await db.select({ * userId: users.id, * petId: pets.id, * }) * .from(users) * .fullJoin(pets, eq(users.id, pets.ownerId)) * ``` */ fullJoin = this.createJoin("full"); /** * Executes a `cross join` operation by combining rows from two tables into a new table. * * Calling this method retrieves all rows from both main and joined tables, merging all rows from each table. * * See docs: {@link https://orm.drizzle.team/docs/joins#cross-join} * * @param table the table to join. * * @example * * ```ts * // Select all users, each user with every pet * const usersWithPets: { user: User; pets: Pet; }[] = await db.select() * .from(users) * .crossJoin(pets) * * // Select userId and petId * const usersIdsAndPetIds: { userId: number; petId: number; }[] = await db.select({ * userId: users.id, * petId: pets.id, * }) * .from(users) * .crossJoin(pets) * ``` */ crossJoin = this.createJoin("cross"); createSetOperator(type, isAll) { return (rightSelection) => { const rightSelect = typeof rightSelection === "function" ? rightSelection(getSQLiteSetOperators()) : rightSelection; if (!haveSameKeys(this.getSelectedFields(), rightSelect.getSelectedFields())) { throw new Error( "Set operator error (union / intersect / except): selected fields are not the same or are in a different order" ); } this.config.setOperators.push({ type, isAll, rightSelect }); return this; }; } /** * Adds `union` set operator to the query. * * Calling this method will combine the result sets of the `select` statements and remove any duplicate rows that appear across them. * * See docs: {@link https://orm.drizzle.team/docs/set-operations#union} * * @example * * ```ts * // Select all unique names from customers and users tables * await db.select({ name: users.name }) * .from(users) * .union( * db.select({ name: customers.name }).from(customers) * ); * // or * import { union } from 'drizzle-orm/sqlite-core' * * await union( * db.select({ name: users.name }).from(users), * db.select({ name: customers.name }).from(customers) * ); * ``` */ union = this.createSetOperator("union", false); /** * Adds `union all` set operator to the query. * * Calling this method will combine the result-set of the `select` statements and keep all duplicate rows that appear across them. * * See docs: {@link https://orm.drizzle.team/docs/set-operations#union-all} * * @example * * ```ts * // Select all transaction ids from both online and in-store sales * await db.select({ transaction: onlineSales.transactionId }) * .from(onlineSales) * .unionAll( * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) * ); * // or * import { unionAll } from 'drizzle-orm/sqlite-core' * * await unionAll( * db.select({ transaction: onlineSales.transactionId }).from(onlineSales), * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) * ); * ``` */ unionAll = this.createSetOperator("union", true); /** * Adds `intersect` set operator to the query. * * Calling this method will retain only the rows that are present in both result sets and eliminate duplicates. * * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect} * * @example * * ```ts * // Select course names that are offered in both departments A and B * await db.select({ courseName: depA.courseName }) * .from(depA) * .intersect( * db.select({ courseName: depB.courseName }).from(depB) * ); * // or * import { intersect } from 'drizzle-orm/sqlite-core' * * await intersect( * db.select({ courseName: depA.courseName }).from(depA), * db.select({ courseName: depB.courseName }).from(depB) * ); * ``` */ intersect = this.createSetOperator("intersect", false); /** * Adds `except` set operator to the query. * * Calling this method will retrieve all unique rows from the left query, except for the rows that are present in the result set of the right query. * * See docs: {@link https://orm.drizzle.team/docs/set-operations#except} * * @example * * ```ts * // Select all courses offered in department A but not in department B * await db.select({ courseName: depA.courseName }) * .from(depA) * .except( * db.select({ courseName: depB.courseName }).from(depB) * ); * // or * import { except } from 'drizzle-orm/sqlite-core' * * await except( * db.select({ courseName: depA.courseName }).from(depA), * db.select({ courseName: depB.courseName }).from(depB) * ); * ``` */ except = this.createSetOperator("except", false); /** @internal */ addSetOperators(setOperators) { this.config.setOperators.push(...setOperators); return this; } /** * Adds a `where` clause to the query. * * Calling this method will select only those rows that fulfill a specified condition. * * See docs: {@link https://orm.drizzle.team/docs/select#filtering} * * @param where the `where` clause. * * @example * You can use conditional operators and `sql function` to filter the rows to be selected. * * ```ts * // Select all cars with green color * await db.select().from(cars).where(eq(cars.color, 'green')); * // or * await db.select().from(cars).where(sql`${cars.color} = 'green'`) * ``` * * You can logically combine conditional operators with `and()` and `or()` operators: * * ```ts * // Select all BMW cars with a green color * await db.select().from(cars).where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); * * // Select all cars with the green or blue color * await db.select().from(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); * ``` */ where(where) { if (typeof where === "function") { where = where( new Proxy( this.config.fields, new SelectionProxyHandler({ sqlAliasedBehavior: "sql", sqlBehavior: "sql" }) ) ); } this.config.where = where; return this; } /** * Adds a `having` clause to the query. * * Calling this method will select only those rows that fulfill a specified condition. It is typically used with aggregate functions to filter the aggregated data based on a specified condition. * * See docs: {@link https://orm.drizzle.team/docs/select#aggregations} * * @param having the `having` clause. * * @example * * ```ts * // Select all brands with more than one car * await db.select({ * brand: cars.brand, * count: sql<number>`cast(count(${cars.id}) as int)`, * }) * .from(cars) * .groupBy(cars.brand) * .having(({ count }) => gt(count, 1)); * ``` */ having(having) { if (typeof having === "function") { having = having( new Proxy( this.config.fields, new SelectionProxyHandler({ sqlAliasedBehavior: "sql", sqlBehavior: "sql" }) ) ); } this.config.having = having; return this; } groupBy(...columns) { if (typeof columns[0] === "function") { const groupBy = columns[0]( new Proxy( this.config.fields, new SelectionProxyHandler({ sqlAliasedBehavior: "alias", sqlBehavior: "sql" }) ) ); this.config.groupBy = Array.isArray(groupBy) ? groupBy : [groupBy]; } else { this.config.groupBy = columns; } return this; } orderBy(...columns) { if (typeof columns[0] === "function") { const orderBy = columns[0]( new Proxy( this.config.fields, new SelectionProxyHandler({ sqlAliasedBehavior: "alias", sqlBehavior: "sql" }) ) ); const orderByArray = Array.isArray(orderBy) ? orderBy : [orderBy]; if (this.config.setOperators.length > 0) { this.config.setOperators.at(-1).orderBy = orderByArray; } else { this.config.orderBy = orderByArray; } } else { const orderByArray = columns; if (this.config.setOperators.length > 0) { this.config.setOperators.at(-1).orderBy = orderByArray; } else { this.config.orderBy = orderByArray; } } return this; } /** * Adds a `limit` clause to the query. * * Calling this method will set the maximum number of rows that will be returned by this query. * * See docs: {@link https://orm.drizzle.team/docs/select#limit--offset} * * @param limit the `limit` clause. * * @example * * ```ts * // Get the first 10 people from this query. * await db.select().from(people).limit(10); * ``` */ limit(limit) { if (this.config.setOperators.length > 0) { this.config.setOperators.at(-1).limit = limit; } else { this.config.limit = limit; } return this; } /** * Adds an `offset` clause to the query. * * Calling this method will skip a number of rows when returning results from this query. * * See docs: {@link https://orm.drizzle.team/docs/select#limit--offset} * * @param offset the `offset` clause. * * @example * * ```ts * // Get the 10th-20th people from this query. * await db.select().from(people).offset(10).limit(10); * ``` */ offset(offset) { if (this.config.setOperators.length > 0) { this.config.setOperators.at(-1).offset = offset; } else { this.config.offset = offset; } return this; } /** @internal */ getSQL() { return this.dialect.buildSelectQuery(this.config); } toSQL() { const { typings: _typings, ...rest } = this.dialect.sqlToQuery(this.getSQL()); return rest; } as(alias) { return new Proxy( new Subquery(this.getSQL(), this.config.fields, alias), new SelectionProxyHandler({ alias, sqlAliasedBehavior: "alias", sqlBehavior: "error" }) ); } /** @internal */ getSelectedFields() { return new Proxy( this.config.fields, new SelectionProxyHandler({ alias: this.tableName, sqlAliasedBehavior: "alias", sqlBehavior: "error" }) ); } $dynamic() { return this; } } class SQLiteSelectBase extends SQLiteSelectQueryBuilderBase { static [entityKind] = "SQLiteSelect"; /** @internal */ _prepare(isOneTimeQuery = true) { if (!this.session) { throw new Error("Cannot execute a query on a query builder. Please use a database instance instead."); } const fieldsList = orderSelectedFields(this.config.fields); const query = this.session[isOneTimeQuery ? "prepareOneTimeQuery" : "prepareQuery"]( this.dialect.sqlToQuery(this.getSQL()), fieldsList, "all", true ); query.joinsNotNullableMap = this.joinsNotNullableMap; return query; } prepare() { return this._prepare(false); } run = (placeholderValues) => { return this._prepare().run(placeholderValues); }; all = (placeholderValues) => { return this._prepare().all(placeholderValues); }; get = (placeholderValues) => { return this._prepare().get(placeholderValues); }; values = (placeholderValues) => { return this._prepare().values(placeholderValues); }; async execute() { return this.all(); } } applyMixins(SQLiteSelectBase, [QueryPromise]); function createSetOperator(type, isAll) { return (leftSelect, rightSelect, ...restSelects) => { const setOperators = [rightSelect, ...restSelects].map((select) => ({ type, isAll, rightSelect: select })); for (const setOperator of setOperators) { if (!haveSameKeys(leftSelect.getSelectedFields(), setOperator.rightSelect.getSelectedFields())) { throw new Error( "Set operator error (union / intersect / except): selected fields are not the same or are in a different order" ); } } return leftSelect.addSetOperators(setOperators); }; } const getSQLiteSetOperators = () => ({ union, unionAll, intersect, except }); const union = createSetOperator("union", false); const unionAll = createSetOperator("union", true); const intersect = createSetOperator("intersect", false); const except = createSetOperator("except", false); export { SQLiteSelectBase, SQLiteSelectBuilder, SQLiteSelectQueryBuilderBase, except, intersect, union, unionAll }; //# sourceMappingURL=select.js.map