Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor/combine #44

Merged
merged 5 commits into from
Feb 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
"start": "webpack-dev-server --inline --colors --progress --port 3000 --content-base src",
"test": "npm run lint && tman --mocha spec-js/test/run.js",
"version": "ts-node tools/version.ts && git add .",
"watch": "ts-node ./tools/watch.ts & npm run watch_test",
"watch_cjs": "tsc src/index.ts -m commonjs --outDir dist --sourcemap --sourceRoot src --target ES5 -d --diagnostics --pretty --noImplicitAny --noImplicitReturns --experimentalDecorators --suppressImplicitAnyIndexErrors --moduleResolution node --noEmitHelpers --lib es5,es2015.iterable,es2015.collection,es2015.promise,dom -w",
"watch_test": "tsc test/run.ts -m commonjs --outDir spec-js --sourcemap --target ES2015 --diagnostics --pretty --noImplicitAny --noImplicitReturns --experimentalDecorators --suppressImplicitAnyIndexErrors --moduleResolution node -w & ts-node ./tools/watch.ts"
"watch_test": "tsc test/run.ts -m commonjs --outDir spec-js --sourcemap --target ES2015 --diagnostics --pretty --noImplicitAny --noImplicitReturns --experimentalDecorators --suppressImplicitAnyIndexErrors --moduleResolution node -w"
},
"keywords": [
"lovefield",
Expand Down
4 changes: 2 additions & 2 deletions src/storage/Database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ export interface SchemaMetadata<T> {
* 这里需要定义表名,字段和查询条件
*/
virtual?: {
name?: string
where? (virtualTable: TableShape<T>): PredicateDescription<T>
name: string
where(virtualTable: TableShape<T>): PredicateDescription<T>
}
// 被 Database.prototype.createRow 动态挂上去的
// readonly isHidden?: boolean
Expand Down
4 changes: 4 additions & 0 deletions src/storage/PredicateProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ export class PredicateProvider<T> {
}
}

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

private normalizeMeta(meta: PredicateDescription<T>, 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 concated' + `${ msg ? ' due to: ' + msg : '' }.`
return new ReactiveDBError(errMsg)
}

/**
* Warning
Expand Down
42 changes: 35 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((x, y) => x - y)
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 @@ -109,6 +128,7 @@ export class Selector <T> {
BUILD_PREDICATE_FAILED_WARN(e, shape.mainTable.getName())
}
}
skip = limit && !skip ? 0 : skip
if (limit || skip) {
const { pk, mainTable } = this.shape
this.change$ = this.buildPrefetchingObserve()
Expand Down Expand Up @@ -172,11 +192,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 =>
Copy link
Collaborator

@Saviio Saviio Feb 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider:

SELECT * FROM TABLE WHERE id >= 0 SKIP 1 LIMIT 10
SELECT * FROM TABLE WHERE id >= 0 SKIP 2 LIMIT 20
...

these queries will violate constraint of concat method, maybe we should add a term to check if skip mod limit is equal to 0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

limit should be equal here

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
193 changes: 184 additions & 9 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 All @@ -22,7 +22,7 @@ interface Fixture {
folded?: boolean
}

export default describe('SelectMeta test', () => {
export default describe('Selector test', () => {
let db: lf.Database
let table: lf.schema.Table
let version = 1
Expand All @@ -31,7 +31,7 @@ export default describe('SelectMeta test', () => {
let storeData: any[]

beforeEach(function * () {
const schemaBuilder = lf.schema.create('SelectMetaTest', version ++)
const schemaBuilder = lf.schema.create('SelectorTest', version ++)
const db$ = lfFactory(schemaBuilder, {
storeType: lf.schema.DataStoreType.MEMORY,
enableInspector: false
Expand Down Expand Up @@ -191,7 +191,7 @@ export default describe('SelectMeta test', () => {
})
})

describe('SelectMeta.prototype.changes', () => {
describe('Selector.prototype.changes', () => {
it('observe should ok', done => {
const selector = new Selector(db,
db.select().from(table),
Expand Down Expand Up @@ -512,7 +512,7 @@ export default describe('SelectMeta test', () => {
})
})

describe('SelectMeta.prototype.combine', () => {
describe('Selector.prototype.combine', () => {
let selector1: Selector<any>
let selector2: Selector<any>
let selector3: Selector<any>
Expand Down Expand Up @@ -687,12 +687,187 @@ export default describe('SelectMeta 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(selector3, selector4, selector2)
})

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) }
`)
})
})

})