Skip to content

Commit

Permalink
chore(gatsby): create single filter cache key generator (#22716)
Browse files Browse the repository at this point in the history
* chore(gatsby): create single filter cache key generator

* Add comment about future proofing
  • Loading branch information
pvdz authored Apr 1, 2020
1 parent 372d127 commit 8b96f95
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 25 deletions.
26 changes: 9 additions & 17 deletions packages/gatsby/src/redux/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { store } from "./"
import { IGatsbyNode } from "./types"
import { createPageDependency } from "./actions/add-page-dependency"

export type FilterCacheKey = string

/**
* Get all nodes from redux store.
*/
Expand Down Expand Up @@ -155,27 +157,22 @@ export const addResolvedNodes = (
* looping over all the nodes, when the number of pages (/nodes) scale up.
*/
export const ensureIndexByTypedChain = (
cacheKey: FilterCacheKey,
chain: string[],
nodeTypeNames: string[],
typedKeyValueIndexes: Map<
string,
FilterCacheKey,
Map<string | number | boolean, Set<IGatsbyNode>>
>
): void => {
const chained = chain.join(`+`)

const nodeTypeNamePrefix = nodeTypeNames.join(`,`) + `/`
// The format of the typedKey is `type,type/path+to+eqobj`
const typedKey = nodeTypeNamePrefix + chained

if (typedKeyValueIndexes.has(typedKey)) {
if (typedKeyValueIndexes.has(cacheKey)) {
return
}

const { nodes, resolvedNodesCache } = store.getState()

const byKeyValue = new Map<string | number | boolean, Set<IGatsbyNode>>()
typedKeyValueIndexes.set(typedKey, byKeyValue)
typedKeyValueIndexes.set(cacheKey, byKeyValue)

nodes.forEach(node => {
if (!nodeTypeNames.includes(node.internal.type)) {
Expand Down Expand Up @@ -232,18 +229,13 @@ export const ensureIndexByTypedChain = (
* per `id` so there's a minor optimization for that (no need for Sets).
*/
export const getNodesByTypedChain = (
chain: string[],
cacheKey: FilterCacheKey,
value: boolean | number | string,
nodeTypeNames: string[],
typedKeyValueIndexes: Map<
string,
FilterCacheKey,
Map<string | number | boolean, Set<IGatsbyNode>>
>
): Set<IGatsbyNode> | undefined => {
const key = chain.join(`+`)

const typedKey = nodeTypeNames.join(`,`) + `/` + key

const byTypedKey = typedKeyValueIndexes?.get(typedKey)
const byTypedKey = typedKeyValueIndexes?.get(cacheKey)
return byTypedKey?.get(value)
}
52 changes: 44 additions & 8 deletions packages/gatsby/src/redux/run-sift.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// @flow

const { default: sift } = require(`sift`)
const { prepareRegex } = require(`../utils/prepare-regex`)
const { makeRe } = require(`micromatch`)
Expand All @@ -19,6 +20,35 @@ const {
getNode: siftGetNode,
} = require(`./nodes`)

/**
* Creates a key for the filterCache
*
* @param {Array<string>} typeNames
* @param {DbQuery} filter
* @returns {FilterCacheKey} (a string: `types.join()/path.join()/operator` )
*/
const createTypedFilterCacheKey = (typeNames, filter) => {
// Note: while `elemMatch` is a special case, in the key it's just `elemMatch`
// (This function is future proof for elemMatch support, won't receive it yet)
let f = filter
let comparator = ``
let paths /*: Array<string>*/ = []
while (f) {
paths.push(...f.path)
if (f.type === `elemMatch`) {
let q /*: IDbQueryElemMatch*/ = f
f = q.nestedQuery
} else {
let q /*: IDbQueryQuery*/ = f
comparator = q.query.comparator
break
}
}

// Note: the separators (`,` and `/`) are arbitrary but must be different
return typeNames.join(`,`) + `/` + comparator + `/` + paths.join(`,`)
}

/////////////////////////////////////////////////////////////////////
// Parse filter
/////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -105,7 +135,7 @@ function handleMany(siftArgs, nodes) {
*
* @param {Array<DbQuery>} filters Resolved. (Should be checked by caller to exist)
* @param {Array<string>} nodeTypeNames
* @param {Map<string, Map<string | number | boolean, Set<IGatsbyNode>>>} typedKeyValueIndexes
* @param {Map<FilterCacheKey, Map<string | number | boolean, Set<IGatsbyNode>>>} typedKeyValueIndexes
* @returns {Array<IGatsbyNode> | undefined}
*/
const runFlatFiltersWithoutSift = (
Expand Down Expand Up @@ -149,7 +179,7 @@ const runFlatFiltersWithoutSift = (
/**
* @param {Array<DbQuery>} filters
* @param {Array<string>} nodeTypeNames
* @param {Map<string, Map<string | number | boolean, Set<IGatsbyNode>>>} typedKeyValueIndexes
* @param {Map<FilterCacheKey, Map<string | number | boolean, Set<IGatsbyNode>>>} typedKeyValueIndexes
* @returns {Array<Set<IGatsbyNode>> | undefined} Undefined means at least one
* cache was not found. Must fallback to sift.
*/
Expand All @@ -163,15 +193,21 @@ const getBucketsForFilters = (filters, nodeTypeNames, typedKeyValueIndexes) => {
query: { value: targetValue },
} = filter

ensureIndexByTypedChain(chain, nodeTypeNames, typedKeyValueIndexes)
let cacheKey = createTypedFilterCacheKey(nodeTypeNames, filter)

const nodesByKeyValue = getNodesByTypedChain(
ensureIndexByTypedChain(
cacheKey,
chain,
targetValue,
nodeTypeNames,
typedKeyValueIndexes
)

const nodesByKeyValue = getNodesByTypedChain(
cacheKey,
targetValue,
typedKeyValueIndexes
)

// If we couldn't find the needle then maybe sift can, for example if the
// schema contained a proxy; `slug: String @proxy(from: "slugInternal")`
// There are also cases (and tests) where id exists with a different type
Expand Down Expand Up @@ -202,7 +238,7 @@ const getBucketsForFilters = (filters, nodeTypeNames, typedKeyValueIndexes) => {
* @property {boolean} args.firstOnly true if you want to return only the first
* result found. This will return a collection of size 1. Not a single element
* @property {{filter?: Object, sort?: Object} | undefined} args.queryArgs
* @property {undefined | Map<string, Map<string | number | boolean, Set<IGatsbyNode>>>} args.typedKeyValueIndexes
* @property {undefined | Map<FilterCacheKey, Map<string | number | boolean, Set<IGatsbyNode>>>} args.typedKeyValueIndexes
* May be undefined. A cache of indexes where you can look up Nodes grouped
* by a key: `types.join(',')+'/'+filterPath.join('+')`, which yields a Map
* which holds a Set of Nodes for the value that the filter is trying to eq
Expand Down Expand Up @@ -243,7 +279,7 @@ exports.runSift = runFilterAndSort
* @param {Array<DbQuery> | undefined} filterFields
* @param {boolean} firstOnly
* @param {Array<string>} nodeTypeNames
* @param {undefined | Map<string, Map<string | number | boolean, Set<IGatsbyNode>>>} typedKeyValueIndexes
* @param {undefined | Map<FilterCacheKey, Map<string | number | boolean, Set<IGatsbyNode>>>} typedKeyValueIndexes
* @param resolvedFields
* @returns {Array<IGatsbyNode> | undefined} Collection of results. Collection
* will be limited to 1 if `firstOnly` is true
Expand Down Expand Up @@ -318,7 +354,7 @@ const filterToStats = (
*
* @param {Array<DbQuery>} filters Resolved. (Should be checked by caller to exist)
* @param {Array<string>} nodeTypeNames
* @param {Map<string, Map<string | number | boolean, Set<IGatsbyNode>>>} typedKeyValueIndexes
* @param {Map<FilterCacheKey, Map<string | number | boolean, Set<IGatsbyNode>>>} typedKeyValueIndexes
* @returns {Array|undefined} Collection of results
*/
const filterWithoutSift = (filters, nodeTypeNames, typedKeyValueIndexes) => {
Expand Down

0 comments on commit 8b96f95

Please sign in to comment.