/*
* Copyright 2009-2016 Tilmann Zaeschke. All rights reserved.
*
* This file is part of ZooDB.
*
* ZooDB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ZooDB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ZooDB. If not, see <http://www.gnu.org/licenses/>.
*
* See the README and COPYING files for further information.
*/
package org.zoodb.test.jdo;
import static org.junit.Assert.*;
import java.util.Collection;
import javax.jdo.Extent;
import javax.jdo.JDOHelper;
import javax.jdo.JDOUserException;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.zoodb.internal.server.index.BitTools;
import org.zoodb.jdo.ZooJdoHelper;
import org.zoodb.test.api.TestSuper;
import org.zoodb.test.testutil.TestTools;
public class Test_091_IndexUpdates {
@Before
public void setUp() {
TestTools.removeDb();
TestTools.createDb();
TestTools.defineSchema(TestClass.class);
}
@Test
public void testSimpleIndexUpdate() {
TestTools.defineIndex(TestClass.class, "_long", false);
TestTools.defineIndex(TestClass.class, "_string", true);
TestTools.defineIndex(TestClass.class, "_float", false);
TestTools.defineIndex(TestClass.class, "_double", true);
PersistenceManager pm = TestTools.openPM();
pm.currentTransaction().begin();
//create data
TestClass tc1 = new TestClass();
tc1.setString("1");
tc1.setLong(1);
tc1.setFloat(1.1f);
tc1.setDouble(1.2);
pm.makePersistent(tc1);
pm.currentTransaction().commit();
pm.currentTransaction().begin();
checkQuery(pm, "_long == 1", 1);
checkQuery(pm, "_string == '1'", 1);
checkQuery(pm, "_float == 1.1f", 1);
checkQuery(pm, "_double == 1.2", 1);
//modify
tc1.setString("2");
tc1.setLong(2);
tc1.setFloat(2.1f);
tc1.setDouble(2.2);
pm.currentTransaction().commit();
pm.currentTransaction().begin();
//check old values
checkQuery(pm, "_long == 1", 0);
checkQuery(pm, "_string == '1'", 0);
checkQuery(pm, "_float == 1.1f", 0);
checkQuery(pm, "_double == 1.2", 0);
//check new values
checkQuery(pm, "_long == 2", 1);
checkQuery(pm, "_string == '2'", 1);
checkQuery(pm, "_float == 2.1f", 1);
checkQuery(pm, "_double == 2.2", 1);
//delete
pm.deletePersistent(tc1);
pm.currentTransaction().commit();
pm.currentTransaction().begin();
//check is empty
checkQuery(pm, "_long == 2", 0);
checkQuery(pm, "_string == '2'", 0);
checkQuery(pm, "_float == 2.1f", 0);
checkQuery(pm, "_double == 2.2", 0);
pm.currentTransaction().commit();
TestTools.closePM(pm);
}
@SuppressWarnings("unchecked")
private void checkQuery(PersistenceManager pm, String qStr, int n) {
Query q = pm.newQuery(TestClass.class, qStr);
Collection<TestClass> col = (Collection<TestClass>) q.execute();
assertEquals(n, col.size());
}
@SuppressWarnings("unchecked")
@Test
public void testDeletionOfModifiedObjects() {
TestTools.defineIndex(TestClass.class, "_long", true);
TestTools.defineIndex(TestClass.class, "_string", true);
TestTools.defineIndex(TestClass.class, "_float", true);
TestTools.defineIndex(TestClass.class, "_double", true);
PersistenceManager pm = TestTools.openPM();
pm.currentTransaction().begin();
//create data
TestClass tc1 = new TestClass();
tc1.setString("1xyz");
tc1.setLong(11234);
tc1.setFloat(1.1f);
tc1.setDouble(1.2);
pm.makePersistent(tc1);
pm.currentTransaction().commit();
pm.currentTransaction().begin();
//modify and delete
tc1.setString("1-del");
tc1.setLong(1000);
tc1.setFloat(3.1f);
tc1.setDouble(3.2);
pm.deletePersistent(tc1);
pm.currentTransaction().commit();
pm.currentTransaction().begin();
checkQuery(pm, "_long > 0", 0);
checkQuery(pm, "_string > \"\"", 0);
checkQuery(pm, "_string <= \"\"", 0);
Query q = pm.newQuery(TestClass.class);
Collection<TestClass> col = (Collection<TestClass>) q.execute();
assertFalse(col.iterator().hasNext());
Extent<TestClass> ex = pm.getExtent(TestClass.class, false);
assertFalse(ex.iterator().hasNext());
pm.currentTransaction().commit();
TestTools.closePM(pm);
}
/**
* Test what happens if a unique index is update because objects swap value
* or one is even deleted.
* This should confirm that index additions occur AFTER index removals.
*/
@Test
public void testUniqueIndexCollisionString() {
TestTools.defineIndex(TestClass.class, "_string", true);
PersistenceManager pm = TestTools.openPM();
pm.currentTransaction().begin();
//create data
TestClass tc1 = new TestClass();
tc1.setString("1");
TestClass tc2 = new TestClass();
tc2.setString("1");
pm.makePersistent(tc1);
pm.makePersistent(tc2);
//Check whether to fail immediately or only during commit (deferred).
//... or during make persistent????
System.err.println("FIXME JDO 3.0: Check UniqueMetadata.getDeferred()");
System.err.println("FIXME Implement proper tests for node-revert on failed commit().");
try {
pm.currentTransaction().commit();
Assert.fail();
} catch (JDOUserException e) {
//good
}
pm.currentTransaction().begin();
//should be rolled back now
assertFalse(JDOHelper.isPersistent(tc1));
assertFalse(JDOHelper.isPersistent(tc2));
//rollback should work.
pm.currentTransaction().rollback();
TestTools.closePM(pm);
}
/**
* Test what happens if a unique index is update because objects swap value
* or one is even deleted.
*/
@Test
public void testUniqueIndexCollisionString2() {
TestTools.defineIndex(TestClass.class, "_string", true);
PersistenceManager pm = TestTools.openPM();
pm.currentTransaction().begin();
//create data
TestClass tc1 = new TestClass();
tc1.setString("1");
pm.makePersistent(tc1);
pm.currentTransaction().commit();
pm.currentTransaction().begin();
TestClass tc2 = new TestClass();
tc2.setString("1");
pm.makePersistent(tc2);
//Check whether to fail immediately or only during commit (deferred).
//... or during make persistent????
System.err.println("FIXME JDO 3.0: Check UniqueMetadata.getDeferred()");
System.err.println("FIXME Implement proper tests for node-revert on failed commit().");
try {
pm.currentTransaction().commit();
Assert.fail();
} catch (JDOUserException e) {
//good
}
pm.currentTransaction().begin();
//should be rolled back now
assertTrue(JDOHelper.isPersistent(tc1));
assertFalse(JDOHelper.isPersistent(tc2));
//rollback should work.
pm.currentTransaction().rollback();
TestTools.closePM(pm);
}
/**
* Test what happens if a unique index is update because objects swap value
* or one is even deleted.
* This should confirm that index additions occur AFTER index removals.
*/
@Test
public void testUniqueIndexCollisionPrimitive() {
TestTools.defineIndex(TestClass.class, "_long", true);
PersistenceManager pm = TestTools.openPM();
pm.currentTransaction().begin();
//create data
TestClass tc1 = new TestClass();
tc1.setLong(1);
TestClass tc2 = new TestClass();
tc2.setLong(1);
pm.makePersistent(tc1);
pm.makePersistent(tc2);
//Check whether to fail immediately or only during commit (deferred).
//... or during make persistent????
System.err.println("FIXME JDO 3.0: Check UniqueMetadata.getDeferred()");
System.err.println("FIXME Implement proper tests for node-revert on failed commit().");
try {
pm.currentTransaction().commit();
Assert.fail();
} catch (JDOUserException e) {
//good
}
pm.currentTransaction().begin();
//should be rolled back now
assertFalse(JDOHelper.isPersistent(tc1));
assertFalse(JDOHelper.isPersistent(tc2));
//rollback should work.
pm.currentTransaction().rollback();
TestTools.closePM(pm);
}
/**
* Test what happens if a unique index is update because objects swap value
* or one is even deleted.
*/
@Test
public void testUniqueIndexCollisionPrimitive2() {
TestTools.defineIndex(TestClass.class, "_long", true);
PersistenceManager pm = TestTools.openPM();
pm.currentTransaction().begin();
//create data
TestClass tc1 = new TestClass();
tc1.setLong(1);
pm.makePersistent(tc1);
pm.currentTransaction().commit();
pm.currentTransaction().begin();
TestClass tc2 = new TestClass();
tc2.setLong(1);
pm.makePersistent(tc2);
//Check whether to fail immediately or only during commit (deferred).
//... or during make persistent????
System.err.println("FIXME JDO 3.0: Check UniqueMetadata.getDeferred()");
System.err.println("FIXME Implement proper tests for node-revert on failed commit().");
try {
pm.currentTransaction().commit();
Assert.fail();
} catch (JDOUserException e) {
//good
}
pm.currentTransaction().begin();
//should be rolled back now
assertTrue(JDOHelper.isPersistent(tc1));
assertFalse(JDOHelper.isPersistent(tc2));
//rollback should work.
pm.currentTransaction().rollback();
TestTools.closePM(pm);
}
/**
* Test what happens if a unique index is update because objects swap values.
* The evil part is that re-ordering the objects to solve the collision is not possibles,
* because either object needs the other one to be removed from the unique index first.
*
* This is only possible with deferred update, of course.
*/
@Test
public void testUniqueIndexCollisionSwap() {
TestTools.defineIndex(TestClass.class, "_long", true);
TestTools.defineIndex(TestClass.class, "_string", true);
PersistenceManager pm = TestTools.openPM();
pm.currentTransaction().begin();
//create data
TestClass tc1 = new TestClass();
tc1.setString("1");
tc1.setInt(2);
TestClass tc2 = new TestClass();
tc2.setString("2");
tc2.setLong(1);
pm.currentTransaction().commit();
pm.currentTransaction().begin();
tc1.setString("2");
tc1.setInt(1);
tc2.setString("1");
tc2.setLong(2);
//check that commit is possible
pm.currentTransaction().commit();
TestTools.closePM(pm);
}
/**
* Test what happens if a unique index is update because objects swap value
* or one is even deleted.
* This should confirm that index additions occur AFTER index removals.
*/
@Test
public void testMixedUpdateOfUniqueIndex() {
TestTools.defineIndex(TestClass.class, "_long", true);
TestTools.defineIndex(TestClass.class, "_string", true);
checkMixedUpdate(true);
}
@Test
public void testMixedUpdateOfNonUniqueIndex() {
TestTools.defineIndex(TestClass.class, "_long", false);
TestTools.defineIndex(TestClass.class, "_string", false);
checkMixedUpdate(false);
}
private void checkMixedUpdate(boolean isUnique) {
PersistenceManager pm = TestTools.openPM();
pm.currentTransaction().begin();
final int N = 10;
//create data
TestClass[] tca1 = new TestClass[N];
TestClass[] tca2 = new TestClass[N];
TestClass[] tca3 = new TestClass[N];
for (int i = 0; i < N; i++) {
TestClass tc1 = new TestClass();
tc1.setString("1-" + i);
tc1.setLong(i);
tc1.setInt(1);
tca1[i] = tc1;
TestClass tc2 = new TestClass();
tc2.setString("2-" + i);
tc2.setLong(N+i);
tc2.setInt(2);
tca2[N-i-1] = tc2;
TestClass tc3 = new TestClass();
tc3.setString("3-" + i);
tc3.setLong(2*N+i);
tc3.setInt(1);
tca3[i] = tc3;
pm.makePersistent(tc1);
pm.makePersistent(tc2);
pm.makePersistent(tc3);
}
pm.currentTransaction().commit();
pm.currentTransaction().begin();
//modify:
//-add new class with values of tca1
//- change tca1 to tca2 values
//- change tca2 to tca3 values
//- delete tca3 values
for (int i = 0; i < N; i++) {
TestClass tc = new TestClass();
tc.setString("1-" + i);
tc.setLong(i);
tc.setInt(4);
pm.makePersistent(tc);
tca1[i].setString("2-" + i);
tca1[i].setLong(N+i);
tca2[N-i-1].setString("3-" + i);
tca2[N-i-1].setLong(2*N+i);
}
if (isUnique) {
//if unique, we attempt a commit, which should fail. We then fix the problem and should
//be able to do it again.
try {
pm.currentTransaction().commit();
fail();
} catch (JDOUserException e) {
//indeed ...
}
//okay try again
pm.currentTransaction().begin();
for (int i = 0; i < N; i++) {
TestClass tc = new TestClass();
tc.setString("1-" + i);
tc.setLong(i);
tc.setInt(4);
pm.makePersistent(tc);
tca1[i].setString("2-" + i);
tca1[i].setLong(N+i);
tca2[N-i-1].setString("3-" + i);
tca2[N-i-1].setLong(2*N+i);
}
}
//fix the problem and try again
for (int i = 0; i < N; i++) {
pm.deletePersistent(tca3[i]);
}
pm.currentTransaction().commit();
pm.currentTransaction().begin();
//check object count
int[] res = new int[5];
Query q = pm.newQuery(TestClass.class);
@SuppressWarnings("unchecked")
Collection<TestClass> col = (Collection<TestClass>) q.execute();
for (TestClass tc: col) {
res[tc.getInt()]++;
}
Assert.assertEquals(0, res[0]);
Assert.assertEquals(N, res[1]);
Assert.assertEquals(N, res[2]);
Assert.assertEquals(0, res[3]);
Assert.assertEquals(N, res[4]);
//check field indices
checkQueryResult(pm, N, 1, "_string >= '2-' && _string < '3-'");
checkQueryResult(pm, N, 2, "_string >= '3-'");
checkQueryResult(pm, 0, 3, "_string >= '2-' && _string < '3-' && _int == 3");
checkQueryResult(pm, N, 4, "_string >= '1-' && _string < '2-'");
checkQueryResult(pm, N, 1, "_long >= "+N+" && _long < "+2*N);
checkQueryResult(pm, N, 2, "_long >= "+2*N);
checkQueryResult(pm, 0, 3, "_long >= "+N+" && _long < "+2*N+" && _int == 3");
checkQueryResult(pm, N, 4, "_long >= 0 && _long < "+N);
pm.currentTransaction().commit();
TestTools.closePM(pm);
}
private void checkQueryResult(PersistenceManager pm, int nObj, int id, String query) {
Query q = pm.newQuery(TestClass.class, query);
@SuppressWarnings("unchecked")
Collection<TestClass> col = (Collection<TestClass>) q.execute();
for (TestClass tc: col) {
Assert.assertEquals(id, tc.getInt());
}
Assert.assertEquals(nObj, col.size());
}
/**
* This used to cause an NPE in the DataSink where the field-indexes are updated.
* The NPE occurred because there was no field-backup array because backup
* arrays are only created if the indexes were known during creation of the session.
*/
@Test
public void testSchemaAttrIndexUpdatesUniqueBug1() {
TestTools.defineSchema(TestSuper.class);
PersistenceManager pm = TestTools.openPM();
pm.currentTransaction().begin();
TestSuper t11 = new TestSuper(1, 11, null);
pm.makePersistent(t11);
ZooJdoHelper.schema(pm).getClass(TestSuper.class).getField("_id").createIndex(true);
pm.currentTransaction().commit();
pm.currentTransaction().begin();
//update non-indexed field
t11.setId(123);
t11.setTime(1234);
pm.currentTransaction().commit();
pm.currentTransaction().begin();
checkQuerySuper(pm, 123);
pm.currentTransaction().commit();
TestTools.closePM();
}
/**
* This used to cause an NPE in the DataSink where the field-indexes are updated.
* The NPE occurred because there was no field-backup array because backup
* arrays are only created if the indexes were known during creation of the session.
*/
@Test
public void testSchemaAttrIndexUpdatesUniqueBug2() {
TestTools.defineSchema(TestSuper.class);
PersistenceManager pm = TestTools.openPM();
pm.currentTransaction().begin();
TestSuper t11 = new TestSuper(1, 11, null);
pm.makePersistent(t11);
pm.currentTransaction().commit();
pm.currentTransaction().begin();
ZooJdoHelper.schema(pm).getClass(TestSuper.class).getField("_id").createIndex(true);
//update non-indexed field
t11.setId(123);
t11.setTime(1234);
pm.currentTransaction().commit();
pm.currentTransaction().begin();
checkQuerySuper(pm, 123);
pm.currentTransaction().commit();
TestTools.closePM();
}
/**
* This used to cause an NPE in the DataSink where the field-indexes are updated.
* The NPE occurred because there was no field-backup array because backup
* arrays are only created if the indexes were known during creation of the session.
*
* The delete() did not actually fail, of course we test it anyway.
*/
@Test
public void testSchemaAttrIndexUpdatesUniqueBug3() {
TestTools.defineSchema(TestSuper.class);
TestTools.defineIndex(TestSuper.class, "_id", true);
PersistenceManager pm = TestTools.openPM();
pm.currentTransaction().begin();
TestSuper t11 = new TestSuper(1, 11, null);
pm.makePersistent(t11);
ZooJdoHelper.schema(pm).getClass(TestSuper.class).getField("_id").removeIndex();
pm.currentTransaction().commit();
pm.currentTransaction().begin();
//update non-indexed field
t11.setId(123);
t11.setTime(1234);
pm.currentTransaction().commit();
pm.currentTransaction().begin();
checkQuerySuper(pm, 123);
pm.currentTransaction().commit();
TestTools.closePM();
}
/**
* This used to cause an NPE in the DataSink where the field-indexes are updated.
* The NPE occurred because there was no field-backup array because backup
* arrays are only created if the indexes were known during creation of the session.
*
* The delete() did not actually fail, of course we test it anyway.
*/
@Test
public void testSchemaAttrIndexUpdatesUniqueBug4() {
TestTools.defineSchema(TestSuper.class);
TestTools.defineIndex(TestSuper.class, "_id", true);
PersistenceManager pm = TestTools.openPM();
pm.currentTransaction().begin();
TestSuper t11 = new TestSuper(1, 11, null);
pm.makePersistent(t11);
pm.currentTransaction().commit();
pm.currentTransaction().begin();
ZooJdoHelper.schema(pm).getClass(TestSuper.class).getField("_id").removeIndex();
//update non-indexed field
t11.setId(123);
t11.setTime(1234);
pm.currentTransaction().commit();
pm.currentTransaction().begin();
checkQuerySuper(pm, 123);
pm.currentTransaction().commit();
TestTools.closePM();
}
@Test
public void testStringIndexCollisionBug_Issue_55() {
TestTools.defineIndex(TestClass.class, "_string", true);
PersistenceManager pm = TestTools.openPM();
pm.currentTransaction().begin();
// **** Class2: id= journals-comcom-Sebestyen87 oid=1259 cls=class ch.ethz.globi
// s.isk.domain.zoodb.Db4oArticle Publication{id='journals-comcom-Sebestyen87', ti
// tle='Public text and data services in the FRG.', year=1987}
// **** State: persistent-new true true
// **** Class22: id= journals-tc-Devries79 oid=470 cls=class ch.ethz.globis.isk.
// domain.zoodb.Db4oArticle Publication{id='journals-tc-Devries79', title='Comment
// s on ``A Readily Implemented Single-Error-Correcting Unit-Distance Counting Code
// ''.', year=1979}
// **** State22: persistent-new
TestClass t1 = new TestClass();
t1.setString("journals-comcom-Sebestyen87");
pm.makePersistent(t1);
TestClass t2 = new TestClass();
t2.setString("journals-tc-Devries79");
pm.makePersistent(t2);
long hash1 = BitTools.toSortableLong(t1.getString());
long hash2 = BitTools.toSortableLong(t2.getString());
//System.out.println(BitTools.toSortableLong(t1.getString()));
//System.out.println(BitTools.toSortableLong(t2.getString()));
assertEquals(hash1, hash2);
pm.currentTransaction().commit();
pm.currentTransaction().begin();
Collection<?> c = (Collection<?>) pm.newQuery(TestClass.class).execute();
assertEquals(2, c.size());
assertTrue(c.contains(t1));
assertTrue(c.contains(t2));
pm.currentTransaction().commit();
pm.currentTransaction().begin();
t2.setString(t1.getString());
try {
pm.currentTransaction().commit();
fail();
} catch (JDOUserException e) {
//good, this is a collision
}
pm.currentTransaction().begin();
//this should NOT collide
t2.setString(null);
t1.setString(null);
pm.currentTransaction().commit();
TestTools.closePM();
}
private void checkQuerySuper(PersistenceManager pm, int id) {
Query q = pm.newQuery(TestSuper.class, "_id == " + id);
@SuppressWarnings("unchecked")
Collection<TestSuper> col = (Collection<TestSuper>) q.execute();
for (TestSuper tc: col) {
Assert.assertEquals(id, tc.getId());
}
Assert.assertEquals(1, col.size());
}
@After
public void afterTest() {
TestTools.closePM();
TestTools.removeDb();
}
}