Files
evento/node_modules/@mikro-orm/sql/query/CriteriaNode.js
2026-03-18 14:55:56 -03:00

151 lines
5.3 KiB
JavaScript

import { RawQueryFragment, ReferenceKind, Utils, inspect } from '@mikro-orm/core';
/**
* Helper for working with deeply nested where/orderBy/having criteria. Uses composite pattern to build tree from the payload.
* Auto-joins relations and converts payload from { books: { publisher: { name: '...' } } } to { 'publisher_alias.name': '...' }
* @internal
*/
export class CriteriaNode {
metadata;
entityName;
parent;
key;
validate;
strict;
payload;
prop;
index;
constructor(metadata, entityName, parent, key, validate = true, strict = false) {
this.metadata = metadata;
this.entityName = entityName;
this.parent = parent;
this.key = key;
this.validate = validate;
this.strict = strict;
const meta = parent && metadata.find(parent.entityName);
if (meta && key && !RawQueryFragment.isKnownFragmentSymbol(key)) {
const pks = Utils.splitPrimaryKeys(key);
if (pks.length > 1) {
return;
}
for (const k of pks) {
this.prop = meta.props.find(
prop =>
prop.name === k || (prop.fieldNames?.length === 1 && prop.fieldNames[0] === k && prop.persist !== false),
);
const isProp = this.prop || meta.props.find(prop => (prop.fieldNames || []).includes(k));
// do not validate if the key is prefixed or type casted (e.g. `k::text`)
if (validate && !isProp && !k.includes('.') && !k.includes('::') && !Utils.isOperator(k)) {
throw new Error(`Trying to query by not existing property ${Utils.className(entityName)}.${k}`);
}
}
}
}
process(qb, options) {
return this.payload;
}
unwrap() {
return this.payload;
}
shouldInline(payload) {
return false;
}
willAutoJoin(qb, alias, options) {
return false;
}
shouldRename(payload) {
const type = this.prop ? this.prop.kind : null;
const composite = this.prop?.joinColumns ? this.prop.joinColumns.length > 1 : false;
const rawField = RawQueryFragment.isKnownFragmentSymbol(this.key);
const scalar =
payload === null ||
Utils.isPrimaryKey(payload) ||
payload instanceof RegExp ||
payload instanceof Date ||
rawField;
const operator =
Utils.isPlainObject(payload) && Utils.getObjectQueryKeys(payload).every(k => Utils.isOperator(k, false));
if (composite) {
return true;
}
switch (type) {
case ReferenceKind.MANY_TO_ONE:
return false;
case ReferenceKind.ONE_TO_ONE:
return !this.prop.owner;
case ReferenceKind.ONE_TO_MANY:
return scalar || operator;
case ReferenceKind.MANY_TO_MANY:
return scalar || operator;
default:
return false;
}
}
renameFieldToPK(qb, ownerAlias) {
const joinAlias = qb.getAliasForJoinPath(this.getPath(), { matchPopulateJoins: true });
if (
!joinAlias &&
this.parent &&
[ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind) &&
this.prop.owner
) {
const alias = qb.getAliasForJoinPath(this.parent.getPath()) ?? ownerAlias ?? qb.alias;
return Utils.getPrimaryKeyHash(this.prop.joinColumns.map(col => `${alias}.${col}`));
}
const alias = joinAlias ?? ownerAlias ?? qb.alias;
if (this.prop.kind === ReferenceKind.MANY_TO_MANY) {
return Utils.getPrimaryKeyHash(this.prop.inverseJoinColumns.map(col => `${alias}.${col}`));
}
return Utils.getPrimaryKeyHash(this.prop.referencedColumnNames.map(col => `${alias}.${col}`));
}
getPath(opts) {
// use index on parent only if we are processing to-many relation
const addParentIndex =
this.prop && [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(this.prop.kind);
const parentPath =
opts?.parentPath ?? this.parent?.getPath({ addIndex: addParentIndex }) ?? Utils.className(this.entityName);
const index = opts?.addIndex && this.index != null ? `[${this.index}]` : '';
// ignore group operators to allow easier mapping (e.g. for orderBy)
const key =
this.key && !RawQueryFragment.isKnownFragmentSymbol(this.key) && !['$and', '$or', '$not'].includes(this.key)
? '.' + this.key
: '';
const ret = parentPath + index + key;
if (this.isPivotJoin()) {
// distinguish pivot table join from target entity join
return this.getPivotPath(ret);
}
return ret;
}
isPivotJoin() {
if (!this.key || !this.prop) {
return false;
}
const rawField = RawQueryFragment.isKnownFragmentSymbol(this.key);
const scalar =
this.payload === null ||
Utils.isPrimaryKey(this.payload) ||
this.payload instanceof RegExp ||
this.payload instanceof Date ||
rawField;
const operator =
Utils.isObject(this.payload) && Utils.getObjectQueryKeys(this.payload).every(k => Utils.isOperator(k, false));
return this.prop.kind === ReferenceKind.MANY_TO_MANY && (scalar || operator);
}
getPivotPath(path) {
return `${path}[pivot]`;
}
aliased(field, alias) {
return alias ? `${alias}.${field}` : field;
}
isStrict() {
return this.strict;
}
/** @ignore */
/* v8 ignore next */
[Symbol.for('nodejs.util.inspect.custom')]() {
const o = {};
['entityName', 'key', 'index', 'payload'].filter(k => this[k] !== undefined).forEach(k => (o[k] = this[k]));
return `${this.constructor.name} ${inspect(o)}`;
}
}