Skip to content

Commit

Permalink
implement expand
Browse files Browse the repository at this point in the history
  • Loading branch information
umutozel committed Dec 6, 2018
1 parent 03c013f commit 9c23ab7
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 16 deletions.
39 changes: 31 additions & 8 deletions lib/odata-query-provider.ts
Expand Up @@ -64,28 +64,28 @@ export class ODataQueryProvider implements IQueryProvider {
const sel = e.args[1] ? this.handlePartArg(e.args[1]) : null;

const path = exp.split('/');
let ec = es[path[0]] || (es[path[0]] = { children: { } });
let ec = es[path[0]] || (es[path[0]] = { children: {} });

path.slice(1).forEach(p => {
ec = ec.children[p] || (ec.children[p] = { children: { } });
ec = ec.children[p] || (ec.children[p] = { children: {} });
});

ec.select = sel;
});

queryParams.push({ key: '$expand', value: expands.join(',') });
queryParams.push({ key: '$expand', value: walkExpands(es) });
}

if (orders.length) {
const value = orders.map(o => {
const v = this.handlePartArg(o.args[0]);
return o.type.endsWith('Descending') ? (v + ' desc') : v;
return ~descFuncs.indexOf(o.type) ? (v + ' desc') : v;
}).join(',');
queryParams.push({ key: '$orderby', value });
}

for (var p in params) {
queryParams.push({ key: '$' + p.replace('where', 'filter').toLowerCase(), value: this.handlePartArg(params[p]) });
queryParams.push({ key: '$' + p.replace('where', 'filter'), value: this.handlePartArg(params[p]) });
}

return this.requestProvider.request<TResult>(queryParams, options);
Expand All @@ -98,6 +98,11 @@ export class ODataQueryProvider implements IQueryProvider {
: this.expToStr(arg.exp, arg.scopes, arg.exp.type === ExpressionType.Func ? (arg.exp as FuncExpression).parameters : [])
}

handleExp(exp: Expression, scopes: any[]) {
this.rootLambda = true;
return this.expToStr(exp, scopes, exp.type === ExpressionType.Func ? (exp as FuncExpression).parameters : [])
}

