From 9da2844225ba45938b9d3cca043db5428c6b3ab4 Mon Sep 17 00:00:00 2001 From: forehalo Date: Thu, 2 Jan 2025 02:32:16 +0000 Subject: [PATCH] feat(infra): add not-condition to orm (#9466) --- .../infra/src/orm/core/__tests__/yjs.spec.ts | 55 +++++++++++++++++++ .../src/orm/core/adapters/memory/table.ts | 23 +++++++- .../infra/src/orm/core/adapters/types.ts | 16 ++++-- .../infra/src/orm/core/adapters/yjs/table.ts | 26 ++++++++- packages/common/infra/src/orm/core/table.ts | 5 +- 5 files changed, 115 insertions(+), 10 deletions(-) diff --git a/packages/common/infra/src/orm/core/__tests__/yjs.spec.ts b/packages/common/infra/src/orm/core/__tests__/yjs.spec.ts index 845840e72a..71b2a14b69 100644 --- a/packages/common/infra/src/orm/core/__tests__/yjs.spec.ts +++ b/packages/common/infra/src/orm/core/__tests__/yjs.spec.ts @@ -102,6 +102,61 @@ describe('ORM entity CRUD', () => { expect(user2).toEqual(user); }); + test('should be able to filter with nullable condition', t => { + const { client } = t; + + client.users.create({ + name: 'u1', + email: 'e1@example.com', + }); + + client.users.create({ + name: 'u2', + }); + + const users = client.users.find({ + email: null, + }); + + expect(users).toHaveLength(1); + expect(users[0].email).toBeFalsy(); + + const users2 = client.users.find({ + email: { + not: null, + }, + }); + + expect(users2).toHaveLength(1); + expect(users2[0].email).toEqual('e1@example.com'); + }); + + test('should be able to filter with `not` condition', t => { + const { client } = t; + + client.users.create({ + name: 'u1', + email: 'e1@example.com', + }); + + const users = client.users.find({ + email: { + not: 'e1@example.com', + }, + }); + + expect(users).toHaveLength(0); + + const users2 = client.users.find({ + name: { + not: 'u2', + }, + }); + + expect(users2).toHaveLength(1); + expect(users2[0].name).toEqual('u1'); + }); + test('should be able to update entity', t => { const { client } = t; diff --git a/packages/common/infra/src/orm/core/adapters/memory/table.ts b/packages/common/infra/src/orm/core/adapters/memory/table.ts index 0c61bc2cce..96da89d86e 100644 --- a/packages/common/infra/src/orm/core/adapters/memory/table.ts +++ b/packages/common/infra/src/orm/core/adapters/memory/table.ts @@ -152,9 +152,26 @@ export class MemoryTableAdapter implements TableAdapter { } private match(record: any, where: WhereCondition) { - return Array.isArray(where) - ? where.every(c => record[c.field] === c.value) - : where.byKey === record[this.keyField]; + if (Array.isArray(where)) { + return where.every(c => { + const value = record[c.field] || null; + const condition = c.value; + + if (typeof condition === 'object') { + if (condition === null) { + return value === null; + } + + if ('not' in condition) { + return value !== condition.not; + } + } + + return value === condition; + }); + } + + return where.byKey === record[this.keyField]; } private dispatch(key: string, data: any) { diff --git a/packages/common/infra/src/orm/core/adapters/types.ts b/packages/common/infra/src/orm/core/adapters/types.ts index 57c9f707f7..1324503720 100644 --- a/packages/common/infra/src/orm/core/adapters/types.ts +++ b/packages/common/infra/src/orm/core/adapters/types.ts @@ -4,9 +4,17 @@ export interface TableAdapterOptions extends TableOptions { keyField: string; } -type WhereEqCondition = { +type OrmPrimitiveValues = string | number | boolean | null; + +type SimpleCondition = + | OrmPrimitiveValues + | { + not: OrmPrimitiveValues; + }; + +type WhereSimpleCondition = { field: string; - value: any; + value: SimpleCondition; }; type WhereByKeyCondition = { @@ -14,8 +22,8 @@ type WhereByKeyCondition = { }; // currently only support eq condition -// TODO(@forehalo): on the way [gt, gte, lt, lte, in, notIn, like, notLike, isNull, isNotNull, And, Or] -export type WhereCondition = WhereEqCondition[] | WhereByKeyCondition; +// TODO(@forehalo): on the way [gt, gte, lt, lte, in, notIn, like, notLike, Or] +export type WhereCondition = Array | WhereByKeyCondition; export type Select = '*' | 'key' | string[]; export type InsertQuery = { diff --git a/packages/common/infra/src/orm/core/adapters/yjs/table.ts b/packages/common/infra/src/orm/core/adapters/yjs/table.ts index 1564fb8fe7..72ca0d60a9 100644 --- a/packages/common/infra/src/orm/core/adapters/yjs/table.ts +++ b/packages/common/infra/src/orm/core/adapters/yjs/table.ts @@ -226,7 +226,22 @@ export class YjsTableAdapter implements TableAdapter { (Array.isArray(where) ? where.length === 0 ? false - : where.every(c => this.field(record, c.field) === c.value) + : where.every(c => { + const field = this.field(record, c.field); + const condition = c.value; + + if (typeof condition === 'object') { + if (condition === null) { + return field === null; + } + + if ('not' in condition) { + return field !== condition.not; + } + } + + return field === condition; + }) : where.byKey === this.keyof(record)) ); } @@ -242,7 +257,14 @@ export class YjsTableAdapter implements TableAdapter { } private field(ty: AbstractType, field: string) { - return YMap.prototype.get.call(ty, field); + const val = YMap.prototype.get.call(ty, field); + + // only handle null will make the day easier + if (val === undefined) { + return null; + } + + return val; } private setField(ty: AbstractType, field: string, value: any) { diff --git a/packages/common/infra/src/orm/core/table.ts b/packages/common/infra/src/orm/core/table.ts index 02a3b93119..3d02411d2c 100644 --- a/packages/common/infra/src/orm/core/table.ts +++ b/packages/common/infra/src/orm/core/table.ts @@ -116,7 +116,10 @@ export type FindEntityInput = Pretty< T, { [key in TableDefinedFieldNames]?: key extends keyof TableDefinedEntity - ? TableDefinedEntity[key] + ? + | TableDefinedEntity[key] + | { not: TableDefinedEntity[key] | null } + | null : never; } >