DATACASS-121 - Automatically quote string-like parameter values in @Query methods
This commit is contained in:
@@ -15,6 +15,10 @@
|
||||
*/
|
||||
package org.springframework.cassandra.core.cql;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import com.datastax.driver.core.DataType;
|
||||
|
||||
public class CqlStringUtils {
|
||||
@@ -26,6 +30,8 @@ public class CqlStringUtils {
|
||||
protected static final String EMPTY_STRING = "";
|
||||
protected static final String TYPE_PARAMETER_PREFIX = "<";
|
||||
protected static final String TYPE_PARAMETER_SUFFIX = ">";
|
||||
public static final String ISO8601_DATE_FORMAT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSX";
|
||||
public static final DateFormat ISO8601_DATE_FORMAT = new SimpleDateFormat(ISO8601_DATE_FORMAT_PATTERN);
|
||||
|
||||
public static StringBuilder noNull(StringBuilder sb) {
|
||||
return sb == null ? new StringBuilder() : sb;
|
||||
@@ -128,4 +134,12 @@ public class CqlStringUtils {
|
||||
}
|
||||
return s.substring(1, s.length() - 1);
|
||||
}
|
||||
|
||||
public static String date(Date date) {
|
||||
return ISO8601_DATE_FORMAT.format(date);
|
||||
}
|
||||
|
||||
public static String date(long time) {
|
||||
return ISO8601_DATE_FORMAT.format(new Date(time));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,12 @@ public class CassandraQueryMethod extends QueryMethod {
|
||||
Long.class, boolean.class, Boolean.class, BigDecimal.class, BigInteger.class, double.class, Double.class,
|
||||
float.class, Float.class, InetAddress.class, Date.class, UUID.class, }));
|
||||
|
||||
public static final List<Class<?>> STRING_LIKE_PARAMETER_TYPES = Collections.unmodifiableList(Arrays
|
||||
.asList(new Class<?>[] { CharSequence.class, char.class, Character.class, char[].class }));
|
||||
|
||||
public static final List<Class<?>> DATE_PARAMETER_TYPES = Collections.unmodifiableList(Arrays
|
||||
.asList(new Class<?>[] { Date.class }));
|
||||
|
||||
public static boolean isMapOfCharSequenceToObject(TypeInformation<?> type) {
|
||||
|
||||
if (!type.isMap()) {
|
||||
@@ -52,6 +58,8 @@ public class CassandraQueryMethod extends QueryMethod {
|
||||
protected Query query;
|
||||
protected String queryString;
|
||||
protected boolean queryCached = false;
|
||||
protected Set<Integer> stringLikeParameterIndexes = new HashSet<Integer>();
|
||||
protected Set<Integer> dateParameterIndexes = new HashSet<Integer>();
|
||||
|
||||
public CassandraQueryMethod(Method method, RepositoryMetadata metadata, CassandraMappingContext mappingContext) {
|
||||
|
||||
@@ -73,10 +81,23 @@ public class CassandraQueryMethod extends QueryMethod {
|
||||
}
|
||||
|
||||
Set<Class<?>> offendingTypes = new HashSet<Class<?>>();
|
||||
|
||||
int i = 0;
|
||||
for (Class<?> type : method.getParameterTypes()) {
|
||||
if (!ALLOWED_PARAMETER_TYPES.contains(type)) {
|
||||
offendingTypes.add(type);
|
||||
}
|
||||
for (Class<?> quotedType : STRING_LIKE_PARAMETER_TYPES) {
|
||||
if (quotedType.isAssignableFrom(type)) {
|
||||
stringLikeParameterIndexes.add(i);
|
||||
}
|
||||
}
|
||||
for (Class<?> quotedType : DATE_PARAMETER_TYPES) {
|
||||
if (quotedType.isAssignableFrom(type)) {
|
||||
dateParameterIndexes.add(i);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (offendingTypes.size() > 0) {
|
||||
@@ -153,4 +174,12 @@ public class CassandraQueryMethod extends QueryMethod {
|
||||
|
||||
return isMapOfCharSequenceToObject(type.getComponentType());
|
||||
}
|
||||
|
||||
public boolean isStringLikeParameter(int parameterIndex) {
|
||||
return stringLikeParameterIndexes.contains(parameterIndex);
|
||||
}
|
||||
|
||||
public boolean isDateParameter(int parameterIndex) {
|
||||
return dateParameterIndexes.contains(parameterIndex);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
package org.springframework.data.cassandra.repository.query;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.cassandra.core.cql.CqlStringUtils;
|
||||
import org.springframework.data.cassandra.core.CassandraOperations;
|
||||
|
||||
public class StringBasedCassandraQuery extends AbstractCassandraQuery {
|
||||
@@ -38,7 +40,19 @@ public class StringBasedCassandraQuery extends AbstractCassandraQuery {
|
||||
while (matcher.find()) {
|
||||
String group = matcher.group();
|
||||
int index = Integer.parseInt(matcher.group(1));
|
||||
result = result.replace(group, getParameterWithIndex(accessor, index).toString());
|
||||
Object value = getParameterWithIndex(accessor, index);
|
||||
String stringValue = null;
|
||||
CassandraQueryMethod queryMethod = getQueryMethod();
|
||||
|
||||
if (queryMethod.isStringLikeParameter(index)) {
|
||||
stringValue = "'" + CqlStringUtils.escapeSingle(value) + "'";
|
||||
} else if (queryMethod.isDateParameter(index)) {
|
||||
stringValue = "'" + CqlStringUtils.date((Date) value) + "'";
|
||||
} else {
|
||||
stringValue = value.toString();
|
||||
}
|
||||
|
||||
result = result.replace(group, stringValue);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -248,4 +248,24 @@ public abstract class QueryIntegrationTests extends AbstractSpringDataEmbeddedCa
|
||||
assertEquals(found.getFirstname(), saved.getFirstname());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEscapeSingleQuoteInQueryParameterValue() {
|
||||
|
||||
Person saved = new Person();
|
||||
saved.setFirstname("Bri'an" + uuid());
|
||||
String lastname = "O'Brian" + uuid();
|
||||
saved.setLastname(lastname);
|
||||
|
||||
saved = r.save(saved);
|
||||
|
||||
List<Person> results = r.findFolksWithLastnameAsList(lastname);
|
||||
|
||||
assertNotNull(results);
|
||||
assertTrue(results.size() == 1);
|
||||
for (Person person : results) {
|
||||
assertNotNull(person);
|
||||
assertEquals(saved.getLastname(), person.getLastname());
|
||||
assertEquals(saved.getFirstname(), person.getFirstname());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,10 +100,10 @@ public class StringBasedCassandraQueryIntegrationTests {
|
||||
|
||||
private interface SampleRepository {
|
||||
|
||||
@Query("SELECT * FROM person WHERE lastname='?0';")
|
||||
@Query("SELECT * FROM person WHERE lastname=?0;")
|
||||
Person findByLastname(String lastname);
|
||||
|
||||
@Query("SELECT * FROM person WHERE lastname='?0' AND firstname='?1';")
|
||||
@Query("SELECT * FROM person WHERE lastname=?0 AND firstname=?1;")
|
||||
Person findByLastnameAndFirstname(String lastname, String firstname);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,38 +17,38 @@ import com.datastax.driver.core.ResultSet;
|
||||
public interface PersonRepositoryWithQueryAnnotations extends PersonRepository {
|
||||
|
||||
@Override
|
||||
@Query("select * from person where lastname = '?0'")
|
||||
@Query("select * from person where lastname = ?0")
|
||||
List<Person> findFolksWithLastnameAsList(String lastname);
|
||||
|
||||
@Override
|
||||
@Query("select * from person where lastname = '?0'")
|
||||
@Query("select * from person where lastname = ?0")
|
||||
ResultSet findFolksWithLastnameAsResultSet(String last);
|
||||
|
||||
@Override
|
||||
@Query("select * from person where lastname = '?0'")
|
||||
@Query("select * from person where lastname = ?0")
|
||||
Person[] findFolksWithLastnameAsArray(String lastname);
|
||||
|
||||
@Override
|
||||
@Query("select * from person where lastname = '?0' and firstname = '?1'")
|
||||
@Query("select * from person where lastname = ?0 and firstname = ?1")
|
||||
Person findSingle(String last, String first);
|
||||
|
||||
@Override
|
||||
@Query("select * from person where lastname = '?0'")
|
||||
@Query("select * from person where lastname = ?0")
|
||||
List<Map<String, Object>> findFolksWithLastnameAsListOfMapOfStringToObject(String last);
|
||||
|
||||
@Override
|
||||
@Query("select nickname from person where lastname = '?0' and firstname = '?1'")
|
||||
@Query("select nickname from person where lastname = ?0 and firstname = ?1")
|
||||
String findSingleNickname(String last, String first);
|
||||
|
||||
@Override
|
||||
@Query("select birthdate from person where lastname = '?0' and firstname = '?1'")
|
||||
@Query("select birthdate from person where lastname = ?0 and firstname = ?1")
|
||||
Date findSingleBirthdate(String last, String first);
|
||||
|
||||
@Override
|
||||
@Query("select cool from person where lastname = '?0' and firstname = '?1'")
|
||||
@Query("select cool from person where lastname = ?0 and firstname = ?1")
|
||||
boolean findSingleCool(String last, String first);
|
||||
|
||||
@Override
|
||||
@Query("select numberofchildren from person where lastname = '?0' and firstname = '?1'")
|
||||
@Query("select numberofchildren from person where lastname = ?0 and firstname = ?1")
|
||||
int findSingleNumberOfChildren(String last, String first);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
Person.findFolksWithLastnameAsList=select * from person where lastname = '?0'
|
||||
Person.findFolksWithLastnameAsResultSet=select * from person where lastname = '?0'
|
||||
Person.findFolksWithLastnameAsArray=select * from person where lastname = '?0'
|
||||
Person.findSingle=select * from person where lastname = '?0' and firstname = '?1'
|
||||
Person.findFolksWithLastnameAsListOfMapOfStringToObject=select * from person where lastname = '?0'
|
||||
Person.findSingleNickname=select nickname from person where lastname = '?0' and firstname = '?1'
|
||||
Person.findSingleBirthdate=select birthdate from person where lastname = '?0' and firstname = '?1'
|
||||
Person.findSingleCool=select cool from person where lastname = '?0' and firstname = '?1'
|
||||
Person.findSingleNumberOfChildren=select numberofchildren from person where lastname = '?0' and firstname = '?1'
|
||||
Person.findFolksWithLastnameAsList=select * from person where lastname = ?0
|
||||
Person.findFolksWithLastnameAsResultSet=select * from person where lastname = ?0
|
||||
Person.findFolksWithLastnameAsArray=select * from person where lastname = ?0
|
||||
Person.findSingle=select * from person where lastname = ?0 and firstname = ?1
|
||||
Person.findFolksWithLastnameAsListOfMapOfStringToObject=select * from person where lastname = ?0
|
||||
Person.findSingleNickname=select nickname from person where lastname = ?0 and firstname = ?1
|
||||
Person.findSingleBirthdate=select birthdate from person where lastname = ?0 and firstname = ?1
|
||||
Person.findSingleCool=select cool from person where lastname = ?0 and firstname = ?1
|
||||
Person.findSingleNumberOfChildren=select numberofchildren from person where lastname = ?0 and firstname = ?1
|
||||
|
||||
@@ -1 +1 @@
|
||||
User.findByNamedQuery=SELECT firstname FROM users WHERE username='?0'
|
||||
User.findByNamedQuery=SELECT firstname FROM users WHERE username=?0
|
||||
|
||||
Reference in New Issue
Block a user