Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>4.2.0-SNAPSHOT</version>
<version>4.2.0-2325-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data Relational Parent</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-jdbc-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>4.2.0-SNAPSHOT</version>
<version>4.2.0-2325-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
4 changes: 2 additions & 2 deletions spring-data-jdbc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-data-jdbc</artifactId>
<version>4.2.0-SNAPSHOT</version>
<version>4.2.0-2325-SNAPSHOT</version>

<name>Spring Data JDBC</name>
<description>Spring Data module for JDBC repositories.</description>
Expand All @@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>4.2.0-SNAPSHOT</version>
<version>4.2.0-2325-SNAPSHOT</version>
</parent>

<properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,11 @@ public Escaper getLikeEscaper() {
return delegate.getLikeEscaper();
}

@Override
public Escaper getStringLiteralEscaper() {
return delegate.getStringLiteralEscaper();
}

@Override
public LimitClause limit() {
return delegate.limit();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
class EscapingParameterSourceUnitTests {

MapSqlParameterSource delegate = new MapSqlParameterSource();
Escaper escaper = Escaper.of('x');
Escaper escaper = Escaper.escapeLikeWith('x');
EscapingParameterSource escapingParameterSource = new EscapingParameterSource(delegate, escaper);

@Nested
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -717,8 +717,8 @@ void createsQueryByEmbeddedObject() throws Exception {
.contains(TABLE + ".\"USER_STREET\" = :user_street", //
" AND ", //
TABLE + ".\"USER_CITY\" = :user_city");
assertThat(query.getParameterSource(Escaper.DEFAULT).getValue("user_street")).isEqualTo("Hello");
assertThat(query.getParameterSource(Escaper.DEFAULT).getValue("user_city")).isEqualTo("World");
assertThat(query.getParameterSource(Escaper.ANSI_LIKE_ESCAPER).getValue("user_street")).isEqualTo("Hello");
assertThat(query.getParameterSource(Escaper.ANSI_LIKE_ESCAPER).getValue("user_city")).isEqualTo("World");
}

@Test // GH-2187
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public QueryAssert contains(String expected) {
*/
public QueryAssert hasBindValue(String key, Object value) {

SqlParameterSource parameterSource = actual.getParameterSource(Escaper.DEFAULT);
SqlParameterSource parameterSource = actual.getParameterSource(Escaper.ANSI_LIKE_ESCAPER);
Assertions.assertThat(parameterSource.getValue(key))
.describedAs("Parameter source [%s] shouldn contain value [%s] for key [%s]", parameterSource, value, key)
.isEqualTo(value);
Expand Down
4 changes: 2 additions & 2 deletions spring-data-r2dbc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-data-r2dbc</artifactId>
<version>4.2.0-SNAPSHOT</version>
<version>4.2.0-2325-SNAPSHOT</version>

<name>Spring Data R2DBC</name>
<description>Spring Data module for R2DBC</description>
Expand All @@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>4.2.0-SNAPSHOT</version>
<version>4.2.0-2325-SNAPSHOT</version>
</parent>

<properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ private Escaper getEscaper(Comparator comparator) {
return dialect.getLikeEscaper();
}

return Escaper.DEFAULT;
return Escaper.ANSI_LIKE_ESCAPER;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ private Collection<Assignment> getAssignments(SqlIdentifier columnName, @Nullabl

} else if (value instanceof ValueFunction<?> valueFunction) {

mappedValue = valueFunction.map(v -> convertValue(v, propertyField.getTypeHint())).apply(Escaper.DEFAULT);
mappedValue = valueFunction.map(v -> convertValue(v, propertyField.getTypeHint())).apply(Escaper.ANSI_LIKE_ESCAPER);

if (mappedValue == null) {
return List.of(Assignments.value(column, SQL.nullLiteral()));
Expand Down
4 changes: 2 additions & 2 deletions spring-data-relational/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-data-relational</artifactId>
<version>4.2.0-SNAPSHOT</version>
<version>4.2.0-2325-SNAPSHOT</version>

<name>Spring Data Relational</name>
<description>Spring Data Relational support</description>

<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>4.2.0-SNAPSHOT</version>
<version>4.2.0-2325-SNAPSHOT</version>
</parent>

<properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,20 @@ default boolean supportsSingleQueryLoading() {
* @since 2.0
*/
default Escaper getLikeEscaper() {
return Escaper.DEFAULT;
return Escaper.ANSI_LIKE_ESCAPER;
}

/**
* Returns the {@link Escaper} used for escaping the content of string literals.
* <p>
* The default follows the SQL standard of doubling a single quote ({@code '} &rarr; {@code ''}). Dialects that
* require a different strategy must override this method.
*
* @return the {@link Escaper} used for string literal escaping.
* @since 4.2
*/
default Escaper getStringLiteralEscaper() {
return Escaper.ANSI_LITERAL_ESCAPER;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
*/
package org.springframework.data.relational.core.dialect;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.HashSet;
import java.util.Set;

import org.jspecify.annotations.Nullable;

Expand All @@ -28,35 +28,74 @@
* @author Roman Chigvintsev
* @author Mark Paluch
* @author Alexander Tochin
* @author Jens Schauder
* @since 2.0
*/
public class Escaper {

public static final Escaper DEFAULT = Escaper.of('\\');
public static final Escaper ANSI_LIKE_ESCAPER = Escaper.escapeLikeWith('\\');
public static final Escaper ANSI_LITERAL_ESCAPER = Escaper.of('\'', Set.of("'"));

/**
* @deprecated since 4.2, use {@link #ANSI_LIKE_ESCAPER} instead.
*/
@Deprecated
public static final Escaper DEFAULT = ANSI_LIKE_ESCAPER;

private final char escapeCharacter;
private final List<String> toReplace;
private final Set<String> toReplace;

private Escaper(char escapeCharacter, List<String> toReplace) {
private Escaper(char escapeCharacter, Set<String> toReplace) {

this.escapeCharacter = escapeCharacter;
this.toReplace = new HashSet<>(toReplace);
}

/**
* Creates a new instance of this class with the given escape character escaping the {@code LIKE} special characters
* {@code _} and {@code %}.
*
* @param escapeCharacter escape character
* @return new instance of {@link Escaper}.
* @throws IllegalArgumentException if the escape character is one of the special characters ('_' and '%')
*/
public static Escaper escapeLikeWith(char escapeCharacter) {

Set<String> toReplace = Set.of("_", "%");
if (toReplace.contains(Character.toString(escapeCharacter))) {
throw new IllegalArgumentException(
String.format("'%s' and cannot be used as escape character as it should be replaced", escapeCharacter));
String.format("'%s' cannot be used as escape character as it should be replaced", escapeCharacter));
}

this.escapeCharacter = escapeCharacter;
this.toReplace = toReplace;
return Escaper.of(escapeCharacter, toReplace);
}

/**
* Creates a new instance of this class with the given escape character.
* Creates a new instance of this class with the given escape character escaping the {@code LIKE} special characters
* {@code _} and {@code %}.
*
* @param escapeCharacter escape character
* @return new instance of {@link Escaper}.
* @throws IllegalArgumentException if the escape character is one of special characters ('_' and '%')
* @throws IllegalArgumentException if the escape character is one of the special characters ('_' and '%')
* @deprecated since 4.2, use {@link #escapeLikeWith(char)} instead.
*/
@Deprecated
public static Escaper of(char escapeCharacter) {
return new Escaper(escapeCharacter, Arrays.asList("_", "%"));
return escapeLikeWith(escapeCharacter);
}

/**
* Creates a new instance of this class with the given escape character and the characters to be escaped. In contrast
* to {@link #of(char)} the {@code escapeCharacter} may itself be part of {@code toReplace}; this is the standard SQL
* way of escaping a single quote inside a string literal by doubling it ({@code '} &rarr; {@code ''}).
*
* @param escapeCharacter escape character.
* @param toReplace characters/char sequences that should be escaped.
* @return new instance of {@link Escaper}.
* @since 4.1
*/
public static Escaper of(char escapeCharacter, Set<String> toReplace) {
return new Escaper(escapeCharacter, toReplace);
}

/**
Expand All @@ -67,7 +106,7 @@ public static Escaper of(char escapeCharacter) {
*/
public Escaper withRewriteFor(String... chars) {

List<String> toReplace = new ArrayList<>(this.toReplace.size() + chars.length);
HashSet<String> toReplace = new HashSet<>(this.toReplace.size() + chars.length);
toReplace.addAll(this.toReplace);
toReplace.addAll(Arrays.asList(chars));

Expand All @@ -84,8 +123,8 @@ public char getEscapeCharacter() {
}

/**
* Escapes all special like characters ({@code _}, {@code %}) using the configured escape character.
* Escape character itself is also escaped.
* Escapes all special like characters ({@code _}, {@code %}) using the configured escape character. Escape character
* itself is also escaped.
*
* @param value value to be escaped
* @return escaped value
Expand All @@ -99,6 +138,12 @@ public char getEscapeCharacter() {
String escapeCharString = String.valueOf(escapeCharacter);
String escapedValue = value.replace(escapeCharString, escapeCharString.repeat(2));
for (String character : toReplace) {

// the escape character was already doubled in the step above; doubling it again would be wrong
if (character.equals(escapeCharString)) {
continue;
}

escapedValue = escapedValue.replace(character, escapeCharacter + character);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;

import org.springframework.data.relational.core.sql.IdentifierProcessing;
import org.springframework.data.relational.core.sql.IdentifierProcessing.LetterCasing;
Expand Down Expand Up @@ -119,6 +121,14 @@ public Position getClausePosition() {
private static final Collection<Object> CONVERTERS = Arrays.asList(TimestampAtUtcToOffsetDateTimeConverter.INSTANCE,
NumberToBooleanConverter.INSTANCE);

/**
* MySQL (and MariaDB) interpret the backslash as an escape character inside string literals by default. Therefore
* both the backslash itself and the single quote have to be escaped with a leading backslash ({@code \} &rarr;
* {@code \\}, {@code '} &rarr; {@code \'}). This differs from the SQL standard of doubling the single quote. Note that
* this is not correct if the server runs with the {@code NO_BACKSLASH_ESCAPES} SQL mode.
*/
private static final Escaper STRING_LITERAL_ESCAPER = Escaper.of('\\', Set.of("'", "\\"));

private final IdentifierProcessing identifierProcessing;

protected MySqlDialect() {
Expand Down Expand Up @@ -168,6 +178,11 @@ public OrderByNullPrecedence orderByNullHandling() {
return OrderByNullPrecedence.NONE;
}

@Override
public Escaper getStringLiteralEscaper() {
return STRING_LITERAL_ESCAPER;
}

@Override
public UpsertRenderContext getUpsertRenderContext() {
return UpsertRenderContexts.MYSQL;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ public UpsertRenderContext getUpsertRenderContext() {
return upsertRenderContext;
}

@Override
public Escaper getStringLiteralEscaper() {
return renderingDialect.getStringLiteralEscaper();
}

@Override
public Dialect getDialect() {
return renderingDialect;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public LockClause lock() {

@Override
public Escaper getLikeEscaper() {
return Escaper.DEFAULT.withRewriteFor("[", "]");
return Escaper.ANSI_LIKE_ESCAPER.withRewriteFor("[", "]");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ private static String renderValue(@Nullable Object value) {
}

if (value instanceof ValueFunction<?>) {
value = ((ValueFunction<?>) value).apply(Escaper.DEFAULT);
value = ((ValueFunction<?>) value).apply(Escaper.ANSI_LIKE_ESCAPER);
}

if (value != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,13 @@ Delegation enterMatched(Expression segment) {
CaseExpressionVisitor visitor = new CaseExpressionVisitor(context);
partRenderer = visitor;
return Delegation.delegateTo(visitor);
} else if (segment instanceof StringLiteral stringLiteral) {

CharSequence content = stringLiteral.getContent();
String escaped = context.getStringLiteralEscaper().escape(content == null ? null : content.toString());
value = "'" + (escaped == null ? "" : escaped) + "'";
} else {
// works for literals and just and possibly more
// works for `just` and possibly more
value = segment.toString();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
*/
package org.springframework.data.relational.core.sql.render;

import java.util.Set;

import org.springframework.data.relational.core.dialect.Escaper;
import org.springframework.data.relational.core.dialect.InsertRenderContext;
import org.springframework.data.relational.core.sql.IdentifierProcessing;

Expand Down Expand Up @@ -60,4 +63,15 @@ public interface RenderContext {
*/
UpsertRenderContext getUpsertRenderContext();

/**
* Returns the {@link Escaper} used for escaping the content of string literals. Defaults to the SQL standard of
* doubling a single quote ({@code '} &rarr; {@code ''}).
*
* @return the {@link Escaper} used for string literal escaping.
* @since 4.1
*/
default Escaper getStringLiteralEscaper() {
return Escaper.of('\'', Set.of("'"));
}

}
Loading
Loading