var _a; import { EntityMetadata, helper, inspect, isRaw, LoadStrategy, LockMode, PopulateHint, QueryFlag, QueryHelper, raw, RawQueryFragment, Reference, ReferenceKind, serialize, Utils, ValidationError, } from '@mikro-orm/core'; import { JoinType, QueryType } from './enums.js'; import { QueryBuilderHelper } from './QueryBuilderHelper.js'; import { CriteriaNodeFactory } from './CriteriaNodeFactory.js'; import { NativeQueryBuilder } from './NativeQueryBuilder.js'; /** Matches 'path as alias' — safe because ORM property names are JS identifiers (no spaces). */ const FIELD_ALIAS_RE = /^(.+?)\s+as\s+(\w+)$/i; /** * SQL query builder with fluent interface. * * ```ts * const qb = orm.em.createQueryBuilder(Publisher); * qb.select('*') * .where({ * name: 'test 123', * type: PublisherType.GLOBAL, * }) * .orderBy({ * name: QueryOrder.DESC, * type: QueryOrder.ASC, * }) * .limit(2, 1); * * const publisher = await qb.getSingleResult(); * ``` */ export class QueryBuilder { metadata; driver; context; connectionType; em; loggerContext; #state = _a.createDefaultState(); #helper; #query; /** @internal */ static createDefaultState() { return { aliasCounter: 0, explicitAlias: false, populateHintFinalized: false, joins: {}, cond: {}, orderBy: [], groupBy: [], having: {}, comments: [], hintComments: [], subQueries: {}, aliases: {}, tptAlias: {}, ctes: [], tptJoinsApplied: false, autoJoinedPaths: [], populate: [], populateMap: {}, flags: new Set([QueryFlag.CONVERT_CUSTOM_TYPES]), finalized: false, joinedProps: new Map(), }; } get mainAlias() { this.ensureFromClause(); return this.#state.mainAlias; } get alias() { return this.mainAlias.aliasName; } get helper() { this.ensureFromClause(); return this.#helper; } get type() { return this.#state.type ?? QueryType.SELECT; } /** @internal */ get state() { return this.#state; } platform; /** * @internal */ constructor(entityName, metadata, driver, context, alias, connectionType, em, loggerContext) { this.metadata = metadata; this.driver = driver; this.context = context; this.connectionType = connectionType; this.em = em; this.loggerContext = loggerContext; this.platform = this.driver.getPlatform(); if (alias) { this.#state.aliasCounter++; this.#state.explicitAlias = true; } // @ts-expect-error union type does not match the overloaded method signature this.from(entityName, alias); } select(fields, distinct = false) { this.ensureNotFinalized(); this.#state.fields = Utils.asArray(fields).flatMap(f => { if (typeof f !== 'string') { // Normalize sql.ref('prop') and sql.ref('prop').as('alias') to string form if (isRaw(f) && f.sql === '??' && f.params.length === 1) { return this.resolveNestedPath(String(f.params[0])); } if (isRaw(f) && f.sql === '?? as ??' && f.params.length === 2) { return `${this.resolveNestedPath(String(f.params[0]))} as ${String(f.params[1])}`; } return f; } const asMatch = FIELD_ALIAS_RE.exec(f); if (asMatch) { return `${this.resolveNestedPath(asMatch[1].trim())} as ${asMatch[2]}`; } return this.resolveNestedPath(f); }); if (distinct) { this.#state.flags.add(QueryFlag.DISTINCT); } return this.init(QueryType.SELECT); } /** * Adds fields to an existing SELECT query. */ addSelect(fields) { this.ensureNotFinalized(); if (this.#state.type && this.#state.type !== QueryType.SELECT) { return this; } return this.select([...Utils.asArray(this.#state.fields), ...Utils.asArray(fields)]); } distinct() { this.ensureNotFinalized(); return this.setFlag(QueryFlag.DISTINCT); } distinctOn(fields) { this.ensureNotFinalized(); this.#state.distinctOn = Utils.asArray(fields); return this; } /** * Creates an INSERT query with the given data. * * @example * ```ts * await em.createQueryBuilder(User) * .insert({ name: 'John', email: 'john@example.com' }) * .execute(); * * // Bulk insert * await em.createQueryBuilder(User) * .insert([{ name: 'John' }, { name: 'Jane' }]) * .execute(); * ``` */ insert(data) { return this.init(QueryType.INSERT, data); } /** * Creates an UPDATE query with the given data. * Use `where()` to specify which rows to update. * * @example * ```ts * await em.createQueryBuilder(User) * .update({ name: 'John Doe' }) * .where({ id: 1 }) * .execute(); * ``` */ update(data) { return this.init(QueryType.UPDATE, data); } /** * Creates a DELETE query. * Use `where()` to specify which rows to delete. * * @example * ```ts * await em.createQueryBuilder(User) * .delete() * .where({ id: 1 }) * .execute(); * * // Or pass the condition directly * await em.createQueryBuilder(User) * .delete({ isActive: false }) * .execute(); * ``` */ delete(cond) { return this.init(QueryType.DELETE, undefined, cond); } /** * Creates a TRUNCATE query to remove all rows from the table. */ truncate() { return this.init(QueryType.TRUNCATE); } /** * Creates a COUNT query to count matching rows. * * @example * ```ts * const count = await em.createQueryBuilder(User) * .count() * .where({ isActive: true }) * .execute('get'); * ``` */ count(field, distinct = false) { if (field) { this.#state.fields = Utils.asArray(field); } else if (distinct || this.hasToManyJoins()) { this.#state.fields = this.mainAlias.meta.primaryKeys; } else { this.#state.fields = [raw('*')]; } if (distinct) { this.#state.flags.add(QueryFlag.DISTINCT); } return this.init(QueryType.COUNT); } join(field, alias, cond = {}, type = JoinType.innerJoin, path, schema) { this.joinReference(field, alias, cond, type, path, schema); return this; } innerJoin(field, alias, cond = {}, schema) { this.join(field, alias, cond, JoinType.innerJoin, undefined, schema); return this; } innerJoinLateral(field, alias, cond = {}, schema) { return this.join(field, alias, cond, JoinType.innerJoinLateral, undefined, schema); } leftJoin(field, alias, cond = {}, schema) { return this.join(field, alias, cond, JoinType.leftJoin, undefined, schema); } leftJoinLateral(field, alias, cond = {}, schema) { return this.join(field, alias, cond, JoinType.leftJoinLateral, undefined, schema); } /** * Adds a JOIN clause and automatically selects the joined entity's fields. * This is useful for eager loading related entities. * * @example * ```ts * const qb = em.createQueryBuilder(Book, 'b'); * qb.select('*') * .joinAndSelect('b.author', 'a') * .where({ 'a.name': 'John' }); * ``` */ joinAndSelect(field, alias, cond = {}, type = JoinType.innerJoin, path, fields, schema) { if (!this.#state.type) { this.select('*'); } let subquery; if (Array.isArray(field)) { const rawFragment = field[1] instanceof _a ? field[1].toRaw() : field[1]; subquery = this.platform.formatQuery(rawFragment.sql, rawFragment.params); field = field[0]; } const { prop, key } = this.joinReference(field, alias, cond, type, path, schema, subquery); const [fromAlias] = this.helper.splitField(field); if (subquery) { this.#state.joins[key].subquery = subquery; } const populate = this.#state.joinedProps.get(fromAlias); const item = { field: prop.name, strategy: LoadStrategy.JOINED, children: [] }; if (populate) { populate.children.push(item); } else { // root entity this.#state.populate.push(item); } this.#state.joinedProps.set(alias, item); this.addSelect(this.getFieldsForJoinedLoad(prop, alias, fields)); return this; } leftJoinAndSelect(field, alias, cond = {}, fields, schema) { return this.joinAndSelect(field, alias, cond, JoinType.leftJoin, undefined, fields, schema); } leftJoinLateralAndSelect(field, alias, cond = {}, fields, schema) { this.joinAndSelect(field, alias, cond, JoinType.leftJoinLateral, undefined, fields, schema); return this; } innerJoinAndSelect(field, alias, cond = {}, fields, schema) { return this.joinAndSelect(field, alias, cond, JoinType.innerJoin, undefined, fields, schema); } innerJoinLateralAndSelect(field, alias, cond = {}, fields, schema) { this.joinAndSelect(field, alias, cond, JoinType.innerJoinLateral, undefined, fields, schema); return this; } getFieldsForJoinedLoad(prop, alias, explicitFields) { const fields = []; const populate = []; const joinKey = Object.keys(this.#state.joins).find(join => join.endsWith(`#${alias}`)); const targetMeta = prop.targetMeta; const schema = this.#state.schema ?? (targetMeta.schema !== '*' ? targetMeta.schema : undefined); if (joinKey) { const path = this.#state.joins[joinKey].path.split('.').slice(1); let children = this.#state.populate; for (let i = 0; i < path.length; i++) { const child = children.filter(hint => { const [propName] = hint.field.split(':', 2); return propName === path[i]; }); children = child.flatMap(c => c.children); } populate.push(...children); } for (const p of targetMeta.getPrimaryProps()) { fields.push(...this.driver.mapPropToFieldNames(this, p, alias, targetMeta, schema)); } if (explicitFields && explicitFields.length > 0) { for (const field of explicitFields) { const [a, f] = this.helper.splitField(field); const p = targetMeta.properties[f]; if (p) { fields.push(...this.driver.mapPropToFieldNames(this, p, alias, targetMeta, schema)); } else { fields.push(`${a}.${f} as ${a}__${f}`); } } } targetMeta.props .filter(prop => { if (!explicitFields || explicitFields.length === 0) { return this.platform.shouldHaveColumn(prop, populate); } return prop.primary && !explicitFields.includes(prop.name) && !explicitFields.includes(`${alias}.${prop.name}`); }) .forEach(prop => fields.push(...this.driver.mapPropToFieldNames(this, prop, alias, targetMeta, schema))); return fields; } /** * Apply filters to the QB where condition. */ async applyFilters(filterOptions = {}) { /* v8 ignore next */ if (!this.em) { throw new Error('Cannot apply filters, this QueryBuilder is not attached to an EntityManager'); } const cond = await this.em.applyFilters(this.mainAlias.entityName, {}, filterOptions, 'read'); this.andWhere(cond); } /** * @internal */ scheduleFilterCheck(path) { this.#state.autoJoinedPaths.push(path); } /** * @internal */ async applyJoinedFilters(em, filterOptions) { for (const path of this.#state.autoJoinedPaths) { const join = this.getJoinForPath(path); if (join.type === JoinType.pivotJoin) { continue; } filterOptions = QueryHelper.mergePropertyFilters(join.prop.filters, filterOptions); let cond = await em.applyFilters(join.prop.targetMeta.class, join.cond, filterOptions, 'read'); const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, join.prop.targetMeta.class, cond); cond = criteriaNode.process(this, { matchPopulateJoins: true, filter: true, alias: join.alias, ignoreBranching: true, parentPath: join.path, }); if (Utils.hasObjectKeys(cond) || RawQueryFragment.hasObjectFragments(cond)) { // remove nested filters, we only care about scalars here, nesting would require another join branch for (const key of Object.keys(cond)) { if ( Utils.isPlainObject(cond[key]) && Object.keys(cond[key]).every( k => !(Utils.isOperator(k) && !['$some', '$none', '$every', '$size'].includes(k)), ) ) { delete cond[key]; } } if (Utils.hasObjectKeys(join.cond) || RawQueryFragment.hasObjectFragments(join.cond)) { /* v8 ignore next */ join.cond = { $and: [join.cond, cond] }; } else { join.cond = { ...cond }; } } } } withSubQuery(subQuery, alias) { this.ensureNotFinalized(); if (isRaw(subQuery)) { this.#state.subQueries[alias] = this.platform.formatQuery(subQuery.sql, subQuery.params); } else { this.#state.subQueries[alias] = subQuery.toString(); } return this; } where(cond, params, operator) { this.ensureNotFinalized(); let processedCond; if (isRaw(cond)) { const sql = this.platform.formatQuery(cond.sql, cond.params); processedCond = { [raw(`(${sql})`)]: Utils.asArray(params) }; operator ??= '$and'; } else if (typeof cond === 'string') { processedCond = { [raw(`(${cond})`, Utils.asArray(params))]: [] }; operator ??= '$and'; } else { processedCond = QueryHelper.processWhere({ where: cond, entityName: this.mainAlias.entityName, metadata: this.metadata, platform: this.platform, aliasMap: this.getAliasMap(), aliased: [QueryType.SELECT, QueryType.COUNT].includes(this.type), convertCustomTypes: this.#state.flags.has(QueryFlag.CONVERT_CUSTOM_TYPES), }); } const op = operator || params; const topLevel = !op || !(Utils.hasObjectKeys(this.#state.cond) || RawQueryFragment.hasObjectFragments(this.#state.cond)); const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processedCond); const ignoreBranching = this.#state.resolvedPopulateWhere === 'infer'; if ( [QueryType.UPDATE, QueryType.DELETE].includes(this.type) && criteriaNode.willAutoJoin(this, undefined, { ignoreBranching }) ) { // use sub-query to support joining this.setFlag(this.type === QueryType.UPDATE ? QueryFlag.UPDATE_SUB_QUERY : QueryFlag.DELETE_SUB_QUERY); this.select(this.mainAlias.meta.primaryKeys, true); } if (topLevel) { this.#state.cond = criteriaNode.process(this, { ignoreBranching }); } else if (Array.isArray(this.#state.cond[op])) { this.#state.cond[op].push(criteriaNode.process(this, { ignoreBranching })); } else { const cond1 = [this.#state.cond, criteriaNode.process(this, { ignoreBranching })]; this.#state.cond = { [op]: cond1 }; } if (this.#state.onConflict) { this.#state.onConflict[this.#state.onConflict.length - 1].where = this.helper.processOnConflictCondition( this.#state.cond, this.#state.schema, ); this.#state.cond = {}; } return this; } andWhere(cond, params) { return this.where(cond, params, '$and'); } orWhere(cond, params) { return this.where(cond, params, '$or'); } orderBy(orderBy) { return this.processOrderBy(orderBy, true); } andOrderBy(orderBy) { return this.processOrderBy(orderBy, false); } processOrderBy(orderBy, reset = true) { this.ensureNotFinalized(); if (reset) { this.#state.orderBy = []; } const selectAliases = this.getSelectAliases(); Utils.asArray(orderBy).forEach(orig => { // Shallow clone to avoid mutating the caller's object — safe because the clone // is only used within this loop iteration and `orig` is not referenced afterward. const o = { ...orig }; // Wrap known select aliases in raw() so they bypass property validation and alias prefixing for (const key of Object.keys(o)) { if (selectAliases.has(key)) { o[raw('??', [key])] = o[key]; delete o[key]; } } this.helper.validateQueryOrder(o); const processed = QueryHelper.processWhere({ where: o, entityName: this.mainAlias.entityName, metadata: this.metadata, platform: this.platform, aliasMap: this.getAliasMap(), aliased: [QueryType.SELECT, QueryType.COUNT].includes(this.type), convertCustomTypes: false, type: 'orderBy', }); this.#state.orderBy.push( CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processed).process(this, { matchPopulateJoins: true, type: 'orderBy', }), ); }); return this; } /** Collect custom aliases from select fields (stored as 'resolved as alias' strings by select()). */ getSelectAliases() { const aliases = new Set(); for (const field of this.#state.fields ?? []) { if (typeof field === 'string') { const m = FIELD_ALIAS_RE.exec(field); if (m) { aliases.add(m[2]); } } } return aliases; } groupBy(fields) { this.ensureNotFinalized(); this.#state.groupBy = Utils.asArray(fields).flatMap(f => { if (typeof f !== 'string') { // Normalize sql.ref('prop') to string for proper formula resolution if (isRaw(f) && f.sql === '??' && f.params.length === 1) { return this.resolveNestedPath(String(f.params[0])); } return f; } return this.resolveNestedPath(f); }); return this; } /** * Adds a HAVING clause to the query, typically used with GROUP BY. * * @example * ```ts * qb.select([raw('count(*) as count'), 'status']) * .groupBy('status') * .having({ count: { $gt: 5 } }); * ``` */ having(cond = {}, params, operator) { this.ensureNotFinalized(); if (typeof cond === 'string') { cond = { [raw(`(${cond})`, params)]: [] }; } const processed = CriteriaNodeFactory.createNode( this.metadata, this.mainAlias.entityName, cond, undefined, undefined, false, ).process(this, { type: 'having' }); if (!this.#state.having || !operator) { this.#state.having = processed; } else { const cond1 = [this.#state.having, processed]; this.#state.having = { [operator]: cond1 }; } return this; } andHaving(cond, params) { return this.having(cond, params, '$and'); } orHaving(cond, params) { return this.having(cond, params, '$or'); } onConflict(fields = []) { const meta = this.mainAlias.meta; this.ensureNotFinalized(); this.#state.onConflict ??= []; this.#state.onConflict.push({ fields: isRaw(fields) ? fields : Utils.asArray(fields).flatMap(f => { const key = f.toString(); /* v8 ignore next */ return meta.properties[key]?.fieldNames ?? [key]; }), }); return this; } ignore() { if (!this.#state.onConflict) { throw new Error('You need to call `qb.onConflict()` first to use `qb.ignore()`'); } this.#state.onConflict[this.#state.onConflict.length - 1].ignore = true; return this; } merge(data) { if (!this.#state.onConflict) { throw new Error('You need to call `qb.onConflict()` first to use `qb.merge()`'); } if (Array.isArray(data) && data.length === 0) { return this.ignore(); } this.#state.onConflict[this.#state.onConflict.length - 1].merge = data; return this; } returning(fields) { this.#state.returning = Utils.asArray(fields); return this; } /** * @internal */ populate(populate, populateWhere, populateFilter) { this.ensureNotFinalized(); this.#state.populate = populate; this.#state.populateWhere = populateWhere; this.#state.populateFilter = populateFilter; return this; } /** * Sets a LIMIT clause to restrict the number of results. * * @example * ```ts * qb.select('*').limit(10); // First 10 results * qb.select('*').limit(10, 20); // 10 results starting from offset 20 * ``` */ limit(limit, offset = 0) { this.ensureNotFinalized(); this.#state.limit = limit; if (offset) { this.offset(offset); } return this; } /** * Sets an OFFSET clause to skip a number of results. * * @example * ```ts * qb.select('*').limit(10).offset(20); // Results 21-30 * ``` */ offset(offset) { this.ensureNotFinalized(); this.#state.offset = offset; return this; } withSchema(schema) { this.ensureNotFinalized(); this.#state.schema = schema; return this; } setLockMode(mode, tables) { this.ensureNotFinalized(); if (mode != null && ![LockMode.OPTIMISTIC, LockMode.NONE].includes(mode) && !this.context) { throw ValidationError.transactionRequired(); } this.#state.lockMode = mode; this.#state.lockTables = tables; return this; } setFlushMode(flushMode) { this.ensureNotFinalized(); this.#state.flushMode = flushMode; return this; } setFlag(flag) { this.ensureNotFinalized(); this.#state.flags.add(flag); return this; } unsetFlag(flag) { this.ensureNotFinalized(); this.#state.flags.delete(flag); return this; } hasFlag(flag) { return this.#state.flags.has(flag); } cache(config = true) { this.ensureNotFinalized(); this.#state.cache = config; return this; } /** * Adds index hint to the FROM clause. */ indexHint(sql) { this.ensureNotFinalized(); this.#state.indexHint = sql; return this; } /** * Adds COLLATE clause to ORDER BY expressions. */ collation(collation) { this.ensureNotFinalized(); this.#state.collation = collation; return this; } /** * Prepend comment to the sql query using the syntax `/* ... *‍/`. Some characters are forbidden such as `/*, *‍/` and `?`. */ comment(comment) { this.ensureNotFinalized(); this.#state.comments.push(...Utils.asArray(comment)); return this; } /** * Add hints to the query using comment-like syntax `/*+ ... *‍/`. MySQL and Oracle use this syntax for optimizer hints. * Also various DB proxies and routers use this syntax to pass hints to alter their behavior. In other dialects the hints * are ignored as simple comments. */ hintComment(comment) { this.ensureNotFinalized(); this.#state.hintComments.push(...Utils.asArray(comment)); return this; } from(target, aliasName) { this.ensureNotFinalized(); if (target instanceof _a) { this.fromSubQuery(target, aliasName); } else if (typeof target === 'string' && !this.metadata.find(target)) { this.fromRawTable(target, aliasName); } else { if (aliasName && this.#state.mainAlias && Utils.className(target) !== this.#state.mainAlias.aliasName) { throw new Error( `Cannot override the alias to '${aliasName}' since a query already contains references to '${this.#state.mainAlias.aliasName}'`, ); } this.fromEntityName(target, aliasName); } return this; } getNativeQuery(processVirtualEntity = true) { if (this.#state.unionQuery) { if (!this.#query?.qb) { this.#query = {}; const nqb = this.platform.createNativeQueryBuilder(); nqb.select('*'); nqb.from(raw(`(${this.#state.unionQuery.sql})`, this.#state.unionQuery.params)); this.#query.qb = nqb; } return this.#query.qb; } if (this.#query?.qb) { return this.#query.qb; } this.#query = {}; this.finalize(); const qb = this.getQueryBase(processVirtualEntity); for (const cte of this.#state.ctes) { const query = cte.query; const opts = { columns: cte.columns, materialized: cte.materialized }; if (cte.recursive) { qb.withRecursive(cte.name, query, opts); } else { qb.with(cte.name, query, opts); } } const schema = this.getSchema(this.mainAlias); const isNotEmptyObject = obj => Utils.hasObjectKeys(obj) || RawQueryFragment.hasObjectFragments(obj); Utils.runIfNotEmpty( () => this.helper.appendQueryCondition(this.type, this.#state.cond, qb), this.#state.cond && !this.#state.onConflict, ); Utils.runIfNotEmpty( () => qb.groupBy(this.prepareFields(this.#state.groupBy, 'groupBy', schema)), isNotEmptyObject(this.#state.groupBy), ); Utils.runIfNotEmpty( () => this.helper.appendQueryCondition(this.type, this.#state.having, qb, undefined, 'having'), isNotEmptyObject(this.#state.having), ); Utils.runIfNotEmpty(() => { const queryOrder = this.helper.getQueryOrder( this.type, this.#state.orderBy, this.#state.populateMap, this.#state.collation, ); if (queryOrder.length > 0) { const sql = Utils.unique(queryOrder).join(', '); qb.orderBy(sql); return; } }, isNotEmptyObject(this.#state.orderBy)); Utils.runIfNotEmpty(() => qb.limit(this.#state.limit), this.#state.limit != null); Utils.runIfNotEmpty(() => qb.offset(this.#state.offset), this.#state.offset); Utils.runIfNotEmpty(() => qb.comment(this.#state.comments), this.#state.comments); Utils.runIfNotEmpty(() => qb.hintComment(this.#state.hintComments), this.#state.hintComments); Utils.runIfNotEmpty( () => this.helper.appendOnConflictClause(QueryType.UPSERT, this.#state.onConflict, qb), this.#state.onConflict, ); if (this.#state.lockMode) { this.helper.getLockSQL(qb, this.#state.lockMode, this.#state.lockTables, this.#state.joins); } this.processReturningStatement(qb, this.mainAlias.meta, this.#state.data, this.#state.returning); return (this.#query.qb = qb); } processReturningStatement(qb, meta, data, returning) { const usesReturningStatement = this.platform.usesReturningStatement() || this.platform.usesOutputStatement(); if (!meta || !data || !usesReturningStatement) { return; } // always respect explicit returning hint if (returning && returning.length > 0) { qb.returning(returning.map(field => this.helper.mapper(field, this.type))); return; } if (this.type === QueryType.INSERT) { const returningProps = meta.hydrateProps .filter( prop => prop.returning || (prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw)), ) .filter(prop => !(prop.name in data)); if (returningProps.length > 0) { qb.returning(Utils.flatten(returningProps.map(prop => prop.fieldNames))); } return; } if (this.type === QueryType.UPDATE) { const returningProps = meta.hydrateProps.filter(prop => prop.fieldNames && isRaw(data[prop.fieldNames[0]])); if (returningProps.length > 0) { qb.returning( returningProps.flatMap(prop => { if (prop.hasConvertToJSValueSQL) { const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]); const sql = prop.customType.convertToJSValueSQL(aliased, this.platform) + ' as ' + this.platform.quoteIdentifier(prop.fieldNames[0]); return [raw(sql)]; } return prop.fieldNames; }), ); } } } /** * Returns the query with parameters as wildcards. */ getQuery() { return this.toQuery().sql; } /** * Returns raw fragment representation of this QueryBuilder. */ toRaw() { const { sql, params } = this.toQuery(); return raw(sql, params); } toQuery() { if (this.#state.unionQuery) { return this.#state.unionQuery; } if (this.#query?.sql) { return { sql: this.#query.sql, params: this.#query.params }; } const query = this.getNativeQuery().compile(); this.#query.sql = query.sql; this.#query.params = query.params; return { sql: this.#query.sql, params: this.#query.params }; } /** * Returns the list of all parameters for this query. */ getParams() { return this.toQuery().params; } /** * Returns raw interpolated query string with all the parameters inlined. */ getFormattedQuery() { const query = this.toQuery(); return this.platform.formatQuery(query.sql, query.params); } /** * @internal */ getAliasForJoinPath(path, options) { if (!path || path === Utils.className(this.mainAlias.entityName)) { return this.mainAlias.aliasName; } const join = typeof path === 'string' ? this.getJoinForPath(path, options) : path; if (join?.path?.endsWith('[pivot]')) { return join.alias; } return join?.inverseAlias || join?.alias; } /** * @internal */ getJoinForPath(path, options) { const joins = Object.values(this.#state.joins); if (joins.length === 0) { return undefined; } let join = joins.find(j => j.path === path); if (options?.preferNoBranch) { join = joins.find(j => { return j.path?.replace(/\[\d+]|\[populate]/g, '') === path.replace(/\[\d+]|\[populate]/g, ''); }); } if (!join && options?.ignoreBranching) { join = joins.find(j => { return j.path?.replace(/\[\d+]/g, '') === path.replace(/\[\d+]/g, ''); }); } if (!join && options?.matchPopulateJoins && options?.ignoreBranching) { join = joins.find(j => { return j.path?.replace(/\[\d+]|\[populate]/g, '') === path.replace(/\[\d+]|\[populate]/g, ''); }); } if (!join && options?.matchPopulateJoins) { join = joins.find(j => { return j.path?.replace(/\[populate]/g, '') === path.replace(/\[populate]/g, ''); }); } return join; } /** * @internal */ getNextAlias(entityName = 'e') { entityName = Utils.className(entityName); return this.driver.config.getNamingStrategy().aliasName(entityName, this.#state.aliasCounter++); } /** * Registers a join for a specific polymorphic target type. * Used by the driver to create per-target LEFT JOINs for JOINED loading. * @internal */ addPolymorphicJoin(prop, targetMeta, ownerAlias, alias, type, path, schema) { // Override referencedColumnNames to use the specific target's PK columns // (polymorphic targets may have different PK column names, e.g. org_id vs user_id) const referencedColumnNames = targetMeta.getPrimaryProps().flatMap(pk => pk.fieldNames); const targetProp = { ...prop, targetMeta, referencedColumnNames }; const aliasedName = `${ownerAlias}.${prop.name}[${targetMeta.className}]#${alias}`; this.#state.joins[aliasedName] = this.helper.joinManyToOneReference( targetProp, ownerAlias, alias, type, {}, schema, ); this.#state.joins[aliasedName].path = path; this.createAlias(targetMeta.class, alias); } /** * @internal */ getAliasMap() { return Object.fromEntries(Object.entries(this.#state.aliases).map(([key, value]) => [key, value.entityName])); } /** * Executes this QB and returns the raw results, mapped to the property names (unless disabled via last parameter). * Use `method` to specify what kind of result you want to get (array/single/meta). */ async execute(method, options) { options = typeof options === 'boolean' ? { mapResults: options } : (options ?? {}); options.mergeResults ??= true; options.mapResults ??= true; const isRunType = [QueryType.INSERT, QueryType.UPDATE, QueryType.DELETE, QueryType.TRUNCATE].includes(this.type); method ??= isRunType ? 'run' : 'all'; if (!this.connectionType && (isRunType || this.context)) { this.connectionType = 'write'; } if (!this.#state.finalized && method === 'get' && this.type === QueryType.SELECT) { this.limit(1); } const query = this.toQuery(); const cached = await this.em?.tryCache(this.mainAlias.entityName, this.#state.cache, [ 'qb.execute', query.sql, query.params, method, ]); if (cached?.data !== undefined) { return cached.data; } const loggerContext = { id: this.em?.id, ...this.loggerContext }; const res = await this.getConnection().execute(query.sql, query.params, method, this.context, loggerContext); const meta = this.mainAlias.meta; if (!options.mapResults || !meta) { await this.em?.storeCache(this.#state.cache, cached, res); return res; } if (method === 'run') { return res; } const joinedProps = this.driver.joinedProps(meta, this.#state.populate); let mapped; if (Array.isArray(res)) { const map = {}; mapped = res.map(r => this.driver.mapResult(r, meta, this.#state.populate, this, map)); if (options.mergeResults && joinedProps.length > 0) { mapped = this.driver.mergeJoinedResult(mapped, this.mainAlias.meta, joinedProps); } } else { mapped = [this.driver.mapResult(res, meta, joinedProps, this)]; } if (method === 'get') { await this.em?.storeCache(this.#state.cache, cached, mapped[0]); return mapped[0]; } await this.em?.storeCache(this.#state.cache, cached, mapped); return mapped; } getConnection() { const write = !this.platform.getConfig().get('preferReadReplicas'); const type = this.connectionType || (write ? 'write' : 'read'); return this.driver.getConnection(type); } /** * Executes the query and returns an async iterable (async generator) that yields results one by one. * By default, the results are merged and mapped to entity instances, without adding them to the identity map. * You can disable merging and mapping by passing the options `{ mergeResults: false, mapResults: false }`. * This is useful for processing large datasets without loading everything into memory at once. * * ```ts * const qb = em.createQueryBuilder(Book, 'b'); * qb.select('*').where({ title: '1984' }).leftJoinAndSelect('b.author', 'a'); * * for await (const book of qb.stream()) { * // book is an instance of Book entity * console.log(book.title, book.author.name); * } * ``` */ async *stream(options) { options ??= {}; options.mergeResults ??= true; options.mapResults ??= true; const query = this.toQuery(); const loggerContext = { id: this.em?.id, ...this.loggerContext }; const res = this.getConnection().stream(query.sql, query.params, this.context, loggerContext); const meta = this.mainAlias.meta; if (options.rawResults || !meta) { yield* res; return; } const joinedProps = this.driver.joinedProps(meta, this.#state.populate); const stack = []; const hash = data => { return Utils.getPrimaryKeyHash(meta.primaryKeys.map(pk => data[pk])); }; for await (const row of res) { const mapped = this.driver.mapResult(row, meta, this.#state.populate, this); if (!options.mergeResults || joinedProps.length === 0) { yield this.mapResult(mapped, options.mapResults); continue; } if (stack.length > 0 && hash(stack[stack.length - 1]) !== hash(mapped)) { const res = this.driver.mergeJoinedResult(stack, this.mainAlias.meta, joinedProps); for (const row of res) { yield this.mapResult(row, options.mapResults); } stack.length = 0; } stack.push(mapped); } if (stack.length > 0) { const merged = this.driver.mergeJoinedResult(stack, this.mainAlias.meta, joinedProps); yield this.mapResult(merged[0], options.mapResults); } } /** * Alias for `qb.getResultList()` */ async getResult() { return this.getResultList(); } /** * Executes the query, returning array of results mapped to entity instances. */ async getResultList(limit) { await this.em.tryFlush(this.mainAlias.entityName, { flushMode: this.#state.flushMode }); const res = await this.execute('all', true); return this.mapResults(res, limit); } propagatePopulateHint(entity, hint) { helper(entity).__serializationContext.populate = hint.concat(helper(entity).__serializationContext.populate ?? []); hint.forEach(hint => { const [propName] = hint.field.split(':', 2); const value = Reference.unwrapReference(entity[propName]); if (Utils.isEntity(value)) { this.propagatePopulateHint(value, hint.children ?? []); } else if (Utils.isCollection(value)) { value.populated(); value.getItems(false).forEach(item => this.propagatePopulateHint(item, hint.children ?? [])); } }); } mapResult(row, map = true) { if (!map) { return row; } const entity = this.em.map(this.mainAlias.entityName, row, { schema: this.#state.schema }); this.propagatePopulateHint(entity, this.#state.populate); return entity; } mapResults(res, limit) { const entities = []; for (const row of res) { const entity = this.mapResult(row); this.propagatePopulateHint(entity, this.#state.populate); entities.push(entity); if (limit != null && --limit === 0) { break; } } return Utils.unique(entities); } /** * Executes the query, returning the first result or null */ async getSingleResult() { if (!this.#state.finalized) { this.limit(1); } const [res] = await this.getResultList(1); return res || null; } async getCount(field, distinct) { let res; if (this.type === QueryType.COUNT) { res = await this.execute('get', false); } else { const qb = this.#state.type === undefined ? this : this.clone(); qb.processPopulateHint(); // needs to happen sooner so `qb.hasToManyJoins()` reports correctly qb.count(field, distinct ?? qb.hasToManyJoins()) .limit(undefined) .offset(undefined) .orderBy([]); res = await qb.execute('get', false); } return res ? +res.count : 0; } /** * Executes the query, returning both array of results and total count query (without offset and limit). */ async getResultAndCount() { return [await this.clone().getResultList(), await this.clone().getCount()]; } as(aliasOrTargetEntity, alias) { const qb = this.getNativeQuery(); let finalAlias = aliasOrTargetEntity; /* v8 ignore next */ if (typeof aliasOrTargetEntity === 'string' && aliasOrTargetEntity.includes('.')) { throw new Error( 'qb.as(alias) no longer supports target entity name prefix, use qb.as(TargetEntity, key) signature instead', ); } if (alias) { const meta = this.metadata.get(aliasOrTargetEntity); /* v8 ignore next */ finalAlias = meta.properties[alias]?.fieldNames[0] ?? alias; } qb.as(finalAlias); // tag the instance, so it is possible to detect it easily Object.defineProperty(qb, '__as', { enumerable: false, value: finalAlias }); return qb; } /** * Combines the current query with one or more other queries using `UNION ALL`. * All queries must select the same columns. Returns a `QueryBuilder` that * can be used with `$in`, passed to `qb.from()`, or converted via `.getQuery()`, * `.getParams()`, `.toQuery()`, `.toRaw()`, etc. * * ```ts * const qb1 = em.createQueryBuilder(Employee).select('id').where(condition1); * const qb2 = em.createQueryBuilder(Employee).select('id').where(condition2); * const qb3 = em.createQueryBuilder(Employee).select('id').where(condition3); * const subquery = qb1.unionAll(qb2, qb3); * * const results = await em.find(Employee, { id: { $in: subquery } }); * ``` */ unionAll(...others) { return this.buildUnionQuery('union all', others); } /** * Combines the current query with one or more other queries using `UNION` (with deduplication). * All queries must select the same columns. Returns a `QueryBuilder` that * can be used with `$in`, passed to `qb.from()`, or converted via `.getQuery()`, * `.getParams()`, `.toQuery()`, `.toRaw()`, etc. * * ```ts * const qb1 = em.createQueryBuilder(Employee).select('id').where(condition1); * const qb2 = em.createQueryBuilder(Employee).select('id').where(condition2); * const subquery = qb1.union(qb2); * * const results = await em.find(Employee, { id: { $in: subquery } }); * ``` */ union(...others) { return this.buildUnionQuery('union', others); } buildUnionQuery(separator, others) { const all = [this, ...others]; const parts = []; const params = []; for (const qb of all) { const compiled = qb instanceof _a ? qb.toQuery() : qb.compile(); parts.push(`(${compiled.sql})`); params.push(...compiled.params); } const result = this.clone(true); result.#state.unionQuery = { sql: parts.join(` ${separator} `), params }; return result; } with(name, query, options) { return this.addCte(name, query, options); } withRecursive(name, query, options) { return this.addCte(name, query, options, true); } addCte(name, query, options, recursive) { this.ensureNotFinalized(); if (this.#state.ctes.some(cte => cte.name === name)) { throw new Error(`CTE with name '${name}' already exists`); } // Eagerly compile QueryBuilder to RawQueryFragment — later mutations to the sub-query won't be reflected const compiled = query instanceof _a ? query.toRaw() : query; this.#state.ctes.push({ name, query: compiled, recursive, columns: options?.columns, materialized: options?.materialized, }); return this; } clone(reset, preserve) { const qb = new _a( this.#state.mainAlias.entityName, this.metadata, this.driver, this.context, this.#state.mainAlias.aliasName, this.connectionType, this.em, ); if (reset !== true) { qb.#state = Utils.copy(this.#state); // CTEs contain NativeQueryBuilder instances that should not be deep-cloned qb.#state.ctes = this.#state.ctes.map(cte => ({ ...cte })); if (Array.isArray(reset)) { const fresh = _a.createDefaultState(); for (const key of reset) { qb.#state[key] = fresh[key]; } } } else if (preserve) { for (const key of preserve) { qb.#state[key] = Utils.copy(this.#state[key]); } } qb.#state.finalized = false; qb.#query = undefined; qb.#helper = qb.createQueryBuilderHelper(); return qb; } /** * Sets logger context for this query builder. */ setLoggerContext(context) { this.loggerContext = context; } /** * Gets logger context for this query builder. */ getLoggerContext() { this.loggerContext ??= {}; return this.loggerContext; } fromVirtual(meta) { if (typeof meta.expression === 'string') { return `(${meta.expression}) as ${this.platform.quoteIdentifier(this.alias)}`; } const res = meta.expression(this.em, this.#state.cond, {}); if (typeof res === 'string') { return `(${res}) as ${this.platform.quoteIdentifier(this.alias)}`; } if (res instanceof _a) { return `(${res.getFormattedQuery()}) as ${this.platform.quoteIdentifier(this.alias)}`; } if (isRaw(res)) { const query = this.platform.formatQuery(res.sql, res.params); return `(${query}) as ${this.platform.quoteIdentifier(this.alias)}`; } /* v8 ignore next */ return res; } /** * Adds a join from a property object. Used internally for TPT joins where the property * is synthetic (not in entity.properties) but defined on metadata (e.g., tptParentProp). * The caller must create the alias first via createAlias(). * @internal */ addPropertyJoin(prop, ownerAlias, alias, type, path, schema) { schema ??= prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta); const key = `[tpt]${ownerAlias}#${alias}`; this.#state.joins[key] = prop.kind === ReferenceKind.MANY_TO_ONE ? this.helper.joinManyToOneReference(prop, ownerAlias, alias, type, {}, schema) : this.helper.joinOneToReference(prop, ownerAlias, alias, type, {}, schema); this.#state.joins[key].path = path; return key; } joinReference(field, alias, cond, type, path, schema, subquery) { this.ensureNotFinalized(); if (typeof field === 'object') { const prop = { name: '__subquery__', kind: ReferenceKind.MANY_TO_ONE, }; if (field instanceof _a) { prop.type = Utils.className(field.mainAlias.entityName); prop.targetMeta = field.mainAlias.meta; field = field.getNativeQuery(); } if (isRaw(field)) { field = this.platform.formatQuery(field.sql, field.params); } const key = `${this.alias}.${prop.name}#${alias}`; this.#state.joins[key] = { prop, alias, type, cond, schema, subquery: field.toString(), ownerAlias: this.alias, }; return { prop, key }; } if (!subquery && type.includes('lateral')) { throw new Error(`Lateral join can be used only with a sub-query.`); } const [fromAlias, fromField] = this.helper.splitField(field); const q = str => `'${str}'`; if (!this.#state.aliases[fromAlias]) { throw new Error( `Trying to join ${q(fromField)} with alias ${q(fromAlias)}, but ${q(fromAlias)} is not a known alias. Available aliases are: ${Object.keys(this.#state.aliases).map(q).join(', ')}.`, ); } const entityName = this.#state.aliases[fromAlias].entityName; const meta = this.metadata.get(entityName); const prop = meta.properties[fromField]; if (!prop) { throw new Error( `Trying to join ${q(field)}, but ${q(fromField)} is not a defined relation on ${meta.className}.`, ); } // For TPT inheritance, owning relations (M:1 and owning 1:1) may have FK columns in a parent table // Resolve the correct alias for the table that owns the FK column const ownerAlias = prop.kind === ReferenceKind.MANY_TO_ONE || (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner) ? this.helper.getTPTAliasForProperty(fromField, fromAlias) : fromAlias; this.createAlias(prop.targetMeta.class, alias); cond = QueryHelper.processWhere({ where: cond, entityName: this.mainAlias.entityName, metadata: this.metadata, platform: this.platform, aliasMap: this.getAliasMap(), aliased: [QueryType.SELECT, QueryType.COUNT].includes(this.type), }); const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, prop.targetMeta.class, cond); cond = criteriaNode.process(this, { ignoreBranching: true, alias }); let aliasedName = `${fromAlias}.${prop.name}#${alias}`; path ??= `${Object.values(this.#state.joins).find(j => j.alias === fromAlias)?.path ?? Utils.className(entityName)}.${prop.name}`; if (prop.kind === ReferenceKind.ONE_TO_MANY) { this.#state.joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema); this.#state.joins[aliasedName].path ??= path; } else if (prop.kind === ReferenceKind.MANY_TO_MANY) { let pivotAlias = alias; if (type !== JoinType.pivotJoin) { const oldPivotAlias = this.getAliasForJoinPath(path + '[pivot]'); pivotAlias = oldPivotAlias ?? this.getNextAlias(prop.pivotEntity); aliasedName = `${fromAlias}.${prop.name}#${pivotAlias}`; } const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path, schema); Object.assign(this.#state.joins, joins); this.createAlias(prop.pivotEntity, pivotAlias); this.#state.joins[aliasedName].path ??= path; aliasedName = Object.keys(joins)[1]; } else if (prop.kind === ReferenceKind.ONE_TO_ONE) { this.#state.joins[aliasedName] = this.helper.joinOneToReference(prop, ownerAlias, alias, type, cond, schema); this.#state.joins[aliasedName].path ??= path; } else { // MANY_TO_ONE this.#state.joins[aliasedName] = this.helper.joinManyToOneReference(prop, ownerAlias, alias, type, cond, schema); this.#state.joins[aliasedName].path ??= path; } return { prop, key: aliasedName }; } prepareFields(fields, type = 'where', schema) { const ret = []; const getFieldName = (name, customAlias) => { const alias = customAlias ?? (type === 'groupBy' ? null : undefined); return this.helper.mapper(name, this.type, undefined, alias, schema); }; fields.forEach(originalField => { if (typeof originalField !== 'string') { ret.push(originalField); return; } // Strip 'as alias' suffix if present — the alias is passed to mapper at the end let field = originalField; let customAlias; const asMatch = FIELD_ALIAS_RE.exec(originalField); if (asMatch) { field = asMatch[1].trim(); customAlias = asMatch[2]; } const join = Object.keys(this.#state.joins).find(k => field === k.substring(0, k.indexOf('#'))); if (join && type === 'where') { ret.push(...this.helper.mapJoinColumns(this.type, this.#state.joins[join])); return; } const [a, f] = this.helper.splitField(field); const prop = this.helper.getProperty(f, a); /* v8 ignore next */ if (prop && [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind)) { return; } if (prop?.persist === false && !prop.embedded && !prop.formula && type === 'where') { return; } if (prop?.embedded || (prop?.kind === ReferenceKind.EMBEDDED && prop.object)) { const name = prop.embeddedPath?.join('.') ?? prop.fieldNames[0]; const aliased = this.#state.aliases[a] ? `${a}.${name}` : name; ret.push(getFieldName(aliased, customAlias)); return; } if (prop?.kind === ReferenceKind.EMBEDDED) { if (customAlias) { throw new Error( `Cannot use 'as ${customAlias}' alias on embedded property '${field}' because it expands to multiple columns. Alias individual fields instead (e.g. '${field}.propertyName as ${customAlias}').`, ); } const nest = prop => { for (const childProp of Object.values(prop.embeddedProps)) { if ( childProp.fieldNames && (childProp.kind !== ReferenceKind.EMBEDDED || childProp.object) && childProp.persist !== false ) { ret.push(getFieldName(childProp.fieldNames[0])); } else { nest(childProp); } } }; nest(prop); return; } if (prop && prop.fieldNames.length > 1 && !prop.fieldNames.includes(f)) { if (customAlias) { throw new Error( `Cannot use 'as ${customAlias}' alias on '${field}' because it expands to multiple columns (${prop.fieldNames.join(', ')}).`, ); } ret.push(...prop.fieldNames.map(f => getFieldName(f))); return; } ret.push(getFieldName(field, customAlias)); }); const requiresSQLConversion = this.mainAlias.meta.props.filter( p => p.hasConvertToJSValueSQL && p.persist !== false, ); if ( this.#state.flags.has(QueryFlag.CONVERT_CUSTOM_TYPES) && (fields.includes('*') || fields.includes(`${this.mainAlias.aliasName}.*`)) && requiresSQLConversion.length > 0 ) { for (const p of requiresSQLConversion) { ret.push(this.helper.mapper(p.name, this.type)); } } for (const f of Object.keys(this.#state.populateMap)) { if (type === 'where' && this.#state.joins[f]) { ret.push(...this.helper.mapJoinColumns(this.type, this.#state.joins[f])); } } return Utils.unique(ret); } /** * Resolves nested paths like `a.books.title` to their actual field references. * Auto-joins relations as needed and returns `{alias}.{field}`. * For embeddeds: navigates into flattened embeddeds to return the correct field name. */ resolveNestedPath(field) { if (typeof field !== 'string' || !field.includes('.')) { return field; } const parts = field.split('.'); // Simple alias.property case - let prepareFields handle it if (parts.length === 2 && this.#state.aliases[parts[0]]) { return field; } // Start with root alias let currentAlias = parts[0]; let currentMeta = this.#state.aliases[currentAlias] ? this.metadata.get(this.#state.aliases[currentAlias].entityName) : this.mainAlias.meta; // If first part is not an alias, it's a property of the main entity if (!this.#state.aliases[currentAlias]) { currentAlias = this.mainAlias.aliasName; parts.unshift(currentAlias); } // Walk through the path parts (skip the alias) for (let i = 1; i < parts.length; i++) { const propName = parts[i]; const prop = currentMeta.properties[propName]; if (!prop) { return field; // Unknown property, return as-is for raw SQL support } const isLastPart = i === parts.length - 1; // Handle embedded properties - navigate into flattened embeddeds if (prop.kind === ReferenceKind.EMBEDDED) { if (prop.object) { return `${currentAlias}.${propName}`; } // Navigate through remaining path to find the leaf property const remainingPath = parts.slice(i + 1); let embeddedProp = prop; for (const part of remainingPath) { embeddedProp = embeddedProp?.embeddedProps?.[part]; if (embeddedProp?.object && embeddedProp.fieldNames?.[0]) { return `${currentAlias}.${embeddedProp.fieldNames[0]}`; } } return `${currentAlias}.${embeddedProp?.fieldNames?.[0] ?? propName}`; } // Handle relations - auto-join if not the last part if ( prop.kind === ReferenceKind.MANY_TO_ONE || prop.kind === ReferenceKind.ONE_TO_ONE || prop.kind === ReferenceKind.ONE_TO_MANY || prop.kind === ReferenceKind.MANY_TO_MANY ) { if (isLastPart) { return `${currentAlias}.${propName}`; } // Find existing join or create new one const joinPath = parts.slice(0, i + 1).join('.'); const existingJoinKey = Object.keys(this.#state.joins).find(k => { const join = this.#state.joins[k]; // Check by path or by key prefix (key format is `alias.field#joinAlias`) return join.path === joinPath || k.startsWith(`${currentAlias}.${propName}#`); }); let joinAlias; if (existingJoinKey) { joinAlias = this.#state.joins[existingJoinKey].alias; } else { joinAlias = this.getNextAlias(prop.targetMeta?.className ?? propName); this.join(`${currentAlias}.${propName}`, joinAlias, {}, JoinType.leftJoin); } currentAlias = joinAlias; currentMeta = prop.targetMeta; continue; } // Scalar property - return it (if not last part, it's an invalid path but let SQL handle it) return `${currentAlias}.${propName}`; } return field; } init(type, data, cond) { this.ensureNotFinalized(); this.#state.type = type; if ([QueryType.UPDATE, QueryType.DELETE].includes(type) && Utils.hasObjectKeys(this.#state.cond)) { throw new Error( `You are trying to call \`qb.where().${type.toLowerCase()}()\`. Calling \`qb.${type.toLowerCase()}()\` before \`qb.where()\` is required.`, ); } if (!this.helper.isTableNameAliasRequired(type)) { this.#state.fields = undefined; } if (data) { if (Utils.isEntity(data)) { data = this.em?.getComparator().prepareEntity(data) ?? serialize(data); } this.#state.data = this.helper.processData(data, this.#state.flags.has(QueryFlag.CONVERT_CUSTOM_TYPES), false); } if (cond) { this.where(cond); } return this; } getQueryBase(processVirtualEntity) { const qb = this.platform.createNativeQueryBuilder().setFlags(this.#state.flags); const { subQuery, aliasName, entityName, meta, rawTableName } = this.mainAlias; const requiresAlias = this.#state.finalized && (this.#state.explicitAlias || this.helper.isTableNameAliasRequired(this.type)); const alias = requiresAlias ? aliasName : undefined; const schema = this.getSchema(this.mainAlias); const tableName = rawTableName ? rawTableName : subQuery instanceof NativeQueryBuilder ? subQuery.as(aliasName) : subQuery ? raw(`(${subQuery.sql}) as ${this.platform.quoteIdentifier(aliasName)}`, subQuery.params) : this.helper.getTableName(entityName); const joinSchema = this.#state.schema ?? this.em?.schema ?? schema; const schemaOverride = this.#state.schema ?? this.em?.schema; if (meta.virtual && processVirtualEntity) { qb.from(raw(this.fromVirtual(meta)), { indexHint: this.#state.indexHint }); } else { qb.from(tableName, { schema: rawTableName ? undefined : schema, alias, indexHint: this.#state.indexHint, }); } switch (this.type) { case QueryType.SELECT: qb.select(this.prepareFields(this.#state.fields, 'where', schema)); if (this.#state.distinctOn) { qb.distinctOn(this.prepareFields(this.#state.distinctOn, 'where', schema)); } else if (this.#state.flags.has(QueryFlag.DISTINCT)) { qb.distinct(); } this.helper.processJoins(qb, this.#state.joins, joinSchema, schemaOverride); break; case QueryType.COUNT: { const fields = this.#state.fields.map(f => this.helper.mapper(f, this.type, undefined, undefined, schema)); qb.count(fields, this.#state.flags.has(QueryFlag.DISTINCT)); this.helper.processJoins(qb, this.#state.joins, joinSchema, schemaOverride); break; } case QueryType.INSERT: qb.insert(this.#state.data); break; case QueryType.UPDATE: qb.update(this.#state.data); this.helper.processJoins(qb, this.#state.joins, joinSchema, schemaOverride); this.helper.updateVersionProperty(qb, this.#state.data); break; case QueryType.DELETE: qb.delete(); break; case QueryType.TRUNCATE: qb.truncate(); break; } return qb; } applyDiscriminatorCondition() { const meta = this.mainAlias.meta; if (meta.root.inheritanceType !== 'sti' || !meta.discriminatorValue) { return; } const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.get(cls)); const children = []; const lookUpChildren = (ret, type) => { const children = types.filter(meta2 => meta2.extends === type); children.forEach(m => lookUpChildren(ret, m.class)); ret.push(...children.filter(c => c.discriminatorValue)); return children; }; lookUpChildren(children, meta.class); this.andWhere({ [meta.root.discriminatorColumn]: children.length > 0 ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] } : meta.discriminatorValue, }); } /** * Ensures TPT joins are applied. Can be called early before finalize() to populate * the _tptAlias map for use in join resolution. Safe to call multiple times. * @internal */ ensureTPTJoins() { this.applyTPTJoins(); } /** * For TPT (Table-Per-Type) inheritance: INNER JOINs parent tables. * When querying a child entity, we need to join all parent tables. * Field selection is handled separately in addTPTParentFields(). */ applyTPTJoins() { const meta = this.mainAlias.meta; if ( meta?.inheritanceType !== 'tpt' || !meta.tptParent || ![QueryType.SELECT, QueryType.COUNT].includes(this.type) ) { return; } if (this.#state.tptJoinsApplied) { return; } this.#state.tptJoinsApplied = true; let childMeta = meta; let childAlias = this.mainAlias.aliasName; while (childMeta.tptParent) { const parentMeta = childMeta.tptParent; const parentAlias = this.getNextAlias(parentMeta.className); this.createAlias(parentMeta.class, parentAlias); this.#state.tptAlias[parentMeta.className] = parentAlias; this.addPropertyJoin( childMeta.tptParentProp, childAlias, parentAlias, JoinType.innerJoin, `[tpt]${childMeta.className}`, ); childMeta = parentMeta; childAlias = parentAlias; } } /** * For TPT inheritance: adds field selections from parent tables. */ addTPTParentFields() { const meta = this.mainAlias.meta; if ( meta?.inheritanceType !== 'tpt' || !meta.tptParent || ![QueryType.SELECT, QueryType.COUNT].includes(this.type) ) { return; } if (!this.#state.fields?.includes('*') && !this.#state.fields?.includes(`${this.mainAlias.aliasName}.*`)) { return; } let parentMeta = meta.tptParent; while (parentMeta) { const parentAlias = this.#state.tptAlias[parentMeta.className]; if (parentAlias) { const schema = parentMeta.schema === '*' ? '*' : this.driver.getSchemaName(parentMeta); parentMeta.ownProps .filter(prop => this.platform.shouldHaveColumn(prop, [])) .forEach(prop => this.#state.fields.push(...this.driver.mapPropToFieldNames(this, prop, parentAlias, parentMeta, schema)), ); } parentMeta = parentMeta.tptParent; } } /** * For TPT polymorphic queries: LEFT JOINs all child tables when querying a TPT base class. * Adds discriminator and child fields to determine and load the concrete type. */ applyTPTPolymorphicJoins() { const meta = this.mainAlias.meta; const descendants = meta?.allTPTDescendants; if (!descendants?.length || ![QueryType.SELECT, QueryType.COUNT].includes(this.type)) { return; } if (!this.#state.fields?.includes('*') && !this.#state.fields?.includes(`${this.mainAlias.aliasName}.*`)) { return; } // LEFT JOIN each descendant table and add their fields for (const childMeta of descendants) { const childAlias = this.getNextAlias(childMeta.className); this.createAlias(childMeta.class, childAlias); this.#state.tptAlias[childMeta.className] = childAlias; this.addPropertyJoin( childMeta.tptInverseProp, this.mainAlias.aliasName, childAlias, JoinType.leftJoin, `[tpt]${meta.className}`, ); // Add child fields const schema = childMeta.schema === '*' ? '*' : this.driver.getSchemaName(childMeta); childMeta.ownProps .filter(prop => !prop.primary && this.platform.shouldHaveColumn(prop, [])) .forEach(prop => this.#state.fields.push(...this.driver.mapPropToFieldNames(this, prop, childAlias, childMeta, schema)), ); } // Add computed discriminator (CASE WHEN to determine concrete type) // descendants is pre-sorted by depth (deepest first) during discovery if (meta.tptDiscriminatorColumn) { this.#state.fields.push( this.driver.buildTPTDiscriminatorExpression(meta, descendants, this.#state.tptAlias, this.mainAlias.aliasName), ); } } finalize() { if (this.#state.finalized) { return; } if (!this.#state.type) { this.select('*'); } const meta = this.mainAlias.meta; this.applyDiscriminatorCondition(); this.applyTPTJoins(); this.addTPTParentFields(); this.applyTPTPolymorphicJoins(); this.processPopulateHint(); this.processNestedJoins(); if (meta && (this.#state.fields?.includes('*') || this.#state.fields?.includes(`${this.mainAlias.aliasName}.*`))) { const schema = this.getSchema(this.mainAlias); // Create a column mapping with unquoted aliases - quoting should be handled by the user via `quote` helper // For TPT, use helper to resolve correct alias per property (inherited props use parent alias) const quotedMainAlias = this.platform.quoteIdentifier(this.mainAlias.aliasName).toString(); const columns = meta.createColumnMappingObject( prop => this.helper.getTPTAliasForProperty(prop.name, this.mainAlias.aliasName), quotedMainAlias, ); meta.props .filter(prop => prop.formula && (!prop.lazy || this.#state.flags.has(QueryFlag.INCLUDE_LAZY_FORMULAS))) .map(prop => { const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]); const table = this.helper.createFormulaTable(quotedMainAlias, meta, schema); return `${this.driver.evaluateFormula(prop.formula, columns, table)} as ${aliased}`; }) .filter( field => !this.#state.fields.some(f => { if (isRaw(f)) { return f.sql === field && f.params.length === 0; } return f === field; }), ) .forEach(field => this.#state.fields.push(raw(field))); } QueryHelper.processObjectParams(this.#state.data); QueryHelper.processObjectParams(this.#state.cond); QueryHelper.processObjectParams(this.#state.having); // automatically enable paginate flag when we detect to-many joins, but only if there is no `group by` clause if ( !this.#state.flags.has(QueryFlag.DISABLE_PAGINATE) && this.#state.groupBy.length === 0 && this.hasToManyJoins() ) { this.#state.flags.add(QueryFlag.PAGINATE); } if ( meta && !meta.virtual && this.#state.flags.has(QueryFlag.PAGINATE) && !this.#state.flags.has(QueryFlag.DISABLE_PAGINATE) && (this.#state.limit > 0 || this.#state.offset > 0) ) { this.wrapPaginateSubQuery(meta); } if ( meta && (this.#state.flags.has(QueryFlag.UPDATE_SUB_QUERY) || this.#state.flags.has(QueryFlag.DELETE_SUB_QUERY)) ) { this.wrapModifySubQuery(meta); } this.#state.finalized = true; } /** @internal */ processPopulateHint() { if (this.#state.populateHintFinalized) { return; } const meta = this.mainAlias.meta; if (meta && this.#state.flags.has(QueryFlag.AUTO_JOIN_ONE_TO_ONE_OWNER)) { const relationsToPopulate = this.#state.populate.map(({ field }) => field); meta.relations .filter( prop => prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name) && !relationsToPopulate.includes(`${prop.name}:ref`), ) .map(prop => ({ field: `${prop.name}:ref` })) .forEach(item => this.#state.populate.push(item)); } this.#state.populate.forEach(({ field }) => { const [fromAlias, fromField] = this.helper.splitField(field); const aliasedField = `${fromAlias}.${fromField}`; const join = Object.keys(this.#state.joins).find(k => `${aliasedField}#${this.#state.joins[k].alias}` === k); if (join && this.#state.joins[join] && this.helper.isOneToOneInverse(fromField)) { this.#state.populateMap[join] = this.#state.joins[join].alias; return; } if (meta && this.helper.isOneToOneInverse(fromField)) { const prop = meta.properties[fromField]; const alias = this.getNextAlias(prop.pivotEntity ?? prop.targetMeta.class); const aliasedName = `${fromAlias}.${prop.name}#${alias}`; this.#state.joins[aliasedName] = this.helper.joinOneToReference( prop, this.mainAlias.aliasName, alias, JoinType.leftJoin, ); this.#state.joins[aliasedName].path = `${Object.values(this.#state.joins).find(j => j.alias === fromAlias)?.path ?? meta.className}.${prop.name}`; this.#state.populateMap[aliasedName] = this.#state.joins[aliasedName].alias; this.createAlias(prop.targetMeta.class, alias); } }); this.processPopulateWhere(false); this.processPopulateWhere(true); this.#state.populateHintFinalized = true; } processPopulateWhere(filter) { const value = filter ? this.#state.populateFilter : this.#state.populateWhere; if (value == null || value === PopulateHint.ALL) { return; } let joins = Object.values(this.#state.joins); for (const join of joins) { join.cond_ ??= join.cond; join.cond = { ...join.cond }; } if (typeof value === 'object') { const cond = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, value).process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true, filter, }); // there might be new joins created by processing the `populateWhere` object joins = Object.values(this.#state.joins); this.mergeOnConditions(joins, cond, filter); } } mergeOnConditions(joins, cond, filter, op) { for (const k of Object.keys(cond)) { if (Utils.isOperator(k)) { if (Array.isArray(cond[k])) { cond[k].forEach(c => this.mergeOnConditions(joins, c, filter, k)); } /* v8 ignore next */ this.mergeOnConditions(joins, cond[k], filter, k); } const [alias] = this.helper.splitField(k); const join = joins.find(j => j.alias === alias); if (join) { const parentJoin = joins.find(j => j.alias === join.ownerAlias); // https://stackoverflow.com/a/56815807/3665878 if (parentJoin && !filter) { const nested = (parentJoin.nested ??= new Set()); join.type = join.type === JoinType.innerJoin || [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(parentJoin.prop.kind) ? JoinType.nestedInnerJoin : JoinType.nestedLeftJoin; nested.add(join); } if (join.cond[k]) { /* v8 ignore next */ join.cond = { [op ?? '$and']: [join.cond, { [k]: cond[k] }] }; } else if (op === '$or') { join.cond.$or ??= []; join.cond.$or.push({ [k]: cond[k] }); } else { join.cond = { ...join.cond, [k]: cond[k] }; } } } } /** * When adding an inner join on a left joined relation, we need to nest them, * otherwise the inner join could discard rows of the root table. */ processNestedJoins() { if (this.#state.flags.has(QueryFlag.DISABLE_NESTED_INNER_JOIN)) { return; } const joins = Object.values(this.#state.joins); const lookupParentGroup = j => { return j.nested ?? (j.parent ? lookupParentGroup(j.parent) : undefined); }; for (const join of joins) { if (join.type === JoinType.innerJoin) { join.parent = joins.find(j => j.alias === join.ownerAlias); // https://stackoverflow.com/a/56815807/3665878 if (join.parent?.type === JoinType.leftJoin || join.parent?.type === JoinType.nestedLeftJoin) { const nested = (join.parent.nested ??= new Set()); join.type = join.type === JoinType.innerJoin ? JoinType.nestedInnerJoin : JoinType.nestedLeftJoin; nested.add(join); } else if (join.parent?.type === JoinType.nestedInnerJoin) { const group = lookupParentGroup(join.parent); const nested = group ?? (join.parent.nested ??= new Set()); join.type = join.type === JoinType.innerJoin ? JoinType.nestedInnerJoin : JoinType.nestedLeftJoin; nested.add(join); } } } } hasToManyJoins() { return Object.values(this.#state.joins).some(join => { return [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(join.prop.kind); }); } wrapPaginateSubQuery(meta) { const schema = this.getSchema(this.mainAlias); const pks = this.prepareFields(meta.primaryKeys, 'sub-query', schema); const subQuery = this.clone(['orderBy', 'fields', 'lockMode', 'lockTables']) .select(pks) .groupBy(pks) .limit(this.#state.limit); // revert the on conditions added via populateWhere, we want to apply those only once for (const join of Object.values(subQuery.#state.joins)) { if (join.cond_) { join.cond = join.cond_; } } if (this.#state.offset) { subQuery.offset(this.#state.offset); } const addToSelect = []; if (this.#state.orderBy.length > 0) { const orderBy = []; for (const orderMap of this.#state.orderBy) { for (const field of Utils.getObjectQueryKeys(orderMap)) { const direction = orderMap[field]; if (RawQueryFragment.isKnownFragmentSymbol(field)) { orderBy.push({ [field]: direction }); continue; } const [a, f] = this.helper.splitField(field); const prop = this.helper.getProperty(f, a); const type = this.platform.castColumn(prop); const fieldName = this.helper.mapper(field, this.type, undefined, null); if (!prop?.persist && !prop?.formula && !prop?.hasConvertToJSValueSQL && !pks.includes(fieldName)) { addToSelect.push(fieldName); } const quoted = this.platform.quoteIdentifier(fieldName); const key = raw(`min(${quoted}${type})`); orderBy.push({ [key]: direction }); } } subQuery.orderBy(orderBy); } subQuery.#state.finalized = true; const innerQuery = subQuery.as(this.mainAlias.aliasName).clear('select').select(pks); if (addToSelect.length > 0) { addToSelect.forEach(prop => { const field = this.#state.fields.find(field => { if (typeof field === 'object' && field && '__as' in field) { return field.__as === prop; } if (isRaw(field)) { // not perfect, but should work most of the time, ideally we should check only the alias (`... as alias`) return field.sql.includes(prop); } return false; }); /* v8 ignore next */ if (isRaw(field)) { innerQuery.select(field); } else if (field instanceof NativeQueryBuilder) { innerQuery.select(field.toRaw()); } else if (field) { innerQuery.select(field); } }); } // multiple sub-queries are needed to get around mysql limitations with order by + limit + where in + group by (o.O) // https://stackoverflow.com/questions/17892762/mysql-this-version-of-mysql-doesnt-yet-support-limit-in-all-any-some-subqu const subSubQuery = this.platform.createNativeQueryBuilder(); subSubQuery.select(pks).from(innerQuery); this.#state.limit = undefined; this.#state.offset = undefined; // Save the original WHERE conditions before pruning joins const originalCond = this.#state.cond; const populatePaths = this.getPopulatePaths(); if (!this.#state.fields.some(field => isRaw(field))) { this.pruneJoinsForPagination(meta, populatePaths); } // Transfer WHERE conditions to ORDER BY joins (GH #6160) this.transferConditionsForOrderByJoins(meta, originalCond, populatePaths); const { sql, params } = subSubQuery.compile(); this.select(this.#state.fields).where({ [Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: raw(sql, params) }, }); } /** * Computes the set of populate paths from the _populate hints. */ getPopulatePaths() { const paths = new Set(); function addPath(hints, prefix = '') { for (const hint of hints) { const field = hint.field.split(':')[0]; const fullPath = prefix ? prefix + '.' + field : field; paths.add(fullPath); if (hint.children) { addPath(hint.children, fullPath); } } } addPath(this.#state.populate); return paths; } normalizeJoinPath(join, meta) { return join.path?.replace(/\[populate]|\[pivot]|:ref/g, '').replace(new RegExp(`^${meta.className}.`), '') ?? ''; } /** * Transfers WHERE conditions to ORDER BY joins that are not used for population. * This ensures the outer query's ORDER BY uses the same filtered rows as the subquery. * GH #6160 */ transferConditionsForOrderByJoins(meta, cond, populatePaths) { if (!cond || this.#state.orderBy.length === 0) { return; } const orderByAliases = new Set( this.#state.orderBy .flatMap(hint => Object.keys(hint)) .filter(k => !RawQueryFragment.isKnownFragmentSymbol(k)) .map(k => k.split('.')[0]), ); for (const join of Object.values(this.#state.joins)) { const joinPath = this.normalizeJoinPath(join, meta); const isPopulateJoin = populatePaths.has(joinPath); // Only transfer conditions for joins used for ORDER BY but not for population if (orderByAliases.has(join.alias) && !isPopulateJoin) { this.transferConditionsToJoin(cond, join); } } } /** * Removes joins that are not used for population or ordering to improve performance. */ pruneJoinsForPagination(meta, populatePaths) { const orderByAliases = this.#state.orderBy.flatMap(hint => Object.keys(hint)).map(k => k.split('.')[0]); const joins = Object.entries(this.#state.joins); const rootAlias = this.alias; function addParentAlias(alias) { const join = joins.find(j => j[1].alias === alias); if (join && join[1].ownerAlias !== rootAlias) { orderByAliases.push(join[1].ownerAlias); addParentAlias(join[1].ownerAlias); } } for (const orderByAlias of orderByAliases) { addParentAlias(orderByAlias); } for (const [key, join] of joins) { const path = this.normalizeJoinPath(join, meta); if (!populatePaths.has(path) && !orderByAliases.includes(join.alias)) { delete this.#state.joins[key]; } } } /** * Transfers WHERE conditions that reference a join alias to the join's ON clause. * This is needed when a join is kept for ORDER BY after pagination wrapping, * so the outer query orders by the same filtered rows as the subquery. * @internal */ transferConditionsToJoin(cond, join, path = '') { const aliasPrefix = join.alias + '.'; for (const key of Object.keys(cond)) { const value = cond[key]; // Handle $and/$or operators - recurse into nested conditions if (key === '$and' || key === '$or') { if (Array.isArray(value)) { for (const item of value) { this.transferConditionsToJoin(item, join, path); } } continue; } // Check if this condition references the join alias if (key.startsWith(aliasPrefix)) { // Add condition to the join's ON clause join.cond[key] = value; } } } wrapModifySubQuery(meta) { const subQuery = this.clone(); subQuery.#state.finalized = true; // wrap one more time to get around MySQL limitations // https://stackoverflow.com/questions/45494/mysql-error-1093-cant-specify-target-table-for-update-in-from-clause const subSubQuery = this.platform.createNativeQueryBuilder(); const method = this.#state.flags.has(QueryFlag.UPDATE_SUB_QUERY) ? 'update' : 'delete'; const schema = this.getSchema(this.mainAlias); const pks = this.prepareFields(meta.primaryKeys, 'sub-query', schema); this.#state.cond = {}; // otherwise we would trigger validation error this.#state.joins = {}; // included in the subquery subSubQuery.select(pks).from(subQuery.as(this.mainAlias.aliasName)); const { sql, params } = subSubQuery.compile(); this[method](this.#state.data).where({ [Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: raw(sql, params) }, }); } getSchema(alias) { const { meta } = alias; const metaSchema = meta.schema && meta.schema !== '*' ? meta.schema : undefined; const schema = this.#state.schema ?? metaSchema ?? this.em?.schema ?? this.em?.config.getSchema(true); if (schema === this.platform.getDefaultSchemaName()) { return undefined; } return schema; } /** @internal */ createAlias(entityName, aliasName, subQuery) { const meta = this.metadata.find(entityName); const alias = { aliasName, entityName, meta, subQuery }; this.#state.aliases[aliasName] = alias; return alias; } createMainAlias(entityName, aliasName, subQuery) { this.#state.mainAlias = this.createAlias(entityName, aliasName, subQuery); this.#helper = this.createQueryBuilderHelper(); return this.#state.mainAlias; } fromSubQuery(target, aliasName) { const { entityName } = target.mainAlias; aliasName ??= this.getNextAlias(entityName); const subQuery = target.#state.unionQuery ? target.toRaw() : target.getNativeQuery(); this.createMainAlias(entityName, aliasName, subQuery); } fromEntityName(entityName, aliasName) { aliasName ??= this.#state.mainAlias?.aliasName ?? this.getNextAlias(entityName); this.createMainAlias(entityName, aliasName); } fromRawTable(tableName, aliasName) { aliasName ??= this.#state.mainAlias?.aliasName ?? this.getNextAlias(tableName); const meta = new EntityMetadata({ className: tableName, collection: tableName, }); meta.root = meta; this.#state.mainAlias = { aliasName, entityName: tableName, meta, rawTableName: tableName, }; this.#state.aliases[aliasName] = this.#state.mainAlias; this.#helper = this.createQueryBuilderHelper(); } createQueryBuilderHelper() { return new QueryBuilderHelper( this.mainAlias.entityName, this.mainAlias.aliasName, this.#state.aliases, this.#state.subQueries, this.driver, this.#state.tptAlias, ); } ensureFromClause() { /* v8 ignore next */ if (!this.#state.mainAlias) { throw new Error(`Cannot proceed to build a query because the main alias is not set.`); } } ensureNotFinalized() { if (this.#state.finalized) { throw new Error('This QueryBuilder instance is already finalized, clone it first if you want to modify it.'); } } /** @ignore */ /* v8 ignore next */ [Symbol.for('nodejs.util.inspect.custom')](depth = 2) { const object = { ...this }; const hidden = ['metadata', 'driver', 'context', 'platform', 'type']; Object.keys(object) .filter(k => k.startsWith('_')) .forEach(k => delete object[k]); Object.keys(object) .filter(k => object[k] == null) .forEach(k => delete object[k]); hidden.forEach(k => delete object[k]); let prefix = this.type ? this.type.substring(0, 1) + this.type.toLowerCase().substring(1) : ''; if (this.#state.data) { object.data = this.#state.data; } if (this.#state.schema) { object.schema = this.#state.schema; } if (!Utils.isEmpty(this.#state.cond)) { object.where = this.#state.cond; } if (this.#state.onConflict?.[0]) { prefix = 'Upsert'; object.onConflict = this.#state.onConflict[0]; } if (!Utils.isEmpty(this.#state.orderBy)) { object.orderBy = this.#state.orderBy; } const name = this.#state.mainAlias ? `${prefix}QueryBuilder<${Utils.className(this.#state.mainAlias?.entityName)}>` : 'QueryBuilder'; const ret = inspect(object, { depth }); return ret === '[Object]' ? `[${name}]` : name + ' ' + ret; } } _a = QueryBuilder;