Skip to content

Commit

Permalink
[Blazebit#709] Test and fix for Updatable entity view containing deep…
Browse files Browse the repository at this point in the history
…ly nested collection that is empty wrongly removes tuples from result
  • Loading branch information
beikov committed Dec 19, 2018
1 parent 16b1c7c commit 55341da
Show file tree
Hide file tree
Showing 9 changed files with 354 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ public TupleId(int[] idPositions, Object[] tuple) {
}
}

public boolean isEmpty() {
for (int i = 0; i < id.length; i++) {
if (id[i] != null) {
return false;
}
}
return true;
}

@Override
public int hashCode() {
int hash = 7;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,33 +62,36 @@ public List<Object[]> transform(List<Object[]> tuples) {
while (tupleListIter.hasNext()) {
Object[] tuple = tupleListIter.next();
TupleId id = new TupleId(parentIdPositions, tuple);
TupleIndexValue tupleIndexValue = tupleIndex.get(id);
// Skip constructing the collection and removing tuples when the parent is empty i.e. null
if (!id.isEmpty()) {
TupleIndexValue tupleIndexValue = tupleIndex.get(id);

// At startIndex we have the index/key of the list/map
// At valueStartIndex is the actual element that should be put into the collection
if (tupleIndexValue == null) {
Object collection = createCollection();
tupleIndexValue = new TupleIndexValue(collection, tuple, startIndex, valueOffset + 1);
Object key = tuple[startIndex];
add(collection, key, tuple[valueStartIndex]);
tuple[startIndex] = collection;
tuple[valueStartIndex] = TupleReuse.CONSUMED;
tupleIndex.put(id, tupleIndexValue);
} else if (tupleIndexValue.addRestTuple(tuple, startIndex, valueOffset + 1)) {
Object collection = tupleIndexValue.getTupleValue();
Object key = tuple[startIndex];
add(collection, key, tuple[valueStartIndex]);
tuple[startIndex] = collection;
tuple[valueStartIndex] = TupleReuse.CONSUMED;
// Check if the tuple after the offset is contained
if (tupleIndexValue.containsRestTuple(tuple, startIndex, valueOffset + 1)) {
// At startIndex we have the index/key of the list/map
// At valueStartIndex is the actual element that should be put into the collection
if (tupleIndexValue == null) {
Object collection = createCollection();
tupleIndexValue = new TupleIndexValue(collection, tuple, startIndex, valueOffset + 1);
Object key = tuple[startIndex];
add(collection, key, tuple[valueStartIndex]);
tuple[startIndex] = collection;
tuple[valueStartIndex] = TupleReuse.CONSUMED;
tupleIndex.put(id, tupleIndexValue);
} else if (tupleIndexValue.addRestTuple(tuple, startIndex, valueOffset + 1)) {
Object collection = tupleIndexValue.getTupleValue();
Object key = tuple[startIndex];
add(collection, key, tuple[valueStartIndex]);
tuple[startIndex] = collection;
tuple[valueStartIndex] = TupleReuse.CONSUMED;
// Check if the tuple after the offset is contained
if (tupleIndexValue.containsRestTuple(tuple, startIndex, valueOffset + 1)) {
tupleListIter.remove();
}
} else {
Object key = tuple[startIndex];
add(tupleIndexValue.getTupleValue(), key, tuple[valueStartIndex]);
tuple[valueStartIndex] = TupleReuse.CONSUMED;
tupleListIter.remove();
}
} else {
Object key = tuple[startIndex];
add(tupleIndexValue.getTupleValue(), key, tuple[valueStartIndex]);
tuple[valueStartIndex] = TupleReuse.CONSUMED;
tupleListIter.remove();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,25 +55,28 @@ public List<Object[]> transform(List<Object[]> tuples) {
while (tupleListIter.hasNext()) {
Object[] tuple = tupleListIter.next();
TupleId id = new TupleId(parentIdPositions, tuple);
TupleIndexValue tupleIndexValue = tupleIndex.get(id);
// Skip constructing the collection and removing tuples when the parent is empty i.e. null
if (!id.isEmpty()) {
TupleIndexValue tupleIndexValue = tupleIndex.get(id);

if (tupleIndexValue == null) {
Object collection = createCollection();
tupleIndexValue = new TupleIndexValue(collection, tuple, startIndex, 1);
add(collection, tuple[startIndex]);
tuple[startIndex] = collection;
tupleIndex.put(id, tupleIndexValue);
} else if (tupleIndexValue.addRestTuple(tuple, startIndex, 1)) {
Object collection = tupleIndexValue.getTupleValue();
add(collection, tuple[startIndex]);
tuple[startIndex] = collection;
// Check if the tuple after the offset is contained
if (tupleIndexValue.containsRestTuple(tuple, startIndex, 1)) {
if (tupleIndexValue == null) {
Object collection = createCollection();
tupleIndexValue = new TupleIndexValue(collection, tuple, startIndex, 1);
add(collection, tuple[startIndex]);
tuple[startIndex] = collection;
tupleIndex.put(id, tupleIndexValue);
} else if (tupleIndexValue.addRestTuple(tuple, startIndex, 1)) {
Object collection = tupleIndexValue.getTupleValue();
add(collection, tuple[startIndex]);
tuple[startIndex] = collection;
// Check if the tuple after the offset is contained
if (tupleIndexValue.containsRestTuple(tuple, startIndex, 1)) {
tupleListIter.remove();
}
} else {
add(tupleIndexValue.getTupleValue(), tuple[startIndex]);
tupleListIter.remove();
}
} else {
add(tupleIndexValue.getTupleValue(), tuple[startIndex]);
tupleListIter.remove();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
Expand All @@ -43,7 +44,8 @@ public class PersonForCollections implements Serializable {
private Long id;
private String name;
private DocumentForCollections partnerDocument;
private Set<DocumentForCollections> ownedDocuments = new HashSet<DocumentForCollections>();
private Set<DocumentForCollections> ownedDocuments = new HashSet<>();
private Set<PersonForCollections> someCollection = new HashSet<>();

public PersonForCollections() {
}
Expand Down Expand Up @@ -89,6 +91,16 @@ public void setOwnedDocuments(Set<DocumentForCollections> ownedDocuments) {
this.ownedDocuments = ownedDocuments;
}

@OneToMany
@JoinTable(name = "pers_coll_some_coll")
public Set<PersonForCollections> getSomeCollection() {
return someCollection;
}

public void setSomeCollection(Set<PersonForCollections> someCollection) {
this.someCollection = someCollection;
}

@Override
public int hashCode() {
final int prime = 31;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ public void testCollections() {
assertEquals(pers1.getName(), results.get(0).getName());
assertSubviewCollectionEquals(pers1.getOwnedDocuments(), results.get(0).getOwnedDocuments());

// Pers1
// Pers2
assertEquals(pers2.getName(), results.get(1).getName());
assertSubviewCollectionEquals(pers2.getOwnedDocuments(), results.get(1).getOwnedDocuments());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Copyright 2014 - 2018 Blazebit.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.blazebit.persistence.view.testsuite.collections.subview;

import com.blazebit.persistence.CriteriaBuilder;
import com.blazebit.persistence.testsuite.tx.TxVoidWork;
import com.blazebit.persistence.view.EntityViewManager;
import com.blazebit.persistence.view.EntityViewSetting;
import com.blazebit.persistence.view.EntityViews;
import com.blazebit.persistence.view.spi.EntityViewConfiguration;
import com.blazebit.persistence.view.testsuite.AbstractEntityViewTest;
import com.blazebit.persistence.view.testsuite.collections.entity.simple.DocumentForCollections;
import com.blazebit.persistence.view.testsuite.collections.entity.simple.PersonForCollections;
import com.blazebit.persistence.view.testsuite.collections.subview.model.PersonForCollectionsSelectFetchNestedView;
import com.blazebit.persistence.view.testsuite.collections.subview.model.SubviewDocumentCollectionsView;
import com.blazebit.persistence.view.testsuite.collections.subview.model.SubviewDocumentSelectFetchView;
import com.blazebit.persistence.view.testsuite.collections.subview.model.SubviewPersonForCollectionsSelectFetchView;
import com.blazebit.persistence.view.testsuite.collections.subview.model.SubviewPersonForCollectionsView;
import com.blazebit.persistence.view.testsuite.collections.subview.model.variations.PersonForCollectionsMasterView;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import javax.persistence.EntityManager;
import java.util.List;
import java.util.Set;

import static com.blazebit.persistence.view.testsuite.collections.subview.SubviewAssert.assertSubviewEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

/**
*
* @author Christian Beikov
* @since 1.4.0
*/
public class SelectFetchNestedEmptyCollectionsTest<T extends PersonForCollectionsMasterView, U extends SubviewDocumentCollectionsView> extends AbstractEntityViewTest {

private PersonForCollections pers1;
private PersonForCollections pers2;

@Override
protected Class<?>[] getEntityClasses() {
return new Class<?>[]{
DocumentForCollections.class,
PersonForCollections.class
};
}

@Override
public void setUpOnce() {
cleanDatabase();
transactional(new TxVoidWork() {
@Override
public void work(EntityManager em) {
DocumentForCollections doc1 = new DocumentForCollections("doc1");
DocumentForCollections doc2 = new DocumentForCollections("doc2");
DocumentForCollections doc3 = new DocumentForCollections("doc3");
DocumentForCollections doc4 = new DocumentForCollections("doc4");

pers1 = new PersonForCollections("pers1");
pers2 = new PersonForCollections("pers2");

doc1.setOwner(pers1);
doc2.setOwner(pers1);
doc3.setOwner(pers1);
doc4.setOwner(pers1);

em.persist(pers1);
em.persist(pers2);

em.persist(doc1);
em.persist(doc2);
em.persist(doc3);
em.persist(doc4);
}
});
}

@Before
public void setUp() {
pers1 = cbf.create(em, PersonForCollections.class).where("name").eq("pers1").getSingleResult();
pers2 = cbf.create(em, PersonForCollections.class).where("name").eq("pers2").getSingleResult();
}

@Test
public void testCollections() {
EntityViewConfiguration cfg = EntityViews.createDefaultConfiguration();
cfg.addEntityView(PersonForCollectionsSelectFetchNestedView.class);
cfg.addEntityView(SubviewDocumentSelectFetchView.class);
cfg.addEntityView(SubviewPersonForCollectionsSelectFetchView.class);
cfg.addEntityView(SubviewPersonForCollectionsSelectFetchView.Id.class);
cfg.addEntityView(SubviewPersonForCollectionsView.class);
EntityViewManager evm = cfg.createEntityViewManager(cbf);

CriteriaBuilder<PersonForCollections> criteria = cbf.create(em, PersonForCollections.class, "p")
.where("id").in(pers1.getId(), pers2.getId())
.orderByAsc("id");
CriteriaBuilder<PersonForCollectionsSelectFetchNestedView> cb = evm.applySetting(EntityViewSetting.create(PersonForCollectionsSelectFetchNestedView.class), criteria);
List<PersonForCollectionsSelectFetchNestedView> results = cb.getResultList();

assertEquals(2, results.size());
// Pers1
assertEquals(pers1.getName(), results.get(0).getName());
assertSubviewCollectionEquals(pers1.getOwnedDocuments(), results.get(0).getOwnedDocuments());

// Pers2
assertEquals(pers2.getName(), results.get(1).getName());
assertSubviewCollectionEquals(pers2.getOwnedDocuments(), results.get(1).getOwnedDocuments());
}

private void assertSubviewCollectionEquals(Set<DocumentForCollections> ownedDocuments, Set<? extends SubviewDocumentSelectFetchView> ownedSubviewDocuments) {
assertEquals(ownedDocuments.size(), ownedSubviewDocuments.size());
for (DocumentForCollections doc : ownedDocuments) {
boolean found = false;
for (SubviewDocumentSelectFetchView docSub : ownedSubviewDocuments) {
if (doc.getName().equals(docSub.getName())) {
found = true;

assertTrue(docSub.getPartners().isEmpty());
break;
}
}

if (!found) {
Assert.fail("Could not find a SubviewDocumentCollectionsView with the name: " + doc.getName());
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2014 - 2018 Blazebit.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.blazebit.persistence.view.testsuite.collections.subview.model;

import com.blazebit.persistence.view.EntityView;
import com.blazebit.persistence.view.FetchStrategy;
import com.blazebit.persistence.view.IdMapping;
import com.blazebit.persistence.view.Mapping;
import com.blazebit.persistence.view.UpdatableEntityView;
import com.blazebit.persistence.view.testsuite.collections.entity.simple.PersonForCollections;
import com.blazebit.persistence.view.testsuite.collections.subview.model.variations.PersonForCollectionsMasterView;

import java.util.Set;

/**
*
* @author Christian Beikov
* @since 1.4.0
*/
@UpdatableEntityView
@EntityView(PersonForCollections.class)
public interface PersonForCollectionsSelectFetchNestedView {

@IdMapping
public Long getId();

public String getName();

@Mapping(fetch = FetchStrategy.SELECT)
public Set<SubviewDocumentSelectFetchView> getOwnedDocuments();
}
Loading

0 comments on commit 55341da

Please sign in to comment.