From 9109bada641866fd50b731c6714ad79f8bfa1c5a Mon Sep 17 00:00:00 2001 From: Matthew Adams Date: Thu, 3 Apr 2014 17:08:13 -0500 Subject: [PATCH] DATACASS-121 - Automatically quote string-like parameter values in @Query methods --- .../cassandra/core/cql/CqlStringUtils.java | 14 +++++++++ .../query/CassandraQueryMethod.java | 29 +++++++++++++++++++ .../query/StringBasedCassandraQuery.java | 16 +++++++++- .../declared/QueryIntegrationTests.java | 20 +++++++++++++ ...ngBasedCassandraQueryIntegrationTests.java | 4 +-- .../PersonRepositoryWithQueryAnnotations.java | 18 ++++++------ ...ersonRepositoryWithNamedQueries.properties | 18 ++++++------ .../cassandra-named-queries.properties | 2 +- 8 files changed, 99 insertions(+), 22 deletions(-) diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/cql/CqlStringUtils.java b/spring-cql/src/main/java/org/springframework/cassandra/core/cql/CqlStringUtils.java index 59670fce3..81aaaedee 100644 --- a/spring-cql/src/main/java/org/springframework/cassandra/core/cql/CqlStringUtils.java +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/cql/CqlStringUtils.java @@ -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)); + } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraQueryMethod.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraQueryMethod.java index fcba6a282..3fca7049f 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraQueryMethod.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraQueryMethod.java @@ -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> STRING_LIKE_PARAMETER_TYPES = Collections.unmodifiableList(Arrays + .asList(new Class[] { CharSequence.class, char.class, Character.class, char[].class })); + + public static final List> 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 stringLikeParameterIndexes = new HashSet(); + protected Set dateParameterIndexes = new HashSet(); public CassandraQueryMethod(Method method, RepositoryMetadata metadata, CassandraMappingContext mappingContext) { @@ -73,10 +81,23 @@ public class CassandraQueryMethod extends QueryMethod { } Set> offendingTypes = new HashSet>(); + + 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); + } } \ No newline at end of file diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQuery.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQuery.java index 133bf72bb..5adfff90e 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQuery.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQuery.java @@ -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; diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/querymethods/declared/QueryIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/querymethods/declared/QueryIntegrationTests.java index 08c932b16..907cf0feb 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/querymethods/declared/QueryIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/querymethods/declared/QueryIntegrationTests.java @@ -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 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()); + } + } } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/querymethods/declared/StringBasedCassandraQueryIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/querymethods/declared/StringBasedCassandraQueryIntegrationTests.java index 911214815..7aa31f4d7 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/querymethods/declared/StringBasedCassandraQueryIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/querymethods/declared/StringBasedCassandraQueryIntegrationTests.java @@ -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); } } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/querymethods/declared/anno/PersonRepositoryWithQueryAnnotations.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/querymethods/declared/anno/PersonRepositoryWithQueryAnnotations.java index 71f881843..273e533c6 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/querymethods/declared/anno/PersonRepositoryWithQueryAnnotations.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/querymethods/declared/anno/PersonRepositoryWithQueryAnnotations.java @@ -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 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> 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); } diff --git a/spring-data-cassandra/src/test/resources/META-INF/PersonRepositoryWithNamedQueries.properties b/spring-data-cassandra/src/test/resources/META-INF/PersonRepositoryWithNamedQueries.properties index 6f9ad4229..cc6191e89 100644 --- a/spring-data-cassandra/src/test/resources/META-INF/PersonRepositoryWithNamedQueries.properties +++ b/spring-data-cassandra/src/test/resources/META-INF/PersonRepositoryWithNamedQueries.properties @@ -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 diff --git a/spring-data-cassandra/src/test/resources/META-INF/cassandra-named-queries.properties b/spring-data-cassandra/src/test/resources/META-INF/cassandra-named-queries.properties index 76c051295..c596c8305 100644 --- a/spring-data-cassandra/src/test/resources/META-INF/cassandra-named-queries.properties +++ b/spring-data-cassandra/src/test/resources/META-INF/cassandra-named-queries.properties @@ -1 +1 @@ -User.findByNamedQuery=SELECT firstname FROM users WHERE username='?0' +User.findByNamedQuery=SELECT firstname FROM users WHERE username=?0