/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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.
*/
/*
* QueryAndJtaJUnitTest.java
*
* Created on June 15, 2005, 12:49 PM
*/
package org.apache.geode.cache.query.transaction;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Collection;
import javax.naming.Context;
import javax.transaction.RollbackException;
import javax.transaction.UserTransaction;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.apache.geode.CopyHelper;
import org.apache.geode.cache.AttributesFactory;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.DataPolicy;
import org.apache.geode.cache.EntryEvent;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionAttributes;
import org.apache.geode.cache.query.Index;
import org.apache.geode.cache.query.IndexType;
import org.apache.geode.cache.query.Query;
import org.apache.geode.cache.query.QueryService;
import org.apache.geode.cache.query.SelectResults;
import org.apache.geode.cache.query.data.Portfolio;
import org.apache.geode.cache.query.internal.QueryObserverAdapter;
import org.apache.geode.cache.query.internal.QueryObserverHolder;
import org.apache.geode.cache.util.CacheListenerAdapter;
import org.apache.geode.internal.jta.CacheUtils;
import org.apache.geode.test.junit.categories.IntegrationTest;
@Category(IntegrationTest.class)
public class QueryAndJtaJUnitTest {
private Region currRegion;
private Cache cache;
private QueryService qs;
private Query q;
private int tblIDFld;
private String tblNameFld;
private String tblName;
@Before
public void setUp() throws Exception {
tblName = CacheUtils.init("CacheTest");
cache = CacheUtils.getCache();
AttributesFactory attributesFactory = new AttributesFactory();
attributesFactory.setValueConstraint(Portfolio.class);
RegionAttributes regionAttributes = attributesFactory.create();
currRegion = cache.createRegion("portfolios", regionAttributes);
qs = CacheUtils.getQueryService();
}
@After
public void tearDown() throws Exception {
CacheUtils.destroyTable(tblName);
cache.close();
}
@Test
public void testScenario1() throws Exception {
Context ctx = cache.getJNDIContext();
UserTransaction ta = null;
// Connection conn = null;
try {
qs.createIndex("iIndex", IndexType.FUNCTIONAL, "ID", "/portfolios");
// PUT 4 objects in region, QUERY, CREATEINDEX
for (int i = 0; i < 4; i++) {
currRegion.put("key" + i, new Portfolio(i));
}
QueryObserverImpl observer2 = new QueryObserverImpl();
QueryObserverHolder.setInstance(observer2);
q = qs.newQuery("select distinct * from /portfolios where ID != 53");
Object r = q.execute();
if (!observer2.isIndexesUsed) {
fail("NO INDEX WAS USED, IT WAS EXPECTED TO BE USED");
}
if (((Collection) r).size() != 4) {
fail("Query result not of expected size");
}
// print("Size of query result :"+ ((Collection)r).size());
// print("Result of query =" + Utils.printResult(r));
// print("Index IS: " + ((RangeIndex)i2).dump());
// BEGIN TX PUT new 4 objects in region, QUERY,CREATEINDEX, ROLLBACK
ta = (UserTransaction) ctx.lookup("java:/UserTransaction");
ta.begin();
for (int i = 9; i < 13; i++) {
currRegion.put("key" + i, new Portfolio(i));
}
observer2 = new QueryObserverImpl();
QueryObserverHolder.setInstance(observer2);
q = qs.newQuery("select distinct * from /portfolios where ID != 53");
r = q.execute();
if (!observer2.isIndexesUsed) {
fail("NO INDEX WAS USED, IT WAS EXPECTED TO BE USED");
}
if (((Collection) r).size() != 4) {
fail("Query result not of expected size");
}
// print("Size of query result :"+ ((Collection)r).size());
// print("Result of query =" + Utils.printResult(r));
Index i1 = qs.createIndex("tIndex", IndexType.FUNCTIONAL, "status", "/portfolios");
// print("Index IS: " + ((RangeIndex)i1).dump());
observer2 = new QueryObserverImpl();
QueryObserverHolder.setInstance(observer2);
q = qs.newQuery("select distinct * from /portfolios where status = 'active'");
r = q.execute();
if (!observer2.isIndexesUsed) {
fail("NO INDEX WAS USED, IT WAS EXPECTED TO BE USED");
}
if (((Collection) r).size() != 2) {
fail("Query result not of expected size");
}
observer2 = new QueryObserverImpl();
QueryObserverHolder.setInstance(observer2);
q = qs.newQuery("select distinct * from /portfolios where status = 'inactive'");
r = q.execute();
if (!observer2.isIndexesUsed) {
fail("NO INDEX WAS USED, IT WAS EXPECTED TO BE USED");
}
if (((Collection) r).size() != 2) {
fail("Query result not of expected size");
}
ta.rollback();
// PRINT INDEX AFTER ROLLBACK, REMOVEINDEX.
observer2 = new QueryObserverImpl();
QueryObserverHolder.setInstance(observer2);
q = qs.newQuery("select distinct * from /portfolios where status = 'active'");
r = q.execute();
if (!observer2.isIndexesUsed) {
fail("NO INDEX WAS USED, IT WAS EXPECTED TO BE USED");
}
if (((Collection) r).size() != 2) {
fail("Query result not of expected size");
}
// print("AfterRollback \n"+currRegion.values());
// print("Index IS: " + ((RangeIndex)i1).dump());
qs.removeIndex(i1);
// BEGIN TX PUT new 4 objects in region,CREATEINDEX, QUERY ,COMMIT
ta.begin();
for (int i = 9; i < 13; i++) {
currRegion.put("key" + i, new Portfolio(i));
}
i1 = qs.createIndex("tIndex", IndexType.FUNCTIONAL, "status", "/portfolios");
// print("Index IS: " + ((RangeIndex)i1).dump());
observer2 = new QueryObserverImpl();
QueryObserverHolder.setInstance(observer2);
q = qs.newQuery("select distinct * from /portfolios where status = 'active'");
r = q.execute();
if (!observer2.isIndexesUsed) {
fail("NO INDEX WAS USED, IT WAS EXPECTED TO BE USED");
}
if (((Collection) r).size() != 2) {
fail("Query result not of expected size");
}
for (int i = 9; i < 13; i++) {
currRegion.put("key" + i, new Portfolio(i));
}
observer2 = new QueryObserverImpl();
QueryObserverHolder.setInstance(observer2);
q = qs.newQuery("select distinct * from /portfolios where status = 'active'");
r = q.execute();
if (!observer2.isIndexesUsed) {
fail("NO INDEX WAS USED, IT WAS EXPECTED TO BE USED");
}
if (((Collection) r).size() != 2) {
fail("Query result not of expected size");
}
// print("Size of query result :"+ ((Collection)r).size());
// print("Result of query =" + Utils.printResult(r));
// print("Index on status IS: " + ((RangeIndex)i1).dump());
// print("Index On ID IS: " + ((RangeIndex)i2).dump());
ta.commit();
// WAIT FOR 2 secs DISPLAYINDEX, QUERY
Thread.sleep(2000);
// print("Aftercommit \n"+currRegion.values());
// print("Index IS: " + ((RangeIndex)i1).dump());
observer2 = new QueryObserverImpl();
QueryObserverHolder.setInstance(observer2);
q = qs.newQuery("select distinct * from /portfolios where status = 'active'");
r = q.execute();
if (!observer2.isIndexesUsed) {
fail("NO INDEX WAS USED, IT WAS EXPECTED TO BE USED");
}
if (((Collection) r).size() != 4) {
fail("Query result not of expected size");
}
observer2 = new QueryObserverImpl();
QueryObserverHolder.setInstance(observer2);
q = qs.newQuery("select distinct * from /portfolios where status = 'inactive'");
r = q.execute();
if (!observer2.isIndexesUsed) {
fail("NO INDEX WAS USED, IT WAS EXPECTED TO BE USED");
}
if (((Collection) r).size() != 4) {
fail("Query result not of expected size");
}
observer2 = new QueryObserverImpl();
QueryObserverHolder.setInstance(observer2);
q = qs.newQuery("select distinct * from /portfolios where ID != 53");
r = q.execute();
if (!observer2.isIndexesUsed) {
fail("NO INDEX WAS USED, IT WAS EXPECTED TO BE USED");
}
if (((Collection) r).size() != 8) {
fail("Query result not of expected size");
}
// print("Size of query result :"+ ((Collection)r).size());
// print("Result of query =" + Utils.printResult(r));
// print("Index On ID IS: " + ((RangeIndex)i2).dump());
} catch (RollbackException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
ta.rollback();
}
}
private static void print(String str) {
CacheUtils.log("\n" + str + "\n");
}
private static class QueryObserverImpl extends QueryObserverAdapter {
boolean isIndexesUsed = false;
ArrayList indexesUsed = new ArrayList();
public void beforeIndexLookup(Index index, int oper, Object key) {
indexesUsed.add(index.getName());
}
public void afterIndexLookup(Collection results) {
if (results != null) {
isIndexesUsed = true;
}
}
}
/**
* verify that queries on indexes work with transaction
*
* @throws Exception
*/
@Test
public void testIndexOnCommitForPut() throws Exception {
AttributesFactory af = new AttributesFactory();
af.setDataPolicy(DataPolicy.REPLICATE);
Region region = cache.createRegion("sample", af.create());
qs.createIndex("foo", IndexType.FUNCTIONAL, "age", "/sample");
Context ctx = cache.getJNDIContext();
UserTransaction utx = (UserTransaction) ctx.lookup("java:/UserTransaction");
Integer x = new Integer(0);
utx.begin();
region.create(x, new Person("xyz", 45));
utx.commit();
Query q = qs.newQuery("select * from /sample where age < 50");
assertEquals(1, ((SelectResults) q.execute()).size());
Person dsample = (Person) CopyHelper.copy(region.get(x));
dsample.setAge(55);
utx.begin();
region.put(x, dsample);
utx.commit();
CacheUtils.log(((Person) region.get(x)));
assertEquals(0, ((SelectResults) q.execute()).size());
}
@Test
public void testIndexOnCommitForInvalidate() throws Exception {
AttributesFactory af = new AttributesFactory();
af.setDataPolicy(DataPolicy.REPLICATE);
Region region = cache.createRegion("sample", af.create());
qs.createIndex("foo", IndexType.FUNCTIONAL, "age", "/sample");
Context ctx = cache.getJNDIContext();
UserTransaction utx = (UserTransaction) ctx.lookup("java:/UserTransaction");
Integer x = new Integer(0);
utx.begin();
region.create(x, new Person("xyz", 45));
utx.commit();
Query q = qs.newQuery("select * from /sample where age < 50");
assertEquals(1, ((SelectResults) q.execute()).size());
Person dsample = (Person) CopyHelper.copy(region.get(x));
dsample.setAge(55);
utx.begin();
region.invalidate(x);
utx.commit();
CacheUtils.log(((Person) region.get(x)));
assertEquals(0, ((SelectResults) q.execute()).size());
}
@Test
public void testAllIndexesOnCommitForPut() throws Exception {
// create region
AttributesFactory af = new AttributesFactory();
af.setDataPolicy(DataPolicy.REPLICATE);
Region region = cache.createRegion("sample", af.create());
// put data
for (int i = 0; i < 10; i++) {
region.put(i, new Portfolio(i));
}
String[] queries = {"select * from /sample where ID = 5", "select ID from /sample where ID < 5",
"select ID from /sample where ID > 5", "select ID from /sample where ID != 5",
"select status from /sample where status = 'active'",
"select status from /sample where status > 'active'",
"select status from /sample where status < 'active'",
"select status from /sample where status != 'active'",
"select pos.secId from /sample p, p.positions.values pos where pos.secId = 'IBM'",
"select pos.secId from /sample p, p.positions.values pos where pos.secId < 'VMW'",
"select pos.secId from /sample p, p.positions.values pos where pos.secId > 'IBM'",
"select pos.secId from /sample p, p.positions.values pos where pos.secId != 'IBM'"};
SelectResults[][] sr = new SelectResults[queries.length][2];
// execute queries without indexes
for (int i = 0; i < queries.length; i++) {
sr[i][0] = (SelectResults) qs.newQuery(queries[i]).execute();
}
// create indexes
qs.createKeyIndex("IDIndex", "ID", "/sample");
qs.createIndex("statusIndex", "status", "/sample");
qs.createIndex("secIdIndex", "pos.secId", "/sample p, p.positions.values pos");
// begin transaction
Context ctx = cache.getJNDIContext();
UserTransaction utx = (UserTransaction) ctx.lookup("java:/UserTransaction");
utx.begin();
// update data
for (int i = 0; i < 10; i++) {
region.put(i, new Portfolio(i));
}
// execute queries with indexes during transaction
for (int i = 0; i < queries.length; i++) {
sr[i][1] = (SelectResults) qs.newQuery(queries[i]).execute();
}
// complete transaction
utx.commit();
// verify results
org.apache.geode.cache.query.CacheUtils.compareResultsOfWithAndWithoutIndex(sr);
}
@Test
public void testIndexOnCommitForDestroy() throws Exception {
AttributesFactory af = new AttributesFactory();
af.setDataPolicy(DataPolicy.REPLICATE);
Region region = cache.createRegion("sample", af.create());
qs.createIndex("foo", IndexType.FUNCTIONAL, "age", "/sample");
Context ctx = cache.getJNDIContext();
UserTransaction utx = (UserTransaction) ctx.lookup("java:/UserTransaction");
Integer x = new Integer(0);
utx.begin();
region.create(x, new Person("xyz", 45));
utx.commit();
Query q = qs.newQuery("select * from /sample where age < 50");
assertEquals(1, ((SelectResults) q.execute()).size());
Person dsample = (Person) CopyHelper.copy(region.get(x));
dsample.setAge(55);
utx.begin();
region.destroy(x);
utx.commit();
CacheUtils.log(((Person) region.get(x)));
assertEquals(0, ((SelectResults) q.execute()).size());
}
/*
* Enable this test when indexes are made transactional.
*/
@Ignore
@Test
public void testFailedIndexUpdateOnJTACommitForPut() throws Exception {
Person.THROW_ON_INDEX = true;
AttributesFactory af = new AttributesFactory();
af.setDataPolicy(DataPolicy.REPLICATE);
Region region = cache.createRegion("sample", af.create());
qs.createIndex("foo", IndexType.FUNCTIONAL, "index", "/sample");
Context ctx = cache.getJNDIContext();
UserTransaction utx = (UserTransaction) ctx.lookup("java:/UserTransaction");
Integer x = new Integer(0);
utx.begin();
region.create(x, new Person("xyz", 45));
try {
utx.commit();
fail("Commit should have thrown an exception because the index update threw");
} catch (Exception e) {
// this is desired
}
}
@Test
public void testFailedIndexUpdateOnCommitForPut() throws Exception {
Person.THROW_ON_INDEX = true;
AttributesFactory af = new AttributesFactory();
af.setDataPolicy(DataPolicy.REPLICATE);
SimpleListener sl = new SimpleListener();
af.setCacheListener(sl);
Region region = cache.createRegion("sample", af.create());
qs.createIndex("foo", IndexType.FUNCTIONAL, "index", "/sample");
Context ctx = cache.getJNDIContext();
Integer x = new Integer(0);
region.getCache().getCacheTransactionManager().begin();
region.create(x, new Person("xyz", 45));
try {
region.getCache().getCacheTransactionManager().commit();
fail("commit should have thrown an exception because the index maintenance threw");
} catch (org.apache.geode.cache.query.IndexMaintenanceException ie) {
// this is the desired case
}
Person p = (Person) region.get(x);
assertEquals("object shouldn't have made it into region", null, p);
assertEquals(0, sl.creates);
assertEquals(0, sl.updates);
}
private static class SimpleListener extends CacheListenerAdapter {
public int creates = 0;
public int updates = 0;
public void afterCreate(EntryEvent event) {
// TODO Auto-generated method stub
CacheUtils.log("SimpleListener.create!:" + event);
creates++;
}
public void afterUpdate(EntryEvent event) {
// TODO Auto-generated method stub
updates++;
}
}
}