UNPKG

@athenna/database

Version:

The Athenna database handler for SQL/NoSQL.

161 lines (160 loc) 6.22 kB
/** * @athenna/database * * (c) João Lenon <lenon@athenna.io> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ import { String } from '@athenna/common'; export class HasManyThroughRelation { /** * Get the options with defined default values. */ static options(Parent, relation) { const Through = relation.through(); const Final = relation.model(); relation.localKey = relation.localKey || Parent.schema().getMainPrimaryKeyProperty(); relation.firstKey = relation.firstKey || `${String.toCamelCase(Parent.name)}Id`; if (relation.inverse) { relation.secondLocalKey = relation.secondLocalKey || `${String.toCamelCase(Final.name)}Id`; relation.secondKey = relation.secondKey || Final.schema().getMainPrimaryKeyProperty(); } else { relation.secondLocalKey = relation.secondLocalKey || Through.schema().getMainPrimaryKeyProperty(); relation.secondKey = relation.secondKey || `${String.toCamelCase(Through.name)}Id`; } return relation; } /** * Load a has many through relation. */ static async load(model, relation) { this.options(model.constructor, relation); const throughRows = await relation .through() .query() .where(relation.firstKey, model[relation.localKey]) .findMany(); const linkValues = throughRows .map(r => r[relation.secondLocalKey]) .filter(v => v !== undefined && v !== null); if (!linkValues.length) { model[relation.property] = []; return model; } model[relation.property] = await relation .model() .query() .whereIn(relation.secondKey, linkValues) .when(relation.withClosure, relation.withClosure) .findMany(); return model; } /** * Load all models that has many through relation. */ static async loadAll(models, relation) { if (!models.length) { return models; } this.options(models[0].constructor, relation); const Final = relation.model(); const finalPK = Final.schema().getMainPrimaryKeyProperty(); const parentValues = models.map(m => m[relation.localKey]); const throughRows = await relation .through() .query() .whereIn(relation.firstKey, parentValues) .findMany(); const parentToLinks = new Map(); const allLinks = []; for (const t of throughRows) { const link = t[relation.secondLocalKey]; if (link === undefined || link === null) { continue; } allLinks.push(link); const arr = parentToLinks.get(t[relation.firstKey]) || []; arr.push(link); parentToLinks.set(t[relation.firstKey], arr); } const finals = allLinks.length ? await relation .model() .query() .whereIn(relation.secondKey, allLinks) .when(relation.withClosure, relation.withClosure) .findMany() : []; const linkToFinals = new Map(); for (const f of finals) { const arr = linkToFinals.get(f[relation.secondKey]) || []; arr.push(f); linkToFinals.set(f[relation.secondKey], arr); } return models.map(m => { const links = parentToLinks.get(m[relation.localKey]) || []; const collected = []; const seen = new Set(); for (const link of links) { const matches = linkToFinals.get(link) || []; for (const f of matches) { const pk = f[finalPK]; if (seen.has(pk)) { continue; } seen.add(pk); collected.push(f); } } m[relation.property] = collected; return m; }); } /** * Apply a where has relation to the query when the given model * has many through relations. */ static whereHas(Model, query, relation) { this.options(Model, relation); const Through = relation.through(); const Final = relation.model(); const modelTable = Model.table(); const throughTable = Through.table(); const finalTable = Final.table(); const modelLocal = Model.schema().getColumnNameByProperty(relation.localKey) || Model.schema().getMainPrimaryKeyName(); const throughFK = Through.schema().getColumnNameByProperty(relation.firstKey) || Through.schema().getColumnNameByProperty(`${String.toCamelCase(Model.name)}Id`); const throughLink = Through.schema().getColumnNameByProperty(relation.secondLocalKey); const finalLink = Final.schema().getColumnNameByProperty(relation.secondKey); let outerWhereRaw = `${throughTable}.${throughFK} = ${modelTable}.${modelLocal}`; switch (Through.schema().getModelDriverName()) { case 'sqlite': case 'postgres': outerWhereRaw = `"${throughTable}"."${throughFK}" = "${modelTable}"."${modelLocal}"`; } Through.query() .setDriver(query, throughTable) .whereRaw(outerWhereRaw) .whereExists(innerQuery => { let innerWhereRaw = `${finalTable}.${finalLink} = ${throughTable}.${throughLink}`; switch (Final.schema().getModelDriverName()) { case 'sqlite': case 'postgres': innerWhereRaw = `"${finalTable}"."${finalLink}" = "${throughTable}"."${throughLink}"`; } Final.query() .setDriver(innerQuery, finalTable) .whereRaw(innerWhereRaw) .when(relation.closure, relation.closure); }); } }