/*
* Copyright 2008 The Topaz Foundation
*
* 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.
*
* Contributions:
*/
package org.mulgara.resolver.lucene;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.apache.log4j.Logger;
import org.jrdf.graph.Literal;
import org.jrdf.graph.URIReference;
import org.mulgara.itql.TqlInterpreter;
import org.mulgara.query.Answer;
import org.mulgara.query.ConstraintConjunction;
import org.mulgara.query.ConstraintExpression;
import org.mulgara.query.Query;
import org.mulgara.query.Variable;
import org.mulgara.query.operation.Modification;
import org.mulgara.query.rdf.LiteralImpl;
import org.mulgara.query.rdf.Mulgara;
import org.mulgara.query.rdf.URIReferenceImpl;
import org.mulgara.resolver.Database;
import org.mulgara.resolver.JotmTransactionManagerFactory;
import org.mulgara.resolver.spi.MutableLocalQuery;
import org.mulgara.resolver.spi.SymbolicTransformationContext;
import org.mulgara.resolver.spi.SymbolicTransformationException;
import org.mulgara.server.Session;
import org.mulgara.util.FileUtil;
/**
* Unit tests for the lucene resolver.
*
* @created 2008-10-13
* @author Ronald Tschalär
* @copyright © 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
* @licence Apache License v2.0
*/
public class LuceneResolverUnitTest extends TestCase {
private static final Logger logger = Logger.getLogger(LuceneResolverUnitTest.class);
private static final URI databaseURI = URI.create("local:database");
private static final URI modelURI = URI.create("local:lucene");
private static final URI luceneModelType = URI.create(Mulgara.NAMESPACE + "LuceneModel");
private final static String textDirectory =
System.getProperty("cvs.root") + File.separator + "data" + File.separator + "fullTextTestData";
private static Database database = null;
private static TqlInterpreter ti = null;
public LuceneResolverUnitTest(String name) {
super(name);
}
public static Test suite() {
TestSuite suite = new TestSuite();
suite.addTest(new LuceneResolverUnitTest("testBasicQueries"));
suite.addTest(new LuceneResolverUnitTest("testSubqueries"));
suite.addTest(new LuceneResolverUnitTest("testSubqueries2"));
suite.addTest(new LuceneResolverUnitTest("testConcurrentQueries"));
suite.addTest(new LuceneResolverUnitTest("testConcurrentReadTransaction"));
suite.addTest(new LuceneResolverUnitTest("testTransactionIsolation"));
suite.addTest(new LuceneResolverUnitTest("testLuceneConstraint"));
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.stringpool.xa11.XA11StringPoolFactory";
// Define the string pool factory
String stringPoolFactoryClassName = "org.mulgara.store.stringpool.xa11.XA11StringPoolFactory";
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.addContentHandler("org.mulgara.content.n3.N3ContentHandler");
database.addResolverFactory("org.mulgara.resolver.lucene.LuceneResolverFactory", persistenceDirectory);
ti = new TqlInterpreter();
// Load some test data
Session session = database.newSession();
try {
URI fileURI = new File(textDirectory + File.separator + "data.n3").toURI();
if (session.modelExists(modelURI)) {
session.removeModel(modelURI);
}
session.createModel(modelURI, luceneModelType);
session.setModel(modelURI, fileURI);
} finally {
session.close();
}
}
}
private synchronized Query parseQuery(String q) throws Exception {
return (Query) ti.parseCommand(q);
}
/**
* The teardown method for JUnit
*/
public void tearDown() {
}
/**
* Basic queries.
*/
public void testBasicQueries() throws Exception {
logger.info("Testing basic queries");
try {
Session session = database.newSession();
try {
// Run simple query with variable subject and fixed predicate
String q = "select $s from <foo:bar> where $s <foo:hasText> 'American' in <" + modelURI + ">;";
Answer answer = session.query(parseQuery(q));
compareResults(new String[][] { { "foo:node5" }, { "foo:node6" }, { "foo:node7" } }, answer);
answer.close();
// Run simple query with variable subject and predicate
q = "select $s $p from <foo:bar> where $s $p 'American' in <" + modelURI + ">;";
answer = session.query(parseQuery(q));
compareResults(new String[][] { { "foo:node5", "foo:hasText" },
{ "foo:node6", "foo:hasText" },
{ "foo:node7", "foo:hasText" } },
answer);
answer.close();
// Run simple query with fixed subject and variable predicate
q = "select $p from <foo:bar> where <foo:node6> $p 'American' in <" + modelURI + ">;";
answer = session.query(parseQuery(q));
compareResults(new String[][] { { "foo:hasText" } }, answer);
answer.close();
// Run simple query with fixed subject and variable predicate and object
q = "select $p $o from <foo:bar> where <foo:node9> $p $o in <" + modelURI + ">;";
answer = session.query(parseQuery(q));
compareResults(new String[][] { { "foo:hasText", "Antibiotic Use Working Group" } }, answer, true);
answer.close();
// Run simple query with fixed predicate and variable subject and object
q = "select $s $o from <foo:bar> where $s <foo:hasText> $o in <" + modelURI + "> order by $s limit 3;";
answer = session.query(parseQuery(q));
compareResults(new String[][] {
{ "foo:node1", "AACP Pneumothorax Consensus Group" },
{ "foo:node10", "Atypical Squamous Cells Intraepithelial" },
{ "foo:node11", "Lesion Triage Study (ALTS) Group" },
},
answer, true);
answer.close();
// Run simple query with variable subject, predicate, and object
q = "select $s $p $o from <foo:bar> where $s $p $o in <" + modelURI + "> order by $s limit 3;";
answer = session.query(parseQuery(q));
compareResults(new String[][] {
{ "foo:node1", "foo:hasText", "AACP Pneumothorax Consensus Group" },
{ "foo:node10", "foo:hasText", "Atypical Squamous Cells Intraepithelial" },
{ "foo:node11", "foo:hasText", "Lesion Triage Study (ALTS) Group" },
},
answer, true);
answer.close();
// Run extended query with variable subject and fixed predicate
q = "select $s from <foo:bar> where $s <mulgara:search> $b in <" + modelURI + "> and $b <foo:hasText> 'American' in <" + modelURI + ">;";
answer = session.query(parseQuery(q));
compareResults(new String[][] { { "foo:node5" }, { "foo:node6" }, { "foo:node7" } }, answer);
answer.close();
// Run extended query with variable subject and predicate
q = "select $s $p from <foo:bar> where $s <mulgara:search> $b in <" + modelURI + "> and $b $p 'American' in <" + modelURI + ">;";
answer = session.query(parseQuery(q));
compareResults(new String[][] { { "foo:node5", "foo:hasText" },
{ "foo:node6", "foo:hasText" },
{ "foo:node7", "foo:hasText" } },
answer);
answer.close();
// Run extended query with fixed subject and variable predicate
q = "select $p from <foo:bar> where <foo:node6> <mulgara:search> $b in <" + modelURI + "> and $b $p 'American' in <" + modelURI + ">;";
answer = session.query(parseQuery(q));
compareResults(new String[][] { { "foo:hasText" } }, answer);
answer.close();
} finally {
session.close();
}
} catch (Exception e) {
fail(e);
}
}
/**
* Subqueries.
*/
public void testSubqueries() throws Exception {
logger.info("Testing subqueries");
try {
Session session = database.newSession();
try {
// Lucene query in outer query
String q = "select $s subquery(select $y $z from <" + modelURI + "> where $s $y $z)" +
" from <foo:bar> where $s $p 'b*' in <" + modelURI + "> order by $s;";
Answer answer = session.query(parseQuery(q));
compareResults(new Object[][] {
{ "foo:node13", new Object[][] { { "foo:hasText", "Benefit Evaluation of Direct Coronary Stenting Study Group" } } },
{ "foo:node14", new Object[][] { { "foo:hasText", "Biomarkers Definitions Working Group." } } },
}, answer, true);
answer.close();
// Lucene query in both
q = "select $x subquery(select $y from <foo:bar> where $x $y 'a*' in <" + modelURI + ">) " +
" from <foo:bar> where $x <foo:hasText> 'Group' in <" + modelURI + "> order by $x;";
answer = session.query(parseQuery(q));
compareResults(new Object[][] {
{ "foo:node1", new Object[][] { { "foo:hasText" } } },
{ "foo:node11", new Object[][] { { "foo:hasText" } } },
{ "foo:node12", new Object[][] { { "foo:hasText" } } },
{ "foo:node13", new Object[][] { } },
{ "foo:node14", new Object[][] { } },
{ "foo:node18", new Object[][] { } },
{ "foo:node2", new Object[][] { { "foo:hasText" } } },
{ "foo:node4", new Object[][] { { "foo:hasText" } } },
{ "foo:node9", new Object[][] { { "foo:hasText" } } },
}, answer);
answer.close();
} finally {
session.close();
}
} catch (Exception e) {
fail(e);
}
}
/**
* Subqueries.
*/
public void testSubqueries2() throws Exception {
logger.info("Testing subqueries2");
try {
Session session = database.newSession();
try {
// create models
URI dataModel = new URI("local:sampledata");
URI textModel = new URI("local:sampletext");
if (session.modelExists(dataModel)) session.removeModel(dataModel);
if (session.modelExists(textModel)) session.removeModel(textModel);
session.createModel(dataModel, null);
session.createModel(textModel, luceneModelType);
// load models
URI fileURI = new File(new File(System.getProperty("cvs.root"), "data"), "w3c-news.rss").toURI();
session.setModel(dataModel, fileURI);
String q = "select $s $p $o from <local:sampledata> where $s $p $o and (" +
" $p <mulgara:is> <http://purl.org/rss/1.0/description> or " +
" $p <mulgara:is> <http://purl.org/rss/1.0/title>);";
session.insert(textModel, parseQuery(q));
// run queries
q = "select $s subquery(select $z from <local:sampledata> where $s <http://purl.org/rss/1.0/title> $z) " +
" from <local:sampledata> where $s $p $o and $s $p 'W*' in <local:sampletext> order by $s;";
Answer answer = session.query(parseQuery(q));
compareResults(new Object[][] {
{ "http://www.w3.org/2000/08/w3c-synd/home.rss", new Object[][] { { "The World Wide Web Consortium" } } },
{ "http://www.w3.org/News/2002#item12", new Object[][] { { "W3C Launches Web Services Activity" } } },
{ "http://www.w3.org/News/2002#item13", new Object[][] { { "Platform for Privacy Preferences (P3P) Becomes a W3C Proposed Recommendation" } } },
{ "http://www.w3.org/News/2002#item14", new Object[][] { { "XHTML+SMIL Profile Published" } } },
{ "http://www.w3.org/News/2002#item15", new Object[][] { { "W3C Team Presentations in February" } } },
{ "http://www.w3.org/News/2002#item16", new Object[][] { { "QA Framework First Public Working Drafts Published" } } },
{ "http://www.w3.org/News/2002#item17", new Object[][] { { "DOM Level 3 Working Drafts Published" } } },
{ "http://www.w3.org/News/2002#item18", new Object[][] { { "P3P Deployment Guide Updated" } } },
}, answer, true);
answer.close();
q = "select $s subquery(select $z from <local:sampledata> where $s <http://purl.org/rss/1.0/title> $z and $s <http://purl.org/rss/1.0/title> 'W*' in <local:sampletext>) " +
" from <local:sampledata> where $s <http://purl.org/rss/1.0/title> $o order by $s;";
answer = session.query(parseQuery(q));
compareResults(new Object[][] {
{ "http://www.w3.org/2000/08/w3c-synd/home.rss", new Object[][] { { "The World Wide Web Consortium" } } },
{ "http://www.w3.org/News/2002#item12", new Object[][] { { "W3C Launches Web Services Activity" } } },
{ "http://www.w3.org/News/2002#item13", new Object[][] { { "Platform for Privacy Preferences (P3P) Becomes a W3C Proposed Recommendation" } } },
{ "http://www.w3.org/News/2002#item14", new Object[][] { } },
{ "http://www.w3.org/News/2002#item15", new Object[][] { { "W3C Team Presentations in February" } } },
{ "http://www.w3.org/News/2002#item16", new Object[][] { { "QA Framework First Public Working Drafts Published" } } },
{ "http://www.w3.org/News/2002#item17", new Object[][] { { "DOM Level 3 Working Drafts Published" } } },
{ "http://www.w3.org/News/2002#item18", new Object[][] { } },
}, answer, true);
answer.close();
} finally {
session.close();
}
} catch (Exception e) {
fail(e);
}
}
/**
* Two queries, in parallel.
*/
public void testConcurrentQueries() throws Exception {
logger.info("Testing concurrentQueries");
try {
Session session = database.newSession();
// Run the queries
try {
String q = "select $x from <foo:bar> where $x <foo:hasText> 'American' in <" + modelURI + ">;";
Query qry1 = parseQuery(q);
Query qry2 = parseQuery(q);
Answer answer1 = session.query(qry1);
Answer answer2 = session.query(qry2);
compareResults(answer1, answer2);
answer1.close();
answer2.close();
} finally {
session.close();
}
} catch (Exception e) {
fail(e);
}
}
/**
* Two queries, in concurrent transactions.
*/
public void testConcurrentReadTransaction() throws Exception {
logger.info("Testing concurrentReadTransaction");
try {
Session session1 = database.newSession();
try {
XAResource resource1 = session1.getReadOnlyXAResource();
Xid xid1 = new TestXid(1);
resource1.start(xid1, XAResource.TMNOFLAGS);
final boolean[] flag = new boolean[] { false };
Thread t2 = new Thread("tx2Test") {
public void run() {
try {
Session session2 = database.newSession();
try {
XAResource resource2 = session2.getReadOnlyXAResource();
Xid xid2 = new TestXid(2);
resource2.start(xid2, XAResource.TMNOFLAGS);
synchronized (flag) {
flag[0] = true;
flag.notify();
}
// Evaluate the query
String q = "select $x from <foo:bar> where $x <foo:hasText> 'Study' in <" + modelURI + ">;";
Answer answer = session2.query(parseQuery(q));
compareResults(expectedStudyResults(), answer);
answer.close();
synchronized (flag) {
while (flag[0])
flag.wait();
}
resource2.end(xid2, XAResource.TMSUCCESS);
resource2.commit(xid2, true);
} finally {
session2.close();
}
} catch (Exception e) {
fail(e);
}
}
};
t2.start();
synchronized (flag) {
if (!flag[0]) {
try {
flag.wait(2000L);
} catch (InterruptedException ie) {
logger.error("wait for tx2-started interrupted", ie);
fail(ie);
}
}
assertTrue("second transaction should have proceeded", flag[0]);
}
String q = "select $x from <foo:bar> where $x <foo:hasText> 'Group' in <" + modelURI + ">;";
Answer answer = session1.query(parseQuery(q));
compareResults(expectedGroupResults(), answer);
answer.close();
synchronized (flag) {
flag[0] = false;
flag.notify();
}
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());
resource1.end(xid1, XAResource.TMSUCCESS);
resource1.commit(xid1, true);
} finally {
session1.close();
}
} catch (Exception e) {
fail(e);
}
}
/**
* Two concurrent transactions, one reader, one writer. Verify transaction isolation.
*/
public void testTransactionIsolation() throws Exception {
logger.info("Testing transactionIsolation");
try {
Session session1 = database.newSession();
try {
// start read-only txn
XAResource resource1 = session1.getReadOnlyXAResource();
Xid xid1 = new TestXid(1);
resource1.start(xid1, XAResource.TMNOFLAGS);
// run query before second txn starts
String q = "select $x from <foo:bar> where $x <foo:hasText> 'Group' in <" + modelURI + ">;";
Answer answer = session1.query(parseQuery(q));
compareResults(expectedGroupResults(), answer);
answer.close();
// run a second transaction that writes new data
final boolean[] flag = new boolean[] { false };
Thread t2 = new Thread("tx2Test") {
public void run() {
try {
Session session2 = database.newSession();
try {
XAResource resource2 = session2.getXAResource();
Xid xid2 = new TestXid(2);
resource2.start(xid2, XAResource.TMNOFLAGS);
synchronized (flag) {
flag[0] = true;
flag.notify();
while (flag[0])
flag.wait();
}
String q = "insert <foo:nodeX> <foo:hasText> 'Another Group text' into <" + modelURI + ">;";
synchronized (LuceneResolverUnitTest.this) {
session2.insert(modelURI, ((Modification) ti.parseCommand(q)).getStatements());
}
synchronized (flag) {
flag[0] = true;
flag.notify();
while (flag[0])
flag.wait();
}
resource2.end(xid2, XAResource.TMSUCCESS);
resource2.commit(xid2, true);
} finally {
session2.close();
}
} catch (Exception e) {
fail(e);
}
}
};
t2.start();
// wait for 2nd txn to have started
synchronized (flag) {
while (!flag[0])
flag.wait();
}
// run query before insert
answer = session1.query(parseQuery(q));
compareResults(expectedGroupResults(), answer);
answer.close();
// wait for insert to complete
synchronized (flag) {
flag[0] = false;
flag.notify();
while (!flag[0])
flag.wait();
}
// run query after insert and before commit
answer = session1.query(parseQuery(q));
compareResults(expectedGroupResults(), answer);
answer.close();
// wait for commit to complete
synchronized (flag) {
flag[0] = false;
flag.notify();
}
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());
// run query after commit
answer = session1.query(parseQuery(q));
compareResults(expectedGroupResults(), answer);
answer.close();
// clean up
resource1.end(xid1, XAResource.TMSUCCESS);
resource1.commit(xid1, true);
// start new tx - we should see new data now
xid1 = new TestXid(3);
resource1.start(xid1, XAResource.TMNOFLAGS);
answer = session1.query(parseQuery(q));
compareResults(concat(expectedGroupResults(), new String[][] { { "foo:nodeX" } }), answer);
answer.close();
resource1.end(xid1, XAResource.TMSUCCESS);
resource1.commit(xid1, true);
} finally {
session1.close();
}
} catch (Exception e) {
fail(e);
}
}
/**
* Test LuceneConstraint generation.
*/
public void testLuceneConstraint() throws Exception {
logger.info("Testing LuceneConstraint generation");
LuceneTransformer transf = new LuceneTransformer(LuceneResolverFactory.modelTypeURI,
LuceneResolverFactory.searchURI,
LuceneResolverFactory.scoreURI);
Map<URI,URI> modelsToTypes = new HashMap<URI,URI>();
modelsToTypes.put(URI.create("test:lucene"), LuceneResolverFactory.modelTypeURI);
SymbolicTransformationContext context = new TestSymbolicTransformationContext(modelsToTypes);
try {
// simple query
MutableLocalQuery q = new TestMutableLocalQuery(parseQuery(
"select $foo from <test:bar> where $foo <test:title> 'blah' in <test:lucene>;"));
transf.transform(context, q);
ConstraintExpression ce = q.getConstraintExpression();
checkConstraint(ce, "foo", "test:title", "blah", null, null);
// basic complex query
q = new TestMutableLocalQuery(parseQuery(
"select $foo from <test:bar> where " +
"$foo <mulgara:search> $search1 in <test:lucene> and " +
"$search1 <test:title> 'blah' in <test:lucene>;"));
transf.transform(context, q);
ConstraintConjunction cc = checkConstraint(q.getConstraintExpression(), 1);
checkConstraint(cc.getElements().get(0), "foo", "test:title", "blah", "search1", null);
// complex query with score
q = new TestMutableLocalQuery(parseQuery(
"select $foo $score1 from <test:bar> where " +
"$foo <mulgara:search> $search1 in <test:lucene> and " +
"$search1 <test:title> 'blah' in <test:lucene> and " +
"$search1 <mulgara:score> $score1 in <test:lucene>;"));
transf.transform(context, q);
cc = checkConstraint(q.getConstraintExpression(), 1);
checkConstraint(cc.getElements().get(0), "foo", "test:title", "blah", "search1", "score1");
// complex query with score, different constraint order
q = new TestMutableLocalQuery(parseQuery(
"select $foo $score1 from <test:bar> where " +
"$search1 <test:title> 'blah' in <test:lucene> and " +
"$foo <mulgara:search> $search1 in <test:lucene> and " +
"$search1 <mulgara:score> $score1 in <test:lucene>;"));
transf.transform(context, q);
cc = checkConstraint(q.getConstraintExpression(), 1);
checkConstraint(cc.getElements().get(0), "foo", "test:title", "blah", "search1", "score1");
// complex query with score, another different constraint order
q = new TestMutableLocalQuery(parseQuery(
"select $foo $score1 from <test:bar> where " +
"$search1 <mulgara:score> $score1 in <test:lucene> and " +
"$search1 <test:title> 'blah' in <test:lucene> and " +
"$foo <mulgara:search> $search1 in <test:lucene>;"));
transf.transform(context, q);
cc = checkConstraint(q.getConstraintExpression(), 1);
checkConstraint(cc.getElements().get(0), "foo", "test:title", "blah", "search1", "score1");
// two simple queries, shared var
q = new TestMutableLocalQuery(parseQuery(
"select $foo from <test:bar> where " +
"$foo <test:title> 'blah' in <test:lucene> and " +
"$foo <test:author> 'Smith' in <test:lucene>;"));
transf.transform(context, q);
cc = checkConstraint(q.getConstraintExpression(), 2);
checkConstraint(cc.getElements().get(0), "foo", "test:title", "blah", null, null);
checkConstraint(cc.getElements().get(1), "foo", "test:author", "Smith", null, null);
// two simple queries, shared var and predicate
q = new TestMutableLocalQuery(parseQuery(
"select $foo from <test:bar> where " +
"$foo <test:title> 'blah' in <test:lucene> and " +
"$foo <test:title> 'Smith' in <test:lucene>;"));
transf.transform(context, q);
cc = checkConstraint(q.getConstraintExpression(), 2);
checkConstraint(cc.getElements().get(0), "foo", "test:title", "blah", null, null);
checkConstraint(cc.getElements().get(1), "foo", "test:title", "Smith", null, null);
// two simple queries, separate vars
q = new TestMutableLocalQuery(parseQuery(
"select $foo $bar from <test:bar> where " +
"$foo <test:title> 'blah' in <test:lucene> and " +
"$bar <test:author> 'Smith' in <test:lucene>;"));
transf.transform(context, q);
cc = checkConstraint(q.getConstraintExpression(), 2);
checkConstraint(cc.getElements().get(0), "bar", "test:author", "Smith", null, null);
checkConstraint(cc.getElements().get(1), "foo", "test:title", "blah", null, null);
// two complex queries with scores but shared var
q = new TestMutableLocalQuery(parseQuery(
"select $foo $score1 $score2 from <test:bar> where " +
"$foo <mulgara:search> $search1 in <test:lucene> and " +
"$search1 <test:title> 'blah' in <test:lucene> and " +
"$search1 <mulgara:score> $score1 in <test:lucene> and " +
"$foo <mulgara:search> $search2 in <test:lucene> and " +
"$search2 <test:author> 'Smith' in <test:lucene> and " +
"$search2 <mulgara:score> $score2 in <test:lucene>;"));
transf.transform(context, q);
cc = checkConstraint(q.getConstraintExpression(), 2);
checkConstraint(cc.getElements().get(0), "foo", "test:author", "Smith", "search2", "score2");
checkConstraint(cc.getElements().get(1), "foo", "test:title", "blah", "search1", "score1");
// two complex queries with scores and separate vars
q = new TestMutableLocalQuery(parseQuery(
"select $foo $score1 $bar $score2 from <test:bar> where " +
"$foo <mulgara:search> $search1 in <test:lucene> and " +
"$search1 <test:title> 'blah' in <test:lucene> and " +
"$search1 <mulgara:score> $score1 in <test:lucene> and " +
"$bar <mulgara:search> $search2 in <test:lucene> and " +
"$search2 <test:author> 'Smith' in <test:lucene> and " +
"$search2 <mulgara:score> $score2 in <test:lucene>;"));
transf.transform(context, q);
cc = checkConstraint(q.getConstraintExpression(), 2);
checkConstraint(cc.getElements().get(0), "bar", "test:author", "Smith", "search2", "score2");
checkConstraint(cc.getElements().get(1), "foo", "test:title", "blah", "search1", "score1");
// a simple query and a complex query, shared var
q = new TestMutableLocalQuery(parseQuery(
"select $foo $score2 from <test:bar> where " +
"$foo <test:title> 'blah' in <test:lucene> and " +
"$foo <mulgara:search> $search2 in <test:lucene> and " +
"$search2 <test:author> 'Smith' in <test:lucene> and " +
"$search2 <mulgara:score> $score2 in <test:lucene>;"));
transf.transform(context, q);
cc = checkConstraint(q.getConstraintExpression(), 2);
checkConstraint(cc.getElements().get(0), "foo", "test:title", "blah", null, null);
checkConstraint(cc.getElements().get(1), "foo", "test:author", "Smith", "search2", "score2");
// a simple query and a complex query, shared var, different constraint order
q = new TestMutableLocalQuery(parseQuery(
"select $foo $score2 from <test:bar> where " +
"$foo <mulgara:search> $search2 in <test:lucene> and " +
"$search2 <test:author> 'Smith' in <test:lucene> and " +
"$foo <test:title> 'blah' in <test:lucene> and " +
"$search2 <mulgara:score> $score2 in <test:lucene>;"));
transf.transform(context, q);
cc = checkConstraint(q.getConstraintExpression(), 2);
checkConstraint(cc.getElements().get(0), "foo", "test:title", "blah", null, null);
checkConstraint(cc.getElements().get(1), "foo", "test:author", "Smith", "search2", "score2");
// a simple query and a complex query, separate vars
q = new TestMutableLocalQuery(parseQuery(
"select $foo $bar $score2 from <test:bar> where " +
"$foo <test:title> 'blah' in <test:lucene> and " +
"$bar <mulgara:search> $search2 in <test:lucene> and " +
"$search2 <test:author> 'Smith' in <test:lucene> and " +
"$search2 <mulgara:score> $score2 in <test:lucene>;"));
transf.transform(context, q);
cc = checkConstraint(q.getConstraintExpression(), 2);
checkConstraint(cc.getElements().get(0), "foo", "test:title", "blah", null, null);
checkConstraint(cc.getElements().get(1), "bar", "test:author", "Smith", "search2", "score2");
// invalid: complex query with multiple different predicates
q = new TestMutableLocalQuery(parseQuery(
"select $foo $score1 from <test:bar> where " +
"$search1 <test:author> 'Smith' in <test:lucene> and " +
"$foo <mulgara:search> $search1 in <test:lucene> and " +
"$search1 <test:title> 'blah' in <test:lucene> and " +
"$search1 <mulgara:score> $score1 in <test:lucene>;"));
try {
transf.transform(context, q);
fail("query transform should've failed: " + q);
} catch (SymbolicTransformationException ste) {
logger.debug("Caught expected transformation exception", ste);
}
// invalid: complex query with multiple same predicates
q = new TestMutableLocalQuery(parseQuery(
"select $foo $score1 from <test:bar> where " +
"$search1 <test:author> 'Smith' in <test:lucene> and " +
"$foo <mulgara:search> $search1 in <test:lucene> and " +
"$search1 <test:author> 'Jones' in <test:lucene> and " +
"$search1 <mulgara:score> $score1 in <test:lucene>;"));
try {
transf.transform(context, q);
fail("query transform should've failed: " + q);
} catch (SymbolicTransformationException ste) {
logger.debug("Caught expected transformation exception", ste);
}
// invalid: complex query with multiple scores
q = new TestMutableLocalQuery(parseQuery(
"select $foo $score1 from <test:bar> where " +
"$search1 <mulgara:score> $score2 in <test:lucene> and " +
"$foo <mulgara:search> $search1 in <test:lucene> and " +
"$search1 <test:author> 'Jones' in <test:lucene> and " +
"$search1 <mulgara:score> $score1 in <test:lucene>;"));
try {
transf.transform(context, q);
fail("query transform should've failed: " + q);
} catch (SymbolicTransformationException ste) {
logger.debug("Caught expected transformation exception", ste);
}
// invalid: complex query with binder and subject shared
q = new TestMutableLocalQuery(parseQuery(
"select $foo $score2 from <test:bar> where " +
"$foo <mulgara:search> $foo in <test:lucene> and " +
"$foo <test:author> 'Smith' in <test:lucene> and " +
"$foo <mulgara:score> $score2 in <test:lucene>;"));
try {
transf.transform(context, q);
fail("query transform should've failed: " + q);
} catch (SymbolicTransformationException ste) {
logger.debug("Caught expected transformation exception", ste);
}
// invalid: complex query with binder not a variable
q = new TestMutableLocalQuery(parseQuery(
"select $foo $score2 from <test:bar> where " +
"$foo <mulgara:search> <test:it> in <test:lucene> and " +
"<test:it> <test:author> 'Smith' in <test:lucene> and " +
"<test:it> <mulgara:score> $score2 in <test:lucene>;"));
try {
transf.transform(context, q);
fail("query transform should've failed: " + q);
} catch (SymbolicTransformationException ste) {
logger.debug("Caught expected transformation exception", ste);
}
// invalid: complex query with missing predicate
q = new TestMutableLocalQuery(parseQuery(
"select $bar $score2 from <test:bar> where " +
"$bar <mulgara:search> $search2 in <test:lucene> and " +
"$search2 <mulgara:score> $score2 in <test:lucene>;"));
try {
transf.transform(context, q);
fail("query transform should've failed: " + q);
} catch (SymbolicTransformationException ste) {
logger.debug("Caught expected transformation exception", ste);
}
// invalid: complex query with missing <mulgara:search>
q = new TestMutableLocalQuery(parseQuery(
"select $score2 from <test:bar> where " +
"$search2 <test:author> 'Smith' in <test:lucene> and " +
"$search2 <mulgara:score> $score2 in <test:lucene>;"));
try {
transf.transform(context, q);
fail("query transform should've failed: " + q);
} catch (SymbolicTransformationException ste) {
logger.debug("Caught expected transformation exception", ste);
}
} catch (Exception e) {
fail(e);
}
}
/*
* Internal helpers
*/
private static ConstraintConjunction checkConstraint(ConstraintExpression ce, int numConstr) {
assertTrue(ce instanceof ConstraintConjunction);
ConstraintConjunction cc = (ConstraintConjunction)ce;
assertEquals(numConstr, cc.getElements().size());
return cc;
}
private static void checkConstraint(ConstraintExpression ce, String expSubj, String expPred,
String expObj, String expBind, String expScore)
throws Exception {
assertTrue(ce instanceof LuceneConstraint);
LuceneConstraint lc = (LuceneConstraint)ce;
assertEquals(expSubj, ((Variable)lc.getSubject()).getName());
assertTrue(lc.getPredicate() instanceof URIReference);
assertEquals(URI.create(expPred), ((URIReference)lc.getPredicate()).getURI());
assertTrue(lc.getObject() instanceof Literal);
assertEquals(expObj, ((Literal)lc.getObject()).getLexicalForm());
if (expBind != null) {
assertEquals(expBind, lc.getBindingVar().getName());
} else {
assertNull(lc.getBindingVar());
}
if (expScore != null) {
assertEquals(expScore, lc.getScoreVar().getName());
} else {
assertNull(lc.getScoreVar());
}
}
private String[][] expectedStudyResults() {
return new String[][] {
{ "foo:node3" }, { "foo:node4" }, { "foo:node11" }, { "foo:node13" }, { "foo:node19" },
{ "foo:node22" },
};
}
private String[][] expectedGroupResults() {
return new String[][] {
{ "foo:node1" }, { "foo:node2" }, { "foo:node4" }, { "foo:node9" }, { "foo:node11" },
{ "foo:node12" }, { "foo:node13" }, { "foo:node14" }, { "foo:node18" },
};
}
private static String[][] concat(String[][] a1, String[][] a2) {
String[][] res = new String[a1.length + a2.length][];
System.arraycopy(a1, 0, res, 0, a1.length);
System.arraycopy(a2, 0, res, a1.length, a2.length);
return res;
}
private void compareResults(Object[][] expected, Answer answer) throws Exception {
compareResults(expected, answer, false);
}
private void compareResults(Object[][] expected, Answer answer, boolean lastIsLiteral)
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++) {
if (expected[i][j] == null) {
assertNull(answer.getObject(j));
} else if (expected[i][j] instanceof String) {
Object exp = (lastIsLiteral && j == expected[i].length - 1) ?
new LiteralImpl((String) expected[i][j]) :
new URIReferenceImpl(new URI((String) expected[i][j]));
assertEquals(exp, answer.getObject(j));
} else if (expected[i][j] instanceof Object[][]) {
compareResults((Object[][]) expected[i][j], (Answer) answer.getObject(j), lastIsLiteral);
} else {
throw new IllegalArgumentException("Don't know how to handle expected value '" +
expected[i][j] + "' of type " +
expected[i][j].getClass() + "' at index " + i +
"," + j);
}
}
}
assertFalse("Answer too long", answer.next());
} catch (Exception e) {
logger.error("Failed test - \n" + answer);
throw e;
} catch (Error e) {
logger.error("Failed test - \n" + dumpAnswer(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());
}
private static String dumpAnswer(Answer answer, String indent) throws Exception {
StringBuilder sb = new StringBuilder(500);
answer.beforeFirst();
while (answer.next()) {
sb.append(indent).append("next-row\n");
for (int j = 0; j < answer.getNumberOfVariables(); j++) {
sb.append(indent).append(" column: " + answer.getObject(j) + "\n");
if (answer.getObject(j) instanceof Answer) {
sb.append(dumpAnswer((Answer) answer.getObject(j), indent + " "));
}
}
}
sb.append(indent).append("end\n");
return sb.toString();
}
private void fail(Throwable throwable) {
StringWriter stringWriter = new StringWriter();
throwable.printStackTrace(new PrintWriter(stringWriter));
fail(stringWriter.toString());
}
private static class TestXid implements Xid {
private final int xid;
public TestXid(int xid) {
this.xid = xid;
}
public int getFormatId() {
return 'X';
}
public byte[] getBranchQualifier() {
return new byte[] { (byte)(xid >> 0x00), (byte)(xid >> 0x08) };
}
public byte[] getGlobalTransactionId() {
return new byte[] { (byte)(xid >> 0x10), (byte)(xid >> 0x18) };
}
}
private static class TestSymbolicTransformationContext implements SymbolicTransformationContext {
private final Map<URI,URI> mappings;
public TestSymbolicTransformationContext(Map<URI,URI> mappings) {
this.mappings = mappings;
}
public URI mapToModelTypeURI(URI modelURI) {
return mappings.get(modelURI);
}
}
private static class TestMutableLocalQuery implements MutableLocalQuery {
private ConstraintExpression expr;
public TestMutableLocalQuery(Query query) {
expr = query.getConstraintExpression();
}
public ConstraintExpression getConstraintExpression() {
return expr;
}
public void setConstraintExpression(ConstraintExpression newExpr) {
expr = newExpr;
}
}
}