expToStr(exp: Expression, scopes: any[], parameters: string[]): string {
switch (exp.type) {
case ExpressionType.Literal:
Expand Down Expand Up @@ -193,11 +198,15 @@ export class ODataQueryProvider implements IQueryProvider {
if (callee.type !== ExpressionType.Member && callee.type !== ExpressionType.Variable)
throw new Error(`Invalid function call ${this.expToStr(exp.callee, scopes, parameters)}`);

let args = exp.args.map(a => this.expToStr(a, scopes, parameters)).join(', ');
let args: string;
if (callee.type === ExpressionType.Member) {
const member = callee as MemberExpression;
const ownerStr = this.expToStr(member.owner, scopes, parameters);

if (member.name === '$expand')
return ownerStr + '/' + this.handleExp(exp.args[0], scopes);

args = exp.args.map(a => this.expToStr(a, scopes, parameters)).join(', ');
// handle Math functions
if (~mathFuncs.indexOf(callee.name) && ownerStr === 'Math')
return `${callee.name}(${args})`;
Expand All @@ -208,9 +217,12 @@ export class ODataQueryProvider implements IQueryProvider {
if (callee.name === 'any' || callee.name === 'all')
return `${ownerStr}/${callee.name}(${args})`;

// other supported functions takes owner as the first argument`
// other supported functions takes owner as the first argument
args = args ? `${ownerStr}, ${args}` : ownerStr;
}
else {
args = exp.args.map(a => this.expToStr(a, scopes, parameters)).join(', ');
}

const oDataFunc = functions[callee.name] || callee.name;
return `${oDataFunc}(${args})`;
Expand Down Expand Up @@ -285,4 +297,15 @@ const functions = {
type ExpandContainer = { select?: string, children: ExpandCollection };
type ExpandCollection = { [expand: string]: ExpandContainer };

// TODO: multi navigation expand desteği için diziye $expand extension metodu. callToStr işlerken $expand için metod adı yerine owner/metod dön!
function walkExpands(e: ExpandCollection) {
const expStrs = [];
for (const p in e) {
const exp = e[p];
let childStr = walkExpands(exp.children);
const expStr = exp.select
? `${p}(${childStr ? `$expand=${childStr},` : ''}$select=${exp.select})`
: childStr ? `${p}/${childStr}` : p;
expStrs.push(expStr);
}
return expStrs.join(',');
}
10 changes: 5 additions & 5 deletions lib/odata-query.ts
@@ -1,5 +1,5 @@
import {
IQueryProvider, IQueryPart, Predicate, Func1, Func2, IQueryBase,
IQueryProvider, IQueryPart, Predicate, Func1, IQueryBase,
InlineCountInfo, QueryPart, PartArgument, AjaxOptions, AjaxFuncs
} from "jinqu";

Expand Down Expand Up @@ -44,7 +44,7 @@ export class ODataQuery<T> implements IODataQuery<T> {
return this.create(QueryPart.select(selector, scopes));
}

expand<TNav>(navigationSelector: Func2<T, TNav>, selector?: Func2<TNav, any>, ...scopes): IODataQuery<T> {
expand<TNav>(navigationSelector: Func1<T, TNav[] | TNav>, selector?: Func1<TNav, any>, ...scopes): IODataQuery<T> {
return this.create(createExpandPart(navigationSelector, selector, scopes));
}

Expand All @@ -71,7 +71,7 @@ export interface IODataQuery<T> extends IQueryBase {
orderBy(keySelector: Func1<T>, ...scopes): IOrderedODataQuery<T>;
orderByDescending(keySelector: Func1<T>, ...scopes): IOrderedODataQuery<T>;
select<TResult = any>(selector: Func1<T, TResult>, ...scopes): IODataQuery<T>;
expand<TNav>(navigationSelector: Func2<T, TNav>, selector?: Func2<TNav, any>, ...scopes): IODataQuery<T>;
expand<TNav>(navigationSelector: Func1<T, TNav[] | TNav>, selector?: Func1<TNav, any>, ...scopes): IODataQuery<T>;
skip(count: number): IODataQuery<T>;
top(count: number): IODataQuery<T>;
toArrayAsync(): PromiseLike<T[] & InlineCountInfo>;
Expand All @@ -82,7 +82,7 @@ export interface IOrderedODataQuery<T> extends IODataQuery<T> {
thenByDescending(keySelector: Func1<T>, ...scopes): IOrderedODataQuery<T>;
}

export function createExpandPart<T, TNav>(navigationSelector: Func2<T, TNav>, selector: Func2<TNav, any>, scopes: any[]) {
export function createExpandPart<T, TNav>(navigationSelector: Func1<T, TNav[] | TNav>, selector: Func1<TNav, any>, scopes: any[]) {
const args = [PartArgument.identifier(navigationSelector, scopes)];
if (selector) {
args.push(PartArgument.identifier(selector, scopes));
Expand All @@ -96,7 +96,7 @@ export const ODataFuncs = {

declare global {
interface Array<T> {
$expand<TNav>(navigationSelector: Func1<T, TNav>, selector?: Func1<TNav, any>): TNav;
$expand<TNav>(navigationSelector: Func1<T, TNav>): TNav;
}
}

Expand Down
5 changes: 4 additions & 1 deletion test/fixture.ts
Expand Up @@ -11,9 +11,12 @@ export class MockRequestProvider implements IAjaxProvider {
}
}

export class Country {}
export class Country {
name: string;
}

export class City {
name: string;
country: Country;
}

Expand Down
79 changes: 77 additions & 2 deletions test/service.spec.ts
Expand Up @@ -2,7 +2,7 @@ import { expect } from 'chai';
import 'mocha';
import chai = require('chai');
import chaiAsPromised = require('chai-as-promised');
import { CompanyService, MockRequestProvider } from './fixture';
import { CompanyService, MockRequestProvider, Address } from './fixture';

chai.use(chaiAsPromised);

Expand Down Expand Up @@ -30,4 +30,79 @@ describe('Service tests', () => {
expect(prm).property('key').to.equal('$orderby');
expect(prm).property('value').to.equal('id,name desc');
});
});

it('should create expand with multi level', () => {
const query = service.companies().expand(c => c.addresses.$expand(a => a.city).country);
expect(query.toArrayAsync()).to.be.fulfilled.and.eventually.be.null;
expect(provider).property('options').property('params').to.have.length(1);

const prm = provider.options.params[0];
expect(prm).property('key').to.equal('$expand');
expect(prm).property('value').to.equal('addresses/city/country');
});

it('should create expand with multi level using strings 1', () => {
const query = service.companies().expand('addresses.city.country');
expect(query.toArrayAsync()).to.be.fulfilled.and.eventually.be.null;
expect(provider).property('options').property('params').to.have.length(1);

const prm = provider.options.params[0];
expect(prm).property('key').to.equal('$expand');
expect(prm).property('value').to.equal('addresses/city/country');
});

it('should create expand with multi level using strings 2', () => {
const query = service.companies().expand('c => addresses.city.country');
expect(query.toArrayAsync()).to.be.fulfilled.and.eventually.be.null;
expect(provider).property('options').property('params').to.have.length(1);

const prm = provider.options.params[0];
expect(prm).property('key').to.equal('$expand');
expect(prm).property('value').to.equal('addresses/city/country');
});

it('should create expand with multi level using strings 3', () => {
const query = service.companies().expand('c => c.addresses.$expand(a => a.city).country');
expect(query.toArrayAsync()).to.be.fulfilled.and.eventually.be.null;
expect(provider).property('options').property('params').to.have.length(1);

const prm = provider.options.params[0];
expect(prm).property('key').to.equal('$expand');
expect(prm).property('value').to.equal('addresses/city/country');
});

it('should create expand with multi level with explicit calls', () => {
const query = service.companies().expand(c => c.addresses)
.expand(c => c.addresses.$expand(a => a.city))
.expand(c => c.addresses.$expand(a => a.city).country);
expect(query.toArrayAsync()).to.be.fulfilled.and.eventually.be.null;
expect(provider).property('options').property('params').to.have.length(1);

const prm = provider.options.params[0];
expect(prm).property('key').to.equal('$expand');
expect(prm).property('value').to.equal('addresses/city/country');
});

it('should create expand with multi level with explicit calls and selects', () => {
const query = service.companies().expand(c => c.addresses, a => a.city)
.expand(c => c.addresses.$expand(a => a.city), c => c.country)
.expand(c => c.addresses.$expand(a => a.city).country, c => c.name);
expect(query.toArrayAsync()).to.be.fulfilled.and.eventually.be.null;
expect(provider).property('options').property('params').to.have.length(1);

const prm = provider.options.params[0];
expect(prm).property('key').to.equal('$expand');
expect(prm).property('value').to.equal('addresses($expand=city($expand=country($select=name),$select=country),$select=city)');
});

it('should create expand with multi level with explicit calls and mixed selects', () => {
const query = service.companies().expand(c => c.addresses, a => a.city)
.expand(c => c.addresses.$expand(a => a.city))
.expand(c => c.addresses.$expand(a => a.city).country, c => c.name);
expect(query.toArrayAsync()).to.be.fulfilled.and.eventually.be.null;
expect(provider).property('options').property('params').to.have.length(1);

const prm = provider.options.params[0];
expect(prm).property('key').to.equal('$expand');
expect(prm).property('value').to.equal('addresses($expand=city/country($select=name),$select=city)');
});});

0 comments on commit 9c23ab7

Please sign in to comment.