Skip to content

Commit

Permalink
Added escaping backticks when generating SQL.
Browse files Browse the repository at this point in the history
  • Loading branch information
CollinAlpert committed Feb 11, 2019
1 parent 66b9654 commit e323004
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 90 deletions.
4 changes: 3 additions & 1 deletion README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ For example, the lambda expression\
``person -> person.getAge() > 18 && person.getName().startsWith("Steve")``\
would translate to:\
``person.age > 18 AND person.name LIKE 'Steve%'``

Lambda2Sql also automatically escapes table names and columns with backticks (\`). If you do not wish this, you can specify it as an argument in the `Lambda2Sql.toSql()` method.

Feel free to open an issue with any requests you might have.

Expand All @@ -62,7 +64,7 @@ You can include the Maven dependency:
<dependency>
<groupId>com.github.collinalpert</groupId>
<artifactId>lambda2sql</artifactId>
<version>2.1.1</version>
<version>2.1.2</version>
</dependency>
```

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.github.collinalpert</groupId>
<artifactId>lambda2sql</artifactId>
<version>2.1.1</version>
<version>2.1.2</version>
<packaging>jar</packaging>

<name>lambda2sql</name>
Expand Down
14 changes: 10 additions & 4 deletions src/main/java/com/github/collinalpert/lambda2sql/Lambda2Sql.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,21 @@ public class Lambda2Sql {
* Supported operators: {@code >,>=,<,<=,=,!=,&&,||,!}
*
* @param functionalInterface A {@link FunctionalInterface} lambda to convert.
* @param prefix An optional prefix to proceed the column name. Usually it is the table name.
* @param tableName The table name which the column belongs to. This will explicitly reference the column.
* It is optional to specify this.
* @param withBackticks Specifies if the table and the column name should be escaped with backticks. The default behavior is {@code true}.
* @return A {@link String} describing the SQL where condition.
*/
public static String toSql(SerializedFunctionalInterface functionalInterface, String prefix) {
public static String toSql(SerializedFunctionalInterface functionalInterface, String tableName, boolean withBackticks) {
var lambdaExpression = LambdaExpression.parse(functionalInterface);
return lambdaExpression.accept(new SqlVisitor(prefix)).toString();
return lambdaExpression.accept(new SqlVisitor(tableName, withBackticks)).toString();
}

public static String toSql(SerializedFunctionalInterface functionalInterface, String tableName) {
return toSql(functionalInterface, tableName, true);
}

public static String toSql(SerializedFunctionalInterface functionalInterface) {
return toSql(functionalInterface, null);
return toSql(functionalInterface, null, false);
}
}
53 changes: 30 additions & 23 deletions src/main/java/com/github/collinalpert/lambda2sql/SqlVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
Expand All @@ -51,7 +52,8 @@ public class SqlVisitor implements ExpressionVisitor<StringBuilder> {
}
}};

private final String prefix;
private final String tableName;
private final boolean withBackticks;
private final LinkedListStack<List<ConstantExpression>> arguments;

