From 415f11bef1a3078cf2d83376c2b2073e1c20a66b Mon Sep 17 00:00:00 2001 From: chuan6 Date: Mon, 14 May 2018 15:18:27 +0800 Subject: [PATCH] =?UTF-8?q?fix(typing/Predicate):=20=E4=BF=AE=E5=A4=8D=20P?= =?UTF-8?q?redicate=20=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原来的定义中,P in keyof T & PredicateMeta 的操作符优先级有误,想 表达的语义是 P in keyof (T & PredicateMeta)(这种写法 ts 不支持), 而实际的语义是好像并非如此,不在 T 或 PredicateMeta 中的属性名不会导致 报错,降低了类型检查的有效性。 --- src/interface/index.ts | 19 ++++++-- src/storage/Database.ts | 22 +++++---- src/storage/modules/PredicateProvider.ts | 18 +++++-- test/schemas/Project.ts | 4 +- test/schemas/Test.ts | 57 +--------------------- test/schemas/index.ts | 2 +- test/specs/storage/Database.public.spec.ts | 21 ++++---- 7 files changed, 52 insertions(+), 91 deletions(-) diff --git a/src/interface/index.ts b/src/interface/index.ts index bbf2346a..bda8f7e7 100644 --- a/src/interface/index.ts +++ b/src/interface/index.ts @@ -6,6 +6,10 @@ export type DeepPartial = { [K in keyof T]?: Partial } +export type RelationshipPredicate = { + [P in keyof T]?: lf.schema.Column +} + export interface SchemaMetadata { type: RDBType | Relationship primaryKey?: boolean @@ -17,7 +21,7 @@ export interface SchemaMetadata { */ virtual?: { name: string - where(ref: TableShape): Predicate + where(ref: TableShape): RelationshipPredicate } } @@ -26,7 +30,7 @@ export type TableShape = lf.schema.Table & { } export type SchemaDef = { - [P in keyof T]: SchemaMetadata + [P in keyof T]: SchemaMetadata } & { dispose?: SchemaDisposeFunction ['@@dispose']?: SchemaDisposeFunction @@ -170,9 +174,14 @@ export interface PredicateMeta { $isNotNull: boolean } -export type Predicate = { - [P in keyof T & PredicateMeta]?: Partial> | ValueLiteral | Predicate -} +export type Predicate = Partial<{ + [P in keyof T]: + | Partial> // 操作符表达式,如 { $or: [{ $gt: 1 }, { $lt: 5 }] } + | ValueLiteral // 字面量值 + | String // 一种特殊的字面量值类型,常用 interface X extends String { kind?: 'x' } 表达 + } + & PredicateMeta +> export { StatementType, JoinMode, LeafType, Relationship, DataStoreType, RDBType } diff --git a/src/storage/Database.ts b/src/storage/Database.ts index f870936e..bc3f5166 100644 --- a/src/storage/Database.ts +++ b/src/storage/Database.ts @@ -171,14 +171,14 @@ export class Database { return this.database$.pipe(concatMap(insert)) } - get(tableName: string, query: Query = {}, mode: JoinMode = JoinMode.imlicit): QueryToken { + get(tableName: string, query: Query = {}, mode: JoinMode = JoinMode.imlicit): QueryToken { const selector$ = this.database$.pipe( map(db => this.buildSelector(db, tableName, query, mode)) ) return new QueryToken(selector$) } - update(tableName: string, clause: Predicate, raw: Partial): Observable { + update(tableName: string, clause: Predicate, raw: Partial): Observable { const type = getType(raw) if (type !== 'Object') { return Observable.throw(Exception.InvalidType(['Object', type])) @@ -226,7 +226,7 @@ export class Database { return this.database$.pipe(concatMap(update)) } - delete(tableName: string, clause: Predicate = {}): Observable { + delete(tableName: string, clause: Predicate = {}): Observable { const [ pk, err ] = tryCatch(this.findPrimaryKey)(tableName) if (err) { return Observable.throw(err) @@ -266,13 +266,13 @@ export class Database { return this.database$.pipe(concatMap(deletion)) } - upsert(tableName: string, raw: T): Observable + upsert(tableName: string, raw: T): Observable - upsert(tableName: string, raw: T[]): Observable + upsert(tableName: string, raw: T[]): Observable - upsert(tableName: string, raw: T | T[]): Observable + upsert(tableName: string, raw: T | T[]): Observable - upsert(tableName: string, raw: T | T[]): Observable { + upsert(tableName: string, raw: T | T[]): Observable { const upsert = (db: lf.Database) => { const sharing = new Map() const insert: Mutation[] = [] @@ -297,7 +297,7 @@ export class Database { return this.database$.pipe(concatMap(upsert)) } - remove(tableName: string, clause: Clause = {}): Observable { + remove(tableName: string, clause: Clause = {}): Observable { const [schema, err] = tryCatch(this.findSchema)(tableName) if (err) { return Observable.throw(err) @@ -571,7 +571,7 @@ export class Database { mainTable: table! } const { limit, skip } = clause - const provider = new PredicateProvider(table!, clause.where) + const provider = new PredicateProvider(table!, clause.where) return new Selector(db, query, matcher, provider, limit, skip, orderDesc) } @@ -852,7 +852,9 @@ export class Database { entities.forEach(entity => { const pkVal = entity[pk] const clause = createPkClause(pk, pkVal) - const predicate = createPredicate(table, clause) + // todo(dingwen): 调查是该用 clause 还是 clause.where, + // 可以确定的是,clause 是带 where 的。 + const predicate = createPredicate(table, clause.where) const query = predicatableQuery(db, table, predicate!, StatementType.Delete) queryCollection.push(query) diff --git a/src/storage/modules/PredicateProvider.ts b/src/storage/modules/PredicateProvider.ts index c6684262..b59bcfa0 100644 --- a/src/storage/modules/PredicateProvider.ts +++ b/src/storage/modules/PredicateProvider.ts @@ -53,6 +53,8 @@ const predicateFactory = { }, } +type Pred = typeof predicateFactory + const compoundPredicateFactory = { $and (predicates: lf.Predicate[]): lf.Predicate { return lf.op.and(...predicates) @@ -67,7 +69,9 @@ const compoundPredicateFactory = { }, } -export class PredicateProvider { +type CompPred = typeof compoundPredicateFactory + +export class PredicateProvider { constructor( private table: lf.schema.Table, @@ -93,8 +97,12 @@ export class PredicateProvider { } private normalizeMeta(meta: Predicate, column?: lf.schema.Column): lf.Predicate[] { - const buildSinglePred = (col: lf.schema.Column, val: any, key: string): lf.Predicate => - this.checkMethod(key) ? predicateFactory[key](col, val) : col.eq(val as ValueLiteral) + const buildSinglePred = ( + col: lf.schema.Column, + val: K extends (keyof Pred) ? Pred[K] : ValueLiteral, + key: K + ): lf.Predicate => + this.checkMethod(key) ? predicateFactory[key](col, val) : col.eq(val) const predicates: lf.Predicate[] = [] @@ -124,11 +132,11 @@ export class PredicateProvider { return predicates } - private checkMethod(methodName: string) { + private checkMethod(methodName: string): methodName is keyof Pred { return typeof predicateFactory[methodName] === 'function' } - private checkCompound(methodName: string) { + private checkCompound(methodName: string): methodName is keyof CompPred { return typeof compoundPredicateFactory[methodName] === 'function' } diff --git a/test/schemas/Project.ts b/test/schemas/Project.ts index 282c74f4..f33fd7df 100644 --- a/test/schemas/Project.ts +++ b/test/schemas/Project.ts @@ -4,7 +4,7 @@ export interface ProjectSchema { _id: TeambitionTypes.ProjectId name: string isArchived: boolean - posts: any[] + posts: any } export default (db: Database) => db.defineSchema('Project', { _id: { @@ -21,7 +21,7 @@ export default (db: Database) => db.defineSchema('Project', { type: Relationship.oneToMany, virtual: { name: 'Post', - where: ref => ({ + where: (ref) => ({ _id: ref.belongTo }) } diff --git a/test/schemas/Test.ts b/test/schemas/Test.ts index c401c56b..50dab00b 100644 --- a/test/schemas/Test.ts +++ b/test/schemas/Test.ts @@ -1,4 +1,4 @@ -import { TeambitionTypes, Database, RDBType, Relationship } from '../index' +import { TeambitionTypes, Database, RDBType } from '../index' export interface TestSchema { _id: string @@ -6,38 +6,6 @@ export interface TestSchema { taskId: TeambitionTypes.TaskId } -export const TestFixture = (db: Database) => { - const schema = { - _id: { - type: RDBType.STRING, - primaryKey: true - }, - data1: { - type: RDBType.ARRAY_BUFFER, - }, - data2: { - type: RDBType.NUMBER - }, - data3: { - type: RDBType.STRING, - virtual: { - name: 'Project', - where: (ref: any) => ({ - _projectId: ref._id - }) - } - }, - data4: { - type: RDBType.OBJECT - }, - data5: { - type: RDBType.INTEGER - } - } - - db.defineSchema('Fixture1', schema) -} - export const TestFixture2 = (db: Database) => { const schema = { _id: { @@ -67,26 +35,3 @@ export const TestFixture2 = (db: Database) => { return db.defineSchema('Fixture2', schema) } - -export const TestFixture3 = (db: Database) => { - const schema = { - id: { - type: RDBType.STRING, - primaryKey: true - }, - data1: { - type: RDBType.NUMBER - }, - data2: { - type: Relationship.oneToMany, - virtual: { - name: 'Project', - where: (ref: any) => ({ - id: ref['_id'] - }) - } - } - } - - return db.defineSchema('Test', schema) -} diff --git a/test/schemas/index.ts b/test/schemas/index.ts index e422c22b..ea690745 100644 --- a/test/schemas/index.ts +++ b/test/schemas/index.ts @@ -25,7 +25,7 @@ export { TaskSchema } from './Task' export { ProgramSchema } from './Program' export { ModuleSchema } from './Module' export { EngineerSchema } from './Engineer' -export { TestSchema, TestFixture, TestFixture2 } from './Test' +export { TestSchema, TestFixture2 } from './Test' /** * import ActivitySelectMeta from './Activity' diff --git a/test/specs/storage/Database.public.spec.ts b/test/specs/storage/Database.public.spec.ts index e905f83c..20dde623 100644 --- a/test/specs/storage/Database.public.spec.ts +++ b/test/specs/storage/Database.public.spec.ts @@ -610,7 +610,7 @@ export default describe('Database Testcase: ', () => { it('shouldn\'t to update column which is defined as primarykey', function* () { const note = 'foo' - yield database.update('Task', target._id as string, { + yield database.update('Task', target._id as any, { _id: 'bar', note }) @@ -652,7 +652,7 @@ export default describe('Database Testcase: ', () => { const u1 = uuid() const u2 = uuid() - yield database.update('Task', clause, { + yield database.update('Task', clause, { _stageId: u1 }) @@ -662,7 +662,7 @@ export default describe('Database Testcase: ', () => { } }).values() - yield database.update('Task', clause, { + yield database.update('Task', clause, { _stageId: u2 }) @@ -678,7 +678,7 @@ export default describe('Database Testcase: ', () => { it('should be able to update property which is stored as hidden column', function* () { const newCreated = new Date(2017, 1, 1) - yield database.update('Task', { + yield database.update('Task', { _id: target._id }, { created: newCreated.toISOString() @@ -760,7 +760,7 @@ export default describe('Database Testcase: ', () => { const errSpy = sinon.spy((): void => void 0) tmpDB.connect() - tmpDB.update(T, { id: 1 }, { + tmpDB.update(T, { id: 1 } as any, { members: ['1', '2'] }) .catch(errSpy) @@ -1048,11 +1048,8 @@ export default describe('Database Testcase: ', () => { const execRet1 = yield database.upsert('Program', program) - yield database.delete('Program', { - where: { - _id: program._id - } - }) + // todo(dingwen): delete 的参数究竟需不需要 where + yield database.delete('Program', { _id: program._id }) const execRet2 = yield database.upsert('Program', program) @@ -1164,7 +1161,7 @@ export default describe('Database Testcase: ', () => { const moduleCount = 20 const programs = programGen(programCount, moduleCount) - const engineerIds = Array.from(new Set( + const engineerIds: string[] = Array.from(new Set( programs .map(p => p.modules) .reduce((acc, pre) => acc.concat(pre)) @@ -1173,7 +1170,7 @@ export default describe('Database Testcase: ', () => { ) yield database.upsert('Program', programs) - const clause = { where: { $in: engineerIds } } + const clause = { where: { _id: { $in: engineerIds } } } const storedEngineers = yield database.get('Engineer', clause).values() const execRet = yield database.remove('Module')