IGNITE-10353: Spring Add Update/Delete support for Spring Data - Fixes #5532.
authorJonathan Camargo <jcamargos@psl.com.co>
Thu, 27 Dec 2018 10:36:29 +0000 (13:36 +0300)
committerIlya Kasnacheev <ilya.kasnacheev@gmail.com>
Thu, 27 Dec 2018 10:36:29 +0000 (13:36 +0300)
Signed-off-by: Ilya Kasnacheev <ilya.kasnacheev@gmail.com>
modules/spring-data-2.0/src/main/java/org/apache/ignite/springdata20/repository/query/IgniteQueryGenerator.java
modules/spring-data-2.0/src/main/java/org/apache/ignite/springdata20/repository/query/IgniteRepositoryQuery.java
modules/spring-data-2.0/src/main/java/org/apache/ignite/springdata20/repository/support/IgniteRepositoryFactory.java
modules/spring-data-2.0/src/test/java/org/apache/ignite/springdata/IgniteSpringDataCrudSelfTest.java
modules/spring-data-2.0/src/test/java/org/apache/ignite/springdata/misc/PersonRepository.java

index a8f5494..3648991 100644 (file)
@@ -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));
     }
 
     /**
index 493c5f2..3bae2cc 100644 (file)
@@ -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());
index ea51b7d..09c8735 100644 (file)
@@ -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.*");
     }
 }
index 5e717bf..6689cdb 100644 (file)
@@ -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");
     }
 }
index 8977bbc..90d8123 100644 (file)
@@ -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);
 }