UNPKG

ghost

Version:

The professional publishing platform

207 lines (171 loc) 7.95 kB
const _ = require('lodash'); const debug = require('@tryghost/debug')('models:base:raw-knex'); const plugins = require('@tryghost/bookshelf-plugins'); const schema = require('../../../data/schema'); /** * @param {import('bookshelf')} Bookshelf */ module.exports = function (Bookshelf) { Bookshelf.Model = Bookshelf.Model.extend({}, { /** * If you want to fetch all data fast, i recommend using this function. * Bookshelf is just too slow, too much ORM overhead. * * If we e.g. instantiate for each object a model, it takes twice long. */ raw_knex: { fetchAll: async function (options = {}) { const { modelName, exclude, filter, shouldHavePosts, withRelated, withRelatedFields, id, offset, limit } = options; const bookshelfPrototype = Bookshelf.registry.models[modelName].prototype; const tableNames = { Post: 'posts', User: 'users', Tag: 'tags' }; const relations = { tags: { targetTable: 'tags', name: 'tags', innerJoin: { relation: 'posts_tags', condition: ['posts_tags.tag_id', '=', 'tags.id'] }, select: ['posts_tags.post_id as post_id', 'tags.visibility'], whereIn: 'posts_tags.post_id', whereInKey: 'post_id', orderBy: 'sort_order' }, authors: { targetTable: 'users', name: 'authors', innerJoin: { relation: 'posts_authors', condition: ['posts_authors.author_id', '=', 'users.id'] }, select: ['posts_authors.post_id as post_id'], whereIn: 'posts_authors.post_id', whereInKey: 'post_id', orderBy: 'sort_order' } }; let query = Bookshelf.knex(tableNames[modelName]); if (offset) { query.offset(offset); } if (limit) { query.limit(limit); } // exclude fields if provided if (exclude) { const toSelect = Object .keys(schema.tables[tableNames[modelName]]) .filter(key => !key.startsWith('@@') && !exclude.includes(key)); query.select(toSelect); } // @NOTE: We can't use the filter plugin, because we are not using bookshelf. // Lazyloaded to keep boot fast const nql = require('@tryghost/nql'); nql(filter).querySQL(query); if (shouldHavePosts) { plugins.hasPosts.addHasPostsWhere(tableNames[modelName], shouldHavePosts)(query); } if (id) { query.where({id}); } let objects = await query; debug('fetched', modelName, filter); if (!objects.length) { debug('No more entries found'); return []; } let props = {}; if (!withRelated) { return _.map(objects, (object) => { object = bookshelfPrototype.toJSON.bind({ attributes: object, related: function (key) { return object[key]; }, serialize: bookshelfPrototype.serialize, formatsToJSON: bookshelfPrototype.formatsToJSON })(); object = bookshelfPrototype.fixBools(object); object = bookshelfPrototype.fixDatesWhenFetch(object); return object; }); } _.each(withRelated, (withRelatedKey) => { const relation = relations[withRelatedKey]; props[relation.name] = (async () => { debug('fetch withRelated', relation.name); let relationQuery = Bookshelf.knex(relation.targetTable); // default fields to select _.each(relation.select, (fieldToSelect) => { relationQuery.select(fieldToSelect); }); // custom fields to select _.each(withRelatedFields[withRelatedKey], (toSelect) => { relationQuery.select(toSelect); }); relationQuery.innerJoin( relation.innerJoin.relation, relation.innerJoin.condition[0], relation.innerJoin.condition[1], relation.innerJoin.condition[2] ); relationQuery.whereIn(relation.whereIn, _.map(objects, 'id')); relationQuery.orderBy(relation.orderBy); const queryRelations = await relationQuery; debug('fetched withRelated', relation.name); // arr => obj[post_id] = [...] (faster access) return queryRelations.reduce((obj, item) => { if (!obj[item[relation.whereInKey]]) { obj[item[relation.whereInKey]] = []; } obj[item[relation.whereInKey]].push(_.omit(item, relation.select)); return obj; }, {}); })(); }); debug('attaching relations to', modelName); const relationsToAttachArray = await Promise.all(Object.values(props)); const relationsToAttach = _.zipObject(_.keys(props), relationsToAttachArray); objects = _.map(objects, (object) => { for (const relation in relationsToAttach) { if (!relationsToAttach[relation][object.id]) { object[relation] = []; continue; } object[relation] = relationsToAttach[relation][object.id]; } object = bookshelfPrototype.toJSON.bind({ attributes: object, _originalOptions: { withRelated: Object.keys(relationsToAttach) }, related: function (key) { return object[key]; }, serialize: bookshelfPrototype.serialize, formatsToJSON: bookshelfPrototype.formatsToJSON })(); object = bookshelfPrototype.fixBools(object); object = bookshelfPrototype.fixDatesWhenFetch(object); return object; }); debug('attached relations to', modelName); return objects; } } }); };