Skip to content

Commit

Permalink
Added support for IN operator.
Browse files Browse the repository at this point in the history
  • Loading branch information
cbrianball committed May 8, 2019
1 parent 67fe19b commit aae8833
Show file tree
Hide file tree
Showing 8 changed files with 53 additions and 32 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# ts-odata-client

## Warning: This library is not yet ready for use

A simple library for creating and executing OData queries. Makes heavy use of TypeScript for a better developer experience.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ts-odata-client",
"version": "0.1.0",
"version": "1.0.0",
"description": "OData TypeScript Client",
"main": "/index.js",
"types": "/index.d.ts",
Expand Down
1 change: 1 addition & 0 deletions src/lib/ExpressionOperator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const enum ExpressionOperator {
GetWithCount = "getWithCount",
GreaterThan = "greaterThan",
GreaterThanOrEqualTo = "greaterThanOrEqualTo",
In = "in",
LessThan = "lessThan",
LessThanOrEqualTo = "lessThanOrEqualTo",
Literal = "literal",
Expand Down
8 changes: 7 additions & 1 deletion src/lib/ODataV4ExpressionVisitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,11 @@ export class ODataV4ExpressionVisitor extends TypedExpressionVisitor {
}
else if (operand instanceof Expression) {
translation.push(this.translatePredicateExpression(operand));
} else //assume this is a literal without the type specified
}
else if (operand instanceof Array) {
translation.push([operand.map(i => this.deriveLiteral(new Literal(i))).join(',')]);
}
else //assume this is a literal without the type specified
translation.push([this.deriveLiteral(new Literal(operand))]);
}

Expand Down Expand Up @@ -149,6 +153,8 @@ export class ODataV4ExpressionVisitor extends TypedExpressionVisitor {
return [`startsWith(${this.reduceTranslatedExpression(left)},${this.reduceTranslatedExpression(right)})`];
case ExpressionOperator.EndsWith:
return [`endsWith(${this.reduceTranslatedExpression(left)},${this.reduceTranslatedExpression(right)})`];
case ExpressionOperator.In:
return [`${this.reduceTranslatedExpression(left)} in (${this.reduceTranslatedExpression(right)})`];
default:
throw new Error(`Operator '${expression.operator}' is not supported`);
}
Expand Down
8 changes: 4 additions & 4 deletions src/lib/ODataV4QueryProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ export class ODataV4QueryProvider extends ODataQueryProvider {
const queryString: string[] = [];

if (query.filter)
queryString.push("$filter=" + query.filter);
queryString.push("$filter=" + encodeURIComponent(query.filter));

if (query.orderBy) {
queryString.push("$orderby=" + query.orderBy.map(o => o.sort ? `${o.field} ${o.sort}` : o.field).join(','));
queryString.push("$orderby=" + encodeURIComponent(query.orderBy.map(o => o.sort ? `${o.field} ${o.sort}` : o.field).join(',')));
}

if (query.select)
queryString.push("$select=" + query.select);
queryString.push("$select=" + encodeURIComponent(query.select.join(',')));

if (query.skip)
queryString.push("$skip=" + Math.floor(query.skip));
Expand All @@ -72,7 +72,7 @@ export class ODataV4QueryProvider extends ODataQueryProvider {
queryString.push("$count=true");

if (query.expand)
queryString.push("$expand=" + query.expand)
queryString.push("$expand=" + encodeURIComponent(query.expand.join(',')));

if (queryString.length > 0) return '?' + queryString.join("&");
return "";
Expand Down
10 changes: 10 additions & 0 deletions src/lib/PredicateBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,14 @@ export class PredicateBuilder<T> {
const expression = new Expression(ExpressionOperator.EndsWith, [new FieldReference<T>(field as any), value], this.expression);
return new BooleanPredicateBuilder<T>(expression);
}

/**
* Filters based on the field being any one of the provided values.
* @param field
* @param values
*/
public any<K extends Extract<keyof T, string>>(field: K, values: ArrayLike<KeyExpressionOrUnkonwn<T, K>> | Iterable<KeyExpressionOrUnkonwn<T, K>>){
const expression = new Expression(ExpressionOperator.In, [new FieldReference<T>(field), Array.from(values)], this.expression);
return new BooleanPredicateBuilder<T>(expression);
}
}
2 changes: 1 addition & 1 deletion test/ODataContext.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe("ODataConext", () => {
it("should create query", async () => {
const query = context.subjects.filter(p => p.contains("Name", "vet"));

expect(query[resolveQuery]().toString()).to.be.eql("http://api.purdue.io/odata/Subjects?$filter=contains(Name,'vet')");
expect(query[resolveQuery]().toString()).to.be.eql("http://api.purdue.io/odata/Subjects?$filter=contains(Name%2C'vet')");

});
});
Expand Down
52 changes: 29 additions & 23 deletions test/ODataQuery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe("ODataQuery", () => {
it("should set select filter with mulitple fields", () => {
const query = baseQuery.select("firstName", "lastName");

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$select=firstName,lastName`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$select=${encodeURIComponent("firstName,lastName")}`);
});

it("should set combination select filter", () => {
Expand All @@ -39,37 +39,37 @@ describe("ODataQuery", () => {
it("should set orderBy with multiple fields", () => {
const query = baseQuery.orderBy("firstName", "lastName");

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$orderby=firstName,lastName`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$orderby=${encodeURIComponent("firstName,lastName")}`);
});

it("should set orderBy multiple times", () => {
const query = baseQuery.orderBy("firstName", "lastName").orderBy("age");

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$orderby=firstName,lastName,age`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$orderby=${encodeURIComponent("firstName,lastName,age")}`);
});

it("should set orderByDescending", () => {
const query = baseQuery.orderByDescending("firstName");

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$orderby=firstName desc`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$orderby=${encodeURIComponent("firstName desc")}`);
});

it("should set orderByDescending with multiple fields", () => {
const query = baseQuery.orderByDescending("firstName", "lastName");

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$orderby=firstName desc,lastName desc`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$orderby=${encodeURIComponent("firstName desc,lastName desc")}`);
});

it("should set orderByDescending multiple times", () => {
const query = baseQuery.orderByDescending("firstName", "lastName").orderByDescending("age");

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$orderby=firstName desc,lastName desc,age desc`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$orderby=${encodeURIComponent("firstName desc,lastName desc,age desc")}`);
});

it("should set orderBy and orderByDescending multiple times", () => {
const query = baseQuery.orderByDescending("firstName").orderBy("age").orderByDescending("lastName");

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$orderby=firstName desc,age,lastName desc`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$orderby=${encodeURIComponent("firstName desc,age,lastName desc")}`);
});

