From 32c432dd1376b9faec51376294f27d116b1f6370 Mon Sep 17 00:00:00 2001
From: paultranvan
Date: Thu, 23 Jun 2022 18:29:39 +0200
Subject: [PATCH] feat: Add the partial filter fields in the index name
When using a partial index, its fields weren't part of the index name.
Therefore, we might have a conflict situation where there is already an
existing index without the partial index.
Fix https://github.com/cozy/cozy-client/issues/1054
---
docs/api/cozy-stack-client.md | 23 ++++++
.../src/DocumentCollection.js | 8 +-
.../src/DocumentCollection.spec.js | 2 +-
packages/cozy-stack-client/src/mangoIndex.js | 27 ++++++-
.../cozy-stack-client/src/mangoIndex.spec.js | 81 ++++++++++++++++++-
5 files changed, 134 insertions(+), 7 deletions(-)
diff --git a/docs/api/cozy-stack-client.md b/docs/api/cozy-stack-client.md
index 82598af0a..88f6f9cca 100644
--- a/docs/api/cozy-stack-client.md
+++ b/docs/api/cozy-stack-client.md
@@ -73,6 +73,11 @@ through OAuth.
getIllegalCharacters ⇒ string
Get the list of illegal characters in the file name
+getIndexNameFromFields ⇒ string
+Name an index, based on its indexed fields and partial filter.
+It follows this naming convention:
+`by_{indexed_field1}and {indexed_field2}filter {partial_filter_field1}and {partial_filter_field2}
+
getIndexFields ⇒ Array
Compute fields that should be indexed for a mango
query to work
@@ -1796,6 +1801,23 @@ Get the list of illegal characters in the file name
| --- | --- | --- |
| name | string
| the file name |
+
+
+## getIndexNameFromFields ⇒ string
+Name an index, based on its indexed fields and partial filter.
+
+It follows this naming convention:
+`by_{indexed_field1}_and_{indexed_field2}_filter_{partial_filter_field1}_and_{partial_filter_field2}
+
+**Kind**: global constant
+**Returns**: string
- The index name, built from the fields
+
+| Param | Type | Description |
+| --- | --- | --- |
+| fields | Array.<string>
| The indexed fields |
+| params | object
| The additional params |
+| params.partialFilterFields | Array.<string>
| The partial filter fields |
+
## getIndexFields ⇒ Array
@@ -2402,6 +2424,7 @@ Document representing a io.cozy.jobs
| --- | --- | --- |
| [sort] | Array.<object>
| The sorting parameters |
| [fields] | Array.<string>
| The fields to return |
+| [partialFilterFields] | Array.<string>
| The partial filter fields |
| [limit] | number
\| null
| For pagination, the number of results to return |
| [skip] | number
\| null
| For skip-based pagination, the number of referenced files to skip |
| [indexId] | string
\| null
| The _id of the CouchDB index to use for this request |
diff --git a/packages/cozy-stack-client/src/DocumentCollection.js b/packages/cozy-stack-client/src/DocumentCollection.js
index 77d641991..96314fbe3 100644
--- a/packages/cozy-stack-client/src/DocumentCollection.js
+++ b/packages/cozy-stack-client/src/DocumentCollection.js
@@ -220,8 +220,14 @@ class DocumentCollection {
if (!indexedFields) {
indexedFields = getIndexFields({ sort: options.sort, selector })
}
+ const partialFilterFields = partialFilter
+ ? getIndexFields({ partialFilter })
+ : null
+
const existingIndex = await this.findExistingIndex(selector, options)
- const indexName = getIndexNameFromFields(indexedFields)
+ const indexName = getIndexNameFromFields(indexedFields, {
+ partialFilterFields
+ })
if (!existingIndex) {
await this.createIndex(indexedFields, {
partialFilter,
diff --git a/packages/cozy-stack-client/src/DocumentCollection.spec.js b/packages/cozy-stack-client/src/DocumentCollection.spec.js
index 7b398eab2..af7c2181b 100644
--- a/packages/cozy-stack-client/src/DocumentCollection.spec.js
+++ b/packages/cozy-stack-client/src/DocumentCollection.spec.js
@@ -759,7 +759,7 @@ describe('DocumentCollection', () => {
'POST',
'/data/io.cozy.todos/_design/123456/copy?rev=1-123',
null,
- { headers: { Destination: '_design/by_label_and_done' } }
+ { headers: { Destination: '_design/by_label_and_done_filter_trashed' } }
)
expect(client.fetchJSON).toHaveBeenNthCalledWith(
4,
diff --git a/packages/cozy-stack-client/src/mangoIndex.js b/packages/cozy-stack-client/src/mangoIndex.js
index 1063087cd..f035311af 100644
--- a/packages/cozy-stack-client/src/mangoIndex.js
+++ b/packages/cozy-stack-client/src/mangoIndex.js
@@ -12,6 +12,7 @@ import isEqual from 'lodash/isEqual'
*
* @property {Array} [sort] The sorting parameters
* @property {Array} [fields] The fields to return
+ * @property {Array} [partialFilterFields] The partial filter fields
* @property {number|null} [limit] For pagination, the number of results to return
* @property {number|null} [skip] For skip-based pagination, the number of referenced files to skip
* @property {string|null} [indexId] The _id of the CouchDB index to use for this request
@@ -35,8 +36,25 @@ export const normalizeDesignDoc = designDoc => {
return { id, _id: id, ...designDoc.doc }
}
-export const getIndexNameFromFields = fields => {
- return `by_${fields.join('_and_')}`
+/**
+ * Name an index, based on its indexed fields and partial filter.
+ *
+ * It follows this naming convention:
+ * `by_{indexed_field1}_and_{indexed_field2}_filter_{partial_filter_field1}_and_{partial_filter_field2}
+ *
+ * @param {Array} fields - The indexed fields
+ * @param {object} params - The additional params
+ * @param {Array} params.partialFilterFields - The partial filter fields
+ * @returns {string} The index name, built from the fields
+ */
+export const getIndexNameFromFields = (
+ fields,
+ { partialFilterFields } = {}
+) => {
+ const indexName = `by_${fields.join('_and_')}`
+ return partialFilterFields
+ ? `${indexName}_filter_${partialFilterFields.join('_and_')}`
+ : indexName
}
export const transformSort = sort => {
@@ -61,11 +79,12 @@ export const transformSort = sort => {
*
* @returns {Array} - Fields to index
*/
-export const getIndexFields = ({ selector, sort = [] }) => {
+export const getIndexFields = ({ selector, partialFilter, sort = [] }) => {
return Array.from(
new Set([
...sort.map(sortOption => head(Object.keys(sortOption))),
- ...(selector ? Object.keys(selector) : [])
+ ...(selector ? Object.keys(selector) : []),
+ ...(partialFilter ? Object.keys(partialFilter) : [])
])
)
}
diff --git a/packages/cozy-stack-client/src/mangoIndex.spec.js b/packages/cozy-stack-client/src/mangoIndex.spec.js
index c5ff65254..c7802c229 100644
--- a/packages/cozy-stack-client/src/mangoIndex.spec.js
+++ b/packages/cozy-stack-client/src/mangoIndex.spec.js
@@ -1,4 +1,9 @@
-import { isMatchingIndex, isInconsistentIndex } from './mangoIndex'
+import {
+ isMatchingIndex,
+ isInconsistentIndex,
+ getIndexFields,
+ getIndexNameFromFields
+} from './mangoIndex'
const buildDesignDoc = (fields, { partialFilter, id } = {}) => {
return {
@@ -103,3 +108,77 @@ describe('inconsistent index', () => {
expect(isInconsistentIndex(index2)).toBe(false)
})
})
+
+describe('getIndexFields', () => {
+ it('should return nothing', () => {
+ expect(getIndexFields({})).toEqual([])
+ })
+
+ it('should return fields from selector', () => {
+ const selector = {
+ _id: {
+ $gt: null
+ },
+ name: 'toto'
+ }
+ expect(getIndexFields({ selector })).toEqual(['_id', 'name'])
+ })
+
+ it('should return fields from sort', () => {
+ const selector = {}
+ const sort = [{ _id: 'asc' }, { name: 'asc' }]
+ expect(getIndexFields({ selector, sort })).toEqual(['_id', 'name'])
+ })
+
+ it('should return fields from partial filter', () => {
+ const partialFilter = {
+ date: {
+ $exists: false
+ },
+ trashed: {
+ $ne: true
+ }
+ }
+ expect(getIndexFields({ partialFilter })).toEqual(['date', 'trashed'])
+ })
+
+ it('should return all fields', () => {
+ const selector = {
+ _id: {
+ $gt: null
+ },
+ name: 'toto'
+ }
+ const sort = [{ _id: 'asc' }, { name: 'asc' }]
+
+ const partialFilter = {
+ date: {
+ $exists: false
+ },
+ trashed: {
+ $ne: true
+ }
+ }
+ expect(getIndexFields({ selector, sort, partialFilter })).toEqual([
+ '_id',
+ 'name',
+ 'date',
+ 'trashed'
+ ])
+ })
+})
+
+describe('getIndexNameFromFields', () => {
+ it('should return index fields', () => {
+ const fields = ['_id', 'name']
+ expect(getIndexNameFromFields(fields)).toEqual('by__id_and_name')
+ })
+
+ it('should return index fields with partial filter', () => {
+ const fields = ['_id', 'name']
+ const partialFilterFields = ['date', 'trashed']
+ expect(getIndexNameFromFields(fields, { partialFilterFields })).toEqual(
+ 'by__id_and_name_filter_date_and_trashed'
+ )
+ })
+})