From 534a6e43ee72d8585f05b7cdd2de0bb9b5d2badc Mon Sep 17 00:00:00 2001 From: ChengLei Shao Date: Fri, 4 Mar 2022 18:13:48 +0800 Subject: [PATCH] fix: array $noneOf with null (#220) * fix: array $noneOf with null * fix: mysql test --- .../__tests__/operator/array-operator.test.ts | 38 ++++++++++++++++--- packages/database/src/fields/array-field.ts | 1 + packages/database/src/operators/array.ts | 33 ++++++++++------ 3 files changed, 55 insertions(+), 17 deletions(-) diff --git a/packages/database/src/__tests__/operator/array-operator.test.ts b/packages/database/src/__tests__/operator/array-operator.test.ts index 063beb906..96126fd8a 100644 --- a/packages/database/src/__tests__/operator/array-operator.test.ts +++ b/packages/database/src/__tests__/operator/array-operator.test.ts @@ -26,14 +26,14 @@ describe('array field operator', function () { t1 = await Test.repository.create({ values: { - selected: [1, 2, 'a', 'b'], + selected: ['1', '2', 'a', 'b'], name: 't1', }, }); t2 = await Test.repository.create({ values: { - selected: [11, 22, 'aa', 'bb', 'cc'], + selected: ['11', '22', 'aa', 'bb', 'cc'], name: 't2', }, }); @@ -133,7 +133,7 @@ describe('array field operator', function () { test('$match', async () => { const filter1 = await Test.repository.find({ filter: { - 'selected.$match': [2, 1, 'a', 'b'], + 'selected.$match': ['2', '1', 'a', 'b'], }, }); @@ -146,7 +146,7 @@ describe('array field operator', function () { filter: { $and: [ { - selected: { $match: [2, 1, 'a', 'b'] }, + selected: { $match: ['2', '1', 'a', 'b'] }, }, ], }, @@ -159,7 +159,7 @@ describe('array field operator', function () { test('$notMatch', async () => { const filter2 = await Test.repository.find({ filter: { - 'selected.$notMatch': [1, 2, 'a', 'b'], + 'selected.$notMatch': ['1', '2', 'a', 'b'], }, }); @@ -193,6 +193,16 @@ describe('array field operator', function () { expect(filter3[0].get('name')).toEqual(t2.get('name')); }); + test('$anyOf with multiple items', async () => { + const filter3 = await Test.repository.find({ + filter: { + 'selected.$anyOf': ['aa', 'a', '1'], + }, + }); + + expect(filter3.length).toEqual(2); + }); + test('$noneOf', async () => { const filter = await Test.repository.find({ filter: { @@ -204,6 +214,24 @@ describe('array field operator', function () { expect(filter[0].get('name')).toEqual(t1.get('name')); }); + test('$noneOf with null', async () => { + const t3 = await Test.repository.create({ + values: { + name: 't3', + selected: null, + }, + }); + + const filter = await Test.repository.find({ + filter: { + 'selected.$noneOf': ['a', 'aa', '1'], + }, + }); + + expect(filter.length).toEqual(1); + expect(filter[0].get('name')).toEqual(t3.get('name')); + }); + test('$empty', async () => { const t3 = await Test.repository.create({ values: { diff --git a/packages/database/src/fields/array-field.ts b/packages/database/src/fields/array-field.ts index 481708f49..888dabc60 100644 --- a/packages/database/src/fields/array-field.ts +++ b/packages/database/src/fields/array-field.ts @@ -12,6 +12,7 @@ export class ArrayField extends Field { sortValue(model) { const oldValue = model.get(this.options.name); + if (oldValue) { const newValue = oldValue.sort(); model.set(this.options.name, newValue); diff --git a/packages/database/src/operators/array.ts b/packages/database/src/operators/array.ts index 5534eef8c..33e7ec94a 100644 --- a/packages/database/src/operators/array.ts +++ b/packages/database/src/operators/array.ts @@ -81,9 +81,12 @@ export default { const fieldName = getFieldName(ctx); if (isPg(ctx)) { - const queryValue = JSON.stringify(value).replace("'", "''"); - - return Sequelize.literal(`${fieldName} @> ${escape(queryValue, ctx)}::JSONB`); + return Sequelize.literal( + `${fieldName} ?| ${escape( + value.map((i) => `${i}`), + ctx, + )}`, + ); } if (isMySQL(ctx)) { @@ -98,23 +101,29 @@ export default { }, $noneOf(value, ctx) { + let where; + if (isPg(ctx)) { const fieldName = getFieldName(ctx); // pg single quote - const queryValue = JSON.stringify(value).replace("'", "''"); - return Sequelize.literal(`not (${fieldName} @> ${escape(queryValue, ctx)}::JSONB)`); - } - - if (isMySQL(ctx)) { + where = Sequelize.literal( + `not (${fieldName} ?| ${escape( + value.map((i) => `${i}`), + ctx, + )})`, + ); + } else if (isMySQL(ctx)) { const fieldName = getFieldName(ctx); value = escape(JSON.stringify(value), ctx); - return Sequelize.literal(`NOT JSON_OVERLAPS(${fieldName}, ${value})`); + where = Sequelize.literal(`NOT JSON_OVERLAPS(${fieldName}, ${value})`); + } else { + const subQuery = sqliteExistQuery(value, ctx); + + where = Sequelize.literal(`not ${subQuery}`); } - const subQuery = sqliteExistQuery(value, ctx); - return { - [Op.and]: [Sequelize.literal(`not ${subQuery}`)], + [Op.or]: [where, { [Op.is]: null }], }; },