Adds a CAST expression to the SQL DSL.
Example: Expressions.cast(table_user.column("name"),"VARCHAR2")
Also adds a toString to AbstractSegment to avoid stack overflows.
Closes #1066
Original pull request: #1071.
This commit is contained in:
committed by
Mark Paluch
parent
b2c45094fb
commit
cf39fa2cfb
@@ -15,7 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.relational.core.sql;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Abstract implementation to support {@link Segment} implementations.
|
||||
@@ -64,4 +67,10 @@ abstract class AbstractSegment implements Segment {
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof Segment && toString().equals(obj.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StringUtils.collectionToDelimitedString(Arrays.asList(children), ", ", getClass().getSimpleName() + "(",
|
||||
")");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.relational.core.sql;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Represents a CAST expression like {@code CAST(something AS JSON}.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @since 2.3
|
||||
*/
|
||||
public class Cast extends AbstractSegment implements Expression {
|
||||
|
||||
private final String targetType;
|
||||
private final Expression expression;
|
||||
|
||||
private Cast(Expression expression, String targetType) {
|
||||
|
||||
super(expression);
|
||||
|
||||
Assert.notNull(targetType, "Cast target must not be null!");
|
||||
|
||||
this.expression = expression;
|
||||
this.targetType = targetType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CAST expression.
|
||||
*
|
||||
* @param expression the expression to cast. Must not be {@literal null}.
|
||||
* @param targetType the type to cast to. Must not be {@literal null}.
|
||||
* @return guaranteed to be not {@literal null}.
|
||||
*/
|
||||
static Expression create(Expression expression, String targetType) {
|
||||
return new Cast(expression, targetType);
|
||||
}
|
||||
|
||||
public String getTargetType() {
|
||||
return targetType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CAST(" + expression + " AS " + targetType + ")";
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,13 @@ public abstract class Expressions {
|
||||
return table.asterisk();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a new {@link Cast} expression.
|
||||
*/
|
||||
public static Expression cast(Expression expression, String targetType) {
|
||||
return Cast.create(expression, targetType);
|
||||
}
|
||||
|
||||
// Utility constructor.
|
||||
private Expressions() {}
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.relational.core.sql.render;
|
||||
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import org.springframework.data.relational.core.sql.Cast;
|
||||
import org.springframework.data.relational.core.sql.Visitable;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Renders a CAST expression, by delegating to an {@link ExpressionVisitor} and building the expression out of the
|
||||
* rendered parts.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @since 2.3
|
||||
*/
|
||||
class CastVisitor extends TypedSubtreeVisitor<Cast> implements PartRenderer {
|
||||
|
||||
private final RenderContext context;
|
||||
@Nullable private StringJoiner joiner;
|
||||
@Nullable private ExpressionVisitor expressionVisitor;
|
||||
|
||||
CastVisitor(RenderContext context) {
|
||||
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
Delegation enterMatched(Cast cast) {
|
||||
|
||||
joiner = new StringJoiner(", ", "CAST(", " AS " + cast.getTargetType() + ")");
|
||||
|
||||
return super.enterMatched(cast);
|
||||
}
|
||||
|
||||
@Override
|
||||
Delegation enterNested(Visitable segment) {
|
||||
|
||||
expressionVisitor = new ExpressionVisitor(context, ExpressionVisitor.AliasHandling.IGNORE);
|
||||
return Delegation.delegateTo(expressionVisitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
Delegation leaveNested(Visitable segment) {
|
||||
|
||||
Assert.state(joiner != null, "Joiner must not be null.");
|
||||
Assert.state(expressionVisitor != null, "ExpressionVisitor must not be null.");
|
||||
|
||||
joiner.add(expressionVisitor.getRenderedPart());
|
||||
return super.leaveNested(segment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getRenderedPart() {
|
||||
|
||||
if (joiner == null) {
|
||||
throw new IllegalStateException("Joiner must not be null.");
|
||||
}
|
||||
|
||||
return joiner.toString();
|
||||
}
|
||||
}
|
||||
@@ -15,15 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.relational.core.sql.render;
|
||||
|
||||
import org.springframework.data.relational.core.sql.AsteriskFromTable;
|
||||
import org.springframework.data.relational.core.sql.BindMarker;
|
||||
import org.springframework.data.relational.core.sql.Column;
|
||||
import org.springframework.data.relational.core.sql.Condition;
|
||||
import org.springframework.data.relational.core.sql.Expression;
|
||||
import org.springframework.data.relational.core.sql.Named;
|
||||
import org.springframework.data.relational.core.sql.SimpleFunction;
|
||||
import org.springframework.data.relational.core.sql.SubselectExpression;
|
||||
import org.springframework.data.relational.core.sql.Visitable;
|
||||
import org.springframework.data.relational.core.sql.*;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -105,6 +97,11 @@ class ExpressionVisitor extends TypedSubtreeVisitor<Expression> implements PartR
|
||||
}
|
||||
} else if (segment instanceof AsteriskFromTable) {
|
||||
value = NameRenderer.render(context, ((AsteriskFromTable) segment).getTable()) + ".*";
|
||||
} else if (segment instanceof Cast) {
|
||||
|
||||
CastVisitor visitor = new CastVisitor(context);
|
||||
partRenderer = visitor;
|
||||
return Delegation.delegateTo(visitor);
|
||||
} else {
|
||||
// works for literals and just and possibly more
|
||||
value = segment.toString();
|
||||
@@ -138,6 +135,7 @@ class ExpressionVisitor extends TypedSubtreeVisitor<Expression> implements PartR
|
||||
Delegation leaveMatched(Expression segment) {
|
||||
|
||||
if (partRenderer != null) {
|
||||
|
||||
value = partRenderer.getRenderedPart();
|
||||
partRenderer = null;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,10 @@ public class ExpressionVisitorUnitTests {
|
||||
fixture("Count *", Functions.count(Expressions.asterisk()), "COUNT(*)"), //
|
||||
fixture("Function", SimpleFunction.create("Function", asList(SQL.literalOf("one"), SQL.literalOf("two"))), //
|
||||
"Function('one', 'two')"), //
|
||||
fixture("Null", SQL.nullLiteral(), "NULL")); //
|
||||
fixture("Null", SQL.nullLiteral(), "NULL"), //
|
||||
fixture("Cast", Expressions.cast(Column.create("col", Table.create("tab")), "JSON"), "CAST(tab.col AS JSON)"), //
|
||||
fixture("Cast with alias", Expressions.cast(Column.create("col", Table.create("tab")).as("alias"), "JSON"),
|
||||
"CAST(tab.col AS JSON)")); //
|
||||
}
|
||||
|
||||
@Test // GH-1003
|
||||
|
||||
@@ -168,8 +168,7 @@ class SelectRendererUnitTests {
|
||||
.join(department) //
|
||||
.on(Conditions.isEqual(employee.column("department_id"), department.column("id")) //
|
||||
.or(Conditions.isNotEqual(employee.column("tenant"), department.column("tenant")) //
|
||||
))
|
||||
.build();
|
||||
)).build();
|
||||
|
||||
assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT employee.id, department.name FROM employee " //
|
||||
+ "JOIN department ON employee.department_id = department.id " //
|
||||
@@ -183,12 +182,11 @@ class SelectRendererUnitTests {
|
||||
Table department = SQL.table("department");
|
||||
|
||||
Select select = Select.builder().select(employee.column("id"), department.column("name")).from(employee) //
|
||||
.join(department)
|
||||
.on(Expressions.just("alpha")).equals(Expressions.just("beta")) //
|
||||
.join(department).on(Expressions.just("alpha")).equals(Expressions.just("beta")) //
|
||||
.build();
|
||||
|
||||
assertThat(SqlRenderer.toString(select)).isEqualTo("SELECT employee.id, department.name FROM employee "
|
||||
+ "JOIN department ON alpha = beta");
|
||||
assertThat(SqlRenderer.toString(select))
|
||||
.isEqualTo("SELECT employee.id, department.name FROM employee " + "JOIN department ON alpha = beta");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-309
|
||||
@@ -458,4 +456,15 @@ class SelectRendererUnitTests {
|
||||
final String rendered = SqlRenderer.toString(select);
|
||||
assertThat(rendered).isEqualTo("SELECT User.name, User.age FROM User WHERE User.age > 20");
|
||||
}
|
||||
|
||||
@Test // GH-1066
|
||||
void shouldRenderCast() {
|
||||
|
||||
Table table_user = SQL.table("User");
|
||||
Select select = StatementBuilder.select(Expressions.cast(table_user.column("name"), "VARCHAR2")).from(table_user)
|
||||
.build();
|
||||
|
||||
final String rendered = SqlRenderer.toString(select);
|
||||
assertThat(rendered).isEqualTo("SELECT CAST(User.name AS VARCHAR2) FROM User");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user