initialize-query-cache.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. // ██╗███╗ ██╗██╗████████╗██╗ █████╗ ██╗ ██╗███████╗███████╗
  2. // ██║████╗ ██║██║╚══██╔══╝██║██╔══██╗██║ ██║╚══███╔╝██╔════╝
  3. // ██║██╔██╗ ██║██║ ██║ ██║███████║██║ ██║ ███╔╝ █████╗
  4. // ██║██║╚██╗██║██║ ██║ ██║██╔══██║██║ ██║ ███╔╝ ██╔══╝
  5. // ██║██║ ╚████║██║ ██║ ██║██║ ██║███████╗██║███████╗███████╗
  6. // ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝╚══════╝╚══════╝
  7. //
  8. // ██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗ ██████╗ █████╗ ██████╗██╗ ██╗███████╗
  9. // ██╔═══██╗██║ ██║██╔════╝██╔══██╗╚██╗ ██╔╝ ██╔════╝██╔══██╗██╔════╝██║ ██║██╔════╝
  10. // ██║ ██║██║ ██║█████╗ ██████╔╝ ╚████╔╝ ██║ ███████║██║ ███████║█████╗
  11. // ██║▄▄ ██║██║ ██║██╔══╝ ██╔══██╗ ╚██╔╝ ██║ ██╔══██║██║ ██╔══██║██╔══╝
  12. // ╚██████╔╝╚██████╔╝███████╗██║ ██║ ██║ ╚██████╗██║ ██║╚██████╗██║ ██║███████╗
  13. // ╚══▀▀═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝
  14. //
  15. // Builds up a query cache for use when native joins are performed. The purpose
  16. // of this is because in some cases a query can't be fulfilled in a single query.
  17. // The Query Cache is responsible for holding intermediate values until all of
  18. // the operations are completed. The records can then be nested together and
  19. // returned as a single array of nested values.
  20. var _ = require('@sailshq/lodash');
  21. var utils = require('waterline-utils');
  22. module.exports = function initializeQueryCache(options) {
  23. // ╦ ╦╔═╗╦ ╦╔╦╗╔═╗╔╦╗╔═╗ ┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐
  24. // ╚╗╔╝╠═╣║ ║ ║║╠═╣ ║ ║╣ │ │├─┘ │ ││ ││││└─┐
  25. // ╚╝ ╩ ╩╩═╝╩═╩╝╩ ╩ ╩ ╚═╝ └─┘┴ ┴ ┴└─┘┘└┘└─┘
  26. if (_.isUndefined(options) || !_.isPlainObject(options)) {
  27. throw new Error('Invalid options argument. Options must contain: connection, query, model, schemaName, and tableName.');
  28. }
  29. if (!_.has(options, 'instructions') || !_.isPlainObject(options.instructions)) {
  30. throw new Error('Invalid option used in options argument. Missing or invalid instructions.');
  31. }
  32. if (!_.has(options, 'models') || !_.isPlainObject(options.models)) {
  33. throw new Error('Invalid option used in options argument. Missing or invalid models.');
  34. }
  35. if (!_.has(options, 'sortedResults') || !_.isPlainObject(options.sortedResults)) {
  36. throw new Error('Invalid option used in options argument. Missing or invalid sortedResults.');
  37. }
  38. // ╔╗ ╦ ╦╦╦ ╔╦╗ ┌┐┌┌─┐┬ ┬ ┌─┐┌─┐┌─┐┬ ┬┌─┐
  39. // ╠╩╗║ ║║║ ║║ │││├┤ │││ │ ├─┤│ ├─┤├┤
  40. // ╚═╝╚═╝╩╩═╝═╩╝ ┘└┘└─┘└┴┘ └─┘┴ ┴└─┘┴ ┴└─┘
  41. // Build up a new cache to use to hold query results
  42. var queryCache = utils.joins.queryCache();
  43. // ╔═╗╦═╗╔═╗╔═╗╔═╗╔═╗╔═╗ ┌─┐┌─┐┌─┐┬ ┬┌─┐ ┬ ┬┌─┐┬ ┬ ┬┌─┐┌─┐
  44. // ╠═╝╠╦╝║ ║║ ║╣ ╚═╗╚═╗ │ ├─┤│ ├─┤├┤ └┐┌┘├─┤│ │ │├┤ └─┐
  45. // ╩ ╩╚═╚═╝╚═╝╚═╝╚═╝╚═╝ └─┘┴ ┴└─┘┴ ┴└─┘ └┘ ┴ ┴┴─┘└─┘└─┘└─┘
  46. _.each(options.instructions, function processInstruction(val, key) {
  47. // Grab the instructions that define a particular join set
  48. var popInstructions = val.instructions;
  49. // Grab the strategy used for the join
  50. var strategy = val.strategy.strategy;
  51. // Find the Primary Key of the parent used in the join
  52. var model = options.models[_.first(popInstructions).parent];
  53. if (!model) {
  54. throw new Error('Invalid parent table name used when caching query results. Perhaps the join criteria is invalid?');
  55. }
  56. var pkAttr = model.primaryKey;
  57. var pkColumnName = model.definition[pkAttr].columnName || pkAttr;
  58. // Build an alias to use for the association. The alias is the name of the
  59. // assocation defined by the user. It's created in a model whenever a
  60. // model or collection is defined.
  61. var alias;
  62. // Hold an optional keyName to use in strategy 1. This represents the
  63. // foreign key value on the parent that will be replaced by the populated
  64. // value.
  65. var keyName;
  66. // If the join strategy is a hasFk strategy this means the parent contains
  67. // the value being populated - i.e. populating a model record. Therefore
  68. // the keyName is the name of the attribute on the parent record.
  69. if (val.strategy && val.strategy.strategy === 1) {
  70. alias = _.first(popInstructions).alias;
  71. keyName = _.first(popInstructions).parentKey;
  72. // Otherwise this must be a collection populating so just grab the alias
  73. // directly off the instructions.
  74. } else {
  75. alias = _.first(popInstructions).alias;
  76. }
  77. // Process each of the parents and build up a local cache containing
  78. // values for the populated children.
  79. _.each(options.sortedResults.parents, function buildAliasCache(parentRecord) {
  80. var cache = {
  81. attrName: key,
  82. parentPkAttr: pkColumnName,
  83. belongsToPkValue: parentRecord[pkColumnName],
  84. keyName: keyName || alias,
  85. type: strategy
  86. };
  87. // Grab the join keys used in the query
  88. var childKey = _.first(popInstructions).childKey;
  89. var parentKey = _.first(popInstructions).parentKey;
  90. // Find any records in the children that match up to the join keys
  91. var records = _.filter(options.sortedResults.children[alias], function findChildren(child) {
  92. // If this is a VIA_JUNCTOR join, use the foreign key we built up,
  93. // otherwise check equality between child and parent join keys.
  94. if (strategy === 3) {
  95. return child._parent_fk === parentRecord[parentKey];
  96. }
  97. return child[childKey] === parentRecord[parentKey];
  98. });
  99. // If this is a many-to-many strategy, be sure to clear the foreign
  100. // key value that was added as part of the join process. The end user
  101. // doesn't care about that.
  102. if (strategy === 3) {
  103. _.each(records, function cleanRecords(record) {
  104. delete record._parent_fk;
  105. });
  106. }
  107. // Store the child on the cache
  108. if (records.length) {
  109. cache.records = records;
  110. }
  111. // Store the local cache value in the query cache
  112. queryCache.set(cache);
  113. }); // </ buildAliasCache >
  114. }); // </ processInstructions >
  115. // Return the QueryCache
  116. return queryCache;
  117. };