Skip to content

Commit

Permalink
refactor(Selector): Rename combine to concat and implement new combin…
Browse files Browse the repository at this point in the history
…e method
  • Loading branch information
Brooooooklyn committed Feb 10, 2017
1 parent e19ea2c commit 9281902
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 14 deletions.
4 changes: 4 additions & 0 deletions src/storage/PredicateProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ export class PredicateProvider {
}
}

toString(): string {
return this.getPredicate().toString()
}

private normalizeMeta(meta: PredicateDescription, column?: lf.schema.Column) {
let predicates: lf.Predicate[] = []
forEach(meta, (val, key) => {
Expand Down
7 changes: 5 additions & 2 deletions src/storage/RuntimeError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,11 @@ export const INVALID_PATCH_TYPE_ERR =
export const TOKEN_CONSUMED_ERR =
() => new ReactiveDBError('QueryToken was already consumed.')

export const TOKEN_INVALID_ERR =
() => new ReactiveDBError(`Token cannot be combined.`)
export const TOKEN_CONCAT_ERR =
(msg?: string) => {
const errMsg = `Token cannot be concat ${ msg ? `due to: ${ msg }` : '' }`
return new ReactiveDBError(errMsg)
}

/**
* Warning
Expand Down
41 changes: 34 additions & 7 deletions src/storage/Selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Observable } from 'rxjs/Observable'
import * as lf from 'lovefield'
import { PredicateProvider } from './PredicateProvider'
import {
TOKEN_INVALID_ERR,
TOKEN_CONCAT_ERR,
TOKEN_CONSUMED_ERR,
BUILD_PREDICATE_FAILED_WARN
} from './RuntimeError'
Expand All @@ -26,8 +26,27 @@ export interface OrderInfo {
}

export class Selector <T> {
static factory<U>(... metaDatas: Selector<U>[]) {
const originalToken = metaDatas[0]
static concatFactory<U>(... metaDatas: Selector<U>[]) {
const [ meta ] = metaDatas
const skips = metaDatas
.map(m => m.skip)
.sort()
const { db, lselect, shape, predicateProvider, limit } = meta
const [ minSkip ] = skips
if (!skips.every((s, i) => s === limit * i) ) {
throw TOKEN_CONCAT_ERR(`
skip should be serial,
expect:
${ JSON.stringify(skips.map((_, i) => i * limit), null, 2) }, limit = ${ limit }
actual:
${ JSON.stringify(skips, null, 2) }
`)
}
return new Selector(db, lselect, shape, predicateProvider, limit * skips.length, minSkip)
}

static combineFactory<U>(... metaDatas: Selector<U>[]) {
const [ originalToken ] = metaDatas
const fakeQuery = { toSql: identity }
// 初始化一个空的 QuerySelector,然后在初始化以后替换它上面的属性和方法
const dist = new Selector<U>(originalToken.db, fakeQuery as any, { } as any)
Expand Down Expand Up @@ -172,11 +191,19 @@ export class Selector <T> {
}

combine(... selectMetas: Selector<T>[]): Selector<T> {
const isEqual = selectMetas.every(meta => meta.select === this.select)
if (!isEqual) {
throw TOKEN_INVALID_ERR()
return Selector.combineFactory(this, ... selectMetas)
}

concat(... selectMetas: Selector<T>[]): Selector<T> {
const equal = selectMetas.every(m =>
m.limit === this.limit &&
m.select === this.select &&
m.predicateProvider.toString() === this.predicateProvider.toString()
)
if (!equal) {
throw TOKEN_CONCAT_ERR()
}
return Selector.factory(this, ... selectMetas)
return Selector.concatFactory(this, ... selectMetas)
}

changes(): Observable<T[]> | never {
Expand Down
185 changes: 180 additions & 5 deletions test/specs/storage/Selector.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
TableShape,
PredicateProvider,
TOKEN_CONSUMED_ERR,
TOKEN_INVALID_ERR
TOKEN_CONCAT_ERR
} from '../../index'

use(SinonChai)
Expand Down Expand Up @@ -687,12 +687,187 @@ export default describe('Selector test', () => {
})
})

it('should throw when combine two different SelectMetas', () => {
const different = new Selector(db, db.select(table['_id']).from(table), tableShape)
const fn = () => dist.combine(different)
expect(fn).to.throw(TOKEN_INVALID_ERR().message)
})

describe('Selector.prototype.concat', () => {
let selector1: Selector<any>
let selector2: Selector<any>
let selector3: Selector<any>
let selector4: Selector<any>
let dist: Selector<any>

beforeEach(() => {
selector1 = new Selector(db,
db.select().from(table),
tableShape,
new PredicateProvider(table, { time: { $gte: 50 } }),
20, 0
)
selector2 = new Selector(db,
db.select().from(table),
tableShape,
new PredicateProvider(table, { time: { $gte: 50 } }),
20, 20
)

selector3 = new Selector(db,
db.select().from(table),
tableShape,
new PredicateProvider(table, { time: { $gte: 50 } }),
20, 40
)

selector4 = new Selector(db,
db.select().from(table),
tableShape,
new PredicateProvider(table, { time: { $gte: 50 } }),
20, 60
)

dist = selector1.concat(selector2, selector3, selector4)
})

it('should return SelectMeta', () => {
expect(dist).instanceof(Selector)
})

it('result metadata should combine all results', done => {
dist.values()
.subscribe(r => {
expect(r).to.have.lengthOf(80)
done()
})
})

it('result should be combined', function* () {
const result = yield dist.values()
const count = 80
expect(result).to.have.lengthOf(count)
result.forEach((r: any, index: number) => {
expect(r).to.deep.equal(storeData[index + 50])
})
})

it('changes should observe all values from original Selector', function* () {
const changes$ = dist.changes()
.publish()
.refCount()

changes$.subscribe()

const update1 = 'test update name 1'
const update2 = 'test update name 2'
const update3 = 'test update name 3'

yield db.update(table)
.set(table['name'], update1)
.where(table['_id'].eq('_id:51'))
.exec()

yield changes$.take(1)
.do(r => expect(r[1].name).equal(update1))

yield db.update(table)
.set(table['name'], update2)
.where(table['_id'].eq('_id:55'))
.exec()

yield changes$.take(1)
.do(r => expect(r[5].name).equal(update2))

yield db.update(table)
.set(table['name'], update3)
.where(table['_id'].eq('_id:125'))
.exec()

yield changes$.take(1)
.do(r => expect(r[75].name).equal(update3))
})

it('concat two selector limit not match should throw', () => {
const selector5 = new Selector(db,
db.select().from(table),
tableShape,
new PredicateProvider(table, { time: { $gte: 50 } }),
20, 0
)
const selector6 = new Selector(db,
db.select().from(table),
tableShape,
new PredicateProvider(table, { time: { $gte: 50 } }),
21, 20
)

const fn = () => selector5.concat(selector6)
const error = TOKEN_CONCAT_ERR()

expect(fn).to.throw(error.message)
})

it('concat two selector predicate not match should throw', () => {
const selector5 = new Selector(db,
db.select().from(table),
tableShape,
new PredicateProvider(table, { time: { $gt: 50 } }),
20, 0
)
const selector6 = new Selector(db,
db.select().from(table),
tableShape,
new PredicateProvider(table, { time: { $gte: 50 } }),
20, 20
)

const fn = () => selector5.concat(selector6)
const error = TOKEN_CONCAT_ERR()

expect(fn).to.throw(error.message)
})

it('concat two selector select not match should throw', () => {
const selector5 = new Selector(db,
db.select(table['name']).from(table),
tableShape,
new PredicateProvider(table, { time: { $gt: 50 } }),
20, 0
)
const selector6 = new Selector(db,
db.select().from(table),
tableShape,
new PredicateProvider(table, { time: { $gt: 50 } }),
20, 20
)

const fn = () => selector5.concat(selector6)
const error = TOKEN_CONCAT_ERR()

expect(fn).to.throw(error.message)
})

it('concat two selector skip not serial should throw', () => {
const selector5 = new Selector(db,
db.select().from(table),
tableShape,
new PredicateProvider(table, { time: { $gt: 50 } }),
20, 0
)
const selector6 = new Selector(db,
db.select().from(table),
tableShape,
new PredicateProvider(table, { time: { $gt: 50 } }),
20, 40
)

const fn = () => selector5.concat(selector6)

expect(fn).to.throw(`
skip should be serial,
expect:
${ JSON.stringify([0, 20], null, 2) }, limit = ${ 20 }
actual:
${ JSON.stringify([0, 40], null, 2) }
`)
})
})

})

0 comments on commit 9281902

Please sign in to comment.