it("should set skip", () => {
Expand Down Expand Up @@ -111,74 +111,74 @@ describe("ODataQuery", () => {
it("should set simple filter", () => {
const query = baseQuery.filter(p => p.equals("firstName", "john"));

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=firstName eq 'john'`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=${encodeURIComponent("firstName eq 'john'")}`);
});

it("should set compound filter", () => {
const query = baseQuery.filter(p => p.equals("firstName", "john").and(p.greaterThanOrEqualTo("age", 30)));

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=firstName eq 'john' and age ge 30`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=${encodeURIComponent("firstName eq 'john' and age ge 30")}`);
});

it("should set complex filter", () => {
const query = baseQuery.filter(p => p.equals("firstName", "john").and(p.greaterThanOrEqualTo("age", 30).or(p.notEquals("lastName", "Jones"))));

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=firstName eq 'john' and (age ge 30 or lastName ne 'Jones')`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=${encodeURIComponent("firstName eq 'john' and (age ge 30 or lastName ne 'Jones')")}`);
});

it("should set complex filter", () => {
const query = baseQuery.filter(p => p.equals("firstName", "john").and(p.greaterThanOrEqualTo("age", 30))
.or(p.notEquals("lastName", "Jones").and(p.equals("email", ".com"))));

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=(firstName eq 'john' and age ge 30) or (lastName ne 'Jones' and email eq '.com')`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=${encodeURIComponent("(firstName eq 'john' and age ge 30) or (lastName ne 'Jones' and email eq '.com')")}`);
});

it("should handle contains", () => {
const query = baseQuery.filter(p => p.contains("firstName", "jac"));

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=contains(firstName,'jac')`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=${encodeURIComponent("contains(firstName,'jac')")}`);
});

it("should handle startsWith", () => {
const query = baseQuery.filter(p => p.startsWith("firstName", "jac"));

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=startsWith(firstName,'jac')`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=${encodeURIComponent("startsWith(firstName,'jac')")}`);
});

it("should handle endsWith", () => {
const query = baseQuery.filter(p => p.endsWith("firstName", "jac"));

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=endsWith(firstName,'jac')`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=${encodeURIComponent("endsWith(firstName,'jac')")}`);
});

it("should handle equals and notEquals", () => {
const query = baseQuery.filter(p => p.equals("firstName", "jac").and(p.notEquals("age", 50)));

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=firstName eq 'jac' and age ne 50`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=${encodeURIComponent("firstName eq 'jac' and age ne 50")}`);
});

it("should handle greaterThan and greaterThanEqualTo", () => {
const query = baseQuery.filter(p => p.greaterThan("firstName", "jac").and(p.greaterThanOrEqualTo("age", 50)));

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=firstName gt 'jac' and age ge 50`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=${encodeURIComponent("firstName gt 'jac' and age ge 50")}`);
});

it("should handle lessThan and lessThanEqualTo", () => {
const query = baseQuery.filter(p => p.lessThan("firstName", "jac").and(p.lessThanOrEqualTo("age", 50)));

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=firstName lt 'jac' and age le 50`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=${encodeURIComponent("firstName lt 'jac' and age le 50")}`);
});

it("should handle null comparisons", () => {
const query = baseQuery.filter(p => p.equals("firstName", null));

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=firstName eq null`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=${encodeURIComponent("firstName eq null")}`);
});

it("should handle undefined comparisons", () => {
const query = baseQuery.filter(p => p.equals("firstName", undefined));

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=firstName eq null`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=${encodeURIComponent("firstName eq null")}`);
});

it("should handle cumulative expand", () => {
Expand All @@ -190,19 +190,25 @@ describe("ODataQuery", () => {
it("should handle cumulative expands", () => {
const query = baseQuery.expand("children").expand("pets");

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$expand=children,pets`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$expand=${encodeURIComponent("children,pets")}`);
});

it("should handle mulitple expands in one call", () => {
const query = baseQuery.expand("children", "pets");

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$expand=children,pets`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$expand=${encodeURIComponent("children,pets")}`);
});

it("should handle not repeat expands", () => {
it("should not repeat expands", () => {
const query = baseQuery.expand("children", "pets").expand("pets");

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$expand=children,pets`);
expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$expand=${encodeURIComponent("children,pets")}`);
});

it("should handle any", () => {
const query = baseQuery.filter(p => p.any("lastName", ["Jones", "Smith", "Ng"]));

expect(query.provider.buildQuery(query.expression)).to.be.eql(`${endpoint}?$filter=${encodeURIComponent("lastName in ('Jones','Smith','Ng')")}`);
});
});

Expand Down

0 comments on commit aae8833

Please sign in to comment.