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

112 lines
4.9 KiB
JavaScript

import {
GroupOperator,
isRaw,
JsonType,
RawQueryFragment,
ReferenceKind,
Utils,
ValidationError,
} from '@mikro-orm/core';
import { ObjectCriteriaNode } from './ObjectCriteriaNode.js';
import { ArrayCriteriaNode } from './ArrayCriteriaNode.js';
import { ScalarCriteriaNode } from './ScalarCriteriaNode.js';
import { EMBEDDABLE_ARRAY_OPS } from './enums.js';
/**
* @internal
*/
export class CriteriaNodeFactory {
static createNode(metadata, entityName, payload, parent, key, validate = true) {
const rawField = RawQueryFragment.isKnownFragmentSymbol(key);
const scalar =
Utils.isPrimaryKey(payload) || isRaw(payload) || payload instanceof RegExp || payload instanceof Date || rawField;
if (Array.isArray(payload) && !scalar) {
return this.createArrayNode(metadata, entityName, payload, parent, key, validate);
}
if (Utils.isPlainObject(payload) && !scalar) {
return this.createObjectNode(metadata, entityName, payload, parent, key, validate);
}
return this.createScalarNode(metadata, entityName, payload, parent, key, validate);
}
static createScalarNode(metadata, entityName, payload, parent, key, validate = true) {
const node = new ScalarCriteriaNode(metadata, entityName, parent, key, validate);
node.payload = payload;
return node;
}
static createArrayNode(metadata, entityName, payload, parent, key, validate = true) {
const node = new ArrayCriteriaNode(metadata, entityName, parent, key, validate);
node.payload = payload.map((item, index) => {
const n = this.createNode(metadata, entityName, item, node, undefined, validate);
// we care about branching only for $and
if (key === '$and' && payload.length > 1) {
n.index = index;
}
return n;
});
return node;
}
static createObjectNode(metadata, entityName, payload, parent, key, validate = true) {
const meta = metadata.find(entityName);
const node = new ObjectCriteriaNode(metadata, entityName, parent, key, validate, payload.__strict);
node.payload = {};
for (const k of Utils.getObjectQueryKeys(payload)) {
node.payload[k] = this.createObjectItemNode(metadata, entityName, node, payload, k, meta, validate);
}
return node;
}
static createObjectItemNode(metadata, entityName, node, payload, key, meta, validate = true) {
const rawField = RawQueryFragment.isKnownFragmentSymbol(key);
const prop = rawField ? null : meta?.properties[key];
const childEntity = prop && prop.kind !== ReferenceKind.SCALAR ? prop.targetMeta.class : entityName;
const isNotEmbedded = rawField || prop?.kind !== ReferenceKind.EMBEDDED;
const val = payload[key];
if (isNotEmbedded && prop?.customType instanceof JsonType) {
return this.createScalarNode(metadata, childEntity, val, node, key, validate);
}
if (prop?.kind === ReferenceKind.SCALAR && val != null && Object.keys(val).some(f => f in GroupOperator)) {
throw ValidationError.cannotUseGroupOperatorsInsideScalars(entityName, prop.name, payload);
}
if (isNotEmbedded) {
return this.createNode(metadata, childEntity, val, node, key, validate);
}
if (val == null) {
const map = Object.keys(prop.embeddedProps).reduce((oo, k) => {
oo[prop.embeddedProps[k].name] = null;
return oo;
}, {});
return this.createNode(metadata, entityName, map, node, key, validate);
}
// For array embeddeds stored as real columns, route property-level queries
// as scalar nodes so QueryBuilderHelper generates EXISTS subqueries with
// JSON array iteration. Keys containing `~` indicate the property lives
// inside a parent's object-mode JSON column (MetadataDiscovery uses `~` as
// the glue for object embeds), where JSON path access is used instead.
if (prop.array && !String(key).includes('~')) {
const keys = Object.keys(val);
const hasOnlyArrayOps = keys.every(k => EMBEDDABLE_ARRAY_OPS.includes(k));
if (!hasOnlyArrayOps) {
return this.createScalarNode(metadata, entityName, val, node, key, validate);
}
}
// array operators can be used on embedded properties
const operator = Object.keys(val).some(f => Utils.isOperator(f) && !EMBEDDABLE_ARRAY_OPS.includes(f));
if (operator) {
throw ValidationError.cannotUseOperatorsInsideEmbeddables(entityName, prop.name, payload);
}
const map = Object.keys(val).reduce((oo, k) => {
const embeddedProp = prop.embeddedProps[k] ?? Object.values(prop.embeddedProps).find(p => p.name === k);
if (!embeddedProp && !EMBEDDABLE_ARRAY_OPS.includes(k)) {
throw ValidationError.invalidEmbeddableQuery(entityName, k, prop.type);
}
if (embeddedProp) {
oo[embeddedProp.name] = val[k];
} else if (typeof val[k] === 'object') {
oo[k] = JSON.stringify(val[k]);
} else {
oo[k] = val[k];
}
return oo;
}, {});
return this.createNode(metadata, entityName, map, node, key, validate);
}
}