Skip to content

Commit

Permalink
wip: path exclusion
Browse files Browse the repository at this point in the history
  • Loading branch information
juice49 committed Nov 27, 2024
1 parent c1cad0d commit 6cc4b4f
Showing 1 changed file with 63 additions and 17 deletions.
80 changes: 63 additions & 17 deletions packages/sanity/src/core/search/groq2024/createSearchQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ function toOrderClause(orderBy: SearchSort[]): string {
.join(',')
}

// TODO: Remove.
const TEMP_ENABLE_PATH_EXCLUSION = false

/**
* @internal
*/
Expand All @@ -70,34 +73,77 @@ export function createSearchQuery(
const flattenedSpecs = specs
.map(({typeName, paths}) => paths.map((path) => ({...path, typeName})))
.flat()
.filter(({weight}) => weight !== 0)

// TODO: Unnecessary when `!isScored`.
const groupedSpecs = groupBy(
flattenedSpecs,
(entry) => `${entry.path} match text::query($__query), ${entry.weight}`,
)
const groupedSpecs = groupBy(flattenedSpecs, (entry) => [entry.path, entry.weight].join(':'))

const zeroWeightedSpecs = flattenedSpecs.filter(({weight}) => weight === 0)
const zeroWeightedSpecsByType = groupBy(zeroWeightedSpecs, 'typeName')
const zeroWeightedTypes = Object.keys(zeroWeightedSpecsByType)
const hasZeroWeightedSpec = zeroWeightedSpecs.length !== 0

// Construct a GROQ expression that:
// 1. Matches all attributes if the type has no excluded attributes.
// 2. Matches all non-excluded attributes if the type has excluded attributes.
const conditionalMatches = Object.entries(zeroWeightedSpecsByType)
.map(([typeName, spec]) => {
const excludedPath = spec.map(({path}) => path).join(', ')
return [
// [2]
`(`,
`_type == ${JSON.stringify(typeName)}`,
['&&', `@ match text::query($__query, { "exclude": (${excludedPath}) })`],
')',
]
.flat()
.join('')
})
.join(' || ')

// The initial negation (1) could be removed if types containing zero weights equal the types being searched for.
const _baseMatch = hasZeroWeightedSpec
? [
'(',
[
// [1]
'(',
`!(_type in ${JSON.stringify(zeroWeightedTypes)})`,
'&&',
'@ match text::query($__query)',
')',
],
['||', conditionalMatches],
')',
]
.flat()
.join('')
: '@ match text::query($__query)'

// TODO: Remove.
const baseMatch = TEMP_ENABLE_PATH_EXCLUSION ? _baseMatch : '@ match text::query($__query)'

// TODO: Unnecessary when `!isScored`.
const score = Object.entries(groupedSpecs)
.map(
([args, entries]) =>
`boost(_type in ${JSON.stringify(entries.map((entry) => entry.typeName))} && ${args})`,
)
.concat([`@ match text::query($__query)`])
.flatMap(([, entries]) => {
if (entries.some(({weight}) => weight === 0)) {
return []
}
return `boost(_type in ${JSON.stringify(entries.map((entry) => entry.typeName))} && ${entries[0].path} match text::query($__query), ${entries[0].weight})`
})
.concat(baseMatch)

const sortOrder = options?.sort ?? [{field: '_score', direction: 'desc'}]
const isScored = sortOrder.some(({field}) => field === '_score')

const filters = [
const filters: string[] = [
'_type in $__types',
// TODO: It will be necessary to omit zero-weighted paths when `!isScored`.
isScored ? false : `@ match text::query($__query)`,
options.filter ? `(${options.filter})` : false,
searchTerms.filter ? `(${searchTerms.filter})` : false,
// If the search request doesn't use scoring, directly filter documents.
isScored ? [] : baseMatch,
options.filter ? `(${options.filter})` : [],
searchTerms.filter ? `(${searchTerms.filter})` : [],
'!(_id in path("versions.**"))',
options.cursor,
].filter((baseFilter) => typeof baseFilter === 'string')
options.cursor ?? [],
].flat()

const projectionFields = sortOrder.map(({field}) => field).concat('_type', '_id')
const projection = projectionFields.join(', ')
Expand Down

0 comments on commit 6cc4b4f

Please sign in to comment.