(
result: EntityData<T>,
meta: EntityMetadata<T>,
populate: PopulateOptions<T>[],
qb: AnyQueryBuilder<T>,
root: EntityData<T>,
map: Dictionary,
parentJoinPath?: string,
)
| 589 | } |
| 590 | |
| 591 | private mapJoinedProps<T extends object>( |
| 592 | result: EntityData<T>, |
| 593 | meta: EntityMetadata<T>, |
| 594 | populate: PopulateOptions<T>[], |
| 595 | qb: AnyQueryBuilder<T>, |
| 596 | root: EntityData<T>, |
| 597 | map: Dictionary, |
| 598 | parentJoinPath?: string, |
| 599 | ) { |
| 600 | const joinedProps = this.joinedProps(meta, populate); |
| 601 | |
| 602 | joinedProps.forEach(hint => { |
| 603 | const [propName, ref] = hint.field.split(':', 2) as [EntityKey<T>, string | undefined]; |
| 604 | const prop = meta.properties[propName]; |
| 605 | |
| 606 | /* v8 ignore next */ |
| 607 | if (!prop) { |
| 608 | return; |
| 609 | } |
| 610 | |
| 611 | // Polymorphic to-one: iterate targets, find the matching one, build entity from its columns. |
| 612 | // Skip :ref hints — no JOINs were created, so the FK reference is already set by the result mapper. |
| 613 | if ( |
| 614 | prop.polymorphic && |
| 615 | prop.polymorphTargets?.length && |
| 616 | !ref && |
| 617 | [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) |
| 618 | ) { |
| 619 | const basePath = parentJoinPath ? `${parentJoinPath}.${prop.name}` : `${meta.name}.${prop.name}`; |
| 620 | const pathPrefix = !parentJoinPath ? '[populate]' : ''; |
| 621 | let matched = false; |
| 622 | |
| 623 | for (const targetMeta of prop.polymorphTargets) { |
| 624 | const targetPath = `${pathPrefix}${basePath}[${targetMeta.className}]`; |
| 625 | const relationAlias = qb.getAliasForJoinPath(targetPath, { matchPopulateJoins: true })!; |
| 626 | const meta2 = targetMeta as EntityMetadata<T>; |
| 627 | const targetProps = meta2.props.filter(p => this.platform.shouldHaveColumn(p, (hint.children as any) || [])); |
| 628 | |
| 629 | const hasPK = meta2 |
| 630 | .getPrimaryProps() |
| 631 | .every(pk => pk.fieldNames.every(name => root[`${relationAlias}__${name}` as EntityKey] != null)); |
| 632 | |
| 633 | if (hasPK && !matched) { |
| 634 | matched = true; |
| 635 | const relationPojo: EntityData<T> = {}; |
| 636 | const tz = this.platform.getTimezone(); |
| 637 | |
| 638 | for (const p of targetProps) { |
| 639 | this.mapJoinedProp(relationPojo, p, relationAlias, root, tz, meta2); |
| 640 | } |
| 641 | |
| 642 | // For TPT base targets, map child-specific fields and resolve the |
| 643 | // concrete class so the factory creates the correct subtype. |
| 644 | const concreteMeta = this.mapTPTChildFields(relationPojo, meta2, relationAlias, qb, root); |
| 645 | |
| 646 | Object.defineProperty(relationPojo, 'constructor', { |
| 647 | value: concreteMeta?.class ?? meta2.class, |
| 648 | enumerable: false, |
nothing calls this directly
no test coverage detected