From 5f73c296f15d53778f80f52f38bc437104b9f47d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20No=C3=ABl?= Date: Wed, 2 Nov 2022 10:43:30 +0100 Subject: [PATCH] remove populate.js file --- .../database/lib/query/helpers/populate.js | 655 ------------------ 1 file changed, 655 deletions(-) delete mode 100644 packages/core/database/lib/query/helpers/populate.js diff --git a/packages/core/database/lib/query/helpers/populate.js b/packages/core/database/lib/query/helpers/populate.js deleted file mode 100644 index 4bb8906d9a..0000000000 --- a/packages/core/database/lib/query/helpers/populate.js +++ /dev/null @@ -1,655 +0,0 @@ -'use strict'; - -const _ = require('lodash/fp'); - -const types = require('../../types'); -const { fromRow } = require('./transform'); - -const getRootLevelPopulate = (meta) => { - const populate = {}; - - for (const attributeName of Object.keys(meta.attributes)) { - const attribute = meta.attributes[attributeName]; - if (attribute.type === 'relation') { - populate[attributeName] = true; - } - } - - return populate; -}; - -/** - * Converts and prepares the query for populate - * - * @param {boolean|string[]|object} populate populate param - * @param {object} ctx query context - * @param {object} ctx.db database instance - * @param {object} ctx.qb query builder instance - * @param {string} ctx.uid model uid - */ -const processPopulate = (populate, ctx) => { - const { qb, db, uid } = ctx; - const meta = db.metadata.get(uid); - - let populateMap = {}; - - if (populate === false || _.isNil(populate)) { - return null; - } - - if (populate === true) { - populateMap = getRootLevelPopulate(meta); - } else if (Array.isArray(populate)) { - for (const key of populate) { - const [root, ...rest] = key.split('.'); - - if (rest.length > 0) { - const subPopulate = rest.join('.'); - - if (populateMap[root]) { - if (populateMap[root] === true) { - populateMap[root] = { - populate: [subPopulate], - }; - } else { - populateMap[root].populate = [subPopulate].concat(populateMap[root].populate || []); - } - } else { - populateMap[root] = { - populate: [subPopulate], - }; - } - } else { - populateMap[root] = populateMap[root] ? populateMap[root] : true; - } - } - } else { - populateMap = populate; - } - - if (!_.isPlainObject(populateMap)) { - throw new Error('Populate must be an object'); - } - - const finalPopulate = {}; - for (const key of Object.keys(populateMap)) { - const attribute = meta.attributes[key]; - - if (!attribute) { - continue; - } - - if (!types.isRelation(attribute.type)) { - continue; - } - - // Make sure to query the join column value if needed, - // so that we can apply the populate later on - if (attribute.joinColumn) { - qb.addSelect(attribute.joinColumn.name); - } - - // Make sure id is present for future populate queries - if (_.has('id', meta.attributes)) { - qb.addSelect('id'); - } - - finalPopulate[key] = populateMap[key]; - } - - return finalPopulate; -}; - -// TODO: Omit limit & offset to avoid needing a query per result to avoid making too many queries -const pickPopulateParams = _.pick([ - 'select', - 'count', - 'where', - 'populate', - 'orderBy', - 'limit', - 'offset', - 'filters', -]); - -// TODO: cleanup code -// TODO: create aliases for pivot columns -// TODO: optimize depth to avoid overfetching -// TODO: handle count for join columns -// TODO: cleanup count -const applyPopulate = async (results, populate, ctx) => { - const { db, uid, qb } = ctx; - const meta = db.metadata.get(uid); - - if (_.isEmpty(results)) { - return results; - } - - for (const key of Object.keys(populate)) { - const attribute = meta.attributes[key]; - const targetMeta = db.metadata.get(attribute.target); - - const populateValue = { - filters: qb.state.filters, - ...pickPopulateParams(populate[key]), - }; - - const isCount = populateValue.count === true; - - const fromTargetRow = (rowOrRows) => fromRow(targetMeta, rowOrRows); - - if (attribute.relation === 'oneToOne' || attribute.relation === 'manyToOne') { - if (attribute.joinColumn) { - const { name: joinColumnName, referencedColumn: referencedColumnName } = - attribute.joinColumn; - - const referencedValues = _.uniq( - results.map((r) => r[joinColumnName]).filter((value) => !_.isNil(value)) - ); - - if (_.isEmpty(referencedValues)) { - results.forEach((result) => { - result[key] = null; - }); - - continue; - } - - const rows = await db.entityManager - .createQueryBuilder(targetMeta.uid) - .init(populateValue) - .addSelect(`${qb.alias}.${referencedColumnName}`) - .where({ [referencedColumnName]: referencedValues }) - .execute({ mapResults: false }); - - const map = _.groupBy(referencedColumnName, rows); - - results.forEach((result) => { - result[key] = fromTargetRow(_.first(map[result[joinColumnName]])); - }); - - continue; - } - - if (attribute.joinTable) { - const { joinTable } = attribute; - - const qb = db.entityManager.createQueryBuilder(targetMeta.uid); - - const { name: joinColumnName, referencedColumn: referencedColumnName } = - joinTable.joinColumn; - - const alias = qb.getAlias(); - const joinColAlias = `${alias}.${joinColumnName}`; - - const referencedValues = _.uniq( - results.map((r) => r[referencedColumnName]).filter((value) => !_.isNil(value)) - ); - - if (_.isEmpty(referencedValues)) { - results.forEach((result) => { - result[key] = null; - }); - continue; - } - - const rows = await qb - .init(populateValue) - .join({ - alias, - referencedTable: joinTable.name, - referencedColumn: joinTable.inverseJoinColumn.name, - rootColumn: joinTable.inverseJoinColumn.referencedColumn, - rootTable: qb.alias, - on: joinTable.on, - orderBy: joinTable.orderBy, - }) - .addSelect(joinColAlias) - .where({ [joinColAlias]: referencedValues }) - .execute({ mapResults: false }); - - const map = _.groupBy(joinColumnName, rows); - - results.forEach((result) => { - result[key] = fromTargetRow(_.first(map[result[referencedColumnName]])); - }); - - continue; - } - - continue; - } else if (attribute.relation === 'oneToMany') { - if (attribute.joinColumn) { - const { name: joinColumnName, referencedColumn: referencedColumnName } = - attribute.joinColumn; - - const referencedValues = _.uniq( - results.map((r) => r[joinColumnName]).filter((value) => !_.isNil(value)) - ); - - if (_.isEmpty(referencedValues)) { - results.forEach((result) => { - result[key] = null; - }); - continue; - } - - const rows = await db.entityManager - .createQueryBuilder(targetMeta.uid) - .init(populateValue) - .addSelect(`${qb.alias}.${referencedColumnName}`) - .where({ [referencedColumnName]: referencedValues }) - .execute({ mapResults: false }); - - const map = _.groupBy(referencedColumnName, rows); - - results.forEach((result) => { - result[key] = fromTargetRow(map[result[joinColumnName]] || []); - }); - - continue; - } - - if (attribute.joinTable) { - const { joinTable } = attribute; - - const qb = db.entityManager.createQueryBuilder(targetMeta.uid); - - const { name: joinColumnName, referencedColumn: referencedColumnName } = - joinTable.joinColumn; - - const alias = qb.getAlias(); - const joinColAlias = `${alias}.${joinColumnName}`; - - const referencedValues = _.uniq( - results.map((r) => r[referencedColumnName]).filter((value) => !_.isNil(value)) - ); - - if (isCount) { - if (_.isEmpty(referencedValues)) { - results.forEach((result) => { - result[key] = { count: 0 }; - }); - continue; - } - - const rows = await qb - .init(populateValue) - .join({ - alias, - referencedTable: joinTable.name, - referencedColumn: joinTable.inverseJoinColumn.name, - rootColumn: joinTable.inverseJoinColumn.referencedColumn, - rootTable: qb.alias, - on: joinTable.on, - }) - .select([joinColAlias, qb.raw('count(*) AS count')]) - .where({ [joinColAlias]: referencedValues }) - .groupBy(joinColAlias) - .execute({ mapResults: false }); - - const map = rows.reduce((map, row) => { - map[row[joinColumnName]] = { count: Number(row.count) }; - return map; - }, {}); - - results.forEach((result) => { - result[key] = map[result[referencedColumnName]] || { count: 0 }; - }); - - continue; - } - - if (_.isEmpty(referencedValues)) { - results.forEach((result) => { - result[key] = []; - }); - continue; - } - - const rows = await qb - .init(populateValue) - .join({ - alias, - referencedTable: joinTable.name, - referencedColumn: joinTable.inverseJoinColumn.name, - rootColumn: joinTable.inverseJoinColumn.referencedColumn, - rootTable: qb.alias, - on: joinTable.on, - orderBy: joinTable.orderBy, - }) - .addSelect(joinColAlias) - .where({ [joinColAlias]: referencedValues }) - .execute({ mapResults: false }); - - const map = _.groupBy(joinColumnName, rows); - - results.forEach((r) => { - r[key] = fromTargetRow(map[r[referencedColumnName]] || []); - }); - continue; - } - - continue; - } else if (attribute.relation === 'manyToMany') { - const { joinTable } = attribute; - - const qb = db.entityManager.createQueryBuilder(targetMeta.uid); - - const { name: joinColumnName, referencedColumn: referencedColumnName } = joinTable.joinColumn; - - const alias = qb.getAlias(); - const joinColAlias = `${alias}.${joinColumnName}`; - const referencedValues = _.uniq( - results.map((r) => r[referencedColumnName]).filter((value) => !_.isNil(value)) - ); - - if (isCount) { - if (_.isEmpty(referencedValues)) { - results.forEach((result) => { - result[key] = { count: 0 }; - }); - continue; - } - - const rows = await qb - .init(populateValue) - .join({ - alias, - referencedTable: joinTable.name, - referencedColumn: joinTable.inverseJoinColumn.name, - rootColumn: joinTable.inverseJoinColumn.referencedColumn, - rootTable: qb.alias, - on: joinTable.on, - }) - .select([joinColAlias, qb.raw('count(*) AS count')]) - .where({ [joinColAlias]: referencedValues }) - .groupBy(joinColAlias) - .execute({ mapResults: false }); - - const map = rows.reduce((map, row) => { - map[row[joinColumnName]] = { count: Number(row.count) }; - return map; - }, {}); - - results.forEach((result) => { - result[key] = map[result[referencedColumnName]] || { count: 0 }; - }); - - continue; - } - - if (_.isEmpty(referencedValues)) { - results.forEach((result) => { - result[key] = []; - }); - continue; - } - - const rows = await qb - .init(populateValue) - .join({ - alias, - referencedTable: joinTable.name, - referencedColumn: joinTable.inverseJoinColumn.name, - rootColumn: joinTable.inverseJoinColumn.referencedColumn, - rootTable: qb.alias, - on: joinTable.on, - orderBy: joinTable.orderBy, - }) - .addSelect(joinColAlias) - .where({ [joinColAlias]: referencedValues }) - .execute({ mapResults: false }); - - const map = _.groupBy(joinColumnName, rows); - - results.forEach((result) => { - result[key] = fromTargetRow(map[result[referencedColumnName]] || []); - }); - - continue; - } else if (['morphOne', 'morphMany'].includes(attribute.relation)) { - const { target, morphBy } = attribute; - - const targetAttribute = db.metadata.get(target).attributes[morphBy]; - - if (targetAttribute.relation === 'morphToOne') { - const { idColumn, typeColumn } = targetAttribute.morphColumn; - - const referencedValues = _.uniq( - results.map((r) => r[idColumn.referencedColumn]).filter((value) => !_.isNil(value)) - ); - - if (_.isEmpty(referencedValues)) { - results.forEach((result) => { - result[key] = null; - }); - - continue; - } - - const rows = await db.entityManager - .createQueryBuilder(target) - .init(populateValue) - // .addSelect(`${qb.alias}.${idColumn.referencedColumn}`) - .where({ [idColumn.name]: referencedValues, [typeColumn.name]: uid }) - .execute({ mapResults: false }); - - const map = _.groupBy(idColumn.name, rows); - - results.forEach((result) => { - const matchingRows = map[result[idColumn.referencedColumn]]; - - const matchingValue = - attribute.relation === 'morphOne' ? _.first(matchingRows) : matchingRows; - - result[key] = fromTargetRow(matchingValue); - }); - } else if (targetAttribute.relation === 'morphToMany') { - const { joinTable } = targetAttribute; - - const { joinColumn, morphColumn } = joinTable; - - const { idColumn, typeColumn } = morphColumn; - - const referencedValues = _.uniq( - results.map((r) => r[idColumn.referencedColumn]).filter((value) => !_.isNil(value)) - ); - - if (_.isEmpty(referencedValues)) { - results.forEach((result) => { - result[key] = attribute.relation === 'morphOne' ? null : []; - }); - - continue; - } - - // find with join table - const qb = db.entityManager.createQueryBuilder(target); - - const alias = qb.getAlias(); - - const rows = await qb - .init(populateValue) - .join({ - alias, - referencedTable: joinTable.name, - referencedColumn: joinColumn.name, - rootColumn: joinColumn.referencedColumn, - rootTable: qb.alias, - on: { - ...(joinTable.on || {}), - field: key, - }, - orderBy: joinTable.orderBy, - }) - .addSelect([`${alias}.${idColumn.name}`, `${alias}.${typeColumn.name}`]) - .where({ - [`${alias}.${idColumn.name}`]: referencedValues, - [`${alias}.${typeColumn.name}`]: uid, - }) - .execute({ mapResults: false }); - - const map = _.groupBy(idColumn.name, rows); - - results.forEach((result) => { - const matchingRows = map[result[idColumn.referencedColumn]]; - - const matchingValue = - attribute.relation === 'morphOne' ? _.first(matchingRows) : matchingRows; - - result[key] = fromTargetRow(matchingValue); - }); - } - - continue; - } else if (attribute.relation === 'morphToMany') { - // find with join table - const { joinTable } = attribute; - - const { joinColumn, morphColumn } = joinTable; - const { idColumn, typeColumn, typeField = '__type' } = morphColumn; - - // fetch join table to create the ids map then do the same as morphToOne without the first - - const referencedValues = _.uniq( - results.map((r) => r[joinColumn.referencedColumn]).filter((value) => !_.isNil(value)) - ); - - const qb = db.entityManager.createQueryBuilder(joinTable.name); - - const joinRows = await qb - .where({ - [joinColumn.name]: referencedValues, - ...(joinTable.on || {}), - }) - .orderBy([joinColumn.name, 'order']) - .execute({ mapResults: false }); - - const joinMap = _.groupBy(joinColumn.name, joinRows); - - const idsByType = joinRows.reduce((acc, result) => { - const idValue = result[morphColumn.idColumn.name]; - const typeValue = result[morphColumn.typeColumn.name]; - - if (!idValue || !typeValue) { - return acc; - } - - if (!_.has(typeValue, acc)) { - acc[typeValue] = []; - } - - acc[typeValue].push(idValue); - - return acc; - }, {}); - - const map = {}; - for (const type of Object.keys(idsByType)) { - const ids = idsByType[type]; - - // type was removed but still in morph relation - if (!db.metadata.get(type)) { - map[type] = {}; - continue; - } - - const qb = db.entityManager.createQueryBuilder(type); - - const rows = await qb - .init(populateValue) - .addSelect(`${qb.alias}.${idColumn.referencedColumn}`) - .where({ [idColumn.referencedColumn]: ids }) - .execute({ mapResults: false }); - - map[type] = _.groupBy(idColumn.referencedColumn, rows); - } - - results.forEach((result) => { - const joinResults = joinMap[result[joinColumn.referencedColumn]] || []; - - const matchingRows = joinResults.flatMap((joinResult) => { - const id = joinResult[idColumn.name]; - const type = joinResult[typeColumn.name]; - - const fromTargetRow = (rowOrRows) => fromRow(db.metadata.get(type), rowOrRows); - - return (map[type][id] || []).map((row) => { - return { - [typeField]: type, - ...fromTargetRow(row), - }; - }); - }); - - result[key] = matchingRows; - }); - } else if (attribute.relation === 'morphToOne') { - const { morphColumn } = attribute; - const { idColumn, typeColumn } = morphColumn; - - // make a map for each type what ids to return - // make a nested map per id - - const idsByType = results.reduce((acc, result) => { - const idValue = result[morphColumn.idColumn.name]; - const typeValue = result[morphColumn.typeColumn.name]; - - if (!idValue || !typeValue) { - return acc; - } - - if (!_.has(typeValue, acc)) { - acc[typeValue] = []; - } - - acc[typeValue].push(idValue); - - return acc; - }, {}); - - const map = {}; - for (const type of Object.keys(idsByType)) { - const ids = idsByType[type]; - - // type was removed but still in morph relation - if (!db.metadata.get(type)) { - map[type] = {}; - continue; - } - - const qb = db.entityManager.createQueryBuilder(type); - - const rows = await qb - .init(populateValue) - .addSelect(`${qb.alias}.${idColumn.referencedColumn}`) - .where({ [idColumn.referencedColumn]: ids }) - .execute({ mapResults: false }); - - map[type] = _.groupBy(idColumn.referencedColumn, rows); - } - - results.forEach((result) => { - const id = result[idColumn.name]; - const type = result[typeColumn.name]; - - if (!type || !id) { - result[key] = null; - return; - } - - const matchingRows = map[type][id]; - - const fromTargetRow = (rowOrRows) => fromRow(db.metadata.get(type), rowOrRows); - - result[key] = fromTargetRow(_.first(matchingRows)); - }); - } - } -}; - -module.exports = { - processPopulate, - applyPopulate, -};