cozy-pouch-link 60.30.1 → 60.30.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -144,9 +144,9 @@ var quoteSQLValue = function quoteSQLValue(value) {
144
144
  return value;
145
145
  };
146
146
 
147
- var parseCondition = function parseCondition(field, condition) {
147
+ var parseCondition = function parseCondition(field, condition, columnName) {
148
148
  var conditions = [];
149
- var sqlField = transformMangoFieldInJSONSQL(field);
149
+ var sqlField = transformMangoFieldInJSONSQL(field, columnName);
150
150
 
151
151
  if (typeof condition === 'object' && !Array.isArray(condition)) {
152
152
  for (var operator in condition) {
@@ -189,27 +189,45 @@ var parseCondition = function parseCondition(field, condition) {
189
189
  return conditions.join(' AND ');
190
190
  };
191
191
 
192
- var parseLogicalOperator = function parseLogicalOperator(operator, conditionsArray) {
192
+ var parseLogicalOperator = function parseLogicalOperator(operator, conditionsArray, columnName) {
193
193
  var sqlOperator = operator === '$and' ? 'AND' : 'OR';
194
194
  var parsedConditions = conditionsArray.map(function (cond) {
195
- return "(".concat(mangoSelectorToSQL(cond).replace(/^WHERE /, ''), ")");
195
+ return "(".concat(mangoSelectorToSQL(cond, columnName).replace(/^WHERE /, ''), ")");
196
196
  });
197
197
  return parsedConditions.join(" ".concat(sqlOperator, " "));
198
+ }; // PouchDB keeps a document's id and rev in by-sequence COLUMNS and strips them
199
+ // from the stored JSON, so json_extract(json, '$._id') is NULL on every row. A
200
+ // selector on `_id` would therefore match nothing it is meant to match, and a
201
+ // `$nin` on it would filter nothing at all.
202
+
203
+
204
+ var PHYSICAL_COLUMNS = {
205
+ _id: 'doc_id',
206
+ _rev: 'rev'
198
207
  };
199
208
 
200
209
  var transformMangoFieldInJSONSQL = function transformMangoFieldInJSONSQL(field) {
201
210
  var columnName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'data';
211
+ var physicalColumn = PHYSICAL_COLUMNS[field];
212
+
213
+ if (physicalColumn) {
214
+ // `data` is the query alias, where by-sequence is joined with document-store
215
+ // and the column needs qualifying; `json` is the CREATE INDEX context, which
216
+ // is scoped to by-sequence alone and must stay unqualified.
217
+ return columnName === 'json' ? physicalColumn : "'by-sequence'.".concat(physicalColumn);
218
+ }
219
+
202
220
  return "json_extract(".concat(columnName, ", '$.").concat(field, "')");
203
221
  };
204
222
 
205
- var mangoSelectorToSQL = function mangoSelectorToSQL(selector) {
223
+ var mangoSelectorToSQL = function mangoSelectorToSQL(selector, columnName) {
206
224
  var conditions = [];
207
225
 
208
226
  for (var key in selector) {
209
227
  if (key === '$and' || key === '$or') {
210
- conditions.push(parseLogicalOperator(key, selector[key]));
228
+ conditions.push(parseLogicalOperator(key, selector[key], columnName));
211
229
  } else {
212
- conditions.push(parseCondition(key, selector[key]));
230
+ conditions.push(parseCondition(key, selector[key], columnName));
213
231
  }
214
232
  }
215
233
 
@@ -218,14 +236,14 @@ var mangoSelectorToSQL = function mangoSelectorToSQL(selector) {
218
236
 
219
237
  exports.mangoSelectorToSQL = mangoSelectorToSQL;
220
238
 
221
- var makeWhereClause = function makeWhereClause(selector) {
239
+ var makeWhereClause = function makeWhereClause(selector, columnName) {
222
240
  var baseWhere = 'DELETED = 0';
223
241
 
224
242
  if (!selector) {
225
243
  return baseWhere;
226
244
  }
227
245
 
228
- var mangoWhere = mangoSelectorToSQL(selector);
246
+ var mangoWhere = mangoSelectorToSQL(selector, columnName);
229
247
 
230
248
  if (!mangoWhere) {
231
249
  return baseWhere;
@@ -260,11 +278,24 @@ var makeSQLQueryFromMango = function makeSQLQueryFromMango(_ref2) {
260
278
  var selector = _ref2.selector,
261
279
  sort = _ref2.sort,
262
280
  indexName = _ref2.indexName,
281
+ partialFilter = _ref2.partialFilter,
263
282
  _ref2$limit = _ref2.limit,
264
283
  limit = _ref2$limit === void 0 ? -1 : _ref2$limit,
265
284
  _ref2$skip = _ref2.skip,
266
285
  skip = _ref2$skip === void 0 ? 0 : _ref2$skip;
267
- var whereClause = makeWhereClause(selector);
286
+ var whereClause = makeWhereClause(selector); // Restate the partial filter in the query itself. Relying on the partial index
287
+ // alone is unsafe: CREATE INDEX IF NOT EXISTS never rewrites an index built
288
+ // under an older definition, so a stale unfiltered index would silently return
289
+ // the rows the filter is meant to hide.
290
+
291
+ if (partialFilter) {
292
+ var partialWhere = mangoSelectorToSQL(partialFilter);
293
+
294
+ if (partialWhere) {
295
+ whereClause += " AND (".concat(partialWhere, ")");
296
+ }
297
+ }
298
+
268
299
  var sortClause = makeSortClause(sort); // Scan by-sequence via the mango index, then join document-store on its
269
300
  // winningseq so only WINNING revisions survive (matches CouchDB mango, which
270
301
  // queries the winning rev). `data` aliases 'by-sequence'.json; `deleted` lives
@@ -342,7 +373,10 @@ var makeSQLCreateMangoIndex = function makeSQLCreateMangoIndex(indexName, fields
342
373
  var sql = "\n CREATE INDEX IF NOT EXISTS '".concat(indexName, "'\n ON 'by-sequence'\n (").concat(jsonIndex, ")\n ");
343
374
 
344
375
  if (partialFilter) {
345
- var whereClause = makeWhereClause(partialFilter);
376
+ // `data` only exists as a SELECT alias of 'by-sequence'.json; inside CREATE
377
+ // INDEX the column is `json`. Emitting the alias made every partial index
378
+ // fail with "no such column: data", so the filter was never enforced.
379
+ var whereClause = makeWhereClause(partialFilter, 'json');
346
380
  sql += " WHERE ".concat(whereClause);
347
381
  }
348
382
 
@@ -7,7 +7,8 @@ import {
7
7
  makeSQLQueryForIds,
8
8
  keepDocWitHighestRev,
9
9
  makeSQLQueryAll,
10
- parseResults
10
+ parseResults,
11
+ makeSQLCreateMangoIndex
11
12
  } from './sql'
12
13
 
13
14
  describe('mangoSelectorToSQL', () => {
@@ -435,3 +436,52 @@ describe('parseResults', () => {
435
436
  })
436
437
  })
437
438
  })
439
+
440
+ describe('partialFilter', () => {
441
+ it('should build the index filter against the json column', () => {
442
+ const sql = makeSQLCreateMangoIndex('by_name', ['name'], {
443
+ partialFilter: { trashed: false }
444
+ })
445
+
446
+ // `data` is only a SELECT alias, CREATE INDEX would reject it.
447
+ expect(sql).toContain(`json_extract(json, '$.trashed') = false`)
448
+ expect(sql).not.toContain(`json_extract(data`)
449
+ })
450
+
451
+ it('should restate the partial filter in the query', () => {
452
+ const sql = makeSQLQueryFromMango({
453
+ selector: { dir_id: 'root' },
454
+ indexName: 'by_dir_id',
455
+ partialFilter: { trashed: false }
456
+ })
457
+
458
+ expect(sql).toContain(`json_extract(data, '$.trashed') = false`)
459
+ })
460
+ })
461
+
462
+ describe('_id and _rev selectors', () => {
463
+ it('should resolve _id to the by-sequence column in a query', () => {
464
+ // PouchDB strips _id from the stored JSON, so json_extract would be NULL.
465
+ expect(mangoSelectorToSQL({ _id: 'abc' })).toEqual(
466
+ `'by-sequence'.doc_id = 'abc'`
467
+ )
468
+ })
469
+
470
+ it('should resolve _rev to the by-sequence column in a query', () => {
471
+ expect(mangoSelectorToSQL({ _rev: '1-abc' })).toEqual(
472
+ `'by-sequence'.rev = '1-abc'`
473
+ )
474
+ })
475
+
476
+ it('should leave the column unqualified in an index context', () => {
477
+ expect(mangoSelectorToSQL({ _id: 'abc' }, 'json')).toEqual(`doc_id = 'abc'`)
478
+ })
479
+
480
+ it('should keep filtering hidden ids out of a partial index', () => {
481
+ const sql = makeSQLCreateMangoIndex('by_name', ['name'], {
482
+ partialFilter: { _id: { $nin: ['io.cozy.files.trash-dir'] } }
483
+ })
484
+
485
+ expect(sql).toContain(`doc_id NOT IN ('io.cozy.files.trash-dir')`)
486
+ })
487
+ })
@@ -256,6 +256,7 @@ var SQLiteQueryEngine = /*#__PURE__*/function (_DatabaseQueryEngine) {
256
256
  selector: selector,
257
257
  sort: sort,
258
258
  indexName: indexName,
259
+ partialFilter: partialFilter,
259
260
  limit: limit,
260
261
  skip: skip
261
262
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-pouch-link",
3
- "version": "60.30.1",
3
+ "version": "60.30.2",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "types": "types/index.d.ts",
@@ -41,5 +41,5 @@
41
41
  "typecheck": "tsc -p tsconfig.json"
42
42
  },
43
43
  "sideEffects": false,
44
- "gitHead": "f209689b3951ddbedc83db50a2dcd976082777b3"
44
+ "gitHead": "65d4da4479058f57d0e77f15eb40b9cc89cb7aec"
45
45
  }
@@ -16,13 +16,14 @@ export function parseResults(client: any, result: any, doctype: any, { isSingleD
16
16
  skip: number;
17
17
  next: boolean;
18
18
  };
19
- export function mangoSelectorToSQL(selector: any): string;
20
- export function makeWhereClause(selector: any): string;
19
+ export function mangoSelectorToSQL(selector: any, columnName: any): string;
20
+ export function makeWhereClause(selector: any, columnName: any): string;
21
21
  export function makeSortClause(mangoSortBy: any): string;
22
- export function makeSQLQueryFromMango({ selector, sort, indexName, limit, skip }: {
22
+ export function makeSQLQueryFromMango({ selector, sort, indexName, partialFilter, limit, skip }: {
23
23
  selector: any;
24
24
  sort: any;
25
25
  indexName: any;
26
+ partialFilter: any;
26
27
  limit?: number;
27
28
  skip?: number;
28
29
  }): string;