/**
Expand All @@ -69,16 +71,18 @@ public class SqlVisitor implements ExpressionVisitor<StringBuilder> {
e.printStackTrace();
}
}};

private StringBuilder sb;
private Expression body;
private Expression javaMethodParameter;

SqlVisitor(String prefix) {
this(prefix, new LinkedListStack<>());
SqlVisitor(String tableName, boolean withBackTicks) {
this(tableName, withBackTicks, new LinkedListStack<>());
}

private SqlVisitor(String prefix, LinkedListStack<List<ConstantExpression>> arguments) {
this.prefix = prefix;
private SqlVisitor(String tableName, boolean withBackticks, LinkedListStack<List<ConstantExpression>> arguments) {
this.tableName = tableName;
this.withBackticks = withBackticks;
this.arguments = arguments;
this.sb = new StringBuilder();
}
Expand Down Expand Up @@ -222,21 +226,22 @@ public StringBuilder visit(DelegateExpression e) {
@Override
public StringBuilder visit(MemberExpression e) {
if (registeredMethods.containsKey(e.getMember())) {
return Expression.binary(registeredMethods.get(e.getMember()), e.getInstance(), javaMethodParameter).accept(this);
return Expression.binary(registeredMethods.get(e.getMember()), e.getInstance(), this.javaMethodParameter).accept(this);
}

if (complexMethods.containsKey(e.getMember())) {
return sb.append(complexMethods.get(e.getMember()).apply(e.getInstance(), javaMethodParameter, false));
if (this.complexMethods.containsKey(e.getMember())) {
return sb.append(this.complexMethods.get(e.getMember()).apply(e.getInstance(), this.javaMethodParameter, false));
}

var nameArray = e.getMember().getName().replaceAll("^(get)", "").toCharArray();
nameArray[0] = Character.toLowerCase(nameArray[0]);
var name = new String(nameArray);
if (prefix == null) {
if (this.tableName == null) {
return sb.append(name);
}

return sb.append(prefix).append(".").append(name);
String escape = this.withBackticks ? "`" : "";
return sb.append(escape).append(this.tableName).append(escape).append(".").append(escape).append(name).append(escape);
}

/**
Expand Down Expand Up @@ -287,28 +292,30 @@ public StringBuilder visit(UnaryExpression e) {

//region Complex Java methods

private StringBuilder stringStartsWith(Expression e, Expression e1, boolean negated) {
var valueBuilder = e1.accept(new SqlVisitor(this.prefix, this.arguments));
valueBuilder.insert(valueBuilder.length() - 1, '%');
return e.accept(new SqlVisitor(this.prefix, this.arguments)).append(negated ? " NOT" : "").append(" LIKE ").append(valueBuilder);
private StringBuilder stringStartsWith(Expression member, Expression argument, boolean negated) {
return doStringOperation(member, argument, negated, valueBuilder -> valueBuilder.insert(valueBuilder.length() - 1, '%'));
}

private StringBuilder stringEndsWith(Expression e, Expression e1, boolean negated) {
return e.accept(new SqlVisitor(this.prefix, this.arguments)).append(negated ? " NOT" : "").append(" LIKE ").append(e1.accept(new SqlVisitor(this.prefix, this.arguments)).insert(1, '%'));
private StringBuilder stringEndsWith(Expression member, Expression argument, boolean negated) {
return doStringOperation(member, argument, negated, valueBuilder -> valueBuilder.insert(1, '%'));
}

private StringBuilder stringContains(Expression e, Expression e1, boolean negated) {
var valueBuilder = e1.accept(new SqlVisitor(this.prefix, this.arguments));
valueBuilder.insert(1, '%').insert(valueBuilder.length() - 1, '%');
return e.accept(new SqlVisitor(this.prefix, this.arguments)).append(negated ? " NOT" : "").append(" LIKE ").append(valueBuilder);
private StringBuilder stringContains(Expression member, Expression argument, boolean negated) {
return doStringOperation(member, argument, negated, valueBuilder -> valueBuilder.insert(1, '%').insert(valueBuilder.length() - 1, '%'));
}

private StringBuilder listContains(Expression e, Expression e1, boolean negated) {
List l = (List) arguments.pop().get(((ParameterExpression) e).getIndex()).getValue();
private StringBuilder listContains(Expression listAsArgument, Expression argument, boolean negated) {
List l = (List) arguments.pop().get(((ParameterExpression) listAsArgument).getIndex()).getValue();
var joiner = new StringJoiner(", ", "(", ")");
l.forEach(x -> joiner.add(x.toString()));
return e1.accept(new SqlVisitor(this.prefix, this.arguments)).append(negated ? " NOT" : "").append(" IN ").append(joiner.toString());
return argument.accept(new SqlVisitor(this.tableName, this.withBackticks, this.arguments)).append(negated ? " NOT" : "").append(" IN ").append(joiner.toString());
}

//endregion

private StringBuilder doStringOperation(Expression member, Expression argument, boolean negated, Consumer<StringBuilder> modifier) {
var valueBuilder = argument.accept(new SqlVisitor(this.tableName, this.withBackticks, this.arguments));
modifier.accept(valueBuilder);
return member.accept(new SqlVisitor(this.tableName, this.withBackticks, this.arguments)).append(negated ? " NOT" : "").append(" LIKE ").append(valueBuilder);
}
}
Loading

0 comments on commit e323004

Please sign in to comment.