Skip to content

Commit

Permalink
feat: Improve index naming
Browse files Browse the repository at this point in the history
  • Loading branch information
cballevre committed Jul 1, 2024
1 parent b02a87b commit 700db23
Show file tree
Hide file tree
Showing 3 changed files with 392 additions and 12 deletions.
69 changes: 58 additions & 11 deletions packages/cozy-stack-client/src/DocumentCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import {
transformSort,
getIndexFields,
isMatchingIndex,
normalizeDesignDoc
normalizeDesignDoc,
getNewIndexName
} from './mangoIndex'
import * as querystring from './querystring'
import { FetchError } from './errors'
Expand Down Expand Up @@ -219,6 +220,19 @@ class DocumentCollection {
}
}

async migrateOldNamedIndex(
indexedFields,
partialFilter,
existingIndex,
indexName
) {
await this.destroyIndex(existingIndex)
await this.createIndex(indexedFields, {
partialFilter,
indexName
})
}

/**
* Handle index creation if it is missing.
*
Expand All @@ -240,15 +254,25 @@ class DocumentCollection {
? getIndexFields({ partialFilter })
: null

const existingIndex = await this.findExistingIndex(selector, options)
const indexName = getIndexNameFromFields(indexedFields, {
const oldName = `_design/${getIndexNameFromFields(indexedFields, {
partialFilterFields
})
})}`

const existingIndex = await this.findExistingIndex(selector, options)

const indexName = getNewIndexName(selector, { partialFilter })
if (!existingIndex) {
await this.createIndex(indexedFields, {
partialFilter,
indexName
})
} else if (existingIndex._id === oldName) {
await this.migrateOldNamedIndex(
indexedFields,
partialFilter,
existingIndex,
indexName
)
} else if (existingIndex._id !== `_design/${indexName}`) {
await this.migrateUnamedIndex(existingIndex, indexName)
} else {
Expand Down Expand Up @@ -493,7 +517,7 @@ The returned documents are paginated by the stack.
* @returns {MangoQueryOptions} Mango options
*/
toMangoOptions(selector, options = {}) {
let { sort, indexedFields, partialFilter } = options
let { sort, indexedFields, partialFilter, partialIndex } = options
const { fields, skip = 0, limit, bookmark } = options

sort = transformSort(sort)
Expand All @@ -508,14 +532,14 @@ The returned documents are paginated by the stack.
? indexedFields
: getIndexFields({ sort, selector })

const partialFilterFields = partialFilter
? getIndexFields({ partialFilter })
: null
const indexName =
options.indexId ||
`_design/${getIndexNameFromFields(indexedFields, {
partialFilterFields
`_design/${getNewIndexName(selector, {
partialFilter
})}`

console.log('indexName', indexName)

if (sort) {
const sortOrders = uniq(
sort.map(sortOption => head(Object.values(sortOption)))
Expand Down Expand Up @@ -714,6 +738,7 @@ The returned documents are paginated by the stack.
async findExistingIndex(selector, options) {
let { sort, indexedFields, partialFilter } = options
const indexes = await this.fetchAllMangoIndexes()
console.log('indexes', indexes)
if (indexes.length < 1) {
return null
}
Expand All @@ -725,7 +750,29 @@ The returned documents are paginated by the stack.
const existingIndex = indexes.find(index => {
return isMatchingIndex(index, fieldsToIndex, partialFilter)
})
return existingIndex

if (existingIndex) {
return existingIndex
}

const partialFilterFields = partialFilter
? getIndexFields({ partialFilter })
: null

const oldName = `_design/${getIndexNameFromFields(indexedFields, {
partialFilterFields
})}`

const existingIndexWithOldName = indexes.find(
index => index._id === oldName
)

if (existingIndexWithOldName) {
console.log('existingIndexWithOldName', existingIndexWithOldName)
return existingIndexWithOldName
}

return null
}

/**
Expand Down
97 changes: 97 additions & 0 deletions packages/cozy-stack-client/src/mangoIndex.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,102 @@ export const normalizeDesignDoc = designDoc => {
return { id, _id: id, ...designDoc.doc }
}

/**
* Process a condition to generate a string key
*
* @param {object} condition - An object representing tcondition
* @param {number} [depth] - Level of recursion
* @returns {string} - The string key of the processed condition
*/
export const processCondition = (condition, depth = 0) => {
if (condition.$or && depth < 3) {
return `(${condition.$or
.map(subCondition => processCondition(subCondition, depth + 1))
.join('_or_')})`
} else if (condition.$and && depth < 3) {
return `(${condition.$and
.map(subCondition => processCondition(subCondition, depth + 1))
.join('_and_')})`
} else {
return Object.keys(condition)
.map(key => (key.startsWith('$') ? key.slice(1) : key))
.join('_and_')
}
}

/**
* Process a selector to generate a string key
*
* @example
* // returns `field1_and_(field2_or_field3)_and_field4`
* processCondition({
* field1: 'value1',
* $or: [{ field2: 'value2' }, { field3: 'value3' }],
* field4: 'value4'
* });
*
* @param {object} selector - An object representing the selector
* @returns {string} - The string key of the processed selector
*/
export const processSelector = selector => {
const conditions = Object.entries(selector).map(([key, value]) =>
processCondition({ [key]: value })
)
return conditions.join('_and_')
}

/**
* Flatten an object
*
* @param {*} obj - The object to flatten
* @param {*} parent - The parent key
* @param {*} res - The result object
* @returns {object} - And object with only one level of depth
*/
export const flattenObject = (obj, parent = '', res = {}) => {
Object.entries(obj).forEach(([key, value]) => {
const cleanedKey = key.startsWith('$') ? key.slice(1) : key
const propName = parent ? `${parent}_${cleanedKey}` : cleanedKey
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
flattenObject(value, propName, res)
} else {
res[propName] = value
}
})
return res
}

/**
* Process a partial filter to generate a string key
*
* @param {object} partialFilter - An object representing the partial filter
* @returns {string} - The string key of the processed partial filter
*/
export const processPartialFilter = partialFilter => {
const flatPartialFilter = flattenObject(partialFilter)
return `(${Object.keys(flatPartialFilter)
.map(key => {
if (Array.isArray(flatPartialFilter[key])) {
return `${key}_${flatPartialFilter[key].join('_')}`
} else {
return `${key}_${flatPartialFilter[key]}`
}
})
.join(')_and_(')})`
}

export const getNewIndexName = (selector, options = {}) => {
let baseName = processSelector(selector)

if (options.partialFilter) {
return `by_${baseName}_filter_${processPartialFilter(
options.partialFilter
)}`
}

return `by_${baseName}`
}

/**
* Name an index, based on its indexed fields and partial filter.
*
Expand All @@ -66,6 +162,7 @@ export const getIndexNameFromFields = (
? `${indexName}_filter_${partialFilterFields.join('_and_')}`
: indexName
}

/**
* Transform sort into Array
*
Expand Down
Loading

0 comments on commit 700db23

Please sign in to comment.