You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2013/04/05 13:26:09 UTC

svn commit: r1464929 - /jena/Experimental/jena-text/larq2-score.patch

Author: andy
Date: Fri Apr  5 11:26:09 2013
New Revision: 1464929

URL: http://svn.apache.org/r1464929
Log:
Patch for exposing the score.

Added:
    jena/Experimental/jena-text/larq2-score.patch

Added: jena/Experimental/jena-text/larq2-score.patch
URL: http://svn.apache.org/viewvc/jena/Experimental/jena-text/larq2-score.patch?rev=1464929&view=auto
==============================================================================
--- jena/Experimental/jena-text/larq2-score.patch (added)
+++ jena/Experimental/jena-text/larq2-score.patch Fri Apr  5 11:26:09 2013
@@ -0,0 +1,794 @@
+diff --git a/src/main/java/org/apache/jena/query/text/QueryPF.java b/src/main/java/org/apache/jena/query/text/QueryPF.java
+index 9f6533e..d5ad2ae 100644
+--- a/src/main/java/org/apache/jena/query/text/QueryPF.java
++++ b/src/main/java/org/apache/jena/query/text/QueryPF.java
+@@ -1,29 +1,34 @@
+ package org.apache.jena.query.text;
+ 
+-import java.util.List ;
+-import java.util.Map ;
+-
+-import org.apache.jena.atlas.iterator.Iter ;
+-import org.apache.jena.atlas.lib.InternalErrorException ;
+-import org.apache.jena.atlas.logging.Log ;
+-
+-import com.hp.hpl.jena.datatypes.RDFDatatype ;
+-import com.hp.hpl.jena.datatypes.xsd.XSDDatatype ;
+-import com.hp.hpl.jena.graph.Node ;
+-import com.hp.hpl.jena.query.QueryBuildException ;
+-import com.hp.hpl.jena.sparql.core.DatasetGraph ;
+-import com.hp.hpl.jena.sparql.core.Var ;
+-import com.hp.hpl.jena.sparql.engine.ExecutionContext ;
+-import com.hp.hpl.jena.sparql.engine.QueryIterator ;
+-import com.hp.hpl.jena.sparql.engine.binding.Binding ;
+-import com.hp.hpl.jena.sparql.engine.iterator.QueryIterExtendByVar ;
+-import com.hp.hpl.jena.sparql.engine.iterator.QueryIterSlice ;
+-import com.hp.hpl.jena.sparql.pfunction.PropFuncArg ;
+-import com.hp.hpl.jena.sparql.pfunction.PropertyFunctionBase ;
+-import com.hp.hpl.jena.sparql.util.IterLib ;
+-import com.hp.hpl.jena.sparql.util.NodeFactory ;
+-
+-/** property function that accesses a Solr server */ 
++import java.util.ArrayList;
++import java.util.Iterator;
++import java.util.List;
++import java.util.Map;
++
++import org.apache.jena.atlas.iterator.Iter;
++import org.apache.jena.atlas.logging.Log;
++
++import com.hp.hpl.jena.datatypes.RDFDatatype;
++import com.hp.hpl.jena.datatypes.xsd.XSDDatatype;
++import com.hp.hpl.jena.graph.Node;
++import com.hp.hpl.jena.query.QueryBuildException;
++import com.hp.hpl.jena.sparql.core.DatasetGraph;
++import com.hp.hpl.jena.sparql.core.Var;
++import com.hp.hpl.jena.sparql.engine.ExecutionContext;
++import com.hp.hpl.jena.sparql.engine.QueryIterator;
++import com.hp.hpl.jena.sparql.engine.binding.Binding;
++import com.hp.hpl.jena.sparql.engine.binding.BindingFactory;
++import com.hp.hpl.jena.sparql.engine.iterator.QueryIter;
++import com.hp.hpl.jena.sparql.engine.iterator.QueryIterSlice;
++import com.hp.hpl.jena.sparql.pfunction.PropFuncArg;
++import com.hp.hpl.jena.sparql.pfunction.PropertyFunctionBase;
++import com.hp.hpl.jena.sparql.util.IterLib;
++import com.hp.hpl.jena.sparql.util.NodeFactory;
++
++/**
++ *  property function that accesses a text index server 
++ *  
++ */ 
+ public class QueryPF extends PropertyFunctionBase
+ {
+     private TextIndex server = null ;  
+@@ -31,6 +36,20 @@ public class QueryPF extends PropertyFunctionBase
+     
+     public QueryPF() { } 
+ 
++    /**
++     * Construct the property function
++     * 
++     * The subject of the property function  may be either:
++     *     ?var              bound to a resource matching the search
++     *     ( ?var ?score? )
++     *     ( ?var ?score)   ?var is bound to a matching resource,
++     *                      ?score is bound to the score assigned to the match
++     *                    
++     * The object of the property may be either:
++     *     "literal"                   A solr/lucene search expression
++     *     ( prop? "literal" limit? )  optional ?prop is the property to search
++     *                                 optional limit is an integer limiting the number of results
++     */
+     @Override
+     public void build(PropFuncArg argSubject, Node predicate, PropFuncArg argObject, ExecutionContext execCxt)
+     {
+@@ -39,8 +58,14 @@ public class QueryPF extends PropertyFunctionBase
+         DatasetGraph dsg = execCxt.getDataset() ;
+         server = chooseTextIndex(dsg) ;
+         
+-        if ( ! argSubject.isNode() )
+-            throw new QueryBuildException("Subject is not a single node: "+argSubject) ;
++        if ( argSubject.isList()) {
++        	List<Node> list = argSubject.getArgList();
++        	if (list.size() == 0) {
++        		throw new QueryBuildException(predicate + ": has empty subject list");
++        	} else if (list.size() > 2) {
++        		throw new QueryBuildException(predicate + ": subject list too long");
++        	}
++        }
+         
+         if ( argObject.isList() )
+         {
+@@ -48,7 +73,7 @@ public class QueryPF extends PropertyFunctionBase
+             if ( list.size() == 0 )
+                 throw new QueryBuildException("Zero-length argument list") ;
+ 
+-            if ( list.size() > 4 )
++            if ( list.size() > 3 )
+                 throw new QueryBuildException("Too many arguments in list : "+list) ;
+         }
+     }
+@@ -66,14 +91,16 @@ public class QueryPF extends PropertyFunctionBase
+         private final String queryString ;
+         private final int limit ;
+         private final float scoreLimit ;
++        private final Node scoreVar;
+ 
+-        public StrMatch(Node property, String queryString, int limit, float scoreLimit)
++        public StrMatch(Node property, String queryString, int limit, float scoreLimit, Node scoreVar)
+         {
+             super() ;
+             this.property = property ;
+             this.queryString = queryString ;
+             this.limit = limit ;
+             this.scoreLimit = scoreLimit ;
++            this.scoreVar = scoreVar;
+         }
+ 
+         public Node getProperty()           { return property ; }
+@@ -83,6 +110,8 @@ public class QueryPF extends PropertyFunctionBase
+         public int getLimit()               { return limit ; }
+ 
+         public float getScoreLimit()        { return scoreLimit ; }
++        
++        public Node getScoreVar()           { return scoreVar ; }
+     }
+     
+     private static TextIndex chooseTextIndex(DatasetGraph dsg)
+@@ -118,18 +147,31 @@ public class QueryPF extends PropertyFunctionBase
+             return IterLib.result(binding, execCxt) ;
+         }
+      
+-        DatasetGraph dsg = execCxt.getDataset() ;
+-        
+-        if ( ! argSubject.isNode() )
+-            throw new InternalErrorException("Subject is not a node (it was earlier!)") ;
+-            
+-        Node s = argSubject.getArg() ;
++ //       DatasetGraph dsg = execCxt.getDataset() ;
+         
++        Node s;
++        Node scoreVar = null;
++        if (argSubject.isNode() )
++        {
++        	s = argSubject.getArg() ;
++        } else {
++        	List<Node> list = argSubject.getArgList();
++        	if (list.size() == 0 || list.size() > 2) {
++        		throw new TextIndexException(predicate + ": change in subject list size");
++        	}
++        	s = list.get(0);
++        	if (list.size() == 2) {
++        		scoreVar = list.get(1);
++        		// this could have been picked up at query build time
++        		if (! scoreVar.isVariable())
++        			throw new TextIndexException(predicate + ": score variable is not a variable");
++        	}
++        }
+         if ( s.isLiteral() )
+             // Does not match
+             return IterLib.noResults(execCxt) ;
+         
+-        StrMatch match = objectToStruct(argObject) ;
++        StrMatch match = objectToStruct(scoreVar, argObject) ;
+ 
+         // ----
+         
+@@ -142,13 +184,18 @@ public class QueryPF extends PropertyFunctionBase
+         return qIter ;
+     }
+ 
+-    private QueryIterator variableSubject(Binding binding, Node s, StrMatch match, ExecutionContext execCxt )
++    private QueryIterator variableSubject(Binding binding, Node subject, StrMatch match, ExecutionContext execCxt )
+     {
+-        Var v = Var.alloc(s) ;
+-        List<Node> r = server.query(match.getQueryString(), match.getLimit()) ;
++        List<Var> vars = new ArrayList<Var>();
++        vars.add(Var.alloc(subject));
++        if (match.getScoreVar() != null)
++            vars.add(Var.alloc(match.getScoreVar()));
++        
++//        List<Node> r = server.query(match.getQueryString(), match.getLimit()) ;
++        List<TextIndex.NodeAndScore> r = server.queryWithScore(match.getQueryString(), match.getLimit());
+         // Make distinct.  Note interaction with limit is imperfect
+         r = Iter.iter(r).distinct().toList() ;
+-        QueryIterator qIter = new QueryIterExtendByVar(binding, v, r.iterator(), execCxt) ;
++        QueryIterator qIter = new QueryIterExtendByVars(binding, vars, new NodeAndScoreToListIterator(r.iterator()), execCxt) ;
+         return qIter ;
+     }
+ 
+@@ -169,9 +216,8 @@ public class QueryPF extends PropertyFunctionBase
+     }
+ 
+     /** Deconstruct the node or list object argument and make a StrMatch */ 
+-    private StrMatch objectToStruct(PropFuncArg argObject)
++    private StrMatch objectToStruct(Node scoreVar, PropFuncArg argObject)
+     {
+-        
+         EntityDefinition docDef = server.getDocDef()  ;
+         if ( argObject.isNode() )
+         {
+@@ -185,7 +231,7 @@ public class QueryPF extends PropertyFunctionBase
+             { System.err.println("Bad") ; }
+                 
+             String qs = o.getLiteralLexicalForm() ;
+-            return new StrMatch(docDef.getPrimaryPredicate(), qs, -1, 0) ; 
++            return new StrMatch(docDef.getPrimaryPredicate(), qs, -1, 0, scoreVar) ; 
+         }
+          
+         List<Node> list = argObject.getArgList() ;
+@@ -226,12 +272,101 @@ public class QueryPF extends PropertyFunctionBase
+             int v = NodeFactory.nodeToInt(x) ;
+             limit = ( v < 0 ) ? -1 : v ; 
+         }
+-
++        
+         String qs = queryString ;
+         if ( field != null )
+             qs = field+":"+qs ;
+         
+-        return new StrMatch(p, qs, limit, score) ;
++        return new StrMatch(p, qs, limit, score, scoreVar) ;
++    }
++    
++    //
++    // turn a NodeAndScoreIterator into an List<Node> iterator
++    //
++    private class NodeAndScoreToListIterator implements Iterator<List<Node>> {
++    	
++    	private Iterator<TextIndex.NodeAndScore> iter;
++    	
++    	NodeAndScoreToListIterator(Iterator<TextIndex.NodeAndScore> iter) {
++    		this.iter = iter;
++    	}
++
++		@Override
++		public boolean hasNext() {
++			return iter.hasNext();
++		}
++
++		@Override
++		public List<Node> next() {
++			List<Node> result = new ArrayList<Node>();
++			TextIndex.NodeAndScore ns = iter.next();
++			result.add(ns.getNode());
++			result.add(Node.createLiteral(Float.toString(ns.getScore()), XSDDatatype.XSDfloat));
++			return result;
++		}
++
++		@Override
++		public void remove() {
++			iter.remove();			
++		}   	
++    }
++    
++    /**
++     * Yield new bindings, with a fixed parent, with multiple variables from an iterator. 
++     */
++    
++    // this code is cloned from QueryIterExtendByVar (not singular Var)
++    // and generalized to handle a list of vars and nodes to bind.
++    // Not being familiar with the internals of ARQ, there may be an existing
++    // query iterator it would be better to use, but bwm didn't find it.
++    
++    private class QueryIterExtendByVars extends QueryIter
++    {
++        // Use QueryIterProcessBinding?
++        private Binding binding ;
++        private List<Var> vars ;
++        private Iterator<List<Node>> members ;
++        
++        QueryIterExtendByVars(Binding binding, List<Var> vars, Iterator<List<Node>> members, ExecutionContext execCxt)
++        {
++            super(execCxt) ;
++            this.binding = binding ;
++            this.vars = vars ;
++            this.members = members ;
++        }
++
++        @Override
++        protected boolean hasNextBinding()
++        {
++            return members.hasNext() ;
++        }
++
++        @Override
++        protected Binding moveToNextBinding()
++        {
++            List<Node> nodes = members.next() ;
++            
++            // silently ignore differences in the number of nodes and vars to bind them to
++            // bind as many vars as there is data
++            // ignore any nodes for which there are no variables
++         
++            Iterator<Node> nodeIter = nodes.iterator();
++            Iterator<Var> varIter = vars.iterator();
++            Binding b = binding;
++            for ( ; nodeIter.hasNext() && varIter.hasNext(); )
++            {
++                b = BindingFactory.binding(b, varIter.next(), nodeIter.next()) ;
++            }
++            return b ;
++        }
++
++        @Override
++        protected void closeIterator()
++        { }
++        
++        @Override
++        protected void requestCancel()
++        { }
+     }
+ }
+ 
+diff --git a/src/main/java/org/apache/jena/query/text/TextIndex.java b/src/main/java/org/apache/jena/query/text/TextIndex.java
+index 8806eb6..a7f995f 100644
+--- a/src/main/java/org/apache/jena/query/text/TextIndex.java
++++ b/src/main/java/org/apache/jena/query/text/TextIndex.java
+@@ -23,4 +23,24 @@ public interface TextIndex extends Closeable //, Transactional
+     public abstract List<Node> query(String qs) ;
+ 
+     public abstract EntityDefinition getDocDef() ;
++    
++    //
++    // methods which return score for each result
++    // At some point may refactor these to replace the methods without score
++    //
++    public abstract List<NodeAndScore> queryWithScore(String qs, int limit);
++    public abstract List<NodeAndScore> queryWithScore(String qs) ;
++    
++    public class NodeAndScore {
++    	private Node node;
++    	private float score;
++    	
++    	public NodeAndScore(Node node, float score) {
++    		this.node = node;
++    		this.score = score;
++    	}
++    	
++    	public Node getNode() { return node ; }
++    	public float getScore() { return score ; }
++    }
+ }
+diff --git a/src/main/java/org/apache/jena/query/text/TextIndexLucene.java b/src/main/java/org/apache/jena/query/text/TextIndexLucene.java
+index c29caf7..2ea1aa9 100644
+--- a/src/main/java/org/apache/jena/query/text/TextIndexLucene.java
++++ b/src/main/java/org/apache/jena/query/text/TextIndexLucene.java
+@@ -159,7 +159,7 @@ public class TextIndexLucene implements TextIndex
+ 
+ 
+     @Override
+-    public List<Node> query(String qs) { return query(qs, MAX_N) ; } 
++    public List<Node> query(String qs) { return query(qs, MAX_N) ; }
+     
+     @Override
+     public List<Node> query(String qs, int limit)
+@@ -195,7 +195,50 @@ public class TextIndexLucene implements TextIndex
+             }
+         }
+         return results ;
++    } 
++    
++    public List<NodeAndScore> queryWithScore(String qs) { return queryWithScore(qs, 0) ; }
++    
++    public List<NodeAndScore> queryWithScore(String qs, int limit) {
++        try {
++            // Upgrade at Java7 ... 
++            IndexReader indexReader = DirectoryReader.open(directory) ;
++            try { 
++            	return queryWithScore$(indexReader, qs, limit) ;
++           } finally { 
++        	   indexReader.close() ; 
++           }
++        } catch (ParseException | IOException ex) { 
++        	exception(ex) ; 
++        	return null ; 
++        } 
+     }
++    
++    public List<NodeAndScore> queryWithScore$(IndexReader indexReader , String qs, int limit) throws ParseException, IOException {
++	    IndexSearcher indexSearcher = new IndexSearcher(indexReader);
++	    QueryParser queryParser = new QueryParser(VER, docDef.getPrimaryField(), analyzer);
++	    Query query = queryParser.parse(qs);
++	    
++	    if ( limit <= 0 )
++	        limit = MAX_N ;
++	    ScoreDoc[] sDocs = indexSearcher.search(query, limit).scoreDocs ;
++	    
++	    List<NodeAndScore> results = new ArrayList<>() ;
++	    
++	    // Align and DRY with Solr.
++	    for ( ScoreDoc sd : sDocs )
++	    {
++	        Document doc = indexSearcher.doc(sd.doc) ;
++	        String[] values = doc.getValues(docDef.getEntityField()) ;
++	        float score = sd.score;
++	        for ( String v : values )
++	        {
++	            Node n = Node.createURI(v);
++	            results.add(new NodeAndScore(n, score)) ;
++	        }
++	    }
++	    return results ;
++    } 
+ 
+     
+     @Override
+diff --git a/src/main/java/org/apache/jena/query/text/TextIndexSolr.java b/src/main/java/org/apache/jena/query/text/TextIndexSolr.java
+index 5491eb5..b528d48 100644
+--- a/src/main/java/org/apache/jena/query/text/TextIndexSolr.java
++++ b/src/main/java/org/apache/jena/query/text/TextIndexSolr.java
+@@ -160,9 +160,35 @@ public class TextIndexSolr implements TextIndex
+         return results ; 
+     }
+     
++    @Override
++    public List<NodeAndScore> queryWithScore(String qs) { return queryWithScore(qs, 0) ; }
++    
++    @Override
++    public List<NodeAndScore> queryWithScore(String qs, int limit)
++    {
++        SolrDocumentList solrResults = solrQuery(qs, limit) ;
++        List<NodeAndScore> results = new ArrayList<>() ;
++
++        for ( SolrDocument sd : solrResults )
++        {
++            String uriStr = (String)sd.getFieldValue(docDef.getEntityField()) ;
++            float score = (float) sd.getFieldValue("score");
++            //log.info("Entity: "+uriStr) ;
++            results.add(new NodeAndScore(Node.createURI(uriStr), score)) ;
++        }
++
++        if ( limit > 0 && results.size() > limit )
++            results = results.subList(0, limit) ;
++        
++        return results ; 
++    }
++    
+     private SolrDocumentList solrQuery(String qs, int limit)
+     {
+         SolrQuery sq = new SolrQuery(qs) ;
++        // include score in results
++        sq.addField(docDef.getEntityField())
++          .addField("score");
+         try {
+             QueryResponse rsp = solrServer.query( sq ) ;
+             SolrDocumentList docs = rsp.getResults();
+diff --git a/src/test/java/org/apache/jena/query/text/AbstractTestDatasetWithTextIndex.java b/src/test/java/org/apache/jena/query/text/AbstractTestDatasetWithTextIndex.java
+index 0b851f3..b14b1c3 100644
+--- a/src/test/java/org/apache/jena/query/text/AbstractTestDatasetWithTextIndex.java
++++ b/src/test/java/org/apache/jena/query/text/AbstractTestDatasetWithTextIndex.java
+@@ -8,6 +8,7 @@ import java.io.Reader;
+ import java.io.StringReader;
+ import java.util.Arrays;
+ import java.util.HashSet;
++import java.util.Iterator;
+ import java.util.Set;
+ 
+ import org.apache.jena.atlas.lib.StrUtils;
+@@ -17,6 +18,7 @@ import com.hp.hpl.jena.query.Query;
+ import com.hp.hpl.jena.query.QueryExecution;
+ import com.hp.hpl.jena.query.QueryExecutionFactory;
+ import com.hp.hpl.jena.query.QueryFactory;
++import com.hp.hpl.jena.query.QuerySolution;
+ import com.hp.hpl.jena.query.ReadWrite;
+ import com.hp.hpl.jena.query.ResultSet;
+ import com.hp.hpl.jena.rdf.model.Model;
+@@ -43,6 +45,7 @@ public abstract class AbstractTestDatasetWithTextIndex {
+ 						);
+ 	
+ 	public void testOneSimpleResult() {
++		final String label = "testOneSimpleResults";
+ 		final String turtle = StrUtils.strjoinNL(
+ 				TURTLE_PROLOG,
+ 				"<" + RESOURCE_BASE + "testOneSimpleResult>",
+@@ -58,7 +61,7 @@ public abstract class AbstractTestDatasetWithTextIndex {
+ 				);
+ 		Set<String> expectedURIs = (new HashSet<String>());
+ 		expectedURIs.addAll( Arrays.asList((new String[] {"http://example.org/data/resource/testOneSimpleResult"})));
+-		doTestSearch(turtle, queryString, expectedURIs);
++		doTestSearch(label, turtle, queryString, expectedURIs);
+ 	}
+ 
+ 	public void testMultipleResults() {
+@@ -86,7 +89,7 @@ public abstract class AbstractTestDatasetWithTextIndex {
+ 			    "http://example.org/data/resource/" + label + "2"
+ 				}
+ 		)));
+-		doTestSearch(turtle, queryString, expectedURIs);
++		doTestSearch(label, turtle, queryString, expectedURIs);
+ 	}
+ 
+ 	public void testSearchCorrectField() {
+@@ -199,16 +202,49 @@ public abstract class AbstractTestDatasetWithTextIndex {
+ 		)));
+ 		doTestSearch("default field:", turtle, queryString, expectedURIs, 3 );
+ 	}
++
++
++	public void testReturnsScore() {
++		String label = "testReturnsScore";
++		final String turtle = StrUtils.strjoinNL(
++				TURTLE_PROLOG,
++				"<" + RESOURCE_BASE + label +"1>",
++				"  rdfs:label \"" + label + "1\"",
++				".",
++				"<" + RESOURCE_BASE + label + "2>",
++				"  rdfs:label \"" + label + "2\"",
++				"."
++				);
++		String queryString = StrUtils.strjoinNL(
++				QUERY_PROLOG,
++				"SELECT ?s ?score",
++				"WHERE {",
++				"    (?s ?score) text:query ( rdfs:label \"" + label + "?\" 10 ) .",
++				"}"
++				);
++		Set<String> expectedURIs = (new HashSet<String>());
++		expectedURIs.addAll( Arrays.asList((new String[]
++				{
++			    "http://example.org/data/resource/" + label + "1",
++			    "http://example.org/data/resource/" + label + "2"
++				}
++		)));
++		doTestSearch(label, turtle, queryString, expectedURIs, "score");
++	}
++	
++	protected void doTestSearch(String label, String turtle, String queryString, Set<String> expectedEntityURIs) {
++		doTestSearch(label, turtle, queryString, expectedEntityURIs, null);
++	}
+ 	
+-	protected void doTestSearch(String turtle, String queryString, Set<String> expectedEntityURIs) {
+-		doTestSearch("", turtle, queryString, expectedEntityURIs);
++	protected void doTestSearch(String label, String turtle, String queryString, Set<String> expectedEntityURIs, int expectedNumResults) {
++		doTestSearch(label, turtle, queryString, expectedEntityURIs, expectedNumResults, null);
+ 	}
+ 	
+-	private void doTestSearch(String label, String turtle, String queryString, Set<String> expectedEntityURIs) {
+-		doTestSearch(label, turtle, queryString, expectedEntityURIs, expectedEntityURIs.size());
++	protected void doTestSearch(String label, String turtle, String queryString, Set<String> expectedEntityURIs, String scoreVarName) {
++		doTestSearch(label, turtle, queryString, expectedEntityURIs, expectedEntityURIs.size(), scoreVarName);
+ 	}
+ 	
+-	private void doTestSearch(String label, String turtle, String queryString, Set<String> expectedEntityURIs, int expectedNumResults) {
++	private void doTestSearch(String label, String turtle, String queryString, Set<String> expectedEntityURIs, int expectedNumResults, String scoreVarName) {
+ 		Model model = dataset.getDefaultModel();
+ 		Reader reader = new StringReader(turtle);
+ 		dataset.begin(ReadWrite.WRITE);
+@@ -222,9 +258,13 @@ public abstract class AbstractTestDatasetWithTextIndex {
+ 		    ResultSet results = qexec.execSelect() ;
+ 		    assertEquals(label, expectedNumResults > 0, results.hasNext());
+ 		    int count;
+-		    for (count=0; results.hasNext(); count++) {
+-		    	String entityURI = results.next().getResource("s").getURI();
++		    for (count=0; results.hasNext(); count++)
++		    {
++		    	QuerySolution soln = results.next();
++		    	String entityURI = soln.getResource("s").getURI();
+ 		        assertTrue(label, expectedEntityURIs.contains(entityURI));
++		        if (scoreVarName != null)
++		            assertTrue(label, soln.contains(scoreVarName));
+ 		    }
+ 		    assertEquals(label, expectedNumResults, count);
+ 		} finally { qexec.close() ; dataset.end() ; }		
+diff --git a/src/test/java/org/apache/jena/query/text/TestDatasetWithEmbeddedSolrTextIndex.java b/src/test/java/org/apache/jena/query/text/TestDatasetWithEmbeddedSolrTextIndex.java
+index 113785d..d5c4ccb 100644
+--- a/src/test/java/org/apache/jena/query/text/TestDatasetWithEmbeddedSolrTextIndex.java
++++ b/src/test/java/org/apache/jena/query/text/TestDatasetWithEmbeddedSolrTextIndex.java
+@@ -84,25 +84,17 @@ public class TestDatasetWithEmbeddedSolrTextIndex extends AbstractTestDatasetWit
+ 		deleteOldFiles();
+ 	}
+ 
+-	@Test public void testOneSimpleResult() {
+-		super.testOneSimpleResult();
+-	}
++	@Test public void testOneSimpleResult() 	{ super.testOneSimpleResult(); }
+ 
+-	@Test public void testMultipleResults() {
+-		super.testMultipleResults();
+-	}
++	@Test public void testMultipleResults() 	{ super.testMultipleResults(); }
+ 
+-	@Test public void testSearchCorrectField() {
+-		super.testSearchCorrectField();
+-	}
++	@Test public void testSearchCorrectField() 	{ super.testSearchCorrectField(); }
+ 
+-	@Test public void testSearchDefaultField() {
+-		super.testSearchDefaultField();
+-	}
++	@Test public void testSearchDefaultField() 	{ super.testSearchDefaultField();	}
+ 
+-	@Test public void testSearchLimitsResults() {
+-		super.testSearchLimitsResults(); 
+-	}
++	@Test public void testSearchLimitsResults()	{ super.testSearchLimitsResults(); }
++	
++	@Test public void testReturnsScore() 		{ super.testReturnsScore() ; }
+ 	
+ 	public static void deleteOldFiles() {
+ 		if (indexDir.exists()) emptyAndDeleteDirectory(indexDir);
+diff --git a/src/test/java/org/apache/jena/query/text/TestDatasetWithLuceneTextIndex.java b/src/test/java/org/apache/jena/query/text/TestDatasetWithLuceneTextIndex.java
+index c88343b..6abb19d 100644
+--- a/src/test/java/org/apache/jena/query/text/TestDatasetWithLuceneTextIndex.java
++++ b/src/test/java/org/apache/jena/query/text/TestDatasetWithLuceneTextIndex.java
+@@ -14,23 +14,16 @@ public class TestDatasetWithLuceneTextIndex extends AbstractTestDatasetWithLucen
+ 		deleteOldFiles();
+ 	}
+ 
+-	@Test public void testOneSimpleResult() {
+-		super.testOneSimpleResult();
+-	}
++	@Test public void testOneSimpleResult() 	{ super.testOneSimpleResult(); }
+ 
+-	@Test public void testMultipleResults() {
+-		super.testMultipleResults();
+-	}
++	@Test public void testMultipleResults() 	{ super.testMultipleResults(); }
+ 
+-	@Test public void testSearchCorrectField() {
+-		super.testSearchCorrectField();
+-	}
++	@Test public void testSearchCorrectField() 	{ super.testSearchCorrectField(); }
+ 
+-	@Test public void testSearchDefaultField() {
+-		super.testSearchDefaultField();
+-	}
++	@Test public void testSearchDefaultField()	{ super.testSearchDefaultField(); }
++	
++	@Test public void testSearchLimitsResults() { super.testSearchLimitsResults(); }
++	
++	@Test public void testReturnsScore() 		{ super.testReturnsScore() ; }
+ 
+-	@Test public void testSearchLimitsResults() {
+-		super.testSearchLimitsResults(); 
+-	}
+ }
+diff --git a/src/test/java/org/apache/jena/query/text/TestSearchBeforeWriteOnDatasetWithLuceneTextIndex.java b/src/test/java/org/apache/jena/query/text/TestSearchBeforeWriteOnDatasetWithLuceneTextIndex.java
+index f2288bf..d692bb7 100644
+--- a/src/test/java/org/apache/jena/query/text/TestSearchBeforeWriteOnDatasetWithLuceneTextIndex.java
++++ b/src/test/java/org/apache/jena/query/text/TestSearchBeforeWriteOnDatasetWithLuceneTextIndex.java
+@@ -18,6 +18,7 @@ public class TestSearchBeforeWriteOnDatasetWithLuceneTextIndex extends AbstractT
+ 	}
+ 	
+ 	@Test public void testNoResultsOnFirstCreateIndex(){
++		String label = "testNoResultsOnFirstCreateIndex";
+ 		String turtle = "";
+ 		String queryString = StrUtils.strjoinNL(
+ 				QUERY_PROLOG,
+@@ -26,6 +27,6 @@ public class TestSearchBeforeWriteOnDatasetWithLuceneTextIndex extends AbstractT
+ 				"    ?s text:query ( rdfs:label \"a* OR b* OR c* OR d* OR e* OR f* OR g* OR h* OR i* OR j* OR k* OR l* OR m* OR n* OR o* OR p* OR q* OR r* OR s* OR t* OR u* OR v* OR w* OR x* OR y* OR z*\" 10 ) .",
+ 				"}"
+ 				);
+-		doTestSearch(turtle, queryString, new HashSet<String>());
++		doTestSearch(label, turtle, queryString, new HashSet<String>());
+ 	}
+ }
+diff --git a/src/test/java/org/apache/jena/query/text/TestTextSearchQueryForms.java b/src/test/java/org/apache/jena/query/text/TestTextSearchQueryForms.java
+new file mode 100644
+index 0000000..a426588
+--- /dev/null
++++ b/src/test/java/org/apache/jena/query/text/TestTextSearchQueryForms.java
+@@ -0,0 +1,122 @@
++package org.apache.jena.query.text;
++
++import static org.junit.Assert.fail;
++
++import java.util.HashSet;
++import java.util.Set;
++
++import org.apache.jena.atlas.lib.StrUtils;
++import org.junit.AfterClass;
++import org.junit.BeforeClass;
++import org.junit.Test;
++
++public class TestTextSearchQueryForms extends AbstractTestDatasetWithLuceneTextIndex {
++	public static final String RESOURCE_BASE="http://example.org/testTextSearchQueryForms/resource/";
++	final String turtle = StrUtils.strjoinNL(
++			TURTLE_PROLOG,
++			"<" + RESOURCE_BASE + "testOneSimpleResult>",
++			"  rdfs:label \"bar foobar barfoo foo\"",
++			"."
++			);
++	
++	@BeforeClass public static void beforeClass() {
++		init();
++	}	
++	
++	@AfterClass public static void afterClass() {
++		deleteOldFiles();
++	}
++	
++	@Test public void testSubjecttListTooShort() {
++		final String label = "subjectListTooShort";
++		String queryString = StrUtils.strjoinNL(
++				QUERY_PROLOG,
++				"SELECT ?s",
++				"WHERE {",
++				"    () text:query \"foobar\" .",
++				"}"
++				);
++        testBadQuery(label, queryString);
++	}
++	
++	@Test public void testSubjectListTooLong() {
++		final String label = "subjectListTooLong";
++		String queryString = StrUtils.strjoinNL(
++				QUERY_PROLOG,
++				"SELECT ?s",
++				"WHERE {",
++				"    (?s ?score ?another) text:query \"foobar\" .",
++				"}"
++				);
++		testBadQuery(label, queryString);
++	}
++	
++	@Test public void testScoreIsNotAVariable() {
++		final String label = "scoreIsNotAVariable";
++		String queryString = StrUtils.strjoinNL(
++				QUERY_PROLOG,
++				"SELECT ?s",
++				"WHERE {",
++				"    (?s 1) text:query \"foobar\" .",
++				"}"
++				);
++		testBadQuery(label, queryString);
++	}
++	
++	@Test public void testSubjectIsALiteral() {
++		final String label = "testSubjectIsALiteral";
++		String queryString = StrUtils.strjoinNL(
++				QUERY_PROLOG,
++				"SELECT ?s",
++				"WHERE {",
++				"    \"foo\" text:query \"foobar\" .",
++				"}"
++				);
++		Set<String> expectedURIs = (new HashSet<String>());
++		doTestSearch(label, turtle, queryString, expectedURIs);
++	}
++		
++	@Test public void testObjectListTooShort() {
++		final String label = "objectListTooShort";
++		String queryString = StrUtils.strjoinNL(
++				QUERY_PROLOG,
++				"SELECT ?s",
++				"WHERE {",
++				"    ?s text:query ( ) .",
++				"}"
++				);
++		testBadQuery(label, queryString);
++	}
++	
++	@Test public void testObjectListTooLong() {
++		final String label = "objectListTooLong";
++		String queryString = StrUtils.strjoinNL(
++				QUERY_PROLOG,
++				"SELECT ?s",
++				"WHERE {",
++				"    ?s text:query (?a ?b ?c ?d ) .",
++				"}"
++				);
++		testBadQuery(label, queryString);
++	}
++	
++	@Test public void testNoQueryString() {
++		final String label = "noQueryString";
++		String queryString = StrUtils.strjoinNL(
++				QUERY_PROLOG,
++				"SELECT ?s",
++				"WHERE {",
++				"    ?s text:query (rdfs:label) .",
++				"}"
++				);
++		testBadQuery(label, queryString);
++	}
++	
++	private void testBadQuery(String label, String queryString) {
++		Set<String> expectedURIs = (new HashSet<String>());
++        try {
++		    doTestSearch(label, turtle, queryString, expectedURIs);
++		    fail("object list too long should have thrown exception");
++		} catch (Exception e) {}
++	}
++}