Skip to content

Commit 6896ddc

Browse files
authored
repsert based on AQL UPSERT (#193)
* insertOrReplace based on AQL UPSERT * test repsert on sharded collection * repsert method signature update * repsert method signature update * reverted modifications to upsert methods
1 parent df54509 commit 6896ddc

File tree

4 files changed

+182
-51
lines changed

4 files changed

+182
-51
lines changed

src/main/java/com/arangodb/springframework/core/ArangoOperations.java

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,19 @@
2020

2121
package com.arangodb.springframework.core;
2222

23-
import java.util.Map;
24-
import java.util.Optional;
25-
26-
import org.springframework.dao.DataAccessException;
27-
2823
import com.arangodb.ArangoCursor;
2924
import com.arangodb.ArangoDB;
3025
import com.arangodb.entity.ArangoDBVersion;
3126
import com.arangodb.entity.DocumentEntity;
3227
import com.arangodb.entity.MultiDocumentEntity;
3328
import com.arangodb.entity.UserEntity;
34-
import com.arangodb.model.AqlQueryOptions;
35-
import com.arangodb.model.CollectionCreateOptions;
36-
import com.arangodb.model.DocumentCreateOptions;
37-
import com.arangodb.model.DocumentDeleteOptions;
38-
import com.arangodb.model.DocumentReadOptions;
39-
import com.arangodb.model.DocumentReplaceOptions;
40-
import com.arangodb.model.DocumentUpdateOptions;
29+
import com.arangodb.model.*;
4130
import com.arangodb.springframework.core.convert.ArangoConverter;
4231
import com.arangodb.springframework.core.convert.resolver.ResolverFactory;
32+
import org.springframework.dao.DataAccessException;
33+
34+
import java.util.Map;
35+
import java.util.Optional;
4336

4437
/**
4538
* Interface that specifies a basic set of ArangoDB operations.
@@ -501,14 +494,14 @@ public enum UpsertStrategy {
501494
* Creates new documents from the given documents, unless there already exists. In that case it replaces the
502495
* documents.
503496
*
504-
* @param value
497+
* @param values
505498
* A List of documents
506499
* @param entityClass
507500
* The entity class which represents the collection
508501
* @throws DataAccessException
509502
* @since ArangoDB 3.4
510503
*/
511-
<T> void repsert(Iterable<T> value, Class<T> entityClass) throws DataAccessException;
504+
<T> void repsert(Iterable<? extends T> values, Class<T> entityClass) throws DataAccessException;
512505

513506
/**
514507
* Checks whether the document exists by reading a single document head
@@ -582,7 +575,7 @@ public enum UpsertStrategy {
582575
Iterable<UserEntity> getUsers() throws DataAccessException;
583576

584577
ArangoConverter getConverter();
585-
578+
586579
ResolverFactory getResolverFactory();
587580

588581
}

src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,17 @@
100100
*/
101101
public class ArangoTemplate implements ArangoOperations, CollectionCallback, ApplicationContextAware {
102102

103+
private static final String REPSERT_QUERY_BODY =
104+
"UPSERT { _key: doc._key } " +
105+
"INSERT doc._key == null ? UNSET(doc, \"_key\") : doc " +
106+
"REPLACE doc " +
107+
"IN @@col " +
108+
"OPTIONS { ignoreRevs: false } " +
109+
"RETURN NEW";
110+
111+
private static final String REPSERT_QUERY = "LET doc = @doc " + REPSERT_QUERY_BODY;
112+
private static final String REPSERT_MANY_QUERY = "FOR doc IN @docs " + REPSERT_QUERY_BODY;
113+
103114
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
104115

105116
private volatile ArangoDBVersion version;
@@ -659,12 +670,90 @@ public <T> void upsert(final Iterable<T> value, final UpsertStrategy strategy) t
659670

660671
@Override
661672
public <T> void repsert(final T value) throws DataAccessException {
662-
insert(value, new DocumentCreateOptions().overwrite(true));
673+
@SuppressWarnings("unchecked") final Class<T> clazz = (Class<T>) value.getClass();
674+
final String collectionName = _collection(clazz).name();
675+
676+
potentiallyEmitEvent(new BeforeSaveEvent<>(value));
677+
678+
final T result;
679+
try {
680+
result = query(
681+
REPSERT_QUERY,
682+
new MapBuilder()
683+
.put("@col", collectionName)
684+
.put("doc", value)
685+
.get(),
686+
clazz
687+
).first();
688+
} catch (final ArangoDBException e) {
689+
throw exceptionTranslator.translateExceptionIfPossible(e);
690+
}
691+
692+
updateDBFieldsFromObject(value, result);
693+
potentiallyEmitEvent(new AfterSaveEvent<>(result));
663694
}
664695

665696
@Override
666-
public <T> void repsert(final Iterable<T> value, final Class<T> entityClass) throws DataAccessException {
667-
insert(value, entityClass, new DocumentCreateOptions().overwrite(true));
697+
public <T> void repsert(final Iterable<? extends T> values, final Class<T> entityClass) throws DataAccessException {
698+
if (!values.iterator().hasNext()) {
699+
return;
700+
}
701+
702+
final String collectionName = _collection(entityClass).name();
703+
potentiallyEmitBeforeSaveEvent(values);
704+
705+
final Iterable<? extends T> result;
706+
try {
707+
result = query(
708+
REPSERT_MANY_QUERY,
709+
new MapBuilder()
710+
.put("@col", collectionName)
711+
.put("docs", values)
712+
.get(),
713+
entityClass
714+
).asListRemaining();
715+
} catch (final ArangoDBException e) {
716+
throw translateExceptionIfPossible(e);
717+
}
718+
719+
updateDBFieldsFromObjects(values, result);
720+
result.forEach(it -> potentiallyEmitEvent(new AfterSaveEvent<>(it)));
721+
}
722+
723+
private void updateDBFieldsFromObjects(final Iterable<?> values, final Iterable<?> res) {
724+
final Iterator<?> valueIterator = values.iterator();
725+
final Iterator<?> resIterator = res.iterator();
726+
while (valueIterator.hasNext() && resIterator.hasNext()) {
727+
updateDBFieldsFromObject(valueIterator.next(), resIterator.next());
728+
}
729+
}
730+
731+
private void updateDBFieldsFromObject(final Object toModify, final Object toRead) {
732+
final ArangoPersistentEntity<?> entityToRead = converter.getMappingContext().getPersistentEntity(toRead.getClass());
733+
final PersistentPropertyAccessor<?> accessorToRead = entityToRead.getPropertyAccessor(toRead);
734+
final ArangoPersistentProperty idPropertyToRead = entityToRead.getIdProperty();
735+
final Optional<ArangoPersistentProperty> arangoIdPropertyToReadOptional = entityToRead.getArangoIdProperty();
736+
final Optional<ArangoPersistentProperty> revPropertyToReadOptional = entityToRead.getRevProperty();
737+
738+
final ArangoPersistentEntity<?> entityToModify = converter.getMappingContext().getPersistentEntity(toModify.getClass());
739+
final PersistentPropertyAccessor<?> accessorToWrite = entityToModify.getPropertyAccessor(toModify);
740+
final ArangoPersistentProperty idPropertyToWrite = entityToModify.getIdProperty();
741+
742+
if (idPropertyToWrite != null && !idPropertyToWrite.isImmutable()) {
743+
accessorToWrite.setProperty(idPropertyToWrite, accessorToRead.getProperty(idPropertyToRead));
744+
}
745+
746+
if (arangoIdPropertyToReadOptional.isPresent()) {
747+
ArangoPersistentProperty arangoIdPropertyToRead = arangoIdPropertyToReadOptional.get();
748+
entityToModify.getArangoIdProperty().filter(arangoId -> !arangoId.isImmutable())
749+
.ifPresent(arangoId -> accessorToWrite.setProperty(arangoId, accessorToRead.getProperty(arangoIdPropertyToRead)));
750+
}
751+
752+
if (revPropertyToReadOptional.isPresent()) {
753+
ArangoPersistentProperty revPropertyToRead = revPropertyToReadOptional.get();
754+
entityToModify.getRevProperty().filter(rev -> !rev.isImmutable())
755+
.ifPresent(rev -> accessorToWrite.setProperty(rev, accessorToRead.getProperty(revPropertyToRead)));
756+
}
668757
}
669758

670759
private <T> void updateDBFields(final Iterable<T> values, final MultiDocumentEntity<? extends DocumentEntity> res) {

src/main/java/com/arangodb/springframework/repository/SimpleArangoRepository.java

Lines changed: 11 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,18 @@
2020

2121
package com.arangodb.springframework.repository;
2222

23-
import java.util.HashMap;
24-
import java.util.Iterator;
25-
import java.util.List;
26-
import java.util.Map;
27-
import java.util.Optional;
28-
import java.util.stream.StreamSupport;
29-
30-
import org.slf4j.Logger;
31-
import org.slf4j.LoggerFactory;
32-
import org.springframework.data.domain.Example;
33-
import org.springframework.data.domain.Page;
34-
import org.springframework.data.domain.PageImpl;
35-
import org.springframework.data.domain.Pageable;
36-
import org.springframework.data.domain.Sort;
37-
import org.springframework.lang.Nullable;
38-
import org.springframework.stereotype.Repository;
39-
4023
import com.arangodb.ArangoCursor;
4124
import com.arangodb.model.AqlQueryOptions;
4225
import com.arangodb.springframework.core.ArangoOperations;
43-
import com.arangodb.springframework.core.ArangoOperations.UpsertStrategy;
4426
import com.arangodb.springframework.core.mapping.ArangoMappingContext;
4527
import com.arangodb.springframework.core.util.AqlUtils;
28+
import org.slf4j.Logger;
29+
import org.slf4j.LoggerFactory;
30+
import org.springframework.data.domain.*;
31+
import org.springframework.lang.Nullable;
32+
import org.springframework.stereotype.Repository;
33+
34+
import java.util.*;
4635

4736
/**
4837
* The implementation of all CRUD, paging and sorting functionality in
@@ -75,38 +64,27 @@ public SimpleArangoRepository(final ArangoOperations arangoOperations, final Cla
7564
}
7665

7766
/**
78-
* Saves the passed entity to the database using upsert from the template
67+
* Saves the passed entity to the database using repsert from the template
7968
*
8069
* @param entity the entity to be saved to the database
8170
* @return the updated entity with any id/key/rev saved
8271
*/
83-
@SuppressWarnings("deprecation")
8472
@Override
8573
public <S extends T> S save(final S entity) {
86-
if (arangoOperations.getVersion().getVersion().compareTo("3.4.0") < 0) {
87-
arangoOperations.upsert(entity, UpsertStrategy.REPLACE);
88-
} else {
89-
arangoOperations.repsert(entity);
90-
}
74+
arangoOperations.repsert(entity);
9175
return entity;
9276
}
9377

9478
/**
95-
* Saves the given iterable of entities to the database
79+
* Saves the given iterable of entities to the database using repsert from the template
9680
*
9781
* @param entities the iterable of entities to be saved to the database
9882
* @return the iterable of updated entities with any id/key/rev saved in each
9983
* entity
10084
*/
101-
@SuppressWarnings("deprecation")
10285
@Override
10386
public <S extends T> Iterable<S> saveAll(final Iterable<S> entities) {
104-
if (arangoOperations.getVersion().getVersion().compareTo("3.4.0") < 0) {
105-
arangoOperations.upsert(entities, UpsertStrategy.UPDATE);
106-
} else {
107-
final S first = StreamSupport.stream(entities.spliterator(), false).findFirst().get();
108-
arangoOperations.repsert(entities, (Class<S>) first.getClass());
109-
}
87+
arangoOperations.repsert(entities, domainClass);
11088
return entities;
11189
}
11290

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* DISCLAIMER
3+
*
4+
* Copyright 2016 ArangoDB GmbH, Cologne, Germany
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*
18+
* Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
*/
20+
21+
package com.arangodb.springframework.repository;
22+
23+
import com.arangodb.springframework.AbstractArangoTest;
24+
import com.arangodb.springframework.annotation.Document;
25+
import org.junit.Test;
26+
import org.springframework.beans.factory.annotation.Autowired;
27+
import org.springframework.data.annotation.Id;
28+
29+
import static org.assertj.core.api.Assertions.assertThat;
30+
31+
/**
32+
* @author Michele Rastelli
33+
*/
34+
public class ShardedCollectionRepositoryTest extends AbstractArangoTest {
35+
36+
@Autowired
37+
ShardedRepository shardedRepository;
38+
39+
@Test
40+
public void save() {
41+
ShardedUser d1 = shardedRepository.save(new ShardedUser(null, "name1", "country1"));
42+
d1.name = "name2";
43+
ShardedUser d2 = shardedRepository.save(d1);
44+
assertThat(d2.key).isEqualTo(d1.key);
45+
assertThat(d2.country).isEqualTo("country1");
46+
assertThat(d2.name).isEqualTo("name2");
47+
48+
ShardedUser d3 = shardedRepository.save(new ShardedUser(d1.key, "name3", "country1"));
49+
assertThat(d3.key).isEqualTo(d1.key);
50+
assertThat(d3.country).isEqualTo("country1");
51+
assertThat(d3.name).isEqualTo("name3");
52+
}
53+
}
54+
55+
@Document(shardKeys = "country", numberOfShards = 10)
56+
class ShardedUser {
57+
@Id
58+
String key;
59+
String name;
60+
String country;
61+
62+
public ShardedUser(String key, String name, String country) {
63+
this.key = key;
64+
this.name = name;
65+
this.country = country;
66+
}
67+
68+
}
69+
70+
interface ShardedRepository extends ArangoRepository<ShardedUser, String> {
71+
}

0 commit comments

Comments
 (0)