Skip to content

Commit

Permalink
feat: add ability for escaping for Raw() find operator (#6850)
Browse files Browse the repository at this point in the history
* feat: add ability for escaping for Raw() find operator

* fix: update Raw() find operator

* fix: fix tests for Raw() find operator

* feat: add ability for escaping with object literal parameters for Raw() find operator

* docs: correct the example comment of Raw() find operator

* fix: delete redundant functional
  • Loading branch information
temnov98 committed Oct 6, 2020
1 parent 7147a0d commit 91b85bf
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 12 deletions.
45 changes: 44 additions & 1 deletion src/find-options/FindOperator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import {ObjectLiteral} from "../common/ObjectLiteral";
import {FindOperatorType} from "./FindOperatorType";

type SqlGeneratorType = (aliasPath: string) => string;

/**
* Find Operator used in Find Conditions.
*/
Expand All @@ -19,6 +22,11 @@ export class FindOperator<T> {
*/
private _value: T|FindOperator<T>;

/**
* ObjectLiteral parameters.
*/
private _objectLiteralParameters: ObjectLiteral|undefined;

/**
* Indicates if parameter is used or not for this operator.
*/
Expand All @@ -29,15 +37,29 @@ export class FindOperator<T> {
*/
private _multipleParameters: boolean;

/**
* SQL generator
*/
private _getSql: SqlGeneratorType|undefined;

// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------

constructor(type: FindOperatorType, value: T|FindOperator<T>, useParameter: boolean = true, multipleParameters: boolean = false) {
constructor(
type: FindOperatorType,
value: T|FindOperator<T>,
useParameter: boolean = true,
multipleParameters: boolean = false,
getSql?: SqlGeneratorType,
objectLiteralParameters?: ObjectLiteral,
) {
this._type = type;
this._value = value;
this._useParameter = useParameter;
this._multipleParameters = multipleParameters;
this._getSql = getSql;
this._objectLiteralParameters = objectLiteralParameters;
}

// -------------------------------------------------------------------------
Expand Down Expand Up @@ -83,6 +105,17 @@ export class FindOperator<T> {
return this._value;
}

/**
* Gets ObjectLiteral parameters.
*/
get objectLiteralParameters(): ObjectLiteral|undefined {
if (this._value instanceof FindOperator)
return this._value.objectLiteralParameters;

return this._objectLiteralParameters;
}


/**
* Gets the child FindOperator if it exists
*/
Expand All @@ -92,4 +125,14 @@ export class FindOperator<T> {

return undefined;
}

/**
* Gets the SQL generator
*/
get getSql(): SqlGeneratorType|undefined {
if (this._value instanceof FindOperator)
return this._value.getSql;

return this._getSql;
}
}
29 changes: 26 additions & 3 deletions src/find-options/operator/Raw.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
import {FindOperator} from "../FindOperator";
import {ObjectLiteral} from "../../common/ObjectLiteral";

/**
* Find Options Operator.
* Example: { someField: Raw([...]) }
* Example: { someField: Raw("12") }
*/
export function Raw<T>(value: string|((columnAlias: string) => string)): FindOperator<any> {
return new FindOperator("raw", value as any, false);
export function Raw<T>(value: string): FindOperator<any>;

/**
* Find Options Operator.
* Example: { someField: Raw((columnAlias) => `${columnAlias} = 5`) }
*/
export function Raw<T>(sqlGenerator: ((columnAlias: string) => string)): FindOperator<any>;

/**
* Find Options Operator.
* For escaping parameters use next syntax:
* Example: { someField: Raw((columnAlias) => `${columnAlias} = :value`, { value: 5 }) }
*/
export function Raw<T>(sqlGenerator: ((columnAlias: string) => string), parameters: ObjectLiteral): FindOperator<any>;

export function Raw<T>(
valueOrSqlGenerator: string | ((columnAlias: string) => string),
sqlGeneratorParameters?: ObjectLiteral,
): FindOperator<any> {
if (typeof valueOrSqlGenerator !== 'function') {
return new FindOperator("raw", valueOrSqlGenerator, false);
}

return new FindOperator("raw", [], true, true, valueOrSqlGenerator, sqlGeneratorParameters);
}
20 changes: 12 additions & 8 deletions src/query-builder/QueryBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -810,12 +810,16 @@ export abstract class QueryBuilder<Entity> {
} else if (parameterValue instanceof FindOperator) {
let parameters: any[] = [];
if (parameterValue.useParameter) {
const realParameterValues: any[] = parameterValue.multipleParameters ? parameterValue.value : [parameterValue.value];
realParameterValues.forEach((realParameterValue, realParameterValueIndex) => {
this.expressionMap.nativeParameters[parameterName + (parameterBaseCount + realParameterValueIndex)] = realParameterValue;
parameterIndex++;
parameters.push(this.connection.driver.createParameter(parameterName + (parameterBaseCount + realParameterValueIndex), parameterIndex - 1));
});
if (parameterValue.objectLiteralParameters) {
this.setParameters(parameterValue.objectLiteralParameters);
} else {
const realParameterValues: any[] = parameterValue.multipleParameters ? parameterValue.value : [parameterValue.value];
realParameterValues.forEach((realParameterValue, realParameterValueIndex) => {
this.expressionMap.nativeParameters[parameterName + (parameterBaseCount + realParameterValueIndex)] = realParameterValue;
parameterIndex++;
parameters.push(this.connection.driver.createParameter(parameterName + (parameterBaseCount + realParameterValueIndex), parameterIndex - 1));
});
}
}

return this.computeFindOperatorExpression(parameterValue, aliasPath, parameters);
Expand Down Expand Up @@ -892,8 +896,8 @@ export abstract class QueryBuilder<Entity> {
case "isNull":
return `${aliasPath} IS NULL`;
case "raw":
if (typeof operator.value === "function") {
return operator.value(aliasPath);
if (operator.getSql) {
return operator.getSql(aliasPath);
} else {
return `${aliasPath} = ${operator.value}`;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,80 @@ describe("repository > find options > operators", () => {
likes: Raw(columnAlias => "1 + " + columnAlias + " = 4")
});
loadedPosts.should.be.eql([{ id: 2, likes: 3, title: "About #2" }]);
})));

it("raw (function with object literal parameters)", () => Promise.all(connections.map(async connection => {
const createPost = (index: number): Post => {
const post = new Post();
post.title = `About #${index}`;
post.likes = index;

return post;
}

// insert some fake data
await connection.manager.save([
createPost(1),
createPost(2),
createPost(3),
createPost(4),
createPost(5),
createPost(6),
]);

// check operator
const result1 = await connection.getRepository(Post).find({
likes: Raw((columnAlias) => {
return `(${columnAlias} = :value1) OR (${columnAlias} = :value2)`
}, { value1: 2, value2: 3 }),
});
result1.should.be.eql([
{ id: 2, likes: 2, title: "About #2" },
{ id: 3, likes: 3, title: "About #3" },
]);

// check operator
const result2 = await connection.getRepository(Post).find({
likes: Raw((columnAlias) => {
return `(${columnAlias} IN (1, 4, 5, 6)) AND (${columnAlias} < :maxValue)`
}, { maxValue: 6 }),
});
result2.should.be.eql([
{ id: 1, likes: 1, title: "About #1" },
{ id: 4, likes: 4, title: "About #4" },
{ id: 5, likes: 5, title: "About #5" },
]);

// check operator
const result3 = await connection.getRepository(Post).find({
title: Raw((columnAlias) => {
return `${columnAlias} IN (:a, :b, :c)`;
}, { a: "About #1", b: "About #3", c: "About #5" }),
likes: Raw((columnAlias) => `${columnAlias} IN (:d, :e)`, { d: 5, e: 1 }),
});
result3.should.be.eql([
{ id: 1, likes: 1, title: "About #1" },
{ id: 5, likes: 5, title: "About #5" },
]);

// check operator
const result4 = await connection.getRepository(Post).find({
likes: Raw((columnAlias) => `${columnAlias} IN (2, 6)`, { }),
});
result4.should.be.eql([
{ id: 2, likes: 2, title: "About #2" },
{ id: 6, likes: 6, title: "About #6" },
]);

// check operator
const result5 = await connection.getRepository(Post).find({
likes: Raw((columnAlias) => `${columnAlias} IN (2, :value, 6)`, { value: 3 }),
});
result5.should.be.eql([
{ id: 2, likes: 2, title: "About #2" },
{ id: 3, likes: 3, title: "About #3" },
{ id: 6, likes: 6, title: "About #6" },
]);
})));

it("should work with ActiveRecord model", async () => {
Expand Down

0 comments on commit 91b85bf

Please sign in to comment.