blob: 65561255be6d5d9d908b3ee86efa9813b724d747 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.solr.schema;
import java.util.Arrays;
import java.util.Currency;
import java.util.List;
import java.util.Random;
import java.util.Set;
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import org.apache.lucene.index.IndexableField;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.core.SolrCore;
import org.apache.solr.util.RTimer;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
/** Tests CurrencyField and CurrencyFieldType. */
public class CurrencyFieldTypeTest extends SolrTestCaseJ4 {
private final String fieldName;
private final Class<? extends ExchangeRateProvider> expectedProviderClass;
public CurrencyFieldTypeTest(String fieldName, Class<? extends ExchangeRateProvider> expectedProviderClass) {
this.fieldName = fieldName;
this.expectedProviderClass = expectedProviderClass;
}
@ParametersFactory
public static Iterable<Object[]> parameters() {
return Arrays.asList(new Object[][] {
{"amount", FileExchangeRateProvider.class}, // CurrencyField
{"mock_amount", MockExchangeRateProvider.class}, // CurrencyField
{"oer_amount", OpenExchangeRatesOrgProvider.class}, // CurrencyField
{"amount_CFT", FileExchangeRateProvider.class}, // CurrencyFieldType
{"mock_amount_CFT", MockExchangeRateProvider.class}, // CurrencyFieldType
{"oer_amount_CFT", OpenExchangeRatesOrgProvider.class} // CurrencyFieldType
});
}
/**
* "Assumes" that the specified list of currency codes are
* supported in this JVM
*/
public static void assumeCurrencySupport(String... codes) {
try {
// each JDK might have a diff list of supported currencies,
// these are the ones needed for this test to work.
for (String code : codes) {
Currency obj = Currency.getInstance(code);
assertNotNull(code, obj);
}
} catch (IllegalArgumentException e) {
Assume.assumeNoException(e);
}
}
@BeforeClass
public static void beforeClass() throws Exception {
assumeCurrencySupport("USD", "EUR", "MXN", "GBP", "JPY", "NOK");
initCore("solrconfig.xml", "schema.xml");
}
@Test
public void testCurrencySchema() throws Exception {
IndexSchema schema = h.getCore().getLatestSchema();
SchemaField amount = schema.getField(fieldName);
assertNotNull(amount);
assertTrue(amount.isPolyField());
CurrencyFieldType type = (CurrencyFieldType)amount.getType();
String currencyDynamicField
= "*" + (type instanceof CurrencyField ? FieldType.POLY_FIELD_SEPARATOR : "") + type.fieldSuffixCurrency;
String amountDynamicField
= "*" + (type instanceof CurrencyField ? FieldType.POLY_FIELD_SEPARATOR : "") + type.fieldSuffixAmountRaw;
SchemaField[] dynFields = schema.getDynamicFieldPrototypes();
boolean seenCurrency = false;
boolean seenAmount = false;
for (SchemaField dynField : dynFields) {
if (dynField.getName().equals(amountDynamicField)) {
seenAmount = true;
}
if (dynField.getName().equals(currencyDynamicField)) {
seenCurrency = true;
}
}
assertTrue("Didn't find the expected currency code dynamic field " + currencyDynamicField, seenCurrency);
assertTrue("Didn't find the expected value dynamic field " + amountDynamicField, seenAmount);
}
@Test
public void testCurrencyFieldType() throws Exception {
assumeTrue("This test is only applicable to the XML file based exchange rate provider",
expectedProviderClass.equals(FileExchangeRateProvider.class));
SolrCore core = h.getCore();
IndexSchema schema = core.getLatestSchema();
SchemaField amount = schema.getField(fieldName);
assertNotNull(amount);
assertTrue(fieldName + " is not a poly field", amount.isPolyField());
FieldType tmp = amount.getType();
assertTrue(fieldName + " is not an instance of CurrencyFieldType", tmp instanceof CurrencyFieldType);
String currencyValue = "1.50,EUR";
List<IndexableField> fields = amount.createFields(currencyValue);
assertEquals(fields.size(), 3);
// First field is currency code, second is value, third is stored.
for (int i = 0; i < 3; i++) {
boolean hasValue = fields.get(i).readerValue() != null
|| fields.get(i).numericValue() != null
|| fields.get(i).stringValue() != null;
assertTrue("Doesn't have a value: " + fields.get(i), hasValue);
}
assertEquals(schema.getFieldTypeByName("string").toExternal(fields.get(2)), "1.50,EUR");
// A few tests on the provider directly
ExchangeRateProvider p = ((CurrencyFieldType)tmp).getProvider();
Set<String> availableCurrencies = p.listAvailableCurrencies();
assertEquals(5, availableCurrencies.size());
assertTrue(p.reload());
assertEquals(2.5, p.getExchangeRate("USD", "EUR"), 0.00000000001);
}
@Test
public void testMockExchangeRateProvider() throws Exception {
assumeTrue("This test is only applicable to the mock exchange rate provider",
expectedProviderClass.equals(MockExchangeRateProvider.class));
SolrCore core = h.getCore();
IndexSchema schema = core.getLatestSchema();
SchemaField field = schema.getField(fieldName);
FieldType fieldType = field.getType();
ExchangeRateProvider provider = ((CurrencyFieldType)fieldType).getProvider();
// A few tests on the provider directly
assertEquals(3, provider.listAvailableCurrencies().size());
assertTrue(provider.reload());
assertEquals(0.8, provider.getExchangeRate("USD", "EUR"), 0.00000000001);
}
@Test
public void testCurrencyRangeSearch() throws Exception {
assumeTrue("This test is only applicable to the XML file based exchange rate provider",
expectedProviderClass.equals(FileExchangeRateProvider.class));
clearIndex();
final int emptyDocs = atLeast(50); // times 2
final int negDocs = atLeast(5);
assertU(adoc("id", "0", fieldName, "0,USD")); // 0
// lots of docs w/o values
for (int i = 100; i <= 100 + emptyDocs; i++) {
assertU(adoc("id", "" + i));
}
// docs with values in ranges we'll query
for (int i = 1; i <= 10; i++) {
assertU(adoc("id", "" + i, fieldName, i + ",USD"));
}
// more docs w/o values
for (int i = 500; i <= 500 + emptyDocs; i++) {
assertU(adoc("id", "" + i));
}
// some negative values
for (int i = -100; i > -100 - negDocs; i--) {
assertU(adoc("id", "" + i, fieldName, i + ",USD"));
}
assertU(adoc("id", "40", fieldName, "0,USD")); // 0
assertU(commit());
assertQ(req("fl", "*,score", "q",
fieldName+":[2.00,USD TO 5.00,USD]"),
"//*[@numFound='4']");
assertQ(req("fl", "*,score", "q",
fieldName+":[0.50,USD TO 1.00,USD]"),
"//*[@numFound='1']");
assertQ(req("fl", "*,score", "q",
fieldName+":[24.00,USD TO 25.00,USD]"),
"//*[@numFound='0']");
// "GBP" currency code is 1/2 of a USD dollar, for testing.
assertQ(req("fl", "*,score", "q",
fieldName+":[0.50,GBP TO 1.00,GBP]"),
"//*[@numFound='2']");
// "EUR" currency code is 2.5X of a USD dollar, for testing.
assertQ(req("fl", "*,score", "q",
fieldName+":[24.00,EUR TO 25.00,EUR]"),
"//*[@numFound='1']");
// Slight asymmetric rate should work.
assertQ(req("fl", "*,score", "q",
fieldName+":[24.99,EUR TO 25.01,EUR]"),
"//*[@numFound='1']");
// Open ended ranges without currency
assertQ(req("fl", "*,score", "q",
fieldName+":[* TO *]"),
"//*[@numFound='" + (2 + 10 + negDocs) + "']");
// Open ended ranges without currency
assertQ(req("fl", "*,score", "q",
fieldName+":*"),
"//*[@numFound='" + (2 + 10 + negDocs) + "']");
// Open ended ranges with currency
assertQ(req("fl", "*,score", "q",
fieldName+":[*,EUR TO *,EUR]"),
"//*[@numFound='" + (2 + 10 + negDocs) + "']");
// Open ended start range without currency
assertQ(req("fl", "*,score", "q",
fieldName+":[* TO 5,USD]"),
"//*[@numFound='" + (2 + 5 + negDocs) + "']");
// Open ended start range with currency (currency for the * won't matter)
assertQ(req("fl", "*,score", "q",
fieldName+":[*,USD TO 5,USD]"),
"//*[@numFound='" + (2 + 5 + negDocs) + "']");
// Open ended end range
assertQ(req("fl", "*,score", "q",
fieldName+":[3 TO *]"),
"//*[@numFound='8']");
}
@Test
public void testBogusCurrency() throws Exception {
ignoreException("HOSS");
// bogus currency
assertQEx("Expected exception for invalid currency",
req("fl", "*,score", "q",
fieldName+":[3,HOSS TO *]"),
400);
}
@Test
public void testCurrencyPointQuery() throws Exception {
assumeTrue("This test is only applicable to the XML file based exchange rate provider",
expectedProviderClass.equals(FileExchangeRateProvider.class));
clearIndex();
assertU(adoc("id", "" + 1, fieldName, "10.00,USD"));
assertU(adoc("id", "" + 2, fieldName, "15.00,MXN"));
assertU(commit());
assertQ(req("fl", "*,score", "q", fieldName+":10.00,USD"), "//str[@name='id']='1'");
assertQ(req("fl", "*,score", "q", fieldName+":9.99,USD"), "//*[@numFound='0']");
assertQ(req("fl", "*,score", "q", fieldName+":10.01,USD"), "//*[@numFound='0']");
assertQ(req("fl", "*,score", "q", fieldName+":15.00,MXN"), "//str[@name='id']='2'");
assertQ(req("fl", "*,score", "q", fieldName+":7.50,USD"), "//str[@name='id']='2'");
assertQ(req("fl", "*,score", "q", fieldName+":7.49,USD"), "//*[@numFound='0']");
assertQ(req("fl", "*,score", "q", fieldName+":7.51,USD"), "//*[@numFound='0']");
}
@Ignore
public void testPerformance() throws Exception {
clearIndex();
Random r = random();
int initDocs = 200000;
for (int i = 1; i <= initDocs; i++) {
assertU(adoc("id", "" + i, fieldName, (r.nextInt(10) + 1.00) + ",USD"));
if (i % 1000 == 0)
System.out.println(i);
}
assertU(commit());
for (int i = 0; i < 1000; i++) {
double lower = r.nextInt(10) + 1.00;
assertQ(req("fl", "*,score", "q", fieldName+":[" + lower + ",USD TO " + (lower + 10.00) + ",USD]"), "//*");
assertQ(req("fl", "*,score", "q", fieldName+":[" + lower + ",EUR TO " + (lower + 10.00) + ",EUR]"), "//*");
}
for (int j = 0; j < 3; j++) {
final RTimer timer = new RTimer();
for (int i = 0; i < 1000; i++) {
double lower = r.nextInt(10) + 1.00;
assertQ(req("fl", "*,score", "q", fieldName+":[" + lower + ",USD TO " + (lower + (9.99 - (j * 0.01))) + ",USD]"), "//*");
}
System.out.println(timer.getTime());
}
System.out.println("---");
for (int j = 0; j < 3; j++) {
final RTimer timer = new RTimer();
for (int i = 0; i < 1000; i++) {
double lower = r.nextInt(10) + 1.00;
assertQ(req("fl", "*,score", "q", fieldName+":[" + lower + ",EUR TO " + (lower + (9.99 - (j * 0.01))) + ",EUR]"), "//*");
}
System.out.println(timer.getTime());
}
}
@Test
public void testCurrencySort() throws Exception {
assumeTrue("This test is only applicable to the XML file based exchange rate provider",
expectedProviderClass.equals(FileExchangeRateProvider.class));
clearIndex();
assertU(adoc("id", "" + 1, fieldName, "10.00,USD"));
assertU(adoc("id", "" + 2, fieldName, "15.00,EUR"));
assertU(adoc("id", "" + 3, fieldName, "7.00,EUR"));
assertU(adoc("id", "" + 4, fieldName, "6.00,GBP"));
assertU(adoc("id", "" + 5, fieldName, "2.00,GBP"));
assertU(commit());
assertQ(req("fl", "*,score", "q", "*:*", "sort", fieldName+" desc", "limit", "1"), "//str[@name='id']='4'");
assertQ(req("fl", "*,score", "q", "*:*", "sort", fieldName+" asc", "limit", "1"), "//str[@name='id']='3'");
}
public void testExpectedProvider() {
SolrCore core = h.getCore();
IndexSchema schema = core.getLatestSchema();
SchemaField field = schema.getField(fieldName);
FieldType fieldType = field.getType();
ExchangeRateProvider provider = ((CurrencyFieldType)fieldType).getProvider();
assertEquals(expectedProviderClass, provider.getClass());
}
public void testFunctionUsage() throws Exception {
assumeTrue("This test is only applicable to the XML file based exchange rate provider",
expectedProviderClass.equals(FileExchangeRateProvider.class));
clearIndex();
for (int i = 1; i <= 8; i++) {
// "GBP" currency code is 1/2 of a USD dollar, for testing.
assertU(adoc("id", "" + i, fieldName, (((float)i)/2) + ",GBP"));
}
for (int i = 9; i <= 11; i++) {
assertU(adoc("id", "" + i, fieldName, i + ",USD"));
}
assertU(commit());
// direct value source usage, gets "raw" form od default currency
// default==USD, so raw==penies
assertQ(req("fl", "id,func:field($f)",
"f", fieldName,
"q", "id:5"),
"//*[@numFound='1']",
"//doc/float[@name='func' and .=500]");
assertQ(req("fl", "id,func:field($f)",
"f", fieldName,
"q", "id:10"),
"//*[@numFound='1']",
"//doc/float[@name='func' and .=1000]");
assertQ(req("fl", "id,score,"+fieldName,
"q", "{!frange u=500}"+fieldName)
,"//*[@numFound='5']"
,"//str[@name='id']='1'"
,"//str[@name='id']='2'"
,"//str[@name='id']='3'"
,"//str[@name='id']='4'"
,"//str[@name='id']='5'"
);
assertQ(req("fl", "id,score,"+fieldName,
"q", "{!frange l=500 u=1000}"+fieldName)
,"//*[@numFound='6']"
,"//str[@name='id']='5'"
,"//str[@name='id']='6'"
,"//str[@name='id']='7'"
,"//str[@name='id']='8'"
,"//str[@name='id']='9'"
,"//str[@name='id']='10'"
);
// use the currency function to convert to default (USD)
assertQ(req("fl", "id,func:currency($f)",
"f", fieldName,
"q", "id:10"),
"//*[@numFound='1']",
"//doc/float[@name='func' and .=10]");
assertQ(req("fl", "id,func:currency($f)",
"f", fieldName,
"q", "id:5"),
"//*[@numFound='1']",
"//doc/float[@name='func' and .=5]");
assertQ(req("fl", "id,score"+fieldName,
"f", fieldName,
"q", "{!frange u=5}currency($f)")
,"//*[@numFound='5']"
,"//str[@name='id']='1'"
,"//str[@name='id']='2'"
,"//str[@name='id']='3'"
,"//str[@name='id']='4'"
,"//str[@name='id']='5'"
);
assertQ(req("fl", "id,score"+fieldName,
"f", fieldName,
"q", "{!frange l=5 u=10}currency($f)")
,"//*[@numFound='6']"
,"//str[@name='id']='5'"
,"//str[@name='id']='6'"
,"//str[@name='id']='7'"
,"//str[@name='id']='8'"
,"//str[@name='id']='9'"
,"//str[@name='id']='10'"
);
// use the currency function to convert to MXN
assertQ(req("fl", "id,func:currency($f,MXN)",
"f", fieldName,
"q", "id:5"),
"//*[@numFound='1']",
"//doc/float[@name='func' and .=10]");
assertQ(req("fl", "id,func:currency($f,MXN)",
"f", fieldName,
"q", "id:10"),
"//*[@numFound='1']",
"//doc/float[@name='func' and .=20]");
assertQ(req("fl", "*,score,"+fieldName,
"f", fieldName,
"q", "{!frange u=10}currency($f,MXN)")
,"//*[@numFound='5']"
,"//str[@name='id']='1'"
,"//str[@name='id']='2'"
,"//str[@name='id']='3'"
,"//str[@name='id']='4'"
,"//str[@name='id']='5'"
);
assertQ(req("fl", "*,score,"+fieldName,
"f", fieldName,
"q", "{!frange l=10 u=20}currency($f,MXN)")
,"//*[@numFound='6']"
,"//str[@name='id']='5'"
,"//str[@name='id']='6'"
,"//str[@name='id']='7'"
,"//str[@name='id']='8'"
,"//str[@name='id']='9'"
,"//str[@name='id']='10'"
);
}
@Test
public void testStringValue() throws Exception {
assertEquals("3.14,USD", new CurrencyValue(314, "USD").strValue());
assertEquals("-3.14,GBP", new CurrencyValue(-314, "GBP").strValue());
assertEquals("3.14,GBP", new CurrencyValue(314, "GBP").strValue());
CurrencyValue currencyValue = new CurrencyValue(314, "XYZ");
expectThrows(SolrException.class, currencyValue::strValue);
}
@Test
public void testRangeFacet() throws Exception {
assumeTrue("This test is only applicable to the XML file based exchange rate provider " +
"because it excercies the asymetric exchange rates option it supports",
expectedProviderClass.equals(FileExchangeRateProvider.class));
clearIndex();
// NOTE: in our test conversions EUR uses an asynetric echange rate
// these are the equivalent values when converting to: USD EUR GBP
assertU(adoc("id", "" + 1, fieldName, "10.00,USD")); // 10.00,USD 25.00,EUR 5.00,GBP
assertU(adoc("id", "" + 2, fieldName, "15.00,EUR")); // 7.50,USD 15.00,EUR 7.50,GBP
assertU(adoc("id", "" + 3, fieldName, "6.00,GBP")); // 12.00,USD 12.00,EUR 6.00,GBP
assertU(adoc("id", "" + 4, fieldName, "7.00,EUR")); // 3.50,USD 7.00,EUR 3.50,GBP
assertU(adoc("id", "" + 5, fieldName, "2,GBP")); // 4.00,USD 4.00,EUR 2.00,GBP
assertU(commit());
for (String suffix : Arrays.asList("", ",USD")) {
assertQ("Ensure that we get correct facet counts back in USD (explicit or implicit default) (facet.range)",
req("fl", "*,score", "q", "*:*", "rows", "0", "facet", "true",
"facet.range", fieldName,
"f." + fieldName + ".facet.range.start", "4.00" + suffix,
"f." + fieldName + ".facet.range.end", "11.00" + suffix,
"f." + fieldName + ".facet.range.gap", "1.00" + suffix,
"f." + fieldName + ".facet.range.other", "all")
,"count(//lst[@name='counts']/int)=7"
,"//lst[@name='counts']/int[@name='4.00,USD']='1'"
,"//lst[@name='counts']/int[@name='5.00,USD']='0'"
,"//lst[@name='counts']/int[@name='6.00,USD']='0'"
,"//lst[@name='counts']/int[@name='7.00,USD']='1'"
,"//lst[@name='counts']/int[@name='8.00,USD']='0'"
,"//lst[@name='counts']/int[@name='9.00,USD']='0'"
,"//lst[@name='counts']/int[@name='10.00,USD']='1'"
,"//int[@name='after']='1'"
,"//int[@name='before']='1'"
,"//int[@name='between']='3'"
);
assertQ("Ensure that we get correct facet counts back in USD (explicit or implicit default) (json.facet)",
req("fl", "*,score", "q", "*:*", "rows", "0", "json.facet",
"{ xxx : { type:range, field:" + fieldName + ", " +
" start:'4.00"+suffix+"', gap:'1.00"+suffix+"', end:'11.00"+suffix+"', other:all } }")
,"count(//lst[@name='xxx']/arr[@name='buckets']/lst)=7"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='1']][str[@name='val'][.='4.00,USD']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='0']][str[@name='val'][.='5.00,USD']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='0']][str[@name='val'][.='6.00,USD']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='1']][str[@name='val'][.='7.00,USD']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='0']][str[@name='val'][.='8.00,USD']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='0']][str[@name='val'][.='9.00,USD']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='1']][str[@name='val'][.='10.00,USD']]"
,"//lst[@name='xxx']/lst[@name='before' ]/int[@name='count'][.='1']"
,"//lst[@name='xxx']/lst[@name='after' ]/int[@name='count'][.='1']"
,"//lst[@name='xxx']/lst[@name='between']/int[@name='count'][.='3']"
);
}
assertQ("Zero value as start range point + mincount (facet.range)",
req("fl", "*,score", "q", "*:*", "rows", "0", "facet", "true", "facet.mincount", "1",
"facet.range", fieldName,
"f." + fieldName + ".facet.range.start", "0,USD",
"f." + fieldName + ".facet.range.end", "11.00,USD",
"f." + fieldName + ".facet.range.gap", "1.00,USD",
"f." + fieldName + ".facet.range.other", "all")
,"count(//lst[@name='counts']/int)=4"
,"//lst[@name='counts']/int[@name='3.00,USD']='1'"
,"//lst[@name='counts']/int[@name='4.00,USD']='1'"
,"//lst[@name='counts']/int[@name='7.00,USD']='1'"
,"//lst[@name='counts']/int[@name='10.00,USD']='1'"
,"//int[@name='before']='0'"
,"//int[@name='after']='1'"
,"//int[@name='between']='4'"
);
assertQ("Zero value as start range point + mincount (json.facet)",
req("fl", "*,score", "q", "*:*", "rows", "0", "json.facet",
"{ xxx : { type:range, mincount:1, field:" + fieldName +
", start:'0.00,USD', gap:'1.00,USD', end:'11.00,USD', other:all } }")
,"count(//lst[@name='xxx']/arr[@name='buckets']/lst)=4"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='1']][str[@name='val'][.='3.00,USD']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='1']][str[@name='val'][.='4.00,USD']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='1']][str[@name='val'][.='7.00,USD']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='1']][str[@name='val'][.='10.00,USD']]"
,"//lst[@name='xxx']/lst[@name='before' ]/int[@name='count'][.='0']"
,"//lst[@name='xxx']/lst[@name='after' ]/int[@name='count'][.='1']"
,"//lst[@name='xxx']/lst[@name='between']/int[@name='count'][.='4']"
);
// NOTE: because of asymetric EUR exchange rate, these buckets are diff then the similar looking USD based request above
// This request converts the values in each doc into EUR to decide what range buck it's in.
assertQ("Ensure that we get correct facet counts back in EUR (facet.range)",
req("fl", "*,score", "q", "*:*", "rows", "0", "facet", "true",
"facet.range", fieldName,
"f." + fieldName + ".facet.range.start", "8.00,EUR",
"f." + fieldName + ".facet.range.end", "22.00,EUR",
"f." + fieldName + ".facet.range.gap", "2.00,EUR",
"f." + fieldName + ".facet.range.other", "all"
)
, "count(//lst[@name='counts']/int)=7"
, "//lst[@name='counts']/int[@name='8.00,EUR']='0'"
, "//lst[@name='counts']/int[@name='10.00,EUR']='0'"
, "//lst[@name='counts']/int[@name='12.00,EUR']='1'"
, "//lst[@name='counts']/int[@name='14.00,EUR']='1'"
, "//lst[@name='counts']/int[@name='16.00,EUR']='0'"
, "//lst[@name='counts']/int[@name='18.00,EUR']='0'"
, "//lst[@name='counts']/int[@name='20.00,EUR']='0'"
, "//int[@name='before']='2'"
, "//int[@name='after']='1'"
, "//int[@name='between']='2'"
);
assertQ("Ensure that we get correct facet counts back in EUR (json.facet)",
req("fl", "*,score", "q", "*:*", "rows", "0", "json.facet",
"{ xxx : { type:range, field:" + fieldName + ", start:'8.00,EUR', gap:'2.00,EUR', end:'22.00,EUR', other:all } }")
,"count(//lst[@name='xxx']/arr[@name='buckets']/lst)=7"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='0']][str[@name='val'][.='8.00,EUR']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='0']][str[@name='val'][.='10.00,EUR']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='1']][str[@name='val'][.='12.00,EUR']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='1']][str[@name='val'][.='14.00,EUR']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='0']][str[@name='val'][.='16.00,EUR']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='0']][str[@name='val'][.='18.00,EUR']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='0']][str[@name='val'][.='20.00,EUR']]"
,"//lst[@name='xxx']/lst[@name='before' ]/int[@name='count'][.='2']"
,"//lst[@name='xxx']/lst[@name='after' ]/int[@name='count'][.='1']"
,"//lst[@name='xxx']/lst[@name='between']/int[@name='count'][.='2']"
);
// GBP has a symetric echange rate with USD, so these counts are *similar* to the USD based request above...
// but the asymetric EUR/USD rate means that when computing counts realtive to GBP the EUR based docs wind up in
// diff buckets
assertQ("Ensure that we get correct facet counts back in GBP (facet.range)",
req("fl", "*,score", "q", "*:*", "rows", "0", "facet", "true",
"facet.range", fieldName,
"f." + fieldName + ".facet.range.start", "2.00,GBP",
"f." + fieldName + ".facet.range.end", "5.50,GBP",
"f." + fieldName + ".facet.range.gap", "0.50,GBP",
"f." + fieldName + ".facet.range.other", "all"
)
, "count(//lst[@name='counts']/int)=7"
, "//lst[@name='counts']/int[@name='2.00,GBP']='1'"
, "//lst[@name='counts']/int[@name='2.50,GBP']='0'"
, "//lst[@name='counts']/int[@name='3.00,GBP']='0'"
, "//lst[@name='counts']/int[@name='3.50,GBP']='1'"
, "//lst[@name='counts']/int[@name='4.00,GBP']='0'"
, "//lst[@name='counts']/int[@name='4.50,GBP']='0'"
, "//lst[@name='counts']/int[@name='5.00,GBP']='1'"
, "//int[@name='before']='0'"
, "//int[@name='after']='2'"
, "//int[@name='between']='3'"
);
assertQ("Ensure that we get correct facet counts back in GBP (json.facet)",
req("fl", "*,score", "q", "*:*", "rows", "0", "json.facet",
"{ xxx : { type:range, field:" + fieldName + ", start:'2.00,GBP', gap:'0.50,GBP', end:'5.50,GBP', other:all } }")
,"count(//lst[@name='xxx']/arr[@name='buckets']/lst)=7"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='1']][str[@name='val'][.='2.00,GBP']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='0']][str[@name='val'][.='2.50,GBP']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='0']][str[@name='val'][.='3.00,GBP']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='1']][str[@name='val'][.='3.50,GBP']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='0']][str[@name='val'][.='4.00,GBP']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='0']][str[@name='val'][.='4.50,GBP']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='1']][str[@name='val'][.='5.00,GBP']]"
,"//lst[@name='xxx']/lst[@name='before' ]/int[@name='count'][.='0']"
,"//lst[@name='xxx']/lst[@name='after' ]/int[@name='count'][.='2']"
,"//lst[@name='xxx']/lst[@name='between']/int[@name='count'][.='3']"
);
assertQ("Ensure that we can set a gap in a currency other than the start and end currencies (facet.range)",
req("fl", "*,score", "q", "*:*", "rows", "0", "facet", "true",
"facet.range", fieldName,
"f." + fieldName + ".facet.range.start", "4.00,USD",
"f." + fieldName + ".facet.range.end", "11.00,USD",
"f." + fieldName + ".facet.range.gap", "0.50,GBP",
"f." + fieldName + ".facet.range.other", "all"
)
, "count(//lst[@name='counts']/int)=7"
, "//lst[@name='counts']/int[@name='4.00,USD']='1'"
, "//lst[@name='counts']/int[@name='5.00,USD']='0'"
, "//lst[@name='counts']/int[@name='6.00,USD']='0'"
, "//lst[@name='counts']/int[@name='7.00,USD']='1'"
, "//lst[@name='counts']/int[@name='8.00,USD']='0'"
, "//lst[@name='counts']/int[@name='9.00,USD']='0'"
, "//lst[@name='counts']/int[@name='10.00,USD']='1'"
, "//int[@name='before']='1'"
, "//int[@name='after']='1'"
, "//int[@name='between']='3'"
);
assertQ("Ensure that we can set a gap in a currency other than the start and end currencies (json.facet)",
req("fl", "*,score", "q", "*:*", "rows", "0", "json.facet",
"{ xxx : { type:range, field:" + fieldName + ", start:'4.00,USD', gap:'0.50,GBP', end:'11.00,USD', other:all } }")
,"count(//lst[@name='xxx']/arr[@name='buckets']/lst)=7"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='1']][str[@name='val'][.='4.00,USD']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='0']][str[@name='val'][.='5.00,USD']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='0']][str[@name='val'][.='6.00,USD']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='1']][str[@name='val'][.='7.00,USD']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='0']][str[@name='val'][.='8.00,USD']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='0']][str[@name='val'][.='9.00,USD']]"
,"//lst[@name='xxx']/arr[@name='buckets']/lst[int[@name='count'][.='1']][str[@name='val'][.='10.00,USD']]"
,"//lst[@name='xxx']/lst[@name='before' ]/int[@name='count'][.='1']"
,"//lst[@name='xxx']/lst[@name='after' ]/int[@name='count'][.='1']"
,"//lst[@name='xxx']/lst[@name='between']/int[@name='count'][.='3']"
);
for (SolrParams facet : Arrays.asList(params("facet", "true",
"facet.range", fieldName,
"f." + fieldName + ".facet.range.start", "4.00,USD",
"f." + fieldName + ".facet.range.end", "11.00,EUR",
"f." + fieldName + ".facet.range.gap", "1.00,USD",
"f." + fieldName + ".facet.range.other", "all"),
params("json.facet",
"{ xxx : { type:range, field:" + fieldName + ", start:'4.00,USD', " +
" gap:'1.00,USD', end:'11.00,EUR', other:all } }"))) {
assertQEx("Ensure that we throw an error if we try to use different start and end currencies",
"Cannot compare CurrencyValues when their currencies are not equal",
req(facet, "q", "*:*"),
SolrException.ErrorCode.BAD_REQUEST);
}
}
@Test
public void testMockFieldType() throws Exception {
assumeTrue("This test is only applicable to the mock exchange rate provider",
expectedProviderClass.equals(MockExchangeRateProvider.class));
clearIndex();
assertU(adoc("id", "1", fieldName, "1.00,USD"));
assertU(adoc("id", "2", fieldName, "1.00,EUR"));
assertU(adoc("id", "3", fieldName, "1.00,NOK"));
assertU(commit());
assertQ(req("fl", "*,score", "q", fieldName+":5.0,NOK"), "//*[@numFound='1']", "//str[@name='id']='1'");
assertQ(req("fl", "*,score", "q", fieldName+":1.2,USD"), "//*[@numFound='1']", "//str[@name='id']='2'");
assertQ(req("fl", "*,score", "q", fieldName+":0.2,USD"), "//*[@numFound='1']", "//str[@name='id']='3'");
assertQ(req("fl", "*,score", "q", fieldName+":99,USD"), "//*[@numFound='0']");
}
@Test
public void testAsymmetricPointQuery() throws Exception {
assumeTrue("This test is only applicable to the XML file based exchange rate provider",
expectedProviderClass.equals(FileExchangeRateProvider.class));
clearIndex();
assertU(adoc("id", "" + 1, fieldName, "10.00,USD"));
assertU(adoc("id", "" + 2, fieldName, "15.00,EUR"));
assertU(commit());
assertQ(req("fl", "*,score", "q", fieldName+":15.00,EUR"), "//str[@name='id']='2'");
assertQ(req("fl", "*,score", "q", fieldName+":7.50,USD"), "//str[@name='id']='2'");
assertQ(req("fl", "*,score", "q", fieldName+":7.49,USD"), "//*[@numFound='0']");
assertQ(req("fl", "*,score", "q", fieldName+":7.51,USD"), "//*[@numFound='0']");
}
}