123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- // ██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗ ██████╗ █████╗ ██████╗██╗ ██╗███████╗
- // ██╔═══██╗██║ ██║██╔════╝██╔══██╗╚██╗ ██╔╝ ██╔════╝██╔══██╗██╔════╝██║ ██║██╔════╝
- // ██║ ██║██║ ██║█████╗ ██████╔╝ ╚████╔╝ ██║ ███████║██║ ███████║█████╗
- // ██║▄▄ ██║██║ ██║██╔══╝ ██╔══██╗ ╚██╔╝ ██║ ██╔══██║██║ ██╔══██║██╔══╝
- // ╚██████╔╝╚██████╔╝███████╗██║ ██║ ██║ ╚██████╗██║ ██║╚██████╗██║ ██║███████╗
- // ╚══▀▀═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝
- //
- // The query cache is used to hold the results of multiple queries. It's used in
- // adapters that need to perform multiple queries to fufill a request. This is
- // most commonly required when joins are performed with certain keys such as
- // skip, sort, or limit. It acts as a mini query heap and provides a way to
- // generate nested records that can be returned to the user.
- //
- // It provides a few methods for working with it:
- //
- // setParents - Sets the top level records. These will be returned as an array
- // whenever the results of the cache are generated.
- //
- // getParents - Returns an array of the current parent records.
- //
- // set - Adds an instruction set for a parent and children records to
- // the cache. Each item that is passed in should represent the logic
- // for connecting an array of children records to a single parent.
- //
- // extend - Extends a child cache to add more records. This is called when
- // multiple queries are run to add more values to an association.
- //
- // combineRecords - Responsible for creating a set of nested records where the
- // parents contain nested children records based on the join
- // logic set on it's cache item.
- //
- var _ = require('@sailshq/lodash');
- module.exports = function queryCache() {
- // Hold values used to keep track of records internally
- var store = [];
- var parents = [];
- // ╔═╗╔═╗╔╦╗ ┌─┐┌─┐┬─┐┌─┐┌┐┌┌┬┐┌─┐
- // ╚═╗║╣ ║ ├─┘├─┤├┬┘├┤ │││ │ └─┐
- // ╚═╝╚═╝ ╩ ┴ ┴ ┴┴└─└─┘┘└┘ ┴ └─┘
- var setParents = function setParents(values) {
- // Normalize values to an array
- if (!_.isArray(values)) {
- values = [values];
- }
- parents = parents.concat(values);
- };
- // ╔═╗╔═╗╔╦╗ ┌─┐┌─┐┬─┐┌─┐┌┐┌┌┬┐┌─┐
- // ║ ╦║╣ ║ ├─┘├─┤├┬┘├┤ │││ │ └─┐
- // ╚═╝╚═╝ ╩ ┴ ┴ ┴┴└─└─┘┘└┘ ┴ └─┘
- var getParents = function getParents() {
- return parents;
- };
- // ╔═╗╔═╗╔╦╗ ┌─┐ ┌─┐┬ ┬┬┬ ┌┬┐ ┌─┐┌─┐┌─┐┬ ┬┌─┐
- // ╚═╗║╣ ║ ├─┤ │ ├─┤││ ││ │ ├─┤│ ├─┤├┤
- // ╚═╝╚═╝ ╩ ┴ ┴ └─┘┴ ┴┴┴─┘─┴┘ └─┘┴ ┴└─┘┴ ┴└─┘
- var setChildCache = function setChildCache(values) {
- // Normalize values to an array
- if (!_.isArray(values)) {
- values = [values];
- }
- // Remove any records that are all null
- _.each(values, function cleanseRecords(val) {
- _.remove(val.records, function cleanseRecords(record) {
- var empty = true;
- _.each(_.keys(record), function checkRecordKeys(key) {
- if (!_.isNull(record[key])) {
- empty = false;
- }
- });
- return empty;
- });
- });
- _.each(values, function valueParser(val) {
- store.push({
- attrName: val.attrName,
- parentPkAttr: val.parentPkAttr,
- records: val.records || [],
- keyName: val.keyName,
- type: val.type,
- belongsToPkValue: val.belongsToPkValue,
- // Optional (only used if implementing a HAS_FK strategy)
- belongsToFkValue: val.belongsToFkValue
- });
- });
- };
- // ╔═╗═╗ ╦╔╦╗╔═╗╔╗╔╔╦╗ ┌─┐ ┌─┐┌─┐┌─┐┬ ┬┌─┐ ┬─┐┌─┐┌─┐┌─┐┬─┐┌┬┐
- // ║╣ ╔╩╦╝ ║ ║╣ ║║║ ║║ ├─┤ │ ├─┤│ ├─┤├┤ ├┬┘├┤ │ │ │├┬┘ ││
- // ╚═╝╩ ╚═ ╩ ╚═╝╝╚╝═╩╝ ┴ ┴ └─┘┴ ┴└─┘┴ ┴└─┘ ┴└─└─┘└─┘└─┘┴└──┴┘
- // Given a result set from a child query, parse the records and attach them to
- // the correct cache parent.
- var extend = function extend(records, instructions) {
- // Create a local cache to hold the grouped records.
- var localCache = {};
- // Grab the alias being used by the records
- var alias = _.isArray(instructions) ? _.first(instructions).alias : instructions.alias;
- // Process each record grouping them together as needed.
- _.each(records, function processRecord(record) {
- // Hold the child key used to group
- var childKey;
- // If this is not a many-to-many query then just group the records by
- // the child key defined in the instructions. This will be used to
- // determine which cache record they belong to.
- if (!_.isArray(instructions)) {
- childKey = instructions.childKey;
- // Ensure a value in the cache exists for the parent
- if (!_.has(localCache, record[childKey])) {
- localCache[record[childKey]] = [];
- }
- localCache[record[childKey]].push(record);
- }
- // If this IS a many-to-many then there is a bit more to do.
- if (_.isArray(instructions)) {
- // Grab the special "foreign key" we attach and make sure to remove it
- var fk = '_parent_fk';
- var fkValue = record[fk];
- // Ensure a value in the cache exists for the parent
- if (!_.has(localCache, fkValue)) {
- localCache[fkValue] = [];
- }
- // Delete the foreign key value that was added as a part of the join
- // process. It's not a value that the user is interested in.
- delete record[fk];
- // Ensure the record is valid and not made up of all `null` values
- var values = _.uniq(_.values(record));
- if (values.length < 2 && _.isNull(values[0])) {
- return;
- }
- // Add the record to the local cache
- localCache[fkValue].push(record);
- // Ensure there aren't duplicates in here
- localCache[fkValue] = _.uniq(localCache[fkValue], _.last(instructions).childKey);
- }
- });
- // Find the cached parents for this alias
- var cachedParents = _.filter(store, { attrName: alias });
- // Extend the parent cache with the child records related to them
- _.each(cachedParents, function extendCache(parent) {
- var childRecords = localCache[parent.belongsToPkValue];
- // If there are no child records, there is nothing to do
- if (!childRecords || !childRecords.length) {
- return;
- }
- if (!parent.records) {
- parent.records = [];
- }
- parent.records = parent.records.concat(childRecords);
- });
- };
- // ╔═╗╔═╗╔╦╗╔╗ ╦╔╗╔╔═╗ ┬─┐┌─┐┌─┐┌─┐┬─┐┌┬┐┌─┐
- // ║ ║ ║║║║╠╩╗║║║║║╣ ├┬┘├┤ │ │ │├┬┘ ││└─┐
- // ╚═╝╚═╝╩ ╩╚═╝╩╝╚╝╚═╝ ┴└─└─┘└─┘└─┘┴└──┴┘└─┘
- // Use the values in the query cache to output a set of nested dictionaries.
- // Each item in the set represents a parent and all the nested children records.
- var combineRecords = function combineRecords() {
- // If there are no parents then the results are always empty
- if (!parents.length) {
- return [];
- }
- // If there are no children records being populated, just return the parents.
- if (!store.length) {
- return parents;
- }
- // For each child in the cache, attach it to the parent
- _.each(store, function attachChildren(cache) {
- // Find the parent for this cache item
- var matchingParentRecord = _.find(parents, function match(parentRecord) {
- return parentRecord[cache.parentPkAttr] === cache.belongsToPkValue;
- });
- // This should always be true, but checking just in case.
- if (_.isObject(matchingParentRecord)) {
- // If the value in `attrName` for this record is not an array,
- // it is probably a foreign key value. Fortunately, at this point
- // we can go ahead and replace it safely since any logic relying on it
- // is complete.
- //
- // In fact, and for the same reason, we can safely override the value of
- // `buffer.attrName` for the parent record at this point, no matter what!
- // This is nice, because `buffer.records` is already sorted, limited, and
- // skipped, so we don't have to mess with that.
- //
- if (cache.records && cache.records.length) {
- matchingParentRecord[cache.keyName] = _.map(cache.records, _.clone);
- } else {
- matchingParentRecord[cache.keyName] = [];
- }
- // Check if the value should be an array or dictionary
- if (_.has(cache, 'type') && cache.type === 1) {
- matchingParentRecord[cache.keyName] = _.first(matchingParentRecord[cache.keyName]) || [];
- }
- }
- });
- // Collect all the aliases used by the query and ensure the nested objects
- // have a value for it.
- var aliases = _.uniq(_.map(store, 'keyName'));
- _.each(aliases, function normalizeAlias(alias) {
- _.each(parents, function setParentAlias(parentRecord) {
- parentRecord[alias] = parentRecord[alias] || [];
- });
- });
- return parents;
- };
- return {
- setParents: setParents,
- getParents: getParents,
- set: setChildCache,
- extend: extend,
- combineRecords: combineRecords
- };
- };
|