/*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the Kowari Metadata Store.
*
* The Initial Developer of the Original Code is Plugged In Software Pty
* Ltd (http://www.pisoftware.com, mailto:info@pisoftware.com). Portions
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
* Contributor(s):
* All tests in this file (c) Netymon Pty Ltd 2006 All rights reserved.
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
* should use the text of this Exhibit A rather than the text found in the
* Original Code Source Code for Your Modifications.]
*
*/
package org.mulgara.resolver;
// Java 2 standard packages
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.apache.log4j.Logger;
import org.jrdf.graph.BlankNode;
import org.jrdf.graph.Literal;
import org.jrdf.graph.ObjectNode;
import org.jrdf.graph.PredicateNode;
import org.jrdf.graph.SubjectNode;
import org.jrdf.graph.Triple;
import org.jrdf.graph.URIReference;
import org.mulgara.query.Answer;
import org.mulgara.query.ConstraintConjunction;
import org.mulgara.query.ConstraintDisjunction;
import org.mulgara.query.ConstraintExpression;
import org.mulgara.query.ConstraintImpl;
import org.mulgara.query.ConstraintIs;
import org.mulgara.query.GraphResource;
import org.mulgara.query.Order;
import org.mulgara.query.Query;
import org.mulgara.query.QueryException;
import org.mulgara.query.SelectElement;
import org.mulgara.query.Subquery;
import org.mulgara.query.TuplesException;
import org.mulgara.query.UnconstrainedAnswer;
import org.mulgara.query.Variable;
import org.mulgara.query.rdf.LiteralImpl;
import org.mulgara.query.rdf.Mulgara;
import org.mulgara.query.rdf.TripleImpl;
import org.mulgara.query.rdf.URIReferenceImpl;
import org.mulgara.query.rdf.VariableNodeImpl;
import org.mulgara.server.Session;
import org.mulgara.util.FileUtil;
/**
* Test case for {@link DatabaseSession}.
*
* @created 2006-11-20
* @author <a href="mailto:andrae@netymon.com">Andrae Muys</a>
* @company <a href="mailto:mail@netymon.com">Netymon Pty Ltd</a>
* @copyright © 2004 <a href="http://www.PIsoftware.com/">Plugged In
* Software Pty Ltd</a>
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
public class AdvDatabaseSessionUnitTest extends TestCase {
/** Logger. */
private static Logger logger =
Logger.getLogger(AdvDatabaseSessionUnitTest.class.getName());
private static final URI databaseURI = URI.create("local:database");
private static final URI systemModelURI = URI.create("local:database#");
private static final URI modelURI = URI.create("local:database#model");
private static final URI model2URI = URI.create("local:database#model2");
private static final URI model3URI = URI.create("local:database#model3");
private static final URI model4URI = URI.create("local:database#model4");
private static final URI model5URI = URI.create("local:database#model5");
private static final URI xsdModelTypeURI = URI.create(Mulgara.NAMESPACE + "XMLSchemaModel");
private static Database database = null;
public AdvDatabaseSessionUnitTest(String name) {
super(name);
}
public static Test suite() {
TestSuite suite = new TestSuite();
suite.addTest(new AdvDatabaseSessionUnitTest("testSetModel"));
suite.addTest(new AdvDatabaseSessionUnitTest("testBasicQuery"));
suite.addTest(new AdvDatabaseSessionUnitTest("testConcurrentQuery"));
suite.addTest(new AdvDatabaseSessionUnitTest("testSubqueryQuery"));
suite.addTest(new AdvDatabaseSessionUnitTest("testConcurrentSubqueryQuery"));
suite.addTest(new AdvDatabaseSessionUnitTest("testConcurrentReadWrite"));
suite.addTest(new AdvDatabaseSessionUnitTest("testExplicitBasicQuery"));
suite.addTest(new AdvDatabaseSessionUnitTest("testAnswerWriteCloseIsolation"));
suite.addTest(new AdvDatabaseSessionUnitTest("testExplicitIsolationQuery"));
suite.addTest(new AdvDatabaseSessionUnitTest("testExplicitRollbackIsolationQuery"));
suite.addTest(new AdvDatabaseSessionUnitTest("testExplicitCommitIsolationQuery"));
suite.addTest(new AdvDatabaseSessionUnitTest("testImplicitCommitQuery"));
suite.addTest(new AdvDatabaseSessionUnitTest("testConcurrentExplicitTxn"));
suite.addTest(new AdvDatabaseSessionUnitTest("testConcurrentImplicitTxn"));
suite.addTest(new AdvDatabaseSessionUnitTest("testConcurrentImplicitRecovery"));
suite.addTest(new AdvDatabaseSessionUnitTest("testPrefixingWithUnbound"));
suite.addTest(new AdvDatabaseSessionUnitTest("testDatabaseDelete"));
suite.addTest(new AdvDatabaseSessionUnitTest("testCreateModel"));
suite.addTest(new AdvDatabaseSessionUnitTest("testInsertionBlankNodes"));
suite.addTest(new AdvDatabaseSessionUnitTest("testAutoCommitTransactionOps"));
suite.addTest(new AdvDatabaseSessionUnitTest("testExplicitTransactionFailure"));
suite.addTest(new AdvDatabaseSessionUnitTest("testAutoCommitTransactionFailure"));
suite.addTest(new AdvDatabaseSessionUnitTest("testSessionClose"));
return suite;
}
/**
* Create test objects.
*/
public void setUp() throws Exception {
if (database == null) {
// Create the persistence directory
File persistenceDirectory =
new File(new File(System.getProperty("cvs.root")), "testDatabase");
if (persistenceDirectory.isDirectory()) {
if (!FileUtil.deleteDirectory(persistenceDirectory)) {
throw new RuntimeException(
"Unable to remove old directory " + persistenceDirectory
);
}
}
if (!persistenceDirectory.mkdirs()) {
throw new Exception("Unable to create directory "+persistenceDirectory);
}
// Define the the node pool factory
String nodePoolFactoryClassName =
"org.mulgara.store.nodepool.xa.XANodePoolFactory";
// Define the string pool factory
String stringPoolFactoryClassName =
"org.mulgara.store.stringpool.xa.XAStringPoolFactory";
String tempNodePoolFactoryClassName =
"org.mulgara.store.nodepool.memory.MemoryNodePoolFactory";
// Define the string pool factory
String tempStringPoolFactoryClassName =
"org.mulgara.store.stringpool.memory.MemoryStringPoolFactory";
// Define the resolver factory used to manage system models
String systemResolverFactoryClassName =
"org.mulgara.resolver.store.StatementStoreResolverFactory";
// Define the resolver factory used to manage system models
String tempResolverFactoryClassName =
"org.mulgara.resolver.memory.MemoryResolverFactory";
// Create a database which keeps its system models on the Java heap
database = new Database(
databaseURI,
persistenceDirectory,
null, // no security domain
new JotmTransactionManagerFactory(),
0, // default transaction timeout
0, // default idle timeout
nodePoolFactoryClassName, // persistent
new File(persistenceDirectory, "xaNodePool"),
stringPoolFactoryClassName, // persistent
new File(persistenceDirectory, "xaStringPool"),
systemResolverFactoryClassName, // persistent
new File(persistenceDirectory, "xaStatementStore"),
tempNodePoolFactoryClassName, // temporary nodes
null, // no dir for temp nodes
tempStringPoolFactoryClassName, // temporary strings
null, // no dir for temp strings
tempResolverFactoryClassName, // temporary models
null, // no dir for temp models
"org.mulgara.content.rdfxml.RDFXMLContentHandler");
database.addResolverFactory("org.mulgara.resolver.url.URLResolverFactory", null);
database.addResolverFactory("org.mulgara.resolver.xsd.XSDResolverFactory", null);
database.addResolverFactory("org.mulgara.resolver.MockResolverFactory", null);
}
}
/**
* The teardown method for JUnit
*/
public void tearDown() {
}
//
// Test cases
//
/**
* Test the {@link DatabaseSession#setModel} method.
*/
public void testSetModel() throws URISyntaxException {
logger.info("testSetModel");
URI fileURI = new File("data/xatest-model1.rdf").toURI();
try {
// Load some test data
Session session = database.newSession();
try {
session.createModel(modelURI, null);
session.setModel(modelURI, fileURI);
} finally {
session.close();
}
} catch (Exception e) {
fail(e);
}
}
public void testBasicQuery() throws URISyntaxException {
logger.info("Testing basicQuery");
try {
// Load some test data
Session session = database.newSession();
try {
// Evaluate the query
Answer answer = session.query(createQuery(modelURI));
compareResults(expectedResults(), answer);
answer.close();
} finally {
session.close();
}
} catch (Exception e) {
fail(e);
}
}
public void testConcurrentQuery() throws URISyntaxException {
logger.info("Testing concurrentQuery");
try {
// Load some test data
Session session = database.newSession();
try {
// Evaluate the query
Answer answer1 = session.query(createQuery(modelURI));
Answer answer2 = session.query(createQuery(modelURI));
compareResults(answer1, answer2);
answer1.close();
answer2.close();
} finally {
session.close();
}
} catch (Exception e) {
fail(e);
}
}
public void testSubqueryQuery() throws URISyntaxException {
logger.info("Testing subqueryQuery");
try {
// Load some test data
Session session = database.newSession();
try {
Variable subjectVariable = new Variable("subject");
Variable predicateVariable = new Variable("predicate");
Variable objectVariable = new Variable("object");
List<SelectElement> selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(new Subquery(new Variable("k0"), new Query(
Collections.singletonList(objectVariable),
new GraphResource(modelURI), // FROM
new ConstraintImpl(subjectVariable, // WHERE
predicateVariable,
objectVariable),
null, // HAVING
Collections.singletonList( // ORDER BY
new Order(objectVariable, true)
),
null, // LIMIT
0, // OFFSET
true, // DISTINCT
new UnconstrainedAnswer() // GIVEN
)));
// Evaluate the query
Answer answer = session.query(new Query(
selectList, // SELECT
new GraphResource(modelURI), // FROM
new ConstraintImpl(subjectVariable, // WHERE
new URIReferenceImpl(new URI("test:p03")),
objectVariable),
null, // HAVING
Collections.singletonList( // ORDER BY
new Order(subjectVariable, true)
),
null, // LIMIT
0, // OFFSET
true, // DISTINCT
new UnconstrainedAnswer() // GIVEN
));
answer.beforeFirst();
assertTrue(answer.next());
assertEquals(new URIReferenceImpl(new URI("test:s01")),
answer.getObject(0));
Answer sub1 = (Answer)answer.getObject(1);
compareResults(new String[][] { new String[] { "test:o01" },
new String[] { "test:o02" } }, sub1);
sub1.close();
assertTrue(answer.next());
assertEquals(new URIReferenceImpl(new URI("test:s02")),
answer.getObject(0));
Answer sub2 = (Answer)answer.getObject(1);
compareResults(new String[][] { new String[] { "test:o02" },
new String[] { "test:o03" } }, sub2);
// Leave sub2 open.
assertFalse(answer.next());
answer.close();
sub2.close();
} finally {
session.close();
}
} catch (Exception e) {
fail(e);
}
}
public void testConcurrentSubqueryQuery() throws URISyntaxException {
logger.info("Testing concurrentSubqueryQuery");
try {
// Load some test data
Session session = database.newSession();
try {
Variable subjectVariable = new Variable("subject");
Variable predicateVariable = new Variable("predicate");
Variable objectVariable = new Variable("object");
List<SelectElement> selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(new Subquery(new Variable("k0"), new Query(
Collections.singletonList(objectVariable),
new GraphResource(modelURI), // FROM
new ConstraintImpl(subjectVariable, // WHERE
predicateVariable,
objectVariable),
null, // HAVING
Collections.singletonList( // ORDER BY
new Order(objectVariable, true)
),
null, // LIMIT
0, // OFFSET
true, // DISTINCT
new UnconstrainedAnswer() // GIVEN
)));
// Evaluate the query
Answer answer = session.query(new Query(
selectList, // SELECT
new GraphResource(modelURI), // FROM
new ConstraintImpl(subjectVariable, // WHERE
new URIReferenceImpl(new URI("test:p03")),
objectVariable),
null, // HAVING
Collections.singletonList( // ORDER BY
new Order(subjectVariable, true)
),
null, // LIMIT
0, // OFFSET
true, // DISTINCT
new UnconstrainedAnswer() // GIVEN
));
answer.beforeFirst();
assertTrue(answer.next());
assertEquals(new URIReferenceImpl(new URI("test:s01")),
answer.getObject(0));
Answer sub1 = (Answer)answer.getObject(1);
assertTrue(answer.next());
assertEquals(new URIReferenceImpl(new URI("test:s02")),
answer.getObject(0));
Answer sub2 = (Answer)answer.getObject(1);
assertFalse(answer.next());
assertEquals(1, sub1.getNumberOfVariables());
assertEquals(1, sub2.getNumberOfVariables());
sub1.beforeFirst();
sub2.beforeFirst();
assertTrue(sub1.next());
assertTrue(sub2.next());
assertEquals(new URIReferenceImpl(new URI("test:o01")), sub1.getObject(0));
assertEquals(new URIReferenceImpl(new URI("test:o02")), sub2.getObject(0));
assertTrue(sub1.next());
assertTrue(sub2.next());
assertEquals(new URIReferenceImpl(new URI("test:o02")), sub1.getObject(0));
assertEquals(new URIReferenceImpl(new URI("test:o03")), sub2.getObject(0));
assertFalse(sub1.next());
assertFalse(sub2.next());
answer.close();
} finally {
session.close();
}
} catch (Exception e) {
fail(e);
}
}
/**
* Note: What this test does is a really bad idea - there is no
* isolation provided as each operation is within its own
* transaction. It does however provide a good test.
*/
public void testConcurrentReadWrite() throws URISyntaxException {
logger.info("Testing concurrentReadWrite");
try {
Session session = database.newSession();
session.createModel(model2URI, null);
try {
// Evaluate the query
Answer answer = session.query(createQuery(modelURI));
answer.beforeFirst();
while (answer.next()) {
session.insert(model2URI, Collections.singleton((Triple)new TripleImpl(
(SubjectNode)answer.getObject(0),
(PredicateNode)answer.getObject(1),
(ObjectNode)answer.getObject(2))));
}
answer.close();
Answer answer2 = session.query(createQuery(model2URI));
compareResults(expectedResults(), answer2);
answer2.close();
session.removeModel(model2URI);
} finally {
session.close();
}
} catch (Exception e) {
fail(e);
}
}
public void testExplicitBasicQuery() throws URISyntaxException {
logger.info("Testing basicQuery");
try {
// Load some test data
Session session = database.newSession();
try {
session.setAutoCommit(false);
// Evaluate the query
Answer answer = session.query(createQuery(modelURI));
compareResults(expectedResults(), answer);
session.setAutoCommit(true);
// Should throw an Exception here as commit should close answer
boolean thrown = false;;
try {
answer.beforeFirst();
} catch (TuplesException et) {
thrown = true;
} finally {
assertTrue("Answer failed to throw exception, should be closed by commit",
thrown);
}
} finally {
session.close();
}
} catch (Exception e) {
fail(e);
}
}
public void testAnswerWriteCloseIsolation() throws URISyntaxException {
logger.info("Testing AnswerWriteCloseIsolation");
try {
Session session = database.newSession();
try {
// Evaluate the query
Answer answer = session.query(createQuery(systemModelURI));
session.setAutoCommit(false);
answer.close();
session.commit();
session.setAutoCommit(true);
} finally {
session.close();
}
} catch (Exception e) {
fail(e);
}
}
public void testExplicitIsolationQuery() throws URISyntaxException {
logger.info("testExplicitIsolationQuery");
URI fileURI = new File("data/xatest-model1.rdf").toURI();
try {
Session session1 = database.newSession();
try {
Session session2 = database.newSession();
try {
session1.createModel(model3URI, null);
session1.setAutoCommit(false);
session1.setModel(model3URI, fileURI);
// Evaluate the query
Answer answer = session2.query(createQuery(model3URI));
answer.beforeFirst();
assertFalse(answer.next());
answer.close();
session1.setAutoCommit(true);
// Evaluate the query
answer = session2.query(createQuery(model3URI));
compareResults(expectedResults(), answer);
answer.close();
session1.removeModel(model3URI);
} finally {
session2.close();
}
} finally {
session1.close();
}
} catch (Exception e) {
fail(e);
}
}
public void testExplicitRollbackIsolationQuery() throws URISyntaxException {
logger.info("testExplicitRollbackIsolationQuery");
URI fileURI = new File("data/xatest-model1.rdf").toURI();
try {
Session session1 = database.newSession();
try {
Session session2 = database.newSession();
try {
session1.createModel(model3URI, null);
session1.setAutoCommit(false);
session1.setModel(model3URI, fileURI);
// Evaluate the query
Answer answer = session2.query(createQuery(model3URI));
answer.beforeFirst();
assertFalse(answer.next());
answer.close();
session1.rollback();
session1.setAutoCommit(true);
// Evaluate the query
answer = session2.query(createQuery(model3URI));
answer.beforeFirst();
assertFalse(answer.next());
answer.close();
} finally {
session2.close();
}
} finally {
session1.close();
}
} catch (Exception e) {
fail(e);
}
}
public void testExplicitCommitIsolationQuery() throws URISyntaxException {
logger.info("testExplicitCommitIsolationQuery");
URI fileURI = new File("data/xatest-model1.rdf").toURI();
try {
Session session1 = database.newSession();
try {
Session session2 = database.newSession();
try {
session1.createModel(model3URI, null);
session1.setAutoCommit(false);
session1.setModel(model3URI, fileURI);
// Evaluate the query
Answer answer = session2.query(createQuery(model3URI));
answer.beforeFirst();
assertFalse(answer.next());
answer.close();
session1.commit();
// Evaluate the query
answer = session2.query(createQuery(model3URI));
compareResults(expectedResults(), answer);
answer.close();
session1.removeModel(model3URI);
session1.createModel(model3URI, null);
// Evaluate the query
answer = session2.query(createQuery(model3URI));
compareResults(expectedResults(), answer);
answer.close();
session1.setAutoCommit(true);
// Evaluate the query
answer = session2.query(createQuery(model3URI));
answer.beforeFirst();
assertFalse(answer.next());
answer.close();
} finally {
session2.close();
}
} finally {
session1.close();
}
} catch (Exception e) {
fail(e);
}
}
public void testImplicitCommitQuery() throws URISyntaxException {
logger.info("testImplicitCommitQuery");
URI fileURI = new File("data/xatest-model1.rdf").toURI();
try {
Session session1 = database.newSession();
try {
Session session2 = database.newSession();
try {
session1.createModel(model4URI, null);
session1.setModel(model4URI, fileURI);
// Check data loaded
Answer answer = session1.query(createQuery(model4URI));
compareResults(expectedResults(), answer);
answer.close();
// Delete all the data from the model
session2.delete(model4URI, createQuery(model4URI));
// Check all data removed from the model
// This also checks that the delete successfully
// performed the implicit commit.
answer = session1.query(createQuery(model4URI));
answer.beforeFirst();
assertFalse(answer.next());
answer.close();
session1.removeModel(model4URI);
} finally {
session2.close();
}
} finally {
session1.close();
}
} catch (Exception e) {
fail(e);
}
}
/**
* Test two insertions in the same transaction using the same variable name.
* The variable blank node should map to different internal node ID's in each
* insert operation.
*/
public void testInsertionBlankNodes() {
logger.info("testInsertionBlankNodes");
try {
Session session = database.newSession();
try {
session.createModel(model2URI, null);
session.setAutoCommit(false);
URIReference refA = new URIReferenceImpl(URI.create("test:a"));
URIReference refP1 = new URIReferenceImpl(URI.create("test:p1"));
URIReference refP2 = new URIReferenceImpl(URI.create("test:p2"));
Literal o1 = new LiteralImpl("o1");
Literal o2 = new LiteralImpl("o2");
BlankNode bn = new VariableNodeImpl("bn");
Set<Triple> insert1 = new HashSet<Triple>();
insert1.add(new TripleImpl(refA, refP1, bn));
insert1.add(new TripleImpl(bn, refP2, o1));
Set<Triple> insert2 = new HashSet<Triple>();
insert2.add(new TripleImpl(refA, refP1, bn));
insert2.add(new TripleImpl(bn, refP2, o2));
session.insert(model2URI, insert1);
session.insert(model2URI, insert2);
session.setAutoCommit(true);
Answer answer = session.query(createQuery(model2URI));
assertEquals(4, answer.getRowCount());
} finally {
session.close();
}
} catch (Exception e) {
fail(e);
}
}
/**
* Test two simultaneous, explicit transactions, in two threads. The second one should block
* until the first one sets auto-commit back to true.
*/
public void testConcurrentExplicitTxn() throws URISyntaxException {
logger.info("testConcurrentExplicitTxn");
URI fileURI = new File("data/xatest-model1.rdf").toURI();
try {
Session session1 = database.newSession();
try {
session1.createModel(model3URI, null);
session1.setAutoCommit(false);
session1.setModel(model3URI, fileURI);
final boolean[] tx2Started = new boolean[] { false };
Thread t2 = new Thread("tx2Test") {
public void run() {
try {
Session session2 = database.newSession();
try {
session2.setAutoCommit(false);
synchronized (tx2Started) {
tx2Started[0] = true;
tx2Started.notify();
}
// Evaluate the query
Answer answer = session2.query(createQuery(model3URI));
compareResults(expectedResults(), answer);
answer.close();
session2.commit();
session2.setAutoCommit(true);
} finally {
session2.close();
}
} catch (Exception e) {
fail(e);
}
}
};
t2.start();
synchronized (tx2Started) {
if (!tx2Started[0]) {
try {
tx2Started.wait(2000L);
} catch (InterruptedException ie) {
logger.error("wait for tx2-started interrupted", ie);
fail(ie);
}
}
assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
}
session1.commit();
session1.setAutoCommit(true);
synchronized (tx2Started) {
if (!tx2Started[0]) {
try {
tx2Started.wait(2000L);
} catch (InterruptedException ie) {
logger.error("wait for tx2-started interrupted", ie);
fail(ie);
}
assertTrue("second transaction should've started", tx2Started[0]);
}
}
try {
t2.join(2000L);
} catch (InterruptedException ie) {
logger.error("wait for tx2-terminated interrupted", ie);
fail(ie);
}
assertFalse("second transaction should've terminated", t2.isAlive());
session1.removeModel(model3URI);
} finally {
session1.close();
}
} catch (Exception e) {
fail(e);
}
}
/**
* Test two simultaneous transactions, the first one explicit and the second one in auto-commit,
* in two threads. The second one should proceed but not see uncommitted data.
*/
public void testConcurrentImplicitTxn() throws URISyntaxException {
logger.info("testConcurrentImplicitTxn");
URI fileURI = new File("data/xatest-model1.rdf").toURI();
try {
Session session1 = database.newSession();
try {
session1.createModel(model3URI, null);
session1.setAutoCommit(false);
session1.setModel(model3URI, fileURI);
Thread t2 = new Thread("tx2Test") {
public void run() {
try {
Session session2 = database.newSession();
try {
// Evaluate the query
Answer answer = session2.query(createQuery(model3URI));
answer.beforeFirst();
assertFalse(answer.next());
answer.close();
} finally {
session2.close();
}
} catch (Exception e) {
fail(e);
}
}
};
t2.start();
try {
t2.join(2000L);
} catch (InterruptedException ie) {
logger.error("wait for tx2-terminated interrupted", ie);
fail(ie);
}
assertFalse("second transaction should've terminated", t2.isAlive());
session1.commit();
t2 = new Thread("tx2Test") {
public void run() {
try {
Session session2 = database.newSession();
try {
// Evaluate the query
Answer answer = session2.query(createQuery(model3URI));
compareResults(expectedResults(), answer);
answer.close();
} finally {
session2.close();
}
} catch (Exception e) {
fail(e);
}
}
};
t2.start();
try {
t2.join(2000L);
} catch (InterruptedException ie) {
logger.error("wait for tx2-terminated interrupted", ie);
fail(ie);
}
assertFalse("second transaction should've terminated", t2.isAlive());
session1.setAutoCommit(true);
session1.removeModel(model3URI);
} finally {
session1.close();
}
} catch (Exception e) {
fail(e);
}
}
/**
* Test two simultaneous transactions, the first one obtains the write-lock
* and sleeps longer than the recovery timeout.
*/
public void testConcurrentImplicitRecovery() throws URISyntaxException {
logger.info("testConcurrentImplicitRecovery");
URI fileURI = new File("data/xatest-model1.rdf").toURI();
// test idle timeout
try {
Session session1 = database.newSession();
session1.setIdleTimeout(1000);
try {
session1.createModel(model3URI, null);
logger.debug("Obtaining autocommit for session1");
session1.setAutoCommit(false);
logger.debug("Obtained autocommit for session1");
Thread t2 = new Thread("tx2IdleTest") {
public void run() {
try {
Session session2 = database.newSession();
try {
logger.debug("Obtaining autocommit for session2");
session2.setAutoCommit(false);
logger.debug("Obtained autocommit for session2");
// Evaluate the query
Answer answer = session2.query(createQuery(model3URI));
answer.beforeFirst();
assertFalse(answer.next());
answer.close();
logger.debug("Releasing autocommit for session2");
session2.setAutoCommit(true);
} finally {
session2.close();
}
} catch (Exception e) {
fail(e);
}
}
};
t2.start();
session1.setModel(model3URI, fileURI);
logger.debug("Sleeping for 1sec");
Thread.sleep(1000);
logger.debug("Slept for 1sec");
try {
t2.join(2000L);
} catch (InterruptedException ie) {
logger.error("wait for tx2-terminated interrupted", ie);
fail(ie);
}
assertFalse("second transaction should've terminated", t2.isAlive());
rollbackTimedOutTxn(session1);
} finally {
session1.close();
}
} catch (Exception e) {
fail(e);
}
// test transaction timeout
try {
Session session1 = database.newSession();
session1.setTransactionTimeout(1000);
try {
session1.createModel(model3URI, null);
logger.debug("Obtaining autocommit for session1");
session1.setAutoCommit(false);
logger.debug("Obtained autocommit for session1");
Thread t2 = new Thread("tx2TxToTest") {
public void run() {
try {
Session session2 = database.newSession();
try {
logger.debug("Obtaining autocommit for session2");
session2.setAutoCommit(false);
logger.debug("Obtained autocommit for session2");
// Evaluate the query
Answer answer = session2.query(createQuery(model3URI));
answer.beforeFirst();
assertFalse(answer.next());
answer.close();
logger.debug("Releasing autocommit for session2");
session2.setAutoCommit(true);
} finally {
session2.close();
}
} catch (Exception e) {
fail(e);
}
}
};
t2.start();
session1.setModel(model3URI, fileURI);
logger.debug("Sleeping for 1sec");
Thread.sleep(1000);
logger.debug("Slept for 1sec");
try {
t2.join(2000L);
} catch (InterruptedException ie) {
logger.error("wait for tx2-terminated interrupted", ie);
fail(ie);
}
assertFalse("second transaction should've terminated", t2.isAlive());
rollbackTimedOutTxn(session1);
} finally {
session1.close();
}
} catch (Exception e) {
fail(e);
}
// test transaction timeout interrupting active operation
try {
Session session1 = database.newSession();
try {
session1.setTransactionTimeout(1000L);
session1.setAutoCommit(false);
logger.debug("Started transaction for session1");
URI delayTwoSecs = new URI("foo://mulgara/timeoutTest?active=mr&wait=2000");
session1.createModel(delayTwoSecs, new URI(Mulgara.NAMESPACE + "MockModel"));
try {
session1.query(createQuery(delayTwoSecs));
fail("query should've gotten interrupted");
} catch (QueryException qe) {
logger.debug("query was interrupted", qe);
}
rollbackTimedOutTxn(session1);
} finally {
session1.close();
}
} catch (Exception e) {
fail(e);
}
// test transaction timeout timing out not-yet-active operation
try {
Session session1 = database.newSession();
try {
session1.setTransactionTimeout(1000L);
session1.setAutoCommit(false);
logger.debug("Started transaction for session1");
logger.debug("Sleeping for 2sec");
Thread.sleep(2000);
logger.debug("Slept for 2sec");
try {
session1.query(createQuery(model3URI));
fail("query should've gotten interrupted");
} catch (QueryException qe) {
logger.debug("query was interrupted", qe);
}
rollbackTimedOutTxn(session1);
} finally {
session1.close();
}
} catch (Exception e) {
fail(e);
}
// test transaction timeout while operation is waiting for mutex
try {
final Session session1 = database.newSession();
try {
session1.setTransactionTimeout(1000L);
session1.setAutoCommit(false);
logger.debug("Started transaction for session1");
final URI delayTwoSecs = new URI("foo://mulgara/timeoutTest?active=mr&hardWait=2000");
session1.createModel(delayTwoSecs, new URI(Mulgara.NAMESPACE + "MockModel"));
Thread t2 = new Thread("timeoutTest") {
public void run() {
try {
try {
// this acquires mutex and holds it for 2s
Answer answer = session1.query(createQuery(delayTwoSecs));
Thread.sleep(100L); // allow rollback to proceed
answer.beforeFirst();
assertFalse(answer.next());
answer.close();
fail("query should've gotten interrupted");
} catch (TuplesException te) {
logger.debug("query was interrupted", te);
}
} catch (Exception e) {
fail(e);
}
}
};
t2.start();
Thread.sleep(100L);
// blocks, waiting for mutex; when it does get it, it should rollback or see a rb
try {
Answer answer = session1.query(createQuery(model3URI));
Thread.sleep(100L); // allow rollback to proceed
answer.beforeFirst();
assertFalse(answer.next());
answer.close();
fail("query should've gotten aborted");
} catch (QueryException qe) {
logger.debug("query was aborted", qe);
} catch (TuplesException te) {
logger.debug("query was aborted", te);
}
rollbackTimedOutTxn(session1);
} finally {
session1.close();
}
} catch (Exception e) {
fail(e);
}
// test transaction timeout on autocommit transaction
try {
final Session session1 = database.newSession();
try {
session1.setTransactionTimeout(1000L);
final URI delayTwoSecs = new URI("foo://mulgara/timeoutTest?active=mr&hardWait=2000");
session1.createModel(delayTwoSecs, new URI(Mulgara.NAMESPACE + "MockModel"));
try {
// this acquires mutex and holds it for 2s
Answer answer = session1.query(createQuery(delayTwoSecs));
Thread.sleep(100L);
answer.beforeFirst();
assertFalse(answer.next());
answer.close();
fail("query should've gotten interrupted");
} catch (TuplesException te) {
logger.debug("query was interrupted", te);
}
} finally {
session1.close();
}
} catch (Exception e) {
fail(e);
}
}
private void rollbackTimedOutTxn(Session session) throws QueryException {
try {
session.commit();
fail("Commit should have failed due to transaction timeout");
} catch (QueryException qe) {
}
logger.debug("Rolling back transaction on session");
session.rollback();
session.setAutoCommit(true);
}
public void testPrefixingWithUnbound() throws URISyntaxException {
logger.warn("testPrefixingWithUnbound");
URI fileURI = new File("data/prefix-unbound.rdf").toURI();
try {
Session session = database.newSession();
try {
session.createModel(model5URI, null);
session.setModel(model5URI, fileURI);
Variable varA = new Variable("a");
Variable varB = new Variable("b");
Variable varT = new Variable("t");
List<SelectElement> selectList = new ArrayList<SelectElement>(2);
selectList.add(varA);
selectList.add(varT);
// Check data loaded
Answer answer = session.query(new Query(
selectList, // SELECT
new GraphResource(model5URI), // FROM
new ConstraintConjunction(Arrays.asList(
new ConstraintExpression[] {
new ConstraintImpl(varB,
new URIReferenceImpl(new URI("test:p01")),
new URIReferenceImpl(new URI("test:o01"))),
new ConstraintImpl(varB,
new URIReferenceImpl(new URI("test:p02")),
varA),
new ConstraintDisjunction(
new ConstraintConjunction(
new ConstraintImpl(varB,
new URIReferenceImpl(new URI("test:p03")),
new URIReferenceImpl(new URI("test:o03"))),
new ConstraintIs(varT,
new URIReferenceImpl(new URI("result:0")))),
new ConstraintIs(varT,
new URIReferenceImpl(new URI("result:1")))),
})), // WHERE
null, // HAVING
Arrays.asList(new Order[] { // ORDER BY
new Order(varA, true),
new Order(varT, true),
}),
null, // LIMIT
0, // OFFSET
true, // DISTINCT
new UnconstrainedAnswer() // GIVEN
));
String[][] results = {
{ "test:o02", "result:0" },
{ "test:o02", "result:1" },
{ "test:o04", "result:0" },
{ "test:o04", "result:1" },
};
answer.beforeFirst();
String s = "comparing results:\n" + answer.getVariables();
while (answer.next()) {
s += "\n[";
for (int i = 0; i < answer.getNumberOfVariables(); i++) {
s += " " + answer.getObject(i);
}
s += " ]";
}
compareResults(results, answer);
answer.close();
session.removeModel(model5URI);
} finally {
session.close();
}
} catch (Exception e) {
fail(e);
}
}
public void testDatabaseDelete() {
database.delete();
database = null;
}
/**
* Test various transaction operations in auto-commit mode.
*/
public void testAutoCommitTransactionOps() {
logger.info("Testing autoCommitTransactionOps");
try {
Session session = database.newSession();
try {
// should be a no-op
session.setAutoCommit(true);
// commit should not be allowed
try {
session.commit();
fail("Commit did not fail in auto-commit mode");
} catch (QueryException qe) {
}
// rollback should not be allowed
try {
session.rollback();
fail("Rollback did not fail in auto-commit mode");
} catch (QueryException qe) {
}
// verify we're still good to go
session.query(createQuery(modelURI)).close();
// verify second setAutoCommit(false) is a no-op
session.createModel(model3URI, null);
session.setAutoCommit(false);
session.setModel(model3URI, new File("data/xatest-model1.rdf").toURI());
session.setAutoCommit(false);
session.commit();
Answer answer = session.query(createQuery(model3URI));
compareResults(expectedResults(), answer);
answer.close();
session.removeModel(model3URI);
} finally {
session.close();
}
} catch (Exception e) {
fail(e);
}
}
/**
* Test various operations after an explicit transaction fails.
*/
public void testExplicitTransactionFailure() {
logger.info("Testing transactionExplicitFailure");
try {
// query after failure should fail
shouldFailExplicit(new TestOp() {
public void run(Session session) throws Exception {
session.query(createQuery(modelURI)).close();
}
}, "Query in failed transaction did not fail");
// insert after failure should fail
shouldFailExplicit(new TestOp() {
public void run(Session session) throws Exception {
session.insert(modelURI, Collections.singleton((Triple)new TripleImpl(
new URIReferenceImpl(URI.create("test:a")),
new URIReferenceImpl(URI.create("test:b")),
new URIReferenceImpl(URI.create("test:c")))));
}
}, "Insert in failed transaction did not fail");
// commit after failure should fail
shouldFailExplicit(new TestOp() {
public void run(Session session) throws Exception {
session.commit();
}
}, "Commit in failed transaction did not fail");
// rollback after failure should succeed
shouldSucceedExplicit(new TestOp() {
public void run(Session session) throws Exception {
session.rollback();
}
});
// setAutoCommit(false) after failure should fail
shouldFailExplicit(new TestOp() {
public void run(Session session) throws Exception {
session.setAutoCommit(false);
}
}, "setAutoCommit(false) in failed transaction did not fail");
// setAutoCommit(true) after failure should succeed
shouldSucceedExplicit(new TestOp() {
public void run(Session session) throws Exception {
session.setAutoCommit(true);
}
});
} catch (Exception e) {
fail(e);
}
}
private void shouldFailExplicit(TestOp op, String msg) throws Exception {
testExplicitTransactionFailureOp(op, true, msg);
}
private void shouldSucceedExplicit(TestOp op) throws Exception {
testExplicitTransactionFailureOp(op, false, null);
}
private void testExplicitTransactionFailureOp(TestOp op, boolean shouldFail, String msg) throws Exception {
Session session = database.newSession();
try {
// start tx
session.setAutoCommit(false);
// run bad query -> failed tx
try {
session.query(createQuery(URI.create("urn:no:such:model")));
fail("Bad query failed to throw exception");
} catch (QueryException qe) {
}
// run test op, verify it succeeds/fails, and reset
if (shouldFail) {
try {
op.run(session);
fail(msg);
} catch (QueryException qe) {
}
session.setAutoCommit(true);
} else {
op.run(session);
}
// verify we're good to go
session.query(createQuery(modelURI)).close();
session.setAutoCommit(false);
session.query(createQuery(modelURI)).close();
session.commit();
} finally {
session.close();
}
}
/**
* Test various operations after an operation in auto-commit fails.
*/
public void testAutoCommitTransactionFailure() {
logger.info("Testing transactionAutoCommitFailure");
try {
// query after failure should succeed
shouldSucceedImplicit(new TestOp() {
public void run(Session session) throws Exception {
session.query(createQuery(modelURI)).close();
}
});
// insert after failure should succeed
shouldSucceedImplicit(new TestOp() {
public void run(Session session) throws Exception {
session.insert(modelURI, Collections.singleton((Triple)new TripleImpl(
new URIReferenceImpl(URI.create("test:a")),
new URIReferenceImpl(URI.create("test:b")),
new URIReferenceImpl(URI.create("test:c")))));
}
});
// commit should fail
shouldFailImplicit(new TestOp() {
public void run(Session session) throws Exception {
session.commit();
}
}, "Commit after failed query did not fail");
// rollback should fail
shouldFailImplicit(new TestOp() {
public void run(Session session) throws Exception {
session.rollback();
}
}, "Rollback after failed query did not fail");
// setAutoCommit(false) after failure should succeed
shouldSucceedImplicit(new TestOp() {
public void run(Session session) throws Exception {
session.setAutoCommit(false);
session.setAutoCommit(true);
}
});
// setAutoCommit(true) after failure should succeed
shouldSucceedImplicit(new TestOp() {
public void run(Session session) throws Exception {
session.setAutoCommit(true);
}
});
} catch (Exception e) {
fail(e);
}
}
private void shouldFailImplicit(TestOp op, String msg) throws Exception {
testImplicitTransactionFailureOp(op, true, msg);
}
private void shouldSucceedImplicit(TestOp op) throws Exception {
testImplicitTransactionFailureOp(op, false, null);
}
private void testImplicitTransactionFailureOp(TestOp op, boolean shouldFail, String msg) throws Exception {
Session session = database.newSession();
try {
// run bad query -> failed tx
try {
session.query(createQuery(URI.create("urn:no:such:model")));
fail("Bad query failed to throw exception");
} catch (QueryException qe) {
}
// run test op, verify it succeeds/fails
if (shouldFail) {
try {
op.run(session);
fail(msg);
} catch (QueryException qe) {
}
} else {
op.run(session);
}
// verify we're good to go
session.query(createQuery(modelURI)).close();
session.setAutoCommit(false);
session.query(createQuery(modelURI)).close();
session.setAutoCommit(true);
} finally {
session.close();
}
}
private static interface TestOp {
public void run(Session session) throws Exception;
}
/**
* Test session close on still-active session.
*/
public void testSessionClose() {
logger.info("Testing sessionClose");
// test close while waiting for write-lock
try {
Session session1 = database.newSession();
session1.setAutoCommit(false);
try {
final Session session2 = database.newSession();
Thread t1 = new Thread("closeTest") {
public void run() {
try {
session2.setAutoCommit(false);
fail("Acquired write-lock unexpectedly");
} catch (QueryException qe) {
logger.debug("Caught expected exception", qe);
} finally {
try {
session2.close();
} catch (QueryException qe) {
logger.debug("Caught expected exception", qe);
}
}
}
};
t1.start();
Thread.sleep(100L); // give thread some time to start and block
assertTrue("second session should still be active", t1.isAlive());
session2.close();
try {
t1.join(100L);
} catch (InterruptedException ie) {
// this could be the interrupt from the close(), so try again
try {
t1.join(100L);
} catch (InterruptedException ie2) {
logger.error("wait for thread-termination interrupted", ie2);
fail(ie2);
}
}
assertFalse("second session should've terminated", t1.isAlive());
} finally {
logger.debug("Releasing autocommit for session1");
session1.setAutoCommit(true);
session1.close();
}
} catch (Exception e) {
fail(e);
}
// test close while operation active in auto-commit
try {
final Session session1 = database.newSession();
final URI delayTwoSecs = new URI("foo://mulgara/closeTest?active=l&hardWait=1000");
session1.createModel(delayTwoSecs, new URI(Mulgara.NAMESPACE + "MockModel"));
final boolean[] closing = new boolean[1];
try {
Thread t1 = new Thread("closeTest") {
public void run() {
try {
Answer answer = session1.query(createQuery(delayTwoSecs));
answer.close();
synchronized (closing) {
assertTrue("close didn't block", closing[0]);
}
} catch (Exception e) {
fail(e);
}
}
};
t1.start();
Thread.sleep(100L); // give thread some time to start and block
assertTrue("query should still be active", t1.isAlive());
synchronized (closing) { closing[0] = true; }
session1.close();
synchronized (closing) { closing[0] = false; }
try {
t1.join(100L);
} catch (InterruptedException ie) {
// this could be the interrupt from the close(), so try again
try {
t1.join(100L);
} catch (InterruptedException ie2) {
logger.error("wait for thread-termination interrupted", ie2);
fail(ie2);
}
}
assertFalse("second session should've terminated", t1.isAlive());
} finally {
session1.close();
}
} catch (Exception e) {
fail(e);
}
}
//
// Internal methods
//
private Query createQuery(URI model) {
Variable subjectVariable = new Variable("subject");
Variable predicateVariable = new Variable("predicate");
Variable objectVariable = new Variable("object");
List<SelectElement> selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(predicateVariable);
selectList.add(objectVariable);
return new Query(
selectList, // SELECT
new GraphResource(model), // FROM
new ConstraintImpl(subjectVariable, // WHERE
predicateVariable,
objectVariable),
null, // HAVING
Arrays.asList(new Order[] { // ORDER BY
new Order(subjectVariable, true),
new Order(predicateVariable, true),
new Order(objectVariable, true)
}),
null, // LIMIT
0, // OFFSET
true, // DISTINCT
new UnconstrainedAnswer() // GIVEN
);
}
private String[][] expectedResults() {
return new String[][] {
{ "test:s01", "test:p01", "test:o01" },
{ "test:s01", "test:p02", "test:o01" },
{ "test:s01", "test:p02", "test:o02" },
{ "test:s01", "test:p03", "test:o02" },
{ "test:s02", "test:p03", "test:o02" },
{ "test:s02", "test:p04", "test:o02" },
{ "test:s02", "test:p04", "test:o03" },
{ "test:s02", "test:p05", "test:o03" },
{ "test:s03", "test:p01", "test:o01" },
{ "test:s03", "test:p05", "test:o03" },
{ "test:s03", "test:p06", "test:o01" },
{ "test:s03", "test:p06", "test:o03" },
};
}
private void compareResults(String[][] expected, Answer answer) throws Exception {
try {
answer.beforeFirst();
for (int i = 0; i < expected.length; i++) {
assertTrue("Answer short at row " + i, answer.next());
assertEquals(expected[i].length, answer.getNumberOfVariables());
for (int j = 0; j < expected[i].length; j++) {
URIReferenceImpl uri = new URIReferenceImpl(new URI(expected[i][j]));
assertEquals(uri, answer.getObject(j));
}
}
assertFalse(answer.next());
} catch (Exception e) {
logger.error("Failed test - " + answer);
answer.close();
throw e;
}
}
private void compareResults(Answer answer1, Answer answer2) throws Exception {
answer1.beforeFirst();
answer2.beforeFirst();
assertEquals(answer1.getNumberOfVariables(), answer2.getNumberOfVariables());
while (answer1.next()) {
assertTrue(answer2.next());
for (int i = 0; i < answer1.getNumberOfVariables(); i++) {
assertEquals(answer1.getObject(i), answer2.getObject(i));
}
}
assertFalse(answer2.next());
}
public void testCreateModel() throws URISyntaxException {
logger.info("testCreateModel");
try {
// Load some test data
Session session = database.newSession();
try {
session.createModel(modelURI, null);
try {
session.createModel(modelURI, xsdModelTypeURI);
assertFalse("createModel should have thrown QueryException", true);
} catch (QueryException eq) {
// We expect this to be thrown.
}
session.createModel(modelURI, null);
} finally {
session.close();
}
} catch (Exception e) {
fail(e);
}
}
/**
* Fail with an unexpected exception
*/
private void fail(Throwable throwable) {
StringWriter stringWriter = new StringWriter();
throwable.printStackTrace(new PrintWriter(stringWriter));
fail(stringWriter.toString());
}
}