Skip to content

Commit

Permalink
pass closure params (#87)
Browse files Browse the repository at this point in the history
* pass closure params

* 2.12.0
  • Loading branch information
kbarbounakis authored Jun 29, 2024
1 parent 10072bf commit db22f9c
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 29 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@themost/query",
"version": "2.11.4",
"version": "2.12.0",
"description": "MOST Web Framework Codename ZeroGravity - Query Module",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
Expand Down
17 changes: 15 additions & 2 deletions spec/ClosureParser.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe('ClosureParser', () => {
familyName,
givenName
})
.from(People).where( x => {
.from(People).where( (x, identifier) => {
return x.id === identifier;
}, {
identifier
Expand Down Expand Up @@ -216,7 +216,7 @@ describe('ClosureParser', () => {
x.name,
x.price
})
.from(Products).where( x => {
.from(Products).where( (x, maximumPrice) => {
return x.price < maximumPrice;
}, {
maximumPrice
Expand All @@ -226,6 +226,19 @@ describe('ClosureParser', () => {
result.forEach( x => {
expect(x.price).toBeLessThan(maximumPrice);
});

a = new QueryExpression().select( x => {
x.name,
x.price
})
.from(Products).where((x, maximumPrice, category) => {
return x.price < maximumPrice && x.category === category;
}, 1000, 'Desktops');
result = await db.executeAsync(a);
expect(result.length).toBeTruthy();
result.forEach( x => {
expect(x.price).toBeLessThan(1000);
});
});

it('should use Date.prototype.getFullYear()', async () => {
Expand Down
19 changes: 19 additions & 0 deletions spec/QueryExpression.where.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -499,4 +499,23 @@ describe('QueryExpression.where', () => {
expect(results.length).toBe(1);
});


it('should use param array', async () => {
const People = new QueryEntity('PersonData');
const emailAddress = 'cameron.ball@example.com';
let query = new QueryExpression()
.select(({ id, familyName: lastName, givenName: firstName, email, dateCreated }) => {
return { id, lastName, firstName, email, dateCreated }
})
.from(People)
.where((x, value) => {
return x.email === value;
}, emailAddress)
.take(1);
let results = await db.executeAsync(query);
expect(results.length).toBe(1);
expect(results[0].email).toEqual('cameron.ball@example.com');
expect(results[0].firstName).toEqual('Cameron');
});

});
8 changes: 4 additions & 4 deletions src/closures/ClosureParser.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// MOST Web Framework Codename Zero Gravity Copyright (c) 2017-2022, THEMOST LP All rights reserved
import {SyncSeriesEventEmitter} from '@themost/events';

export type SelectClosure = (x: any) => any;
export type FilterClosure = (x: any) => any;
export type SelectClosure = (x: any, ...params: any[]) => any;
export type FilterClosure = (x: any, ...params: any[]) => any;

export declare function count(...args: any): number;
export declare function round(n: any, precision?: number): number;
Expand All @@ -18,8 +18,8 @@ export declare function length(value: any): any;
*/
export declare class ClosureParser {
static binaryToExpressionOperator(binaryOperator: string): string;
parseSelect(func: SelectClosure, params?: any): any;
parseFilter(func: FilterClosure, params?: any): any;
parseSelect(func: SelectClosure, ...params: any[]): any;
parseFilter(func: FilterClosure, ...params: any[]): any;
parseCommon(expr: any): any;
parseLogical(expr: any): any;
parseBinary(expr: any): any;
Expand Down
58 changes: 52 additions & 6 deletions src/closures/ClosureParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { StringMethodParser } from './StringMethodParser';
import { MathMethodParser } from './MathMethodParser';
import { FallbackMethodParser } from './FallbackMethodParser';
import { SyncSeriesEventEmitter } from '@themost/events';
import { isObjectDeep } from '../is-object';

let ExpressionTypes = {
LogicalExpression: 'LogicalExpression',
Expand Down Expand Up @@ -94,7 +95,6 @@ function round(n, precision) {
}
return Math.round(n);
}

// noinspection JSCommentMatchesSignature
/**
* @param {...*} args
Expand Down Expand Up @@ -243,13 +243,16 @@ class ClosureParser {
/**
* Parses a javascript expression and returns the equivalent select expression.
* @param {Function} func The closure expression to parse
* @param {*} params An object which represents closure parameters
* @param {...*} params An object which represents closure parameters
*/
// eslint-disable-next-line no-unused-vars
parseSelect(func, params) {
if (func == null) {
return;
}
this.params = params;
const args = Array.from(arguments);
// remove first argument
args.splice(0,1);
if (typeof func !== 'function') {
throw new Error('Select closure must a function.');
}
Expand All @@ -259,6 +262,8 @@ class ClosureParser {
let funcExpr = expr.body[0].expression.argument;
//get named parameters
this.namedParams = funcExpr.params;
// parse params
this.parseParams(args);
let res = this.parseCommon(funcExpr.body);
if (res && res instanceof SequenceExpression) {
return res.value.map(function (x) {
Expand Down Expand Up @@ -303,26 +308,60 @@ class ClosureParser {
}
throw new Error('Invalid select closure');
}

parseParams(args) {
// closure params can be:
// 1. an object which has properties with the same name with the arguments of the given closure
// e.g. { p1: 'Peter' } where the closure may be something like (x, p1) => x.givenName === p1
// or
// 2. a param array where closure arguments should be bound by index
// e.g. where((x, p1) => x.givenName === p1, 'Peter')
// for backward compatibility issues we will try to create an object with closure params
this.params = {};
this.namedParams.forEach((namedParam, index) => {
// omit the first param because it's the reference of the enumerable object
if (index > 0) {
// preserve backward compatibility
if (args.length === 1 && isObjectDeep(args[0])) {
// get param by name
const [arg0] = args;
if (Object.prototype.hasOwnProperty.call(arg0, namedParam.name)) {
Object.assign(this.params, {
[namedParam.name]: arg0[namedParam.name]
})
}
} else {
// get param by index
Object.assign(this.params, {
[namedParam.name]: args[index - 1]
})
}
}
});
}
/**
* Parses a javascript expression and returns the equivalent QueryExpression instance.
* @param {Function} func The closure expression to parse
* @param {*} params An object which represents closure parameters
* @param {...*} params An object which represents closure parameters
*/
// eslint-disable-next-line no-unused-vars
parseFilter(func, params) {
let self = this;
if (func == null) {
return;
}
this.params = params;
//convert the given function to javascript expression
let expr = parse('void(' + func.toString() + ')');
//get FunctionExpression
let fnExpr = expr.body[0].expression.argument;
if (fnExpr == null) {
throw new Error('Invalid closure statement. Closure expression cannot be found.');
}
//get named parameters
// get named parameters
self.namedParams = fnExpr.params;
const args = Array.from(arguments);
args.splice(0, 1);
this.parseParams(args);
//validate expression e.g. return [EXPRESSION];
if (fnExpr.body.type === ExpressionTypes.MemberExpression) {
return this.parseMember(fnExpr.body);
Expand Down Expand Up @@ -960,10 +999,17 @@ class ClosureParser {
}
}
}

parseIdentifier(expr) {
if (this.params && Object.prototype.hasOwnProperty.call(this.params, expr.name)) {
return new LiteralExpression(this.params[expr.name]);
}
const paramIndex = this.namedParams.findIndex(
(param) => param.type === 'Identifier' && param.name === expr.name
);
if (paramIndex > 0) {
return new LiteralExpression(this.params[paramIndex - 1]);
}
const namedParam0 = this.namedParams && this.namedParams[0];
if (namedParam0.type === 'ObjectPattern') {
return this.parseMember(expr);
Expand Down
39 changes: 39 additions & 0 deletions src/is-object.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {isPlainObject, isObjectLike, isNative} from 'lodash';

const objectToString = Function.prototype.toString.call(Object);

function isObjectDeep(any) {
// check if it is a plain object
let result = isPlainObject(any);
if (result) {
return result;
}
// check if it's object
if (isObjectLike(any) === false) {
return false;
}
// get prototype
let proto = Object.getPrototypeOf(any);
// if prototype exists, try to validate prototype recursively
while(proto != null) {
// get constructor
const Ctor = Object.prototype.hasOwnProperty.call(proto, 'constructor')
&& proto.constructor;
// check if constructor is native object constructor
result = (typeof Ctor == 'function') && (Ctor instanceof Ctor)
&& Function.prototype.toString.call(Ctor) === objectToString;
// if constructor is not object constructor and belongs to a native class
if (result === false && isNative(Ctor) === true) {
// return false
return false;
}
// otherwise. get parent prototype and continue
proto = Object.getPrototypeOf(proto);
}
// finally, return result
return result;
}

export {
isObjectDeep
}
24 changes: 12 additions & 12 deletions src/query.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export declare class QueryExpression {
hasFields(): boolean;
hasPaging(): boolean;
distinct(value: any): this;
where<T>(expr: (value: T, ...param: any[]) => any, params?: any): this;
where<T>(expr: (value: T, ...param: any[]) => any, ...params: any[]): this;
where(expr: string | any): this;
injectWhere(where: any): void;
delete(entity: string): this;
Expand All @@ -61,8 +61,8 @@ export declare class QueryExpression {
update(entity: string): this;
update(entity: QueryEntity): this;
set(obj: any): this;
select<T>(expr: QueryFunc<T>, params?: any): this;
select<T,J>(expr: QueryJoinFunc<T, J>, params?: any): this;
select<T>(expr: QueryFunc<T>, ...params: any[]): this;
select<T,J>(expr: QueryJoinFunc<T, J>, ...params: any[]): this;
select(...expr: (string | any)[]): this;
count(alias: string): this;
from(entity: string): this;
Expand All @@ -74,27 +74,27 @@ export declare class QueryExpression {
rightJoin(entity: any, props?: any, alias?: any): this;
rightJoin(entity: QueryEntity): this;
with(obj: any): this;
with<T,J>(expr: (value: T, otherValue: J, ...param: any[]) => any, params?: any): this;
with<T,J>(expr: (value: T, otherValue: J, ...param: any[]) => any, ...params: any[]): this;
orderBy(expr: string | any): this;
orderBy<T>(expr: QueryFunc<T>, params?: any): this;
orderBy<T>(expr: QueryFunc<T>, ...params: any[]): this;
orderByDescending(expr: string | any): this;
orderByDescending<T>(expr: QueryFunc<T>, params?: any): this;
orderByDescending<T>(expr: QueryFunc<T>, ...params: any[]): this;
thenBy(expr: string | any): this;
thenBy<T>(expr: QueryFunc<T>, params?: any): this;
thenByDescending(expr: string | any): this;
thenByDescending<T>(expr: (value: T) => any): this;
groupBy(...expr: (string | any)[]): this;
groupBy<T>(arg1: QueryFunc<T>, params?: any): this;
groupBy<T>(arg1: QueryFunc<T>, arg2: QueryFunc<T>, params?: any): this;
groupBy<T>(arg1: QueryFunc<T>, arg2: QueryFunc<T>, arg3: QueryFunc<T>, params?: any): this;
groupBy<T>(arg1: QueryFunc<T>, arg2: QueryFunc<T>, ...params: any[]): this;
groupBy<T>(arg1: QueryFunc<T>, arg2: QueryFunc<T>, arg3: QueryFunc<T>, ...params: any[]): this;
groupBy<T>(arg1: QueryFunc<T>, arg2: QueryFunc<T>, arg3: QueryFunc<T>,
arg4: QueryFunc<T>, params?: any): this;
arg4: QueryFunc<T>, ...params: any[]): this;
groupBy<T>(arg1: QueryFunc<T>, arg2: QueryFunc<T>, arg3: QueryFunc<T>,
arg4: QueryFunc<T>, arg5: QueryFunc<T>, params?: any): this;
arg4: QueryFunc<T>, arg5: QueryFunc<T>, ...params: any[]): this;
groupBy<T>(arg1: QueryFunc<T>, arg2: QueryFunc<T>, arg3: QueryFunc<T>,
arg4: QueryFunc<T>, arg5: QueryFunc<T>, arg6: QueryFunc<T>, params?: any): this;
arg4: QueryFunc<T>, arg5: QueryFunc<T>, arg6: QueryFunc<T>, ...params: any[]): this;
groupBy<T>(arg1: QueryFunc<T>, arg2: QueryFunc<T>, arg3: QueryFunc<T>,
arg4: QueryFunc<T>, arg5: QueryFunc<T>, arg6: QueryFunc<T> , arg7: QueryFunc<T>, params?: any): this;
arg4: QueryFunc<T>, arg5: QueryFunc<T>, arg6: QueryFunc<T> , arg7: QueryFunc<T>, ...params: any[]): this;
or(field: any): this;
and(field: any): this;
equal(value: any): this;
Expand Down
5 changes: 3 additions & 2 deletions src/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,14 +260,15 @@ class QueryExpression {

/**
* @param {*} expr
* @param {*=} params
* @param {...*} params
* @returns {QueryExpression}
*/
// eslint-disable-next-line no-unused-vars
where(expr, params) {
if (typeof expr === 'function') {
// parse closure
const closureParser = this.getClosureParser();
this.$where = closureParser.parseFilter(expr, params);
this.$where = closureParser.parseFilter.apply(closureParser, Array.from(arguments))
return this;
}
if (isNil(expr))
Expand Down

0 comments on commit db22f9c

Please sign in to comment.