Browse Source

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.
pull/1078/head
Jens Schauder 4 years ago committed by Mark Paluch
parent
commit
cf39fa2cfb
No known key found for this signature in database
GPG Key ID: 4406B84C1661DCD1
  1. 9
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractSegment.java
  2. 60
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Cast.java
  3. 7
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expressions.java
  4. 77
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/CastVisitor.java
  5. 16
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ExpressionVisitor.java
  6. 5
      spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/ExpressionVisitorUnitTests.java
  7. 21
      spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SelectRendererUnitTests.java

9
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractSegment.java

@ -15,7 +15,10 @@ @@ -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 { @@ -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() + "(",
")");
}
}

60
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Cast.java

@ -0,0 +1,60 @@ @@ -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 + ")";
}
}

7
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expressions.java

@ -53,6 +53,13 @@ public abstract class Expressions { @@ -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() {}

77
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/CastVisitor.java

@ -0,0 +1,77 @@ @@ -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();
}
}

16
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ExpressionVisitor.java

@ -15,15 +15,7 @@ @@ -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 @@ -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 @@ -138,6 +135,7 @@ class ExpressionVisitor extends TypedSubtreeVisitor<Expression> implements PartR
Delegation leaveMatched(Expression segment) {
if (partRenderer != null) {
value = partRenderer.getRenderedPart();
partRenderer = null;
}

5
spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/ExpressionVisitorUnitTests.java

@ -68,7 +68,10 @@ public class ExpressionVisitorUnitTests { @@ -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

21
spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/SelectRendererUnitTests.java

@ -168,8 +168,7 @@ class SelectRendererUnitTests { @@ -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 { @@ -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 { @@ -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");
}
}

Loading…
Cancel
Save