lux.solr.LuxSolrTest.java Source code

Java tutorial

Introduction

Here is the source code for lux.solr.LuxSolrTest.java

Source

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/. */