@sqb/connect
Version:
Multi-dialect database connection framework written with TypeScript
164 lines (163 loc) • 8.28 kB
JavaScript
import { And, Eq, Exists, Field, InnerJoin, isCompOperator, isLogicalOperator, LeftOuterJoin, op, OperatorType, Raw, Select, } from '@sqb/builder';
import { EntityMetadata } from '../model/entity-metadata.js';
import { isAssociationField, isColumnField, isEmbeddedField, resolveEntityForEmbeddedField, } from '../util/orm.helper.js';
export async function joinAssociationGetFirst(joinInfos, association, parentAlias, innerJoin) {
const joins = await joinAssociation(joinInfos, association, parentAlias, innerJoin);
return joins[0];
}
export async function joinAssociationGetLast(joinInfos, association, parentAlias, innerJoin) {
const joins = await joinAssociation(joinInfos, association, parentAlias, innerJoin);
return joins[joins.length - 1];
}
export async function joinAssociation(joinInfos, association, parentAlias, innerJoin) {
let joinInfo;
let node = association;
const result = [];
while (node) {
joinInfo = joinInfos.find(j => j.association === node && j.parentAlias === parentAlias);
if (!joinInfo) {
const targetEntity = await node.resolveTarget();
const sourceEntity = await node.resolveSource();
const keyCol = await node.resolveSourceProperty();
const targetCol = await node.resolveTargetProperty();
const joinAlias = 'J' + (joinInfos.length + 1);
const join = innerJoin
? InnerJoin(targetEntity.tableName + ' as ' + joinAlias)
: LeftOuterJoin(targetEntity.tableName + ' as ' + joinAlias);
join.on(Eq(Field(joinAlias + '.' + targetCol.fieldName, targetCol.dataType, targetCol.isArray), Field(parentAlias + '.' + keyCol.fieldName, keyCol.dataType, keyCol.isArray)));
if (node.conditions)
await prepareFilter(targetEntity, node.conditions, join._conditions, joinAlias);
joinInfo = {
association: node,
sourceEntity,
targetEntity,
parentAlias,
joinAlias,
join,
};
joinInfos.push(joinInfo);
}
result.push(joinInfo);
if (!node.next)
break;
parentAlias = joinInfo.joinAlias;
node = node.next;
}
return result;
}
export async function prepareFilter(entityDef, filter, trgOp, tableAlias = 'T') {
let srcOp;
if (isLogicalOperator(filter) && filter._operatorType === trgOp._operatorType)
srcOp = filter;
else {
srcOp = And();
if (Array.isArray(filter))
srcOp.add(...filter);
else
srcOp.add(filter);
}
// const associationPathCache: Record<string, any> = {};
for (const item of srcOp._items) {
if (isLogicalOperator(item)) {
const ctor = Object.getPrototypeOf(item).constructor;
const logOp = new ctor();
await prepareFilter(entityDef, item, logOp, tableAlias);
trgOp.add(logOp);
continue;
}
if (isCompOperator(item)) {
if (typeof item._left === 'string') {
const itemPath = item._left.split('.');
const l = itemPath.length;
let pt;
let _curEntity = entityDef;
let _curAlias = tableAlias;
let _curPrefix = '';
let _curSuffix = '';
let subSelect;
let currentOp = trgOp;
let i = 0;
// const parentPath = itemPath
// .slice(0, l - 2)
// .join('.')
// .toLowerCase();
// const cached = associationPathCache[parentPath];
// if (cached) {
// /** Jump to last item */
// i = l - 1;
// _curEntity = cached._curEntity;
// _curAlias = cached._curAlias;
// currentOp = cached.currentOp;
// }
for (i; i < l; i++) {
pt = itemPath[i];
const col = EntityMetadata.getField(_curEntity, pt);
if (!col)
throw new Error(`Unknown property (${item._left}) defined in filter`);
/** if last item on path */
if (i === l - 1) {
if (!isColumnField(col))
throw new Error(`Invalid column expression (${item._left}) defined in filter`);
const ctor = Object.getPrototypeOf(item).constructor;
currentOp.add(new ctor(Field(_curAlias + '.' + _curPrefix + col.fieldName + _curSuffix, col.dataType, col.isArray), item._right));
}
else {
/** if not last item on path */
if (isColumnField(col))
throw new Error(`Invalid column (${item._left}) defined in filter`);
if (isEmbeddedField(col)) {
_curEntity = await resolveEntityForEmbeddedField(col);
_curPrefix = _curPrefix + (col.fieldNamePrefix || '');
_curSuffix = (col.fieldNameSuffix || '') + _curSuffix;
continue;
}
if (!isAssociationField(col))
throw new Error(`Invalid column (${item._left}) defined in filter`);
let node;
_curEntity = await col.association.resolveTarget();
if (!subSelect) {
const keyCol = await col.association.resolveSourceProperty();
const targetCol = await col.association.resolveTargetProperty();
subSelect = Select(Raw('1')).from(_curEntity.tableName + ' K');
subSelect.where(Eq(Field('K.' + targetCol.fieldName, targetCol.dataType, targetCol.isArray), Field(tableAlias + '.' + keyCol.fieldName, keyCol.dataType, keyCol.isArray)));
currentOp.add(Exists(subSelect));
if (currentOp._operatorType !== OperatorType.and) {
currentOp = op.or();
subSelect.where(currentOp);
}
else
currentOp = subSelect._where;
if (col.association.conditions) {
await prepareFilter(_curEntity, col.association.conditions, trgOp, 'K');
}
node = col.association.next;
_curAlias = 'K';
}
else
node = col.association;
while (node) {
const targetEntity = await node.resolveTarget();
const sourceColumn = await node.resolveSourceProperty();
const targetColumn = await node.resolveTargetProperty();
const joinAlias = 'J' + ((subSelect?._joins?.length || 0) + 1);
subSelect.join(InnerJoin(targetEntity.tableName + ' ' + joinAlias).on(Eq(Field(joinAlias + '.' + targetColumn.fieldName, targetColumn.dataType, targetColumn.isArray), Field(_curAlias + '.' + sourceColumn.fieldName, sourceColumn.dataType, sourceColumn.isArray))));
_curEntity = targetEntity;
_curAlias = joinAlias;
node = node.next;
}
// /** If next path is the last one */
// if (i === l - 2) {
// associationPathCache[parentPath] = {
// _curEntity,
// _curAlias,
// currentOp,
// };
// }
}
}
continue;
}
}
trgOp.add(item);
}
}