Skip to content

Commit

Permalink
Merge pull request #7 from tfso/feature/sets
Browse files Browse the repository at this point in the history
feature/sets
  • Loading branch information
lostfields authored Jan 21, 2021
2 parents a66a560 + 6daebd6 commit a9b8a78
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 85 deletions.
2 changes: 1 addition & 1 deletion src/linq/operator/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type LinqOperatorSkip = { type: LinqType.Skip, count: number }
type LinqOperatorTake = { type: LinqType.Take, count: number }
type LinqOperatorSlice = { type: LinqType.Slice, begin: number | string, end?: number }
type LinqOperatorIncludes<T extends Entity = Entity> = { type: LinqType.Includes, entity: Partial<T> }
type LinqOperatorWhere<T extends Entity = Entity> = { type: LinqType.Where, expression: IExpression, readonly intersection: IterableIterator<WhereExpression> }
type LinqOperatorWhere<T extends Entity = Entity> = { type: LinqType.Where, expression: IExpression, readonly intersection: IterableIterator<WhereExpression>, readonly sets: IterableIterator<IterableIterator<WhereExpression>> }
type LinqOperatorOrderBy<T extends Entity = Entity> = { type: LinqType.OrderBy, property: undefined | keyof T | string }
type LinqOperatorSelect = { type: LinqType.Select }

