package lux.solr; import static org.junit.Assert.*; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.TimeZone; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.SolrServer; import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.util.NamedList; import org.junit.BeforeClass; import org.junit.Test; public class LuxSolrTest extends BaseSolrTest { private static final String XML_TEXT = "lux_text"; private static final String LUX_PATH = "lux_path"; @BeforeClass public static void setup () throws Exception { BaseSolrTest.setup("solr", "core2"); // use core2 since its schema has the element visibility configuration Collection<SolrInputDocument> docs = new ArrayList<SolrInputDocument> (); addSolrDocFromFile("src/test/resources/conf/schema.xml", docs, "uri", "xml"); addSolrDocFromFile("src/test/resources/conf/solrconfig.xml", docs, "uri", "xml"); for (int i = 1; i <= 100; i++) { addSolrDoc ("test" + i, "<doc><title id='" + i + "'>" + (101-i) + "</title><test>cat</test></doc>", docs, "uri", "xml"); } solr.add (docs); solr.commit(); } @Test public void testIndex() throws Exception { // make sure the documents have the values we expect assertSolrQueryCount (102, "*:*"); // QNAME index is no longer part of the default setup created by LuxUpdateProcessor //assertQueryCount (1, XmlIndexer.ELT_QNAME.getName() + ":config"); //assertQueryCount (1, XmlIndexer.ELT_QNAME.getName() + ":schema"); assertSolrQueryCount (1, LUX_PATH + ":\"{} schema types fieldType\""); assertSolrQueryCount (1, LUX_PATH + ":schema"); assertSolrQueryCount (102, LUX_PATH + ":\"{}\""); assertSolrQueryCount (1, LUX_PATH + ":\"{} config luceneMatchVersion\""); assertSolrQueryCount (2, XML_TEXT + ":true"); assertQueryCount (2, 2, "xs:string", "schema", "lux:search('<enableLazyFieldLoading:true')/*/name()"); // this fails due to lower-casing of the embedded tag // assertQueryCount (2, LUX_ELT_TEXT + ":enableLazyFieldLoading\\:true"); assertQueryCount (1, 1, "xs:string", "doc", "lux:search('<@id:1')/*/name()"); assertQueryCount (1, 1, "xs:string", "schema", "lux:search('<@type:random')/*/name()"); // these fails due to tokenization of the tagged term // assertQueryCount (1, LUX_ATT_TEXT + ":id\\:1"); // assertQueryCount (1, LUX_ATT_TEXT + ":type\\:random"); } @Test public void testXPathSearch() throws Exception { // test search using standard search query handler, custom query parser assertQueryCount (1, 1, "element", "config", "//config"); assertQueryCount (34, 1, 50, "element", "abortOnConfigurationError", "/config/*"); } @Test public void testAtomicResult () throws Exception { // This also tests lazy evaluation - like paging within xpath. Because we only retrieve // the first value (in document order), we only need to retrieve one value. assertQueryCount (1, 1, "xs:double", "100.0", "number((/doc/title)[1])"); } @Test public void testLiteral () throws Exception { // no documents were retrieved, 1 result returned = 12 assertQueryCount(1, 0, "xs:double", "12.0", "xs:double(12.0)"); } @Test public void testFirstPage () throws Exception { // returns only the page including the first 10 results assertQueryCount (10, 10, "document", "doc", "(/)[doc]"); assertQueryCount (10, 20, "element", "doc", "(//doc)[position() > 10]"); } @Test public void testPaging () throws Exception { // make the searcher page past the first 10 documents to find 10 xpath matches assertQueryCount (10, 16, "element", "doc", "//doc[title[number(.) < 95]]"); } /** * This test confirms that fields declared in solrconfig.xml/schema.xml are indexed * and made available for sorting, as well as exercising sorting optimization and the * string sorting implementations * @throws Exception */ @Test public void testSorting () throws Exception { // should be 1, 10, 100, 11, 12, ..., 2, 21, 22, ... // which is docs 101, 92, 2, (since there are 2 docs with no title that are loaded first) assertQueryCount(1, 5, "xs:string", "1,10,100,11,12", "string-join(subsequence((for $doc in //doc order by $doc/lux:key('title') return $doc/title/string()),1,5),',')"); assertQueryCount(1, 1, "xs:string", "1", "(for $doc in //doc order by $doc/lux:key('title') return $doc/title/string())[1]"); assertQueryCount(1, 1, "xs:string", "99", "(for $doc in //doc order by $doc/lux:key('title') descending return $doc/title/string())[1]"); assertQueryCount(1, 2, "xs:string", "10", "(for $doc in //doc order by $doc/lux:key('title') return $doc/title/string())[2]"); // test providing the sort criteria directly to lux:search() assertQueryCount(1, 2, "xs:string", "10", "(for $doc in lux:search('<test:cat', 'title') return $doc/doc/title/string())[2]"); // TODO: implement wildcard element query to test for existence of some element // assertXPathSearchCount(1, 2, "xs:string", "10", "lux:search('<doc:*', (), 'title')[2]"); } @Test public void testDocFunction () throws Exception { assertQueryCount (1, 0, "document", "doc", "doc('test50')"); assertQueryCount (0, 0, "error", "document not found: /foo\n", "doc('/foo')"); } @Test public void testCollectionFunction () throws Exception { assertQueryCount (1, 1, "xs:anyURI", "lux:/src/test/resources/conf/schema.xml", "collection()[1]/base-uri()"); assertQueryCount (1, 102, "xs:anyURI", "lux:/test100", "collection()[last()]/base-uri()"); assertQueryCount (1, 102, "xs:integer", "102", "count(collection())"); } @Test public void testQueryError () throws Exception { assertQueryError("Prefix lux_elt_name_ms has not been declared; Line#: 1; Column#: 22\n", "lux_elt_name_ms:config"); } @Test public void testSyntaxError () throws Exception { assertQueryError("Unexpected token name \"bad\" beyond end of query; Line#: 1; Column#: 4\n", "hey bad boy"); } @Test public void testCreateCore () throws Exception { SolrQuery q = new SolrQuery(); q.setRequestHandler(coreContainer.getAdminPath()); q.setParam ("action", "CREATE"); q.setParam ("name", "core3"); q.setParam ("instanceDir", "core3"); solr.query(q); SolrServer core3 = new EmbeddedSolrServer(coreContainer, "core3"); core3.deleteByQuery("*:*"); core3.commit(); assertSolrQueryCount (0, "*:*", core3); // main core still works assertQueryCount (1, 102, "xs:integer", "102", "count(collection())", solr); // new core working too assertQueryCount(1, 0, "xs:integer", "0", "count(collection())", core3); } @Test public void testAppServer () throws Exception { SolrQuery q = new SolrQuery(); q.setRequestHandler("/testapp"); q.setParam("test-param", "test-value"); q.setParam("wt", "lux"); q.setParam("lux.contentType", "text/xml"); QueryResponse resp = solr.query(q); assertEquals ("query was blank", resp.getResponse().get("xpath-error")); q.setParam("lux.xquery", "lux/solr/echo.xqy"); resp = solr.query(q); NamedList<?> xpathResults = (NamedList<?>) resp.getResponse().get("xpath-results"); assertEquals ( "<http><params>" + "<param name=\"wt\"><value>lux</value></param>" + "<param name=\"qt\"><value>/testapp</value></param>" + "<param name=\"test-param\"><value>test-value</value></param>" + "<param name=\"wt\"><value>lux</value></param></params><context-path/></http>", xpathResults.get("document").toString()); assertTrue(resp.getResults().isEmpty()); } @Test public void testPrimitiveValues () throws Exception { assertQuery (true, "xs:boolean", "true()"); assertQuery (false, "xs:boolean", "false()"); assertQuery ("{local}name", "xs:QName", "fn:QName('local','l:name')"); assertQuery (null, null, "()"); assertQuery ("x", "xs:untypedAtomic", "xs:untypedAtomic('x')"); assertQuery (1L, "xs:integer", "xs:integer(1)"); assertQuery (1L, "xs:integer", "1"); assertQuery (1L, "xs:int", "xs:int(1)"); assertQuery (1.0, "xs:decimal", "1.0"); assertQuery (1.0, "xs:double", "xs:double(1.0)"); assertQuery (1.0f, "xs:float", "xs:float(1.0)"); assertQuery ("1", "xs:string", "'1'"); assertQuery ("1", "xs:string", "xs:anyURI('1')"); Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); cal.set(2013,3,6,0,0,0); cal.set(Calendar.MILLISECOND, 0); assertQuery (cal.getTime(), "xs:date", "xs:date('2013-04-06')"); assertQuery (cal.getTime(), "xs:dateTime", "xs:dateTime('2013-04-06T00:00:00')"); assertQuery ("00:00:00", "xs:time", "xs:time('00:00:00')"); assertQuery ("0900", "xs:gYear", "xs:gYear('0900')"); assertQuery ("--11", "xs:gMonth", "xs:gMonth('--11')"); assertQuery ("2012-12", "xs:gYearMonth", "xs:gYearMonth('2012-12')"); assertQuery ("---01", "xs:gDay", "xs:gDay('---01')"); assertQuery ("--12-01", "xs:gMonthDay", "xs:gMonthDay('--12-01')"); } @Test public void testMultiNodeConstruct () throws Exception { String xml = "document {comment { ' this is a test ' }, \n" + "processing-instruction test-pi { 'this is a test pi' },\n" + "element test { 'Hello, World' } }"; String output = "<!-- this is a test --><?test-pi this is a test pi?><test>Hello, World</test>"; assertQuery (output, "document", xml); } /* * Make sure we can index date-valued fields. Test inserting via REST and via XQuery, retrieving * field values, and querying. */ @Test public void testDateField () throws Exception { assertQuery ("ok", "(lux:insert('/test', <dfdoc modified='2000-01-01T01:02:03Z'>ok</dfdoc>), lux:commit(), 'ok')"); assertQuery ("ok", "(lux:insert('/test2', <dfdoc modified='2000-01-01T02:03:04Z'>dokey</dfdoc>), lux:commit(), 'ok')"); Date d = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz").parse("2000-01-01T01:02:03GMT+00:00"); assertSolrQuery(d, "modified_dt", "uri:\\/test"); assertSolrQuery(d, "modified_tdt", "uri:\\/test"); assertQuery ("ok", "/dfdoc[@modified='2000-01-01T01:02:03Z']/string()"); assertQuery ("ok", "/dfdoc[@modified=xs:dateTime('2000-01-01T01:02:03Z')]/string()"); // DateField assertQuery ("2000-01-01T01:02:03Z", "lux:key('modified_dt', doc('/test'))"); assertQuery ("dokey", "(for $doc in /dfdoc order by $doc/lux:key('modified_dt') return $doc)[2]/string()"); assertQuery ("dokey", "(for $doc in /dfdoc order by $doc/lux:key('modified_dt') descending return $doc)[1]/string()"); // TrieDateField assertQuery ("2000-01-01T01:02:03Z", "lux:key('modified_tdt', doc('/test'))"); assertQuery ("dokey", "(for $doc in /dfdoc order by $doc/lux:key('modified_tdt') return $doc)[2]/string()"); assertQuery ("dokey", "(for $doc in /dfdoc order by $doc/lux:key('modified_tdt') descending return $doc)[1]/string()"); // queries assertQuery ("ok", "lux:search('modified_dt:\"2000-01-01T01:02:03Z\"')/string()"); // query parser can't handle date ranges // assertQuery ("ok", "lux:search('modified_dt:[\"2000-01-01T01:02:03Z\" TO *]')/string()"); // lack of precision makes this return 2? // assertQuery ("ok", "lux:search('modified_tdt:\"2000-01-01T01:02:03Z\"')/string()"); // assertQuery ("ok", "lux:search('modified_tdt:[\"2000-01-01T01:02:03Z\" TO *]')/string()"); assertQuery ("ok", "lux:search('<@modified:\"2000-01-01T01:02:03Z\"')/string()"); // assertQuery ("ok", "lux:search('<@modified:[\"2000-01-01T01:02:03Z\" TO *]')/string()"); // bad date format try { assertQueryError("ok", "(lux:insert('/test', <doc modified='2000-01-01' />), lux:commit(), 'ok')"); assertFalse ("no exception thrown", true); } catch (Exception ex) { assertTrue (ex.getMessage().contains("2000-01-01")); } assertQuery ("ok", "(lux:delete('/test'), lux:delete('/test2'), lux:commit(), 'ok')"); } @Test public void testInsertRandomFields () throws Exception { // test inserting a document that doesn't have the Lux XML field SolrInputDocument doc = new SolrInputDocument(); doc.addField ("uri", "/doc/string10"); doc.addField ("string_s", "string"); doc.addField("number_i", "10"); try { solr.add(doc); solr.commit(); assertQuery ("<binary xmlns=\"http://luxdb.net\"/>", "document", "doc('/doc/string10')"); } finally { // now clean up solr.deleteById("/doc/string10"); solr.commit(); } } @Test public void testConfigElementVisibility () throws Exception { assertQuery ("ok", "(lux:insert('/test', <doc><div>ok <hidden>bad</hidden><i>go</i> <x>away</x></div> <x><i>often</i> enough</x></doc>), lux:commit(), 'ok')"); // xml text field includes all text assertQueryCount (1, "lux:search('ok')/string()"); assertQueryCount (1, "lux:search('go')/string()"); // except text in hidden elements assertQueryCount (0, "lux:search('bad')/string()"); // xml element text field includes transparent elements assertQueryCount (1, "lux:search('<i:go')/string()"); // container element sees into opaque and transparent elements assertQueryCount (1, "lux:search('<div:go')/string()"); assertQueryCount (1, "lux:search('<div:away')/string()"); // but not hidden elements assertQueryCount (0, "lux:search('<hidden:bad')/string()"); // even as part of container elements assertQueryCount (0, "lux:search('<div:bad')/string()"); // phrase wraps around hidden element assertQueryCount (1, "lux:search('ok go')/string()"); // regular element does not "see" into opaque or container elements assertQueryCount (0, "lux:search('<doc:ok')/string()"); assertQueryCount (0, "lux:search('<doc:often')/string()"); assertQueryCount (0, "lux:search('<doc:enough')/string()"); // but does see into itself, and transparent element assertQueryCount (1, "lux:search('<x:\"often enough\"')/string()"); } } /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */