112 lines
4.9 KiB
JavaScript
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);
|
|
}
|
|
}
|