Expand Down
185 changes: 102 additions & 83 deletions src/linq/operator/whereoperator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,21 @@ export function whereOperator<TEntity extends Entity>(): LinqOperator<TEntity> {
},
get intersection() {
return visitIntersection(typeof predicate == 'string' ? 'odata' : 'javascript', scopeName, expression)
},
get sets() {
function * visitChild(expressions: IExpression[]): IterableIterator<WhereExpression> {
for(let expr of expressions) {
yield * visitExpression(typeof predicate == 'string' ? 'odata' : 'javascript', scopeName, expr)
}
}

function * visit() {
for(let set of expression.sets) {
yield visitChild(set)
}
}

return visit()
}
}
}
Expand All @@ -64,103 +79,107 @@ export function whereOperator<TEntity extends Entity>(): LinqOperator<TEntity> {
*/
function * visitIntersection(type: 'odata' | 'javascript', it: string, expression: IExpression): IterableIterator<WhereExpression> {
for(let expr of expression.intersection) {
for(let leaf of visitLeaf(type, it, expr)) {
if(LogicalExpression.instanceof(leaf)) {
let value = getPropertyValue(leaf.right),
property = getPropertyName(leaf.left)?.name.join('.') ?? '',
operator = getOperator(leaf)

switch(typeof value) {
case 'string':
yield {
type: 'string', property, operator, value, wildcard: getWildcard(leaf.right)
}
break
yield * visitExpression(type, it, expr)
}
}

case 'bigint':
yield {
type: 'bigint', property, operator, value
}
break
function * visitExpression(type: 'odata' | 'javascript', it: string, expression: IExpression): IterableIterator<WhereExpression> {
for(let leaf of visitLeaf(type, it, expression)) {
if(LogicalExpression.instanceof(leaf)) {
let value = getPropertyValue(leaf.right),
property = getPropertyName(leaf.left)?.name.join('.') ?? '',
operator = getOperator(leaf)

switch(typeof value) {
case 'string':
yield {
type: 'string', property, operator, value, wildcard: getWildcard(leaf.right)
}
break

case 'number':
yield {
type: 'number', property, operator, value
}
break
case 'bigint':
yield {
type: 'bigint', property, operator, value
}
break

case 'number':
yield {
type: 'number', property, operator, value
}
break

case 'boolean':
case 'boolean':
yield {
type: 'boolean', property, operator, value
}
break

case 'object':
if(value == null) {
yield {
type: 'boolean', property, operator, value
}
break

case 'object':
if(value == null) {
yield {
type: 'null', property, operator, value
}
type: 'null', property, operator, value
}
else if(typeof value.getTime == 'function' && value.getTime() >= 0) {
yield {
type: 'date', property, operator, value
}
}
else if(typeof value.getTime == 'function' && value.getTime() >= 0) {
yield {
type: 'date', property, operator, value
}
else if(Array.isArray(value) == true) {
yield {
type: 'array', property, operator, value
}
}
else if(Array.isArray(value) == true) {
yield {
type: 'array', property, operator, value
}
}

break
break

case 'undefined':
yield {
type: 'null', property, operator, value
}
case 'undefined':
yield {
type: 'null', property, operator, value
}

default:

}
default:

}
else if(MethodExpression.instanceof(leaf)) {
switch(type) {
case 'odata':
switch(leaf.name) {
case 'any':
case 'all':
let lambda = leaf.parameters[0]
}
else if(MethodExpression.instanceof(leaf)) {
switch(type) {
case 'odata':
switch(leaf.name) {
case 'any':
case 'all':
let lambda = leaf.parameters[0]

if(LambdaExpression.instanceof(lambda))
yield {
type: 'expression',
property: getPropertyName(leaf.caller)?.name.join('.'),
operator: leaf.name,
value: visitIntersection(type, getPropertyName(lambda.parameters[0])?.name.join('.'), lambda.expression)
}

if(LambdaExpression.instanceof(lambda))
yield {
type: 'expression',
property: getPropertyName(leaf.caller)?.name.join('.'),
operator: leaf.name,
value: visitIntersection(type, getPropertyName(lambda.parameters[0])?.name.join('.'), lambda.expression)
}
break
}
break

break
}
break

case 'javascript':
switch(leaf.name) {
case 'some':
case 'every':
let lambda = leaf.parameters[0]

if(LambdaExpression.instanceof(lambda))
yield {
type: 'expression',
property: getPropertyName(leaf.caller)?.name.join('.'),
operator: leaf.name == 'some' ? 'any' : 'all',
value: visitIntersection(type, getPropertyName(lambda.parameters[0])?.name.join('.'), lambda.expression)
}
case 'javascript':
switch(leaf.name) {
case 'some':
case 'every':
let lambda = leaf.parameters[0]

if(LambdaExpression.instanceof(lambda))
yield {
type: 'expression',
property: getPropertyName(leaf.caller)?.name.join('.'),
operator: leaf.name == 'some' ? 'any' : 'all',
value: visitIntersection(type, getPropertyName(lambda.parameters[0])?.name.join('.'), lambda.expression)
}

break
}
break
}
break
}
break
}
}
}
Expand Down
22 changes: 22 additions & 0 deletions src/linq/peg/expression/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ export abstract class Expression implements IExpression {
return intersection ?? []
}

public get difference(): IExpression[] {
let difference: Array<IExpression>

difference = Array.from(visit(this)).reduce((acc, curr, idx) => {
return acc
}, difference)

return []
}

public get union(): IExpression[] {
let union: Array<IExpression>

Expand All @@ -114,6 +124,18 @@ export abstract class Expression implements IExpression {

return union ?? []
}

public get sets(): IExpression[][] {
let sets: Array<IExpression[]> = []

sets = Array.from(visit(this)).reduce((acc, curr, idx) => {
acc.push(Array.from(curr))

return acc
}, sets)

return sets
}
}

function * visit(expression: IExpression): Iterable<IterableIterator<IExpression>> {
Expand Down
2 changes: 2 additions & 0 deletions src/linq/peg/interface/iexpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export interface IExpression {

readonly intersection: IExpression[]
readonly union: IExpression[]
readonly difference: IExpression[]
readonly sets: IExpression[][]

toString(): string
}
91 changes: 91 additions & 0 deletions src/test/expression.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import * as assert from 'assert'
import * as Expr from './../linq/peg/expressionvisitor'

import { ODataVisitor } from './../linq/peg/odatavisitor'

describe('When using Expression', () => {
let reducer: ODataVisitor

beforeEach(() => {
reducer = new ODataVisitor()
})

it('should use sets to intersect a expression', () => {
let expression = reducer.parseOData(`(year eq 2015 and location eq 'NO') or year eq 2015 or (location eq 'SE' and year eq 2015)`)

let count = 0
for(let part of expression.intersection) {
switch(part.toString()) {
case 'year == 2015':
count++; break
}
}

chai.expect(count).to.equal(1)
})

it('should use sets to union a expression', () => {
let expression = reducer.parseOData(`(year eq 2015 and location eq 'NO') or year eq 2015 or (location eq 'SE' and year eq 2015)`)

let count = 0
for(let part of expression.union) {
count++
}

chai.expect(count).to.equal(5)
})

it('should use sets for a expression', () => {
let expression = reducer.parseOData(`(year eq 2015 and location eq 'NO') or year eq 2015 or (location eq 'SE' and year eq 2015)`)

let count = 0
for(let part of expression.sets) {
switch(part.map(p => p.toString()).join(',')) {
case 'year == 2015,location == "NO"': count++; break
case 'year == 2015': count++; break
case 'location == "SE",year == 2015': count++; break
}
}

chai.expect(count).to.equal(3)
})

it('should use sets for a expression and only pick the usable ones', () => {
let expression = reducer.parseOData(`(year eq 2015 and (location eq 'DK' or location eq 'NO')) or year eq 2015 or (location eq 'SE' and year eq 2015)`)

let count = 0
for(let part of expression.sets) {
switch(part.map(p => p.toString()).join(',')) {
case 'year == 2015': count++; break
case 'year == 2015': count++; break
case 'location == "SE",year == 2015': count++; break
}
}

chai.expect(count).to.equal(3)

chai.expect(Array.from(expression.sets).length).to.equal(3)
chai.expect(Array.from(expression.union).length).to.equal(4)
chai.expect(Array.from(expression.intersection).length).to.equal(1)
})

it('should use sets for a expression with only or', () => {
let expression = reducer.parseOData(`year eq 2015 or location eq 'NO'`)

let count = 0
for(let part of expression.sets) {
switch(part.map(p => p.toString()).join(',')) {
case 'year == 2015': count++; break
case 'location == "NO"': count++; break
}
}

chai.expect(count).to.equal(2)

chai.expect(Array.from(expression.sets).length).to.equal(2)
chai.expect(Array.from(expression.union).length).to.equal(2)
chai.expect(Array.from(expression.intersection).length).to.equal(0)

})

})
4 changes: 3 additions & 1 deletion src/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ import './expressionvisitor/unary'

import './javascriptvisitor'
import './odatavisitor'
import './reducervisitor'
import './reducervisitor'

import './expression'
20 changes: 20 additions & 0 deletions src/test/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,26 @@ describe('When using repository', () => {

describe('analyzing where operator', () => {

it('should use sets for a expression', () => {
let enumerable = new jsEnumerable.Enumerable(repository).where(car => (car.year == 2015 && car.location.toUpperCase() == 'DK') || car.location.toUpperCase() == 'NO'),
operator = enumerable.operators.pop(),
count = 0,
countset = 0

chai.expect(operator.type).to.equal(LinqType.Where)

if(operator.type == LinqType.Where) {
for(let set of operator.sets) {
count += Array.from(set).length

countset++
}
}

chai.expect(countset).to.equal(2)
chai.expect(count).to.equal(3)
})

it('should intersect expression properties that is only and\'ed', () => {
let enumerable = new jsEnumerable.Enumerable(repository).where(car => car.year == 2015 && car.location.toUpperCase() == 'NO' && car.id > 5),
operator = enumerable.operators.pop(),
Expand Down

0 comments on commit a9b8a78

Please sign in to comment.