IGNITE-10353: Spring Add Update/Delete support for Spring Data - Fixes #5532.
Signed-off-by: Ilya Kasnacheev <ilya.kasnach...@gmail.com> Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/e539dfc1 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/e539dfc1 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/e539dfc1 Branch: refs/heads/ignite-601 Commit: e539dfc19b3c3ece41bfec39dff6506b54d4a0d4 Parents: 9f9bb75 Author: Jonathan Camargo <jcamar...@psl.com.co> Authored: Thu Dec 27 13:36:29 2018 +0300 Committer: Ilya Kasnacheev <ilya.kasnach...@gmail.com> Committed: Thu Dec 27 13:36:29 2018 +0300 ---------------------------------------------------------------------- .../repository/query/IgniteQueryGenerator.java | 14 ++- .../repository/query/IgniteRepositoryQuery.java | 17 +-- .../support/IgniteRepositoryFactory.java | 16 ++- .../IgniteSpringDataCrudSelfTest.java | 124 ++++++++++++++++++- .../springdata/misc/PersonRepository.java | 28 +++++ 5 files changed, 180 insertions(+), 19 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/e539dfc1/modules/spring-data-2.0/src/main/java/org/apache/ignite/springdata20/repository/query/IgniteQueryGenerator.java ---------------------------------------------------------------------- diff --git a/modules/spring-data-2.0/src/main/java/org/apache/ignite/springdata20/repository/query/IgniteQueryGenerator.java b/modules/spring-data-2.0/src/main/java/org/apache/ignite/springdata20/repository/query/IgniteQueryGenerator.java index a8f5494..3648991 100644 --- a/modules/spring-data-2.0/src/main/java/org/apache/ignite/springdata20/repository/query/IgniteQueryGenerator.java +++ b/modules/spring-data-2.0/src/main/java/org/apache/ignite/springdata20/repository/query/IgniteQueryGenerator.java @@ -37,17 +37,23 @@ public class IgniteQueryGenerator { @NotNull public static IgniteQuery generateSql(Method mtd, RepositoryMetadata metadata) { PartTree parts = new PartTree(mtd.getName(), metadata.getDomainType()); + boolean isCountOrFieldQuery = parts.isCountProjection(); + StringBuilder sql = new StringBuilder(); - if (parts.isDelete()) - throw new UnsupportedOperationException("DELETE clause is not supported now."); + if (parts.isDelete()) { + sql.append("DELETE "); + + // For the DML queries aside from SELECT *, they should run over SqlFieldQuery + isCountOrFieldQuery = true; + } else { sql.append("SELECT "); if (parts.isDistinct()) throw new UnsupportedOperationException("DISTINCT clause in not supported."); - if (parts.isCountProjection()) + if (isCountOrFieldQuery) sql.append("COUNT(1) "); else sql.append(" * "); @@ -81,7 +87,7 @@ public class IgniteQueryGenerator { sql.append(parts.getMaxResults().intValue()); } - return new IgniteQuery(sql.toString(), parts.isCountProjection(), getOptions(mtd)); + return new IgniteQuery(sql.toString(), isCountOrFieldQuery, getOptions(mtd)); } /** http://git-wip-us.apache.org/repos/asf/ignite/blob/e539dfc1/modules/spring-data-2.0/src/main/java/org/apache/ignite/springdata20/repository/query/IgniteRepositoryQuery.java ---------------------------------------------------------------------- diff --git a/modules/spring-data-2.0/src/main/java/org/apache/ignite/springdata20/repository/query/IgniteRepositoryQuery.java b/modules/spring-data-2.0/src/main/java/org/apache/ignite/springdata20/repository/query/IgniteRepositoryQuery.java index 493c5f2..3bae2cc 100644 --- a/modules/spring-data-2.0/src/main/java/org/apache/ignite/springdata20/repository/query/IgniteRepositoryQuery.java +++ b/modules/spring-data-2.0/src/main/java/org/apache/ignite/springdata20/repository/query/IgniteRepositoryQuery.java @@ -20,6 +20,7 @@ package org.apache.ignite.springdata20.repository.query; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.util.List; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; @@ -196,27 +197,27 @@ public class IgniteRepositoryQuery implements RepositoryQuery { */ @Nullable private Object transformQueryCursor(Object[] prmtrs, QueryCursor qryCursor) { if (this.qry.isFieldQuery()) { - Iterable<ArrayList> qryIter = (Iterable<ArrayList>)qryCursor; + Iterable<List> qryIter = (Iterable<List>)qryCursor; switch (returnStgy) { case LIST_OF_VALUES: - ArrayList list = new ArrayList(); + List list = new ArrayList<>(); - for (ArrayList entry : qryIter) + for (List entry : qryIter) list.add(entry.get(0)); return list; case ONE_VALUE: - Iterator<ArrayList> iter = qryIter.iterator(); + Iterator<List> iter = qryIter.iterator(); if (iter.hasNext()) return iter.next().get(0); return null; case SLICE_OF_VALUES: - ArrayList content = new ArrayList(); + List content = new ArrayList<>(); - for (ArrayList entry : qryIter) + for (List entry : qryIter) content.add(entry.get(0)); return new SliceImpl(content, (Pageable)prmtrs[prmtrs.length - 1], true); @@ -233,7 +234,7 @@ public class IgniteRepositoryQuery implements RepositoryQuery { switch (returnStgy) { case LIST_OF_VALUES: - ArrayList list = new ArrayList(); + List list = new ArrayList<>(); for (CacheEntryImpl entry : qryIter) list.add(entry.getValue()); @@ -254,7 +255,7 @@ public class IgniteRepositoryQuery implements RepositoryQuery { return null; case SLICE_OF_VALUES: - ArrayList content = new ArrayList(); + List content = new ArrayList<>(); for (CacheEntryImpl entry : qryIter) content.add(entry.getValue()); http://git-wip-us.apache.org/repos/asf/ignite/blob/e539dfc1/modules/spring-data-2.0/src/main/java/org/apache/ignite/springdata20/repository/support/IgniteRepositoryFactory.java ---------------------------------------------------------------------- diff --git a/modules/spring-data-2.0/src/main/java/org/apache/ignite/springdata20/repository/support/IgniteRepositoryFactory.java b/modules/spring-data-2.0/src/main/java/org/apache/ignite/springdata20/repository/support/IgniteRepositoryFactory.java index ea51b7d..09c8735 100644 --- a/modules/spring-data-2.0/src/main/java/org/apache/ignite/springdata20/repository/support/IgniteRepositoryFactory.java +++ b/modules/spring-data-2.0/src/main/java/org/apache/ignite/springdata20/repository/support/IgniteRepositoryFactory.java @@ -148,9 +148,21 @@ public class IgniteRepositoryFactory extends RepositoryFactorySupport { /** * @param qry Query string. - * @return {@code true} if query is SQLFieldsQuery. + * @return {@code true} if query is SqlFieldsQuery. */ private boolean isFieldQuery(String qry) { - return qry.matches("^SELECT.*") && !qry.matches("^SELECT\\s+(?:\\w+\\.)?+\\*.*"); + return isStatement(qry) && !qry.matches("^SELECT\\s+(?:\\w+\\.)?+\\*.*"); + } + + /** + * Evaluates if the query starts with a clause.<br> + * <code>SELECT, INSERT, UPDATE, MERGE, DELETE</code> + * + * @param qry Query string. + * @return {@code true} if query is full SQL statement. + */ + private boolean isStatement(String qry) { + return qry.matches("^SELECT.*") || qry.matches("^UPDATE.*") || qry.matches("^DELETE.*") || + qry.matches("^MERGE.*") || qry.matches("^INSERT.*"); } } http://git-wip-us.apache.org/repos/asf/ignite/blob/e539dfc1/modules/spring-data-2.0/src/test/java/org/apache/ignite/springdata/IgniteSpringDataCrudSelfTest.java ---------------------------------------------------------------------- diff --git a/modules/spring-data-2.0/src/test/java/org/apache/ignite/springdata/IgniteSpringDataCrudSelfTest.java b/modules/spring-data-2.0/src/test/java/org/apache/ignite/springdata/IgniteSpringDataCrudSelfTest.java index 5e717bf..6689cdb 100644 --- a/modules/spring-data-2.0/src/test/java/org/apache/ignite/springdata/IgniteSpringDataCrudSelfTest.java +++ b/modules/spring-data-2.0/src/test/java/org/apache/ignite/springdata/IgniteSpringDataCrudSelfTest.java @@ -20,6 +20,7 @@ package org.apache.ignite.springdata; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.List; import java.util.Optional; import java.util.TreeSet; import org.apache.ignite.springdata.misc.ApplicationConfiguration; @@ -76,6 +77,22 @@ public class IgniteSpringDataCrudSelfTest extends GridCommonAbstractTest { super.afterTest(); } + /** + * + */ + private void fillInRepository() { + for (int i = 0; i < CACHE_SIZE - 5; i++) { + repo.save(i, new Person("person" + Integer.toHexString(i), + "lastName" + Integer.toHexString((i + 16) % 256))); + } + + repo.save((int) repo.count(), new Person("uniquePerson", "uniqueLastName")); + repo.save((int) repo.count(), new Person("nonUniquePerson", "nonUniqueLastName")); + repo.save((int) repo.count(), new Person("nonUniquePerson", "nonUniqueLastName")); + repo.save((int) repo.count(), new Person("nonUniquePerson", "nonUniqueLastName")); + repo.save((int) repo.count(), new Person("nonUniquePerson", "nonUniqueLastName")); + } + /** {@inheritDoc} */ @Override protected void afterTestsStopped() throws Exception { ctx.destroy(); @@ -231,12 +248,109 @@ public class IgniteSpringDataCrudSelfTest extends GridCommonAbstractTest { } /** - * + * Delete existing record */ - private void fillInRepository() { - for (int i = 0; i < CACHE_SIZE; i++) { - repo.save(i, new Person("person" + Integer.toHexString(i), - "lastName" + Integer.toHexString((i + 16) % 256))); + @Test + public void testDeleteByFirstName() { + assertEquals(repo.countByFirstNameLike("uniquePerson"), 1); + + long cnt = repo.deleteByFirstName("uniquePerson"); + + assertEquals(1, cnt); + } + + /** + * Delete NON existing record + */ + @Test + public void testDeleteExpression() { + long cnt = repo.deleteByFirstName("880"); + + assertEquals(0, cnt); + } + + /** + * Delete Multiple records due to where + */ + @Test + public void testDeleteExpressionMultiple() { + long count = repo.countByFirstName("nonUniquePerson"); + long cnt = repo.deleteByFirstName("nonUniquePerson"); + + assertEquals(cnt, count); + } + + /** + * Remove should do the same than Delete + */ + @Test + public void testRemoveExpression() { + repo.removeByFirstName("person3f"); + + long count = repo.count(); + assertEquals(CACHE_SIZE - 1, count); + } + + /** + * Delete unique record + */ + @Test + public void testDeleteQuery() { + repo.deleteBySecondNameQuery("uniqueLastName"); + + long countAfter = repo.count(); + assertEquals(CACHE_SIZE - 1, countAfter); + } + + /** + * Try to delete with a wrong @Query + */ + @Test + public void testWrongDeleteQuery() { + long countBefore = repo.countByFirstNameLike("person3f"); + + try { + repo.deleteWrongByFirstNameQuery("person3f"); + } + catch (Exception e) { + //expected } + + long countAfter = repo.countByFirstNameLike("person3f"); + assertEquals(countBefore, countAfter); + } + + /** + * Update with a @Query a record + */ + @Test + public void testUpdateQuery() { + final String newSecondName = "updatedUniqueSecondName"; + int cnt = repo.setFixedSecondNameFor(newSecondName, "uniquePerson"); + + assertEquals(1, cnt); + + List<Person> person = repo.findByFirstName("uniquePerson"); + assertEquals(person.get(0).getSecondName(), "updatedUniqueSecondName"); + } + + /** + * Update with a wrong @Query + */ + @Test + public void testWrongUpdateQuery() { + final String newSecondName = "updatedUniqueSecondName"; + int rowsUpdated = 0; + try { + rowsUpdated = repo.setWrongFixedSecondName(newSecondName, "uniquePerson"); + } + catch (Exception e) { + //expected + } + + assertEquals(0, rowsUpdated); + + List<Person> person = repo.findByFirstName("uniquePerson"); + assertEquals(person.get(0).getSecondName(), "uniqueLastName"); } } http://git-wip-us.apache.org/repos/asf/ignite/blob/e539dfc1/modules/spring-data-2.0/src/test/java/org/apache/ignite/springdata/misc/PersonRepository.java ---------------------------------------------------------------------- diff --git a/modules/spring-data-2.0/src/test/java/org/apache/ignite/springdata/misc/PersonRepository.java b/modules/spring-data-2.0/src/test/java/org/apache/ignite/springdata/misc/PersonRepository.java index 8977bbc..90d8123 100644 --- a/modules/spring-data-2.0/src/test/java/org/apache/ignite/springdata/misc/PersonRepository.java +++ b/modules/spring-data-2.0/src/test/java/org/apache/ignite/springdata/misc/PersonRepository.java @@ -48,6 +48,9 @@ public interface PersonRepository extends IgniteRepository<Person, Integer> { public Iterable<Person> findFirst10ByFirstNameLike(String val); /** */ + public int countByFirstName(String val); + + /** */ public int countByFirstNameLike(String val); /** */ @@ -88,4 +91,29 @@ public interface PersonRepository extends IgniteRepository<Person, Integer> { /** */ @Query("SELECT count(1) FROM (SELECT DISTINCT secondName FROM Person WHERE firstName REGEXP ?)") public int countQuery(String val); + + /** Top 3 query */ + public List<Person> findTop3ByFirstName(String val); + + /** Delete query */ + public long deleteByFirstName(String firstName); + + /** Remove Query */ + public List<Person> removeByFirstName(String firstName); + + /** Delete using @Query */ + @Query("DELETE FROM Person WHERE secondName = ?") + public void deleteBySecondNameQuery(String secondName); + + /** Delete using @Query but with errors on the query */ + @Query("DELETE FROM Person WHERE firstName = ? AND ERRORS = 'ERRORS'") + public void deleteWrongByFirstNameQuery(String firstName); + + /** Update using @Query */ + @Query("UPDATE Person SET secondName = ? WHERE firstName = ?") + public int setFixedSecondNameFor(String secondName, String firstName); + + /** Update using @Query but with errors on the query */ + @Query("UPDATE Person SET secondName = ? WHERE firstName = ? AND ERRORS = 'ERRORS'") + public int setWrongFixedSecondName(String secondName, String firstName); }