Skip to content

Commit

Permalink
Merge pull request #13 from tfso/bugfix/nullable-odata-props
Browse files Browse the repository at this point in the history
bugfix/Handling optional/nullable values for OData
  • Loading branch information
lostfields authored Feb 29, 2024
2 parents 2670940 + 0d1f506 commit 7d56363
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 21 deletions.
74 changes: 60 additions & 14 deletions src/linq/peg/odatavisitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export class ODataVisitor extends ReducerVisitor {
let getParams = (expression: IMethodExpression, ...typeofs: Array<string>) => {
let params: Array<any> | undefined,
getType = (t: any) => {
if(t == null)
return 'undefined'

if(typeof t == 'object') {
if(t.getTime && t.getTime() >= 0)
return 'date'
Expand All @@ -45,7 +48,7 @@ export class ODataVisitor extends ReducerVisitor {
if(parameters.every(expression => expression.type == ExpressionType.Literal) == true) {
params = parameters.map(expr => (<LiteralExpression>expr).value)

if(new RegExp('^' + typeofs.map(t => t.endsWith('?') ? '(' + t.slice(0, -1) + ')?' : t).join(';') + ';?$').test(params.map(p => getType(p)).join(';') + ';') == false)
if(new RegExp('^' + typeofs.map(t => t.endsWith('?') ? `(${t.slice(0, -1)})?` : `(${t})`).join(';') + ';?$').test(params.map(p => getType(p)).join(';') + ';') == false)
throw new TypeError(params.map(p => getType(p)).join(', '))
}
else if((parameters.length == typeofs.length) == false) {
Expand All @@ -62,72 +65,115 @@ export class ODataVisitor extends ReducerVisitor {
switch(expression.name) {
// String Functions
case 'substringof': // bool substringof(string po, string p1)
if((params = getParams(expression, 'string', 'string')) != null)
if((params = getParams(expression, 'string|undefined', 'string')) != null) {
if(params[0] == null)
return new LiteralExpression(false)

return new LiteralExpression(String(params[0]).indexOf(String(params[1])) >= 0)
}

break

case 'endswith': // bool endswith(string p0, string p1)
if((params = getParams(expression, 'string', 'string')) != null)
if((params = getParams(expression, 'string|undefined', 'string')) != null) {
if(params[0] == null)
return new LiteralExpression(false)

return new LiteralExpression(String(params[0]).endsWith(String(params[1])))
}

break

case 'startswith': // bool startswith(string p0, string p1)
if((params = getParams(expression, 'string', 'string')) != null)
if((params = getParams(expression, 'string|undefined', 'string')) != null) {
if(params[0] == null)
return new LiteralExpression(false)

return new LiteralExpression(String(params[0]).startsWith(String(params[1])))

}
break

case 'contains': // bool contains(string p0, string p1)
if((params = getParams(expression, 'string', 'string')) != null)
if((params = getParams(expression, 'string|undefined', 'string')) != null) {
if(params[0] == null)
return new LiteralExpression(false)

return new LiteralExpression(String(params[0]).indexOf(String(params[1])) >= 0)
}

break
case 'length': // int length(string p0)
if((params = getParams(expression, 'string')) != null)
if((params = getParams(expression, 'string|undefined')) != null) {
if(params[0] == null)
return new LiteralExpression(0)

return new LiteralExpression(String(params[0]).length)
}

break

case 'indexof': // int indexof(string p0, string p1)
if((params = getParams(expression, 'string', 'string')) != null)
if((params = getParams(expression, 'string|undefined', 'string')) != null) {
if(params[0] == null)
return new LiteralExpression(-1)

return new LiteralExpression(String(params[0]).indexOf(String(params[1])))
}

break

case 'replace': // string replace(string p0, string find, string replace)
if((params = getParams(expression, 'string', 'string', 'string')) != null)
if((params = getParams(expression, 'string|undefined', 'string', 'string')) != null) {
if(params[0] == null)
return new LiteralExpression(null)

return new LiteralExpression(String(params[0]).replace(String(params[1]), String(params[2])))
}

break

case 'substring': // string substring(string p0, int pos, int? length)
if((params = getParams(expression, 'string', 'number', 'number?')) != null)
if((params = getParams(expression, 'string|undefined', 'number', 'number?')) != null) {
if(params[0] == null)
return new LiteralExpression(null)

return new LiteralExpression(String(params[0]).replace(String(params[1]), String(params[2])))
}

break

case 'tolower': // string tolower(string p0)
if((params = getParams(expression, 'string')) != null)
if((params = getParams(expression, 'string|undefined')) != null) {
if(params[0] == null)
return new LiteralExpression(null)

return new LiteralExpression(String(params[0]).toLowerCase())
}

break

case 'toupper': // string toupper(string p0)
if((params = getParams(expression, 'string')) != null)
if((params = getParams(expression, 'string|undefined')) != null) {
if(params[0] == null)
return new LiteralExpression(null)

return new LiteralExpression(String(params[0]).toUpperCase())
}

break

case 'trim': // string trim(string p0)
if((params = getParams(expression, 'string')) != null)
if((params = getParams(expression, 'string|undefined')) != null) {
if(params[0] == null)
return new LiteralExpression(null)

return new LiteralExpression(String(params[0]).trim())
}

break

case 'concat': // string concat(string p0, string p1)
if((params = getParams(expression, 'string', 'string')) != null)
if((params = getParams(expression, 'string|undefined', 'string|undefined')) != null)
return new LiteralExpression(String(params[0]) + String(params[1]))

break
Expand Down
4 changes: 3 additions & 1 deletion src/linq/peg/reducervisitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,9 @@ export class ReducerVisitor extends ExpressionVisitor {


// this object
if(isRecord(currentScope) && identifier.name in currentScope && (value = currentScope[identifier.name]) !== undefined) {
if(isRecord(currentScope) && identifier.name in currentScope) {
value = currentScope[identifier.name]

if(value == null)
return new LiteralExpression(null)

Expand Down
21 changes: 15 additions & 6 deletions src/test/enumerable/record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ if(jsEnumerable! == null) {
}

function * iterator() {
yield { id: 1, make: 'Toyota', model: 'Corolla', year: 1990, details: { revisions: [{ year: 1966, name: 'E10' }, { year: 1970, name: 'E20' }, { year: 1974, name: 'E30-E60' }, { year: 1979, name: 'E70' }, { year: 1983, name: 'E80' }, { year: 1987, name: 'E90' }, { year: 1991, name: 'E100' }, { year: 1995, name: 'E110' }, { year: 2000, name: 'E120-130' }, { year: 2006, name: 'E140-150' }, { year: 2012, name: 'E160-180' }, { year: 2018, name: 'E210' }]} }
yield { id: 1, make: 'Toyota', optional: 'yes', model: 'Corolla', year: 1990, details: { revisions: [{ year: 1966, name: 'E10' }, { year: 1970, name: 'E20' }, { year: 1974, name: 'E30-E60' }, { year: 1979, name: 'E70' }, { year: 1983, name: 'E80' }, { year: 1987, name: 'E90' }, { year: 1991, name: 'E100' }, { year: 1995, name: 'E110' }, { year: 2000, name: 'E120-130' }, { year: 2006, name: 'E140-150' }, { year: 2012, name: 'E160-180' }, { year: 2018, name: 'E210' }]} }
yield { id: 2, make: 'Nissan', model: 'Leaf', year: 2019, details: { revisions: [{ year: 2010, name: 'MY2011' }, { year: 2013, name: 'MY2013' }, { year: 2016, name: 'MY2016' }, { year: 2017, name: 'MY2017' }]} }
yield { id: 3, make: 'Nissan', model: 'Qashqai', year: 2009, details: { revisions: [{ year: 2006, name: 'J10' }, { year: 2010, name: 'J10-2' }, { year: 2013, name: 'J11' }, { year: 2017, name: 'J11-2' }]} }
yield { id: 4, make: 'Volkswagen', model: 'Golf', year: 1999, details: { revisions: [{ year: 1974, name: 'Mk1' }, { year: 1983, name: 'Mk2' }, { year: 1991, name: 'Mk3' }, { year: 1997, name: 'Mk4' }, { year: 2003, name: 'Mk5' }, { year: 2008, name: 'Mk6' }, { year: 2012, name: 'Mk7' }, { year: 2019, name: 'Mk8' }]} }
yield { id: 3, make: 'Nissan', optional: null, model: 'Qashqai', year: 2009, details: { revisions: [{ year: 2006, name: 'J10' }, { year: 2010, name: 'J10-2' }, { year: 2013, name: 'J11' }, { year: 2017, name: 'J11-2' }]} }
yield { id: 4, make: 'Volkswagen', optional: 'yes', model: 'Golf', year: 1999, details: { revisions: [{ year: 1974, name: 'Mk1' }, { year: 1983, name: 'Mk2' }, { year: 1991, name: 'Mk3' }, { year: 1997, name: 'Mk4' }, { year: 2003, name: 'Mk5' }, { year: 2008, name: 'Mk6' }, { year: 2012, name: 'Mk7' }, { year: 2019, name: 'Mk8' }]} }
yield { id: 5, make: 'Mazda', model: '323', year: 2004, details: { revisions: [{ year: 1963, name: '1gen' }, { year: 1967, name: '2gen' }, { year: 1977, name: 'FA4' }, { year: 1980, name: 'BD' }, { year: 1985, name: 'BF' }, { year: 1989, name: 'BG' }, { year: 1994, name: 'BH' }, { year: 1998, name: 'BJ' }]} }
}

async function * asyncIterator() {
yield { id: 1, make: 'Toyota', model: 'Corolla', year: 1990, details: { revisions: [{ year: 1966, name: 'E10' }, { year: 1970, name: 'E20' }, { year: 1974, name: 'E30-E60' }, { year: 1979, name: 'E70' }, { year: 1983, name: 'E80' }, { year: 1987, name: 'E90' }, { year: 1991, name: 'E100' }, { year: 1995, name: 'E110' }, { year: 2000, name: 'E120-130' }, { year: 2006, name: 'E140-150' }, { year: 2012, name: 'E160-180' }, { year: 2018, name: 'E210' }]} }
yield { id: 1, make: 'Toyota', optional: 'yes', model: 'Corolla', year: 1990, details: { revisions: [{ year: 1966, name: 'E10' }, { year: 1970, name: 'E20' }, { year: 1974, name: 'E30-E60' }, { year: 1979, name: 'E70' }, { year: 1983, name: 'E80' }, { year: 1987, name: 'E90' }, { year: 1991, name: 'E100' }, { year: 1995, name: 'E110' }, { year: 2000, name: 'E120-130' }, { year: 2006, name: 'E140-150' }, { year: 2012, name: 'E160-180' }, { year: 2018, name: 'E210' }]} }
yield { id: 2, make: 'Nissan', model: 'Leaf', year: 2019, details: { revisions: [{ year: 2010, name: 'MY2011' }, { year: 2013, name: 'MY2013' }, { year: 2016, name: 'MY2016' }, { year: 2017, name: 'MY2017' }]} }
yield { id: 3, make: 'Nissan', model: 'Qashqai', year: 2009, details: { revisions: [{ year: 2006, name: 'J10' }, { year: 2010, name: 'J10-2' }, { year: 2013, name: 'J11' }, { year: 2017, name: 'J11-2' }]} }
yield { id: 4, make: 'Volkswagen', model: 'Golf', year: 1999, details: { revisions: [{ year: 1974, name: 'Mk1' }, { year: 1983, name: 'Mk2' }, { year: 1991, name: 'Mk3' }, { year: 1997, name: 'Mk4' }, { year: 2003, name: 'Mk5' }, { year: 2008, name: 'Mk6' }, { year: 2012, name: 'Mk7' }, { year: 2019, name: 'Mk8' }]} }
yield { id: 3, make: 'Nissan', optional: null, model: 'Qashqai', year: 2009, details: { revisions: [{ year: 2006, name: 'J10' }, { year: 2010, name: 'J10-2' }, { year: 2013, name: 'J11' }, { year: 2017, name: 'J11-2' }]} }
yield { id: 4, make: 'Volkswagen', optional: 'yes', model: 'Golf', year: 1999, details: { revisions: [{ year: 1974, name: 'Mk1' }, { year: 1983, name: 'Mk2' }, { year: 1991, name: 'Mk3' }, { year: 1997, name: 'Mk4' }, { year: 2003, name: 'Mk5' }, { year: 2008, name: 'Mk6' }, { year: 2012, name: 'Mk7' }, { year: 2019, name: 'Mk8' }]} }
yield { id: 5, make: 'Mazda', model: '323', year: 2004, details: { revisions: [{ year: 1963, name: '1gen' }, { year: 1967, name: '2gen' }, { year: 1977, name: 'FA4' }, { year: 1980, name: 'BD' }, { year: 1985, name: 'BF' }, { year: 1989, name: 'BG' }, { year: 1994, name: 'BH' }, { year: 1998, name: 'BJ' }]} }
}

Expand Down Expand Up @@ -459,6 +459,15 @@ describe('When using enumerable for record type', () => {

chai.expect(cars.length).to.equal(1)
})

it('should iterate using optional values', () => {
let cars = []

for(let car of new jsEnumerable.Enumerable(iterator()).where(`contains(optional, 'es')`))
cars.push(car)

chai.expect(cars.length).to.equal(2)
})

it('should iterate using where cars having models for over 40 years since 1970 (javascript)', () => {
let cars = []
Expand Down

0 comments on commit 7d56363

Please sign in to comment.